use rbp_cards::*;
use rbp_core::*;
use std::hash::Hash;
#[derive(Default, Copy, Clone, Hash, Eq, PartialEq, Debug, PartialOrd, Ord)]
#[cfg_attr(feature = "client", derive(serde::Serialize, serde::Deserialize))]
pub struct Abstraction(u16);
const INDEX_MASK: u16 = 0xFF;
const STREET_BITS: u16 = 8;
const STREET_MASK: u16 = 0xFF << STREET_BITS;
impl Abstraction {
pub const DELIM: &'static str = "::";
pub const N: usize = rbp_core::KMEANS_EQTY_CLUSTER_COUNT - 1;
pub const fn size() -> usize {
rbp_core::KMEANS_EQTY_CLUSTER_COUNT
}
pub fn range() -> impl Iterator<Item = Self> {
(0..=Self::N).map(|i| Self::from((Street::Rive, i)))
}
pub const fn street(&self) -> Street {
match (self.0 & STREET_MASK) >> STREET_BITS {
0 => Street::Pref,
1 => Street::Flop,
2 => Street::Turn,
3 => Street::Rive,
_ => panic!("invalid street"),
}
}
pub const fn index(&self) -> usize {
(self.0 & INDEX_MASK) as usize
}
pub fn all(street: Street) -> Vec<Self> {
if street == Street::Rive {
Self::range().collect()
} else {
(0..street.k()).map(|i| Self::from((street, i))).collect()
}
}
fn quantize(p: Probability) -> usize {
(p * Self::N as Probability).round() as usize
}
fn floatize(q: usize) -> Probability {
q as Probability / Self::N as Probability
}
}
impl From<(Street, usize)> for Abstraction {
fn from((street, index): (Street, usize)) -> Self {
let hi_bits = (street as u16) << STREET_BITS;
let lo_bits = index as u16 & INDEX_MASK;
Self(hi_bits | lo_bits)
}
}
impl From<Street> for Abstraction {
fn from(street: Street) -> Self {
Self::from((street, rand::random_range(0..street.n_abstractions())))
}
}
impl From<Probability> for Abstraction {
fn from(p: Probability) -> Self {
debug_assert!(p >= 0.);
debug_assert!(p <= 1.);
Self::from((Street::Rive, Self::quantize(p)))
}
}
impl From<Abstraction> for Probability {
fn from(abstraction: Abstraction) -> Self {
match abstraction.street() {
Street::Rive => Abstraction::floatize(abstraction.index()),
_ => unreachable!("no non-river into probability"),
}
}
}
impl From<Abstraction> for u16 {
fn from(a: Abstraction) -> Self {
a.0
}
}
impl From<u16> for Abstraction {
fn from(n: u16) -> Self {
Self(n)
}
}
impl From<Abstraction> for i16 {
fn from(abstraction: Abstraction) -> Self {
abstraction.0 as i16
}
}
impl From<i16> for Abstraction {
fn from(n: i16) -> Self {
Self(n as u16)
}
}
impl TryFrom<&str> for Abstraction {
type Error = anyhow::Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let s = s.trim().split(Self::DELIM).collect::<Vec<_>>();
let a = s
.get(0)
.copied()
.ok_or_else(|| anyhow::anyhow!("broken delimiter"))?;
let b = s
.get(1)
.copied()
.ok_or_else(|| anyhow::anyhow!("broken delimiter"))?;
let street = Street::try_from(a).map_err(|e| anyhow::anyhow!(e))?;
let index = usize::from_str_radix(b, 16).map_err(|e| anyhow::anyhow!(e))?;
Ok(Abstraction::from((street, index)))
}
}
impl std::fmt::Display for Abstraction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}{}{:02x}",
self.street().symbol(),
Self::DELIM,
self.index()
)
}
}
impl Arbitrary for Abstraction {
fn random() -> Self {
let street = Street::Flop;
let k = street.k();
let i = rand::random_range(0..k);
Abstraction::from((street, i))
}
}
#[cfg(test)]
mod tests {
use super::*;
use rbp_core::Arbitrary;
#[test]
fn is_quantize_inverse_floatize() {
for p in (0..=100).map(|x| x as Probability / 100.) {
let q = Abstraction::quantize(p);
let f = Abstraction::floatize(q);
assert!((p - f).abs() < 1. / Abstraction::N as Probability);
}
}
#[test]
fn is_floatize_inverse_quantize() {
for q in 0..=Abstraction::N {
let p = Abstraction::floatize(q);
let i = Abstraction::quantize(p);
assert!(q == i);
}
}
#[test]
fn bijective_u16_random() {
let random = Abstraction::random();
assert_eq!(random, Abstraction::from(u16::from(random)));
}
#[test]
fn bijective_u16_equity() {
let equity = Abstraction::from(Observation::from(Street::Rive).equity());
assert_eq!(equity, Abstraction::from(u16::from(equity)));
}
#[test]
fn bijective_str() {
let abs = Abstraction::random();
let str = format!("{}", abs);
assert_eq!(abs, Abstraction::try_from(str.as_str()).unwrap());
}
#[test]
fn street_index_roundtrip() {
for street in Street::all() {
for i in 0..street.n_abstractions() {
let abs = Abstraction::from((street, i));
assert_eq!(abs.street(), street);
assert_eq!(abs.index(), i);
}
}
}
}