A simple, bitboard-based chess variant built blindly from scratch.
In the spirit of learning through blunder, none of the binary chess techniques here were borrowed from or inspired by any other source, aside from the storing of piece information in separate bitboards. A possibly embarassing and novel approach to moving a knight can be found detailed below.
In this limited version of chess, the board state and all move calculations are carried out through the use of bitboards and bitwise operations. The game itself consists of all the legal moves of chess except for en-passant and castling. There is no check or check-mate, and the first player to capture all of the opponents pieces of one type wins.
python chessVar.py
to play
The left bitboard below represents all white pawns. (1 = pawn)
if the bottom left bit of this 8x8 bitboard is the 0th bit,
and the top right bit of the bitboard represents the 63rd bit,
then the bitboard is also equal to the binary number:
0b1111111100000000
, and the decimal number: 65280
.
The bitboard on the right is the result of shifting the bits
of the first bitboard to the left by 8 65280 << 8
. This same
method will be used while determining all legal rules of a few
types of chess pieces, including the rook, king, queen, and pawn.
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
This 8x8 bitboard calculator was very useful: https://gekomad.github.io/Cinnamon/BitboardCalculator/
-
LEFT SHIFT
x << y
Shifts bits to the left by y places, effectively multiplying the number by 2 for each shift.
All new bits on the right are 0. Equivalent tox * 2**y
. This is used to move pieces:- left
x << 1
- up
x << 8
- diagonally up-right
x << 7
- diagonally up-left
x << 9
- left
-
RIGHT SHIFT
x >> y
Shifts bits to the right by y places, effectively dividing the number by 2 for each shift.
All new bits on the left are 0. Equivalent tox // 2**y
. This is used to move pieces:- right
x >> 1
- down
x >> 8
- diagonally down-left
x >> 7
- diagonally down-right
x >> 9
- right
-
BITWISE 'AND'
x & y
Performs an 'and' of all of x and y's bits. ex.0011b & 1111b = 0011b
.
Used to find moves that collide with other pieces such as pawn attacks. -
BITWISE 'OR'
x | y
Performs an 'or' of all of x and y's bits. ex.0011b | 1000b = 1011b
.
Used to combine different bitboards or adding moves with special conditions such as pawn double-jump. -
BITWISE 'NOT'
~ x
Performs a 'not' of x, or returns the compliment of x by flipping all of x's bits.
Equivalent to:-x -1 (inverse of two's compliment)
-
bitwise 'xor'
x ^ y
Performs a 'xor' of all of x, y's bits. ex.0111b ^ 1110b = 1001b
Used to remove or add bits to bitboards in the case of movement or captures.
After determining that the move_from location coincides with a piece belonging to the player whose turn it is to move, and finding what type of chess piece that is, the Board class will generate a bitboard representing all legal moves for that piece. These are the general steps for that process for a rook:
Given these two initial bitboards representing possible vertical or horizontal moves, we can determine whether the intended 'move_to' location would be legal on a blank board, then determine in which direction the move would be:
0x8080808080808080 0xff
8 1 0 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0
7 1 0 0 0 0 0 0 0 7 0 0 0 0 0 0 0 0
6 1 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0
5 1 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0
4 1 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0
3 1 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0
2 1 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0
1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1
A B C D E F G H A B C D E F G H
An example move_from of "C4" converted to a tuple of indexes representing the file and rank of the board would be (2, 3). This tuple can then be used to generate the possible horizontal and vertical moves available to a rook given a blank board:
0x0x8080808080808080 >> 2 0xff << 8*3
8 0 0 1 0 0 0 0 0 8 0 0 0 0 0 0 0 0
7 0 0 1 0 0 0 0 0 7 0 0 0 0 0 0 0 0
6 0 0 1 0 0 0 0 0 6 0 0 0 0 0 0 0 0
5 0 0 1 0 0 0 0 0 5 0 0 0 0 0 0 0 0
4 0 0 1 0 0 0 0 0 4 1 1 1 1 1 1 1 1
3 0 0 1 0 0 0 0 0 3 0 0 0 0 0 0 0 0
2 0 0 1 0 0 0 0 0 2 0 0 0 0 0 0 0 0
1 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0
A B C D E F G H A B C D E F G H
An 'and' operation with the bitboard representing the move_to and either of these bitboards would mean the rook is capable of moving in that vector. To find direction, we see whether the bitboard that resulted from the 'and' operation (consisting of a single bit) is less than or greater than the bitboard representing the move_from.
If less than, and the match was with the bitboard representing horizontal moves, then we know the move was to the right. If greater than, and the match was with the bitboard representing vertical moves, then the move was up.
After determining direction, we need to check whether that piece, before reaching its destination, would collide with any piece along the way. To do this, we just bit-shift the rook in the direction of the move_to (<<1 for left, >>1 for right, <<8 for up, >>8 for down), then check whether the new position matches ('and') the move_to or any piece on the board (collision) until it matches it the move-to. any match prior to reaching the destination would be illegal.
Once the destination is reached, then we can check if the move_to coincides with any of the player's pieces (illegal), or any of the opposing player's pieces (signifies a capture).