feat: Module 12, Combat (attack as one Sequence, Taunt, defender-first as a queue priority, end of turn; Bonechewer Brawler as the Expansion Drop)
Adds course/12-combat: lesson, scenarios, nine test_12 tests in both tracks, the cumulative reference (Module 11's plus combat) and the drop. The Damage action carries per-target hits with a priority; react stable-sorts its Queue by priority before order of play, so the triggers answering the defender's Damage Event go first. orderAttack/canAttack/endTurn join the harness (orderAttack because attack(id) already reads Attack). endOfTurn triggers fire, so Drakkari Enchanter's data is live. Test Bell is scoped to Module 12's cards. Harness contract, catalog, research notes and earlier lessons' forward pointers follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c8fff05ccb
commit
fb9d0a4eb4
89 files changed
+11852
-6
No files matched your search
@@ -0,0 +1,70 @@
|
||||
// The SpellGrid command line, played from a script (the reference's own check; not a course test).
|
||||
import { readFileSync } from "node:fs";
|
||||
import { expect, test } from "vitest";
|
||||
import { run } from "../src/cli";
|
||||
|
||||
function play(level: string, input: string[]): { outcome: string; out: string } {
|
||||
let out = "";
|
||||
const outcome = run(readFileSync(`levels/${level}.json`, "utf8"), () => input.shift(), (s) => (out += s));
|
||||
return { outcome, out };
|
||||
}
|
||||
|
||||
test("cli_wins_level_1", () => {
|
||||
const { outcome, out } = play("l1-double-tap", ["w", "w", "c Whirlwind", "s", "s", "a", "s", "s", "d", "d"]);
|
||||
expect(outcome).toBe("won");
|
||||
expect(out).toContain("DEA IS DOU");
|
||||
expect(out).toContain("You win!");
|
||||
});
|
||||
|
||||
test("cli_wins_level_2_with_choices_and_play", () => {
|
||||
const input = ["c Graft", "Ember Moth", "Rune Scribe", "bottom", "c Ember Moth (Assembled) 4 3", "a", "a"];
|
||||
const { outcome, out } = play("l2-spliced-corpse", input);
|
||||
expect(outcome).toBe("won");
|
||||
expect(out).toContain("Choose the base");
|
||||
expect(out).toContain("Hand: Ember Moth (Assembled)");
|
||||
});
|
||||
|
||||
test("cli_prints_the_log_tree_of_each_command", () => {
|
||||
const { out } = play("l1-double-tap", ["w", "w", "c Whirlwind", "q"]);
|
||||
// The moves run no Sequence, so only the cast prints a log: its tree, Deathrattles nested.
|
||||
expect(out.split("Log:\n")).toHaveLength(2);
|
||||
expect(out).toContain(
|
||||
[
|
||||
"Log:",
|
||||
'SpellCast{card: "Whirlwind"}',
|
||||
"Damaged{target: 32, amount: 1}",
|
||||
"Damaged{target: 33, amount: 1}",
|
||||
"Destroyed{id: 32}",
|
||||
"DeathPhase{round: 1}",
|
||||
'Triggered{source: 32, trigger: "deathrattle"}',
|
||||
" Damaged{target: 33, amount: 1}",
|
||||
'Triggered{source: 32, trigger: "deathrattle"}',
|
||||
" Damaged{target: 33, amount: 1}",
|
||||
"Destroyed{id: 33}",
|
||||
"DeathPhase{round: 2}",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
});
|
||||
|
||||
test("cli_reports_errors_and_quits", () => {
|
||||
const { outcome, out } = play("l3-simultaneous-paradox", ["c Spark", "x", "q"]);
|
||||
expect(outcome).toBe("quit");
|
||||
expect(out).toContain('"Spark" is not in the hand');
|
||||
expect(out).toContain("Commands:");
|
||||
});
|
||||
|
||||
// The two reference CLIs print the same text for the same input (tools/diff-tracks.mjs checks it).
|
||||
test("cli_prints_card_names_as_typed", () => {
|
||||
const { out } = play("l3-simultaneous-paradox", ['c a"b', "q"]);
|
||||
expect(out).toContain('"a"b" is not in the hand');
|
||||
});
|
||||
|
||||
test("cli_numbers_are_plain_digits", () => {
|
||||
// "-1" is not a coordinate, so it is part of the card's name.
|
||||
const cast = play("l1-double-tap", ["c Whirlwind -1 0", "q"]);
|
||||
expect(cast.out).toContain('"Whirlwind -1 0" is not in the hand');
|
||||
// "+2" is not a number, so the chooser takes the first option.
|
||||
const graft = play("l2-spliced-corpse", ["c Graft", "+2", "1", "1", "q"]);
|
||||
expect(graft.out).toContain("Hand: Clay Golem (Assembled)");
|
||||
});
|
||||
Reference in new issue
Block a user