CONTEXT
I'm making this chess engine in python and one aspect of the program is that the board is represented by 64-bit numbers called bitboards, so I created a class to do just that, here is a snippet:
from __future__ import annotations
FULL_MASK = 0xFFFFFFFFFFFFFFFF
class BitBoard:
def __init__(self, board: int) -> None:
self.__board: int = board
@property
def board(self) -> int:
return self.__board & FULL_MASK
@board.setter
def board(self, value: int) -> None:
self.__board = value & FULL_MASK
def set_bit(self, square: int) -> None:
self.__board |= (1 << square) & FULL_MASK
def get_bit(self, square: int) -> None:
self.__board &= (1 << square) & FULL_MASK
def pop_bit(self, square: int) -> None:
self.__board &= ~(1 << square) & FULL_MASK
def __and__(self, value: int) -> BitBoard:
return BitBoard((self.__board & value) & FULL_MASK)
def __or__(self, value: int) -> BitBoard:
return BitBoard((self.__board | value) & FULL_MASK)
def __xor__(self, value: int) -> BitBoard:
return BitBoard((self.__board ^ value) & FULL_MASK)
def __inv__(self) -> BitBoard:
return BitBoard(~self.__board & FULL_MASK)
def __lshift__(self, value: int) -> BitBoard:
return BitBoard((self.__board << value) & FULL_MASK)
def __rshift__(self, value: int) -> BitBoard:
return BitBoard((self.__board >> value) & FULL_MASK)
As shown I've overidden all bitwise operations. While the NOT, SHIFT-LEFT, and SHIFT-RIGHT are rarely and/or never used with other bitboards, the AND, OR and XOR operators sometimes are.
QUESTION
Using the AND operator as in example, I want it so that if a bitboard is ANDed with a literal, it will bitwise AND its board with that literal. similarly, if it is ANDed with another bitboard, it will bitwise AND its board with the other's board. is there a way to do this? preferably without external modules.
ATTEMPTS
I've tried
def __and__(self, value: int | BitBoard) -> BitBoard:
result: BitBoard
if type(value) == int:
result = BitBoard((self.__board & value) & FULL_MASK)
if type(value) == Bitboard:
result = BitBoard((self.__board & value.board) & FULL_MASK)
return result
and other similar things but everytime the type checker yells at me for doing them. I know there is a way to do it with metaclasses but that would be counter productive since the purpose of using bitboards is to reduce computation. I am aware of the multipledispatch module but I'm aiming for this project to be pure base python.
CodePudding user response:
I think you almost have it.
(And I'm sad that python typing isn't quite there, yet. I've been routinely using it in signatures for years, now, I hadn't realized there are still some pretty large rough edges.)
I am running mypy 0.990
, with Python 3.10.8
.
Randomly exercising the primitives looks like it works out just fine.
def manipulate_bitboard_with_literals():
b = BitBoard(0x3)
b.set_bit(4) # set a high bit
b = b & 0x3 # clear that bit
b = b ^ 0x3 # back to zero
b = b | 0x2
assert b.board == 2
def combine_bitboards():
a = BitBoard(0x3)
b = BitBoard(0x8)
c = a | b
assert c.board == 11
c &= ~0x2 # now it's 9
c &= a
assert c.board == 1
Here's what I changed to get that to work.
def __or__(self, value: int | BitBoard) -> BitBoard:
if isinstance(value, BitBoard):
return BitBoard((self.__board | value.board) & FULL_MASK)
return BitBoard((self.__board | value) & FULL_MASK)
I just coded that up without looking at anything, it seemed natural.
I saw a | b
produce the diagnostic
TypeError: unsupported operand type(s) for |: 'int' and 'BitBoard'
so I wrote what felt like a straightforward response to that.
Sorry if there's a DRY aspect to that which feels too verbose. I would love to see an improvement on it.
And then I tackled AND, referring to your code this time. It was nearly perfect, I think there was just a minor capitalization typo in "Bitboard" vs "BitBoard".
def __and__(self, value: int | BitBoard) -> BitBoard:
result: BitBoard
if type(value) == int:
result = BitBoard((self.__board & value) & FULL_MASK)
if type(value) == BitBoard:
result = BitBoard((self.__board & value.board) & FULL_MASK)
return result
It's saying the same thing as the OR function, just a slightly different phrasing.
Imagine that another type, like None
,
found its way into that function.
From the perspective of proving type safety,
it seems like such a case is not yet handled
by the above implementation,
given that python's not doing runtime checking.
Overall, your class is in good shape. LGTM, ship it!
On reflection,
I think I find this form preferable.
It is concise, and there's just a single |
OR operator.
The "give me an int!" expression could be extracted into a helper if desired.
def __or__(self, value: int | BitBoard) -> BitBoard:
value = value.board if isinstance(value, BitBoard) else value
return BitBoard((self.__board | value) & FULL_MASK)