Skip to content

Commit

Permalink
Introduce MoveGen as structure
Browse files Browse the repository at this point in the history
  • Loading branch information
alex65536 committed Aug 5, 2023
1 parent 30cc593 commit 587e7a8
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 81 deletions.
4 changes: 2 additions & 2 deletions bench/core/bench_is_move_legal.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// This file is part of SoFCheck
//
// Copyright (c) 2020-2021 Alexander Kernozhitsky and SoFCheck contributors
// Copyright (c) 2020-2021, 2023 Alexander Kernozhitsky and SoFCheck contributors
//
// SoFCheck is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -34,7 +34,7 @@ inline void runCheckValid(benchmark::State &state, const char *fen) {

Board board = Board::fromFen(fen).unwrap();
Move moves[BUFSZ_MOVES];
size_t cnt = genAllMoves(board, moves);
size_t cnt = MoveGen(board).genAllMoves(moves);

for ([[maybe_unused]] auto _ : state) {
for (size_t i = 0; i < cnt; ++i) {
Expand Down
3 changes: 2 additions & 1 deletion selftest/sofcheck/intf.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ inline void moveStr(const Board &, const Move &mv, char *str) { SoFCore::moveToS

inline MoveList generateMoves(const Board &board) {
MoveList moves;
moves.count = static_cast<int>(SoFCore::genAllMoves(board, moves.moves));
SoFCore::MoveGen gen(board);
moves.count = static_cast<int>(gen.genAllMoves(moves.moves));
return moves;
}

Expand Down
101 changes: 64 additions & 37 deletions src/core/movegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,61 +329,88 @@ inline static size_t genImplInner(const Board &b, Move *list, const GenFilter fi
return size;
}

template <Color C, bool GenSimple, bool GenCaptures, bool GenSimplePromote>
inline static size_t genImpl(const Board &b, Move *list) {
static_assert(GenSimple || GenCaptures);
static_assert(GenSimple || !GenSimplePromote);

const auto king = b.kingPos(C);
const bitboard_t kingAttackers = cellAttackers(b, king, invert(C));
MoveGen::MoveGen(const Board &b) : b_(b), side_(b.side) {
const coord_t king = b.kingPos(side_);
const bitboard_t kingAttackers = cellAttackers(b, king, invert(side_));

if (!kingAttackers) {
// No check.
return genImplInner<C, GenSimple, GenCaptures, GenSimplePromote>(b, list, SimpleGenFilter{});
check_ = CheckKind::None;
return;
}

if (!SoFUtil::hasZeroOrOneBit(kingAttackers)) {
// Double check or more. We can move only the king.
return genKing<C, GenSimple, GenCaptures>(b, list);
check_ = CheckKind::Double;
return;
}

// Regular check.
if constexpr (!GenSimple && GenCaptures) {
return genImplInner<C, GenSimple, GenCaptures, GenSimplePromote>(b, list,
CheckGenFilter{kingAttackers});
} else {
const auto checker = static_cast<coord_t>(SoFUtil::getLowest(kingAttackers));
const auto bbMask = Private::between(checker, king) | kingAttackers;
return genImplInner<C, GenSimple, GenCaptures, GenSimplePromote>(b, list,
CheckGenFilter{bbMask});
check_ = CheckKind::Single;
const auto checker = static_cast<coord_t>(SoFUtil::getLowest(kingAttackers));
checkMask_ = Private::between(checker, king) | kingAttackers;
}

template <Color C, bool GenSimple, bool GenCaptures, bool GenSimplePromote>
size_t MoveGen::genImpl(Move *list) const {
static_assert(GenSimple || GenCaptures);
static_assert(GenSimple || !GenSimplePromote);

switch (check_) {
case CheckKind::None: {
return genImplInner<C, GenSimple, GenCaptures, GenSimplePromote>(b_, list, SimpleGenFilter{});
}
case CheckKind::Single: {
return genImplInner<C, GenSimple, GenCaptures, GenSimplePromote>(b_, list,
CheckGenFilter{checkMask_});
}
case CheckKind::Double: {
// Double check or more. We can move only the king.
return genKing<C, GenSimple, GenCaptures>(b_, list);
}
}

SOF_UNREACHABLE();
}

template <Color C>
size_t MoveGen::genSimplePromotesImpl(Move *list) const {
switch (check_) {
case CheckKind::None: {
return genPawnSimple<C, PromoteGenPolicy::PromoteOnly>(b_, list, SimpleGenFilter{});
}
case CheckKind::Single: {
return genPawnSimple<C, PromoteGenPolicy::PromoteOnly>(b_, list, CheckGenFilter{checkMask_});
}
case CheckKind::Double: {
// Double check or more. We can move only the king.
return 0;
}
}

SOF_UNREACHABLE();
}

size_t genCaptures(const Board &b, Move *list) {
return (b.side == Color::White) ? genImpl<Color::White, false, true, false>(b, list)
: genImpl<Color::Black, false, true, false>(b, list);
size_t MoveGen::genCaptures(Move *list) const {
return (side_ == Color::White) ? genImpl<Color::White, false, true, false>(list)
: genImpl<Color::Black, false, true, false>(list);
}

size_t genAllMoves(const Board &b, Move *list) {
return (b.side == Color::White) ? genImpl<Color::White, true, true, true>(b, list)
: genImpl<Color::Black, true, true, true>(b, list);
size_t MoveGen::genAllMoves(Move *list) const {
return (side_ == Color::White) ? genImpl<Color::White, true, true, true>(list)
: genImpl<Color::Black, true, true, true>(list);
}

size_t genSimpleMoves(const Board &b, Move *list) {
return (b.side == Color::White) ? genImpl<Color::White, true, false, true>(b, list)
: genImpl<Color::Black, true, false, true>(b, list);
size_t MoveGen::genSimpleMoves(Move *list) const {
return (side_ == Color::White) ? genImpl<Color::White, true, false, true>(list)
: genImpl<Color::Black, true, false, true>(list);
}

size_t genSimpleMovesNoPromote(const Board &b, Move *list) {
return (b.side == Color::White) ? genImpl<Color::White, true, false, false>(b, list)
: genImpl<Color::Black, true, false, false>(b, list);
size_t MoveGen::genSimpleMovesNoPromote(Move *list) const {
return (side_ == Color::White) ? genImpl<Color::White, true, false, false>(list)
: genImpl<Color::Black, true, false, false>(list);
}

size_t genSimplePromotes(const Board &b, Move *list) {
return (b.side == Color::White) ? genPawnSimple<Color::White, PromoteGenPolicy::PromoteOnly>(
b, list, SimpleGenFilter{})
: genPawnSimple<Color::Black, PromoteGenPolicy::PromoteOnly>(
b, list, SimpleGenFilter{});
size_t MoveGen::genSimplePromotes(Move *list) const {
return (side_ == Color::White) ? genSimplePromotesImpl<Color::White>(list)
: genSimplePromotesImpl<Color::Black>(list);
}

template <Color C>
Expand Down
53 changes: 39 additions & 14 deletions src/core/movegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,45 @@ inline bitboard_t cellAttackers(const Board &b, const coord_t coord, const Color
// legal, and then unmake it if it's illegal.
bool isMoveLegal(const Board &b);

// All these functions generate all the legal moves plus some pseudo-legal moves. Thus, after
// applying some of the generated moves, the king may become under check, but are otherwise valid
// according to the rules of chess. The arguments are passed in the following way:
//
// - `board` is the current position
// - `list` is an output list in which the moves will be written
// - the return value indicates the number of moves generated
//
// To generate only legal moves you can use `isMoveLegal()` function
size_t genAllMoves(const Board &b, Move *list);
size_t genSimpleMoves(const Board &b, Move *list);
size_t genSimpleMovesNoPromote(const Board &b, Move *list);
size_t genSimplePromotes(const Board &b, Move *list);
size_t genCaptures(const Board &b, Move *list);
class MoveGen {
public:
// Creates a move generator for the board `b`. The provided board must not be changed from the
// outside, though making some moves and reverting them between the calls is allowed.
explicit MoveGen(const Board &b);

// Returns a pointer to the board, for which the moves are being generated.
const Board &board() const { return b_; }

// All these functions generate all the legal moves plus some pseudo-legal moves. Thus, after
// applying some of the generated moves, the king may become under check, but are otherwise valid
// according to the rules of chess. The moves are written into `list`, and the return value
// indicates the number of moves generated.
//
// To generate only legal moves you can use `isMoveLegal()` function
size_t genAllMoves(Move *list) const;
size_t genSimpleMoves(Move *list) const;
size_t genSimpleMovesNoPromote(Move *list) const;
size_t genSimplePromotes(Move *list) const;
size_t genCaptures(Move *list) const;

private:
enum class CheckKind : int8_t {
None = 0,
Single = 1,
Double = 2,
};

const Board &b_;
bitboard_t checkMask_ = 0;
CheckKind check_;
Color side_;

template <Color, bool, bool, bool>
size_t genImpl(Move *list) const;

template <Color>
size_t genSimplePromotesImpl(Move *list) const;
};

// Upper bound for total number of pseudo-legal moves in any valid position. You can use it as a
// buffer size for `genAllMoves()`.
Expand Down
30 changes: 16 additions & 14 deletions src/core/test/selftest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ static bool boardsBitCompare(const Board &a, const Board &b) {
sizeof(Board)) == 0;
}

static size_t filterLegalMoves(Board &b, Move *moves, size_t origSize) {
static size_t filterLegalMoves(Board b, Move *moves, size_t origSize, const char *type) {
size_t newSize = 0;
for (size_t i = 0; i < origSize; ++i) {
const Move move = moves[i];
if (!move.isWellFormed(b.side)) {
panic("Move \"" + moveToStr(moves[i]) + "\" generated by genAllMoves() is not well-formed");
panic("Move \"" + moveToStr(moves[i]) + "\" of type " + type + " is not well-formed");
}
const Board saved = b;
MovePersistence p = moveMake(b, move);
Expand All @@ -92,13 +92,14 @@ static size_t filterLegalMoves(Board &b, Move *moves, size_t origSize) {
}

template <typename GenFunc>
static size_t genOnlyLegal(GenFunc func, Board &b, Move *moves, const char *type, size_t limit) {
const size_t size = func(b, moves);
static size_t genOnlyLegal(GenFunc func, const MoveGen &g, Move *moves, const char *type,
size_t limit) {
const size_t size = (g.*func)(moves);
if (size > limit) {
panic("Number of generated moves of type " + std::string(type) + "exceeds limit " +
std::to_string(limit));
}
return filterLegalMoves(b, moves, size);
return filterLegalMoves(g.board(), moves, size, type);
}

void runSelfTest(Board b) {
Expand Down Expand Up @@ -140,23 +141,24 @@ void runSelfTest(Board b) {

// Try to generate moves in total and compare the result if we generate simple moves and captures
// separately
MoveGen gen(b);
Move moves[1500];
const size_t moveCnt = genOnlyLegal(genAllMoves, b, moves, "ALL", BUFSZ_MOVES);
const size_t moveCnt = genOnlyLegal(&MoveGen::genAllMoves, gen, moves, "ALL", BUFSZ_MOVES);
Move movesSeparate[1500];
const size_t simpleCnt =
genOnlyLegal(genSimpleMovesNoPromote, b, movesSeparate, "SIMPLE_NO_PROMOTE", BUFSZ_MOVES);
const size_t promoteCnt = genOnlyLegal(genSimplePromotes, b, movesSeparate + simpleCnt, "PROMOTE",
BUFSZ_SIMPLE_PROMOTES);
const size_t captureCnt = genOnlyLegal(genCaptures, b, movesSeparate + simpleCnt + promoteCnt,
"CAPTURE", BUFSZ_CAPTURES);
const size_t simpleCnt = genOnlyLegal(&MoveGen::genSimpleMovesNoPromote, gen, movesSeparate,
"SIMPLE_NO_PROMOTE", BUFSZ_MOVES);
const size_t promoteCnt = genOnlyLegal(&MoveGen::genSimplePromotes, gen, movesSeparate + simpleCnt,
"PROMOTE", BUFSZ_SIMPLE_PROMOTES);
const size_t captureCnt = genOnlyLegal(
&MoveGen::genCaptures, gen, movesSeparate + simpleCnt + promoteCnt, "CAPTURE", BUFSZ_CAPTURES);
if (simpleCnt + promoteCnt + captureCnt != moveCnt) {
panic(
"Moves generated by genAllMoves() differ from genSimpleMovesNoPromote() + "
"genSimplePromotes() + genCaptures()");
}
Move movesAllSimple[1500];
const size_t allSimpleCnt =
genOnlyLegal(genSimpleMoves, b, movesAllSimple, "SIMPLE", BUFSZ_MOVES);
genOnlyLegal(&MoveGen::genSimpleMoves, gen, movesAllSimple, "SIMPLE", BUFSZ_MOVES);
if (simpleCnt + promoteCnt != allSimpleCnt) {
panic(
"Moves generated by genSimpleMoves() differ from genSimpleMovesNoPromote() + "
Expand Down Expand Up @@ -214,7 +216,7 @@ void runSelfTest(Board b) {
}
}
}
validMoves.resize(filterLegalMoves(b, validMoves.data(), validMoves.size()));
validMoves.resize(filterLegalMoves(b, validMoves.data(), validMoves.size(), "VALID"));
std::sort(validMoves.begin(), validMoves.end(), cmpMoves);
if (!std::equal(validMoves.begin(), validMoves.end(), moves, moves + moveCnt)) {
panic("Valid move list and generated move list mismatch");
Expand Down
4 changes: 2 additions & 2 deletions src/search/private/job_runner.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// This file is part of SoFCheck
//
// Copyright (c) 2020-2022 Alexander Kernozhitsky and SoFCheck contributors
// Copyright (c) 2020-2023 Alexander Kernozhitsky and SoFCheck contributors
//
// SoFCheck is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -87,7 +87,7 @@ class Stats {

static Move pickRandomMove(Board board) {
Move moves[SoFCore::BUFSZ_MOVES];
const size_t count = genAllMoves(board, moves);
const size_t count = SoFCore::MoveGen(board).genAllMoves(moves);
SoFUtil::randomShuffle(moves, moves + count);
for (size_t i = 0; i < count; ++i) {
const Move move = moves[i];
Expand Down
16 changes: 8 additions & 8 deletions src/search/private/move_picker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ void sortMvvLva(const Board &board, Move *moves, const size_t count) {
}

QuiescenseMovePicker::QuiescenseMovePicker(const Board &board)
: board_(board), moveCount_(genCaptures(board, moves_)) {
: gen_(board), moveCount_(gen_.genCaptures(moves_)) {
sortMvvLva(board, moves_, moveCount_);
}

void QuiescenseMovePicker::addSimplePromotes() {
moveCount_ = genSimplePromotes(board_, moves_);
moveCount_ = gen_.genSimplePromotes(moves_);
movePosition_ = 0;
std::sort(moves_, moves_ + moveCount_, [](const Move m1, const Move m2) {
return static_cast<int8_t>(m1.kind) > static_cast<int8_t>(m2.kind);
Expand Down Expand Up @@ -81,13 +81,13 @@ void MovePicker::nextStage() {
}
case MovePickerStage::Capture: {
// Generate captures and sort them by MVV/LVA
moveCount_ = genCaptures(board_, moves_);
sortMvvLva(board_, moves_, moveCount_);
moveCount_ = gen_.genCaptures(moves_);
sortMvvLva(gen_.board(), moves_, moveCount_);
break;
}
case MovePickerStage::SimplePromote: {
// Generate simple promotes and sort them by promoting piece
moveCount_ = genSimplePromotes(board_, moves_);
moveCount_ = gen_.genSimplePromotes(moves_);
std::sort(moves_, moves_ + moveCount_, [](const Move m1, const Move m2) {
return static_cast<int8_t>(m1.kind) > static_cast<int8_t>(m2.kind);
});
Expand All @@ -96,11 +96,11 @@ void MovePicker::nextStage() {
case MovePickerStage::Killer: {
// Try two killers if they are valid
const Move firstKiller = killers_.first();
if (isValidKiller(board_, firstKiller)) {
if (isValidKiller(gen_.board(), firstKiller)) {
moves_[moveCount_++] = firstKiller;
}
const Move secondKiller = killers_.second();
if (isValidKiller(board_, secondKiller)) {
if (isValidKiller(gen_.board(), secondKiller)) {
moves_[moveCount_++] = secondKiller;
}
savedKillers_[0] = firstKiller;
Expand All @@ -109,7 +109,7 @@ void MovePicker::nextStage() {
}
case MovePickerStage::History: {
// Sort the moves by history heuristic
moveCount_ = genSimpleMovesNoPromote(board_, moves_);
moveCount_ = gen_.genSimpleMovesNoPromote(moves_);
std::sort(moves_, moves_ + moveCount_,
[this](const Move m1, const Move m2) { return history_[m1] > history_[m2]; });
for (size_t i = 0; i < moveCount_; ++i) {
Expand Down
6 changes: 3 additions & 3 deletions src/search/private/move_picker.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class MovePicker {
MovePicker(const SoFCore::Board &board, const SoFCore::Move hashMove, const KillerLine &killers,
const HistoryTable &history)
: hashMove_(hashMove),
board_(board),
gen_(board),
killers_(killers),
history_(history),
savedKillers_{SoFCore::Move::null(), SoFCore::Move::null()} {}
Expand All @@ -80,7 +80,7 @@ class MovePicker {

MovePickerStage stage_ = MovePickerStage::Start;
SoFCore::Move hashMove_;
const SoFCore::Board &board_;
SoFCore::MoveGen gen_;
const KillerLine &killers_;
const HistoryTable &history_;
SoFCore::Move moves_[SoFCore::BUFSZ_MOVES];
Expand Down Expand Up @@ -115,7 +115,7 @@ class QuiescenseMovePicker {

void addSimplePromotes();

const SoFCore::Board &board_;
SoFCore::MoveGen gen_;
SoFCore::Move moves_[std::max(SoFCore::BUFSZ_CAPTURES, SoFCore::BUFSZ_SIMPLE_PROMOTES)];
size_t moveCount_;
size_t movePosition_ = 0;
Expand Down

0 comments on commit 587e7a8

Please sign in to comment.