T0 + T1 + T2: engine redesign through new API surface #1
@@ -16,6 +16,7 @@ harness = false
|
||||
|
||||
[dependencies]
|
||||
approx = { version = "0.5.1", optional = true }
|
||||
smallvec = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5"
|
||||
|
||||
@@ -18,6 +18,7 @@ pub mod gaussian;
|
||||
mod history;
|
||||
mod key_table;
|
||||
mod matrix;
|
||||
mod outcome;
|
||||
mod rating;
|
||||
pub(crate) mod schedule;
|
||||
pub mod storage;
|
||||
@@ -30,6 +31,7 @@ pub use gaussian::Gaussian;
|
||||
pub use history::History;
|
||||
pub use key_table::KeyTable;
|
||||
use matrix::Matrix;
|
||||
pub use outcome::Outcome;
|
||||
pub use rating::Rating;
|
||||
pub use schedule::ScheduleReport;
|
||||
pub use time::{Time, Untimed};
|
||||
|
||||
87
src/outcome.rs
Normal file
87
src/outcome.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
//! Outcome of a match.
|
||||
//!
|
||||
//! In T2, only `Ranked` is supported; `Scored` will be added together with
|
||||
//! `MarginFactor` in T4. The enum is `#[non_exhaustive]` so adding `Scored`
|
||||
//! is non-breaking for downstream `match` expressions.
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// Final outcome of a match.
|
||||
///
|
||||
/// `Ranked(ranks)`: lower rank = better. Equal ranks mean a tie between those
|
||||
/// teams. `ranks.len()` must equal the number of teams in the event.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum Outcome {
|
||||
Ranked(SmallVec<[u32; 4]>),
|
||||
}
|
||||
|
||||
impl Outcome {
|
||||
/// `N`-team outcome where team `winner` won and everyone else tied for last.
|
||||
///
|
||||
/// Panics if `winner >= n`.
|
||||
pub fn winner(winner: u32, n: u32) -> Self {
|
||||
assert!(winner < n, "winner index {winner} out of range 0..{n}");
|
||||
let ranks: SmallVec<[u32; 4]> = (0..n).map(|i| if i == winner { 0 } else { 1 }).collect();
|
||||
Self::Ranked(ranks)
|
||||
}
|
||||
|
||||
/// All `n` teams tied.
|
||||
pub fn draw(n: u32) -> Self {
|
||||
Self::Ranked(SmallVec::from_vec(vec![0; n as usize]))
|
||||
}
|
||||
|
||||
/// Explicit per-team ranking.
|
||||
pub fn ranking<I: IntoIterator<Item = u32>>(ranks: I) -> Self {
|
||||
Self::Ranked(ranks.into_iter().collect())
|
||||
}
|
||||
|
||||
pub fn team_count(&self) -> usize {
|
||||
match self {
|
||||
Self::Ranked(r) => r.len(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn as_ranks(&self) -> &[u32] {
|
||||
match self {
|
||||
Self::Ranked(r) => r,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn winner_two_teams() {
|
||||
let o = Outcome::winner(0, 2);
|
||||
assert_eq!(o.as_ranks(), &[0u32, 1]);
|
||||
assert_eq!(o.team_count(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn winner_three_teams_second_wins() {
|
||||
let o = Outcome::winner(1, 3);
|
||||
assert_eq!(o.as_ranks(), &[1u32, 0, 1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn draw_three_teams() {
|
||||
let o = Outcome::draw(3);
|
||||
assert_eq!(o.as_ranks(), &[0u32, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ranking_from_iter() {
|
||||
let o = Outcome::ranking([2, 0, 1]);
|
||||
assert_eq!(o.as_ranks(), &[2u32, 0, 1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "winner index 2 out of range")]
|
||||
fn winner_out_of_range_panics() {
|
||||
let _ = Outcome::winner(2, 2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user