feat(api): add Outcome enum with Ranked variant
Outcome::winner(i, n), Outcome::draw(n), Outcome::ranking(iter) are the convenience constructors. Marked #[non_exhaustive] so Scored can be added in T4 without breaking match exhaustiveness. Adds smallvec = "1" as a direct dependency. Part of T2 of docs/superpowers/specs/2026-04-23-trueskill-engine-redesign-design.md.
This commit is contained in:
@@ -16,6 +16,7 @@ harness = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
approx = { version = "0.5.1", optional = true }
|
approx = { version = "0.5.1", optional = true }
|
||||||
|
smallvec = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.5"
|
criterion = "0.5"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub mod gaussian;
|
|||||||
mod history;
|
mod history;
|
||||||
mod key_table;
|
mod key_table;
|
||||||
mod matrix;
|
mod matrix;
|
||||||
|
mod outcome;
|
||||||
mod rating;
|
mod rating;
|
||||||
pub(crate) mod schedule;
|
pub(crate) mod schedule;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
@@ -30,6 +31,7 @@ pub use gaussian::Gaussian;
|
|||||||
pub use history::History;
|
pub use history::History;
|
||||||
pub use key_table::KeyTable;
|
pub use key_table::KeyTable;
|
||||||
use matrix::Matrix;
|
use matrix::Matrix;
|
||||||
|
pub use outcome::Outcome;
|
||||||
pub use rating::Rating;
|
pub use rating::Rating;
|
||||||
pub use schedule::ScheduleReport;
|
pub use schedule::ScheduleReport;
|
||||||
pub use time::{Time, Untimed};
|
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