
com.kelseyde.calvin.movegen.Attacks Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of calvin-move-generator Show documentation
Show all versions of calvin-move-generator Show documentation
A magic bitboard based chess move generator.
The newest version!
package com.kelseyde.calvin.movegen;
import com.kelseyde.calvin.board.Bits;
import com.kelseyde.calvin.board.File;
import com.kelseyde.calvin.board.Rank;
import com.kelseyde.calvin.board.Square;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class Attacks {
// All the possible move 'vectors' for a sliding piece, i.e., the offsets for the directions in which a sliding
// piece is permitted to move. Bishops will use only the diagonal vectors, rooks only the orthogonal vectors, while
// queens will use both.
public static final Set DIAGONAL_MOVE_VECTORS = Set.of(-9, -7, 7, 9);
public static final Set ORTHOGONAL_MOVE_VECTORS = Set.of(-8, -1, 1, 8);
// The following sets are exceptions to the initial rule, in scenarios where the sliding piece is placed on the a or h-files.
// These exceptions prevent the piece from 'wrapping' around to the other side of the board.
public static final Set A_FILE_OFFSET_EXCEPTIONS = Set.of(-9, -1, 7);
public static final Set H_FILE_OFFSET_EXCEPTIONS = Set.of(-7, 1, 9);
public static final long[] KNIGHT_ATTACKS = new long[] {
0x0000000000020400L, 0x0000000000050800L, 0x00000000000a1100L, 0x0000000000142200L,
0x0000000000284400L, 0x0000000000508800L, 0x0000000000a01000L, 0x0000000000402000L,
0x0000000002040004L, 0x0000000005080008L, 0x000000000a110011L, 0x0000000014220022L,
0x0000000028440044L, 0x0000000050880088L, 0x00000000a0100010L, 0x0000000040200020L,
0x0000000204000402L, 0x0000000508000805L, 0x0000000a1100110aL, 0x0000001422002214L,
0x0000002844004428L, 0x0000005088008850L, 0x000000a0100010a0L, 0x0000004020002040L,
0x0000020400040200L, 0x0000050800080500L, 0x00000a1100110a00L, 0x0000142200221400L,
0x0000284400442800L, 0x0000508800885000L, 0x0000a0100010a000L, 0x0000402000204000L,
0x0002040004020000L, 0x0005080008050000L, 0x000a1100110a0000L, 0x0014220022140000L,
0x0028440044280000L, 0x0050880088500000L, 0x00a0100010a00000L, 0x0040200020400000L,
0x0204000402000000L, 0x0508000805000000L, 0x0a1100110a000000L, 0x1422002214000000L,
0x2844004428000000L, 0x5088008850000000L, 0xa0100010a0000000L, 0x4020002040000000L,
0x0400040200000000L, 0x0800080500000000L, 0x1100110a00000000L, 0x2200221400000000L,
0x4400442800000000L, 0x8800885000000000L, 0x100010a000000000L, 0x2000204000000000L,
0x0004020000000000L, 0x0008050000000000L, 0x00110a0000000000L, 0x0022140000000000L,
0x0044280000000000L, 0x0088500000000000L, 0x0010a00000000000L, 0x0020400000000000L
};
public static final long[] KING_ATTACKS = new long[] {
0x0000000000000302L, 0x0000000000000705L, 0x0000000000000e0aL, 0x0000000000001c14L,
0x0000000000003828L, 0x0000000000007050L, 0x000000000000e0a0L, 0x000000000000c040L,
0x0000000000030203L, 0x0000000000070507L, 0x00000000000e0a0eL, 0x00000000001c141cL,
0x0000000000382838L, 0x0000000000705070L, 0x0000000000e0a0e0L, 0x0000000000c040c0L,
0x0000000003020300L, 0x0000000007050700L, 0x000000000e0a0e00L, 0x000000001c141c00L,
0x0000000038283800L, 0x0000000070507000L, 0x00000000e0a0e000L, 0x00000000c040c000L,
0x0000000302030000L, 0x0000000705070000L, 0x0000000e0a0e0000L, 0x0000001c141c0000L,
0x0000003828380000L, 0x0000007050700000L, 0x000000e0a0e00000L, 0x000000c040c00000L,
0x0000030203000000L, 0x0000070507000000L, 0x00000e0a0e000000L, 0x00001c141c000000L,
0x0000382838000000L, 0x0000705070000000L, 0x0000e0a0e0000000L, 0x0000c040c0000000L,
0x0003020300000000L, 0x0007050700000000L, 0x000e0a0e00000000L, 0x001c141c00000000L,
0x0038283800000000L, 0x0070507000000000L, 0x00e0a0e000000000L, 0x00c040c000000000L,
0x0302030000000000L, 0x0705070000000000L, 0x0e0a0e0000000000L, 0x1c141c0000000000L,
0x3828380000000000L, 0x7050700000000000L, 0xe0a0e00000000000L, 0xc040c00000000000L,
0x0203000000000000L, 0x0507000000000000L, 0x0a0e000000000000L, 0x141c000000000000L,
0x2838000000000000L, 0x5070000000000000L, 0xa0e0000000000000L, 0x40c0000000000000L
};
public static final long[] ROOK_MAGICS = new long[] {
0x0080001020400080L, 0x0040001000200040L, 0x0080081000200080L, 0x0080040800100080L,
0x0080020400080080L, 0x0080010200040080L, 0x0080008001000200L, 0x0080002040800100L,
0x0000800020400080L, 0x0000400020005000L, 0x0000801000200080L, 0x0000800800100080L,
0x0000800400080080L, 0x0000800200040080L, 0x0000800100020080L, 0x0000800040800100L,
0x0000208000400080L, 0x0000404000201000L, 0x0000808010002000L, 0x0000808008001000L,
0x0000808004000800L, 0x0000808002000400L, 0x0000010100020004L, 0x0000020000408104L,
0x0000208080004000L, 0x0000200040005000L, 0x0000100080200080L, 0x0000080080100080L,
0x0000040080080080L, 0x0000020080040080L, 0x0000010080800200L, 0x0000800080004100L,
0x0000204000800080L, 0x0000200040401000L, 0x0000100080802000L, 0x0000080080801000L,
0x0000040080800800L, 0x0000020080800400L, 0x0000020001010004L, 0x0000800040800100L,
0x0000204000808000L, 0x0000200040008080L, 0x0000100020008080L, 0x0000080010008080L,
0x0000040008008080L, 0x0000020004008080L, 0x0000010002008080L, 0x0000004081020004L,
0x0000204000800080L, 0x0000200040008080L, 0x0000100020008080L, 0x0000080010008080L,
0x0000040008008080L, 0x0000020004008080L, 0x0000800100020080L, 0x0000800041000080L,
0x00FFFCDDFCED714AL, 0x007FFCDDFCED714AL, 0x003FFFCDFFD88096L, 0x0000040810002101L,
0x0001000204080011L, 0x0001000204000801L, 0x0001000082000401L, 0x0001FFFAABFAD1A2L
};
public static final long[] BISHOP_MAGICS = new long[] {
0x0002020202020200L, 0x0002020202020000L, 0x0004010202000000L, 0x0004040080000000L,
0x0001104000000000L, 0x0000821040000000L, 0x0000410410400000L, 0x0000104104104000L,
0x0000040404040400L, 0x0000020202020200L, 0x0000040102020000L, 0x0000040400800000L,
0x0000011040000000L, 0x0000008210400000L, 0x0000004104104000L, 0x0000002082082000L,
0x0004000808080800L, 0x0002000404040400L, 0x0001000202020200L, 0x0000800802004000L,
0x0000800400A00000L, 0x0000200100884000L, 0x0000400082082000L, 0x0000200041041000L,
0x0002080010101000L, 0x0001040008080800L, 0x0000208004010400L, 0x0000404004010200L,
0x0000840000802000L, 0x0000404002011000L, 0x0000808001041000L, 0x0000404000820800L,
0x0001041000202000L, 0x0000820800101000L, 0x0000104400080800L, 0x0000020080080080L,
0x0000404040040100L, 0x0000808100020100L, 0x0001010100020800L, 0x0000808080010400L,
0x0000820820004000L, 0x0000410410002000L, 0x0000082088001000L, 0x0000002011000800L,
0x0000080100400400L, 0x0001010101000200L, 0x0002020202000400L, 0x0001010101000200L,
0x0000410410400000L, 0x0000208208200000L, 0x0000002084100000L, 0x0000000020880000L,
0x0000001002020000L, 0x0000040408020000L, 0x0004040404040000L, 0x0002020202020000L,
0x0000104104104000L, 0x0000002082082000L, 0x0000000020841000L, 0x0000000000208800L,
0x0000000010020200L, 0x0000000404080200L, 0x0000040404040400L, 0x0002020202020200L
};
public static final int[] ROOK_SHIFTS = new int[]{
52, 53, 53, 53, 53, 53, 53, 52,
53, 54, 54, 54, 54, 54, 54, 53,
53, 54, 54, 54, 54, 54, 54, 53,
53, 54, 54, 54, 54, 54, 54, 53,
53, 54, 54, 54, 54, 54, 54, 53,
53, 54, 54, 54, 54, 54, 54, 53,
53, 54, 54, 54, 54, 54, 54, 53,
53, 54, 54, 53, 53, 53, 53, 53
};
public static final int[] BISHOP_SHIFTS = new int[] {
58, 59, 59, 59, 59, 59, 59, 58,
59, 59, 59, 59, 59, 59, 59, 59,
59, 59, 57, 57, 57, 57, 59, 59,
59, 59, 57, 55, 55, 57, 59, 59,
59, 59, 57, 55, 55, 57, 59, 59,
59, 59, 57, 57, 57, 57, 59, 59,
59, 59, 59, 59, 59, 59, 59, 59,
58, 59, 59, 59, 59, 59, 59, 58
};
public static final long[] ROOK_MASKS = initMagicMask(true);
public static final long[] BISHOP_MASKS = initMagicMask(false);
public static final long[][] ROOK_ATTACKS = initMagicAttacks(true, ROOK_MAGICS, ROOK_SHIFTS);
public static final long[][] BISHOP_ATTACKS = initMagicAttacks(false, BISHOP_MAGICS, BISHOP_SHIFTS);
public static final MagicLookup[] ROOK_MAGIC_LOOKUP = initMagicLookups(ROOK_ATTACKS, ROOK_MASKS, ROOK_MAGICS, ROOK_SHIFTS);
public static final MagicLookup[] BISHOP_MAGIC_LOOKUP = initMagicLookups(BISHOP_ATTACKS, BISHOP_MASKS, BISHOP_MAGICS, BISHOP_SHIFTS);
public static long pawnAttacks(long pawns, boolean white) {
return white ?
(Bits.northWest(pawns) &~ File.H) | (Bits.northEast(pawns) &~ File.A) :
(Bits.southWest(pawns) &~ File.H) | (Bits.southEast(pawns) &~ File.A);
}
public static long kingAttacks(int square) {
return KING_ATTACKS[square];
}
public static long knightAttacks(int square) {
return KNIGHT_ATTACKS[square];
}
public static long rookAttacks(int square, long blockers) {
return sliderAttacks(square, blockers, ROOK_MAGIC_LOOKUP);
}
public static long bishopAttacks(int square, long blockers) {
return sliderAttacks(square, blockers, BISHOP_MAGIC_LOOKUP);
}
/**
* Calculate single pawn moves.
*/
public static long pawnSingleMoves(long pawns, long occupied, boolean white) {
return white ?
Bits.north(pawns) & ~occupied & ~Rank.EIGHTH :
Bits.south(pawns) & ~occupied & ~Rank.FIRST;
}
/**
* Calculate double pawn moves.
*/
public static long pawnDoubleMoves(long pawns, long occupied, boolean white) {
return white ?
Bits.north(pawnSingleMoves(pawns, occupied, true)) & ~occupied & Rank.FOURTH :
Bits.south(pawnSingleMoves(pawns, occupied, false)) & ~occupied & Rank.FIFTH;
}
/**
* Calculate pawn push promotions.
*/
public static long pawnPushPromotions(long pawns, long occupied, boolean white) {
return white ?
Bits.north(pawns) & ~occupied & Rank.EIGHTH :
Bits.south(pawns) & ~occupied & Rank.FIRST;
}
/**
* Calculate left captures by pawns.
*/
public static long pawnLeftCaptures(long pawns, long opponents, boolean white) {
return white ?
Bits.northWest(pawns) & opponents & ~File.H & ~Rank.EIGHTH :
Bits.southWest(pawns) & opponents & ~File.H & ~Rank.FIRST;
}
/**
* Calculate right captures by pawns.
*/
public static long pawnRightCaptures(long pawns, long opponents, boolean white) {
return white ?
Bits.northEast(pawns) & opponents & ~File.A & ~Rank.EIGHTH :
Bits.southEast(pawns) & opponents & ~File.A & ~Rank.FIRST;
}
/**
* Calculate left en passant captures by pawns.
*/
public static long pawnLeftEnPassants(long pawns, long enPassantFile, boolean white) {
return white ?
Bits.northWest(pawns) & enPassantFile & Rank.SIXTH & ~File.H :
Bits.southWest(pawns) & enPassantFile & Rank.THIRD & ~File.H;
}
/**
* Calculate right en passant captures by pawns.
*/
public static long pawnRightEnPassants(long pawns, long enPassantFile, boolean white) {
return white ?
Bits.northEast(pawns) & enPassantFile & Rank.SIXTH & ~File.A :
Bits.southEast(pawns) & enPassantFile & Rank.THIRD & ~File.A;
}
/**
* Calculate left capture promotions by pawns.
*/
public static long pawnLeftCapturePromotions(long pawns, long opponents, boolean white) {
return white ?
Bits.northWest(pawns) & opponents & ~File.H & Rank.EIGHTH :
Bits.southWest(pawns) & opponents & ~File.H & Rank.FIRST;
}
/**
* Calculate right capture promotions by pawns.
*/
public static long pawnRightCapturePromotions(long pawns, long opponents, boolean white) {
return white ?
Bits.northEast(pawns) & opponents & ~File.A & Rank.EIGHTH :
Bits.southEast(pawns) & opponents & ~File.A & Rank.FIRST;
}
public static long sliderAttacks(int sq, long occ, MagicLookup[] lookups) {
MagicLookup lookup = lookups[sq];
occ &= lookup.mask;
occ *= lookup.magic;
occ >>>= lookup.shift;
return lookup.attacks[(int) occ];
}
public static long[] initMagicMask(boolean isOrthogonal) {
long[] magicMasks = new long[Square.COUNT];
for (int square = 0; square < Square.COUNT; square++) {
magicMasks[square] = initMovementMask(square, isOrthogonal);
}
return magicMasks;
}
public static long[][] initMagicAttacks(boolean isOrthogonal, long[] magics, int[] shifts) {
long[][] magicAttacks = new long[Square.COUNT][];
for (int square = 0; square < Square.COUNT; square++) {
magicAttacks[square] = initMagicTable(square, isOrthogonal, magics[square], shifts[square]);
}
return magicAttacks;
}
public static long[] initMagicTable(int square, boolean isOrthogonal, long magic, int shift) {
int numBits = Square.COUNT - shift;
int tableSize = 1 << numBits;
long[] table = new long[tableSize];
long movementMask = initMovementMask(square, isOrthogonal);
long[] blockerMasks = initBlockerMasks(movementMask);
for (long blockerMask : blockerMasks) {
long index = (blockerMask * magic) >>> shift;
long attacks = initAttackMask(square, blockerMask, isOrthogonal);
table[(int) index] = attacks;
}
return table;
}
public static long[] initBlockerMasks(long movementMask) {
List moveSquares = new ArrayList<>();
for (int i = 0; i < Square.COUNT; i++) {
if ((movementMask & Bits.of(i)) != 0) {
moveSquares.add(i);
}
}
int patternsCount = 1 << moveSquares.size();
long[] blockerBitboards = new long[patternsCount];
for (int patternIndex = 0; patternIndex < patternsCount; patternIndex++) {
for (int bitIndex = 0; bitIndex < moveSquares.size(); bitIndex++) {
int bit = (patternIndex >>> bitIndex) & 1;
blockerBitboards[patternIndex] |= (long) bit << moveSquares.get(bitIndex);
}
}
return blockerBitboards;
}
public static long initMovementMask(int from, boolean isOrthogonal) {
long movementMask = 0L;
Set vectors = isOrthogonal ? ORTHOGONAL_MOVE_VECTORS : DIAGONAL_MOVE_VECTORS;
for (int vector : vectors) {
int currentSquare = from;
if (!isValidVectorOffset(currentSquare, vector)) {
continue;
}
for (int distance = 1; distance < 8; distance++) {
currentSquare = currentSquare + vector;
if (Square.isValid(currentSquare + vector) && isValidVectorOffset(currentSquare, vector)) {
movementMask |= 1L << currentSquare;
} else {
break;
}
}
}
return movementMask;
}
public static long initAttackMask(int from, long blockers, boolean isOrthogonal) {
long attackMask = 0L;
Set vectors = isOrthogonal ? ORTHOGONAL_MOVE_VECTORS : DIAGONAL_MOVE_VECTORS;
for (int vector : vectors) {
int currentSquare = from;
for (int distance = 1; distance < 8; distance++) {
if (Square.isValid(currentSquare + vector) && isValidVectorOffset(currentSquare, vector)) {
currentSquare = currentSquare + vector;
attackMask |= 1L << currentSquare;
if ((blockers & 1L << currentSquare) != 0) {
break;
}
} else {
break;
}
}
}
return attackMask;
}
public record MagicLookup(long[] attacks, long mask, long magic, int shift) {}
public static MagicLookup[] initMagicLookups(long[][] allAttacks, long[] masks, long[] magics, int[] shifts) {
MagicLookup[] magicLookups = new MagicLookup[Square.COUNT];
for (int i = 0; i < Square.COUNT; i++) {
long[] attacks = allAttacks[i];
long mask = masks[i];
long magic = magics[i];
int shift = shifts[i];
magicLookups[i] = new MagicLookup(attacks, mask, magic, shift);
}
return magicLookups;
}
private static boolean isValidVectorOffset(int square, int vectorOffset) {
boolean isAFile = (File.A & Bits.of(square)) != 0;
boolean isHFile = (File.H & Bits.of(square)) != 0;
boolean isVectorAFileException = A_FILE_OFFSET_EXCEPTIONS.contains(vectorOffset);
boolean isVectorHFileException = H_FILE_OFFSET_EXCEPTIONS.contains(vectorOffset);
return (!isAFile || !isVectorAFileException) && (!isHFile || !isVectorHFileException);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy