trictrac/client_cli/src/app.rs

305 lines
10 KiB
Rust
Raw Normal View History

2024-05-20 19:04:46 +02:00
use itertools::Itertools;
2024-03-24 18:37:35 +01:00
use bot::Bot;
2024-02-18 18:40:45 +01:00
use pretty_assertions::assert_eq;
2024-09-22 16:11:42 +02:00
use store::{
CheckerMove, DiceRoller, GameEvent, GameState, PlayerId, PointsRules, 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>,
}
// Application Game
#[derive(Debug, Default)]
pub struct Game {
pub state: GameState,
pub dice_roller: DiceRoller,
2024-03-09 22:20:11 +01:00
first_move: Option<CheckerMove>,
player_id: Option<PlayerId>,
2024-03-24 18:37:35 +01:00
bot: Bot,
2024-02-09 17:47:34 +01:00
}
2024-03-11 20:45:36 +01:00
impl Game {
2024-02-09 17:47:34 +01:00
// Constructs a new instance of [`App`].
2024-09-22 16:11:42 +02:00
pub fn new(schools_enabled: bool, seed: Option<u64>) -> Self {
let mut state = GameState::new(schools_enabled);
2024-03-09 22:20:11 +01:00
// local : player
let player_id: Option<PlayerId> = state.init_player("myself");
2024-03-24 18:37:35 +01:00
// bot
let bot_id: PlayerId = state.init_player("bot").unwrap();
let bot_color = state.player_color_by_id(&bot_id).unwrap();
2024-09-22 16:11:42 +02:00
let bot: Bot = Bot::new(bot_color, schools_enabled);
2024-03-24 18:37:35 +01:00
let mut game = Self {
2024-03-11 20:45:36 +01:00
state,
dice_roller: DiceRoller::new(seed),
2024-03-09 22:20:11 +01:00
first_move: None,
player_id,
2024-03-24 18:37:35 +01:00
bot,
};
2024-03-30 16:10:53 +01:00
game.handle_event(&GameEvent::BeginGame {
2024-03-24 18:37:35 +01:00
goes_first: player_id.unwrap(),
});
game
}
2024-03-30 16:10:53 +01:00
pub fn handle_event(&mut self, event: &GameEvent) -> Option<GameEvent> {
2024-03-29 21:04:58 +01:00
if !self.state.validate(event) {
return None;
}
// println!("consuming {:?}", event);
self.state.consume(event);
// chain all successive bot actions
let bot_event = self
.bot
2024-03-30 16:10:53 +01:00
.handle_event(event)
.and_then(|evt| self.handle_event(&evt));
2024-03-29 21:04:58 +01:00
// roll dice for bot if needed
if self.bot_needs_dice_roll() {
let dice = self.dice_roller.roll();
2024-03-30 16:10:53 +01:00
self.handle_event(&GameEvent::RollResult {
2024-03-29 21:04:58 +01:00
player_id: self.bot.player_id,
dice,
})
} else {
bot_event
2024-03-25 20:49:24 +01:00
}
2024-02-09 17:47:34 +01:00
}
2024-03-27 21:10:15 +01:00
fn bot_needs_dice_roll(&self) -> bool {
self.state.active_player_id == self.bot.player_id
&& self.state.turn_stage == TurnStage::RollWaiting
}
2024-03-11 20:45:36 +01:00
}
2024-02-09 17:47:34 +01:00
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-03-11 20:45:36 +01:00
pub game: Game,
}
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 {
2024-09-22 16:11:42 +02:00
let schools_enabled = false;
2024-03-11 20:45:36 +01:00
Self {
2024-09-22 16:11:42 +02:00
game: Game::new(schools_enabled, args.seed.map(|s| s as u64)),
2024-03-11 20:45:36 +01:00
should_quit: false,
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(),
"roll" => self.roll_dice(),
_ => 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
}
// 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
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
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-03-10 11:49:23 +01:00
let mut output = "-------------------------------".to_owned();
2024-03-31 15:23:18 +02:00
output += format!(
"\n{:?} > {} > {:?}",
self.game.state.stage,
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 {
// 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} :: {}",
&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::*;
#[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 = "-------------------------------
InGame > myself > RollDice
2024-03-27 21:10:15 +01:00
Rolled dice : 4 & 6
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-03-11 20:45:36 +01:00
let mut app = App::new(AppArgs { seed: Some(1327) });
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
}