2024-02-26 08:54:35 -08:00
|
|
|
|
use anyhow::Result;
|
2024-03-04 15:49:40 -08:00
|
|
|
|
use std::io::{stdin, stdout, StdoutLock, Write};
|
2024-02-26 08:54:35 -08:00
|
|
|
|
use termion::{
|
|
|
|
|
color::{self},
|
|
|
|
|
cursor::HideCursor,
|
|
|
|
|
event::Key,
|
|
|
|
|
input::TermRead,
|
|
|
|
|
raw::IntoRawMode,
|
|
|
|
|
};
|
|
|
|
|
|
2024-03-03 21:48:33 -08:00
|
|
|
|
mod agents;
|
2024-02-26 08:54:35 -08:00
|
|
|
|
mod board;
|
|
|
|
|
mod util;
|
|
|
|
|
use board::Board;
|
|
|
|
|
use util::{Player, Symb};
|
|
|
|
|
|
|
|
|
|
fn play(
|
|
|
|
|
stdout: &mut StdoutLock,
|
|
|
|
|
player_max: bool,
|
2024-03-03 21:48:33 -08:00
|
|
|
|
computer: &mut dyn agents::PlayerAgent,
|
2024-02-26 08:54:35 -08:00
|
|
|
|
) -> Result<Board> {
|
|
|
|
|
let mut cursor = 0usize;
|
|
|
|
|
let cursor_offset = 10usize - 1;
|
|
|
|
|
let cursor_max = 10usize;
|
|
|
|
|
|
|
|
|
|
let mut board = Board::new(if player_max {
|
|
|
|
|
Player::Human
|
|
|
|
|
} else {
|
|
|
|
|
Player::Computer
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let mut is_first = true;
|
|
|
|
|
let mut print_board = true;
|
2024-03-04 15:49:40 -08:00
|
|
|
|
|
|
|
|
|
// For human player UI
|
|
|
|
|
let symbols = [
|
|
|
|
|
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '+', '-', '×', '÷',
|
|
|
|
|
];
|
|
|
|
|
let mut selected_symbol = 0;
|
|
|
|
|
|
2024-02-26 08:54:35 -08:00
|
|
|
|
'outer: loop {
|
|
|
|
|
// Computer turn
|
|
|
|
|
if board.current_player() == Player::Computer && !board.is_done() {
|
|
|
|
|
computer.step(&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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-04 15:49:40 -08:00
|
|
|
|
while board.contains(Symb::from_char(&symbols[selected_symbol]).unwrap()) {
|
|
|
|
|
if selected_symbol == symbols.len() - 1 {
|
|
|
|
|
selected_symbol = 0;
|
|
|
|
|
} else {
|
|
|
|
|
selected_symbol += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-26 08:54:35 -08:00
|
|
|
|
print!(
|
|
|
|
|
"\r{}╙{}{}{}{}{}╜",
|
|
|
|
|
" ".repeat(cursor_offset),
|
|
|
|
|
" ".repeat(cursor),
|
|
|
|
|
color::Fg(board.current_player().color()),
|
2024-03-04 15:49:40 -08:00
|
|
|
|
if board.is_done() {
|
|
|
|
|
' '
|
|
|
|
|
} else {
|
|
|
|
|
symbols[selected_symbol]
|
|
|
|
|
},
|
2024-02-26 08:54:35 -08:00
|
|
|
|
color::Fg(color::Reset),
|
|
|
|
|
" ".repeat(cursor_max - cursor),
|
|
|
|
|
);
|
|
|
|
|
stdout.flush()?;
|
|
|
|
|
|
|
|
|
|
if board.is_done() {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-03 21:48:33 -08:00
|
|
|
|
// Player turn
|
2024-02-26 08:54:35 -08:00
|
|
|
|
let stdin = stdin();
|
|
|
|
|
for c in stdin.keys() {
|
|
|
|
|
print_board = match c.unwrap() {
|
2024-03-04 15:49:40 -08:00
|
|
|
|
Key::Char('q') => break 'outer,
|
2024-02-26 08:54:35 -08:00
|
|
|
|
Key::Right => {
|
|
|
|
|
cursor = cursor_max.min(cursor + 1);
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
Key::Left => {
|
|
|
|
|
if cursor != 0 {
|
|
|
|
|
cursor -= 1
|
|
|
|
|
}
|
|
|
|
|
false
|
|
|
|
|
}
|
2024-03-04 15:49:40 -08:00
|
|
|
|
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 s = Symb::from_char(&symbols[selected_symbol]);
|
|
|
|
|
if let Some(s) = s {
|
|
|
|
|
board.play(cursor, s)
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Key::Char(c) => {
|
|
|
|
|
let s = Symb::from_char(&c);
|
|
|
|
|
if let Some(s) = s {
|
|
|
|
|
board.play(cursor, s)
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-26 08:54:35 -08:00
|
|
|
|
_ => false,
|
|
|
|
|
};
|
|
|
|
|
continue 'outer;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Ok(board);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() -> Result<()> {
|
|
|
|
|
let stdout = HideCursor::from(stdout().into_raw_mode().unwrap());
|
|
|
|
|
let mut stdout = stdout.lock();
|
|
|
|
|
|
2024-03-04 15:49:40 -08:00
|
|
|
|
let mut agent = agents::MinMaxTree {};
|
2024-02-26 08:54:35 -08:00
|
|
|
|
let a = play(&mut stdout, true, &mut agent)?;
|
|
|
|
|
if a.is_done() {
|
|
|
|
|
println!(
|
|
|
|
|
"\r\n{}Your score:{} {:.2}\n\n",
|
|
|
|
|
color::Fg(Player::Human.color()),
|
|
|
|
|
color::Fg(color::Reset),
|
|
|
|
|
a.evaluate().unwrap()
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
println!(
|
|
|
|
|
"\r\n{}Quitting{}\r\n",
|
|
|
|
|
color::Fg(color::Red),
|
|
|
|
|
color::Fg(color::Reset),
|
|
|
|
|
);
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-03 21:48:33 -08:00
|
|
|
|
let mut agent = agents::RandomAgent {};
|
2024-02-26 08:54:35 -08:00
|
|
|
|
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(())
|
|
|
|
|
}
|