basalt_bedrock/
scoring.rs1use 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}