Baba.undo() restores the level before the last step or move (board snapshot: objects in age order with ids, the id counter, and won; rules recompiled). place clears the history. SpellGrid's undo throws/panics 'not supported'. stackAt / stack_at lists a tile's kinds oldest first, on Baba and SpellGrid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
188 lines
4.6 KiB
TypeScript
188 lines
4.6 KiB
TypeScript
// Module 8: Push chains. Each test matches a heading in scenarios.md.
|
|
import { expect, test } from "vitest";
|
|
import { newBaba, type Baba } from "../src/harness";
|
|
|
|
/** An empty level `width` wide and `height` high. */
|
|
const emptyLevel = (width: number, height: number) => ({ width, height, objects: [] });
|
|
|
|
/** Place one text object per word: `[word, x, y]`. */
|
|
function write(b: Baba, words: [string, number, number][]): void {
|
|
for (const [word, x, y] of words) b.place(`text:${word}`, x, y);
|
|
}
|
|
|
|
test("test_08_chain_moves_together", () => {
|
|
const b = newBaba(emptyLevel(6, 4));
|
|
write(b, [
|
|
["BABA", 0, 0],
|
|
["IS", 1, 0],
|
|
["YOU", 2, 0],
|
|
]);
|
|
write(b, [
|
|
["ROCK", 0, 1],
|
|
["IS", 1, 1],
|
|
["PUSH", 2, 1],
|
|
]);
|
|
const baba = b.place("baba", 0, 3);
|
|
const rocks = [b.place("rock", 1, 3), b.place("rock", 2, 3), b.place("rock", 3, 3)];
|
|
|
|
b.move("right");
|
|
|
|
expect(b.posOf(baba)).toEqual([1, 3]);
|
|
expect(b.posOf(rocks[0])).toEqual([2, 3]);
|
|
expect(b.posOf(rocks[1])).toEqual([3, 3]);
|
|
expect(b.posOf(rocks[2])).toEqual([4, 3]);
|
|
expect(b.kindsAt(0, 3)).toEqual([]);
|
|
expect(b.kindsAt(4, 3)).toEqual(["rock"]);
|
|
});
|
|
|
|
test("test_08_chain_into_stop_nothing_moves", () => {
|
|
const b = newBaba(emptyLevel(6, 5));
|
|
write(b, [
|
|
["BABA", 0, 0],
|
|
["IS", 1, 0],
|
|
["YOU", 2, 0],
|
|
]);
|
|
write(b, [
|
|
["ROCK", 0, 1],
|
|
["IS", 1, 1],
|
|
["PUSH", 2, 1],
|
|
]);
|
|
write(b, [
|
|
["WALL", 0, 2],
|
|
["IS", 1, 2],
|
|
["STOP", 2, 2],
|
|
]);
|
|
const baba = b.place("baba", 0, 4);
|
|
const rocks = [b.place("rock", 1, 4), b.place("rock", 2, 4), b.place("rock", 3, 4)];
|
|
const wall = b.place("wall", 4, 4);
|
|
|
|
b.move("right");
|
|
|
|
expect(b.posOf(baba)).toEqual([0, 4]);
|
|
expect(b.posOf(rocks[0])).toEqual([1, 4]);
|
|
expect(b.posOf(rocks[1])).toEqual([2, 4]);
|
|
expect(b.posOf(rocks[2])).toEqual([3, 4]);
|
|
expect(b.posOf(wall)).toEqual([4, 4]);
|
|
});
|
|
|
|
test("test_08_open_shut_during_push", () => {
|
|
const b = newBaba(emptyLevel(5, 5));
|
|
write(b, [
|
|
["BABA", 0, 0],
|
|
["IS", 1, 0],
|
|
["YOU", 2, 0],
|
|
]);
|
|
write(b, [
|
|
["KEY", 0, 1],
|
|
["IS", 1, 1],
|
|
["PUSH", 2, 1],
|
|
["AND", 3, 1],
|
|
["OPEN", 4, 1],
|
|
]);
|
|
write(b, [
|
|
["DOOR", 0, 2],
|
|
["IS", 1, 2],
|
|
["STOP", 2, 2],
|
|
["AND", 3, 2],
|
|
["SHUT", 4, 2],
|
|
]);
|
|
const baba = b.place("baba", 0, 4);
|
|
const key = b.place("key", 1, 4);
|
|
const door = b.place("door", 2, 4);
|
|
|
|
b.move("right");
|
|
|
|
expect(b.posOf(key)).toBeNull();
|
|
expect(b.posOf(door)).toBeNull();
|
|
// Inferred, not stated by a source: Baba lands on the key's former tile.
|
|
expect(b.posOf(baba)).toEqual([1, 4]);
|
|
expect(b.kindsAt(2, 4)).toEqual([]);
|
|
expect(b.kindsAt(1, 4)).toEqual(["baba"]);
|
|
});
|
|
|
|
test("test_08_text_pushed", () => {
|
|
const b = newBaba(emptyLevel(4, 5));
|
|
write(b, [
|
|
["BABA", 0, 0],
|
|
["IS", 1, 0],
|
|
["YOU", 2, 0],
|
|
]);
|
|
const baba = b.place("baba", 0, 2);
|
|
const rockText = b.place("text:ROCK", 1, 2);
|
|
write(b, [
|
|
["IS", 2, 3],
|
|
["PUSH", 2, 4],
|
|
]);
|
|
|
|
expect(b.rules()).toEqual(["BABA IS YOU", "TEXT IS PUSH"]);
|
|
|
|
b.move("right");
|
|
|
|
expect(b.posOf(baba)).toEqual([1, 2]);
|
|
expect(b.posOf(rockText)).toEqual([2, 2]);
|
|
expect(b.rules()).toEqual(["BABA IS YOU", "ROCK IS PUSH", "TEXT IS PUSH"]);
|
|
});
|
|
|
|
test("test_08_level_edge_is_stop", () => {
|
|
const b = newBaba(emptyLevel(3, 3));
|
|
write(b, [
|
|
["BABA", 0, 0],
|
|
["IS", 1, 0],
|
|
["YOU", 2, 0],
|
|
]);
|
|
const baba = b.place("baba", 1, 2);
|
|
const flagText = b.place("text:FLAG", 2, 2);
|
|
|
|
b.move("right");
|
|
|
|
expect(b.posOf(baba)).toEqual([1, 2]);
|
|
expect(b.posOf(flagText)).toEqual([2, 2]);
|
|
|
|
b.move("down");
|
|
|
|
expect(b.posOf(baba)).toEqual([1, 2]);
|
|
expect(b.posOf(flagText)).toEqual([2, 2]);
|
|
});
|
|
|
|
test("test_08_stacked_you_moves_together", () => {
|
|
const b = newBaba(emptyLevel(5, 3));
|
|
write(b, [
|
|
["BABA", 0, 0],
|
|
["IS", 1, 0],
|
|
["YOU", 2, 0],
|
|
["AND", 3, 0],
|
|
["PUSH", 4, 0],
|
|
]);
|
|
const first = b.place("baba", 1, 2);
|
|
const second = b.place("baba", 1, 2);
|
|
const ahead = b.place("baba", 2, 2);
|
|
|
|
b.move("right");
|
|
|
|
expect(b.posOf(ahead)).toEqual([3, 2]);
|
|
expect(b.posOf(first)).toEqual([2, 2]);
|
|
expect(b.posOf(second)).toEqual([2, 2]);
|
|
});
|
|
|
|
test("test_08_stack_is_age_ordered", () => {
|
|
const b = newBaba(emptyLevel(3, 3));
|
|
write(b, [
|
|
["BABA", 0, 0],
|
|
["IS", 1, 0],
|
|
["YOU", 2, 0],
|
|
]);
|
|
const baba = b.place("baba", 0, 2);
|
|
b.place("rock", 1, 2);
|
|
b.place("key", 1, 2);
|
|
|
|
expect(b.stackAt(1, 2)).toEqual(["rock", "key"]);
|
|
|
|
b.move("right");
|
|
|
|
expect(b.posOf(baba)).toEqual([1, 2]);
|
|
expect(b.kindsAt(1, 2)).toEqual(["baba", "key", "rock"]);
|
|
// Oldest first: Baba arrived last, but it was placed first.
|
|
expect(b.stackAt(1, 2)).toEqual(["baba", "rock", "key"]);
|
|
expect(b.stackAt(0, 2)).toEqual([]);
|
|
});
|