From 25a5470a12bdc73fd244462399047a8940b15cf9 Mon Sep 17 00:00:00 2001 From: Henri Bourcereau Date: Sat, 20 Jan 2024 21:40:06 +0100 Subject: [PATCH] GameState::to_string_id --- Cargo.lock | 7 +- doc/backlog.md | 4 +- doc/traité.md | 5 +- store/Cargo.toml | 1 + store/src/board.rs | 310 ++++++++++---------------------------------- store/src/dice.rs | 21 +++ store/src/game.rs | 125 ++++++++++-------- store/src/player.rs | 31 +++++ 8 files changed, 206 insertions(+), 298 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index feaa2a7..fb211e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,9 +284,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bevy" @@ -3041,7 +3041,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bitflags 2.4.1", "serde", "serde_derive", @@ -3248,6 +3248,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" name = "store" version = "0.1.0" dependencies = [ + "base64 0.21.7", "log", "rand", "serde", diff --git a/doc/backlog.md b/doc/backlog.md index 16d3ada..df1da9c 100644 --- a/doc/backlog.md +++ b/doc/backlog.md @@ -57,11 +57,11 @@ Encodage efficace : https://www.gnu.org/software/gnubg/manual/html_node/A-techni * roll dice * mark points (jeton & fichet) & set bredouille markers (3rd jeton & pavillon) * move pieces -* dice roll -> 4bits +* dice roll -> 6bits * points 10bits x2 joueurs = 20bits * points -> 4bits * trous -> 4bits * 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) diff --git a/doc/traité.md b/doc/traité.md index fb8dd47..19fc711 100644 --- a/doc/traité.md +++ b/doc/traité.md @@ -1,5 +1,8 @@ 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 - le matériel - le mouvement @@ -15,7 +18,7 @@ La stratégie L'encyclopédie - comparaison avec d'autres jeux - - échecs/go + - échecs/go ? - histoire - traités - vocabulaire diff --git a/store/Cargo.toml b/store/Cargo.toml index c212c01..d474ce3 100644 --- a/store/Cargo.toml +++ b/store/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +base64 = "0.21.7" log = "0.4.20" rand = "0.8.5" serde = { version = "1.0", features = ["derive"] } diff --git a/store/src/board.rs b/store/src/board.rs index 13d9e2c..506c8c3 100644 --- a/store/src/board.rs +++ b/store/src/board.rs @@ -34,39 +34,39 @@ impl Board { Board::default() } - pub fn toGnupgPosId(&self) -> Vec { + pub fn to_gnupg_pos_id(&self) -> String { // 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(); - let mut posBits = white_board.iter().fold(vec![], |acc, nb| { - let mut newAcc = acc.clone(); - if *nb as usize > 0 { + let mut pos_bits = white_board.iter().fold(vec![], |acc, nb| { + let mut new_acc = acc.clone(); + if *nb > 0 { // 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 - newAcc + new_acc.push('0'); // arrow separator + new_acc }); // - black positions let mut black_board = self.board.clone(); black_board.reverse(); - let mut posBlackBits = black_board.iter().fold(vec![], |acc, nb| { - let mut newAcc = acc.clone(); - if (*nb as usize) < 0 { + let mut pos_black_bits = black_board.iter().fold(vec![], |acc, nb| { + let mut new_acc = acc.clone(); + if *nb < 0 { // 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 - newAcc + new_acc.push('0'); // arrow separator + new_acc }); - posBits.append(&mut posBlackBits); + pos_bits.append(&mut pos_black_bits); // fill with 0 bits until 77 - posBits.resize(77, false); - posBits + pos_bits.resize(77, '0'); + pos_bits.iter().collect::() } /// Set checkers for a player on a field @@ -124,7 +124,7 @@ impl Board { } } Color::Black => { - if self.raw_board.0.board[23 - field] > 1 { + if self.board[23 - field] > 1 { Ok(true) } else { Ok(false) @@ -157,105 +157,17 @@ mod tests { 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] fn blocked_player0() -> Result<(), Error> { let board = Board::new(); assert!(board.blocked( &Player { name: "".into(), - color: Color::White + color: Color::White, + holes: 0, + points: 0, + can_bredouille: true, + can_big_bredouille: true }, 0 )?); @@ -268,7 +180,11 @@ mod tests { assert!(board.blocked( &Player { name: "".into(), - color: Color::Black + color: Color::Black, + holes: 0, + points: 0, + can_bredouille: true, + can_big_bredouille: true }, 0 )?); @@ -282,6 +198,10 @@ mod tests { &Player { name: "".into(), color: Color::Black, + holes: 0, + points: 0, + can_bredouille: true, + can_big_bredouille: true }, 1, 2, @@ -289,7 +209,11 @@ mod tests { assert!(board.blocked( &Player { name: "".into(), - color: Color::White + color: Color::White, + holes: 0, + points: 0, + can_bredouille: true, + can_big_bredouille: true }, 22 )?); @@ -303,6 +227,10 @@ mod tests { &Player { name: "".into(), color: Color::White, + holes: 0, + points: 0, + can_bredouille: true, + can_big_bredouille: true }, 1, 2, @@ -310,7 +238,11 @@ mod tests { assert!(board.blocked( &Player { name: "".into(), - color: Color::Black + color: Color::Black, + holes: 0, + points: 0, + can_bredouille: true, + can_big_bredouille: true }, 22 )?); @@ -324,135 +256,17 @@ mod tests { .blocked( &Player { name: "".into(), - color: Color::White + color: Color::White, + holes: 0, + points: 0, + can_bredouille: true, + can_big_bredouille: true }, 24 ) .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] fn set_field_blocked() { let mut board = Board::new(); @@ -460,7 +274,11 @@ mod tests { .set( &Player { name: "".into(), - color: Color::White + color: Color::White, + holes: 0, + points: 0, + can_bredouille: true, + can_big_bredouille: true }, 0, 2 @@ -475,7 +293,11 @@ mod tests { .set( &Player { name: "".into(), - color: Color::White + color: Color::White, + holes: 0, + points: 0, + can_bredouille: true, + can_big_bredouille: true }, 50, 2 @@ -490,7 +312,11 @@ mod tests { .set( &Player { name: "".into(), - color: Color::White + color: Color::White, + holes: 0, + points: 0, + can_bredouille: true, + can_big_bredouille: true }, 23, -3 @@ -505,7 +331,11 @@ mod tests { .set( &Player { name: "".into(), - color: Color::Black + color: Color::Black, + holes: 0, + points: 0, + can_bredouille: true, + can_big_bredouille: true }, 23, -3 diff --git a/store/src/dice.rs b/store/src/dice.rs index 5266e32..ae07d76 100644 --- a/store/src/dice.rs +++ b/store/src/dice.rs @@ -23,6 +23,21 @@ impl Dices { 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 @@ -42,4 +57,10 @@ mod tests { 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"); + } + } diff --git a/store/src/game.rs b/store/src/game.rs index 6791af1..7c14784 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -5,11 +5,12 @@ use crate::player::{Color, Player, PlayerId}; use crate::Error; use log::{error, info, trace, warn}; +// use itertools::Itertools; use serde::{Deserialize, Serialize}; 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") #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -74,6 +75,10 @@ impl GameState { 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) // fn toTGPN(&self, f: &mut fmt::Formatter) -> TGPN { pub fn toTGPN(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -83,52 +88,67 @@ impl GameState { } /// 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) - let mut pos_bits = self.board.toGnupgPosId(); + let mut pos_bits = self.board.to_gnupg_pos_id(); // active player -> 1 bit // white : 0 (false) // black : 1 (true) pos_bits.push( self.who_plays() - .map(|player| player.color == Color::Black) - .unwrap_or(false), // White by default + .map(|player| if player.color == Color::Black { '1'} else {'0'}) + .unwrap_or('0'), // White by default ); // step -> 2 bits - // * roll dice - // * mark points (jeton & fichet) & set bredouille markers (3rd jeton & pavillon) - // * move pieces - let mut step_bits = match self.turn_stage { - TurnStage::RollDice => [false, false], - TurnStage::MarkPoints => [false, true], - TurnStage::Move => [true, false], + let step_bits = match self.turn_stage { + TurnStage::RollDice => "01", + TurnStage::MarkPoints => "01", + TurnStage::Move => "10", }; - 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 -> 4bits - // * trous -> 4bits - // * bredouille possible 1bit - // * grande bredouille possible 1bit + let white_bits = self.get_white_player().unwrap().to_bits_string(); + let black_bits = self.get_black_player().unwrap().to_bits_string(); + pos_bits.push_str(&white_bits); + pos_bits.push_str(&black_bits); - let mut s = String::new(); - // s.push_str(&format!("Dices: {:?}\n", self.dices)); - write!(f, "{}", s) + pos_bits = format!("{:0>108}", pos_bits); + // println!("{}", pos_bits); + 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::>(); + general_purpose::STANDARD.encode(pos_u8) } pub fn who_plays(&self) -> Option<&Player> { 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) { let other_player_id = self .players @@ -252,6 +272,10 @@ impl GameState { Player { name: name.to_string(), color, + holes: 0, + points: 0, + can_bredouille: true, + can_big_bredouille: true }, ); } @@ -322,10 +346,6 @@ pub enum GameEvent { impl Roll for GameState { 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(); if self.who_plays().is_none() { let diff = self.dices.values.0 - self.dices.values.1; @@ -344,36 +364,20 @@ impl Move for GameState { // check if move is permitted 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 self.board.set(player, from, -1)?; // 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; if new_position < 0 { - self.board.set_off(player, 1)?; + // self.board.set_off(player, 1)?; } else { - 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; + // self.board.set(player, new_position as usize, 1)?; } // switch to other player if all dices have been consumed - if self.dices.consumed.0 && self.dices.consumed.1 { - self.switch_active_player(); - self.roll_first = true; - } + self.switch_active_player(); + self.roll_first = true; Ok(self) } @@ -403,4 +407,21 @@ impl Move for GameState { 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"); + } + } diff --git a/store/src/player.rs b/store/src/player.rs index 98003df..8b15cba 100644 --- a/store/src/player.rs +++ b/store/src/player.rs @@ -16,6 +16,29 @@ pub enum Color { pub struct Player { pub name: String, 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. @@ -71,4 +94,12 @@ mod tests { assert_eq!(CurrentPlayer::Player0.other(), CurrentPlayer::Player1); 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"); + } + }