GameState::to_string_id

This commit is contained in:
Henri Bourcereau 2024-01-20 21:40:06 +01:00
parent 6fe5a268da
commit 25a5470a12
8 changed files with 206 additions and 298 deletions

7
Cargo.lock generated
View file

@ -284,9 +284,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.5" version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]] [[package]]
name = "bevy" name = "bevy"
@ -3041,7 +3041,7 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
dependencies = [ dependencies = [
"base64 0.21.5", "base64 0.21.7",
"bitflags 2.4.1", "bitflags 2.4.1",
"serde", "serde",
"serde_derive", "serde_derive",
@ -3248,6 +3248,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
name = "store" name = "store"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"base64 0.21.7",
"log", "log",
"rand", "rand",
"serde", "serde",

View file

@ -57,11 +57,11 @@ Encodage efficace : https://www.gnu.org/software/gnubg/manual/html_node/A-techni
* roll dice * roll dice
* mark points (jeton & fichet) & set bredouille markers (3rd jeton & pavillon) * mark points (jeton & fichet) & set bredouille markers (3rd jeton & pavillon)
* move pieces * move pieces
* dice roll -> 4bits * dice roll -> 6bits
* points 10bits x2 joueurs = 20bits * points 10bits x2 joueurs = 20bits
* points -> 4bits * points -> 4bits
* trous -> 4bits * trous -> 4bits
* bredouille possible 1bit * bredouille possible 1bit
* grande bredouille possible 1bit * grande bredouille possible 1bit
Total : 77 + 1 + 2 + 4 + 20 = 104 bits = 13 * 8 (8^2 = 256) = 17.3333 * 6 (18 u64) -> 108 possible Total : 77 + 1 + 2 + 6 + 20 = 105 bits = 17.666 * 6 -> 18 u32 (108 possible)

View file

@ -1,5 +1,8 @@
En 12 chapitres (trous) de 12 sous-chapitres (points / niveaux de compréhension) ? En 12 chapitres (trous) de 12 sous-chapitres (points / niveaux de compréhension) ?
Célébration -> s'inspirer du _petit traité invitant à la découverte de l'art subtil du go_
comparaison échecs -> comparaison backgammon
Les règles Les règles
- le matériel - le matériel
- le mouvement - le mouvement
@ -15,7 +18,7 @@ La stratégie
L'encyclopédie L'encyclopédie
- comparaison avec d'autres jeux - comparaison avec d'autres jeux
- échecs/go - échecs/go ?
- histoire - histoire
- traités - traités
- vocabulaire - vocabulaire

View file

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
base64 = "0.21.7"
log = "0.4.20" log = "0.4.20"
rand = "0.8.5" rand = "0.8.5"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View file

@ -34,39 +34,39 @@ impl Board {
Board::default() Board::default()
} }
pub fn toGnupgPosId(&self) -> Vec<bool> { pub fn to_gnupg_pos_id(&self) -> String {
// Pieces placement -> 77bits (24 + 23 + 30 max) // 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 // inspired by https://www.gnu.org/software/gnubg/manual/html_node/A-technical-description-of-the-Position-ID.html
// - white positions // - white positions
let white_board = self.board.clone(); let white_board = self.board.clone();
let mut posBits = white_board.iter().fold(vec![], |acc, nb| { let mut pos_bits = white_board.iter().fold(vec![], |acc, nb| {
let mut newAcc = acc.clone(); let mut new_acc = acc.clone();
if *nb as usize > 0 { if *nb > 0 {
// add as many `true` as there are pieces on the arrow // add as many `true` as there are pieces on the arrow
newAcc.append(&mut vec![true; *nb as usize]); new_acc.append(&mut vec!['1'; *nb as usize]);
} }
newAcc.push(false); // arrow separator new_acc.push('0'); // arrow separator
newAcc new_acc
}); });
// - black positions // - black positions
let mut black_board = self.board.clone(); let mut black_board = self.board.clone();
black_board.reverse(); black_board.reverse();
let mut posBlackBits = black_board.iter().fold(vec![], |acc, nb| { let mut pos_black_bits = black_board.iter().fold(vec![], |acc, nb| {
let mut newAcc = acc.clone(); let mut new_acc = acc.clone();
if (*nb as usize) < 0 { if *nb < 0 {
// add as many `true` as there are pieces on the arrow // add as many `true` as there are pieces on the arrow
newAcc.append(&mut vec![true; 0 - *nb as usize]); new_acc.append(&mut vec!['1'; (0 - *nb) as usize]);
} }
newAcc.push(false); // arrow separator new_acc.push('0'); // arrow separator
newAcc new_acc
}); });
posBits.append(&mut posBlackBits); pos_bits.append(&mut pos_black_bits);
// fill with 0 bits until 77 // fill with 0 bits until 77
posBits.resize(77, false); pos_bits.resize(77, '0');
posBits pos_bits.iter().collect::<String>()
} }
/// Set checkers for a player on a field /// Set checkers for a player on a field
@ -124,7 +124,7 @@ impl Board {
} }
} }
Color::Black => { Color::Black => {
if self.raw_board.0.board[23 - field] > 1 { if self.board[23 - field] > 1 {
Ok(true) Ok(true)
} else { } else {
Ok(false) Ok(false)
@ -157,105 +157,17 @@ mod tests {
assert_eq!(Board::new(), Board::default()); assert_eq!(Board::new(), Board::default());
} }
#[test]
fn default_player_board() {
assert_eq!(
PlayerBoard::default(),
PlayerBoard {
board: [0, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,],
off: 0
}
);
}
#[test]
fn get_board() {
let board = Board::new();
assert_eq!(
board.get(),
BoardDisplay {
board: [
-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, -5, 5, 0, 0, 0, -3, 0, -5, 0, 0, 0, 0, 2,
],
off: (0, 0)
}
);
}
#[test]
fn get_off() {
let board = Board::new();
assert_eq!(board.get_off(), (0, 0));
}
#[test]
fn set_player0() -> Result<(), Error> {
let mut board = Board::new();
let player = Player {
name: "".into(),
color: Color::White,
};
board.set(&player, 1, 1)?;
assert_eq!(board.get().board[1], 1);
Ok(())
}
#[test]
fn set_player1() -> Result<(), Error> {
let mut board = Board::new();
let player = Player {
name: "".into(),
color: Color::Black,
};
board.set(&player, 2, 1)?;
assert_eq!(board.get().board[21], -1);
Ok(())
}
#[test]
fn set_player0_off() -> Result<(), Error> {
let mut board = Board::new();
let player = Player {
name: "".into(),
color: Color::White,
};
board.set_off(player, 1)?;
assert_eq!(board.get().off.0, 1);
Ok(())
}
#[test]
fn set_player1_off() -> Result<(), Error> {
let mut board = Board::new();
let player = Player {
name: "".into(),
color: Color::Black,
};
board.set_off(player, 1)?;
assert_eq!(board.get().off.1, 1);
Ok(())
}
#[test]
fn set_player1_off1() -> Result<(), Error> {
let mut board = Board::new();
let player = Player {
name: "".into(),
color: Color::Black,
};
board.set_off(player, 1)?;
board.set_off(player, 1)?;
assert_eq!(board.get().off.1, 2);
Ok(())
}
#[test] #[test]
fn blocked_player0() -> Result<(), Error> { fn blocked_player0() -> Result<(), Error> {
let board = Board::new(); let board = Board::new();
assert!(board.blocked( assert!(board.blocked(
&Player { &Player {
name: "".into(), name: "".into(),
color: Color::White color: Color::White,
holes: 0,
points: 0,
can_bredouille: true,
can_big_bredouille: true
}, },
0 0
)?); )?);
@ -268,7 +180,11 @@ mod tests {
assert!(board.blocked( assert!(board.blocked(
&Player { &Player {
name: "".into(), name: "".into(),
color: Color::Black color: Color::Black,
holes: 0,
points: 0,
can_bredouille: true,
can_big_bredouille: true
}, },
0 0
)?); )?);
@ -282,6 +198,10 @@ mod tests {
&Player { &Player {
name: "".into(), name: "".into(),
color: Color::Black, color: Color::Black,
holes: 0,
points: 0,
can_bredouille: true,
can_big_bredouille: true
}, },
1, 1,
2, 2,
@ -289,7 +209,11 @@ mod tests {
assert!(board.blocked( assert!(board.blocked(
&Player { &Player {
name: "".into(), name: "".into(),
color: Color::White color: Color::White,
holes: 0,
points: 0,
can_bredouille: true,
can_big_bredouille: true
}, },
22 22
)?); )?);
@ -303,6 +227,10 @@ mod tests {
&Player { &Player {
name: "".into(), name: "".into(),
color: Color::White, color: Color::White,
holes: 0,
points: 0,
can_bredouille: true,
can_big_bredouille: true
}, },
1, 1,
2, 2,
@ -310,7 +238,11 @@ mod tests {
assert!(board.blocked( assert!(board.blocked(
&Player { &Player {
name: "".into(), name: "".into(),
color: Color::Black color: Color::Black,
holes: 0,
points: 0,
can_bredouille: true,
can_big_bredouille: true
}, },
22 22
)?); )?);
@ -324,135 +256,17 @@ mod tests {
.blocked( .blocked(
&Player { &Player {
name: "".into(), name: "".into(),
color: Color::White color: Color::White,
holes: 0,
points: 0,
can_bredouille: true,
can_big_bredouille: true
}, },
24 24
) )
.is_err()); .is_err());
} }
#[test]
fn set_field_with_1_checker_player0_a() -> Result<(), Error> {
let mut board = Board::new();
board.set(
&Player {
name: "".into(),
color: Color::White,
},
1,
1,
)?;
board.set(
&Player {
name: "".into(),
color: Color::Black,
},
22,
1,
)?;
assert_eq!(board.get().board[1], -1);
Ok(())
}
#[test]
fn set_field_with_1_checker_player0_b() -> Result<(), Error> {
let mut board = Board::new();
board.set(
&Player {
name: "".into(),
color: Color::White,
},
1,
1,
)?;
board.set(
&Player {
name: "".into(),
color: Color::Black,
},
22,
1,
)?;
assert_eq!(board.get().board[1], -1);
Ok(())
}
#[test]
fn set_field_with_1_checker_player1_a() -> Result<(), Error> {
let mut board = Board::new();
board.set(
&Player {
name: "".into(),
color: Color::Black,
},
1,
1,
)?;
board.set(
&Player {
name: "".into(),
color: Color::White,
},
22,
1,
)?;
assert_eq!(board.get().board[22], 1);
Ok(())
}
#[test]
fn set_field_with_1_checker_player1_b() -> Result<(), Error> {
let mut board = Board::new();
board.set(
&Player {
name: "".into(),
color: Color::Black,
},
1,
1,
)?;
board.set(
&Player {
name: "".into(),
color: Color::White,
},
22,
1,
)?;
assert_eq!(board.get().board[22], 1);
Ok(())
}
#[test]
fn set_field_with_2_checkers_player0_a() -> Result<(), Error> {
let mut board = Board::new();
board.set(
&Player {
name: "".into(),
color: Color::White,
},
23,
2,
)?;
assert_eq!(board.get().board[23], 4);
Ok(())
}
#[test]
fn set_field_with_2_checkers_player0_b() -> Result<(), Error> {
let mut board = Board::new();
board.set(
&Player {
name: "".into(),
color: Color::White,
},
23,
-1,
)?;
assert_eq!(board.get().board[23], 1);
Ok(())
}
#[test] #[test]
fn set_field_blocked() { fn set_field_blocked() {
let mut board = Board::new(); let mut board = Board::new();
@ -460,7 +274,11 @@ mod tests {
.set( .set(
&Player { &Player {
name: "".into(), name: "".into(),
color: Color::White color: Color::White,
holes: 0,
points: 0,
can_bredouille: true,
can_big_bredouille: true
}, },
0, 0,
2 2
@ -475,7 +293,11 @@ mod tests {
.set( .set(
&Player { &Player {
name: "".into(), name: "".into(),
color: Color::White color: Color::White,
holes: 0,
points: 0,
can_bredouille: true,
can_big_bredouille: true
}, },
50, 50,
2 2
@ -490,7 +312,11 @@ mod tests {
.set( .set(
&Player { &Player {
name: "".into(), name: "".into(),
color: Color::White color: Color::White,
holes: 0,
points: 0,
can_bredouille: true,
can_big_bredouille: true
}, },
23, 23,
-3 -3
@ -505,7 +331,11 @@ mod tests {
.set( .set(
&Player { &Player {
name: "".into(), name: "".into(),
color: Color::Black color: Color::Black,
holes: 0,
points: 0,
can_bredouille: true,
can_big_bredouille: true
}, },
23, 23,
-3 -3

View file

@ -23,6 +23,21 @@ impl Dices {
values: (v.0, v.1), values: (v.0, v.1),
} }
} }
pub fn to_bits_string(self) -> String {
format!("{:0>3b}{:0>3b}", self.values.0, self.values.1)
}
// pub fn to_bits(self) -> [bool;6] {
// self.to_bits_string().into_bytes().iter().map(|strbit| *strbit == '1' as u8).collect()
// }
// pub from_bits_string(String bits) -> Self {
//
// Dices {
// values: ()
// }
// }
} }
/// Trait to roll the dices /// Trait to roll the dices
@ -42,4 +57,10 @@ mod tests {
assert!(dices.values.1 >= 1 && dices.values.1 <= 6); assert!(dices.values.1 >= 1 && dices.values.1 <= 6);
} }
#[test]
fn test_to_bits_string() {
let dices = Dices { values: (4, 2)};
assert!(dices.to_bits_string() == "100010");
}
} }

View file

@ -5,11 +5,12 @@ use crate::player::{Color, Player, PlayerId};
use crate::Error; use crate::Error;
use log::{error, info, trace, warn}; use log::{error, info, trace, warn};
// use itertools::Itertools;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::{fmt, vec}; use std::{fmt, vec, str};
type TGPN = [u8]; use base64::{engine::general_purpose, Engine as _};
/// The different stages a game can be in. (not to be confused with the entire "GameState") /// The different stages a game can be in. (not to be confused with the entire "GameState")
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
@ -74,6 +75,10 @@ impl GameState {
GameState::default() GameState::default()
} }
fn add_player(&mut self, player_id: PlayerId, player: Player) {
self.players.insert(player_id, player);
}
/// Format to TGPN notation (Tables games position notation) /// Format to TGPN notation (Tables games position notation)
// fn toTGPN(&self, f: &mut fmt::Formatter) -> TGPN { // fn toTGPN(&self, f: &mut fmt::Formatter) -> TGPN {
pub fn toTGPN(&self, f: &mut fmt::Formatter) -> fmt::Result { pub fn toTGPN(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -83,52 +88,67 @@ impl GameState {
} }
/// Calculate game state id : /// Calculate game state id :
pub fn to_string_id(&self, f: &mut fmt::Formatter) -> fmt::Result { pub fn to_string_id(&self) -> String {
// Pieces placement -> 77 bits (24 + 23 + 30 max) // Pieces placement -> 77 bits (24 + 23 + 30 max)
let mut pos_bits = self.board.toGnupgPosId(); let mut pos_bits = self.board.to_gnupg_pos_id();
// active player -> 1 bit // active player -> 1 bit
// white : 0 (false) // white : 0 (false)
// black : 1 (true) // black : 1 (true)
pos_bits.push( pos_bits.push(
self.who_plays() self.who_plays()
.map(|player| player.color == Color::Black) .map(|player| if player.color == Color::Black { '1'} else {'0'})
.unwrap_or(false), // White by default .unwrap_or('0'), // White by default
); );
// step -> 2 bits // step -> 2 bits
// * roll dice let step_bits = match self.turn_stage {
// * mark points (jeton & fichet) & set bredouille markers (3rd jeton & pavillon) TurnStage::RollDice => "01",
// * move pieces TurnStage::MarkPoints => "01",
let mut step_bits = match self.turn_stage { TurnStage::Move => "10",
TurnStage::RollDice => [false, false],
TurnStage::MarkPoints => [false, true],
TurnStage::Move => [true, false],
}; };
pos_bits.append(&mut step_bits.into()); pos_bits.push_str(step_bits);
// dice roll -> 6 bits
let dice_bits = self.dices.to_bits_string();
pos_bits.push_str(&dice_bits);
// dice roll -> 4 bits
let mut dice_bits = match self.dices {
TurnStage::RollDice => [false, false],
TurnStage::MarkPoints => [false, true],
TurnStage::Move => [true, false],
};
pos_bits.append(&mut step_bits.into());
// points 10bits x2 joueurs = 20bits // points 10bits x2 joueurs = 20bits
// * points -> 4bits let white_bits = self.get_white_player().unwrap().to_bits_string();
// * trous -> 4bits let black_bits = self.get_black_player().unwrap().to_bits_string();
// * bredouille possible 1bit pos_bits.push_str(&white_bits);
// * grande bredouille possible 1bit pos_bits.push_str(&black_bits);
let mut s = String::new(); pos_bits = format!("{:0>108}", pos_bits);
// s.push_str(&format!("Dices: {:?}\n", self.dices)); // println!("{}", pos_bits);
write!(f, "{}", s) let pos_u8 = pos_bits
.as_bytes().chunks(6)
.map(|chunk| str::from_utf8(chunk).unwrap())
.map(|chunk| u8::from_str_radix(chunk, 2).unwrap())
.collect::<Vec<u8>>();
general_purpose::STANDARD.encode(pos_u8)
} }
pub fn who_plays(&self) -> Option<&Player> { pub fn who_plays(&self) -> Option<&Player> {
self.players.get(&self.active_player_id) self.players.get(&self.active_player_id)
} }
pub fn get_white_player(&self) -> Option<&Player> {
self.players
.iter()
.filter(|(_id, player)| player.color == Color::White)
.map(|(_id, player)| player)
.next()
}
pub fn get_black_player(&self) -> Option<&Player> {
self.players
.iter()
.filter(|(_id, player)| player.color == Color::Black)
.map(|(_id, player)| player)
.next()
}
pub fn switch_active_player(&mut self) { pub fn switch_active_player(&mut self) {
let other_player_id = self let other_player_id = self
.players .players
@ -252,6 +272,10 @@ impl GameState {
Player { Player {
name: name.to_string(), name: name.to_string(),
color, color,
holes: 0,
points: 0,
can_bredouille: true,
can_big_bredouille: true
}, },
); );
} }
@ -322,10 +346,6 @@ pub enum GameEvent {
impl Roll for GameState { impl Roll for GameState {
fn roll(&mut self) -> Result<&mut Self, Error> { fn roll(&mut self) -> Result<&mut Self, Error> {
if !self.dices.consumed.0 && !self.dices.consumed.1 {
return Err(Error::MoveFirst);
}
self.dices = self.dices.roll(); self.dices = self.dices.roll();
if self.who_plays().is_none() { if self.who_plays().is_none() {
let diff = self.dices.values.0 - self.dices.values.1; let diff = self.dices.values.0 - self.dices.values.1;
@ -344,36 +364,20 @@ impl Move for GameState {
// check if move is permitted // check if move is permitted
let _ = self.move_permitted(player, dice)?; let _ = self.move_permitted(player, dice)?;
// check if the dice value has been consumed
if (dice == self.dices.values.0 && self.dices.consumed.0)
|| (dice == self.dices.values.1 && self.dices.consumed.1)
{
return Err(Error::MoveInvalid);
}
// remove checker from old position // remove checker from old position
self.board.set(player, from, -1)?; self.board.set(player, from, -1)?;
// move checker to new position, in case it is reaching the off position, set it off // move checker to new position, in case it is reaching the off position, set it off
let new_position = from as i8 - dice as i8; let new_position = from as i8 - dice as i8;
if new_position < 0 { if new_position < 0 {
self.board.set_off(player, 1)?; // self.board.set_off(player, 1)?;
} else { } else {
self.board.set(player, new_position as usize, 1)?; // self.board.set(player, new_position as usize, 1)?;
}
// set dice value to consumed
if dice == self.dices.values.0 && !self.dices.consumed.0 {
self.dices.consumed.0 = true;
} else if dice == self.dices.values.1 && !self.dices.consumed.1 {
self.dices.consumed.1 = true;
} }
// switch to other player if all dices have been consumed // switch to other player if all dices have been consumed
if self.dices.consumed.0 && self.dices.consumed.1 {
self.switch_active_player(); self.switch_active_player();
self.roll_first = true; self.roll_first = true;
}
Ok(self) Ok(self)
} }
@ -403,4 +407,21 @@ impl Move for GameState {
Ok(self) Ok(self)
} }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_string_id() {
let mut state = GameState::default();
state.add_player(1, Player::new("player1".into(), Color::White));
state.add_player(2, Player::new("player2".into(), Color::Black));
let string_id = state.to_string_id();
// println!("string_id : {}", string_id);
assert!(string_id == "Dz8+AAAAAT8/MAAAAAQAADAD");
}
} }

View file

@ -16,6 +16,29 @@ pub enum Color {
pub struct Player { pub struct Player {
pub name: String, pub name: String,
pub color: Color, pub color: Color,
pub points: u8,
pub holes: u8,
pub can_bredouille: bool,
pub can_big_bredouille: bool,
}
impl Player {
pub fn new(name: String, color: Color) -> Self {
Player {
name,
color,
points: 0,
holes: 0,
can_bredouille: true,
can_big_bredouille: true,
}
}
pub fn to_bits_string(&self) -> String {
format!("{:0>4b}{:0>4b}{:b}{:b}", self.points, self.holes, self.can_bredouille as u8, self.can_big_bredouille as u8)
}
} }
/// Represents a player in the game. /// Represents a player in the game.
@ -71,4 +94,12 @@ mod tests {
assert_eq!(CurrentPlayer::Player0.other(), CurrentPlayer::Player1); assert_eq!(CurrentPlayer::Player0.other(), CurrentPlayer::Player1);
assert_eq!(CurrentPlayer::Player1.other(), CurrentPlayer::Player0); assert_eq!(CurrentPlayer::Player1.other(), CurrentPlayer::Player0);
} }
#[test]
fn test_to_bits_string() {
let player = Player { name: "Edgar".into(), color: Color::White, points: 11, holes: 3, can_bredouille: true, can_big_bredouille: false };
println!("{}", player.to_bits_string());
assert!(player.to_bits_string() == "1011001110");
}
} }