Big API refactor

This commit is contained in:
2026-06-28 00:12:52 -05:00
parent db9a5d37b6
commit 9de31d933b
31 changed files with 1036 additions and 1029 deletions
+20 -48
View File
@@ -80,6 +80,11 @@ impl Board {
self.layers.len()
}
/// Return a list of all `ObjectId`s currently on the board.
pub fn all_ids(&self) -> Vec<ObjectId> {
self.objects.keys().cloned().collect()
}
/// Returns a reference to the cell at `(x, y)` on layer `z`.
///
/// The cell is a `(Glyph, Archetype)` tuple. Panics if `z`, `x`, or `y` are
@@ -98,7 +103,7 @@ impl Board {
/// Replace the solid (if any) at `(x, y)` with `Empty`
pub fn clear_solid(&mut self, x: usize, y: usize) {
if self.in_bounds((x as i32, y as i32)) {
if self.in_bounds((x as i64, y as i64)) {
if let Some(z) = self.solid_cell_layer(x, y) {
*self.get_mut(z, x, y) = (Archetype::Empty.default_glyph(), Archetype::Empty)
}
@@ -121,7 +126,7 @@ impl Board {
/// Panics if out of bounds.
pub fn glyph_at(&self, x: usize, y: usize) -> Glyph {
// The player is rendered above the whole stack (see the `Player` notes).
if self.player.x == x as i32 && self.player.y == y as i32 {
if self.player.x == x as i64 && self.player.y == y as i64 {
return Glyph::player();
}
@@ -169,7 +174,7 @@ impl Board {
///
/// Takes signed coords so callers can pass a raw `pos + delta` without first
/// checking for negatives.
pub fn in_bounds(&self, pos: (i32, i32)) -> bool {
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
}
@@ -195,7 +200,7 @@ impl Board {
/// Panics if `x` or `y` are out of bounds.
pub fn solid_at(&self, x: usize, y: usize) -> Option<Solid> {
// The player wins its cell (load-time invariant), so it is the solid there.
if self.player.x == x as i32 && self.player.y == y as i32 {
if self.player.x == x as i64 && self.player.y == y as i64 {
return Some(Solid::player_at(x, y));
}
// A solid object shadows the cell it sits on; capture its behavior now.
@@ -236,39 +241,6 @@ impl Board {
self.solid_at(x, y).is_none()
}
/// Returns `true` if the solids at `(x1, y1)` and `(x2, y2)` may share a cell —
/// i.e. moving one onto the other wouldn't break the "one solid per cell"
/// invariant. That holds when at least one cell is empty, or one holds the
/// player and the other a **grab** thing (the player collects it).
///
/// Off-board coordinates are never combinable (returns `false` rather than
/// panicking). Backs the script-facing `combinable(x1, y1, x2, y2)` fn.
pub fn is_combinable(&self, x1: usize, y1: usize, x2: usize, y2: usize) -> bool {
// Are either out of bounds?
if !self.in_bounds((x1 as i32, y1 as i32)) || !self.in_bounds((x2 as i32, y2 as i32)) {
return false
}
// Grab the solids
let solid1 = self.solid_at(x1, y1);
let solid2 = self.solid_at(x2, y2);
// Is one cell empty?
if solid1.is_none() || solid2.is_none() { return true }
// They're both present, unwrap them:
let solid1 = solid1.unwrap();
let solid2 = solid2.unwrap();
// This is probably disallowed then, but let's check for a player coexisting with a grab:
if solid1.player() && solid2.grab() || solid2.player() && solid1.grab() {
return true
}
// Nope, two solids that can't coexist:
false
}
/// Whether the cell's single solid occupant (if any) can be pushed in `dir`.
///
/// Non-solid things are never pushable: `pushable` only matters for solids.
@@ -289,14 +261,14 @@ impl Board {
/// `(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): (i32, i32) = dir.into();
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 i32 + dx, cy as i32 + dy);
let next = (cx as i64 + dx, cy as i64 + dy);
if !self.in_bounds(next) {
return false; // chain runs off the board
}
@@ -329,8 +301,8 @@ impl Board {
if !self.is_pushable(x, y, dir) {
return false;
}
let (dx, dy): (i32, i32) = dir.into();
let next = (x as i32 + dx, y as i32 + dy);
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
}
@@ -354,7 +326,7 @@ impl Board {
if !self.can_push(x, y, dir) {
return;
}
let (dx, dy): (i32, i32) = dir.into();
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.
@@ -362,8 +334,8 @@ impl Board {
let (mut cx, mut cy) = (x, y);
while !self.is_passable(cx, cy) {
chain.push((cx, cy));
cx = (cx as i32 + dx) as usize;
cy = (cy as i32 + dy) as usize;
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);
@@ -376,8 +348,8 @@ impl Board {
/// terrain archetype (a crate) is moved within its own layer, leaving a
/// transparent cell behind so the layer beneath (e.g. floor) shows through.
/// The caller guarantees the destination is already clear.
fn shift_solid(&mut self, x: usize, y: usize, dx: i32, dy: i32) {
let (tx, ty) = ((x as i32 + dx) as usize, (y as i32 + dy) as usize);
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);
let Some(solid) = self.solid_at(x, y) else {
return; // nothing to shift
};
@@ -541,7 +513,7 @@ impl Board {
/// Shifts a set of cells, given as `(x, y)` coordinates. Backs the script
/// `shift()` fn. Returns any errors as [`LogLine`]s for the caller to log.
pub fn apply_shift(&mut self, cells: &[(i32, i32)]) -> Vec<LogLine> {
pub fn apply_shift(&mut self, cells: &[(i64, i64)]) -> Vec<LogLine> {
// Validate all the cells are in bounds, error if not:
if cells.iter().any(|&c| !self.in_bounds(c)) {
return vec![LogLine::error("Called shift() with a cell out of bounds")]
@@ -637,7 +609,7 @@ pub(crate) mod tests {
pub(crate) fn open_board(
w: usize,
h: usize,
player: (i32, i32),
player: (i64, i64),
objects: Vec<ObjectDef>,
) -> Board {
let mut object_map: BTreeMap<ObjectId, ObjectDef> = BTreeMap::new();