cardinal_kernel/engine/
reducer.rs1use crate::{
2 engine::core::GameEngine,
3 ids::PlayerId,
4 model::action::Action,
5 model::event::Event,
6 model::command::Command,
7 error::CardinalError,
8};
9
10pub fn apply(engine: &mut GameEngine, player: PlayerId, action: Action) -> Result<Vec<Event>, CardinalError> {
13 match action {
14 Action::PassPriority => {
15 if player != engine.state.turn.priority_player {
17 return Err(CardinalError("Only the priority player can pass priority".to_string()));
18 }
19
20 engine.state.turn.priority_passes += 1;
22
23 let num_players = engine.state.players.len() as u32;
25 let all_passed = engine.state.turn.priority_passes >= num_players;
26
27 if !all_passed {
29 let next_priority_idx = (player.0 + 1) % num_players as u8;
30 engine.state.turn.priority_player = crate::ids::PlayerId(next_priority_idx);
31 }
32
33 Ok(vec![Event::PriorityPassed { by: player }])
34 }
35 Action::Concede => {
36 let winner = engine.state.players.iter()
38 .find(|p| p.id != player)
39 .map(|p| p.id);
40
41 engine.state.ended = Some(crate::state::gamestate::GameEnd {
43 winner,
44 reason: format!("Player {:?} conceded", player),
45 });
46
47 Ok(vec![Event::GameEnded {
48 winner,
49 reason: format!("Player {:?} conceded", player),
50 }])
51 }
52 Action::PlayCard { card, from } => {
53 let action_def = engine.rules.actions.iter()
55 .find(|a| a.id == "play_card")
56 .ok_or_else(|| CardinalError("play_card action not defined in rules".to_string()))?;
57
58 let target_zone_str = action_def.target_zone.as_ref()
59 .ok_or_else(|| CardinalError("play_card action has no target_zone defined".to_string()))?;
60
61 let target_zone_id = if let Some(zone_def) = engine.rules.zones.iter()
63 .find(|z| z.id == *target_zone_str)
64 {
65 match zone_def.owner_scope {
66 crate::rules::schema::ZoneOwnerScope::Player => {
67 format!("{}@{}", target_zone_str, player.0)
68 }
69 crate::rules::schema::ZoneOwnerScope::Shared => {
70 target_zone_str.clone()
71 }
72 }
73 } else {
74 return Err(CardinalError(format!("target zone '{}' not found in rules", target_zone_str)));
75 };
76
77 let target_zone_box: Box<str> = target_zone_id.into_boxed_str();
78 let target_zone = crate::ids::ZoneId(Box::leak(target_zone_box));
79
80 let commands = vec![
82 Command::MoveCard { card, from, to: target_zone },
83 ];
84
85 let mut events = crate::engine::events::commit_commands(&mut engine.state, &commands);
87
88 let card_played_event = Event::CardPlayed { player, card };
90 events.push(card_played_event.clone());
91
92 let trigger_commands = crate::engine::triggers::evaluate_triggers(engine, &card_played_event);
94 let trigger_events = crate::engine::events::commit_commands(&mut engine.state, &trigger_commands);
95 events.extend(trigger_events);
96
97 let card_moved_events: Vec<Event> = events.iter()
99 .filter(|e| matches!(e, Event::CardMoved { .. }))
100 .cloned()
101 .collect();
102
103 for event in card_moved_events {
104 let trigger_commands = crate::engine::triggers::evaluate_triggers(engine, &event);
105 let trigger_events = crate::engine::events::commit_commands(&mut engine.state, &trigger_commands);
106 events.extend(trigger_events);
107 }
108
109 Ok(events)
110 }
111 Action::ChooseTarget { choice_id: _, target: _ } => {
112 engine.state.pending_choice = None;
116
117 Ok(vec![])
123 }
124 }
125}