- Rename course/11-break-your-engine to 13-break-your-engine and update every link and mention; start-module explains that Module 13 has no tests to start. - tools/diff-tracks.mjs (npm run diff:tracks): stages both references of a module, generates a seeded random SpellGrid level and script per seed, pipes it to the Rust and TypeScript CLIs and diffs their output; crashes on one or both sides fail. stagePlay takes a module number. - register_assembled: the TypeScript track compared only the text of an existing Assembled card, so a base re-registered with a new cost (or a different split) silently rewrote the earlier card. It now compares every field the engine reads, as Rust does (Modules 5-10). New test_05_assemble_never_rewrites_an_earlier_card. - SpellGrid CLIs: coordinates and chooser numbers are 1-9 plain digits in both tracks (Rust accepted signs, TypeScript accepted "+2", "0x2", "1e0"); Rust quotes card names verbatim instead of with Debug escapes; Rust joins the minion list with "; " instead of rewriting ", ". Two new CLI tests per track. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 lines
2.9 KiB
Rust
90 lines
2.9 KiB
Rust
// The SpellGrid command line, played from a script (the reference's own check; not a course test).
|
|
use std::cell::RefCell;
|
|
use std::io::{Cursor, Write};
|
|
use std::rc::Rc;
|
|
|
|
use engine::cli::{run, Outcome};
|
|
|
|
/// Collects what the CLI prints.
|
|
#[derive(Clone, Default)]
|
|
struct Screen(Rc<RefCell<Vec<u8>>>);
|
|
|
|
impl Write for Screen {
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
self.0.borrow_mut().extend_from_slice(buf);
|
|
Ok(buf.len())
|
|
}
|
|
fn flush(&mut self) -> std::io::Result<()> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn play(level: &str, input: &[&str]) -> (Outcome, String) {
|
|
let screen = Screen::default();
|
|
let input = Box::new(Cursor::new(input.join("\n") + "\n"));
|
|
let outcome = run(level, input, Box::new(screen.clone()));
|
|
let out = String::from_utf8(screen.0.borrow().clone()).unwrap();
|
|
(outcome, out)
|
|
}
|
|
|
|
#[test]
|
|
fn cli_wins_level_1() {
|
|
let input = ["w", "w", "c Whirlwind", "s", "s", "a", "s", "s", "d", "d"];
|
|
let (outcome, out) = play(include_str!("../levels/l1-double-tap.json"), &input);
|
|
assert_eq!(outcome, Outcome::Won);
|
|
assert!(out.contains("DEA IS DOU"));
|
|
assert!(out.contains("You win!"));
|
|
}
|
|
|
|
#[test]
|
|
fn cli_wins_level_2_with_choices_and_play() {
|
|
let input = [
|
|
"c Graft",
|
|
"Ember Moth",
|
|
"Rune Scribe",
|
|
"bottom",
|
|
"c Ember Moth (Assembled) 4 3",
|
|
"a",
|
|
"a",
|
|
];
|
|
let (outcome, out) = play(include_str!("../levels/l2-spliced-corpse.json"), &input);
|
|
assert_eq!(outcome, Outcome::Won);
|
|
assert!(out.contains("Choose the base"));
|
|
assert!(out.contains("Hand: Ember Moth (Assembled)"));
|
|
}
|
|
|
|
#[test]
|
|
fn cli_reports_errors_and_quits() {
|
|
let input = ["c Spark", "x", "q"];
|
|
let (outcome, out) = play(
|
|
include_str!("../levels/l3-simultaneous-paradox.json"),
|
|
&input,
|
|
);
|
|
assert_eq!(outcome, Outcome::Quit);
|
|
assert!(out.contains("\"Spark\" is not in the hand"));
|
|
assert!(out.contains("Commands:"));
|
|
}
|
|
|
|
// The two reference CLIs print the same text for the same input (tools/diff-tracks.mjs checks it).
|
|
#[test]
|
|
fn cli_prints_card_names_as_typed() {
|
|
let level = include_str!("../levels/l3-simultaneous-paradox.json");
|
|
let (_, out) = play(level, &["c a\"b", "q"]);
|
|
assert!(out.contains("\"a\"b\" is not in the hand"), "{out}");
|
|
}
|
|
|
|
#[test]
|
|
fn cli_numbers_are_plain_digits() {
|
|
// "-1" is not a coordinate, so it is part of the card's name.
|
|
let level = include_str!("../levels/l1-double-tap.json");
|
|
let (_, out) = play(level, &["c Whirlwind -1 0", "q"]);
|
|
assert!(
|
|
out.contains("\"Whirlwind -1 0\" is not in the hand"),
|
|
"{out}"
|
|
);
|
|
// "+2" is not a number, so the chooser takes the first option.
|
|
let level = include_str!("../levels/l2-spliced-corpse.json");
|
|
let (_, out) = play(level, &["c Graft", "+2", "1", "1", "q"]);
|
|
assert!(out.contains("Hand: Ember Moth (Assembled)"), "{out}");
|
|
}
|