use std::fmt::{Debug, Display};
use std::hash::Hash;
use std::marker::PhantomData;
use std::ops::ControlFlow;
use std::panic::{RefUnwindSafe, UnwindSafe};
use internal_iterator::InternalIterator;
use rand::Rng;
use crate::symmetry::{Symmetry, UnitSymmetry};
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Player {
A,
B,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Outcome {
WonBy(Player),
Draw,
}
pub trait Board:
'static + Debug + Display + Clone + Eq + Hash + Send + Sync + UnwindSafe + RefUnwindSafe + BoardSymmetry<Self>
where
for<'a> Self: BoardMoves<'a, Self>,
{
type Move: Debug + Display + Eq + Ord + Hash + Copy + Send + Sync + UnwindSafe + RefUnwindSafe;
fn next_player(&self) -> Player;
fn is_available_move(&self, mv: Self::Move) -> bool;
fn random_available_move(&self, rng: &mut impl Rng) -> Self::Move {
let count = self.available_moves().count();
let index = rng.gen_range(0..count);
self.available_moves().nth(index).unwrap()
}
fn play(&mut self, mv: Self::Move);
fn clone_and_play(&self, mv: Self::Move) -> Self {
let mut next = self.clone();
next.play(mv);
next
}
fn outcome(&self) -> Option<Outcome>;
fn is_done(&self) -> bool {
self.outcome().is_some()
}
fn can_lose_after_move() -> bool;
}
pub trait Alternating {}
pub trait AltBoard: Board + Alternating {}
impl<B: Board + Alternating> AltBoard for B {}
pub trait BoardMoves<'a, B: Board> {
type AllMovesIterator: InternalIterator<Item = B::Move>;
type AvailableMovesIterator: InternalIterator<Item = B::Move>;
fn all_possible_moves() -> Self::AllMovesIterator;
fn available_moves(&'a self) -> Self::AvailableMovesIterator;
}
pub trait BoardSymmetry<B: Board> {
type Symmetry: Symmetry;
fn map(&self, sym: Self::Symmetry) -> Self;
fn map_move(&self, sym: Self::Symmetry, mv: B::Move) -> B::Move;
}
pub trait UnitSymmetryBoard: Board {}
impl<B: UnitSymmetryBoard> BoardSymmetry<B> for B {
type Symmetry = UnitSymmetry;
fn map(&self, _: Self::Symmetry) -> Self {
self.clone()
}
fn map_move(&self, _: Self::Symmetry, mv: B::Move) -> B::Move {
mv
}
}
impl Player {
pub const BOTH: [Player; 2] = [Player::A, Player::B];
pub fn other(self) -> Player {
match self {
Player::A => Player::B,
Player::B => Player::A,
}
}
pub fn index(self) -> u8 {
match self {
Player::A => 0,
Player::B => 1,
}
}
pub fn to_char(self) -> char {
match self {
Player::A => 'A',
Player::B => 'B',
}
}
pub fn sign<V: num_traits::One + std::ops::Neg<Output = V>>(self, pov: Player) -> V {
if self == pov {
V::one()
} else {
-V::one()
}
}
}
#[derive(Debug)]
pub struct AllMovesIterator<B: Board>(PhantomData<B>);
impl<B: Board> Default for AllMovesIterator<B> {
fn default() -> Self {
AllMovesIterator(PhantomData)
}
}
#[derive(Debug)]
pub struct AvailableMovesIterator<'a, B: Board>(pub &'a B);
#[derive(Debug)]
pub struct BruteforceMoveIterator<'a, B: Board> {
board: &'a B,
}
impl<'a, B: Board> BruteforceMoveIterator<'a, B> {
pub fn new(board: &'a B) -> Self {
assert!(
!board.is_done(),
"Cannot get available moves for done board {:?}",
board
);
BruteforceMoveIterator { board }
}
}
impl<'a, B: Board> InternalIterator for BruteforceMoveIterator<'a, B> {
type Item = B::Move;
fn try_for_each<R, F>(self, mut f: F) -> ControlFlow<R>
where
F: FnMut(Self::Item) -> ControlFlow<R>,
{
B::all_possible_moves().try_for_each(|mv: B::Move| {
if self.board.is_available_move(mv) {
f(mv)
} else {
ControlFlow::Continue(())
}
})
}
}