use crate::{
error::{EngineError, LegalityError},
ids::PlayerId,
model::action::Action,
model::event::Event,
rules::schema::Ruleset,
state::gamestate::GameState,
engine::scripting::RhaiEngine,
};
pub struct GameEngine {
pub rules: Ruleset,
pub state: GameState,
pub cards: crate::engine::cards::CardRegistry,
pub scripting: RhaiEngine,
seed: u64,
next_choice_id: u32,
next_stack_id: u32,
}
pub struct StepResult {
pub events: Vec<Event>,
}
impl GameEngine {
pub fn new(rules: Ruleset, seed: u64, initial_state: GameState) -> Self {
let cards = crate::engine::cards::build_registry(&rules.cards);
let scripting = RhaiEngine::new();
Self { rules, state: initial_state, cards, scripting, seed, next_choice_id: 1, next_stack_id: 1 }
}
pub fn from_ruleset(rules: Ruleset, seed: u64) -> Self {
let initial = GameState::from_ruleset(&rules);
let cards = crate::engine::cards::build_registry(&rules.cards);
let scripting = RhaiEngine::new();
Self { rules, state: initial, cards, scripting, seed, next_choice_id: 1, next_stack_id: 1 }
}
pub fn legal_actions(&self, _player: PlayerId) -> Vec<Action> {
vec![Action::PassPriority]
}
pub fn next_stack_id(&mut self) -> u32 {
let id = self.next_stack_id;
self.next_stack_id += 1;
id
}
pub fn next_choice_id(&mut self) -> u32 {
let id = self.next_choice_id;
self.next_choice_id += 1;
id
}
pub fn apply_action(&mut self, player: PlayerId, action: Action) -> Result<StepResult, EngineError> {
self.validate_action(player, &action)?;
let mut events = crate::engine::reducer::apply(self, player, action)?;
self.check_game_end(&mut events);
self.auto_resolve_stack(&mut events);
self.advance_phase_if_ready(&mut events);
Ok(StepResult { events })
}
fn check_game_end(&mut self, events: &mut Vec<Event>) {
let losers: Vec<PlayerId> = self.state.players.iter()
.filter(|p| p.life <= 0)
.map(|p| p.id)
.collect();
if !losers.is_empty() {
let winner = self.state.players.iter()
.find(|p| p.life > 0)
.map(|p| p.id);
self.state.ended = Some(crate::state::gamestate::GameEnd {
winner,
reason: "Life total reached 0".to_string(),
});
events.push(Event::GameEnded { winner, reason: "Life total reached 0".to_string() });
}
}
fn auto_resolve_stack(&mut self, events: &mut Vec<Event>) {
while !self.state.stack.is_empty() && self.state.pending_choice.is_none() {
if let Some(item) = self.state.stack.pop() {
let item_id = item.id;
match crate::engine::effect_executor::execute_effect(
&item.effect,
item.source,
item.controller,
&self.state,
Some(&self.scripting),
) {
Ok(commands) => {
let effect_events = crate::engine::events::commit_commands(&mut self.state, &commands);
events.extend(effect_events);
}
Err(_err) => {
}
}
events.push(Event::StackResolved { item_id });
}
}
}
fn advance_phase_if_ready(&mut self, events: &mut Vec<Event>) {
if !self.state.stack.is_empty() || self.state.pending_choice.is_some() {
return;
}
let num_players = self.state.players.len() as u32;
if self.state.turn.priority_passes < num_players {
return;
}
self.state.turn.priority_passes = 0;
self.state.turn.priority_player = self.state.turn.active_player;
let current_phase_idx = self.rules.turn.phases.iter()
.position(|p| p.id.as_str() == self.state.turn.phase.0)
.unwrap_or(0);
let current_phase = &self.rules.turn.phases[current_phase_idx];
let current_step_idx = current_phase.steps.iter()
.position(|s| s.id.as_str() == self.state.turn.step.0)
.unwrap_or(0);
if current_step_idx + 1 < current_phase.steps.len() {
let next_step = ¤t_phase.steps[current_step_idx + 1];
let step_box: Box<str> = next_step.id.clone().into_boxed_str();
let step_static: &'static str = Box::leak(step_box);
self.state.turn.step = crate::ids::StepId(step_static);
events.push(Event::PhaseAdvanced {
phase: self.state.turn.phase.clone(),
step: self.state.turn.step.clone(),
});
return;
}
if current_phase_idx + 1 < self.rules.turn.phases.len() {
let next_phase = &self.rules.turn.phases[current_phase_idx + 1];
let phase_box: Box<str> = next_phase.id.clone().into_boxed_str();
let phase_static: &'static str = Box::leak(phase_box);
self.state.turn.phase = crate::ids::PhaseId(phase_static);
if let Some(first_step) = next_phase.steps.first() {
let step_box: Box<str> = first_step.id.clone().into_boxed_str();
let step_static: &'static str = Box::leak(step_box);
self.state.turn.step = crate::ids::StepId(step_static);
} else {
self.state.turn.step = crate::ids::StepId("start");
}
events.push(Event::PhaseAdvanced {
phase: self.state.turn.phase.clone(),
step: self.state.turn.step.clone(),
});
return;
}
if let Some(first_phase) = self.rules.turn.phases.first() {
let phase_box: Box<str> = first_phase.id.clone().into_boxed_str();
let phase_static: &'static str = Box::leak(phase_box);
self.state.turn.phase = crate::ids::PhaseId(phase_static);
if let Some(first_step) = first_phase.steps.first() {
let step_box: Box<str> = first_step.id.clone().into_boxed_str();
let step_static: &'static str = Box::leak(step_box);
self.state.turn.step = crate::ids::StepId(step_static);
} else {
self.state.turn.step = crate::ids::StepId("start");
}
self.state.turn.number += 1;
let next_player_idx = (self.state.turn.active_player.0 + 1) % num_players as u8;
self.state.turn.active_player = crate::ids::PlayerId(next_player_idx);
self.state.turn.priority_player = self.state.turn.active_player;
events.push(Event::PhaseAdvanced {
phase: self.state.turn.phase.clone(),
step: self.state.turn.step.clone(),
});
}
}
fn validate_action(&self, player: PlayerId, action: &Action) -> Result<(), LegalityError> {
crate::engine::legality::validate(self, player, action)
}
}