cardinal_kernel/engine/
legality.rs1use crate::{
2 engine::core::GameEngine,
3 ids::PlayerId,
4 model::action::Action,
5 error::CardinalError,
6};
7
8pub fn validate(engine: &GameEngine, player: PlayerId, action: &Action) -> Result<(), CardinalError> {
16 if engine.state.ended.is_some() {
18 return Err(CardinalError("Game has ended".to_string()));
19 }
20
21 match action {
23 Action::PassPriority => {
24 if player != engine.state.turn.priority_player {
26 return Err(CardinalError(format!(
27 "Only priority player ({:?}) can pass priority",
28 engine.state.turn.priority_player
29 )));
30 }
31 Ok(())
32 }
33 Action::Concede => {
34 Ok(())
36 }
37 Action::PlayCard { card, from } => {
38 if player != engine.state.turn.active_player {
40 return Err(CardinalError(format!(
41 "Only active player ({:?}) can take this action",
42 engine.state.turn.active_player
43 )));
44 }
45
46 let current_phase = engine.rules.turn.phases.iter()
48 .find(|p| p.id.as_str() == engine.state.turn.phase.0)
49 .ok_or_else(|| CardinalError("Invalid phase".to_string()))?;
50
51 if !current_phase.allow_actions {
53 return Err(CardinalError(format!(
54 "Current phase '{}' does not allow card plays",
55 current_phase.name
56 )));
57 }
58
59 let zone = engine.state.zones.iter()
61 .find(|z| z.id == *from)
62 .ok_or_else(|| CardinalError("Source zone does not exist".to_string()))?;
63
64 if let Some(owner) = zone.owner {
65 if owner != player {
66 return Err(CardinalError("Cannot play cards from opponent's zones".to_string()));
67 }
68 }
69
70 if !zone.cards.contains(card) {
72 return Err(CardinalError("Card is not in the specified source zone".to_string()));
73 }
74
75 if let Some(action_def) = engine.rules.actions.iter()
77 .find(|a| a.id == "play_card")
78 {
79 if action_def.requires_empty_stack && !engine.state.stack.is_empty() {
80 return Err(CardinalError(
81 "Cannot play card: stack is not empty and action requires empty stack"
82 .to_string(),
83 ));
84 }
85 }
86
87 Ok(())
88 }
89 Action::ChooseTarget { choice_id, target: _ } => {
90 match &engine.state.pending_choice {
92 Some(choice) if choice.id == *choice_id => Ok(()),
93 Some(choice) => Err(CardinalError(format!(
94 "Choice ID mismatch: expected {}, got {}",
95 choice.id, choice_id
96 ))),
97 None => Err(CardinalError("No pending choice to respond to".to_string())),
98 }
99 }
100 }
101}