Skip to main content

proof_engine/worldgen/
language.rs

1//! Language generation — phonology, morphology, syntax, vocabulary.
2//!
3//! Each procedural culture gets a unique language with consistent
4//! phonological rules, word formation patterns, and basic grammar.
5
6use super::Rng;
7use super::history::Civilization;
8
9/// Phoneme categories.
10#[derive(Debug, Clone)]
11pub struct Phonology {
12    pub consonants: Vec<char>,
13    pub vowels: Vec<char>,
14    /// Allowed syllable structures (C=consonant, V=vowel).
15    pub syllable_patterns: Vec<String>,
16    /// Forbidden consonant clusters.
17    pub forbidden_clusters: Vec<String>,
18}
19
20/// Word class.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum WordClass { Noun, Verb, Adjective, Adverb, Preposition, Article, Pronoun, Conjunction }
23
24/// A word in the language.
25#[derive(Debug, Clone)]
26pub struct Word {
27    pub form: String,
28    pub class: WordClass,
29    pub meaning: String,
30    pub root: String,
31}
32
33/// Morphological rules.
34#[derive(Debug, Clone)]
35pub struct Morphology {
36    pub plural_suffix: String,
37    pub past_suffix: String,
38    pub future_prefix: String,
39    pub negation_prefix: String,
40    pub diminutive_suffix: String,
41    pub augmentative_suffix: String,
42    pub adjective_suffix: String,
43    pub adverb_suffix: String,
44}
45
46/// Word order type.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum WordOrder { SVO, SOV, VSO, VOS, OVS, OSV }
49
50/// Basic syntax rules.
51#[derive(Debug, Clone)]
52pub struct Syntax {
53    pub word_order: WordOrder,
54    pub adjective_before_noun: bool,
55    pub postpositions: bool,
56    pub head_final: bool,
57}
58
59/// A complete procedural language.
60#[derive(Debug, Clone)]
61pub struct Language {
62    pub id: u32,
63    pub name: String,
64    pub phonology: Phonology,
65    pub morphology: Morphology,
66    pub syntax: Syntax,
67    pub vocabulary: Vec<Word>,
68    pub owner_civ: u32,
69}
70
71impl Language {
72    /// Generate a word from this language's phonology.
73    pub fn generate_word(&self, rng: &mut Rng, syllables: usize) -> String {
74        let mut word = String::new();
75        for _ in 0..syllables {
76            if let Some(pattern) = rng.pick(&self.phonology.syllable_patterns) {
77                for ch in pattern.chars() {
78                    match ch {
79                        'C' => {
80                            if let Some(&c) = rng.pick(&self.phonology.consonants) {
81                                word.push(c);
82                            }
83                        }
84                        'V' => {
85                            if let Some(&v) = rng.pick(&self.phonology.vowels) {
86                                word.push(v);
87                            }
88                        }
89                        _ => word.push(ch),
90                    }
91                }
92            }
93        }
94        word
95    }
96
97    /// Apply plural morphology.
98    pub fn pluralize(&self, word: &str) -> String {
99        format!("{}{}", word, self.morphology.plural_suffix)
100    }
101
102    /// Apply past tense.
103    pub fn past_tense(&self, word: &str) -> String {
104        format!("{}{}", word, self.morphology.past_suffix)
105    }
106
107    /// Construct a simple sentence: subject verb object.
108    pub fn simple_sentence(&self, subject: &str, verb: &str, object: &str) -> String {
109        match self.syntax.word_order {
110            WordOrder::SVO => format!("{} {} {}", subject, verb, object),
111            WordOrder::SOV => format!("{} {} {}", subject, object, verb),
112            WordOrder::VSO => format!("{} {} {}", verb, subject, object),
113            WordOrder::VOS => format!("{} {} {}", verb, object, subject),
114            WordOrder::OVS => format!("{} {} {}", object, verb, subject),
115            WordOrder::OSV => format!("{} {} {}", object, subject, verb),
116        }
117    }
118}
119
120/// Generate languages for civilizations.
121pub fn generate(num_languages: usize, civs: &[Civilization], rng: &mut Rng) -> Vec<Language> {
122    let mut languages = Vec::with_capacity(num_languages);
123
124    for i in 0..num_languages {
125        let phonology = generate_phonology(rng);
126        let morphology = generate_morphology(&phonology, rng);
127        let syntax = generate_syntax(rng);
128
129        let mut lang = Language {
130            id: i as u32,
131            name: String::new(),
132            phonology,
133            morphology,
134            syntax,
135            vocabulary: Vec::new(),
136            owner_civ: civs.get(i).map(|c| c.id).unwrap_or(i as u32),
137        };
138
139        // Generate base vocabulary
140        let concepts = [
141            ("sun", WordClass::Noun), ("moon", WordClass::Noun), ("water", WordClass::Noun),
142            ("fire", WordClass::Noun), ("earth", WordClass::Noun), ("sky", WordClass::Noun),
143            ("mountain", WordClass::Noun), ("river", WordClass::Noun), ("forest", WordClass::Noun),
144            ("sea", WordClass::Noun), ("wind", WordClass::Noun), ("stone", WordClass::Noun),
145            ("tree", WordClass::Noun), ("star", WordClass::Noun), ("rain", WordClass::Noun),
146            ("snow", WordClass::Noun), ("life", WordClass::Noun), ("death", WordClass::Noun),
147            ("war", WordClass::Noun), ("peace", WordClass::Noun), ("king", WordClass::Noun),
148            ("god", WordClass::Noun), ("hero", WordClass::Noun), ("beast", WordClass::Noun),
149            ("sword", WordClass::Noun), ("shield", WordClass::Noun), ("home", WordClass::Noun),
150            ("walk", WordClass::Verb), ("fight", WordClass::Verb), ("speak", WordClass::Verb),
151            ("see", WordClass::Verb), ("hear", WordClass::Verb), ("make", WordClass::Verb),
152            ("give", WordClass::Verb), ("take", WordClass::Verb), ("live", WordClass::Verb),
153            ("die", WordClass::Verb), ("love", WordClass::Verb), ("hate", WordClass::Verb),
154            ("big", WordClass::Adjective), ("small", WordClass::Adjective),
155            ("old", WordClass::Adjective), ("new", WordClass::Adjective),
156            ("good", WordClass::Adjective), ("bad", WordClass::Adjective),
157            ("dark", WordClass::Adjective), ("bright", WordClass::Adjective),
158        ];
159
160        for (meaning, class) in &concepts {
161            let syllables = rng.range_usize(1, 4);
162            let form = lang.generate_word(rng, syllables);
163            lang.vocabulary.push(Word {
164                form: form.clone(),
165                class: *class,
166                meaning: meaning.to_string(),
167                root: form,
168            });
169        }
170
171        // Name the language from its own phonology
172        lang.name = lang.generate_word(rng, 2);
173        lang.name = capitalize(&lang.name);
174
175        languages.push(lang);
176    }
177
178    languages
179}
180
181fn generate_phonology(rng: &mut Rng) -> Phonology {
182    let all_consonants: Vec<char> = "ptknmslrwjfvbdgzhʃ".chars().collect();
183    let all_vowels: Vec<char> = "aeiouəæɛɔ".chars().collect();
184
185    // Pick a subset
186    let num_c = rng.range_usize(8, 16);
187    let num_v = rng.range_usize(3, 7);
188    let mut consonants: Vec<char> = all_consonants.clone();
189    rng.shuffle(&mut consonants);
190    consonants.truncate(num_c);
191    let mut vowels: Vec<char> = all_vowels.clone();
192    rng.shuffle(&mut vowels);
193    vowels.truncate(num_v);
194
195    let all_patterns = vec![
196        "CV".to_string(), "CVC".to_string(), "VC".to_string(), "V".to_string(),
197        "CCV".to_string(), "CVCC".to_string(), "CCVC".to_string(),
198    ];
199    let num_patterns = rng.range_usize(2, 5);
200    let mut patterns = all_patterns.clone();
201    rng.shuffle(&mut patterns);
202    patterns.truncate(num_patterns);
203    // Always include CV
204    if !patterns.contains(&"CV".to_string()) { patterns.push("CV".to_string()); }
205
206    Phonology { consonants, vowels, syllable_patterns: patterns, forbidden_clusters: Vec::new() }
207}
208
209fn generate_morphology(phon: &Phonology, rng: &mut Rng) -> Morphology {
210    let gen_suffix = |rng: &mut Rng, phon: &Phonology| -> String {
211        let v = phon.vowels[rng.next_u64() as usize % phon.vowels.len()];
212        let c = phon.consonants[rng.next_u64() as usize % phon.consonants.len()];
213        if rng.coin(0.5) { format!("{}{}", v, c) } else { format!("{}", v) }
214    };
215
216    Morphology {
217        plural_suffix: gen_suffix(rng, phon),
218        past_suffix: gen_suffix(rng, phon),
219        future_prefix: gen_suffix(rng, phon),
220        negation_prefix: gen_suffix(rng, phon),
221        diminutive_suffix: gen_suffix(rng, phon),
222        augmentative_suffix: gen_suffix(rng, phon),
223        adjective_suffix: gen_suffix(rng, phon),
224        adverb_suffix: gen_suffix(rng, phon),
225    }
226}
227
228fn generate_syntax(rng: &mut Rng) -> Syntax {
229    let order = match rng.range_u32(0, 6) {
230        0 => WordOrder::SVO,
231        1 => WordOrder::SOV,
232        2 => WordOrder::VSO,
233        3 => WordOrder::VOS,
234        4 => WordOrder::OVS,
235        _ => WordOrder::OSV,
236    };
237    Syntax {
238        word_order: order,
239        adjective_before_noun: rng.coin(0.5),
240        postpositions: matches!(order, WordOrder::SOV | WordOrder::OSV),
241        head_final: matches!(order, WordOrder::SOV | WordOrder::OVS),
242    }
243}
244
245fn capitalize(s: &str) -> String {
246    let mut c = s.chars();
247    match c.next() {
248        None => String::new(),
249        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn test_generate_languages() {
259        let civs = vec![]; // no civs needed for language gen
260        let mut rng = Rng::new(42);
261        let langs = generate(3, &civs, &mut rng);
262        assert_eq!(langs.len(), 3);
263        for lang in &langs {
264            assert!(!lang.name.is_empty());
265            assert!(!lang.vocabulary.is_empty());
266            assert!(!lang.phonology.consonants.is_empty());
267            assert!(!lang.phonology.vowels.is_empty());
268        }
269    }
270
271    #[test]
272    fn test_word_generation() {
273        let mut rng = Rng::new(42);
274        let civs = vec![];
275        let langs = generate(1, &civs, &mut rng);
276        let word = langs[0].generate_word(&mut rng, 3);
277        assert!(!word.is_empty());
278    }
279
280    #[test]
281    fn test_sentence_construction() {
282        let mut rng = Rng::new(42);
283        let civs = vec![];
284        let langs = generate(1, &civs, &mut rng);
285        let sentence = langs[0].simple_sentence("warrior", "fights", "beast");
286        assert!(sentence.contains("warrior"));
287        assert!(sentence.contains("fights"));
288        assert!(sentence.contains("beast"));
289    }
290}