1#[cfg(test)]
2mod test;
3
4use crate::contract::{Contract, Penalty, Strain};
5use crate::deal::{Card, Deal, Holding, Seat, Suit};
6use core::ffi::c_int;
7use core::fmt;
8use core::num::Wrapping;
9use core::ops::BitOr as _;
10use dds_bridge_sys as sys;
11use std::sync::{LazyLock, Mutex, PoisonError};
12use thiserror::Error;
13
14static THREAD_POOL: LazyLock<Mutex<()>> = LazyLock::new(|| {
15 unsafe { sys::SetMaxThreads(0) };
17 Mutex::new(())
18});
19
20#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, Hash)]
22#[repr(i32)]
23pub enum SystemError {
24 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_NO_FAULT) })]
26 #[allow(clippy::cast_possible_wrap)]
27 Success = sys::RETURN_NO_FAULT as i32,
28
29 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_UNKNOWN_FAULT) })]
31 UnknownFault = sys::RETURN_UNKNOWN_FAULT,
32
33 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_ZERO_CARDS) })]
35 ZeroCards = sys::RETURN_ZERO_CARDS,
36
37 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_TARGET_TOO_HIGH) })]
39 TargetTooHigh = sys::RETURN_TARGET_TOO_HIGH,
40
41 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_DUPLICATE_CARDS) })]
43 DuplicateCards = sys::RETURN_DUPLICATE_CARDS,
44
45 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_TARGET_WRONG_LO) })]
47 NegativeTarget = sys::RETURN_TARGET_WRONG_LO,
48
49 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_TARGET_WRONG_HI) })]
51 InvalidTarget = sys::RETURN_TARGET_WRONG_HI,
52
53 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_SOLNS_WRONG_LO) })]
55 LowSolvingParameter = sys::RETURN_SOLNS_WRONG_LO,
56
57 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_SOLNS_WRONG_HI) })]
59 HighSolvingParameter = sys::RETURN_SOLNS_WRONG_HI,
60
61 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_TOO_MANY_CARDS) })]
63 TooManyCards = sys::RETURN_TOO_MANY_CARDS,
64
65 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_SUIT_OR_RANK) })]
67 CurrentSuitOrRank = sys::RETURN_SUIT_OR_RANK,
68
69 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_PLAYED_CARD) })]
71 PlayedCard = sys::RETURN_PLAYED_CARD,
72
73 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_CARD_COUNT) })]
75 CardCount = sys::RETURN_CARD_COUNT,
76
77 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_THREAD_INDEX) })]
79 ThreadIndex = sys::RETURN_THREAD_INDEX,
80
81 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_MODE_WRONG_LO) })]
83 NegativeModeParameter = sys::RETURN_MODE_WRONG_LO,
84
85 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_MODE_WRONG_HI) })]
87 HighModeParameter = sys::RETURN_MODE_WRONG_HI,
88
89 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_TRUMP_WRONG) })]
91 Trump = sys::RETURN_TRUMP_WRONG,
92
93 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_FIRST_WRONG) })]
95 First = sys::RETURN_FIRST_WRONG,
96
97 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_PLAY_FAULT) })]
102 AnalysePlay = sys::RETURN_PLAY_FAULT,
103
104 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_PBN_FAULT) })]
106 PBN = sys::RETURN_PBN_FAULT,
107
108 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_TOO_MANY_BOARDS) })]
110 TooManyBoards = sys::RETURN_TOO_MANY_BOARDS,
111
112 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_THREAD_CREATE) })]
114 ThreadCreate = sys::RETURN_THREAD_CREATE,
115
116 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_THREAD_WAIT) })]
118 ThreadWait = sys::RETURN_THREAD_WAIT,
119
120 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_THREAD_MISSING) })]
122 ThreadMissing = sys::RETURN_THREAD_MISSING,
123
124 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_NO_SUIT) })]
126 NoSuit = sys::RETURN_NO_SUIT,
127
128 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_TOO_MANY_TABLES) })]
130 TooManyTables = sys::RETURN_TOO_MANY_TABLES,
131
132 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_CHUNK_SIZE) })]
134 ChunkSize = sys::RETURN_CHUNK_SIZE,
135}
136
137impl SystemError {
138 pub const fn propagate<T: Copy>(x: T, status: i32) -> Result<T, Self> {
146 match status {
147 0.. => Ok(x),
148 sys::RETURN_ZERO_CARDS => Err(Self::ZeroCards),
149 sys::RETURN_TARGET_TOO_HIGH => Err(Self::TargetTooHigh),
150 sys::RETURN_DUPLICATE_CARDS => Err(Self::DuplicateCards),
151 sys::RETURN_TARGET_WRONG_LO => Err(Self::NegativeTarget),
152 sys::RETURN_TARGET_WRONG_HI => Err(Self::InvalidTarget),
153 sys::RETURN_SOLNS_WRONG_LO => Err(Self::LowSolvingParameter),
154 sys::RETURN_SOLNS_WRONG_HI => Err(Self::HighSolvingParameter),
155 sys::RETURN_TOO_MANY_CARDS => Err(Self::TooManyCards),
156 sys::RETURN_SUIT_OR_RANK => Err(Self::CurrentSuitOrRank),
157 sys::RETURN_PLAYED_CARD => Err(Self::PlayedCard),
158 sys::RETURN_CARD_COUNT => Err(Self::CardCount),
159 sys::RETURN_THREAD_INDEX => Err(Self::ThreadIndex),
160 sys::RETURN_MODE_WRONG_LO => Err(Self::NegativeModeParameter),
161 sys::RETURN_MODE_WRONG_HI => Err(Self::HighModeParameter),
162 sys::RETURN_TRUMP_WRONG => Err(Self::Trump),
163 sys::RETURN_FIRST_WRONG => Err(Self::First),
164 sys::RETURN_PLAY_FAULT => Err(Self::AnalysePlay),
165 sys::RETURN_PBN_FAULT => Err(Self::PBN),
166 sys::RETURN_TOO_MANY_BOARDS => Err(Self::TooManyBoards),
167 sys::RETURN_THREAD_CREATE => Err(Self::ThreadCreate),
168 sys::RETURN_THREAD_WAIT => Err(Self::ThreadWait),
169 sys::RETURN_THREAD_MISSING => Err(Self::ThreadMissing),
170 sys::RETURN_NO_SUIT => Err(Self::NoSuit),
171 sys::RETURN_TOO_MANY_TABLES => Err(Self::TooManyTables),
172 sys::RETURN_CHUNK_SIZE => Err(Self::ChunkSize),
173 _ => Err(Self::UnknownFault),
174 }
175 }
176}
177
178#[derive(Debug, Error)]
180pub enum Error {
181 #[error(transparent)]
183 System(SystemError),
184
185 #[error("The thread pool is poisoned")]
187 Poison,
188}
189
190impl From<SystemError> for Error {
191 fn from(err: SystemError) -> Self {
192 Self::System(err)
193 }
194}
195
196impl<T> From<PoisonError<T>> for Error {
197 fn from(_: PoisonError<T>) -> Self {
198 Self::Poison
199 }
200}
201
202bitflags::bitflags! {
203 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
205 pub struct StrainFlags : u8 {
206 const CLUBS = 0x01;
208 const DIAMONDS = 0x02;
210 const HEARTS = 0x04;
212 const SPADES = 0x08;
214 const NOTRUMP = 0x10;
216 }
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
221pub struct TricksRow(u16);
222
223impl TricksRow {
224 #[must_use]
226 pub const fn new(n: u8, e: u8, s: u8, w: u8) -> Self {
227 Self(
228 (n as u16) << (4 * Seat::North as u8)
229 | (e as u16) << (4 * Seat::East as u8)
230 | (s as u16) << (4 * Seat::South as u8)
231 | (w as u16) << (4 * Seat::West as u8),
232 )
233 }
234
235 #[must_use]
237 pub const fn get(self, seat: Seat) -> u8 {
238 (self.0 >> (4 * seat as u8) & 0xF) as u8
239 }
240
241 #[must_use]
243 pub fn hex(self, seat: Seat) -> impl fmt::UpperHex {
244 TricksRowHex { deal: self, seat }
245 }
246}
247
248struct TricksRowHex {
249 deal: TricksRow,
250 seat: Seat,
251}
252
253impl fmt::UpperHex for TricksRowHex {
254 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
255 write!(
256 f,
257 "{:X}{:X}{:X}{:X}",
258 self.deal.get(self.seat),
259 self.deal.get(self.seat + Wrapping(1)),
260 self.deal.get(self.seat + Wrapping(2)),
261 self.deal.get(self.seat + Wrapping(3)),
262 )
263 }
264}
265
266#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
268pub struct TricksTable(pub [TricksRow; 5]);
269
270impl core::ops::Index<Strain> for TricksTable {
271 type Output = TricksRow;
272
273 fn index(&self, strain: Strain) -> &TricksRow {
274 &self.0[strain as usize]
275 }
276}
277
278struct TricksTableHex<T: AsRef<[Strain]>> {
279 deal: TricksTable,
280 seat: Seat,
281 strains: T,
282}
283
284impl<T: AsRef<[Strain]>> fmt::UpperHex for TricksTableHex<T> {
285 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
286 for &strain in self.strains.as_ref() {
287 self.deal[strain].hex(self.seat).fmt(f)?;
288 }
289 Ok(())
290 }
291}
292
293impl TricksTable {
294 #[must_use]
296 pub fn hex(self, seat: Seat, strains: impl AsRef<[Strain]>) -> impl fmt::UpperHex {
297 TricksTableHex {
298 deal: self,
299 seat,
300 strains,
301 }
302 }
303}
304
305impl Strain {
306 #[must_use]
308 const fn to_sys(self) -> usize {
309 match self {
310 Self::Spades => 0,
311 Self::Hearts => 1,
312 Self::Diamonds => 2,
313 Self::Clubs => 3,
314 Self::Notrump => 4,
315 }
316 }
317}
318
319impl From<sys::ddTableResults> for TricksTable {
320 fn from(table: sys::ddTableResults) -> Self {
321 const fn make_row(row: [c_int; 4]) -> TricksRow {
322 #[allow(clippy::cast_sign_loss)]
323 TricksRow::new(
324 (row[0] & 0xFF) as u8,
325 (row[1] & 0xFF) as u8,
326 (row[2] & 0xFF) as u8,
327 (row[3] & 0xFF) as u8,
328 )
329 }
330
331 Self([
332 make_row(table.resTable[Strain::Clubs.to_sys()]),
333 make_row(table.resTable[Strain::Diamonds.to_sys()]),
334 make_row(table.resTable[Strain::Hearts.to_sys()]),
335 make_row(table.resTable[Strain::Spades.to_sys()]),
336 make_row(table.resTable[Strain::Notrump.to_sys()]),
337 ])
338 }
339}
340
341impl From<TricksTable> for sys::ddTableResults {
342 fn from(table: TricksTable) -> Self {
343 const fn make_row(row: TricksRow) -> [c_int; 4] {
344 [
345 row.get(Seat::North) as c_int,
346 row.get(Seat::East) as c_int,
347 row.get(Seat::South) as c_int,
348 row.get(Seat::West) as c_int,
349 ]
350 }
351
352 Self {
353 resTable: [
354 make_row(table[Strain::Spades]),
355 make_row(table[Strain::Hearts]),
356 make_row(table[Strain::Diamonds]),
357 make_row(table[Strain::Clubs]),
358 make_row(table[Strain::Notrump]),
359 ],
360 }
361 }
362}
363
364impl From<Deal> for sys::ddTableDeal {
365 fn from(deal: Deal) -> Self {
366 Self {
367 cards: deal.0.map(|hand| {
368 [
369 hand[Suit::Spades].to_bits().into(),
370 hand[Suit::Hearts].to_bits().into(),
371 hand[Suit::Diamonds].to_bits().into(),
372 hand[Suit::Clubs].to_bits().into(),
373 ]
374 }),
375 }
376 }
377}
378
379pub fn solve_deal(deal: Deal) -> Result<TricksTable, Error> {
384 let mut result = sys::ddTableResults::default();
385 let _guard = THREAD_POOL.lock()?;
386 let status = unsafe { sys::CalcDDtable(deal.into(), &mut result) };
388 Ok(SystemError::propagate(result.into(), status)?)
389}
390
391pub unsafe fn solve_deal_segment(
403 deals: &[Deal],
404 flags: StrainFlags,
405) -> Result<sys::ddTablesRes, Error> {
406 debug_assert!(deals.len() * flags.bits().count_ones() as usize <= sys::MAXNOOFBOARDS as usize);
407 let mut pack = sys::ddTableDeals {
408 noOfTables: c_int::try_from(deals.len()).unwrap_or(c_int::MAX),
409 ..Default::default()
410 };
411 deals
412 .iter()
413 .enumerate()
414 .for_each(|(i, &deal)| pack.deals[i] = deal.into());
415
416 let mut res = sys::ddTablesRes::default();
417 let _guard = THREAD_POOL.lock()?;
418 let status = sys::CalcAllTables(
419 &mut pack,
420 -1,
421 &mut [
422 c_int::from(!flags.contains(StrainFlags::SPADES)),
423 c_int::from(!flags.contains(StrainFlags::HEARTS)),
424 c_int::from(!flags.contains(StrainFlags::DIAMONDS)),
425 c_int::from(!flags.contains(StrainFlags::CLUBS)),
426 c_int::from(!flags.contains(StrainFlags::NOTRUMP)),
427 ][0],
428 &mut res,
429 &mut sys::allParResults::default(),
430 );
431 Ok(SystemError::propagate(res, status)?)
432}
433
434pub fn solve_deals(deals: &[Deal], flags: StrainFlags) -> Result<Vec<TricksTable>, Error> {
442 let mut tables = Vec::new();
443 for chunk in deals.chunks((sys::MAXNOOFBOARDS / flags.bits().count_ones()) as usize) {
444 tables.extend(
445 unsafe { solve_deal_segment(chunk, flags) }?.results[..chunk.len()]
447 .iter()
448 .map(|&x| TricksTable::from(x)),
449 );
450 }
451 Ok(tables)
452}
453
454bitflags::bitflags! {
455 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
457 pub struct Vulnerability: u8 {
458 const NS = 1;
460 const EW = 2;
462 }
463}
464
465impl Vulnerability {
466 #[must_use]
468 #[inline]
469 pub const fn to_sys(self) -> i32 {
470 const ALL: u8 = Vulnerability::all().bits();
471 const NS: u8 = Vulnerability::NS.bits();
472 const EW: u8 = Vulnerability::EW.bits();
473
474 match self.bits() {
475 0 => 0,
476 ALL => 1,
477 NS => 2,
478 EW => 3,
479 _ => unreachable!(),
480 }
481 }
482
483 #[must_use]
485 #[inline]
486 pub const fn rotate(self, condition: bool) -> Self {
487 Self::from_bits_truncate((self.bits() * 0x55_u8) >> (condition as u8))
488 }
489}
490
491const _: () = {
493 const ALL: Vulnerability = Vulnerability::all();
494 const NONE: Vulnerability = Vulnerability::empty();
495
496 assert!(matches!(ALL.rotate(true), ALL));
497 assert!(matches!(NONE.rotate(true), NONE));
498 assert!(matches!(Vulnerability::NS.rotate(true), Vulnerability::EW));
499 assert!(matches!(Vulnerability::EW.rotate(true), Vulnerability::NS));
500
501 assert!(matches!(ALL.rotate(false), ALL));
502 assert!(matches!(NONE.rotate(false), NONE));
503 assert!(matches!(Vulnerability::NS.rotate(false), Vulnerability::NS));
504 assert!(matches!(Vulnerability::EW.rotate(false), Vulnerability::EW));
505};
506
507#[derive(Debug, Clone)]
509pub struct Par {
510 pub score: i32,
512
513 pub contracts: Vec<(Contract, Seat, i8)>,
518}
519
520impl PartialEq for Par {
521 fn eq(&self, other: &Self) -> bool {
522 fn key(contracts: &[(Contract, Seat, i8)]) -> u32 {
526 contracts
527 .iter()
528 .map(|&(contract, seat, _)| 1 << ((contract.bid.strain as u8) << 2 | seat as u8))
529 .fold(0, u32::bitor)
530 }
531 self.score == other.score && key(&self.contracts) == key(&other.contracts)
532 }
533}
534
535impl Eq for Par {}
536
537impl From<sys::parResultsMaster> for Par {
538 fn from(par: sys::parResultsMaster) -> Self {
539 #[allow(clippy::cast_sign_loss)]
542 let len = par.number as usize * usize::from(par.contracts[0].level != 0);
543
544 #[allow(clippy::cast_sign_loss)]
545 let contracts = par.contracts[..len]
546 .iter()
547 .flat_map(|contract| {
548 let strain = [
549 Strain::Notrump,
550 Strain::Spades,
551 Strain::Hearts,
552 Strain::Diamonds,
553 Strain::Clubs,
554 ][contract.denom as usize];
555
556 let (penalty, overtricks) = if contract.underTricks > 0 {
557 assert!(contract.underTricks <= 13);
558 (Penalty::Doubled, -((contract.underTricks & 0xFF) as i8))
559 } else {
560 assert!(contract.overTricks >= 0 && contract.overTricks <= 13);
561 (Penalty::None, (contract.overTricks & 0xFF) as i8)
562 };
563
564 assert_eq!(contract.level, contract.level & 7);
565 let seat: Seat = unsafe { core::mem::transmute((contract.seats & 3) as u8) };
567 let is_pair = contract.seats >= 4;
568 let contract = Contract::new((contract.level & 7) as u8, strain, penalty);
569
570 core::iter::once((contract, seat, overtricks)).chain(if is_pair {
571 Some((contract, seat + core::num::Wrapping(2), overtricks))
572 } else {
573 None
574 })
575 })
576 .collect();
577
578 Self {
579 score: par.score,
580 contracts,
581 }
582 }
583}
584
585pub fn calculate_par(
594 tricks: TricksTable,
595 vul: Vulnerability,
596 dealer: Seat,
597) -> Result<Par, SystemError> {
598 let mut par = sys::parResultsMaster::default();
599 let status = unsafe { sys::DealerParBin(&mut tricks.into(), &mut par, vul.to_sys(), dealer as c_int) };
601 Ok(SystemError::propagate(par, status)?.into())
602}
603
604pub fn calculate_pars(tricks: TricksTable, vul: Vulnerability) -> Result<[Par; 2], SystemError> {
612 let mut pars = [sys::parResultsMaster::default(); 2];
613 let status = unsafe { sys::SidesParBin(&mut tricks.into(), &mut pars[0], vul.to_sys()) };
615 Ok(SystemError::propagate(pars, status)?.map(Into::into))
616}
617
618#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
625pub enum Target {
626 Any(i8),
631
632 All(i8),
637
638 Legal,
642}
643
644impl Target {
645 #[must_use]
647 #[inline]
648 pub const fn target(self) -> c_int {
649 match self {
650 Self::Any(target) | Self::All(target) => target as c_int,
651 Self::Legal => -1,
652 }
653 }
654
655 #[must_use]
657 #[inline]
658 pub const fn solutions(self) -> c_int {
659 match self {
660 Self::Any(_) => 1,
661 Self::All(_) => 2,
662 Self::Legal => 3,
663 }
664 }
665}
666
667#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
669pub struct Board {
670 pub trump: Strain,
672 pub lead: Seat,
674 pub current_cards: [Option<Card>; 3],
676 pub deal: Deal,
678}
679
680impl From<Board> for sys::deal {
681 fn from(board: Board) -> Self {
682 let mut suits = [0; 3];
683 let mut ranks = [0; 3];
684
685 for (i, card) in board.current_cards.into_iter().flatten().enumerate() {
686 suits[i] = 3 - card.suit() as c_int;
687 ranks[i] = c_int::from(card.rank());
688 }
689
690 Self {
691 trump: match board.trump {
692 Strain::Spades => 0,
693 Strain::Hearts => 1,
694 Strain::Diamonds => 2,
695 Strain::Clubs => 3,
696 Strain::Notrump => 4,
697 },
698 first: board.lead as c_int,
699 currentTrickSuit: suits,
700 currentTrickRank: ranks,
701 remainCards: sys::ddTableDeal::from(board.deal).cards,
702 }
703 }
704}
705
706#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
708pub struct Play {
709 pub card: Card,
714
715 pub equals: Holding,
721
722 pub score: i8,
724}
725
726#[derive(Debug, Clone, Copy, PartialEq, Eq)]
728pub struct FoundPlays {
729 pub plays: [Option<Play>; 13],
731 pub nodes: u32,
733}
734
735impl From<sys::futureTricks> for FoundPlays {
736 #[allow(clippy::cast_sign_loss)]
737 fn from(future: sys::futureTricks) -> Self {
738 let mut plays = [None; 13];
739 plays
740 .iter_mut()
741 .enumerate()
742 .take(future.cards as usize)
743 .for_each(|(i, play)| {
744 let card = Card::new(
745 Suit::DESC[future.suit[i] as usize],
746 (future.rank[i] & 0xFF) as u8,
747 );
748 let equals = Holding::from_bits_truncate((future.equals[i] & 0xFFFF) as u16);
749 let score = (future.score[i] & 0xFF) as i8;
750 *play = Some(Play {
751 card,
752 equals,
753 score,
754 });
755 });
756 Self {
757 plays,
758 nodes: future.nodes as u32,
759 }
760 }
761}
762
763pub fn solve_board(board: Board, target: Target) -> Result<FoundPlays, Error> {
771 let mut result = sys::futureTricks::default();
772 let status = unsafe {
774 let _guard = THREAD_POOL.lock()?;
775 sys::SolveBoard(
776 board.into(),
777 target.target(),
778 target.solutions(),
779 0,
780 &mut result,
781 0,
783 )
784 };
785 Ok(SystemError::propagate(result, status)?.into())
786}
787
788pub unsafe fn solve_board_segment(args: &[(Board, Target)]) -> Result<sys::solvedBoards, Error> {
798 debug_assert!(args.len() <= sys::MAXNOOFBOARDS as usize);
799 let mut pack = sys::boards {
800 noOfBoards: c_int::try_from(args.len()).unwrap_or(c_int::MAX),
801 ..Default::default()
802 };
803 args.iter().enumerate().for_each(|(i, &(board, target))| {
804 pack.deals[i] = board.into();
805 pack.target[i] = target.target();
806 pack.solutions[i] = target.solutions();
807 });
808 let mut res = sys::solvedBoards::default();
809 let _guard = THREAD_POOL.lock()?;
810 let status = unsafe { sys::SolveAllBoardsBin(&mut pack, &mut res) };
812 Ok(SystemError::propagate(res, status)?)
813}
814
815pub fn solve_boards(args: &[(Board, Target)]) -> Result<Vec<FoundPlays>, Error> {
822 let mut solutions = Vec::new();
823 for chunk in args.chunks(sys::MAXNOOFBOARDS as usize) {
824 solutions.extend(
825 unsafe { solve_board_segment(chunk) }?.solvedBoard[..chunk.len()]
827 .iter()
828 .map(|&x| FoundPlays::from(x)),
829 );
830 }
831 Ok(solutions)
832}