card-game 0.2.0

Framework for building card games
Documentation
use crate::{
    cards::Card,
    zones::{FiniteZone, InfiniteZone, Zone},
};

pub trait SlotZone: Zone {
    fn max_slots(&self) -> usize;
}
pub struct Slot<CardKind> {
    card: Option<Card<CardKind>>,
}
impl<CardKind> Slot<CardKind> {
    pub fn new() -> Self {
        Slot { card: None }
    }
    pub fn is_occupied(&self) -> bool {
        self.card.is_some()
    }
    pub fn put(&mut self, card: Card<CardKind>) -> Option<Card<CardKind>> {
        self.card.replace(card)
    }
    pub fn occupier(&self) -> Option<&Card<CardKind>> {
        self.card.as_ref()
    }
    pub fn occupier_mut(&mut self) -> Option<&mut Card<CardKind>> {
        self.card.as_mut()
    }
    pub fn transfer_to_slot<'a, ToCardKind>(
        &'a mut self,
        to_slot: &'a mut Slot<ToCardKind>,
    ) -> SlotToSlotTransport<'a, CardKind, ToCardKind>
    where
        Card<CardKind>: Into<Card<ToCardKind>>,
    {
        SlotToSlotTransport {
            from_slot: self,
            to_slot,
        }
    }
    pub fn transfer_to_infinite_zone<'a, Zone: InfiniteZone>(
        &'a mut self,
        to_zone: &'a mut Zone,
    ) -> SlotToInfiniteZoneTransport<'a, CardKind, Zone>
    where
        Card<CardKind>: Into<Card<Zone::CardKind>>,
    {
        SlotToInfiniteZoneTransport {
            from_slot: self,
            to_zone,
        }
    }
    pub fn transfer_to_finite_zone<'a, Zone: FiniteZone>(
        &'a mut self,
        to_zone: &'a mut Zone,
    ) -> SlotToFiniteZoneTransport<'a, CardKind, Zone>
    where
        Card<CardKind>: Into<Card<Zone::CardKind>>,
    {
        SlotToFiniteZoneTransport {
            from_slot: self,
            to_zone,
        }
    }
}

pub struct SlotToSlotTransport<'a, FromCardKind, ToCardKind>
where
    Card<FromCardKind>: Into<Card<ToCardKind>>,
{
    from_slot: &'a mut Slot<FromCardKind>,
    to_slot: &'a mut Slot<ToCardKind>,
}
pub struct SlotToFiniteZoneTransport<'a, FromCardKind, ToZone: FiniteZone>
where
    Card<FromCardKind>: Into<Card<<ToZone as Zone>::CardKind>>,
{
    from_slot: &'a mut Slot<FromCardKind>,
    to_zone: &'a mut ToZone,
}
pub struct SlotToInfiniteZoneTransport<'a, FromCardKind, ToZone: InfiniteZone>
where
    Card<FromCardKind>: Into<Card<<ToZone as Zone>::CardKind>>,
{
    from_slot: &'a mut Slot<FromCardKind>,
    to_zone: &'a mut ToZone,
}

impl<'a, FromCardKind, ToCardKind> SlotToSlotTransport<'a, FromCardKind, ToCardKind>
where
    Card<FromCardKind>: Into<Card<ToCardKind>>,
{
    pub fn transport(mut self) -> Result<(), SlotToSlotTransportError> {
        if self.to_slot.is_occupied() {
            Err(SlotToSlotTransportError::SlotOccupied(
                ZoneSlotOccupiedError,
            ))
        } else if let Some(card) = self.from_slot.card.take() {
            self.to_slot.card = Some(card.into());
            Ok(())
        } else {
            Err(SlotToSlotTransportError::NoSlotCard(CardNotInSlotError))
        }
    }
}
#[derive(thiserror::Error, Debug)]
#[error("slot is occupied already")]
pub struct ZoneSlotOccupiedError;
#[derive(thiserror::Error, Debug)]
#[error("no card in slot to transport")]
pub struct CardNotInSlotError;
#[derive(thiserror::Error, Debug)]
pub enum SlotToSlotTransportError {
    #[error(transparent)]
    SlotOccupied(#[from] ZoneSlotOccupiedError),
    #[error(transparent)]
    NoSlotCard(#[from] CardNotInSlotError),
}

impl<'a, FromCardKind, ToZone: FiniteZone> SlotToFiniteZoneTransport<'a, FromCardKind, ToZone>
where
    Card<FromCardKind>: Into<Card<<ToZone as Zone>::CardKind>>,
{
    pub fn transport(mut self) -> Result<(), SlotToFiniteZoneTransportError> {
        if self.to_zone.has_space() {
            if let Some(card) = self.from_slot.card.take() {
                self.to_zone.add_card_unchecked(card.into());
                Ok(())
            } else {
                Err(SlotToFiniteZoneTransportError::CardNotInSlot(
                    CardNotInSlotError,
                ))
            }
        } else {
            Err(SlotToFiniteZoneTransportError::ZoneIsFull)
        }
    }
}
#[derive(thiserror::Error, Debug)]
pub enum SlotToFiniteZoneTransportError {
    #[error("zone is full")]
    ZoneIsFull,
    #[error(transparent)]
    CardNotInSlot(#[from] CardNotInSlotError),
}

impl<'a, FromCardKind, ToZone: InfiniteZone> SlotToInfiniteZoneTransport<'a, FromCardKind, ToZone>
where
    Card<FromCardKind>: Into<Card<<ToZone as Zone>::CardKind>>,
{
    pub fn transport(mut self) -> Result<(), CardNotInSlotError> {
        if let Some(card) = self.from_slot.card.take() {
            self.to_zone.add_card(card.into());
            Ok(())
        } else {
            Err(CardNotInSlotError)
        }
    }
}

#[macro_export]
macro_rules! define_slot_iter {
    ($iter_name: ident, $zone: ty, $card_type: ty, $($index: literal => $slots: ident,)+) => {
        struct $iter_name<'a> {
            index: usize,
            zone: &'a $zone,
        }
        impl<'a> ::std::iter::Iterator for $iter_name<'a> {
            type Item = &'a ::card_game::cards::Card<$card_type>;
            fn next(&mut self) -> ::std::option::Option<Self::Item> {
                loop {
                    match self.index {
                        $(
                            $index => {
                                self.index += 1;
                                if let Some(occupier) = self.zone.$slots.occupier() {
                                    break Some(occupier);
                                }
                            }
                        )*
                        _ => break None,
                    }
                }
            }
        }
    };
}