File size: 3,605 Bytes
cf2aacd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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)