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