pointify board.rs
This commit is contained in:
+148
-205
@@ -86,23 +86,26 @@ impl Board {
|
||||
/// Returns a reference to the `(Glyph, Archetype)` cell at `(x, y)`.
|
||||
///
|
||||
/// Panics if `x` or `y` are out of bounds.
|
||||
pub fn get(&self, x: usize, y: usize) -> &Option<Tile> {
|
||||
&self.grid[y * self.width + x]
|
||||
pub fn get<P: Into<Point>>(&self, p: P) -> &Option<Tile> {
|
||||
let p = p.into();
|
||||
&self.grid[p.uy() * self.width + p.ux()]
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the cell at `(x, y)`.
|
||||
///
|
||||
/// 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;
|
||||
&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`
|
||||
/// cell, revealing the floor beneath.
|
||||
pub fn clear_cell(&mut self, x: usize, y: usize) {
|
||||
if self.in_bounds((x as i64, y as i64)) {
|
||||
*self.get_mut(x, y) = None;
|
||||
pub fn clear_cell<P: Into<Point>>(&mut self, p: P) {
|
||||
let p = p.into();
|
||||
if self.in_bounds(p) {
|
||||
*self.get_mut(p) = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,10 +125,11 @@ impl Board {
|
||||
/// 7. the canonical black `Empty` glyph.
|
||||
///
|
||||
/// Panics if out of bounds.
|
||||
pub fn glyph_at(&self, x: usize, y: usize) -> Glyph {
|
||||
let grid_glyph = self.get(x, y).as_ref().map(Tile::glyph);
|
||||
pub fn glyph_at<P: Into<Point>>(&self, p: P) -> 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?
|
||||
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.
|
||||
self.floor
|
||||
.glyph_at(x, y, self.width)
|
||||
.glyph_at(p.ux(), p.uy(), self.width)
|
||||
.unwrap_or_else(|| Glyph::transparent())
|
||||
}
|
||||
|
||||
@@ -152,17 +156,9 @@ impl Board {
|
||||
///
|
||||
/// Takes signed coords so callers can pass a raw `pos + delta` without first
|
||||
/// checking for negatives.
|
||||
pub fn in_bounds(&self, pos: (i64, i64)) -> bool {
|
||||
let (x, y) = pos;
|
||||
x >= 0 && y >= 0 && (x as usize) < self.width && (y as usize) < 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()
|
||||
pub fn in_bounds<P: Into<Point>>(&self, pos: P) -> bool {
|
||||
let p = pos.into();
|
||||
p.x >= 0 && p.y >= 0 && p.ux() < self.width && p.uy() < self.height
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// [`dark`](Board::dark) boards; see [`Board::lighting`].
|
||||
/// Panics if `x` or `y` are out of bounds.
|
||||
pub fn is_opaque_at(&self, x: usize, y: usize) -> bool {
|
||||
if self.sensors.iter().any(|s| s.x == x && s.y == y && s.scripting.optics.opaque) {
|
||||
pub fn is_opaque_at<P: Into<Point>>(&self, p: P) -> bool {
|
||||
let p = p.into();
|
||||
if self.sensors.iter().any(|s| s.x == p.ux() && s.y == p.uy() && s.scripting.optics.opaque) {
|
||||
true
|
||||
} else {
|
||||
match self.get(x, y) {
|
||||
match self.get(p) {
|
||||
None => false,
|
||||
Some(Tile::Player) => false,
|
||||
Some(Tile::Object(def)) => {
|
||||
@@ -203,9 +200,12 @@ impl Board {
|
||||
let mut lighting = Lighting::new(w, h);
|
||||
// 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).
|
||||
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.
|
||||
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.
|
||||
for y in 0..h {
|
||||
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;
|
||||
if radius > 0 {
|
||||
add_source(&mut lighting, x, y, radius, color_to_rgb(obj.scripting.glyph.fg));
|
||||
@@ -247,180 +247,121 @@ impl Board {
|
||||
Some(lighting)
|
||||
}
|
||||
|
||||
/// Whether the cell's single solid occupant (if any) can be pushed in `dir`.
|
||||
///
|
||||
/// This is whether the thing in this cell will _transmit_ a push impulse through
|
||||
/// it. Empty cells and cells that can't be pushed in that direction break the
|
||||
/// chain.
|
||||
fn is_pushable(&self, x: usize, y: usize, dir: Direction) -> bool {
|
||||
match self.get(x, y) {
|
||||
Some(Tile::Player) => true,
|
||||
Some(Tile::Object(obj)) => {
|
||||
if let EnterResponse::Push(p) = obj.enter_response {
|
||||
p.allows(dir)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
// /// 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
|
||||
// /// pushable solid.
|
||||
// ///
|
||||
// /// Unlike [`can_push`](Board::can_push) this inspects only the single cell
|
||||
// /// ahead — it does **not** verify the whole chain ends in open space. It is the
|
||||
// /// right test for a simultaneous rotation/shift (applied via
|
||||
// /// [`apply_swap`](Board::apply_swap)), where a destination is occupied by
|
||||
// /// another pushable that is itself moving the same frame. Returns `false` if
|
||||
// /// `(x, y)` holds no pushable, or the cell ahead runs off the board.
|
||||
// ///
|
||||
// /// The **player** is always a blocker: a shift can't relocate the player, and
|
||||
// /// `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 {
|
||||
// // 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
|
||||
/// step in `dir` — i.e. the chain ends at a passable cell rather than the board
|
||||
/// edge or a non-pushable solid.
|
||||
///
|
||||
/// Read-only (`&self`); pairs with [`push`](Board::push). Returns `false` when
|
||||
/// `(x, y)` itself holds no pushable solid, so it doubles as the "is the cell
|
||||
/// ahead shovable?" half of a "can I move here?" query.
|
||||
pub fn can_push(&self, x: usize, y: usize, dir: Direction) -> bool {
|
||||
let (dx, dy): (i64, i64) = dir.into();
|
||||
let (mut cx, mut cy) = (x, y);
|
||||
loop {
|
||||
// This cell must hold a solid pushable in `dir` to advance the chain.
|
||||
if !self.is_pushable(cx, cy, dir) {
|
||||
return false;
|
||||
}
|
||||
let next = (cx as i64 + dx, cy as i64 + dy);
|
||||
if !self.in_bounds(next) {
|
||||
return false; // chain runs off the board
|
||||
}
|
||||
let (nx, ny) = (next.0 as usize, next.1 as usize);
|
||||
if self.is_passable(nx, ny) {
|
||||
return true; // open space at the end: the whole chain can move
|
||||
}
|
||||
// Next cell holds a solid too; continue (it must itself be pushable).
|
||||
cx = nx;
|
||||
cy = ny;
|
||||
}
|
||||
}
|
||||
// /// 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()
|
||||
// }
|
||||
|
||||
/// 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
|
||||
/// pushable solid.
|
||||
///
|
||||
/// Unlike [`can_push`](Board::can_push) this inspects only the single cell
|
||||
/// ahead — it does **not** verify the whole chain ends in open space. It is the
|
||||
/// right test for a simultaneous rotation/shift (applied via
|
||||
/// [`apply_swap`](Board::apply_swap)), where a destination is occupied by
|
||||
/// another pushable that is itself moving the same frame. Returns `false` if
|
||||
/// `(x, y)` holds no pushable, or the cell ahead runs off the board.
|
||||
///
|
||||
/// The **player** is always a blocker: a shift can't relocate the player, and
|
||||
/// `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 {
|
||||
// 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)
|
||||
}
|
||||
|
||||
/// 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();
|
||||
}
|
||||
// /// 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<P: Into<Point>>(&mut self, p: P, dir: Direction) {
|
||||
// let p = p.into();
|
||||
// // Is this even a cell?
|
||||
// if self.in_bounds(p) {
|
||||
// // Is there anything in this cell?
|
||||
// if !self.is_empty(p) {
|
||||
// let (tx, ty) = dir.from_point(p.x, p.y);
|
||||
// // 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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/// 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
|
||||
/// 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).
|
||||
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
|
||||
.iter()
|
||||
.filter(|s| s.x == x && s.y == y)
|
||||
.filter(|s| s.x == p.ux() && s.y == p.uy())
|
||||
.map(|s| s.scripting.id)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Find and return the portal at the given location
|
||||
pub fn portal_at(&self, x: usize, y: usize) -> Option<&Portal> {
|
||||
self.portals.iter().find(|&portal| portal.location() == (x, y))
|
||||
pub fn portal_at<P: Into<Point>>(&self, p: P) -> Option<&Portal> {
|
||||
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
|
||||
@@ -436,12 +377,12 @@ impl Board {
|
||||
///
|
||||
/// A vacated grid cell becomes a transparent `Empty` so the floor shows through.
|
||||
/// 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 {
|
||||
let tile = spec.into_tile(&mut self.next_object_id)?;
|
||||
*self.get_mut(x, y) = Some(tile);
|
||||
*self.get_mut(p) = Some(tile);
|
||||
} else {
|
||||
*self.get_mut(x, y) = None;
|
||||
*self.get_mut(p) = None;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -457,7 +398,7 @@ impl Board {
|
||||
}
|
||||
|
||||
// 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
|
||||
let mut immobile = HashSet::new();
|
||||
@@ -491,12 +432,12 @@ impl Board {
|
||||
// Not blocked, write it to target
|
||||
let origin = cells[curr_idx];
|
||||
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));
|
||||
} else {
|
||||
// it was blocked so just write it back where it was
|
||||
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)| {
|
||||
if matches!(cell, Some(Tile::Player)) {
|
||||
Some((i % self.width, i / self.width))
|
||||
Some((i % self.width, i / self.width).into())
|
||||
} else { None }
|
||||
}).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
|
||||
/// `from` or `to` is out of bounds, or if they're the same cell.
|
||||
pub fn move_cell(&mut self, from: Point, to: Point) {
|
||||
if from != to && self.in_bounds(from.into()) && self.in_bounds(to.into()) {
|
||||
let thing = self.get_mut(from.x as usize, from.y as usize).take();
|
||||
self.grid[to.x as usize + to.y as usize * self.width] = thing;
|
||||
pub fn move_cell<P1: Into<Point>, P2: Into<Point>>(&mut self, from: P1, to: P2) {
|
||||
let from = from.into();
|
||||
let to = to.into();
|
||||
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
|
||||
pub fn is_empty(&self, p: Point) -> bool {
|
||||
self.get(p.x as usize, p.y as usize).is_none()
|
||||
pub fn is_empty<P: Into<Point>>(&self, p: P) -> bool {
|
||||
self.get(p).is_none()
|
||||
}
|
||||
|
||||
/// Return whether the given point contains the player
|
||||
pub fn is_player(&self, p: Point) -> bool {
|
||||
matches!(self.get(p.x as usize, p.y as usize), Some(Tile::Player))
|
||||
pub fn is_player<P: Into<Point>>(&self, p: P) -> bool {
|
||||
matches!(self.get(p), Some(Tile::Player))
|
||||
}
|
||||
|
||||
/// Return whether the given point contains an object
|
||||
pub fn is_object(&self, p: Point) -> bool {
|
||||
matches!(self.get(p.x as usize, p.y as usize), Some(Tile::Object(_)))
|
||||
pub fn is_object<P: Into<Point>>(&self, p: P) -> bool {
|
||||
matches!(self.get(p), Some(Tile::Object(_)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user