use crate::divination_method::DivinationMethod;
use num_bigint::BigInt;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Line {
Unbroken { changing: bool },
Broken { changing: bool },
}
impl Line {
pub fn new_random(divination_method: DivinationMethod) -> Self {
match divination_method {
DivinationMethod::CoinToss => Self::from_coin_tosses(),
DivinationMethod::AncientYarrowStalk => Self::from_yarrow_stalks(),
}
}
pub fn is_changing(&self) -> bool {
match self {
Self::Broken { changing } | Self::Unbroken { changing } => *changing,
}
}
pub const fn broken() -> Self {
Self::Broken { changing: false }
}
pub const fn broken_changing() -> Self {
Self::Broken { changing: true }
}
pub const fn unbroken() -> Self {
Self::Unbroken { changing: false }
}
pub const fn unbroken_changing() -> Self {
Self::Unbroken { changing: true }
}
pub fn from_coin_tosses() -> Self {
match fastrand::u8(1..=16) {
1..=2 => Line::broken_changing(),
3..=8 => Line::broken(),
9..=10 => Line::unbroken_changing(),
11..=16 => Line::unbroken(),
_ => unreachable!(),
}
}
pub fn from_yarrow_stalks() -> Self {
match fastrand::u8(1..=16) {
1 => Line::broken_changing(),
2..=8 => Line::broken(),
9..=11 => Line::unbroken_changing(),
12..=16 => Line::unbroken(),
_ => unreachable!(),
}
}
pub fn settle(&self) -> Line {
match self {
Self::Broken { changing: false } | Self::Unbroken { changing: true } => Self::broken(),
Self::Unbroken { changing: false } | Self::Broken { changing: true } => {
Self::unbroken()
}
}
}
pub fn print_big(&self) {
use crate::symbols::big_line::*;
match self {
Self::Broken { changing: true } => print!("{BROKEN_CHANGING}"),
Self::Broken { changing: false } => print!("{BROKEN}"),
Self::Unbroken { changing: true } => print!("{UNBROKEN_CHANGING}"),
Self::Unbroken { changing: false } => print!("{UNBROKEN}"),
};
}
fn try_from<N>(n: N) -> Result<Self, Error>
where
N: Into<BigInt> + TryInto<u8> + Copy,
{
let n = TryInto::<u8>::try_into(n).map_err(|_err| Error::IntegerOutOfRange(n.into()))?;
match n {
6 => Ok(Line::broken_changing()),
7 => Ok(Line::unbroken()),
8 => Ok(Line::broken()),
9 => Ok(Line::unbroken_changing()),
_ => Err(Error::IntegerOutOfRange(n.into())),
}
}
}
impl TryFrom<u8> for Line {
type Error = Error;
fn try_from(n: u8) -> Result<Self, Self::Error> {
Line::try_from(n)
}
}
impl TryFrom<u32> for Line {
type Error = Error;
fn try_from(n: u32) -> Result<Self, Self::Error> {
Line::try_from(n)
}
}
impl TryFrom<i32> for Line {
type Error = Error;
fn try_from(n: i32) -> Result<Self, Self::Error> {
Line::try_from(n)
}
}
impl fmt::Display for Line {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let line_string = match self {
Line::Broken { changing: true } => "-X-",
Line::Broken { changing: false } => "- -",
Line::Unbroken { changing: true } => "-O-",
Line::Unbroken { changing: false } => "---",
};
write!(f, "{line_string}")
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(
"Invalid conversion from integer to Line. Integer must be between 6-9 inclusive but was {0}"
)]
IntegerOutOfRange(BigInt),
}