Skip to main content

turnbase/
player.rs

1//! Player identity.
2
3/// Identifies one seat in a match.
4///
5/// A seat is not necessarily a human: scripted opponents, a blackjack dealer,
6/// and the reserved chance "player" ([`PlayerId::CHANCE`]) are all seats. Real
7/// players are numbered from 0; games map their own seat concepts onto these
8/// indices.
9#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11#[cfg_attr(feature = "serde", serde(transparent))]
12pub struct PlayerId(u32);
13
14impl PlayerId {
15    /// The reserved chance pseudo-player.
16    ///
17    /// Committed random outcomes (a card revealed, a hand dealt) are modeled as
18    /// actions taken by this seat, so they become real, undoable state rather
19    /// than an RNG side effect. See the chance-node design in `ARCHITECTURE.md`.
20    /// Real players must not use this index.
21    pub const CHANCE: Self = Self(u32::MAX);
22
23    /// Creates a seat with the given index.
24    ///
25    /// Indices should be small and dense (0, 1, 2, ...); `u32::MAX` is reserved
26    /// for [`PlayerId::CHANCE`].
27    #[must_use]
28    pub const fn new(index: u32) -> Self {
29        Self(index)
30    }
31
32    /// Returns the zero-based seat index.
33    #[must_use]
34    pub const fn index(self) -> u32 {
35        self.0
36    }
37
38    /// Returns true if this is the reserved chance pseudo-player.
39    #[must_use]
40    pub const fn is_chance(self) -> bool {
41        self.0 == Self::CHANCE.0
42    }
43}
44
45impl core::fmt::Display for PlayerId {
46    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47        if self.is_chance() {
48            f.write_str("chance")
49        } else {
50            write!(f, "p{}", self.0)
51        }
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::PlayerId;
58
59    #[test]
60    fn index_round_trips() {
61        assert_eq!(PlayerId::new(3).index(), 3);
62    }
63
64    #[test]
65    fn chance_is_distinct_and_flagged() {
66        assert!(PlayerId::CHANCE.is_chance());
67        assert!(!PlayerId::new(0).is_chance());
68        assert_ne!(PlayerId::CHANCE, PlayerId::new(0));
69    }
70
71    #[test]
72    fn ordering_is_by_index() {
73        assert!(PlayerId::new(0) < PlayerId::new(1));
74        assert!(PlayerId::new(2) < PlayerId::CHANCE);
75    }
76
77    #[test]
78    fn display() {
79        assert_eq!(PlayerId::new(1).to_string(), "p1");
80        assert_eq!(PlayerId::CHANCE.to_string(), "chance");
81    }
82}