refact: remove unwrap() & panic!
This commit is contained in:
parent
953b5f451a
commit
ad157e1626
4 changed files with 177 additions and 52 deletions
|
|
@ -55,8 +55,12 @@ impl BoardGameBoard for TrictracBoard {
|
||||||
|
|
||||||
fn play(&mut self, mv: Self::Move) -> Result<(), PlayError> {
|
fn play(&mut self, mv: Self::Move) -> Result<(), PlayError> {
|
||||||
self.check_can_play(mv)?;
|
self.check_can_play(mv)?;
|
||||||
self.0.consume(&mv.to_event(&self.0).unwrap());
|
if let Some(evt) = mv.to_event(&self.0) {
|
||||||
Ok(())
|
self.0.consume(&evt);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(PlayError::UnavailableMove)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outcome(&self) -> Option<Outcome> {
|
fn outcome(&self) -> Option<Outcome> {
|
||||||
|
|
@ -159,9 +163,9 @@ impl InternalIterator for TrictracAvailableMovesIterator<'_> {
|
||||||
where
|
where
|
||||||
F: FnMut(Self::Item) -> ControlFlow<R>,
|
F: FnMut(Self::Item) -> ControlFlow<R>,
|
||||||
{
|
{
|
||||||
get_valid_actions(&self.board.0)
|
match get_valid_actions(&self.board.0) {
|
||||||
.unwrap()
|
Ok(actions) => actions.into_iter().try_for_each(f),
|
||||||
.into_iter()
|
Err(_) => ControlFlow::Continue(()),
|
||||||
.try_for_each(f)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use crate::dice::Dice;
|
||||||
use crate::game_rules_moves::MoveRules;
|
use crate::game_rules_moves::MoveRules;
|
||||||
use crate::game_rules_points::{PointsRules, PossibleJans, PossibleJansMethods};
|
use crate::game_rules_points::{PointsRules, PossibleJans, PossibleJansMethods};
|
||||||
use crate::player::{Color, Player, PlayerId};
|
use crate::player::{Color, Player, PlayerId};
|
||||||
|
// use anyhow::{Context, Result};
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
|
|
||||||
// use itertools::Itertools;
|
// use itertools::Itertools;
|
||||||
|
|
@ -90,11 +91,12 @@ impl fmt::Display for GameState {
|
||||||
self.stage, self.turn_stage
|
self.stage, self.turn_stage
|
||||||
));
|
));
|
||||||
s.push_str(&format!("Dice: {:?}\n", self.dice));
|
s.push_str(&format!("Dice: {:?}\n", self.dice));
|
||||||
|
let empty_string = String::from("");
|
||||||
s.push_str(&format!(
|
s.push_str(&format!(
|
||||||
"Who plays: {}\n",
|
"Who plays: {}\n",
|
||||||
self.who_plays()
|
self.who_plays()
|
||||||
.map(|player| &player.name)
|
.map(|player| &player.name)
|
||||||
.unwrap_or(&String::from(""))
|
.unwrap_or_else(|| &empty_string)
|
||||||
));
|
));
|
||||||
s.push_str(&format!("Board: {:?}\n", self.board));
|
s.push_str(&format!("Board: {:?}\n", self.board));
|
||||||
// s.push_str(&format!("History: {:?}\n", self.history));
|
// s.push_str(&format!("History: {:?}\n", self.history));
|
||||||
|
|
@ -233,20 +235,13 @@ impl GameState {
|
||||||
// points, trous, bredouille, grande bredouille length=4 x2 joueurs = 8
|
// points, trous, bredouille, grande bredouille length=4 x2 joueurs = 8
|
||||||
let white_player: Vec<i8> = self
|
let white_player: Vec<i8> = self
|
||||||
.get_white_player()
|
.get_white_player()
|
||||||
.unwrap()
|
.map(|p| p.to_vec().iter().map(|&x| x as i8).collect())
|
||||||
.to_vec()
|
.unwrap_or(vec![0; 10]);
|
||||||
.iter()
|
|
||||||
.map(|&x| x as i8)
|
|
||||||
.collect();
|
|
||||||
state.extend(white_player);
|
state.extend(white_player);
|
||||||
let black_player: Vec<i8> = self
|
let black_player: Vec<i8> = self
|
||||||
.get_black_player()
|
.get_black_player()
|
||||||
.unwrap()
|
.map(|p| p.to_vec().iter().map(|&x| x as i8).collect())
|
||||||
.to_vec()
|
.unwrap_or(vec![0; 10]);
|
||||||
.iter()
|
|
||||||
.map(|&x| x as i8)
|
|
||||||
.collect();
|
|
||||||
// .iter().map(|&x| x as i8) .collect()
|
|
||||||
state.extend(black_player);
|
state.extend(black_player);
|
||||||
|
|
||||||
// ensure state has length state_len
|
// ensure state has length state_len
|
||||||
|
|
@ -258,7 +253,7 @@ impl GameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate game state id :
|
/// Calculate game state id :
|
||||||
pub fn to_string_id(&self) -> String {
|
pub fn to_string_id_slow(&self) -> String {
|
||||||
// Pieces placement -> 77 bits (24 + 23 + 30 max)
|
// Pieces placement -> 77 bits (24 + 23 + 30 max)
|
||||||
let mut pos_bits = self.board.to_gnupg_pos_id();
|
let mut pos_bits = self.board.to_gnupg_pos_id();
|
||||||
|
|
||||||
|
|
@ -293,22 +288,141 @@ impl GameState {
|
||||||
pos_bits.push_str(&dice_bits);
|
pos_bits.push_str(&dice_bits);
|
||||||
|
|
||||||
// points 10bits x2 joueurs = 20bits
|
// points 10bits x2 joueurs = 20bits
|
||||||
let white_bits = self.get_white_player().unwrap().to_bits_string();
|
let white_bits = self
|
||||||
let black_bits = self.get_black_player().unwrap().to_bits_string();
|
.get_white_player()
|
||||||
|
.map(|p| p.to_bits_string())
|
||||||
|
.unwrap_or("0000000000".into());
|
||||||
|
let black_bits = self
|
||||||
|
.get_black_player()
|
||||||
|
.map(|p| p.to_bits_string())
|
||||||
|
.unwrap_or("0000000000".into());
|
||||||
pos_bits.push_str(&white_bits);
|
pos_bits.push_str(&white_bits);
|
||||||
pos_bits.push_str(&black_bits);
|
pos_bits.push_str(&black_bits);
|
||||||
|
|
||||||
pos_bits = format!("{pos_bits:0<108}");
|
pos_bits = format!("{pos_bits:0<108}");
|
||||||
// println!("{}", 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::<Vec<u8>>();
|
||||||
|
|
||||||
let pos_u8 = pos_bits
|
let pos_u8 = pos_bits
|
||||||
.as_bytes()
|
.as_bytes()
|
||||||
.chunks(6)
|
.chunks(6)
|
||||||
.map(|chunk| str::from_utf8(chunk).unwrap())
|
.map(|chunk| chunk.iter().fold(0u8, |acc, &b| (acc << 1) | (b - b'0')))
|
||||||
.map(|chunk| u8::from_str_radix(chunk, 2).unwrap())
|
|
||||||
.collect::<Vec<u8>>();
|
.collect::<Vec<u8>>();
|
||||||
|
|
||||||
general_purpose::STANDARD.encode(pos_u8)
|
general_purpose::STANDARD.encode(pos_u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_string_id(&self) -> String {
|
||||||
|
const TOTAL_BITS: usize = 108;
|
||||||
|
const TOTAL_BYTES: usize = TOTAL_BITS / 6; // 18 bytes
|
||||||
|
|
||||||
|
let mut output = Vec::with_capacity(TOTAL_BYTES);
|
||||||
|
|
||||||
|
let mut current: u8 = 0;
|
||||||
|
let mut bit_count: u8 = 0;
|
||||||
|
|
||||||
|
// helper to push a single bit
|
||||||
|
let mut push_bit = |bit: u8, output: &mut Vec<u8>, current: &mut u8, bit_count: &mut u8| {
|
||||||
|
*current = (*current << 1) | (bit & 1);
|
||||||
|
*bit_count += 1;
|
||||||
|
|
||||||
|
if *bit_count == 6 {
|
||||||
|
output.push(*current);
|
||||||
|
*current = 0;
|
||||||
|
*bit_count = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// helper to push a string of '0'/'1'
|
||||||
|
let mut push_bits_str =
|
||||||
|
|bits: &str, output: &mut Vec<u8>, current: &mut u8, bit_count: &mut u8| {
|
||||||
|
for b in bits.bytes() {
|
||||||
|
push_bit(b - b'0', output, current, bit_count);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 1️⃣ Board position bits
|
||||||
|
// --------------------------------------------------
|
||||||
|
push_bits_str(
|
||||||
|
&self.board.to_gnupg_pos_id(),
|
||||||
|
&mut output,
|
||||||
|
&mut current,
|
||||||
|
&mut bit_count,
|
||||||
|
);
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 2️⃣ Active player (1 bit)
|
||||||
|
// --------------------------------------------------
|
||||||
|
let active_bit = self
|
||||||
|
.who_plays()
|
||||||
|
.map(|player| (player.color == Color::Black) as u8)
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
push_bit(active_bit, &mut output, &mut current, &mut bit_count);
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 3️⃣ Turn stage (3 bits)
|
||||||
|
// --------------------------------------------------
|
||||||
|
let stage_bits: u8 = match self.turn_stage {
|
||||||
|
TurnStage::RollWaiting => 0b000,
|
||||||
|
TurnStage::RollDice => 0b001,
|
||||||
|
TurnStage::MarkPoints => 0b010,
|
||||||
|
TurnStage::HoldOrGoChoice => 0b011,
|
||||||
|
TurnStage::Move => 0b100,
|
||||||
|
TurnStage::MarkAdvPoints => 0b101,
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in (0..3).rev() {
|
||||||
|
push_bit(
|
||||||
|
(stage_bits >> i) & 1,
|
||||||
|
&mut output,
|
||||||
|
&mut current,
|
||||||
|
&mut bit_count,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 4️⃣ Dice (6 bits)
|
||||||
|
// --------------------------------------------------
|
||||||
|
push_bits_str(
|
||||||
|
&self.dice.to_bits_string(),
|
||||||
|
&mut output,
|
||||||
|
&mut current,
|
||||||
|
&mut bit_count,
|
||||||
|
);
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 5️⃣ Players points (10 bits each)
|
||||||
|
// --------------------------------------------------
|
||||||
|
let white_bits = self
|
||||||
|
.get_white_player()
|
||||||
|
.map(|p| p.to_bits_string())
|
||||||
|
.unwrap_or_else(|| "0000000000".to_string());
|
||||||
|
|
||||||
|
let black_bits = self
|
||||||
|
.get_black_player()
|
||||||
|
.map(|p| p.to_bits_string())
|
||||||
|
.unwrap_or_else(|| "0000000000".to_string());
|
||||||
|
|
||||||
|
push_bits_str(&white_bits, &mut output, &mut current, &mut bit_count);
|
||||||
|
push_bits_str(&black_bits, &mut output, &mut current, &mut bit_count);
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 6️⃣ Pad remaining bits (if needed)
|
||||||
|
// --------------------------------------------------
|
||||||
|
while output.len() < TOTAL_BYTES {
|
||||||
|
push_bit(0, &mut output, &mut current, &mut bit_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
base64::engine::general_purpose::STANDARD.encode(output)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_string_id(id: &str) -> Result<Self, String> {
|
pub fn from_string_id(id: &str) -> Result<Self, String> {
|
||||||
let bytes = general_purpose::STANDARD
|
let bytes = general_purpose::STANDARD
|
||||||
.decode(id)
|
.decode(id)
|
||||||
|
|
@ -326,7 +440,9 @@ impl GameState {
|
||||||
let board_bits = &bits[0..77];
|
let board_bits = &bits[0..77];
|
||||||
let board = Board::from_gnupg_pos_id(board_bits)?;
|
let board = Board::from_gnupg_pos_id(board_bits)?;
|
||||||
|
|
||||||
let active_player_bit = bits.chars().nth(77).unwrap();
|
let Some(active_player_bit) = bits.chars().nth(77) else {
|
||||||
|
return Err("No bit at 77th position".to_string());
|
||||||
|
};
|
||||||
let active_player_color = if active_player_bit == '1' {
|
let active_player_color = if active_player_bit == '1' {
|
||||||
Color::Black
|
Color::Black
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -621,23 +737,14 @@ impl GameState {
|
||||||
.next();
|
.next();
|
||||||
self.active_player_id = other_player_id.unwrap_or(0);
|
self.active_player_id = other_player_id.unwrap_or(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes an event, modifying the GameState and adding the event to its history
|
/// Consumes an event, modifying the GameState and adding the event to its history
|
||||||
/// NOTE: consume assumes the event to have already been validated and will accept *any* event passed to it
|
/// NOTE: consume assumes the event to have already been validated and will accept *any* event passed to it
|
||||||
pub fn consume(&mut self, valid_event: &GameEvent) {
|
pub fn consume(&mut self, valid_event: &GameEvent) -> Result<(), String> {
|
||||||
use GameEvent::*;
|
use GameEvent::*;
|
||||||
match valid_event {
|
match valid_event {
|
||||||
BeginGame { goes_first } => {
|
BeginGame { goes_first } => {
|
||||||
self.active_player_id = *goes_first;
|
self.active_player_id = *goes_first;
|
||||||
// if self.who_plays().is_none() {
|
|
||||||
// let active_color = match self.dice.coin() {
|
|
||||||
// false => Color::Black,
|
|
||||||
// true => Color::White,
|
|
||||||
// };
|
|
||||||
// let color_player_id = self.player_id_by_color(active_color);
|
|
||||||
// if color_player_id.is_some() {
|
|
||||||
// self.active_player_id = *color_player_id.unwrap();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
self.stage = Stage::InGame;
|
self.stage = Stage::InGame;
|
||||||
self.turn_stage = TurnStage::RollDice;
|
self.turn_stage = TurnStage::RollDice;
|
||||||
}
|
}
|
||||||
|
|
@ -673,14 +780,16 @@ impl GameState {
|
||||||
self.dice = *dice;
|
self.dice = *dice;
|
||||||
self.inc_roll_count(self.active_player_id);
|
self.inc_roll_count(self.active_player_id);
|
||||||
self.turn_stage = TurnStage::MarkPoints;
|
self.turn_stage = TurnStage::MarkPoints;
|
||||||
(self.dice_jans, self.dice_points) = self.get_rollresult_jans(dice);
|
(self.dice_jans, self.dice_points) = self.get_rollresult_jans(dice)?;
|
||||||
debug!("points from result : {:?}", self.dice_points);
|
debug!("points from result : {:?}", self.dice_points);
|
||||||
if !self.schools_enabled {
|
if !self.schools_enabled {
|
||||||
// Schools are not enabled. We mark points automatically
|
// Schools are not enabled. We mark points automatically
|
||||||
// the points earned by the opponent will be marked on its turn
|
// the points earned by the opponent will be marked on its turn
|
||||||
let new_hole = self.mark_points(self.active_player_id, self.dice_points.0);
|
let new_hole = self.mark_points(self.active_player_id, self.dice_points.0);
|
||||||
if new_hole {
|
if new_hole {
|
||||||
let holes_count = self.get_active_player().unwrap().holes;
|
let Some(holes_count) = self.get_active_player().map(|p| p.holes) else {
|
||||||
|
return Err("No active player".into());
|
||||||
|
};
|
||||||
debug!("new hole -> {holes_count:?}");
|
debug!("new hole -> {holes_count:?}");
|
||||||
if holes_count > 12 {
|
if holes_count > 12 {
|
||||||
self.stage = Stage::Ended;
|
self.stage = Stage::Ended;
|
||||||
|
|
@ -696,7 +805,10 @@ impl GameState {
|
||||||
if self.schools_enabled {
|
if self.schools_enabled {
|
||||||
let new_hole = self.mark_points(*player_id, *points);
|
let new_hole = self.mark_points(*player_id, *points);
|
||||||
if new_hole {
|
if new_hole {
|
||||||
if self.get_active_player().unwrap().holes > 12 {
|
let Some(holes) = self.get_active_player().map(|p| p.holes) else {
|
||||||
|
return Err("No active player".into());
|
||||||
|
};
|
||||||
|
if holes > 12 {
|
||||||
self.stage = Stage::Ended;
|
self.stage = Stage::Ended;
|
||||||
} else {
|
} else {
|
||||||
self.turn_stage = if self.turn_stage == TurnStage::MarkAdvPoints {
|
self.turn_stage = if self.turn_stage == TurnStage::MarkAdvPoints {
|
||||||
|
|
@ -716,17 +828,26 @@ impl GameState {
|
||||||
}
|
}
|
||||||
Go { player_id: _ } => self.new_pick_up(),
|
Go { player_id: _ } => self.new_pick_up(),
|
||||||
Move { player_id, moves } => {
|
Move { player_id, moves } => {
|
||||||
let player = self.players.get(player_id).unwrap();
|
let Some(player) = self.players.get(player_id) else {
|
||||||
self.board.move_checker(&player.color, moves.0).unwrap();
|
return Err("unknown player {player_id}".into());
|
||||||
self.board.move_checker(&player.color, moves.1).unwrap();
|
};
|
||||||
|
self.board
|
||||||
|
.move_checker(&player.color, moves.0)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
self.board
|
||||||
|
.move_checker(&player.color, moves.1)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
self.dice_moves = *moves;
|
self.dice_moves = *moves;
|
||||||
self.active_player_id = *self.players.keys().find(|id| *id != player_id).unwrap();
|
let Some(active_player_id) = self.players.keys().find(|id| *id != player_id) else {
|
||||||
|
return Err("Can't find player id {id}".into());
|
||||||
|
};
|
||||||
|
self.active_player_id = *active_player_id;
|
||||||
self.turn_stage = if self.schools_enabled {
|
self.turn_stage = if self.schools_enabled {
|
||||||
TurnStage::MarkAdvPoints
|
TurnStage::MarkAdvPoints
|
||||||
} else {
|
} else {
|
||||||
// The player has moved, we can mark its opponent's points (which is now the current player)
|
// The player has moved, we can mark its opponent's points (which is now the current player)
|
||||||
let new_hole = self.mark_points(self.active_player_id, self.dice_points.1);
|
let new_hole = self.mark_points(self.active_player_id, self.dice_points.1);
|
||||||
if new_hole && self.get_active_player().unwrap().holes > 12 {
|
if new_hole && self.get_active_player().map(|p| p.holes).unwrap_or(0) > 12 {
|
||||||
self.stage = Stage::Ended;
|
self.stage = Stage::Ended;
|
||||||
}
|
}
|
||||||
TurnStage::RollDice
|
TurnStage::RollDice
|
||||||
|
|
@ -735,6 +856,7 @@ impl GameState {
|
||||||
PlayError => {}
|
PlayError => {}
|
||||||
}
|
}
|
||||||
self.history.push(valid_event.clone());
|
self.history.push(valid_event.clone());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a new pick up ('relevé') after a player won a hole and choose to 'go',
|
/// Set a new pick up ('relevé') after a player won a hole and choose to 'go',
|
||||||
|
|
@ -757,14 +879,16 @@ impl GameState {
|
||||||
self.board = Board::new();
|
self.board = Board::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rollresult_jans(&self, dice: &Dice) -> (PossibleJans, (u8, u8)) {
|
fn get_rollresult_jans(&self, dice: &Dice) -> Result<(PossibleJans, (u8, u8)), String> {
|
||||||
let player = &self.players.get(&self.active_player_id).unwrap();
|
let Some(player) = &self.players.get(&self.active_player_id) else {
|
||||||
|
return Err("No active player".into());
|
||||||
|
};
|
||||||
debug!(
|
debug!(
|
||||||
"get rollresult for {:?} {:?} {:?} (roll count {:?})",
|
"get rollresult for {:?} {:?} {:?} (roll count {:?})",
|
||||||
player.color, self.board, dice, player.dice_roll_count
|
player.color, self.board, dice, player.dice_roll_count
|
||||||
);
|
);
|
||||||
let points_rules = PointsRules::new(&player.color, &self.board, *dice);
|
let points_rules = PointsRules::new(&player.color, &self.board, *dice);
|
||||||
points_rules.get_result_jans(player.dice_roll_count)
|
Ok(points_rules.get_result_jans(player.dice_roll_count))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines if someone has won the game
|
/// Determines if someone has won the game
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,8 @@ impl Player {
|
||||||
}
|
}
|
||||||
let points = u8::from_str_radix(&bits[0..4], 2).map_err(|e| e.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 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_bredouille = bits.chars().nth(8).ok_or_else(|| "8th bit unreadable")? == '1';
|
||||||
let can_big_bredouille = bits.chars().nth(9).unwrap() == '1';
|
let can_big_bredouille = bits.chars().nth(9).ok_or_else(|| "9th bit unreadable")? == '1';
|
||||||
|
|
||||||
Ok(Player {
|
Ok(Player {
|
||||||
name,
|
name,
|
||||||
|
|
|
||||||
|
|
@ -323,10 +323,7 @@ fn white_checker_moves_to_trictrac_action(
|
||||||
let checker1 = board.get_field_checker(&crate::Color::White, from1) as usize;
|
let checker1 = board.get_field_checker(&crate::Color::White, from1) as usize;
|
||||||
let mut tmp_board = board.clone();
|
let mut tmp_board = board.clone();
|
||||||
// should not raise an error for a valid action
|
// should not raise an error for a valid action
|
||||||
let move_res = tmp_board.move_checker(&crate::Color::White, *move1);
|
tmp_board.move_checker(&crate::Color::White, *move1)?;
|
||||||
if move_res.is_err() {
|
|
||||||
panic!("error while moving checker {move_res:?}");
|
|
||||||
}
|
|
||||||
let checker2 = tmp_board.get_field_checker(&crate::Color::White, from2) as usize;
|
let checker2 = tmp_board.get_field_checker(&crate::Color::White, from2) as usize;
|
||||||
Ok(TrictracAction::Move {
|
Ok(TrictracAction::Move {
|
||||||
dice_order,
|
dice_order,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue