Bitboard-Based Game Representation in Stockfish
Stockfish represents the chessboard using bitboards: 64-bit unsigned integers where each bit corresponds to a square on the board.
typedef uint64_t Bitboard;
- Bit 0 (LSB) → A1
- Bit 63 (MSB) → H8
This representation allows the engine to manipulate entire sets of squares using fast bitwise operations, which is critical for performance.
Piece Encoding
enum Piece {
NO_PIECE,
W_PAWN = 1, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
B_PAWN = 9, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
PIECE_NB = 16
};
Numeric Structure
| Piece | Value | Binary |
|---|---|---|
| W_PAWN | 1 | 0001 |
| W_KING | 6 | 0110 |
| B_PAWN | 9 | 1001 |
| B_KING | 14 | 1110 |
Key observations:
- White pieces occupy values 1–6
- Black pieces occupy values 9–14
- The 4th bit distinguishes color
This enables a compact and efficient encoding:
Piece = (Color << 3) | PieceType
Extracting Type and Color
inline PieceType type_of(Piece pc) { return PieceType(pc & 7); }
inline Color color_of(Piece pc) { return Color(pc >> 3); }
pc & 7strips color → piece typepc >> 3extracts color
Flipping Color
~pc == pc ^ 8
Toggles the color bit while keeping the piece type unchanged.
PIECE_NB
PIECE_NB = 16
Used for array sizing and indexing. It is not the number of actual chess pieces, but the size of the encoding space.
PieceType: Color-Independent Identity
enum PieceType {
NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
ALL_PIECES = 0,
PIECE_TYPE_NB = 8
};
Purpose
Represents kind of piece, independent of color
Used for:
- Move generation
- Attack generation
- Evaluation
- Bitboard indexing
This enum deliberately excludes color, which is handled separately.
The encoding aligns perfectly with Piece:
Piece = (Color << 3) | PieceType
Special Values
ALL_PIECES = 0Used as a generic index when aggregating attacks.PIECE_TYPE_NB = 8Total number of piece types including sentinel values.
Square Representation
enum Square {
SQ_A1, SQ_B1, ..., SQ_H8,
SQ_NONE,
SQUARE_NB = 64,
NORTH = 8,
EAST = 1,
SOUTH = -8,
WEST = -1,
NORTH_EAST = NORTH + EAST,
SOUTH_EAST = SOUTH + EAST,
SOUTH_WEST = SOUTH + WEST,
NORTH_WEST = NORTH + WEST
};
Numeric Layout
Squares are encoded in rank-major order:
A1 = 0 B1 = 1 ... H1 = 7
A2 = 8 B2 = 9 ... H2 = 15
...
A8 = 56 B8 = 57 ... H8 = 63
Example:
int(SQ_E4) == 4 + 3 * 8 == 28
Why This Layout?
- Files increment by
+1 - Ranks increment by
+8 - Board geometry becomes simple integer arithmetic
Directions as Integer Offsets
NORTH = 8
EAST = 1
SOUTH = -8
WEST = -1
This allows simple move computation:
SQ_E2 + NORTH == SQ_E3
SQ_E2 + NORTH_EAST == SQ_F3
Diagonal directions are composed, not duplicated:
NORTH_EAST = NORTH + EAST
File and Rank Enums
enum File : int { FILE_A, FILE_B, ..., FILE_H, FILE_NB };
enum Rank : int { RANK_1, RANK_2, ..., RANK_8, RANK_NB };
These are semantic types, not just integers.
They improve:
- Readability
- Type safety
- Template specialization
Extraction helpers:
inline File file_of(Square s) { return File(s & 7); }
inline Rank rank_of(Square s) { return Rank(s >> 3); }
Construction:
inline Square make_square(File f, Rank r) {
return Square((r << 3) + f);
}
Everything reduces to:
square = rank * 8 + file
Color-Relative Squares
inline Square relative_square(Color c, Square s) {
return Square(s ^ (c * 56));
}
- For WHITE (c = 0): square unchanged
- For BLACK (c = 1): square vertically flipped
Why 56?
56 = 7 * 8 = A8
Examples:
A1 ^ 56 = A8
C1 ^ 56 = C8
This allows writing color-independent evaluation logic.
Castling Representation
CastlingSide
enum CastlingSide {
KING_SIDE,
QUEEN_SIDE,
CASTLING_SIDE_NB = 2
};
- Simple selector enum
- Not a bitmask
- Used in templates and branching logic
CastlingRight: Bitmask Encoding
enum CastlingRight {
NO_CASTLING,
WHITE_OO,
WHITE_OOO = WHITE_OO << 1,
BLACK_OO = WHITE_OO << 2,
BLACK_OOO = WHITE_OO << 3,
ANY_CASTLING = WHITE_OO | WHITE_OOO | BLACK_OO | BLACK_OOO,
CASTLING_RIGHT_NB = 16
};
Numeric Values
WHITE_OO = 1 // 0001
WHITE_OOO = 2 // 0010
BLACK_OO = 4 // 0100
BLACK_OOO = 8 // 1000
Why Bitmasks?
Castling rights are independent flags:
A position may have:
- Only king-side rights
- Only queen-side rights
- Both
- None
Bitmasks allow all combinations efficiently:
WHITE_OO | WHITE_OOO // White can castle both sides
BLACK_OO | BLACK_OOO // Black can castle both sides
WHITE_OO | BLACK_OO // Both can castle king-side
ANY_CASTLING // All rights available
Score: Middlegame and Endgame Packed Together
Stockfish does not evaluate a position with a single number. Instead, it evaluates two positions in parallel:
- Middlegame (MG) score
- Endgame (EG) score
These two values are packed into a single 32-bit integer called Score.
/// Score enum stores a middlegame and an endgame value in a single integer
/// The upper 16 bits store the middlegame value
/// The lower 16 bits store the endgame value
enum Score : int { SCORE_ZERO };
Bit Layout
32-bit Score integer
| MG (signed 16 bits) | EG (signed 16 bits) |
|----------------------|----------------------|
| bits 31 ........ 16 | bits 15 ........ 0 |
This allows Stockfish to accumulate middlegame and endgame evaluations simultaneously, without branching on game phase.
Creating a Score
inline Score make_score(int mg, int eg) {
return Score((int)((unsigned int)eg << 16) + mg);
}
Conceptually:
Score = (EG << 16) | MG
The implementation uses unsigned arithmetic to avoid undefined behavior when shifting signed integers.
Extracting Values
Endgame value:
inline Value eg_value(Score s) {
union { uint16_t u; int16_t s; } eg = {
uint16_t(unsigned(s + 0x8000) >> 16)
};
return Value(eg.s);
}
Middlegame value:
inline Value mg_value(Score s) {
union { uint16_t u; int16_t s; } mg = {
uint16_t(unsigned(s))
};
return Value(mg.s);
}
These functions carefully preserve sign and avoid implementation-defined behavior in C++.
Why Stockfish Uses Score
This design enables:
- Continuous transition between middlegame and endgame
- No runtime branching on game phase
- Extremely cache-friendly evaluation
- Simple accumulation of evaluation terms
Throughout evaluation, Stockfish accumulates Score values:
Score score = SCORE_ZERO;
score += MobilityBonus;
score += PawnStructure;
score += KingSafety;
Only at the very end is the final value computed by interpolating between MG and EG using the game phase.
Mental Model
Think of Score as a 2-component vector:
Score = ⟨middlegame, endgame⟩
Evaluation is vector addition, and the final numeric score is a weighted projection based on how far the game has progressed.
Move Representation
/// A move needs 16 bits to be stored
///
/// bit 0- 5: destination square (from 0 to 63)
/// bit 6-11: origin square (from 0 to 63)
/// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2)
/// bit 14-15: special move flag: promotion (1), en passant (2), castling (3)
/// NOTE: EN-PASSANT bit is set only when a pawn can be captured
///
/// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in
/// any normal move destination square is always different from origin square
/// while MOVE_NONE and MOVE_NULL have the same origin and destination square.
enum Move : int {
MOVE_NONE,
MOVE_NULL = 65
};
Stockfish represents every chess move in just 16 bits for speed and cache efficiency.
Bit Layout (16 bits)
15 14 | 13 12 | 11 ...... 6 | 5 ...... 0
----------------------------------------
flags | promo | from | to
| Bits | Meaning |
| ------- | ----------------------------- |
| 0–5 | Destination square (0–63) |
| 6–11 | Origin square (0–63) |
| 12–13 | Promotion piece − 2 |
| 14–15 | Special move flag |
Squares (6 bits each)
Squares are encoded as integers:
A1 = 0, B1 = 1, ..., H8 = 63
So:
from_sq(m)→ bits 6–11to_sq(m)→ bits 0–5
Special Move Flags (bits 14–15)
| Value | Meaning |
| ----: | ----------- |
| `00` | Normal move |
| `01` | Promotion |
| `10` | En passant |
| `11` | Castling |
Promotion Encoding (bits 12–13)
Promotion piece type is encoded as:
promotion_piece = (promotion_type + 2)
| Promotion | Encoded |
| --------- | ------- |
| Knight | 0 |
| Bishop | 1 |
| Rook | 2 |
| Queen | 3 |
Because as per Stockfish representation, NO_PIECE_TYPE and PAWN are not useful for promotion, so we can save bits.
enum PieceType {
NO_PIECE_TYPE = 0,
PAWN = 1,
KNIGHT = 2,
BISHOP = 3,
ROOK = 4,
QUEEN = 5,
KING = 6
};
MOVE_NONE and MOVE_NULL
enum Move : int {
MOVE_NONE,
MOVE_NULL = 65
};
Why this works
- A legal move always has from != to
- These special moves violate that rule
MOVE_NONE
from = 0
to = 0
Used to mean:
- “no move”
- invalid move
- search sentinel
MOVE_NULL
from = 1
to = 1 // 1 | (1 << 6) = 65
Used for:
- null move pruning
- represents “pass move” (side switches, no piece moved)
Accessor Macros
Internally Stockfish uses helpers like:
to_sq(m) = m & 0x3F
from_sq(m) = (m >> 6) & 0x3F
type_of(m) = m & 0xC000
promo_type(m) = ((m >> 12) & 3) + KNIGHT
StepAttacksBB - Pre-computed Attack Tables
StepAttacksBB is a lookup table that stores pre-computed attack patterns for pieces that move in fixed steps (not sliding pieces).
Bitboard StepAttacksBB[PIECE_NB][SQUARE_NB];
// [16 piece types][64 squares] = 1024 bitboards
“Step attacks” = pieces that attack a fixed pattern of squares:
- Pawns (different for white/black)
- Knights
- Kings
- NOT bishops, rooks, queens (these are “sliding” pieces)
What’s Stored
// For each piece type and square, store which squares it attacks
StepAttacksBB[piece][from_square] → Bitboard of attacked squares
Examples:
StepAttacksBB[W_PAWN][e4] → Bitboard with d5, f5 set (white pawn attacks)
StepAttacksBB[B_PAWN][e5] → Bitboard with d4, f4 set (black pawn attacks)
StepAttacksBB[W_KNIGHT][e4] → Bitboard with d2, f2, c3, g3, c5, g5, d6, f6
StepAttacksBB[W_KING][e1] → Bitboard with d1, f1, d2, e2, f2
LineBB
Bitboard LineBB[SQUARE_NB][SQUARE_NB];
LineBB is a precomputed bitboard table that represents:
The entire straight line passing through two squares if they are aligned (same rank/file/diagonal)
Otherwise:
LineBB[a][b] = 0
Intuition
If you pick two squares:
- e1 and e8 → same file
- c1 and h6 → same diagonal
- a1 and h1 → same rank
Then the squares between them lie on a straight line.
So LineBB[s1][s2] is a bitboard answers:
Which squares belong to the line containing both s1 and s2?
Example:
Squares: e1 and e8
They are aligned vertically.
So LineBB[e1][e8] returns bitboard containing e1 e2 e3 e4 e5 e6 e7 e8.
Squares: c1 and h6
Diagonal alignment: So LineBB[c1][h6] returns c1 d2 e3 f4 g5 h6.
Squares: a1 and c2
Not aligned (not same file/rank/diagonal). So LineBB[a1][c2] == 0.
Used in aligned()
aligned(from, to, kingSquare)
This is implemented as:
inline bool aligned(Square a, Square b, Square c) {
return LineBB[a][b] & c;
}
Meaning:
Is square c on the line through a and b?
BetweenBB
Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
This is a precomputed lookup table:
BetweenBB[a][b] gives the bitboard of all squares strictly between square a and square b, if they are aligned.
What does “between” mean?
If two squares lie on the same:
- rank (horizontal)
- file (vertical)
- diagonal
Why is this useful?
Because chess is full of “line relationships”:
- pinned pieces
- blocking checks
- sliding attacks (rook/bishop/queen)
- discovering check
- legality testing
Example BetweenBB[a1][a8] returns a bitboard of these squares {a2, a3, a4, a5, a6, a7}
If squares are not aligned → empty bitboard
Key usage: Blocking a check
Suppose black queen gives check:
Black queen: e7
White king: e1
BetweenBB[E7][E1] = {E6,E5,E4,E3,E2}
Now, if white is in check, legal responses include:
- capture attacker
- move king
- block the line
Stockfish uses this here:
if (!((between_bb(checkerSq, kingSq) | checkers()) & to))
return false;
Means you must do either
- capture the checker, or
- land on a square between checker and king
BetweenBB is precomputed for speed
SquareBB
Bitboard SquareBB[SQUARE_NB];
This is an array of 64 bitboards:
- SquareBB[SQ_A1] → bitboard with only A1 set
- SquareBB[SQ_E4] → bitboard with only E4 set
Example
SquareBB[E4] = 1ULL << E4
SquareBB[E4] = 00000000
00000000
00000000
00010000 ← only e4 bit is 1
00000000
00000000
00000000
00000000
Used for:
- Adding/removing pieces quickly
- XOR toggling squares
- Building masks
byTypeBB[PAWN] |= SquareBB[s];
So instead of computing (1ULL « s) every time, Stockfish just does:
SquareBB[s]
FileBB
Meaning: Bitboard for an entire file
Bitboard FileBB[FILE_NB];
There are 8 files:
- File A
- File B
- …
- File H
So:
- FileBB[FILE_A] = all squares on file A set
- FileBB[FILE_E] = all squares on file E set
Example: File A
FileBB[A] =
8 1 . . . . . . .
7 1 . . . . . . .
6 1 . . . . . . .
5 1 . . . . . . .
4 1 . . . . . . .
3 1 . . . . . . .
2 1 . . . . . . .
1 1 . . . . . . .
a b c d e f g h
Hex:
FileBB[A] = 0x0101010101010101
Used for:
- Pawn structure evaluation
- Open/semi-open files
- Rook file attacks
if (!(pieces(PAWN) & FileBB[file]))
// file is open
RankBB
Meaning: Bitboard for an entire rank
Bitboard RankBB[RANK_NB];
There are 8 ranks:
- Rank 1
- Rank 2
- …
- Rank 8
So:
- RankBB[RANK_1] = all squares on rank 1 set
- RankBB[RANK_4] = all squares on rank 4 set
Example: Rank 4
RankBB[4] =
8 . . . . . . . .
7 . . . . . . . .
6 . . . . . . . .
5 . . . . . . . .
4 1 1 1 1 1 1 1 1
3 . . . . . . . .
2 . . . . . . . .
1 . . . . . . . .
a b c d e f g h
Hex:
RankBB[4] = 0x00000000FF000000
Used for:
- Passed pawn detection
- Back rank weakness
- Rank-based attack masks
Bitboard rank4Pieces = pieces() & RankBB[RANK_4];