Skip to content
60 changes: 60 additions & 0 deletions crates/engine/src/parser/oracle_nom/condition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3262,10 +3262,70 @@ fn creatures_you_controlled_left_battlefield_this_turn_ref() -> QuantityRef {
}
}

/// CR 205.3i: "a land of each basic land type" — you control a land
/// with each of the five basic land types (i.e. full domain). Expressed as
/// `BasicLandTypeCount{You} >= 5`, reusing the domain quantity so the semantics
/// match every "of each basic land type" clause, not just Coalition Victory.
fn parse_land_of_each_basic_land_type(input: &str) -> OracleResult<'_, StaticCondition> {
let (rest, _) = tag("a land of each basic land type").parse(input)?;
Ok((
rest,
make_quantity_ge(
QuantityRef::BasicLandTypeCount {
controller: ControllerRef::You,
},
5,
),
))
}

/// CR 105.2: "a creature of each color" — you control creatures
/// spanning all five colors. Expressed as
/// `DistinctColorsAmongPermanents{creatures you control} >= 5`, reusing the
/// distinct-color quantity so the semantics match every "of each color" clause.
fn parse_creature_of_each_color(input: &str) -> OracleResult<'_, StaticCondition> {
let (rest, _) = tag("a creature of each color").parse(input)?;
Ok((
rest,
make_quantity_ge(
QuantityRef::DistinctColorsAmongPermanents {
filter: TargetFilter::Typed(TypedFilter::creature().controller(ControllerRef::You)),
},
5,
),
))
}

/// CR 104.2b + CR 608.2h: "you control a land of each basic land type and a
/// creature of each color" — Coalition Victory's win condition. CR 104.2b: an
/// effect may state that a player wins the game; CR 608.2h: the game-state
/// information the condition needs is determined once, as the spell resolves.
/// Composes the two reusable "of each …" sub-combinators with `And`; each is a
/// standalone building block usable wherever its clause appears.
fn parse_control_land_each_basic_and_creature_each_color(
input: &str,
) -> OracleResult<'_, StaticCondition> {
let (rest, _) = tag("you control ").parse(input)?;
let (rest, land) = parse_land_of_each_basic_land_type(rest)?;
let (rest, _) = tag(" and ").parse(rest)?;
let (rest, creature) = parse_creature_of_each_color(rest)?;
Ok((
rest,
StaticCondition::And {
conditions: vec![land, creature],
},
))
}

/// Parse "you control" condition patterns. Exposed for rule-static parsers that
/// attach a trailing "unless you control <X>" clause as a negated condition.
pub(crate) fn parse_control_conditions(input: &str) -> OracleResult<'_, StaticCondition> {
alt((
// CR 104.2b: "you control a land of each basic land type and a creature
// of each color" (Coalition Victory) — tried before the generic
// `you control a/an [type]` arm so its "a land …" prefix isn't
// mis-classified as a bare presence check.
parse_control_land_each_basic_and_creature_each_color,
// CR 201.2 + CR 603.4: "you control N or more [type] with different names"
// → QuantityComparison(ObjectCountDistinct[Name] >= N). Tried before the
// plain ObjectCount arm so the `with different names` suffix is not
Expand Down
93 changes: 93 additions & 0 deletions crates/engine/tests/coalition_victory_win_condition_runtime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//! Runtime (engine-path) regression for Coalition Victory.
//!
//! Oracle: "You win the game if you control a land of each basic land type and a
//! creature of each color." The trailing "if …" condition was dropped, so the
//! spell parsed to a bare `Effect::WinTheGame` and casting it won the game
//! immediately regardless of the board (CR 104.2b — an effect may state a player
//! wins the game; CR 608.2h — the game-state check is made once, as the spell
//! resolves). This drives the real cast pipeline:
//! casting with an incomplete board must NOT win; casting while controlling a
//! land of each basic type and a creature of each color must win.

use engine::game::scenario::{GameScenario, P0};
use engine::types::game_state::WaitingFor;
use engine::types::mana::{ManaColor, ManaCost};
use engine::types::phase::Phase;

const ORACLE: &str =
"You win the game if you control a land of each basic land type and a creature of each color.";

fn caster_won(runner: &engine::game::scenario::GameRunner) -> bool {
matches!(runner.state().waiting_for, WaitingFor::GameOver { winner: Some(w) } if w == P0)
}

/// Condition FALSE (empty board): casting Coalition Victory must NOT win.
/// Revert-probe: on the old bare-`WinTheGame` parse this wins unconditionally.
#[test]
fn coalition_victory_does_not_win_without_the_board() {
let mut scenario = GameScenario::new();
scenario.at_phase(Phase::PreCombatMain);
let cv = scenario
.add_spell_to_hand_from_oracle(P0, "Coalition Victory", false, ORACLE)
.with_mana_cost(ManaCost::generic(0))
.id();
let mut runner = scenario.build();

runner.cast(cv).resolve();

assert!(
!caster_won(&runner),
"Coalition Victory must NOT win with no lands/creatures (its condition is unmet); \
the old bare WinTheGame parse won unconditionally"
);
}

/// Condition TRUE: controlling a land of each basic land type and a creature of
/// each color, casting Coalition Victory wins the game.
#[test]
fn coalition_victory_wins_with_full_domain_and_all_colors() {
let mut scenario = GameScenario::new();
scenario.at_phase(Phase::PreCombatMain);
let cv = scenario
.add_spell_to_hand_from_oracle(P0, "Coalition Victory", false, ORACLE)
.with_mana_cost(ManaCost::generic(0))
.id();
// A land of each basic land type (full domain).
for color in [
ManaColor::White,
ManaColor::Blue,
ManaColor::Black,
ManaColor::Red,
ManaColor::Green,
] {
scenario.add_basic_land(P0, color);
}
// A creature of each color.
let mut creature_ids = Vec::new();
for color in [
ManaColor::White,
ManaColor::Blue,
ManaColor::Black,
ManaColor::Red,
ManaColor::Green,
] {
let id = scenario.add_creature(P0, "Coalition Soldier", 1, 1).id();
creature_ids.push((id, color));
}
let mut runner = scenario.build();
// Paint each creature its color (DistinctColorsAmongPermanents reads obj.color).
for (id, color) in creature_ids {
let obj = runner.state_mut().objects.get_mut(&id).unwrap();
obj.color = vec![color];
obj.base_color = vec![color];
}

runner.cast(cv).resolve();

assert!(
caster_won(&runner),
"Coalition Victory must win the game when its controller has a land of each basic \
land type and a creature of each color; waiting_for = {:?}",
runner.state().waiting_for
);
}
Loading