diff --git a/store/src/board.rs b/store/src/board.rs index d0f3615..d9f0fb5 100644 --- a/store/src/board.rs +++ b/store/src/board.rs @@ -639,6 +639,55 @@ impl Board { self.positions[field - 1] += unit; Ok(()) } + + pub fn from_gnupg_pos_id(bits: &str) -> Result { + let mut positions = [0i8; 24]; + let mut bit_idx = 0; + let bit_chars: Vec = bits.chars().collect(); + + // White checkers (points 1 to 24) + for i in 0..24 { + if bit_idx >= bit_chars.len() { + break; + } + let mut count = 0; + while bit_idx < bit_chars.len() && bit_chars[bit_idx] == '1' { + count += 1; + bit_idx += 1; + } + positions[i] = count; + if bit_idx < bit_chars.len() && bit_chars[bit_idx] == '0' { + bit_idx += 1; // Consume the '0' separator + } + } + + // Black checkers (points 24 down to 1) + for i in (0..24).rev() { + if bit_idx >= bit_chars.len() { + break; + } + let mut count = 0; + while bit_idx < bit_chars.len() && bit_chars[bit_idx] == '1' { + count += 1; + bit_idx += 1; + } + + if positions[i] == 0 { + positions[i] = -count; + } else if count > 0 { + return Err(format!( + "Invalid board: checkers of both colors on point {}", + i + 1 + )); + } + + if bit_idx < bit_chars.len() && bit_chars[bit_idx] == '0' { + bit_idx += 1; // Consume the '0' separator + } + } + + Ok(Board { positions }) + } } // Unit Tests diff --git a/store/src/dice.rs b/store/src/dice.rs index 348410d..4b5fca6 100644 --- a/store/src/dice.rs +++ b/store/src/dice.rs @@ -55,6 +55,17 @@ impl Dice { format!("{:0>3b}{:0>3b}", self.values.0, self.values.1) } + pub fn from_bits_string(bits: &str) -> Result { + if bits.len() != 6 { + return Err("Invalid bit string length for dice".to_string()); + } + let d1_str = &bits[0..3]; + let d2_str = &bits[3..6]; + let d1 = u8::from_str_radix(d1_str, 2).map_err(|e| e.to_string())?; + let d2 = u8::from_str_radix(d2_str, 2).map_err(|e| e.to_string())?; + Ok(Dice { values: (d1, d2) }) + } + pub fn to_display_string(self) -> String { format!("{} & {}", self.values.0, self.values.1) } diff --git a/store/src/game.rs b/store/src/game.rs index b63ffcd..5506824 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -253,7 +253,7 @@ impl GameState { pos_bits.push_str(&white_bits); pos_bits.push_str(&black_bits); - pos_bits = format!("{pos_bits:0>108}"); + pos_bits = format!("{pos_bits:0<108}"); // println!("{}", pos_bits); let pos_u8 = pos_bits .as_bytes() @@ -264,6 +264,81 @@ impl GameState { general_purpose::STANDARD.encode(pos_u8) } + pub fn from_string_id(id: &str) -> Result { + let bytes = general_purpose::STANDARD + .decode(id) + .map_err(|e| e.to_string())?; + + let bits_str: String = bytes.iter().map(|byte| format!("{:06b}", byte)).collect(); + + // The original string was padded to 108 bits. + let bits = if bits_str.len() >= 108 { + &bits_str[..108] + } else { + return Err("Invalid decoded string length".to_string()); + }; + + let board_bits = &bits[0..77]; + let board = Board::from_gnupg_pos_id(board_bits)?; + + let active_player_bit = bits.chars().nth(77).unwrap(); + let active_player_color = if active_player_bit == '1' { + Color::Black + } else { + Color::White + }; + + let turn_stage_bits = &bits[78..81]; + let turn_stage = match turn_stage_bits { + "000" => TurnStage::RollWaiting, + "001" => TurnStage::RollDice, + "010" => TurnStage::MarkPoints, + "011" => TurnStage::HoldOrGoChoice, + "100" => TurnStage::Move, + "101" => TurnStage::MarkAdvPoints, + _ => return Err(format!("Invalid bits for turn stage : {turn_stage_bits}")), + }; + + let dice_bits = &bits[81..87]; + let dice = Dice::from_bits_string(dice_bits).map_err(|e| e.to_string())?; + + let white_player_bits = &bits[87..97]; + let black_player_bits = &bits[97..107]; + + let white_player = + Player::from_bits_string(white_player_bits, "Player 1".to_string(), Color::White) + .map_err(|e| e.to_string())?; + let black_player = + Player::from_bits_string(black_player_bits, "Player 2".to_string(), Color::Black) + .map_err(|e| e.to_string())?; + + let mut players = HashMap::new(); + players.insert(1, white_player); + players.insert(2, black_player); + + let active_player_id = if active_player_color == Color::White { + 1 + } else { + 2 + }; + + // Some fields are not in the ID, so we use defaults. + Ok(GameState { + stage: Stage::InGame, // Assume InGame from ID + turn_stage, + board, + active_player_id, + players, + history: Vec::new(), + dice, + dice_points: (0, 0), + dice_moves: (CheckerMove::default(), CheckerMove::default()), + dice_jans: PossibleJans::default(), + roll_first: false, // Assume not first roll + schools_enabled: false, // Assume disabled + }) + } + pub fn who_plays(&self) -> Option<&Player> { self.get_active_player() } @@ -850,7 +925,16 @@ mod tests { let state = init_test_gamestate(TurnStage::RollDice); let string_id = state.to_string_id(); // println!("string_id : {}", string_id); - assert_eq!(string_id, "Hz88AAAAAz8/IAAAAAQAADAD"); + assert_eq!(string_id, "Pz84AAAABz8/AAAAAAgAASAG"); + let new_state = GameState::from_string_id(&string_id).unwrap(); + assert_eq!(state.board, new_state.board); + assert_eq!(state.active_player_id, new_state.active_player_id); + assert_eq!(state.turn_stage, new_state.turn_stage); + assert_eq!(state.dice, new_state.dice); + assert_eq!( + state.get_white_player().unwrap().points, + new_state.get_white_player().unwrap().points + ); } #[test] diff --git a/store/src/player.rs b/store/src/player.rs index d42120b..d990a1f 100644 --- a/store/src/player.rs +++ b/store/src/player.rs @@ -53,6 +53,26 @@ impl Player { ) } + pub fn from_bits_string(bits: &str, name: String, color: Color) -> Result { + if bits.len() != 10 { + return Err("Invalid bit string length for player".to_string()); + } + let points = u8::from_str_radix(&bits[0..4], 2).map_err(|e| e.to_string())?; + let holes = u8::from_str_radix(&bits[4..8], 2).map_err(|e| e.to_string())?; + let can_bredouille = bits.chars().nth(8).unwrap() == '1'; + let can_big_bredouille = bits.chars().nth(9).unwrap() == '1'; + + Ok(Player { + name, + color, + points, + holes, + can_bredouille, + can_big_bredouille, + dice_roll_count: 0, // This info is not in the string id + }) + } + pub fn to_vec(&self) -> Vec { vec![ self.points,