manabrew_engine/phase/
phase_handler.rs1use std::collections::HashMap;
9
10use forge_foundation::PhaseType;
11
12use crate::ids::PlayerId;
13
14use super::extra_phase::ExtraPhase;
15use super::extra_turn::ExtraTurn;
16
17#[derive(Debug, Clone)]
20pub struct PhaseHandler {
21 phase: Option<PhaseType>,
22 turn: i32,
23
24 extra_turns: Vec<ExtraTurn>,
26 extra_phases: HashMap<PhaseType, Vec<ExtraPhase>>,
28
29 pub n_upkeeps_this_turn: i32,
30 pub n_upkeeps_this_game: i32,
31 pub n_combats_this_turn: i32,
32 pub n_mains_this_turn: i32,
33 pub n_end_of_turns_this_turn: i32,
34 pub planar_dice_special_action_this_turn: i32,
35
36 player_turn: Option<PlayerId>,
37 player_previous_turn: Option<PlayerId>,
38
39 priority_player: Option<PlayerId>,
40 first_priority: Option<PlayerId>,
41
42 pub skip_damage_steps: bool,
43 pub repeat_cleanup: bool,
44 pub give_priority_to_player: bool,
45}
46
47impl PhaseHandler {
48 pub fn new() -> Self {
49 PhaseHandler {
50 phase: None,
51 turn: 0,
52 extra_turns: Vec::new(),
53 extra_phases: HashMap::new(),
54 n_upkeeps_this_turn: 0,
55 n_upkeeps_this_game: 0,
56 n_combats_this_turn: 0,
57 n_mains_this_turn: 0,
58 n_end_of_turns_this_turn: 0,
59 planar_dice_special_action_this_turn: 0,
60 player_turn: None,
61 player_previous_turn: None,
62 priority_player: None,
63 first_priority: None,
64 skip_damage_steps: false,
65 repeat_cleanup: false,
66 give_priority_to_player: false,
67 }
68 }
69
70 pub fn get_phase(&self) -> Option<PhaseType> {
71 self.phase
72 }
73
74 pub fn set_phase(&mut self, phase: PhaseType) {
75 self.phase = Some(phase);
76 }
77
78 pub fn get_turn(&self) -> i32 {
79 self.turn
80 }
81
82 pub fn is_player_turn(&self, player: PlayerId) -> bool {
83 self.player_turn == Some(player)
84 }
85
86 pub fn get_player_turn(&self) -> Option<PlayerId> {
87 self.player_turn
88 }
89
90 pub fn set_player_turn(&mut self, player: PlayerId) {
91 self.player_turn = Some(player);
92 self.set_priority(player);
93 }
94
95 pub fn get_previous_player_turn(&self) -> Option<PlayerId> {
96 self.player_previous_turn
97 }
98
99 pub fn get_priority_player(&self) -> Option<PlayerId> {
100 self.priority_player
101 }
102
103 pub fn set_priority(&mut self, player: PlayerId) {
104 self.first_priority = Some(player);
105 self.priority_player = Some(player);
106 }
107
108 pub fn reset_priority(&mut self) {
109 if let Some(player) = self.player_turn {
110 self.set_priority(player);
111 }
112 }
113
114 pub fn is_first_combat(&self) -> bool {
115 self.n_combats_this_turn == 1
116 }
117
118 pub fn get_num_combat(&self) -> i32 {
119 self.n_combats_this_turn
120 }
121
122 pub fn get_num_upkeep(&self) -> i32 {
123 self.n_upkeeps_this_turn
124 }
125
126 pub fn is_first_upkeep(&self) -> bool {
127 self.phase == Some(PhaseType::Upkeep) && self.n_upkeeps_this_turn == 0
128 }
129
130 pub fn is_first_upkeep_this_game(&self) -> bool {
131 self.phase == Some(PhaseType::Upkeep) && self.n_upkeeps_this_game == 0
132 }
133
134 pub fn get_num_main(&self) -> i32 {
135 self.n_mains_this_turn
136 }
137
138 pub fn before_first_post_combat_main_end(&self) -> bool {
139 self.n_mains_this_turn
140 <= if self.phase == Some(PhaseType::Main2) {
141 2
142 } else {
143 1
144 }
145 }
146
147 pub fn skipped_declare_blockers(&self) -> bool {
148 self.skip_damage_steps
149 }
150
151 pub fn get_num_end_of_turn(&self) -> i32 {
152 self.n_end_of_turns_this_turn
153 }
154
155 pub fn is(&self, phase: PhaseType) -> bool {
156 self.phase == Some(phase)
157 }
158
159 pub fn is_phase_player(&self, phase: PhaseType, player: PlayerId) -> bool {
160 self.phase == Some(phase) && self.player_turn == Some(player)
161 }
162
163 pub fn add_extra_turn(&mut self, player: PlayerId, player_order: &[PlayerId]) -> &ExtraTurn {
166 if self.extra_turns.is_empty() {
168 if let Some(current) = self.player_turn {
169 let next = next_player_after(current, player_order);
170 self.extra_turns.push(ExtraTurn::new(next));
171 }
172 }
173 self.extra_turns.push(ExtraTurn::new(player));
174 self.extra_turns.last().unwrap()
175 }
176
177 pub fn add_extra_phase(
180 &mut self,
181 after_phase: PhaseType,
182 extra_phase_list: &[PhaseType],
183 next_phase: PhaseType,
184 ) {
185 for (i, &extra) in extra_phase_list.iter().enumerate() {
186 let entry = self.extra_phases.entry(extra).or_default();
187 if i < extra_phase_list.len() - 1 {
188 entry.push(ExtraPhase::new(extra_phase_list[i + 1]));
189 } else {
190 entry.push(ExtraPhase::new(next_phase));
191 }
192 }
193 let after_entry = self.extra_phases.entry(after_phase).or_default();
194 after_entry.push(ExtraPhase::new(extra_phase_list[0]));
195 }
196
197 pub fn get_next_turn(&self, player_order: &[PlayerId]) -> Option<PlayerId> {
198 if let Some(last) = self.extra_turns.last() {
199 Some(last.get_player())
200 } else {
201 self.player_turn.map(|p| next_player_after(p, player_order))
202 }
203 }
204
205 pub fn restart(&mut self) {
208 self.extra_phases.clear();
209 self.extra_turns.clear();
210 self.turn = 0;
211 }
212
213 pub fn on_stack_resolved(&mut self) {
216 self.give_priority_to_player = true;
217 }
218
219 pub fn get_planar_dice_special_action_this_turn(&self) -> i32 {
220 self.planar_dice_special_action_this_turn
221 }
222
223 pub fn inc_planar_dice_special_action_this_turn(&mut self) {
224 self.planar_dice_special_action_this_turn += 1;
225 }
226
227 pub fn get_extra_turn_for_player(&self, player: PlayerId) -> i32 {
230 if self.extra_turns.len() < 2 {
231 return 0;
232 }
233 let mut count = 0;
234 for et in self.extra_turns.iter().skip(1) {
236 if et.get_player() != player {
237 break;
238 }
239 count += 1;
240 }
241 count
242 }
243
244 pub fn handle_next_turn(&mut self, player_order: &[PlayerId]) -> PlayerId {
247 self.player_previous_turn = self.player_turn;
248
249 let next = if let Some(extra) = self.extra_turns.pop() {
250 let player = extra.get_player();
251 if extra.is_skip_untap() {
253 }
255 player
256 } else {
257 self.player_turn
258 .map(|p| next_player_after(p, player_order))
259 .unwrap_or(player_order[0])
260 };
261
262 self.turn += 1;
263 self.extra_phases.clear();
264 self.n_upkeeps_this_turn = 0;
265 self.n_combats_this_turn = 0;
266 self.n_mains_this_turn = 0;
267 self.n_end_of_turns_this_turn = 0;
268 self.planar_dice_special_action_this_turn = 0;
269
270 self.set_player_turn(next);
271 next
272 }
273
274 pub fn end_combat_phase_by_effect(&mut self) {
275 self.phase = Some(PhaseType::CombatEnd);
276 }
277
278 pub fn end_turn_by_effect(&mut self) {
279 self.extra_phases.clear();
280 self.phase = Some(PhaseType::Cleanup);
281 }
282
283 pub fn debug_print_state(&self, has_priority: bool) -> String {
284 format!(
285 "{:?}'s {:?} [{}P] {:?}",
286 self.player_turn,
287 self.phase,
288 if has_priority { "+" } else { "-" },
289 self.priority_player
290 )
291 }
292
293 pub fn in_combat(&self) -> bool {
296 self.phase.is_some_and(|p| p.is_combat())
297 }
298
299 pub fn end_combat(&mut self) {
302 self.skip_damage_steps = false;
305 }
306
307 pub fn setup_first_turn(&mut self, goes_first: PlayerId) {
310 self.set_player_turn(goes_first);
311 self.set_phase(PhaseType::Untap);
312 self.turn = 1;
313 self.give_priority_to_player = false;
314 }
315
316 pub fn start_first_turn(&mut self, goes_first: PlayerId) {
320 self.setup_first_turn(goes_first);
321 }
322
323 pub fn dev_advance_to_phase(&mut self, target_phase: PhaseType) -> bool {
326 while let Some(current) = self.phase {
327 if !current.is_before(target_phase) {
328 break;
329 }
330 self.phase = Some(current.next());
331 }
332 true
333 }
334
335 pub fn dev_mode_set(
338 &mut self,
339 phase: Option<PhaseType>,
340 player: Option<PlayerId>,
341 end_combat: bool,
342 cturn: i32,
343 ) {
344 if let Some(p) = phase {
345 self.set_phase(p);
346 }
347 if let Some(pl) = player {
348 self.set_player_turn(pl);
349 }
350 self.turn = cturn;
351 if end_combat {
352 self.end_combat();
353 }
354 }
355}
356
357impl Default for PhaseHandler {
358 fn default() -> Self {
359 Self::new()
360 }
361}
362
363fn next_player_after(current: PlayerId, player_order: &[PlayerId]) -> PlayerId {
365 if let Some(pos) = player_order.iter().position(|&p| p == current) {
366 let next = (pos + 1) % player_order.len();
367 player_order[next]
368 } else {
369 player_order[0]
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn new_phase_handler() {
379 let ph = PhaseHandler::new();
380 assert_eq!(ph.get_phase(), None);
381 assert_eq!(ph.get_turn(), 0);
382 }
383
384 #[test]
385 fn set_phase() {
386 let mut ph = PhaseHandler::new();
387 ph.set_phase(PhaseType::Upkeep);
388 assert_eq!(ph.get_phase(), Some(PhaseType::Upkeep));
389 assert!(ph.is(PhaseType::Upkeep));
390 }
391
392 #[test]
393 fn extra_turn_count() {
394 let mut ph = PhaseHandler::new();
395 let p0 = PlayerId(0);
396 let p1 = PlayerId(1);
397 ph.set_player_turn(p0);
398 ph.add_extra_turn(p0, &[p0, p1]);
399 assert_eq!(ph.get_extra_turn_for_player(p0), 1);
400 assert_eq!(ph.get_extra_turn_for_player(p1), 0);
401 }
402}