Adds course/11-conditions: lesson, scenarios, nine test_11 tests in both tracks, the cumulative reference (Module 10's plus conditions) and the NEAR drop. The engine learns a condition role, rules carry conditions, and Level.hasProperty and conversion evaluate content's condition checks against the board whenever a rule is used. Resolution pairs only rules with the same conditions. Catalog, harness contract, research notes and the diff-tracks generator follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
306 lines
7.8 KiB
TypeScript
306 lines
7.8 KiB
TypeScript
// Module 11: Conditions. 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_11_condition_gates_property", () => {
|
|
const b = newBaba(emptyLevel(6, 4));
|
|
write(b, [
|
|
["KEY", 0, 0],
|
|
["ON", 1, 0],
|
|
["ROCK", 2, 0],
|
|
["IS", 3, 0],
|
|
["WIN", 4, 0],
|
|
]);
|
|
const onRock = b.place("key", 0, 2);
|
|
const rock = b.place("rock", 0, 2);
|
|
const alone = b.place("key", 2, 2);
|
|
|
|
expect(b.rules()).toEqual(["KEY ON ROCK IS WIN", "TEXT IS PUSH"]);
|
|
expect(b.activeRules()).toEqual(["KEY ON ROCK IS WIN", "TEXT IS PUSH"]);
|
|
expect(b.hasProperty(onRock, "WIN")).toBe(true);
|
|
expect(b.hasProperty(alone, "WIN")).toBe(false);
|
|
expect(b.hasProperty(rock, "WIN")).toBe(false);
|
|
});
|
|
|
|
test("test_11_condition_follows_the_object", () => {
|
|
const b = newBaba(emptyLevel(6, 5));
|
|
write(b, [
|
|
["BABA", 0, 0],
|
|
["IS", 1, 0],
|
|
["YOU", 2, 0],
|
|
]);
|
|
write(b, [
|
|
["LAVA", 0, 1],
|
|
["IS", 1, 1],
|
|
["HOT", 2, 1],
|
|
]);
|
|
write(b, [
|
|
["BABA", 0, 2],
|
|
["ON", 1, 2],
|
|
["KEY", 2, 2],
|
|
["IS", 3, 2],
|
|
["MELT", 4, 2],
|
|
]);
|
|
const toKey = b.place("baba", 0, 4);
|
|
b.place("key", 1, 4);
|
|
b.place("lava", 1, 4);
|
|
const toLava = b.place("baba", 3, 4);
|
|
b.place("lava", 4, 4);
|
|
const before = b.activeRules();
|
|
|
|
expect(b.hasProperty(toKey, "MELT")).toBe(false);
|
|
expect(b.hasProperty(toLava, "MELT")).toBe(false);
|
|
|
|
b.step("right");
|
|
|
|
expect(b.activeRules()).toEqual(before);
|
|
expect(b.posOf(toKey)).toBeNull();
|
|
expect(b.kindsAt(1, 4)).toEqual(["key", "lava"]);
|
|
expect(b.posOf(toLava)).toEqual([4, 4]);
|
|
expect(b.kindsAt(4, 4)).toEqual(["baba", "lava"]);
|
|
});
|
|
|
|
test("test_11_and_requires_both", () => {
|
|
const b = newBaba(emptyLevel(7, 4));
|
|
write(b, [
|
|
["KEY", 0, 0],
|
|
["ON", 1, 0],
|
|
["ROCK", 2, 0],
|
|
["AND", 3, 0],
|
|
["FLAG", 4, 0],
|
|
["IS", 5, 0],
|
|
["WIN", 6, 0],
|
|
]);
|
|
const withRock = b.place("key", 0, 2);
|
|
b.place("rock", 0, 2);
|
|
const withFlag = b.place("key", 2, 2);
|
|
b.place("flag", 2, 2);
|
|
const withBoth = b.place("key", 4, 2);
|
|
b.place("rock", 4, 2);
|
|
b.place("flag", 4, 2);
|
|
|
|
expect(b.rules()).toEqual(["KEY ON ROCK AND FLAG IS WIN", "TEXT IS PUSH"]);
|
|
expect(b.hasProperty(withRock, "WIN")).toBe(false);
|
|
expect(b.hasProperty(withFlag, "WIN")).toBe(false);
|
|
expect(b.hasProperty(withBoth, "WIN")).toBe(true);
|
|
});
|
|
|
|
test("test_11_and_counts_objects", () => {
|
|
const b = newBaba(emptyLevel(7, 4));
|
|
write(b, [
|
|
["KEY", 0, 0],
|
|
["ON", 1, 0],
|
|
["ROCK", 2, 0],
|
|
["AND", 3, 0],
|
|
["ROCK", 4, 0],
|
|
["IS", 5, 0],
|
|
["WIN", 6, 0],
|
|
]);
|
|
const oneRock = b.place("key", 0, 2);
|
|
b.place("rock", 0, 2);
|
|
const twoRocks = b.place("key", 2, 2);
|
|
b.place("rock", 2, 2);
|
|
b.place("rock", 2, 2);
|
|
|
|
expect(b.rules()).toEqual(["KEY ON ROCK AND ROCK IS WIN", "TEXT IS PUSH"]);
|
|
expect(b.hasProperty(oneRock, "WIN")).toBe(false);
|
|
expect(b.hasProperty(twoRocks, "WIN")).toBe(true);
|
|
});
|
|
|
|
test("test_11_conditional_not_only_where_it_holds", () => {
|
|
const b = newBaba(emptyLevel(6, 7));
|
|
write(b, [
|
|
["BABA", 0, 0],
|
|
["IS", 1, 0],
|
|
["YOU", 2, 0],
|
|
]);
|
|
write(b, [
|
|
["BABA", 0, 2],
|
|
["ON", 1, 2],
|
|
["KEY", 2, 2],
|
|
["IS", 3, 2],
|
|
["NOT", 4, 2],
|
|
["YOU", 5, 2],
|
|
]);
|
|
const onKey = b.place("baba", 0, 6);
|
|
b.place("key", 0, 6);
|
|
const free = b.place("baba", 2, 6);
|
|
|
|
expect(b.activeRules()).toEqual(["BABA IS YOU", "BABA ON KEY IS NOT YOU", "TEXT IS PUSH"]);
|
|
expect(b.hasProperty(onKey, "YOU")).toBe(false);
|
|
expect(b.hasProperty(free, "YOU")).toBe(true);
|
|
|
|
write(b, [
|
|
["BABA", 0, 4],
|
|
["ON", 1, 4],
|
|
["KEY", 2, 4],
|
|
["IS", 3, 4],
|
|
["YOU", 4, 4],
|
|
]);
|
|
|
|
expect(b.rules()).toContain("BABA ON KEY IS YOU");
|
|
expect(b.activeRules()).toEqual(["BABA IS YOU", "BABA ON KEY IS NOT YOU", "TEXT IS PUSH"]);
|
|
|
|
b.step("right");
|
|
|
|
expect(b.posOf(onKey)).toEqual([0, 6]);
|
|
expect(b.posOf(free)).toEqual([3, 6]);
|
|
});
|
|
|
|
test("test_11_conditional_conversion", () => {
|
|
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, [
|
|
["KEY", 0, 2],
|
|
["ON", 1, 2],
|
|
["ROCK", 2, 2],
|
|
["IS", 3, 2],
|
|
["FLAG", 4, 2],
|
|
]);
|
|
const resting = b.place("key", 0, 4);
|
|
b.place("rock", 0, 4);
|
|
const baba = b.place("baba", 2, 4);
|
|
const pushed = b.place("rock", 3, 4);
|
|
const reached = b.place("key", 4, 4);
|
|
const alone = b.place("key", 5, 4);
|
|
|
|
b.step("right");
|
|
|
|
expect(b.posOf(baba)).toEqual([3, 4]);
|
|
expect(b.posOf(pushed)).toEqual([4, 4]);
|
|
expect(b.kindsAt(0, 4)).toEqual(["flag", "rock"]);
|
|
expect(b.posOf(resting)).toEqual([0, 4]);
|
|
expect(b.kindsAt(4, 4)).toEqual(["flag", "rock"]);
|
|
expect(b.posOf(reached)).toEqual([4, 4]);
|
|
expect(b.kindsAt(5, 4)).toEqual(["key"]);
|
|
expect(b.posOf(alone)).toEqual([5, 4]);
|
|
});
|
|
|
|
test("test_11_condition_and_stacked_text", () => {
|
|
const b = newBaba(emptyLevel(6, 7));
|
|
write(b, [
|
|
["BABA", 0, 0],
|
|
["ON", 1, 0],
|
|
["KEY", 2, 0],
|
|
["ROCK", 2, 0],
|
|
["IS", 3, 0],
|
|
["WIN", 4, 0],
|
|
]);
|
|
write(b, [
|
|
["FLAG", 0, 2],
|
|
["ON", 1, 2],
|
|
["AND", 1, 2],
|
|
["KEY", 2, 2],
|
|
["IS", 3, 2],
|
|
["PUSH", 4, 2],
|
|
]);
|
|
const onRock = b.place("baba", 0, 4);
|
|
b.place("rock", 0, 4);
|
|
const onKey = b.place("baba", 2, 4);
|
|
b.place("key", 2, 4);
|
|
const alone = b.place("baba", 4, 4);
|
|
|
|
expect(b.rules()).toEqual([
|
|
"BABA ON KEY IS WIN",
|
|
"BABA ON ROCK IS WIN",
|
|
"FLAG IS PUSH",
|
|
"FLAG ON KEY IS PUSH",
|
|
"KEY IS PUSH",
|
|
"TEXT IS PUSH",
|
|
]);
|
|
expect(b.hasProperty(onRock, "WIN")).toBe(true);
|
|
expect(b.hasProperty(onKey, "WIN")).toBe(true);
|
|
expect(b.hasProperty(alone, "WIN")).toBe(false);
|
|
});
|
|
|
|
test("test_11_on_group_noun", () => {
|
|
const b = newBaba(emptyLevel(8, 4));
|
|
write(b, [
|
|
["ROCK", 0, 0],
|
|
["ON", 1, 0],
|
|
["ALL", 2, 0],
|
|
["IS", 3, 0],
|
|
["WIN", 4, 0],
|
|
]);
|
|
const alone = b.place("rock", 0, 2);
|
|
const onText = b.place("rock", 2, 2);
|
|
b.place("text:BABA", 2, 2);
|
|
const onKey = b.place("rock", 4, 2);
|
|
b.place("key", 4, 2);
|
|
const first = b.place("rock", 6, 2);
|
|
const second = b.place("rock", 6, 2);
|
|
|
|
expect(b.rules()).toEqual(["ROCK ON ALL IS WIN", "TEXT IS PUSH"]);
|
|
expect(b.hasProperty(alone, "WIN")).toBe(false);
|
|
expect(b.hasProperty(onText, "WIN")).toBe(false);
|
|
expect(b.hasProperty(onKey, "WIN")).toBe(true);
|
|
expect(b.hasProperty(first, "WIN")).toBe(true);
|
|
expect(b.hasProperty(second, "WIN")).toBe(true);
|
|
});
|
|
|
|
test("test_11_condition_rechecked_between_hazards", () => {
|
|
const b = newBaba(emptyLevel(6, 8));
|
|
write(b, [
|
|
["BABA", 0, 0],
|
|
["IS", 1, 0],
|
|
["YOU", 2, 0],
|
|
]);
|
|
write(b, [
|
|
["BABA", 0, 1],
|
|
["AND", 1, 1],
|
|
["LAVA", 2, 1],
|
|
["IS", 3, 1],
|
|
["FLOAT", 4, 1],
|
|
]);
|
|
write(b, [
|
|
["LAVA", 0, 2],
|
|
["IS", 1, 2],
|
|
["HOT", 2, 2],
|
|
]);
|
|
write(b, [
|
|
["KEY", 0, 3],
|
|
["IS", 1, 3],
|
|
["SINK", 2, 3],
|
|
]);
|
|
write(b, [
|
|
["BABA", 0, 4],
|
|
["ON", 1, 4],
|
|
["KEY", 2, 4],
|
|
["IS", 3, 4],
|
|
["MELT", 4, 4],
|
|
]);
|
|
const baba = b.place("baba", 2, 6);
|
|
const lava = b.place("lava", 3, 6);
|
|
const key = b.place("key", 3, 6);
|
|
const rock = b.place("rock", 3, 6);
|
|
|
|
expect(b.rules()).toContain("BABA ON KEY IS MELT");
|
|
expect(b.hasProperty(baba, "MELT")).toBe(false);
|
|
|
|
b.step("right");
|
|
|
|
expect(b.posOf(key)).toBeNull();
|
|
expect(b.posOf(rock)).toBeNull();
|
|
expect(b.posOf(lava)).toEqual([3, 6]);
|
|
expect(b.posOf(baba)).toEqual([3, 6]);
|
|
expect(b.kindsAt(3, 6)).toEqual(["baba", "lava"]);
|
|
expect(b.lost()).toBe(false);
|
|
});
|