split off game_inner

This commit is contained in:
Simon Gardling 2025-03-02 21:38:04 -05:00
parent ed1c785cb3
commit f6c2ef753d
Signed by: titaniumtown
GPG Key ID: 9AB28AC10ECE533D
4 changed files with 134 additions and 97 deletions

View File

@ -1,115 +1,22 @@
use crate::{
agent::Agent,
game_inner::GameInner,
repr::{Board, Piece, Winner},
};
pub struct Game {
players: [Box<dyn Agent>; 2],
board: Board,
}
impl std::fmt::Display for Game {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"Players: {} ({}) and {} ({})",
self.players[0].name(),
self.players[0].color().symbol(),
self.players[1].name(),
self.players[1].color().symbol()
)?;
write!(f, "{}", self.board)?;
Ok(())
}
inner: GameInner,
}
#[allow(dead_code)]
impl Game {
pub fn new(player1: Box<dyn Agent>, player2: Box<dyn Agent>) -> Self {
let player1_color = player1.color();
let player2_color = player2.color();
assert_ne!(
player1_color,
player2_color,
"Both players cannot have the same color {}",
player1_color.text()
);
assert_eq!(player1_color, Piece::Black, "player 1 must playing black");
assert_eq!(player2_color, Piece::White, "player 2 must play white");
Self {
players: [player1, player2],
board: Board::new().starting_pos(),
inner: GameInner::new(player1, player2, true),
}
}
// Handle when a move is made
pub fn step(&mut self, player_i: usize) {
let player_color = self.players[player_i].color();
// TODO! move this to `ManualAgent` as it's only really useful
// when it comes to human input
loop {
let player_move = self.players[player_i].next_move(&self.board);
if let Some(coord) = player_move {
match self.board.place(coord, player_color) {
// Check if its a valid move
Ok(_) => {
println!("Player {} placed at {}", player_i, coord);
break;
}
// Lets the player try again if the move is invalid
// Now we dont need to restart the game if we mess up
Err(err) => {
panic!(
"Invalid move by Player {}: {}. Try again. Move: {}",
player_i, err, coord
);
}
}
} else {
println!("Player {} did not make a move!", player_i);
// players are able to skip a move if they have no valid moves to make
if self.board.possible_moves(player_color).any(|_| true) {
println!(
"Player {} has possible moves, but skipped (invalid)",
player_i
);
continue;
}
return; // No valid move available
}
}
}
// TODO! make this loop good
pub fn game_loop(&mut self) {
let mut current_player: usize = 0;
loop {
// alternate which player plays
current_player %= self.players.len();
println!("{}", self);
match self.board.game_winner() {
Winner::Player(piece) => {
println!("{} Wins!", piece.text());
break;
}
Winner::Tie => {
println!("Game Tied!");
break;
}
Winner::None => {}
}
self.step(current_player);
current_player += 1;
}
self.inner.game_loop();
}
}

128
src/game_inner.rs Normal file
View File

@ -0,0 +1,128 @@
use crate::{
agent::Agent,
repr::{Board, Piece, Winner},
};
impl std::fmt::Display for GameInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"Players: {} ({}) and {} ({})",
self.players[0].name(),
self.players[0].color().symbol(),
self.players[1].name(),
self.players[1].color().symbol()
)?;
write!(f, "{}", self.board)?;
Ok(())
}
}
pub struct GameInner {
players: [Box<dyn Agent>; 2],
board: Board,
do_print: bool,
}
impl GameInner {
pub fn new(player1: Box<dyn Agent>, player2: Box<dyn Agent>, do_print: bool) -> Self {
let player1_color = player1.color();
let player2_color = player2.color();
assert_ne!(
player1_color,
player2_color,
"Both players cannot have the same color {}",
player1_color.text()
);
assert_eq!(player1_color, Piece::Black, "player 1 must playing black");
assert_eq!(player2_color, Piece::White, "player 2 must play white");
Self {
players: [player1, player2],
board: Board::new().starting_pos(),
do_print,
}
}
// Handle when a move is made
pub fn step(&mut self, player_i: usize) {
let player_color = self.players[player_i].color();
// TODO! move this to `ManualAgent` as it's only really useful
// when it comes to human input
loop {
let player_move = self.players[player_i].next_move(&self.board);
if let Some(coord) = player_move {
match self.board.place(coord, player_color) {
// Check if its a valid move
Ok(_) => {
if self.do_print {
println!("Player {} placed at {}", player_i, coord);
}
break;
}
Err(err) => {
panic!(
"Invalid move by Player {}: {}. Move: {}",
player_i, err, coord
);
}
}
} else {
println!("Player {} did not make a move!", player_i);
// players are able to skip a move if they have no valid moves to make
if self.board.possible_moves(player_color).next().is_some() {
if self.do_print {
println!(
"Player {} has possible moves, but skipped (invalid)",
player_i
);
}
continue;
}
return; // No valid move available
}
}
}
// TODO! make this loop good
pub fn game_loop(&mut self) {
let mut current_player: usize = 0;
loop {
// alternate which player plays
current_player %= self.players.len();
if self.do_print {
println!("{}", self);
}
match self.board.game_winner() {
Winner::Player(piece) => {
if self.do_print {
println!("{} Wins!", piece.text());
}
break;
}
Winner::Tie => {
if self.do_print {
println!("Game Tied!");
}
break;
}
Winner::None => {}
}
self.step(current_player);
current_player += 1;
}
}
pub fn loop_until_result(&mut self) -> Winner {
self.game_loop();
self.board.game_winner()
}
}

View File

@ -1,5 +1,6 @@
mod agent;
mod complexagent;
mod game;
mod game_inner;
pub mod logic;
pub mod repr;

View File

@ -5,6 +5,7 @@ use repr::Piece;
mod agent;
mod complexagent;
mod game;
mod game_inner;
mod logic;
pub mod repr;