use crate::arena::error::{ArenaError, GameError, PlayerError};
use crate::board::{Board, BoardError, Turn};
use indicatif::{MultiProgress, ProgressBar, ProgressState, ProgressStyle};
use std::io::{BufRead, Write};
use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
pub struct Player<W, R>
where
W: Write + Send + 'static,
R: BufRead + Send + 'static,
{
stdin: W,
stdout: Arc<Mutex<R>>,
}
impl<W, R> Player<W, R>
where
R: BufRead + Send + 'static,
W: Write + Send + 'static,
{
pub fn new(stdin: W, stdout: R) -> Self {
Player {
stdin,
stdout: Arc::new(Mutex::new(stdout)),
}
}
pub fn get_move_with_timeout(
&mut self,
board: &Board,
timeout: Duration,
) -> Result<usize, PlayerError> {
let (tx, rx) = mpsc::channel();
let mut board_line = board.get_board_line().map_err(|_| PlayerError::Board)?;
board_line.push('\n');
self.stdin
.write(board_line.as_bytes())
.map_err(|_| PlayerError::Io)?;
self.stdin.flush().map_err(|_| PlayerError::Io)?;
let stdout = Arc::clone(&self.stdout);
let handle = thread::spawn(move || {
let mut stdout = stdout.lock().unwrap();
let mut response = String::new();
let result = stdout
.read_line(&mut response)
.map_err(|_| PlayerError::Io)
.and_then(|_| {
response
.trim()
.parse::<usize>()
.map_err(|_| PlayerError::Parse)
});
tx.send(result).unwrap();
});
match rx.recv_timeout(timeout) {
Ok(result) => {
handle.join().unwrap();
result
}
Err(_) => {
handle.thread().unpark();
Err(PlayerError::Timeout)
}
}
}
}
#[derive(Debug, Clone)]
enum GameResult {
BlackWin(usize, usize),
WhiteWin(usize, usize),
Draw(usize, usize),
}
#[derive(Debug, Clone)]
enum GameStatus {
Finished(GameResult),
Playing,
}
struct Game<'a, W, R>
where
W: Write + Send + 'static,
R: BufRead + Send + 'static,
{
board: Board,
black_player: &'a mut Player<W, R>,
white_player: &'a mut Player<W, R>,
moves: Vec<Option<usize>>,
board_log: Vec<(u64, u64, Turn)>,
status: GameStatus,
}
impl<'a, W, R> Game<'a, W, R>
where
W: Write + Send + 'static,
R: BufRead + Send + 'static,
{
fn new(black_player: &'a mut Player<W, R>, white_player: &'a mut Player<W, R>) -> Self {
Game {
board: Board::new(),
black_player,
white_player,
moves: Vec::new(),
board_log: Vec::new(),
status: GameStatus::Playing,
}
}
fn get_move(&mut self) -> Result<usize, GameError> {
let turn = self.board.get_turn();
let player = match turn {
Turn::Black => &mut self.black_player,
Turn::White => &mut self.white_player,
};
player
.get_move_with_timeout(&self.board, DEFAULT_TIMEOUT)
.map_err(|e| match e {
PlayerError::Io => match turn {
Turn::Black => GameError::BlackCrash,
Turn::White => GameError::WhiteCrash,
},
PlayerError::Parse => match turn {
Turn::Black => GameError::BlackInvalidMove,
Turn::White => GameError::WhiteInvalidMove,
},
PlayerError::Timeout => match turn {
Turn::Black => GameError::BlackTimeout,
Turn::White => GameError::WhiteTimeout,
},
PlayerError::Board => GameError::UnexpectedError,
})
}
fn play(&mut self) -> Result<(), GameError> {
while !self.board.is_game_over() {
if self.board.is_pass() {
self.board.do_pass().unwrap();
self.moves.push(None);
continue;
}
let mv = self.get_move()?;
self.board.do_move(mv).map_err(|e| match e {
BoardError::InvalidMove => match self.board.get_turn() {
Turn::Black => GameError::BlackInvalidMove,
Turn::White => GameError::WhiteInvalidMove,
},
BoardError::InvalidPosition => match self.board.get_turn() {
Turn::Black => GameError::BlackInvalidMove,
Turn::White => GameError::WhiteInvalidMove,
},
_ => GameError::UnexpectedError,
})?;
self.moves.push(Some(mv));
self.board_log.push(self.board.get_board());
}
let winner = self.board.get_winner().unwrap();
let black_pieces = self.board.black_piece_num() as usize;
let white_pieces = self.board.white_piece_num() as usize;
self.status = match winner {
Some(Turn::Black) => {
GameStatus::Finished(GameResult::BlackWin(black_pieces, white_pieces))
}
Some(Turn::White) => {
GameStatus::Finished(GameResult::WhiteWin(black_pieces, white_pieces))
}
None => GameStatus::Finished(GameResult::Draw(black_pieces, white_pieces)),
};
Ok(())
}
fn get_result(&self) -> Result<GameResult, GameError> {
match &self.status {
GameStatus::Finished(result) => Ok(result.clone()),
GameStatus::Playing => Err(GameError::GameNotOverYet),
}
}
}
pub enum PlayerOrder {
P1equalsBlack,
P2equalsBlack,
}
type PlayerPair<W, R> = Arc<Mutex<(Player<W, R>, Player<W, R>)>>;
pub struct Arena<W, R>
where
W: Write + Send + 'static,
R: BufRead + Send + 'static,
{
games: Vec<(PlayerOrder, GameResult)>,
players: Vec<PlayerPair<W, R>>,
show_progress: bool,
}
impl<W, R> Arena<W, R>
where
W: Write + Send + 'static,
R: BufRead + Send + 'static,
{
pub fn new(players: Vec<(Player<W, R>, Player<W, R>)>, show_progress: bool) -> Self {
Arena {
games: Vec::new(),
players: players
.into_iter()
.map(|(p1, p2)| Arc::new(Mutex::new((p1, p2))))
.collect(),
show_progress,
}
}
pub fn play_n(&mut self, n: usize) -> Result<(), ArenaError> {
if n % 2 != 0 {
return Err(ArenaError::GameNumberInvalid);
}
let half_n = n / 2;
let players0 = Arc::clone(&self.players[0]);
let players1 = Arc::clone(&self.players[1]);
let m = match self.show_progress {
true => Some(MultiProgress::new()),
false => None,
};
let pb1 = match m {
Some(ref m) => {
let pb = m.add(ProgressBar::new(half_n as u64));
pb.set_style(
ProgressStyle::with_template("[{wide_bar}] [{elapsed_precise}] ({eta})")
.unwrap()
.with_key(
"eta",
|state: &ProgressState, w: &mut dyn std::fmt::Write| {
write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()
},
)
.progress_chars("#>-"),
);
Some(pb)
}
None => None,
};
let pb2 = match m {
Some(ref m) => {
let pb = m.add(ProgressBar::new(half_n as u64));
pb.set_style(
ProgressStyle::with_template("[{wide_bar}] [{elapsed_precise}] ({eta})")
.unwrap()
.with_key(
"eta",
|state: &ProgressState, w: &mut dyn std::fmt::Write| {
write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()
},
)
.progress_chars("#>-"),
);
Some(pb)
}
None => None,
};
let mut handles = vec![];
{
handles.push(thread::spawn(move || {
let mut results = Vec::with_capacity(half_n);
for _ in 0..half_n {
let mut player0 = players0.lock().unwrap();
let (p1, p2) = &mut *player0;
let mut game = Game::new(p1, p2);
match game.play() {
Ok(_) => {
let result = game.get_result();
results.push((PlayerOrder::P1equalsBlack, result));
}
Err(e) => return Err(ArenaError::GameError(e)),
}
if let Some(ref pb1) = pb1 {
pb1.inc(1);
}
}
Ok(results)
}));
}
{
handles.push(thread::spawn(move || {
let mut results = Vec::with_capacity(half_n);
for _ in 0..half_n {
let mut player1 = players1.lock().unwrap();
let (p1, p2) = &mut *player1;
let mut game = Game::new(p1, p2);
match game.play() {
Ok(_) => {
let result = game.get_result();
results.push((PlayerOrder::P2equalsBlack, result));
}
Err(e) => return Err(ArenaError::GameError(e)),
}
if let Some(ref pb2) = pb2 {
pb2.inc(1);
}
}
Ok(results)
}));
}
let mut all_results = Vec::with_capacity(n);
for handle in handles {
match handle.join() {
Ok(Ok(mut results)) => {
for result in results.drain(..) {
match result {
(order, Ok(result)) => all_results.push((order, result)),
(_, Err(e)) => {
return Err(ArenaError::GameError(e));
}
}
}
}
Ok(Err(e)) => return Err(e),
Err(_) => return Err(ArenaError::ThreadJoinError),
}
}
self.games.extend(all_results);
Ok(())
}
pub fn get_stats(&self) -> (usize, usize, usize) {
let mut p1_win = 0;
let mut p2_win = 0;
let mut draw = 0;
for (order, game_result) in self.games.iter() {
match game_result {
GameResult::BlackWin(_, _) => match order {
PlayerOrder::P1equalsBlack => p1_win += 1,
PlayerOrder::P2equalsBlack => p2_win += 1,
},
GameResult::WhiteWin(_, _) => match order {
PlayerOrder::P1equalsBlack => p2_win += 1,
PlayerOrder::P2equalsBlack => p1_win += 1,
},
GameResult::Draw(_, _) => draw += 1,
}
}
(p1_win, p2_win, draw)
}
pub fn get_pieces(&self) -> (usize, usize) {
let mut p1_pieces = 0;
let mut p2_pieces = 0;
for (order, game_result) in self.games.iter() {
match game_result {
GameResult::BlackWin(black_pieces, white_pieces) => match order {
PlayerOrder::P1equalsBlack => {
p1_pieces += black_pieces;
p2_pieces += white_pieces;
}
PlayerOrder::P2equalsBlack => {
p1_pieces += white_pieces;
p2_pieces += black_pieces;
}
},
GameResult::WhiteWin(black_pieces, white_pieces) => match order {
PlayerOrder::P1equalsBlack => {
p2_pieces += white_pieces;
p1_pieces += black_pieces;
}
PlayerOrder::P2equalsBlack => {
p2_pieces += black_pieces;
p1_pieces += white_pieces;
}
},
GameResult::Draw(black_pieces, white_pieces) => {
p1_pieces += black_pieces;
p2_pieces += white_pieces;
}
}
}
(p1_pieces, p2_pieces)
}
}