sim/ui/src/model.rs

302 lines
8.9 KiB
Rust
Raw Normal View History

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<u64> {
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<String> {
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<Person>,
pub rabbits: Vec<Animal>,
pub wolves: Vec<Animal>,
pub res: Vec<Res>,
pub log: Vec<String>,
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<String> {
let (x0, y0, x1, y1) = sel.rect();
let r = format!("{x0} {y0} {x1} {y1}");
let a = amount.parse::<i64>().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<String> {
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<String> = counts
.iter()
.enumerate()
.filter(|(_, n)| **n > 0)
.map(|(i, n)| format!("{} {n}", TERRAIN[i]))
.collect();
out.push(format!("Terrain: {}", terrain.join(", ")));
let kinds: Vec<String> = (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
}