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 crate::deal::FullDeal;
use crate::seat::Seat;
use dds_bridge_sys as sys;
use parking_lot::Mutex;
use core::ffi::c_int;
use core::mem::MaybeUninit;
use std::sync::LazyLock;
const MAX_BOARD_COUNT: usize = sys::MAXNOOFBOARDS as usize;
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 mut par = sys::parResultsMaster::default();
let status = unsafe {
sys::DealerParBin(
&mut tricks.into(),
&raw mut par,
vul.to_sys(),
dealer as c_int,
)
};
check(status);
par.into()
}
#[must_use]
pub fn calculate_pars(tricks: TrickCountTable, vul: Vulnerability) -> [Par; 2] {
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<Mutex<()>> = LazyLock::new(|| {
unsafe { sys::SetMaxThreads(0) };
Mutex::new(())
});
pub struct Solver(#[allow(dead_code)] parking_lot::MutexGuard<'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 system_info(&self) -> SystemInfo {
let mut inner = MaybeUninit::uninit();
unsafe { sys::GetDDSInfo(inner.as_mut_ptr()) };
SystemInfo(unsafe { inner.assume_init() })
}
#[must_use]
pub fn solve_deal(&self, deal: FullDeal) -> TrickCountTable {
let mut result = sys::ddTableResults::default();
let status = unsafe { sys::CalcDDtable(deal.into(), &raw mut result) };
check(status);
result.into()
}
unsafe fn solve_deal_segment(
deals: &[FullDeal],
flags: NonEmptyStrainFlags,
) -> sys::ddTablesRes {
let flags = flags.get();
let strain_count = flags.bits().count_ones() as usize;
let mut pack = sys::ddTableDeals {
noOfTables: ffi::count_to_sys(deals.len(), MAX_BOARD_COUNT / strain_count),
..Default::default()
};
deals
.iter()
.enumerate()
.for_each(|(i, &deal)| pack.deals[i] = deal.into());
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 = sys::ddTablesRes::default();
let status = unsafe {
sys::CalcAllTables(
&raw mut pack,
-1,
filter.as_mut_ptr(),
&raw mut res,
&mut sys::allParResults::default(),
)
};
check(status);
res
}
#[must_use]
pub fn solve_deals(
&self,
deals: &[FullDeal],
flags: NonEmptyStrainFlags,
) -> Vec<TrickCountTable> {
let mut tables = Vec::new();
for chunk in deals.chunks(MAX_BOARD_COUNT / flags.get().bits().count_ones() as usize) {
tables.extend(
unsafe { Self::solve_deal_segment(chunk, flags) }.results[..chunk.len()]
.iter()
.map(|&x| TrickCountTable::from(x)),
);
}
tables
}
#[must_use]
pub fn solve_board(&self, objective: Objective) -> FoundPlays {
let mut result = sys::futureTricks::default();
let status = unsafe {
sys::SolveBoard(
objective.board.into(),
objective.target.target(),
objective.target.solutions(),
0,
&raw mut result,
0,
)
};
check(status);
FoundPlays::from(result)
}
unsafe fn solve_board_segment(args: &[Objective]) -> sys::solvedBoards {
let mut pack = sys::boards {
noOfBoards: ffi::count_to_sys(args.len(), MAX_BOARD_COUNT),
..Default::default()
};
args.iter().enumerate().for_each(|(i, obj)| {
pack.deals[i] = obj.board.clone().into();
pack.target[i] = obj.target.target();
pack.solutions[i] = obj.target.solutions();
});
let mut res = sys::solvedBoards::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::new();
for chunk in args.chunks(MAX_BOARD_COUNT) {
solutions.extend(
unsafe { Self::solve_board_segment(chunk) }.solvedBoard[..chunk.len()]
.iter()
.map(|&x| FoundPlays::from(x)),
);
}
solutions
}
#[must_use]
pub fn analyse_play(&self, trace: PlayTrace) -> PlayAnalysis {
let mut result = sys::solvedPlay::default();
let play = PlayTraceBin::from(&trace.cards);
let status = unsafe { sys::AnalysePlayBin(trace.board.into(), play.0, &raw mut result, 0) };
check(status);
PlayAnalysis::from(result)
}
unsafe fn analyse_play_segment(traces: &[PlayTrace]) -> sys::solvedPlays {
let mut pack = sys::boards {
noOfBoards: ffi::count_to_sys(traces.len(), MAX_BOARD_COUNT),
..Default::default()
};
let mut plays = sys::playTracesBin {
noOfBoards: ffi::count_to_sys(traces.len(), MAX_BOARD_COUNT),
..Default::default()
};
traces.iter().enumerate().for_each(|(i, trace)| {
pack.deals[i] = trace.board.clone().into();
plays.plays[i] = PlayTraceBin::from(&trace.cards).0;
});
let mut res = sys::solvedPlays::default();
let status =
unsafe { sys::AnalyseAllPlaysBin(&raw mut pack, &raw mut plays, &raw mut res, 0) };
check(status);
res
}
#[must_use]
pub fn analyse_plays(&self, traces: &[PlayTrace]) -> Vec<PlayAnalysis> {
let mut results = Vec::new();
for chunk in traces.chunks(MAX_BOARD_COUNT) {
results.extend(
unsafe { Self::analyse_play_segment(chunk) }.solved[..chunk.len()]
.iter()
.map(|&x| PlayAnalysis::from(x)),
);
}
results
}
}