backgammon 0.18.0

The Rust Backgammon library
Documentation
/*
 * BSD 2-Clause License
 *
 * Copyright (c) 2020-2026, Carlo Strub <cs@carlostrub.ch>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

use crate::Error;
use std::fmt;

pub use board::{Board, BoardDisplay, Move, Position};
pub use cube::Cube;
pub use dice::{Dice, Roll};
pub use player::Player;

/// Implements the board.
mod board;
/// Implements the double dice or cube.
mod cube;
/// Implements the pair of dice.
mod dice;
/// Implements the players.
mod player;

/// Provides commonly used types and structures for ease of use in external modules. It is
/// recommended to always use the prelude module for convenience.
pub mod prelude {
    pub use crate::rules::{Board, BoardDisplay, Dice, MatchRules, Move, Player, Position, Roll};
}

/// Holds all the rule settings.
#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Rules {
    /// The amount points to reach for declaring a winner of the match, default is 7.
    points: u64,
    /// Use the double dice or cube, default is true.
    cube_play: bool,
    /// When offered the cube, allow to re-double but keep it, default is false.
    beaver: bool,
    /// If a player plays “beaver”, the other may double again, letting the opponent keep the cube.
    /// Default is false.
    raccoon: bool,
    /// If both players roll the same opening number, the dice is doubled, remaining in the middle
    /// of the board. Default is false.
    murphy: bool,
    /// How often to apply the automatic doubling rule in a Murphy game. 0 means always on.
    /// Default is 0.
    murphy_limit: u8,
    /// Gammon and Backgammon only count for double or triple values if the cube has already been
    /// offered. Default is false.
    jacobi: bool,
    /// When a player first reaches a score of points - 1, no doubling is allowed for the following
    /// game. Default is true.
    crawford: bool,
    /// Permits to double after the Crawford game only if both players have rolled at least twice.
    /// Default is false.
    holland: bool,
}

impl Default for Rules {
    /// Sets the default rules:
    /// - Points: 7,
    /// - Beaver: false,
    /// - Raccoon: false,
    /// - Murphy: false,
    /// - Jacobi: false,
    /// - Crawford: true,
    /// - Holland: false.
    fn default() -> Self {
        Rules {
            points: 7,
            cube_play: true,
            beaver: false,
            raccoon: false,
            murphy: false,
            murphy_limit: 0,
            jacobi: false,
            crawford: true,
            holland: false,
        }
    }
}

// implement Display trait
impl fmt::Display for Rules {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "Points: {}, Cube: {}, Beaver: {}, Raccoon: {}, Murphy: {}, Murphy Limit: {}, Jacobi: {}, \
            Crawford: {}, Holland: {}",
            self.points,
            self.cube_play,
            self.beaver,
            self.raccoon,
            self.murphy,
            self.murphy_limit,
            self.jacobi,
            self.crawford,
            self.holland
        )
    }
}

/// This trait provides methods to modify the rules of a match.
pub trait MatchRules {
    /// Set the number of points required to win the match.
    fn set_points(&mut self, points: u64) -> Result<(), Error>;

    /// Get the number of points required to win the match.
    fn get_points(&self) -> Result<u64, Error>;

    /// Set the cube status.
    fn set_cube_play(&mut self, cube: bool) -> Result<(), Error>;

    /// Get the cube status.
    fn get_cube_play(&self) -> Result<bool, Error>;

    /// Apply the Crawford rule: if the first player reaches a score of points - 1, no doubling is
    /// allowed for the following game.
    fn set_crawford(&mut self, state: bool) -> Result<(), Error>;

    /// Get the Crawford rule status.
    fn get_crawford(&self) -> Result<bool, Error>;

    /// Apply the Beaver rule: if offered the cube, allow to re-double but keep it.
    fn set_beaver(&mut self, state: bool) -> Result<(), Error>;

    /// Get the Beaver rule status.
    fn get_beaver(&self) -> Result<bool, Error>;

    /// Apply the Raccoon rule: if a player beavered the offered cube, the other may double again,
    /// letting the player keep the cube. This requires the Crawford rule to be enabled.
    fn set_raccoon(&mut self, state: bool) -> Result<(), Error>;

    /// Get the Raccoon rule status.
    fn get_raccoon(&self) -> Result<bool, Error>;

    /// Apply the Murphy rule: if both players roll the same opening number, the cube is doubled,
    /// remaining in the middle of the board. The limit parameter defines how often to apply the
    /// automatic doubling rule in a Murphy game. 0 means always on.
    fn set_murphy(&mut self, state: bool, limit: u8) -> Result<(), Error>;

    /// Get the Murphy rule status and the limit setting. A limit of 0 means that the Murphy rule is
    /// always on, given the rule is enabled.
    fn get_murphy(&self) -> Result<(bool, u8), Error>;

    /// Apply the Jacobi rule: Gammon and Backgammon only count for double or triple values if the
    /// cube has already been offered.
    fn set_jacobi(&mut self, state: bool) -> Result<(), Error>;

    /// Get the Jacobi rule status.
    fn get_jacobi(&self) -> Result<bool, Error>;

    /// Apply the Holland rule: Permits to double after the Crawford game only if both players have
    /// rolled at least twice. Requires the Crawford rule to be enabled.
    fn set_holland(&mut self, state: bool) -> Result<(), Error>;

    /// Get the Holland rule status.
    fn get_holland(&self) -> Result<bool, Error>;
}

impl MatchRules for Rules {
    fn set_points(&mut self, points: u64) -> Result<(), Error> {
        if points < 1 {
            return Err(Error::PointsInvalid);
        }
        self.points = points;
        Ok(())
    }

    /// Returns the number of points required to win the match.
    fn get_points(&self) -> Result<u64, Error> {
        Ok(self.points)
    }

    fn set_cube_play(&mut self, cube: bool) -> Result<(), Error> {
        self.cube_play = cube;
        Ok(())
    }

    fn get_cube_play(&self) -> Result<bool, Error> {
        Ok(self.cube_play)
    }

    fn set_crawford(&mut self, state: bool) -> Result<(), Error> {
        self.crawford = state;
        Ok(())
    }

    /// Returns whether the Crawford rule is enabled.
    fn get_crawford(&self) -> Result<bool, Error> {
        Ok(self.crawford)
    }

    fn set_beaver(&mut self, state: bool) -> Result<(), Error> {
        self.beaver = state;
        Ok(())
    }

    /// Returns whether the beaver rule is enabled.
    fn get_beaver(&self) -> Result<bool, Error> {
        Ok(self.beaver)
    }

    fn set_raccoon(&mut self, state: bool) -> Result<(), Error> {
        self.raccoon = state;
        Ok(())
    }

    /// Returns whether the raccoon rule is enabled.
    fn get_raccoon(&self) -> Result<bool, Error> {
        Ok(self.raccoon)
    }

    fn set_murphy(&mut self, state: bool, limit: u8) -> Result<(), Error> {
        self.murphy = state;
        self.murphy_limit = limit;
        Ok(())
    }

    /// Returns the Murphy limit if murphy rule is enabled.
    fn get_murphy(&self) -> Result<(bool, u8), Error> {
        if !self.murphy {
            return Ok((false, 0));
        }
        Ok((true, self.murphy_limit))
    }

    fn set_jacobi(&mut self, state: bool) -> Result<(), Error> {
        self.jacobi = state;
        Ok(())
    }

    /// Returns whether the Jacobi rule is enabled.
    fn get_jacobi(&self) -> Result<bool, Error> {
        Ok(self.jacobi)
    }

    fn set_holland(&mut self, state: bool) -> Result<(), Error> {
        self.crawford = state;
        self.holland = state;
        Ok(())
    }

    /// Returns whether the Holland rule is enabled.
    fn get_holland(&self) -> Result<bool, Error> {
        Ok(self.holland)
    }
}

/// Test if the default rule is created correctly and if the rules can be modified.
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_default_rules() {
        let rules = Rules::default();
        assert_eq!(rules.points, 7);
        assert!(!rules.beaver);
        assert!(!rules.raccoon);
        assert!(!rules.murphy);
        assert_eq!(rules.murphy_limit, 0);
        assert!(!rules.jacobi);
        assert!(rules.crawford);
        assert!(!rules.holland);
    }

    #[test]
    fn test_display() {
        let rules = Rules::default();
        assert_eq!(
            format!("{}", rules),
            "Points: 7, Cube: true, Beaver: false, Raccoon: false, Murphy: false, Murphy Limit: \
            0, Jacobi: false, Crawford: true, Holland: false"
        );
    }

    #[test]
    fn test_set_points_valid() -> Result<(), Error> {
        let mut rules = Rules::default();
        rules.set_points(10)?;

        assert_eq!(rules.get_points(), Ok(10));
        Ok(())
    }

    #[test]
    fn test_set_points_invalid() {
        let mut rules = Rules::default();
        let result = rules.set_points(0);
        assert!(result.is_err());
        assert_eq!(result.unwrap_err(), Error::PointsInvalid);
    }

    #[test]
    fn test_set_crawford() -> Result<(), Error> {
        let mut rules = Rules::default();
        rules.set_crawford(true)?;

        assert_eq!(rules.get_crawford(), Ok(true));
        Ok(())
    }

    #[test]
    fn test_set_beaver() -> Result<(), Error> {
        let mut rules = Rules::default();
        rules.set_beaver(true)?;

        assert_eq!(rules.get_beaver(), Ok(true));
        Ok(())
    }

    #[test]
    fn test_set_raccoon() -> Result<(), Error> {
        let mut rules = Rules::default();
        rules.set_raccoon(true)?;

        assert_eq!(rules.get_raccoon(), Ok(true));
        Ok(())
    }

    #[test]
    fn test_set_murphy() -> Result<(), Error> {
        let mut rules = Rules::default();
        rules.set_murphy(true, 8)?;

        assert_eq!(rules.get_murphy(), Ok((true, 8)));
        Ok(())
    }

    #[test]
    fn test_set_murphy_false() -> Result<(), Error> {
        let rules = Rules::default();

        assert_eq!(rules.get_murphy(), Ok((false, 0)));
        Ok(())
    }

    #[test]
    fn test_set_jacobi() -> Result<(), Error> {
        let mut rules = Rules::default();
        rules.set_jacobi(true)?;

        assert_eq!(rules.get_jacobi(), Ok(true));
        Ok(())
    }

    #[test]
    fn test_set_holland() -> Result<(), Error> {
        let mut rules = Rules::default();
        rules.set_crawford(false)?;
        rules.set_holland(true)?;

        assert_eq!(rules.get_holland(), Ok(true));
        assert_eq!(rules.get_crawford(), Ok(true));
        Ok(())
    }
}