#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
mod board;
mod ffi;
mod par;
mod play;
mod strain_flags;
mod system_info;
mod tricks;
mod vulnerability;
pub use board::*;
pub use par::*;
pub use play::*;
pub use strain_flags::*;
pub use system_info::*;
pub use tricks::*;
pub use vulnerability::*;
use contract_bridge::deal::FullDeal;
use contract_bridge::seat::Seat;
use ddss_sys as sys;
use parking_lot::{ReentrantMutex, ReentrantMutexGuard};
use core::ffi::c_int;
use core::mem::MaybeUninit;
use std::sync::LazyLock;
const fn check(status: i32) {
let msg: &[u8] = match status {
0.. => return,
sys::RETURN_ZERO_CARDS => sys::TEXT_ZERO_CARDS,
sys::RETURN_TARGET_TOO_HIGH => sys::TEXT_TARGET_TOO_HIGH,
sys::RETURN_DUPLICATE_CARDS => sys::TEXT_DUPLICATE_CARDS,
sys::RETURN_TARGET_WRONG_LO => sys::TEXT_TARGET_WRONG_LO,
sys::RETURN_TARGET_WRONG_HI => sys::TEXT_TARGET_WRONG_HI,
sys::RETURN_SOLNS_WRONG_LO => sys::TEXT_SOLNS_WRONG_LO,
sys::RETURN_SOLNS_WRONG_HI => sys::TEXT_SOLNS_WRONG_HI,
sys::RETURN_TOO_MANY_CARDS => sys::TEXT_TOO_MANY_CARDS,
sys::RETURN_SUIT_OR_RANK => sys::TEXT_SUIT_OR_RANK,
sys::RETURN_PLAYED_CARD => sys::TEXT_PLAYED_CARD,
sys::RETURN_CARD_COUNT => sys::TEXT_CARD_COUNT,
sys::RETURN_THREAD_INDEX => sys::TEXT_THREAD_INDEX,
sys::RETURN_MODE_WRONG_LO => sys::TEXT_MODE_WRONG_LO,
sys::RETURN_MODE_WRONG_HI => sys::TEXT_MODE_WRONG_HI,
sys::RETURN_TRUMP_WRONG => sys::TEXT_TRUMP_WRONG,
sys::RETURN_FIRST_WRONG => sys::TEXT_FIRST_WRONG,
sys::RETURN_PLAY_FAULT => sys::TEXT_PLAY_FAULT,
sys::RETURN_PBN_FAULT => sys::TEXT_PBN_FAULT,
sys::RETURN_TOO_MANY_BOARDS => sys::TEXT_TOO_MANY_BOARDS,
sys::RETURN_THREAD_CREATE => sys::TEXT_THREAD_CREATE,
sys::RETURN_THREAD_WAIT => sys::TEXT_THREAD_WAIT,
sys::RETURN_THREAD_MISSING => sys::TEXT_THREAD_MISSING,
sys::RETURN_NO_SUIT => sys::TEXT_NO_SUIT,
sys::RETURN_TOO_MANY_TABLES => sys::TEXT_TOO_MANY_TABLES,
sys::RETURN_CHUNK_SIZE => sys::TEXT_CHUNK_SIZE,
_ => sys::TEXT_UNKNOWN_FAULT,
};
panic!("{}", unsafe { core::str::from_utf8_unchecked(msg) });
}
#[must_use]
pub fn calculate_par(tricks: TrickCountTable, vul: Vulnerability, dealer: Seat) -> Par {
let _guard = THREAD_POOL.lock();
let mut par = sys::parResultsMaster::default();
let status = unsafe {
sys::DealerParBin(
&mut tricks.into(),
&raw mut par,
dealer as c_int,
vul.to_sys(),
)
};
check(status);
par.into()
}
#[must_use]
pub fn calculate_pars(tricks: TrickCountTable, vul: Vulnerability) -> [Par; 2] {
let _guard = THREAD_POOL.lock();
let mut pars = [sys::parResultsMaster::default(); 2];
let status = unsafe { sys::SidesParBin(&mut tricks.into(), &raw mut pars[0], vul.to_sys()) };
check(status);
pars.map(Into::into)
}
static THREAD_POOL: LazyLock<ReentrantMutex<()>> = LazyLock::new(|| {
unsafe { sys::SetMaxThreads(0) };
ReentrantMutex::new(())
});
pub struct Solver(#[allow(dead_code)] ReentrantMutexGuard<'static, ()>);
impl Solver {
#[must_use]
pub fn lock() -> Self {
Self(THREAD_POOL.lock())
}
#[must_use]
pub fn try_lock() -> Option<Self> {
THREAD_POOL.try_lock().map(Self)
}
#[must_use]
pub fn solve_deal(&self, deal: FullDeal) -> TrickCountTable {
let mut result = sys::ddTableResults::default();
let table_deal = tricks::dd_table_deal_from(deal);
let status = unsafe { sys::CalcDDtable(table_deal, &raw mut result) };
check(status);
result.into()
}
unsafe fn solve_deal_segment(deals: &[FullDeal], flags: StrainFlags) -> Box<sys::ddTablesRes> {
debug_assert!(
deals.len() * flags.bits().count_ones() as usize <= sys::MAXNOOFBOARDS as usize
);
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let deal_count = deals.len() as c_int;
let mut pack: Box<sys::ddTableDeals> = Box::default();
pack.noOfTables = deal_count;
for (i, &deal) in deals.iter().enumerate() {
pack.deals[i] = tricks::dd_table_deal_from(deal);
}
let mut filter = [
c_int::from(!flags.contains(StrainFlags::SPADES)),
c_int::from(!flags.contains(StrainFlags::HEARTS)),
c_int::from(!flags.contains(StrainFlags::DIAMONDS)),
c_int::from(!flags.contains(StrainFlags::CLUBS)),
c_int::from(!flags.contains(StrainFlags::NOTRUMP)),
];
let mut res: Box<sys::ddTablesRes> = Box::default();
let mut pars: Box<sys::allParResults> = Box::default();
let status = unsafe {
sys::CalcAllTables(
&raw mut *pack,
-1,
filter.as_mut_ptr(),
&raw mut *res,
&raw mut *pars,
)
};
check(status);
res
}
#[must_use]
pub fn solve_deals(
&self,
deals: &[FullDeal],
flags: NonEmptyStrainFlags,
) -> Vec<TrickCountTable> {
let flags = flags.get();
let chunk_size = (sys::MAXNOOFBOARDS / flags.bits().count_ones()) as usize;
let mut tables = Vec::with_capacity(deals.len());
for chunk in deals.chunks(chunk_size) {
let res = unsafe { Self::solve_deal_segment(chunk, flags) };
tables.extend(
res.results[..chunk.len()]
.iter()
.copied()
.map(TrickCountTable::from),
);
}
tables
}
#[must_use]
pub fn solve_board(&self, objective: &Objective) -> FoundPlays {
let deal = sys::deal::from(objective.board.clone());
let mut result = sys::futureTricks::default();
let status = unsafe {
sys::SolveBoard(
deal,
objective.target.target(),
objective.target.solutions(),
0,
&raw mut result,
0,
)
};
check(status);
FoundPlays::from(result)
}
unsafe fn solve_board_segment(args: &[Objective]) -> Box<sys::solvedBoards> {
debug_assert!(args.len() <= sys::MAXNOOFBOARDS as usize);
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let board_count = args.len() as c_int;
let mut pack: Box<sys::boards> = Box::default();
pack.noOfBoards = board_count;
for (i, obj) in args.iter().enumerate() {
pack.deals[i] = sys::deal::from(obj.board.clone());
pack.target[i] = obj.target.target();
pack.solutions[i] = obj.target.solutions();
}
let mut res: Box<sys::solvedBoards> = Box::default();
let status = unsafe { sys::SolveAllBoardsBin(&raw mut *pack, &raw mut *res) };
check(status);
res
}
#[must_use]
pub fn solve_boards(&self, args: &[Objective]) -> Vec<FoundPlays> {
let mut solutions = Vec::with_capacity(args.len());
for chunk in args.chunks(sys::MAXNOOFBOARDS as usize) {
let res = unsafe { Self::solve_board_segment(chunk) };
solutions.extend(
res.solvedBoard[..chunk.len()]
.iter()
.copied()
.map(FoundPlays::from),
);
}
solutions
}
#[must_use]
pub fn analyse_play(&self, trace: &PlayTrace) -> PlayAnalysis {
let deal = sys::deal::from(trace.board.clone());
let play = PlayTraceBin::from(&trace.cards);
let mut result = sys::solvedPlay::default();
let status = unsafe { sys::AnalysePlayBin(deal, play.0, &raw mut result, 0) };
check(status);
PlayAnalysis::from(result)
}
#[must_use]
pub fn analyse_plays(&self, traces: &[PlayTrace]) -> Vec<PlayAnalysis> {
traces.iter().map(|t| self.analyse_play(t)).collect()
}
}
#[must_use]
pub fn system_info() -> SystemInfo {
let _guard = THREAD_POOL.lock();
let mut inner = MaybeUninit::uninit();
unsafe { sys::GetDDSInfo(inner.as_mut_ptr()) };
SystemInfo(unsafe { inner.assume_init() })
}