1use std::collections::{BTreeSet, HashMap};
2
3use serde::{Deserialize, Serialize};
4
5use crate::ids::{CardId, PlayerId};
6
7use super::player_outcome::PlayerOutcome;
8use super::player_statistics::PlayerStatistics;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct PlayerState {
13 pub id: PlayerId,
14 pub name: String,
15
16 pub life: i32,
17 pub starting_life: i32,
18 pub life_started_this_turn_with: i32,
19 pub life_gained_this_turn: i32,
20 pub life_gained_by_team_this_turn: i32,
21 pub life_gained_times_this_turn: i32,
22 pub life_lost_this_turn: i32,
23 pub life_lost_last_turn: i32,
24
25 pub poison_counters: i32,
26
27 pub lands_played_this_turn: i32,
28 pub lands_played_last_turn: i32,
29 pub max_land_plays_per_turn: i32,
30 pub spells_cast_this_turn: i32,
31 pub spells_cast_last_turn: i32,
32 pub spells_cast_this_game: i32,
33 pub cards_cast_this_turn: Vec<CardId>,
34
35 pub max_hand_size: i32,
36 pub starting_hand_size: i32,
37 pub unlimited_hand_size: bool,
38
39 pub drawn_this_turn: i32,
40 pub drawn_last_turn: i32,
41 pub drawn_this_draw_step: i32,
42 pub tried_to_draw_from_empty_library: bool,
43 pub num_cards_in_hand_started_this_turn_with: i32,
44
45 pub has_lost: bool,
46 pub has_won: bool,
47 pub has_conceded: bool,
48 pub left_game: bool,
51 pub outcome: Option<PlayerOutcome>,
52
53 pub commander_damage_received: HashMap<u32, i32>,
54 pub commanders: Vec<CardId>,
55 pub commander_casts: HashMap<u32, u32>,
56 pub commander_damage_enabled: bool,
57
58 pub skip_turns: i32,
59 pub skip_next_draw: bool,
60 pub skip_next_combat: bool,
61 pub skip_next_untap: bool,
62
63 pub damage_prevention: i32,
64
65 pub energy_counters: i32,
66 pub mana_shards: i32,
73
74 pub mana_expended_this_turn: i32,
75 pub surveilled_this_turn: i32,
76 pub tokens_created_this_turn: i32,
77 pub foretold_this_turn: i32,
78 pub investigated_this_turn: i32,
79 pub ventured_this_turn: i32,
80 pub sacrificed_this_turn: i32,
81 pub library_searched_this_turn: i32,
82
83 pub controlled_by: Option<PlayerId>,
84 pub team_number: i32,
85 pub unlimited_land_plays: bool,
86
87 pub has_city_blessing: bool,
88 pub ring_level: i32,
89 pub speed: i32,
90 pub speed_effect_card: Option<CardId>,
91 pub commander_effect_card: Option<CardId>,
92 pub monarch_effect_card: Option<CardId>,
93 pub initiative_effect_card: Option<CardId>,
94 pub blessing_effect_card: Option<CardId>,
95 pub radiation_effect_card: Option<CardId>,
96 pub ring_effect_card: Option<CardId>,
97 pub contraption_sprocket_effect_card: Option<CardId>,
98 pub keyword_effect_card: Option<CardId>,
99 pub planar_dice_effect_card: Option<CardId>,
100 pub companion_effect_card: Option<CardId>,
101 pub discarded_this_turn: i32,
102 pub explored_this_turn: i32,
103 pub assigned_damage_this_turn: i32,
104 pub assigned_combat_damage_this_turn: i32,
105 pub opponents_assigned_damage_this_turn: i32,
106 pub attacked_players_this_turn: Vec<PlayerId>,
107 pub attacked_players_last_turn: Vec<PlayerId>,
108 pub attacked_players_this_combat: Vec<PlayerId>,
109 pub been_dealt_combat_damage_since_last_turn: bool,
110 pub attractions_visited_this_turn: i32,
111 pub num_flips_this_turn: i32,
112 pub num_rolls_this_turn: i32,
113 pub dice_rolls_this_turn: Vec<i32>,
114 pub ring_bearer: Option<CardId>,
115 pub radiation_counters: i32,
116 pub permanents_left_battlefield_this_turn: i32,
117 pub lands_entered_battlefield_this_turn: i32,
118 pub permanents_put_into_graveyard_this_turn: i32,
119 pub completed_dungeons: Vec<CardId>,
120 pub notes: HashMap<String, Vec<String>>,
121 pub noted_num: HashMap<String, i32>,
122 pub tapped_land_for_mana_this_turn: bool,
123 pub committed_crime_this_turn: i32,
124 pub changed_keywords: Vec<String>,
125 #[serde(default)]
126 pub keywords_until_my_next_turn: Vec<String>,
127 #[serde(default)]
128 pub keywords_until_end_of_turn: Vec<String>,
129 pub maingame_card_mapping: HashMap<CardId, CardId>,
130 pub controlled_while_searching: BTreeSet<PlayerId>,
131 pub avatar_index: i32,
132 pub sleeve_index: i32,
133 pub crank_counter: i32,
134 pub additional_votes: HashMap<i64, i32>,
135 pub additional_optional_votes: HashMap<i64, i32>,
136 pub control_votes: BTreeSet<i64>,
137 pub additional_villainous_choices: HashMap<i64, i32>,
138 pub declares_attackers: BTreeSet<PlayerId>,
139 pub declares_blockers: BTreeSet<PlayerId>,
140 pub elemental_bend_triggers: BTreeSet<String>,
141 pub inbound_tokens: Vec<CardId>,
142 pub planeswalked_to_this_turn: Vec<CardId>,
143 pub lost_ownership: Vec<CardId>,
144 pub gained_ownership: Vec<CardId>,
145 pub paid_for_stack: Vec<crate::spellability::SpellAbility>,
146 pub devotion_mod: i32,
147 pub draft_notes: HashMap<String, String>,
148 pub statistics: PlayerStatistics,
149}
150
151impl PlayerState {
152 pub fn new(id: PlayerId, name: String, starting_life: i32) -> Self {
153 PlayerState {
154 id,
155 name,
156 life: starting_life,
157 starting_life,
158 life_started_this_turn_with: starting_life,
159 life_gained_this_turn: 0,
160 life_gained_by_team_this_turn: 0,
161 life_gained_times_this_turn: 0,
162 life_lost_this_turn: 0,
163 life_lost_last_turn: 0,
164 poison_counters: 0,
165 lands_played_this_turn: 0,
166 lands_played_last_turn: 0,
167 max_land_plays_per_turn: 1,
168 spells_cast_this_turn: 0,
169 spells_cast_last_turn: 0,
170 spells_cast_this_game: 0,
171 cards_cast_this_turn: Vec::new(),
172 max_hand_size: 7,
173 starting_hand_size: 7,
174 unlimited_hand_size: false,
175 drawn_this_turn: 0,
176 drawn_last_turn: 0,
177 drawn_this_draw_step: 0,
178 tried_to_draw_from_empty_library: false,
179 num_cards_in_hand_started_this_turn_with: 0,
180 has_lost: false,
181 has_won: false,
182 has_conceded: false,
183 left_game: false,
184 outcome: None,
185 commander_damage_received: HashMap::new(),
186 commanders: Vec::new(),
187 commander_casts: HashMap::new(),
188 commander_damage_enabled: true,
189 skip_turns: 0,
190 skip_next_draw: false,
191 skip_next_combat: false,
192 skip_next_untap: false,
193 damage_prevention: 0,
194 energy_counters: 0,
195 mana_shards: 0,
196 mana_expended_this_turn: 0,
197 surveilled_this_turn: 0,
198 tokens_created_this_turn: 0,
199 foretold_this_turn: 0,
200 investigated_this_turn: 0,
201 ventured_this_turn: 0,
202 sacrificed_this_turn: 0,
203 library_searched_this_turn: 0,
204 controlled_by: None,
205 team_number: -1,
206 unlimited_land_plays: false,
207 has_city_blessing: false,
208 ring_level: 0,
209 speed: 0,
210 speed_effect_card: None,
211 commander_effect_card: None,
212 monarch_effect_card: None,
213 initiative_effect_card: None,
214 blessing_effect_card: None,
215 radiation_effect_card: None,
216 ring_effect_card: None,
217 contraption_sprocket_effect_card: None,
218 keyword_effect_card: None,
219 planar_dice_effect_card: None,
220 companion_effect_card: None,
221 discarded_this_turn: 0,
222 explored_this_turn: 0,
223 assigned_damage_this_turn: 0,
224 assigned_combat_damage_this_turn: 0,
225 opponents_assigned_damage_this_turn: 0,
226 attacked_players_this_turn: Vec::new(),
227 attacked_players_last_turn: Vec::new(),
228 attacked_players_this_combat: Vec::new(),
229 been_dealt_combat_damage_since_last_turn: false,
230 attractions_visited_this_turn: 0,
231 num_flips_this_turn: 0,
232 num_rolls_this_turn: 0,
233 dice_rolls_this_turn: Vec::new(),
234 ring_bearer: None,
235 radiation_counters: 0,
236 permanents_left_battlefield_this_turn: 0,
237 lands_entered_battlefield_this_turn: 0,
238 permanents_put_into_graveyard_this_turn: 0,
239 completed_dungeons: Vec::new(),
240 notes: HashMap::new(),
241 noted_num: HashMap::new(),
242 tapped_land_for_mana_this_turn: false,
243 committed_crime_this_turn: 0,
244 changed_keywords: Vec::new(),
245 keywords_until_my_next_turn: Vec::new(),
246 keywords_until_end_of_turn: Vec::new(),
247 maingame_card_mapping: HashMap::new(),
248 controlled_while_searching: BTreeSet::new(),
249 avatar_index: 0,
250 sleeve_index: 0,
251 crank_counter: 3,
252 additional_votes: HashMap::new(),
253 additional_optional_votes: HashMap::new(),
254 control_votes: BTreeSet::new(),
255 additional_villainous_choices: HashMap::new(),
256 declares_attackers: BTreeSet::new(),
257 declares_blockers: BTreeSet::new(),
258 elemental_bend_triggers: BTreeSet::new(),
259 inbound_tokens: Vec::new(),
260 planeswalked_to_this_turn: Vec::new(),
261 lost_ownership: Vec::new(),
262 gained_ownership: Vec::new(),
263 paid_for_stack: Vec::new(),
264 devotion_mod: 0,
265 draft_notes: HashMap::new(),
266 statistics: PlayerStatistics {
267 opening_hand_size: 7,
268 ..PlayerStatistics::default()
269 },
270 }
271 }
272
273 pub fn gain_life(&mut self, amount: i32) {
274 if amount <= 0 {
275 return;
276 }
277 self.life += amount;
278 self.life_gained_this_turn += amount;
279 self.life_gained_times_this_turn += 1;
280 }
281
282 pub fn lose_life(&mut self, amount: i32) {
283 if amount <= 0 {
284 return;
285 }
286 self.life -= amount;
287 self.life_lost_this_turn += amount;
288 }
289
290 pub fn set_life(&mut self, amount: i32) -> i32 {
291 let diff = amount - self.life;
292 self.life = amount;
293 if diff > 0 {
294 self.life_gained_this_turn += diff;
295 self.life_gained_times_this_turn += 1;
296 } else if diff < 0 {
297 self.life_lost_this_turn += diff.abs();
298 }
299 diff
300 }
301
302 pub fn deal_damage(&mut self, amount: i32) {
303 if amount > 0 {
304 self.lose_life(amount);
305 }
306 }
307
308 pub fn can_play_land(&self) -> bool {
309 self.lands_played_this_turn < self.max_land_plays_per_turn
310 }
311
312 pub fn is_alive(&self) -> bool {
313 !self.has_lost && !self.has_conceded
314 }
315
316 pub fn has_outcome(&self) -> bool {
317 self.outcome.is_some()
318 }
319
320 pub fn mark_lost(&mut self, outcome: PlayerOutcome) {
321 self.has_lost = true;
322 self.has_won = false;
323 self.has_conceded = matches!(outcome, PlayerOutcome::Conceded);
324 self.outcome = Some(outcome.clone());
325 self.statistics.set_outcome(Some(outcome));
326 }
327
328 pub fn mark_won(&mut self, outcome: PlayerOutcome) {
329 self.has_won = true;
330 self.has_lost = false;
331 self.has_conceded = false;
332 self.outcome = Some(outcome.clone());
333 self.statistics.set_outcome(Some(outcome));
334 }
335
336 pub fn clear_outcome(&mut self) {
337 self.has_lost = false;
338 self.has_won = false;
339 self.has_conceded = false;
340 self.left_game = false;
341 self.outcome = None;
342 self.statistics.set_outcome(None);
343 }
344
345 pub fn reset_for_restart(&mut self) {
346 self.life = self.starting_life;
347 self.life_started_this_turn_with = self.starting_life;
348 self.life_gained_this_turn = 0;
349 self.life_gained_by_team_this_turn = 0;
350 self.life_gained_times_this_turn = 0;
351 self.life_lost_this_turn = 0;
352 self.life_lost_last_turn = 0;
353 self.poison_counters = 0;
354 self.lands_played_this_turn = 0;
355 self.lands_played_last_turn = 0;
356 self.max_land_plays_per_turn = 1;
357 self.spells_cast_this_turn = 0;
358 self.spells_cast_last_turn = 0;
359 self.spells_cast_this_game = 0;
360 self.cards_cast_this_turn.clear();
361 self.max_hand_size = 7;
362 self.starting_hand_size = 7;
363 self.unlimited_hand_size = false;
364 self.drawn_this_turn = 0;
365 self.drawn_last_turn = 0;
366 self.drawn_this_draw_step = 0;
367 self.tried_to_draw_from_empty_library = false;
368 self.num_cards_in_hand_started_this_turn_with = 0;
369 self.clear_outcome();
370 self.commander_damage_received.clear();
371 self.commanders.clear();
372 self.commander_casts.clear();
373 self.skip_turns = 0;
374 self.skip_next_draw = false;
375 self.skip_next_combat = false;
376 self.skip_next_untap = false;
377 self.damage_prevention = 0;
378 self.energy_counters = 0;
379 self.mana_shards = 0;
380 self.mana_expended_this_turn = 0;
381 self.surveilled_this_turn = 0;
382 self.tokens_created_this_turn = 0;
383 self.foretold_this_turn = 0;
384 self.investigated_this_turn = 0;
385 self.ventured_this_turn = 0;
386 self.sacrificed_this_turn = 0;
387 self.library_searched_this_turn = 0;
388 self.controlled_by = None;
389 self.unlimited_land_plays = false;
390 self.has_city_blessing = false;
391 self.ring_level = 0;
392 self.speed = 0;
393 self.speed_effect_card = None;
394 self.commander_effect_card = None;
395 self.monarch_effect_card = None;
396 self.initiative_effect_card = None;
397 self.blessing_effect_card = None;
398 self.radiation_effect_card = None;
399 self.ring_effect_card = None;
400 self.contraption_sprocket_effect_card = None;
401 self.keyword_effect_card = None;
402 self.planar_dice_effect_card = None;
403 self.companion_effect_card = None;
404 self.discarded_this_turn = 0;
405 self.explored_this_turn = 0;
406 self.assigned_damage_this_turn = 0;
407 self.assigned_combat_damage_this_turn = 0;
408 self.opponents_assigned_damage_this_turn = 0;
409 self.attacked_players_this_turn.clear();
410 self.attacked_players_last_turn.clear();
411 self.attacked_players_this_combat.clear();
412 self.been_dealt_combat_damage_since_last_turn = false;
413 self.attractions_visited_this_turn = 0;
414 self.num_flips_this_turn = 0;
415 self.num_rolls_this_turn = 0;
416 self.dice_rolls_this_turn.clear();
417 self.ring_bearer = None;
418 self.radiation_counters = 0;
419 self.permanents_left_battlefield_this_turn = 0;
420 self.lands_entered_battlefield_this_turn = 0;
421 self.permanents_put_into_graveyard_this_turn = 0;
422 self.completed_dungeons.clear();
423 self.notes.clear();
424 self.noted_num.clear();
425 self.tapped_land_for_mana_this_turn = false;
426 self.committed_crime_this_turn = 0;
427 self.changed_keywords.clear();
428 self.maingame_card_mapping.clear();
429 self.controlled_while_searching.clear();
430 self.avatar_index = 0;
431 self.sleeve_index = 0;
432 self.crank_counter = 3;
433 self.additional_votes.clear();
434 self.additional_optional_votes.clear();
435 self.control_votes.clear();
436 self.additional_villainous_choices.clear();
437 self.declares_attackers.clear();
438 self.declares_blockers.clear();
439 self.elemental_bend_triggers.clear();
440 self.inbound_tokens.clear();
441 self.planeswalked_to_this_turn.clear();
442 self.lost_ownership.clear();
443 self.gained_ownership.clear();
444 self.paid_for_stack.clear();
445 self.devotion_mod = 0;
446 self.draft_notes.clear();
447 self.statistics = PlayerStatistics::default();
448 }
449
450 pub fn new_turn(&mut self) {
451 self.statistics.next_turn();
452 self.lands_played_last_turn = self.lands_played_this_turn;
453 self.lands_played_this_turn = 0;
454 self.spells_cast_last_turn = self.spells_cast_this_turn;
455 self.spells_cast_this_turn = 0;
456 self.cards_cast_this_turn.clear();
457 self.life_started_this_turn_with = self.life;
458 self.life_lost_last_turn = self.life_lost_this_turn;
459 self.life_gained_this_turn = 0;
460 self.life_gained_by_team_this_turn = 0;
461 self.life_gained_times_this_turn = 0;
462 self.life_lost_this_turn = 0;
463 self.drawn_last_turn = self.drawn_this_turn;
464 self.drawn_this_turn = 0;
465 self.drawn_this_draw_step = 0;
466 self.mana_expended_this_turn = 0;
467 self.surveilled_this_turn = 0;
468 self.tokens_created_this_turn = 0;
469 self.foretold_this_turn = 0;
470 self.investigated_this_turn = 0;
471 self.ventured_this_turn = 0;
472 self.sacrificed_this_turn = 0;
473 self.library_searched_this_turn = 0;
474 self.discarded_this_turn = 0;
475 self.explored_this_turn = 0;
476 self.assigned_damage_this_turn = 0;
477 self.assigned_combat_damage_this_turn = 0;
478 self.opponents_assigned_damage_this_turn = 0;
479 self.attacked_players_last_turn = self.attacked_players_this_turn.clone();
480 self.attacked_players_this_turn.clear();
481 self.attacked_players_this_combat.clear();
482 self.been_dealt_combat_damage_since_last_turn = false;
483 self.attractions_visited_this_turn = 0;
484 self.num_flips_this_turn = 0;
485 self.num_rolls_this_turn = 0;
486 self.dice_rolls_this_turn.clear();
487 self.permanents_left_battlefield_this_turn = 0;
488 self.lands_entered_battlefield_this_turn = 0;
489 self.permanents_put_into_graveyard_this_turn = 0;
490 self.tapped_land_for_mana_this_turn = false;
491 self.committed_crime_this_turn = 0;
492 self.elemental_bend_triggers.clear();
493 self.planeswalked_to_this_turn.clear();
494 self.statistics.clear_turn_cache();
495 }
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501
502 #[test]
503 fn player_life() {
504 let mut p = PlayerState::new(PlayerId(0), "Alice".to_string(), 20);
505 assert_eq!(p.life, 20);
506 p.deal_damage(3);
507 assert_eq!(p.life, 17);
508 p.gain_life(2);
509 assert_eq!(p.life, 19);
510 }
511
512 #[test]
513 fn land_plays() {
514 let mut p = PlayerState::new(PlayerId(0), "Alice".to_string(), 20);
515 assert!(p.can_play_land());
516 p.lands_played_this_turn = 1;
517 assert!(!p.can_play_land());
518 }
519}