pointify board.rs

This commit is contained in:
2026-08-10 22:57:54 -05:00
parent a8444b25ad
commit 3b5bf21ac5
4 changed files with 156 additions and 217 deletions
+1 -10
View File
@@ -177,7 +177,7 @@ pub fn apply_teleport(board: &mut Board, target: i64, x: i64, y: i64) -> Result<
}; };
if let Some(from) = from { if let Some(from) = from {
// Check if we're blocked: // Check if we're blocked:
if (from.0 != x || from.1 != y) && board.get(x, y).is_none() { if (from.0 != x || from.1 != y) && board.get((x, y)).is_none() {
// Not blocked, move it // Not blocked, move it
board.move_cell(from.into(), (x, y).into()); board.move_cell(from.into(), (x, y).into());
} }
@@ -188,15 +188,6 @@ pub fn apply_teleport(board: &mut Board, target: i64, x: i64, y: i64) -> Result<
} }
} }
pub fn apply_push(board: &mut Board, x: i64, y: i64, dir: Direction) -> Result<(), String> {
if !board.in_bounds((x, y)) {
Err(format!("push({x},{y}): out of bounds"))
} else {
board.push(x as usize, y as usize, dir);
Ok(())
}
}
pub fn apply_shift(board: &mut Board, cells: &[(i64, i64)]) -> Result<(), String> { pub fn apply_shift(board: &mut Board, cells: &[(i64, i64)]) -> Result<(), String> {
board.apply_shift(cells)?; board.apply_shift(cells)?;
Ok(()) Ok(())
+148 -205
View File
@@ -86,23 +86,26 @@ impl Board {
/// Returns a reference to the `(Glyph, Archetype)` cell at `(x, y)`. /// Returns a reference to the `(Glyph, Archetype)` cell at `(x, y)`.
/// ///
/// Panics if `x` or `y` are out of bounds. /// Panics if `x` or `y` are out of bounds.
pub fn get(&self, x: usize, y: usize) -> &Option<Tile> { pub fn get<P: Into<Point>>(&self, p: P) -> &Option<Tile> {
&self.grid[y * self.width + x] let p = p.into();
&self.grid[p.uy() * self.width + p.ux()]
} }
/// Returns a mutable reference to the cell at `(x, y)`. /// Returns a mutable reference to the cell at `(x, y)`.
/// ///
/// Panics if `x` or `y` are out of bounds. /// Panics if `x` or `y` are out of bounds.
pub fn get_mut(&mut self, x: usize, y: usize) -> &mut Option<Tile> { pub fn get_mut<P: Into<Point>>(&mut self, p: P) -> &mut Option<Tile> {
let p = p.into();
let w = self.width; let w = self.width;
&mut self.grid[y * w + x] &mut self.grid[p.uy() * w + p.ux()]
} }
/// Replace the solid terrain (if any) at `(x, y)` with a transparent `Empty` /// Replace the solid terrain (if any) at `(x, y)` with a transparent `Empty`
/// cell, revealing the floor beneath. /// cell, revealing the floor beneath.
pub fn clear_cell(&mut self, x: usize, y: usize) { pub fn clear_cell<P: Into<Point>>(&mut self, p: P) {
if self.in_bounds((x as i64, y as i64)) { let p = p.into();
*self.get_mut(x, y) = None; if self.in_bounds(p) {
*self.get_mut(p) = None;
} }
} }
@@ -122,10 +125,11 @@ impl Board {
/// 7. the canonical black `Empty` glyph. /// 7. the canonical black `Empty` glyph.
/// ///
/// Panics if out of bounds. /// Panics if out of bounds.
pub fn glyph_at(&self, x: usize, y: usize) -> Glyph { pub fn glyph_at<P: Into<Point>>(&self, p: P) -> Glyph {
let grid_glyph = self.get(x, y).as_ref().map(Tile::glyph); let p = p.into();
let grid_glyph = self.get(p).as_ref().map(Tile::glyph);
let sensors = self.sensors.iter().filter(|&s| s.x == x && s.y == y); let sensors = self.sensors.iter().filter(|&s| s.x == p.ux() && s.y == p.uy());
// Is there a sensor above the grid? // Is there a sensor above the grid?
if let Some(above) = sensors.clone().find(|s| s.draw_layer == DrawLayer::Above && s.scripting.glyph.is_visible()) { if let Some(above) = sensors.clone().find(|s| s.draw_layer == DrawLayer::Above && s.scripting.glyph.is_visible()) {
@@ -144,7 +148,7 @@ impl Board {
// Otherwise the floor, or the canonical black empty cell. // Otherwise the floor, or the canonical black empty cell.
self.floor self.floor
.glyph_at(x, y, self.width) .glyph_at(p.ux(), p.uy(), self.width)
.unwrap_or_else(|| Glyph::transparent()) .unwrap_or_else(|| Glyph::transparent())
} }
@@ -152,17 +156,9 @@ impl Board {
/// ///
/// Takes signed coords so callers can pass a raw `pos + delta` without first /// Takes signed coords so callers can pass a raw `pos + delta` without first
/// checking for negatives. /// checking for negatives.
pub fn in_bounds(&self, pos: (i64, i64)) -> bool { pub fn in_bounds<P: Into<Point>>(&self, pos: P) -> bool {
let (x, y) = pos; let p = pos.into();
x >= 0 && y >= 0 && (x as usize) < self.width && (y as usize) < self.height p.x >= 0 && p.y >= 0 && p.ux() < self.width && p.uy() < self.height
}
/// Returns `true` if a mover can enter `(x, y)` — i.e. no solid occupies it.
///
/// Convenience inverse of [`solid_at`](Board::solid_at).
/// Panics if `x` or `y` are out of bounds.
pub fn is_passable(&self, x: usize, y: usize) -> bool {
self.get(x, y).is_none()
} }
/// Returns `true` if cell `(x, y)` blocks line of sight (and light). /// Returns `true` if cell `(x, y)` blocks line of sight (and light).
@@ -171,11 +167,12 @@ impl Board {
/// **or** any object on it is opaque. This is the input to lighting on /// **or** any object on it is opaque. This is the input to lighting on
/// [`dark`](Board::dark) boards; see [`Board::lighting`]. /// [`dark`](Board::dark) boards; see [`Board::lighting`].
/// Panics if `x` or `y` are out of bounds. /// Panics if `x` or `y` are out of bounds.
pub fn is_opaque_at(&self, x: usize, y: usize) -> bool { pub fn is_opaque_at<P: Into<Point>>(&self, p: P) -> bool {
if self.sensors.iter().any(|s| s.x == x && s.y == y && s.scripting.optics.opaque) { let p = p.into();
if self.sensors.iter().any(|s| s.x == p.ux() && s.y == p.uy() && s.scripting.optics.opaque) {
true true
} else { } else {
match self.get(x, y) { match self.get(p) {
None => false, None => false,
Some(Tile::Player) => false, Some(Tile::Player) => false,
Some(Tile::Object(def)) => { Some(Tile::Object(def)) => {
@@ -203,9 +200,12 @@ impl Board {
let mut lighting = Lighting::new(w, h); let mut lighting = Lighting::new(w, h);
// One caster whose transparency is seeded once from the opaque cells; // One caster whose transparency is seeded once from the opaque cells;
// reused for the LOS pass and every light source (its FOV is cleared per cast). // reused for the LOS pass and every light source (its FOV is cleared per cast).
let mut caster = FovCaster::new(w, h, |x, y| !self.is_opaque_at(x, y)); let mut caster = FovCaster::new(w, h, |x, y| !self.is_opaque_at((x, y)));
let (px, py) = self.player_pos(); let (px, py) = {
let p = self.player_pos();
(p.ux(), p.uy())
};
// (a) Player line of sight — unbounded (radius 0), pure geometry. // (a) Player line of sight — unbounded (radius 0), pure geometry.
caster.cast(px, py, 0, |x, y| lighting.set_los(x, y)); caster.cast(px, py, 0, |x, y| lighting.set_los(x, y));
@@ -229,7 +229,7 @@ impl Board {
// Glowing terrain (e.g. a `Torch` cell): color = the cell's glyph foreground. // Glowing terrain (e.g. a `Torch` cell): color = the cell's glyph foreground.
for y in 0..h { for y in 0..h {
for x in 0..w { for x in 0..w {
if let Some(Tile::Object(obj)) = self.get(x, y) { if let Some(Tile::Object(obj)) = self.get((x, y)) {
let radius = obj.scripting.optics.glow; let radius = obj.scripting.optics.glow;
if radius > 0 { if radius > 0 {
add_source(&mut lighting, x, y, radius, color_to_rgb(obj.scripting.glyph.fg)); add_source(&mut lighting, x, y, radius, color_to_rgb(obj.scripting.glyph.fg));
@@ -247,180 +247,121 @@ impl Board {
Some(lighting) Some(lighting)
} }
/// Whether the cell's single solid occupant (if any) can be pushed in `dir`. // /// Whether the solid at `(x, y)` can be **shifted** one step in `dir`: it is
/// // /// itself a pushable solid *and* the next cell is either empty or holds another
/// This is whether the thing in this cell will _transmit_ a push impulse through // /// pushable solid.
/// it. Empty cells and cells that can't be pushed in that direction break the // ///
/// chain. // /// Unlike [`can_push`](Board::can_push) this inspects only the single cell
fn is_pushable(&self, x: usize, y: usize, dir: Direction) -> bool { // /// ahead — it does **not** verify the whole chain ends in open space. It is the
match self.get(x, y) { // /// right test for a simultaneous rotation/shift (applied via
Some(Tile::Player) => true, // /// [`apply_swap`](Board::apply_swap)), where a destination is occupied by
Some(Tile::Object(obj)) => { // /// another pushable that is itself moving the same frame. Returns `false` if
if let EnterResponse::Push(p) = obj.enter_response { // /// `(x, y)` holds no pushable, or the cell ahead runs off the board.
p.allows(dir) // ///
} else { // /// The **player** is always a blocker: a shift can't relocate the player, and
false // /// `apply_swap` refuses to overwrite it, so a cell holding the player is never
} // /// an acceptable shift destination.
} // pub fn can_shift(&self, x: usize, y: usize, dir: Direction) -> bool {
_ => false, // // The source must hold a solid pushable in `dir`.
} // if !self.is_pushable(x, y, dir) {
} // return false;
// }
// let (dx, dy): (i64, i64) = dir.into();
// let next = (x as i64 + dx, y as i64 + dy);
// if !self.in_bounds(next) {
// return false; // nothing to shift into off the board
// }
// let (nx, ny) = (next.0 as usize, next.1 as usize);
// // The player always blocks a shift: a shift can't relocate it and
// // `apply_swap` refuses to overwrite it. (The player reads as pushable, so it
// // must be excluded explicitly before the cell-ahead test below.)
// if self.get(nx, ny).as_ref().is_some_and(Tile::player) {
// return false;
// }
// // The cell ahead is acceptable if it is empty or another pushable solid.
// self.is_passable(nx, ny) || self.is_pushable(nx, ny, dir)
// }
/// Whether the chain of pushable solids starting at `(x, y)` can be shoved one // /// Shoves the chain of pushable solids starting at `(x, y)` one step in `dir`,
/// step in `dir` — i.e. the chain ends at a passable cell rather than the board // /// leaving `Empty` floor behind each moved cell.
/// edge or a non-pushable solid. // ///
/// // /// No-op when the chain can't move (it self-checks via [`can_push`](Board::can_push)),
/// Read-only (`&self`); pairs with [`push`](Board::push). Returns `false` when // /// so it is safe to call unconditionally.
/// `(x, y)` itself holds no pushable solid, so it doubles as the "is the cell // /// Returns the cells the shoved solids moved **into** (each chain cell stepped
/// ahead shovable?" half of a "can I move here?" query. // /// one cell in `dir`), so the caller can fire `enter` on any non-solid object a
pub fn can_push(&self, x: usize, y: usize, dir: Direction) -> bool { // /// pushed solid landed on. Empty when nothing moved.
let (dx, dy): (i64, i64) = dir.into(); // pub fn push(&mut self, x: usize, y: usize, dir: Direction) -> Vec<(usize, usize)> {
let (mut cx, mut cy) = (x, y); // if !self.can_push(x, y, dir) {
loop { // return Vec::new();
// This cell must hold a solid pushable in `dir` to advance the chain. // }
if !self.is_pushable(cx, cy, dir) { // let (dx, dy): (i64, i64) = dir.into();
return false; // // can_push guaranteed the chain ends at an in-bounds passable cell, so
} // // re-walk it (no bounds checks needed) and shift the far end first, which
let next = (cx as i64 + dx, cy as i64 + dy); // // keeps each destination cell vacated before its occupant arrives.
if !self.in_bounds(next) { // let mut chain: Vec<(usize, usize)> = Vec::new();
return false; // chain runs off the board // let (mut cx, mut cy) = (x, y);
} // while !self.is_passable(cx, cy) {
let (nx, ny) = (next.0 as usize, next.1 as usize); // chain.push((cx, cy));
if self.is_passable(nx, ny) { // cx = (cx as i64 + dx) as usize;
return true; // open space at the end: the whole chain can move // cy = (cy as i64 + dy) as usize;
} // }
// Next cell holds a solid too; continue (it must itself be pushable). // for &(px, py) in chain.iter().rev() {
cx = nx; // self.shift_solid(px, py, dx, dy);
cy = ny; // }
} // // Each solid ended up one step along `dir`; those destination cells are
} // // where an `enter` may need to fire.
// chain
// .iter()
// .map(|&(px, py)| ((px as i64 + dx) as usize, (py as i64 + dy) as usize))
// .collect()
// }
/// Whether the solid at `(x, y)` can be **shifted** one step in `dir`: it is // /// Tries to walk the object in x,y in the given direction. Pushes the cell in that direction
/// itself a pushable solid *and* the next cell is either empty or holds another // /// first. This won't move anything if:
/// pushable solid. // /// - the cell x,y is empty; no-op
/// // /// - x,y is out of bounds, no-op
/// Unlike [`can_push`](Board::can_push) this inspects only the single cell // /// - the target cell can't be pushed in that direction, either because it's `Block` or
/// ahead — it does **not** verify the whole chain ends in open space. It is the // /// something in its chain is
/// right test for a simultaneous rotation/shift (applied via // pub fn move_object<P: Into<Point>>(&mut self, p: P, dir: Direction) {
/// [`apply_swap`](Board::apply_swap)), where a destination is occupied by // let p = p.into();
/// another pushable that is itself moving the same frame. Returns `false` if // // Is this even a cell?
/// `(x, y)` holds no pushable, or the cell ahead runs off the board. // if self.in_bounds(p) {
/// // // Is there anything in this cell?
/// The **player** is always a blocker: a shift can't relocate the player, and // if !self.is_empty(p) {
/// `apply_swap` refuses to overwrite it, so a cell holding the player is never // let (tx, ty) = dir.from_point(p.x, p.y);
/// an acceptable shift destination. // // Is the _target_ in bounds?
pub fn can_shift(&self, x: usize, y: usize, dir: Direction) -> bool { // if self.in_bounds((tx, ty)) {
// The source must hold a solid pushable in `dir`. // // Attempt to push the target out of the way
if !self.is_pushable(x, y, dir) { // self.push(tx as usize, ty as usize, dir);
return false; // // Is the cell now empty?
} // if self.get(tx as usize, ty as usize).is_none() {
let (dx, dy): (i64, i64) = dir.into(); // // Then finally, move the thing:
let next = (x as i64 + dx, y as i64 + dy); // let thing = self.grid[x + y * self.width].take();
if !self.in_bounds(next) { // self.grid[tx as usize + ty as usize * self.width] = thing;
return false; // nothing to shift into off the board // }
} // }
let (nx, ny) = (next.0 as usize, next.1 as usize); // }
// The player always blocks a shift: a shift can't relocate it and // }
// `apply_swap` refuses to overwrite it. (The player reads as pushable, so it // }
// must be excluded explicitly before the cell-ahead test below.)
if self.get(nx, ny).as_ref().is_some_and(Tile::player) {
return false;
}
// The cell ahead is acceptable if it is empty or another pushable solid.
self.is_passable(nx, ny) || self.is_pushable(nx, ny, dir)
}
/// Shoves the chain of pushable solids starting at `(x, y)` one step in `dir`,
/// leaving `Empty` floor behind each moved cell.
///
/// No-op when the chain can't move (it self-checks via [`can_push`](Board::can_push)),
/// so it is safe to call unconditionally.
/// Returns the cells the shoved solids moved **into** (each chain cell stepped
/// one cell in `dir`), so the caller can fire `enter` on any non-solid object a
/// pushed solid landed on. Empty when nothing moved.
pub fn push(&mut self, x: usize, y: usize, dir: Direction) -> Vec<(usize, usize)> {
if !self.can_push(x, y, dir) {
return Vec::new();
}
let (dx, dy): (i64, i64) = dir.into();
// can_push guaranteed the chain ends at an in-bounds passable cell, so
// re-walk it (no bounds checks needed) and shift the far end first, which
// keeps each destination cell vacated before its occupant arrives.
let mut chain: Vec<(usize, usize)> = Vec::new();
let (mut cx, mut cy) = (x, y);
while !self.is_passable(cx, cy) {
chain.push((cx, cy));
cx = (cx as i64 + dx) as usize;
cy = (cy as i64 + dy) as usize;
}
for &(px, py) in chain.iter().rev() {
self.shift_solid(px, py, dx, dy);
}
// Each solid ended up one step along `dir`; those destination cells are
// where an `enter` may need to fire.
chain
.iter()
.map(|&(px, py)| ((px as i64 + dx) as usize, (py as i64 + dy) as usize))
.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)
/// is moved, leaving a transparent cell behind so the floor shows through. The
/// caller guarantees the destination is already clear.
fn shift_solid(&mut self, x: usize, y: usize, dx: i64, dy: i64) {
let (tx, ty) = ((x as i64 + dx) as usize, (y as i64 + dy) as usize);
if self.get(x, y).is_none() {
return; // nothing to shift
}
*self.get_mut(tx, ty) = self.get_mut(x, y).take();
}
/// Returns the [`ObjectId`]s of the **non-solid** objects at `(x, y)`. /// Returns the [`ObjectId`]s of the **non-solid** objects at `(x, y)`.
/// ///
/// These are the targets of an `enter` hook when a solid relocates onto the /// These are the targets of an `enter` hook when a solid relocates onto the
/// cell (terrain is always solid, so only objects can be non-solid). Mirrors /// cell (terrain is always solid, so only objects can be non-solid). Mirrors
/// [`object_ids_at`](Board::object_ids_at) / [`solid_object_id_at`](Board::solid_object_id_at). /// [`object_ids_at`](Board::object_ids_at) / [`solid_object_id_at`](Board::solid_object_id_at).
pub fn sensor_ids_at(&self, x: usize, y: usize) -> Vec<ObjectId> { pub fn sensor_ids_at<P: Into<Point>>(&self, p: P) -> Vec<ObjectId> {
let p = p.into();
self.sensors self.sensors
.iter() .iter()
.filter(|s| s.x == x && s.y == y) .filter(|s| s.x == p.ux() && s.y == p.uy())
.map(|s| s.scripting.id) .map(|s| s.scripting.id)
.collect() .collect()
} }
/// Find and return the portal at the given location /// Find and return the portal at the given location
pub fn portal_at(&self, x: usize, y: usize) -> Option<&Portal> { pub fn portal_at<P: Into<Point>>(&self, p: P) -> Option<&Portal> {
self.portals.iter().find(|&portal| portal.location() == (x, y)) let p = p.into();
self.portals.iter().find(|&portal| portal.location() == (p.ux(), p.uy()))
} }
/// Editor primitive: stamps `arch` (with visual `glyph`) into the cell at /// Editor primitive: stamps `arch` (with visual `glyph`) into the cell at
@@ -436,12 +377,12 @@ impl Board {
/// ///
/// A vacated grid cell becomes a transparent `Empty` so the floor shows through. /// A vacated grid cell becomes a transparent `Empty` so the floor shows through.
/// Panics if `(x, y)` is out of bounds. /// Panics if `(x, y)` is out of bounds.
pub fn place(&mut self, x: usize, y: usize, spec: Option<TileSpec>) -> Result<(), String> { pub fn place<P: Into<Point>>(&mut self, p: P, spec: Option<TileSpec>) -> Result<(), String> {
if let Some(spec) = spec { if let Some(spec) = spec {
let tile = spec.into_tile(&mut self.next_object_id)?; let tile = spec.into_tile(&mut self.next_object_id)?;
*self.get_mut(x, y) = Some(tile); *self.get_mut(p) = Some(tile);
} else { } else {
*self.get_mut(x, y) = None; *self.get_mut(p) = None;
} }
Ok(()) Ok(())
} }
@@ -457,7 +398,7 @@ impl Board {
} }
// Get all the Solids at these cells: // Get all the Solids at these cells:
let solids: Vec<_> = cells.iter().map(|&c| self.get_mut(c.0 as usize, c.1 as usize).take()).collect(); let solids: Vec<_> = cells.iter().map(|&c| self.get_mut(c).take()).collect();
// Find which ones are blockers // Find which ones are blockers
let mut immobile = HashSet::new(); let mut immobile = HashSet::new();
@@ -491,12 +432,12 @@ impl Board {
// Not blocked, write it to target // Not blocked, write it to target
let origin = cells[curr_idx]; let origin = cells[curr_idx];
let target = cells[(curr_idx + 1) % cells.len()]; let target = cells[(curr_idx + 1) % cells.len()];
*self.get_mut(target.0 as usize, target.1 as usize) = Some(solid); *self.get_mut(target) = Some(solid);
moves.push((origin, target)); moves.push((origin, target));
} else { } else {
// it was blocked so just write it back where it was // it was blocked so just write it back where it was
let origin = cells[curr_idx]; let origin = cells[curr_idx];
*self.get_mut(origin.0 as usize, origin.1 as usize) = Some(solid); *self.get_mut(origin) = Some(solid);
} }
} }
} }
@@ -515,10 +456,10 @@ impl Board {
} }
} }
pub fn player_pos(&self) -> (usize, usize) { pub fn player_pos(&self) -> Point {
self.grid.iter().enumerate().find_map(|(i, cell)| { self.grid.iter().enumerate().find_map(|(i, cell)| {
if matches!(cell, Some(Tile::Player)) { if matches!(cell, Some(Tile::Player)) {
Some((i % self.width, i / self.width)) Some((i % self.width, i / self.width).into())
} else { None } } else { None }
}).expect("No player found!") // This should never happen, player presence is validated when building a board }).expect("No player found!") // This should never happen, player presence is validated when building a board
} }
@@ -622,26 +563,28 @@ impl Board {
/// Moves whatever is in `from` to `to`, leaving an empty cell behind. Silent no-op if either /// Moves whatever is in `from` to `to`, leaving an empty cell behind. Silent no-op if either
/// `from` or `to` is out of bounds, or if they're the same cell. /// `from` or `to` is out of bounds, or if they're the same cell.
pub fn move_cell(&mut self, from: Point, to: Point) { pub fn move_cell<P1: Into<Point>, P2: Into<Point>>(&mut self, from: P1, to: P2) {
if from != to && self.in_bounds(from.into()) && self.in_bounds(to.into()) { let from = from.into();
let thing = self.get_mut(from.x as usize, from.y as usize).take(); let to = to.into();
self.grid[to.x as usize + to.y as usize * self.width] = thing; if from != to && self.in_bounds(from) && self.in_bounds(to) {
let thing = self.get_mut(from).take();
*self.get_mut(to) = thing;
} }
} }
/// Return whether the given point is empty /// Return whether the given point is empty
pub fn is_empty(&self, p: Point) -> bool { pub fn is_empty<P: Into<Point>>(&self, p: P) -> bool {
self.get(p.x as usize, p.y as usize).is_none() self.get(p).is_none()
} }
/// Return whether the given point contains the player /// Return whether the given point contains the player
pub fn is_player(&self, p: Point) -> bool { pub fn is_player<P: Into<Point>>(&self, p: P) -> bool {
matches!(self.get(p.x as usize, p.y as usize), Some(Tile::Player)) matches!(self.get(p), Some(Tile::Player))
} }
/// Return whether the given point contains an object /// Return whether the given point contains an object
pub fn is_object(&self, p: Point) -> bool { pub fn is_object<P: Into<Point>>(&self, p: P) -> bool {
matches!(self.get(p.x as usize, p.y as usize), Some(Tile::Object(_))) matches!(self.get(p), Some(Tile::Object(_)))
} }
} }
+2 -2
View File
@@ -1,4 +1,4 @@
use crate::action::{apply_push, apply_shift, apply_teleport, Action, BoardAction, SendArg}; use crate::action::{apply_shift, apply_teleport, Action, BoardAction, SendArg};
use crate::board::Board; use crate::board::Board;
use crate::log::LogLine; use crate::log::LogLine;
use crate::script::ScriptHost; use crate::script::ScriptHost;
@@ -540,7 +540,7 @@ fn step_object(board: &mut Board, id: ObjectId, dir: Direction) {
if solid { if solid {
// This is a real object on the board, try and push it // This is a real object on the board, try and push it
board.move_object(loc.0, loc.1, dir); board.move_object((loc.0, loc.1), dir);
} else { } else {
// This is a sensor, we can just teleport it // This is a sensor, we can just teleport it
board.move_sensor(id, dir); board.move_sensor(id, dir);
+5
View File
@@ -159,6 +159,11 @@ impl Display for Point {
} }
} }
impl Point {
pub fn ux(&self) -> usize { self.x as usize }
pub fn uy(&self) -> usize { self.y as usize }
}
impl Registerable for Point { impl Registerable for Point {
fn register(engine: &mut Engine, _log_sink: LogSink) { fn register(engine: &mut Engine, _log_sink: LogSink) {
engine.register_type_with_name::<Point>("Point") engine.register_type_with_name::<Point>("Point")