basalt-bedrock 1.0.0

Definitions for Basalt competition packets
Documentation
use evalexpr::{context_map, eval_with_context, DefaultNumericTypes, HashMapContext, Value};

use crate::Config;

pub struct EvaluationContext {
    pub num_completions: u32,
    pub num_attempts: u32,
    pub passed_tests: u32,
    pub failed_tests: u32,
    pub number_tests: u32,
}

fn type_name(v: Value) -> String {
    match v {
        Value::Empty => "Empty".into(),
        Value::Int(_) => "Int".into(),
        Value::Float(_) => "Float".into(),
        Value::Boolean(_) => "Boolean".into(),
        Value::String(_) => "String".into(),
        Value::Tuple(_) => "Tuple".into(),
    }
}

#[derive(thiserror::Error, Debug)]
pub enum ScoreError {
    #[error("Something went wrong initializing the evaluation context")]
    ContextInitialization(String),
    #[error("Something went wrong evaluating the score {0}")]
    Evaluation(String),
    #[error("Invalid value produced: expected Int | Float, got {0}")]
    ValueError(String),
    #[error("This kind of competition cannot produce a numeric score")]
    Unscorable,
    #[error("The requested problem does not exist")]
    NoSuchProblem,
}

pub trait Scorable {
    fn score(&self, problem_idx: usize, ctx: EvaluationContext) -> Result<f64, ScoreError>;
}

impl Scorable for Config {
    fn score(&self, problem_idx: usize, ctx: EvaluationContext) -> Result<f64, ScoreError> {
        match &self.game {
            crate::Game::Points(settings) => {
                let points = self
                    .packet
                    .problems
                    .get(problem_idx)
                    .ok_or(ScoreError::NoSuchProblem)?
                    .points
                    .unwrap_or(settings.question_point_value);
                let teams = self.accounts.competitors.len() as i32;
                let inner_ctx: HashMapContext<DefaultNumericTypes> = context_map! {
                    "p" => int points,
                    "points" => int points,
                    "c" => int ctx.num_completions,
                    "completed" => int ctx.num_completions,
                    "t" => int teams,
                    "teams" => int teams,
                    "a" => int ctx.num_attempts,
                    "attempts" => int ctx.num_attempts,
                    "pass" => int ctx.passed_tests,
                    "passed_tests" => int ctx.passed_tests,
                    "fail" => int ctx.failed_tests,
                    "failed_tests" => int ctx.failed_tests,
                    "tests" => int ctx.number_tests,
                    "number_tests" => int ctx.number_tests,
                }
                .map_err(|e| ScoreError::ContextInitialization(e.to_string()))?;

                match eval_with_context(&settings.score, &inner_ctx)
                    .map_err(|e| ScoreError::Evaluation(e.to_string()))?
                {
                    evalexpr::Value::Int(value) => Ok(value as f64),
                    evalexpr::Value::Float(value) => Ok(value),
                    ev => Err(ScoreError::ValueError(type_name(ev))),
                }
            }
            _ => Err(ScoreError::Unscorable),
        }
    }
}