Add assignment

This commit is contained in:
Job Noorman
2022-10-27 12:29:19 +02:00
commit 9f05ab03c1
49 changed files with 4339 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/Build*/
/build*/
__pycache__/
uci-log.txt

44
.gitlab-ci.yml Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

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

14
EngineFactory.hpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
[*ForsythEdwards 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
View 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
View 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
View 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
View 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

Submodule Tests/Catch2 added at c4e3767e26

48
Tests/EngineTests.cpp Normal file
View 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
View 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
View File

@@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"

111
Tests/MoveTests.cpp Normal file
View 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
View 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);
}

View 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
1 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
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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
14 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
15 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
16 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
17 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
18 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
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
20 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

View 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
1 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
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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
14 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
15 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
16 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
17 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
18 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
19 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
20 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

View 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
1 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
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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
14 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
15 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
16 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
17 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
18 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

View 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
1 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
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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
14 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
15 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
16 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
17 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
18 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
19 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
20 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

View 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
1 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
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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
14 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
15 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
16 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
17 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
18 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
19 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
20 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

View 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
1 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
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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
14 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
15 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
16 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
17 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
18 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
19 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
20 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

View 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
1 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
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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
14 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
15 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
16 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
17 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
18 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
19 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
20 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

View 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
1 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
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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
14 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
15 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
16 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
17 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
18 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
19 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
20 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

View 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
1 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
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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
14 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
15 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
16 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
17 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
18 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
19 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
20 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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