1use forge_foundation::ZoneType;
2
3use crate::game_rng::GameRng;
4use crate::ids::{CardId, PlayerId};
5
6use super::{Zone, ZoneKey};
7
8pub const STORED_ZONE_TYPES: [ZoneType; 16] = [
14 ZoneType::Hand,
15 ZoneType::Library,
16 ZoneType::Graveyard,
17 ZoneType::Battlefield,
18 ZoneType::Exile,
19 ZoneType::Command,
20 ZoneType::Sideboard,
21 ZoneType::SchemeDeck,
22 ZoneType::PlanarDeck,
23 ZoneType::AttractionDeck,
24 ZoneType::ContraptionDeck,
25 ZoneType::Junkyard,
26 ZoneType::Ante,
27 ZoneType::ExtraHand,
28 ZoneType::Subgame,
29 ZoneType::Stack,
30];
31
32#[derive(Debug, Clone, Default)]
38pub struct ZoneStore {
39 zones: Vec<Zone>,
40 card_locations: Vec<Option<ZoneKey>>,
41 player_count: usize,
42}
43
44impl ZoneStore {
45 pub fn new(players: &[PlayerId]) -> Self {
46 let mut zones = Vec::with_capacity(players.len() * STORED_ZONE_TYPES.len());
47 for &pid in players {
48 for &zone_type in &STORED_ZONE_TYPES {
49 zones.push(Zone::new(zone_type, pid));
50 }
51 }
52 Self {
53 zones,
54 card_locations: Vec::new(),
55 player_count: players.len(),
56 }
57 }
58
59 pub fn get(&self, zone_type: ZoneType, owner: PlayerId) -> Option<&Zone> {
60 self.index(zone_type, owner)
61 .and_then(|index| self.zones.get(index))
62 }
63
64 pub fn get_mut(&mut self, zone_type: ZoneType, owner: PlayerId) -> Option<&mut Zone> {
65 self.index(zone_type, owner)
66 .and_then(|index| self.zones.get_mut(index))
67 }
68
69 pub fn remove_card(&mut self, zone_type: ZoneType, owner: PlayerId, card: CardId) -> bool {
70 let removed = self
71 .get_mut(zone_type, owner)
72 .expect("Zone not found")
73 .remove(card);
74 if removed {
75 self.clear_card_location(card, zone_type, owner);
76 }
77 removed
78 }
79
80 pub fn add_card_to_top(&mut self, zone_type: ZoneType, owner: PlayerId, card: CardId) {
81 self.get_mut(zone_type, owner)
82 .expect("Zone not found")
83 .add_to_top(card);
84 self.set_card_location(card, zone_type, owner);
85 }
86
87 pub fn add_card_to_bottom(&mut self, zone_type: ZoneType, owner: PlayerId, card: CardId) {
88 self.get_mut(zone_type, owner)
89 .expect("Zone not found")
90 .add_to_bottom(card);
91 self.set_card_location(card, zone_type, owner);
92 }
93
94 pub fn take_top_card(&mut self, zone_type: ZoneType, owner: PlayerId) -> Option<CardId> {
95 let card = self
96 .get_mut(zone_type, owner)
97 .expect("Zone not found")
98 .take_top()?;
99 self.clear_card_location(card, zone_type, owner);
100 Some(card)
101 }
102
103 pub fn reorder_card(
104 &mut self,
105 zone_type: ZoneType,
106 owner: PlayerId,
107 card: CardId,
108 index: usize,
109 ) {
110 self.get_mut(zone_type, owner)
111 .expect("Zone not found")
112 .reorder(card, index);
113 }
114
115 pub fn move_cards_to_top(&mut self, zone_type: ZoneType, owner: PlayerId, cards: &[CardId]) {
116 let zone = self.get_mut(zone_type, owner).expect("Zone not found");
117 for card in cards {
118 if let Some(pos) = zone.cards.iter().position(|&c| c == *card) {
119 zone.cards.remove(pos);
120 }
121 }
122 for card in cards {
123 zone.cards.push(*card);
124 }
125 }
126
127 pub fn move_cards_to_bottom(&mut self, zone_type: ZoneType, owner: PlayerId, cards: &[CardId]) {
128 let zone = self.get_mut(zone_type, owner).expect("Zone not found");
129 for card in cards {
130 if let Some(pos) = zone.cards.iter().position(|&c| c == *card) {
131 zone.cards.remove(pos);
132 }
133 }
134 for card in cards.iter().rev() {
135 zone.cards.insert(0, *card);
136 }
137 }
138
139 pub fn replace_cards(&mut self, zone_type: ZoneType, owner: PlayerId, cards: Vec<CardId>) {
140 let previous = {
141 let zone = self.get_mut(zone_type, owner).expect("Zone not found");
142 std::mem::replace(&mut zone.cards, cards)
143 };
144 for card in previous {
145 self.clear_card_location(card, zone_type, owner);
146 }
147 let current = self
148 .get(zone_type, owner)
149 .expect("Zone not found")
150 .cards
151 .clone();
152 for card in current {
153 self.set_card_location(card, zone_type, owner);
154 }
155 }
156
157 pub fn shuffle_cards(&mut self, zone_type: ZoneType, owner: PlayerId, rng: &mut dyn GameRng) {
158 self.get_mut(zone_type, owner)
159 .expect("Zone not found")
160 .shuffle(rng);
161 }
162
163 pub fn shuffle_cards_with_rand<R: rand::Rng + ?Sized>(
164 &mut self,
165 zone_type: ZoneType,
166 owner: PlayerId,
167 rng: &mut R,
168 ) {
169 use rand::seq::SliceRandom;
170
171 self.get_mut(zone_type, owner)
172 .expect("Zone not found")
173 .cards
174 .shuffle(rng);
175 }
176
177 pub fn save_lki(&mut self, zone_type: ZoneType, owner: PlayerId, card: CardId, from: ZoneType) {
178 self.get_mut(zone_type, owner)
179 .expect("Zone not found")
180 .save_lki(card, from);
181 }
182
183 pub fn card_location(&self, card: CardId) -> Option<ZoneKey> {
184 self.card_locations.get(card.index()).copied().flatten()
185 }
186
187 pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Zone> {
188 self.zones.iter_mut()
189 }
190
191 pub fn iter(&self) -> impl Iterator<Item = (ZoneKey, &Zone)> {
192 self.zones
193 .iter()
194 .map(|zone| (ZoneKey::new(zone.zone_type, zone.owner), zone))
195 }
196
197 pub fn len(&self) -> usize {
198 self.zones.len()
199 }
200
201 pub fn is_empty(&self) -> bool {
202 self.zones.is_empty()
203 }
204
205 fn index(&self, zone_type: ZoneType, owner: PlayerId) -> Option<usize> {
206 if owner.index() >= self.player_count {
207 return None;
208 }
209 let zone_index = stored_zone_index(zone_type)?;
210 Some(owner.index() * STORED_ZONE_TYPES.len() + zone_index)
211 }
212
213 fn set_card_location(&mut self, card: CardId, zone_type: ZoneType, owner: PlayerId) {
214 self.ensure_card_location_slot(card);
215 self.card_locations[card.index()] = Some(ZoneKey::new(zone_type, owner));
216 }
217
218 fn clear_card_location(&mut self, card: CardId, zone_type: ZoneType, owner: PlayerId) {
219 let Some(slot) = self.card_locations.get_mut(card.index()) else {
220 return;
221 };
222 if *slot == Some(ZoneKey::new(zone_type, owner)) {
223 *slot = None;
224 }
225 }
226
227 fn ensure_card_location_slot(&mut self, card: CardId) {
228 let len = card.index() + 1;
229 if self.card_locations.len() < len {
230 self.card_locations.resize(len, None);
231 }
232 }
233}
234
235fn stored_zone_index(zone_type: ZoneType) -> Option<usize> {
236 match zone_type {
237 ZoneType::Hand => Some(0),
238 ZoneType::Library => Some(1),
239 ZoneType::Graveyard => Some(2),
240 ZoneType::Battlefield => Some(3),
241 ZoneType::Exile => Some(4),
242 ZoneType::Command => Some(5),
243 ZoneType::Sideboard => Some(6),
244 ZoneType::SchemeDeck => Some(7),
245 ZoneType::PlanarDeck => Some(8),
246 ZoneType::AttractionDeck => Some(9),
247 ZoneType::ContraptionDeck => Some(10),
248 ZoneType::Junkyard => Some(11),
249 ZoneType::Ante => Some(12),
250 ZoneType::ExtraHand => Some(13),
251 ZoneType::Subgame => Some(14),
252 ZoneType::Stack => Some(15),
253 ZoneType::Flashback | ZoneType::Merged | ZoneType::None => None,
254 }
255}