Skip to main content

basalt_bedrock/
scoring.rs

1use evalexpr::{context_map, eval_with_context, DefaultNumericTypes, HashMapContext, Value};
2
3use crate::Config;
4
5pub struct EvaluationContext {
6    pub num_completions: u32,
7    pub num_attempts: u32,
8    pub passed_tests: u32,
9    pub failed_tests: u32,
10    pub number_tests: u32,
11}
12
13fn type_name(v: Value) -> String {
14    match v {
15        Value::Empty => "Empty".into(),
16        Value::Int(_) => "Int".into(),
17        Value::Float(_) => "Float".into(),
18        Value::Boolean(_) => "Boolean".into(),
19        Value::String(_) => "String".into(),
20        Value::Tuple(_) => "Tuple".into(),
21    }
22}
23
24#[derive(thiserror::Error, Debug)]
25pub enum ScoreError {
26    #[error("Something went wrong initializing the evaluation context")]
27    ContextInitialization(String),
28    #[error("Something went wrong evaluating the score {0}")]
29    Evaluation(String),
30    #[error("Invalid value produced: expected Int | Float, got {0}")]
31    ValueError(String),
32    #[error("This kind of competition cannot produce a numeric score")]
33    Unscorable,
34    #[error("The requested problem does not exist")]
35    NoSuchProblem,
36}
37
38pub trait Scorable {
39    fn score(&self, problem_idx: usize, ctx: EvaluationContext) -> Result<f64, ScoreError>;
40}
41
42impl Scorable for Config {
43    fn score(&self, problem_idx: usize, ctx: EvaluationContext) -> Result<f64, ScoreError> {
44        match &self.game {
45            crate::Game::Points(settings) => {
46                let points = self
47                    .packet
48                    .problems
49                    .get(problem_idx)
50                    .ok_or(ScoreError::NoSuchProblem)?
51                    .points
52                    .unwrap_or(settings.question_point_value);
53                let teams = self.accounts.competitors.len() as i32;
54                let inner_ctx: HashMapContext<DefaultNumericTypes> = context_map! {
55                    "p" => int points,
56                    "points" => int points,
57                    "c" => int ctx.num_completions,
58                    "completed" => int ctx.num_completions,
59                    "t" => int teams,
60                    "teams" => int teams,
61                    "a" => int ctx.num_attempts,
62                    "attempts" => int ctx.num_attempts,
63                    "pass" => int ctx.passed_tests,
64                    "passed_tests" => int ctx.passed_tests,
65                    "fail" => int ctx.failed_tests,
66                    "failed_tests" => int ctx.failed_tests,
67                    "tests" => int ctx.number_tests,
68                    "number_tests" => int ctx.number_tests,
69                }
70                .map_err(|e| ScoreError::ContextInitialization(e.to_string()))?;
71
72                match eval_with_context(&settings.score, &inner_ctx)
73                    .map_err(|e| ScoreError::Evaluation(e.to_string()))?
74                {
75                    evalexpr::Value::Int(value) => Ok(value as f64),
76                    evalexpr::Value::Float(value) => Ok(value),
77                    ev => Err(ScoreError::ValueError(type_name(ev))),
78                }
79            }
80            _ => Err(ScoreError::Unscorable),
81        }
82    }
83}