//! Movement: validate, then commit. //! //! A move is planned before anything on the board changes. The `Planner` asks //! whether one object can step one tile; to answer, it runs every movement //! response content provides for that step, and a response may ask the planner //! to move other objects too (a push), or to destroy objects. If any response //! says no, the planner forgets everything that step added to the plan, so a //! failed push leaves no trace. Only a plan that succeeded is committed. //! //! The engine knows the board, its edge, and the order of the steps. What each //! property does when something moves (`PUSH`, `STOP`, `OPEN`/`SHUT`, ...) is a //! response in content, run in the order content lists them. use std::collections::BTreeSet; use super::grid::ObjectId; use super::level::Level; /// One of the four directions a move can take. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Dir { Up, Down, Left, Right, } impl Dir { /// One tile in this direction, as `(dx, dy)`; `y` grows down. pub fn delta(self) -> (i32, i32) { match self { Dir::Up => (0, -1), Dir::Down => (0, 1), Dir::Left => (-1, 0), Dir::Right => (1, 0), } } } /// One object stepping one tile, from `from` to `to`, in direction `dir`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Step { pub mover: ObjectId, pub from: (i32, i32), pub to: (i32, i32), pub dir: Dir, } /// A movement response: content's rule for what happens when an object steps. /// It returns `false` to block the step. It may plan other moves or destroy /// objects through the planner. pub type Response = fn(&mut Planner<'_>, Step) -> bool; /// What a move will do: the objects that step one tile, and the objects destroyed. /// A destroyed object doesn't step, even if it was pushed. #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct Plan { pub moving: BTreeSet, pub destroyed: BTreeSet, } /// Plans one move on a board it only reads. `stepped` holds the objects that have /// already stepped in this movement phase, in an earlier committed plan: an object /// steps at most once per phase, so none of them can step again. pub struct Planner<'a> { level: &'a Level, dir: Dir, stepped: &'a BTreeSet, plan: Plan, } impl<'a> Planner<'a> { pub fn new(level: &'a Level, dir: Dir, stepped: &'a BTreeSet) -> Planner<'a> { Planner { level, dir, stepped, plan: Plan::default(), } } /// Does object `id` have `property`, under the rules in force before the move? pub fn has(&self, id: ObjectId, property: &str) -> bool { self.level.has_property(id, property) } /// The objects that will still be on tile `at` when the plan so far is /// committed: those there now, less those planned to move away or be destroyed. /// Oldest first. pub fn staying_at(&self, (x, y): (i32, i32)) -> Vec { self.level .board() .at(x, y) .map(|o| o.id) .filter(|id| !self.plan.moving.contains(id) && !self.plan.destroyed.contains(id)) .collect() } /// Plan object `id`'s destruction. pub fn destroy(&mut self, id: ObjectId) { self.plan.destroyed.insert(id); } /// Plan object `id` stepping one tile in the move's direction, with whatever /// the responses add. Returns whether it can. If it can't, the plan is left as /// it was before the call. An object already planned to move, or to be /// destroyed, is out of the way: the answer is yes, and nothing is added. An /// object that has already stepped in this movement phase can't step again: the /// answer is no. pub fn try_move(&mut self, id: ObjectId) -> bool { if self.plan.moving.contains(&id) || self.plan.destroyed.contains(&id) { return true; } if self.stepped.contains(&id) { return false; } let level = self.level; let o = level.board().get(id); let (dx, dy) = self.dir.delta(); let to = (o.x + dx, o.y + dy); // The edge of the level blocks every step (the course's choice; see the lesson). if !level.board().in_bounds(to.0, to.1) { return false; } let before = self.plan.clone(); self.plan.moving.insert(id); let step = Step { mover: id, from: (o.x, o.y), to, dir: self.dir, }; let allowed = level.responses().iter().all(|respond| respond(self, step)); if !allowed { self.plan = before; } allowed } pub fn into_plan(self) -> Plan { self.plan } }