Add assignment
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/Build*/
|
||||
/build*/
|
||||
__pycache__/
|
||||
uci-log.txt
|
44
.gitlab-ci.yml
Normal file
44
.gitlab-ci.yml
Normal file
@@ -0,0 +1,44 @@
|
||||
image: ubuntu:22.04
|
||||
|
||||
.prepare-apt: &prepare-apt
|
||||
- apt-get update -yqq
|
||||
# Prevent interactive prompt when installing tzdata
|
||||
- DEBIAN_FRONTEND=noninteractive apt-get install tzdata -yqq
|
||||
|
||||
build:
|
||||
stage: build
|
||||
before_script:
|
||||
- *prepare-apt
|
||||
# Install build dependencies
|
||||
- apt-get install build-essential cmake git -yqq
|
||||
# Update all submodules (Catch2)
|
||||
- git submodule update --init
|
||||
script:
|
||||
# Configure CMake
|
||||
- cmake -S . -B Build -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
# Build project
|
||||
- cmake --build Build/
|
||||
artifacts:
|
||||
paths:
|
||||
- Build/cplchess
|
||||
- Build/Tests/tests
|
||||
|
||||
unit_tests:
|
||||
stage: test
|
||||
script:
|
||||
- ./Build/Tests/tests -r junit -o junit.xml
|
||||
artifacts:
|
||||
reports:
|
||||
junit: junit.xml
|
||||
|
||||
puzzle_tests:
|
||||
stage: test
|
||||
before_script:
|
||||
- *prepare-apt
|
||||
- apt-get install python3 python3-pip -yqq
|
||||
- pip3 install chess junit-xml
|
||||
script:
|
||||
- ./Tests/puzzlerunner.py --engine ./Build/cplchess --junit junit.xml --timeout 180 ./Tests/Puzzles/*
|
||||
artifacts:
|
||||
reports:
|
||||
junit: junit.xml
|
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[submodule "Tests/Catch2"]
|
||||
path = Tests/Catch2
|
||||
url = https://github.com/catchorg/Catch2.git
|
||||
branch = v2.x
|
62
Board.cpp
Normal file
62
Board.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "Board.hpp"
|
||||
|
||||
#include <ostream>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
Board::Board()
|
||||
{
|
||||
}
|
||||
|
||||
void Board::setPiece(const Square& square, const Piece::Optional& piece) {
|
||||
(void)square;
|
||||
(void)piece;
|
||||
}
|
||||
|
||||
Piece::Optional Board::piece(const Square& square) const {
|
||||
(void)square;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Board::setTurn(PieceColor turn) {
|
||||
(void)turn;
|
||||
}
|
||||
|
||||
PieceColor Board::turn() const {
|
||||
return PieceColor::White;
|
||||
}
|
||||
|
||||
void Board::setCastlingRights(CastlingRights cr) {
|
||||
(void)cr;
|
||||
}
|
||||
|
||||
CastlingRights Board::castlingRights() const {
|
||||
return CastlingRights::None;
|
||||
}
|
||||
|
||||
void Board::setEnPassantSquare(const Square::Optional& square) {
|
||||
(void)square;
|
||||
}
|
||||
|
||||
Square::Optional Board::enPassantSquare() const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Board::makeMove(const Move& move) {
|
||||
(void)move;
|
||||
}
|
||||
|
||||
void Board::pseudoLegalMoves(MoveVec& moves) const {
|
||||
(void)moves;
|
||||
}
|
||||
|
||||
void Board::pseudoLegalMovesFrom(const Square& from,
|
||||
Board::MoveVec& moves) const {
|
||||
(void)from;
|
||||
(void)moves;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Board& board) {
|
||||
(void)board;
|
||||
return os;
|
||||
}
|
38
Board.hpp
Normal file
38
Board.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef CHESS_ENGINE_BOARD_HPP
|
||||
#define CHESS_ENGINE_BOARD_HPP
|
||||
|
||||
#include "Piece.hpp"
|
||||
#include "Square.hpp"
|
||||
#include "Move.hpp"
|
||||
#include "CastlingRights.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <iosfwd>
|
||||
#include <vector>
|
||||
|
||||
class Board {
|
||||
public:
|
||||
|
||||
using Optional = std::optional<Board>;
|
||||
using MoveVec = std::vector<Move>;
|
||||
|
||||
Board();
|
||||
|
||||
void setPiece(const Square& square, const Piece::Optional& piece);
|
||||
Piece::Optional piece(const Square& square) const;
|
||||
void setTurn(PieceColor turn);
|
||||
PieceColor turn() const;
|
||||
void setCastlingRights(CastlingRights cr);
|
||||
CastlingRights castlingRights() const;
|
||||
void setEnPassantSquare(const Square::Optional& square);
|
||||
Square::Optional enPassantSquare() const;
|
||||
|
||||
void makeMove(const Move& move);
|
||||
|
||||
void pseudoLegalMoves(MoveVec& moves) const;
|
||||
void pseudoLegalMovesFrom(const Square& from, MoveVec& moves) const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Board& board);
|
||||
|
||||
#endif
|
46
CMakeLists.txt
Normal file
46
CMakeLists.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(cplchess CXX)
|
||||
|
||||
# Set the default build the to Debug if it's not set on the command line.
|
||||
# This is not done for multi configuration generator like Visual Studio
|
||||
# (detected thought CMAKE_CONFIGURATION_TYPES).
|
||||
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type" FORCE)
|
||||
|
||||
# Set the possible values of build type for cmake-gui/ccmake
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||
"Debug" "Release" "MinSizeRel" "RelWithDebInfo")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if (MSVC)
|
||||
# warning level 4 and all warnings as errors
|
||||
add_compile_options(/W4 /WX)
|
||||
else ()
|
||||
# lots of warnings and all warnings as errors
|
||||
add_compile_options(-Wall -Wextra -pedantic -Werror)
|
||||
endif ()
|
||||
|
||||
add_library(cplchess_lib OBJECT
|
||||
Square.cpp
|
||||
Move.cpp
|
||||
Piece.cpp
|
||||
Board.cpp
|
||||
CastlingRights.cpp
|
||||
Fen.cpp
|
||||
PrincipalVariation.cpp
|
||||
Engine.cpp
|
||||
EngineFactory.cpp
|
||||
Uci.cpp
|
||||
)
|
||||
|
||||
target_include_directories(cplchess_lib PUBLIC .)
|
||||
|
||||
add_executable(cplchess Main.cpp)
|
||||
target_link_libraries(cplchess cplchess_lib)
|
||||
|
||||
include(CTest)
|
||||
add_subdirectory(Tests/)
|
53
CastlingRights.cpp
Normal file
53
CastlingRights.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "CastlingRights.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
#include <ostream>
|
||||
|
||||
CastlingRights operator&(CastlingRights lhs, CastlingRights rhs) {
|
||||
using T = std::underlying_type_t<CastlingRights>;
|
||||
return static_cast<CastlingRights>(static_cast<T>(lhs) & static_cast<T>(rhs));
|
||||
}
|
||||
|
||||
CastlingRights& operator&=(CastlingRights& lhs, CastlingRights rhs) {
|
||||
lhs = lhs & rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
CastlingRights operator|(CastlingRights lhs, CastlingRights rhs) {
|
||||
using T = std::underlying_type_t<CastlingRights>;
|
||||
return static_cast<CastlingRights>(static_cast<T>(lhs) | static_cast<T>(rhs));
|
||||
}
|
||||
|
||||
CastlingRights& operator|=(CastlingRights& lhs, CastlingRights rhs) {
|
||||
lhs = lhs | rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
CastlingRights operator~(CastlingRights cr) {
|
||||
using T = std::underlying_type_t<CastlingRights>;
|
||||
return static_cast<CastlingRights>(~static_cast<T>(cr));
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, CastlingRights cr) {
|
||||
if (cr == CastlingRights::None) {
|
||||
return os << "-";
|
||||
} else {
|
||||
if ((cr & CastlingRights::WhiteKingside) != CastlingRights::None) {
|
||||
os << "K";
|
||||
}
|
||||
|
||||
if ((cr & CastlingRights::WhiteQueenside) != CastlingRights::None) {
|
||||
os << "Q";
|
||||
}
|
||||
|
||||
if ((cr & CastlingRights::BlackKingside) != CastlingRights::None) {
|
||||
os << "k";
|
||||
}
|
||||
|
||||
if ((cr & CastlingRights::BlackQueenside) != CastlingRights::None) {
|
||||
os << "q";
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
}
|
25
CastlingRights.hpp
Normal file
25
CastlingRights.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef CHESS_ENGINE_CASTLINGRIGHTS_HPP
|
||||
#define CHESS_ENGINE_CASTLINGRIGHTS_HPP
|
||||
|
||||
#include <iosfwd>
|
||||
|
||||
enum class CastlingRights {
|
||||
None = 0,
|
||||
WhiteKingside = 1 << 0,
|
||||
WhiteQueenside = 1 << 1,
|
||||
BlackKingside = 1 << 2,
|
||||
BlackQueenside = 1 << 3,
|
||||
White = WhiteKingside | WhiteQueenside,
|
||||
Black = BlackKingside | BlackQueenside,
|
||||
All = White | Black
|
||||
};
|
||||
|
||||
CastlingRights operator&(CastlingRights lhs, CastlingRights rhs);
|
||||
CastlingRights& operator&=(CastlingRights& lhs, CastlingRights rhs);
|
||||
CastlingRights operator|(CastlingRights lhs, CastlingRights rhs);
|
||||
CastlingRights& operator|=(CastlingRights& lhs, CastlingRights rhs);
|
||||
CastlingRights operator~(CastlingRights cr);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, CastlingRights cr);
|
||||
|
||||
#endif
|
7
Engine.cpp
Normal file
7
Engine.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "Engine.hpp"
|
||||
|
||||
std::optional<HashInfo> Engine::hashInfo() const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Engine::setHashSize(std::size_t) {}
|
37
Engine.hpp
Normal file
37
Engine.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef CHESS_ENGINE_ENGINE_HPP
|
||||
#define CHESS_ENGINE_ENGINE_HPP
|
||||
|
||||
#include "PrincipalVariation.hpp"
|
||||
#include "Board.hpp"
|
||||
#include "TimeInfo.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <cstddef>
|
||||
|
||||
struct HashInfo {
|
||||
std::size_t defaultSize;
|
||||
std::size_t minSize;
|
||||
std::size_t maxSize;
|
||||
};
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
|
||||
virtual ~Engine() = default;
|
||||
|
||||
virtual std::string name() const = 0;
|
||||
virtual std::string version() const = 0;
|
||||
virtual std::string author() const = 0;
|
||||
|
||||
virtual void newGame() = 0;
|
||||
virtual PrincipalVariation pv(
|
||||
const Board& board,
|
||||
const TimeInfo::Optional& timeInfo = std::nullopt
|
||||
) = 0;
|
||||
|
||||
virtual std::optional<HashInfo> hashInfo() const;
|
||||
virtual void setHashSize(std::size_t size);
|
||||
};
|
||||
|
||||
#endif
|
5
EngineFactory.cpp
Normal file
5
EngineFactory.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#include "EngineFactory.hpp"
|
||||
|
||||
std::unique_ptr<Engine> EngineFactory::createEngine() {
|
||||
return nullptr;
|
||||
}
|
14
EngineFactory.hpp
Normal file
14
EngineFactory.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef CHESS_ENGINE_ENGINEFACTORY_HPP
|
||||
#define CHESS_ENGINE_ENGINEFACTORY_HPP
|
||||
|
||||
#include "Engine.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class EngineFactory {
|
||||
public:
|
||||
|
||||
static std::unique_ptr<Engine> createEngine();
|
||||
};
|
||||
|
||||
#endif
|
168
Fen.cpp
Normal file
168
Fen.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#include "Fen.hpp"
|
||||
|
||||
#include "Square.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
const char* const Fen::StartingPos =
|
||||
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||
|
||||
static std::string nextField(std::istream& stream) {
|
||||
auto field = std::string();
|
||||
stream >> field;
|
||||
return field;
|
||||
}
|
||||
|
||||
static bool parsePlacement(const std::string& placement, Board& board) {
|
||||
auto currentFile = Square::Coordinate(0);
|
||||
auto currentRank = Square::Coordinate(7);
|
||||
|
||||
for (auto c: placement) {
|
||||
if (c >= '1' && c <='8') {
|
||||
auto offset = c - '0';
|
||||
currentFile += offset;
|
||||
|
||||
if (currentFile > 8) {
|
||||
return false;
|
||||
}
|
||||
} else if (c == '/') {
|
||||
if (currentRank == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
currentFile = 0;
|
||||
currentRank--;
|
||||
} else {
|
||||
auto optPiece = Piece::fromSymbol(c);
|
||||
|
||||
if (!optPiece.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto optSquare = Square::fromCoordinates(currentFile, currentRank);
|
||||
|
||||
if (!optSquare.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
board.setPiece(optSquare.value(), optPiece.value());
|
||||
currentFile++;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseTurn(const std::string& turn, Board& board) {
|
||||
if (turn == "w") {
|
||||
board.setTurn(PieceColor::White);
|
||||
return true;
|
||||
} else if (turn == "b") {
|
||||
board.setTurn(PieceColor::Black);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool parseCastlingRights(const std::string& rights, Board& board) {
|
||||
if (rights == "-") {
|
||||
board.setCastlingRights(CastlingRights::None);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto cr = CastlingRights::None;
|
||||
|
||||
for (auto right : rights) {
|
||||
switch (right) {
|
||||
case 'K': cr |= CastlingRights::WhiteKingside; break;
|
||||
case 'Q': cr |= CastlingRights::WhiteQueenside; break;
|
||||
case 'k': cr |= CastlingRights::BlackKingside; break;
|
||||
case 'q': cr |= CastlingRights::BlackQueenside; break;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
board.setCastlingRights(cr);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseEnPassantSquare(const std::string& ep, Board& board) {
|
||||
if (ep == "-") {
|
||||
// No en passant square
|
||||
return true;
|
||||
}
|
||||
|
||||
auto square = Square::fromName(ep);
|
||||
|
||||
if (square.has_value()) {
|
||||
board.setEnPassantSquare(square);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Board::Optional Fen::createBoard(std::istream& fenStream) {
|
||||
auto placement = nextField(fenStream);
|
||||
|
||||
if (placement.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto board = Board();
|
||||
|
||||
if (!parsePlacement(placement, board)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto turn = nextField(fenStream);
|
||||
|
||||
if (turn.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!parseTurn(turn, board)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto castlingRights = nextField(fenStream);
|
||||
|
||||
if (castlingRights.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!parseCastlingRights(castlingRights, board)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto ep = nextField(fenStream);
|
||||
|
||||
if (ep.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!parseEnPassantSquare(ep, board)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto halfmove = nextField(fenStream);
|
||||
|
||||
if (halfmove.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto fullmove = nextField(fenStream);
|
||||
|
||||
if (fullmove.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return board;
|
||||
}
|
||||
|
||||
Board::Optional Fen::createBoard(const std::string& fen) {
|
||||
auto fenStream = std::stringstream(fen);
|
||||
return createBoard(fenStream);
|
||||
}
|
16
Fen.hpp
Normal file
16
Fen.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef CHESS_ENGINE_FEN_HPP
|
||||
#define CHESS_ENGINE_FEN_HPP
|
||||
|
||||
#include "Board.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <iosfwd>
|
||||
|
||||
namespace Fen {
|
||||
extern const char* const StartingPos;
|
||||
|
||||
Board::Optional createBoard(std::istream& fenStream);
|
||||
Board::Optional createBoard(const std::string& fen);
|
||||
}
|
||||
|
||||
#endif
|
34
Main.cpp
Normal file
34
Main.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "Uci.hpp"
|
||||
#include "EngineFactory.hpp"
|
||||
#include "Fen.hpp"
|
||||
#include "Engine.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
auto engine = EngineFactory::createEngine();
|
||||
|
||||
if (engine == nullptr) {
|
||||
std::cerr << "Failed to create engine\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (argc > 1) {
|
||||
auto fen = argv[1];
|
||||
auto board = Fen::createBoard(fen);
|
||||
|
||||
if (!board.has_value()) {
|
||||
std::cerr << "Parsing FEN failed\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
auto pv = engine->pv(board.value());
|
||||
std::cout << "PV: " << pv << '\n';
|
||||
} else {
|
||||
auto uciLog = std::ofstream("uci-log.txt");
|
||||
auto uci = Uci(std::move(engine), std::cin, std::cout, uciLog);
|
||||
uci.run();
|
||||
}
|
||||
}
|
46
Move.cpp
Normal file
46
Move.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "Move.hpp"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
Move::Move(const Square& from, const Square& to,
|
||||
const std::optional<PieceType>& promotion)
|
||||
{
|
||||
(void)from;
|
||||
(void)to;
|
||||
(void)promotion;
|
||||
}
|
||||
|
||||
Move::Optional Move::fromUci(const std::string& uci) {
|
||||
(void)uci;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Square Move::from() const {
|
||||
return Square::A1;
|
||||
}
|
||||
|
||||
Square Move::to() const {
|
||||
return Square::A1;
|
||||
}
|
||||
|
||||
std::optional<PieceType> Move::promotion() const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Move& move) {
|
||||
(void)move;
|
||||
return os;
|
||||
}
|
||||
|
||||
|
||||
bool operator<(const Move& lhs, const Move& rhs) {
|
||||
(void)lhs;
|
||||
(void)rhs;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator==(const Move& lhs, const Move& rhs) {
|
||||
(void)lhs;
|
||||
(void)rhs;
|
||||
return false;
|
||||
}
|
33
Move.hpp
Normal file
33
Move.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef CHESS_ENGINE_MOVE_HPP
|
||||
#define CHESS_ENGINE_MOVE_HPP
|
||||
|
||||
#include "Square.hpp"
|
||||
#include "Piece.hpp"
|
||||
|
||||
#include <iosfwd>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
class Move {
|
||||
public:
|
||||
|
||||
using Optional = std::optional<Move>;
|
||||
|
||||
Move(const Square& from, const Square& to,
|
||||
const std::optional<PieceType>& promotion = std::nullopt);
|
||||
|
||||
static Optional fromUci(const std::string& uci);
|
||||
|
||||
Square from() const;
|
||||
Square to() const;
|
||||
std::optional<PieceType> promotion() const;
|
||||
|
||||
};
|
||||
|
||||
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
|
38
Piece.cpp
Normal file
38
Piece.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "Piece.hpp"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
Piece::Piece(PieceColor color, PieceType type)
|
||||
{
|
||||
(void)color;
|
||||
(void)type;
|
||||
}
|
||||
|
||||
Piece::Optional Piece::fromSymbol(char symbol) {
|
||||
(void)symbol;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
PieceColor Piece::color() const {
|
||||
return PieceColor::Black;
|
||||
}
|
||||
|
||||
PieceType Piece::type() const {
|
||||
return PieceType::Pawn;
|
||||
}
|
||||
|
||||
bool operator==(const Piece& lhs, const Piece& rhs) {
|
||||
(void)lhs;
|
||||
(void)rhs;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Piece& piece) {
|
||||
(void)piece;
|
||||
return os;
|
||||
}
|
||||
|
||||
PieceColor operator!(PieceColor color) {
|
||||
(void)color;
|
||||
return PieceColor::White;
|
||||
}
|
40
Piece.hpp
Normal file
40
Piece.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef CHESS_ENGINE_PIECE_HPP
|
||||
#define CHESS_ENGINE_PIECE_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <iosfwd>
|
||||
|
||||
enum class PieceColor {
|
||||
White,
|
||||
Black
|
||||
};
|
||||
|
||||
enum class PieceType {
|
||||
Pawn,
|
||||
Knight,
|
||||
Bishop,
|
||||
Rook,
|
||||
Queen,
|
||||
King
|
||||
};
|
||||
|
||||
class Piece {
|
||||
public:
|
||||
|
||||
using Optional = std::optional<Piece>;
|
||||
|
||||
Piece(PieceColor color, PieceType type);
|
||||
|
||||
static Optional fromSymbol(char symbol);
|
||||
|
||||
PieceColor color() const;
|
||||
PieceType type() const;
|
||||
};
|
||||
|
||||
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)
|
||||
PieceColor operator!(PieceColor color);
|
||||
|
||||
#endif
|
29
PrincipalVariation.cpp
Normal file
29
PrincipalVariation.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "PrincipalVariation.hpp"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
|
||||
bool PrincipalVariation::isMate() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
int PrincipalVariation::score() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t PrincipalVariation::length() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
PrincipalVariation::MoveIter PrincipalVariation::begin() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PrincipalVariation::MoveIter PrincipalVariation::end() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const PrincipalVariation& pv) {
|
||||
(void)pv;
|
||||
return os;
|
||||
}
|
25
PrincipalVariation.hpp
Normal file
25
PrincipalVariation.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef CHESS_ENGINE_PRINCIPALVARIATION_HPP
|
||||
#define CHESS_ENGINE_PRINCIPALVARIATION_HPP
|
||||
|
||||
#include "Move.hpp"
|
||||
#include "Piece.hpp"
|
||||
|
||||
#include <iosfwd>
|
||||
#include <cstddef>
|
||||
|
||||
class PrincipalVariation {
|
||||
public:
|
||||
|
||||
using MoveIter = Move*;
|
||||
|
||||
bool isMate() const;
|
||||
int score() const;
|
||||
|
||||
std::size_t length() const;
|
||||
MoveIter begin() const;
|
||||
MoveIter end() const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const PrincipalVariation& pv);
|
||||
|
||||
#endif
|
844
README.md
Normal file
844
README.md
Normal file
@@ -0,0 +1,844 @@
|
||||
[TOC]
|
||||
|
||||
# Introduction
|
||||
|
||||
In this project, the goal is to develop a [*chess engine*][chess engine] in C++.
|
||||
A chess engine is a computer program that analyzes chess positions and generates a move that it considers best.
|
||||
It typically consists of three major parts:
|
||||
- *Board representation*: the data structure that stores all relevant information about a chess position;
|
||||
- *Move generation*: given a position, generate all valid moves from that position;
|
||||
- *Search*: use move generation to create a game tree and search it for good moves using an *evaluation heuristic*.
|
||||
|
||||
You will be implementing all three parts in this project.
|
||||
|
||||
## Goal
|
||||
|
||||
The main goal of this project is to create a *correct* implementation.
|
||||
Many [tests](#testing) are provided to help you validate the correctness of your implementation.
|
||||
*However*, performance is an important aspect of chess engines.
|
||||
The search space is simply too large for a brute-force search, even at very small depths.
|
||||
Therefore, you are expected to put some effort in optimizing your engine.
|
||||
|
||||
Some important points about our evaluation:
|
||||
- Passing all [unit tests](#unit-tests) is a good indication of a correct board representation and move generation implementation.
|
||||
However, not all corner cases are tested and we *will* run more tests;
|
||||
- Passing all [puzzle tests](#puzzles) is a good indication of a correct and adequately performing search/evaluation implementation.
|
||||
However, we *will* use more puzzles of increasing difficulty for our evaluation;
|
||||
- All given puzzles are run with a time limit of 3 minutes.
|
||||
Puzzles that take longer to solve will fail.
|
||||
|
||||
It is a good idea to add more tests yourself!
|
||||
|
||||
## Grading
|
||||
|
||||
This project counts for 5/20 points of the CPL course.
|
||||
Of those 5 points, 4 are used to grade the correctness and quality of your implementation and 1 is used to assess its performance.
|
||||
More concretely:
|
||||
|
||||
- 3/5: Correctness.
|
||||
Tested by the unit tests and "mate-in-N" puzzles.
|
||||
We *will* use more tests than the ones provided.
|
||||
- 1/5: Code quality.
|
||||
We will check some objective measures like the absence of undefined behavior and memory leaks.
|
||||
- 1/5: Performance.
|
||||
This might include puzzles in the style of the provided "crushing" ones, playing games against a reference engine,...
|
||||
|
||||
## Submitting
|
||||
|
||||
This is an **individual assignment**!
|
||||
A private Git repository will be created for you on the KU Leuven GitLab servers which you can use for development and will be used for submitting your project.
|
||||
Do not share this repository with anyone.
|
||||
|
||||
|
||||
**Submitting**:
|
||||
- Deadline: **Friday December 23, 2022 at 23:59**;
|
||||
- How: push your solution to the `main` branch of your private repository.
|
||||
|
||||
> :bulb: At the time of the deadline, your repository will be archived.
|
||||
> This means it's still available but read-only.
|
||||
|
||||
**Important guidelines**:
|
||||
- Whatever is on the `main` branch of your private repository at the time of the deadline is considered your solution, nothing else.
|
||||
Make sure *all* your code is on the *correct branch* in the *correct repository*;
|
||||
- Your **code must work on our test infrastructure**.
|
||||
If it does not, we **cannot grade it**.
|
||||
When you push to your private repository, the tests are [automatically run](#automatic-test-runs) on our infrastructure.
|
||||
Do this regularly;
|
||||
- Do not change how tests are run, do not remove any tests, and do not change the compiler options related to warnings in [CMakeLists.txt](CMakeLists.txt).
|
||||
If you do, the automatic test runs will not be representative anymore since we will restore everything when running our evaluation tests;
|
||||
- Do **not share your code** with anyone and do **not include any code not written by you** in your repository (except, of course, the initial code we provided).
|
||||
|
||||
## Allowed language features
|
||||
|
||||
You are allowed to use all features of [C++20][c++20] including everything in the standard library as long as the compiler on our test infrastructure supports it.
|
||||
However, we recommend sticking with [C++17][c++17] as this is what was used during the lecture and exercise sessions.
|
||||
See [this][c++ features] for an overview of all (library) features and in which C++ version they are available.
|
||||
|
||||
> :bulb: The compiler used on our test infrastructure is GCC 11.3.0 which [supports][gcc c++ status] (nearly) all C++20 features.
|
||||
|
||||
> :bulb: To use C++20, you will have to update the `CMAKE_CXX_STANDARD` variable in [CMakeLists.txt](CMakeLists.txt).
|
||||
|
||||
You are **not allowed to use *any* external libraries**.
|
||||
This includes header-only libraries.
|
||||
|
||||
> :warning: Including *any* source files not fully written by you in your repository will be considered plagiarism!
|
||||
|
||||
## Questions and issues
|
||||
|
||||
All code contains bugs, so I expect that some will be found in the code provided for this assignment.
|
||||
If you happen to find one, please [report][questions] it and I will try to fix it as soon as possible.
|
||||
I will then publish the fix on the [assignment repository][assignment repo] from which it can be merged into your private repository.
|
||||
|
||||
> :bulb: To make sure merging is as painless as possible, try not to commit any unnecessary changes to the code, especially in the provided implementation files like [Fen.cpp](Fen.cpp) and [Uci.cpp](Uci.cpp).
|
||||
> Always check what you are about to commit using, for example, `git diff --staged`.
|
||||
|
||||
> :bulb: The easiest way to merge changes into your private repository is to use Git:
|
||||
> ```
|
||||
> git remote add assignment https://gitlab.kuleuven.be/distrinet/education/cpl/cplusplus-project-assignment.git
|
||||
> git fetch assignment
|
||||
> git merge assignment/main
|
||||
> ```
|
||||
> The first command is only needed the first time you merge.
|
||||
|
||||
|
||||
The same issue tracker can be used to **ask questions** about the project.
|
||||
Just open [open an issue][questions] and I will try to answer as soon as possible.
|
||||
|
||||
# Setup
|
||||
|
||||
The basic setup for the project is the same as for the [exercise sessions][exercises setup].
|
||||
|
||||
From now on, we use the following variables in commands:
|
||||
- `$REPO_URL`: the Git URL of your private repository;
|
||||
- `$REPO_DIR`: the directory where you will clone your repository;
|
||||
- `$BUILD_DIR`: the directory where you will build the project.
|
||||
|
||||
[Catch2][catch2] is used for unit testing and its source code is included as a [submodule][git submodules].
|
||||
Therefore, we have to clone the project repository recursively:
|
||||
|
||||
```
|
||||
$ git clone --recurse-submodules $REPO_URL $REPO_DIR
|
||||
```
|
||||
|
||||
If you did a normal clone, don't worry, this can be fixed:
|
||||
```
|
||||
$ cd $REPO_DIR
|
||||
$ git submodule update --init
|
||||
```
|
||||
|
||||
Now the project can be built in the usual way:
|
||||
|
||||
```
|
||||
$ mkdir $BUILD_DIR
|
||||
$ cd $BUILD_DIR
|
||||
$ cmake $REPO_DIR
|
||||
$ cmake --build .
|
||||
```
|
||||
|
||||
By default, CMake will perform a *debug build*.
|
||||
This means, among others, that optimizations are disabled.
|
||||
Since performance is important for a chess engine, you might want perform a *release build*.
|
||||
This can be done by replacing the third command above by the following:
|
||||
```
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Release $REPO_DIR
|
||||
```
|
||||
|
||||
> :bulb: You can have multiple build directories, for example, one with a debug build and one with a release build.
|
||||
> This is useful since debug builds are preferred if you ever need to use a debugger.
|
||||
> Debugging release builds is difficult since they do not contain debugging symbols (so you cannot put breakpoints on functions, for example) and are highly optimized.
|
||||
|
||||
> :bulb: The above procedure for creating a debug build does not work on Visual Studio since it creates multiple builds in the same directory.
|
||||
> Therefore, a release build can be created as follows (replaces the fourth command above):
|
||||
> ```
|
||||
> $ cmake --build . --config Release
|
||||
> ```
|
||||
|
||||
If all went well, there should now be an executable called `cplchess` for the engine, and one called `Tests/tests` for the unit tests.
|
||||
Try to execute the latter to verify your build worked.
|
||||
The tests will obviously fail but it should not crash.
|
||||
|
||||
If you want to add extra `.cpp` files to your project (which you will have to), you should add them to the `cplchess_lib` library in [CMakeLists.txt](CMakeLists.txt).
|
||||
This library is linked into both the engine and the unit tests executables.
|
||||
If you want to add extra unit test files, add them to the `tests` executable in [Tests/CMakeLists.txt](Tests/CMakeLists.txt)
|
||||
|
||||
> :bulb: As explained [earlier](#submitting), you are not allowed to change the compiler options related to warnings in [CMakeLists.txt](CMakeLists.txt).
|
||||
> We have enabled many [compiler warnings][gcc warnings] in the build and made sure that warnings are treated as errors.
|
||||
> This forces you to write warning-free code to help you discover many mistakes early.
|
||||
|
||||
# Problem description
|
||||
|
||||
This section describes the parts you have to implement in your chess engine.
|
||||
If you are not familiar with chess, it might be worthwhile to learn its [rules][chess rules] first.
|
||||
|
||||
Although it is entirely up to you in which order to implement the parts, following the order in this section is probably a good idea.
|
||||
The unit tests allow you to [select certain tests](#test-tags) to focus on specific parts.
|
||||
This also allows you to postpone the implementation of more difficult move types (i.e., [castling][castling], [promotion][promotion], and [en passant][en passant]).
|
||||
It could be interesting, for example, to test the full search algorithm before adding those moves.
|
||||
|
||||
> :bulb: Most code you have to implement can be added to existing source files.
|
||||
> We have provided skeleton code to ensure that all tests compile and that you adhere to the needed API.
|
||||
> All method bodies are empty but to prevent compiler warnings/errors we
|
||||
> - return some arbitrary value in non-`void` methods;
|
||||
> - pretend to use arguments by casting them to `void` (e.g., `(void)arg`).
|
||||
|
||||
## Fundamental data structures
|
||||
|
||||
> :bulb: Relevant unit test [tag](#test-tags): `[Fundamental]`
|
||||
|
||||
We start with describing the fundamental data structures used to represent the board state and moves.
|
||||
All these data structures need to follow the given API but how they are implemented is up to you.
|
||||
|
||||
### Piece
|
||||
|
||||
Pieces are the main tools at the disposal of the chess players.
|
||||
Although the different kind of pieces vary in behavior, we represent them simply by their basic properties: *color* (black or white) and *type* (pawn, knight, etc).
|
||||
Their behavior (i.e., how they move) is [handled elsewhere](#move-generation).
|
||||
|
||||
Piece representation is implemented in [Piece.hpp](Piece.hpp) and [Piece.cpp](Piece.cpp).
|
||||
Three types are defined here:
|
||||
- `PieceColor` (`enum`);
|
||||
- `PieceType` (`enum`);
|
||||
- `Piece` (`class` that stores a color and a type).
|
||||
|
||||
Besides their representation, the `Piece` class also handles conversion to and from standard piece symbols: `Piece::fromSymbol()` takes a `char` and converts it to a `Piece` (if possible) and streaming to `std::ostream` can be used for the opposite.
|
||||
The following symbols are used for chess pieces: `P` (pawn), `N` (knight), `B` (bishop), `R` (rook), `Q` (queen), and `K` (king). Uppercase letters are used for white, lowercase for black.
|
||||
|
||||
> :bulb: Throughout this project, [`std::optional`][std optional] is used to represent optional values.
|
||||
> For example, creation methods like `Piece::fromSymbol()` return an optional if they may fail.
|
||||
> Many classes that are often used as optional values define a type alias called `Optional` for brevity.
|
||||
|
||||
### Square
|
||||
|
||||
Chessboards consist of a grid of 8x8 *squares*.
|
||||
In [*algebraic notation*][san], each square is identified by a coordinate pair from white's point of view.
|
||||
Columns (called *files*) are labeled *a* through *h* while rows (called *ranks*) are labeled *1* through *8*.
|
||||
So, the lower-left square is called *a1* while the upper-right square is called *h8*.
|
||||
|
||||
The `Square` class (implemented in [Square.hpp](Square.hpp) and [Square.cpp](Square.cpp)) is used to identify squares.
|
||||
It offers two ways of identifying squares:
|
||||
- Coordinates: create using `fromCoordinates()` and get using `file()` and `rank()`.
|
||||
Note that both coordinates are numbers in the range `[0, 8)`.
|
||||
- Index: create using `fromIndex()` and get using `index()`.
|
||||
In this representation, all squares have a unique index in the range `[0, 64)` where the index of *a1* is *0*, *b1* is *1*, and that of *h8* is *63*.
|
||||
|
||||
Squares also offer conversion from (`fromName`) and to (streaming to `std::ostream`) their name.
|
||||
|
||||
Two comparison operator are needed:
|
||||
- `operator==` for comparing squares in the tests and elsewhere;
|
||||
- `operator<` for using squares as keys in associative (sorted) containers like [`std::map`][std_map].
|
||||
You can use any total ordering.
|
||||
|
||||
Constants are provided to conveniently references all squares (e.g., `Square::E4`).
|
||||
These are implemented using a private constructor that takes an index as argument.
|
||||
You don't have to keep this constructor, but if you don't, you will have to update the initialization of the constants.
|
||||
|
||||
### Move
|
||||
|
||||
Moves are how a game of chess progresses from turn to turn.
|
||||
Although the concept of a move potentially contains a lot of information (e.g., which piece is moved, if the move is valid, etc.), the `Move` class (implemented in [Move.hpp](Move.hpp) and [Move.cpp](Move.cpp)) only uses the bare minimum: the *from* and *to* squares and optionally the [promoted][promotion]-to piece type.
|
||||
Its constructor takes all three items (although the promotion type is optional and by default a move is not a promotion).
|
||||
|
||||
As a textual representation, the `Move` class uses the [UCI][uci] notation (also sometimes called the [*long algebraic notation*][long algebraic notation]).
|
||||
In this notation, moves are represented by the concatenation of the names of the from and to squares, optionally followed by the lower case symbol of the promotion piece type.
|
||||
[*Castling*][castling] is considered a king move so is represented by the from and to squares of the king (e.g., white kingside is castling is *e1g1*).
|
||||
Moves can be created from this representation using `fromUci` and the conversion to UCI is done by streaming to `std::ostream`.
|
||||
|
||||
Two comparison operator are needed:
|
||||
- `operator==` for comparing moves in the tests and elsewhere;
|
||||
- `operator<` for using moves as keys in associative (sorted) containers like [`std::map`][std_map] and [`std::set`][std_set].
|
||||
You can use any total ordering.
|
||||
|
||||
> :warning: If you don't correctly implement a total ordering on `Move`s with `operator<`, you might run into strange behavior.
|
||||
> For example, the [unit tests](#unit-tests) for pseudo-legal move generation store moves in a `std::set` and compare sets of expected- and generated moves.
|
||||
> If you ever notice that tests pass or fail depending on the order in which you generate moves, it's probably because `operator<` does not correctly implement a total ordering.
|
||||
|
||||
Note that this simple representation allows the creation of illegal moves.
|
||||
Indeed, since the `Move` class has no relation with a `Board`, it's impossible to validate the legality of moves in isolation.
|
||||
We will see how to handle illegal moves [later](#move-making).
|
||||
|
||||
> :bulb: There are some things that *can* be checked about a move's validity.
|
||||
> For example, whether a promotion is certainly invalid (e.g., *a7a8k* or *e6e8q* are *never* valid).
|
||||
> You are free to check this in `fromUci()` but this is not necessary, especially since the constructor has no way of reporting errors so creating such invalid moves is always possible.
|
||||
|
||||
### Board
|
||||
|
||||
Arguably the most important decision for chess engines is how the [board state is represented][board representation wp] as it will have a large impact on how [*move generation*](#move-generation) will be implemented and thus how efficient this will be.
|
||||
The most popular among high-rated chess engines is probably a [bitboard][bitboard] representation (e.g., [Stockfish][stockfish] uses this).
|
||||
Although very efficient for move generation, this representation can be difficult to grasp and work with.
|
||||
Square-centric, array-based representations (e.g., [8x8 boards][8x8 board]) might be easier to work with at the cost of less efficient move generation.
|
||||
|
||||
You are entirely free to choose the way the `Board` class represents its state as long as you can implement its interface:
|
||||
- Getting and setting pieces: `piece(Square)`, `setPiece(Square, Piece)`;
|
||||
- Getting and setting the turn: `turn()`, `setTurn(PieceColor)`;
|
||||
- Getting and setting the [castling rights](#castling-rights): `castlingRights()`, `setCastlingRights(CastlingRights)`;
|
||||
- Getting and setting the [en passant square](#en-passant-square): `enPassantSquare()`, `setEnPassantSquare(Square)`;
|
||||
- Default construction (`Board()`): create an _empty_ board.
|
||||
|
||||
You should also implement streaming a `Board` to `std::ostream` for debugging purposes.
|
||||
You are free to choose how a board is printed.
|
||||
I like the following format:
|
||||
|
||||
```
|
||||
r n b q k b n r
|
||||
p p p p p p p p
|
||||
. . . . . . . .
|
||||
. . . . . . . .
|
||||
. . . . . . . .
|
||||
. . . . . . . .
|
||||
P P P P P P P P
|
||||
R N B Q K B N R
|
||||
```
|
||||
|
||||
#### Castling rights
|
||||
|
||||
[*Castling*][castling] is probably the most complex move type in chess and requires extra state to be stored in `Board` (i.e., knowing which castling moves are valid does not depend only on the state of the pieces).
|
||||
For example, once a rook has moved, its king permanently loses the right to castle on that side of the board, even when the rook returns to its original square.
|
||||
|
||||
To represent *castling rights*, the `CastlingRights` enumeration is provided (see [CastlingRights.hpp](CastlingRights.hpp) and [CastlingRights.cpp](CastlingRights.cpp)).
|
||||
Overloads are provided for many bitwise operations so that this type can be used as [bit flags][bit flags].
|
||||
For example:
|
||||
|
||||
```c++
|
||||
CastlingRights rights = ...;
|
||||
|
||||
if ((rights & CastlingRights::BlackQueenside) != CastlingRights::None) {
|
||||
// Queenside castling is available for black
|
||||
}
|
||||
|
||||
// Add kingside castling rights for white
|
||||
rights |= CastlingRights::WhiteKingside;
|
||||
|
||||
// Remove all castling rights for black
|
||||
rights &= ~CastlingRights::Black;
|
||||
```
|
||||
|
||||
#### En passant square
|
||||
|
||||
[*En passant*][en passant], probably the least-known move type in chess, also requires extra state to be stored since it is only allowed for one move after a pawn made a two-square move.
|
||||
Therefore, to implement pawn captures correctly, `Board` should store the square to which an en passant capture can be made.
|
||||
|
||||
## Move generation
|
||||
|
||||
> :bulb: Relevant unit test [tag](#test-tags): `[MoveGen]`
|
||||
|
||||
[*Move generation*][move generation] is the process of generating valid moves from a board state.
|
||||
These moves will be used for [searching](#searching).
|
||||
|
||||
There are generally two ways of generating moves: legal or pseudo-legal.
|
||||
|
||||
### Legal move generation
|
||||
|
||||
Only moves that are [legal][legal move] according to the [rules of chess][chess rules] are generated.
|
||||
This is convenient for the search algorithm but might not be that easy to implement efficiently.
|
||||
The main issue is verifying that the king is not left in [check][chess rules check] after a move (which is illegal).
|
||||
The naive way of doing this might be to generate the opponent's moves and verifying that none of them captures the king.
|
||||
While this might work, it would result in doubling the amount of generated moves.
|
||||
|
||||
### Pseudo-legal move generation
|
||||
|
||||
A [*pseudo-legal move*][pseudo legal move] is one that adheres to all the rules of chess *except* that it might leave the king in check.
|
||||
From the description above it should be clear that this is easier to generate but might complicate the search algorithm.
|
||||
Indeed, we now have to make sure that we somehow reject illegal moves while searching.
|
||||
One way to tackle this could be to search until a move that captures the king and then reject the previous move.
|
||||
|
||||
> :bulb: Due to the complex rules of [castling][castling], a pseudo-legal castling move is usually also legal.
|
||||
|
||||
### Interface
|
||||
|
||||
Move generation should be implemented by the `Board` class through the following method:
|
||||
|
||||
```c++
|
||||
using MoveVec = std::vector<Move>;
|
||||
|
||||
void Board::pseudoLegalMoves(MoveVec& moves) const;
|
||||
```
|
||||
|
||||
This method should add all (pseudo) legal moves for the current player to the `moves` vector.
|
||||
|
||||
For [testing](#unit-tests) purposes, the following method should also be implemented to generate all moves from a specific square:
|
||||
|
||||
```c++
|
||||
void Board::pseudoLegalMovesFrom(const Square& from, MoveVec& moves) const;
|
||||
```
|
||||
|
||||
> :bulb: You are free to use an entirely different interface to communicate between the search algorithm and `Board` as long as you can still implement this one for testing.
|
||||
|
||||
> :bulb: Even though "pseudo-legal" is part of the method names, you are free to generate legal moves here.
|
||||
> The tests will only verify the correct generation of pseudo-legal moves, though.
|
||||
|
||||
## Move making
|
||||
|
||||
> :bulb: Relevant unit test [tag](#test-tags): `[MoveMaking]`
|
||||
|
||||
[*Move making*][move making] is the process of updating the board state to reflect a move.
|
||||
Obviously, this should include moving the moved piece from the source square to the destination square (possibly replacing a captured piece) but it may also include updating [castling rights](#castling-rights) and the [en passant square](#en-passant-square).
|
||||
|
||||
Move making is used by the search algorithm to generate child nodes after move generation.
|
||||
|
||||
It should be implemented in the `Board` class by the following method:
|
||||
|
||||
```c++
|
||||
void Board::makeMove(const Move& move);
|
||||
```
|
||||
|
||||
> :bulb: Since this method is only used by your search algorithm, it is probably not necessary to verify that the given move is legal in this method.
|
||||
> You just have to make sure that *if* a legal move is given, it is correctly executed.
|
||||
|
||||
## Searching
|
||||
|
||||
> :bulb: Relevant tests: [puzzles](#puzzles).
|
||||
|
||||
The basic question a chess engine tries to answer is what the best move to play is at a particular position.
|
||||
To answer this, a [search tree][game tree] is typically constructed with the initial position in the root node and the children at every level being positions resulting from valid moves from their parents.
|
||||
|
||||
Given this tree, a search algorithm tries to find a path that *guarantees* the player will end-up in *the most favorable* position.
|
||||
There are two words to discuss a bit here:
|
||||
- *Guaranteed* most favorable position: what we mean here is that we must assume the opponent always makes the best possible move for them.
|
||||
That is, we assume the *worst case scenario* and try to minimize our loss (or maximize our win);
|
||||
- *Favorable*: we try to win the chess game but most of the time it will not feasible to search the whole tree to find a [checkmate][checkmate].
|
||||
Therefore, we have to limit the search depth and use a [heuristic][eval] to evaluate positions that are not the [end of the game][chess rules game end].
|
||||
|
||||
[*Minimax*][minimax] is a well-known search algorithm that guarantees finding the move that ends-up in the most favorable position.
|
||||
You are free to use any correct algorithm but minimax is definitely a good choice.
|
||||
|
||||
> :bulb: Here are some tips for using minimax:
|
||||
> - Since chess is a [zero-sum game][zero-sum game], the slightly simpler [negamax][negamax] variant can be used;
|
||||
> - It is highly recommended to implement [*alpha-beta pruning*][alpha-beta pruning] to improve search performance.
|
||||
> Without it, it is probably impossible to solve most puzzles within the time limit;
|
||||
> - When using alpha-beta pruning, [*move ordering*][move ordering] becomes important: if potentially good moves are searched first, it may lead to earlier pruning reducing the size of the search tree;
|
||||
> - If you want to take time into account (see below), you might want to use [*iterative deepening*][iterative deepening] to make sure you have a reasonable move available quickly while continuing the search at higher depths to find better moves.
|
||||
|
||||
[Evaluating][eval] chess positions is very complex.
|
||||
The most naive way is to simply calculate the [value][piece value] of all remaining pieces.
|
||||
However, this is clearly not optimal so more advanced strategies will take other aspects into account (e.g., [center control][center control], [king safety][king safety], etc).
|
||||
|
||||
> :bulb: It is up to you to decide how far you want to go with improving the heuristic.
|
||||
> However, it should at least be good enough to solve the provided [puzzles](#puzzles).
|
||||
|
||||
Remember that if you use [pseudo-legal move generation](#pseudo-legal-move-generation), the search algorithm needs to somehow reject illegal moves.
|
||||
And unless you evaluation function can detect [checkmate][checkmate] and [stalemate][stalemate], it also needs to be able to handle [end of the game][chess rules game end] conditions.
|
||||
|
||||
### Principal variation
|
||||
|
||||
The result of a search is a [*principal variation*][pv] (PV) which is the sequence of moves the engine considers best.
|
||||
A PV is represented by the `PrincipalVariation` class (in [PrincipalVariation.hpp](PrincipalVariation.hpp) and [PrincipalVariation.cpp](PrincipalVariation.cpp)).
|
||||
The main interface that has to be implemented is the following:
|
||||
|
||||
```c++
|
||||
using MoveIter = /* your iterator type here */;
|
||||
|
||||
std::size_t PrincipalVariation::length() const;
|
||||
MoveIter PrincipalVariation::begin() const;
|
||||
MoveIter PrincipalVariation::end() const;
|
||||
```
|
||||
|
||||
Where `length()` returns the number of [plies][ply] in the PV and `begin()` and `end()` allows for iterating over the `Move`s.
|
||||
|
||||
> :bulb: You are free to choose any container to store the `Move`s.
|
||||
> Adjust `MoveIter` accordingly.
|
||||
|
||||
The `PrincipalVariation` class also stores information about the evaluation [score][eval score]:
|
||||
|
||||
```c++
|
||||
bool PrincipalVariation::isMate() const;
|
||||
int PrincipalVariation::score() const;
|
||||
```
|
||||
|
||||
`isMate()` should return `true` if the PV ends in checkmate.
|
||||
In this case, `score()` should return the number of plies that leads to the checkmate.
|
||||
Otherwise, `score()` returns the evaluation of the position the PV leads to.
|
||||
In both cases, the score is from the point of view of the engine: positive values are advantageous for the engine, negative ones for its opponent.
|
||||
|
||||
> :bulb: Your are free to choose the unit of the evaluation score as long as larger scores mean better evaluations.
|
||||
> Typically, [*centipawns*][centipawns] are used where 100 centipawns corresponds to the value of one pawn.
|
||||
|
||||
There is also an overload declared to stream `PrincipalVariation` to a `std::ostream`.
|
||||
You can choose how PVs are printed.
|
||||
|
||||
### Engine
|
||||
|
||||
The main interface to the search algorithm is provided by the abstract class `Engine` (see [Engine.hpp](Engine.hpp)).
|
||||
This class functions as an interface so you have to add you own derived class to implement this interface.
|
||||
You should then adapt `EngineFactory::createEngine()` (in [EngineFactory.cpp](EngineFactory.cpp)) to return an instance of your class.
|
||||
|
||||
The most important method in the `Engine` interface is the following:
|
||||
|
||||
```c++
|
||||
virtual PrincipalVariation Engine::pv(
|
||||
const Board& board,
|
||||
const TimeInfo::Optional& timeInfo = std::nullopt
|
||||
) = 0;
|
||||
```
|
||||
|
||||
This should calculate and return the PV starting from the position represented by `board`.
|
||||
|
||||
> :bulb: The second argument, `timeInfo`, provides timing information (see [TimeInfo.hpp](TimeInfo.hpp)) if it is available.
|
||||
> Most chess games are played with a [time control][time control] and for those games, `timeInfo` contains the time left on the clock of both players as well as their increment.
|
||||
> It is *completely optional* to use this information but will be especially useful if you choose to use [iterative deepening][iterative deepening].
|
||||
|
||||
The following method is called whenever a new game starts (not guaranteed to be called for the first game played by an `Engine` instance):
|
||||
|
||||
```c++
|
||||
virtual void Engine::newGame() = 0;
|
||||
```
|
||||
|
||||
> :bulb: You probably don't need this method but it could be useful if you store state inside your engine.
|
||||
|
||||
The following methods are used to identify your engine over the [UCI interface](#uci):
|
||||
|
||||
```c++
|
||||
virtual std::string Engine::name() const = 0;
|
||||
virtual std::string Engine::version() const = 0;
|
||||
virtual std::string Engine::author() const = 0;
|
||||
```
|
||||
|
||||
Note that the value of `name()` will be used to identify your engine during the
|
||||
tournament so make sure to chose something appropriate!
|
||||
|
||||
Lastly, most chess engines use [_transposition tables_][transpositiion table] as
|
||||
an optimization technique. While it is not necessary to implement this technique
|
||||
for this project, if you do chose to implement it, you _have to_ respect the
|
||||
maximum table size specified over UCI. In order to do this, you will have to
|
||||
override the following methods:
|
||||
|
||||
```c++
|
||||
virtual std::optional<HashInfo> hashInfo() const;
|
||||
virtual void setHashSize(std::size_t size);
|
||||
```
|
||||
|
||||
# Testing
|
||||
|
||||
A number of tests are provided in the [Tests](Tests/) directory that you can run locally and are also run automatically when pushing commits to GitLab.
|
||||
You are free (even encouraged) to add more tests but **do not modify any of the existing test files**.
|
||||
They will be overwritten when we run our automatic tests after the deadline.
|
||||
Also remember that the tests do not try to cover all edge cases and we will run more tests when grading the projects.
|
||||
|
||||
Unit tests are provided to verify the correctness of the [board representation](#fundamental-data-structures) and [pseudo-legal move generation](#move-generation).
|
||||
To test the search algorithm, chess puzzles are used.
|
||||
|
||||
> :bulb: It is possible to pass all the tests with a relatively shallow search depth (about 5 [plies](ply)) and a relatively simple evaluation function.
|
||||
> The most naive search, however, will probably not suffice to finish most puzzles within the time limit.
|
||||
|
||||
## Automatic test runs
|
||||
|
||||
Whenever you push commits to your private repository, a [CI/CD pipeline][gitlab pipelines] will start automatically that builds and tests your code.
|
||||
The pipelines can be viewed by going to "CI/CD -> Pipelines" in the web interface of your repository.
|
||||
|
||||
When clicking on a pipeline, you can see it consist of two stages and three jobs:
|
||||
- Build stage: runs the "build" job that compiles your code and produces the engine and unit tests executables for the next stage;
|
||||
- Test stage: runs the "unit_tests" and "puzzle_tests" jobs that perform all automatic tests.
|
||||
|
||||
> :bulb: If the build stage fails, the test stage will not run.
|
||||
> Both jobs in the test stage are independent and run in parallel, though.
|
||||
|
||||
You can click on a specific job to view its output which can be useful when debugging failures (e.g., to see compiler errors).
|
||||
|
||||
The "Tests" tab contains detailed information about the result of the test runs.
|
||||
If you want to know why a test failed, click on the job name in the "Tests" tab first (e.g., "unit_tests") and then on "View details" next to the failed test.
|
||||
|
||||
> :bulb: For those who are interested, the pipeline is defined in [.gitlab-ci.yml](.gitlab-ci.yml).
|
||||
> **Do not modify this file, though!**
|
||||
|
||||
## Unit tests
|
||||
|
||||
Unit tests are implemented using the [Catch2][catch2] framework.
|
||||
After building the project, a test executable is available at `$BUILD_DIR/Tests/tests`.
|
||||
Running this executable without any arguments runs all tests and looks something like this when everything passes:
|
||||
|
||||
```
|
||||
$ $BUILD_DIR/Tests/tests
|
||||
===============================================================================
|
||||
All tests passed (1698 assertions in 95 test cases)
|
||||
```
|
||||
|
||||
### Test tags
|
||||
|
||||
Since initially (almost) all tests will fail, test cases are tagged to be able to only run a subset of tests.
|
||||
The following tags are provided:
|
||||
- `[Fundamental]`: tests for functionality that is fundamental.
|
||||
For example, for classes or methods used by the tests themselves.
|
||||
This includes the `Piece`, `Square`, and `Move` classes as well as the state of the `Board` class.
|
||||
**This functionality should be the first thing to implement!**
|
||||
- `[ClassName`]: each class with unit tests has a corresponding tag (e.g., `[Board]`) that runs all tests of that class;
|
||||
- `[MoveGen]`: all tests for pseudo-legal move generation;
|
||||
- `[MoveMaking]`: all tests for move making;
|
||||
- `[Castling]`, `[Promotion]`, `[EnPassant]`: all tests for these "special" moves are tagged accordingly.
|
||||
This allows you to implement and test "normal" moves first;
|
||||
- `[PieceType]`: tests for a specific piece type (e.g., `[Queen]`) are tagged accordingly.
|
||||
This is mostly related to `[MoveGen]` tests.
|
||||
|
||||
Tags can be selected by providing them as an argument to the test executable.
|
||||
If one is preceded by a `~`, it is deselected.
|
||||
For example, to test the move generation for pawns except en passant:
|
||||
|
||||
```
|
||||
$ $BUILD_DIR/Tests/tests '[MoveGen][Pawn]~[EnPassant]'
|
||||
Filters: [MoveGen][Pawn]~[EnPassant]
|
||||
===============================================================================
|
||||
All tests passed (65 assertions in 11 test cases)
|
||||
```
|
||||
|
||||
See [this][catch2 filter tags] for more information.
|
||||
|
||||
## Puzzles
|
||||
|
||||
Puzzles are chess problems that have a clear, short, and unique solution.
|
||||
They are often used to train a chess player's [tactical][chess tactic] awareness.
|
||||
Simple puzzles typically present a position where a checkmate can be forced or a large material advantage can be gained.
|
||||
For more advanced puzzles, one might need to find moves that gain a [positional advantage][positional advantage].
|
||||
|
||||
We have selected a number of easy puzzles from the freely available [puzzle database][lichess puzzle db] of [Lichess][lichess].
|
||||
They can be found in [Tests/Puzzles/](Tests/Puzzles/) and contain three major categories:
|
||||
- `mateIn1`: checkmate can be forced in one move;
|
||||
- `mateIn2`: checkmate can be forced in two moves;
|
||||
- `crushing`: a significant material advantage can be gained.
|
||||
|
||||
For each category, three variants are provided:
|
||||
- `simple`: puzzles do not involve castling or en passant moves;
|
||||
- `castling`: all puzzles involve castling moves;
|
||||
- `enPassant`: all puzzles involve en passant moves;
|
||||
|
||||
The puzzle files are named according to the category and variant.
|
||||
For example, the simple mate-in-one puzzles can be found in [Tests/Puzzles/mateIn1_simple.csv](Tests/Puzzles/mateIn1_simple.csv).
|
||||
|
||||
### Running puzzles
|
||||
|
||||
> :bulb: To run the puzzles locally, you need Python 3 (at least 3.7) and the `chess` and `junit-xml` packages which can be installed through `pip`:
|
||||
> ```
|
||||
> pip3 install chess junit-xml
|
||||
> ```
|
||||
|
||||
A Python script is provided ([Tests/puzzlerunner.py](Tests/puzzlerunner.py)) to run puzzles through an engine and verify its solution. It uses the [UCI interface](#uci) to communicate with the engine.
|
||||
The script can be used as follows:
|
||||
```
|
||||
$ ./puzzlerunner.py --engine /path/to/engine/executable [csv files...]
|
||||
```
|
||||
|
||||
For example, to run the simple mate-in-one puzzles using your engine:
|
||||
|
||||
```
|
||||
$ ./puzzlerunner.py --engine $BUILD_DIR/cplchess Puzzles/mateIn1_simple.csv
|
||||
=== Running puzzles from /.../Tests/Puzzles/mateIn1_simple.csv ===
|
||||
Running puzzle jrJls ... OK (1.645s)
|
||||
Running puzzle rZoKr ... OK (0.834s)
|
||||
[snip]
|
||||
Running puzzle BEUkI ... OK (0.435s)
|
||||
Running puzzle EAnMf ... OK (0.027s)
|
||||
Total time: 25.294s
|
||||
All tests passed
|
||||
```
|
||||
|
||||
> :bulb: The timing information shown is not very accurate since it's measured in wall-clock time.
|
||||
> Because running a puzzle involves spawning a new process (the engine) and inter-process communication between the Python script and this process, the measured time will depend on the current load of the system.
|
||||
|
||||
When a puzzle fails because the engine returned a wrong move, some information is shown to help debug the issue.
|
||||
For example:
|
||||
|
||||
```
|
||||
Running puzzle 93ERa ... FAIL (5.034s)
|
||||
===
|
||||
Failure reason: unexpected move
|
||||
URL: https://lichess.org/training/93ERa
|
||||
position=4rqk1/5rp1/7R/4B3/4P1Q1/6PK/PP5P/2n5 w - - 1 39
|
||||
move=e5d6
|
||||
expected move=g4g6
|
||||
===
|
||||
```
|
||||
|
||||
It shows a link to the puzzle on Lichess, the position from which the wrong move was generated (in [FEN](#fen) notation), and the generated and expected moves.
|
||||
|
||||
### Generating puzzles
|
||||
|
||||
The full [Lichess puzzle database][lichess puzzle db] currently contains about three million puzzles.
|
||||
We have selected only a few to be included in the automatic tests but you are encouraged to use more puzzles to test your engine.
|
||||
To help you with this, we provide a script ([Tests/puzzledb.py](Tests/puzzledb.py)) that can parse and filter the database.
|
||||
|
||||
Puzzles can be filtered on a number of criteria, including their [tags][lichess puzzle themes], rating, whether they include castling or en passant moves, etc.
|
||||
For a full list of options, run `./puzzledb.py --help`.
|
||||
|
||||
As an example, the simple mate-in-one puzzles were generated like this:
|
||||
|
||||
```
|
||||
$ ./puzzledb.py --tag=mateIn1 --castling=no --en-passant=no lichess_db_puzzle.csv | sort -R | head -n 20
|
||||
```
|
||||
|
||||
> :bulb: `sort -R` randomly shuffles all matches and `head -n 20` selects the first 20.
|
||||
> If you run this locally, you will (most likely) get different puzzles than the ones in [Tests/Puzzles/mateIn1_simple.csv](Tests/Puzzles/mateIn1_simple.csv).
|
||||
> Also note that this is Bash syntax, the full command will not work in other (incompatible) shells but the invocation of `puzzledb.py` itself will.
|
||||
|
||||
> :bulb: Note that the options `--tag` and `--not-tag` can be given multiple times and it will filter on puzzles that (don't) have *all* given tags.
|
||||
|
||||
> :bulb: [Tests/puzzledb.py](Tests/puzzledb.py) is not used by our testing infrastructure so feel free to modify it to add more filter options.
|
||||
|
||||
### Puzzle rating
|
||||
|
||||
We also provide a script to determine your engine's puzzle rating. All puzzles
|
||||
in the Lichess puzzle database have a rating and we can estimate an engine's
|
||||
rating by making it "play against" a large number of puzzles and using
|
||||
[Glicko 2][glicko] to update its rating.
|
||||
|
||||
> :bulb: You need to install the `glicko2` Python package to run the script:
|
||||
> ```
|
||||
> pip3 install glicko2
|
||||
> ```
|
||||
|
||||
You can run the script as follows:
|
||||
|
||||
```
|
||||
$ ./puzzlerating.py --engine $BUILD_DIR/cplchess --puzzle-db lichess_db_puzzle.csv
|
||||
```
|
||||
|
||||
Note that the script takes quite some time to start since it parses the full
|
||||
database.
|
||||
|
||||
# Tools
|
||||
|
||||
Here we list some freely available tools that you can use while developing your chess engine.
|
||||
|
||||
## FEN
|
||||
|
||||
[*Forsyth–Edwards Notation*][fen] (FEN) is a standard notation for describing chess positions.
|
||||
It contains information about piece placement, current turn, castling rights, en passant square, and number of moves made.
|
||||
|
||||
A FEN parser is provided (see [Fen.hpp](Fen.hpp) and [Fen.cpp](Fen.cpp)) that converts a FEN string to a `Board`.
|
||||
|
||||
> :warning: The FEN parser will not work properly until all `[Fundamental]` [unit tests](#unit-tests) pass.
|
||||
|
||||
> :bulb: The FEN parser currently does not support parsing the move number information (nor does `Board` store it) because you don't need it for a simple engine.
|
||||
> [Some extensions](#draw-conditions) might need it, though, so if you want to implement those, you'll have to extend the FEN parser.
|
||||
|
||||
When running the engine executable with a FEN string as argument, the position is evaluated and the PV printed.
|
||||
For example:
|
||||
|
||||
```
|
||||
$ $BUILD_DIR/cplchess "2kr4/ppp2Q2/3Pp3/1q2P3/n1r4p/5P2/6PB/R1R3K1 b - - 0 29"
|
||||
PV: +600 [b5c5 g1h1 c4c1 a1c1 c5c1]
|
||||
```
|
||||
|
||||
> :bulb: The FEN string should be passed *as a single argument* so you have to put it in quotes.
|
||||
|
||||
> :bulb: Since you are free to choose [how a PV is printed](#principal-variation), the output may look different.
|
||||
|
||||
While developing your engine, you will most likely want to test it on specific positions.
|
||||
To manually create positions and convert them to FEN, the Lichess [board editor][lichess board editor] is a very handy tool.
|
||||
All unit tests that start from a position (which is most of them) contain a link to the board editor with that specific position in a comment.
|
||||
|
||||
> :bulb: The en passant square cannot be set from the editor so you'll have to fill that in by hand if you need it.
|
||||
|
||||
## UCI
|
||||
|
||||
The [*Universal Chess Protocol*][uci] is a protocol that is mainly used to communicate between chess engines and user interfaces.
|
||||
It allows user interfaces to send positions (and other information like timing) to an engine and for the engine to send its best move (and optionally a PV and evaluation) back.
|
||||
See [this][uci protocol] for a description of the protocol.
|
||||
|
||||
A UCI implementation is provided (see [Uci.hpp](Uci.hpp) and [Uci.cpp](Uci.cpp)) that allows you to use your engine with a GUI or to let it play against another engine (or itself).
|
||||
Running the engine executable without any arguments starts it in UCI mode.
|
||||
It will listen on stdin for commands and write replies to stdout.
|
||||
It will also log some information to a file called `uci-log.txt` in its current working directory:
|
||||
- All incoming and outgoing UCI commands;
|
||||
- After receiving a new position and after getting a move from the engine, the board is printed;
|
||||
- The PV received from the engine.
|
||||
|
||||
|
||||
There are many [UCI GUIs][uci gui] and all of them should work but the one I use is [Cute Chess][cutechess].
|
||||
|
||||
To use your engine in Cute Chess, you first have to add it to its engine list.
|
||||
Go to "Tools -> Settings" and then to the "Engines" tab to add your engine by clicking on the "+" symbol at the bottom.
|
||||
Once you added your engine, you can use it by going to "Game -> New" and then selecting "CPU" and your engine for one or both of the players.
|
||||
|
||||
> :bulb: Cute Chess also includes a command line tool (`cutechess-cli`) that you can use to make two engines play each other.
|
||||
> See [this][cutechess cli] and `cutechess-cli -help` for more info.
|
||||
|
||||
# Extensions
|
||||
|
||||
This section describes some (optional) extensions that could give your engine an edge during the [tournament](#tournament).
|
||||
|
||||
## Time control
|
||||
|
||||
As described [before](#engine), timing information is passed to the engine.
|
||||
This can be used to prevent the engine from timing out during games.
|
||||
|
||||
## Draw conditions
|
||||
|
||||
Besides [stalemate][stalemate], there are some other conditions that can draw a game.
|
||||
Most notably the [*fifty-move rule*][fifty-move rule] and [*threefold repetition*][threefold repetition].
|
||||
These rules can be used to turn a losing position into a draw, especially against an engine that does not understand these rules.
|
||||
They will be applied in the tournament.
|
||||
|
||||
> :bulb: Last year, many games during the tournament were drawn due to threefold
|
||||
> repetition.
|
||||
|
||||
# Tournament
|
||||
|
||||
We plan to organize a tournament between the engines that pass all tests.
|
||||
More details will follow.
|
||||
|
||||
[san]: https://en.wikipedia.org/wiki/Algebraic_notation_(chess)
|
||||
[long algebraic notation]: https://en.wikipedia.org/wiki/Algebraic_notation_(chess)#Long_algebraic_notation
|
||||
[std_map]: https://en.cppreference.com/w/cpp/container/map
|
||||
[std_set]: https://en.cppreference.com/w/cpp/container/set
|
||||
[promotion]: https://en.wikipedia.org/wiki/Promotion_(chess)
|
||||
[uci]: https://en.wikipedia.org/wiki/Universal_Chess_Interface
|
||||
[board representation wp]: https://en.wikipedia.org/wiki/Board_representation_(computer_chess)
|
||||
[board representation cpw]: https://www.chessprogramming.org/Board_Representation
|
||||
[bitboard]: https://en.wikipedia.org/wiki/Bitboard
|
||||
[stockfish]: https://stockfishchess.org/
|
||||
[8x8 board]: https://www.chessprogramming.org/8x8_Board
|
||||
[castling]: https://en.wikipedia.org/wiki/Castling
|
||||
[bit flags]: https://blog.podkalicki.com/bit-level-operations-bit-flags-and-bit-masks/
|
||||
[en passant]: https://en.wikipedia.org/wiki/En_passant
|
||||
[move generation]: https://www.chessprogramming.org/Move_Generation
|
||||
[pseudo legal move]: https://www.chessprogramming.org/Pseudo-Legal_Move
|
||||
[legal move]: https://www.chessprogramming.org/Legal_Move
|
||||
[chess rules]: https://en.wikipedia.org/wiki/Rules_of_chess
|
||||
[chess rules check]: https://en.wikipedia.org/wiki/Rules_of_chess#Check
|
||||
[chess rules game end]: https://en.wikipedia.org/wiki/Rules_of_chess#End_of_the_game
|
||||
[move making]: https://www.chessprogramming.org/Make_Move
|
||||
[game tree]: https://en.wikipedia.org/wiki/Game_tree
|
||||
[checkmate]: https://en.wikipedia.org/wiki/Checkmate
|
||||
[stalemate]: https://en.wikipedia.org/wiki/Stalemate
|
||||
[minimax]: https://en.wikipedia.org/wiki/Minimax
|
||||
[zero-sum game]: https://en.wikipedia.org/wiki/Zero-sum_game
|
||||
[negamax]: https://en.wikipedia.org/wiki/Negamax
|
||||
[alpha-beta pruning]: https://en.wikipedia.org/wiki/Alpha-beta_pruning
|
||||
[move ordering]: https://www.chessprogramming.org/Move_Ordering
|
||||
[iterative deepening]: https://www.chessprogramming.org/Iterative_Deepening
|
||||
[pv]: https://www.chessprogramming.org/Principal_Variation
|
||||
[eval]: https://www.chessprogramming.org/Evaluation
|
||||
[piece value]: https://en.wikipedia.org/wiki/Chess_piece_relative_value
|
||||
[center control]: https://www.chessprogramming.org/Center_Control
|
||||
[king safety]: https://www.chessprogramming.org/King_Safety
|
||||
[ply]: https://en.wikipedia.org/wiki/Ply_(game_theory)
|
||||
[eval score]: https://www.chessprogramming.org/Score
|
||||
[centipawns]: https://www.chessprogramming.org/Centipawns
|
||||
[time control]: https://en.wikipedia.org/wiki/Time_control#Chess
|
||||
[catch2]: https://github.com/catchorg/Catch2
|
||||
[catch2 filter tags]: https://github.com/catchorg/Catch2/blob/v2.x/docs/command-line.md#specifying-which-tests-to-run
|
||||
[chess tactic]: https://en.wikipedia.org/wiki/Chess_tactic
|
||||
[positional advantage]: https://www.chessstrategyonline.com/content/tutorials/introduction-to-chess-strategy-positional-advantage
|
||||
[lichess]: https://lichess.org/
|
||||
[lichess puzzle db]: https://database.lichess.org/#puzzles
|
||||
[lichess puzzle themes]: https://lichess.org/training/themes
|
||||
[fen]: https://en.wikipedia.org/wiki/Forsyth-Edwards_Notation
|
||||
[lichess board editor]: https://lichess.org/editor
|
||||
[uci]: https://en.wikipedia.org/wiki/Universal_Chess_Interface
|
||||
[uci protocol]: https://backscattering.de/chess/uci/
|
||||
[uci gui]: https://www.chessprogramming.org/UCI#GUIs
|
||||
[cutechess]: https://github.com/cutechess/cutechess
|
||||
[cutechess cli]: https://github.com/cutechess/cutechess#running
|
||||
[chess engine]: https://en.wikipedia.org/wiki/Chess_engine
|
||||
[questions]: https://gitlab.kuleuven.be/distrinet/education/cpl/cplusplus-project-assignment/-/issues
|
||||
[assignment repo]: https://gitlab.kuleuven.be/distrinet/education/cpl/cplusplus-project-assignment
|
||||
[exercises setup]: https://gitlab.kuleuven.be/distrinet/education/cpl/cplusplus-exercises-assignment#setup
|
||||
[git submodules]: https://git-scm.com/book/en/v2/Git-Tools-Submodules
|
||||
[c++17]: https://en.cppreference.com/w/cpp/17
|
||||
[c++20]: https://en.cppreference.com/w/cpp/20
|
||||
[c++ features]: https://en.cppreference.com/w/cpp
|
||||
[fifty-move rule]: https://en.wikipedia.org/wiki/Fifty-move_rule
|
||||
[threefold repetition]: https://en.wikipedia.org/wiki/Threefold_repetition
|
||||
[gcc c++ status]: https://gcc.gnu.org/projects/cxx-status.html
|
||||
[gitlab pipelines]: https://docs.gitlab.com/ee/ci/pipelines/
|
||||
[gcc warnings]: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
|
||||
[std optional]: https://en.cppreference.com/w/cpp/utility/optional
|
||||
[transposition table]: https://www.chessprogramming.org/Transposition_Table
|
||||
[glicko]: https://en.wikipedia.org/wiki/Glicko_rating_system
|
126
Square.cpp
Normal file
126
Square.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include "Square.hpp"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
Square::Square(Index index)
|
||||
{
|
||||
(void)index;
|
||||
}
|
||||
|
||||
Square::Optional Square::fromCoordinates(Coordinate file, Coordinate rank) {
|
||||
(void)file;
|
||||
(void)rank;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Square::Optional Square::fromIndex(Index index) {
|
||||
(void)index;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Square::Optional Square::fromName(const std::string& name) {
|
||||
(void)name;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Square::Coordinate Square::file() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Square::Coordinate Square::rank() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Square::Index Square::index() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
const Square Square::A1 = Square( 0 + 0);
|
||||
const Square Square::B1 = Square( 0 + 1);
|
||||
const Square Square::C1 = Square( 0 + 2);
|
||||
const Square Square::D1 = Square( 0 + 3);
|
||||
const Square Square::E1 = Square( 0 + 4);
|
||||
const Square Square::F1 = Square( 0 + 5);
|
||||
const Square Square::G1 = Square( 0 + 6);
|
||||
const Square Square::H1 = Square( 0 + 7);
|
||||
|
||||
const Square Square::A2 = Square( 8 + 0);
|
||||
const Square Square::B2 = Square( 8 + 1);
|
||||
const Square Square::C2 = Square( 8 + 2);
|
||||
const Square Square::D2 = Square( 8 + 3);
|
||||
const Square Square::E2 = Square( 8 + 4);
|
||||
const Square Square::F2 = Square( 8 + 5);
|
||||
const Square Square::G2 = Square( 8 + 6);
|
||||
const Square Square::H2 = Square( 8 + 7);
|
||||
|
||||
const Square Square::A3 = Square(16 + 0);
|
||||
const Square Square::B3 = Square(16 + 1);
|
||||
const Square Square::C3 = Square(16 + 2);
|
||||
const Square Square::D3 = Square(16 + 3);
|
||||
const Square Square::E3 = Square(16 + 4);
|
||||
const Square Square::F3 = Square(16 + 5);
|
||||
const Square Square::G3 = Square(16 + 6);
|
||||
const Square Square::H3 = Square(16 + 7);
|
||||
|
||||
const Square Square::A4 = Square(24 + 0);
|
||||
const Square Square::B4 = Square(24 + 1);
|
||||
const Square Square::C4 = Square(24 + 2);
|
||||
const Square Square::D4 = Square(24 + 3);
|
||||
const Square Square::E4 = Square(24 + 4);
|
||||
const Square Square::F4 = Square(24 + 5);
|
||||
const Square Square::G4 = Square(24 + 6);
|
||||
const Square Square::H4 = Square(24 + 7);
|
||||
|
||||
const Square Square::A5 = Square(32 + 0);
|
||||
const Square Square::B5 = Square(32 + 1);
|
||||
const Square Square::C5 = Square(32 + 2);
|
||||
const Square Square::D5 = Square(32 + 3);
|
||||
const Square Square::E5 = Square(32 + 4);
|
||||
const Square Square::F5 = Square(32 + 5);
|
||||
const Square Square::G5 = Square(32 + 6);
|
||||
const Square Square::H5 = Square(32 + 7);
|
||||
|
||||
const Square Square::A6 = Square(40 + 0);
|
||||
const Square Square::B6 = Square(40 + 1);
|
||||
const Square Square::C6 = Square(40 + 2);
|
||||
const Square Square::D6 = Square(40 + 3);
|
||||
const Square Square::E6 = Square(40 + 4);
|
||||
const Square Square::F6 = Square(40 + 5);
|
||||
const Square Square::G6 = Square(40 + 6);
|
||||
const Square Square::H6 = Square(40 + 7);
|
||||
|
||||
const Square Square::A7 = Square(48 + 0);
|
||||
const Square Square::B7 = Square(48 + 1);
|
||||
const Square Square::C7 = Square(48 + 2);
|
||||
const Square Square::D7 = Square(48 + 3);
|
||||
const Square Square::E7 = Square(48 + 4);
|
||||
const Square Square::F7 = Square(48 + 5);
|
||||
const Square Square::G7 = Square(48 + 6);
|
||||
const Square Square::H7 = Square(48 + 7);
|
||||
|
||||
const Square Square::A8 = Square(56 + 0);
|
||||
const Square Square::B8 = Square(56 + 1);
|
||||
const Square Square::C8 = Square(56 + 2);
|
||||
const Square Square::D8 = Square(56 + 3);
|
||||
const Square Square::E8 = Square(56 + 4);
|
||||
const Square Square::F8 = Square(56 + 5);
|
||||
const Square Square::G8 = Square(56 + 6);
|
||||
const Square Square::H8 = Square(56 + 7);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Square& square) {
|
||||
(void)square;
|
||||
return os;
|
||||
}
|
||||
|
||||
bool operator<(const Square& lhs, const Square& rhs) {
|
||||
(void)lhs;
|
||||
(void)rhs;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator==(const Square& lhs, const Square& rhs) {
|
||||
(void)lhs;
|
||||
(void)rhs;
|
||||
return false;
|
||||
}
|
43
Square.hpp
Normal file
43
Square.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef CHESS_ENGINE_SQUARE_HPP
|
||||
#define CHESS_ENGINE_SQUARE_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
class Square {
|
||||
public:
|
||||
|
||||
using Coordinate = unsigned;
|
||||
using Index = unsigned;
|
||||
using Optional = std::optional<Square>;
|
||||
|
||||
static Optional fromCoordinates(Coordinate file, Coordinate rank);
|
||||
static Optional fromIndex(Index index);
|
||||
static Optional fromName(const std::string& name);
|
||||
|
||||
Coordinate file() const;
|
||||
Coordinate rank() const;
|
||||
Index index() const;
|
||||
|
||||
static const Square A1, B1, C1, D1, E1, F1, G1, H1;
|
||||
static const Square A2, B2, C2, D2, E2, F2, G2, H2;
|
||||
static const Square A3, B3, C3, D3, E3, F3, G3, H3;
|
||||
static const Square A4, B4, C4, D4, E4, F4, G4, H4;
|
||||
static const Square A5, B5, C5, D5, E5, F5, G5, H5;
|
||||
static const Square A6, B6, C6, D6, E6, F6, G6, H6;
|
||||
static const Square A7, B7, C7, D7, E7, F7, G7, H7;
|
||||
static const Square A8, B8, C8, D8, E8, F8, G8, H8;
|
||||
|
||||
private:
|
||||
|
||||
Square(Index index);
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Square& square);
|
||||
|
||||
// Necessary to support Square as the key in std::map.
|
||||
bool operator<(const Square& lhs, const Square& rhs);
|
||||
bool operator==(const Square& lhs, const Square& rhs);
|
||||
|
||||
#endif
|
945
Tests/BoardTests.cpp
Normal file
945
Tests/BoardTests.cpp
Normal file
@@ -0,0 +1,945 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "TestUtils.hpp"
|
||||
|
||||
#include "Board.hpp"
|
||||
#include "Square.hpp"
|
||||
#include "Fen.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
TEST_CASE("A default-constructed board is empty", "[Board][Fundamental]") {
|
||||
auto board = Board();
|
||||
|
||||
for (auto i = 0; i < 64; ++i) {
|
||||
auto optSquare = Square::fromIndex(i);
|
||||
REQUIRE(optSquare.has_value());
|
||||
|
||||
auto square = optSquare.value();
|
||||
auto optPiece = board.piece(square);
|
||||
REQUIRE_FALSE(optPiece.has_value());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Pieces can be set on a board", "[Board][Fundamental]") {
|
||||
auto board = Board();
|
||||
|
||||
auto optSquare = Square::fromIndex(41);
|
||||
REQUIRE(optSquare.has_value());
|
||||
|
||||
auto square = optSquare.value();
|
||||
auto piece = Piece(PieceColor::White, PieceType::Rook);
|
||||
board.setPiece(square, piece);
|
||||
|
||||
auto optSetPiece = board.piece(square);
|
||||
REQUIRE(optSetPiece.has_value());
|
||||
|
||||
auto setPiece = optSetPiece.value();
|
||||
REQUIRE(setPiece == piece);
|
||||
|
||||
SECTION("Setting a piece on an occupied square overrides") {
|
||||
auto newPiece = Piece(PieceColor::Black, PieceType::King);
|
||||
board.setPiece(square, newPiece);
|
||||
|
||||
auto optNewSetPiece = board.piece(square);
|
||||
REQUIRE(optNewSetPiece.has_value());
|
||||
|
||||
auto newSetPiece = optNewSetPiece.value();
|
||||
REQUIRE(newSetPiece == newPiece);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("The turn can be set on a board", "[Board][Fundamental]") {
|
||||
auto color = GENERATE(PieceColor::Black, PieceColor::White);
|
||||
|
||||
auto board = Board();
|
||||
board.setTurn(color);
|
||||
REQUIRE(board.turn() == color);
|
||||
}
|
||||
|
||||
static void testPseudoLegalMoves(
|
||||
const char* fen,
|
||||
const std::string& fromName,
|
||||
const std::vector<std::string>& expectedTargets)
|
||||
{
|
||||
auto optBoard = Fen::createBoard(fen);
|
||||
REQUIRE(optBoard.has_value());
|
||||
|
||||
auto from = Square::Optional();
|
||||
|
||||
if (!fromName.empty()) {
|
||||
from = Square::fromName(fromName);
|
||||
REQUIRE(from.has_value());
|
||||
}
|
||||
|
||||
auto board = optBoard.value();
|
||||
|
||||
using MoveSet = std::set<Move>;
|
||||
auto expectedMoves = MoveSet();
|
||||
|
||||
for (auto targetName : expectedTargets) {
|
||||
if (targetName.length() == 2) {
|
||||
// targetName is a square
|
||||
auto optTarget = Square::fromName(targetName);
|
||||
REQUIRE(optTarget.has_value());
|
||||
REQUIRE(from.has_value());
|
||||
|
||||
auto target = optTarget.value();
|
||||
auto move = Move(from.value(), target);
|
||||
expectedMoves.insert(move);
|
||||
} else {
|
||||
// targetName is a move
|
||||
auto optMove = Move::fromUci(targetName);
|
||||
REQUIRE(optMove.has_value());
|
||||
|
||||
expectedMoves.insert(optMove.value());
|
||||
}
|
||||
}
|
||||
|
||||
auto generatedMovesVec = Board::MoveVec();
|
||||
|
||||
if (from.has_value()) {
|
||||
board.pseudoLegalMovesFrom(from.value(), generatedMovesVec);
|
||||
} else {
|
||||
board.pseudoLegalMoves(generatedMovesVec);
|
||||
}
|
||||
|
||||
auto generatedMoves = MoveSet(generatedMovesVec.begin(),
|
||||
generatedMovesVec.end());
|
||||
|
||||
auto unexpectedMoves = MoveSet();
|
||||
auto notGeneratedMoves = MoveSet();
|
||||
|
||||
std::set_difference(
|
||||
generatedMoves.begin(), generatedMoves.end(),
|
||||
expectedMoves.begin(), expectedMoves.end(),
|
||||
std::inserter(unexpectedMoves, unexpectedMoves.begin())
|
||||
);
|
||||
|
||||
std::set_difference(
|
||||
expectedMoves.begin(), expectedMoves.end(),
|
||||
generatedMoves.begin(), generatedMoves.end(),
|
||||
std::inserter(notGeneratedMoves, notGeneratedMoves.begin())
|
||||
);
|
||||
|
||||
CAPTURE(fen, from, unexpectedMoves, notGeneratedMoves);
|
||||
REQUIRE(generatedMoves == expectedMoves);
|
||||
}
|
||||
|
||||
#define TEST_CASE_PSEUDO_MOVES(name, tag) \
|
||||
TEST_CASE(name, "[Board][MoveGen]" tag)
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal knight moves, empty board", "[Knight]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3N4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/8/3N4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c6", "e6",
|
||||
"f5", "f3",
|
||||
"c2", "e2",
|
||||
"b3", "b5"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal knight moves, side of board", "[Knight]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/n7/8_b_-_-_0_1
|
||||
"8/8/8/8/8/8/n7/8 b - - 0 1",
|
||||
"a2",
|
||||
{
|
||||
"b4",
|
||||
"c3", "c1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal knight moves, captures", "[Knight]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/2p1P3/8/2PN4/2Pp4/8/8_w_-_-_0_1
|
||||
"8/8/2p1P3/8/2PN4/2Pp4/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c6",
|
||||
"f5", "f3",
|
||||
"c2", "e2",
|
||||
"b3", "b5"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal king moves, empty board", "[King]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3K4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/8/3K4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c5", "d5", "e5",
|
||||
"c4", "e4",
|
||||
"c3", "d3", "e3",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal king moves, side of board", "[King]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/8/K7_w_-_-_0_1
|
||||
"8/8/8/8/8/8/8/K7 w - - 0 1",
|
||||
"a1",
|
||||
{
|
||||
"a2", "b2",
|
||||
"b1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal king moves, captures", "[King]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/2PK4/3p4/8/8_w_-_-_0_1
|
||||
"8/8/8/8/2PK4/3p4/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c5", "d5", "e5",
|
||||
"e4",
|
||||
"c3", "d3", "e3",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal bishop moves, empty board", "[Bishop]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3B4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/8/3B4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c5", "b6", "a7",
|
||||
"e5", "f6", "g7", "h8",
|
||||
"c3", "b2", "a1",
|
||||
"e3", "f2", "g1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
"8/8/1p3P2/8/3B4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c5", "b6",
|
||||
"e5",
|
||||
"c3", "b2", "a1",
|
||||
"e3", "f2", "g1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal rook moves, empty board", "[Rook]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3R4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/8/3R4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"d5", "d6", "d7", "d8",
|
||||
"d3", "d2", "d1",
|
||||
"c4", "b4", "a4",
|
||||
"e4", "f4", "g4", "h4"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
"8/8/3p4/8/3R1P2/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"d5", "d6",
|
||||
"d3", "d2", "d1",
|
||||
"c4", "b4", "a4",
|
||||
"e4"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal queen moves, empty board", "[Queen]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3Q4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/8/3Q4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c5", "b6", "a7",
|
||||
"e5", "f6", "g7", "h8",
|
||||
"c3", "b2", "a1",
|
||||
"e3", "f2", "g1",
|
||||
"d5", "d6", "d7", "d8",
|
||||
"d3", "d2", "d1",
|
||||
"c4", "b4", "a4",
|
||||
"e4", "f4", "g4", "h4"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal queen moves, captures", "[Queen]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/1p3P2/8/1P1Q4/8/3p4/8_w_-_-_0_1
|
||||
"8/8/1p3P2/8/1P1Q4/8/3p4/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c5", "b6",
|
||||
"e5",
|
||||
"c3", "b2", "a1",
|
||||
"e3", "f2", "g1",
|
||||
"d5", "d6", "d7", "d8",
|
||||
"d3", "d2",
|
||||
"c4",
|
||||
"e4", "f4", "g4", "h4"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, step, empty board", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3P4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/8/3P4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"d5"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, step, opponent blocked", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/3p4/3P4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/3p4/3P4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, step, self blocked", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/3p4/3b4/8/8/8_b_-_-_0_1
|
||||
"8/8/8/3p4/3b4/8/8/8 b - - 0 1",
|
||||
"d5",
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, capture left", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/2ppP3/3P4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/2ppP3/3P4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c5"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, capture both", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/3p4/2PRQ3/8/8/8_b_-_-_0_1
|
||||
"8/8/8/3p4/2PRQ3/8/8/8 b - - 0 1",
|
||||
"d5",
|
||||
{
|
||||
"c4", "e4"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, base step, empty board", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/3p4/8/8/8/8/8/8_b_-_-_0_1
|
||||
"8/3p4/8/8/8/8/8/8 b - - 0 1",
|
||||
"d7",
|
||||
{
|
||||
"d6", "d5"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, base step, blocked 1", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/3p4/3P4/8/8/8/8/8_b_-_-_0_1
|
||||
"8/3p4/3P4/8/8/8/8/8 b - - 0 1",
|
||||
"d7",
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, base step, blocked 2", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/7p/8/7P/8_w_-_-_0_1
|
||||
"8/8/8/8/7p/8/7P/8 w - - 0 1",
|
||||
"h2",
|
||||
{
|
||||
"h3"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, en passant, black left", "[Pawn][EnPassant]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3Pp3/8/8/8_b_-_d3_0_1
|
||||
"8/8/8/8/3Pp3/8/8/8 b - d3 0 1",
|
||||
"e4",
|
||||
{
|
||||
"e3", "d3"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, en passant, black right", "[Pawn][EnPassant]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/2pP4/2P5/8/8_b_-_d3_0_1
|
||||
"8/8/8/8/2pP4/2P5/8/8 b - d3 0 1",
|
||||
"c4",
|
||||
{
|
||||
"d3"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, en passant, white left", "[Pawn][EnPassant]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/2n5/pP6/8/8/8/8_w_-_a6_0_1
|
||||
"8/8/2n5/pP6/8/8/8/8 w - a6 0 1",
|
||||
"b5",
|
||||
{
|
||||
"a6", "b6", "c6"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, en passant, white right", "[Pawn][EnPassant]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/n7/Pp6/8/8/8/8_w_-_b6_0_1
|
||||
"8/8/n7/Pp6/8/8/8/8 w - b6 0 1",
|
||||
"a5",
|
||||
{
|
||||
"b6"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, en passant, black both", "[Pawn][EnPassant]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/5pPp/7P/8/8_b_-_g3_0_1
|
||||
"8/8/8/8/5pPp/7P/8/8 b - g3 0 1",
|
||||
"",
|
||||
{
|
||||
"f4f3", "f4g3",
|
||||
"h4g3"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, en passant, white both", "[Pawn][EnPassant]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/2r2n2/2PpP3/8/8/8/8_w_-_d6_0_1
|
||||
"8/8/2r2n2/2PpP3/8/8/8/8 w - d6 0 1",
|
||||
"",
|
||||
{
|
||||
"c5d6",
|
||||
"e5d6", "e5e6", "e5f6"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, en passant, wrong square", "[Pawn][EnPassant]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3p1P2/8/8/8_b_-_f3_0_1
|
||||
"8/8/8/8/3p1P2/8/8/8 b - f3 0 1",
|
||||
"d4",
|
||||
{
|
||||
"d3"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, visual en passant, no square", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/1pPp4/8/8/8/8_w_-_-_0_1
|
||||
"8/8/8/1pPp4/8/8/8/8 w - - 0 1",
|
||||
"c5",
|
||||
{
|
||||
"c6"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, white promotions", "[Pawn][Promotion]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/5r2/4P3/8/8/8/8/8/8_w_-_-_0_1
|
||||
"5r2/4P3/8/8/8/8/8/8 w - - 0 1",
|
||||
"e7",
|
||||
{
|
||||
"e7e8q", "e7e8r", "e7e8b", "e7e8n",
|
||||
"e7f8q", "e7f8r", "e7f8b", "e7f8n"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, black promotions", "[Pawn][Promotion]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/1p6/2Q5_b_-_-_0_1
|
||||
"8/8/8/8/8/8/1p6/2Q5 b - - 0 1",
|
||||
"b2",
|
||||
{
|
||||
"b2b1q", "b2b1r", "b2b1b", "b2b1n",
|
||||
"b2c1q", "b2c1r", "b2c1b", "b2c1n"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, white kingside", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/3PPP2/3QK2R_w_K_-_0_1
|
||||
"8/8/8/8/8/8/3PPP2/3QK2R w K - 0 1",
|
||||
"e1",
|
||||
{
|
||||
"f1", "g1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, white queenside", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/3PPP2/R3KB2_w_Q_-_0_1
|
||||
"8/8/8/8/8/8/3PPP2/R3KB2 w Q - 0 1",
|
||||
"e1",
|
||||
{
|
||||
"d1", "c1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, black kingside", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/3qk2r/3ppp2/8/8/8/8/8/8_b_k_-_0_1
|
||||
"3qk2r/3ppp2/8/8/8/8/8/8 b k - 0 1",
|
||||
"e8",
|
||||
{
|
||||
"f8", "g8"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, black queenside", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/r3kb2/3ppp2/8/8/8/8/8/8_b_q_-_0_1
|
||||
"r3kb2/3ppp2/8/8/8/8/8/8 b q - 0 1",
|
||||
"e8",
|
||||
{
|
||||
"d8", "c8"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, kingside blocked", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/3PPP2/3QK1NR_w_K_-_0_1
|
||||
"8/8/8/8/8/8/3PPP2/3QK1NR w K - 0 1",
|
||||
"e1",
|
||||
{
|
||||
"f1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, queenside blocked", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/rn2kb2/3ppp2/8/8/8/8/8/8_b_q_-_0_1
|
||||
"rn2kb2/3ppp2/8/8/8/8/8/8 b q - 0 1",
|
||||
"e8",
|
||||
{
|
||||
"d8"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, kingside attacked", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/3qk2r/3ppp2/5N2/8/8/8/8/8_b_k_-_0_1
|
||||
"3qk2r/3ppp2/5N2/8/8/8/8/8 b k - 0 1",
|
||||
"e8",
|
||||
{
|
||||
"f8"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, queenside attacked", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/5n2/3PPP2/R3KB2_w_Q_-_0_1
|
||||
"8/8/8/8/8/5n2/3PPP2/R3KB2 w Q - 0 1",
|
||||
"e1",
|
||||
{
|
||||
"d1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, queenside rook attacked", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/r3kb2/3ppp2/8/8/8/5B2/8/8_b_q_-_0_1
|
||||
"r3kb2/3ppp2/8/8/8/5B2/8/8 b q - 0 1",
|
||||
"e8",
|
||||
{
|
||||
"d8", "c8"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, kingside rook attacked", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/2q5/8/8/8/3PPP2/3QK2R_w_K_-_0_1
|
||||
"8/8/2q5/8/8/8/3PPP2/3QK2R w K - 0 1",
|
||||
"e1",
|
||||
{
|
||||
"f1", "g1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, queenside b-square attacked", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/r3kb2/3ppp2/8/8/8/8/8/1R6_b_q_-_0_1
|
||||
"r3kb2/3ppp2/8/8/8/8/8/1R6 b q - 0 1",
|
||||
"e8",
|
||||
{
|
||||
"d8", "c8"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, no rights", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/r3k2r/8/8/8/8/8/3PPP2/R3K2R_w_Kkq_-_0_1
|
||||
"r3k2r/8/8/8/8/8/3PPP2/R3K2R w Kkq - 0 1",
|
||||
"e1",
|
||||
{
|
||||
"f1", "g1",
|
||||
"d1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static void testMakeMove(const char* fen, const Move& move) {
|
||||
auto board = Fen::createBoard(fen);
|
||||
REQUIRE(board.has_value());
|
||||
|
||||
auto piece = board->piece(move.from());
|
||||
REQUIRE(piece.has_value());
|
||||
|
||||
auto turn = board->turn();
|
||||
board->makeMove(move);
|
||||
REQUIRE(board->turn() == !turn);
|
||||
|
||||
REQUIRE_FALSE(board->piece(move.from()).has_value());
|
||||
|
||||
auto toPiece = board->piece(move.to());
|
||||
REQUIRE(toPiece.has_value());
|
||||
REQUIRE(piece == toPiece.value());
|
||||
}
|
||||
|
||||
static void testMakeMove(const char* fen, const Move& move,
|
||||
const std::map<Square, Piece>& expectedBoard) {
|
||||
CAPTURE(fen, move);
|
||||
|
||||
auto board = Fen::createBoard(fen);
|
||||
REQUIRE(board.has_value());
|
||||
|
||||
auto turn = board->turn();
|
||||
board->makeMove(move);
|
||||
REQUIRE(board->turn() == !turn);
|
||||
|
||||
for (auto [square, expectedPiece] : expectedBoard) {
|
||||
auto piece = board->piece(square);
|
||||
INFO("Expected piece not on board");
|
||||
CAPTURE(square, piece, expectedPiece);
|
||||
REQUIRE(piece.has_value());
|
||||
REQUIRE(piece.value() == expectedPiece);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 64; ++i) {
|
||||
auto square = Square::fromIndex(i);
|
||||
REQUIRE(square.has_value());
|
||||
|
||||
auto piece = board->piece(square.value());
|
||||
CAPTURE(square.value(), piece);
|
||||
auto expectedPieceIt = expectedBoard.find(square.value());
|
||||
|
||||
if (piece.has_value()) {
|
||||
INFO("Unexpected piece on board");
|
||||
REQUIRE(expectedPieceIt != expectedBoard.end());
|
||||
CAPTURE(expectedPieceIt->second);
|
||||
REQUIRE(piece.value() == expectedPieceIt->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void testMakeMove(const char* fen, const Move& move, CastlingRights cr) {
|
||||
auto board = Fen::createBoard(fen);
|
||||
REQUIRE(board.has_value());
|
||||
|
||||
auto turn = board->turn();
|
||||
board->makeMove(move);
|
||||
REQUIRE(board->turn() == !turn);
|
||||
|
||||
REQUIRE(board->castlingRights() == cr);
|
||||
}
|
||||
|
||||
static void testMakeMove(const char* fen, const Move& move,
|
||||
const Square::Optional& epSquare) {
|
||||
auto board = Fen::createBoard(fen);
|
||||
REQUIRE(board.has_value());
|
||||
|
||||
auto turn = board->turn();
|
||||
board->makeMove(move);
|
||||
REQUIRE(board->turn() == !turn);
|
||||
|
||||
REQUIRE(board->enPassantSquare() == epSquare);
|
||||
}
|
||||
|
||||
#define TEST_CASE_MAKE_MOVE(name, tag) \
|
||||
TEST_CASE(name, "[Board][MoveMaking]" tag)
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making moves a piece on the board", "") {
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/8/2Q5_w_-_-_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/8/8/8/8/2Q5 w - - 0 1",
|
||||
Move(Square::C1, Square::F4)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making supports captures", "") {
|
||||
// https://lichess.org/editor/8/8/8/2n5/8/3P4/8/8_b_-_-_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/2n5/8/3P4/8/8 b - - 0 1",
|
||||
Move(Square::C5, Square::D3)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, white promotion", "[Promotion]") {
|
||||
// https://lichess.org/editor/8/4P3/8/8/8/8/8/8_w_-_-_0_1
|
||||
testMakeMove(
|
||||
"8/4P3/8/8/8/8/8/8 w - - 0 1",
|
||||
Move(Square::E7, Square::E8, PieceType::Queen),
|
||||
{
|
||||
{Square::E8, Piece(PieceColor::White, PieceType::Queen)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, black promotion", "[Promotion]") {
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/p7/8_b_-_-_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/8/8/8/p7/8 b - - 0 1",
|
||||
Move(Square::A2, Square::A1, PieceType::Knight),
|
||||
{
|
||||
{Square::A1, Piece(PieceColor::Black, PieceType::Knight)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling white kingside", "[Castling]") {
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/8/4K2R_w_K_-_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/8/8/8/8/4K2R w K - 0 1",
|
||||
Move(Square::E1, Square::G1),
|
||||
{
|
||||
{Square::G1, Piece(PieceColor::White, PieceType::King)},
|
||||
{Square::F1, Piece(PieceColor::White, PieceType::Rook)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling white queenside", "[Castling]") {
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/8/R3K3_w_Q_-_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/8/8/8/8/R3K3 w Q - 0 1",
|
||||
Move(Square::E1, Square::C1),
|
||||
{
|
||||
{Square::C1, Piece(PieceColor::White, PieceType::King)},
|
||||
{Square::D1, Piece(PieceColor::White, PieceType::Rook)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling black kingside", "[Castling]") {
|
||||
// https://lichess.org/editor/4k2r/8/8/8/8/8/8/8_b_k_-_0_1
|
||||
testMakeMove(
|
||||
"4k2r/8/8/8/8/8/8/8 b k - 0 1",
|
||||
Move(Square::E8, Square::G8),
|
||||
{
|
||||
{Square::G8, Piece(PieceColor::Black, PieceType::King)},
|
||||
{Square::F8, Piece(PieceColor::Black, PieceType::Rook)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling black queenside", "[Castling]") {
|
||||
// https://lichess.org/editor/r3k3/8/8/8/8/8/8/8_b_q_-_0_1
|
||||
testMakeMove(
|
||||
"r3k3/8/8/8/8/8/8/8 b q - 0 1",
|
||||
Move(Square::E8, Square::C8),
|
||||
{
|
||||
{Square::C8, Piece(PieceColor::Black, PieceType::King)},
|
||||
{Square::D8, Piece(PieceColor::Black, PieceType::Rook)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling rights king move", "[Castling]") {
|
||||
// https://lichess.org/editor/r3k2r/8/8/8/8/8/8/R3K2R_w_KQkq_-_0_1
|
||||
testMakeMove(
|
||||
"r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1",
|
||||
Move(Square::E1, Square::D1),
|
||||
CastlingRights::Black
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling rights rook move", "[Castling]") {
|
||||
// https://lichess.org/editor/r3k2r/8/8/8/8/8/8/R3K2R_b_KQkq_-_0_1
|
||||
testMakeMove(
|
||||
"r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1",
|
||||
Move(Square::H8, Square::G8),
|
||||
CastlingRights::White | CastlingRights::BlackQueenside
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling rights rook capture", "[Castling]") {
|
||||
// https://lichess.org/editor/r3k2r/8/8/8/8/8/8/R3K2R_w_KQkq_-_0_1
|
||||
testMakeMove(
|
||||
"r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1",
|
||||
Move(Square::A1, Square::A8),
|
||||
CastlingRights::WhiteKingside | CastlingRights::BlackKingside
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling rights king back to base", "[Castling]") {
|
||||
// https://lichess.org/editor/r3k2r/8/8/8/8/8/8/R2K3R_w_kq_-_0_1
|
||||
testMakeMove(
|
||||
"r3k2r/8/8/8/8/8/8/R2K3R w kq - 0 1",
|
||||
Move(Square::D1, Square::E1),
|
||||
CastlingRights::Black
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant, black left", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/8/8/8/5Pp1/8/8/8_b_-_f3_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/8/5Pp1/8/8/8 b - f3 0 1",
|
||||
Move(Square::G4, Square::F3),
|
||||
{
|
||||
{Square::F3, Piece(PieceColor::Black, PieceType::Pawn)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant, black right", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/8/8/3P4/1PpPP3/2P5/8/8_b_-_d3_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/3P4/1PpPP3/2P5/8/8 b - d3 0 1",
|
||||
Move(Square::C4, Square::D3),
|
||||
{
|
||||
{Square::D3, Piece(PieceColor::Black, PieceType::Pawn)},
|
||||
{Square::B4, Piece(PieceColor::White, PieceType::Pawn)},
|
||||
{Square::C3, Piece(PieceColor::White, PieceType::Pawn)},
|
||||
{Square::D5, Piece(PieceColor::White, PieceType::Pawn)},
|
||||
{Square::E4, Piece(PieceColor::White, PieceType::Pawn)},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant, white left", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/8/8/3pPp2/8/8/8/8_w_-_d6_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/3pPp2/8/8/8/8 w - d6 0 1",
|
||||
Move(Square::E5, Square::D6),
|
||||
{
|
||||
{Square::D6, Piece(PieceColor::White, PieceType::Pawn)},
|
||||
{Square::F5, Piece(PieceColor::Black, PieceType::Pawn)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant, white right", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/8/8/1Pp5/8/8/8/8_w_-_c6_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/1Pp5/8/8/8/8 w - c6 0 1",
|
||||
Move(Square::B5, Square::C6),
|
||||
{
|
||||
{Square::C6, Piece(PieceColor::White, PieceType::Pawn)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant square, white double step update", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/8/8/8/3p4/8/4P3/8_w_-_-_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/8/3p4/8/4P3/8 w - - 0 1",
|
||||
Move(Square::E2, Square::E4),
|
||||
Square::E3
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant square, black double step update", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/p7/8/1P6/8/8/8/8_b_-_-_0_1
|
||||
testMakeMove(
|
||||
"8/p7/8/1P6/8/8/8/8 b - - 0 1",
|
||||
Move(Square::A7, Square::A5),
|
||||
Square::A6
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant square, remove after capture", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/8/8/8/Pp6/8/8/8_b_-_a3_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/8/Pp6/8/8/8 b - a3 0 1",
|
||||
Move(Square::B4, Square::A3),
|
||||
std::nullopt
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant square, remove after move", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/8/8/4pP2/8/8/1N6/8_w_-_e6_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/4pP2/8/8/1N6/8 w - e6 0 1",
|
||||
Move(Square::B2, Square::C4),
|
||||
std::nullopt
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal moves, multiple pieces, white", "") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/2P5/NB6_w_-_-_0_1
|
||||
"8/8/8/8/8/8/2P5/NB6 w - - 0 1",
|
||||
"",
|
||||
{
|
||||
"a1b3",
|
||||
"b1a2",
|
||||
"c2c3", "c2c4"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal moves, multiple pieces, black", "") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/7n/7b/6p1/8/8/8/8/8_b_-_-_0_1
|
||||
"7n/7b/6p1/8/8/8/8/8 b - - 0 1",
|
||||
"",
|
||||
{
|
||||
"h8f7",
|
||||
"h7g8",
|
||||
"g6g5"
|
||||
}
|
||||
);
|
||||
}
|
16
Tests/CMakeLists.txt
Normal file
16
Tests/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
add_subdirectory(Catch2/)
|
||||
|
||||
add_executable(tests
|
||||
Main.cpp
|
||||
SquareTests.cpp
|
||||
MoveTests.cpp
|
||||
PieceTests.cpp
|
||||
BoardTests.cpp
|
||||
FenTests.cpp
|
||||
EngineTests.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(tests cplchess_lib Catch2::Catch2)
|
||||
|
||||
include(Catch2/contrib/Catch.cmake)
|
||||
catch_discover_tests(tests)
|
1
Tests/Catch2
Submodule
1
Tests/Catch2
Submodule
Submodule Tests/Catch2 added at c4e3767e26
48
Tests/EngineTests.cpp
Normal file
48
Tests/EngineTests.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "TestUtils.hpp"
|
||||
|
||||
#include "EngineFactory.hpp"
|
||||
#include "Engine.hpp"
|
||||
#include "Fen.hpp"
|
||||
#include "Board.hpp"
|
||||
|
||||
static std::unique_ptr<Engine> createEngine() {
|
||||
return EngineFactory::createEngine();
|
||||
}
|
||||
|
||||
static void testGameEnd(const char* fen, bool isMate) {
|
||||
auto engine = createEngine();
|
||||
REQUIRE(engine != nullptr);
|
||||
|
||||
auto board = Fen::createBoard(fen);
|
||||
REQUIRE(board.has_value());
|
||||
|
||||
auto pv = engine->pv(board.value());
|
||||
|
||||
REQUIRE(pv.isMate() == isMate);
|
||||
REQUIRE(pv.score() == 0);
|
||||
REQUIRE(pv.length() == 0);
|
||||
}
|
||||
|
||||
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
|
||||
"4R2k/6pp/8/8/8/8/8/K7 b - - 0 1",
|
||||
// https://lichess.org/editor/7k/8/8/8/1bb5/8/r7/3BK3_w_-_-_0_1
|
||||
"7k/8/8/8/1bb5/8/r7/3BK3 w - - 0 1"
|
||||
);
|
||||
|
||||
testGameEnd(fen, true);
|
||||
}
|
||||
|
||||
TEST_CASE("Engine detects stalemate", "[Engine][Stalemate]") {
|
||||
auto fen = GENERATE(
|
||||
// https://lichess.org/editor/k7/p7/P7/8/8/8/8/KR6_b_-_-_0_1
|
||||
"k7/p7/P7/8/8/8/8/KR6 b - - 0 1",
|
||||
// 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"
|
||||
);
|
||||
|
||||
testGameEnd(fen, false);
|
||||
}
|
117
Tests/FenTests.cpp
Normal file
117
Tests/FenTests.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "TestUtils.hpp"
|
||||
|
||||
#include "Fen.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
TEST_CASE("Legal placements are correctly parsed", "[Fen]") {
|
||||
// https://lichess.org/editor/k7/2B5/7p/1q3P2/8/3N4/8/5r2_w_-_-_0_1
|
||||
auto fen = "k7/2B5/7p/1q3P2/8/3N4/8/5r2 w - - 0 1";
|
||||
|
||||
auto optBoard = Fen::createBoard(fen);
|
||||
REQUIRE(optBoard.has_value());
|
||||
|
||||
auto board = optBoard.value();
|
||||
|
||||
auto expectedPlacement = std::map<Square, Piece>{
|
||||
{Square::F1, Piece(PieceColor::Black, PieceType::Rook)},
|
||||
{Square::D3, Piece(PieceColor::White, PieceType::Knight)},
|
||||
{Square::B5, Piece(PieceColor::Black, PieceType::Queen)},
|
||||
{Square::F5, Piece(PieceColor::White, PieceType::Pawn)},
|
||||
{Square::H6, Piece(PieceColor::Black, PieceType::Pawn)},
|
||||
{Square::C7, Piece(PieceColor::White, PieceType::Bishop)},
|
||||
{Square::A8, Piece(PieceColor::Black, PieceType::King)}
|
||||
};
|
||||
|
||||
for (auto index = 0; index < 64; ++index) {
|
||||
auto optSquare = Square::fromIndex(index);
|
||||
REQUIRE(optSquare.has_value());
|
||||
|
||||
auto square = optSquare.value();
|
||||
INFO("Checking square " << square);
|
||||
|
||||
auto expectedPieceIt = expectedPlacement.find(square);
|
||||
auto optActualPiece = board.piece(square);
|
||||
|
||||
if (expectedPieceIt == expectedPlacement.end()) {
|
||||
INFO("Expecting empty")
|
||||
REQUIRE_FALSE(optActualPiece.has_value());
|
||||
} else {
|
||||
auto expectedPiece = expectedPieceIt->second;
|
||||
INFO("Expecting " << expectedPiece);
|
||||
|
||||
REQUIRE(optActualPiece.has_value());
|
||||
|
||||
auto actualPiece = optActualPiece.value();
|
||||
REQUIRE(actualPiece == expectedPiece);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Legal turns are correctly parsed", "[Fen]") {
|
||||
auto [fen, turn] = GENERATE(table<const char*, PieceColor>({
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/8/8_w_-_-_0_1
|
||||
{"8/8/8/8/8/8/8/8 w - - 0 1", PieceColor::White},
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/8/8_b_-_-_0_1
|
||||
{"8/8/8/8/8/8/8/8 b - - 0 1", PieceColor::Black}
|
||||
}));
|
||||
|
||||
auto optBoard = Fen::createBoard(fen);
|
||||
REQUIRE(optBoard.has_value());
|
||||
|
||||
auto board = optBoard.value();
|
||||
REQUIRE(board.turn() == turn);
|
||||
}
|
||||
|
||||
TEST_CASE("Castling rights are correctly parsed", "[Fen]") {
|
||||
auto [fen, cr] = GENERATE(table<const char*, CastlingRights>({
|
||||
// https://lichess.org/editor/rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR_w_KQkq_-_0_1
|
||||
{
|
||||
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
|
||||
CastlingRights::All
|
||||
},
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/8/8_b_-_-_0_1
|
||||
{
|
||||
"8/8/8/8/8/8/8/8 b - - 0 1",
|
||||
CastlingRights::None
|
||||
},
|
||||
// https://lichess.org/editor/rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR_w_Qk_-_0_1
|
||||
{
|
||||
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w Qk - 0 1",
|
||||
CastlingRights::WhiteQueenside | CastlingRights::BlackKingside
|
||||
}
|
||||
}));
|
||||
|
||||
CAPTURE(fen, cr);
|
||||
|
||||
auto optBoard = Fen::createBoard(fen);
|
||||
REQUIRE(optBoard.has_value());
|
||||
|
||||
auto board = optBoard.value();
|
||||
REQUIRE(board.castlingRights() == cr);
|
||||
}
|
||||
|
||||
TEST_CASE("En passant square is correctly parsed", "[Fen][EnPassant]") {
|
||||
auto [fen, ep] = GENERATE(table<const char*, Square::Optional>({
|
||||
// https://lichess.org/editor/rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR_w_KQkq_-_0_1
|
||||
{
|
||||
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
|
||||
std::nullopt
|
||||
},
|
||||
// https://lichess.org/editor/rnbqkbnr/ppppp1pp/8/8/2PPPp2/8/PP3PPP/RNBQKBNR_b_KQkq_e3_0_3
|
||||
{
|
||||
"rnbqkbnr/ppppp1pp/8/8/2PPPp2/8/PP3PPP/RNBQKBNR b KQkq e3 0 3",
|
||||
Square::E3
|
||||
}
|
||||
}));
|
||||
|
||||
CAPTURE(fen, ep);
|
||||
|
||||
auto optBoard = Fen::createBoard(fen);
|
||||
REQUIRE(optBoard.has_value());
|
||||
|
||||
auto board = optBoard.value();
|
||||
REQUIRE(board.enPassantSquare() == ep);
|
||||
}
|
2
Tests/Main.cpp
Normal file
2
Tests/Main.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch2/catch.hpp"
|
111
Tests/MoveTests.cpp
Normal file
111
Tests/MoveTests.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "TestUtils.hpp"
|
||||
|
||||
#include "Move.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
TEST_CASE("Moves store the squares they are constructed with", "[Move][Fundamental]") {
|
||||
auto from = Square::E2;
|
||||
auto to = Square::G6;
|
||||
auto move = Move(from, to);
|
||||
REQUIRE(move.from() == from);
|
||||
REQUIRE(move.to() == to);
|
||||
REQUIRE_FALSE(move.promotion().has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("Moves store the promotion they are constructed with", "[Move][Promotion]") {
|
||||
auto from = Square::A7;
|
||||
auto to = Square::A8;
|
||||
auto promotion = PieceType::Knight;
|
||||
auto move = Move(from, to, promotion);
|
||||
REQUIRE(move.from() == from);
|
||||
REQUIRE(move.to() == to);
|
||||
REQUIRE(move.promotion().has_value());
|
||||
REQUIRE(move.promotion().value() == promotion);
|
||||
}
|
||||
|
||||
TEST_CASE("Moves support equality checks", "[Move][Fundamental]") {
|
||||
auto move1 = Move(Square::D4, Square::H7);
|
||||
auto move2 = Move(Square::D4, Square::G3);
|
||||
auto move3 = Move(Square::A3, Square::H7);
|
||||
auto move1Copy = move1;
|
||||
REQUIRE(move1 == move1Copy);
|
||||
REQUIRE_FALSE(move1 == move2);
|
||||
REQUIRE_FALSE(move1 == move3);
|
||||
}
|
||||
|
||||
TEST_CASE("Moves with promotions support equality checks", "[Move][Promotion]") {
|
||||
auto move1 = Move(Square::B2, Square::B1, PieceType::Queen);
|
||||
auto move2 = Move(Square::B2, Square::B1);
|
||||
auto move3 = Move(Square::C2, Square::C1, PieceType::Queen);
|
||||
auto move1Copy = move1;
|
||||
REQUIRE(move1 == move1Copy);
|
||||
REQUIRE_FALSE(move1 == move2);
|
||||
REQUIRE_FALSE(move1 == move3);
|
||||
}
|
||||
|
||||
TEST_CASE("Moves stream their UCI correctly", "[Move][Fundamental]") {
|
||||
auto [from, to, uci] = GENERATE(table<Square, Square, const char*>({
|
||||
{Square::A1, Square::D7, "a1d7"},
|
||||
{Square::C8, Square::F2, "c8f2"},
|
||||
{Square::H6, Square::B1, "h6b1"}
|
||||
}));
|
||||
|
||||
auto move = Move(from, to);
|
||||
auto stream = std::stringstream();
|
||||
stream << move;
|
||||
REQUIRE(stream.str() == uci);
|
||||
}
|
||||
|
||||
TEST_CASE("Moves with promotions stream their UCI correctly", "[Move][Promotion]") {
|
||||
auto [from, to, promotion, uci] = GENERATE(table<Square, Square, PieceType, const char*>({
|
||||
{Square::H7, Square::H8, PieceType::Knight, "h7h8n"},
|
||||
{Square::C2, Square::C1, PieceType::Rook, "c2c1r"},
|
||||
{Square::E7, Square::E8, PieceType::Queen, "e7e8q"}
|
||||
}));
|
||||
|
||||
auto move = Move(from, to, promotion);
|
||||
auto stream = std::stringstream();
|
||||
stream << move;
|
||||
REQUIRE(stream.str() == uci);
|
||||
}
|
||||
|
||||
TEST_CASE("Moves can be created from valid UCI notation", "[Move][Fundamental]") {
|
||||
auto [uci, from, to] = GENERATE(table<const char*, Square, Square>({
|
||||
{"a1d7", Square::A1, Square::D7},
|
||||
{"c8f2", Square::C8, Square::F2},
|
||||
{"h6b1", Square::H6, Square::B1}
|
||||
}));
|
||||
|
||||
auto move = Move::fromUci(uci);
|
||||
auto expectedMove = Move(from, to);
|
||||
|
||||
CAPTURE(uci, expectedMove, move);
|
||||
REQUIRE(move.has_value());
|
||||
REQUIRE(move.value() == expectedMove);
|
||||
}
|
||||
|
||||
TEST_CASE("Moves can be created from valid UCI notation with promotion", "[Move][Promotion]") {
|
||||
auto [uci, from, to, promotion] = GENERATE(table<const char*, Square, Square, PieceType>({
|
||||
{"a7a8n", Square::A7, Square::A8, PieceType::Knight},
|
||||
{"c2c1b", Square::C2, Square::C1, PieceType::Bishop},
|
||||
{"h7h8r", Square::H7, Square::H8, PieceType::Rook}
|
||||
}));
|
||||
|
||||
auto move = Move::fromUci(uci);
|
||||
auto expectedMove = Move(from, to, promotion);
|
||||
|
||||
CAPTURE(uci, expectedMove, move);
|
||||
REQUIRE(move.has_value());
|
||||
REQUIRE(move.value() == expectedMove);
|
||||
}
|
||||
|
||||
TEST_CASE("Moves are not created from invalid UCI notation", "[Move][Fundamental]") {
|
||||
auto uci = GENERATE("a1d7x", "a1d", "a1d7123", "a1", "", "a9d1", "a1c0");
|
||||
auto move = Move::fromUci(uci);
|
||||
|
||||
CAPTURE(uci, move);
|
||||
REQUIRE_FALSE(move.has_value());
|
||||
}
|
74
Tests/PieceTests.cpp
Normal file
74
Tests/PieceTests.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "Piece.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <cctype>
|
||||
|
||||
TEST_CASE("Pieces store color and type correctly", "[Piece][Fundamental]") {
|
||||
auto color = PieceColor::Black;
|
||||
auto type = PieceType::Queen;
|
||||
auto piece = Piece(color, type);
|
||||
REQUIRE(piece.color() == color);
|
||||
REQUIRE(piece.type() == type);
|
||||
}
|
||||
|
||||
TEST_CASE("Pieces can be created from valid symbols", "[Piece][Fundamental]") {
|
||||
auto [blackSymbol, pieceType] = GENERATE(table<char, PieceType>({
|
||||
{'p', PieceType::Pawn},
|
||||
{'n', PieceType::Knight},
|
||||
{'b', PieceType::Bishop},
|
||||
{'r', PieceType::Rook},
|
||||
{'q', PieceType::Queen},
|
||||
{'k', PieceType::King}
|
||||
}));
|
||||
|
||||
auto optBlackPiece = Piece::fromSymbol(blackSymbol);
|
||||
REQUIRE(optBlackPiece.has_value());
|
||||
|
||||
auto createdBlackPiece = optBlackPiece.value();
|
||||
REQUIRE(createdBlackPiece == Piece(PieceColor::Black, pieceType));
|
||||
|
||||
auto whiteSymbol = static_cast<char>(std::toupper(blackSymbol));
|
||||
auto optWhitePiece = Piece::fromSymbol(whiteSymbol);
|
||||
REQUIRE(optWhitePiece.has_value());
|
||||
|
||||
auto createdWhitePiece = optWhitePiece.value();
|
||||
REQUIRE(createdWhitePiece == Piece(PieceColor::White, pieceType));
|
||||
}
|
||||
|
||||
TEST_CASE("Pieces are not created from invalid symbols", "[Piece][Fundamental]") {
|
||||
auto symbol = GENERATE('x', 'Z');
|
||||
|
||||
auto optPiece = Piece::fromSymbol(symbol);
|
||||
REQUIRE_FALSE(optPiece.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("Pieces stream their symbol correctly", "[Piece][Fundamental]") {
|
||||
auto [blackSymbol, pieceType] = GENERATE(table<char, PieceType>({
|
||||
{'p', PieceType::Pawn},
|
||||
{'n', PieceType::Knight},
|
||||
{'b', PieceType::Bishop},
|
||||
{'r', PieceType::Rook},
|
||||
{'q', PieceType::Queen},
|
||||
{'k', PieceType::King}
|
||||
}));
|
||||
|
||||
auto blackStream = std::stringstream();
|
||||
auto blackPiece = Piece(PieceColor::Black, pieceType);
|
||||
blackStream << blackPiece;
|
||||
auto blackSymbolStr = std::string(1, blackSymbol);
|
||||
REQUIRE(blackStream.str() == blackSymbolStr);
|
||||
|
||||
auto whiteStream = std::stringstream();
|
||||
auto whitePiece = Piece(PieceColor::White, pieceType);
|
||||
whiteStream << whitePiece;
|
||||
auto whiteSymbol = static_cast<char>(std::toupper(blackSymbol));
|
||||
auto whiteSymbolStr = std::string(1, whiteSymbol);
|
||||
REQUIRE(whiteStream.str() == whiteSymbolStr);
|
||||
}
|
||||
|
||||
TEST_CASE("PieceColor can be inverted correctly", "[Piece][Fundamental]") {
|
||||
REQUIRE((!PieceColor::White) == PieceColor::Black);
|
||||
REQUIRE((!PieceColor::Black) == PieceColor::White);
|
||||
}
|
20
Tests/Puzzles/crushing_castling.csv
Normal file
20
Tests/Puzzles/crushing_castling.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
1nIpz,4r3/pppkn3/8/3p2R1/3P4/8/PPP2P1b/R3K3 w Q - 1 21,e1c1 h2f4 c1b1 f4g5,816,95,90,588,crushing endgame fork short,https://lichess.org/hNgUKDs9#41
|
||||
Mu4H8,2r1k2r/1p3ppp/p1nqp3/3N4/6P1/3Q3P/PPP2P2/R3R1K1 b k - 0 18,e8g8 d5f6 g7f6 d3d6,1242,76,93,2547,crushing discoveredAttack middlegame short,https://lichess.org/4TidVneQ/black#36
|
||||
tXqKo,r1b1k2r/1pB2ppp/p7/n2q4/8/3B1N2/P5PP/RN1Q1K2 b kq - 0 19,e8g8 d3h7 g8h7 d1d5,1119,83,94,746,crushing discoveredAttack kingsideAttack opening short,https://lichess.org/7uOa49aA/black#38
|
||||
w24YE,r3k1r1/pbp1qp2/1pn2bp1/1N2p3/4Q3/PP2P1P1/RBPP2B1/4K2R b Kq - 0 20,e8c8 b5a7 c6a7 e4b7,1418,76,96,4838,crushing middlegame queensideAttack short,https://lichess.org/Xg4JUKxt/black#40
|
||||
3FThf,2kr1r2/pp3ppp/2n2nq1/4p3/8/2PP1Q2/PP2NPPP/RN2K2R w KQ - 2 16,e1g1 d8d3 f3d3 g6d3,1468,76,85,526,crushing middlegame short trappedPiece,https://lichess.org/IsNuAAN9#31
|
||||
jJ2Y6,r3kb1r/ppp2ppp/1nb5/6q1/3Q4/2P2P2/PP1NN1PP/R1B1K2R w KQkq - 1 12,e1g1 f8c5 d2b3 c5d4,1251,79,92,1234,crushing opening pin short,https://lichess.org/jXD6blSd#23
|
||||
RgLWD,r4r1k/1pp1q1pp/p2b1pn1/8/P2P4/1PQ1PN2/1B3PPP/R3K2R w KQ - 3 17,d4d5 d6b4 e1g1 b4c3,1061,76,91,782,crushing middlegame pin short,https://lichess.org/5BMsYBNs#33
|
||||
Oy4HD,4r1k1/1ppnqpb1/p5pp/3p4/1n1P2PP/PPQ2P2/1B1NN3/R3K2R w KQ - 1 20,e1c1 b4a2 c1b1 a2c3,1022,75,95,2356,crushing fork middlegame short,https://lichess.org/rLlXAUHf#39
|
||||
mYVLz,r3k2r/pp1q1pp1/2n4p/3Nb3/2P1pN2/P7/R4PPP/3Q1RK1 b kq - 0 16,e8g8 d5f6 g7f6 d1d7,1228,75,96,4950,crushing discoveredAttack middlegame short,https://lichess.org/4Td7Ylh7/black#32
|
||||
Xru4M,r1b1k2r/ppp2ppp/8/2bq4/8/3B4/PPPQ1PPP/R1B2RK1 b kq - 1 11,e8g8 d3h7 g8h7 d2d5,832,80,98,741,crushing discoveredAttack kingsideAttack middlegame short,https://lichess.org/SUQJEod2/black#22
|
||||
O3mf8,r1bqk2r/pp3pp1/2nb3p/2pQ4/3P4/2P2N2/PP3PPP/RNB1K2R w KQkq - 0 11,e1g1 d6h2 f3h2 d8d5,992,77,98,503,crushing discoveredAttack kingsideAttack opening short,https://lichess.org/25COtgWc#21
|
||||
YZphi,1rb2rk1/p4p1p/4p1p1/q1ppb3/6PP/1PN5/P1PQBB2/R3K2R w KQ - 0 17,e1c1 e5c3 d2c3 a5c3,1455,76,73,794,crushing master middlegame short,https://lichess.org/YUCwTutk#33
|
||||
Xp0Fu,rnbqk2r/pp1p1ppp/2p1p3/3n4/1b1P4/1P1BPN2/P1P2PPP/RNBQK2R w KQkq - 1 6,b1d2 d5c3 e1g1 c3d1,1291,74,98,1101,crushing opening short trappedPiece,https://lichess.org/ZLMwfhTb#11
|
||||
gSuce,r3k2r/ppp2p1p/2q3p1/3N1b2/4P2P/2bQ4/P1P2PP1/1R1K1B1R b kq - 0 15,e8c8 d5e7 c8b8 e7c6,978,93,97,502,crushing fork middlegame pin short,https://lichess.org/s986T8cn/black#30
|
||||
ojyVA,r4rk1/1pp2ppp/p7/8/3nq3/P3N1Q1/BPP2PP1/R3KR2 w Q - 5 19,e1c1 d4e2 c1b1 e2g3,1467,75,99,1838,crushing fork middlegame short,https://lichess.org/9QGJ7rZV#37
|
||||
JRXLt,2q1r1k1/5pbp/r4np1/8/1B2P1b1/Pp3PN1/1P1Q3P/R2NK2R w KQ - 0 19,f3g4 f6e4 e1g1 e4d2,1400,75,93,653,crushing master middlegame short,https://lichess.org/uOcxibZc#37
|
||||
Mqwig,r3k3/pp1q4/3p3Q/2p1p3/2Pn4/2NP2P1/PP1K2BP/8 b q - 0 22,e8c8 g2h3 d7h3 h6h3,1459,74,95,7529,crushing endgame pin short,https://lichess.org/eMX64gmi/black#44
|
||||
or8tr,r1bq3r/pp2kppp/3bpn2/1B6/3Q4/2N5/PPP2PPP/R1B1K2R w KQ - 3 10,e1g1 d6h2 g1h2 d8d4,1251,75,97,2368,crushing discoveredAttack kingsideAttack opening short,https://lichess.org/aTyyc4D6#19
|
||||
ufB48,r3k2r/pp2pp2/n4b1p/1Npq2p1/8/3B1P2/2P2P1P/1R1Q1RK1 b kq - 0 16,e8g8 d3h7 g8h7 d1d5,1044,76,100,578,crushing discoveredAttack master middlegame short,https://lichess.org/4O7cI2vX/black#32
|
||||
PPhgy,2r1k2r/pp1b1ppp/4p2n/3pP3/1b1q1P2/2NB4/PP1Q2PP/R1B2R1K b k - 5 13,e8g8 d3h7 g8h7 d2d4,1120,79,89,831,crushing discoveredAttack kingsideAttack middlegame short,https://lichess.org/NDtiGLde/black#26
|
|
20
Tests/Puzzles/crushing_enPassant.csv
Normal file
20
Tests/Puzzles/crushing_enPassant.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
LjCmb,1k1r3r/pp4Rp/1n2Bp2/2pPq3/1Q2N3/P6P/1P6/K2R4 w - c6 0 29,d5c6 d8d1 a1a2 e5e6,1177,76,94,1259,crushing hangingPiece middlegame short,https://lichess.org/Yk2zG0UY#57
|
||||
04bSF,8/8/4kp2/2pPp1p1/1p4P1/1P1PK3/r1P1RP2/8 b - - 0 40,e6d5 c2c4 b4c3 e2a2,1055,78,94,722,crushing discoveredAttack endgame rookEndgame short,https://lichess.org/Ecn7TQaC/black#80
|
||||
AF9UO,2kr2nr/ppp3pp/2bb1p2/5q2/3Pp3/1BP3P1/PP3P1P/RNBQR1K1 b - d3 0 13,e4d3 b3e6 f5e6 e1e6,976,78,99,1079,crushing fork opening short,https://lichess.org/fVTwPwnn/black#26
|
||||
DeL19,2b2rk1/p4ppp/2p1p3/2qpP3/5B1P/3B1PPK/1r6/R2Q3R w - d6 0 24,e5d6 e6e5 g3g4 e5f4,1338,76,93,539,crushing discoveredAttack middlegame short,https://lichess.org/xu3NKiH3#47
|
||||
m2n9Q,2r5/B2q2bp/2Np4/1Q1Ppkp1/2P2pP1/5P2/P6P/6K1 b - g3 0 33,f4g3 c6d4 e5d4 b5d7,1426,75,97,7352,crushing discoveredAttack endgame short,https://lichess.org/mYaK9ddh/black#66
|
||||
r2rqH,1B6/7p/2p1k1p1/1pKpnpP1/8/8/PPP2P2/8 w - f6 0 33,g5f6 e5d7 c5c6 d7b8,1329,77,91,528,crushing endgame fork short,https://lichess.org/3jItgkvn#65
|
||||
t18DC,8/7p/3k2p1/8/1p1p2P1/1P1P1K2/r1P2R2/8 b - - 2 44,d6d5 c2c4 d4c3 f2a2,1060,83,83,516,crushing discoveredAttack endgame rookEndgame short,https://lichess.org/RIDIKGiy/black#88
|
||||
Gc8wi,4rk2/1p5p/2p1q1pP/1p1pPp2/rP1P2P1/P1R1QPK1/8/4R3 w - f6 0 34,e5f6 e6d6 e3e5 e8e5,1417,74,95,6861,crushing endgame short,https://lichess.org/YiVM7jB1#67
|
||||
RCtAF,r1bq1rk1/1p3pp1/p1np4/2p1pPPp/P1B1P1n1/3P4/1PP3P1/RNBQ1RK1 w - h6 0 12,g5h6 d8h4 d1g4 h4g4,1372,76,91,547,crushing middlegame short,https://lichess.org/rXkJ3qWC#23
|
||||
yLkJ8,2r2rk1/3q1ppp/3b4/2pP3b/3QP3/2N5/PP3PPP/R1B2RK1 w - c6 0 17,d5c6 d6h2 g1h2 d7d4,1278,74,90,1312,crushing discoveredAttack kingsideAttack middlegame short,https://lichess.org/zu4W2Xjc#33
|
||||
hQiNO,8/2r2p1R/pk2p3/4P3/1P1K1P2/1P6/8/8 w - - 5 45,d4e4 f7f5 e5f6 c7h7,1226,75,98,3368,crushing discoveredAttack endgame rookEndgame short,https://lichess.org/kiGIgx3X#89
|
||||
XcSfQ,8/R4pr1/2p3k1/1p4Pp/3P1P1P/P4K2/1P6/8 w - - 1 34,f3e4 f7f5 g5f6 g7a7,1435,74,95,7150,crushing discoveredAttack endgame rookEndgame short,https://lichess.org/H2FyMIZK#67
|
||||
mqUlP,r3r1k1/bb1q1pp1/p6p/2p5/1PPp4/P2BnP2/3QNBPP/R3R2K b - c3 0 22,d4c3 d3h7 g8h7 d2d7,1389,75,97,7843,crushing discoveredAttack middlegame short,https://lichess.org/2GkHylJl/black#44
|
||||
BlgSP,3r1k2/p2r1p1R/1pn1p1p1/2p3P1/2P1KP2/P1RPP3/5N2/8 w - - 1 34,f2g4 f7f5 g5f6 d7h7,1414,75,97,8105,crushing discoveredAttack endgame fork master short,https://lichess.org/SVDzwDrX#67
|
||||
8Wf37,2r3k1/8/1B1Qp1r1/8/4PPp1/P5Pp/1P5P/2q2RK1 b - f3 0 28,g4f3 f1c1 c8c1 g1f2,1131,77,93,2318,crushing endgame short,https://lichess.org/MgBKmMyw/black#56
|
||||
x1ST3,8/5p1r/r1pkp1p1/p5P1/P2PKP2/1RR4P/8/8 w - - 6 44,b3b7 f7f5 g5f6 h7b7,1206,75,88,120,crushing discoveredAttack endgame rookEndgame short,https://lichess.org/VKjN7j2C#87
|
||||
8zW22,1R6/1P6/5p2/5k2/5Pp1/1r4P1/6K1/8 b - f3 0 67,g4f3 g2f2 b3b2 f2f3,1125,77,93,2162,crushing defensiveMove endgame rookEndgame short,https://lichess.org/IGmSHmFL/black#134
|
||||
GNTiD,r1bq1rk1/2p4p/1p1p2p1/p2Pp1N1/P1P1p3/6P1/1P3P1P/R2Q1RK1 w - e6 0 17,d5e6 d8g5 d1d5 g5d5,1172,77,93,560,crushing hangingPiece middlegame short,https://lichess.org/Dvdu67gt#33
|
||||
DyB55,8/kpr2p1R/p3p1p1/2b1P1P1/4pP2/1PP2K2/3B4/8 w - - 0 34,f3e4 f7f5 e5f6 c7h7,988,76,95,633,crushing discoveredAttack endgame short,https://lichess.org/E6h2U3g9#67
|
||||
a3jh8,4r1k1/p2n2b1/1p1Br1pp/2p1Pp2/P3R3/5N1P/1PP2PP1/4R1K1 w - f6 0 28,e5f6 e6e4 e1e4 e8e4,1156,77,95,3041,crushing middlegame short,https://lichess.org/PMYUPzpW#55
|
|
18
Tests/Puzzles/crushing_simple.csv
Normal file
18
Tests/Puzzles/crushing_simple.csv
Normal file
@@ -0,0 +1,18 @@
|
||||
gjB0I,r1r3k1/p3nppp/5q2/3pp3/3nQ1P1/P1P2N1P/P2PPPB1/R1B1KR2 w Q - 0 15,e4e5 d4f3 g2f3 f6e5,1441,77,96,7017,crushing intermezzo middlegame short,https://lichess.org/NNlhThCX#29
|
||||
DjF9F,7k/1q4p1/PP2r2p/8/2Pp4/3p3P/4QPK1/RR6 w - - 0 37,e2f3 e6g6 g2h2 b7f3,1026,79,96,936,crushing deflection endgame pin short,https://lichess.org/ZRdpTIc7#73
|
||||
sdK1f,7R/N3p3/4k1b1/6p1/5bP1/1P3P1P/PKP2n2/8 w - - 5 32,h3h4 f4e5 b2b1 e5h8,1350,75,94,4319,crushing endgame fork short,https://lichess.org/jRPMtIwq#63
|
||||
c4Q2K,6k1/2pp4/1p1b2p1/p2n1rNp/7P/1P1Q3K/P4P2/5R2 w - - 2 40,g5e4 d5f4 h3h2 f4d3,964,75,100,633,crushing discoveredAttack endgame fork short,https://lichess.org/Wr2XI4kL#79
|
||||
JMggH,r1b1k1nr/pp2bBpp/1np5/8/3P4/3q1N2/PP1N1KPP/R1BQ3R b kq - 0 13,e8f7 f3e5 f7f8 e5d3,1062,76,98,531,crushing fork opening short,https://lichess.org/cV3k9SJS/black#26
|
||||
UkI37,8/7p/1b4k1/pB6/P2p1BP1/4nK2/8/8 b - - 3 48,e3c2 b5d3,1186,76,94,1406,crushing endgame oneMove,https://lichess.org/6Zy6lEOJ/black#96
|
||||
u2uUc,4r3/pp3qk1/2p2n2/3pQP1p/8/2NB4/PPP2bPP/2K2R2 w - - 2 24,e5f4 f2e3 f4e3 e8e3,1382,74,97,6750,crushing fork middlegame short,https://lichess.org/SA2OAz7V#47
|
||||
95aih,r1b1r1k1/pp3ppp/3p4/4n3/3p2B1/1P6/P1P2PPP/RN2R1K1 w - - 0 17,g4c8 e5f3 g2f3 e8e1,1180,78,94,4398,crushing discoveredAttack kingsideAttack middlegame short,https://lichess.org/OFPiuoKw#33
|
||||
TGtYY,2r2rk1/ppqb1pp1/7p/4p3/4Qn2/1P1B4/PBPP1PPP/2KR3R w - - 6 19,b2e5 f4d3 e4d3 c7e5,1257,75,94,975,crushing middlegame pin queensideAttack short,https://lichess.org/VKOdc5WH#37
|
||||
UFDsq,2R5/p2rr1pk/1p2b2p/8/5R1P/4B1P1/P4P2/6K1 w - - 11 30,a2a4 d7d1 g1h2 e6c8,1021,75,98,882,crushing discoveredAttack endgame short,https://lichess.org/6WDvsr9F#59
|
||||
EhBCM,6k1/4bp2/p3b1p1/1pp1P3/5P1p/1P2BK1P/P1B3P1/8 w - - 0 28,f3e4 e6f5 e4d5 f5c2,1106,75,99,1353,bishopEndgame crushing endgame master short skewer,https://lichess.org/kSGgZyH6#55
|
||||
1udMI,7k/1pp3p1/3p4/p7/6P1/2P1Pr1P/1P2K3/7R b - - 1 29,f3g3 e2f2 g3g4 h3g4,1165,76,90,743,crushing discoveredAttack endgame rookEndgame short trappedPiece,https://lichess.org/BwjVuezV/black#58
|
||||
TpwxP,2kr4/R1pq1ppp/8/8/8/2PPr3/P1P3P1/R2QK3 w - - 0 23,e1d2 e3d3 c2d3 d7d3,1300,75,96,3003,crushing endgame sacrifice short,https://lichess.org/wRRxYzZB#45
|
||||
v4SMo,3r2k1/5p1p/p3p1p1/2P1P1P1/5P2/P7/4p2P/2R3K1 w - - 0 31,g1f2 d8d1 f2e2 d1c1,1231,74,99,2872,crushing endgame rookEndgame short,https://lichess.org/80LcrMO8#61
|
||||
pv5iO,2kr1bnr/pp1n1p1p/2pp1q2/6p1/4Pp2/1PNP3P/1PP3PN/R1BQ1RK1 w - - 0 12,a1a7 f6d4 g1h1 d4a7,1164,75,98,835,crushing fork middlegame short,https://lichess.org/JeND0wKx#23
|
||||
IFFS9,3q2k1/3brppp/8/p1pp2N1/3P4/N7/4QPPP/1R4K1 w - - 0 25,e2e7 d8e7 b1b8 d7e8,994,75,98,970,crushing defensiveMove endgame hangingPiece short,https://lichess.org/L4E4AcIg#49
|
||||
2x4l0,5r1k/3b1pp1/p2qp2p/3p4/P1n5/2NBP1P1/1r1P1P1P/R1Q2RK1 b - - 5 24,b2d2 d3c4 d5c4 c3e4,1448,74,93,1998,crushing middlegame short,https://lichess.org/3YGuCQvX/black#48
|
||||
E9fXH,2kr4/3n1p2/2p2qp1/1p6/6P1/1Bb1P1Br/2P1QP2/1R1R2K1 w - - 2 23,d1d6 h3g3 f2g3 f6d6,1484,74,98,7693,crushing middlegame short,https://lichess.org/OhNQYiqs#45
|
|
20
Tests/Puzzles/mateIn1_castling.csv
Normal file
20
Tests/Puzzles/mateIn1_castling.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
WW5uk,r3kb1r/2p1ppp1/p1b2n1p/3qN3/3P4/4P3/PP3PPP/RNBQK2R w KQkq - 4 11,e1g1 d5g2,760,100,91,649,kingsideAttack mate mateIn1 oneMove opening,https://lichess.org/oBiCLIGA#21
|
||||
zjfWz,rnbqk2r/ppp1bppp/8/3p2P1/5PP1/3Q4/PPPP4/RNB1K1NR b KQkq - 0 10,e8g8 d3h7,1210,79,90,285,kingsideAttack mate mateIn1 oneMove opening,https://lichess.org/4kj3hdb1/black#20
|
||||
swBXg,r1b1k1nr/1p3ppp/p2bp3/4q3/8/2NBB3/PPP2PPP/R2QK2R w KQkq - 2 12,e1g1 e5h2,836,85,71,126,kingsideAttack mate mateIn1 oneMove opening,https://lichess.org/lhIYvgoM#23
|
||||
E1wln,Qn2kbnr/p1q1ppp1/2p5/5bp1/4p3/2N5/PPPPBPPP/R1B1K2R w KQk - 3 10,e1g1 c7h2,1350,77,75,146,kingsideAttack mate mateIn1 oneMove opening,https://lichess.org/mB3slcX0#19
|
||||
hIf6g,rn2k2r/2qbpp1p/2Np2p1/pB1P4/1p1Q4/7P/PPP2PP1/2KR3R b kq - 0 15,e8g8 c6e7,1020,163,82,11,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/IlQIKE1K/black#30
|
||||
PKlaU,r1bk3N/1p1pqBpQ/p1p5/2b1p3/3n4/8/PPP2nPP/RNB1K2R w KQ - 0 12,e1g1 d4e2,1427,131,86,61,mate mateIn1 middlegame oneMove,https://lichess.org/KStAa02H#23
|
||||
HgNow,r3k2r/pppn1p2/4pq1p/8/3P1Q2/P1P1P1B1/5P1P/R3K1R1 b Qkq - 2 18,e8c8 f4c7,931,76,100,195,mate mateIn1 middlegame oneMove queensideAttack,https://lichess.org/kzE6a9h5/black#36
|
||||
6b7Wt,r3kbnr/ppp4p/2p1bqp1/4Q3/3PPB2/8/PPP2PPP/RN2K2R b KQkq - 2 9,e8c8 e5c7,915,75,99,526,mate mateIn1 oneMove opening queensideAttack,https://lichess.org/rjh2BgbH/black#18
|
||||
nAfqs,r3k2r/2pq2p1/1b3p2/p6p/P1pP4/2P1PQP1/6P1/R3NRK1 b kq - 0 26,e8c8 f3a8,849,94,87,421,mate mateIn1 middlegame oneMove,https://lichess.org/65g7kSa0/black#52
|
||||
0sw4O,r3k2r/ppB2ppp/2n1pq2/6b1/4Q1P1/2PB4/PP5P/3K3R b kq - 4 19,e8g8 e4h7,1074,80,71,71,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/g1if65nb/black#38
|
||||
6kzIH,r1b1k2r/ppp2ppp/2n1pq2/8/3PQn2/P1PB1N2/1BP2PPP/R4RK1 b kq - 8 11,e8g8 e4h7,1154,93,68,44,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/NaDYEX6v/black#22
|
||||
42Bal,2r1k2r/ppq2ppp/1n2b3/4p3/2P5/P3P1P1/1KQN1PP1/3R1B1R b k - 0 19,e8g8 c2h7,835,83,85,44,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/A2xv3ghS/black#38
|
||||
AWZQ7,r2qk2r/2p1bppp/p3p3/n7/Pp1P4/3QPPP1/1PB2P2/RN3RK1 b kq - 0 15,e8g8 d3h7,602,96,60,48,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/m6RqcUwC/black#30
|
||||
P2snV,r3k2r/1p3ppp/2p1bq2/p2n2N1/PbB1Q2P/8/1P3PP1/R1B2K1R b kq - 0 16,e8g8 e4h7,1260,158,82,10,kingsideAttack master mate mateIn1 middlegame oneMove,https://lichess.org/6s9dBTUL/black#32
|
||||
DLdAn,r3k2r/p2q1p1p/b3pp2/1Nb5/P1Bp4/5Q2/1PP2PPP/R4RK1 b kq - 3 16,e8c8 f3a8,1287,76,94,3968,mate mateIn1 middlegame oneMove,https://lichess.org/l2DeuPYc/black#32
|
||||
p3Q6M,r3k2r/1b4R1/p1n1p2p/1p1q1p2/3P1Q1P/P2B1N2/1PP5/2K5 b kq - 3 21,e8c8 f4c7,1193,76,90,517,mate mateIn1 middlegame oneMove,https://lichess.org/heIdcTW0/black#42
|
||||
RsYIC,rnb1k2r/pp3ppp/4p3/q2p2N1/1b1P4/2N1P3/PPQ2PPP/2R1KB1R b Kkq - 0 10,e8g8 c2h7,847,103,89,164,kingsideAttack mate mateIn1 oneMove opening,https://lichess.org/YgEdGJ9v/black#20
|
||||
aozJ2,3bk2r/p3npp1/2q4p/4Q3/4P3/1P6/PBP2PPP/5RK1 b k - 2 19,e8g8 e5g7,846,91,70,103,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/LNWojJtd/black#38
|
||||
6PekE,r2qk2r/ppp2ppp/2n5/3p1Q2/3P3b/3B4/P2B2PP/R4K2 b kq - 0 16,e8g8 f5h7,754,82,97,188,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/Ncwq1lnM/black#32
|
||||
nfUZQ,r2qk2r/pp1n1ppp/8/3Pn3/4Qp2/2N2Bb1/PPP3P1/R1B2K1R b kq - 4 14,e8g8 e4h7,1232,92,69,45,kingsideAttack mate mateIn1 oneMove opening,https://lichess.org/04AZISKa/black#28
|
|
20
Tests/Puzzles/mateIn1_enPassant.csv
Normal file
20
Tests/Puzzles/mateIn1_enPassant.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
HDgzc,2r2r1q/5p1k/p3p2B/1p2P1Q1/4B3/P7/2p3P1/5R1K b - - 0 31,f7f5 e5f6,1990,81,65,148,discoveredAttack enPassant mate mateIn1 middlegame oneMove,https://lichess.org/ol00Tz8q/black#62
|
||||
1j5dV,7k/3rN2p/7P/p1pp3n/5pP1/5R2/PPP5/2K5 b - g3 0 38,f4g3 f3f8,890,86,67,64,endgame mate mateIn1 oneMove,https://lichess.org/zjlGPoxK/black#76
|
||||
QKy7Z,r2q1r2/1p1b1p1R/pnn1p1k1/3pP1P1/3P1P2/P2Q2P1/1P6/R1B2KN1 b - - 1 20,f7f5 e5f6,1840,77,94,5888,discoveredAttack enPassant mate mateIn1 middlegame oneMove,https://lichess.org/gowIv2fI/black#40
|
||||
4588b,r1b4k/1pq2p1p/2pp1N2/p1nPr3/2P1pP2/P3P2P/1PQ1B1P1/2KR3R b - f3 0 19,e4f3 c2h7,1051,76,82,237,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/6u9WW5eB/black#38
|
||||
1ymyA,4brk1/ppq5/4p1pQ/4PpRp/2P4r/3BR3/P1P3PP/7K w - f6 0 32,e5f6 c7h2,973,76,100,270,kingsideAttack master masterVsMaster mate mateIn1 middlegame oneMove,https://lichess.org/QVy1wixN#63
|
||||
SsgM3,r4rk1/1bq3pp/p3p3/1pb1PpN1/6n1/2NB4/PPP1Q1PP/R4R1K w - f6 0 18,e5f6 c7h2,986,75,100,597,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/j8MpBLcv#35
|
||||
YnOUq,1k1r1b1r/1pq1n3/1n2p3/1P1pPp2/3P2B1/2P5/R4PPB/1N1Q1RK1 w - f6 0 22,e5f6 c7h2,1169,205,83,8,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/uSZx2YbU#43
|
||||
94PYq,7k/pb4pp/5p2/8/2PqpP2/1P5P/P3Q1PK/8 b - f3 0 26,e4f3 e2e8,600,95,69,68,backRankMate endgame mate mateIn1 oneMove,https://lichess.org/B1rpV4bq/black#52
|
||||
iKfCM,2b1r1k1/p4p1p/B2N1b2/6pP/3P1K2/2P2P2/P5r1/RN5R w - g6 0 25,h5g6 f6g5,1434,76,85,278,mate mateIn1 middlegame oneMove,https://lichess.org/hLbfMhxx#49
|
||||
Fyn9D,2r2rk1/2b3p1/4p2p/pp2Pp2/2pPQ1Pq/P1P1P3/1P1B4/R4RK1 w - f6 0 21,e5f6 h4h2,1324,75,98,3566,mate mateIn1 middlegame oneMove,https://lichess.org/RqPUtwYt#41
|
||||
GAWcc,r4rk1/p1q3p1/bpp1p2p/4Pp2/1bpPB1nB/2N1P3/PP2Q1PP/R4RK1 w - f6 0 16,e5f6 c7h2,1098,80,92,453,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/T2TeS6dB#31
|
||||
GAhR6,r3r1k1/p7/2p4p/1p2Ppp1/6R1/1P6/PP3PPP/4R1K1 w - f6 0 22,e5f6 e8e1,1010,167,67,10,backRankMate endgame hangingPiece mate mateIn1 oneMove rookEndgame,https://lichess.org/QSZy78Od#43
|
||||
aA1q8,r7/8/p1R5/1p1P1kp1/1PnP1pP1/5K2/2P1N3/8 b - g3 0 40,f4g3 e2g3,970,78,98,633,endgame mate mateIn1 oneMove,https://lichess.org/dTA4VEdI/black#80
|
||||
4fBpP,1q6/pp6/4r1pk/2p1PpRn/3p2Q1/1P1P4/1PP3P1/4R2K w - f6 0 35,e5f6 e6e1,761,80,99,539,endgame hangingPiece mate mateIn1 oneMove,https://lichess.org/UA8Kpwh5#69
|
||||
CX1BO,8/p2n1p2/1p3b1p/3P1bk1/3pPN2/1P3KPP/PB4B1/8 b - e3 0 31,d4e3 h3h4,1177,74,97,3570,endgame mate mateIn1 oneMove,https://lichess.org/oyn66GTz/black#62
|
||||
ZQj16,r3kb1r/ppqn2p1/2p1p3/3pPpp1/N2P4/3QN3/PPP2PPP/R4RK1 w kq f6 0 14,e5f6 c7h2,1225,77,73,405,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/NaUlTKhY#27
|
||||
TfjSn,r2qk2r/ppp2pb1/3pn2p/6pn/2N1PpP1/2PB3P/PP3Q2/R1B2RK1 b kq g3 0 15,f4g3 f2f7,1033,86,88,455,attackingF2F7 mate mateIn1 oneMove opening,https://lichess.org/puM6gguU/black#30
|
||||
0Px2t,8/1b6/p4Q2/1p2Pppk/1P5p/3B3P/P1PN2PK/4q3 w - - 2 36,g2g4 h4g3,1851,78,71,230,enPassant endgame mate mateIn1 oneMove,https://lichess.org/qyXVm3Xe#71
|
||||
FSGUl,5r1k/8/3p3p/5RpP/5KP1/3Q1P2/8/4q3 w - g6 0 56,h5g6 e1e5,1541,93,84,217579,endgame mate mateIn1 oneMove pin,https://lichess.org/wFoaCTu7#111
|
||||
akDOj,r1b2rk1/p1q3pp/2p1p3/4Pp2/4Q1n1/2NB4/PPP3PP/R4RK1 w - f6 0 17,e5f6 c7h2,1244,77,41,211,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/rC5UCaJ0#33
|
|
20
Tests/Puzzles/mateIn1_simple.csv
Normal file
20
Tests/Puzzles/mateIn1_simple.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
jrJls,2bQ1k1r/1p3p2/p4b1p/4pNp1/2q5/8/PPP2PPP/1K1RR3 b - - 3 23,f6d8 d1d8,779,82,97,239,mate mateIn1 middlegame oneMove,https://lichess.org/gQa01loO/black#46
|
||||
rZoKr,5rk1/ppp5/2n4p/3b2p1/6R1/P1B1P2P/1Pq5/R4QK1 w - - 2 29,f1d1 c2f2,1467,79,81,132,mate mateIn1 middlegame oneMove,https://lichess.org/haKJxadR#57
|
||||
0urF2,2kr4/ppp2ppp/2n5/8/1P1PrP2/q4bP1/P3N2P/1R1Q1RBK w - - 1 18,f1f3 a3f3,922,80,96,245,hangingPiece kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/Ea0aHiBh#35
|
||||
2HNcF,r3rk2/5ppQ/b1ppnq1p/p7/P1P5/2N5/1PB2PP1/R3R1K1 b - - 5 23,e6g5 h7h8,1031,76,92,123,mate mateIn1 middlegame oneMove,https://lichess.org/TQl97w12/black#46
|
||||
qFJW4,r5k1/ppp1nrpp/1b2Q3/8/6n1/B1N5/P5PP/R4RqK w - - 4 21,f1g1 g4f2,935,77,98,647,mate mateIn1 middlegame oneMove smotheredMate,https://lichess.org/4IhbR7Z4#41
|
||||
07FBh,r7/1Rrk1p1P/1R2bQn1/3pP3/3P1p2/p1P3PK/5q2/3B4 w - - 0 46,b6e6 f2g3,1378,75,80,255,master masterVsMaster mate mateIn1 middlegame oneMove,https://lichess.org/N6cvrT1h#91
|
||||
BOh5N,r1b1k2r/ppp2pp1/2p5/2b1q1Bp/4P1n1/2PP4/PP2BPPP/RN1Q1RK1 w kq - 1 10,d3d4 e5h2,1110,79,87,307,kingsideAttack mate mateIn1 oneMove opening,https://lichess.org/hBADdo6I#19
|
||||
8K7dF,5Nr1/1p5k/3p1Q2/p2Pn3/4q3/1P6/P5RP/7K b - - 3 38,g8f8 f6g7,1374,78,85,113,endgame master mate mateIn1 oneMove,https://lichess.org/dVBFepDr/black#76
|
||||
pfcY2,1rq4r/pk3ppp/Q1p5/8/R7/5P2/PPP4P/2KR4 b - - 14 24,b7a8 a6a7,969,76,100,457,endgame mate mateIn1 oneMove queensideAttack,https://lichess.org/iq1SsukA/black#48
|
||||
1bg7G,r4rk1/2q4p/2p5/P1bp1pp1/4pNn1/2B1P2P/P1P2PP1/R2Q1RK1 w - - 0 18,f4d5 c7h2,1313,77,85,102,mate mateIn1 middlegame oneMove,https://lichess.org/6jomSlbK#35
|
||||
Rsm4I,3r1Q1k/6p1/7p/1p2q3/8/1P1B1R2/2P3PP/7K b - - 0 33,d8f8 f3f8,600,90,68,137,endgame mate mateIn1 oneMove,https://lichess.org/cBl2WADX/black#66
|
||||
lq6Om,5Q1k/1b2q1pp/p3B3/1pp1P3/8/2N5/PP4PP/5R1K b - - 0 28,e7f8 f1f8,629,104,84,148,backRankMate endgame mate mateIn1 oneMove,https://lichess.org/Hcocq7AO/black#56
|
||||
WOhqq,8/1b4k1/p1q2rp1/8/2PQ4/1P1p3P/1P3RP1/7K w - - 2 41,f2f6 c6g2,1000,79,81,84,endgame mate mateIn1 oneMove,https://lichess.org/s4VKQPNb#81
|
||||
1ZxkV,r1bqkb1r/p1p2ppp/2p5/3pN3/4n3/5Q2/PPPP1PPP/RNB1K2R b KQkq - 1 7,f8d6 f3f7,1102,79,100,66,attackingF2F7 mate mateIn1 oneMove opening,https://lichess.org/8mudJHJO/black#14
|
||||
ozS9D,6k1/ppp2pp1/1nn2q1p/3p4/5B2/1PPB1P2/P1P3PP/4Q1K1 b - - 0 21,f6f4 e1e8,653,89,60,146,endgame master mate mateIn1 oneMove,https://lichess.org/r5n4dpAN/black#42
|
||||
8xWfh,r4rk1/2p3pp/p2p4/1p1P1b2/5Q2/P1P1P2P/BP4q1/2KR3R w - - 0 19,h1g1 g2c2,1323,149,55,14,mate mateIn1 middlegame oneMove,https://lichess.org/nMPORUnV#37
|
||||
GLtpk,r6r/p1p1kpQ1/3p2p1/2p5/4PP2/5Pq1/PPP1R3/R1B3K1 w - - 2 22,e2g2 g3e1,1195,75,94,1852,mate mateIn1 middlegame oneMove,https://lichess.org/fGjY3KEU#43
|
||||
kOz0U,8/Q4kp1/2qB1p2/pp2p2p/n3P2P/5P2/2P3PK/8 b - - 5 36,f7e6 a7e7,805,75,100,281,endgame master mate mateIn1 oneMove,https://lichess.org/jS8203Ng/black#72
|
||||
BEUkI,r1bq1r1k/pp3pR1/1b1p3p/2p2P2/2BBP2n/2N5/PPPQ2PP/R5K1 b - - 0 15,c5d4 d2h6,1080,84,63,46,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/qUYVfwWj/black#30
|
||||
EAnMf,3R4/5r1k/6p1/4B3/3P1PK1/4r3/8/8 b - - 1 42,e3e1 d8h8,959,118,33,20,endgame master mate mateIn1 oneMove,https://lichess.org/7KoQ0ISo/black#84
|
|
20
Tests/Puzzles/mateIn2_castling.csv
Normal file
20
Tests/Puzzles/mateIn2_castling.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
C3LDZ,N2k1bnr/pp3ppp/8/5b2/1n1p1B2/8/PP2PPPP/R3KBNR w KQ - 3 10,e1c1 b4a2 c1d2 f8b4,946,76,98,512,mate mateIn2 opening queensideAttack short,https://lichess.org/TEvwwNPR#19
|
||||
9LFa6,N4b1r/p1p1kp2/4pp1p/1B6/4b3/8/PP3P1P/RNB1K2R w KQ - 0 17,e1g1 h8g8 c1g5 g8g5,1184,81,88,300,mate mateIn2 middlegame short,https://lichess.org/F32R0fa0#33
|
||||
1pIrM,r3k1r1/p1p1qp2/1p2b1p1/n6p/4Q3/2PB4/2P2PPP/3RR1K1 b q - 3 21,e8c8 e4a8 c8d7 d3b5,1862,77,86,105,doubleCheck mate mateIn2 middlegame queensideAttack short,https://lichess.org/pSb6NXeF/black#42
|
||||
zr2fX,r2qk2r/ppp2ppp/3p4/2b1p2n/2BnPP2/2NP3P/PPP3P1/R1BQK2R w KQkq - 3 10,e1g1 d4f3 g1h1 h5g3,1815,75,95,5210,doubleCheck kingsideAttack mate mateIn2 opening short,https://lichess.org/kjhbiYdz#19
|
||||
YpQqh,r1b1k2r/ppb2ppp/2p1p3/5Nq1/3PQ3/2P4P/PPB2PP1/3RR1K1 b kq - 6 20,e8g8 f5e7 g8h8 e4h7,1226,74,97,2181,discoveredAttack kingsideAttack mate mateIn2 middlegame short,https://lichess.org/oqBsdf2S/black#40
|
||||
DvrCz,r3k3/p1p3p1/1pP3p1/2b5/Q2NP3/4K3/PP3Pq1/R7 b q - 0 24,e8c8 a4a6 c8b8 a6b7,1169,77,93,1757,endgame mate mateIn2 queensideAttack short,https://lichess.org/Foi1kNan/black#48
|
||||
XtR9Q,r3k2r/pp1n1p2/2p1p2p/5bp1/3P4/4P1B1/P2KBPPP/2R1R3 b kq - 3 17,e8c8 c1c6 b7c6 e2a6,1189,117,70,776,bodenMate mate mateIn2 middlegame queensideAttack sacrifice short,https://lichess.org/ALOKPySz/black#34
|
||||
ELwWj,r3k3/pp1nqpb1/2p3p1/8/5BP1/5Q2/PPP1B3/2K4R b q - 2 21,e8c8 f3c6 b7c6 e2a6,1180,158,38,144,bodenMate master mate mateIn2 middlegame queensideAttack sacrifice short,https://lichess.org/MkCYHnmC/black#42
|
||||
c1OJQ,Qn2k2r/p4pp1/1q1bp1p1/8/3P4/3BP3/P2B1PPP/R3K2R w KQk - 2 18,e1c1 d6a3 c1c2 b6b2,1170,76,88,402,mate mateIn2 middlegame queensideAttack short,https://lichess.org/ze00fCw3#35
|
||||
0NplY,3r1q1r/pkp2p1p/2p2p2/7R/3bN3/1P2NQ2/P1PP2P1/R3K3 w Q - 1 19,e1c1 f8a3 c1b1 a3b2,1095,75,91,481,mate mateIn2 middlegame queensideAttack short,https://lichess.org/iqeYu7au#37
|
||||
zaghF,r3k1r1/pp1nbp1p/2p1pn2/8/P2q4/2NB1QBP/1PP2PP1/R3R1K1 b q - 1 17,e8c8 f3c6 b7c6 d3a6,1180,114,72,618,bodenMate mate mateIn2 middlegame queensideAttack sacrifice short,https://lichess.org/2QzZrb7U/black#34
|
||||
micYX,r3k2r/pp1n1pp1/2p2np1/q3p3/3P4/b1P1BQNP/PP3PP1/R3KB1R w KQkq - 1 13,e1c1 a5c3 c1b1 c3b2,1554,80,74,42,mate mateIn2 middlegame pin queensideAttack short,https://lichess.org/6fCoAKep#25
|
||||
LjS86,r1bqk2r/p3bppp/2p1n3/1p2pQ2/4N3/2PB4/P1PP1PPP/R1B2RK1 b kq - 4 12,e8g8 e4f6 g8h8 f5h7,1122,79,72,358,kingsideAttack mate mateIn2 opening short,https://lichess.org/O55hxxVw/black#24
|
||||
0uCn9,r5kr/pp2p1b1/1q1p4/2pPnbP1/4N3/5P2/PPPBQ2P/R3KBNR w KQ - 5 16,e1c1 e5d3 c1b1 b6b2,1522,76,92,1510,mate mateIn2 middlegame queensideAttack short,https://lichess.org/fpIzh0mk#31
|
||||
MLdUA,r3k2r/1p3p1p/p2p1B1b/4pP2/1PP5/q7/2K2PPP/3Q1B1R b kq - 3 21,e8g8 d1g4 h6g7 g4g7,1365,81,40,68,kingsideAttack mate mateIn2 middlegame short,https://lichess.org/eBsszmz6/black#42
|
||||
AhG2i,rn2k2r/2q2pp1/p3p1p1/2Np2N1/1P1P1n2/P7/2PQ1PPP/R3K2R w KQkq - 2 18,e1g1 f4e2 g1h1 c7h2,1345,76,85,185,discoveredAttack kingsideAttack mate mateIn2 middlegame short,https://lichess.org/eNauwPCr#35
|
||||
vnoDi,r3k3/p3q1p1/2p2n2/2P2p2/1P3Br1/5Q2/P4PPP/RN4K1 b q - 1 21,e8c8 f3c6 e7c7 c6c7,1222,79,92,1655,mate mateIn2 middlegame queensideAttack short,https://lichess.org/nq7O55Iv/black#42
|
||||
KCfTj,r1b1k2r/pppp1Npp/2n5/4p3/2B4q/5Q2/PPPP1b1P/RNB4K b kq - 0 10,e8g8 f7h6 g8h8 f3f8,1092,78,98,2011,deflection discoveredAttack doubleCheck kingsideAttack mate mateIn2 middlegame short,https://lichess.org/5aGYiZI2/black#20
|
||||
W6DEV,r3kb1r/2p2p2/p1P1p3/4P1p1/Pp1q4/8/1P1NQ1PP/R3K2R b KQkq - 1 20,e8c8 e2a6 c8b8 a6b7,1100,80,95,536,mate mateIn2 middlegame queensideAttack short,https://lichess.org/GEq3AYMj/black#40
|
||||
lo4i9,r3k2r/pp1nqppp/2p1pn2/5b2/1bB2B2/3P1QPP/PPP1NP2/2KR3R b kq - 4 13,e8c8 f3c6 b7c6 c4a6,1061,95,85,1074,bodenMate mate mateIn2 middlegame queensideAttack sacrifice short,https://lichess.org/LmkZcypC/black#26
|
|
20
Tests/Puzzles/mateIn2_enPassant.csv
Normal file
20
Tests/Puzzles/mateIn2_enPassant.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
4ealO,7R/1r6/4npp1/3p2k1/4p1PN/4P1K1/5P2/8 b - - 1 41,e6g7 f2f4 e4f3 h4f3,1570,74,93,541,endgame mate mateIn2 short,https://lichess.org/Ake1xrvX/black#82
|
||||
0jFaq,3r3R/p1r1kpp1/2pb4/6P1/3p1P2/1PnP4/PB4B1/K3R3 b - - 8 31,e7d7 g2h3 f7f5 g5f6,1473,75,95,8904,discoveredAttack enPassant mate mateIn2 middlegame short,https://lichess.org/PTxvhx4O/black#62
|
||||
IzNuV,rnbq1bkr/pppp2pp/8/4P3/8/1Q6/PPP2nPP/RNB1K1NR b KQ - 3 7,d7d5 e5d6 c8e6 b3e6,2101,76,90,597,discoveredAttack enPassant kingsideAttack mate mateIn2 opening short,https://lichess.org/jeruntSh/black#14
|
||||
pnp1m,6k1/2p2p2/7p/p4Pp1/PpP4K/1N1P3P/1PB1b1r1/R7 w - g6 0 36,f5g6 f7g6 d3d4 g6g5,1332,74,98,5203,endgame mate mateIn2 short,https://lichess.org/RqiEClbR#71
|
||||
C8vzb,7r/6R1/5P1p/3p3k/b1pP3p/4K3/r4PPP/7R b - - 1 31,h8b8 g2g4 h4g3 h2g3,1734,75,93,284,discoveredAttack endgame mate mateIn2 short,https://lichess.org/dc3gg8Ux/black#62
|
||||
m3UQp,r1bq1bkr/pppp2pp/2n5/4P3/6n1/1QN2N2/PP3PPP/R1B1K2R b KQ - 2 9,d7d5 e5d6 c8e6 b3e6,2256,77,99,139,discoveredAttack enPassant kingsideAttack mate mateIn2 opening short,https://lichess.org/VE0cPfvX/black#18
|
||||
jkW7a,r1bq1bkr/pppp2pp/2n5/4P1N1/2Q5/8/Pp3PPP/RNB1K2R b KQ - 1 10,d7d5 e5d6 c8e6 c4e6,2008,85,100,60,discoveredAttack enPassant kingsideAttack mate mateIn2 opening short,https://lichess.org/R6aKczaF/black#20
|
||||
VkFyK,3Q4/1k6/1n1b2p1/1B1p1pP1/P2Pp1K1/4q3/4N3/2R5 w - f6 0 46,g5f6 e3f3 g4g5 f3h5,2129,116,74,30,mate mateIn2 middlegame short,https://lichess.org/gcWm81m8#91
|
||||
7dd9h,6k1/1bQ2p2/p3p1p1/6Pp/2P3K1/1P2PnP1/P6r/R4R2 w - h6 0 30,g5h6 f7f5 g4f4 g6g5,1817,75,96,4785,endgame mate mateIn2 short,https://lichess.org/URt1dC1A#59
|
||||
GJfk2,5nk1/R4pp1/4p2p/2BpP2P/3P2P1/1r3K2/8/2R5 w - - 4 44,f3f4 g7g5 h5g6 f8g6,1637,74,86,400,endgame mate mateIn2 short,https://lichess.org/Rs0Ce6wy#87
|
||||
By846,6k1/5rpp/2pB2b1/2P5/4pP2/2P4P/3r2P1/2R1R1K1 b - f3 0 28,e4f3 e1e8 f7f8 e8f8,687,107,78,374,endgame mate mateIn2 short,https://lichess.org/oVDK3Vtd/black#56
|
||||
aCX4c,7k/1R2B1b1/p5pp/5p2/4p1P1/P2bP2P/5PB1/2r3K1 w - - 1 31,g1h2 g7e5 f2f4 e4f3,1611,74,95,6485,discoveredAttack enPassant endgame mate mateIn2 short,https://lichess.org/dVTQ6KQK#61
|
||||
Ksv3B,6k1/7p/R5p1/5p2/4p3/1RP1B1PP/2P2PK1/3r1r2 w - - 0 35,f2f4 e4f3 g2h2 f1h1,1923,76,92,558,enPassant endgame mate mateIn2 short,https://lichess.org/wi3kg3x1#69
|
||||
3DJKJ,8/4ppk1/3p2p1/1pnP3p/4PK1P/r4PP1/PR6/5B2 w - - 9 43,f1b5 e7e5 d5e6 c5e6,1962,74,96,4418,endgame mate mateIn2 short,https://lichess.org/RR03JsOK#85
|
||||
n4CSG,1r2r1k1/p3qpbp/1pp3p1/3pP3/5BPn/2N3Q1/PPP2P1P/3RR1K1 w - d6 0 21,e5d6 e7e1 d1e1 e8e1,742,95,47,49,kingsideAttack mate mateIn2 middlegame short,https://lichess.org/B4cIJuuo#41
|
||||
12YGT,r2qkbnr/1p2p3/2p2p2/2Pp1bp1/p2P3p/PN2BN1P/RP1KPPP1/3Q1B1R w kq - 0 15,b3c1 d8a5 b2b4 a4b3,1583,76,88,1028,discoveredAttack enPassant mate mateIn2 middlegame short,https://lichess.org/ggbXxMB9#29
|
||||
BOPKK,1r5r/p1p2pk1/4p2p/1BNnP3/6R1/P7/1P4PP/7K b - - 2 23,g7h7 b5d3 f7f5 e5f6,1723,94,85,37,discoveredAttack enPassant endgame mate mateIn2 short,https://lichess.org/9He2SHa7/black#46
|
||||
wU03v,5nk1/p4pp1/4p2p/4Pq1P/1p3P2/6PK/P1R3Q1/8 w - - 4 32,h3h4 g7g5 h5g6 f8g6,1602,75,95,386,endgame mate mateIn2 short,https://lichess.org/adh9umHR#63
|
||||
gLCiH,3r2k1/p2r1ppp/2p5/1pP5/8/1R2P3/P4PPP/1R4K1 w - b6 0 24,c5b6 d7d1 b1d1 d8d1,829,114,90,117,backRankMate endgame mate mateIn2 rookEndgame short,https://lichess.org/EPK4b73V#47
|
||||
5UCLk,8/8/p3RB2/kp3p1p/1Pp2K1P/2P5/6r1/8 b - b3 0 59,c4b3 f6d8 a5a4 e6a6,1610,75,85,304,deflection endgame mate mateIn2 short,https://lichess.org/Z6jDY4v4/black#118
|
|
20
Tests/Puzzles/mateIn2_simple.csv
Normal file
20
Tests/Puzzles/mateIn2_simple.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
uj7Uv,6k1/r4p2/6p1/4B3/p4P2/5r1p/K1R5/8 b - - 5 43,a7b7 c2c8 g8h7 c8h8,840,100,67,43,endgame mate mateIn2 short,https://lichess.org/Z3FRw1Fn/black#86
|
||||
GWKLu,3r2qr/pp2Q3/2p2P1p/7k/6R1/1P6/1PP3PP/R1B4K w - - 5 23,g4g8 d8d1 e7e1 d1e1,1038,85,96,471,backRankMate endgame mate mateIn2 short,https://lichess.org/1kIpfnCX#45
|
||||
ay0e7,1rr3k1/Q4pp1/4p2p/8/1PqPp3/P3P3/5PPP/R4RK1 w - - 3 27,f1c1 c4c1 a1c1 c8c1,630,95,69,168,backRankMate endgame mate mateIn2 short,https://lichess.org/x83fY9GE#53
|
||||
f2HsU,8/2q1kp2/4p3/p7/4Q3/7P/P1Pr1PP1/1R4K1 w - - 0 32,b1b7 d2d1 e4e1 d1e1,941,78,76,128,endgame mate mateIn2 short,https://lichess.org/NxooUJUL#63
|
||||
gEBEu,r3r2k/ppp3pp/3b4/5p2/1PQ1n2q/PnN1PB2/1B3PPP/R3K2R w KQ - 4 18,c4b3 h4f2 e1d1 f2d2,1124,80,94,862,attackingF2F7 mate mateIn2 middlegame short,https://lichess.org/prTiL04V#35
|
||||
Lk2iz,r5k1/p1p2p1Q/2pq1Bp1/3p1b2/5R2/1P1Pr3/P1P3PP/R5K1 b - - 0 21,g8h7 f4h4 h7g8 h4h8,741,91,88,90,master mate mateIn2 middlegame short,https://lichess.org/i2Wi7l4e/black#42
|
||||
t4DNf,2r3k1/5ppp/8/8/4pP2/4P2P/2Q3P1/1R3K2 b - - 0 43,c8c2 b1b8 c2c8 b8c8,655,107,82,33,backRankMate endgame mate mateIn2 rookEndgame short,https://lichess.org/r5W2eFui/black#86
|
||||
xrekH,8/p1P5/8/PP6/8/5rkp/8/6K1 w - - 2 59,c7c8q h3h2 g1h1 f3f1,990,93,96,295,advancedPawn endgame mate mateIn2 queenRookEndgame short,https://lichess.org/dQGDwh3G#117
|
||||
fBhqm,8/3R2kp/p5p1/8/5KP1/2p5/P1P4r/8 b - - 2 39,g7h6 g4g5 h6h5 d7h7,988,75,100,750,deflection endgame mate mateIn2 rookEndgame short,https://lichess.org/rfc0Wh0t/black#78
|
||||
tn9U3,8/4Q3/p2pN3/2pP4/2p1k3/8/1PPK1q2/8 w - - 1 36,d2c3 f2e1 c3c4 e1b4,1917,75,96,2210,endgame mate mateIn2 short,https://lichess.org/js3slxFD#71
|
||||
doGMx,5rk1/pp1R1pp1/4p3/8/6QP/3bB1N1/Pqr2PP1/3K3R w - - 0 22,d7d3 b2b1 e3c1 b1c1,1243,76,90,222,mate mateIn2 middlegame short,https://lichess.org/jYHnS2gv#43
|
||||
TVLHT,r1bqr1k1/p1pnbp2/1p2pn1Q/6N1/3P4/2NB4/PPP3PP/R4RK1 b - - 2 13,e7f8 d3h7 f6h7 h6h7,1274,76,94,1181,kingsideAttack mate mateIn2 middlegame short,https://lichess.org/3luk8Lyr/black#26
|
||||
EfOGc,8/5pk1/7q/3Pr3/8/3Q3P/4KRP1/8 w - - 1 45,e2f1 h6c1 d3d1 c1d1,992,76,95,561,endgame mate mateIn2 short,https://lichess.org/oWIOQKUU#89
|
||||
I7xRS,8/pp1r4/2p5/5b1p/5N2/5kP1/P4P1P/Q5K1 w - - 1 34,a1e5 d7d1 e5e1 d1e1,600,84,83,169,endgame mate mateIn2 short,https://lichess.org/D6tGkCvf#67
|
||||
CnqaD,r1bqk2r/1p6/p1n1pp1p/3p2p1/1QpPNB1P/P3P3/1PP1BPP1/R3K2R b KQkq - 0 14,g5f4 e2h5 e8d7 b4d6,1799,75,95,4247,mate mateIn2 middlegame short,https://lichess.org/gVyTK6U8/black#28
|
||||
yuqa6,4k2r/p2nppb1/6pp/1p1NP3/1P5N/K5P1/P1b2rBP/3R3R b k - 3 25,d7e5 d5c7 e8f8 d1d8,1098,78,77,313,backRankMate mate mateIn2 middlegame short,https://lichess.org/HCiRNU2E/black#50
|
||||
koa5w,r5k1/pbp1qpp1/1p2p1n1/3r2NQ/3P3P/5P2/PP4P1/R2R2K1 b - - 0 20,g6f4 h5h7 g8f8 h7h8,944,76,100,195,kingsideAttack mate mateIn2 middlegame short,https://lichess.org/TXpDhxAx/black#40
|
||||
6HZF3,2rq3r/5k2/p3p3/1pn2Np1/4P3/P1Q2P2/6b1/1KBR2N1 b - - 0 29,d8d1 c3g7 f7e8 g7e7,1524,74,98,7690,mate mateIn2 middlegame short,https://lichess.org/f4k6roov/black#58
|
||||
85ZUB,6k1/2p2pp1/p6p/3pPp2/P7/r3P3/5PPP/3R2K1 w - - 2 27,d1d5 a3a1 d5d1 a1d1,681,104,76,365,backRankMate endgame mate mateIn2 rookEndgame short,https://lichess.org/pBZZo8XP#53
|
||||
YHYZd,8/pp1R4/6pk/8/6PP/5K2/PP1prP2/4r3 b - - 0 36,d2d1q g4g5 h6h5 d7h7,1239,77,90,369,endgame mate mateIn2 queenRookEndgame short,https://lichess.org/E8UBGJ5F/black#72
|
|
80
Tests/SquareTests.cpp
Normal file
80
Tests/SquareTests.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "TestUtils.hpp"
|
||||
|
||||
#include "Square.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
TEST_CASE("Squares can be created from valid coordinates", "[Square][Fundamental]") {
|
||||
auto [file, rank] = GENERATE(table<Square::Coordinate, Square::Coordinate>({
|
||||
{4, 2}, {0, 0}, {7, 7}, {1, 7}
|
||||
}));
|
||||
|
||||
auto optSquare = Square::fromCoordinates(file, rank);
|
||||
REQUIRE(optSquare.has_value());
|
||||
|
||||
auto square = optSquare.value();
|
||||
REQUIRE(square.file() == file);
|
||||
REQUIRE(square.rank() == rank);
|
||||
REQUIRE(square.index() == rank * 8 + file);
|
||||
}
|
||||
|
||||
TEST_CASE("Squares are not created from invalid coordinates", "[Square][Fundamental]") {
|
||||
auto [file, rank] = GENERATE(table<Square::Coordinate, Square::Coordinate>({
|
||||
{4, 8}, {8, 3}, {12, 45}
|
||||
}));
|
||||
|
||||
auto optSquare = Square::fromCoordinates(file, rank);
|
||||
REQUIRE_FALSE(optSquare.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("Squares can be created from valid indices", "[Square][Fundamental]") {
|
||||
auto index = GENERATE(as<Square::Index>{}, 0, 63, 12, 41);
|
||||
|
||||
auto optSquare = Square::fromIndex(index);
|
||||
REQUIRE(optSquare.has_value());
|
||||
|
||||
auto square = optSquare.value();
|
||||
REQUIRE(square.file() == index % 8);
|
||||
REQUIRE(square.rank() == index / 8);
|
||||
REQUIRE(square.index() == index);
|
||||
}
|
||||
|
||||
TEST_CASE("Squares are not created from invalid indices", "[Square][Fundamental]") {
|
||||
auto index = GENERATE(as<Square::Index>{}, 64, 1024);
|
||||
|
||||
auto optSquare = Square::fromIndex(index);
|
||||
REQUIRE_FALSE(optSquare.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("Squares can be created from valid names", "[Square][Fundamental]") {
|
||||
auto [square, name] = GENERATE(table<Square, const char*>({
|
||||
{Square::A1, "a1"}, {Square::H8, "h8"}, {Square::D5, "d5"}
|
||||
}));
|
||||
|
||||
auto optCreatedSquare = Square::fromName(name);
|
||||
CAPTURE(square, name, optCreatedSquare);
|
||||
REQUIRE(optCreatedSquare.has_value());
|
||||
|
||||
auto createdSquare = optCreatedSquare.value();
|
||||
REQUIRE(createdSquare == square);
|
||||
}
|
||||
|
||||
TEST_CASE("Squares are not created from invalid names", "[Square][Fundamental]") {
|
||||
auto name = GENERATE("", "a", "a12", "1a", "xyz");
|
||||
|
||||
auto optSquare = Square::fromName(name);
|
||||
CAPTURE(name, optSquare);
|
||||
REQUIRE_FALSE(optSquare.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("Squares stream their name correctly", "[Square][Fundamental]") {
|
||||
auto [square, name] = GENERATE(table<Square, const char*>({
|
||||
{Square::A1, "a1"}, {Square::H8, "h8"}, {Square::E4, "e4"}
|
||||
}));
|
||||
|
||||
auto stream = std::stringstream();
|
||||
stream << square;
|
||||
REQUIRE(stream.str() == name);
|
||||
}
|
26
Tests/TestUtils.hpp
Normal file
26
Tests/TestUtils.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef CHESS_ENGINE_TESTS_TESTUTILS_HPP
|
||||
#define CHESS_ENGINE_TESTS_TESTUTILS_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
|
||||
template<typename T>
|
||||
inline std::ostream& operator<<(std::ostream& os, const std::optional<T>& opt) {
|
||||
if (opt.has_value()) {
|
||||
return os << opt.value();
|
||||
} else {
|
||||
return os << "nullopt";
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline std::ostream& operator<<(std::ostream& os, const std::unique_ptr<T>& ptr) {
|
||||
if (ptr == nullptr) {
|
||||
return os << "nullptr";
|
||||
} else {
|
||||
return os << ptr.get();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
151
Tests/puzzledb.py
Executable file
151
Tests/puzzledb.py
Executable file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import csv
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
import chess
|
||||
|
||||
|
||||
class Puzzle:
|
||||
def __init__(self, puzzle_id, fen, moves, rating, rd, popularity,
|
||||
num_plays, tags, game_url):
|
||||
self.puzzle_id = puzzle_id
|
||||
self.fen = fen
|
||||
self.moves = moves
|
||||
self.rating = rating
|
||||
self.rd = rd
|
||||
self.popularity = popularity
|
||||
self.num_plays = num_plays
|
||||
self.tags = tags
|
||||
self.game_url = game_url
|
||||
|
||||
@staticmethod
|
||||
def from_csv(row):
|
||||
puzzle_id, fen, moves, rating, rd, popularity, \
|
||||
num_plays, tags, game_url = row[:9]
|
||||
moves = [chess.Move.from_uci(m) for m in moves.split()]
|
||||
return Puzzle(puzzle_id, fen, moves, int(rating), int(rd),
|
||||
int(popularity), int(num_plays), tags.split(), game_url)
|
||||
|
||||
def to_csv(self):
|
||||
moves = ' '.join(m.uci() for m in self.moves)
|
||||
tags = ' '.join(self.tags)
|
||||
|
||||
return (
|
||||
self.puzzle_id, self.fen, moves, self.rating, self.rd,
|
||||
self.popularity, self.num_plays, tags, self.game_url
|
||||
)
|
||||
|
||||
def has_tags(self, tags):
|
||||
return all(tag in self.tags for tag in tags)
|
||||
|
||||
def has_not_tags(self, tags):
|
||||
return not any(tag in self.tags for tag in tags)
|
||||
|
||||
def _has_move(self, predicate):
|
||||
board = chess.Board(self.fen)
|
||||
|
||||
for move in self.moves:
|
||||
if predicate(board, move):
|
||||
return True
|
||||
|
||||
board.push(move)
|
||||
|
||||
return False
|
||||
|
||||
def has_castling(self):
|
||||
return self._has_move(chess.Board.is_castling)
|
||||
|
||||
def has_en_passant(self):
|
||||
return self._has_move(chess.Board.is_en_passant)
|
||||
|
||||
def num_plies(self):
|
||||
return len(self.moves) - 1
|
||||
|
||||
|
||||
class PuzzleDb:
|
||||
def __init__(self, puzzles):
|
||||
self._puzzles = puzzles
|
||||
|
||||
@staticmethod
|
||||
def from_csv(file):
|
||||
def generate_puzzles():
|
||||
csv_reader = csv.reader(file)
|
||||
|
||||
for row in csv_reader:
|
||||
puzzle = Puzzle.from_csv(row)
|
||||
yield puzzle
|
||||
|
||||
return PuzzleDb(generate_puzzles())
|
||||
|
||||
def to_csv(self, file):
|
||||
csv_writer = csv.writer(file)
|
||||
|
||||
for puzzle in self._puzzles:
|
||||
csv_writer.writerow(puzzle.to_csv())
|
||||
|
||||
def filter(self, predicate):
|
||||
return PuzzleDb(filter(predicate, self))
|
||||
|
||||
def collect(self):
|
||||
return list(self)
|
||||
|
||||
def sorted(self, key=None):
|
||||
return PuzzleDb(sorted(self._puzzles, key=key))
|
||||
|
||||
def __iter__(self):
|
||||
yield from self._puzzles
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('puzzle_db', type=Path)
|
||||
parser.add_argument('--tag', action='append', dest='tags')
|
||||
parser.add_argument('--not-tag', action='append', dest='not_tags')
|
||||
parser.add_argument('--castling', choices=['yes', 'no', 'only'],
|
||||
default='yes')
|
||||
parser.add_argument('--en-passant', choices=['yes', 'no', 'only'],
|
||||
default='yes')
|
||||
parser.add_argument('--min-plies', type=int, default=0)
|
||||
parser.add_argument('--max-plies', type=int, default=1000)
|
||||
parser.add_argument('--min-rating', type=int, default=0)
|
||||
parser.add_argument('--max-rating', type=int, default=4000)
|
||||
parser.add_argument('--min-num-plays', type=int, default=0)
|
||||
args = parser.parse_args()
|
||||
|
||||
with args.puzzle_db.open(newline='') as f:
|
||||
puzzles = PuzzleDb.from_csv(f)
|
||||
|
||||
if args.tags is not None:
|
||||
puzzles = puzzles.filter(lambda p: p.has_tags(args.tags))
|
||||
|
||||
if args.not_tags is not None:
|
||||
puzzles = puzzles.filter(lambda p: p.has_not_tags(args.not_tags))
|
||||
|
||||
if args.castling == 'no':
|
||||
puzzles = puzzles.filter(lambda p: not p.has_castling())
|
||||
elif args.castling == 'only':
|
||||
puzzles = puzzles.filter(lambda p: p.has_castling())
|
||||
|
||||
if args.en_passant == 'no':
|
||||
puzzles = puzzles.filter(lambda p: not p.has_en_passant())
|
||||
elif args.en_passant == 'only':
|
||||
puzzles = puzzles.filter(lambda p: p.has_en_passant())
|
||||
|
||||
puzzles = puzzles.filter(
|
||||
lambda p: args.min_plies <= p.num_plies() <= args.max_plies
|
||||
)
|
||||
|
||||
puzzles = puzzles.filter(
|
||||
lambda p: args.min_rating <= p.rating <= args.max_rating
|
||||
)
|
||||
|
||||
puzzles = puzzles.filter(lambda p: args.min_num_plays <= p.num_plays)
|
||||
|
||||
puzzles.to_csv(sys.stdout)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
89
Tests/puzzlerating.py
Executable file
89
Tests/puzzlerating.py
Executable file
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from puzzledb import PuzzleDb
|
||||
from puzzlerunner import run_puzzle
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from random import Random
|
||||
|
||||
import glicko2
|
||||
|
||||
|
||||
def select_puzzles(player, puzzle_db, num_puzzles, played_puzzles, random):
|
||||
rd = player.rd
|
||||
candidates = []
|
||||
|
||||
while len(candidates) < num_puzzles:
|
||||
candidates = puzzle_db \
|
||||
.filter(lambda p: p.rating >= player.rating - 2 * rd) \
|
||||
.filter(lambda p: p.rating <= player.rating + 2 * rd) \
|
||||
.filter(lambda p: p not in played_puzzles) \
|
||||
.collect()
|
||||
|
||||
rd += 50
|
||||
|
||||
return sorted(random.sample(candidates, num_puzzles),
|
||||
key=lambda p: p.rating)
|
||||
|
||||
|
||||
def play_round(player, puzzle_db, num_puzzles, engine,
|
||||
timeout, played_puzzles, random):
|
||||
puzzles = select_puzzles(player, puzzle_db, num_puzzles,
|
||||
played_puzzles, random)
|
||||
played_puzzles.update(puzzles)
|
||||
outcomes = []
|
||||
|
||||
for puzzle in puzzles:
|
||||
print(f'Running puzzle {puzzle.puzzle_id} '
|
||||
f'with rating {puzzle.rating}... ',
|
||||
end='', flush=True)
|
||||
|
||||
result = run_puzzle(puzzle, engine, timeout)
|
||||
success = result.is_success()
|
||||
outcomes.append(success)
|
||||
|
||||
if success:
|
||||
print('OK')
|
||||
else:
|
||||
print('FAIL')
|
||||
|
||||
ratings = [p.rating for p in puzzles]
|
||||
rds = [p.rd for p in puzzles]
|
||||
player.update_player(ratings, rds, outcomes)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--engine', type=Path, required=True)
|
||||
parser.add_argument('--timeout', type=float, default=60)
|
||||
parser.add_argument('--min-plays', type=int, default=500)
|
||||
parser.add_argument('--min-popularity', type=int, default=50)
|
||||
parser.add_argument('--rounds', type=int, default=20)
|
||||
parser.add_argument('--puzzles-per-round', type=int, default=5)
|
||||
parser.add_argument('--random-seed', type=int, default=0)
|
||||
parser.add_argument('--puzzle-db', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
with args.puzzle_db.open(newline='') as f:
|
||||
puzzle_db = PuzzleDb.from_csv(f) \
|
||||
.filter(lambda p: p.num_plays >= args.min_plays) \
|
||||
.filter(lambda p: p.popularity >= args.min_popularity) \
|
||||
.sorted(key=lambda p: p.rating)
|
||||
|
||||
player = glicko2.Player()
|
||||
played_puzzles = set()
|
||||
random = Random(args.random_seed)
|
||||
|
||||
for current_round in range(1, args.rounds + 1):
|
||||
print(f'=== Round {current_round}/{args.rounds}, '
|
||||
f'current rating: {round(player.rating)} '
|
||||
f'(rd: {round(player.rd)}) ===')
|
||||
play_round(player, puzzle_db, args.puzzles_per_round,
|
||||
args.engine, args.timeout, played_puzzles, random)
|
||||
|
||||
print(f'Final rating: {round(player.rating)} (rd: {round(player.rd)})')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
260
Tests/puzzlerunner.py
Executable file
260
Tests/puzzlerunner.py
Executable file
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from puzzledb import Puzzle, PuzzleDb
|
||||
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import defaultdict
|
||||
import sys
|
||||
import time
|
||||
import asyncio
|
||||
import contextlib
|
||||
|
||||
import chess
|
||||
import chess.engine
|
||||
|
||||
import junit_xml
|
||||
from junit_xml import TestSuite, TestCase
|
||||
|
||||
|
||||
class PuzzleRunResult(ABC):
|
||||
def __init__(self, puzzle):
|
||||
self.puzzle = puzzle
|
||||
self.puzzle_type = None
|
||||
self.duration_sec = None
|
||||
|
||||
@abstractmethod
|
||||
def is_success(self):
|
||||
pass
|
||||
|
||||
def to_junit_test_case(self):
|
||||
return TestCase(
|
||||
name=f'Puzzle {self.puzzle.puzzle_id}',
|
||||
status='run',
|
||||
classname=self.puzzle_type,
|
||||
elapsed_sec=self.duration_sec
|
||||
)
|
||||
|
||||
|
||||
class PuzzleRunSuccess(PuzzleRunResult):
|
||||
def __init__(self, puzzle):
|
||||
super().__init__(puzzle)
|
||||
|
||||
def is_success(self):
|
||||
return True
|
||||
|
||||
|
||||
class PuzzleRunFailure(PuzzleRunResult):
|
||||
def __init__(self, puzzle, reason, info):
|
||||
super().__init__(puzzle)
|
||||
self.reason = reason
|
||||
url = f'https://lichess.org/training/{self.puzzle.puzzle_id}'
|
||||
self.info = f'URL: {url}\n{info}'
|
||||
|
||||
def is_success(self):
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
return f'Failure reason: {self.reason}\n{self.info}'
|
||||
|
||||
def to_junit_test_case(self):
|
||||
test_case = super().to_junit_test_case()
|
||||
test_case.add_failure_info(
|
||||
message=self.reason,
|
||||
output=self.info
|
||||
)
|
||||
|
||||
return test_case
|
||||
|
||||
|
||||
class PuzzleRunWrongMove(PuzzleRunFailure):
|
||||
def __init__(self, puzzle, position, move, expected_move):
|
||||
reason = 'unexpected move'
|
||||
info = f'position={position}\n' \
|
||||
f'move={move.uci()}\n' \
|
||||
f'expected move={expected_move.uci()}'
|
||||
|
||||
super().__init__(puzzle, reason, info)
|
||||
|
||||
|
||||
class PuzzleRunTimeout(PuzzleRunFailure):
|
||||
def __init__(self, puzzle, timeout):
|
||||
reason = 'timeout'
|
||||
info = f'Puzzle timed out after {timeout} seconds'
|
||||
super().__init__(puzzle, reason, info)
|
||||
|
||||
|
||||
class PuzzleRunException(PuzzleRunFailure):
|
||||
def __init__(self, puzzle, exception):
|
||||
reason = 'exception'
|
||||
info = exception
|
||||
|
||||
super().__init__(puzzle, reason, info)
|
||||
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
async def create_engine(engine_path):
|
||||
transport, engine = \
|
||||
await chess.engine.popen_uci(engine_path)
|
||||
|
||||
try:
|
||||
yield engine
|
||||
finally:
|
||||
try:
|
||||
await asyncio.wait_for(engine.quit(), timeout=1)
|
||||
except asyncio.TimeoutError:
|
||||
try:
|
||||
transport.kill()
|
||||
transport.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
async def _run_puzzle(puzzle, engine_path, total_time):
|
||||
start_time = time.time()
|
||||
|
||||
moves = puzzle.moves[:]
|
||||
assert len(moves) >= 2
|
||||
assert len(moves) % 2 == 0
|
||||
|
||||
board = chess.Board(puzzle.fen)
|
||||
|
||||
time_limit = chess.engine.Limit()
|
||||
use_limit = total_time is not None
|
||||
|
||||
if use_limit:
|
||||
time_limit.white_clock = total_time
|
||||
time_limit.black_clock = total_time
|
||||
time_limit.white_inc = 0
|
||||
time_limit.black_inc = 0
|
||||
time_limit.remaining_moves = next_multiple(puzzle.num_plies() // 2, 5)
|
||||
time_left = total_time
|
||||
else:
|
||||
time_left = None
|
||||
|
||||
async with create_engine(engine_path) as engine:
|
||||
while len(moves) > 0:
|
||||
board.push(moves.pop(0))
|
||||
|
||||
try:
|
||||
result = await asyncio.wait_for(engine.play(board, time_limit),
|
||||
timeout=time_left)
|
||||
except asyncio.TimeoutError:
|
||||
return PuzzleRunTimeout(puzzle, total_time)
|
||||
|
||||
board.push(result.move)
|
||||
expected_move = moves.pop(0)
|
||||
|
||||
if result.move != expected_move:
|
||||
if len(moves) == 0 and board.is_checkmate():
|
||||
break
|
||||
else:
|
||||
board.pop()
|
||||
return PuzzleRunWrongMove(puzzle, board.fen(),
|
||||
result.move, expected_move)
|
||||
|
||||
if use_limit:
|
||||
time_limit.remaining_moves -= 1
|
||||
current_time = time.time()
|
||||
elapsed_time = current_time - start_time
|
||||
time_left = total_time - elapsed_time
|
||||
|
||||
if time_left < 0:
|
||||
return PuzzleRunTimeout(puzzle, total_time)
|
||||
|
||||
# The last move we made on the board was for the engine. So the
|
||||
# current turn is for the engine's opponent. We only update the
|
||||
# engine's clock to reflect the time limit.
|
||||
if board.turn == chess.WHITE:
|
||||
time_limit.black_clock = time_left
|
||||
else:
|
||||
time_limit.white_clock = time_left
|
||||
|
||||
return PuzzleRunSuccess(puzzle)
|
||||
|
||||
|
||||
def run_puzzle(puzzle, engine_path, timeout):
|
||||
async def run():
|
||||
try:
|
||||
return await _run_puzzle(puzzle, engine_path, timeout)
|
||||
except Exception as e:
|
||||
return PuzzleRunException(puzzle, e)
|
||||
|
||||
start_time = time.time()
|
||||
result = asyncio.run(run())
|
||||
end_time = time.time()
|
||||
result.duration_sec = end_time - start_time
|
||||
return result
|
||||
|
||||
|
||||
def format_duration(duration):
|
||||
return f'{duration:.3f}s'
|
||||
|
||||
|
||||
def next_multiple(n, multiple):
|
||||
return n + (multiple - n % multiple)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--engine', type=Path, required=True)
|
||||
parser.add_argument('--timeout', type=float,
|
||||
help='Timeout in seconds per puzzle')
|
||||
parser.add_argument('--junit', type=Path)
|
||||
parser.add_argument('puzzle_dbs', type=Path, nargs='+')
|
||||
args = parser.parse_args()
|
||||
|
||||
results = defaultdict(list)
|
||||
num_fails = 0
|
||||
total_duration = 0
|
||||
|
||||
for puzzle_db_path in args.puzzle_dbs:
|
||||
print(f'=== Running puzzles from {puzzle_db_path.resolve()} ===')
|
||||
|
||||
with puzzle_db_path.open(newline='') as f:
|
||||
puzzles = PuzzleDb.from_csv(f)
|
||||
|
||||
for puzzle in puzzles:
|
||||
print(f'Running puzzle {puzzle.puzzle_id} ... ',
|
||||
end='', flush=True)
|
||||
result = run_puzzle(puzzle, args.engine, args.timeout)
|
||||
total_duration += result.duration_sec
|
||||
duration_msg = f'({format_duration(result.duration_sec)})'
|
||||
|
||||
if result.is_success():
|
||||
print(f'OK {duration_msg}')
|
||||
else:
|
||||
num_fails += 1
|
||||
print(f'FAIL {duration_msg}')
|
||||
print(f'===\n{result}\n===')
|
||||
|
||||
results[puzzle_db_path].append(result)
|
||||
|
||||
if args.junit is not None:
|
||||
test_suites = []
|
||||
|
||||
for db_path, db_results in results.items():
|
||||
name = f'puzzles.{db_path.stem}'
|
||||
|
||||
def create_test_case(result):
|
||||
result.puzzle_type = name
|
||||
return result.to_junit_test_case()
|
||||
|
||||
test_cases = [create_test_case(r) for r in db_results]
|
||||
test_suite = TestSuite(name, test_cases)
|
||||
test_suites.append(test_suite)
|
||||
|
||||
xml = junit_xml.to_xml_report_string(test_suites)
|
||||
args.junit.write_text(xml)
|
||||
|
||||
print(f'Total time: {format_duration(total_duration)}')
|
||||
|
||||
if num_fails > 0:
|
||||
sys.exit(f'{num_fails} tests failed')
|
||||
else:
|
||||
print('All tests passed')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
20
TimeInfo.hpp
Normal file
20
TimeInfo.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef CHESS_ENGINE_TIMEINFO_HPP
|
||||
#define CHESS_ENGINE_TIMEINFO_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <chrono>
|
||||
|
||||
struct PlayerTimeInfo {
|
||||
std::chrono::milliseconds timeLeft;
|
||||
std::chrono::milliseconds increment;
|
||||
};
|
||||
|
||||
struct TimeInfo {
|
||||
using Optional = std::optional<TimeInfo>;
|
||||
|
||||
PlayerTimeInfo white;
|
||||
PlayerTimeInfo black;
|
||||
std::optional<unsigned> movesToGo;
|
||||
};
|
||||
|
||||
#endif
|
389
Uci.cpp
Normal file
389
Uci.cpp
Normal file
@@ -0,0 +1,389 @@
|
||||
#include "Uci.hpp"
|
||||
|
||||
#include "Engine.hpp"
|
||||
#include "Fen.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
|
||||
class UciOptionBase {
|
||||
public:
|
||||
|
||||
virtual ~UciOptionBase() = default;
|
||||
|
||||
virtual std::string name() const = 0;
|
||||
virtual std::string type() const = 0;
|
||||
virtual void streamOptionCommand(std::ostream& stream) const = 0;
|
||||
virtual bool setValue(Engine& engine, std::istream& stream) const = 0;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class UciOption : public UciOptionBase {
|
||||
public:
|
||||
|
||||
using Value = T;
|
||||
using OptionalValue = std::optional<Value>;
|
||||
|
||||
virtual OptionalValue default_() const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual OptionalValue min() const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual OptionalValue max() const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual std::vector<Value> vars() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
void streamOptionCommand(std::ostream& stream) const override {
|
||||
stream << "option name " << name() << " type " << type();
|
||||
streamIfHasValue(stream, "default", default_());
|
||||
streamIfHasValue(stream, "min", min());
|
||||
streamIfHasValue(stream, "max", max());
|
||||
|
||||
for (auto var : vars()) {
|
||||
stream << ' ' << "var " << var;
|
||||
}
|
||||
}
|
||||
|
||||
bool setValue(Engine& engine, std::istream& stream) const override {
|
||||
if (Value value; stream >> value) {
|
||||
return setValue(engine, value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool setValue(Engine& engine, Value value) const = 0;
|
||||
|
||||
private:
|
||||
|
||||
void streamIfHasValue(std::ostream& stream,
|
||||
const char* name,
|
||||
OptionalValue info) const {
|
||||
if (info.has_value()) {
|
||||
stream << ' ' << name << ' ' << info.value();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename T = std::intmax_t>
|
||||
class UciSpinOption : public UciOption<T> {
|
||||
public:
|
||||
|
||||
std::string type() const override {
|
||||
return "spin";
|
||||
}
|
||||
};
|
||||
|
||||
class UciHashOption : public UciSpinOption<std::size_t> {
|
||||
public:
|
||||
|
||||
UciHashOption(const HashInfo& hashInfo) : hashInfo_(hashInfo) {}
|
||||
|
||||
std::string name() const override {
|
||||
return "Hash";
|
||||
}
|
||||
|
||||
OptionalValue default_() const override {
|
||||
return hashInfo_.defaultSize;
|
||||
}
|
||||
|
||||
OptionalValue min() const override {
|
||||
return hashInfo_.minSize;
|
||||
}
|
||||
|
||||
OptionalValue max() const override {
|
||||
return hashInfo_.maxSize;
|
||||
}
|
||||
|
||||
bool setValue(Engine& engine, Value value) const override {
|
||||
if (value >= hashInfo_.minSize && value <= hashInfo_.maxSize) {
|
||||
engine.setHashSize(value);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
HashInfo hashInfo_;
|
||||
};
|
||||
|
||||
Uci::Uci(std::unique_ptr<Engine> engine,
|
||||
std::istream& cmdIn,
|
||||
std::ostream& cmdOut,
|
||||
std::ostream& log
|
||||
) : engine_(std::move(engine)), cmdIn_(cmdIn), cmdOut_(cmdOut), log_(log) {
|
||||
if (auto hashInfo = engine_->hashInfo(); hashInfo) {
|
||||
auto hashOption = std::make_unique<UciHashOption>(*hashInfo);
|
||||
options_[hashOption->name()] = std::move(hashOption);
|
||||
}
|
||||
}
|
||||
|
||||
// Needed here because Engine is only forward-declared in Uci.hpp causing an
|
||||
// error when compiling the destructor of std::unique_ptr.
|
||||
Uci::~Uci() = default;
|
||||
|
||||
void Uci::run() {
|
||||
log_ << "UCI engine started" << std::endl;
|
||||
|
||||
while (!cmdIn_.eof()) {
|
||||
std::string line;
|
||||
std::getline(cmdIn_, line);
|
||||
runCommand(line);
|
||||
}
|
||||
}
|
||||
|
||||
void Uci::runCommand(const std::string& line) {
|
||||
log_ << "> " << line << std::endl;
|
||||
|
||||
auto stream = std::stringstream(line);
|
||||
auto command = std::string();
|
||||
stream >> command;
|
||||
|
||||
if (command == "uci") {
|
||||
uciCommand(stream);
|
||||
} else if (command == "setoption") {
|
||||
setoptionCommand(stream);
|
||||
} else if (command == "isready") {
|
||||
isreadyCommand(stream);
|
||||
} else if (command == "ucinewgame") {
|
||||
ucinewgameCommand(stream);
|
||||
} else if (command == "position") {
|
||||
positionCommand(stream);
|
||||
} else if (command == "go") {
|
||||
goCommand(stream);
|
||||
} else if (command == "quit") {
|
||||
quitCommand(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void Uci::uciCommand(std::istream&) {
|
||||
std::stringstream nameCommand;
|
||||
nameCommand << "id name " << engine_->name() << " " << engine_->version();
|
||||
sendCommand(nameCommand.str());
|
||||
|
||||
std::stringstream authorCommand;
|
||||
authorCommand << "id author " << engine_->author();
|
||||
sendCommand(authorCommand.str());
|
||||
|
||||
sendOptions();
|
||||
sendCommand("uciok");
|
||||
}
|
||||
|
||||
void Uci::isreadyCommand(std::istream&) {
|
||||
sendCommand("readyok");
|
||||
}
|
||||
|
||||
void Uci::ucinewgameCommand(std::istream&) {
|
||||
engine_->newGame();
|
||||
}
|
||||
|
||||
void Uci::positionCommand(std::istream& stream) {
|
||||
auto type = std::string();
|
||||
stream >> type;
|
||||
|
||||
auto newBoard = Board::Optional();
|
||||
|
||||
if (type == "startpos") {
|
||||
newBoard = Fen::createBoard(Fen::StartingPos);
|
||||
} else if (type == "fen") {
|
||||
newBoard = Fen::createBoard(stream);
|
||||
} else {
|
||||
error("Illegal position type " + type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newBoard.has_value()) {
|
||||
error("Illegal FEN");
|
||||
return;
|
||||
}
|
||||
|
||||
board_ = newBoard.value();
|
||||
|
||||
auto moves = std::string();
|
||||
stream >> moves;
|
||||
|
||||
if (moves == "moves") {
|
||||
while (!stream.eof()) {
|
||||
auto uciMove = std::string();
|
||||
stream >> uciMove;
|
||||
|
||||
if (uciMove.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto optMove = Move::fromUci(uciMove);
|
||||
|
||||
if (!optMove.has_value()) {
|
||||
error("Illegal move " + uciMove);
|
||||
return;
|
||||
}
|
||||
|
||||
board_.makeMove(optMove.value());
|
||||
}
|
||||
}
|
||||
|
||||
log_ << board_ << std::endl;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static std::optional<T> readValue(std::istream& stream) {
|
||||
if (T value; stream >> value) {
|
||||
return value;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
TimeInfo::Optional Uci::readTimeInfo(std::istream& stream) {
|
||||
std::optional<unsigned> wtime, winc, btime, binc, movestogo;
|
||||
|
||||
for (std::string command; stream >> command;) {
|
||||
auto value = readValue<unsigned>(stream);
|
||||
|
||||
if (command == "wtime") {
|
||||
wtime = value;
|
||||
} else if (command == "winc") {
|
||||
winc = value;
|
||||
} else if (command == "btime") {
|
||||
btime = value;
|
||||
} else if (command == "binc") {
|
||||
binc = value;
|
||||
} else if (command == "movestogo") {
|
||||
movestogo = value;
|
||||
} else if (command == "infinite") {
|
||||
error("go infinite not supported");
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
if (wtime.has_value() && btime.has_value()) {
|
||||
PlayerTimeInfo whiteTime, blackTime;
|
||||
whiteTime.timeLeft = std::chrono::milliseconds(wtime.value());
|
||||
whiteTime.increment = std::chrono::milliseconds(winc.value_or(0));
|
||||
blackTime.timeLeft = std::chrono::milliseconds(btime.value());
|
||||
blackTime.increment = std::chrono::milliseconds(binc.value_or(0));
|
||||
|
||||
TimeInfo timeInfo;
|
||||
timeInfo.white = whiteTime;
|
||||
timeInfo.black = blackTime;
|
||||
timeInfo.movesToGo = movestogo;
|
||||
return timeInfo;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void Uci::goCommand(std::istream& stream) {
|
||||
auto timeInfo = readTimeInfo(stream);
|
||||
auto pv = engine_->pv(board_, timeInfo);
|
||||
|
||||
if (pv.length() == 0) {
|
||||
error("Engine returned no PV");
|
||||
return;
|
||||
}
|
||||
|
||||
log_ << "PV: " << pv << std::endl;
|
||||
sendPvInfo(pv);
|
||||
|
||||
auto bestMove = *pv.begin();
|
||||
board_.makeMove(bestMove);
|
||||
log_ << board_ << std::endl;
|
||||
|
||||
auto bestMoveCmd = std::stringstream();
|
||||
bestMoveCmd << "bestmove " << bestMove;
|
||||
sendCommand(bestMoveCmd.str());
|
||||
}
|
||||
|
||||
void Uci::quitCommand(std::istream&) {
|
||||
std::exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
void Uci::setoptionCommand(std::istream& stream) {
|
||||
std::string nameCommand;
|
||||
stream >> nameCommand;
|
||||
|
||||
if (nameCommand != "name") {
|
||||
error("Illegal setoption: did not start with 'name'");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
stream >> name;
|
||||
|
||||
for (std::string nextPart; stream >> nextPart;) {
|
||||
if (nextPart == "value") {
|
||||
break;
|
||||
}
|
||||
|
||||
name += ' ' + nextPart;
|
||||
}
|
||||
|
||||
// We could get here without a "value" command. This is by design because
|
||||
// "button" types don't have one.
|
||||
auto optionIt = options_.find(name);
|
||||
|
||||
if (optionIt == options_.end()) {
|
||||
error("Illegal option: " + name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!optionIt->second->setValue(*engine_, stream)) {
|
||||
error("Illegal option value");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Uci::sendPvInfo(const PrincipalVariation& pv) {
|
||||
auto stream = std::stringstream();
|
||||
stream << "info score ";
|
||||
|
||||
auto score = pv.score();
|
||||
|
||||
if (pv.isMate()) {
|
||||
auto numMoves =
|
||||
score < 0 ? std::floor(score / 2.0) : std::ceil(score / 2.0);
|
||||
stream << "mate " << numMoves;
|
||||
} else {
|
||||
stream << "cp " << score;
|
||||
}
|
||||
|
||||
stream << " pv";
|
||||
|
||||
for (auto move : pv) {
|
||||
stream << ' ' << move;
|
||||
}
|
||||
|
||||
sendCommand(stream.str());
|
||||
}
|
||||
|
||||
void Uci::sendOptions() {
|
||||
for (const auto& [name, option] : options_) {
|
||||
std::stringstream cmd;
|
||||
option->streamOptionCommand(cmd);
|
||||
sendCommand(cmd.str());
|
||||
}
|
||||
}
|
||||
|
||||
void Uci::sendCommand(const std::string& command) {
|
||||
log_ << "< " << command << std::endl;
|
||||
cmdOut_ << command << std::endl;
|
||||
}
|
||||
|
||||
void Uci::error(const std::string& msg) {
|
||||
log_ << "UCI error: " << msg << std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
51
Uci.hpp
Normal file
51
Uci.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef CHESS_ENGINE_UCI_HPP
|
||||
#define CHESS_ENGINE_UCI_HPP
|
||||
|
||||
#include "Board.hpp"
|
||||
#include "TimeInfo.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
class Engine;
|
||||
class PrincipalVariation;
|
||||
class UciOptionBase;
|
||||
|
||||
class Uci {
|
||||
public:
|
||||
|
||||
Uci(std::unique_ptr<Engine> engine,
|
||||
std::istream& cmdIn,
|
||||
std::ostream& cmdOut,
|
||||
std::ostream& log);
|
||||
~Uci();
|
||||
|
||||
void run();
|
||||
|
||||
private:
|
||||
|
||||
void runCommand(const std::string& line);
|
||||
void uciCommand(std::istream& stream);
|
||||
void isreadyCommand(std::istream& stream);
|
||||
void ucinewgameCommand(std::istream& stream);
|
||||
void positionCommand(std::istream& stream);
|
||||
void goCommand(std::istream& stream);
|
||||
void quitCommand(std::istream& stream);
|
||||
void setoptionCommand(std::istream& stream);
|
||||
TimeInfo::Optional readTimeInfo(std::istream& stream);
|
||||
void sendPvInfo(const PrincipalVariation& pv);
|
||||
void sendOptions();
|
||||
void sendCommand(const std::string& line);
|
||||
void error(const std::string& msg);
|
||||
|
||||
std::unique_ptr<Engine> engine_;
|
||||
Board board_;
|
||||
std::istream& cmdIn_;
|
||||
std::ostream& cmdOut_;
|
||||
std::ostream& log_;
|
||||
std::map<std::string, std::unique_ptr<UciOptionBase>> options_;
|
||||
};
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user