Skip to main content

server/domain/scoreboard/
board.rs

1use constants::*;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    display::format_vec,
6    domain::{PLAYER0, PLAYER1, Pegging, Player, Points, Position, Positions},
7};
8
9/// Represents the scoreboard.
10///
11/// Tracks each player's position and the history of score sheets.
12#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
13pub struct Scoreboard {
14    positions: Positions,
15    history: Vec<Pegging>,
16}
17
18/// Trait for types that expose a `Scoreboard`.
19pub trait HasScoreboard {
20    /// Returns an immutable reference to the scoreboard.
21    fn scoreboard(&self) -> &Scoreboard;
22
23    /// Returns a mutable reference to the scoreboard.
24    fn scoreboard_mut(&mut self) -> &mut Scoreboard;
25}
26
27impl Scoreboard {
28    /// Creates a new scoreboard with the given positions.
29    pub fn new(positions: Positions) -> Self {
30        Self {
31            positions,
32            history: Vec::default(),
33        }
34    }
35
36    /// Returns the position of the specified player.
37    #[must_use]
38    pub fn position(&self, player: Player) -> Position {
39        self.positions[player]
40    }
41
42    /// Updates the scoreboard with the given pegging.
43    ///
44    /// If the player reached the winning score then they will be returned
45    /// as Some(winner) otherwise None is returned.
46    #[must_use]
47    pub fn peg(&mut self, pegging: &Pegging) -> Option<Player> {
48        let player = pegging.recipient();
49        let sheet = pegging.score_sheet();
50
51        let points = sheet.points();
52        if points > Points::from(0) {
53            self.positions[player] += points;
54            self.history.push(pegging.clone());
55        }
56
57        (self.positions[player].points() >= Points::from(WINNING_SCORE)).then_some(*player)
58    }
59
60    /// Returns a reference to the most recent pegging from the history
61    /// if any pegging rounds have been completed.
62    ///
63    /// Returns `None` if no pegging has occurred yet.
64    #[must_use]
65    pub fn latest_pegging(&self) -> Option<&Pegging> {
66        self.history.last()
67    }
68
69    /// Returns the winner if any player has reached the winning score.
70    #[must_use]
71    pub fn winner(&self) -> Option<Player> {
72        if self.position(PLAYER0).points() >= Points::from(WINNING_SCORE) {
73            Some(PLAYER0)
74        } else if self.position(PLAYER1).points() >= Points::from(WINNING_SCORE) {
75            Some(PLAYER1)
76        } else {
77            None
78        }
79    }
80}
81
82impl std::fmt::Display for Scoreboard {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        let Self { positions, .. } = self;
85        let positions = format_vec(positions);
86        write!(f, "Scoreboard({positions})")
87    }
88}