cardinal_kernel/engine/
core.rs1use crate::{
2 error::{EngineError, LegalityError},
3 ids::PlayerId,
4 model::action::Action,
5 model::event::Event,
6 rules::schema::Ruleset,
7 state::gamestate::GameState,
8 engine::scripting::RhaiEngine,
9};
10
11pub struct GameEngine {
12 pub rules: Ruleset,
13 pub state: GameState,
14 pub cards: crate::engine::cards::CardRegistry,
15 pub scripting: RhaiEngine,
16 seed: u64,
17 next_choice_id: u32,
18 next_stack_id: u32,
19}
20
21pub struct StepResult {
22 pub events: Vec<Event>,
23}
24
25impl GameEngine {
26 pub fn new(rules: Ruleset, seed: u64, initial_state: GameState) -> Self {
27 let cards = crate::engine::cards::build_registry(&rules.cards);
28 let scripting = RhaiEngine::new();
29 Self { rules, state: initial_state, cards, scripting, seed, next_choice_id: 1, next_stack_id: 1 }
30 }
31
32 pub fn from_ruleset(rules: Ruleset, seed: u64) -> Self {
35 let initial = GameState::from_ruleset(&rules);
36 let cards = crate::engine::cards::build_registry(&rules.cards);
37 let scripting = RhaiEngine::new();
38
39 Self { rules, state: initial, cards, scripting, seed, next_choice_id: 1, next_stack_id: 1 }
45 }
46
47 pub fn legal_actions(&self, _player: PlayerId) -> Vec<Action> {
48 vec![Action::PassPriority]
51 }
52
53 pub fn next_stack_id(&mut self) -> u32 {
55 let id = self.next_stack_id;
56 self.next_stack_id += 1;
57 id
58 }
59
60 pub fn next_choice_id(&mut self) -> u32 {
62 let id = self.next_choice_id;
63 self.next_choice_id += 1;
64 id
65 }
66
67 pub fn apply_action(&mut self, player: PlayerId, action: Action) -> Result<StepResult, EngineError> {
68 self.validate_action(player, &action)?;
70
71 let mut events = crate::engine::reducer::apply(self, player, action)?;
73
74 self.check_game_end(&mut events);
78
79 self.auto_resolve_stack(&mut events);
81
82 self.advance_phase_if_ready(&mut events);
84
85 Ok(StepResult { events })
86 }
87
88 fn check_game_end(&mut self, events: &mut Vec<Event>) {
89 let losers: Vec<PlayerId> = self.state.players.iter()
91 .filter(|p| p.life <= 0)
92 .map(|p| p.id)
93 .collect();
94
95 if !losers.is_empty() {
96 let winner = self.state.players.iter()
98 .find(|p| p.life > 0)
99 .map(|p| p.id);
100
101 self.state.ended = Some(crate::state::gamestate::GameEnd {
102 winner,
103 reason: "Life total reached 0".to_string(),
104 });
105 events.push(Event::GameEnded { winner, reason: "Life total reached 0".to_string() });
106 }
107 }
108
109 fn auto_resolve_stack(&mut self, events: &mut Vec<Event>) {
110 while !self.state.stack.is_empty() && self.state.pending_choice.is_none() {
112 if let Some(item) = self.state.stack.pop() {
113 let item_id = item.id;
114
115 match crate::engine::effect_executor::execute_effect(
117 &item.effect,
118 item.source,
119 item.controller,
120 &self.state,
121 Some(&self.scripting),
122 ) {
123 Ok(commands) => {
124 let effect_events = crate::engine::events::commit_commands(&mut self.state, &commands);
126 events.extend(effect_events);
127 }
128 Err(_err) => {
129 }
132 }
133
134 events.push(Event::StackResolved { item_id });
136 }
137 }
138 }
139
140 fn advance_phase_if_ready(&mut self, events: &mut Vec<Event>) {
141 if !self.state.stack.is_empty() || self.state.pending_choice.is_some() {
148 return;
150 }
151
152 let num_players = self.state.players.len() as u32;
153
154 if self.state.turn.priority_passes < num_players {
156 return;
158 }
159
160 self.state.turn.priority_passes = 0;
162 self.state.turn.priority_player = self.state.turn.active_player;
163
164 let current_phase_idx = self.rules.turn.phases.iter()
166 .position(|p| p.id.as_str() == self.state.turn.phase.0)
167 .unwrap_or(0);
168 let current_phase = &self.rules.turn.phases[current_phase_idx];
169
170 let current_step_idx = current_phase.steps.iter()
172 .position(|s| s.id.as_str() == self.state.turn.step.0)
173 .unwrap_or(0);
174
175 if current_step_idx + 1 < current_phase.steps.len() {
177 let next_step = ¤t_phase.steps[current_step_idx + 1];
178 let step_box: Box<str> = next_step.id.clone().into_boxed_str();
179 let step_static: &'static str = Box::leak(step_box);
180 self.state.turn.step = crate::ids::StepId(step_static);
181 events.push(Event::PhaseAdvanced {
182 phase: self.state.turn.phase.clone(),
183 step: self.state.turn.step.clone(),
184 });
185 return;
186 }
187
188 if current_phase_idx + 1 < self.rules.turn.phases.len() {
190 let next_phase = &self.rules.turn.phases[current_phase_idx + 1];
191 let phase_box: Box<str> = next_phase.id.clone().into_boxed_str();
192 let phase_static: &'static str = Box::leak(phase_box);
193 self.state.turn.phase = crate::ids::PhaseId(phase_static);
194
195 if let Some(first_step) = next_phase.steps.first() {
197 let step_box: Box<str> = first_step.id.clone().into_boxed_str();
198 let step_static: &'static str = Box::leak(step_box);
199 self.state.turn.step = crate::ids::StepId(step_static);
200 } else {
201 self.state.turn.step = crate::ids::StepId("start");
202 }
203
204 events.push(Event::PhaseAdvanced {
205 phase: self.state.turn.phase.clone(),
206 step: self.state.turn.step.clone(),
207 });
208 return;
209 }
210
211 if let Some(first_phase) = self.rules.turn.phases.first() {
213 let phase_box: Box<str> = first_phase.id.clone().into_boxed_str();
214 let phase_static: &'static str = Box::leak(phase_box);
215 self.state.turn.phase = crate::ids::PhaseId(phase_static);
216
217 if let Some(first_step) = first_phase.steps.first() {
218 let step_box: Box<str> = first_step.id.clone().into_boxed_str();
219 let step_static: &'static str = Box::leak(step_box);
220 self.state.turn.step = crate::ids::StepId(step_static);
221 } else {
222 self.state.turn.step = crate::ids::StepId("start");
223 }
224
225 self.state.turn.number += 1;
226
227 let next_player_idx = (self.state.turn.active_player.0 + 1) % num_players as u8;
229 self.state.turn.active_player = crate::ids::PlayerId(next_player_idx);
230 self.state.turn.priority_player = self.state.turn.active_player;
231
232 events.push(Event::PhaseAdvanced {
233 phase: self.state.turn.phase.clone(),
234 step: self.state.turn.step.clone(),
235 });
236 }
237 }
238
239 fn validate_action(&self, player: PlayerId, action: &Action) -> Result<(), LegalityError> {
240 crate::engine::legality::validate(self, player, action)
241 }
242}