2024-01-09 17:58:10 +01:00
|
|
|
use crate::player::{Color, Player};
|
2023-10-07 20:46:24 +02:00
|
|
|
use crate::Error;
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
2024-01-12 17:02:18 +01:00
|
|
|
use std::fmt;
|
2023-10-07 20:46:24 +02:00
|
|
|
|
2023-11-05 17:14:58 +01:00
|
|
|
/// Represents the Tric Trac board
|
2024-01-09 17:58:10 +01:00
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
2024-01-12 17:02:18 +01:00
|
|
|
pub struct Board {
|
2024-01-09 17:58:10 +01:00
|
|
|
board: [i8; 24],
|
2023-10-07 20:46:24 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-09 17:58:10 +01:00
|
|
|
impl Default for Board {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Board {
|
|
|
|
|
board: [
|
|
|
|
|
15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15,
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-07 20:46:24 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-12 17:02:18 +01:00
|
|
|
// implement Display trait
|
|
|
|
|
impl fmt::Display for Board {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
let mut s = String::new();
|
|
|
|
|
s.push_str(&format!("{:?}", self.board));
|
|
|
|
|
write!(f, "{}", s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-07 20:46:24 +02:00
|
|
|
impl Board {
|
|
|
|
|
/// Create a new board
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Board::default()
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-20 21:40:06 +01:00
|
|
|
pub fn to_gnupg_pos_id(&self) -> String {
|
2024-01-12 17:02:18 +01:00
|
|
|
// Pieces placement -> 77bits (24 + 23 + 30 max)
|
|
|
|
|
// inspired by https://www.gnu.org/software/gnubg/manual/html_node/A-technical-description-of-the-Position-ID.html
|
|
|
|
|
// - white positions
|
|
|
|
|
let white_board = self.board.clone();
|
2024-01-20 21:40:06 +01:00
|
|
|
let mut pos_bits = white_board.iter().fold(vec![], |acc, nb| {
|
|
|
|
|
let mut new_acc = acc.clone();
|
|
|
|
|
if *nb > 0 {
|
2024-01-12 17:02:18 +01:00
|
|
|
// add as many `true` as there are pieces on the arrow
|
2024-01-20 21:40:06 +01:00
|
|
|
new_acc.append(&mut vec!['1'; *nb as usize]);
|
2024-01-12 17:02:18 +01:00
|
|
|
}
|
2024-01-20 21:40:06 +01:00
|
|
|
new_acc.push('0'); // arrow separator
|
|
|
|
|
new_acc
|
2024-01-12 17:02:18 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// - black positions
|
|
|
|
|
let mut black_board = self.board.clone();
|
|
|
|
|
black_board.reverse();
|
2024-01-20 21:40:06 +01:00
|
|
|
let mut pos_black_bits = black_board.iter().fold(vec![], |acc, nb| {
|
|
|
|
|
let mut new_acc = acc.clone();
|
|
|
|
|
if *nb < 0 {
|
2024-01-12 17:02:18 +01:00
|
|
|
// add as many `true` as there are pieces on the arrow
|
2024-01-20 21:40:06 +01:00
|
|
|
new_acc.append(&mut vec!['1'; (0 - *nb) as usize]);
|
2024-01-12 17:02:18 +01:00
|
|
|
}
|
2024-01-20 21:40:06 +01:00
|
|
|
new_acc.push('0'); // arrow separator
|
|
|
|
|
new_acc
|
2024-01-12 17:02:18 +01:00
|
|
|
});
|
|
|
|
|
|
2024-01-20 21:40:06 +01:00
|
|
|
pos_bits.append(&mut pos_black_bits);
|
2024-01-12 17:02:18 +01:00
|
|
|
|
|
|
|
|
// fill with 0 bits until 77
|
2024-01-20 21:40:06 +01:00
|
|
|
pos_bits.resize(77, '0');
|
|
|
|
|
pos_bits.iter().collect::<String>()
|
2024-01-12 17:02:18 +01:00
|
|
|
}
|
|
|
|
|
|
2023-10-07 20:46:24 +02:00
|
|
|
/// Set checkers for a player on a field
|
|
|
|
|
///
|
|
|
|
|
/// This method adds the amount of checkers for a player on a field. The field is numbered from
|
2024-01-09 17:58:10 +01:00
|
|
|
/// 1 to 24, starting from the first field of each player in the home board, the most far away
|
|
|
|
|
/// field for each player is number 24.
|
2023-10-07 20:46:24 +02:00
|
|
|
///
|
|
|
|
|
/// If the field is blocked for the player, an error is returned. If the field is not blocked,
|
|
|
|
|
/// but there is already one checker from the other player on the field, that checker is hit and
|
|
|
|
|
/// moved to the bar.
|
2023-10-29 20:48:53 +01:00
|
|
|
pub fn set(&mut self, player: &Player, field: usize, amount: i8) -> Result<(), Error> {
|
2024-01-09 17:58:10 +01:00
|
|
|
if field > 24 {
|
2023-10-07 20:46:24 +02:00
|
|
|
return Err(Error::FieldInvalid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.blocked(player, field)? {
|
|
|
|
|
return Err(Error::FieldBlocked);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-28 15:12:04 +02:00
|
|
|
match player.color {
|
|
|
|
|
Color::White => {
|
2024-01-09 17:58:10 +01:00
|
|
|
let new = self.board[field - 1] + amount;
|
2023-10-07 20:46:24 +02:00
|
|
|
if new < 0 {
|
|
|
|
|
return Err(Error::MoveInvalid);
|
|
|
|
|
}
|
2024-01-09 17:58:10 +01:00
|
|
|
self.board[field - 1] = new;
|
2023-10-07 20:46:24 +02:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-10-28 15:12:04 +02:00
|
|
|
Color::Black => {
|
2024-01-09 17:58:10 +01:00
|
|
|
let new = self.board[24 - field] - amount;
|
|
|
|
|
if new > 0 {
|
2023-10-07 20:46:24 +02:00
|
|
|
return Err(Error::MoveInvalid);
|
|
|
|
|
}
|
2024-01-09 17:58:10 +01:00
|
|
|
self.board[24 - field] = new;
|
2023-10-07 20:46:24 +02:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if a field is blocked for a player
|
2023-10-29 20:48:53 +01:00
|
|
|
pub fn blocked(&self, player: &Player, field: usize) -> Result<bool, Error> {
|
2024-01-09 17:58:10 +01:00
|
|
|
if field > 24 {
|
2023-10-07 20:46:24 +02:00
|
|
|
return Err(Error::FieldInvalid);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-28 15:12:04 +02:00
|
|
|
match player.color {
|
|
|
|
|
Color::White => {
|
2024-01-09 17:58:10 +01:00
|
|
|
if self.board[field - 1] < 0 {
|
2023-10-07 20:46:24 +02:00
|
|
|
Ok(true)
|
|
|
|
|
} else {
|
|
|
|
|
Ok(false)
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-28 15:12:04 +02:00
|
|
|
Color::Black => {
|
2024-01-20 21:40:06 +01:00
|
|
|
if self.board[23 - field] > 1 {
|
2023-10-07 20:46:24 +02:00
|
|
|
Ok(true)
|
|
|
|
|
} else {
|
|
|
|
|
Ok(false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Trait to move checkers
|
|
|
|
|
pub trait Move {
|
|
|
|
|
/// Move a checker
|
2023-10-29 20:48:53 +01:00
|
|
|
fn move_checker(&mut self, player: &Player, dice: u8, from: usize) -> Result<&mut Self, Error>
|
2023-10-07 20:46:24 +02:00
|
|
|
where
|
|
|
|
|
Self: Sized;
|
|
|
|
|
|
|
|
|
|
/// Move permitted
|
2023-10-29 20:48:53 +01:00
|
|
|
fn move_permitted(&mut self, player: &Player, dice: u8) -> Result<&mut Self, Error>
|
2023-10-07 20:46:24 +02:00
|
|
|
where
|
|
|
|
|
Self: Sized;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unit Tests
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn default_board() {
|
|
|
|
|
assert_eq!(Board::new(), Board::default());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn blocked_player0() -> Result<(), Error> {
|
|
|
|
|
let board = Board::new();
|
2024-01-09 17:58:10 +01:00
|
|
|
assert!(board.blocked(
|
|
|
|
|
&Player {
|
|
|
|
|
name: "".into(),
|
2024-01-20 21:40:06 +01:00
|
|
|
color: Color::White,
|
|
|
|
|
holes: 0,
|
|
|
|
|
points: 0,
|
|
|
|
|
can_bredouille: true,
|
|
|
|
|
can_big_bredouille: true
|
2024-01-09 17:58:10 +01:00
|
|
|
},
|
|
|
|
|
0
|
|
|
|
|
)?);
|
2023-10-07 20:46:24 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn blocked_player1() -> Result<(), Error> {
|
|
|
|
|
let board = Board::new();
|
2024-01-09 17:58:10 +01:00
|
|
|
assert!(board.blocked(
|
|
|
|
|
&Player {
|
|
|
|
|
name: "".into(),
|
2024-01-20 21:40:06 +01:00
|
|
|
color: Color::Black,
|
|
|
|
|
holes: 0,
|
|
|
|
|
points: 0,
|
|
|
|
|
can_bredouille: true,
|
|
|
|
|
can_big_bredouille: true
|
2024-01-09 17:58:10 +01:00
|
|
|
},
|
|
|
|
|
0
|
|
|
|
|
)?);
|
2023-10-07 20:46:24 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn blocked_player0_a() -> Result<(), Error> {
|
|
|
|
|
let mut board = Board::new();
|
2024-01-09 17:58:10 +01:00
|
|
|
board.set(
|
|
|
|
|
&Player {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
color: Color::Black,
|
2024-01-20 21:40:06 +01:00
|
|
|
holes: 0,
|
|
|
|
|
points: 0,
|
|
|
|
|
can_bredouille: true,
|
|
|
|
|
can_big_bredouille: true
|
2024-01-09 17:58:10 +01:00
|
|
|
},
|
|
|
|
|
1,
|
|
|
|
|
2,
|
|
|
|
|
)?;
|
|
|
|
|
assert!(board.blocked(
|
|
|
|
|
&Player {
|
|
|
|
|
name: "".into(),
|
2024-01-20 21:40:06 +01:00
|
|
|
color: Color::White,
|
|
|
|
|
holes: 0,
|
|
|
|
|
points: 0,
|
|
|
|
|
can_bredouille: true,
|
|
|
|
|
can_big_bredouille: true
|
2024-01-09 17:58:10 +01:00
|
|
|
},
|
|
|
|
|
22
|
|
|
|
|
)?);
|
2023-10-07 20:46:24 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn blocked_player1_a() -> Result<(), Error> {
|
|
|
|
|
let mut board = Board::new();
|
2024-01-09 17:58:10 +01:00
|
|
|
board.set(
|
|
|
|
|
&Player {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
color: Color::White,
|
2024-01-20 21:40:06 +01:00
|
|
|
holes: 0,
|
|
|
|
|
points: 0,
|
|
|
|
|
can_bredouille: true,
|
|
|
|
|
can_big_bredouille: true
|
2024-01-09 17:58:10 +01:00
|
|
|
},
|
|
|
|
|
1,
|
|
|
|
|
2,
|
|
|
|
|
)?;
|
|
|
|
|
assert!(board.blocked(
|
|
|
|
|
&Player {
|
|
|
|
|
name: "".into(),
|
2024-01-20 21:40:06 +01:00
|
|
|
color: Color::Black,
|
|
|
|
|
holes: 0,
|
|
|
|
|
points: 0,
|
|
|
|
|
can_bredouille: true,
|
|
|
|
|
can_big_bredouille: true
|
2024-01-09 17:58:10 +01:00
|
|
|
},
|
|
|
|
|
22
|
|
|
|
|
)?);
|
2023-10-07 20:46:24 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn blocked_invalid_field() {
|
|
|
|
|
let board = Board::new();
|
2024-01-09 17:58:10 +01:00
|
|
|
assert!(board
|
|
|
|
|
.blocked(
|
|
|
|
|
&Player {
|
|
|
|
|
name: "".into(),
|
2024-01-20 21:40:06 +01:00
|
|
|
color: Color::White,
|
|
|
|
|
holes: 0,
|
|
|
|
|
points: 0,
|
|
|
|
|
can_bredouille: true,
|
|
|
|
|
can_big_bredouille: true
|
2024-01-09 17:58:10 +01:00
|
|
|
},
|
|
|
|
|
24
|
|
|
|
|
)
|
|
|
|
|
.is_err());
|
2023-10-07 20:46:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn set_field_blocked() {
|
|
|
|
|
let mut board = Board::new();
|
2024-01-09 17:58:10 +01:00
|
|
|
assert!(board
|
|
|
|
|
.set(
|
|
|
|
|
&Player {
|
|
|
|
|
name: "".into(),
|
2024-01-20 21:40:06 +01:00
|
|
|
color: Color::White,
|
|
|
|
|
holes: 0,
|
|
|
|
|
points: 0,
|
|
|
|
|
can_bredouille: true,
|
|
|
|
|
can_big_bredouille: true
|
2024-01-09 17:58:10 +01:00
|
|
|
},
|
|
|
|
|
0,
|
|
|
|
|
2
|
|
|
|
|
)
|
|
|
|
|
.is_err());
|
2023-10-07 20:46:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn set_wrong_field1() {
|
|
|
|
|
let mut board = Board::new();
|
2024-01-09 17:58:10 +01:00
|
|
|
assert!(board
|
|
|
|
|
.set(
|
|
|
|
|
&Player {
|
|
|
|
|
name: "".into(),
|
2024-01-20 21:40:06 +01:00
|
|
|
color: Color::White,
|
|
|
|
|
holes: 0,
|
|
|
|
|
points: 0,
|
|
|
|
|
can_bredouille: true,
|
|
|
|
|
can_big_bredouille: true
|
2024-01-09 17:58:10 +01:00
|
|
|
},
|
|
|
|
|
50,
|
|
|
|
|
2
|
|
|
|
|
)
|
|
|
|
|
.is_err());
|
2023-10-07 20:46:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn set_wrong_amount0() {
|
|
|
|
|
let mut board = Board::new();
|
2024-01-09 17:58:10 +01:00
|
|
|
assert!(board
|
|
|
|
|
.set(
|
|
|
|
|
&Player {
|
|
|
|
|
name: "".into(),
|
2024-01-20 21:40:06 +01:00
|
|
|
color: Color::White,
|
|
|
|
|
holes: 0,
|
|
|
|
|
points: 0,
|
|
|
|
|
can_bredouille: true,
|
|
|
|
|
can_big_bredouille: true
|
2024-01-09 17:58:10 +01:00
|
|
|
},
|
|
|
|
|
23,
|
|
|
|
|
-3
|
|
|
|
|
)
|
|
|
|
|
.is_err());
|
2023-10-07 20:46:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn set_wrong_amount1() {
|
|
|
|
|
let mut board = Board::new();
|
2024-01-09 17:58:10 +01:00
|
|
|
assert!(board
|
|
|
|
|
.set(
|
|
|
|
|
&Player {
|
|
|
|
|
name: "".into(),
|
2024-01-20 21:40:06 +01:00
|
|
|
color: Color::Black,
|
|
|
|
|
holes: 0,
|
|
|
|
|
points: 0,
|
|
|
|
|
can_bredouille: true,
|
|
|
|
|
can_big_bredouille: true
|
2024-01-09 17:58:10 +01:00
|
|
|
},
|
|
|
|
|
23,
|
|
|
|
|
-3
|
|
|
|
|
)
|
|
|
|
|
.is_err());
|
2023-10-07 20:46:24 +02:00
|
|
|
}
|
|
|
|
|
}
|