use crate::{
Team,
NUM_TILES,
};
#[allow(clippy::unusual_byte_groupings)]
const VERTICAL_WIN_1: u16 = 0b100_100_100;
#[allow(clippy::unusual_byte_groupings)]
const VERTICAL_WIN_2: u16 = 0b010_010_010;
#[allow(clippy::unusual_byte_groupings)]
const VERTICAL_WIN_3: u16 = 0b001_001_001;
#[allow(clippy::unusual_byte_groupings)]
const HORIZONTAL_WIN_1: u16 = 0b111_000_000;
#[allow(clippy::unusual_byte_groupings)]
const HORIZONTAL_WIN_2: u16 = 0b000_111_000;
#[allow(clippy::unusual_byte_groupings)]
const HORIZONTAL_WIN_3: u16 = 0b000_000_111;
#[allow(clippy::unusual_byte_groupings)]
const DIAGONAL_WIN: u16 = 0b100_010_001;
#[allow(clippy::unusual_byte_groupings)]
const ANTI_DIAGONAL_WIN: u16 = 0b001_010_100;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum WinType {
Horizontal,
Vertical,
Diagonal,
AntiDiagonal,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct WinnerInfo {
pub team: Team,
pub tile_indexes: [u8; 3],
pub win_type: WinType,
}
impl WinnerInfo {
fn new(team: Team, i0: u8, i1: u8, i2: u8, win_type: WinType) -> Self {
Self {
team,
tile_indexes: [i0, i1, i2],
win_type,
}
}
pub fn start_tile_index(&self) -> u8 {
self.tile_indexes[0]
}
pub fn end_tile_index(&self) -> u8 {
self.tile_indexes[2]
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Board {
x_state: u16,
o_state: u16,
}
impl Board {
pub fn new() -> Self {
Board {
x_state: 0,
o_state: 0,
}
}
pub fn get_turn(self) -> Team {
let num_x = self.x_state.count_ones();
let num_o = self.o_state.count_ones();
if num_x > num_o {
Team::O
} else {
Team::X
}
}
pub fn is_draw(self) -> bool {
(self.x_state | self.o_state).count_ones() >= u32::from(NUM_TILES)
}
pub fn has_won(self, team: Team) -> bool {
let state = match team {
Team::X => self.x_state,
Team::O => self.o_state,
};
((state & VERTICAL_WIN_1) == VERTICAL_WIN_1)
|| ((state & VERTICAL_WIN_2) == VERTICAL_WIN_2)
|| ((state & VERTICAL_WIN_3) == VERTICAL_WIN_3)
|| ((state & HORIZONTAL_WIN_1) == HORIZONTAL_WIN_1)
|| ((state & HORIZONTAL_WIN_2) == HORIZONTAL_WIN_2)
|| ((state & HORIZONTAL_WIN_3) == HORIZONTAL_WIN_3)
|| ((state & DIAGONAL_WIN) == DIAGONAL_WIN)
|| ((state & ANTI_DIAGONAL_WIN) == ANTI_DIAGONAL_WIN)
}
pub fn get_winner(self) -> Option<Team> {
if self.has_won(Team::X) {
Some(Team::X)
} else if self.has_won(Team::O) {
Some(Team::O)
} else {
None
}
}
pub fn get_winner_info(self) -> Option<WinnerInfo> {
let winner = self.get_winner()?;
let state = match winner {
Team::X => self.x_state,
Team::O => self.o_state,
};
if (state & VERTICAL_WIN_1) == VERTICAL_WIN_1 {
return Some(WinnerInfo::new(winner, 2, 5, 8, WinType::Vertical));
}
if (state & VERTICAL_WIN_2) == VERTICAL_WIN_2 {
return Some(WinnerInfo::new(winner, 1, 4, 7, WinType::Vertical));
}
if (state & VERTICAL_WIN_3) == VERTICAL_WIN_3 {
return Some(WinnerInfo::new(winner, 0, 3, 6, WinType::Vertical));
}
if (state & HORIZONTAL_WIN_1) == HORIZONTAL_WIN_1 {
return Some(WinnerInfo::new(winner, 0, 1, 2, WinType::Horizontal));
}
if (state & HORIZONTAL_WIN_2) == HORIZONTAL_WIN_2 {
return Some(WinnerInfo::new(winner, 6, 7, 8, WinType::Horizontal));
}
if (state & HORIZONTAL_WIN_3) == HORIZONTAL_WIN_3 {
return Some(WinnerInfo::new(winner, 0, 1, 2, WinType::Horizontal));
}
if (state & DIAGONAL_WIN) == DIAGONAL_WIN {
return Some(WinnerInfo::new(winner, 0, 4, 8, WinType::Diagonal));
}
if (state & ANTI_DIAGONAL_WIN) == ANTI_DIAGONAL_WIN {
return Some(WinnerInfo::new(winner, 2, 4, 6, WinType::AntiDiagonal));
}
None
}
#[must_use]
pub fn set(mut self, index: u8, team: Option<Team>) -> Self {
assert!(index < NUM_TILES);
match team {
Some(Team::X) => {
self.x_state |= 1 << index;
self.o_state &= !(1 << index);
}
Some(Team::O) => {
self.x_state &= !(1 << index);
self.o_state |= 1 << index;
}
None => {
self.x_state &= !(1 << index);
self.o_state &= !(1 << index);
}
}
self
}
pub fn get(self, index: u8) -> Option<Team> {
assert!(index < NUM_TILES);
if self.x_state & (1 << index) != 0 {
Some(Team::X)
} else if self.o_state & (1 << index) != 0 {
Some(Team::O)
} else {
None
}
}
pub fn iter_children(self) -> ChildrenIter {
ChildrenIter::new(self)
}
pub fn iter(self) -> impl Iterator<Item = (u8, Option<Team>)> {
let mut index = 0;
std::iter::from_fn(move || {
if index >= NUM_TILES {
return None;
}
let index_mask = 1 << index;
let ret = if (self.x_state & index_mask) != 0 {
Some(Team::X)
} else if (self.o_state & index_mask) != 0 {
Some(Team::O)
} else {
None
};
let ret = Some((index, ret));
index += 1;
ret
})
}
pub fn encode_u16(self) -> u16 {
let mut ret = 0;
for i in (0..NUM_TILES).rev() {
let tile = self.get(i);
ret *= 3;
ret += match tile {
None => 0,
Some(Team::X) => 1,
Some(Team::O) => 2,
};
}
ret
}
pub fn decode_u16(mut data: u16) -> Self {
let mut ret = Self::new();
for i in 0..NUM_TILES {
let tile = match data % 3 {
0 => None,
1 => Some(Team::X),
2 => Some(Team::O),
unknown_tile => unreachable!("unknown tile \"{unknown_tile}\"",),
};
ret = ret.set(i, tile);
data /= 3;
}
ret
}
}
impl Default for Board {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct ChildrenIter {
board: Board,
turn: Team,
index: u8,
}
impl ChildrenIter {
fn new(board: Board) -> Self {
let turn = board.get_turn();
Self {
board,
turn,
index: 0,
}
}
}
impl Iterator for ChildrenIter {
type Item = (u8, Board);
fn next(&mut self) -> Option<Self::Item> {
if self.board.get_winner().is_some() {
return None;
}
loop {
if self.index >= NUM_TILES {
return None;
}
let index_mask = 1 << self.index;
let tile_does_not_exist = ((self.board.x_state | self.board.o_state) & index_mask) == 0;
if tile_does_not_exist {
let board = self.board.set(self.index, Some(self.turn));
let item = Some((self.index, board));
self.index += 1;
return item;
}
self.index += 1;
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, Some(usize::from(NUM_TILES)))
}
}
impl std::iter::FusedIterator for ChildrenIter {}