use std::{
convert::{TryFrom, TryInto},
fmt::Display,
};
use crate::{error::*, RollHistory, RollResult};
enum Element {
Fire(([Outcome; 10], [&'static str; 5])),
Earth(([Outcome; 10], [&'static str; 5])),
Metal(([Outcome; 10], [&'static str; 5])),
Water(([Outcome; 10], [&'static str; 5])),
Wood(([Outcome; 10], [&'static str; 5])),
}
enum Side {
Yin,
Yang,
}
enum Outcome {
Success,
Lucky,
Ill,
Loksyu(Side),
TinJi,
}
const FIRE: [Outcome; 10] = [
Outcome::TinJi, Outcome::Success, Outcome::Loksyu(Side::Yang), Outcome::Ill, Outcome::Lucky, Outcome::TinJi, Outcome::Success, Outcome::Loksyu(Side::Yin), Outcome::Ill, Outcome::Lucky, ];
const EARTH: [Outcome; 10] = [
Outcome::Loksyu(Side::Yang), Outcome::Ill, Outcome::Lucky, Outcome::TinJi, Outcome::Success, Outcome::Loksyu(Side::Yin), Outcome::Ill, Outcome::Lucky, Outcome::TinJi, Outcome::Success, ];
const METAL: [Outcome; 10] = [
Outcome::Lucky, Outcome::TinJi, Outcome::Success, Outcome::Loksyu(Side::Yin), Outcome::Ill, Outcome::Lucky, Outcome::TinJi, Outcome::Success, Outcome::Loksyu(Side::Yang), Outcome::Ill, ];
const WATER: [Outcome; 10] = [
Outcome::Success, Outcome::Loksyu(Side::Yin), Outcome::Ill, Outcome::Lucky, Outcome::TinJi, Outcome::Success, Outcome::Loksyu(Side::Yang), Outcome::Ill, Outcome::Lucky, Outcome::TinJi, ];
const WOOD: [Outcome; 10] = [
Outcome::Ill, Outcome::Lucky, Outcome::TinJi, Outcome::Success, Outcome::Loksyu(Side::Yang), Outcome::Ill, Outcome::Lucky, Outcome::TinJi, Outcome::Success, Outcome::Loksyu(Side::Yin), ];
const FIRE_SUIT_EN: [&str; 5] = ["㊋ fire", "㊏ earth", "㊍ wood", "㊎ metal", "㊌ water"];
const FIRE_SUIT_FR: [&str; 5] = ["㊋ feu", "㊏ terre", "㊍ bois", "㊎ métal", "㊌ eau"];
const EARTH_SUIT_EN: [&str; 5] = ["㊏ earth", "㊎ metal", "㊋ fire", "㊌ water", "㊍ wood"];
const EARTH_SUIT_FR: [&str; 5] = ["㊏ terre", "㊎ métal", "㊋ feu", "㊌ eau", "㊍ bois"];
const METAL_SUIT_EN: [&str; 5] = ["㊎ metal", "㊌ water", "㊏ earth", "㊍ wood", "㊋ fire"];
const METAL_SUIT_FR: [&str; 5] = ["㊎ métal", "㊌ eau", "㊏ terre", "㊍ bois", "㊋ feu"];
const WATER_SUIT_EN: [&str; 5] = ["㊌ water", "㊍ wood", "㊎ metal", "㊋ fire", "㊏ earth"];
const WATER_SUIT_FR: [&str; 5] = ["㊌ eau", "㊍ bois", "㊎ métal", "㊋ feu", "㊏ terre"];
const WOOD_SUIT_EN: [&str; 5] = ["㊍ wood", "㊋ fire", "㊌ water", "㊏ earth", "㊎ metal"];
const WOOD_SUIT_FR: [&str; 5] = ["㊍ bois", "㊋ feu", "㊌ eau", "㊏ terre", "㊎ métal"];
#[derive(Debug, Default)]
pub struct CdeResult {
pub success: u32,
pub lucky: u32,
pub ill: u32,
pub loksyu: (u32, u32), pub tin_ji: u32,
pub history: Option<RollHistory>, pub elements: [&'static str; 5],
}
impl PartialEq for CdeResult {
fn eq(&self, other: &Self) -> bool {
self.success == other.success
&& self.lucky == other.lucky
&& self.ill == other.ill
&& self.loksyu == other.loksyu
&& self.tin_ji == other.tin_ji
}
}
impl TryFrom<&str> for Element {
type Error = &'static str;
fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
match s.to_lowercase().as_str() {
"fire" => Ok(Element::Fire((FIRE, FIRE_SUIT_EN))),
"feu" => Ok(Element::Fire((FIRE, FIRE_SUIT_FR))),
"earth" => Ok(Element::Earth((EARTH, EARTH_SUIT_EN))),
"terre" => Ok(Element::Earth((EARTH, EARTH_SUIT_FR))),
"metal" => Ok(Element::Metal((METAL, METAL_SUIT_EN))),
"métal" => Ok(Element::Metal((METAL, METAL_SUIT_FR))),
"eau" => Ok(Element::Water((WATER, WATER_SUIT_FR))),
"water" => Ok(Element::Water((WATER, WATER_SUIT_EN))),
"bois" => Ok(Element::Wood((WOOD, WOOD_SUIT_FR))),
"wood" => Ok(Element::Wood((WOOD, WOOD_SUIT_EN))),
_ => Err("Element must be one of `fire`, `earth`, `metal`, `fire` or `wood"),
}
}
}
impl Display for CdeResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let prefixes = if self.elements.contains(&"㊋ feu") {
["Succès", "Dé-fastes", "Dé-néfastes"]
} else {
["Success", "Lucky dice", "Ill dice"]
};
write!(
f,
r#"{}
{} ({}): {}
{} ({}): {}
{} ({}): {}
Loksyu ({}): {} ● Yin / {} ○ Yang
Tin Ji ({}): {}
"#,
self.history.as_ref().unwrap(),
prefixes[0],
self.elements[0],
self.success,
prefixes[1],
self.elements[1],
self.lucky,
prefixes[2],
self.elements[2],
self.ill,
self.elements[3],
self.loksyu.0,
self.loksyu.1,
self.elements[4],
self.tin_ji
)
}
}
pub fn compute_cde(res: &RollResult, element: &str) -> Result<CdeResult> {
let history = res
.as_single()
.ok_or("Not a single roll result")?
.get_history();
if history.len() != 1 {
Err("Should have only one roll".into())
} else {
let res = history
.iter()
.flat_map(|v| {
if let RollHistory::Roll(dices_res) = v {
Some(dices_res)
} else {
None
}
})
.next()
.ok_or("RollHistory must be a Roll variant")?
.clone();
let mapping: Element = element.try_into()?;
let (mapping, elements) = match mapping {
Element::Fire(m) => m,
Element::Earth(m) => m,
Element::Metal(m) => m,
Element::Water(m) => m,
Element::Wood(m) => m,
};
let mut result = res.iter().fold(CdeResult::default(), |mut acc, v| {
match &mapping[(v.res - 1) as usize] {
Outcome::Success => acc.success += 1,
Outcome::Lucky => acc.lucky += 1,
Outcome::Ill => acc.ill += 1,
Outcome::Loksyu(side) => match side {
Side::Yin => acc.loksyu.0 += 1,
Side::Yang => acc.loksyu.1 += 1,
},
Outcome::TinJi => acc.tin_ji += 1,
}
acc
});
result.history = history.get(0).cloned();
result.elements = elements;
Ok(result)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{tests::IteratorDiceRollSource, Critic, DiceResult, Roller};
#[test]
fn test_cde() {
let roll_mock = vec![1, 2, 3, 4, 5, 7, 10, 5];
let r = Roller::new(&format!("{}d10", roll_mock.len())).unwrap();
let roll_res = r
.roll_with_source(&mut IteratorDiceRollSource {
iterator: &mut roll_mock.clone().into_iter(),
})
.unwrap();
let res = compute_cde(&roll_res, "fire").unwrap();
let expected = CdeResult {
success: 2,
lucky: 3,
ill: 1,
loksyu: (0, 1),
tin_ji: 1,
history: Some(RollHistory::Roll(
roll_mock
.iter()
.map(|v| DiceResult {
res: *v,
crit: Critic::No,
})
.collect(),
)),
elements: Default::default(), };
assert_eq!(expected, res);
println!("{}", res);
let roll_res = r
.roll_with_source(&mut IteratorDiceRollSource {
iterator: &mut roll_mock.clone().into_iter(),
})
.unwrap();
let res = compute_cde(&roll_res, "earth").unwrap();
let expected = CdeResult {
success: 3,
lucky: 1,
ill: 2,
loksyu: (0, 1),
tin_ji: 1,
history: Some(RollHistory::Roll(
roll_mock
.iter()
.map(|v| DiceResult {
res: *v,
crit: Critic::No,
})
.collect(),
)),
elements: Default::default(), };
assert_eq!(expected, res);
println!("{}", res);
let roll_res = r
.roll_with_source(&mut IteratorDiceRollSource {
iterator: &mut roll_mock.clone().into_iter(),
})
.unwrap();
let res = compute_cde(&roll_res, "metal").unwrap();
let expected = CdeResult {
success: 1,
lucky: 1,
ill: 3,
loksyu: (1, 0),
tin_ji: 2,
history: Some(RollHistory::Roll(
roll_mock
.iter()
.map(|v| DiceResult {
res: *v,
crit: Critic::No,
})
.collect(),
)),
elements: Default::default(), };
assert_eq!(expected, res);
println!("{}", res);
let roll_res = r
.roll_with_source(&mut IteratorDiceRollSource {
iterator: &mut roll_mock.clone().into_iter(),
})
.unwrap();
let res = compute_cde(&roll_res, "water").unwrap();
let expected = CdeResult {
success: 1,
lucky: 1,
ill: 1,
loksyu: (1, 1),
tin_ji: 3,
history: Some(RollHistory::Roll(
roll_mock
.iter()
.map(|v| DiceResult {
res: *v,
crit: Critic::No,
})
.collect(),
)),
elements: Default::default(), };
assert_eq!(expected, res);
println!("{}", res);
let roll_res = r
.roll_with_source(&mut IteratorDiceRollSource {
iterator: &mut roll_mock.clone().into_iter(),
})
.unwrap();
let res = compute_cde(&roll_res, "bois").unwrap();
let expected = CdeResult {
success: 1,
lucky: 2,
ill: 1,
loksyu: (1, 2),
tin_ji: 1,
history: Some(RollHistory::Roll(
roll_mock
.iter()
.map(|v| DiceResult {
res: *v,
crit: Critic::No,
})
.collect(),
)),
elements: Default::default(), };
assert_eq!(expected, res);
println!("{}", res);
}
}