use super::tricks::TrickCount;
use crate::deal::PartialDeal;
use crate::hand::{Card, Hand};
use crate::seat::Seat;
use crate::{Strain, Suit};
use arrayvec::ArrayVec;
use dds_bridge_sys as sys;
use thiserror::Error;
use core::ffi::c_int;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Target {
Any(Option<TrickCount>),
All(Option<TrickCount>),
Legal,
}
impl Target {
#[must_use]
#[inline]
pub const fn target(self) -> c_int {
match self {
Self::Any(Some(tc)) | Self::All(Some(tc)) => tc.get() as c_int,
Self::Any(None) | Self::All(None) | Self::Legal => -1,
}
}
#[must_use]
#[inline]
pub const fn solutions(self) -> c_int {
match self {
Self::Any(_) => 1,
Self::All(_) => 2,
Self::Legal => 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RevokePosition {
Second,
Third,
Fourth,
}
impl core::fmt::Display for RevokePosition {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Second => f.write_str("second"),
Self::Third => f.write_str("third"),
Self::Fourth => f.write_str("fourth"),
}
}
}
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum BoardError {
#[error("A played card is also present in a remaining hand")]
PlayedCardInHand,
#[error(
"Remaining hand sizes do not match the played-count pattern \
(the k seats from leader must have size m-1; others m)"
)]
InconsistentHandSizes,
#[error("Played card at {position} position is a revoke — player held the led suit")]
Revoke {
position: RevokePosition,
},
}
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CurrentTrickError {
#[error("A trick can hold at most 3 cards on the table before it completes")]
TooManyPlayed,
#[error("Duplicate card in the played cards on the table")]
DuplicatePlayedCard,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CurrentTrick {
trump: Strain,
leader: Seat,
cards: ArrayVec<Card, 3>,
seen: Hand,
}
impl CurrentTrick {
#[must_use]
#[inline]
pub const fn new(trump: Strain, leader: Seat) -> Self {
Self {
trump,
leader,
cards: ArrayVec::new_const(),
seen: Hand::EMPTY,
}
}
pub fn from_slice(
trump: Strain,
leader: Seat,
played: &[Card],
) -> Result<Self, CurrentTrickError> {
let mut trick = Self::new(trump, leader);
for &card in played {
trick.try_push(card)?;
}
Ok(trick)
}
pub fn try_push(&mut self, card: Card) -> Result<(), CurrentTrickError> {
if self.cards.is_full() {
return Err(CurrentTrickError::TooManyPlayed);
}
if !self.seen.insert(card) {
return Err(CurrentTrickError::DuplicatePlayedCard);
}
self.cards.push(card);
Ok(())
}
#[must_use]
#[inline]
pub const fn trump(&self) -> Strain {
self.trump
}
#[must_use]
#[inline]
pub const fn leader(&self) -> Seat {
self.leader
}
#[must_use]
#[inline]
pub fn cards(&self) -> &[Card] {
&self.cards
}
#[must_use]
#[inline]
pub const fn len(&self) -> usize {
self.cards.len()
}
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
self.cards.is_empty()
}
#[must_use]
#[inline]
pub const fn seen(&self) -> Hand {
self.seen
}
#[must_use]
#[inline]
pub fn led_suit(&self) -> Option<Suit> {
self.cards.first().map(|c| c.suit)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Board {
current_trick: CurrentTrick,
remaining: PartialDeal,
}
impl Board {
pub fn try_new(
remaining: PartialDeal,
current_trick: CurrentTrick,
) -> Result<Self, BoardError> {
if !(current_trick.seen() & remaining.collected()).is_empty() {
return Err(BoardError::PlayedCardInHand);
}
let leader = current_trick.leader();
let seats = [leader, leader.lho(), leader.partner(), leader.rho()];
let index = current_trick.len();
let full_len = remaining[leader.rho()].len();
for (j, &seat) in seats.iter().enumerate() {
if remaining[seat].len() + usize::from(j < index) != full_len {
return Err(BoardError::InconsistentHandSizes);
}
}
if let Some(led_suit) = current_trick.led_suit() {
for (j, played_card) in current_trick.cards().iter().enumerate().skip(1) {
if played_card.suit != led_suit && !remaining[seats[j]][led_suit].is_empty() {
return Err(BoardError::Revoke {
position: match j {
1 => RevokePosition::Second,
2 => RevokePosition::Third,
_ => RevokePosition::Fourth,
},
});
}
}
}
Ok(Self {
current_trick,
remaining,
})
}
#[must_use]
#[inline]
pub const fn trump(&self) -> Strain {
self.current_trick.trump()
}
#[must_use]
#[inline]
pub const fn leader(&self) -> Seat {
self.current_trick.leader()
}
#[must_use]
#[inline]
pub fn current_cards(&self) -> &[Card] {
self.current_trick.cards()
}
#[must_use]
#[inline]
pub const fn current_trick(&self) -> &CurrentTrick {
&self.current_trick
}
#[must_use]
#[inline]
pub const fn remaining(&self) -> &PartialDeal {
&self.remaining
}
}
impl From<Board> for sys::deal {
fn from(board: Board) -> Self {
let mut suits = [0; 3];
let mut ranks = [0; 3];
for (i, card) in board.current_trick.cards().iter().enumerate() {
suits[i] = 3 - card.suit as c_int;
ranks[i] = c_int::from(card.rank.get());
}
Self {
trump: match board.current_trick.trump() {
Strain::Spades => 0,
Strain::Hearts => 1,
Strain::Diamonds => 2,
Strain::Clubs => 3,
Strain::Notrump => 4,
},
first: board.current_trick.leader() as c_int,
currentTrickSuit: suits,
currentTrickRank: ranks,
remainCards: sys::ddTableDeal::from(board.remaining).cards,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Objective {
pub board: Board,
pub target: Target,
}