use crate::PositionParsingError;
#[derive(Debug, Copy, Clone)]
pub struct Position {
pub position: u64,
pub mask: u64,
moves: usize,
}
impl Position {
pub const WIDTH: usize = 7;
pub const HEIGHT: usize = 6;
pub const BOARD_SIZE: usize = Self::WIDTH * Self::HEIGHT;
pub const CENTRE: usize = Self::WIDTH / 2;
pub const MIN_SCORE: i8 = -(Self::BOARD_SIZE as i8) / 2 + 3;
pub const MAX_SCORE: i8 = (Self::BOARD_SIZE as i8 + 1) / 2 - 3;
const BOTTOM_MASK: u64 = const {
let mut mask = 0;
let mut i = 0;
while i < Self::WIDTH {
mask |= Self::bottom_mask_col(i);
i += 1;
}
mask
};
const BOARD_MASK: u64 = Self::BOTTOM_MASK * ((1 << Self::HEIGHT) - 1);
pub fn new() -> Position {
Self::default()
}
pub fn from_board_string(board_string: &str) -> Result<Position, PositionParsingError> {
let chars: Vec<char> = board_string
.to_lowercase()
.chars()
.filter(|c| matches!(c, '.' | 'o' | 'x'))
.collect();
if chars.len() != Self::BOARD_SIZE {
return Err(PositionParsingError::InvalidBoardStringLength {
actual: chars.len(),
expected: Self::BOARD_SIZE,
});
}
let mut position = 0;
let mut mask = 0;
let mut moves = 0;
for (i, ¤t_char) in chars.iter().enumerate() {
if current_char == '.' {
continue;
}
let row = Self::HEIGHT - (i / Self::WIDTH) - 1;
let col = i % Self::WIDTH;
let bit_index = row + col * (Self::HEIGHT + 1);
let position_bit = (current_char == 'x') as u64;
position |= position_bit << bit_index;
mask |= 1 << bit_index;
moves += 1;
}
Ok(Position { position, mask, moves })
}
pub fn from_moves(move_sequence: &str) -> Result<Position, PositionParsingError> {
let mut pos = Self::new();
for (i, c) in move_sequence.chars().enumerate() {
match c.to_digit(10)
.map(|digit| (digit - 1) as usize) {
Some(col @ 0..Self::WIDTH) => {
if !pos.is_playable(col) {
return Err(PositionParsingError::InvalidFullColumnMove { column: col + 1, index: i })
}
if pos.is_winning_move(col) {
return Err(PositionParsingError::InvalidWinningMove { column: col + 1, index: i })
}
pos.play(col);
},
Some(col) => return Err(PositionParsingError::InvalidColumn { column: col + 1, index: i }),
None => return Err(PositionParsingError::InvalidCharacter { character: c, index: i }),
}
}
Ok(pos)
}
#[inline(always)]
pub fn get_moves(&self) -> usize {
self.moves
}
#[inline(always)]
pub fn get_key(&self) -> u64 {
let key = self.position + self.mask;
let (mirrored_pos, mirrored_mask) = self.get_mirrored_bitmasks();
let mirrored_key = mirrored_pos + mirrored_mask;
key.min(mirrored_key)
}
fn get_mirrored_bitmasks(&self) -> (u64, u64) {
let mut mirrored_position = 0;
let mut mirrored_mask = 0;
for col in 0..Self::CENTRE {
let mirrored_col = Self::WIDTH - 1 - col;
let shift = (mirrored_col - col) * (Self::HEIGHT + 1);
mirrored_position |= ((self.position & Self::column_mask(col)) << shift)
| ((self.position & Self::column_mask(mirrored_col)) >> shift);
mirrored_mask |= ((self.mask & Self::column_mask(col)) << shift)
| ((self.mask & Self::column_mask(mirrored_col)) >> shift);
}
if Self::WIDTH & 1 == 1 {
mirrored_position |= self.position & Self::column_mask(Self::CENTRE);
mirrored_mask |= self.mask & Self::column_mask(Self::CENTRE);
}
(mirrored_position, mirrored_mask)
}
#[inline(always)]
pub fn is_playable(&self, col: usize) -> bool {
self.mask & Self::top_mask_col(col) == 0
}
pub fn is_winning_move(&self, col: usize) -> bool {
self.winning_positions() & self.possible() & Self::column_mask(col) > 0
}
pub fn can_win_next(&self) -> bool {
self.winning_positions() & self.possible() > 0
}
#[inline(always)]
pub fn play(&mut self, col: usize) {
self.position ^= self.mask;
self.mask |= self.mask + Self::bottom_mask_col(col);
self.moves += 1;
}
#[inline(always)]
pub fn possible(&self) -> u64 {
(self.mask + Self::BOTTOM_MASK) & Self::BOARD_MASK
}
pub fn possible_non_losing_moves(&self) -> u64 {
let mut possible = self.possible();
let opponent_wins = self.opponent_winning_positions();
let forced_moves = possible & opponent_wins;
if forced_moves > 0 {
if forced_moves & (forced_moves - 1) > 0 {
return 0
} else {
possible = forced_moves;
}
}
possible & !(opponent_wins >> 1)
}
fn winning_positions(&self) -> u64 {
Self::compute_winning_positions(self.position, self.mask)
}
fn opponent_winning_positions(&self) -> u64 {
Self::compute_winning_positions(self.position ^ self.mask, self.mask)
}
fn compute_winning_positions(position: u64, mask: u64) -> u64 {
let mut r = (position << 1) & (position << 2) & (position << 3);
let mut p = (position << (Self::HEIGHT + 1)) & (position << (2 * (Self::HEIGHT + 1)));
r |= p & (position << (3 * (Self::HEIGHT + 1)));
r |= p & (position >> (Self::HEIGHT + 1));
p >>= 3 * (Self::HEIGHT + 1);
r |= p & (position << (Self::HEIGHT + 1));
r |= p & (position >> (3 * (Self::HEIGHT + 1)));
let mut p = (position << Self::HEIGHT) & (position << (2 * Self::HEIGHT));
r |= p & (position << (3 * Self::HEIGHT));
r |= p & (position >> Self::HEIGHT);
p >>= 3 * Self::HEIGHT;
r |= p & (position << Self::HEIGHT);
r |= p & (position >> (3 * Self::HEIGHT));
let mut p = (position << (Self::HEIGHT + 2)) & (position << (2 * (Self::HEIGHT + 2)));
r |= p & (position << (3 * (Self::HEIGHT + 2)));
r |= p & (position >> (Self::HEIGHT + 2));
p >>= 3 * (Self::HEIGHT + 2);
r |= p & (position << (Self::HEIGHT + 2));
r |= p & (position >> (3 * (Self::HEIGHT + 2)));
r & (Self::BOARD_MASK ^ mask)
}
pub fn score_move(&self, move_bit: u64) -> u8 {
Self::compute_winning_positions(self.position | move_bit, self.mask).count_ones() as u8
}
pub fn is_won_position(&self) -> bool {
Self::compute_won_position(self.position) || Self::compute_won_position(self.position ^ self.mask)
}
fn compute_won_position(position: u64) -> bool {
let m = position & (position >> (Self::HEIGHT+1));
if m & (m >> (2*(Self::HEIGHT+1))) > 0 { return true; }
let m = position & (position >> Self::HEIGHT);
if m & (m >> (2*Self::HEIGHT)) > 0 { return true; }
let m = position & (position >> (Self::HEIGHT+2));
if m & (m >> (2*(Self::HEIGHT+2))) > 0 { return true; }
let m = position & (position >> 1);
if m & (m >> 2) > 0 { return true; }
false
}
#[inline(always)]
const fn top_mask_col(col: usize) -> u64 {
1 << (Self::HEIGHT - 1 + col * (Self::HEIGHT + 1))
}
#[inline(always)]
const fn bottom_mask_col(col: usize) -> u64 {
1 << (col * (Self::HEIGHT + 1))
}
#[inline(always)]
pub const fn column_mask(col: usize) -> u64 {
((1 << Self::HEIGHT) - 1) << (col * (Self::HEIGHT + 1))
}
}
impl Default for Position {
fn default() -> Position {
Position {
position: 0,
mask: 0,
moves: 0,
}
}
}