Compare commits

...

22 Commits

Author SHA1 Message Date
cb0fcc4702 update 2022-12-23 23:57:29 +01:00
87adca7e66 Implement Search 2022-12-23 18:34:34 +01:00
bcf4d66c49 Add placeholder engine and search 2022-12-23 09:44:57 +01:00
a4d5dbbc84 Cleanup 2022-12-23 00:30:46 +01:00
b819e27f55 Add compiler optimizations for release builds 2022-12-23 00:05:28 +01:00
9552678efe [Board] Implement pseudoLegalMoves 2022-12-23 00:05:18 +01:00
0d7f030634 [MoveGenerator] Add generateAttackedSquares, fix castling 2022-12-22 23:52:53 +01:00
151da7eb51 ckeanup 2022-12-22 23:52:21 +01:00
2c31de83b7 [MoveGenerator] Implement generateKnightMoves 2022-12-22 23:35:07 +01:00
79b5dd4a86 [MoveGenerator] cleanup 2022-12-22 23:34:59 +01:00
8d028ba087 Remove pointers, add BoardState 2022-12-22 22:54:26 +01:00
71d90918b3 [CastlingRights] Add Kingside and QueenSide 2022-12-22 22:35:11 +01:00
256b511569 [BitBoard] Implement bishopAttacks and queenAttacks 2022-12-22 22:34:44 +01:00
4f96d189fa [Board] Initial pseudoLegalMovesFrom 2022-12-22 18:38:16 +01:00
b64530cb3d [Board] Implement MoveGenerator 2022-12-22 18:37:52 +01:00
e18146e00c [BitBoard] Add castlingMoves 2022-12-22 18:36:36 +01:00
db20e49f35 [BitBoard] Improve kingAttacks 2022-12-22 18:36:16 +01:00
cd21c16da7 [BitBoard] Remove unused genShift 2022-12-22 18:35:58 +01:00
91624c62c1 [Square] Make constructor public 2022-12-22 18:33:57 +01:00
30107cc6fa [Piece] Add array with pieces for promotion 2022-12-22 18:33:28 +01:00
fadfab165a [MoveGenerator] Add MoveGenerator 2022-12-22 18:31:13 +01:00
e1d216f06b [BitBoard] Add bishop and pawn methods 2022-12-22 15:58:11 +01:00
24 changed files with 897 additions and 70 deletions

22
BasicEngine.cpp Normal file
View File

@@ -0,0 +1,22 @@
#include <iostream>
#include "BasicEngine.hpp"
#include "Search.hpp"
std::string BasicEngine::name() const {
return mName;
}
std::string BasicEngine::version() const {
return mVersion;
}
std::string BasicEngine::author() const {
return mAuthor;
}
void BasicEngine::newGame() {
}
PrincipalVariation BasicEngine::pv(const Board &board, const TimeInfo::Optional &timeInfo) {
(void)board;
(void)timeInfo;
auto pv =Search::start(board);
return pv;
}

23
BasicEngine.hpp Normal file
View File

@@ -0,0 +1,23 @@
#ifndef CHESS_ENGINE_BASICENGINE_HPP
#define CHESS_ENGINE_BASICENGINE_HPP
#include "Engine.hpp"
class BasicEngine final : public Engine {
public:
std::string name() const override;
std::string version() const override;
std::string author() const override;
void newGame() override;
PrincipalVariation pv(const Board &board, const TimeInfo::Optional &timeInfo) override;
private:
std::string mName = "Egon++";
std::string mVersion = "1.0~Egon+C";
std::string mAuthor = "Bols";
};
#endif //CHESS_ENGINE_BASICENGINE_HPP

View File

