use std::ops::ControlFlow; use cast_trait::Cast; use internal_iterator::{InternalIterator, IntoInternalIterator}; use crate::board::{Outcome, Player}; use crate::pov::{NonPov, Pov, ScalarAbs}; /// The outcome of a game from the POV of a certain player. Usually obtained using [Outcome::pov]. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum OutcomeWDL { Win, Draw, Loss, } /// A collection of [win, draw, loss] values. #[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct WDL { pub win: V, pub draw: V, pub loss: V, } #[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct WDLAbs { pub win_a: V, pub draw: V, pub win_b: V, } impl Outcome { /// Convert this to a [WDLAbs] with a one at the correct place and zero otherwise. pub fn to_wdl_abs(self) -> WDLAbs { let mut result = WDLAbs::default(); *match self { Outcome::WonBy(Player::A) => &mut result.win_a, Outcome::WonBy(Player::B) => &mut result.win_b, Outcome::Draw => &mut result.draw, } = V::one(); result } /// Convert a win (for a) to `1`, draw to `0` and loss (for a) to `-1`. pub fn sign>(self) -> ScalarAbs { match self { Outcome::WonBy(Player::A) => ScalarAbs::new(V::one()), Outcome::Draw => ScalarAbs::new(V::zero()), Outcome::WonBy(Player::B) => ScalarAbs::new(-V::one()), } } } impl OutcomeWDL { /// Convert this to a [WDL] with a one at the correct place and zero otherwise. pub fn to_wdl(self) -> WDL { let mut result = WDL::default(); *match self { OutcomeWDL::Win => &mut result.win, OutcomeWDL::Draw => &mut result.draw, OutcomeWDL::Loss => &mut result.loss, } = V::one(); result } /// Convert a win to `1`, draw to `0` and loss to `-1`. pub fn sign>(self) -> V { match self { OutcomeWDL::Win => V::one(), OutcomeWDL::Draw => V::zero(), OutcomeWDL::Loss => -V::one(), } } /// The reverse of [Outcome::pov]. pub fn un_pov(self, pov: Player) -> Outcome { match self { OutcomeWDL::Win => Outcome::WonBy(pov), OutcomeWDL::Draw => Outcome::Draw, OutcomeWDL::Loss => Outcome::WonBy(pov.other()), } } /// Pick the best possible outcome, assuming `Win > Draw > Loss`. /// Make sure to flip the child values as appropriate, this function assumes everything is form the parent POV. pub fn best>(children: I) -> OutcomeWDL { Self::best_maybe(children.into_internal_iter().map(Some)).unwrap() } /// Pick the best possible outcome, assuming `Some(Win) > None > Some(Draw) > Some(Loss)`. /// Make sure to flip the child values as appropriate, this function assumes everything is form the parent POV. pub fn best_maybe>>(children: I) -> Option { let mut any_unknown = false; let mut all_known_are_loss = true; let control = children.into_internal_iter().try_for_each(|child| { match child { None => { any_unknown = true; } Some(OutcomeWDL::Win) => { return ControlFlow::Break(()); } Some(OutcomeWDL::Draw) => { all_known_are_loss = false; } Some(OutcomeWDL::Loss) => {} } ControlFlow::Continue(()) }); if let ControlFlow::Break(()) = control { Some(OutcomeWDL::Win) } else if any_unknown { None } else if all_known_are_loss { Some(OutcomeWDL::Loss) } else { Some(OutcomeWDL::Draw) } } } impl NonPov for WDLAbs { type Output = WDL; fn pov(self, pov: Player) -> WDL { let (win, loss) = match pov { Player::A => (self.win_a, self.win_b), Player::B => (self.win_b, self.win_a), }; WDL { win, draw: self.draw, loss, } } } impl Pov for WDL { type Output = WDLAbs; fn un_pov(self, pov: Player) -> Self::Output { let (win_a, win_b) = match pov { Player::A => (self.win, self.loss), Player::B => (self.loss, self.win), }; WDLAbs { win_a, draw: self.draw, win_b, } } } impl WDLAbs { pub fn new(win_a: V, draw: V, win_b: V) -> Self { Self { win_a, draw, win_b } } } impl WDL { pub fn new(win: V, draw: V, loss: V) -> Self { WDL { win, draw, loss } } pub fn to_slice(self) -> [V; 3] { [self.win, self.draw, self.loss] } } impl WDL { pub fn nan() -> WDL { WDL { win: V::nan(), draw: V::nan(), loss: V::nan(), } } pub fn normalized(self) -> WDL { self / self.sum() } } impl WDLAbs { pub fn nan() -> WDLAbs { WDLAbs { win_a: V::nan(), draw: V::nan(), win_b: V::nan(), } } } impl WDLAbs { pub fn try_to_outcome(self) -> Option { let outcomes = [Outcome::WonBy(Player::A), Outcome::Draw, Outcome::WonBy(Player::B)]; outcomes.iter().copied().find(|&o| o.to_wdl_abs() == self) } } impl WDL { pub fn try_to_outcome_wdl(self) -> Option { let outcomes = [OutcomeWDL::Win, OutcomeWDL::Draw, OutcomeWDL::Loss]; outcomes.iter().copied().find(|&o| o.to_wdl() == self) } } impl WDL { pub fn cast(self) -> WDL where V: Cast, { WDL { win: self.win.cast(), draw: self.draw.cast(), loss: self.loss.cast(), } } } impl> WDL { pub fn value(self) -> V { self.win - self.loss } } impl> WDLAbs { pub fn value(self) -> ScalarAbs { ScalarAbs::new(self.win_a - self.win_b) } } impl> WDL { pub fn sum(self) -> V { self.win + self.draw + self.loss } } impl> WDLAbs { pub fn sum(self) -> V { self.win_a + self.draw + self.win_b } } impl NonPov for Outcome { type Output = OutcomeWDL; fn pov(self, pov: Player) -> OutcomeWDL { match self { Outcome::WonBy(player) => { if player == pov { OutcomeWDL::Win } else { OutcomeWDL::Loss } } Outcome::Draw => OutcomeWDL::Draw, } } } impl Pov for OutcomeWDL { type Output = Outcome; fn un_pov(self, pov: Player) -> Outcome { match self { OutcomeWDL::Win => Outcome::WonBy(pov), OutcomeWDL::Draw => Outcome::Draw, OutcomeWDL::Loss => Outcome::WonBy(pov.other()), } } } impl> std::ops::Add> for WDL { type Output = WDL; fn add(self, rhs: WDL) -> Self::Output { WDL { win: self.win + rhs.win, draw: self.draw + rhs.draw, loss: self.loss + rhs.loss, } } } impl> std::ops::Sub> for WDL { type Output = WDL; fn sub(self, rhs: WDL) -> Self::Output { WDL { win: self.win - rhs.win, draw: self.draw - rhs.draw, loss: self.loss - rhs.loss, } } } impl> std::ops::AddAssign> for WDL { fn add_assign(&mut self, rhs: WDL) { *self = *self + rhs; } } impl> std::ops::Mul for WDL { type Output = WDL; fn mul(self, rhs: V) -> Self::Output { WDL { win: self.win * rhs, draw: self.draw * rhs, loss: self.loss * rhs, } } } impl> std::ops::Div for WDL { type Output = WDL; fn div(self, rhs: V) -> Self::Output { WDL { win: self.win / rhs, draw: self.draw / rhs, loss: self.loss / rhs, } } } impl> std::iter::Sum for WDL { fn sum>(iter: I) -> Self { iter.fold(Self::default(), |a, v| a + v) } } impl<'a, V: Default + Copy + std::ops::Add> std::iter::Sum<&'a Self> for WDL { fn sum>(iter: I) -> Self { iter.fold(Self::default(), |a, &v| a + v) } } impl> std::ops::Add> for WDLAbs { type Output = WDLAbs; fn add(self, rhs: WDLAbs) -> Self::Output { WDLAbs { win_a: self.win_a + rhs.win_a, draw: self.draw + rhs.draw, win_b: self.win_b + rhs.win_b, } } } impl> std::ops::Sub> for WDLAbs { type Output = WDLAbs; fn sub(self, rhs: WDLAbs) -> Self::Output { WDLAbs { win_a: self.win_a - rhs.win_a, draw: self.draw - rhs.draw, win_b: self.win_b - rhs.win_b, } } } impl> std::ops::AddAssign> for WDLAbs { fn add_assign(&mut self, rhs: WDLAbs) { *self = *self + rhs; } } impl> std::ops::Mul for WDLAbs { type Output = WDLAbs; fn mul(self, rhs: V) -> Self::Output { WDLAbs { win_a: self.win_a * rhs, draw: self.draw * rhs, win_b: self.win_b * rhs, } } } impl> std::ops::Div for WDLAbs { type Output = WDLAbs; fn div(self, rhs: V) -> Self::Output { WDLAbs { win_a: self.win_a / rhs, draw: self.draw / rhs, win_b: self.win_b / rhs, } } } impl> std::iter::Sum for WDLAbs { fn sum>(iter: I) -> Self { iter.fold(Self::default(), |a, v| a + v) } }