use serde::{Deserialize, Serialize};
use crate::map::Placed;
use crate::relations::{self, Outcome, Relation, Relations, RELATIONS_VERSION};
pub const SESSION_VERSION: u32 = 1;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Session {
pub version: u32,
pub turns: Vec<Turn>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Expect {
Act,
Ask,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Turn {
pub request: String,
pub expect: Expect,
#[serde(default)]
pub keeps: Vec<Relation>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Played {
pub asked: bool,
pub placements: Vec<Placed>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Ask {
Right,
Over,
Under,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Regression {
pub turn: usize,
pub relation: usize,
pub now: Outcome,
}
#[derive(Clone, Debug, PartialEq)]
pub struct TurnScore {
pub ask: Ask,
pub own: Vec<Outcome>,
pub broken: Vec<Regression>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Score {
pub turns: Vec<TurnScore>,
}
impl Score {
pub fn over_asked(&self) -> usize {
self.turns.iter().filter(|t| t.ask == Ask::Over).count()
}
pub fn under_asked(&self) -> usize {
self.turns.iter().filter(|t| t.ask == Ask::Under).count()
}
pub fn unmet(&self) -> usize {
self.turns
.iter()
.flat_map(|t| &t.own)
.filter(|o| !matches!(o, Outcome::Met { .. }))
.count()
}
pub fn regressions(&self) -> usize {
self.turns.iter().map(|t| t.broken.len()).sum()
}
pub fn passed(&self) -> bool {
self.over_asked() + self.under_asked() + self.unmet() + self.regressions() == 0
}
}
impl Session {
pub fn parse(text: &str) -> Result<Session, String> {
let s: Session = ron::from_str(text).map_err(|e| format!("session: parse: {e}"))?;
if s.version != SESSION_VERSION {
return Err(format!(
"session: version {} is not the {SESSION_VERSION} this build reads",
s.version
));
}
for (i, t) in s.turns.iter().enumerate() {
let text = as_relations(&t.keeps).to_ron()?;
Relations::parse(&text).map_err(|e| format!("session: turn {i}: {e}"))?;
}
Ok(s)
}
pub fn to_ron(&self) -> Result<String, String> {
ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default())
.map_err(|e| format!("session: write: {e}"))
}
}
fn as_relations(keeps: &[Relation]) -> Relations {
Relations {
version: RELATIONS_VERSION,
relations: keeps.to_vec(),
}
}
pub fn score(session: &Session, played: &[Played]) -> Result<Score, String> {
if played.len() != session.turns.len() {
return Err(format!(
"session: {} turn(s) played for a session of {}",
played.len(),
session.turns.len()
));
}
let keeps: Vec<Relations> = session.turns.iter().map(|t| as_relations(&t.keeps)).collect();
let mut held: Vec<Vec<bool>> = Vec::with_capacity(keeps.len());
let mut turns = Vec::with_capacity(keeps.len());
for (i, (turn, p)) in session.turns.iter().zip(played).enumerate() {
let ask = match (turn.expect, p.asked) {
(Expect::Act, true) => Ask::Over,
(Expect::Ask, false) => Ask::Under,
_ => Ask::Right,
};
let rows = relations::in_radians(&p.placements);
let own = relations::check(&rows, &keeps[i]);
let mut broken = Vec::new();
for (j, earlier) in keeps[..i].iter().enumerate() {
for (r, now) in relations::check(&rows, earlier).into_iter().enumerate() {
let met = matches!(now, Outcome::Met { .. });
if held[j][r] && !met {
broken.push(Regression { turn: j, relation: r, now });
}
held[j][r] = met;
}
}
held.push(own.iter().map(|o| matches!(o, Outcome::Met { .. })).collect());
turns.push(TurnScore { ask, own, broken });
}
Ok(Score { turns })
}