use std::io::{BufRead, BufReader, BufWriter, Read, Write};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use thiserror::Error;
use crate::{Answer, Day, Part, Year};
pub const CORRECT_ANSWER_CHAR: char = '=';
pub const WRONG_ANSWER_CHAR: char = 'X';
pub const LOW_ANSWER_CHAR: char = '[';
pub const HIGH_ANSWER_CHAR: char = ']';
#[derive(Debug, PartialEq)]
pub struct Puzzle {
pub day: Day,
pub year: Year,
pub input: String,
pub part_one_answers: Answers,
pub part_two_answers: Answers,
}
impl Puzzle {
pub fn answers(&self, part: Part) -> &Answers {
match part {
Part::One => &self.part_one_answers,
Part::Two => &self.part_two_answers,
}
}
pub fn answers_mut(&mut self, part: Part) -> &mut Answers {
match part {
Part::One => &mut self.part_one_answers,
Part::Two => &mut self.part_two_answers,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum CheckResult {
Correct,
Wrong,
TooLow,
TooHigh,
}
#[derive(Debug, PartialEq)]
pub struct Answers {
correct_answer: Option<Answer>,
wrong_answers: Vec<Answer>,
low_bounds: Option<i128>,
high_bounds: Option<i128>,
}
impl Answers {
pub fn new() -> Self {
Answers {
correct_answer: None,
wrong_answers: Vec::new(),
low_bounds: None,
high_bounds: None,
}
}
pub fn correct_answer_ref(&self) -> &Option<Answer> {
&self.correct_answer
}
pub fn wrong_answers_ref(&self) -> &Vec<Answer> {
&self.wrong_answers
}
pub fn low_bounds_ref(&self) -> &Option<i128> {
&self.low_bounds
}
pub fn high_bounds_ref(&self) -> &Option<i128> {
&self.high_bounds
}
pub fn check(&self, answer: &Answer) -> Option<CheckResult> {
match (answer.to_i128(), &self.low_bounds, &self.high_bounds) {
(Some(answer), Some(low), _) if answer <= *low => {
return Some(CheckResult::TooLow);
}
(Some(answer), _, Some(high)) if answer >= *high => return Some(CheckResult::TooHigh),
_ => {}
};
for wrong_answer in &self.wrong_answers {
if wrong_answer == answer {
return Some(CheckResult::Wrong);
}
}
match &self.correct_answer {
Some(correct) if correct == answer => Some(CheckResult::Correct),
Some(_) => Some(CheckResult::Wrong),
None => None,
}
}
pub fn add_wrong_answer(&mut self, answer: Answer) {
if self.wrong_answers.iter().all(|x| x != &answer) {
self.wrong_answers.push(answer);
} else {
tracing::warn!(
"skipped adding duplicate wrong answer to answers cache: `{}`",
answer
);
}
}
pub fn set_correct_answer(&mut self, answer: Answer) {
self.correct_answer = Some(answer);
}
pub fn set_low_bounds(&mut self, answer: Answer) -> i128 {
let answer = answer.to_i128().expect("low bounds answer must be numeric");
match &self.low_bounds {
Some(low) if answer > *low => {
self.low_bounds = Some(answer);
answer
}
Some(low) => *low,
None => {
self.low_bounds = Some(answer);
answer
}
}
}
pub fn set_high_bounds(&mut self, answer: Answer) -> i128 {
let answer = answer
.to_i128()
.expect("high bounds answer must be numeric");
match &self.high_bounds {
Some(high) if answer < *high => {
self.high_bounds = Some(answer);
answer
}
Some(high) => *high,
None => {
self.high_bounds = Some(answer);
answer
}
}
}
pub fn serialize_to_string(&self) -> String {
let mut buf = BufWriter::new(Vec::new());
self.serialize(&mut buf);
String::from_utf8(buf.into_inner().unwrap()).unwrap()
}
pub fn serialize<W: Write>(&self, writer: &mut BufWriter<W>) {
let mut wrong_answers: Vec<String> =
self.wrong_answers.iter().map(|x| x.to_string()).collect();
wrong_answers.sort();
fn write_field<S: ToString, W: Write>(
field: &Option<S>,
prefix: char,
writer: &mut BufWriter<W>,
) {
if let Some(f) = field {
let s = f.to_string();
assert!(!s.contains('\n'));
writeln!(writer, "{} {}", prefix, &s).unwrap();
}
}
write_field(&self.correct_answer, CORRECT_ANSWER_CHAR, writer);
write_field(&self.low_bounds, LOW_ANSWER_CHAR, writer);
write_field(&self.high_bounds, HIGH_ANSWER_CHAR, writer);
for wrong_answer in wrong_answers {
write_field(&Some(wrong_answer), WRONG_ANSWER_CHAR, writer);
}
}
pub fn deserialize_from_str(text: &str) -> Result<Self, AnswerDeserializationError> {
let mut buf = BufReader::new(text.as_bytes());
Self::deserialize(&mut buf)
}
pub fn deserialize<R: Read>(
reader: &mut BufReader<R>,
) -> Result<Self, AnswerDeserializationError> {
let mut answers = Answers::new();
for line in reader.lines() {
let line = line.unwrap();
let (ty, value) = line
.split_once(' ')
.ok_or_else(|| AnswerDeserializationError::UnknownLineFormat(line.clone()))?;
match ty.chars().next().unwrap() {
CORRECT_ANSWER_CHAR => {
answers.set_correct_answer(
Answer::from_str(value).expect("Answer::from_str does not return Err"),
);
}
WRONG_ANSWER_CHAR => {
answers.add_wrong_answer(
Answer::from_str(value).expect("Answer::from_str does not return Err"),
);
}
LOW_ANSWER_CHAR => {
let low = value.parse::<i128>().map_err(|_| {
AnswerDeserializationError::LowBoundRequiresInt(value.to_string())
})?;
answers.set_low_bounds(Answer::Int(low));
}
HIGH_ANSWER_CHAR => {
let high = value.parse::<i128>().map_err(|_| {
AnswerDeserializationError::HighBoundRequiresInt(value.to_string())
})?;
answers.set_high_bounds(Answer::Int(high));
}
c => {
return Err(AnswerDeserializationError::UnknownAnswerType(c));
}
}
}
Ok(answers)
}
}
impl Default for Answers {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Error)]
pub enum AnswerDeserializationError {
#[error(
"expected type char followed by a space followed by the answer value, but got `{}`", .0
)]
UnknownLineFormat(String),
#[error(
"unknown answer type char `{}` when deserializing (expected `{}`, `{}`, `{}`, or `{}`)",
.0,
CORRECT_ANSWER_CHAR,
WRONG_ANSWER_CHAR,
LOW_ANSWER_CHAR,
HIGH_ANSWER_CHAR
)]
UnknownAnswerType(char),
#[error("the low bound answer `{}` must be parsable as an i128 integer", .0)]
LowBoundRequiresInt(String),
#[error("the high bound answer `{}` must be parsable as an i128 integer", .0)]
HighBoundRequiresInt(String),
}
#[derive(Serialize, Deserialize)]
pub struct Session {
pub session_id: String,
pub submit_wait_until: Option<chrono::DateTime<chrono::Utc>>,
}
impl Session {
pub fn new<S: Into<String>>(session_id: S) -> Self {
Self {
session_id: session_id.into(),
submit_wait_until: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_wrong_answers() {
let mut answers = Answers::new();
answers.add_wrong_answer(Answer::from_str("hello world").unwrap());
answers.add_wrong_answer(Answer::from_str("foobar").unwrap());
answers.add_wrong_answer(Answer::Int(42));
assert_eq!(
answers.wrong_answers_ref(),
&vec![
Answer::String("hello world".to_string()),
Answer::String("foobar".to_string()),
Answer::Int(42)
]
)
}
#[test]
fn correct_answer_when_checking() {
let mut answers = Answers::new();
answers.set_correct_answer(Answer::from_str("hello").unwrap());
answers.add_wrong_answer(Answer::from_str("abc").unwrap());
answers.add_wrong_answer(Answer::from_str("stop").unwrap());
assert_eq!(
answers.check(&Answer::from_str("hello").unwrap()),
Some(CheckResult::Correct)
);
}
#[test]
fn wrong_answer_when_checking() {
let mut answers = Answers::new();
answers.set_correct_answer(Answer::from_str("hello").unwrap());
answers.add_wrong_answer(Answer::from_str("abc").unwrap());
answers.add_wrong_answer(Answer::from_str("stop").unwrap());
assert_eq!(
answers.check(&Answer::from_str("abc").unwrap()),
Some(CheckResult::Wrong)
);
assert_eq!(
answers.check(&Answer::from_str("stop").unwrap()),
Some(CheckResult::Wrong)
);
}
#[test]
fn set_lower_high_boundary_replaces_prev() {
let mut answers = Answers::new();
assert_eq!(answers.high_bounds_ref(), &None);
assert_eq!(answers.set_high_bounds(Answer::Int(30)), 30);
assert_eq!(answers.high_bounds_ref(), &Some(30));
assert_eq!(answers.set_high_bounds(Answer::Int(31)), 30);
assert_eq!(answers.high_bounds_ref(), &Some(30));
assert_eq!(answers.set_high_bounds(Answer::Int(12)), 12);
assert_eq!(answers.high_bounds_ref(), &Some(12));
}
#[test]
fn set_higher_low_boundary_replaces_prev() {
let mut answers = Answers::new();
assert_eq!(answers.low_bounds_ref(), &None);
assert_eq!(answers.set_low_bounds(Answer::Int(4)), 4);
assert_eq!(answers.low_bounds_ref(), &Some(4));
assert_eq!(answers.set_low_bounds(Answer::Int(-2)), 4);
assert_eq!(answers.low_bounds_ref(), &Some(4));
assert_eq!(answers.set_low_bounds(Answer::Int(187)), 187);
assert_eq!(answers.low_bounds_ref(), &Some(187));
}
#[test]
fn check_answer_uses_low_bounds_if_set() {
let mut answers = Answers::new();
assert!(answers.check(&Answer::Int(100)).is_none());
answers.set_low_bounds(Answer::Int(90));
assert_eq!(answers.check(&Answer::Int(85)), Some(CheckResult::TooLow));
assert_eq!(answers.check(&Answer::Int(90)), Some(CheckResult::TooLow));
assert!(answers.check(&Answer::Int(100)).is_none());
answers.add_wrong_answer(Answer::Int(90));
assert_eq!(answers.check(&Answer::Int(90)), Some(CheckResult::TooLow));
}
#[test]
fn check_answer_uses_high_bounds_if_set() {
let mut answers = Answers::new();
assert!(answers.check(&Answer::Int(100)).is_none());
answers.set_high_bounds(Answer::Int(90));
assert_eq!(answers.check(&Answer::Int(100)), Some(CheckResult::TooHigh));
assert_eq!(answers.check(&Answer::Int(90)), Some(CheckResult::TooHigh));
assert!(answers.check(&Answer::Int(85)).is_none());
answers.add_wrong_answer(Answer::Int(90));
assert_eq!(answers.check(&Answer::Int(90)), Some(CheckResult::TooHigh));
}
#[test]
fn check_answer_checks_high_and_low_bounds_if_set() {
let mut answers = Answers::new();
answers.set_low_bounds(Answer::Int(96));
answers.set_high_bounds(Answer::Int(103));
assert_eq!(answers.check(&Answer::Int(107)), Some(CheckResult::TooHigh));
assert_eq!(answers.check(&Answer::Int(103)), Some(CheckResult::TooHigh));
assert_eq!(answers.check(&Answer::Int(100)), None);
assert_eq!(answers.check(&Answer::Int(98)), None);
assert_eq!(answers.check(&Answer::Int(96)), Some(CheckResult::TooLow));
assert_eq!(answers.check(&Answer::Int(-5)), Some(CheckResult::TooLow));
}
#[test]
fn check_answer_bounds_checked_if_int_or_int_str() {
let mut answers = Answers::new();
answers.set_low_bounds(Answer::Int(-50));
answers.set_high_bounds(Answer::Int(25));
answers.add_wrong_answer(Answer::Int(-9));
answers.add_wrong_answer(Answer::Int(1));
answers.add_wrong_answer(Answer::from_str("xyz").unwrap());
assert_eq!(
answers.check(&Answer::from_str("55").unwrap()),
Some(CheckResult::TooHigh)
);
assert_eq!(answers.check(&Answer::Int(55)), Some(CheckResult::TooHigh));
assert_eq!(answers.check(&Answer::Int(10)), None);
assert_eq!(answers.check(&Answer::from_str("10").unwrap()), None);
assert_eq!(
answers.check(&Answer::from_str("-74").unwrap()),
Some(CheckResult::TooLow)
);
assert_eq!(answers.check(&Answer::Int(-74)), Some(CheckResult::TooLow));
}
#[test]
fn wrong_answers_if_in_bounds() {
let mut answers = Answers::new();
answers.set_low_bounds(Answer::Int(-50));
answers.set_high_bounds(Answer::Int(25));
answers.add_wrong_answer(Answer::Int(-9));
answers.add_wrong_answer(Answer::Int(1));
answers.add_wrong_answer(Answer::Int(100));
answers.add_wrong_answer(Answer::Int(-100));
answers.add_wrong_answer(Answer::from_str("xyz").unwrap());
assert_eq!(
answers.check(&Answer::from_str("-9").unwrap()),
Some(CheckResult::Wrong)
);
assert_eq!(answers.check(&Answer::Int(-9)), Some(CheckResult::Wrong));
assert_eq!(
answers.check(&Answer::from_str("1").unwrap()),
Some(CheckResult::Wrong)
);
assert_eq!(answers.check(&Answer::Int(1)), Some(CheckResult::Wrong));
assert_eq!(
answers.check(&Answer::from_str("xyz").unwrap()),
Some(CheckResult::Wrong)
);
assert_eq!(
answers.check(&Answer::from_str("100").unwrap()),
Some(CheckResult::TooHigh)
);
assert_eq!(answers.check(&Answer::Int(100)), Some(CheckResult::TooHigh));
assert_eq!(
answers.check(&Answer::from_str("-100").unwrap()),
Some(CheckResult::TooLow)
);
assert_eq!(answers.check(&Answer::Int(-100)), Some(CheckResult::TooLow));
}
#[test]
fn answers_are_wrong_when_there_is_correct_answer_that_does_not_match() {
let mut answers = Answers::new();
answers.set_correct_answer(Answer::from_str("yes").unwrap());
assert_eq!(
answers.check(&Answer::from_str("yes").unwrap()),
Some(CheckResult::Correct)
);
assert_eq!(
answers.check(&Answer::from_str("no").unwrap()),
Some(CheckResult::Wrong)
);
assert_eq!(
answers.check(&Answer::from_str("maybe").unwrap()),
Some(CheckResult::Wrong)
);
}
#[test]
fn serialize_answers_to_text() {
let text = Answers {
correct_answer: Some(Answer::Int(12)),
wrong_answers: vec![
Answer::Int(-9),
Answer::Int(1),
Answer::Int(100),
Answer::from_str("xyz").unwrap(),
],
low_bounds: Some(-50),
high_bounds: Some(25),
}
.serialize_to_string();
assert_eq!(text, "= 12\n[ -50\n] 25\nX -9\nX 1\nX 100\nX xyz\n");
}
#[test]
fn serialize_answers_to_text_with_no_correct() {
let text = Answers {
correct_answer: None,
wrong_answers: vec![
Answer::Int(-9),
Answer::Int(1),
Answer::Int(100),
Answer::from_str("xyz").unwrap(),
],
low_bounds: Some(-50),
high_bounds: Some(25),
}
.serialize_to_string();
assert_eq!(text, "[ -50\n] 25\nX -9\nX 1\nX 100\nX xyz\n");
}
#[test]
fn serialize_answers_to_text_with_missing_correct_and_high() {
let text = Answers {
correct_answer: None,
wrong_answers: vec![
Answer::Int(-9),
Answer::Int(1),
Answer::Int(100),
Answer::from_str("xyz").unwrap(),
],
low_bounds: Some(-50),
high_bounds: None,
}
.serialize_to_string();
assert_eq!(text, "[ -50\nX -9\nX 1\nX 100\nX xyz\n");
}
#[test]
fn deserialize_answers_from_text() {
let answers = Answers::deserialize_from_str("= 12\n[ -50\n] 25\nX -9\nX 1\nX 100\nX xyz\n")
.expect("no deserialization errors exepcted");
assert_eq!(
answers,
Answers {
correct_answer: Some(Answer::Int(12)),
wrong_answers: vec![
Answer::Int(-9),
Answer::Int(1),
Answer::Int(100),
Answer::from_str("xyz").unwrap(),
],
low_bounds: Some(-50),
high_bounds: Some(25),
}
);
}
#[test]
fn deserialize_answers_to_text_with_no_correct() {
let answers = Answers::deserialize_from_str("[ -50\n] 25\nX -9\nX 1\nX 100\nX xyz\n")
.expect("no deserialization errors exepcted");
assert_eq!(
answers,
Answers {
correct_answer: None,
wrong_answers: vec![
Answer::Int(-9),
Answer::Int(1),
Answer::Int(100),
Answer::from_str("xyz").unwrap(),
],
low_bounds: Some(-50),
high_bounds: Some(25),
}
);
}
#[test]
fn deserialize_answers_to_text_with_missing_correct_and_high() {
let answers = Answers::deserialize_from_str("[ -50\nX -9\nX 1\nX 100\nX xyz\n")
.expect("no deserialization errors exepcted");
assert_eq!(
answers,
Answers {
correct_answer: None,
wrong_answers: vec![
Answer::Int(-9),
Answer::Int(1),
Answer::Int(100),
Answer::from_str("xyz").unwrap(),
],
low_bounds: Some(-50),
high_bounds: None,
}
);
}
#[test]
fn deserialize_answers_with_spaces() {
let answers = Answers::deserialize_from_str("= hello world\nX foobar\nX one two three\n")
.expect("no deserialization errors exepcted");
assert_eq!(
answers,
Answers {
correct_answer: Some(Answer::from_str("hello world").unwrap()),
wrong_answers: vec![
Answer::from_str("foobar").unwrap(),
Answer::from_str("one two three").unwrap(),
],
low_bounds: None,
high_bounds: None,
}
);
}
}