mod bitboard;
mod precalculated;
mod solver;
mod transposition_table;
use self::bitboard::PlayerStones;
use std::{fmt, io, str::FromStr};
use bitboard::{heuristic, AllStones, NonLoosingMoves};
pub use solver::{score, Solver};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Column(u8);
impl Column {
pub const fn from_index(index: u8) -> Column {
assert!(index < 7);
Column(index)
}
}
impl FromStr for Column {
type Err = &'static str;
fn from_str(source: &str) -> Result<Column, Self::Err> {
match source.as_bytes().first() {
Some(v @ b'1'..=b'7') => Ok(Column(v - b'1')),
_ => Err("Only digits from 1 to 7 count as valid moves."),
}
}
}
impl fmt::Display for Column {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0 + 1)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Cell {
Empty,
PlayerOne,
PlayerTwo,
}
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct ConnectFour {
last: PlayerStones,
both: AllStones,
}
impl ConnectFour {
pub fn new() -> ConnectFour {
ConnectFour {
last: PlayerStones::new(),
both: AllStones::default(),
}
}
pub fn play(&mut self, column: Column) -> bool {
if self.both.is_full(column.0) {
return false;
}
self.both.insert(column.0);
self.last.flip(self.both);
true
}
pub fn is_legal_move(&self, column: Column) -> bool {
!self.both.is_full(column.0)
}
pub fn from_move_list(move_list: &str) -> ConnectFour {
let mut game = ConnectFour::new();
for c in move_list
.as_bytes()
.iter()
.map(|c| c - b'1')
.map(Column::from_index)
{
if !game.play(c) {
panic!("Illegal move in String describing Connect Four Game")
}
}
game
}
pub fn print_to(&self, mut out: impl io::Write) -> io::Result<()> {
write!(out, "{self}")
}
pub fn legal_moves(&self) -> impl Iterator<Item = Column> + use<'_>{
(0..7).map(Column::from_index).filter(move |&c| self.is_legal_move(c))
}
fn cell(&self, row: u8, column: u8) -> Cell {
let players = [Cell::PlayerOne, Cell::PlayerTwo];
if self.both.is_empty(row, column) {
Cell::Empty
} else if self.last.is_empty(row, column) {
players[self.both.stones() as usize % 2]
} else {
players[(self.both.stones() as usize + 1) % 2]
}
}
fn heuristic(&self) -> u32 {
heuristic(self.last, self.both)
}
pub fn stones(&self) -> u8 {
self.both.stones()
}
pub fn is_victory(&self) -> bool {
self.last.is_win()
}
pub fn encode(&self) -> u64 {
self.last.key(self.both)
}
pub fn can_win_in_next_move(&self) -> bool {
let mut current = self.last;
current.flip(self.both);
self.both.possible() & current.winning_positions() != 0
}
pub fn is_over(&self) -> bool {
self.stones() == 42 || self.is_victory()
}
pub fn non_loosing_moves(&self) -> impl Iterator<Item = Column> {
debug_assert!(!self.can_win_in_next_move());
let nlm = self.non_loosing_moves_impl();
(0..7).filter(move |&i| nlm.contains(i)).map(Column::from_index)
}
fn non_loosing_moves_impl(&self) -> NonLoosingMoves {
debug_assert!(!self.can_win_in_next_move());
NonLoosingMoves::new(self.last, self.both)
}
}
impl fmt::Display for ConnectFour {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for row in (0..6).rev() {
for field in (0..7).map(|column| self.cell(row, column)) {
let c = match field {
Cell::PlayerOne => 'X',
Cell::PlayerTwo => 'O',
Cell::Empty => ' ',
};
write!(f, "|{}", c)?;
}
writeln!(f, "|")?;
}
writeln!(f, "---------------\n 1 2 3 4 5 6 7")
}
}