Cards become JSON data in course/cards (one file per card, validated against card.schema.json and the catalog); the M5 references load them via registerCards and interpret effects, replacing content closures. Adds assemble(base, donor, half), the assemble/buffHand effects with an injected chooser, Fleshshape as the data-only Expansion Drop, and stages course/cards in verify-references and start-module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
2.6 KiB
Rust
81 lines
2.6 KiB
Rust
// Module 5 Expansion Drop: Fleshshape. Matches expansion/README.md.
|
|
use std::cell::RefCell;
|
|
use std::rc::Rc;
|
|
|
|
use engine::harness::*;
|
|
|
|
/// Names of the minion files in `cards/` with a trigger clause listening for `on`, sorted.
|
|
fn minions_with_trigger(on: &str) -> Vec<String> {
|
|
let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/cards");
|
|
let mut names = Vec::new();
|
|
for entry in std::fs::read_dir(dir).unwrap_or_else(|e| panic!("cannot read {dir}: {e}")) {
|
|
let path = entry.unwrap().path();
|
|
if path.extension().and_then(|x| x.to_str()) != Some("json") {
|
|
continue;
|
|
}
|
|
let card: serde_json::Value =
|
|
serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
|
|
let has_trigger = card["text"]
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.any(|c| c["kind"] == "trigger" && c["on"] == on);
|
|
if card["type"] == "minion" && has_trigger {
|
|
names.push(card["name"].as_str().unwrap().to_string());
|
|
}
|
|
}
|
|
names.sort();
|
|
names
|
|
}
|
|
|
|
#[test]
|
|
fn test_05x_fleshshape_pool_filter() {
|
|
let mut g = new_game(GameOpts::default());
|
|
let calls: Rc<RefCell<Vec<(String, Vec<String>)>>> = Rc::default();
|
|
let record = Rc::clone(&calls);
|
|
g.set_chooser(move |kind: &str, options: &[String]| {
|
|
record
|
|
.borrow_mut()
|
|
.push((kind.to_string(), options.to_vec()));
|
|
match kind {
|
|
"base" => "Loot Hoarder",
|
|
"donor" => "Novice Engineer",
|
|
_ => "top",
|
|
}
|
|
.to_string()
|
|
});
|
|
g.clear_log();
|
|
|
|
g.cast("Fleshshape", 0, None);
|
|
|
|
let calls = calls.borrow().clone();
|
|
let kinds: Vec<&str> = calls.iter().map(|(k, _)| k.as_str()).collect();
|
|
assert_eq!(kinds, vec!["base", "donor", "half"]);
|
|
let bases = minions_with_trigger("deathrattle");
|
|
assert!(bases.contains(&"Loot Hoarder".to_string()));
|
|
assert!(!bases.contains(&"Novice Engineer".to_string()));
|
|
assert_eq!(calls[0].1, bases);
|
|
assert!(calls[1].1.contains(&"Novice Engineer".to_string()));
|
|
|
|
let hand = g.hand(0);
|
|
assert_eq!(hand.len(), 1);
|
|
let a = hand[0];
|
|
assert_eq!(g.name_of(a), "Loot Hoarder (Assembled)");
|
|
assert_eq!(g.cost(a), 2);
|
|
assert_eq!(g.attack(a), 4);
|
|
assert_eq!(g.health(a), 3);
|
|
assert_eq!(
|
|
g.log(),
|
|
vec![
|
|
GameEvent::SpellCast {
|
|
card: "Fleshshape".to_string()
|
|
},
|
|
GameEvent::StatsChanged {
|
|
id: a,
|
|
attack: 4,
|
|
health: 3
|
|
},
|
|
]
|
|
);
|
|
}
|