Overview
The attacks module provides low-level functions for computing piece attacks and rays on a chessboard. These functions are the foundation for implementing chess rules, move generation, and board analysis.
Sliding attacks use Hyperbola Quintessence rather than Magic Bitboards. While Magic Bitboards offer slightly faster lookups, Hyperbola Quintessence requires significantly smaller attack tables and faster initialization - critical for web applications.
Non-Sliding Piece Attacks
Non-sliding pieces (king, knight, pawn) have pre-computed attack tables for optimal performance.
King Attacks
kingAttacks(square: Square): SquareSet
Returns all squares attacked or defended by a king on the given square.
import { kingAttacks } from 'chessops/attacks';
import { makeSquare } from 'chessops';
const e4 = makeSquare('e4');
const attacks = kingAttacks(e4);
// Returns SquareSet containing: d3, d4, d5, e3, e5, f3, f4, f5
Knight Attacks
knightAttacks(square: Square): SquareSet
Returns all squares attacked or defended by a knight on the given square.
import { knightAttacks } from 'chessops/attacks';
const d4 = makeSquare('d4');
const attacks = knightAttacks(d4);
// Returns SquareSet containing: b3, b5, c2, c6, e2, e6, f3, f5
Pawn Attacks
pawnAttacks(color: Color, square: Square): SquareSet
Returns all squares attacked or defended by a pawn of the given color.
import { pawnAttacks } from 'chessops/attacks';
const e4 = makeSquare('e4');
const whiteAttacks = pawnAttacks('white', e4);
// Returns SquareSet containing: d5, f5
const blackAttacks = pawnAttacks('black', e4);
// Returns SquareSet containing: d3, f3
Pawn attacks only include diagonal captures, not forward moves. Use move generation functions for all legal pawn moves.
Sliding Piece Attacks
Sliding pieces (bishop, rook, queen) require occupied squares to determine where attacks stop.
Bishop Attacks
bishopAttacks(square: Square, occupied: SquareSet): SquareSet
Returns all squares attacked by a bishop, considering occupied squares.
import { bishopAttacks } from 'chessops/attacks';
import { SquareSet } from 'chessops/squareSet';
const d4 = makeSquare('d4');
const occupied = SquareSet.fromSquare(makeSquare('f6'));
const attacks = bishopAttacks(d4, occupied);
// Bishop attacks along diagonals, stopping at f6
Rook Attacks
rookAttacks(square: Square, occupied: SquareSet): SquareSet
Returns all squares attacked by a rook along ranks and files.
import { rookAttacks } from 'chessops/attacks';
const d4 = makeSquare('d4');
const occupied = SquareSet.fromSquare(makeSquare('d7'));
const attacks = rookAttacks(d4, occupied);
// Rook attacks along d-file and 4th rank, stopping at d7 vertically
Queen Attacks
queenAttacks(square: Square, occupied: SquareSet): SquareSet
Combines bishop and rook attacks for queen movement.
import { queenAttacks } from 'chessops/attacks';
const attacks = queenAttacks(square, occupied);
// Equivalent to: bishopAttacks(square, occupied).xor(rookAttacks(square, occupied))
Generic Attack Function
attacks(piece: Piece, square: Square, occupied: SquareSet): SquareSet
A unified interface that dispatches to the appropriate attack function based on piece role.
import { attacks } from 'chessops/attacks';
const piece = { role: 'queen', color: 'white' };
const square = makeSquare('d4');
const attackSquares = attacks(piece, square, occupied);
Rays and Between
Rays represent all squares along a rank, file, or diagonal connecting two squares.
Ray Function
ray(a: Square, b: Square): SquareSet
Returns all squares on the line connecting squares a and b, including both endpoints. Returns an empty set if the squares are not aligned.
import { ray } from 'chessops/attacks';
const a1 = makeSquare('a1');
const h8 = makeSquare('h8');
const diagonal = ray(a1, h8);
// Returns SquareSet: a1, b2, c3, d4, e5, f6, g7, h8
const e4 = makeSquare('e4');
const e8 = makeSquare('e8');
const file = ray(e4, e8);
// Returns SquareSet: e1, e2, e3, e4, e5, e6, e7, e8
Between Function
between(a: Square, b: Square): SquareSet
Returns all squares strictly between a and b, excluding the endpoints. Returns an empty set if the squares are not aligned or adjacent.
import { between } from 'chessops/attacks';
const a1 = makeSquare('a1');
const h8 = makeSquare('h8');
const squares = between(a1, h8);
// Returns SquareSet: b2, c3, d4, e5, f6, g7
const e1 = makeSquare('e1');
const e2 = makeSquare('e2');
const adjacent = between(e1, e2);
// Returns empty SquareSet (no squares between adjacent squares)
Hyperbola Quintessence Algorithm
The implementation uses Hyperbola Quintessence for computing sliding attacks:
const hyperbola = (bit: SquareSet, range: SquareSet, occupied: SquareSet): SquareSet => {
let forward = occupied.intersect(range);
let reverse = forward.bswap64(); // Byte swap for reverse direction
forward = forward.minus64(bit);
reverse = reverse.minus64(bit.bswap64());
return forward.xor(reverse.bswap64()).intersect(range);
};
How It Works
- Isolate relevant occupancy: Intersect occupied squares with the piece’s range (rank, file, or diagonal)
- Forward direction: Subtract the piece’s bit position to get all squares ahead
- Reverse direction: Byte-swap, subtract, and swap back for squares behind
- Combine: XOR both directions and mask with the original range
This algorithm is particularly efficient on modern JavaScript engines because it uses only bitwise operations without table lookups. Initialization time is nearly zero compared to Magic Bitboards.
- Non-sliding pieces: O(1) lookup from pre-computed tables
- Sliding pieces: O(1) with constant-time bitwise operations
- Memory usage: ~4KB for all pre-computed tables (vs. hundreds of KB for Magic Bitboards)
- Initialization: Instant (tables computed at module load time)
Use Cases
Check Detection
import { attacks } from 'chessops/attacks';
function isSquareAttacked(
square: Square,
attacker: Color,
board: Board
): boolean {
for (const attackerSquare of board[attacker]) {
const piece = board.get(attackerSquare);
if (piece && attacks(piece, attackerSquare, board.occupied).has(square)) {
return true;
}
}
return false;
}
Pin Detection
import { between, ray } from 'chessops/attacks';
function isPinned(
piece: Square,
king: Square,
board: Board
): boolean {
const squares = between(piece, king);
if (squares.isEmpty()) return false;
const raySquares = ray(piece, king);
// Check if any enemy sliding piece is on the same ray beyond the piece
// Implementation details omitted for brevity
return false; // Simplified
}
X-Ray Attacks
Use ray() and between() to find pieces that could attack through another piece:
import { ray, between } from 'chessops/attacks';
const pinner = makeSquare('a1');
const pinned = makeSquare('d4');
const king = makeSquare('h8');
if (ray(pinner, king).has(pinned)) {
const squaresBetween = between(pinner, pinned);
if (squaresBetween.isEmpty()) {
// Pinned piece is directly between attacker and king
}
}
For complex board analysis, combine attack functions with SquareSet operations like intersect(), union(), and diff() for optimal performance.