use contract_bridge::{FullDeal, Seat, Strain};
use ddss::TrickCountTable;
const STRAINS: [Strain; 5] = [
Strain::Notrump,
Strain::Spades,
Strain::Hearts,
Strain::Diamonds,
Strain::Clubs,
];
#[must_use]
pub fn parse_line(line: &str) -> Option<(FullDeal, TrickCountTable)> {
if line.len() != 88 {
return None;
}
let deal = format!("W:{}", &line[..67]).parse().ok()?;
let table = TrickCountTable::from_gib(&line.as_bytes()[68..]);
Some((deal, table))
}
#[must_use]
pub fn format_line(deal: &FullDeal, table: &TrickCountTable) -> String {
let pbn = deal.display(Seat::West).to_string();
let body = &pbn[2..]; format!("{body}:{:X}", table.gib())
}
#[must_use]
pub fn relativized_tricks(table: &TrickCountTable, seat: Seat) -> Vec<f32> {
let mut out = Vec::with_capacity(STRAINS.len() * 4);
for &strain in &STRAINS {
let row = table[strain];
for s in [seat, seat.lho(), seat.partner(), seat.rho()] {
out.push(f32::from(row.get(s).get()) / 13.0);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
const LINE: &str =
"T5.K4.652.A98542 K6.QJT976.QT7.Q6 432.A.AKJ93.JT73 AQJ987.8532.84.K:65658888888843433232";
#[test]
fn line_round_trips() {
assert_eq!(LINE.len(), 88);
let (deal, table) = parse_line(LINE).expect("valid GIB line");
assert_eq!(format_line(&deal, &table), LINE);
}
#[test]
fn rejects_malformed() {
assert!(parse_line("").is_none());
assert!(parse_line(&LINE[..87]).is_none());
}
#[test]
fn label_is_seat_relative() {
let (_, table) = parse_line(LINE).expect("valid GIB line");
let label = relativized_tricks(&table, Seat::North);
assert_eq!(label.len(), 20);
assert!((label[4] - 8.0 / 13.0).abs() < 1e-6);
assert!((label[5] - 5.0 / 13.0).abs() < 1e-6);
}
}