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::Error;
use crate::rules::Player;

/// Represents the Backgammon doubling cube.
///
/// This cube represents an increase in the value of the current game.
/// The cube – a doubling of the value of the game – can be offered by any player the first time it
/// is used. After that, it can only be offered by the player who last took the cube.
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Cube {
    exponential: u8,
    /// Owner of the cube
    owner: Player,
}

impl Cube {
    /// Returns the value of the cube.
    pub fn get(&self) -> u64 {
        2u64.pow(self.exponential as u32)
    }

    /// Returns the owner of the cube.
    pub fn owner(&self) -> Player {
        self.owner
    }

    /// Sets the value of the cube.
    ///
    /// Internally, we store the cube's value as an exponent of 2 as u8. Therefore, there is a
    /// technical limit of 2^64 on the value of the cube, which should be enough.
    pub fn set(&mut self, value: u64) -> Result<(), Error> {
        if value.is_power_of_two() {
            let vf = value as f64;
            self.exponential = vf.log2() as u8;

            Ok(())
        } else {
            Err(Error::CubeValueInvalid)
        }
    }

    /// Sets owner of cube.
    pub fn set_owner(&mut self, owner: Player) {
        self.owner = owner;
    }

    /// Calculates the next value of the cube to offer it to the opponent.
    pub fn offer(&self, opponent: Player) -> Result<u64, Error> {
        if self.owner == Player::Nobody || self.owner != opponent {
            Ok(2u64.pow(1 + self.exponential as u32))
        } else {
            Err(Error::CubeNotPermitted)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn default_value() {
        let cube = Cube::default();
        assert_eq!(cube.get(), 1);
    }

    #[test]
    fn set_value2() -> Result<(), Error> {
        let mut cube = Cube::default();
        cube.set(2)?;
        assert_eq!(cube.get(), 2);
        Ok(())
    }

    #[test]
    fn set_value4() -> Result<(), Error> {
        let mut cube = Cube::default();
        cube.set(4)?;
        assert_eq!(cube.get(), 4);
        Ok(())
    }

    #[test]
    fn set_value8() -> Result<(), Error> {
        let mut cube = Cube::default();
        cube.set(8)?;
        assert_eq!(cube.get(), 8);
        Ok(())
    }

    #[test]
    fn set_value16() -> Result<(), Error> {
        let mut cube = Cube::default();
        cube.set(16)?;
        assert_eq!(cube.get(), 16);
        Ok(())
    }

    #[test]
    fn set_value32() -> Result<(), Error> {
        let mut cube = Cube::default();
        cube.set(32)?;
        assert_eq!(cube.get(), 32);
        Ok(())
    }

    #[test]
    fn set_value64() -> Result<(), Error> {
        let mut cube = Cube::default();
        cube.set(64)?;
        assert_eq!(cube.get(), 64);
        Ok(())
    }

    #[test]
    fn set_value128() -> Result<(), Error> {
        let mut cube = Cube::default();
        cube.set(128)?;
        assert_eq!(cube.get(), 128);
        Ok(())
    }

    #[test]
    fn set_invalid_value() -> Result<(), Error> {
        let mut cube = Cube::default();
        assert!(cube.set(3).is_err());
        Ok(())
    }

    #[test]
    fn owner_nobody() {
        let cube = Cube::default();
        assert_eq!(cube.owner(), Player::Nobody);
    }

    #[test]
    fn owner() {
        let mut cube = Cube::default();
        cube.set_owner(Player::Player0);
        assert_eq!(cube.owner(), Player::Player0);

        cube.set_owner(Player::Player1);
        assert_eq!(cube.owner(), Player::Player1);
    }

    #[test]
    fn offer_from_nobody() -> Result<(), Error> {
        let cube = Cube::default();
        assert_eq!(cube.owner(), Player::Nobody);

        let offer = cube.offer(Player::Player1)?;

        assert_eq!(offer, 2);

        let cube = Cube::default();
        let offer = cube.offer(Player::Player0)?;

        assert_eq!(offer, 2);

        Ok(())
    }

    #[test]
    fn offer_from_player() -> Result<(), Error> {
        let mut cube = Cube::default();
        cube.set(2)?;
        cube.set_owner(Player::Player0);

        let offer = cube.offer(Player::Player1)?;
        assert_eq!(cube.owner(), Player::Player0);
        assert_eq!(cube.get(), 2);
        assert_eq!(offer, 4);
        Ok(())
    }

    #[test]
    fn offer_error() -> Result<(), Error> {
        let mut cube = Cube::default();
        cube.set_owner(Player::Player1);
        assert!(cube.offer(Player::Player1).is_err());
        Ok(())
    }
}