1use super::{Rng};
7use super::biomes::BiomeMap;
8use super::settlements::Settlement;
9
10#[derive(Debug, Clone)]
12pub struct Civilization {
13 pub id: u32,
14 pub name: String,
15 pub founding_year: i32,
16 pub collapse_year: Option<i32>,
17 pub capital_settlement: u32,
18 pub settlements: Vec<u32>,
19 pub population: u64,
20 pub technology_level: f32,
21 pub military_strength: f32,
22 pub culture_score: f32,
23 pub trade_score: f32,
24 pub government: GovernmentType,
25 pub religion: ReligionType,
26 pub relations: Vec<(u32, Relation)>,
27 pub historical_events: Vec<HistoricalEvent>,
28 pub traits: Vec<CivTrait>,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum GovernmentType {
33 Tribal, Monarchy, Republic, Theocracy, Empire, Oligarchy, Democracy, Anarchy,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum ReligionType {
38 Animism, Polytheism, Monotheism, Philosophy, Ancestor, Nature, Void, Chaos,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum Relation {
43 Allied, Friendly, Neutral, Rival, AtWar, Vassal, Overlord,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum CivTrait {
48 Warlike, Peaceful, Mercantile, Scholarly, Nomadic, Seafaring, Religious, Isolationist,
49}
50
51#[derive(Debug, Clone)]
53pub struct HistoricalEvent {
54 pub year: i32,
55 pub event_type: EventType,
56 pub description: String,
57 pub participants: Vec<u32>,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum EventType {
62 Founding, War, Peace, Trade, Discovery, Plague, Famine, GoldenAge,
63 Collapse, Revolution, Migration, Alliance, Betrayal, HeroRise,
64 ArtifactCreation, TempleBuilt, CityFounded, GreatWork,
65}
66
67pub fn simulate(
69 settlements: &[Settlement],
70 biome_map: &BiomeMap,
71 years: usize,
72 num_civs: usize,
73 rng: &mut Rng,
74) -> Vec<Civilization> {
75 let num_civs = num_civs.min(settlements.len());
76 if num_civs == 0 { return Vec::new(); }
77
78 let mut civs: Vec<Civilization> = (0..num_civs)
80 .map(|i| {
81 let settlement = &settlements[i];
82 let traits = vec![random_trait(rng), random_trait(rng)];
83 Civilization {
84 id: i as u32,
85 name: generate_civ_name(rng),
86 founding_year: -(rng.range_u32(500, years as u32) as i32),
87 collapse_year: None,
88 capital_settlement: settlement.id,
89 settlements: vec![settlement.id],
90 population: rng.range_usize(1000, 10000) as u64,
91 technology_level: rng.range_f32(0.1, 0.3),
92 military_strength: rng.range_f32(0.1, 0.5),
93 culture_score: rng.range_f32(0.1, 0.4),
94 trade_score: rng.range_f32(0.1, 0.3),
95 government: random_government(rng),
96 religion: random_religion(rng),
97 relations: Vec::new(),
98 historical_events: Vec::new(),
99 traits,
100 }
101 })
102 .collect();
103
104 for year in 0..years as i32 {
106 let adjusted_year = year - (years as i32 / 2);
107
108 for ci in 0..civs.len() {
109 if civs[ci].collapse_year.is_some() { continue; }
110
111 let growth = 1.0 + 0.01 * civs[ci].technology_level as f64;
113 civs[ci].population = (civs[ci].population as f64 * growth) as u64;
114
115 civs[ci].technology_level += rng.range_f32(0.0, 0.005);
117
118 let event_roll = rng.next_f32();
120 if event_roll < 0.01 {
121 if civs.len() > 1 {
123 let target = rng.range_usize(0, civs.len());
124 if target != ci && civs[target].collapse_year.is_none() {
125 let target_name = civs[target].name.clone();
126 let target_id = civs[target].id;
127 let ci_id = civs[ci].id;
128 civs[ci].historical_events.push(HistoricalEvent {
129 year: adjusted_year,
130 event_type: EventType::War,
131 description: format!("War with {}", target_name),
132 participants: vec![ci_id, target_id],
133 });
134 }
135 }
136 } else if event_roll < 0.02 {
137 let cid = civs[ci].id;
139 civs[ci].technology_level += 0.05;
140 civs[ci].historical_events.push(HistoricalEvent {
141 year: adjusted_year,
142 event_type: EventType::Discovery,
143 description: "A great discovery was made".to_string(),
144 participants: vec![cid],
145 });
146 } else if event_roll < 0.025 {
147 let cid = civs[ci].id;
149 civs[ci].population = (civs[ci].population as f64 * 0.7) as u64;
150 civs[ci].historical_events.push(HistoricalEvent {
151 year: adjusted_year,
152 event_type: EventType::Plague,
153 description: "A terrible plague swept the land".to_string(),
154 participants: vec![cid],
155 });
156 } else if event_roll < 0.03 {
157 let cid = civs[ci].id;
159 civs[ci].culture_score += 0.1;
160 civs[ci].historical_events.push(HistoricalEvent {
161 year: adjusted_year,
162 event_type: EventType::GoldenAge,
163 description: "A golden age of prosperity".to_string(),
164 participants: vec![cid],
165 });
166 } else if event_roll < 0.032 && civs[ci].population < 100 {
167 let cid = civs[ci].id;
169 let cname = civs[ci].name.clone();
170 civs[ci].collapse_year = Some(adjusted_year);
171 civs[ci].historical_events.push(HistoricalEvent {
172 year: adjusted_year,
173 event_type: EventType::Collapse,
174 description: format!("The {} civilization collapsed", cname),
175 participants: vec![cid],
176 });
177 }
178 }
179 }
180
181 civs
182}
183
184fn random_trait(rng: &mut Rng) -> CivTrait {
185 match rng.range_u32(0, 8) {
186 0 => CivTrait::Warlike,
187 1 => CivTrait::Peaceful,
188 2 => CivTrait::Mercantile,
189 3 => CivTrait::Scholarly,
190 4 => CivTrait::Nomadic,
191 5 => CivTrait::Seafaring,
192 6 => CivTrait::Religious,
193 _ => CivTrait::Isolationist,
194 }
195}
196
197fn random_government(rng: &mut Rng) -> GovernmentType {
198 match rng.range_u32(0, 8) {
199 0 => GovernmentType::Tribal,
200 1 => GovernmentType::Monarchy,
201 2 => GovernmentType::Republic,
202 3 => GovernmentType::Theocracy,
203 4 => GovernmentType::Empire,
204 5 => GovernmentType::Oligarchy,
205 6 => GovernmentType::Democracy,
206 _ => GovernmentType::Anarchy,
207 }
208}
209
210fn random_religion(rng: &mut Rng) -> ReligionType {
211 match rng.range_u32(0, 8) {
212 0 => ReligionType::Animism,
213 1 => ReligionType::Polytheism,
214 2 => ReligionType::Monotheism,
215 3 => ReligionType::Philosophy,
216 4 => ReligionType::Ancestor,
217 5 => ReligionType::Nature,
218 6 => ReligionType::Void,
219 _ => ReligionType::Chaos,
220 }
221}
222
223fn generate_civ_name(rng: &mut Rng) -> String {
224 let roots = ["Ael", "Dor", "Val", "Khor", "Thal", "Zer", "Myr", "Nor",
225 "Sar", "Eld", "Vor", "Ash", "Ith", "Orn", "Bel", "Fen"];
226 let suffixes = ["heim", "gard", "land", "oria", "ium", "eth", "zan",
227 "dor", "keth", "mar", "wen", "ost", "uri", "in"];
228 let root = roots[rng.next_u64() as usize % roots.len()];
229 let suffix = suffixes[rng.next_u64() as usize % suffixes.len()];
230 format!("{}{}", root, suffix)
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn test_simulate_history() {
239 let settlements: Vec<Settlement> = (0..5).map(|i| Settlement {
240 id: i, name: format!("Town{i}"), grid_x: i as usize * 10, grid_y: 10,
241 size: super::super::settlements::SettlementSize::Village,
242 population: 500, buildings: Vec::new(), roads: Vec::new(),
243 biome: super::super::biomes::Biome::Grassland, near_river: false,
244 near_coast: false, owner_civ: None, founded_year: -1000,
245 resources: 0.5, defense: 0.3,
246 }).collect();
247 let biome_map = super::super::biomes::BiomeMap {
248 width: 64, height: 64,
249 biomes: vec![super::super::biomes::Biome::Grassland; 64 * 64],
250 };
251 let mut rng = Rng::new(42);
252 let civs = simulate(&settlements, &biome_map, 1000, 3, &mut rng);
253 assert_eq!(civs.len(), 3);
254 assert!(civs.iter().all(|c| !c.name.is_empty()));
255 assert!(civs.iter().any(|c| !c.historical_events.is_empty()));
256 }
257}