Chess Engine
A Chess Engine project written in C++.
Loading...
Searching...
No Matches
magic.hpp
1#pragma once
2
3#include <array>
4#include <cassert>
5#include <cstddef>
6#include <cstdint>
7
8#include "bitboard.hpp"
9#include "navigation.hpp"
10#include "piece.hpp"
11#include "utils.hpp"
12using Shift = uint8_t;
13
14struct Magic {
15 Bitboard magic;
16 Shift shift{};
17 Bitboard premask;
18 size_t attacksOffset{};
19
20 [[nodiscard]] constexpr size_t index(Bitboard occupancy) const {
21 return (((occupancy & premask) * magic) >> shift);
22 }
23};
24
25constexpr size_t ROOK_ATTACKS_SIZE = 102400;
26constexpr size_t BISHOP_ATTACKS_SIZE = 5248;
27constexpr size_t TOTAL_ATTACKS_SIZE = ROOK_ATTACKS_SIZE + BISHOP_ATTACKS_SIZE;
28
29class Magics {
30 static constexpr int MAX_OCCUPANCIES = 4096;
31
32 class PRNG {
33 uint64_t s;
34
35 constexpr uint64_t rand64() {
36 s ^= s >> 12, s ^= s << 25, s ^= s >> 27;
37 return s * 2685821657736338717LL;
38 }
39
40 public:
41 constexpr PRNG(uint64_t seed) : s(seed) { assert(seed); }
42
43 template <typename T>
44 constexpr T rand() {
45 return T(rand64());
46 }
47
48 template <typename T>
49 constexpr T sparse_rand() {
50 return T(rand64() & rand64() & rand64());
51 }
52 };
53
54 static constexpr Bitboard premask(PieceType pieceType, Square square) {
55 switch (pieceType) {
56 case PieceType::ROOK: {
57 Bitboard edges =
58 ((Bitboard::rankBB(Rank::R1) | Bitboard::rankBB(Rank::R8)) & ~Bitboard::rankBB(square.rank())) |
59 ((Bitboard::fileBB(File::FA) | Bitboard::fileBB(File::FH)) & ~Bitboard::fileBB(square.file()));
60 return slidingAttacksBB(PieceType::ROOK, square, Bitboard::zero()) & ~edges;
61 }
62 case PieceType::BISHOP: {
63 Bitboard edges = (Bitboard::rankBB(Rank::R1) | Bitboard::rankBB(Rank::R8) | Bitboard::fileBB(File::FA) |
64 Bitboard::fileBB(File::FH));
65 return slidingAttacksBB(PieceType::BISHOP, square, Bitboard::zero()) & ~edges;
66 }
67 default:
68 return Bitboard::zero();
69 }
70 }
71
72 static constexpr Bitboard slidingAttacksBB(PieceType pieceType, Square square, Bitboard occupancy) {
73 Bitboard attacks = Bitboard::zero();
74
75 constexpr std::array<Direction, 4> rookDirections = {Direction::N, Direction::S, Direction::E, Direction::W};
76 constexpr std::array<Direction, 4> bishopDirections = {Direction::NE, Direction::NW, Direction::SE,
77 Direction::SW};
78
79 const auto& directions = (pieceType == PieceType::BISHOP) ? bishopDirections : rookDirections;
80
81 for (Direction direction : directions) {
82 Square currentSquare = square;
83
84 while (true) {
85 Bitboard destination = Bitboard::destinationBB(currentSquare, direction);
86 if (destination == Bitboard::zero()) break;
87
88 attacks |= destination;
89 currentSquare = currentSquare + direction;
90
91 if ((destination & occupancy) != Bitboard::zero()) {
92 break;
93 }
94 }
95 }
96
97 return attacks;
98 }
99
100 public:
101 static auto generateMagics() {
102 std::array<Magic, Square::number()> rookMagics{};
103 std::array<Magic, Square::number()> bishopMagics{};
104 std::array<Bitboard, TOTAL_ATTACKS_SIZE> attacks{};
105
106 // optimal PRNG seeds from Stockfish to pick the correct magics in the shortest time
107 constexpr std::array<std::array<int, 8>, 2> seeds = {{{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020},
108 {728, 10316, 55013, 32803, 12281, 15100, 16645, 255}}};
109
110 size_t attacksOffset = 0;
111
112 for (auto square : Square::all()) {
113 Magic& magic = rookMagics[square];
114 magic.premask = premask(PieceType::ROOK, square);
115 magic.shift = Square::number() - popCount(magic.premask);
116 magic.attacksOffset = attacksOffset;
117
118 std::array<Bitboard, MAX_OCCUPANCIES> occupancies{};
119 std::array<Bitboard, MAX_OCCUPANCIES> reference{};
120 std::array<int, MAX_OCCUPANCIES> epoch{};
121
122 size_t size = 0;
123 int cnt = 0;
124
125 // use Carry-Rippler trick
126 Bitboard b = Bitboard::zero();
127 do {
128 occupancies[size] = b;
129 reference[size] = slidingAttacksBB(PieceType::ROOK, square, b);
130 size++;
131 b = (b - magic.premask) & magic.premask;
132 } while (b != Bitboard::zero());
133
134 PRNG rng(static_cast<uint64_t>(seeds[0][square.rank()]));
135
136 for (size_t i = 0; i < size;) {
137 for (magic.magic = Bitboard::zero(); popCount((magic.magic * magic.premask) >> 56) < 6;) {
138 magic.magic = rng.sparse_rand<Bitboard>();
139 }
140 for (++cnt, i = 0; i < size; ++i) {
141 size_t idx = magic.index(occupancies[i]);
142 if (epoch[idx] < cnt) {
143 epoch[idx] = cnt;
144 attacks[attacksOffset + idx] = reference[i];
145 } else if (attacks[attacksOffset + idx] != reference[i]) {
146 break;
147 }
148 }
149 }
150
151 attacksOffset += (1ULL << popCount(magic.premask));
152 }
153
154 for (auto square : Square::all()) {
155 Magic& magic = bishopMagics[square];
156 magic.premask = premask(PieceType::BISHOP, square);
157 magic.shift = Square::number() - popCount(magic.premask);
158 magic.attacksOffset = attacksOffset;
159
160 std::array<Bitboard, MAX_OCCUPANCIES> occupancies{};
161 std::array<Bitboard, MAX_OCCUPANCIES> reference{};
162 std::array<int, MAX_OCCUPANCIES> epoch{};
163
164 size_t size = 0;
165 int cnt = 0;
166
167 Bitboard b = Bitboard::zero();
168 do {
169 occupancies[size] = b;
170 reference[size] = slidingAttacksBB(PieceType::BISHOP, square, b);
171 size++;
172 b = (b - magic.premask) & magic.premask;
173 } while (b != Bitboard::zero());
174
175 PRNG rng(static_cast<uint64_t>(seeds[1][square.rank()]));
176
177 for (size_t i = 0; i < size;) {
178 for (magic.magic = Bitboard::zero(); popCount((magic.magic * magic.premask) >> 56) < 6;) {
179 magic.magic = rng.sparse_rand<Bitboard>();
180 }
181
182 for (++cnt, i = 0; i < size; ++i) {
183 size_t idx = magic.index(occupancies[i]);
184 if (epoch[idx] < cnt) {
185 epoch[idx] = cnt;
186 attacks[attacksOffset + idx] = reference[i];
187 } else if (attacks[attacksOffset + idx] != reference[i]) {
188 break;
189 }
190 }
191 }
192
193 attacksOffset += (1ULL << popCount(magic.premask));
194 }
195
196 return std::make_tuple(rookMagics, bishopMagics, attacks);
197 }
198};
199
200auto MAGIC_TABLES = MagicGenerator::generateMagics();
201constexpr auto& ROOK_MAGICS = std::get<0>(MAGIC_TABLES);
202constexpr auto& BISHOP_MAGICS = std::get<1>(MAGIC_TABLES);
203constexpr auto& MAGIC_ATTACKS = std::get<2>(MAGIC_TABLES);
204
205constexpr Bitboard rookAttacksBB(Square square, Bitboard occupancy) {
206 const Magic& magic = ROOK_MAGICS[square];
207 return MAGIC_ATTACKS[magic.attacksOffset + magic.index(occupancy)];
208}
209
210constexpr Bitboard bishopAttacksBB(Square square, Bitboard occupancy) {
211 const Magic& magic = BISHOP_MAGICS[square];
212 return MAGIC_ATTACKS[magic.attacksOffset + magic.index(occupancy)];
213}
214
215constexpr Bitboard queenAttacksBB(Square square, Bitboard occupancy) {
216 return rookAttacksBB(square, occupancy) | bishopAttacksBB(square, occupancy);
217}
Definition magic.hpp:29
Definition bitboard.hpp:9
Definition navigation.hpp:176
Definition magic.hpp:14
Definition piece.hpp:10
Definition navigation.hpp:118