BenBot 1.7.5
A chess engine
Loading...
Searching...
No Matches
Move.hpp
Go to the documentation of this file.
1/*
2 * ======================================================================================
3 *
4 * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓███████▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░▒▓████████▓▒░
5 * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
6 * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
7 * ░▒▓███████▓▒░░▒▓██████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
8 * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
9 * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
10 * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░ ░▒▓█▓▒░
11 *
12 * ======================================================================================
13 */
14
21
26
27#pragma once
28
29#include <cassert>
30#include <compare>
31#include <cstddef> // IWYU pragma: keep - for size_t
32#include <cstdint> // IWYU pragma: keep - for std::uint32_t
39#include <optional>
40#include <utility>
41
45namespace chess::moves {
46
47using std::size_t;
48
49using board::File;
50using board::Rank;
51using board::Square;
52using pieces::Color;
53
55using MaybePieceType = std::optional<PieceType>;
56
75struct Move final {
77 constexpr Move() = default;
78
80 constexpr Move(
81 Square start, Square end,
82 PieceType type,
83 MaybePieceType promotedType = std::nullopt);
84
89 [[nodiscard]] constexpr auto from() const noexcept -> Square;
90
95 [[nodiscard]] constexpr auto to() const noexcept -> Square;
96
100 [[nodiscard]] constexpr auto piece() const noexcept -> PieceType;
101
103 [[nodiscard]] constexpr auto promoted_type() const noexcept -> MaybePieceType;
104
106 [[nodiscard]] constexpr auto hash() const noexcept -> size_t;
107
109 [[nodiscard]] auto is_null() const noexcept -> bool { return std::cmp_equal(data, 0); }
110
112 [[nodiscard]] constexpr auto is_promotion() const noexcept -> bool;
113
115 [[nodiscard]] constexpr auto is_under_promotion() const noexcept -> bool;
116
118 [[nodiscard]] constexpr auto is_castling() const noexcept -> bool;
119
120 constexpr auto operator==(const Move&) const noexcept -> bool = default;
121
122private:
123 using Integer = std::uint16_t;
124
125 [[nodiscard]] constexpr auto get_flags() const noexcept -> Integer;
126
127 // A move needs 16 bits to be stored
128 // Square indices are 0-63, so we need 6 bits to store them
129 //
130 // bit 0- 5: destination square index
131 // bit 6-11: origin square index
132 // bit 12-15: integer (0-15) that encodes both moving type & promoted type
133 // : 0-5 gives moving piece type & implies the move is not a promotion
134 // : 6-11 gives promoted piece type, implies the moving type is pawn
135 // : get_flags() declared above returns this integer
136 //
137 // Special case is a null move, this integer will be 0
138 Integer data { UINT16_C(0) };
139};
140
145[[nodiscard, gnu::const]] constexpr auto operator<=>(const Move& first, const Move& second) noexcept
146 -> std::strong_ordering
147{
148 return first.hash() <=> second.hash();
149}
150
153
159[[nodiscard, gnu::const]] constexpr auto castle_kingside(Color color) noexcept -> Move;
160
166[[nodiscard, gnu::const]] constexpr auto castle_queenside(Color color) noexcept -> Move;
167
173[[nodiscard, gnu::const]] constexpr auto promotion(
174 File file, Color color, PieceType promotedType = PieceType::Queen) noexcept -> Move;
175
177
178/*
179 ___ ,--,
180 ,---, ,--.'|_ ,--, ,--.'|
181 ,---.'| | | :,' ,--.'| | | :
182 | | : : : ' : | |, : : ' .--.--.
183 | | | ,---. .;__,' / ,--.--. `--'_ | ' | / / '
184 ,--.__| | / \ | | | / \ ,' ,'| ' | | | : /`./
185 / ,' | / / |:__,'| : .--. .-. | ' | | | | : | : ;_
186. ' / |. ' / | ' : |__ \__\/: . . | | : ' : |__ \ \ `.
187' ; |: |' ; /| | | '.'| ," .--.; | ' : |__ | | '.'| `----. \
188| | '/ '' | / | ; : ;/ / ,. | | | '.'|; : ;/ /`--' /__ ___ ___
189| : :|| : | | , /; : .' \; : ;| , /'--'. / .\/ .\/ .\
190 \ \ / \ \ / ---`-' | , .-./| , / ---`-' `--'---'\ ; \ ; \ ; |
191 `----' `----' `--`---' ---`-' `--" `--" `--"
192
193 */
194
195#ifndef DOXYGEN
196namespace detail {
197 using Integer = std::uint16_t;
198
199 inline constexpr auto LOWEST_SIX_BITS_MASK = 63uz;
200
201 inline constexpr Integer START_SQUARE_OFFSET = UINT16_C(6);
202 inline constexpr Integer FLAGS_OFFSET = UINT16_C(12);
203
204 inline constexpr auto PROMOTED_TYPE_OFFSET_WITHIN_FLAGS = 6uz;
205
206 [[nodiscard, gnu::const]] constexpr auto pack_fields(
207 const Square start, const Square end, const PieceType type,
208 const MaybePieceType promotedType) noexcept
209 -> Integer
210 {
211 const auto flags = promotedType
212 .transform([](const PieceType prom) {
213 return PROMOTED_TYPE_OFFSET_WITHIN_FLAGS + std::to_underlying(prom);
214 })
215 .value_or(std::to_underlying(type));
216
217 return static_cast<Integer>(
218 static_cast<Integer>(end.index())
219 + (static_cast<Integer>(start.index()) << START_SQUARE_OFFSET)
220 + (static_cast<Integer>(flags) << FLAGS_OFFSET));
221 }
222} // namespace detail
223#endif
224
225constexpr Move::Move(
226 const Square start, const Square end,
227 const PieceType type,
228 const MaybePieceType promotedType)
229 : data { detail::pack_fields(start, end, type, promotedType) }
230{
231#ifndef NDEBUG
232 if (promotedType.has_value()) {
233 assert(type == PieceType::Pawn);
234 assert(promotedType != PieceType::King);
235 assert(promotedType != PieceType::Pawn);
236 }
237#endif
238}
239
240constexpr auto Move::get_flags() const noexcept -> Integer
241{
242 static constexpr auto LOWEST_FOUR_BITS_MASK = 15uz;
243
244 return (data >> detail::FLAGS_OFFSET) & LOWEST_FOUR_BITS_MASK;
245}
246
247constexpr auto Move::from() const noexcept -> Square
248{
249 return Square::from_index((data >> detail::START_SQUARE_OFFSET) & detail::LOWEST_SIX_BITS_MASK);
250}
251
252constexpr auto Move::to() const noexcept -> Square
253{
254 return Square::from_index(data & detail::LOWEST_SIX_BITS_MASK);
255}
256
257constexpr auto Move::piece() const noexcept -> PieceType
258{
259 const auto flags = get_flags();
260
261 if (std::cmp_greater_equal(flags, detail::PROMOTED_TYPE_OFFSET_WITHIN_FLAGS))
262 return PieceType::Pawn;
263
264 return static_cast<PieceType>(flags);
265}
266
267constexpr auto Move::promoted_type() const noexcept -> MaybePieceType
268{
269 const auto flags = get_flags();
270
271 if (std::cmp_less(flags, detail::PROMOTED_TYPE_OFFSET_WITHIN_FLAGS))
272 return std::nullopt;
273
274 return static_cast<PieceType>(flags - detail::PROMOTED_TYPE_OFFSET_WITHIN_FLAGS);
275}
276
277constexpr auto Move::is_promotion() const noexcept -> bool
278{
279 return std::cmp_greater_equal(
280 get_flags(), detail::PROMOTED_TYPE_OFFSET_WITHIN_FLAGS);
281}
282
283constexpr auto Move::is_under_promotion() const noexcept -> bool
284{
285 return promoted_type()
286 .transform([](const PieceType type) { return type != PieceType::Queen; })
287 .value_or(false);
288}
289
290constexpr auto Move::is_castling() const noexcept -> bool
291{
292 return piece() == PieceType::King
293 and std::cmp_greater(
294 file_distance(from(), to()),
295 1uz);
296}
297
298constexpr auto Move::hash() const noexcept -> size_t
299{
300 // based on a congruential pseudo-random number generator
301 return (static_cast<size_t>(data) * 6364136223846793005uz) + 1442695040888963407uz;
302}
303
304constexpr auto castle_kingside(const Color color) noexcept -> Move
305{
306 const auto rank = board::back_rank_for(color);
307
308 return {
309 Square { .file = File::E, .rank = rank },
310 Square { .file = File::G, .rank = rank },
311 PieceType::King
312 };
313}
314
315constexpr auto castle_queenside(const Color color) noexcept -> Move
316{
317 const auto rank = board::back_rank_for(color);
318
319 return {
320 Square { .file = File::E, .rank = rank },
321 Square { .file = File::C, .rank = rank },
322 PieceType::King
323 };
324}
325
326constexpr auto promotion(
327 const File file, const Color color, const PieceType promotedType) noexcept
328 -> Move
329{
330 assert(promotedType != PieceType::King);
331 assert(promotedType != PieceType::Pawn);
332
333 const bool isWhite = color == Color::White;
334
335 return {
336 Square { .file = file, .rank = isWhite ? Rank::Seven : Rank::Two },
337 Square { .file = file, .rank = isWhite ? Rank::Eight : Rank::One },
338 PieceType::Pawn,
339 promotedType
340 };
341}
342
343} // namespace chess::moves
constexpr auto back_rank_for(Color color) noexcept -> Rank
Definition Rank.hpp:132
constexpr auto operator<=>(const Move &first, const Move &second) noexcept -> std::strong_ordering
Definition Move.hpp:145
constexpr auto castle_queenside(const Color color) noexcept -> Move
Definition Move.hpp:315
constexpr auto castle_kingside(const Color color) noexcept -> Move
Definition Move.hpp:304
constexpr auto promotion(const File file, const Color color, const PieceType promotedType) noexcept -> Move
Definition Move.hpp:326
std::optional< PieceType > MaybePieceType
Definition Move.hpp:55
pieces::Type PieceType
Definition Move.hpp:54
constexpr Move()=default
constexpr Move()=default
constexpr auto hash() const noexcept -> size_t
Definition Move.hpp:298
constexpr auto is_promotion() const noexcept -> bool
Definition Move.hpp:277
constexpr auto to() const noexcept -> Square
Definition Move.hpp:252
constexpr auto promoted_type() const noexcept -> MaybePieceType
Definition Move.hpp:267
constexpr auto is_under_promotion() const noexcept -> bool
Definition Move.hpp:283
constexpr auto is_castling() const noexcept -> bool
Definition Move.hpp:290
constexpr auto piece() const noexcept -> PieceType
Definition Move.hpp:257
constexpr auto from() const noexcept -> Square
Definition Move.hpp:247
auto is_null() const noexcept -> bool
Definition Move.hpp:109
static constexpr auto from_index(BitboardIndex index) noexcept -> Square
Definition Square.hpp:191