use forge_foundation::ZoneType;
use crate::game_rng::GameRng;
use crate::ids::{CardId, PlayerId};
use super::{Zone, ZoneKey};
pub const STORED_ZONE_TYPES: [ZoneType; 16] = [
ZoneType::Hand,
ZoneType::Library,
ZoneType::Graveyard,
ZoneType::Battlefield,
ZoneType::Exile,
ZoneType::Command,
ZoneType::Sideboard,
ZoneType::SchemeDeck,
ZoneType::PlanarDeck,
ZoneType::AttractionDeck,
ZoneType::ContraptionDeck,
ZoneType::Junkyard,
ZoneType::Ante,
ZoneType::ExtraHand,
ZoneType::Subgame,
ZoneType::Stack,
];
#[derive(Debug, Clone, Default)]
pub struct ZoneStore {
zones: Vec<Zone>,
card_locations: Vec<Option<ZoneKey>>,
player_count: usize,
}
impl ZoneStore {
pub fn new(players: &[PlayerId]) -> Self {
let mut zones = Vec::with_capacity(players.len() * STORED_ZONE_TYPES.len());
for &pid in players {
for &zone_type in &STORED_ZONE_TYPES {
zones.push(Zone::new(zone_type, pid));
}
}
Self {
zones,
card_locations: Vec::new(),
player_count: players.len(),
}
}
pub fn get(&self, zone_type: ZoneType, owner: PlayerId) -> Option<&Zone> {
self.index(zone_type, owner)
.and_then(|index| self.zones.get(index))
}
pub fn get_mut(&mut self, zone_type: ZoneType, owner: PlayerId) -> Option<&mut Zone> {
self.index(zone_type, owner)
.and_then(|index| self.zones.get_mut(index))
}
pub fn remove_card(&mut self, zone_type: ZoneType, owner: PlayerId, card: CardId) -> bool {
let removed = self
.get_mut(zone_type, owner)
.expect("Zone not found")
.remove(card);
if removed {
self.clear_card_location(card, zone_type, owner);
}
removed
}
pub fn add_card_to_top(&mut self, zone_type: ZoneType, owner: PlayerId, card: CardId) {
self.get_mut(zone_type, owner)
.expect("Zone not found")
.add_to_top(card);
self.set_card_location(card, zone_type, owner);
}
pub fn add_card_to_bottom(&mut self, zone_type: ZoneType, owner: PlayerId, card: CardId) {
self.get_mut(zone_type, owner)
.expect("Zone not found")
.add_to_bottom(card);
self.set_card_location(card, zone_type, owner);
}
pub fn take_top_card(&mut self, zone_type: ZoneType, owner: PlayerId) -> Option<CardId> {
let card = self
.get_mut(zone_type, owner)
.expect("Zone not found")
.take_top()?;
self.clear_card_location(card, zone_type, owner);
Some(card)
}
pub fn reorder_card(
&mut self,
zone_type: ZoneType,
owner: PlayerId,
card: CardId,
index: usize,
) {
self.get_mut(zone_type, owner)
.expect("Zone not found")
.reorder(card, index);
}
pub fn move_cards_to_top(&mut self, zone_type: ZoneType, owner: PlayerId, cards: &[CardId]) {
let zone = self.get_mut(zone_type, owner).expect("Zone not found");
for card in cards {
if let Some(pos) = zone.cards.iter().position(|&c| c == *card) {
zone.cards.remove(pos);
}
}
for card in cards {
zone.cards.push(*card);
}
}
pub fn move_cards_to_bottom(&mut self, zone_type: ZoneType, owner: PlayerId, cards: &[CardId]) {
let zone = self.get_mut(zone_type, owner).expect("Zone not found");
for card in cards {
if let Some(pos) = zone.cards.iter().position(|&c| c == *card) {
zone.cards.remove(pos);
}
}
for card in cards.iter().rev() {
zone.cards.insert(0, *card);
}
}
pub fn replace_cards(&mut self, zone_type: ZoneType, owner: PlayerId, cards: Vec<CardId>) {
let previous = {
let zone = self.get_mut(zone_type, owner).expect("Zone not found");
std::mem::replace(&mut zone.cards, cards)
};
for card in previous {
self.clear_card_location(card, zone_type, owner);
}
let current = self
.get(zone_type, owner)
.expect("Zone not found")
.cards
.clone();
for card in current {
self.set_card_location(card, zone_type, owner);
}
}
pub fn shuffle_cards(&mut self, zone_type: ZoneType, owner: PlayerId, rng: &mut dyn GameRng) {
self.get_mut(zone_type, owner)
.expect("Zone not found")
.shuffle(rng);
}
pub fn shuffle_cards_with_rand<R: rand::Rng + ?Sized>(
&mut self,
zone_type: ZoneType,
owner: PlayerId,
rng: &mut R,
) {
use rand::seq::SliceRandom;
self.get_mut(zone_type, owner)
.expect("Zone not found")
.cards
.shuffle(rng);
}
pub fn save_lki(&mut self, zone_type: ZoneType, owner: PlayerId, card: CardId, from: ZoneType) {
self.get_mut(zone_type, owner)
.expect("Zone not found")
.save_lki(card, from);
}
pub fn card_location(&self, card: CardId) -> Option<ZoneKey> {
self.card_locations.get(card.index()).copied().flatten()
}
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Zone> {
self.zones.iter_mut()
}
pub fn iter(&self) -> impl Iterator<Item = (ZoneKey, &Zone)> {
self.zones
.iter()
.map(|zone| (ZoneKey::new(zone.zone_type, zone.owner), zone))
}
pub fn len(&self) -> usize {
self.zones.len()
}
pub fn is_empty(&self) -> bool {
self.zones.is_empty()
}
fn index(&self, zone_type: ZoneType, owner: PlayerId) -> Option<usize> {
if owner.index() >= self.player_count {
return None;
}
let zone_index = stored_zone_index(zone_type)?;
Some(owner.index() * STORED_ZONE_TYPES.len() + zone_index)
}
fn set_card_location(&mut self, card: CardId, zone_type: ZoneType, owner: PlayerId) {
self.ensure_card_location_slot(card);
self.card_locations[card.index()] = Some(ZoneKey::new(zone_type, owner));
}
fn clear_card_location(&mut self, card: CardId, zone_type: ZoneType, owner: PlayerId) {
let Some(slot) = self.card_locations.get_mut(card.index()) else {
return;
};
if *slot == Some(ZoneKey::new(zone_type, owner)) {
*slot = None;
}
}
fn ensure_card_location_slot(&mut self, card: CardId) {
let len = card.index() + 1;
if self.card_locations.len() < len {
self.card_locations.resize(len, None);
}
}
}
fn stored_zone_index(zone_type: ZoneType) -> Option<usize> {
match zone_type {
ZoneType::Hand => Some(0),
ZoneType::Library => Some(1),
ZoneType::Graveyard => Some(2),
ZoneType::Battlefield => Some(3),
ZoneType::Exile => Some(4),
ZoneType::Command => Some(5),
ZoneType::Sideboard => Some(6),
ZoneType::SchemeDeck => Some(7),
ZoneType::PlanarDeck => Some(8),
ZoneType::AttractionDeck => Some(9),
ZoneType::ContraptionDeck => Some(10),
ZoneType::Junkyard => Some(11),
ZoneType::Ante => Some(12),
ZoneType::ExtraHand => Some(13),
ZoneType::Subgame => Some(14),
ZoneType::Stack => Some(15),
ZoneType::Flashback | ZoneType::Merged | ZoneType::None => None,
}
}