use std::cmp::Ordering;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LoopyWinner {
Left,
Right,
Draw,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LoopyPartizanOutcome {
pub left_to_move: LoopyWinner,
pub right_to_move: LoopyWinner,
}
impl LoopyPartizanOutcome {
pub fn new(left_to_move: LoopyWinner, right_to_move: LoopyWinner) -> Self {
Self {
left_to_move,
right_to_move,
}
}
pub fn partizan_class(&self) -> Option<PartizanOutcome> {
use LoopyWinner::*;
match (self.left_to_move, self.right_to_move) {
(Right, Left) => Some(PartizanOutcome::P),
(Left, Right) => Some(PartizanOutcome::N),
(Left, Left) => Some(PartizanOutcome::L),
(Right, Right) => Some(PartizanOutcome::R),
(Draw, Draw) => Some(PartizanOutcome::Draw),
_ => None,
}
}
pub fn has_draw(&self) -> bool {
self.left_to_move == LoopyWinner::Draw || self.right_to_move == LoopyWinner::Draw
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PartizanOutcome {
P,
N,
L,
R,
Draw,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LoopyValue {
Zero,
Star,
On,
Off,
Over,
Under,
PlusMinus,
Tis,
Tisn,
OnsideOffside { onside: i128, offside: i128 },
Dud,
}
impl LoopyValue {
pub fn onside_offside(onside: i128, offside: i128) -> LoopyValue {
if onside == 0 && offside == 0 {
LoopyValue::Zero
} else {
LoopyValue::OnsideOffside { onside, offside }
}
}
pub fn form(&self) -> String {
match self {
LoopyValue::Zero => "{|}".to_string(),
LoopyValue::Star => "{0|0}".to_string(),
LoopyValue::On => "{on|}".to_string(),
LoopyValue::Off => "{|off}".to_string(),
LoopyValue::Over => "{0|over}".to_string(),
LoopyValue::Under => "{under|0}".to_string(),
LoopyValue::PlusMinus => "{on|off}".to_string(),
LoopyValue::Tis => "{0|tisn}".to_string(),
LoopyValue::Tisn => "{tis|0}".to_string(),
LoopyValue::OnsideOffside { onside, offside } => {
format!("{onside}&{offside}")
}
LoopyValue::Dud => "{dud|dud}".to_string(),
}
}
pub fn name(&self) -> String {
match self {
LoopyValue::Zero => "0".to_string(),
LoopyValue::Star => "*".to_string(),
LoopyValue::On => "on".to_string(),
LoopyValue::Off => "off".to_string(),
LoopyValue::Over => "over".to_string(),
LoopyValue::Under => "under".to_string(),
LoopyValue::PlusMinus => "±".to_string(),
LoopyValue::Tis => "tis".to_string(),
LoopyValue::Tisn => "tisn".to_string(),
LoopyValue::OnsideOffside { onside, offside } => {
format!("{onside}&{offside}")
}
LoopyValue::Dud => "dud".to_string(),
}
}
pub fn outcome(&self) -> LoopyPartizanOutcome {
use LoopyWinner::*;
match self {
LoopyValue::Zero => LoopyPartizanOutcome::new(Right, Left),
LoopyValue::Star | LoopyValue::PlusMinus => LoopyPartizanOutcome::new(Left, Right),
LoopyValue::On | LoopyValue::Over => LoopyPartizanOutcome::new(Left, Left),
LoopyValue::Off | LoopyValue::Under => LoopyPartizanOutcome::new(Right, Right),
LoopyValue::Tis => LoopyPartizanOutcome::new(Left, Draw),
LoopyValue::Tisn => LoopyPartizanOutcome::new(Draw, Right),
LoopyValue::OnsideOffside { onside, offside } => {
LoopyPartizanOutcome::new(winner_from_sign(*onside), winner_from_sign(*offside))
}
LoopyValue::Dud => LoopyPartizanOutcome::new(Draw, Draw),
}
}
pub fn partizan_outcome(&self) -> Option<PartizanOutcome> {
self.outcome().partizan_class()
}
pub fn sides(&self) -> Option<(i128, i128)> {
match *self {
LoopyValue::Tis => Some((1, 0)),
LoopyValue::Tisn => Some((0, -1)),
LoopyValue::OnsideOffside { onside, offside } => Some((onside, offside)),
_ => None,
}
}
pub fn neg(&self) -> LoopyValue {
match self {
LoopyValue::Zero => LoopyValue::Zero,
LoopyValue::Star => LoopyValue::Star,
LoopyValue::On => LoopyValue::Off,
LoopyValue::Off => LoopyValue::On,
LoopyValue::Over => LoopyValue::Under,
LoopyValue::Under => LoopyValue::Over,
LoopyValue::PlusMinus => LoopyValue::PlusMinus,
LoopyValue::Tis => LoopyValue::Tisn,
LoopyValue::Tisn => LoopyValue::Tis,
LoopyValue::OnsideOffside { onside, offside } => {
LoopyValue::onside_offside(-*offside, -*onside)
}
LoopyValue::Dud => LoopyValue::Dud,
}
}
pub fn is_stopper(&self) -> bool {
!matches!(self, LoopyValue::Dud | LoopyValue::Tis | LoopyValue::Tisn)
}
pub fn add(&self, other: &LoopyValue) -> Option<LoopyValue> {
use LoopyValue::*;
let r = match (*self, *other) {
(Dud, _) | (_, Dud) => Dud,
(Zero, x) | (x, Zero) => x,
(On, On) => On,
(Off, Off) => Off,
(On, Off) | (Off, On) => Dud,
(On, Star)
| (Star, On)
| (On, Over)
| (Over, On)
| (On, Under)
| (Under, On)
| (On, PlusMinus)
| (PlusMinus, On) => On,
(Off, Star)
| (Star, Off)
| (Off, Over)
| (Over, Off)
| (Off, Under)
| (Under, Off)
| (Off, PlusMinus)
| (PlusMinus, Off) => Off,
(Star, Star) => Zero,
(Over, Over) | (Star, Over) | (Over, Star) => Over,
(Under, Under) | (Star, Under) | (Under, Star) => Under,
(
OnsideOffside {
onside: a,
offside: b,
},
OnsideOffside {
onside: c,
offside: d,
},
) => LoopyValue::onside_offside(a + c, b + d),
(Over, Under) | (Under, Over) => return None,
(PlusMinus, PlusMinus)
| (PlusMinus, Star)
| (Star, PlusMinus)
| (PlusMinus, Over)
| (Over, PlusMinus)
| (PlusMinus, Under)
| (Under, PlusMinus)
| (Tis, _)
| (_, Tis)
| (Tisn, _)
| (_, Tisn)
| (OnsideOffside { .. }, _)
| (_, OnsideOffside { .. }) => return None,
};
Some(r)
}
}
impl fmt::Display for LoopyValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
fn winner_from_sign(x: i128) -> LoopyWinner {
if x > 0 {
LoopyWinner::Left
} else if x < 0 {
LoopyWinner::Right
} else {
LoopyWinner::Draw
}
}
impl PartialOrd for LoopyValue {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
use LoopyValue::*;
if self == other {
return Some(Ordering::Equal);
}
match (*self, *other) {
(Dud, _) | (_, Dud) => None,
(PlusMinus, _)
| (_, PlusMinus)
| (Tis, _)
| (_, Tis)
| (Tisn, _)
| (_, Tisn)
| (OnsideOffside { .. }, _)
| (_, OnsideOffside { .. }) => None,
(On, _) => Some(Ordering::Greater),
(_, On) => Some(Ordering::Less),
(Off, _) => Some(Ordering::Less),
(_, Off) => Some(Ordering::Greater),
(Star, Zero) | (Zero, Star) => None,
(Star, Over) | (Under, Star) => Some(Ordering::Less),
(Over, Star) | (Star, Under) => Some(Ordering::Greater),
(a, b) => {
let rank = |v: LoopyValue| match v {
Under => -1i128,
Zero => 0,
Over => 1,
_ => unreachable!("on/off/star/dud handled above"),
};
Some(rank(a).cmp(&rank(b)))
}
}
}
}