Skip to content

Commit 1d56261

Browse files
committed
post changes
1 parent 495b949 commit 1d56261

File tree

2 files changed

+68
-56
lines changed

2 files changed

+68
-56
lines changed

components/visuals/bitboards/components.tsx

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { BitboardStep } from "./bitboard-visualizer"
44
const whitePawnAttacks: { codeLines: string[], steps: BitboardStep[] } = {
55
codeLines: [
66
"// Initial white pawn attacks",
7-
"FILE_A = 0x0101010101010101n;",
8-
"FILE_H = 0x8080808080808080n;",
9-
"white_pawns = 0x000000000000FF00n;",
10-
"attacks_left = (white_pawns & ~FILE_A) << 7n;",
11-
"attacks_right = (white_pawns & ~FILE_H) << 9n;",
7+
"FILE_A = 0x0101010101010101;",
8+
"FILE_H = 0x8080808080808080;",
9+
"white_pawns = 0x000000000000FF00;",
10+
"attacks_left = (white_pawns & ~FILE_A) << 7;",
11+
"attacks_right = (white_pawns & ~FILE_H) << 9;",
1212
"pawn_attacks = attacks_left | attacks_right;",
1313
],
1414
steps: (() => {
@@ -32,42 +32,52 @@ const whitePawnAttacks: { codeLines: string[], steps: BitboardStep[] } = {
3232

3333
const knightAttack: { codeLines: string[], steps: BitboardStep[] } = {
3434
codeLines: [
35-
"// Knight attacks from F5",
36-
"KNIGHT_POS = 0x0000000004000000n;",
37-
"attacks = (KNIGHT_POS << 17n) |",
38-
" (KNIGHT_POS << 15n) |",
39-
" (KNIGHT_POS << 10n & NOT_AB_FILE) |",
40-
" (KNIGHT_POS << 6n & NOT_GH_FILE) |",
41-
" (KNIGHT_POS >> 17n) |",
42-
" (KNIGHT_POS >> 15n) |",
43-
" (KNIGHT_POS >> 10n & NOT_AB_FILE) |",
44-
" (KNIGHT_POS >> 6n & NOT_GH_FILE);",
35+
"// Knight attacks from G5",
36+
"KNIGHT_POS = 0x0000000002000000;",
37+
"FILE_A = 0x0101010101010101;",
38+
"FILE_H = 0x8080808080808080;",
39+
"FILE_AB = FILE_A | (FILE_A << 1);",
40+
"FILE_GH = FILE_H | (FILE_H >> 1);",
41+
"attacks = (KNIGHT_POS << 17 & ~FILE_A) |",
42+
" (KNIGHT_POS << 15 & ~FILE_H) |",
43+
" (KNIGHT_POS << 10 & ~FILE_AB) |",
44+
" (KNIGHT_POS << 6 & ~FILE_GH) |",
45+
" (KNIGHT_POS >> 17 & ~FILE_H) |",
46+
" (KNIGHT_POS >> 15 & ~FILE_A) |",
47+
" (KNIGHT_POS >> 10 & ~FILE_GH) |",
48+
" (KNIGHT_POS >> 6 & ~FILE_AB);",
4549
],
4650
steps: (() => {
47-
const KNIGHT_POS = BigInt("0x0000000004000000")
48-
const NOT_AB_FILE = ~BigInt("0x0303030303030303") // Correct mask for files A and B
49-
const NOT_GH_FILE = ~BigInt("0xc0c0c0c0c0c0c0c0") // Correct mask for files G and H
51+
const KNIGHT_POS = BigInt("0x0000000002000000")
52+
const FILE_A = BigInt("0x0101010101010101")
53+
const FILE_H = BigInt("0x8080808080808080")
54+
const FILE_AB = FILE_A | (FILE_A << BigInt(1))
55+
const FILE_GH = FILE_H | (FILE_H >> BigInt(1))
5056

51-
const attacks = (KNIGHT_POS << BigInt(17)) |
52-
(KNIGHT_POS << BigInt(15)) |
53-
(KNIGHT_POS << BigInt(10) & NOT_AB_FILE) |
54-
(KNIGHT_POS << BigInt(6) & NOT_GH_FILE) |
55-
(KNIGHT_POS >> BigInt(17)) |
56-
(KNIGHT_POS >> BigInt(15)) |
57-
(KNIGHT_POS >> BigInt(10) & NOT_AB_FILE) |
58-
(KNIGHT_POS >> BigInt(6) & NOT_GH_FILE)
57+
const attacks = (KNIGHT_POS << BigInt(17) & ~FILE_A) |
58+
(KNIGHT_POS << BigInt(15) & ~FILE_H) |
59+
(KNIGHT_POS << BigInt(10) & ~FILE_AB) |
60+
(KNIGHT_POS << BigInt(6) & ~FILE_GH) |
61+
(KNIGHT_POS >> BigInt(17) & ~FILE_H) |
62+
(KNIGHT_POS >> BigInt(15) & ~FILE_A) |
63+
(KNIGHT_POS >> BigInt(10) & ~FILE_GH) |
64+
(KNIGHT_POS >> BigInt(6) & ~FILE_AB)
5965

6066
return [
6167
{ highlightLines: [2], board: KNIGHT_POS },
62-
{ highlightLines: [3], board: KNIGHT_POS << BigInt(17) },
63-
{ highlightLines: [4], board: KNIGHT_POS << BigInt(15) },
64-
{ highlightLines: [5], board: KNIGHT_POS << BigInt(10) & NOT_AB_FILE },
65-
{ highlightLines: [6], board: KNIGHT_POS << BigInt(6) & NOT_GH_FILE },
66-
{ highlightLines: [7], board: KNIGHT_POS >> BigInt(17) },
67-
{ highlightLines: [8], board: KNIGHT_POS >> BigInt(15) },
68-
{ highlightLines: [9], board: KNIGHT_POS >> BigInt(10) & NOT_AB_FILE },
69-
{ highlightLines: [10], board: KNIGHT_POS >> BigInt(6) & NOT_GH_FILE },
70-
{ highlightLines: [3, 4, 5, 6, 7, 8, 9, 10], board: attacks },
68+
{ highlightLines: [3], board: FILE_A },
69+
{ highlightLines: [4], board: FILE_H },
70+
{ highlightLines: [5], board: FILE_AB },
71+
{ highlightLines: [6], board: FILE_GH },
72+
{ highlightLines: [7], board: KNIGHT_POS << BigInt(17) & ~FILE_A },
73+
{ highlightLines: [8], board: KNIGHT_POS << BigInt(15) & ~FILE_H },
74+
{ highlightLines: [9], board: KNIGHT_POS << BigInt(10) & ~FILE_AB },
75+
{ highlightLines: [10], board: KNIGHT_POS << BigInt(6) & ~FILE_GH },
76+
{ highlightLines: [11], board: KNIGHT_POS >> BigInt(17) & ~FILE_H },
77+
{ highlightLines: [12], board: KNIGHT_POS >> BigInt(15) & ~FILE_A },
78+
{ highlightLines: [13], board: KNIGHT_POS >> BigInt(10) & ~FILE_GH },
79+
{ highlightLines: [14], board: KNIGHT_POS >> BigInt(6) & ~FILE_AB },
80+
{ highlightLines: [7, 8, 9, 10, 11, 12, 13, 14], board: attacks },
7181
]
7282
})(),
7383
}

posts/visualizing-chess-bitboards.md

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ description: "Brief introduction to chess bitboards and move generation with ani
77

88
When simulating board games on a computer, one of the challenges is keeping track of the game pieces. Bitboards are an efficient way to store game state in (usually) 64-bit integers. There are 64 positions on a chess board so we can use each bit as an on/off switch.
99

10-
Let's say I want to describe a board where `f5` is occupied. For that, we can use the number `67108864`. In decimal notation, it doesn't seem much like a chessboard. In hex, we can see that there's some structure: `0x0000000004000000`.
10+
Let's say I want to describe a board where `f5` is occupied. For that, we can use the number `67108864`. In decimal notation, it doesn't look that much like a chessboard. In hex, we can see that there's a little more structure: `0x0000000004000000`.
1111

1212
For me, it starts to make more sense when representated as a binary number with 64 digits.
1313

@@ -24,12 +24,12 @@ For me, it starts to make more sense when representated as a binary number with
2424

2525
We can't pack an _entire_ game's state into a number (e.g. piece color, piece type, castling rights) so we use groups of numbers.
2626

27-
We can use _bitwise_ operations on these numbers to manipulate individual bits using operators like `AND`, `OR`, `XOR`, `NOT`, and bit shifts. These operations are fast (they're directly executed by the CPU in a single clock cycle with no need for complex computation or memory access).
27+
We can use _bitwise_ operations on these numbers to manipulate individual bits using operators like `AND`, `OR`, `XOR`, `NOT`, and bit shifts. These operations are are among the fastest operations a CPU can perform. Bitboards are also cache-friendly since they pack data into fewer memory locations.
2828

2929
In order to move a piece at `f5` forwards we can shift the bitboard left by 8 bits.
3030

3131
```js
32-
pieceOnF5 = 0x0000000004000000n;
32+
pieceOnF5 = 0x0000000004000000;
3333
// Move one rank forward (from f5 to f6)
3434
pieceOnF5 << 8n; // 0x0000000400000000n
3535
```
@@ -40,19 +40,19 @@ Why 8? Well, if you picture all of a chessboard's squares lined up in one long r
4040

4141
A mask is a bitboard used to isolate, modify, or test specific squares using bitwise operations.
4242

43-
An example of a mask might be the `A` file: `0x0101010101010101`. To check if there are any pieces there we can use bitwise `AND`.
43+
An example of a mask might be the `A` file: `0x0101010101010101`. To check if there are any pieces on the `A` file, we can use bitwise `AND`.
4444

4545
```js
46-
if (pieces & 0x0101010101010101n) {
47-
// There is at least one piece on the A file
46+
if (pieces & 0x0101010101010101) {
47+
// At least one piece on the A file
4848
}
4949
```
5050

51-
Even though I know a little bit about bitboards, I still find them fairly confusing and unintuitive. I'm also prone to mistakes while working with them.
51+
Even though I know a little bit about bitboards, I still find them confusing and unintuitive. I'm also prone to mistakes while working with them.
5252

5353
I've found that visual examples (and interactive debugging tools) can be very helpful. Let's take a look at how we can generate the initial white pawn attacks.
5454

55-
Two numbers will probably stick out; `7` and `9`. If you picture that long row of chessboard squares I mentioned before, think about how many squares you'd have to move to be diagonally forwards or backwards from your starting position.
55+
Two numbers will probably stick out in the below example; `7` and `9`. If you picture that long row of chessboard squares I mentioned before, think about how many squares you'd have to move to be diagonally forwards or backwards from your starting position.
5656

5757
<div className="bitboards" id="whitePawnAttacks"></div>
5858

@@ -61,19 +61,19 @@ Without bitboards, a program has to perform many more (magnitudes more!) instruc
6161
```js
6262
attacks = [];
6363
for (i = 0; i < 64; i++) {
64-
piece = board.getPiece(i);
65-
if (piece === "P") { // White pawn
66-
67-
// Check left attack (forward-left)
68-
if (!board.isOnFileA(i)) {
69-
attacks.push(i + 7);
70-
}
71-
72-
// Check right attack (forward-right)
73-
if (!board.isOnFileH(i)) {
74-
attacks.push(i + 9);
75-
}
64+
piece = board.getPiece(i);
65+
if (piece === "P") { // White pawn
66+
67+
// Check left attack (forward-left)
68+
if (!board.isOnFileA(i)) {
69+
attacks.push(i + 7);
70+
}
71+
72+
// Check right attack (forward-right)
73+
if (!board.isOnFileH(i)) {
74+
attacks.push(i + 9);
7675
}
76+
}
7777
}
7878
```
7979

@@ -83,10 +83,12 @@ I've got one more example where we generate a knight's attacks. We start with th
8383

8484
Inside a chess engine, we would then use more bitwise operations to see if those positions were occupied so we could evaluated the strength of each potential move.
8585

86-
The "not file" masks here are used to prevent invalid wraparounds.
86+
The "not file" masks here are used to prevent invalid wraparounds. From `g5`, these masks stop the knight from moving to `l6` and `l4` (which aren't real squares).
8787

8888
<div className="bitboards" id="knightAttack"></div>
8989

90+
In practice, chess engines often pre-compute and store attack masks in lookup tables for even better performance, rather than calculating them on the fly like this – as well as other sophisticated techniques like [zobrist hashing](https://www.chessprogramming.org/Zobrist_Hashing) and [magic bitboard](https://www.chessprogramming.org/Magic_Bitboards).
91+
9092
I hope this helps show how bitboards can be the building blocks of any kind of chess computation.
9193

9294
For further reading, I've found the [Chess Programming Wiki](https://www.chessprogramming.org/Bitboards) and the source code for `python-chess` (e.g. [calculating attack masks](https://github.com/niklasf/python-chess/blob/ffa04827e325de5b4d39a67eee3528474b814285/chess/__init__.py#L875)) to both be very useful.

0 commit comments

Comments
 (0)