use std::{collections::BTreeMap, str::FromStr, io::Write};
use indexmap::IndexMap;
use kdam::tqdm;
use itertools::Itertools;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::{combrs::{multiset_decode, multiset_encode}, deck::{Card, CardMask, Rank, Suit}, hand::{refbest5::RefBest5, Best5, Hand, Rank5}};
#[cfg(feature = "include_lutbest5")]
const LUTBEST5_TEXT: &str = include_str!("lutbest5.json");
#[cfg(not(feature = "include_lutbest5"))]
const LUTBEST5_TEXT: &str = "";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct KeyLutCard5 {
data: u64,
}
impl KeyLutCard5 {
pub fn from_index(index: usize) -> Self {
Self { data: index as u64 }
}
pub fn from_cards(cards: CardMask) -> Self {
assert_eq!(cards.count(), 5, "expected exactly 5 cards");
let mut seq = vec![0; 5];
let mut rankmask = 0b1;
let mut pos = 0;
rankmask = rankmask | (rankmask << 13) | (rankmask << 26) | (rankmask << 39);
for rankidx in 0..13 {
for _ in 0..(cards.to_bits() & rankmask).count_ones() {
seq[pos] = rankidx;
if pos == 4 {
break;
}
pos += 1;
}
rankmask = rankmask << 1;
}
let index: usize = multiset_encode(&seq);
Self::from_index(index)
}
pub fn to_rank_str(&self) -> String {
let mut strs =Vec::new();
let mut items = vec![0; 5];
multiset_decode(self.data as usize, Rank::NUM, 5, &mut items);
for &decnum in items.iter() {
strs.push(Rank::from_index(decnum as u8).to_string());
}
strs.reverse();
strs.join("")
}
pub fn to_rank_vec(&self) -> Vec<Rank> {
let mut items = vec![0; 5];
multiset_decode(self.data as usize, Rank::NUM, 5, &mut items);
items.iter().map(|&decnum| Rank::from_index(decnum as u8)).collect()
}
}
impl Serialize for KeyLutCard5 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_rank_str())
}
}
impl<'de> Deserialize<'de> for KeyLutCard5 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let mut ranks = s.chars().map(|r| Rank::from_str(&r.to_string()).unwrap().to_index() as usize).collect::<Vec<usize>>();
ranks.sort();
Ok(KeyLutCard5 { data: multiset_encode(&ranks) })
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LutBest5 {
pub allsuited: Vec<u16>,
pub nonsuited: Vec<u16>,
}
impl LutBest5 {
pub fn new() -> Self {
Self::from_builtin()
}
pub fn from_builtin() -> Self {
serde_json::from_str::<Self>(&LUTBEST5_TEXT).unwrap()
}
pub fn from_brute_force() -> Self {
let engine_ref = RefBest5::new();
let mut bts_hands = BTreeMap::<Hand, KeyLutCard5>::new();
for ranks in tqdm!(Rank::ALL.iter().combinations_with_replacement(5)) {
if ranks.iter().all_equal() {
continue;
}
let mut mask_allsuited = CardMask::none();
let mut mask_nonsuited = CardMask::none();
for (idx, &&rank) in ranks.iter().enumerate() {
mask_allsuited = mask_allsuited.union(CardMask::from_single(Card::new(rank, Suit::Hearts)));
let mut tries = 0;
while mask_nonsuited.count() < idx+1 {
let suit_for_nonsuited = match (idx + tries) % 4 {
0 => Suit::Hearts,
1 => Suit::Diamonds,
2 => Suit::Clubs,
3 => Suit::Spades,
_ => panic!("invalid suit index: {}", idx),
};
mask_nonsuited = mask_nonsuited.union(CardMask::from_single(Card::new(rank, suit_for_nonsuited)));
tries += 1;
if tries > 4 {
panic!("failed to find a suit for the non-suited case");
}
}
}
if mask_allsuited.count() == 5 {
bts_hands.insert(engine_ref.best5(mask_allsuited).1, KeyLutCard5::from_cards(mask_allsuited));
}
bts_hands.insert(engine_ref.best5(mask_nonsuited).1, KeyLutCard5::from_cards(mask_nonsuited));
}
let mut allsuited = IndexMap::new();
let mut nonsuited = IndexMap::new();
for (rawidx, (hand, &key)) in bts_hands.iter().enumerate() {
let rank_index = rawidx + 1;
match hand {
Hand::StraightFlush { .. } | Hand::Flush { .. } => {
allsuited.insert(key, rank_index as u16);
}
_ => {
nonsuited.insert(key, rank_index as u16);
}
}
}
let max_idx_allsuited = allsuited.iter().map(|(&h,_)| h.data).max().unwrap();
let max_idx_nonsuited = nonsuited.iter().map(|(&h,_)| h.data).max().unwrap();
let mut allsuited_flat = vec![0u16; (max_idx_allsuited + 1) as usize];
let mut nonsuited_flat = vec![0u16; (max_idx_nonsuited + 1) as usize];
for (i, (&flat_idx, _)) in allsuited.iter().enumerate() {
allsuited_flat[flat_idx.data as usize] = (i + 1) as u16;
}
for (i, (&flat_idx, _)) in nonsuited.iter().enumerate() {
nonsuited_flat[flat_idx.data as usize] = (i + 1) as u16;
}
Self { allsuited: allsuited_flat, nonsuited: nonsuited_flat }
}
pub fn find(&self, cards: CardMask) -> Option<u16> {
assert_eq!(cards.count(), 5, "expected exactly 5 cards");
let bits = cards.to_bits();
let bits_suit = 0b1111111111111;
let is_suited = (bits & bits_suit == bits) || ((bits & (bits_suit << 13) == bits)) || ((bits & (bits_suit << 26) == bits)) || ((bits & (bits_suit << 39) == bits));
let key = KeyLutCard5::from_cards(cards);
if is_suited {
Some(*self.allsuited.get(key.data as usize).unwrap())
} else {
Some(*self.nonsuited.get(key.data as usize).unwrap())
}
}
pub fn write_markdown(&self, writer: &mut impl Write) -> Result<(), Box<dyn std::error::Error>> {
let engine_ref = RefBest5::new();
writeln!(writer, "# Rankings of All 5-card Hands")?;
writeln!(writer, "")?;
writeln!(writer, "This file contains the rankings of all 5-card hands, sorted by rank. The ranks are 1-based, with 0 being used for special cases (i.e. no hand).")?;
writeln!(writer, "")?;
writeln!(writer, "## Non-Suited 5-card Hands")?;
writeln!(writer, "")?;
writeln!(writer, "| Rank | Cards | Description |")?;
writeln!(writer, "|-------|-------|------------------------------|")?;
let mut arr_nonsuited = self.nonsuited.iter()
.map(|&x| x)
.enumerate()
.filter(|(_, x)| *x > 0)
.collect::<Vec<(usize, u16)>>();
arr_nonsuited.sort_by_key(|(_, rankidx)| *rankidx);
for (cardsidx, rankidx) in arr_nonsuited {
let cards = KeyLutCard5::from_index(cardsidx);
let mut cards_possible = CardMask::none();
for (which_rank, &rank) in cards.to_rank_vec().iter().enumerate() {
let mut tries = 0;
while cards_possible.count() < which_rank + 1 {
let suit_for_nonsuited = match (which_rank + tries) % 4 {
0 => Suit::Hearts,
1 => Suit::Diamonds,
2 => Suit::Clubs,
3 => Suit::Spades,
_ => panic!("invalid suit index: {}", tries),
};
cards_possible = cards_possible.union(CardMask::from_single(Card::new(rank, suit_for_nonsuited)));
tries += 1;
if tries > 4 {
panic!("failed to find a suit for the non-suited case");
}
}
}
assert_eq!(cards, KeyLutCard5::from_cards(cards_possible));
assert_eq!(cards_possible.count(), 5);
let hand_ref = engine_ref.best5(cards_possible).1;
writeln!(writer, "| {:>5} | {:>5} | {:<28} |", rankidx, cards.to_rank_str(), hand_ref.to_string())?;
}
writeln!(writer, "|-------|-------|------------------------------|")?;
writeln!(writer, "")?;
writeln!(writer, "## All-Suited 5-card Hands")?;
writeln!(writer, "")?;
writeln!(writer, "| Rank | Cards | Description |")?;
writeln!(writer, "|-------|-------|------------------------------|")?;
let mut arr_allsuited = self.allsuited.iter()
.map(|&x| x)
.enumerate()
.filter(|(_, x)| *x > 0)
.collect::<Vec<(usize, u16)>>();
arr_allsuited.sort_by_key(|(_, rankidx)| *rankidx);
for (cardsidx, rankidx) in arr_allsuited {
let cards = KeyLutCard5::from_index(cardsidx);
let mut cards_possible = CardMask::none();
for &rank in cards.to_rank_vec().iter() {
cards_possible = cards_possible.union(CardMask::from_single(Card::new(rank, Suit::Hearts)));
}
assert_eq!(cards, KeyLutCard5::from_cards(cards_possible));
assert_eq!(cards_possible.count(), 5);
let hand_ref = engine_ref.best5(cards_possible).1;
writeln!(writer, "| {:>5} | {:>5} | {:<28} |", rankidx, cards.to_rank_str(), hand_ref.to_string())?;
}
Ok(())
}
}
impl Rank5 for LutBest5 {
fn rank5(&self, cards: CardMask) -> u32 {
if cards.count() == 5 {
self.find(cards).unwrap() as u32
} else {
assert!(cards.count() <= 15);
let mut best_rank = 0;
let mut cards_bits = [CardMask::none(); 15];
for (idx, card) in cards.iter().enumerate() {
cards_bits[idx] = CardMask::from_single(card);
}
for subset in cards_bits[..cards.count()].iter().combinations(5) {
let mut subset_mask = CardMask::none();
for &bits in subset {
subset_mask = subset_mask.union(bits);
}
assert_eq!(subset_mask.count(), 5);
let rank = self.find(subset_mask).unwrap() as u32;
if rank > best_rank {
best_rank = rank;
}
}
best_rank
}
}
}
#[cfg(test)]
mod tests {
use crate::combrs::binom;
use super::*;
#[test]
fn test_multiset_encdec() {
let n = 13;
let k = 5;
for seq in (0..n).combinations_with_replacement(k) {
let mut seq = seq.iter().map(|x| *x as usize).collect::<Vec<usize>>();
seq.sort();
let idx: usize = multiset_encode(&seq);
let mut decoded = vec![0; k];
multiset_decode(idx, n, k, &mut decoded);
assert_eq!(seq, decoded);
}
for idx in 0..binom(n+k-1, k) {
let mut seq = vec![0; k];
multiset_decode(idx, n, k, &mut seq);
let idx_redo: usize = multiset_encode(&seq);
assert_eq!(idx, idx_redo);
}
}
}