Home > front end >  Overload-Like Method Definitions
Overload-Like Method Definitions

Time:12-12

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)
  • Related