99 lines
3.9 KiB
Plaintext
99 lines
3.9 KiB
Plaintext
|
|
// Rotates this object's 8 neighbours one step around a ring every 0.5s, shoving
|
||
|
|
// each pushable into the next ring slot via a single simultaneous `swap`. The spin
|
||
|
|
// direction comes from the `BUILTIN_spinner_*` tag the map loader attaches (it
|
||
|
|
// defaults to clockwise when no tag is present).
|
||
|
|
//
|
||
|
|
// A neighbour may only rotate if its destination slot will actually be free: the
|
||
|
|
// slot is a hole (`passable`), or that slot's own occupant also rotates out. We
|
||
|
|
// can't decide that with `can_shift` alone — `can_shift(j) == false` is ambiguous
|
||
|
|
// between "j is empty" and "j is a blocked solid" — so we seed with `can_shift`
|
||
|
|
// and then cascade-demote using `passable` to spot the genuine holes.
|
||
|
|
fn tick(dt) {
|
||
|
|
// Only start a new rotation when the previous one (and its delay) has drained,
|
||
|
|
// exactly like the built-in pusher's pacing.
|
||
|
|
if Queue.length() != 0 { return; }
|
||
|
|
|
||
|
|
// Direction from the builtin tag; clockwise unless explicitly counter-clockwise.
|
||
|
|
let cw = !Me.has_tag("BUILTIN_spinner_ccw");
|
||
|
|
|
||
|
|
let ox = Me.x;
|
||
|
|
let oy = Me.y;
|
||
|
|
|
||
|
|
// The 8 neighbour offsets in clockwise order, starting at North.
|
||
|
|
let ring = [
|
||
|
|
[ 0, -1], // N
|
||
|
|
[ 1, -1], // NE
|
||
|
|
[ 1, 0], // E
|
||
|
|
[ 1, 1], // SE
|
||
|
|
[ 0, 1], // S
|
||
|
|
[-1, 1], // SW
|
||
|
|
[-1, 0], // W
|
||
|
|
[-1, -1], // NW
|
||
|
|
];
|
||
|
|
|
||
|
|
// The cardinal step that carries each ring cell to the *next* slot in the spin
|
||
|
|
// direction, and the index offset to that slot (+1 clockwise, -1 == +7 counter).
|
||
|
|
// CW: N->NE is East, NE->E is South, ... CCW: N->NW is West, NE->N is West, ...
|
||
|
|
let dirs = if cw {
|
||
|
|
[East, South, South, West, West, North, North, East]
|
||
|
|
} else {
|
||
|
|
[West, West, North, North, East, East, South, South]
|
||
|
|
};
|
||
|
|
let step = if cw { 1 } else { 7 };
|
||
|
|
|
||
|
|
// Animate the glyph one frame per rotation (changing only the character): the
|
||
|
|
// line spins '/'-'\'-, slash-swapped for counter-clockwise. Frame state lives in
|
||
|
|
// the board Registry, keyed per spinner, since script scope resets each tick.
|
||
|
|
let frames = if cw { [47, 0xc4, 92, 0xb3] } else { [92, 0xc4, 47, 0xb3] };
|
||
|
|
let fkey = `spin_${Me.id}`;
|
||
|
|
let f = Registry.get_or(fkey, 0);
|
||
|
|
set_tile(frames[f % 4]);
|
||
|
|
Registry.set(fkey, (f + 1) % 4);
|
||
|
|
|
||
|
|
// moves[i]: will ring cell i rotate into its destination slot? Seed with
|
||
|
|
// can_shift, which is true only when cell i holds a pushable whose immediate
|
||
|
|
// next cell isn't a wall (so it could move *if* that next cell ends up free).
|
||
|
|
let moves = [];
|
||
|
|
for i in 0..8 {
|
||
|
|
let sx = ox + ring[i][0];
|
||
|
|
let sy = oy + ring[i][1];
|
||
|
|
moves.push(can_shift(sx, sy, dirs[i]));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Cascade: a cell may only move if its destination is a hole (`passable`), or
|
||
|
|
// the occupant of that destination also moves. Demote until stable. A broken
|
||
|
|
// arc collapses back from the jam; a full pushable cycle never demotes (every
|
||
|
|
// destination's occupant moves) and rotates whole via swap's cycle handling.
|
||
|
|
loop {
|
||
|
|
let changed = false;
|
||
|
|
for i in 0..8 {
|
||
|
|
let n = (i + step) % 8;
|
||
|
|
let dx = ox + ring[n][0];
|
||
|
|
let dy = oy + ring[n][1];
|
||
|
|
if moves[i] && !passable(dx, dy) && !moves[n] {
|
||
|
|
moves[i] = false;
|
||
|
|
changed = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !changed { break; }
|
||
|
|
}
|
||
|
|
|
||
|
|
// Emit the surviving rotations as one simultaneous batch, then pace to twice
|
||
|
|
// a second. Coordinates are pulled into locals first to keep each expression
|
||
|
|
// simple (Rhai caps expression complexity).
|
||
|
|
let batch = [];
|
||
|
|
for i in 0..8 {
|
||
|
|
if moves[i] {
|
||
|
|
let n = (i + step) % 8;
|
||
|
|
let sx = ox + ring[i][0];
|
||
|
|
let sy = oy + ring[i][1];
|
||
|
|
let dx = ox + ring[n][0];
|
||
|
|
let dy = oy + ring[n][1];
|
||
|
|
batch.push([sx, sy, dx, dy]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
swap(batch);
|
||
|
|
delay(0.5);
|
||
|
|
}
|