dualist / game.py
brandonlanexyz's picture
Initial upload of Dualist Othello AI (Iteration 652)
cf2aacd verified
import numpy as np
from src.bitboard import get_bit, bit_to_row_col, popcount
class OthelloGame:
def __init__(self):
# Initial Board Setup (A1 = MSB, H8 = LSB)
# Black pieces: D5 (35), E4 (28) -> 0x0000000810000000
# White pieces: D4 (36), E5 (27) -> 0x0000001008000000
self.player_bb = 0x0000000810000000 # Black starts
self.opponent_bb = 0x0000001008000000
self.turn = 1 # 1: Black, -1: White
def get_valid_moves(self, player, opponent):
"""Calculates valid moves for 'player' against 'opponent'."""
empty = ~(player | opponent) & 0xFFFFFFFFFFFFFFFF
# Consistent with MSB=A1:
# North: << 8. South: >> 8.
# West: << 1 (mask A). East: >> 1 (mask H).
mask_h = 0x0101010101010101
mask_a = 0x8080808080808080
# Directions
shifts = [
(lambda x: (x & ~mask_h) >> 1), # East
(lambda x: (x & ~mask_a) << 1), # West
(lambda x: (x << 8) & 0xFFFFFFFFFFFFFFFF), # North
(lambda x: (x >> 8) & 0xFFFFFFFFFFFFFFFF), # South
(lambda x: (x & ~mask_h) << 7), # NE (N+E -> <<8 + >>1 = <<7)
(lambda x: (x & ~mask_a) << 9), # NW (N+W -> <<8 + <<1 = <<9)
(lambda x: (x & ~mask_h) >> 9), # SE (S+E -> >>8 + >>1 = >>9)
(lambda x: (x & ~mask_a) >> 7) # SW (S+W -> >>8 + <<1 = >>7)
]
valid_moves = 0
for shift_func in shifts:
candidates = shift_func(player) & opponent
for _ in range(6): # Max 6 opponent pieces can be in between
candidates |= shift_func(candidates) & opponent
valid_moves |= shift_func(candidates) & empty
return valid_moves
def apply_move(self, player, opponent, move_bit):
"""Calculates new boards after move_bit."""
if move_bit == 0:
return player, opponent
flipped = 0
mask_h = 0x0101010101010101
mask_a = 0x8080808080808080
shifts = [
(lambda x: (x & ~mask_h) >> 1), # East
(lambda x: (x & ~mask_a) << 1), # West
(lambda x: (x << 8) & 0xFFFFFFFFFFFFFFFF), # North
(lambda x: (x >> 8) & 0xFFFFFFFFFFFFFFFF), # South
(lambda x: (x & ~mask_h) << 7), # NE
(lambda x: (x & ~mask_a) << 9), # NW
(lambda x: (x & ~mask_h) >> 9), # SE
(lambda x: (x & ~mask_a) >> 7) # SW
]
for shift_func in shifts:
mask = shift_func(move_bit)
potential_flips = 0
while mask & opponent:
potential_flips |= mask
mask = shift_func(mask)
if mask & player:
flipped |= potential_flips
new_player = player | move_bit | flipped
new_opponent = opponent & ~flipped
return new_player, new_opponent
def play_move(self, move_bit):
if move_bit != 0:
self.player_bb, self.opponent_bb = self.apply_move(self.player_bb, self.opponent_bb, move_bit)
# Turn always swaps (even on pass)
self.player_bb, self.opponent_bb = self.opponent_bb, self.player_bb
self.turn *= -1
def is_terminal(self):
p_moves = self.get_valid_moves(self.player_bb, self.opponent_bb)
o_moves = self.get_valid_moves(self.opponent_bb, self.player_bb)
return (p_moves == 0) and (o_moves == 0)