-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsquare.hpp
More file actions
303 lines (267 loc) · 10.7 KB
/
square.hpp
File metadata and controls
303 lines (267 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
#pragma once
#include <bitbishop/color.hpp>
#include <bitbishop/config.hpp>
#include <bitbishop/constants.hpp>
#include <bitbishop/square.hpp>
#include <format>
#include <stdexcept>
#include <string>
/**
* @brief Represents a square on the chess board.
*
* Internally wraps an enum with values 0–63 for squares A1–H8.
* Provides convenient constructors, accessors, and comparison operators.
*/
class Square {
public:
/**
* @brief Enum representing squares of a chess board.
*
* Mapping is row-major: A1 = 0 and H8 = 63.
*
* @note An unscoped enum is used because:
* - Square values must be implicitly convertible to int
* - This allows efficient use in bitboards and shifts
* - Scoped enums (`enum class`) forbid implicit conversions
*/
// clang-format off
enum Value : std::uint8_t {
A1, B1, C1, D1, E1, F1, G1, H1,
A2, B2, C2, D2, E2, F2, G2, H2,
A3, B3, C3, D3, E3, F3, G3, H3,
A4, B4, C4, D4, E4, F4, G4, H4,
A5, B5, C5, D5, E5, F5, G5, H5,
A6, B6, C6, D6, E6, F6, G6, H6,
A7, B7, C7, D7, E7, F7, G7, H7,
A8, B8, C8, D8, E8, F8, G8, H8,
};
// clang-format on
private:
/// @brief The internal square value (0–63).
Value m_value;
public:
/**
* @brief Construct a Square from a raw integer value (flattened index) with runtime validation.
* @param square_index Integer in range [0, 63].
* @throw std::invalid_argument if square_index is out of range.
*/
explicit Square(int square_index) : m_value(static_cast<Value>(square_index)) {
using namespace Const;
if (square_index < 0 || square_index >= BOARD_SIZE) {
const std::string msg = std::format("Invalid flattened square index {}, must stay in range [0,63]", square_index);
throw std::invalid_argument(msg);
}
}
/**
* @brief Construct a Square from a raw integer value (flattened index).
* @param square_index Integer in range [0, 63].
* @param std::in_place_t
*
* The std::in_place_t parameter serves as a compile-time tag to disambiguate this constructor
* from other constructors. It doesn't carry any data—it's an empty struct whose sole purpose is
* to enable overload resolution:
*
* ```c++
* // You'd call it like this:
* Square sq(42, std::in_place); // std::in_place is a constexpr instance of std::in_place_t
*
* // Versus a regular constructor:
* Square sq(42); // Calls a different constructor
* ```
*/
CX_FN Square(int square_index, std::in_place_t tag) noexcept : m_value(static_cast<Value>(square_index)) {
(void)tag;
}
/**
* @brief Construct a Square directly from an enum value.
* @param square_value A valid Value from the Square::Value enum.
*/
CX_FN explicit Square(Value square_value) : m_value(square_value) {}
/**
* @brief Construct a Square from file and rank coordinates.
* @param file File index (0 = 'a', …, 7 = 'h').
* @param rank Rank index (0 = '1', …, 7 = '8').
* @throw std::invalid_argument if file or rank are out of range.
*/
explicit Square(int file, int rank) {
using namespace Const;
// Rank refers to the eight horizontal rows on the board, labelled 1 to 8.
// File refers to the eight vertical columns on the board, labelled a to h.
if (file < FILE_A_IND || file > FILE_H_IND || rank < RANK_1_IND || rank > RANK_8_IND) {
const std::string msg = std::format("Invalid file ({}) or rank({}), must stay within ranke [0,7].", file, rank);
throw std::invalid_argument(msg);
}
m_value = static_cast<Value>((rank * BOARD_WIDTH) + file);
}
/**
* @brief Construct a Square from algebraic notation.
* @param str String like "e4" (case-insensitive).
* @throw std::invalid_argument if the string is malformed or out of range.
*/
explicit Square(const std::string& str) {
using namespace Const;
if (str.size() != 2) {
const std::string msg = std::format("Invalid Square string '{}', must be built out of two characters", str);
throw std::invalid_argument(msg);
}
const char file = std::tolower(str.at(0));
const char rank = str.at(1);
if (file < 'a' || file > 'h' || rank < '1' || rank > '8') {
const std::string msg = std::format(
"Invalid Square string '{}', must be built out of two characters, "
"the first being the file (between a and h, got {}) and the second the rank (between 1 and 8, got {})",
str, file, rank);
throw std::invalid_argument(msg);
}
const int ifile = file - 'a';
const int irank = rank - '1';
m_value = static_cast<Value>((irank * BOARD_WIDTH) + ifile);
}
/**
* @brief Get the underlying enum value.
* @return Square::Value corresponding to this square.
*/
[[nodiscard]] CX_FN Value value() const { return m_value; }
/**
* @brief Get the underlying flqttened square index.
* @return std::uint8_t Flattened index corresponding to this square.
*/
[[nodiscard]] CX_FN std::uint8_t flat_index() const { return static_cast<std::uint8_t>(m_value); }
/**
* @brief Get the file index (column).
* @return Integer 0–7, where 0 = 'a' and 7 = 'h'.
*/
[[nodiscard]] CX_FN int file() const {
using namespace Const;
return static_cast<int>(m_value) % BOARD_WIDTH;
}
/**
* @brief Get the rank index (row).
* @return Integer 0–7, where 0 = '1' and 7 = '8'.
*/
[[nodiscard]] CX_FN int rank() const {
using namespace Const;
return static_cast<int>(m_value) / BOARD_WIDTH;
}
/**
* @brief Tells if two squares lays on the same file.
* @return True if two squares lays on the same file, false otherwise.
*/
[[nodiscard]] CX_FN bool same_file(const Square& other) const { return this->file() == other.file(); }
/**
* @brief Tells if two squares lays on the same rank.
* @return True if two squares lays on the same rank, false otherwise.
*/
[[nodiscard]] CX_FN bool same_rank(const Square& other) const { return this->rank() == other.rank(); }
/**
* @brief Checks whether this square lies on the same NE–SW diagonal as another square.
*
* Two squares are aligned on a NE–SW diagonal if the difference between their
* file and rank coordinates is identical. Along such a diagonal, (file − rank)
* remains constant.
*
* This diagonal orientation corresponds to bishop movement in the NE–SW direction
* and is commonly used when computing diagonal attacks, pins, and interposition
* rays.
*
* @param other The square to compare against.
* @return true if both squares share the same NE–SW diagonal, false otherwise.
*/
[[nodiscard]] CX_FN bool same_ne_sw_diag(const Square& other) const {
return (this->file() - this->rank()) == (other.file() - other.rank());
}
/**
* @brief Checks whether this square lies on the same NW–SE diagonal as another square.
*
* Two squares are aligned on a NW–SE diagonal if the sum of their file and rank
* coordinates is identical. Along such a diagonal, (file + rank) remains constant.
*
* This diagonal orientation corresponds to bishop movement in the NW–SE direction
* and is commonly used when computing diagonal attacks, pins, and interposition
* rays.
*
* @param other The square to compare against.
* @return true if both squares share the same NW–SE diagonal, false otherwise.
*/
[[nodiscard]] CX_FN bool same_nw_se_diag(const Square& other) const {
return (this->file() + this->rank()) == (other.file() + other.rank());
}
/**
* @brief Checks whether this square lies on the same diagonal as another square.
*
* Two squares are on the same diagonal if they share either:
* - the same NE–SW diagonal, or
* - the same NW–SE diagonal.
*
* This geometric property is used to determine bishop and queen alignment,
* detect pins, compute interposition rays, and build diagonal attack lookups.
*
* @param other The square to compare against.
* @return true if both squares are aligned on a common diagonal, false otherwise.
*/
[[nodiscard]] CX_FN bool same_diag(const Square& other) const {
return same_ne_sw_diag(other) || same_nw_se_diag(other);
}
/**
* @brief Convert the square to algebraic notation.
* @return Lowercase string like "a1", "e4", "h8".
*/
[[nodiscard]] std::string to_string() const { return {char('a' + file()), char('1' + rank())}; }
/**
* @brief Returns the color of this square.
*
* A square is white if (file + rank) is even, black otherwise.
* Example: A1 (0+0=0) → White, B1 (1+0=1) → Black.
*
* @return Color::White or Color::Black.
*/
[[nodiscard]] CX_FN Color color() const { return ((file() + rank()) % 2 == 0) ? Color::WHITE : Color::BLACK; }
/**
* @brief Checks whether this square is the same color as another.
* @param other Another Square.
* @return true if both squares share the same color.
*/
[[nodiscard]] CX_FN bool same_color(const Square& other) const { return color() == other.color(); }
/**
* @brief Equality operator.
* @param other Another Square.
* @return true if both refer to the same square.
*/
CX_FN bool operator==(const Square& other) const { return m_value == other.m_value; }
/**
* @brief Inequality operator.
* @param other Another Square.
* @return true if the squares differ.
*/
CX_FN bool operator!=(const Square& other) const { return m_value != other.m_value; }
};
namespace Squares {
#define DEFINE_SQUARE(name) CX_INLINE Square name(Square::name)
// clang-format off
// Rank 1
DEFINE_SQUARE(A1); DEFINE_SQUARE(B1); DEFINE_SQUARE(C1); DEFINE_SQUARE(D1);
DEFINE_SQUARE(E1); DEFINE_SQUARE(F1); DEFINE_SQUARE(G1); DEFINE_SQUARE(H1);
// Rank 2
DEFINE_SQUARE(A2); DEFINE_SQUARE(B2); DEFINE_SQUARE(C2); DEFINE_SQUARE(D2);
DEFINE_SQUARE(E2); DEFINE_SQUARE(F2); DEFINE_SQUARE(G2); DEFINE_SQUARE(H2);
// Rank 3
DEFINE_SQUARE(A3); DEFINE_SQUARE(B3); DEFINE_SQUARE(C3); DEFINE_SQUARE(D3);
DEFINE_SQUARE(E3); DEFINE_SQUARE(F3); DEFINE_SQUARE(G3); DEFINE_SQUARE(H3);
// Rank 4
DEFINE_SQUARE(A4); DEFINE_SQUARE(B4); DEFINE_SQUARE(C4); DEFINE_SQUARE(D4);
DEFINE_SQUARE(E4); DEFINE_SQUARE(F4); DEFINE_SQUARE(G4); DEFINE_SQUARE(H4);
// Rank 5
DEFINE_SQUARE(A5); DEFINE_SQUARE(B5); DEFINE_SQUARE(C5); DEFINE_SQUARE(D5);
DEFINE_SQUARE(E5); DEFINE_SQUARE(F5); DEFINE_SQUARE(G5); DEFINE_SQUARE(H5);
// Rank 6
DEFINE_SQUARE(A6); DEFINE_SQUARE(B6); DEFINE_SQUARE(C6); DEFINE_SQUARE(D6);
DEFINE_SQUARE(E6); DEFINE_SQUARE(F6); DEFINE_SQUARE(G6); DEFINE_SQUARE(H6);
// Rank 7
DEFINE_SQUARE(A7); DEFINE_SQUARE(B7); DEFINE_SQUARE(C7); DEFINE_SQUARE(D7);
DEFINE_SQUARE(E7); DEFINE_SQUARE(F7); DEFINE_SQUARE(G7); DEFINE_SQUARE(H7);
// Rank 8
DEFINE_SQUARE(A8); DEFINE_SQUARE(B8); DEFINE_SQUARE(C8); DEFINE_SQUARE(D8);
DEFINE_SQUARE(E8); DEFINE_SQUARE(F8); DEFINE_SQUARE(G8); DEFINE_SQUARE(H8);
// clang-format on
#undef DEFINE_SQUARE
} // namespace Squares