aoc_framework 0.1.0

A framework used to run Advent of Code Challenges
Documentation
pub mod challenge;
mod example;
mod data;

use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_yaml;
use std::{fmt::{Display, Debug}, fs};
use crate::input::InputLoader;
use crate::aoc_runner::data::AOCData;
use crate::aoc_runner::example::Example;
use crate::aoc_runner::challenge::Challenge;
use crate::aoc_runner::challenge::ChallengePart;

/// The `AOCRunner` is used to run the challenge.
/// 
pub struct AOCRunner<R, T: Challenge<R>> {
    challenge: T,
    examples: Vec<Example<R>>,
    data: AOCData<R>
}

impl<'de, R: Eq + Display + PartialOrd + Serialize + DeserializeOwned + Debug, T: Challenge<R>> AOCRunner<R, T> {
    /// Creates a new `AOCRunner` for the given challenge.
    /// This method needs to be passed the object implementing the `Challenge` trait.
    pub fn new(challenge: T) -> Self {
        if let Ok(data) = fs::read_to_string("assets/aoc.yaml") {
            let data : AOCData<R> = serde_yaml::from_str(&data).expect("Unable to parse yaml");
            return AOCRunner {
                challenge,
                examples: Vec::new(),
                data
            }
        }

        AOCRunner {
            challenge,
            examples: Vec::new(),
            data: AOCData::new()
        }
    }

    /// Adds an example to the `AOCRunner`.
    /// The example consists of the input data, the expected result and the part of the challenge.
    /// The input data is loaded using the `InputLoader`.
    /// The expected result is the result that is expected for the given input data.
    pub fn add_example(mut self, data: impl InputLoader, expected: R, part: ChallengePart) -> Self {
        self.examples.push(Example::new(data.load(), expected, part));
        self
    }

    /// Runs the challenge.
    /// The input data is loaded using the `InputLoader`.
    /// The results are printed to the console with additional information.
    /// Sometimes the user is asked for feedback.
    pub fn run(mut self, input: impl InputLoader) {
        let input = input.load();
        println!("Executing Part 1");
        let result = self.execute(input.clone(), ChallengePart::Part1);
        match result {
            Result::PossibleSolution(r) => {
                self.get_feedback(r, ChallengePart::Part1);
            },
            _ => {}
        }
        println!("Executing Part 2");
        let result = self.execute(input.clone(), ChallengePart::Part2);
        match result {
            Result::PossibleSolution(r) => {
                self.get_feedback(r, ChallengePart::Part2);
            },
            _ => {}
        }

        
        let _data = serde_yaml::to_string(&self.data).expect("Unable to serialize yaml");
        if let Err(e) = fs::write("assets/aoc.yaml", _data) {
            println!("Unable to write yaml: {}", e);
        }
            
    }

    fn execute(&mut self, input: Vec<String>, part: ChallengePart) -> Result<R> {
        for (i, example) in self.examples.iter().filter(|example| example.part == part).enumerate() {
            let result = match part {
                ChallengePart::Part1 => self.challenge.part1(example.input.clone()), 
                ChallengePart::Part2 => self.challenge.part2(example.input.clone())
            };

            match result {
                Some(result) => {
                    println!("Result for Example Nr. {}: {}", i, result);
                    if result == example.expected {
                        println!("Example Nr. {} Passed", i);
                    } else {
                        println!("Example Nr. {} Failed. Expected {}", i, example.expected);
                        return Result::Incorrect;
                    }
                },
                None => { println!("Part {} not implemented", part.to_number()); return Result::NotImplemented; }
            }
        }

        let result = match part {
            ChallengePart::Part1 => self.challenge.part1(input.clone()), 
            ChallengePart::Part2 => self.challenge.part2(input.clone())
        };

        if result.is_none() {
            println!("Part {} not implemented", part.to_number());
            return Result::NotImplemented;
        }
        let result = result.unwrap();
        println!("Result: {}", result);

        let index = (part.to_number() - 1) as usize;

        if self.data.part[index].solution.is_some() {
            if result == *self.data.part[index].solution.as_ref().unwrap() {
                println!("Solution is correct.");
                return Result::Correct;
            } else {
                println!("Solution is incorrect.  Correct answer: {}", self.data.part[index].solution.as_ref().unwrap());
                return Result::Incorrect;
            }
        }

        if self.data.part[index].min.is_some() {
            if result <= *self.data.part[index].min.as_ref().unwrap() {
                println!("The result is too low. Solution is bigger than {} (If the result type is a String ignore the Order Information)", self.data.part[index].min.as_ref().unwrap());
                return Result::TooLow;
            }
        }

        if self.data.part[index].max.is_some() {
            if result >= *self.data.part[index].max.as_ref().unwrap() {
                println!("The result is too high. Solution is smaller than {} (If the result type is a String ignore the Order Information)", self.data.part[index].max.as_ref().unwrap());
                return Result::TooHigh;
            }
        }

       
        Result::PossibleSolution(result)
    }

    fn get_feedback(&mut self, result: R, part: ChallengePart) {
        println!("Enter the answer in AOC and provide the given feedback. (0 = Incorrect, 1 = Correct, 2 = Too Low, 3 = Too High)");
        let mut feedback = String::new();
        let index = (part.to_number() - 1) as usize;
        loop {
            if let Err(e) = std::io::stdin().read_line(&mut feedback) {
                println!("Something went wrong: {}", e);
                continue;
            }
            
            match feedback.trim().parse::<u8>() {
                Ok(1) => {
                    println!("The result is correct ");
                    self.data.part[index].solution = Some(result);
                    break;
                },
                Ok(2) => {
                    println!("The result is too low ");
                    self.data.part[index].min = Some(result);
                    break;
                },
                Ok(3) => {
                    println!("The result is too high ");
                    self.data.part[index].max = Some(result);
                    break;
                },
                _ => {
                    println!("Please enter 0 or 1");
                    continue;
                }
            }   
        }
    }
}

enum Result<T> {
    Correct,
    Incorrect,
    TooLow,
    TooHigh,
    PossibleSolution(T),
    NotImplemented
}