manabrew_engine/phase/
mod.rs1pub mod extra_phase;
6pub mod extra_turn;
7pub mod phase_handler;
8pub mod phase_type;
9pub mod untap;
10
11use std::collections::HashMap;
12
13use forge_foundation::PhaseType;
14use serde::{Deserialize, Serialize};
15
16use crate::ids::{CardId, PlayerId};
17
18pub use extra_phase::ExtraPhase;
20pub use extra_turn::ExtraTurn;
21pub use phase_handler::PhaseHandler;
22
23#[derive(Debug, Clone)]
30pub enum PhaseCommand {
31 RemoveEffect(CardId),
33 RestoreController(CardId),
35 RemoveGrantedKeywords(CardId),
37 Cleanup(CardId),
39}
40
41#[derive(Debug, Clone, Default)]
47pub struct Phase {
48 #[allow(dead_code)]
49 phase_type: Option<PhaseType>,
50 at: Vec<PhaseCommand>,
52 until: Vec<PhaseCommand>,
54 until_map: HashMap<PlayerId, Vec<PhaseCommand>>,
56 until_end_map: HashMap<PlayerId, Vec<PhaseCommand>>,
58 register_map: HashMap<PlayerId, Vec<PhaseCommand>>,
60}
61
62impl Phase {
63 pub fn new(phase_type: PhaseType) -> Self {
64 Phase {
65 phase_type: Some(phase_type),
66 ..Default::default()
67 }
68 }
69
70 pub fn clear_commands(&mut self) {
73 self.at.clear();
74 self.until.clear();
75 self.until_map.clear();
76 self.until_end_map.clear();
77 self.register_map.clear();
78 }
79
80 pub fn add_at(&mut self, cmd: PhaseCommand) {
83 self.at.insert(0, cmd);
84 }
85
86 pub fn execute_at(&mut self) -> Vec<PhaseCommand> {
89 std::mem::take(&mut self.at)
90 }
91
92 pub fn add_until(&mut self, player: Option<PlayerId>, cmd: PhaseCommand) {
97 if let Some(p) = player {
98 self.until_map.entry(p).or_default().insert(0, cmd);
99 } else {
100 self.until.insert(0, cmd);
101 }
102 }
103
104 pub fn execute_until(&mut self, player: Option<PlayerId>) -> Vec<PhaseCommand> {
109 if let Some(p) = player {
110 self.until_map.remove(&p).unwrap_or_default()
111 } else {
112 std::mem::take(&mut self.until)
113 }
114 }
115
116 pub fn register_until_end(&mut self, player: PlayerId, cmd: PhaseCommand) {
119 self.register_map.entry(player).or_default().insert(0, cmd);
120 }
121
122 pub fn add_until_end(&mut self, player: PlayerId, cmd: PhaseCommand) {
125 self.until_end_map.entry(player).or_default().insert(0, cmd);
126 }
127
128 pub fn register_until_end_command(&mut self, player: PlayerId) {
131 if let Some(cmds) = self.register_map.remove(&player) {
132 self.until_end_map.insert(player, cmds);
133 }
134 }
135
136 pub fn execute_until_end_of_phase(&mut self, player: PlayerId) -> Vec<PhaseCommand> {
139 self.until_end_map.remove(&player).unwrap_or_default()
140 }
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct TurnState {
146 pub turn_number: u32,
147 pub active_player: PlayerId,
148 pub phase: PhaseType,
149 pub priority_player: PlayerId,
150 pub num_players: u32,
151
152 pub combat_attackers_declared: bool,
154 pub combat_blockers_declared: bool,
155 pub combat_block_assignments: Vec<(CardId, CardId)>,
157
158 pub drawn_for_turn: bool,
160}
161
162impl TurnState {
163 pub fn new(active_player: PlayerId, num_players: u32) -> Self {
164 TurnState {
165 turn_number: 1,
166 active_player,
167 phase: PhaseType::Untap,
168 priority_player: active_player,
169 num_players,
170 combat_attackers_declared: false,
171 combat_blockers_declared: false,
172 combat_block_assignments: vec![],
173 drawn_for_turn: false,
174 }
175 }
176
177 pub fn advance_phase(&mut self) -> bool {
179 let next = self.phase.next();
180 let turn_ended = next == PhaseType::Untap && self.phase == PhaseType::Cleanup;
181 self.phase = next;
182
183 if turn_ended {
184 self.turn_number += 1;
185 self.combat_attackers_declared = false;
186 self.combat_blockers_declared = false;
187 self.combat_block_assignments.clear();
188 self.drawn_for_turn = false;
189 }
190
191 if self.phase == PhaseType::CombatBegin {
193 self.combat_attackers_declared = false;
194 self.combat_blockers_declared = false;
195 self.combat_block_assignments.clear();
196 }
197
198 turn_ended
199 }
200
201 pub fn next_player_turn(&mut self, player_order: &[PlayerId]) {
203 if let Some(pos) = player_order.iter().position(|&p| p == self.active_player) {
204 let next = (pos + 1) % player_order.len();
205 self.active_player = player_order[next];
206 self.priority_player = self.active_player;
207 self.turn_number += 1;
208 self.combat_attackers_declared = false;
209 self.combat_blockers_declared = false;
210 self.combat_block_assignments.clear();
211 self.drawn_for_turn = false;
212 }
213 }
214
215 pub fn advance_turn(
220 &mut self,
221 extra_turns: &mut std::collections::VecDeque<ExtraTurn>,
222 player_order: &[PlayerId],
223 ) -> Option<(PlayerId, bool)> {
224 if let Some(extra_turn) = extra_turns.pop_front() {
225 let player = extra_turn.player;
226 self.active_player = player;
227 self.priority_player = player;
228 self.turn_number += 1;
229 self.combat_attackers_declared = false;
230 self.combat_blockers_declared = false;
231 self.combat_block_assignments.clear();
232 self.drawn_for_turn = false;
233 if extra_turn.skip_untap {
234 Some((player, true))
235 } else {
236 None
237 }
238 } else {
239 self.next_player_turn(player_order);
240 None
241 }
242 }
243
244 pub fn is_main_phase(&self) -> bool {
245 self.phase.is_main()
246 }
247
248 pub fn is_combat(&self) -> bool {
249 self.phase.is_combat()
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn advance_phases() {
259 let mut ts = TurnState::new(PlayerId(0), 2);
260 assert_eq!(ts.phase, PhaseType::Untap);
261
262 ts.advance_phase();
263 assert_eq!(ts.phase, PhaseType::Upkeep);
264
265 ts.advance_phase();
266 assert_eq!(ts.phase, PhaseType::Draw);
267 }
268
269 #[test]
270 fn turn_wraps() {
271 let mut ts = TurnState::new(PlayerId(0), 2);
272 assert_eq!(ts.turn_number, 1);
273
274 loop {
276 let ended = ts.advance_phase();
277 if ended {
278 break;
279 }
280 }
281 assert_eq!(ts.turn_number, 2);
282 assert_eq!(ts.phase, PhaseType::Untap);
283 }
284
285 #[test]
286 fn phase_commands() {
287 let mut phase = Phase::new(PhaseType::Upkeep);
288 phase.add_at(PhaseCommand::Cleanup(CardId(1)));
289 phase.add_until(None, PhaseCommand::RemoveEffect(CardId(2)));
290
291 let at_cmds = phase.execute_at();
292 assert_eq!(at_cmds.len(), 1);
293
294 let until_cmds = phase.execute_until(None);
295 assert_eq!(until_cmds.len(), 1);
296
297 assert!(phase.execute_at().is_empty());
299 assert!(phase.execute_until(None).is_empty());
300 }
301
302 #[test]
303 fn phase_per_player_commands() {
304 let mut phase = Phase::new(PhaseType::Cleanup);
305 let p0 = PlayerId(0);
306 let p1 = PlayerId(1);
307
308 phase.add_until(Some(p0), PhaseCommand::Cleanup(CardId(1)));
309 phase.add_until(Some(p1), PhaseCommand::Cleanup(CardId(2)));
310
311 let p0_cmds = phase.execute_until(Some(p0));
312 assert_eq!(p0_cmds.len(), 1);
313
314 let p1_cmds = phase.execute_until(Some(p1));
316 assert_eq!(p1_cmds.len(), 1);
317 }
318}