use serde::Deserialize; pub const T: f64 = 10.0; pub const TERRAIN: [&str; 7] = [ "Deep water", "Shallow water", "Sand", "Grass", "Forest", "Hills", "Mountain", ]; pub const RES_NAMES: [&str; 4] = ["", "Tree", "Berry bush", "Stone deposit"]; #[derive(Deserialize, Clone, Default)] pub struct Stock { pub food: u32, pub wood: u32, pub stone: u32, pub tools: u32, } #[derive(Deserialize, Clone)] pub struct StageRange { pub min_days: u32, pub max_days: u32, } #[derive(Deserialize, Clone)] pub struct Calendar { pub ticks_per_day: u64, pub days_per_week: u32, pub weeks_per_month: u32, pub months_per_year: u32, pub toddler: StageRange, pub child: StageRange, pub teen: StageRange, pub young_adult: StageRange, pub adult: StageRange, pub older_adult: StageRange, pub elderly: StageRange, } impl Calendar { pub fn setting(&self, name: &str) -> Option { match name { "ticks_per_day" => Some(self.ticks_per_day), "days_per_week" => Some(self.days_per_week.into()), "weeks_per_month" => Some(self.weeks_per_month.into()), "months_per_year" => Some(self.months_per_year.into()), _ => { let (stage, bound) = name.split_once('.')?; let range = match stage { "toddler" => &self.toddler, "child" => &self.child, "teen" => &self.teen, "young_adult" => &self.young_adult, "adult" => &self.adult, "older_adult" => &self.older_adult, "elderly" => &self.elderly, _ => return None, }; match bound { "min_days" => Some(range.min_days.into()), "max_days" => Some(range.max_days.into()), _ => None, } } } } pub fn command(&self, name: &str, value: u64) -> Option { if let Some(setting) = name.strip_prefix("calendar.") { return Some(format!("calendar {setting} {value}")); } let rest = name.strip_prefix("stage.")?; let (stage, bound) = rest.split_once('.')?; let range = match stage { "toddler" => &self.toddler, "child" => &self.child, "teen" => &self.teen, "young_adult" => &self.young_adult, "adult" => &self.adult, "older_adult" => &self.older_adult, "elderly" => &self.elderly, _ => return None, }; let (min_days, max_days) = match bound { "min_days" => (value.min(u32::MAX as u64) as u32, range.max_days), "max_days" => (range.min_days, value.min(u32::MAX as u64) as u32), _ => return None, }; Some(format!("stage {stage} {min_days} {max_days}")) } } /// id, x, y, action, hunger, food, wood, stone #[derive(Deserialize, Clone)] pub struct Person( pub u32, pub usize, pub usize, pub String, pub f32, pub u32, pub u32, pub u32, ); /// x, y, energy #[derive(Deserialize, Clone)] pub struct Animal(pub usize, pub usize, pub f32); /// x, y, kind, amount #[derive(Deserialize, Clone)] pub struct Res(pub usize, pub usize, pub u8, pub u16); #[derive(Deserialize, Clone)] pub struct State { pub paused: bool, pub speed: u32, pub seed: u64, pub rev: u32, pub day: u64, pub year: u64, pub month: u64, pub week: u64, pub date_day: u64, pub hour: f32, pub daylight: f32, pub stock: Stock, pub home: (usize, usize), pub houses: Vec<(usize, usize)>, pub people: Vec, pub rabbits: Vec, pub wolves: Vec, pub res: Vec, pub log: Vec, pub calendar: Calendar, } #[derive(Deserialize, Clone)] pub struct Terrain { pub w: usize, pub h: usize, pub tiles: String, } impl Terrain { pub fn kind(&self, x: usize, y: usize) -> usize { self.tiles .as_bytes() .get(y * self.w + x) .map_or(0, |b| (b - b'0') as usize) .min(6) } } #[derive(Clone, Copy)] pub struct Sel { pub ax: usize, pub ay: usize, pub bx: usize, pub by: usize, } impl Sel { /// Normalised (x0, y0, x1, y1), inclusive. pub fn rect(&self) -> (usize, usize, usize, usize) { ( self.ax.min(self.bx), self.ay.min(self.by), self.ax.max(self.bx), self.ay.max(self.by), ) } } pub fn tool_options(tool: &str) -> Vec<(String, &'static str)> { match tool { "terrain" => TERRAIN .iter() .enumerate() .map(|(i, n)| (i.to_string(), *n)) .collect(), "res" => vec![ ("1".into(), "Tree"), ("2".into(), "Berry bush"), ("3".into(), "Stone"), ("0".into(), "Clear"), ], "spawn" => vec![ ("person".into(), "Person"), ("rabbit".into(), "Rabbit"), ("wolf".into(), "Wolf"), ], "remove" => vec![ ("all".into(), "Everything"), ("people".into(), "People"), ("rabbits".into(), "Rabbits"), ("wolves".into(), "Wolves"), ("houses".into(), "Houses"), ("res".into(), "Resources"), ], _ => vec![], } } pub fn tool_help(tool: &str) -> &'static str { match tool { "terrain" => "Drag a region to paint it.", "res" => "Drag a region; amount sets units per tile.", "spawn" => "Drag a region; one per walkable tile (max 25).", "remove" => "Drag a region to remove things in it.", "house" => "Drag a region to add houses.", "energy" => "Drag a region; amount sets animal energy (1-100).", _ => "Click or drag to inspect and edit.", } } pub fn tool_command(tool: &str, param: &str, amount: &str, sel: Sel) -> Option { let (x0, y0, x1, y1) = sel.rect(); let r = format!("{x0} {y0} {x1} {y1}"); let a = amount.parse::().unwrap_or(1); match tool { "terrain" => Some(format!("terrain {r} {param}")), "res" => Some(format!("res {r} {param} {a}")), "spawn" => Some(format!("spawn {param} {r}")), "remove" => Some(format!("remove {r} {param}")), "house" => Some(format!("house {r}")), "energy" => Some(format!("energy {r} {a}")), _ => None, } } pub fn describe(sel: Sel, s: &State, t: &Terrain) -> Vec { let (x0, y0, x1, y1) = sel.rect(); let inside = |x: usize, y: usize| x >= x0 && x <= x1 && y >= y0 && y <= y1; let people: Vec<&Person> = s.people.iter().filter(|p| inside(p.1, p.2)).collect(); let rabbits: Vec<&Animal> = s.rabbits.iter().filter(|a| inside(a.0, a.1)).collect(); let wolves: Vec<&Animal> = s.wolves.iter().filter(|a| inside(a.0, a.1)).collect(); let res: Vec<&Res> = s.res.iter().filter(|r| inside(r.0, r.1)).collect(); let houses = s.houses.iter().filter(|h| inside(h.0, h.1)).count(); let mut counts = [0usize; 7]; for y in y0..=y1 { for x in x0..=x1 { counts[t.kind(x, y)] += 1; } } let mut out = vec![]; if (x0, y0) == (x1, y1) { out.push(format!("Tile ({x0}, {y0})")); out.push(format!("Terrain: {}", TERRAIN[t.kind(x0, y0)])); if s.home == (x0, y0) { out.push("Village center (stockpile)".into()); } if houses > 0 { out.push("House".into()); } for r in &res { out.push(format!("{}: {} left", RES_NAMES[r.2 as usize], r.3)); } } else { out.push(format!("Region ({x0},{y0}) to ({x1},{y1})")); let terrain: Vec = counts .iter() .enumerate() .filter(|(_, n)| **n > 0) .map(|(i, n)| format!("{} {n}", TERRAIN[i])) .collect(); out.push(format!("Terrain: {}", terrain.join(", "))); let kinds: Vec = (1..=3u8) .map(|k| { format!( "{} {}", RES_NAMES[k as usize], res.iter().filter(|r| r.2 == k).count() ) }) .collect(); out.push(format!("Resources: {}", kinds.join(", "))); out.push(format!("Houses: {houses}")); } out.push(format!( "People: {}, rabbits: {}, wolves: {}", people.len(), rabbits.len(), wolves.len() )); for p in &people { out.push(format!( "- Person #{}: {}, hunger {}, carrying food {} wood {} stone {}", p.0, p.3, p.4, p.5, p.6, p.7 )); } if rabbits.len() + wolves.len() <= 6 { out.extend(rabbits.iter().map(|a| format!("- Rabbit: energy {}", a.2))); out.extend(wolves.iter().map(|a| format!("- Wolf: energy {}", a.2))); } out }