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
@@ -13,6 +13,7 @@ Each module adds functions; `start-module` prints the line for the module you st
|
||||
- M3: `destroy(target: Id): void` (marks pending-destroy, one Sequence).
|
||||
- M4: no new functions; `play(handId)` (the M1 Expansion Drop) is now required: it runs one Sequence in which the minion enters the right end of its owner's board (`Summoned{id}`), then its Battlecry resolves (`Triggered{source, trigger: "battlecry"}`). The M4 tests also use `Baron Rivendare`, `Novice Engineer`, `Bolf Ramshield` and `Twisting Nether` (the M3 Expansion Drop card). New event `ArmorLost{player, amount}` (see `GameEvent`).
|
||||
- M5: `registerCards(defs: CardDef[]): void`; `assemble(base: string, donor: string, half: "top"|"bottom"): string` (registers and returns new card name `"<base> (Assembled)"`; throws `AssembleError` if the text is unchanged or not well-formed); `cost(id): number`; `setChooser(choose: (kind: "base"|"donor"|"half", options: string[]) => string): void` (makes Assemble's choices). `CardDef` is the card format of `course/schema/card.schema.json`; `newGame` loads every card file in the workspace's `cards/` directory.
|
||||
- M12: `orderAttack(attacker: Id, defender: Id): void` (one Sequence; throws an `Error` whose message is `canAttack`'s reason if the attack is illegal, and changes nothing); `canAttack(attacker: Id, defender: Id): string | null` (why the attack is illegal, or `null`); `endTurn(player: Player): void` (one Sequence: `player`'s end of turn triggers resolve). The M12 tests also use `Test Bell` (`course/12-combat/cards/`) and `Drakkari Enchanter`. The name is `orderAttack` because `attack(id)` (M2) already reads a character's Attack.
|
||||
|
||||
### Side by side (Part I)
|
||||
|
||||
@@ -39,6 +40,9 @@ Each module adds functions; `start-module` prints the line for the module you st
|
||||
| M5 | `assemble(base, donor, half)` (throws `AssembleError`) | `assemble(&mut self, base: &str, donor: &str, half: Half) -> Result<String, AssembleError>` (`Half::Top` / `Half::Bottom`) |
|
||||
| M5 | `cost(id)` | `cost(&self, id: Id) -> i32` |
|
||||
| M5 | `setChooser(choose)` | `set_chooser(&mut self, chooser: impl FnMut(&str, &[String]) -> String + 'static)` |
|
||||
| M12 | `orderAttack(attacker, defender)` (throws `Error`) | `order_attack(&mut self, attacker: Id, defender: Id) -> Result<(), String>` |
|
||||
| M12 | `canAttack(attacker, defender): string \| null` | `can_attack(&self, attacker: Id, defender: Id) -> Option<String>` |
|
||||
| M12 | `endTurn(player)` | `end_turn(&mut self, player: Player)` |
|
||||
|
||||
From Module 5, Rust's `CardDef` must implement `serde::Deserialize` for the card format: the tests parse `cards/*.json` with `serde_json` (add `serde` with the `derive` feature and `serde_json` to `Cargo.toml`). The TypeScript tests import the files with Vite's `import.meta.glob`.
|
||||
|
||||
@@ -46,6 +50,8 @@ From Module 5, Assemble's result must be well-formed. For a minion: every effect
|
||||
|
||||
From Module 5, a Sequence is bounded. A trigger of a minion is not queued for an event raised while that same trigger of that same minion is resolving. More than 64 Death Phases in one Sequence is an error: the call that started the Sequence throws an `Error` (TypeScript) or panics (Rust), with a message containing `Death Phase limit`. A `summon` effect with `count` greater than 1 places its tokens left to right in summon order.
|
||||
|
||||
From Module 12, an attack is one Sequence. It is legal when the attacker is a minion in play, the defender is an enemy minion in play or the enemy hero, the attacker's Attack is more than 0, and, while the defender's side has a minion with the keyword `taunt`, the defender is such a minion. The reason `canAttack` gives contains `Taunt` when Taunt is what forbids the attack, and `Attack` when 0 Attack does. The attacker deals its Attack to the defender and the defender deals its Attack to the attacker at the same time: one Damage action with two targets, each with its own amount, its `Damaged` events in order of play like any area damage. A hero has no Attack and deals nothing back, and neither does a 0 Attack minion. The triggers answering the defender's `Damaged` event are queued before those answering the attacker's, whatever their order of play; otherwise the Queue is as before. Combat damage goes through Armor and redirects like any damage. `endTurn(player)` raises the end of `player`'s turn: each trigger of that player's minions listening for `endOfTurn` responds, in order of play (`Triggered{source, trigger: "endOfTurn"}`), and a trigger multiplier for `endOfTurn` applies as for any trigger. The course has no mana, no limit on attacks per turn and no summoning sickness.
|
||||
|
||||
### `GameEvent`
|
||||
|
||||
Tagged by `type`: `Summoned{id}`, `Damaged{target, amount}`, `Triggered{source, trigger}` (`trigger`: `"deathrattle"|"battlecry"|"damaged"|...`), `Destroyed{id}` (removed in a Death Creation Step), `DeathPhase{round}`, `Drew{player, id}`, `Fatigue{player}`, `ArmorGained{player, amount}`, `ArmorLost{player, amount}` (from M4), `StatsChanged{id, attack, health}`, `SpellCast{card}`.
|
||||
|
||||
Reference in new issue
Block a user