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 once_cell::sync::Lazy;
12use std::sync::{Mutex, PoisonError};
13use thiserror::Error;
14
15static THREAD_POOL: Lazy<Mutex<()>> = Lazy::new(|| {
16 unsafe { sys::SetMaxThreads(0) };
18 Mutex::new(())
19});
20
21#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, Hash)]
23#[repr(i32)]
24pub enum SystemError {
25 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_NO_FAULT) })]
27 #[allow(clippy::cast_possible_wrap)]
28 Success = sys::RETURN_NO_FAULT as i32,
29
30 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_UNKNOWN_FAULT) })]
32 UnknownFault = sys::RETURN_UNKNOWN_FAULT,
33
34 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_ZERO_CARDS) })]
36 ZeroCards = sys::RETURN_ZERO_CARDS,
37
38 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_TARGET_TOO_HIGH) })]
40 TargetTooHigh = sys::RETURN_TARGET_TOO_HIGH,
41
42 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_DUPLICATE_CARDS) })]
44 DuplicateCards = sys::RETURN_DUPLICATE_CARDS,
45
46 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_TARGET_WRONG_LO) })]
48 NegativeTarget = sys::RETURN_TARGET_WRONG_LO,
49
50 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_TARGET_WRONG_HI) })]
52 InvalidTarget = sys::RETURN_TARGET_WRONG_HI,
53
54 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_SOLNS_WRONG_LO) })]
56 LowSolvingParameter = sys::RETURN_SOLNS_WRONG_LO,
57
58 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_SOLNS_WRONG_HI) })]
60 HighSolvingParameter = sys::RETURN_SOLNS_WRONG_HI,
61
62 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_TOO_MANY_CARDS) })]
64 TooManyCards = sys::RETURN_TOO_MANY_CARDS,
65
66 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_SUIT_OR_RANK) })]
68 CurrentSuitOrRank = sys::RETURN_SUIT_OR_RANK,
69
70 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_PLAYED_CARD) })]
72 PlayedCard = sys::RETURN_PLAYED_CARD,
73
74 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_CARD_COUNT) })]
76 CardCount = sys::RETURN_CARD_COUNT,
77
78 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_THREAD_INDEX) })]
80 ThreadIndex = sys::RETURN_THREAD_INDEX,
81
82 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_MODE_WRONG_LO) })]
84 NegativeModeParameter = sys::RETURN_MODE_WRONG_LO,
85
86 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_MODE_WRONG_HI) })]
88 HighModeParameter = sys::RETURN_MODE_WRONG_HI,
89
90 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_TRUMP_WRONG) })]
92 Trump = sys::RETURN_TRUMP_WRONG,
93
94 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_FIRST_WRONG) })]
96 First = sys::RETURN_FIRST_WRONG,
97
98 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_PLAY_FAULT) })]
103 AnalysePlay = sys::RETURN_PLAY_FAULT,
104
105 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_PBN_FAULT) })]
107 PBN = sys::RETURN_PBN_FAULT,
108
109 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_TOO_MANY_BOARDS) })]
111 TooManyBoards = sys::RETURN_TOO_MANY_BOARDS,
112
113 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_THREAD_CREATE) })]
115 ThreadCreate = sys::RETURN_THREAD_CREATE,
116
117 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_THREAD_WAIT) })]
119 ThreadWait = sys::RETURN_THREAD_WAIT,
120
121 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_THREAD_MISSING) })]
123 ThreadMissing = sys::RETURN_THREAD_MISSING,
124
125 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_NO_SUIT) })]
127 NoSuit = sys::RETURN_NO_SUIT,
128
129 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_TOO_MANY_TABLES) })]
131 TooManyTables = sys::RETURN_TOO_MANY_TABLES,
132
133 #[error("{}", unsafe { core::str::from_utf8_unchecked(sys::TEXT_CHUNK_SIZE) })]
135 ChunkSize = sys::RETURN_CHUNK_SIZE,
136}
137
138impl SystemError {
139 pub const fn propagate<T: Copy>(x: T, status: i32) -> Result<T, Self> {
147 match status {
148 0.. => Ok(x),
149 sys::RETURN_ZERO_CARDS => Err(Self::ZeroCards),
150 sys::RETURN_TARGET_TOO_HIGH => Err(Self::TargetTooHigh),
151 sys::RETURN_DUPLICATE_CARDS => Err(Self::DuplicateCards),
152 sys::RETURN_TARGET_WRONG_LO => Err(Self::NegativeTarget),
153 sys::RETURN_TARGET_WRONG_HI => Err(Self::InvalidTarget),
154 sys::RETURN_SOLNS_WRONG_LO => Err(Self::LowSolvingParameter),
155 sys::RETURN_SOLNS_WRONG_HI => Err(Self::HighSolvingParameter),
156 sys::RETURN_TOO_MANY_CARDS => Err(Self::TooManyCards),
157 sys::RETURN_SUIT_OR_RANK => Err(Self::CurrentSuitOrRank),
158 sys::RETURN_PLAYED_CARD => Err(Self::PlayedCard),
159 sys::RETURN_CARD_COUNT => Err(Self::CardCount),
160 sys::RETURN_THREAD_INDEX => Err(Self::ThreadIndex),
161 sys::RETURN_MODE_WRONG_LO => Err(Self::NegativeModeParameter),
162 sys::RETURN_MODE_WRONG_HI => Err(Self::HighModeParameter),
163 sys::RETURN_TRUMP_WRONG => Err(Self::Trump),
164 sys::RETURN_FIRST_WRONG => Err(Self::First),
165 sys::RETURN_PLAY_FAULT => Err(Self::AnalysePlay),
166 sys::RETURN_PBN_FAULT => Err(Self::PBN),
167 sys::RETURN_TOO_MANY_BOARDS => Err(Self::TooManyBoards),
168 sys::RETURN_THREAD_CREATE => Err(Self::ThreadCreate),
169 sys::RETURN_THREAD_WAIT => Err(Self::ThreadWait),
170 sys::RETURN_THREAD_MISSING => Err(Self::ThreadMissing),
171 sys::RETURN_NO_SUIT => Err(Self::NoSuit),
172 sys::RETURN_TOO_MANY_TABLES => Err(Self::TooManyTables),
173 sys::RETURN_CHUNK_SIZE => Err(Self::ChunkSize),
174 _ => Err(Self::UnknownFault),
175 }
176 }
177}
178
179#[derive(Debug, Error)]
181pub enum Error {
182 #[error(transparent)]
184 System(SystemError),
185
186 #[error("The thread pool is poisoned")]
188 Poison,
189}
190
191impl From<SystemError> for Error {
192 fn from(err: SystemError) -> Self {
193 Self::System(err)
194 }
195}
196
197impl<T> From<PoisonError<T>> for Error {
198 fn from(_: PoisonError<T>) -> Self {
199 Self::Poison
200 }
201}
202
203bitflags::bitflags! {
204 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
206 pub struct StrainFlags : u8 {
207 const CLUBS = 0x01;
209 const DIAMONDS = 0x02;
211 const HEARTS = 0x04;
213 const SPADES = 0x08;
215 const NOTRUMP = 0x10;
217 }
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
222pub struct TricksRow(u16);
223
224impl TricksRow {
225 #[must_use]
227 pub const fn new(n: u8, e: u8, s: u8, w: u8) -> Self {
228 Self(
229 (n as u16) << (4 * Seat::North as u8)
230 | (e as u16) << (4 * Seat::East as u8)
231 | (s as u16) << (4 * Seat::South as u8)
232 | (w as u16) << (4 * Seat::West as u8),
233 )
234 }
235
236 #[must_use]
238 pub const fn get(self, seat: Seat) -> u8 {
239 (self.0 >> (4 * seat as u8) & 0xF) as u8
240 }
241
242 #[must_use]
244 pub fn hex(self, seat: Seat) -> impl fmt::UpperHex {
245 TricksRowHex { deal: self, seat }
246 }
247}
248
249struct TricksRowHex {
250 deal: TricksRow,
251 seat: Seat,
252}
253
254impl fmt::UpperHex for TricksRowHex {
255 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
256 write!(
257 f,
258 "{:X}{:X}{:X}{:X}",
259 self.deal.get(self.seat),
260 self.deal.get(self.seat + Wrapping(1)),
261 self.deal.get(self.seat + Wrapping(2)),
262 self.deal.get(self.seat + Wrapping(3)),
263 )
264 }
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
269pub struct TricksTable(pub [TricksRow; 5]);
270
271impl core::ops::Index<Strain> for TricksTable {
272 type Output = TricksRow;
273
274 fn index(&self, strain: Strain) -> &TricksRow {
275 &self.0[strain as usize]
276 }
277}
278
279struct TricksTableHex<T: AsRef<[Strain]>> {
280 deal: TricksTable,
281 seat: Seat,
282 strains: T,
283}
284
285impl<T: AsRef<[Strain]>> fmt::UpperHex for TricksTableHex<T> {
286 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
287 for &strain in self.strains.as_ref() {
288 self.deal[strain].hex(self.seat).fmt(f)?;
289 }
290 Ok(())
291 }
292}
293
294impl TricksTable {
295 #[must_use]
297 pub fn hex(self, seat: Seat, strains: impl AsRef<[Strain]>) -> impl fmt::UpperHex {
298 TricksTableHex {
299 deal: self,
300 seat,
301 strains,
302 }
303 }
304}
305
306impl Strain {
307 #[must_use]
309 const fn to_sys(self) -> usize {
310 match self {
311 Self::Spades => 0,
312 Self::Hearts => 1,
313 Self::Diamonds => 2,
314 Self::Clubs => 3,
315 Self::Notrump => 4,
316 }
317 }
318}
319
320impl From<sys::ddTableResults> for TricksTable {
321 fn from(table: sys::ddTableResults) -> Self {
322 const fn make_row(row: [c_int; 4]) -> TricksRow {
323 #[allow(clippy::cast_sign_loss)]
324 TricksRow::new(
325 (row[0] & 0xFF) as u8,
326 (row[1] & 0xFF) as u8,
327 (row[2] & 0xFF) as u8,
328 (row[3] & 0xFF) as u8,
329 )
330 }
331
332 Self([
333 make_row(table.resTable[Strain::Clubs.to_sys()]),
334 make_row(table.resTable[Strain::Diamonds.to_sys()]),
335 make_row(table.resTable[Strain::Hearts.to_sys()]),
336 make_row(table.resTable[Strain::Spades.to_sys()]),
337 make_row(table.resTable[Strain::Notrump.to_sys()]),
338 ])
339 }
340}
341
342impl From<TricksTable> for sys::ddTableResults {
343 fn from(table: TricksTable) -> Self {
344 const fn make_row(row: TricksRow) -> [c_int; 4] {
345 [
346 row.get(Seat::North) as c_int,
347 row.get(Seat::East) as c_int,
348 row.get(Seat::South) as c_int,
349 row.get(Seat::West) as c_int,
350 ]
351 }
352
353 Self {
354 resTable: [
355 make_row(table[Strain::Spades]),
356 make_row(table[Strain::Hearts]),
357 make_row(table[Strain::Diamonds]),
358 make_row(table[Strain::Clubs]),
359 make_row(table[Strain::Notrump]),
360 ],
361 }
362 }
363}
364
365impl From<Deal> for sys::ddTableDeal {
366 fn from(deal: Deal) -> Self {
367 Self {
368 cards: deal.0.map(|hand| {
369 [
370 hand[Suit::Spades].to_bits().into(),
371 hand[Suit::Hearts].to_bits().into(),
372 hand[Suit::Diamonds].to_bits().into(),
373 hand[Suit::Clubs].to_bits().into(),
374 ]
375 }),
376 }
377 }
378}
379
380pub fn solve_deal(deal: Deal) -> Result<TricksTable, Error> {
385 let mut result = sys::ddTableResults::default();
386 let _guard = THREAD_POOL.lock()?;
387 let status = unsafe { sys::CalcDDtable(deal.into(), &mut result) };
389 Ok(SystemError::propagate(result.into(), status)?)
390}
391
392pub unsafe fn solve_deal_segment(
404 deals: &[Deal],
405 flags: StrainFlags,
406) -> Result<sys::ddTablesRes, Error> {
407 debug_assert!(deals.len() * flags.bits().count_ones() as usize <= sys::MAXNOOFBOARDS as usize);
408 let mut pack = sys::ddTableDeals {
409 noOfTables: c_int::try_from(deals.len()).unwrap_or(c_int::MAX),
410 ..Default::default()
411 };
412 deals
413 .iter()
414 .enumerate()
415 .for_each(|(i, &deal)| pack.deals[i] = deal.into());
416
417 let mut res = sys::ddTablesRes::default();
418 let _guard = THREAD_POOL.lock()?;
419 let status = sys::CalcAllTables(
420 &mut pack,
421 -1,
422 &mut [
423 c_int::from(!flags.contains(StrainFlags::SPADES)),
424 c_int::from(!flags.contains(StrainFlags::HEARTS)),
425 c_int::from(!flags.contains(StrainFlags::DIAMONDS)),
426 c_int::from(!flags.contains(StrainFlags::CLUBS)),
427 c_int::from(!flags.contains(StrainFlags::NOTRUMP)),
428 ][0],
429 &mut res,
430 &mut sys::allParResults::default(),
431 );
432 Ok(SystemError::propagate(res, status)?)
433}
434
435pub fn solve_deals(deals: &[Deal], flags: StrainFlags) -> Result<Vec<TricksTable>, Error> {
443 let mut tables = Vec::new();
444 for chunk in deals.chunks((sys::MAXNOOFBOARDS / flags.bits().count_ones()) as usize) {
445 tables.extend(
446 unsafe { solve_deal_segment(chunk, flags) }?.results[..chunk.len()]
448 .iter()
449 .map(|&x| TricksTable::from(x)),
450 );
451 }
452 Ok(tables)
453}
454
455bitflags::bitflags! {
456 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
458 pub struct Vulnerability: u8 {
459 const NS = 1;
461 const EW = 2;
463 }
464}
465
466impl Vulnerability {
467 #[must_use]
469 #[inline]
470 pub const fn to_sys(self) -> i32 {
471 const ALL: u8 = Vulnerability::all().bits();
472 const NS: u8 = Vulnerability::NS.bits();
473 const EW: u8 = Vulnerability::EW.bits();
474
475 match self.bits() {
476 0 => 0,
477 ALL => 1,
478 NS => 2,
479 EW => 3,
480 _ => unreachable!(),
481 }
482 }
483
484 #[must_use]
490 #[inline]
491 pub const fn swap(self, condition: bool) -> Self {
492 Self::from_bits_truncate(self.bits() * (condition as u8 * 3 + 2) / 2)
493 }
494}
495
496const _: () = {
498 const ALL: Vulnerability = Vulnerability::all();
499 const NONE: Vulnerability = Vulnerability::empty();
500
501 assert!(matches!(ALL.swap(true), ALL));
502 assert!(matches!(NONE.swap(true), NONE));
503 assert!(matches!(Vulnerability::NS.swap(true), Vulnerability::EW));
504 assert!(matches!(Vulnerability::EW.swap(true), Vulnerability::NS));
505
506 assert!(matches!(ALL.swap(false), ALL));
507 assert!(matches!(NONE.swap(false), NONE));
508 assert!(matches!(Vulnerability::NS.swap(false), Vulnerability::NS));
509 assert!(matches!(Vulnerability::EW.swap(false), Vulnerability::EW));
510};
511
512#[derive(Debug, Clone)]
514pub struct Par {
515 pub score: i32,
517
518 pub contracts: Vec<(Contract, Seat, i8)>,
523}
524
525impl PartialEq for Par {
526 fn eq(&self, other: &Self) -> bool {
527 fn key(contracts: &[(Contract, Seat, i8)]) -> u32 {
531 contracts
532 .iter()
533 .map(|&(contract, seat, _)| 1 << ((contract.bid.strain as u8) << 2 | seat as u8))
534 .fold(0, u32::bitor)
535 }
536 self.score == other.score && key(&self.contracts) == key(&other.contracts)
537 }
538}
539
540impl Eq for Par {}
541
542impl From<sys::parResultsMaster> for Par {
543 fn from(par: sys::parResultsMaster) -> Self {
544 #[allow(clippy::cast_sign_loss)]
547 let len = par.number as usize * usize::from(par.contracts[0].level != 0);
548
549 #[allow(clippy::cast_sign_loss)]
550 let contracts = par.contracts[..len]
551 .iter()
552 .flat_map(|contract| {
553 let strain = [
554 Strain::Notrump,
555 Strain::Spades,
556 Strain::Hearts,
557 Strain::Diamonds,
558 Strain::Clubs,
559 ][contract.denom as usize];
560
561 let (penalty, overtricks) = if contract.underTricks > 0 {
562 assert!(contract.underTricks <= 13);
563 (Penalty::Doubled, -((contract.underTricks & 0xFF) as i8))
564 } else {
565 assert!(contract.overTricks >= 0 && contract.overTricks <= 13);
566 (Penalty::None, (contract.overTricks & 0xFF) as i8)
567 };
568
569 assert_eq!(contract.level, contract.level & 7);
570 let seat: Seat = unsafe { core::mem::transmute((contract.seats & 3) as u8) };
572 let is_pair = contract.seats >= 4;
573 let contract = Contract::new((contract.level & 7) as u8, strain, penalty);
574
575 core::iter::once((contract, seat, overtricks)).chain(if is_pair {
576 Some((contract, seat + core::num::Wrapping(2), overtricks))
577 } else {
578 None
579 })
580 })
581 .collect();
582
583 Self {
584 score: par.score,
585 contracts,
586 }
587 }
588}
589
590pub fn calculate_par(
599 tricks: TricksTable,
600 vul: Vulnerability,
601 dealer: Seat,
602) -> Result<Par, SystemError> {
603 let mut par = sys::parResultsMaster::default();
604 let status = unsafe { sys::DealerParBin(&mut tricks.into(), &mut par, vul.to_sys(), dealer as c_int) };
606 Ok(SystemError::propagate(par, status)?.into())
607}
608
609pub fn calculate_pars(tricks: TricksTable, vul: Vulnerability) -> Result<[Par; 2], SystemError> {
617 let mut pars = [sys::parResultsMaster::default(); 2];
618 let status = unsafe { sys::SidesParBin(&mut tricks.into(), &mut pars[0], vul.to_sys()) };
620 Ok(SystemError::propagate(pars, status)?.map(Into::into))
621}
622
623#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
630pub enum Target {
631 Any(i8),
636
637 All(i8),
642
643 Legal,
647}
648
649impl Target {
650 #[must_use]
652 #[inline]
653 pub const fn target(self) -> c_int {
654 match self {
655 Self::Any(target) | Self::All(target) => target as c_int,
656 Self::Legal => -1,
657 }
658 }
659
660 #[must_use]
662 #[inline]
663 pub const fn solutions(self) -> c_int {
664 match self {
665 Self::Any(_) => 1,
666 Self::All(_) => 2,
667 Self::Legal => 3,
668 }
669 }
670}
671
672#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
674pub struct Board {
675 pub trump: Strain,
677 pub lead: Seat,
679 pub current_cards: [Option<Card>; 3],
681 pub deal: Deal,
683}
684
685impl From<Board> for sys::deal {
686 fn from(board: Board) -> Self {
687 let mut suits = [0; 3];
688 let mut ranks = [0; 3];
689
690 for (i, card) in board.current_cards.into_iter().flatten().enumerate() {
691 suits[i] = 3 - card.suit() as c_int;
692 ranks[i] = c_int::from(card.rank());
693 }
694
695 Self {
696 trump: match board.trump {
697 Strain::Spades => 0,
698 Strain::Hearts => 1,
699 Strain::Diamonds => 2,
700 Strain::Clubs => 3,
701 Strain::Notrump => 4,
702 },
703 first: board.lead as c_int,
704 currentTrickSuit: suits,
705 currentTrickRank: ranks,
706 remainCards: sys::ddTableDeal::from(board.deal).cards,
707 }
708 }
709}
710
711#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
713pub struct Play {
714 pub card: Card,
719
720 pub equals: Holding,
726
727 pub score: i8,
729}
730
731#[derive(Debug, Clone, Copy, PartialEq, Eq)]
733pub struct FoundPlays {
734 pub plays: [Option<Play>; 13],
736 pub nodes: u32,
738}
739
740impl From<sys::futureTricks> for FoundPlays {
741 #[allow(clippy::cast_sign_loss)]
742 fn from(future: sys::futureTricks) -> Self {
743 let mut plays = [None; 13];
744 plays
745 .iter_mut()
746 .enumerate()
747 .take(future.cards as usize)
748 .for_each(|(i, play)| {
749 let card = Card::new(
750 Suit::DESC[future.suit[i] as usize],
751 (future.rank[i] & 0xFF) as u8,
752 );
753 let equals = Holding::from_bits_truncate((future.equals[i] & 0xFFFF) as u16);
754 let score = (future.score[i] & 0xFF) as i8;
755 *play = Some(Play {
756 card,
757 equals,
758 score,
759 });
760 });
761 Self {
762 plays,
763 nodes: future.nodes as u32,
764 }
765 }
766}
767
768pub fn solve_board(board: Board, target: Target) -> Result<FoundPlays, Error> {
776 let mut result = sys::futureTricks::default();
777 let status = unsafe {
779 let _guard = THREAD_POOL.lock()?;
780 sys::SolveBoard(
781 board.into(),
782 target.target(),
783 target.solutions(),
784 0,
785 &mut result,
786 0,
788 )
789 };
790 Ok(SystemError::propagate(result, status)?.into())
791}
792
793pub unsafe fn solve_board_segment(args: &[(Board, Target)]) -> Result<sys::solvedBoards, Error> {
803 debug_assert!(args.len() <= sys::MAXNOOFBOARDS as usize);
804 let mut pack = sys::boards {
805 noOfBoards: c_int::try_from(args.len()).unwrap_or(c_int::MAX),
806 ..Default::default()
807 };
808 args.iter().enumerate().for_each(|(i, &(board, target))| {
809 pack.deals[i] = board.into();
810 pack.target[i] = target.target();
811 pack.solutions[i] = target.solutions();
812 });
813 let mut res = sys::solvedBoards::default();
814 let _guard = THREAD_POOL.lock()?;
815 let status = unsafe { sys::SolveAllBoardsBin(&mut pack, &mut res) };
817 Ok(SystemError::propagate(res, status)?)
818}
819
820pub fn solve_boards(args: &[(Board, Target)]) -> Result<Vec<FoundPlays>, Error> {
827 let mut solutions = Vec::new();
828 for chunk in args.chunks(sys::MAXNOOFBOARDS as usize) {
829 solutions.extend(
830 unsafe { solve_board_segment(chunk) }?.solvedBoard[..chunk.len()]
832 .iter()
833 .map(|&x| FoundPlays::from(x)),
834 );
835 }
836 Ok(solutions)
837}