spinners and fmt

This commit is contained in:
2026-06-21 01:32:47 -05:00
parent d32914d99d
commit 8e90084b53
31 changed files with 1445 additions and 284 deletions
+83 -1
View File
@@ -36,6 +36,13 @@
//! - `Board.named(name) -> ObjectInfo | ()` — object with that name (or unit)
//! - `Board.get(id) -> ObjectInfo | ()` — look up by id; `-1` returns player info
//!
//! ## Cell queries (free fns)
//!
//! - `blocked(dir) -> bool` — would the caller be blocked moving one step in `dir`
//! - `can_push(x, y, dir) -> bool` — is the pushable chain at `(x, y)` shovable in `dir`
//! - `can_shift(x, y, dir) -> bool` — like `can_push`, but only checks the cell ahead
//! - `passable(x, y) -> bool` — is `(x, y)` on-board and free of any solid (a hole)
//!
//! ## Self-reference API (`Me.*`)
//!
//! - `Me.id / name / x / y` — getters
@@ -47,7 +54,8 @@
//!
//! `move(dir)`, `delay(secs)`, `now()`, `set_tile(n)`, `log(msg)`, `say(msg)`,
//! `set_fg(fg)`, `set_bg(bg)`, `set_color(fg, bg)`, `set_tag(target, tag, present)`,
//! `send(target_id, fn_name [, arg])`, `teleport(x, y)`
//! `send(target_id, fn_name [, arg])`, `teleport(x, y)`, `push(x, y, dir)`,
//! `swap([[src_x, src_y, dst_x, dst_y], …])`
//!
//! ## Queue API
//!
@@ -672,6 +680,32 @@ fn register_read_api(
is_blocked(&b, &bq, source_of(&ctx), dir)
});
// can_push(x, y, dir) — true if (x, y) holds a pushable whose chain can be
// shoved one step in dir (the read-only half of push()). False off-board.
let b = board.clone();
engine.register_fn("can_push", move |x: i64, y: i64, dir: Direction| -> bool {
let board = b.borrow();
board.in_bounds((x as i32, y as i32)) && board.can_push(x as usize, y as usize, dir)
});
// can_shift(x, y, dir) — like can_push but inspects only the cell ahead: (x, y)
// holds a pushable and the next cell is empty or another pushable. False
// off-board. The right gate for a simultaneous shift/rotation via swap().
let b = board.clone();
engine.register_fn("can_shift", move |x: i64, y: i64, dir: Direction| -> bool {
let board = b.borrow();
board.in_bounds((x as i32, y as i32)) && board.can_shift(x as usize, y as usize, dir)
});
// passable(x, y) — true if (x, y) is on-board and holds no solid (an empty cell
// a mover could enter). Off-board is not passable. Lets a script distinguish a
// hole from a blocked solid (which can_shift/can_push alone cannot).
let b = board.clone();
engine.register_fn("passable", move |x: i64, y: i64| -> bool {
let board = b.borrow();
board.in_bounds((x as i32, y as i32)) && board.is_passable(x as usize, y as usize)
});
// Board.tagged(tag) -> Array[ObjectInfo]
let b = board.clone();
engine.register_fn(
@@ -983,6 +1017,54 @@ fn register_write_api(engine: &mut Engine, queues: &QueueMap) {
},
);
});
// push(x, y, dir): shove the pushable chain at (x, y) one step in dir. Acts on
// arbitrary cells, not the caller, and adds no delay (zero time cost).
let q = queues.clone();
engine.register_fn(
"push",
move |ctx: NativeCallContext, x: i64, y: i64, dir: Direction| {
emit(
&q,
source_of(&ctx),
Action::Push {
x: x as i32,
y: y as i32,
dir,
},
);
},
);
// swap(pairs): apply a batch of one-way solid moves simultaneously. `pairs` is
// an array whose elements are four-int arrays `[src_x, src_y, dst_x, dst_y]`. A
// malformed entry is skipped with a logged error. Zero time cost.
let q = queues.clone();
engine.register_fn("swap", move |ctx: NativeCallContext, arr: Array| {
let src = source_of(&ctx);
let mut pairs: Vec<(i32, i32, i32, i32)> = Vec::new();
for elem in &arr {
// Each entry must be an array of exactly four integers.
let coords: Option<Vec<i64>> = elem.read_lock::<Array>().and_then(|inner| {
if inner.len() == 4 {
inner.iter().map(|v| v.as_int().ok()).collect()
} else {
None
}
});
match coords {
Some(c) => pairs.push((c[0] as i32, c[1] as i32, c[2] as i32, c[3] as i32)),
None => emit(
&q,
src,
Action::Log(LogLine::error(
"swap: each entry must be [src_x, src_y, dst_x, dst_y]".to_string(),
)),
),
}
}
emit(&q, src, Action::Swap(pairs));
});
}
// ── Queue API ─────────────────────────────────────────────────────────────────