1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//! Par-contract results and their conversion from DDS FFI types
use super::ffi;
use crate::contract::{Bid, Contract, Penalty};
use crate::seat::Seat;
use core::ops::BitOr as _;
use dds_bridge_sys as sys;
/// Par contract
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ParContract {
/// The contract
pub contract: Contract,
/// The declarer of the contract
pub declarer: Seat,
/// The number of overtricks (negative for undertricks)
pub overtricks: i8,
}
/// Par score and contracts
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Par {
/// The par score
pub score: i32,
/// The contracts that achieve the par score
pub contracts: Vec<ParContract>,
}
impl Par {
/// Check if two pars are equivalent
///
/// Two pars are equivalent if they have the same par score and the same
/// set of (strain, declarer) pairs. Overtricks and duplicate entries are
/// ignored.
///
/// This is intentionally looser than [`PartialEq`], which compares every
/// field exactly. `equivalent` exists because DDS may report the same
/// par result with different overtrick counts or orderings depending on
/// the code path (e.g. `DealerParBin` vs `SidesParBin`). Use `==` when
/// you need exact structural equality; use `equivalent` when you only
/// care about the strategic meaning of the par result.
#[must_use]
pub fn equivalent(&self, other: &Self) -> bool {
// Since every contract scores the same, we can compare only the set of
// (`Strain`, `Seat`). #`Strain` * #`Seat` = 5 * 4 = 20, which fits
// in a `u32` as a bitset.
fn key(contracts: &[ParContract]) -> u32 {
contracts
.iter()
.map(|p| 1 << ((p.contract.bid.strain as u8) << 2 | p.declarer as u8))
.fold(0, u32::bitor)
}
self.score == other.score && key(&self.contracts) == key(&other.contracts)
}
}
impl From<sys::parResultsMaster> for Par {
fn from(par: sys::parResultsMaster) -> Self {
let number = ffi::count_from_sys(par.number, par.contracts.len());
// DDS returns a zero contract for par-zero deals, but we want to filter
// it out for consistency.
let len = number * usize::from(par.contracts[0].level != 0);
let contracts = par.contracts[..len]
.iter()
.flat_map(|contract| {
let strain = ffi::strain_from_denom(contract.denom);
#[allow(clippy::cast_possible_truncation)]
let (penalty, overtricks) = if contract.underTricks > 0 {
(Penalty::Doubled, -contract.underTricks as i8)
} else {
(Penalty::Undoubled, contract.overTricks as i8)
};
let seat = match contract.seats & 3 {
0 => Seat::North,
1 => Seat::East,
2 => Seat::South,
3 => Seat::West,
_ => unreachable!("The bitmask ensures this is always in 0..=3"),
};
let is_pair = contract.seats >= 4;
let contract = Contract {
bid: Bid {
level: ffi::level_from_sys(contract.level),
strain,
},
penalty,
};
core::iter::once(ParContract {
contract,
declarer: seat,
overtricks,
})
.chain(is_pair.then_some(ParContract {
contract,
declarer: seat.partner(),
overtricks,
}))
})
.collect();
Self {
score: par.score,
contracts,
}
}
}