From ed219d74e5b8cb969fec55408f5b9176c343351a Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Sat, 25 Jul 2026 12:31:13 -0500 Subject: [PATCH] bug with objects moving --- kiln-core/src/board.rs | 87 +++++++++++++++++++++++++++++++++++++++++- kiln-core/src/game.rs | 2 +- kiln-core/src/utils.rs | 5 +++ 3 files changed, 92 insertions(+), 2 deletions(-) diff --git a/kiln-core/src/board.rs b/kiln-core/src/board.rs index e6df337..f3d0341 100644 --- a/kiln-core/src/board.rs +++ b/kiln-core/src/board.rs @@ -396,6 +396,33 @@ impl Board { .collect() } + /// Tries to walk the object in x,y in the given direction. Pushes the cell in that direction + /// first. This won't move anything if: + /// - the cell x,y is empty; no-op + /// - x,y is out of bounds, no-op + /// - the target cell can't be pushed in that direction, either because it's `Block` or + /// something in its chain is + pub fn move_object(&mut self, x: usize, y: usize, dir: Direction) { + // Is this even a cell? + if self.in_bounds((x as i64, y as i64)) { + // Is there anything in this cell? + if self.get(x, y).is_some() { + let (tx, ty) = dir.from_point(x as i64, y as i64); + // Is the _target_ in bounds? + if self.in_bounds((tx, ty)) { + // Attempt to push the target out of the way + self.push(tx as usize, ty as usize, dir); + // Is the cell now empty? + if self.get(tx as usize, ty as usize).is_none() { + // Then finally, move the thing: + let thing = self.grid[x + y * self.width].take(); + self.grid[tx as usize + ty as usize * self.width] = thing; + } + } + } + } + } + /// Moves the single solid occupant of `(x, y)` one step by `(dx, dy)`. /// /// A solid object is relocated; otherwise the solid terrain archetype (a crate) @@ -636,7 +663,11 @@ pub(crate) mod tests { use crate::utils::{Direction, ObjectId}; use color::Rgba8; use std::collections::HashMap; - use crate::tile::{DrawLayer, IntoTile, Optics, ScriptAttributes, Sensor, SensorSpec, Tile, TileSpec}; + use crate::object_def::ObjectDef; + use crate::tile::{ + DrawLayer, EnterResponse, IntoTile, Optics, ScriptAttributes, Sensor, SensorSpec, Tile, + TileSpec, + }; /// Builds an all-empty `w×h` board. /// @@ -704,6 +735,60 @@ pub(crate) mod tests { id } + /// Stamps a scripted object at `(x, y)` running the world script named `script`, + /// answering entry attempts with `enter`, and returns its id. + /// + /// The on-grid counterpart to [`sensor_at`]: this object occupies its cell and + /// takes part in collision, so `enter` decides how it responds to something + /// moving into it (`Block` for an ordinary solid, `Push(..)` for a shovable one). + pub(crate) fn object_at( + board: &mut Board, + x: usize, + y: usize, + script: &str, + enter: EnterResponse, + ) -> ObjectId { + let tile = TileSpec::Object { + script: Some(script.to_string()), + enter, + glyph: ObjectDef::default_glyph(), + optics: Optics { opaque: true, glow: 0 }, + name: None, + tags: Vec::new(), + } + .into_tile(&mut board.next_object_id) + .expect("an object spec always resolves"); + let id = match &tile { + Tile::Object(obj) => obj.scripting.id, + Tile::Player => unreachable!("an object spec never resolves to the player"), + }; + *board.get_mut(x, y) = Some(tile); + id + } + + /// Stamps a solid object at `(x, y)` with **no script attached**, returning its id. + /// + /// For exercising the "an object without a script is inert" path; everything + /// else should use [`object_at`]. + pub(crate) fn plain_object_at(board: &mut Board, x: usize, y: usize) -> ObjectId { + let tile = TileSpec::Object { + script: None, + enter: EnterResponse::Block, + glyph: ObjectDef::default_glyph(), + optics: Optics { opaque: true, glow: 0 }, + name: None, + tags: Vec::new(), + } + .into_tile(&mut board.next_object_id) + .expect("an object spec always resolves"); + let id = match &tile { + Tile::Object(obj) => obj.scripting.id, + Tile::Player => unreachable!("an object spec never resolves to the player"), + }; + *board.get_mut(x, y) = Some(tile); + id + } + /// Adds an invisible, script-only [`Sensor`] at `(x, y)` running the world script /// named `script`, returning its id. /// diff --git a/kiln-core/src/game.rs b/kiln-core/src/game.rs index 31ea1e3..36de6ed 100644 --- a/kiln-core/src/game.rs +++ b/kiln-core/src/game.rs @@ -553,7 +553,7 @@ fn step_object(board: &mut Board, id: ObjectId, dir: Direction) { if solid { // This is a real object on the board, try and push it - board.push(loc.0, loc.1, dir); + board.move_object(loc.0, loc.1, dir); } else { // This is a sensor, we can just teleport it board.move_sensor(id, dir); diff --git a/kiln-core/src/utils.rs b/kiln-core/src/utils.rs index de0e49c..6c06d31 100644 --- a/kiln-core/src/utils.rs +++ b/kiln-core/src/utils.rs @@ -118,6 +118,11 @@ impl Direction { Some(if dy > 0 { Direction::South } else { Direction::North }) } } + + /// Translate the given point in this direction + pub fn from_point(self, x: i64, y: i64) -> (i64, i64) { + (x as i64 + self.dx(), y as i64 + self.dy()) + } } /// A value that can be stored in a board's script registry across board transitions.