feat: Karel Peeters board game implementation
This commit is contained in:
parent
866ba611a6
commit
f2a89f60bc
10 changed files with 494 additions and 92 deletions
|
|
@ -24,3 +24,5 @@ burn = { version = "0.17", features = ["ndarray", "autodiff"] }
|
|||
burn-rl = { git = "https://github.com/yunjhongwu/burn-rl-examples.git", package = "burn-rl" }
|
||||
log = "0.4.20"
|
||||
confy = "1.0.0"
|
||||
board-game = "0.8.2"
|
||||
internal-iterator = "0.2.3"
|
||||
|
|
|
|||
|
|
@ -281,79 +281,8 @@ impl TrictracEnvironment {
|
|||
let mut reward = 0.0;
|
||||
let mut is_rollpoint = false;
|
||||
|
||||
let event = match action {
|
||||
TrictracAction::Roll => {
|
||||
// Lancer les dés
|
||||
Some(GameEvent::Roll {
|
||||
player_id: self.active_player_id,
|
||||
})
|
||||
}
|
||||
// TrictracAction::Mark => {
|
||||
// // Marquer des points
|
||||
// let points = self.game.
|
||||
// Some(GameEvent::Mark {
|
||||
// player_id: self.active_player_id,
|
||||
// points,
|
||||
// })
|
||||
// }
|
||||
TrictracAction::Go => {
|
||||
// Continuer après avoir gagné un trou
|
||||
Some(GameEvent::Go {
|
||||
player_id: self.active_player_id,
|
||||
})
|
||||
}
|
||||
TrictracAction::Move {
|
||||
dice_order,
|
||||
checker1,
|
||||
checker2,
|
||||
} => {
|
||||
// Effectuer un mouvement
|
||||
let (dice1, dice2) = if dice_order {
|
||||
(self.game.dice.values.0, self.game.dice.values.1)
|
||||
} else {
|
||||
(self.game.dice.values.1, self.game.dice.values.0)
|
||||
};
|
||||
|
||||
let color = &store::Color::White;
|
||||
let from1 = self
|
||||
.game
|
||||
.board
|
||||
.get_checker_field(color, checker1 as u8)
|
||||
.unwrap_or(0);
|
||||
let mut to1 = from1 + dice1 as usize;
|
||||
let checker_move1 = store::CheckerMove::new(from1, to1).unwrap_or_default();
|
||||
|
||||
let mut tmp_board = self.game.board.clone();
|
||||
let move_result = tmp_board.move_checker(color, checker_move1);
|
||||
if move_result.is_err() {
|
||||
None
|
||||
// panic!("Error while moving checker {move_result:?}")
|
||||
} else {
|
||||
let from2 = tmp_board
|
||||
.get_checker_field(color, checker2 as u8)
|
||||
.unwrap_or(0);
|
||||
let mut to2 = from2 + dice2 as usize;
|
||||
|
||||
// Gestion prise de coin par puissance
|
||||
let opp_rest_field = 13;
|
||||
if to1 == opp_rest_field && to2 == opp_rest_field {
|
||||
to1 -= 1;
|
||||
to2 -= 1;
|
||||
}
|
||||
|
||||
let checker_move1 = store::CheckerMove::new(from1, to1).unwrap_or_default();
|
||||
let checker_move2 = store::CheckerMove::new(from2, to2).unwrap_or_default();
|
||||
|
||||
Some(GameEvent::Move {
|
||||
player_id: self.active_player_id,
|
||||
moves: (checker_move1, checker_move2),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Appliquer l'événement si valide
|
||||
if let Some(event) = event {
|
||||
if let Some(event) = action.to_event(&self.game) {
|
||||
if self.game.validate(&event) {
|
||||
self.game.consume(&event);
|
||||
reward += REWARD_VALID_MOVE;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ pub mod dqn_simple;
|
|||
pub mod strategy;
|
||||
pub mod training_common;
|
||||
pub mod training_common_big;
|
||||
pub mod trictrac_board;
|
||||
|
||||
use log::debug;
|
||||
use store::{CheckerMove, Color, GameEvent, GameState, PlayerId, PointsRules, Stage, TurnStage};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use std::cmp::{max, min};
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use store::CheckerMove;
|
||||
use store::{CheckerMove, GameEvent, GameState};
|
||||
|
||||
/// Types d'actions possibles dans le jeu
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Eq, Serialize, Deserialize, PartialEq)]
|
||||
pub enum TrictracAction {
|
||||
/// Lancer les dés
|
||||
Roll,
|
||||
|
|
@ -20,6 +21,14 @@ pub enum TrictracAction {
|
|||
// Mark,
|
||||
}
|
||||
|
||||
impl Display for TrictracAction {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let s = format!("{self:?}");
|
||||
writeln!(f, "{}", s.chars().rev().collect::<String>())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TrictracAction {
|
||||
/// Encode une action en index pour le réseau de neurones
|
||||
pub fn to_action_index(&self) -> usize {
|
||||
|
|
@ -44,6 +53,78 @@ impl TrictracAction {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn to_event(&self, state: &GameState) -> Option<GameEvent> {
|
||||
match self {
|
||||
TrictracAction::Roll => {
|
||||
// Lancer les dés
|
||||
Some(GameEvent::Roll {
|
||||
player_id: state.active_player_id,
|
||||
})
|
||||
}
|
||||
// TrictracAction::Mark => {
|
||||
// // Marquer des points
|
||||
// let points = self.game.
|
||||
// Some(GameEvent::Mark {
|
||||
// player_id: self.active_player_id,
|
||||
// points,
|
||||
// })
|
||||
// }
|
||||
TrictracAction::Go => {
|
||||
// Continuer après avoir gagné un trou
|
||||
Some(GameEvent::Go {
|
||||
player_id: state.active_player_id,
|
||||
})
|
||||
}
|
||||
TrictracAction::Move {
|
||||
dice_order,
|
||||
checker1,
|
||||
checker2,
|
||||
} => {
|
||||
// Effectuer un mouvement
|
||||
let (dice1, dice2) = if *dice_order {
|
||||
(state.dice.values.0, state.dice.values.1)
|
||||
} else {
|
||||
(state.dice.values.1, state.dice.values.0)
|
||||
};
|
||||
|
||||
let color = &store::Color::White;
|
||||
let from1 = state
|
||||
.board
|
||||
.get_checker_field(color, *checker1 as u8)
|
||||
.unwrap_or(0);
|
||||
let mut to1 = from1 + dice1 as usize;
|
||||
let checker_move1 = store::CheckerMove::new(from1, to1).unwrap_or_default();
|
||||
|
||||
let mut tmp_board = state.board.clone();
|
||||
let move_result = tmp_board.move_checker(color, checker_move1);
|
||||
if move_result.is_err() {
|
||||
None
|
||||
// panic!("Error while moving checker {move_result:?}")
|
||||
} else {
|
||||
let from2 = tmp_board
|
||||
.get_checker_field(color, *checker2 as u8)
|
||||
.unwrap_or(0);
|
||||
let mut to2 = from2 + dice2 as usize;
|
||||
|
||||
// Gestion prise de coin par puissance
|
||||
let opp_rest_field = 13;
|
||||
if to1 == opp_rest_field && to2 == opp_rest_field {
|
||||
to1 -= 1;
|
||||
to2 -= 1;
|
||||
}
|
||||
|
||||
let checker_move1 = store::CheckerMove::new(from1, to1).unwrap_or_default();
|
||||
let checker_move2 = store::CheckerMove::new(from2, to2).unwrap_or_default();
|
||||
|
||||
Some(GameEvent::Move {
|
||||
player_id: state.active_player_id,
|
||||
moves: (checker_move1, checker_move2),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Décode un index d'action en TrictracAction
|
||||
pub fn from_action_index(index: usize) -> Option<TrictracAction> {
|
||||
match index {
|
||||
|
|
|
|||
149
bot/src/trictrac_board.rs
Normal file
149
bot/src/trictrac_board.rs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
// https://docs.rs/board-game/ implementation
|
||||
use crate::training_common::{get_valid_actions, TrictracAction};
|
||||
use board_game::board::{
|
||||
Board as BoardGameBoard, BoardDone, BoardMoves, Outcome, PlayError, Player as BoardGamePlayer,
|
||||
};
|
||||
use board_game::impl_unit_symmetry_board;
|
||||
use internal_iterator::InternalIterator;
|
||||
use std::fmt;
|
||||
use std::ops::ControlFlow;
|
||||
use store::Color;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct TrictracBoard(crate::GameState);
|
||||
|
||||
impl Default for TrictracBoard {
|
||||
fn default() -> Self {
|
||||
TrictracBoard(crate::GameState::new_with_players("white", "black"))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TrictracBoard {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl_unit_symmetry_board!(TrictracBoard);
|
||||
|
||||
impl BoardGameBoard for TrictracBoard {
|
||||
// impl TrictracBoard {
|
||||
type Move = TrictracAction;
|
||||
|
||||
fn next_player(&self) -> BoardGamePlayer {
|
||||
self.0
|
||||
.who_plays()
|
||||
.map(|p| {
|
||||
if p.color == Color::Black {
|
||||
BoardGamePlayer::B
|
||||
} else {
|
||||
BoardGamePlayer::A
|
||||
}
|
||||
})
|
||||
.unwrap_or(BoardGamePlayer::A)
|
||||
}
|
||||
|
||||
fn is_available_move(&self, mv: Self::Move) -> Result<bool, BoardDone> {
|
||||
self.check_done()?;
|
||||
let is_valid = mv
|
||||
.to_event(&self.0)
|
||||
.map(|evt| self.0.validate(&evt))
|
||||
.unwrap_or(false);
|
||||
Ok(is_valid)
|
||||
}
|
||||
|
||||
fn play(&mut self, mv: Self::Move) -> Result<(), PlayError> {
|
||||
self.check_can_play(mv)?;
|
||||
self.0.consume(&mv.to_event(&self.0).unwrap());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn outcome(&self) -> Option<Outcome> {
|
||||
if self.0.stage == crate::Stage::Ended {
|
||||
self.0.determine_winner().map(|player_id| {
|
||||
Outcome::WonBy(if player_id == 1 {
|
||||
BoardGamePlayer::A
|
||||
} else {
|
||||
BoardGamePlayer::B
|
||||
})
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn can_lose_after_move() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BoardMoves<'a, TrictracBoard> for TrictracBoard {
|
||||
type AllMovesIterator = TrictracAllMovesIterator;
|
||||
type AvailableMovesIterator = TrictracAvailableMovesIterator<'a>;
|
||||
|
||||
fn all_possible_moves() -> Self::AllMovesIterator {
|
||||
TrictracAllMovesIterator::default()
|
||||
}
|
||||
|
||||
fn available_moves(&'a self) -> Result<Self::AvailableMovesIterator, BoardDone> {
|
||||
TrictracAvailableMovesIterator::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TrictracAllMovesIterator;
|
||||
|
||||
impl Default for TrictracAllMovesIterator {
|
||||
fn default() -> Self {
|
||||
TrictracAllMovesIterator
|
||||
}
|
||||
}
|
||||
|
||||
impl InternalIterator for TrictracAllMovesIterator {
|
||||
type Item = TrictracAction;
|
||||
|
||||
fn try_for_each<R, F: FnMut(Self::Item) -> ControlFlow<R>>(self, mut f: F) -> ControlFlow<R> {
|
||||
f(TrictracAction::Roll)?;
|
||||
f(TrictracAction::Go)?;
|
||||
for dice_order in [false, true] {
|
||||
for checker1 in 0..16 {
|
||||
for checker2 in 0..16 {
|
||||
f(TrictracAction::Move {
|
||||
dice_order,
|
||||
checker1,
|
||||
checker2,
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TrictracAvailableMovesIterator<'a> {
|
||||
board: &'a TrictracBoard,
|
||||
}
|
||||
|
||||
impl<'a> TrictracAvailableMovesIterator<'a> {
|
||||
pub fn new(board: &'a TrictracBoard) -> Result<Self, BoardDone> {
|
||||
board.check_done()?;
|
||||
Ok(TrictracAvailableMovesIterator { board })
|
||||
}
|
||||
|
||||
pub fn board(&self) -> &'a TrictracBoard {
|
||||
self.board
|
||||
}
|
||||
}
|
||||
|
||||
impl InternalIterator for TrictracAvailableMovesIterator<'_> {
|
||||
type Item = TrictracAction;
|
||||
|
||||
fn try_for_each<R, F>(self, f: F) -> ControlFlow<R>
|
||||
where
|
||||
F: FnMut(Self::Item) -> ControlFlow<R>,
|
||||
{
|
||||
get_valid_actions(&self.board.0).into_iter().try_for_each(f)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue