2024-03-30 16:10:53 +01:00
|
|
|
use crate::player::Color;
|
2023-10-07 20:46:24 +02:00
|
|
|
use crate::Error;
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
2024-02-18 18:40:45 +01:00
|
|
|
use std::cmp;
|
2024-01-12 17:02:18 +01:00
|
|
|
use std::fmt;
|
2023-10-07 20:46:24 +02:00
|
|
|
|
2024-01-29 21:42:40 +01:00
|
|
|
/// field (aka 'point') position on the board (from 0 to 24, 0 being 'outside')
|
2024-01-27 20:22:20 +01:00
|
|
|
pub type Field = usize;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone, Serialize, PartialEq, Deserialize)]
|
|
|
|
|
pub struct CheckerMove {
|
|
|
|
|
from: Field,
|
|
|
|
|
to: Field,
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 21:49:56 +02:00
|
|
|
pub const EMPTY_MOVE: CheckerMove = CheckerMove { from: 0, to: 0 };
|
|
|
|
|
|
2024-02-18 18:40:45 +01:00
|
|
|
fn transpose(matrix: Vec<Vec<String>>) -> Vec<Vec<String>> {
|
|
|
|
|
let num_cols = matrix.first().unwrap().len();
|
|
|
|
|
let mut row_iters: Vec<_> = matrix.into_iter().map(Vec::into_iter).collect();
|
|
|
|
|
let mut out: Vec<Vec<_>> = (0..num_cols).map(|_| Vec::new()).collect();
|
|
|
|
|
|
|
|
|
|
for out_row in out.iter_mut() {
|
|
|
|
|
for it in row_iters.iter_mut() {
|
|
|
|
|
out_row.push(it.next().unwrap());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
out
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-27 20:22:20 +01:00
|
|
|
impl CheckerMove {
|
|
|
|
|
pub fn new(from: Field, to: Field) -> Result<Self, Error> {
|
2024-03-29 21:04:58 +01:00
|
|
|
// println!("from {} to {}", from, to);
|
2024-01-29 21:42:40 +01:00
|
|
|
// check if the field is on the board
|
|
|
|
|
// we allow 0 for 'to', which represents the exit of a checker
|
2024-05-09 21:49:56 +02:00
|
|
|
// and (0, 0) which represent the absence of a move (when there is only one checker left on the
|
|
|
|
|
// board)
|
|
|
|
|
if ((from, to) != (0, 0)) && (!(1..25).contains(&from) || 24 < to) {
|
2024-01-27 20:22:20 +01:00
|
|
|
return Err(Error::FieldInvalid);
|
|
|
|
|
}
|
2024-01-29 21:42:40 +01:00
|
|
|
// check that the destination is after the origin field
|
2024-03-27 21:10:15 +01:00
|
|
|
// --> not applicable for black moves
|
|
|
|
|
// if to < from && to != 0 {
|
|
|
|
|
// return Err(Error::MoveInvalid);
|
|
|
|
|
// }
|
2024-01-31 15:39:02 +01:00
|
|
|
Ok(Self { from, to })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Construct the move resulting of two successive moves
|
|
|
|
|
pub fn chain(self, cmove: Self) -> Result<Self, Error> {
|
|
|
|
|
if self.to != cmove.from {
|
|
|
|
|
return Err(Error::MoveInvalid);
|
|
|
|
|
}
|
|
|
|
|
Ok(Self {
|
|
|
|
|
from: self.from,
|
|
|
|
|
to: cmove.to,
|
|
|
|
|
})
|
2024-01-27 20:22:20 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-29 21:42:40 +01:00
|
|
|
pub fn get_from(&self) -> Field {
|
|
|
|
|
self.from
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-27 20:22:20 +01:00
|
|
|
pub fn get_to(&self) -> Field {
|
|
|
|
|
self.to
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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-27 20:22:20 +01:00
|
|
|
positions: [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 {
|
2024-01-27 20:22:20 +01:00
|
|
|
positions: [
|
2024-01-09 17:58:10 +01:00
|
|
|
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();
|
2024-01-27 20:22:20 +01:00
|
|
|
s.push_str(&format!("{:?}", self.positions));
|
2024-01-12 17:02:18 +01:00
|
|
|
write!(f, "{}", s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-07 20:46:24 +02:00
|
|
|
impl Board {
|
|
|
|
|
/// Create a new board
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Board::default()
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-08 21:17:13 +02:00
|
|
|
/// Globally set pieces on board ( for tests )
|
|
|
|
|
pub fn set_positions(&mut self, positions: [i8; 24]) {
|
|
|
|
|
self.positions = positions;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-18 13:30:54 +02:00
|
|
|
pub fn count_checkers(&self, color: Color, from: Field, to: Field) -> u8 {
|
|
|
|
|
self.positions[(from - 1)..to]
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|count| {
|
|
|
|
|
if color == Color::White {
|
|
|
|
|
**count > 0 as i8
|
|
|
|
|
} else {
|
|
|
|
|
**count < 0 as i8
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.sum::<i8>()
|
|
|
|
|
.unsigned_abs()
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-25 21:33:46 +01:00
|
|
|
// maybe todo : operate on bits (cf. https://github.com/bungogood/bkgm/blob/a2fb3f395243bcb0bc9f146df73413f73f5ea1e0/src/position.rs#L217)
|
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
|
2024-03-30 16:10:53 +01:00
|
|
|
let white_board = self.positions;
|
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
|
2024-03-30 16:10:53 +01:00
|
|
|
let mut black_board = self.positions;
|
2024-01-12 17:02:18 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2024-02-18 18:40:45 +01:00
|
|
|
/// format positions to a grid of symbols
|
|
|
|
|
pub fn to_display_grid(&self, col_size: usize) -> String {
|
|
|
|
|
// convert numbers to columns of chars
|
|
|
|
|
let mut columns: Vec<Vec<String>> = self
|
|
|
|
|
.positions
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|count| {
|
|
|
|
|
let char = if *count > 0 { "O" } else { "X" };
|
|
|
|
|
let men_count = count.abs();
|
|
|
|
|
let mut cells = vec!["".to_owned(); col_size];
|
|
|
|
|
cells[0..(cmp::min(men_count, col_size as i8) as usize)].fill(char.to_owned());
|
|
|
|
|
if men_count as usize > col_size {
|
|
|
|
|
cells[col_size - 1] = men_count.to_string();
|
|
|
|
|
}
|
|
|
|
|
cells
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
// upper columns (13 to 24)
|
|
|
|
|
let upper_positions: Vec<Vec<String>> = columns.split_off(12).into_iter().collect();
|
|
|
|
|
|
|
|
|
|
// lower columns (12 to 1)
|
|
|
|
|
let mut lower_positions: Vec<Vec<String>> = columns
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|mut col| {
|
|
|
|
|
col.reverse();
|
|
|
|
|
col
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
lower_positions.reverse();
|
|
|
|
|
|
|
|
|
|
// display board columns
|
|
|
|
|
let upper: Vec<String> = transpose(upper_positions)
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|cells| {
|
|
|
|
|
cells
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|cell| format!("{:>5}", cell))
|
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
|
.join("")
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
let lower: Vec<String> = transpose(lower_positions)
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|cells| {
|
|
|
|
|
cells
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|cell| format!("{:>5}", cell))
|
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
|
.join("")
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
let mut output = "
|
2024-03-27 21:10:15 +01:00
|
|
|
13 14 15 16 17 18 19 20 21 22 23 24
|
2024-02-18 18:40:45 +01:00
|
|
|
----------------------------------------------------------------\n"
|
|
|
|
|
.to_owned();
|
|
|
|
|
for mut line in upper {
|
|
|
|
|
// add middle bar
|
2024-03-27 21:10:15 +01:00
|
|
|
line.replace_range(31..31, "| |");
|
2024-02-18 18:40:45 +01:00
|
|
|
output = output + " |" + &line + " |\n";
|
|
|
|
|
}
|
2024-03-30 16:10:53 +01:00
|
|
|
output += " |------------------------------ | | -----------------------------|\n";
|
2024-02-18 18:40:45 +01:00
|
|
|
for mut line in lower {
|
|
|
|
|
// add middle bar
|
2024-03-27 21:10:15 +01:00
|
|
|
line.replace_range(31..31, "| |");
|
2024-02-18 18:40:45 +01:00
|
|
|
output = output + " |" + &line + " |\n";
|
|
|
|
|
}
|
2024-03-30 16:10:53 +01:00
|
|
|
output += " ----------------------------------------------------------------
|
2024-02-18 18:40:45 +01:00
|
|
|
12 11 10 9 8 7 6 5 4 3 2 1 \n";
|
|
|
|
|
output
|
|
|
|
|
}
|
|
|
|
|
|
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.
|
2024-01-27 20:22:20 +01:00
|
|
|
pub fn set(&mut self, color: &Color, field: Field, 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);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-29 21:42:40 +01:00
|
|
|
// the exit : no checker added to the board
|
|
|
|
|
if field == 0 {
|
2024-01-31 15:39:02 +01:00
|
|
|
return Ok(());
|
2024-01-29 21:42:40 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-27 20:22:20 +01:00
|
|
|
if self.blocked(color, field)? {
|
2023-10-07 20:46:24 +02:00
|
|
|
return Err(Error::FieldBlocked);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-27 20:22:20 +01:00
|
|
|
match color {
|
2023-10-28 15:12:04 +02:00
|
|
|
Color::White => {
|
2024-01-27 20:22:20 +01:00
|
|
|
let new = self.positions[field - 1] + amount;
|
2023-10-07 20:46:24 +02:00
|
|
|
if new < 0 {
|
|
|
|
|
return Err(Error::MoveInvalid);
|
|
|
|
|
}
|
2024-01-27 20:22:20 +01:00
|
|
|
self.positions[field - 1] = new;
|
2023-10-07 20:46:24 +02:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-10-28 15:12:04 +02:00
|
|
|
Color::Black => {
|
2024-01-27 20:22:20 +01:00
|
|
|
let new = self.positions[24 - field] - amount;
|
2024-01-09 17:58:10 +01:00
|
|
|
if new > 0 {
|
2023-10-07 20:46:24 +02:00
|
|
|
return Err(Error::MoveInvalid);
|
|
|
|
|
}
|
2024-01-27 20:22:20 +01:00
|
|
|
self.positions[24 - field] = new;
|
2023-10-07 20:46:24 +02:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if a field is blocked for a player
|
2024-01-27 20:22:20 +01:00
|
|
|
pub fn blocked(&self, color: &Color, field: Field) -> Result<bool, Error> {
|
2024-01-29 21:42:40 +01:00
|
|
|
if 24 < field {
|
2023-10-07 20:46:24 +02:00
|
|
|
return Err(Error::FieldInvalid);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-29 21:42:40 +01:00
|
|
|
// the exit is never 'blocked'
|
|
|
|
|
if field == 0 {
|
2024-01-31 15:39:02 +01:00
|
|
|
return Ok(false);
|
2024-01-29 21:42:40 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-27 20:22:20 +01:00
|
|
|
// the square is blocked on the opponent rest corner or if there are opponent's men on the square
|
|
|
|
|
match color {
|
2024-05-09 21:49:56 +02:00
|
|
|
Color::White => Ok(field == 13 || self.positions[field - 1] < 0),
|
|
|
|
|
Color::Black => Ok(field == 12 || self.positions[23 - field] > 1),
|
2023-10-07 20:46:24 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-01-27 20:22:20 +01:00
|
|
|
|
2024-01-29 21:42:40 +01:00
|
|
|
pub fn get_field_checkers(&self, field: Field) -> Result<(u8, Option<&Color>), Error> {
|
2024-03-30 16:10:53 +01:00
|
|
|
if !(1..25).contains(&field) {
|
2024-01-27 20:22:20 +01:00
|
|
|
return Err(Error::FieldInvalid);
|
|
|
|
|
}
|
2024-01-29 21:42:40 +01:00
|
|
|
let checkers_count = self.positions[field - 1];
|
2024-03-30 16:10:53 +01:00
|
|
|
let color = match checkers_count.cmp(&0) {
|
|
|
|
|
cmp::Ordering::Less => Some(&Color::Black),
|
|
|
|
|
cmp::Ordering::Greater => Some(&Color::White),
|
|
|
|
|
cmp::Ordering::Equal => None,
|
2024-01-27 20:22:20 +01:00
|
|
|
};
|
2024-03-30 16:10:53 +01:00
|
|
|
Ok((checkers_count.unsigned_abs(), color))
|
2024-01-29 21:42:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_checkers_color(&self, field: Field) -> Result<Option<&Color>, Error> {
|
2024-03-30 16:10:53 +01:00
|
|
|
self.get_field_checkers(field).map(|(_ount, color)| color)
|
2024-01-29 21:42:40 +01:00
|
|
|
}
|
|
|
|
|
|
2024-03-24 18:37:35 +01:00
|
|
|
/// returns the list of Fields containing Checkers of the Color
|
|
|
|
|
pub fn get_color_fields(&self, color: Color) -> Vec<(usize, i8)> {
|
|
|
|
|
match color {
|
|
|
|
|
Color::White => self
|
|
|
|
|
.positions
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.filter(|&(_, count)| *count > 0)
|
|
|
|
|
.map(|(i, count)| (i + 1, *count))
|
|
|
|
|
.collect(),
|
|
|
|
|
Color::Black => self
|
|
|
|
|
.positions
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.filter(|&(_, count)| *count < 0)
|
|
|
|
|
.rev()
|
|
|
|
|
.map(|(i, count)| (i + 1, (0 - count)))
|
|
|
|
|
.collect(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-29 21:42:40 +01:00
|
|
|
// Get the corner field for the color
|
|
|
|
|
pub fn get_color_corner(&self, color: &Color) -> Field {
|
2024-01-31 15:39:02 +01:00
|
|
|
if color == &Color::White {
|
|
|
|
|
12
|
|
|
|
|
} else {
|
|
|
|
|
13
|
|
|
|
|
}
|
2024-01-27 20:22:20 +01:00
|
|
|
}
|
|
|
|
|
|
2024-05-09 21:49:56 +02:00
|
|
|
pub fn get_possible_moves(
|
|
|
|
|
&self,
|
|
|
|
|
color: Color,
|
|
|
|
|
dice: u8,
|
|
|
|
|
with_excedants: bool,
|
|
|
|
|
) -> Vec<CheckerMove> {
|
|
|
|
|
let mut moves = Vec::new();
|
|
|
|
|
|
|
|
|
|
let get_dest = |from| {
|
|
|
|
|
if color == Color::White {
|
|
|
|
|
if from + dice as i32 == 25 {
|
|
|
|
|
0
|
|
|
|
|
} else {
|
|
|
|
|
from + dice as i32
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
from - dice as i32
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (field, _count) in self.get_color_fields(color) {
|
|
|
|
|
let mut dest = get_dest(field as i32);
|
|
|
|
|
if !(0..25).contains(&dest) {
|
|
|
|
|
if with_excedants {
|
|
|
|
|
dest = 0;
|
|
|
|
|
} else {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let Ok(cmove) = CheckerMove::new(field, dest.unsigned_abs() as usize) {
|
|
|
|
|
if let Ok(false) = self.blocked(&color, dest.unsigned_abs() as usize) {
|
|
|
|
|
moves.push(cmove);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
moves
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-31 15:39:02 +01:00
|
|
|
pub fn move_possible(&self, color: &Color, cmove: &CheckerMove) -> bool {
|
2024-01-27 20:22:20 +01:00
|
|
|
let blocked = self.blocked(color, cmove.to).unwrap_or(true);
|
|
|
|
|
// Check if there is a player's checker on the 'from' square
|
|
|
|
|
let has_checker = self.get_checkers_color(cmove.from).unwrap_or(None) == Some(color);
|
|
|
|
|
has_checker && !blocked
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-18 13:30:54 +02:00
|
|
|
/// Returns whether the `color` player can still fill the quarter containing the `field`
|
|
|
|
|
/// * `color` - color of the player
|
|
|
|
|
/// * `field` - field belonging to the quarter
|
|
|
|
|
pub fn is_quarter_fillable(&self, color: Color, field: Field) -> bool {
|
|
|
|
|
let fields = self.get_quarter_fields(field);
|
|
|
|
|
|
|
|
|
|
// opponent rest corner
|
|
|
|
|
if color == Color::White && fields.contains(&13)
|
|
|
|
|
|| color == Color::Black && fields.contains(&12)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// is there a sufficient number of checkers on or before each fields ?
|
|
|
|
|
for field in fields {
|
|
|
|
|
// Number of checkers needed before this field (included) :
|
|
|
|
|
// 2 checkers by field, from the begining of the quarter
|
|
|
|
|
let mut field_pos = field % 6;
|
|
|
|
|
if field_pos == 0 {
|
|
|
|
|
field_pos = 6;
|
|
|
|
|
}
|
|
|
|
|
if color == Color::Black {
|
|
|
|
|
field_pos = 7 - field_pos;
|
|
|
|
|
}
|
|
|
|
|
let needed = 2 * field_pos;
|
|
|
|
|
|
|
|
|
|
let (from, to) = if color == Color::White {
|
|
|
|
|
(1, field)
|
|
|
|
|
} else {
|
|
|
|
|
(field, 24)
|
|
|
|
|
};
|
|
|
|
|
if self.count_checkers(color, from, to) < needed as u8 {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns the 6 fields of the quarter containing the `field`
|
|
|
|
|
fn get_quarter_fields(&self, field: Field) -> [Field; 6] {
|
|
|
|
|
let min = 1 + ((field - 1) / 6) * 6;
|
|
|
|
|
core::array::from_fn(|i| i + min)
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-27 20:22:20 +01:00
|
|
|
pub fn move_checker(&mut self, color: &Color, cmove: CheckerMove) -> Result<(), Error> {
|
|
|
|
|
self.remove_checker(color, cmove.from)?;
|
|
|
|
|
self.add_checker(color, cmove.to)?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn remove_checker(&mut self, color: &Color, field: Field) -> Result<(), Error> {
|
|
|
|
|
let checker_color = self.get_checkers_color(field)?;
|
|
|
|
|
if Some(color) != checker_color {
|
|
|
|
|
return Err(Error::FieldInvalid);
|
|
|
|
|
}
|
2024-03-27 21:10:15 +01:00
|
|
|
let unit = match color {
|
|
|
|
|
Color::White => 1,
|
|
|
|
|
Color::Black => -1,
|
|
|
|
|
};
|
|
|
|
|
self.positions[field - 1] -= unit;
|
2024-01-27 20:22:20 +01:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn add_checker(&mut self, color: &Color, field: Field) -> Result<(), Error> {
|
2024-05-09 21:49:56 +02:00
|
|
|
// Sortie
|
|
|
|
|
if field == 0 {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-27 20:22:20 +01:00
|
|
|
let checker_color = self.get_checkers_color(field)?;
|
|
|
|
|
// error if the case contains the other color
|
2024-03-30 16:10:53 +01:00
|
|
|
if checker_color.is_some() && Some(color) != checker_color {
|
2024-01-27 20:22:20 +01:00
|
|
|
return Err(Error::FieldInvalid);
|
|
|
|
|
}
|
2024-03-27 21:10:15 +01:00
|
|
|
let unit = match color {
|
|
|
|
|
Color::White => 1,
|
|
|
|
|
Color::Black => -1,
|
|
|
|
|
};
|
|
|
|
|
self.positions[field - 1] += unit;
|
2024-01-27 20:22:20 +01:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-10-07 20:46:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unit Tests
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn default_board() {
|
|
|
|
|
assert_eq!(Board::new(), Board::default());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2024-01-27 18:53:48 +01:00
|
|
|
fn blocked_outofrange() -> Result<(), Error> {
|
2023-10-07 20:46:24 +02:00
|
|
|
let board = Board::new();
|
2024-03-30 16:10:53 +01:00
|
|
|
assert!(board.blocked(&Color::White, 0).is_ok());
|
2024-01-31 15:39:02 +01:00
|
|
|
assert!(board.blocked(&Color::White, 28).is_err());
|
2023-10-07 20:46:24 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2024-01-27 18:53:48 +01:00
|
|
|
fn blocked_otherplayer() -> Result<(), Error> {
|
2023-10-07 20:46:24 +02:00
|
|
|
let board = Board::new();
|
2024-01-31 15:39:02 +01:00
|
|
|
assert!(board.blocked(&Color::White, 24)?);
|
2023-10-07 20:46:24 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2024-01-27 18:53:48 +01:00
|
|
|
fn blocked_notblocked() -> Result<(), Error> {
|
|
|
|
|
let board = Board::new();
|
2024-01-31 15:39:02 +01:00
|
|
|
assert!(!board.blocked(&Color::White, 6)?);
|
2023-10-07 20:46:24 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn set_field_blocked() {
|
|
|
|
|
let mut board = Board::new();
|
2024-01-31 15:39:02 +01:00
|
|
|
assert!(board.set(&Color::White, 24, 2).is_err());
|
2023-10-07 20:46:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn set_wrong_field1() {
|
|
|
|
|
let mut board = Board::new();
|
2024-01-31 15:39:02 +01:00
|
|
|
assert!(board.set(&Color::White, 50, 2).is_err());
|
2023-10-07 20:46:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn set_wrong_amount0() {
|
|
|
|
|
let mut board = Board::new();
|
2024-01-31 15:39:02 +01:00
|
|
|
assert!(board.set(&Color::White, 23, -3).is_err());
|
2023-10-07 20:46:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn set_wrong_amount1() {
|
|
|
|
|
let mut board = Board::new();
|
2024-01-31 15:39:02 +01:00
|
|
|
assert!(board.set(&Color::White, 23, -3).is_err());
|
2023-10-07 20:46:24 +02:00
|
|
|
}
|
2024-03-24 18:37:35 +01:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn get_color_fields() {
|
|
|
|
|
let board = Board::new();
|
|
|
|
|
assert_eq!(board.get_color_fields(Color::White), vec![(1, 15)]);
|
|
|
|
|
assert_eq!(board.get_color_fields(Color::Black), vec![(24, 15)]);
|
|
|
|
|
}
|
2024-05-18 13:30:54 +02:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn is_quarter_fillable() {
|
|
|
|
|
let mut board = Board::new();
|
|
|
|
|
board.set_positions([
|
|
|
|
|
15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15,
|
|
|
|
|
]);
|
|
|
|
|
assert!(board.is_quarter_fillable(Color::Black, 1));
|
|
|
|
|
assert!(!board.is_quarter_fillable(Color::Black, 12));
|
|
|
|
|
assert!(board.is_quarter_fillable(Color::Black, 13));
|
|
|
|
|
assert!(board.is_quarter_fillable(Color::Black, 24));
|
|
|
|
|
assert!(board.is_quarter_fillable(Color::White, 1));
|
|
|
|
|
assert!(board.is_quarter_fillable(Color::White, 12));
|
|
|
|
|
assert!(!board.is_quarter_fillable(Color::White, 13));
|
|
|
|
|
assert!(board.is_quarter_fillable(Color::White, 24));
|
|
|
|
|
board.set_positions([
|
|
|
|
|
5, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -8, 0, 0, 0, 0, 0, -5,
|
|
|
|
|
]);
|
|
|
|
|
assert!(board.is_quarter_fillable(Color::Black, 13));
|
|
|
|
|
assert!(!board.is_quarter_fillable(Color::Black, 24));
|
|
|
|
|
assert!(!board.is_quarter_fillable(Color::White, 1));
|
|
|
|
|
assert!(board.is_quarter_fillable(Color::White, 12));
|
|
|
|
|
}
|
2023-10-07 20:46:24 +02:00
|
|
|
}
|