@@ -25,13 +25,6 @@ std::ostream &operator<<(std::ostream &os, const BitBoard &board) {
return os;
}
BitBoard BitBoard::getRank(int r) {
return (genShift(1ULL, (r + 1) * 8) - 1) & genShift(~1ULL, r * 8 - 1);
}
BitBoard BitBoard::genShift(BitBoard x, const int s) {
return (s < 0) ? (x >> -s) : (s > 63) ? x : (x << s);
}
BitBoard BitBoard::northFill() const {
BitBoard result(mBoard);
result |= (result << 8);
@@ -52,11 +45,88 @@ BitBoard BitBoard::fileFill() const {
return northFill() | southFill();
}
BitBoard BitBoard::kingAttacks(const BitBoard bb) {
BitBoard result = bb.east() | bb.west() | bb;
BitBoard BitBoard::kingMoves(const BitBoard kings) {
BitBoard result = kings.east() | kings.west() | kings;
result |= (result.north() | result.south());
result ^= bb;
result ^= kings;
return result;
}
BitBoard BitBoard::castlingMoves(BitBoard kings, BitBoard rooks, BitBoard empty) {
kings |= (kings.east() | kings.west()) & empty;
kings |= (kings.east() | kings.west()) & empty;
kings |= kings.west() & empty;
rooks |= (rooks.east() | rooks.west()) & empty;
rooks |= (rooks.east() | rooks.west()) & empty;
rooks |= (rooks.east()) & empty;
return kings & rooks & CastlingSquares;
}
BitBoard BitBoard::bishopAttacks(BitBoard bishops, BitBoard empty) {
BitBoard result = 0;
empty ^= bishops;
BitBoard diag1 = (bishops.northWest() | bishops.southEast() | bishops) & empty;
BitBoard diag2 = (bishops.northEast() | bishops.southWest() | bishops) & empty;
for (int i = 0; i < 6; i++) {
result |= (diag1 | diag2);
diag1 = (diag1.northWest() | diag1.southEast()) & empty;
diag2 = (diag2.northEast() | diag2.southWest()) & empty;
}
return (result | diag1.northWest() | diag1.southEast() | diag2.northEast() | diag2.southWest()) & ~bishops;
}
BitBoard BitBoard::rookAttacks(BitBoard rooks, BitBoard empty) {
BitBoard result = 0;
empty ^= rooks;
BitBoard dir1 = (rooks.north() | rooks.south() | rooks) & empty;
BitBoard dir2 = (rooks.east() | rooks.west() | rooks) & empty;
for (int i = 0; i < 6; i++) {
result |= (dir1 | dir2);
dir1 = (dir1.north() | dir1.south()) & empty;
dir2 = (dir2.east() | dir2.west()) & empty;
}
return (result | dir1.north() | dir1.south() | dir2.east() | dir2.west()) & ~rooks;
}
BitBoard BitBoard::queenAttacks(BitBoard queens, BitBoard empty) {
return rookAttacks(queens, empty) | bishopAttacks(queens, empty);
}
BitBoard BitBoard::knightMoves(BitBoard knights) {
BitBoard east, west, result;
east = knights.east();
west = knights.west();
result = (east | west) << 16;
result |= (east | west) >> 16;
east = east.east();
west = west.west();
result |= (east | west) << 8;
result |= (east | west) >> 8;
return result;
}
BitBoard BitBoard::pawnNorthAttacks(BitBoard pawns, BitBoard targets) {
return (pawns.northEast() | pawns.northWest()) & targets;
}
BitBoard BitBoard::pawnNorthMoves(BitBoard pawns, BitBoard empty) {
pawns = pawns.north() & empty;
return (pawns | (pawns.north() & Rank4)) & empty;
}
BitBoard BitBoard::pawnSouthAttacks(BitBoard pawns, BitBoard targets) {
return (pawns.southEast() | pawns.southWest()) & targets;
}
BitBoard BitBoard::pawnSouthMoves(BitBoard pawns, BitBoard empty) {
pawns = pawns.south() & empty;
return (pawns | (pawns.south() & Rank5)) & empty;
}

View File

@@ -14,6 +14,14 @@ enum DefinedBoards : uint64_t {
FFile = AFile << 5,
GFile = AFile << 6,
HFile = AFile << 7,
Rank1 = 0x00000000000000FF,
Rank2 = Rank1 << 8,
Rank3 = Rank1 << 16,
Rank4 = Rank1 << 24,
Rank5 = Rank1 << 32,
Rank6 = Rank1 << 40,
Rank7 = Rank1 << 48,
Rank8 = Rank1 << 56,
WhiteCastlingRank = (1ULL << A2) - 1,
BlackCastlingRank = (~1ULL << H7),
CastlingRanks = WhiteCastlingRank | BlackCastlingRank,
@@ -31,6 +39,9 @@ public:
explicit constexpr operator bool() const {
return mBoard != 0;
}
explicit constexpr operator unsigned int() const {
return mBoard;
}
explicit constexpr operator unsigned long() const {
return mBoard;
}
@@ -98,20 +109,29 @@ public:
BitBoard southFill() const;
BitBoard fileFill() const;
static BitBoard kingAttacks(BitBoard bb);
static BitBoard bishopAttacks(BitBoard pos, BitBoard empty);
static BitBoard rookAttacks(BitBoard rooks, BitBoard empty);
static BitBoard queenAttacks(BitBoard queens, BitBoard empty);
static BitBoard kingMoves(const BitBoard kings);
static BitBoard castlingMoves(BitBoard kings, BitBoard rooks, BitBoard empty);
static BitBoard knightMoves(BitBoard knights);
static BitBoard pawnNorthAttacks(BitBoard pawns, BitBoard targets);
static BitBoard pawnSouthAttacks(BitBoard pawns, BitBoard targets);
static BitBoard pawnNorthMoves(BitBoard pawns, BitBoard empty);
static BitBoard pawnSouthMoves(BitBoard pawns, BitBoard empty);
static BitBoard fromIndex(unsigned i);
static BitBoard getRank(int r);
// Returns the number of trailing 0-bits in b.
// WARN: Check for 0!
int lsb() const;
unsigned lsb() const;
unsigned pop();
constexpr int count() const;
private:
U64 mBoard = {};
static BitBoard genShift(BitBoard x, int s);
U64 mBoard = 0;
};
// Relational operators
@@ -255,8 +275,20 @@ inline BitBoard BitBoard::southEast() const {
inline BitBoard BitBoard::southWest() const {
return (mBoard >> 9) & ~HFile;
}
inline int BitBoard::lsb() const {
inline unsigned BitBoard::lsb() const {
return __builtin_ctzll(mBoard);
}
inline unsigned BitBoard::pop() {
unsigned i = lsb();
mBoard &= mBoard - 1;
return i;
}
constexpr int BitBoard::count() const {
return __builtin_popcountll(mBoard);
}
#endif //CHESS_ENGINE_BITBOARD_HPP

View File

@@ -1,22 +1,19 @@
#include "Board.hpp"
#include "BoardState.hpp"
#include <ostream>
#include <cassert>
#include <cmath>
#include <bitset>
#include <algorithm>
#include <iostream>
Board::Board() {
}
void Board::setPiece(const Square &square, const Piece::Optional &piece) {
if (!piece.has_value())
return;
auto index = square.index();
for (auto &bb : mPieceBBs) {
bb.clear(index);
for (int i = 0; i < BB_NUM; i++) {
mPieceBBs[i].clear(index);
}
mPieceBBs[toIndex(piece->type())].set(index);
@@ -35,6 +32,42 @@ Piece::Optional Board::piece(const Square &square) const {
return Piece(c, t);
}
int Board::evaluate() const {
int score = evaluate(mTurn);
score -= evaluate(!mTurn);
return score;
}
int Board::evaluate(const PieceColor color) const {
int score = 0;
BitBoard bb;
BitBoard colorMask = mPieceBBs[toIndex(color)];
for (int i = 0; i < 6; i++) {
bb = mPieceBBs[i+2] & colorMask;
score += Piece::PieceValue[i] * bb.count();
}
// BoardState bs = BoardState(&mPieceBBs, mOccupiedBB, mTurn, mCR, mEPS);
// score += round(mMobilityWeight * MoveGenerator::Mobility(bs, color));
return score;
}
bool Board::isCheckMate() const {
auto moves = MoveVec();
pseudoLegalMoves(moves);
if (!moves.empty())
return false;
BoardState bs = BoardState(&mPieceBBs, mOccupiedBB, mTurn, mCR, mEPS);
auto attacked = MoveGenerator::generateAttackedSquares(bs, ~mOccupiedBB, !mTurn);
return attacked & mPieceBBs[toIndex(PieceType::King)] & mPieceBBs[toIndex(mTurn)];
}
void Board::setTurn(PieceColor turn) {
mTurn = turn;
}
@@ -146,12 +179,37 @@ void Board::handlePawnDoubleAdvance(const Move &move, BitBoard bb, const Piece &
}
void Board::pseudoLegalMoves(MoveVec &moves) const {
(void) moves;
auto pieces = mPieceBBs[toIndex(mTurn)];
while (pieces) {
auto sq = Square(pieces.pop());
pseudoLegalMovesFrom(sq, moves);
}
}
void Board::pseudoLegalMovesFrom(const Square &from, Board::MoveVec &moves) const {
(void) from;
(void) moves;
auto fromBB = BitBoard::fromIndex(from.index());
if (!(fromBB & mPieceBBs[toIndex(mTurn)])) {
return;
}
BoardState bs = BoardState(&mPieceBBs, mOccupiedBB, mTurn, mCR, mEPS);
switch (pieceType(fromBB)) {
case PieceType::Pawn: MoveGenerator::generatePawnMoves(bs, from, moves);
break;
case PieceType::Knight: MoveGenerator::generateKnightMoves(bs, from, moves);
break;
case PieceType::Bishop: MoveGenerator::generateBishopMoves(bs, from, moves);
break;
case PieceType::Rook: MoveGenerator::generateRookMoves(bs, from, moves);
break;
case PieceType::Queen: MoveGenerator::generateQueenMoves(bs, from, moves);
break;
case PieceType::King: MoveGenerator::generateKingMoves(bs, from, moves);
break;
}
}
void Board::handleCastlingRights(const Piece &piece, const BitBoard &bb) {
if (piece.type() != PieceType::King && piece.type() != PieceType::Rook) {

View File

@@ -6,6 +6,7 @@
#include "Move.hpp"
#include "CastlingRights.hpp"
#include "BitBoard.hpp"
#include "MoveGenerator.hpp"
#include <optional>
#include <iosfwd>
@@ -19,10 +20,12 @@ public:
using Optional = std::optional<Board>;
using MoveVec = std::vector<Move>;
Board();
Board() = default;
void setPiece(const Square &square, const Piece::Optional &piece);
Piece::Optional piece(const Square &square) const;
std::vector<Piece> pieces(PieceColor color) const;
int evaluate() const;
void setTurn(PieceColor turn);
PieceColor turn() const;
void setCastlingRights(CastlingRights cr);
@@ -35,26 +38,32 @@ public:
void pseudoLegalMoves(MoveVec &moves) const;
void pseudoLegalMovesFrom(const Square &from, MoveVec &moves) const;
static constexpr int toIndex(PieceType t) {
return static_cast<int>(t);
}
static constexpr int toIndex(PieceColor c) {
return static_cast<int>(c);
}
bool isCheckMate() const;
private:
BitBoard mPieceBBs[BB_NUM] = {};
BitBoard mOccupiedBB = 0;
BitBoard mOccupiedBB = BitBoard(0);
PieceColor mTurn = PieceColor::White;
PieceColor mTurn = PieceColor(PieceColor::White);
CastlingRights mCR = CastlingRights::None;
std::optional<Square> mEPS;
constexpr static const double mMobilityWeight = 0.1;
void handleCastlingRights(const Piece &piece, const BitBoard &bb);
// Check if the move is castling without checking the rights or validity.
static bool isMoveCastling(const BitBoard &from, const BitBoard &to, const Piece &piece);
static inline int toIndex(PieceType t) {
return static_cast<int>(t);
}
static inline int toIndex(PieceColor c) {
return static_cast<int>(c);
}
inline PieceColor pieceColor(const BitBoard &mask) const {
auto color = PieceColor::White;
if (!(mPieceBBs[static_cast<unsigned>(color)] & mask)) {
@@ -77,8 +86,8 @@ private:
void handlePawnDoubleAdvance(const Move &move, BitBoard bb, const Piece &movedPiece);
void handleEnPassant(const Move &move, const Piece &movedPiece);
int evaluate(const PieceColor color) const;
};
std::ostream &operator<<(std::ostream &os, const Board &board);
#endif

42
BoardState.hpp Normal file
View File

@@ -0,0 +1,42 @@
#ifndef CHESS_ENGINE_BOARDSTATE_HPP
#define CHESS_ENGINE_BOARDSTATE_HPP
#include "Piece.hpp"
#include "BitBoard.hpp"
#include "Board.hpp"
#define BB_NUM 8
struct BoardState {
BoardState(const BitBoard (*const pieceBBs)[8],
const BitBoard occupiedBB,
const PieceColor turn,
const CastlingRights cr,
const std::optional<Square> eps)
: pieceBBs(pieceBBs),
occupiedBB(occupiedBB),
turn(turn),
cr(cr),
eps(eps) {
}
const BitBoard (*const pieceBBs)[8];
const BitBoard occupiedBB;
const PieceColor turn;
const CastlingRights cr;
const std::optional<Square> eps;
inline PieceType pieceType(const BitBoard &mask) const {
for (int i = 2; i < BB_NUM; i++) {
if ((*pieceBBs)[i] & mask) {
return static_cast<PieceType>(i);
}
}
// Should not happen
return static_cast<PieceType>(0);
}
};
#endif //CHESS_ENGINE_BOARDSTATE_HPP

View File

@@ -24,6 +24,10 @@ else ()
add_compile_options(-Wall -Wextra -pedantic -Werror)
endif ()
if (CMAKE_BUILD_TYPE STREQUAL "Release")
add_compile_options(-O3 -march=native -funroll-loops)
endif()
add_library(cplchess_lib OBJECT
Square.cpp
Move.cpp
@@ -36,6 +40,9 @@ add_library(cplchess_lib OBJECT
EngineFactory.cpp
Uci.cpp
BitBoard.cpp
MoveGenerator.cpp
Search.cpp
BasicEngine.cpp
)
target_include_directories(cplchess_lib PUBLIC .)

View File

@@ -11,6 +11,8 @@ enum class CastlingRights {
BlackQueenside = 1 << 3,
White = WhiteKingside | WhiteQueenside,
Black = BlackKingside | BlackQueenside,
KingSide = BlackKingside | WhiteKingside,
QueenSide = BlackQueenside | WhiteQueenside,
All = White | Black
};

View File

@@ -1,5 +1,8 @@
#include "EngineFactory.hpp"
#include <memory>
#include "BasicEngine.hpp"
std::unique_ptr<Engine> EngineFactory::createEngine() {
return nullptr;
return std::make_unique<BasicEngine>();
}

View File

@@ -3,10 +3,21 @@
#include <ostream>
#include <iostream>
Move::Move(const Square &from, const Square &to, const std::optional<PieceType> &promotion)
: mFrom(from), mTo(to), mPromotion(promotion) {
Move::Move(const Square &from, const Square &to) : mFrom(from), mTo(to) {
}
Move::Move(const Square &from, const Square &to, const std::optional<PieceType> &promotion, unsigned score)
: mFrom(from), mTo(to), mPromotion(promotion) {
mScore -= score;
}
Move::Move(const Square &from, const Square &to, unsigned int score) : mFrom(from), mTo(to) {
mScore -= score;
}
Move::Optional Move::fromUci(const std::string &uci) {
if (uci.length() < 4 || uci.length() > 5)
return std::nullopt;
@@ -48,17 +59,31 @@ std::ostream &operator<<(std::ostream &os, const Move &move) {
}
bool operator<(const Move &lhs, const Move &rhs) {
if (!(lhs.from() == rhs.from()))
return lhs.from() < rhs.from();
if (lhs.mScore != rhs.mScore) {
return lhs.mScore < rhs.mScore;
}
if (!(lhs.to() == rhs.to()))
return lhs.to() < rhs.to();
if (lhs.mFrom.index() != rhs.mFrom.index()) {
return lhs.mFrom.index() < rhs.mFrom.index();
}
if (lhs.mTo.index() != rhs.mTo.index()) {
return lhs.mTo.index() < rhs.mTo.index();
}
return lhs.promotion() < rhs.promotion();
}
bool operator==(const Move &lhs, const Move &rhs) {
return lhs.from() == rhs.from()
return lhs.score() == rhs.score()
&& lhs.from() == rhs.from()
&& lhs.to() == rhs.to()
&& lhs.promotion() == rhs.promotion();
}
unsigned int Move::score() const {
return mScore;
}
void Move::setScore(unsigned int score) {
mScore = score;
}

View File

@@ -7,14 +7,17 @@
#include <iosfwd>
#include <optional>
#include <string>
#include <limits>
class Move {
public:
using Optional = std::optional<Move>;
Move(const Square &from, const Square &to,
const std::optional<PieceType> &promotion = std::nullopt);
explicit Move(const Square &from, const Square &to);
explicit Move(const Square &from, const Square &to,
const std::optional<PieceType> &promotion, unsigned score = 0);
explicit Move(const Square &from, const Square &to, unsigned score);
static Optional fromUci(const std::string &uci);
@@ -22,16 +25,23 @@ public:
Square to() const;
std::optional<PieceType> promotion() const;
friend bool operator<(const Move &lhs, const Move &rhs);
friend bool operator==(const Move &lhs, const Move &rhs);
private:
const Square mFrom;
const Square mTo;
const std::optional<PieceType> mPromotion;
Square mFrom;
Square mTo;
std::optional<PieceType> mPromotion = std::nullopt;
unsigned mScore = std::numeric_limits<unsigned>::max();
public:
unsigned int score() const;
void setScore(unsigned int mScore);
};
std::ostream &operator<<(std::ostream &os, const Move &move);
// Needed for std::map, std::set
bool operator<(const Move &lhs, const Move &rhs);
bool operator==(const Move &lhs, const Move &rhs);
#endif

233
MoveGenerator.cpp Normal file
View File

@@ -0,0 +1,233 @@
#include <iostream>
#include "MoveGenerator.hpp"
#include "Board.hpp"
#include "BoardState.hpp"
void MoveGenerator::generatePawnMoves(const BoardState &bs, const Square &from, MoveVec &moves) {
BitBoard targets = 0;
if (bs.eps.has_value()) {
targets |= BitBoard::fromIndex(bs.eps.value().index());
}
generatePawnMoves(bs, from, targets, moves);
}
void MoveGenerator::generatePawnMoves(const BoardState &bs, const Square &from, BitBoard targets, MoveVec &moves) {
auto fromBB = BitBoard::fromIndex(from.index());
targets |= (*bs.pieceBBs)[Board::toIndex(!bs.turn)];
BitBoard movesBB;
if (bs.turn == PieceColor::White) {
movesBB = BitBoard::pawnNorthAttacks(fromBB, targets) & ~(*bs.pieceBBs)[Board::toIndex(bs.turn)];
movesBB |= BitBoard::pawnNorthMoves(fromBB, ~bs.occupiedBB);
} else {
movesBB = BitBoard::pawnSouthAttacks(fromBB, targets) & ~(*bs.pieceBBs)[Board::toIndex(bs.turn)];
movesBB |= BitBoard::pawnSouthMoves(fromBB, ~bs.occupiedBB);
}
bool isPromotion = movesBB & (Rank1 | Rank8);
if (isPromotion) {
generateMovesWithPromotion(bs, from, movesBB, moves);
} else {
generateMoves(bs, from, movesBB, PieceType::Pawn, moves);
}
}
void MoveGenerator::generateBishopMoves(const BoardState &bs, const Square &from, MoveVec &moves) {
auto fromBB = BitBoard::fromIndex(from.index());
auto movesBB = BitBoard::bishopAttacks(fromBB, ~bs.occupiedBB) & ~(*bs.pieceBBs)[Board::toIndex(bs.turn)];
generateMoves(bs, from, movesBB, PieceType::Bishop, moves);
}
void
MoveGenerator::generateMoves(const BoardState &bs, const Square &from, BitBoard movesBB, PieceType pt, MoveVec &moves) {
auto fromBB = BitBoard::fromIndex(from.index());
while (movesBB) {
auto to = Square(movesBB.pop());
auto toBB = BitBoard::fromIndex(to.index());
if (isCheck(bs, fromBB, toBB)) {
continue;
}
moves.emplace_back(from, to, score(bs, toBB, pt));
}
}
void
MoveGenerator::generateMovesWithPromotion(const BoardState &bs, const Square &from, BitBoard movesBB, MoveVec &moves) {
auto fromBB = BitBoard::fromIndex(from.index());
while (movesBB) {
auto to = Square(movesBB.pop());
auto toBB = BitBoard::fromIndex(to.index());
if (isCheck(bs, fromBB, toBB)) {
continue;
}
for (const auto &kItem: Piece::PromotionTypes) {
unsigned s = Piece::PromotionScore[Board::toIndex(kItem) - 2] + score(bs, toBB, PieceType::Pawn);
moves.emplace_back(from, to, static_cast<PieceType>(kItem), s);
}
}
}
void MoveGenerator::generateKingMoves(const BoardState &bs, const Square &from, MoveGenerator::MoveVec &moves) {
auto fromBB = BitBoard::fromIndex(from.index());
auto movesBB = BitBoard::kingMoves(fromBB) & ~(*bs.pieceBBs)[Board::toIndex(bs.turn)];
if ((bs.cr & CastlingRights::All) != CastlingRights::None) {
auto checkCR = CastlingRights::White;
auto castlingRank = WhiteCastlingRank;
if (bs.turn == PieceColor::Black) {
checkCR = CastlingRights::Black;
castlingRank = BlackCastlingRank;
}
checkCR &= bs.cr;
if (checkCR != CastlingRights::None) {
// Generate attacked squares
BitBoard target = CastlingRanks | bs.occupiedBB;
auto attacked = generateAttackedSquares(bs, ~target, !bs.turn);
movesBB |= BitBoard::castlingMoves(fromBB & ~attacked,
(*bs.pieceBBs)[Board::toIndex(PieceType::Rook)],
~bs.occupiedBB) & castlingRank;
if ((checkCR & CastlingRights::KingSide) == CastlingRights::None) {
movesBB &= ~GFile;
}
if ((checkCR & CastlingRights::QueenSide) == CastlingRights::None) {
movesBB &= ~CFile;
}
}
}
generateMoves(bs, from, movesBB, PieceType::King, moves);
}
void MoveGenerator::generateRookMoves(const BoardState &bs, const Square &from, MoveVec &moves) {
auto fromBB = BitBoard::fromIndex(from.index());
auto movesBB = BitBoard::rookAttacks(fromBB, ~bs.occupiedBB) & ~(*bs.pieceBBs)[Board::toIndex(bs.turn)];
generateMoves(bs, from, movesBB, PieceType::Rook, moves);
}
void MoveGenerator::generateQueenMoves(const BoardState &bs, const Square &from, MoveVec &moves) {
auto fromBB = BitBoard::fromIndex(from.index());
auto movesBB = BitBoard::queenAttacks(fromBB, ~bs.occupiedBB) & ~(*bs.pieceBBs)[Board::toIndex(bs.turn)];
generateMoves(bs, from, movesBB, PieceType::Queen, moves);
}
void MoveGenerator::generateKnightMoves(const BoardState &bs, const Square &from, MoveVec &moves) {
auto fromBB = BitBoard::fromIndex(from.index());
auto movesBB = BitBoard::knightMoves(fromBB) & ~(*bs.pieceBBs)[Board::toIndex(bs.turn)];
generateMoves(bs, from, movesBB, PieceType::Knight, moves);
}
BitBoard MoveGenerator::generateAttackedSquares(const BoardState &bs, BitBoard target, PieceColor opColor) {
auto opponentBB = (*bs.pieceBBs)[Board::toIndex(opColor)];
BitBoard attacked = 0;
// pawns
if (opColor == PieceColor::White) {
attacked |= BitBoard::pawnNorthAttacks((*bs.pieceBBs)[Board::toIndex(PieceType::Pawn)] & opponentBB, target);
} else {
attacked |= BitBoard::pawnSouthAttacks((*bs.pieceBBs)[Board::toIndex(PieceType::Pawn)] & opponentBB, target);
}
// knights
attacked |= BitBoard::knightMoves((*bs.pieceBBs)[Board::toIndex(PieceType::Knight)] & opponentBB) & ~target;
// Bishop
attacked |= BitBoard::bishopAttacks((*bs.pieceBBs)[Board::toIndex(PieceType::Bishop)] & opponentBB, target);
// Rook
attacked |= BitBoard::rookAttacks((*bs.pieceBBs)[Board::toIndex(PieceType::Rook)] & opponentBB, target);
// Queen
attacked |= BitBoard::queenAttacks((*bs.pieceBBs)[Board::toIndex(PieceType::Queen)] & opponentBB, target);
// King
attacked |= BitBoard::kingMoves((*bs.pieceBBs)[Board::toIndex(PieceType::Queen)] & opponentBB) & ~target;
return attacked;
}
inline bool MoveGenerator::isCheck(const BoardState &bs, const BitBoard &fromBB, const BitBoard &toBB) {
auto kingBB = (*bs.pieceBBs)[Board::toIndex(bs.turn)] & (*bs.pieceBBs)[Board::toIndex(PieceType::King)];
auto changeBB = (fromBB ^ toBB);
auto target = bs.occupiedBB ^ changeBB;
if (kingBB & fromBB) {
if (toBB & bs.occupiedBB) {
target ^= toBB;
}
if ((toBB & ~bs.occupiedBB)) {
kingBB ^= changeBB;
}
}
auto attacked = generateAttackedSquares(bs, ~target, !bs.turn);
return attacked & kingBB;
}
inline unsigned MoveGenerator::score(const BoardState &bs, const BitBoard &toBB, const PieceType moved) {
unsigned score = 0;
auto opponentBB = bs.occupiedBB & ~(*bs.pieceBBs)[Board::toIndex(bs.turn)];
// Check Capture
auto captureBB = toBB & opponentBB;
if (captureBB) {
auto captured = bs.pieceType(captureBB);
score = Piece::MVV_LVA[Board::toIndex(captured)][Board::toIndex(moved)];
}
return score;
}
unsigned MoveGenerator::Mobility(const BoardState &bs, PieceColor color) {
unsigned count = 0;
auto colorMask = (*bs.pieceBBs)[Board::toIndex(color)];
auto targets = (*bs.pieceBBs)[Board::toIndex(!color)];
// Pawns
auto pawnsBB = (*bs.pieceBBs)[Board::toIndex(PieceType::Pawn)] & colorMask;
if (color == PieceColor::White) {
auto movesBB = BitBoard::pawnNorthAttacks(pawnsBB, targets) & ~colorMask;
movesBB |= BitBoard::pawnNorthMoves(pawnsBB, ~bs.occupiedBB);
count += movesBB.count();
} else {
auto movesBB = BitBoard::pawnSouthAttacks(pawnsBB, targets) & ~colorMask;
movesBB |= BitBoard::pawnSouthMoves(pawnsBB, ~bs.occupiedBB);
count += movesBB.count();
}
// Bishops
count += (BitBoard::bishopAttacks((*bs.pieceBBs)[Board::toIndex(PieceType::Bishop)] & colorMask, ~bs.occupiedBB) &
~colorMask).count();
// Knights
count += (BitBoard::knightMoves((*bs.pieceBBs)[Board::toIndex(PieceType::Knight)] & colorMask) &
~colorMask).count();
// Rook
count += (BitBoard::rookAttacks((*bs.pieceBBs)[Board::toIndex(PieceType::Rook)] & colorMask, ~bs.occupiedBB) &
~colorMask).count();
// Queen
count += (BitBoard::queenAttacks((*bs.pieceBBs)[Board::toIndex(PieceType::Queen)] & colorMask, ~bs.occupiedBB) &
~colorMask).count();
// King
count += (BitBoard::kingMoves((*bs.pieceBBs)[Board::toIndex(PieceType::King)] & colorMask) & ~colorMask).count();
return count;
}

40
MoveGenerator.hpp Normal file
View File

@@ -0,0 +1,40 @@
#ifndef CHESS_ENGINE_MOVEGENERATOR_HPP
#define CHESS_ENGINE_MOVEGENERATOR_HPP
#include "Piece.hpp"
#include "Square.hpp"
#include "Move.hpp"
#include "CastlingRights.hpp"
#include "BitBoard.hpp"
#include "BoardState.hpp"
#include <optional>
#include <iosfwd>
#include <vector>
#include <memory>
class MoveGenerator {
public:
using MoveVec = std::vector<Move>;
static void generatePawnMoves(const BoardState &bs, const Square &from, MoveVec &moves);
static void generateBishopMoves(const BoardState &bs, const Square &from, MoveVec &moves);
static void generateKingMoves(const BoardState &bs, const Square &from, MoveVec &moves);
static void generateRookMoves(const BoardState &bs, const Square &from, MoveVec &moves);
static void generateQueenMoves(const BoardState &bs, const Square &from, MoveVec &moves);
static void generateKnightMoves(const BoardState &bs, const Square &from, MoveVec &moves);
static BitBoard generateAttackedSquares(const BoardState &bs, BitBoard target, PieceColor opColor);
static unsigned int Mobility(const BoardState &bs, PieceColor color);
private:
static void generatePawnMoves(const BoardState &bs, const Square &from, BitBoard targets, MoveVec &moves);
static void generateMoves(const BoardState &bs, const Square &from, BitBoard movesBB, PieceType pt, MoveVec &moves);
static void generateMovesWithPromotion(const BoardState &bs, const Square &from, BitBoard movesBB, MoveVec &moves);
inline static bool isCheck(const BoardState &bs, const BitBoard &fromBB, const BitBoard &toBB);
inline static unsigned score(const BoardState &bs, const BitBoard &toBB, PieceType moved);
};
#endif //CHESS_ENGINE_MOVEGENERATOR_HPP

View File

@@ -24,17 +24,34 @@ class Piece {
public:
constexpr static const PieceType PromotionTypes[4] = {PieceType::Knight, PieceType::Bishop, PieceType::Rook,
PieceType::Queen};
constexpr static const unsigned PieceValue[6] = {100, 350, 350, 525, 1000, 10000};
constexpr static const unsigned PromotionScore[6] = {0, 26, 36, 46, 56, 0};
constexpr static const unsigned MVV_LVA[6][6] = {
{15, 14, 13, 12, 11, 10}, // Pawn
{25, 24, 23, 22, 21, 20}, // Knight
{35, 34, 33, 32, 31, 30}, // Bishop
{45, 44, 43, 42, 41, 40}, // Rook
{55, 54, 53, 52, 51, 50}, // Queen
{65, 64, 63, 62, 61, 60}, // King
};
using Optional = std::optional<Piece>;
Piece(PieceColor color, PieceType type);
static Optional fromSymbol(char symbol);
static char toSymbol(PieceType type);
static std::optional<PieceType> pieceTypeFromSymbol(char symbol);
PieceColor color() const;
PieceType type() const;
char toSymbol() const;
private:
@@ -43,6 +60,7 @@ private:
};
bool operator==(const Piece &lhs, const Piece &rhs);
std::ostream &operator<<(std::ostream &os, const Piece &piece);
// Invert a color (White becomes Black and vice versa)

View File

@@ -1,29 +1,53 @@
#include "PrincipalVariation.hpp"
#include <ostream>
#include <vector>
PrincipalVariation::PrincipalVariation(const std::vector<Move> &v, bool isMate): mMoves(v), mIsMate(isMate) {
}
PrincipalVariation::PrincipalVariation(const std::vector<Move> &v, Board &board): mMoves(v) {
for (const auto &kMove : v) {
board.makeMove(kMove);
}
mIsMate = board.isCheckMate();
if (mIsMate || v.empty()) {
mScore = static_cast<int>(v.size());
} else {
mScore = board.evaluate();
}
}
bool PrincipalVariation::isMate() const {
return false;
return mIsMate;
}
int PrincipalVariation::score() const {
return 0;
return mScore;
}
std::size_t PrincipalVariation::length() const {
return 0;
return mMoves.size();
}
PrincipalVariation::MoveIter PrincipalVariation::begin() const {
return nullptr;
return mMoves.begin();
}
PrincipalVariation::MoveIter PrincipalVariation::end() const {
return nullptr;
return mMoves.end();
}
std::ostream &operator<<(std::ostream &os, const PrincipalVariation &pv) {
(void)pv;
os << "isMate=" << pv.isMate();
os << " score=" << pv.score();
os << " length=" << pv.length() << std::endl;
os << "Moves:" << std::endl;
for (const auto &item : pv) {
os << item << std::endl;
}
return os;
}

View File

@@ -3,21 +3,31 @@
#include "Move.hpp"
#include "Piece.hpp"
#include "Board.hpp"
#include <iosfwd>
#include <cstddef>
#include <vector>
class PrincipalVariation {
public:
using MoveIter = Move*;
using MoveIter = std::vector<Move>::const_iterator;
PrincipalVariation() = default;
PrincipalVariation(const std::vector<Move> &v, bool isMate);
PrincipalVariation(const std::vector<Move> &v, Board &board);
bool isMate() const;
int score() const;
std::size_t length() const;
MoveIter begin() const;
MoveIter end() const;
private:
std::vector<Move> mMoves;
bool mIsMate = false;
int mScore = 0;
};
std::ostream &operator<<(std::ostream &os, const PrincipalVariation &pv);

86
Search.cpp Normal file
View File

@@ -0,0 +1,86 @@
#include <iostream>
#include "Search.hpp"
#include "Move.hpp"
#include "Board.hpp"
#include <algorithm>
int Search::search(Node *node, const Board &board, unsigned depth, int alpha, int beta) {
if (depth == 0) {
return evaluate(board);
}
auto moves = Board::MoveVec();
board.pseudoLegalMoves(moves);
if (moves.empty()) {
return evaluate(board);
}
std::stable_sort(moves.begin(), moves.end());
int maxValue = kNegInfinity;
for (auto &kMove: moves) {
Board nodeBoard = board;
nodeBoard.makeMove(kMove);
Node child(kMove);
child.value = search(&child, nodeBoard, depth - 1, -beta, -alpha);
auto value = -child.value;
if (value > maxValue) {
maxValue = value;
node->next = std::make_unique<Node>(child);
}
alpha = std::max(alpha, value);
if (alpha >= beta) {
break;
}
}
node->value = maxValue;
return maxValue;
}
int Search::evaluate(const Board &board) {
return board.evaluate();
}
PrincipalVariation Search::start(const Board &board) {
auto rootMoves = Board::MoveVec();
board.pseudoLegalMoves(rootMoves);
std::stable_sort(rootMoves.begin(), rootMoves.end());
if (rootMoves.size() <= 1) {
auto b = board;
return PrincipalVariation(rootMoves, b);
}
std::unique_ptr<RootNode> best = std::make_unique<RootNode>();
for (const auto &kMove: rootMoves) {
auto node = Node(kMove);
Board nodeBoard = board;
nodeBoard.makeMove(kMove);
auto value = -search(&node, nodeBoard, 5, kNegInfinity, kPosInfinity);
if (value > best->value) {
best->child = std::make_unique<Node>(node);
best->board = nodeBoard;
best->value = value;
}
}
auto moves = std::vector<Move>();
auto current = std::move(best->child);
do {
moves.push_back(current->move);
current = std::move(current->next);
} while (current);
return PrincipalVariation(moves, best->board);
}

39
Search.hpp Normal file
View File

@@ -0,0 +1,39 @@
#ifndef CHESS_ENGINE_SEARCH_HPP
#define CHESS_ENGINE_SEARCH_HPP
#include "PrincipalVariation.hpp"
#include "Board.hpp"
#include <vector>
#include <limits>
namespace Search {
const int kPosInfinity = std::numeric_limits<int>::max();
const int kNegInfinity = std::numeric_limits<int>::min();
struct Node {
explicit Node(const Move &move) : move(move) {
}
Node(Node &node) : move(node.move) {
next = std::move(node.next);
value = node.value;
}
std::unique_ptr<Node> next;
Move move;
int value = kNegInfinity;
};
struct RootNode {
std::unique_ptr<Node> child;
Board board;
int value = kNegInfinity;
};
int search(Node *node, const Board &board, unsigned depth, int alpha, int beta);
PrincipalVariation start(const Board &board);
int evaluate(const Board &board);
}
#endif //CHESS_ENGINE_SEARCH_HPP

View File

@@ -26,6 +26,9 @@ public:
static Optional fromIndex(Index index);
static Optional fromName(const std::string &name);
Square(Index index);
Coordinate file() const;
Coordinate rank() const;
Index index() const;
@@ -40,9 +43,6 @@ public:
static const Square A8, B8, C8, D8, E8, F8, G8, H8;
private:
Square(Index index);
Index mIndex;
};

View File

@@ -11,7 +11,7 @@ TEST_CASE("King attacks are correctly generated", "[Board][Fundamental]") {
auto board = BitBoard::fromIndex(G2);
board |= BitBoard::fromIndex(B7);
BitBoard ka = BitBoard::kingAttacks(board);
BitBoard ka = BitBoard::kingMoves(board);
REQUIRE((ka == (
BitBoard::fromIndex(G1) | BitBoard::fromIndex(G3) | BitBoard::fromIndex(H1) | BitBoard::fromIndex(H2)
| BitBoard::fromIndex(H3) | BitBoard::fromIndex(F1) | BitBoard::fromIndex(F2)

View File

@@ -224,6 +224,18 @@ TEST_CASE_PSEUDO_MOVES("Pseudo-legal bishop moves, empty board", "[Bishop]") {
);
}
TEST_CASE_PSEUDO_MOVES("Pseudo-legal bishop moves, empty board side", "[Bishop]") {
testPseudoLegalMoves(
// https://lichess.org/editor/B7/8/8/8/8/8/8/8_w_-_-_0_1
"B7/8/8/8/8/8/8/8 w - - 0 1",
"a8",
{
"b7", "c6", "d5",
"e4", "f3", "g2", "h1"
}
);
}
TEST_CASE_PSEUDO_MOVES("Pseudo-legal bishop moves, captures", "[Bishop]") {
testPseudoLegalMoves(
// https://lichess.org/editor/8/8/1p3P2/8/3B4/8/8/8_w_-_-_0_1
@@ -252,6 +264,41 @@ TEST_CASE_PSEUDO_MOVES("Pseudo-legal rook moves, empty board", "[Rook]") {
);
}
TEST_CASE_PSEUDO_MOVES("Pseudo-legal rook moves, empty board side", "[Rook]") {
testPseudoLegalMoves(
// https://lichess.org/editor/3R4/8/8/8/8/8/8/8_w_-_-_0_1
"3R4/8/8/8/8/8/8/8 w - - 0 1",
"d8",
{
"d1", "d2", "d3", "d4", "d5", "d6", "d7",
"a8", "b8", "c8",
"e8", "f8", "g8",
"h8"
}
);
}
TEST_CASE_PSEUDO_MOVES("legal rook moves, king check", "[Rook]") {
testPseudoLegalMoves(
// https://lichess.org/editor/5R1k/6pp/8/8/8/8/8/K7_b_-_-_0_1
"5R1k/6pp/8/8/8/8/8/K7 b - - 0 1",
"g7",
{}
);
}
TEST_CASE_PSEUDO_MOVES("legal king moves, king stale", "[Rook]") {
testPseudoLegalMoves(
// https://lichess.org/editor/k7/8/8/6n1/r7/4K3/2q5/8_w_-_-_0_1
"k7/8/8/6n1/r7/4K3/2q5/8 w - - 0 1",
"e3",
{}
);
}
TEST_CASE_PSEUDO_MOVES("Pseudo-legal rook moves, captures", "[Rook]") {
testPseudoLegalMoves(
// https://lichess.org/editor/8/8/3p4/8/3R1P2/8/8/8_w_-_-_0_1

View File

@@ -25,6 +25,23 @@ static void testGameEnd(const char* fen, bool isMate) {
REQUIRE(pv.length() == 0);
}
static void testMove(const char *fen, const std::vector<std::string> &expectedMoves) {
auto engine = createEngine();
REQUIRE(engine != nullptr);
auto board = Fen::createBoard(fen);
REQUIRE(board.has_value());
for (const auto &moveName : expectedMoves) {
auto pv = engine->pv(board.value());
Move move = *pv.begin();
move.setScore(0);
REQUIRE(move == Move::fromUci(moveName));
board->makeMove(move);
}
}
TEST_CASE("Engine detects checkmate", "[Engine][Checkmate]") {
auto fen = GENERATE(
// https://lichess.org/editor/4R2k/6pp/8/8/8/8/8/K7_b_-_-_0_1
@@ -46,3 +63,13 @@ TEST_CASE("Engine detects stalemate", "[Engine][Stalemate]") {
testGameEnd(fen, false);
}
TEST_CASE("Puzzles mateIn1_simple", "[Engine][Puzzle]") {
auto [fen, moves] = GENERATE(table<const char*, std::vector<std::string>>({
{"2bQ1k1r/1p3p2/p4b1p/4pNp1/2q5/8/PPP2PPP/1K1RR3 b - - 3 23",{"f6d8", "d1d8"}}, // https://lichess.org/gQa01loO/black#46
{"5rk1/ppp5/2n4p/3b2p1/6R1/P1B1P2P/1Pq5/R2Q2K1 b - - 3 29",{"c2f2"}}, // https://lichess.org/haKJxadR#57
{"r1b1k2r/ppp2pp1/2p5/2b1q1Bp/3PP1n1/2P5/PP2BPPP/RN1Q1RK1 b kq - 0 10",{"e5h2"}},
{"r3rk2/5ppQ/b1pp1q1p/p5n1/P1P5/2N5/1PB2PP1/R3R1K1 w - - 6 24", {"h7h8"}},
}));
testMove(fen, moves);
}

View File

@@ -104,7 +104,7 @@ TEST_CASE("En passant square is correctly parsed", "[Fen][EnPassant]") {
{
"rnbqkbnr/ppppp1pp/8/8/2PPPp2/8/PP3PPP/RNBQKBNR b KQkq e3 0 3",
Square::E3
}
},
}));
CAPTURE(fen, ep);