2025-05-24 22:41:44 +02:00
|
|
|
use bot::{BotStrategy, DefaultStrategy, DqnStrategy, ErroneousStrategy, StableBaselines3Strategy};
|
2024-05-20 19:04:46 +02:00
|
|
|
use itertools::Itertools;
|
|
|
|
|
|
2024-11-19 17:28:18 +01:00
|
|
|
use crate::game_runner::GameRunner;
|
2024-11-04 17:37:36 +01:00
|
|
|
use store::{CheckerMove, GameEvent, GameState, Stage, TurnStage};
|
2024-02-17 12:55:36 +01:00
|
|
|
|
2024-02-09 17:47:34 +01:00
|
|
|
#[derive(Debug, Default)]
|
2024-03-11 20:45:36 +01:00
|
|
|
pub struct AppArgs {
|
|
|
|
|
pub seed: Option<u32>,
|
2024-11-05 18:03:14 +01:00
|
|
|
pub bot: Option<String>,
|
2024-03-11 20:45:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Application.
|
|
|
|
|
#[derive(Debug, Default)]
|
|
|
|
|
pub struct App {
|
|
|
|
|
// should the application exit?
|
|
|
|
|
pub should_quit: bool,
|
2024-09-22 16:11:42 +02:00
|
|
|
pub schools_enabled: bool,
|
2024-11-19 17:28:18 +01:00
|
|
|
pub game: GameRunner,
|
2024-03-11 20:45:36 +01:00
|
|
|
}
|
2024-03-09 22:20:11 +01:00
|
|
|
|
2024-03-11 20:45:36 +01:00
|
|
|
impl App {
|
2024-02-17 12:55:36 +01:00
|
|
|
// Constructs a new instance of [`App`].
|
2024-03-11 20:45:36 +01:00
|
|
|
pub fn new(args: AppArgs) -> Self {
|
2025-03-18 21:19:57 +01:00
|
|
|
let bot_strategies: Vec<Box<dyn BotStrategy>> =
|
|
|
|
|
args.bot
|
|
|
|
|
.as_deref()
|
|
|
|
|
.map(|str_bots| {
|
|
|
|
|
str_bots
|
|
|
|
|
.split(",")
|
|
|
|
|
.filter_map(|s| match s.trim() {
|
|
|
|
|
"dummy" => {
|
|
|
|
|
Some(Box::new(DefaultStrategy::default()) as Box<dyn BotStrategy>)
|
|
|
|
|
}
|
|
|
|
|
"erroneous" => {
|
|
|
|
|
Some(Box::new(ErroneousStrategy::default()) as Box<dyn BotStrategy>)
|
|
|
|
|
}
|
|
|
|
|
"ai" => Some(Box::new(StableBaselines3Strategy::default())
|
|
|
|
|
as Box<dyn BotStrategy>),
|
2025-05-24 22:41:44 +02:00
|
|
|
"dqn" => Some(Box::new(DqnStrategy::default())
|
|
|
|
|
as Box<dyn BotStrategy>),
|
2025-03-18 21:19:57 +01:00
|
|
|
s if s.starts_with("ai:") => {
|
|
|
|
|
let path = s.trim_start_matches("ai:");
|
|
|
|
|
Some(Box::new(StableBaselines3Strategy::new(path))
|
|
|
|
|
as Box<dyn BotStrategy>)
|
|
|
|
|
}
|
2025-05-24 22:41:44 +02:00
|
|
|
s if s.starts_with("dqn:") => {
|
|
|
|
|
let path = s.trim_start_matches("dqn:");
|
|
|
|
|
Some(Box::new(DqnStrategy::new_with_model(path))
|
|
|
|
|
as Box<dyn BotStrategy>)
|
|
|
|
|
}
|
2025-03-18 21:19:57 +01:00
|
|
|
_ => None,
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or_default();
|
2024-09-22 16:11:42 +02:00
|
|
|
let schools_enabled = false;
|
2025-01-30 17:42:29 +01:00
|
|
|
let should_quit = bot_strategies.len() > 1;
|
2024-03-11 20:45:36 +01:00
|
|
|
Self {
|
2024-11-19 17:28:18 +01:00
|
|
|
game: GameRunner::new(schools_enabled, bot_strategies, args.seed.map(|s| s as u64)),
|
2025-01-30 17:42:29 +01:00
|
|
|
should_quit,
|
2024-09-22 16:11:42 +02:00
|
|
|
schools_enabled,
|
2024-03-11 20:45:36 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-17 12:55:36 +01:00
|
|
|
pub fn start(&mut self) {
|
2024-09-22 16:11:42 +02:00
|
|
|
self.game.state = GameState::new(self.schools_enabled);
|
2024-02-17 12:55:36 +01:00
|
|
|
}
|
|
|
|
|
|
2024-02-09 17:47:34 +01:00
|
|
|
pub fn input(&mut self, input: &str) {
|
2024-03-29 21:04:58 +01:00
|
|
|
// println!("'{}'", input);
|
2024-03-09 22:20:11 +01:00
|
|
|
match input {
|
2024-03-29 21:04:58 +01:00
|
|
|
"state" => self.show_state(),
|
|
|
|
|
"history" => self.show_history(),
|
2024-03-09 22:20:11 +01:00
|
|
|
"quit" => self.quit(),
|
2024-11-14 17:14:37 +01:00
|
|
|
// run bots game (when two bots)
|
|
|
|
|
"bots" => self.bots_all(),
|
|
|
|
|
"" => self.bots_next_step(),
|
|
|
|
|
// play (when one bot)
|
2024-03-09 22:20:11 +01:00
|
|
|
"roll" => self.roll_dice(),
|
2024-09-26 17:41:03 +02:00
|
|
|
"go" => self.go(),
|
2024-03-09 22:20:11 +01:00
|
|
|
_ => self.add_move(input),
|
2024-02-09 17:47:34 +01:00
|
|
|
}
|
2024-03-09 22:20:11 +01:00
|
|
|
println!("{}", self.display());
|
2024-02-09 17:47:34 +01:00
|
|
|
}
|
|
|
|
|
|
2024-11-14 17:14:37 +01:00
|
|
|
// --- 2 bots game actions
|
|
|
|
|
|
|
|
|
|
fn bots_all(&mut self) {}
|
|
|
|
|
|
|
|
|
|
fn bots_next_step(&mut self) {}
|
|
|
|
|
|
2024-02-09 17:47:34 +01:00
|
|
|
// Set running to false to quit the application.
|
|
|
|
|
pub fn quit(&mut self) {
|
|
|
|
|
self.should_quit = true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 21:04:58 +01:00
|
|
|
pub fn show_state(&self) {
|
|
|
|
|
println!("{:?}", self.game.state)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn show_history(&self) {
|
|
|
|
|
for hist in self.game.state.history.iter() {
|
|
|
|
|
println!("{:?}\n", hist);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-10 11:49:23 +01:00
|
|
|
fn roll_dice(&mut self) {
|
2024-03-11 20:45:36 +01:00
|
|
|
if self.game.player_id.is_none() {
|
2024-03-10 11:49:23 +01:00
|
|
|
println!("player_id not set ");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-03-31 15:39:02 +02:00
|
|
|
if self.game.state.turn_stage != TurnStage::RollDice {
|
|
|
|
|
println!("Not in the dice roll stage");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-03-11 20:45:36 +01:00
|
|
|
let dice = self.game.dice_roller.roll();
|
2024-09-22 16:11:42 +02:00
|
|
|
|
|
|
|
|
// get correct points for these board and dice
|
2024-11-04 17:37:36 +01:00
|
|
|
// let points_rules = PointsRules::new(
|
|
|
|
|
// &self
|
|
|
|
|
// .game
|
|
|
|
|
// .state
|
|
|
|
|
// .player_color_by_id(&self.game.player_id.unwrap())
|
|
|
|
|
// .unwrap(),
|
|
|
|
|
// &self.game.state.board,
|
|
|
|
|
// dice,
|
|
|
|
|
// );
|
2024-03-30 16:10:53 +01:00
|
|
|
self.game.handle_event(&GameEvent::RollResult {
|
2024-03-11 20:45:36 +01:00
|
|
|
player_id: self.game.player_id.unwrap(),
|
|
|
|
|
dice,
|
2024-03-10 11:49:23 +01:00
|
|
|
});
|
|
|
|
|
}
|
2024-03-09 22:20:11 +01:00
|
|
|
|
2024-09-26 17:41:03 +02:00
|
|
|
fn go(&mut self) {
|
|
|
|
|
if self.game.player_id.is_none() {
|
|
|
|
|
println!("player_id not set ");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if self.game.state.turn_stage != TurnStage::HoldOrGoChoice {
|
|
|
|
|
println!("Not in position to go");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
self.game.handle_event(&GameEvent::Go {
|
|
|
|
|
player_id: self.game.player_id.unwrap(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-09 22:20:11 +01:00
|
|
|
fn add_move(&mut self, input: &str) {
|
2024-03-11 20:45:36 +01:00
|
|
|
if self.game.player_id.is_none() {
|
2024-03-09 22:20:11 +01:00
|
|
|
println!("player_id not set ");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let positions: Vec<usize> = input
|
|
|
|
|
.split(' ')
|
|
|
|
|
.map(|str| str.parse().unwrap_or(0))
|
|
|
|
|
.collect();
|
|
|
|
|
if positions.len() == 2 && positions[0] != 0 && positions[1] != 0 {
|
2024-03-30 16:10:53 +01:00
|
|
|
if let Ok(checker_move) = CheckerMove::new(positions[0], positions[1]) {
|
|
|
|
|
// if checker_move.is_ok() {
|
2024-03-11 20:45:36 +01:00
|
|
|
if self.game.first_move.is_some() {
|
2024-03-10 11:49:23 +01:00
|
|
|
let move_event = GameEvent::Move {
|
2024-03-11 20:45:36 +01:00
|
|
|
player_id: self.game.player_id.unwrap(),
|
2024-03-30 16:10:53 +01:00
|
|
|
moves: (self.game.first_move.unwrap(), checker_move),
|
2024-03-10 11:49:23 +01:00
|
|
|
};
|
2024-03-11 20:45:36 +01:00
|
|
|
if !self.game.state.validate(&move_event) {
|
2024-03-10 11:49:23 +01:00
|
|
|
println!("Move invalid");
|
2024-03-11 20:45:36 +01:00
|
|
|
self.game.first_move = None;
|
2024-03-10 11:49:23 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2024-03-30 16:10:53 +01:00
|
|
|
self.game.handle_event(&move_event);
|
2024-03-11 20:45:36 +01:00
|
|
|
self.game.first_move = None;
|
2024-03-09 22:20:11 +01:00
|
|
|
} else {
|
2024-03-30 16:10:53 +01:00
|
|
|
self.game.first_move = Some(checker_move);
|
2024-03-09 22:20:11 +01:00
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
println!("invalid move : {}", input);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-17 12:55:36 +01:00
|
|
|
pub fn display(&mut self) -> String {
|
2024-09-27 12:35:14 +02:00
|
|
|
let winner = self
|
|
|
|
|
.game
|
|
|
|
|
.state
|
|
|
|
|
.determine_winner()
|
|
|
|
|
.and_then(|id| self.game.state.players.get(&id));
|
|
|
|
|
let str_won: String = winner
|
|
|
|
|
.map(|p| {
|
|
|
|
|
let mut name = " winner: ".to_owned();
|
|
|
|
|
name.push_str(&p.name);
|
|
|
|
|
name
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or("".to_owned());
|
2024-03-10 11:49:23 +01:00
|
|
|
let mut output = "-------------------------------".to_owned();
|
2024-03-31 15:23:18 +02:00
|
|
|
output += format!(
|
2024-09-27 12:35:14 +02:00
|
|
|
"\n{:?}{} > {} > {:?}",
|
2024-03-31 15:23:18 +02:00
|
|
|
self.game.state.stage,
|
2024-09-27 12:35:14 +02:00
|
|
|
str_won,
|
2024-03-31 15:23:18 +02:00
|
|
|
self.game
|
2024-03-29 21:04:58 +01:00
|
|
|
.state
|
|
|
|
|
.who_plays()
|
|
|
|
|
.map(|pl| &pl.name)
|
2024-03-31 15:23:18 +02:00
|
|
|
.unwrap_or(&"?".to_owned()),
|
|
|
|
|
self.game.state.turn_stage
|
|
|
|
|
)
|
|
|
|
|
.as_str();
|
2024-03-29 21:04:58 +01:00
|
|
|
|
2024-03-11 20:45:36 +01:00
|
|
|
output = output + "\nRolled dice : " + &self.game.state.dice.to_display_string();
|
2024-03-31 15:23:18 +02:00
|
|
|
|
|
|
|
|
if self.game.state.stage != Stage::PreGame {
|
2024-10-02 18:03:44 +02:00
|
|
|
output = output + "\nRolled dice jans : " + &format!("{:?}", self.game.state.dice_jans);
|
|
|
|
|
output = output
|
|
|
|
|
+ "\nLast move : "
|
|
|
|
|
+ &self.game.state.dice_moves.0.to_display_string()
|
|
|
|
|
+ ", "
|
|
|
|
|
+ &self.game.state.dice_moves.1.to_display_string();
|
|
|
|
|
|
2024-03-31 15:23:18 +02:00
|
|
|
// display players points
|
|
|
|
|
output += format!("\n\n{:<11} :: {:<5} :: {}", "Player", "holes", "points").as_str();
|
2024-05-20 19:04:46 +02:00
|
|
|
|
|
|
|
|
for player_id in self.game.state.players.keys().sorted() {
|
|
|
|
|
let player = &self.game.state.players[player_id];
|
2024-03-31 15:23:18 +02:00
|
|
|
output += format!(
|
|
|
|
|
"\n{}. {:<8} :: {:<5} :: {}",
|
2024-09-23 17:53:21 +02:00
|
|
|
&player_id, &player.name, &player.holes, &player.points,
|
2024-03-31 15:23:18 +02:00
|
|
|
)
|
|
|
|
|
.as_str();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
output += "\n-------------------------------\n";
|
|
|
|
|
output += &self.game.state.board.to_display_grid(9);
|
2024-03-10 11:49:23 +01:00
|
|
|
output
|
2024-02-09 17:47:34 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
2024-11-04 17:37:36 +01:00
|
|
|
use pretty_assertions::assert_eq;
|
2024-02-09 17:47:34 +01:00
|
|
|
|
|
|
|
|
#[test]
|
2024-02-17 12:55:36 +01:00
|
|
|
fn test_display() {
|
2024-03-10 11:49:23 +01:00
|
|
|
let expected = "-------------------------------
|
2024-03-31 15:23:18 +02:00
|
|
|
PreGame > ? > RollDice
|
2024-03-10 11:49:23 +01:00
|
|
|
Rolled dice : 0 & 0
|
|
|
|
|
-------------------------------
|
|
|
|
|
|
2024-03-27 21:10:15 +01:00
|
|
|
13 14 15 16 17 18 19 20 21 22 23 24
|
2024-02-18 18:40:45 +01:00
|
|
|
----------------------------------------------------------------
|
2024-03-27 21:10:15 +01:00
|
|
|
| | | X |
|
|
|
|
|
| | | X |
|
|
|
|
|
| | | X |
|
|
|
|
|
| | | X |
|
|
|
|
|
| | | X |
|
|
|
|
|
| | | X |
|
|
|
|
|
| | | X |
|
|
|
|
|
| | | X |
|
|
|
|
|
| | | 15 |
|
|
|
|
|
|------------------------------ | | -----------------------------|
|
|
|
|
|
| | | 15 |
|
|
|
|
|
| | | O |
|
|
|
|
|
| | | O |
|
|
|
|
|
| | | O |
|
|
|
|
|
| | | O |
|
|
|
|
|
| | | O |
|
|
|
|
|
| | | O |
|
|
|
|
|
| | | O |
|
|
|
|
|
| | | O |
|
2024-02-18 18:40:45 +01:00
|
|
|
----------------------------------------------------------------
|
|
|
|
|
12 11 10 9 8 7 6 5 4 3 2 1
|
|
|
|
|
";
|
2024-02-09 17:47:34 +01:00
|
|
|
let mut app = App::default();
|
2024-02-18 18:40:45 +01:00
|
|
|
self::assert_eq!(app.display(), expected);
|
2024-02-09 17:47:34 +01:00
|
|
|
}
|
2024-03-09 22:20:11 +01:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_move() {
|
2024-03-10 11:49:23 +01:00
|
|
|
let expected = "-------------------------------
|
2024-09-23 17:53:21 +02:00
|
|
|
InGame > myself > RollDice
|
2024-03-27 21:10:15 +01:00
|
|
|
Rolled dice : 4 & 6
|
2024-10-02 18:03:44 +02:00
|
|
|
Rolled dice jans : {}
|
2025-01-06 20:27:16 +01:00
|
|
|
Last move : CheckerMove { from: 24, to: 18 } , CheckerMove { from: 24, to: 20 }
|
2024-03-31 15:23:18 +02:00
|
|
|
|
|
|
|
|
Player :: holes :: points
|
|
|
|
|
1. myself :: 0 :: 0
|
|
|
|
|
2. bot :: 0 :: 0
|
2024-03-10 11:49:23 +01:00
|
|
|
-------------------------------
|
|
|
|
|
|
2024-03-27 21:10:15 +01:00
|
|
|
13 14 15 16 17 18 19 20 21 22 23 24
|
2024-03-09 22:20:11 +01:00
|
|
|
----------------------------------------------------------------
|
2024-03-27 21:10:15 +01:00
|
|
|
| X | | X X |
|
|
|
|
|
| | | X |
|
|
|
|
|
| | | X |
|
|
|
|
|
| | | X |
|
|
|
|
|
| | | X |
|
|
|
|
|
| | | X |
|
|
|
|
|
| | | X |
|
|
|
|
|
| | | X |
|
|
|
|
|
| | | 13 |
|
|
|
|
|
|------------------------------ | | -----------------------------|
|
|
|
|
|
| | | 13 |
|
|
|
|
|
| | | O |
|
|
|
|
|
| | | O |
|
|
|
|
|
| | | O |
|
|
|
|
|
| | | O |
|
|
|
|
|
| | | O |
|
|
|
|
|
| | | O |
|
|
|
|
|
| | | O |
|
|
|
|
|
| | | O O O |
|
2024-03-09 22:20:11 +01:00
|
|
|
----------------------------------------------------------------
|
|
|
|
|
12 11 10 9 8 7 6 5 4 3 2 1
|
|
|
|
|
";
|
2024-11-07 12:52:57 +01:00
|
|
|
let mut app = App::new(AppArgs {
|
|
|
|
|
seed: Some(1327),
|
2024-11-19 17:28:18 +01:00
|
|
|
bot: Some("dummy".into()),
|
2024-11-07 12:52:57 +01:00
|
|
|
});
|
2024-03-11 20:45:36 +01:00
|
|
|
app.input("roll");
|
|
|
|
|
app.input("1 3");
|
2024-03-09 22:20:11 +01:00
|
|
|
app.input("1 4");
|
|
|
|
|
self::assert_eq!(app.display(), expected);
|
|
|
|
|
}
|
2024-02-09 17:47:34 +01:00
|
|
|
}
|