1use std::collections::HashSet;
2
3use forge_foundation::ZoneType;
4
5use crate::card::CardInstance;
6use crate::event::RunParams;
7use crate::game::GameState;
8use crate::ids::{CardId, PlayerId};
9use crate::player::player_factory_util::{add_trigger_ability, new_player_effect_card};
10use crate::player::{GameLossReason, PlayerOutcome, RegisteredPlayer};
11use crate::replacement::replacement_handler::{
12 apply_replacements, apply_replacements_with_agents, ReplacementEvent,
13};
14use crate::replacement::replacement_result::ReplacementResult;
15use crate::trigger::handler::TriggerHandler;
16use crate::trigger::TriggerType;
17
18impl GameState {
19 pub fn new_from_registered_players(players: &[RegisteredPlayer]) -> Self {
20 let names: Vec<&str> = players.iter().map(|p| p.name.as_str()).collect();
21 let starting_life = players.first().map(|p| p.starting_life).unwrap_or(20);
22 let mut game = Self::new(&names, starting_life);
23 for (idx, rp) in players.iter().enumerate() {
24 let pid = PlayerId(idx as u32);
25 let player = game.player_mut(pid);
26 player.name = rp.name.clone();
27 player.id = rp.id.unwrap_or(pid);
28 player.starting_life = rp.starting_life;
29 player.life = rp.starting_life;
30 player.starting_hand_size = rp.starting_hand;
31 player.max_hand_size = rp.max_hand_size;
32 player.statistics.notify_opening_hand_size(rp.starting_hand);
33 player.mana_shards = rp.mana_shards;
34 player.team_number = rp.team_number;
35 player.commander_damage_enabled = rp.commander_damage_enabled;
36 }
37 game
38 }
39
40 pub fn initialize_registered_player_cards(
41 &mut self,
42 player: PlayerId,
43 registered: &RegisteredPlayer,
44 cards: Vec<(CardInstance, ZoneType)>,
45 trigger_handler: Option<&mut TriggerHandler>,
46 ) {
47 for (mut card, zone) in cards {
48 card.set_owner(player);
49 card.set_controller(player);
50 let card_id = self.create_card(card);
51 self.move_card(card_id, zone, player);
52 }
53 self.initialize_player_commanders_from_registered(player, registered, trigger_handler);
54 }
55
56 fn remove_player_effect_card(&mut self, player: PlayerId, effect_id: Option<CardId>) {
57 let Some(effect_id) = effect_id else {
58 return;
59 };
60 let zone = self.card(effect_id).zone;
61 if zone != ZoneType::None {
62 self.remove_card_from_zone(zone, player, effect_id);
63 }
64 self.card_mut(effect_id).zone = ZoneType::None;
65 }
66
67 fn ensure_speed_effect(&mut self, player: PlayerId) -> Option<CardId> {
68 if self.player(player).speed == 0 {
69 if let Some(effect_id) = self.player(player).speed_effect_card {
70 self.remove_player_effect_card(player, Some(effect_id));
71 self.player_mut(player).speed_effect_card = None;
72 }
73 return None;
74 }
75 if let Some(effect_id) = self.player(player).speed_effect_card {
76 return Some(effect_id);
77 }
78
79 let mut effect = new_player_effect_card(player, "Start Your Engines!", None);
80 add_trigger_ability(
81 &mut effect,
82 "Mode$ LifeLostAll | ValidPlayer$ Opponent | TriggerZones$ Command | ActivationLimit$ 1 | PlayerTurn$ True | CheckSVar$ Count$YourSpeed | SVarCompare$ LT4 | Execute$ SpeedUp | TriggerDescription$ Whenever one or more opponents lose life during your turn, if your speed is less than 4, your speed increases by 1. This ability triggers only once each turn.",
83 [("SpeedUp", "DB$ ChangeSpeed")],
84 );
85
86 let effect_id = self.create_card(effect);
87 self.move_card(effect_id, ZoneType::Command, player);
88 self.player_mut(player).speed_effect_card = Some(effect_id);
89 Some(effect_id)
90 }
91
92 fn ensure_monarch_effect(&mut self, player: PlayerId) -> CardId {
93 if let Some(effect_id) = self.player(player).monarch_effect_card {
94 return effect_id;
95 }
96 let mut effect = new_player_effect_card(player, "The Monarch", None);
97 add_trigger_ability(
98 &mut effect,
99 "Mode$ Phase | Phase$ End of Turn | TriggerZones$ Command | ValidPlayer$ You | Execute$ DrawMonarchCard | TriggerDescription$ At the beginning of your end step, draw a card.",
100 [("DrawMonarchCard", "DB$ Draw | Defined$ You")],
101 );
102 add_trigger_ability(
103 &mut effect,
104 "Mode$ DamageDone | ValidSource$ Creature | ValidTarget$ You | CombatDamage$ True | TriggerZones$ Command | Execute$ GainMonarch | TriggerDescription$ Whenever a creature deals combat damage to you, its controller becomes the monarch.",
105 [(
106 "GainMonarch",
107 "DB$ BecomeMonarch | Defined$ TriggeredSourceController",
108 )],
109 );
110 let effect_id = self.create_card(effect);
111 self.move_card(effect_id, ZoneType::Command, player);
112 self.player_mut(player).monarch_effect_card = Some(effect_id);
113 effect_id
114 }
115
116 fn remove_monarch_effect(&mut self, player: PlayerId) {
117 let effect_id = self.player(player).monarch_effect_card;
118 self.remove_player_effect_card(player, effect_id);
119 self.player_mut(player).monarch_effect_card = None;
120 }
121
122 fn ensure_initiative_effect(&mut self, player: PlayerId) -> CardId {
123 if let Some(effect_id) = self.player(player).initiative_effect_card {
124 return effect_id;
125 }
126 let mut effect = new_player_effect_card(player, "The Initiative", None);
127 add_trigger_ability(
128 &mut effect,
129 "Mode$ DamageDoneOnceByController | ValidSource$ Player | ValidTarget$ You | CombatDamage$ True | TriggerZones$ Command | Execute$ TakeTheInitiative | TriggerDescription$ Whenever one or more creatures a player controls deal combat damage to you, that player takes the initiative.",
130 [(
131 "TakeTheInitiative",
132 "DB$ TakeInitiative | Defined$ TriggeredSource",
133 )],
134 );
135 add_trigger_ability(
136 &mut effect,
137 "Mode$ Phase | Phase$ Upkeep | TriggerZones$ Command | ValidPlayer$ You | Execute$ VentureUndercity | TriggerDescription$ At the beginning of your upkeep, venture into Undercity. | Secondary$ True",
138 [("VentureUndercity", "DB$ Venture | Dungeon$ Undercity")],
139 );
140 add_trigger_ability(
141 &mut effect,
142 "Mode$ TakesInitiative | ValidPlayer$ You | TriggerZones$ Command | Execute$ VentureUndercity | TriggerDescription$ Whenever you take the initiative, venture into Undercity.",
143 [("VentureUndercity", "DB$ Venture | Dungeon$ Undercity")],
144 );
145 let effect_id = self.create_card(effect);
146 self.move_card(effect_id, ZoneType::Command, player);
147 self.player_mut(player).initiative_effect_card = Some(effect_id);
148 effect_id
149 }
150
151 fn remove_initiative_effect(&mut self, player: PlayerId) {
152 let effect_id = self.player(player).initiative_effect_card;
153 self.remove_player_effect_card(player, effect_id);
154 self.player_mut(player).initiative_effect_card = None;
155 }
156
157 fn ensure_blessing_effect(&mut self, player: PlayerId) -> Option<CardId> {
158 if !self.player(player).has_city_blessing {
159 let effect_id = self.player(player).blessing_effect_card;
160 self.remove_player_effect_card(player, effect_id);
161 self.player_mut(player).blessing_effect_card = None;
162 return None;
163 }
164 if let Some(effect_id) = self.player(player).blessing_effect_card {
165 return Some(effect_id);
166 }
167 let effect = new_player_effect_card(player, "City's Blessing", None);
168 let effect_id = self.create_card(effect);
169 self.move_card(effect_id, ZoneType::Command, player);
170 self.player_mut(player).blessing_effect_card = Some(effect_id);
171 Some(effect_id)
172 }
173
174 fn ensure_radiation_effect(&mut self, player: PlayerId) -> Option<CardId> {
175 if self.player(player).radiation_counters <= 0 {
176 let effect_id = self.player(player).radiation_effect_card;
177 self.remove_player_effect_card(player, effect_id);
178 self.player_mut(player).radiation_effect_card = None;
179 return None;
180 }
181 if let Some(effect_id) = self.player(player).radiation_effect_card {
182 return Some(effect_id);
183 }
184 let mut effect = new_player_effect_card(player, "Radiation", None);
185 add_trigger_ability(
186 &mut effect,
187 "Mode$ Phase | Phase$ Main1 | ValidPlayer$ You | TriggerZones$ Command | Execute$ ProcessRadiation | TriggerDescription$ At the beginning of your precombat main phase, if you have any rad counters, mill that many cards. For each nonland card milled this way, you lose 1 life and a rad counter.",
188 [("ProcessRadiation", "DB$ InternalRadiation")],
189 );
190 let effect_id = self.create_card(effect);
191 self.move_card(effect_id, ZoneType::Command, player);
192 self.player_mut(player).radiation_effect_card = Some(effect_id);
193 Some(effect_id)
194 }
195
196 pub fn player_register_radiation_effect(
197 &mut self,
198 player: PlayerId,
199 trigger_handler: &mut TriggerHandler,
200 ) {
201 if let Some(effect_id) = self.ensure_radiation_effect(player) {
202 trigger_handler.register_active_trigger(self, effect_id);
203 }
204 }
205
206 pub fn player_cleanup_turn_state(&mut self, player: PlayerId) {
207 self.player_clear_damage_prevention(player);
208 }
209
210 pub fn player_mark_lost(&mut self, player: PlayerId, reason: GameLossReason) {
211 self.player_mut(player).mark_lost(PlayerOutcome::Loss {
212 reason,
213 spell_name: None,
214 });
215 }
216
217 pub fn player_mark_won(&mut self, player: PlayerId) {
218 self.player_mut(player).mark_won(PlayerOutcome::Win);
219 }
220
221 pub fn player_alt_win_by_spell_effect(
222 &mut self,
223 player: PlayerId,
224 source_name: Option<String>,
225 ) {
226 self.player_mut(player)
227 .mark_won(PlayerOutcome::AltWin { source_name });
228 }
229
230 pub fn player_concede(&mut self, player: PlayerId) {
231 self.player_mut(player).mark_lost(PlayerOutcome::Conceded);
232 }
233
234 pub fn player_clear_outcome(&mut self, player: PlayerId) {
235 self.player_mut(player).clear_outcome();
236 }
237
238 pub fn player_reset_for_restart(&mut self, player: PlayerId) {
239 self.player_mut(player).reset_for_restart();
240 }
241
242 pub fn player_set_controlled_by(&mut self, player: PlayerId, controller: Option<PlayerId>) {
243 self.player_mut(player).controlled_by = controller;
244 }
245
246 pub fn player_add_skip_turns(&mut self, player: PlayerId, amount: i32) {
247 self.player_mut(player).skip_turns += amount;
248 }
249
250 pub fn player_decrement_skip_turns(&mut self, player: PlayerId) {
251 let p = self.player_mut(player);
252 p.skip_turns = (p.skip_turns - 1).max(0);
253 }
254
255 pub fn player_set_monarch(
256 &mut self,
257 player: PlayerId,
258 trigger_handler: Option<&mut TriggerHandler>,
259 ) {
260 if let Some(previous) = self.monarch {
261 if previous != player {
262 self.remove_monarch_effect(previous);
263 }
264 }
265 self.monarch = Some(player);
266 let effect_id = self.ensure_monarch_effect(player);
267 if let Some(handler) = trigger_handler {
268 handler.register_active_trigger(self, effect_id);
269 handler.run_trigger(
270 TriggerType::BecomeMonarch,
271 RunParams {
272 player: Some(player),
273 ..Default::default()
274 },
275 false,
276 );
277 }
278 }
279
280 pub fn player_take_initiative(
281 &mut self,
282 player: PlayerId,
283 trigger_handler: Option<&mut TriggerHandler>,
284 ) {
285 if let Some(previous) = self.initiative_holder {
286 if previous != player {
287 self.remove_initiative_effect(previous);
288 }
289 }
290 self.initiative_holder = Some(player);
291 let effect_id = self.ensure_initiative_effect(player);
292 if let Some(handler) = trigger_handler {
293 handler.register_active_trigger(self, effect_id);
294 handler.run_trigger(
295 TriggerType::TakeInitiative,
296 RunParams {
297 player: Some(player),
298 ..Default::default()
299 },
300 false,
301 );
302 }
303 }
304
305 pub fn player_set_ring_bearer(&mut self, player: PlayerId, bearer: Option<CardId>) {
306 self.player_mut(player).ring_bearer = bearer;
307 }
308
309 pub fn player_set_blessing(&mut self, player: PlayerId, value: bool) {
310 self.player_mut(player).has_city_blessing = value;
311 self.ensure_blessing_effect(player);
312 }
313
314 pub fn player_ring_tempt(&mut self, player: PlayerId) {
315 let current = self.player(player).ring_level;
316 if current < 4 {
317 self.player_mut(player).ring_level = current + 1;
318 }
319 }
320
321 pub fn player_add_poison(&mut self, player: PlayerId, amount: i32) {
322 if amount > 0 {
323 self.player_mut(player).poison_counters += amount;
324 }
325 }
326
327 pub fn player_remove_poison(&mut self, player: PlayerId, amount: i32) {
328 if amount > 0 {
329 let p = self.player_mut(player);
330 p.poison_counters = (p.poison_counters - amount).max(0);
331 }
332 }
333
334 pub fn player_add_energy(&mut self, player: PlayerId, amount: i32) {
335 self.player_mut(player).energy_counters += amount;
336 }
337
338 pub fn player_can_pay_energy(&self, player: PlayerId, amount: i32) -> bool {
339 amount <= 0 || self.player(player).energy_counters >= amount
340 }
341
342 pub fn player_lose_energy(&mut self, player: PlayerId, amount: i32) -> i32 {
343 if amount <= 0 {
344 return 0;
345 }
346 let p = self.player_mut(player);
347 let paid = amount.min(p.energy_counters);
348 p.energy_counters -= paid;
349 paid
350 }
351
352 pub fn player_pay_energy(&mut self, player: PlayerId, amount: i32) -> bool {
353 if !self.player_can_pay_energy(player, amount) {
354 return false;
355 }
356 self.player_lose_energy(player, amount);
357 true
358 }
359
360 pub fn player_add_shards(&mut self, player: PlayerId, amount: i32) {
361 self.player_mut(player).mana_shards += amount;
362 }
363
364 pub fn player_can_pay_shards(&self, player: PlayerId, amount: i32) -> bool {
365 amount <= 0 || self.player(player).mana_shards >= amount
366 }
367
368 pub fn player_lose_shards(&mut self, player: PlayerId, amount: i32) -> i32 {
369 if amount <= 0 {
370 return 0;
371 }
372 let p = self.player_mut(player);
373 let paid = amount.min(p.mana_shards);
374 p.mana_shards -= paid;
375 paid
376 }
377
378 pub fn player_pay_shards(&mut self, player: PlayerId, amount: i32) -> bool {
379 if !self.player_can_pay_shards(player, amount) {
380 return false;
381 }
382 self.player_lose_shards(player, amount);
383 true
384 }
385
386 pub fn player_add_radiation(&mut self, player: PlayerId, amount: i32) {
387 self.player_mut(player).radiation_counters += amount;
388 self.ensure_radiation_effect(player);
389 }
390
391 pub fn player_remove_radiation(&mut self, player: PlayerId, amount: i32) {
392 if amount > 0 {
393 let p = self.player_mut(player);
394 p.radiation_counters = (p.radiation_counters - amount).max(0);
395 }
396 self.ensure_radiation_effect(player);
397 }
398
399 pub fn player_set_radiation(&mut self, player: PlayerId, amount: i32) {
400 self.player_mut(player).radiation_counters = amount.max(0);
401 self.ensure_radiation_effect(player);
402 }
403
404 pub fn player_gain_life(&mut self, player: PlayerId, amount: i32) -> i32 {
405 if amount <= 0 {
406 return 0;
407 }
408 self.player_mut(player).gain_life(amount);
409 amount
410 }
411
412 pub fn player_can_gain_life(&self, player: PlayerId) -> bool {
413 !crate::staticability::static_ability_cant_gain_lose_pay_life::cant_gain_life(self, player)
414 }
415
416 pub fn player_lose_life(&mut self, player: PlayerId, amount: i32) -> i32 {
417 if amount <= 0 {
418 return 0;
419 }
420 self.player_mut(player).lose_life(amount);
421 amount
422 }
423
424 pub fn player_deal_damage(&mut self, player: PlayerId, amount: i32) -> i32 {
425 if amount <= 0 {
426 return 0;
427 }
428 if crate::staticability::static_ability_cant_gain_lose_pay_life::cant_lose_life(
429 self, player,
430 ) {
431 return 0;
432 }
433 let mut event = ReplacementEvent::LifeReduced {
434 player,
435 amount,
436 is_damage: true,
437 };
438 let result = apply_replacements(self, &mut event);
439 if result == ReplacementResult::Skipped || result == ReplacementResult::Replaced {
440 return 0;
441 }
442 let final_amount = if let ReplacementEvent::LifeReduced { amount, .. } = event {
443 amount
444 } else {
445 amount
446 };
447 if final_amount <= 0 {
448 return 0;
449 }
450 self.player_mut(player).deal_damage(final_amount);
451 final_amount
452 }
453
454 pub fn player_can_lose_life(&self, player: PlayerId) -> bool {
455 self.player(player).is_alive()
456 }
457
458 pub fn player_can_pay_life(&self, player: PlayerId, amount: i32) -> bool {
459 amount <= 0 || self.player(player).life >= amount
460 }
461
462 pub fn player_pay_life(&mut self, player: PlayerId, amount: i32) -> bool {
463 if !self.player_can_pay_life(player, amount) {
464 return false;
465 }
466 self.player_lose_life(player, amount);
467 true
468 }
469
470 pub fn player_set_life(&mut self, player: PlayerId, amount: i32) -> i32 {
471 self.player_mut(player).set_life(amount)
472 }
473
474 pub fn player_exchange_life_totals(&mut self, a: PlayerId, b: PlayerId) {
475 let life_a = self.player(a).life;
476 let life_b = self.player(b).life;
477 self.player_set_life(a, life_b);
478 self.player_set_life(b, life_a);
479 }
480
481 pub fn player_add_damage_prevention(&mut self, player: PlayerId, amount: i32) {
482 if amount > 0 {
483 self.player_mut(player).damage_prevention += amount;
484 }
485 }
486
487 pub fn player_clear_damage_prevention(&mut self, player: PlayerId) {
488 self.player_mut(player).damage_prevention = 0;
489 }
490
491 pub fn player_record_land_play(&mut self, player: PlayerId) {
492 self.player_mut(player).lands_played_this_turn += 1;
493 }
494
495 pub fn player_add_team_life_gained(&mut self, player: PlayerId, amount: i32) {
496 if amount > 0 {
497 self.player_mut(player).life_gained_by_team_this_turn += amount;
498 }
499 }
500
501 pub fn player_record_permanent_left_battlefield(&mut self, player: PlayerId) {
502 self.player_mut(player)
503 .permanents_left_battlefield_this_turn += 1;
504 }
505
506 pub fn player_record_landfall(&mut self, player: PlayerId) {
507 self.player_mut(player).lands_entered_battlefield_this_turn += 1;
508 }
509
510 pub fn player_record_permanent_put_into_graveyard(&mut self, player: PlayerId) {
511 self.player_mut(player)
512 .permanents_put_into_graveyard_this_turn += 1;
513 }
514
515 pub fn player_add_mana_expended(&mut self, player: PlayerId, amount: i32) {
516 if amount > 0 {
517 self.player_mut(player).mana_expended_this_turn += amount;
518 }
519 }
520
521 pub fn player_set_mana_expended(&mut self, player: PlayerId, amount: i32) {
522 self.player_mut(player).mana_expended_this_turn = amount.max(0);
523 }
524
525 pub fn player_record_discard(&mut self, player: PlayerId, amount: i32) {
526 if amount > 0 {
527 self.player_mut(player).discarded_this_turn += amount;
528 }
529 }
530
531 pub fn player_record_explore(&mut self, player: PlayerId, amount: i32) {
532 if amount > 0 {
533 self.player_mut(player).explored_this_turn += amount;
534 }
535 }
536
537 pub fn player_record_roll(&mut self, player: PlayerId, result: Option<i32>) {
538 let p = self.player_mut(player);
539 p.num_rolls_this_turn += 1;
540 if let Some(v) = result {
541 p.dice_rolls_this_turn.push(v);
542 }
543 }
544
545 pub fn player_record_attraction_visit(&mut self, player: PlayerId, amount: i32) {
546 self.player_mut(player).attractions_visited_this_turn += amount;
547 }
548
549 pub fn player_clear_skip_untap(&mut self, player: PlayerId) {
550 self.player_mut(player).skip_next_untap = false;
551 }
552
553 pub fn player_clear_skip_draw(&mut self, player: PlayerId) {
554 self.player_mut(player).skip_next_draw = false;
555 }
556
557 pub fn player_clear_skip_combat(&mut self, player: PlayerId) {
558 self.player_mut(player).skip_next_combat = false;
559 }
560
561 pub fn player_set_skip_untap(&mut self, player: PlayerId) {
562 self.player_mut(player).skip_next_untap = true;
563 }
564
565 pub fn player_set_skip_draw(&mut self, player: PlayerId) {
566 self.player_mut(player).skip_next_draw = true;
567 }
568
569 pub fn player_set_skip_combat(&mut self, player: PlayerId) {
570 self.player_mut(player).skip_next_combat = true;
571 }
572
573 pub fn player_attack_combat_reset(&mut self, player: PlayerId) {
574 self.player_mut(player).attacked_players_this_combat.clear();
575 }
576
577 pub fn player_record_attacked_player(&mut self, player: PlayerId, defender: PlayerId) {
578 let p = self.player_mut(player);
579 p.attacked_players_this_turn.push(defender);
580 p.attacked_players_this_combat.push(defender);
581 }
582
583 pub fn player_apply_mana_burn(&mut self, player: PlayerId, amount: i32) {
584 if amount > 0 {
585 self.player_lose_life(player, amount);
586 }
587 }
588
589 pub fn player_set_speed_effect(&mut self, player: PlayerId, effect_id: Option<CardId>) {
590 self.player_mut(player).speed_effect_card = effect_id;
591 }
592
593 pub fn player_set_speed(
594 &mut self,
595 player: PlayerId,
596 speed: i32,
597 trigger_handler: Option<&mut TriggerHandler>,
598 ) {
599 self.player_mut(player).speed = speed.clamp(0, 4);
600 let effect_id = self.ensure_speed_effect(player);
601 if let (Some(handler), Some(effect_id)) = (trigger_handler, effect_id) {
602 handler.register_active_trigger(self, effect_id);
603 }
604 }
605
606 pub fn increase_player_speed(
607 &mut self,
608 player: PlayerId,
609 trigger_handler: Option<&mut TriggerHandler>,
610 ) {
611 let current = self.player(player).speed;
612 if current < 4 {
613 self.player_set_speed(player, current + 1, trigger_handler);
614 }
615 }
616
617 pub fn decrease_player_speed(
618 &mut self,
619 player: PlayerId,
620 trigger_handler: Option<&mut TriggerHandler>,
621 ) {
622 let current = self.player(player).speed;
623 if current > 1 {
624 self.player_set_speed(player, current - 1, trigger_handler);
625 }
626 }
627
628 pub fn player_add_commander_damage(
629 &mut self,
630 player: PlayerId,
631 commander: CardId,
632 amount: i32,
633 ) {
634 if amount <= 0 || !self.player(player).commander_damage_enabled {
635 return;
636 }
637 self.player_mut(player)
638 .commander_damage_received
639 .entry(commander.0)
640 .and_modify(|v| *v += amount)
641 .or_insert(amount);
642 }
643
644 pub fn player_record_damage_assignment(
645 &mut self,
646 source: Option<CardId>,
647 target_player: Option<PlayerId>,
648 amount: i32,
649 is_combat: bool,
650 ) {
651 if amount <= 0 {
652 return;
653 }
654 let Some(source_id) = source else {
655 return;
656 };
657 let controller = self.card(source_id).controller;
658 {
659 let controller_state = self.player_mut(controller);
660 controller_state.assigned_damage_this_turn += amount;
661 if is_combat {
662 controller_state.assigned_combat_damage_this_turn += amount;
663 }
664 }
665 if let Some(target) = target_player {
666 if target != controller {
667 self.player_mut(controller)
668 .opponents_assigned_damage_this_turn += amount;
669 }
670 if is_combat {
671 self.player_mut(target)
672 .been_dealt_combat_damage_since_last_turn = true;
673 }
674 }
675 }
676
677 pub fn player_draw_one(&mut self, player: PlayerId) -> Option<CardId> {
678 self.player_draw_one_internal(player, false, None)
679 }
680
681 pub fn player_draw_one_for_turn(&mut self, player: PlayerId) -> Option<CardId> {
683 self.player_draw_one_internal(player, true, None)
684 }
685
686 pub fn player_draw_one_for_turn_with_agents(
690 &mut self,
691 player: PlayerId,
692 agents: &mut [Box<dyn crate::agent::PlayerAgent>],
693 ) -> Option<CardId> {
694 self.player_draw_one_internal(player, true, Some(agents))
695 }
696
697 pub(crate) fn player_draw_one_internal(
701 &mut self,
702 player: PlayerId,
703 is_first_in_draw_step: bool,
704 agents: Option<&mut [Box<dyn crate::agent::PlayerAgent>]>,
705 ) -> Option<CardId> {
706 if !self.player_can_draw(player) {
707 return None;
708 }
709 let mut event = ReplacementEvent::Draw {
710 player,
711 extra_draws: 0,
712 is_first_in_draw_step,
713 };
714 let result = if let Some(agents) = agents {
715 apply_replacements_with_agents(self, agents, &mut event)
716 } else {
717 apply_replacements(self, &mut event)
718 };
719 if result == ReplacementResult::Skipped || result == ReplacementResult::Replaced {
720 return None;
721 }
722 let extra = if let ReplacementEvent::Draw { extra_draws, .. } = &event {
724 *extra_draws
725 } else {
726 0
727 };
728 let Some(card_id) = self.take_top_card_from_zone(ZoneType::Library, player) else {
729 self.player_mut(player).tried_to_draw_from_empty_library = true;
730 return None;
731 };
732 self.move_card(card_id, ZoneType::Hand, player);
733 self.player_mut(player).drawn_this_turn += 1;
734 for _ in 0..extra {
737 self.player_draw_one(player);
738 }
739 Some(card_id)
740 }
741
742 pub fn player_can_draw(&self, player: PlayerId) -> bool {
743 crate::staticability::static_ability_cant_draw::can_draw_amount(self, player, 1) > 0
744 }
745
746 pub fn player_can_draw_amount(&self, player: PlayerId, amount: i32) -> bool {
747 crate::staticability::static_ability_cant_draw::can_draw_amount(self, player, amount) > 0
748 }
749
750 pub fn player_draw_cards(&mut self, player: PlayerId, amount: usize) -> Vec<CardId> {
751 let mut drawn = Vec::with_capacity(amount);
752 for _ in 0..amount {
753 if let Some(card_id) = self.player_draw_one(player) {
754 drawn.push(card_id);
755 } else {
756 break;
757 }
758 }
759 drawn
760 }
761
762 pub fn player_shuffle_library(&mut self, player: PlayerId, rng: &mut impl rand::Rng) {
763 self.shuffle_zone_cards_with_rand(ZoneType::Library, player, rng);
764 }
765
766 pub fn player_record_spell_cast(&mut self, player: PlayerId, card_id: CardId) {
767 let p = self.player_mut(player);
768 p.spells_cast_this_turn += 1;
769 p.spells_cast_this_game += 1;
770 p.cards_cast_this_turn.push(card_id);
771 p.statistics.record_spell_cast(card_id);
772 }
773
774 pub fn player_hand_count(&self, player: PlayerId) -> usize {
775 self.cards_in_zone(ZoneType::Hand, player).len()
776 }
777
778 pub fn player_graveyard_count(&self, player: PlayerId) -> usize {
779 self.cards_in_zone(ZoneType::Graveyard, player).len()
780 }
781
782 pub fn player_graveyard_type_count(&self, player: PlayerId) -> usize {
783 let mut types = HashSet::new();
784 for &cid in self.cards_in_zone(ZoneType::Graveyard, player) {
785 let card = self.card(cid);
786 for core_type in &card.type_line.core_types {
787 types.insert(core_type.name());
788 }
789 }
790 types.len()
791 }
792
793 pub fn player_has_hellbent(&self, player: PlayerId) -> bool {
794 self.player_hand_count(player) == 0
795 }
796
797 pub fn player_has_threshold(&self, player: PlayerId) -> bool {
798 self.player_graveyard_count(player) >= 7
799 }
800
801 pub fn player_has_metalcraft(&self, player: PlayerId) -> bool {
802 self.cards_in_zone(ZoneType::Battlefield, player)
803 .iter()
804 .filter(|&&cid| self.card(cid).type_line.is_artifact())
805 .count()
806 >= 3
807 }
808
809 pub fn player_has_delirium(&self, player: PlayerId) -> bool {
810 self.player_graveyard_type_count(player) >= 4
811 }
812
813 pub fn player_has_ferocious(&self, player: PlayerId) -> bool {
814 self.cards_in_zone(ZoneType::Battlefield, player)
815 .iter()
816 .any(|&cid| {
817 let card = self.card(cid);
818 card.is_creature() && card.power() >= 4
819 })
820 }
821
822 pub fn player_has_desert(&self, player: PlayerId) -> bool {
823 self.cards_in_zone(ZoneType::Battlefield, player)
824 .iter()
825 .any(|&cid| self.card(cid).type_line.has_subtype("Desert"))
826 }
827
828 pub fn player_has_blessing(&self, player: PlayerId) -> bool {
829 self.player(player).has_city_blessing
830 }
831
832 pub fn player_has_revolt(&self, player: PlayerId) -> bool {
833 self.player(player).permanents_left_battlefield_this_turn > 0
834 }
835
836 pub fn player_has_landfall(&self, player: PlayerId) -> bool {
837 self.player(player).lands_entered_battlefield_this_turn > 0
838 }
839
840 pub fn player_has_descended(&self, player: PlayerId) -> bool {
841 self.player(player).permanents_put_into_graveyard_this_turn > 0
842 }
843
844 pub fn player_controls_urza_lands(&self, player: PlayerId) -> bool {
845 let battlefield = self.cards_in_zone(ZoneType::Battlefield, player);
846 let has_mine = battlefield
847 .iter()
848 .any(|&cid| self.card(cid).type_line.has_subtype("Mine"));
849 let has_plant = battlefield
850 .iter()
851 .any(|&cid| self.card(cid).type_line.has_subtype("Power-Plant"));
852 let has_tower = battlefield
853 .iter()
854 .any(|&cid| self.card(cid).type_line.has_subtype("Tower"));
855 has_mine && has_plant && has_tower
856 }
857
858 pub fn player_opponents_lost_life_this_turn(&self, player: PlayerId) -> bool {
859 self.player_order
860 .iter()
861 .copied()
862 .filter(|&other| other != player)
863 .any(|other| self.player(other).life_lost_this_turn > 0)
864 }
865
866 pub fn player_has_bloodthirst(&self, player: PlayerId) -> bool {
867 self.player(player).opponents_assigned_damage_this_turn > 0
868 }
869
870 pub fn player_can_surge(&self, player: PlayerId) -> bool {
871 self.player(player).spells_cast_this_turn > 0
872 }
873
874 pub fn player_spells_cast_this_turn(&self, player: PlayerId) -> i32 {
875 self.player(player).spells_cast_this_turn
876 }
877
878 pub fn player_storm_count(&self, _player: PlayerId) -> i32 {
879 (self.stack.spells_cast_this_turn() as i32 - 1).max(0)
880 }
881
882 pub fn player_reset_drawn_this_turn(&mut self, player: PlayerId) {
883 self.player_mut(player).drawn_this_turn = 0;
884 }
885
886 pub fn player_new_turn(&mut self, player: PlayerId) {
887 self.player_mut(player).new_turn();
888 }
889
890 pub fn player_check_lose_condition(&mut self, player: PlayerId) -> bool {
891 if !self.player(player).is_alive() {
892 return true;
893 }
894 if self.player(player).tried_to_draw_from_empty_library {
895 self.player_mut(player).tried_to_draw_from_empty_library = false;
896 let mut event = ReplacementEvent::GameLoss {
897 player,
898 reason: GameLossReason::Milled,
899 };
900 let result = apply_replacements(self, &mut event);
901 if result != ReplacementResult::Replaced {
902 self.player_mark_lost(player, GameLossReason::Milled);
903 return true;
904 }
905 }
906 if self.player(player).life <= 0 {
907 let mut event = ReplacementEvent::GameLoss {
908 player,
909 reason: GameLossReason::LifeReachedZero,
910 };
911 let result = apply_replacements(self, &mut event);
912 if result != ReplacementResult::Replaced {
913 self.player_mark_lost(player, GameLossReason::LifeReachedZero);
914 return true;
915 }
916 }
917 if self.player(player).poison_counters >= 10 {
918 let mut event = ReplacementEvent::GameLoss {
919 player,
920 reason: GameLossReason::Poisoned,
921 };
922 let result = apply_replacements(self, &mut event);
923 if result != ReplacementResult::Replaced {
924 self.player_mark_lost(player, GameLossReason::Poisoned);
925 return true;
926 }
927 }
928 if self.player(player).commander_damage_enabled
929 && self
930 .player(player)
931 .commander_damage_received
932 .values()
933 .any(|&amount| amount >= 21)
934 {
935 let mut event = ReplacementEvent::GameLoss {
936 player,
937 reason: GameLossReason::CommanderDamage,
938 };
939 let result = apply_replacements(self, &mut event);
940 if result != ReplacementResult::Replaced {
941 self.player_mark_lost(player, GameLossReason::CommanderDamage);
942 return true;
943 }
944 }
945 false
946 }
947}
948
949#[cfg(test)]
950mod tests {
951 use super::*;
952 use crate::player::registered_player::RegisteredPlayerVariant;
953
954 #[test]
955 fn draw_from_empty_library_sets_mill_loss_flag() {
956 let mut game = GameState::new(&["Alice", "Bob"], 20);
957 let player = PlayerId(0);
958
959 assert_eq!(game.player_draw_one(player), None);
960 assert!(game.player(player).tried_to_draw_from_empty_library);
961 }
962
963 #[test]
964 fn empty_library_draw_loses_game_as_milled() {
965 let mut game = GameState::new(&["Alice", "Bob"], 20);
966 let player = PlayerId(0);
967
968 assert_eq!(game.player_draw_one(player), None);
969 assert!(game.player_check_lose_condition(player));
970 assert!(game.player(player).has_lost);
971 assert_eq!(
972 game.player(player).outcome,
973 Some(PlayerOutcome::Loss {
974 reason: GameLossReason::Milled,
975 spell_name: None,
976 })
977 );
978 assert!(!game.player(player).tried_to_draw_from_empty_library);
979 }
980
981 #[test]
982 fn brawl_registration_disables_commander_damage_loss() {
983 let players = vec![
984 RegisteredPlayer::for_variant_set(
985 "Alice",
986 &[RegisteredPlayerVariant::Brawl],
987 vec!["Commander".to_string()],
988 vec![],
989 false,
990 vec![],
991 vec![],
992 ),
993 RegisteredPlayer::new("Bob"),
994 ];
995 let mut game = GameState::new_from_registered_players(&players);
996 let commander = CardId(99);
997
998 game.player_add_commander_damage(PlayerId(0), commander, 21);
999
1000 assert!(!game.player_check_lose_condition(PlayerId(0)));
1001 assert!(!game.player(PlayerId(0)).has_lost);
1002 }
1003}