add back functionality

This commit is contained in:
Simon Gardling 2025-04-10 14:18:50 -04:00
parent 80db0a872d
commit fc8c2b8178
Signed by: titaniumtown
GPG Key ID: 9AB28AC10ECE533D
3 changed files with 83 additions and 39 deletions

View File

@ -3,11 +3,18 @@ use rand::prelude::*;
use std::io; use std::io;
use std::io::prelude::*; use std::io::prelude::*;
#[derive(Debug, Clone, Copy)]
pub enum AgentMove {
CoordPair(CoordPair),
Back,
Skip,
}
#[allow(dead_code)] #[allow(dead_code)]
// implements `Send` so we can use it in a multithreaded `EloArena` // implements `Send` so we can use it in a multithreaded `EloArena`
pub trait Agent: Send { pub trait Agent: Send {
/// Returns the move of an [`Agent`] /// Returns the move of an [`Agent`]
fn next_move(&mut self, board: &Board) -> Option<CoordPair>; fn next_move(&mut self, board: &Board) -> AgentMove;
/// Returns the name of the [`Agent`] /// Returns the name of the [`Agent`]
fn name(&self) -> &'static str; fn name(&self) -> &'static str;
/// Returns the color the [`Agent`] is playing /// Returns the color the [`Agent`] is playing
@ -28,16 +35,23 @@ impl ManualAgent {
#[allow(dead_code)] #[allow(dead_code)]
impl Agent for ManualAgent { impl Agent for ManualAgent {
fn next_move(&mut self, board: &Board) -> Option<CoordPair> { fn next_move(&mut self, board: &Board) -> AgentMove {
let stdin = io::stdin(); let stdin = io::stdin();
let mut input = String::new(); let mut input = String::new();
println!("Your turn! ('Skip' to skip)"); println!("Your turn! ('Skip' to skip 'back' to go back a move)");
loop { loop {
input.clear(); input.clear();
stdin.lock().read_line(&mut input).ok()?; if stdin.lock().read_line(&mut input).is_err() {
return AgentMove::Skip;
};
if input.to_lowercase().trim() == "skip" { if input.to_lowercase().trim() == "skip" {
// skips move // skips move
return None; return AgentMove::Skip;
}
if input.to_lowercase().trim() == "back" {
// skips move
return AgentMove::Back;
} }
let got = input let got = input
@ -53,7 +67,7 @@ impl Agent for ManualAgent {
println!("Invalid move! Try again."); println!("Invalid move! Try again.");
continue; continue;
} }
return Some(got.into()); return AgentMove::CoordPair(got.into());
} }
} }
} }
@ -74,8 +88,11 @@ pub struct RandomAgent {
#[allow(dead_code)] #[allow(dead_code)]
impl Agent for RandomAgent { impl Agent for RandomAgent {
fn next_move(&mut self, board: &Board) -> Option<CoordPair> { fn next_move(&mut self, board: &Board) -> AgentMove {
board.possible_moves(self.color).choose(&mut rand::rng()) match board.possible_moves(self.color).choose(&mut rand::rng()) {
Some(x) => AgentMove::CoordPair(x),
None => AgentMove::Skip,
}
} }
fn name(&self) -> &'static str { fn name(&self) -> &'static str {

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
agent::Agent, agent::{Agent, AgentMove},
logic::{FutureMoveConfig, FutureMoves}, logic::{FutureMoveConfig, FutureMoves},
repr::{Board, CoordPair, Piece}, repr::{Board, Piece},
}; };
pub struct ComplexAgent { pub struct ComplexAgent {
@ -20,13 +20,18 @@ impl ComplexAgent {
} }
impl Agent for ComplexAgent { impl Agent for ComplexAgent {
fn next_move(&mut self, board: &Board) -> Option<CoordPair> { fn next_move(&mut self, board: &Board) -> AgentMove {
self.future_moves.update_from_board(board); self.future_moves.update_from_board(board);
self.future_moves.generate(); self.future_moves.generate();
self.future_moves match self
.future_moves
.best_move() .best_move()
.expect("FutureMoves has no move?") .expect("FutureMoves has no move?")
{
Some(x) => AgentMove::CoordPair(x),
None => AgentMove::Skip,
}
} }
fn name(&self) -> &'static str { fn name(&self) -> &'static str {

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
agent::Agent, agent::{Agent, AgentMove},
repr::{Board, CoordPair, Piece, Winner}, repr::{Board, CoordPair, Piece, Winner},
}; };
@ -70,41 +70,48 @@ impl GameInner {
// when it comes to human input // when it comes to human input
loop { loop {
let player_move = self.players[player_i].next_move(&self.board); let player_move = self.players[player_i].next_move(&self.board);
self.move_log.push((player_move, player_color));
if let Some(coord) = player_move { match player_move {
match self.board.place(coord, player_color) { AgentMove::CoordPair(coord) => {
// Check if its a valid move self.move_log.push((Some(coord), player_color));
Ok(_) => { match self.board.place(coord, player_color) {
if self.do_print { // Check if its a valid move
println!("Player {} placed at {}", player_i, coord); Ok(_) => {
if self.do_print {
println!("Player {} placed at {}", player_i, coord);
}
break;
} }
break;
}
Err(err) => { Err(err) => {
panic!( panic!(
"Invalid move by Player {}: {}. Move: {} State: {:?}", "Invalid move by Player {}: {}. Move: {} State: {:?}",
player_i, err, coord, self player_i, err, coord, self
); );
}
} }
} }
} else { AgentMove::Back => {
if self.do_print { self.back();
println!("Player {} did not make a move!", player_i);
} }
AgentMove::Skip => {
// players are able to skip a move if they have no valid moves to make self.move_log.push((None, player_color));
if self.board.possible_moves(player_color).next().is_some() {
if self.do_print { if self.do_print {
println!( println!("Player {} did not make a move!", player_i);
"Player {} has possible moves, but skipped (invalid)",
player_i
);
} }
continue;
// 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
} }
return; // No valid move available
} }
} }
} }
@ -146,4 +153,19 @@ impl GameInner {
self.game_loop(); self.game_loop();
self.board.game_winner() self.board.game_winner()
} }
pub fn back(&mut self) {
if self.move_log.is_empty() {
println!("This is the initial board state!");
return;
}
self.move_log.pop();
self.board = self.first_board;
for &(l, p) in &self.move_log {
if let Some(loc) = l {
self.board.place(loc, p).expect("unable to go backwards");
}
}
println!("{}", self);
}
} }