I am developing a chess engine and am working on move generation. For example, here is my function for generating moves for the black knight:
/** Pseudolegal moves don't take check into account. */
std::vector<uint8_t>
generate_pseudolegal_bknight_moves(std::shared_ptr<Position> position,
uint8_t square) {
assert(VALID_SQUARE(square));
assert(position->mailbox[square] == B_KNIGHT);
uint8_t candidates[8] = {
NEXT_RANK(PREV_FILE(PREV_FILE(square))),
NEXT_RANK(NEXT_RANK(PREV_FILE(square))),
PREV_RANK(PREV_FILE(PREV_FILE(square))),
PREV_RANK(PREV_RANK(PREV_FILE(square))),
NEXT_RANK(NEXT_FILE(NEXT_FILE(square))),
NEXT_RANK(NEXT_RANK(NEXT_FILE(square))),
PREV_RANK(NEXT_FILE(NEXT_FILE(square))),
PREV_RANK(PREV_RANK(NEXT_FILE(square))),
};
std::vector<uint8_t> moves;
for (int i = 0; i < 8; i++) {
uint8_t candidate = candidates[i];
uint8_t piece = position->mailbox[candidate];
if (VALID_SQUARE(candidate) && (!IS_BLACK_PIECE(piece))) {
moves.push_back(candidate);
}
}
return moves;
}
The function for generating white knight moves is very similar, with only two terms (macros) changing:
B_KNIGHT
-> W_KNIGHT
, and IS_BLACK_PIECE
-> IS_WHITE_PIECE
.
I'd prefer not to essentially duplicate the move generation function for every piece, but have been doing it this way so far because it has the smallest runtime overhead.
I could include bool is_white
or something in the args and switch terms with a ternary is_white ? W_KNIGHT : B_KNIGHT
, but the conditional would add an overhead during runtime that wasn't there previously, and it doesn't seem that elegant. I was wondering if there is some compile-time feature that would help me have one function definition.
I suppose I also could use inline functions to try and reduce the amount of duplicated code, but I was wondering if there was any alternative better than that.
If you don't want overhead you can use a template parameter and if constexpr
:
enum class Color { WHITE, BLACK };
template <Color C> std::vector<uint8_t>
generate_pseudolegal_knight_moves(std::shared_ptr<Position> position,
uint8_t square) {
...
if constexpr (C == Color::WHITE) {
assert(position->mailbox[square] == W_KNIGHT);
} else {
assert(position->mailbox[square] == B_KNIGHT);
}
...
}
// Call
auto moves = generate_pseudolegal_knight_moves<Color::WHITE>(...);
The standard guarantees the condition will be evaluated on compile time, and the false branch will be discarded.