Refactor
parent
17a42356be
commit
39fbf31af7
|
@ -1,3 +1,5 @@
|
|||
use anyhow::Result;
|
||||
|
||||
use super::{MinimizerAgent, RandomAgent};
|
||||
use crate::{
|
||||
board::{Board, PlayerAction},
|
||||
|
@ -67,7 +69,7 @@ impl DiffuseAgent {
|
|||
}
|
||||
|
||||
impl MinimizerAgent for DiffuseAgent {
|
||||
fn step_min(&mut self, board: &Board) -> PlayerAction {
|
||||
fn step_min(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||
let symb = [Symb::Minus, Symb::Times, Symb::Plus, Symb::Div]
|
||||
.iter()
|
||||
.filter(|x| !board.contains(**x))
|
||||
|
@ -77,7 +79,7 @@ impl MinimizerAgent for DiffuseAgent {
|
|||
// No symbols available, play a random number
|
||||
RandomAgent {}.step_min(board)
|
||||
} else {
|
||||
self.step_symb(board, *symb.unwrap())
|
||||
Ok(self.step_symb(board, *symb.unwrap()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
mod diffuse;
|
||||
mod minmaxtree;
|
||||
mod player;
|
||||
mod random;
|
||||
|
||||
pub use diffuse::DiffuseAgent;
|
||||
pub use minmaxtree::MinMaxTree;
|
||||
pub use player::PlayerAgent;
|
||||
pub use random::RandomAgent;
|
||||
|
||||
use crate::board::{Board, PlayerAction};
|
||||
use anyhow::Result;
|
||||
|
||||
/// An agent that tries to minimize the value of a board.
|
||||
pub trait MinimizerAgent {
|
||||
fn step_min(&mut self, board: &Board) -> PlayerAction;
|
||||
fn step_min(&mut self, board: &Board) -> Result<PlayerAction>;
|
||||
}
|
||||
|
||||
/// An agent that tries to maximize the value of a board.
|
||||
pub trait MaximizerAgent {
|
||||
fn step_max(&mut self, board: &Board) -> PlayerAction;
|
||||
fn step_max(&mut self, board: &Board) -> Result<PlayerAction>;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
use std::io::{stdin, stdout, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use termion::{color, cursor::HideCursor, event::Key, input::TermRead, raw::IntoRawMode};
|
||||
|
||||
use crate::{
|
||||
board::{Board, PlayerAction},
|
||||
util::{Player, Symb},
|
||||
};
|
||||
|
||||
use super::{MaximizerAgent, MinimizerAgent};
|
||||
|
||||
struct SymbolSelector {
|
||||
symbols: Vec<char>,
|
||||
cursor: usize,
|
||||
}
|
||||
|
||||
impl SymbolSelector {
|
||||
fn new(symbols: Vec<char>) -> Self {
|
||||
Self { symbols, cursor: 0 }
|
||||
}
|
||||
|
||||
fn current(&self) -> char {
|
||||
self.symbols[self.cursor]
|
||||
}
|
||||
|
||||
fn check(&mut self, board: &Board) {
|
||||
while board.contains(Symb::from_char(self.current()).unwrap()) {
|
||||
if self.cursor == 0 {
|
||||
self.cursor = self.symbols.len() - 1;
|
||||
} else {
|
||||
self.cursor -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn up(&mut self, board: &Board) {
|
||||
if self.cursor == 0 {
|
||||
self.cursor = self.symbols.len() - 1;
|
||||
} else {
|
||||
self.cursor -= 1;
|
||||
}
|
||||
|
||||
while board.contains(Symb::from_char(self.current()).unwrap()) {
|
||||
if self.cursor == 0 {
|
||||
self.cursor = self.symbols.len() - 1;
|
||||
} else {
|
||||
self.cursor -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn down(&mut self, board: &Board) {
|
||||
if self.cursor == self.symbols.len() - 1 {
|
||||
self.cursor = 0;
|
||||
} else {
|
||||
self.cursor += 1;
|
||||
}
|
||||
|
||||
while board.contains(Symb::from_char(self.current()).unwrap()) {
|
||||
if self.cursor == self.symbols.len() - 1 {
|
||||
self.cursor = 0;
|
||||
} else {
|
||||
self.cursor += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PlayerAgent {
|
||||
player: Player,
|
||||
cursor: usize,
|
||||
symbol_selector: SymbolSelector,
|
||||
}
|
||||
|
||||
impl PlayerAgent {
|
||||
pub fn new(player: Player) -> Self {
|
||||
Self {
|
||||
player,
|
||||
cursor: 0,
|
||||
symbol_selector: SymbolSelector::new(vec![
|
||||
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '+', '-', '×', '÷',
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
fn step(&mut self, board: &Board, minimize: bool) -> Result<PlayerAction> {
|
||||
let stdout = HideCursor::from(stdout().into_raw_mode().unwrap());
|
||||
let mut stdout = stdout.lock();
|
||||
let cursor_max = board.size() - 1;
|
||||
|
||||
self.symbol_selector.check(board);
|
||||
|
||||
// Ask for input until we get a valid move
|
||||
'outer: loop {
|
||||
print!(
|
||||
"\r{}{}{} ╙{}{}{}{}{}╜",
|
||||
// Goal
|
||||
color::Fg(self.player.color()),
|
||||
if minimize { "Min" } else { "Max" },
|
||||
color::Fg(color::Reset),
|
||||
// Cursor
|
||||
" ".repeat(self.cursor),
|
||||
color::Fg(self.player.color()),
|
||||
if board.is_done() {
|
||||
' '
|
||||
} else {
|
||||
self.symbol_selector.current()
|
||||
},
|
||||
color::Fg(color::Reset),
|
||||
" ".repeat(cursor_max - self.cursor),
|
||||
);
|
||||
stdout.flush()?;
|
||||
|
||||
// Player turn
|
||||
let stdin = stdin();
|
||||
for c in stdin.keys() {
|
||||
match c.unwrap() {
|
||||
Key::Char('q') => bail!("player ended game"),
|
||||
Key::Right => {
|
||||
self.cursor = cursor_max.min(self.cursor + 1);
|
||||
}
|
||||
Key::Left => {
|
||||
if self.cursor != 0 {
|
||||
self.cursor -= 1;
|
||||
}
|
||||
}
|
||||
Key::Up => self.symbol_selector.up(board),
|
||||
Key::Down => self.symbol_selector.down(board),
|
||||
Key::Char('\n') => {
|
||||
let symb = Symb::from_char(self.symbol_selector.current());
|
||||
if let Some(symb) = symb {
|
||||
let action = PlayerAction {
|
||||
symb,
|
||||
pos: self.cursor,
|
||||
};
|
||||
if board.can_play(&action) {
|
||||
return Ok(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
Key::Char(c) => {
|
||||
let symb = Symb::from_char(c);
|
||||
if let Some(symb) = symb {
|
||||
let action = PlayerAction {
|
||||
symb,
|
||||
pos: self.cursor,
|
||||
};
|
||||
if board.can_play(&action) {
|
||||
return Ok(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MinimizerAgent for PlayerAgent {
|
||||
fn step_min(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||
self.step(board, true)
|
||||
}
|
||||
}
|
||||
|
||||
impl MaximizerAgent for PlayerAgent {
|
||||
fn step_max(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||
self.step(board, false)
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ use crate::{
|
|||
board::{Board, PlayerAction},
|
||||
util::Symb,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use rand::Rng;
|
||||
use std::num::NonZeroU8;
|
||||
|
||||
|
@ -36,21 +37,21 @@ impl RandomAgent {
|
|||
}
|
||||
|
||||
impl MinimizerAgent for RandomAgent {
|
||||
fn step_min(&mut self, board: &Board) -> PlayerAction {
|
||||
fn step_min(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||
let mut action = self.random_action(board);
|
||||
while !board.can_play(&action) {
|
||||
action = self.random_action(board);
|
||||
}
|
||||
action
|
||||
Ok(action)
|
||||
}
|
||||
}
|
||||
|
||||
impl MaximizerAgent for RandomAgent {
|
||||
fn step_max(&mut self, board: &Board) -> PlayerAction {
|
||||
fn step_max(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||
let mut action = self.random_action(board);
|
||||
while !board.can_play(&action) {
|
||||
action = self.random_action(board);
|
||||
}
|
||||
action
|
||||
Ok(action)
|
||||
}
|
||||
}
|
||||
|
|
21
src/board.rs
21
src/board.rs
|
@ -170,7 +170,6 @@ impl TreeElement {
|
|||
pub struct Board {
|
||||
board: [Option<(Symb, Player)>; 11],
|
||||
free_spots: usize,
|
||||
current_player: Player,
|
||||
}
|
||||
|
||||
impl Display for Board {
|
||||
|
@ -195,11 +194,10 @@ impl Display for Board {
|
|||
|
||||
#[allow(dead_code)]
|
||||
impl Board {
|
||||
pub fn new(current_player: Player) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
free_spots: 11,
|
||||
board: Default::default(),
|
||||
current_player,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,10 +209,6 @@ impl Board {
|
|||
self.board.get(idx)
|
||||
}
|
||||
|
||||
pub fn current_player(&self) -> Player {
|
||||
self.current_player
|
||||
}
|
||||
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.free_spots == 0
|
||||
}
|
||||
|
@ -295,13 +289,12 @@ impl Board {
|
|||
|
||||
/// Place the marked symbol at the given position.
|
||||
/// Returns true for valid moves and false otherwise.
|
||||
pub fn play(&mut self, action: PlayerAction) -> bool {
|
||||
pub fn play(&mut self, action: PlayerAction, player: Player) -> bool {
|
||||
if !self.can_play(&action) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.board[action.pos] = Some((action.symb, self.current_player));
|
||||
self.current_player.invert();
|
||||
self.board[action.pos] = Some((action.symb, player));
|
||||
self.free_spots -= 1;
|
||||
true
|
||||
}
|
||||
|
@ -489,7 +482,7 @@ impl Board {
|
|||
.filter_map(|c| {
|
||||
if c == '_' {
|
||||
Some(None)
|
||||
} else if let Some(symb) = Symb::from_char(&c) {
|
||||
} else if let Some(symb) = Symb::from_char(c) {
|
||||
Some(Some((symb, current_player)))
|
||||
} else {
|
||||
None
|
||||
|
@ -510,10 +503,6 @@ impl Board {
|
|||
}
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
board,
|
||||
free_spots,
|
||||
current_player,
|
||||
})
|
||||
Some(Self { board, free_spots })
|
||||
}
|
||||
}
|
||||
|
|
219
src/main.rs
219
src/main.rs
|
@ -1,12 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use std::io::{stdin, stdout, StdoutLock, Write};
|
||||
use termion::{
|
||||
color::{self},
|
||||
cursor::HideCursor,
|
||||
event::Key,
|
||||
input::TermRead,
|
||||
raw::IntoRawMode,
|
||||
};
|
||||
use anyhow::{bail, Result};
|
||||
use termion::color::{self};
|
||||
|
||||
mod agents;
|
||||
mod board;
|
||||
|
@ -14,172 +7,55 @@ mod util;
|
|||
use board::Board;
|
||||
use util::{Player, Symb};
|
||||
|
||||
use crate::board::PlayerAction;
|
||||
|
||||
fn play(
|
||||
stdout: &mut StdoutLock,
|
||||
player_max: bool,
|
||||
computer: &mut dyn agents::MinimizerAgent,
|
||||
maxi: &mut dyn agents::MaximizerAgent,
|
||||
mini: &mut dyn agents::MinimizerAgent,
|
||||
) -> Result<Board> {
|
||||
let mut cursor = 0usize;
|
||||
let cursor_offset = 10usize - 1;
|
||||
let cursor_max = 10usize;
|
||||
let mut board = Board::new();
|
||||
let mut is_first_turn = true;
|
||||
let mut is_maxi_turn = true;
|
||||
|
||||
let mut board = Board::new(if player_max {
|
||||
Player::Human
|
||||
} else {
|
||||
Player::Computer
|
||||
});
|
||||
|
||||
let mut is_first = true;
|
||||
let mut print_board = true;
|
||||
|
||||
// For human player UI
|
||||
let symbols = [
|
||||
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '+', '-', '×', '÷',
|
||||
];
|
||||
let mut selected_symbol = 0;
|
||||
|
||||
'outer: loop {
|
||||
// Computer turn
|
||||
if board.current_player() == Player::Computer && !board.is_done() {
|
||||
computer.step_min(&mut board);
|
||||
}
|
||||
|
||||
let min_color = if !player_max {
|
||||
Player::Human.color()
|
||||
} else {
|
||||
Player::Computer.color()
|
||||
};
|
||||
|
||||
let max_color = if player_max {
|
||||
Player::Human.color()
|
||||
} else {
|
||||
Player::Computer.color()
|
||||
};
|
||||
|
||||
if print_board {
|
||||
println!(
|
||||
"\r{}\r{}Min{}/{}Max{} {}{}{}",
|
||||
" ".repeat(cursor_max + cursor_offset),
|
||||
color::Fg(min_color),
|
||||
color::Fg(color::Reset),
|
||||
color::Fg(max_color),
|
||||
color::Fg(color::Reset),
|
||||
if is_first { '╓' } else { '║' },
|
||||
board,
|
||||
if is_first { '╖' } else { '║' },
|
||||
);
|
||||
is_first = false;
|
||||
}
|
||||
|
||||
while board.contains(Symb::from_char(&symbols[selected_symbol]).unwrap()) {
|
||||
if selected_symbol == symbols.len() - 1 {
|
||||
selected_symbol = 0;
|
||||
} else {
|
||||
selected_symbol += 1;
|
||||
}
|
||||
}
|
||||
|
||||
print!(
|
||||
"\r{}╙{}{}{}{}{}╜",
|
||||
" ".repeat(cursor_offset),
|
||||
" ".repeat(cursor),
|
||||
color::Fg(board.current_player().color()),
|
||||
if board.is_done() {
|
||||
' '
|
||||
} else {
|
||||
symbols[selected_symbol]
|
||||
},
|
||||
color::Fg(color::Reset),
|
||||
" ".repeat(cursor_max - cursor),
|
||||
while !board.is_done() {
|
||||
// Print board
|
||||
println!(
|
||||
"\r{}{}{}{}",
|
||||
" ".repeat(6),
|
||||
if is_first_turn { '╓' } else { '║' },
|
||||
board,
|
||||
if is_first_turn { '╖' } else { '║' },
|
||||
);
|
||||
stdout.flush()?;
|
||||
is_first_turn = false;
|
||||
|
||||
if board.is_done() {
|
||||
break;
|
||||
}
|
||||
// Take action
|
||||
let action = if is_maxi_turn {
|
||||
maxi.step_max(&board)?
|
||||
} else {
|
||||
mini.step_min(&board)?
|
||||
};
|
||||
is_maxi_turn = !is_maxi_turn;
|
||||
|
||||
// Player turn
|
||||
let stdin = stdin();
|
||||
for c in stdin.keys() {
|
||||
print_board = match c.unwrap() {
|
||||
Key::Char('q') => break 'outer,
|
||||
Key::Right => {
|
||||
cursor = cursor_max.min(cursor + 1);
|
||||
false
|
||||
}
|
||||
Key::Left => {
|
||||
if cursor != 0 {
|
||||
cursor -= 1
|
||||
}
|
||||
false
|
||||
}
|
||||
Key::Up => {
|
||||
if selected_symbol == 0 {
|
||||
selected_symbol = symbols.len() - 1;
|
||||
} else {
|
||||
selected_symbol -= 1;
|
||||
}
|
||||
|
||||
while board.contains(Symb::from_char(&symbols[selected_symbol]).unwrap()) {
|
||||
if selected_symbol == 0 {
|
||||
selected_symbol = symbols.len() - 1;
|
||||
} else {
|
||||
selected_symbol -= 1;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
Key::Down => {
|
||||
if selected_symbol == symbols.len() - 1 {
|
||||
selected_symbol = 0;
|
||||
} else {
|
||||
selected_symbol += 1;
|
||||
}
|
||||
|
||||
while board.contains(Symb::from_char(&symbols[selected_symbol]).unwrap()) {
|
||||
if selected_symbol == symbols.len() - 1 {
|
||||
selected_symbol = 0;
|
||||
} else {
|
||||
selected_symbol += 1;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
Key::Char('\n') => {
|
||||
let symb = Symb::from_char(&symbols[selected_symbol]);
|
||||
if let Some(symb) = symb {
|
||||
let action = PlayerAction { symb, pos: cursor };
|
||||
board.play(action)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Key::Char(c) => {
|
||||
let symb = Symb::from_char(&c);
|
||||
if let Some(symb) = symb {
|
||||
let action = PlayerAction { symb, pos: cursor };
|
||||
board.play(action)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
continue 'outer;
|
||||
if !board.play(
|
||||
action,
|
||||
if is_maxi_turn {
|
||||
Player::Human
|
||||
} else {
|
||||
Player::Computer
|
||||
},
|
||||
) {
|
||||
bail!("agent made invalid move")
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(board);
|
||||
println!("\r{}║{}║", " ".repeat(6), board,);
|
||||
println!("\r{}╙{}╜", " ".repeat(6), " ".repeat(board.size()));
|
||||
Ok(board)
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let stdout = HideCursor::from(stdout().into_raw_mode().unwrap());
|
||||
let mut stdout = stdout.lock();
|
||||
let mut maxi = agents::PlayerAgent::new(Player::Human);
|
||||
let mut mini = agents::MinMaxTree {};
|
||||
|
||||
let mut agent = agents::DiffuseAgent {};
|
||||
let a = play(&mut stdout, true, &mut agent)?;
|
||||
let a = play(&mut maxi, &mut mini)?;
|
||||
if a.is_done() {
|
||||
println!(
|
||||
"\r\n{}Your score:{} {:.2}\n\n",
|
||||
|
@ -195,24 +71,5 @@ fn main() -> Result<()> {
|
|||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut agent = agents::RandomAgent {};
|
||||
let b = play(&mut stdout, false, &mut agent)?;
|
||||
if b.is_done() {
|
||||
println!(
|
||||
"\r\n{}Computer score:{} {:.2}\n\n",
|
||||
color::Fg(Player::Computer.color()),
|
||||
color::Fg(color::Reset),
|
||||
b.evaluate().unwrap()
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"\r\n{}Quitting{}\r\n",
|
||||
color::Fg(color::Red),
|
||||
color::Fg(color::Reset),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -12,13 +12,6 @@ pub enum Player {
|
|||
}
|
||||
|
||||
impl Player {
|
||||
pub fn invert(&mut self) {
|
||||
match self {
|
||||
Self::Human => *self = Self::Computer,
|
||||
Self::Computer => *self = Self::Human,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(&self) -> &dyn Color {
|
||||
match self {
|
||||
Player::Computer => &color::LightBlack,
|
||||
|
@ -79,7 +72,7 @@ impl Symb {
|
|||
self == &Self::Minus
|
||||
}
|
||||
|
||||
pub fn from_char(c: &char) -> Option<Self> {
|
||||
pub fn from_char(c: char) -> Option<Self> {
|
||||
match c {
|
||||
'1' => Some(Self::Number(NonZeroU8::new(1).unwrap())),
|
||||
'2' => Some(Self::Number(NonZeroU8::new(2).unwrap())),
|
||||
|
|
Loading…
Reference in New Issue