Compare commits
2 commits
7a501c90ea
...
4e299b04e2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e299b04e2 | ||
|
|
41383eddf6 |
|
|
@ -77,6 +77,16 @@ impl BoardGameBoard for TrictracBoard {
|
|||
}
|
||||
}
|
||||
|
||||
impl TrictracBoard {
|
||||
pub fn to_fen(&self) -> String {
|
||||
self.0.to_string_id()
|
||||
}
|
||||
|
||||
pub fn from_fen(fen: &str) -> Result<TrictracBoard, String> {
|
||||
crate::GameState::from_string_id(fen).map(TrictracBoard)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BoardMoves<'a, TrictracBoard> for TrictracBoard {
|
||||
type AllMovesIterator = TrictracAllMovesIterator;
|
||||
type AvailableMovesIterator = TrictracAvailableMovesIterator<'a>;
|
||||
|
|
|
|||
|
|
@ -639,6 +639,55 @@ impl Board {
|
|||
self.positions[field - 1] += unit;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn from_gnupg_pos_id(bits: &str) -> Result<Board, String> {
|
||||
let mut positions = [0i8; 24];
|
||||
let mut bit_idx = 0;
|
||||
let bit_chars: Vec<char> = 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
|
||||
|
|
|
|||
|
|
@ -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<Self, String> {
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Self, String> {
|
||||
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]
|
||||
|
|
|
|||
|
|
@ -53,6 +53,26 @@ impl Player {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn from_bits_string(bits: &str, name: String, color: Color) -> Result<Self, String> {
|
||||
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<u8> {
|
||||
vec![
|
||||
self.points,
|
||||
|
|
|
|||
Loading…
Reference in a new issue