//! A level in play: the board, the words content provides, and the rules compiled //! from them. Every change to the board recompiles the rules from scratch: parse, //! then resolve. Every question about what an object is goes through `has_property`. //! //! A move (Module 8) is planned for each `YOU` object, then committed; the rules are //! reparsed once, after all of them. use std::collections::BTreeSet; use super::grid::{Board, Kind, LevelDef, ObjectId}; use super::movement::{Dir, Plan, Planner, Response}; use super::parse::{parse, Rule}; use super::resolve::resolve; use super::word::{Lexicon, WordDef}; #[derive(Debug, Clone)] pub struct Level { board: Board, lexicon: Lexicon, responses: Vec, rules: Vec, active: Vec, } /// The property that makes an object move when the player moves. pub const YOU: &str = "YOU"; impl Level { /// A level from its definition, the words content provides, and content's /// movement responses, in the order they are to run. pub fn new(def: &LevelDef, words: &[WordDef], responses: Vec) -> Level { let mut level = Level { board: Board::new(def.width, def.height), lexicon: Lexicon::new(words), responses, rules: vec![], active: vec![], }; for o in &def.objects { level.board.place(Kind::parse(&o.kind), o.x, o.y); } level.reparse(); level } pub fn place(&mut self, kind: &str, x: i32, y: i32) -> ObjectId { let id = self.board.place(Kind::parse(kind), x, y); self.reparse(); id } pub fn board(&self) -> &Board { &self.board } /// The movement responses, in the order they run. pub fn responses(&self) -> &[Response] { &self.responses } /// Every rule the text spells, as parsed: negated rules and duplicates included. pub fn rules(&self) -> &[Rule] { &self.rules } /// The rules in force after the resolution pass. pub fn active_rules(&self) -> &[Rule] { &self.active } /// Does object `id` have `property`? Yes if an active rule gives it to a noun /// that names the object and no active negated rule takes it from one. A /// destroyed object has no properties. pub fn has_property(&self, id: ObjectId, property: &str) -> bool { let Some(object) = self.board.find(id) else { return false; }; let kind = &object.kind; let about_it: Vec<&Rule> = self .active .iter() .filter(|r| r.complement == property && self.lexicon.names(&r.subject, kind)) .collect(); about_it.iter().any(|r| !r.negated) && !about_it.iter().any(|r| r.negated) } /// Move every `YOU` object one tile in `dir`, then reparse. The `YOU` objects /// move one at a time, the one furthest ahead in `dir` first (oldest first on a /// tie); each one's move is planned in full, then committed, before the next is /// planned. Every move uses the rules in force before the first. /// /// An object steps at most once in a movement phase. A `YOU` object that an /// earlier move already carried along (pushed, or moved by any other response) /// doesn't start a move of its own, and no later move can make it step again. pub fn move_you(&mut self, dir: Dir) { let (dx, dy) = dir.delta(); let mut you: Vec<_> = self .board .objects() .filter(|o| self.has_property(o.id, YOU)) .map(|o| (-(o.x * dx + o.y * dy), o.id)) .collect(); you.sort(); let mut stepped = BTreeSet::new(); for (_, id) in you { if self.board.find(id).is_none() { continue; // destroyed by an earlier move } if stepped.contains(&id) { continue; // already moved, by an earlier move } let mut planner = Planner::new(self, dir, &stepped); if planner.try_move(id) { let plan = planner.into_plan(); self.commit(&plan, dir); stepped.extend(plan.moving.difference(&plan.destroyed)); } } self.reparse(); } fn commit(&mut self, plan: &Plan, dir: Dir) { for &id in &plan.destroyed { self.board.remove(id); } for &id in plan.moving.difference(&plan.destroyed) { self.board.shift(id, dir.delta()); } } fn reparse(&mut self) { self.rules = parse(&self.board, &self.lexicon); self.active = resolve(&self.rules); } }