use std::{collections::BTreeMap, io::Write};
use kdam::tqdm;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use crate::{ combrs::bagspace::{BagSpace, SetSpace}, deck::{Card, CardMask, Rank, RankMask, Suit}, hand::{refhand5::RefHand5, Hand5, Hand, Rank5}};
#[derive(Debug, Serialize, Deserialize)]
pub struct LutRank {
pub allsuited: Vec<u16>,
pub nonsuited: Vec<u16>,
pub orders2hands: Vec<Hand>,
pub bagspace_nonsuited: BagSpace,
pub setspace_allsuited: SetSpace,
}
impl LutRank {
pub fn new() -> Self {
Self::from_brute_force()
}
pub fn from_brute_force() -> Self {
let engine_ref = RefHand5::new();
let mut bts_hands = BTreeMap::<Hand, Vec<u64>>::new();
let max_ranks = 7;
let mut ranks_space = Rank::ALL.iter().rev()
.map(|&rank| Some(rank))
.chain([None])
.collect::<Vec<Option<Rank>>>();
ranks_space.reverse();
assert!(ranks_space.len() == 14, "expected 14 ranks (13 normal + 1 for empty)");
let bagspace_nonsuited = BagSpace::new(ranks_space.len(), max_ranks);
let setspace_allsuited = SetSpace::new(ranks_space.len());
for ranks in tqdm!(ranks_space.iter().combinations_with_replacement(max_ranks)) {
if ranks.iter()
.chunk_by(|&rank| rank)
.into_iter()
.any(|(value, chunk)| value.is_some() && chunk.count() >= 5)
{
continue;
}
let mut seq = vec![0usize; max_ranks];
let mut i = 0;
let mut cards = CardMask::NONE;
for rank in ranks.iter() {
if let Some(rank) = rank {
let not_of_rank = !cards.of_rank(*rank);
assert!(not_of_rank.count() > 0, "expected at least card left");
let suit = not_of_rank.iter()
.min_by_key(|&suit| cards.of_suit(suit).count())
.unwrap();
assert!(cards.of_suit(suit).count() < 4, "expected suit to have less than 4 cards");
let card = Card::new(*rank, suit);
assert!(!cards.contains(card.into()), "expected card not to be in the mask");
cards = cards | card.into();
seq[i] = (1 + rank.index()) as usize;
i += 1;
} else {
seq[i] = 0;
i += 1;
}
}
assert!(i == seq.len(), "expected sequence to be complete");
assert!(seq.len() == bagspace_nonsuited.num_seq, "expected sequence to be complete");
assert!(seq.is_sorted(), "expected sequence to be sorted");
let key_nonsuited = bagspace_nonsuited.enc::<usize, usize>(&seq);
let hand_nonsuited = engine_ref.hand5(cards);
bts_hands.entry(hand_nonsuited).or_insert(vec![]).push(key_nonsuited as u64);
if cards.unsuited().count() >= 5 {
let cards = cards.unsuited().iter()
.fold(CardMask::NONE, |acc, rank| acc | Card::new(rank, Suit::Hearts).into());
assert!(cards.count() >= 5, "expected at least 5 cards");
for i in 0..max_ranks {
seq[i] = 0;
}
for (idx, rank) in cards.unsuited().iter_reverse().enumerate() {
seq[max_ranks - idx - 1] = (1 + rank.index()) as usize;
}
let key_allsuited = cards.unsuited().bits() as usize;
let hand_allsuited = engine_ref.hand5(cards);
bts_hands.entry(hand_allsuited).or_insert(vec![]).push(key_allsuited as u64);
}
}
let max_key_nonsuited = bts_hands.iter().map(|(_, keys)| keys.iter().max().unwrap()).max().unwrap();
let max_order = bts_hands.len() + 1;
let max_key_allsuited = (1 << 13) - 1;
let mut allsuited_ranks = vec![0u16; (max_key_allsuited + 1) as usize];
let mut nonsuited_ranks = vec![0u16; (max_key_nonsuited + 1) as usize];
let mut orders2hands = vec![Hand::HighCard { kickers: RankMask::NONE }; (max_order + 1) as usize];
for (order, (&hand, keys)) in bts_hands.iter().enumerate() {
let order = order + 1;
match hand {
Hand::StraightFlush { .. } | Hand::Flush { .. } => {
for key in keys {
allsuited_ranks[*key as usize] = order as u16;
orders2hands[order as usize] = hand;
}
}
_ => {
for key in keys {
nonsuited_ranks[*key as usize] = order as u16;
orders2hands[order as usize] = hand;
}
}
}
}
Self { allsuited: allsuited_ranks, nonsuited: nonsuited_ranks, orders2hands, bagspace_nonsuited, setspace_allsuited }
}
pub fn cards2key(&self, cards: CardMask) -> u64 {
let mut seq = [0usize; 7];
let mut i = 0;
for &rank in Rank::ALL.iter().rev() {
for _ in 0..(cards.of_rank_count(rank)) {
seq[self.bagspace_nonsuited.num_seq - i - 1] = (1 + rank.index()) as usize;
i += 1;
}
}
let idx = self.bagspace_nonsuited.enc::<usize, u64>(&seq);
idx
}
pub fn ranks2key(&self, ranks: RankMask) -> u64 {
let mut seq = [0usize; 7];
let mut i = 0;
for rank in ranks.iter_reverse() {
seq[self.bagspace_nonsuited.num_seq - i - 1] = (1 + rank.index()) as usize;
i += 1;
}
let idx = self.bagspace_nonsuited.enc::<usize, u64>(&seq);
idx
}
pub fn find(&self, cards: CardMask) -> Option<u16> {
let mut max_res: Option<u16> = None;
Suit::ALL.iter().for_each(|&suit| {
let ranks = cards.of_suit(suit);
if ranks.count() >= 5 {
let key = ranks.bits() as usize;
max_res = max_res.max(self.allsuited.get(key).map(|&x| x));
}
});
let key = self.cards2key(cards);
max_res = max_res.max(self.nonsuited.get(key as usize).map(|&x| x));
max_res
}
pub fn write_markdown(&self, writer: &mut impl Write) -> Result<(), Box<dyn std::error::Error>> {
writeln!(writer, "# Ordered Ranking of All 5-card Hands")?;
writeln!(writer, "")?;
writeln!(writer, "This file contains the ordered 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, "| Order | Description |")?;
writeln!(writer, "|-------|--------------------------------------------------|")?;
for (order, hand) in self.orders2hands.iter().enumerate() {
if order == 0 {
continue;
}
match hand {
Hand::StraightFlush { .. } | Hand::Flush { .. } => {
}
_ => {
writeln!(writer, "| {:>5} | {:<48} |", order, hand.to_string())?;
}
}
}
writeln!(writer, "|-------|--------------------------------------------------|")?;
writeln!(writer, "")?;
writeln!(writer, "## All-Suited 5-card Hands")?;
writeln!(writer, "")?;
writeln!(writer, "| Order | Description |")?;
writeln!(writer, "|-------|--------------------------------------------------|")?;
for (order, hand) in self.orders2hands.iter().enumerate() {
if order == 0 {
continue;
}
match hand {
Hand::StraightFlush { .. } | Hand::Flush { .. } => {
writeln!(writer, "| {:>5} | {:<48} |", order, hand.to_string())?;
}
_ => {
}
}
}
writeln!(writer, "|-------|--------------------------------------------------|")?;
Ok(())
}
}
impl Rank5 for LutRank {
fn rank5(&self, cards: CardMask) -> u16 {
self.find(cards).unwrap()
}
}
impl Hand5 for LutRank {
fn hand5(&self, cards: CardMask) -> Hand {
match self.find(cards) {
Some(order) => self.orders2hands[order as usize],
_ => self.orders2hands[0]
}
}
}