Add assignment
This commit is contained in:
945
Tests/BoardTests.cpp
Normal file
945
Tests/BoardTests.cpp
Normal file
@@ -0,0 +1,945 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "TestUtils.hpp"
|
||||
|
||||
#include "Board.hpp"
|
||||
#include "Square.hpp"
|
||||
#include "Fen.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
TEST_CASE("A default-constructed board is empty", "[Board][Fundamental]") {
|
||||
auto board = Board();
|
||||
|
||||
for (auto i = 0; i < 64; ++i) {
|
||||
auto optSquare = Square::fromIndex(i);
|
||||
REQUIRE(optSquare.has_value());
|
||||
|
||||
auto square = optSquare.value();
|
||||
auto optPiece = board.piece(square);
|
||||
REQUIRE_FALSE(optPiece.has_value());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Pieces can be set on a board", "[Board][Fundamental]") {
|
||||
auto board = Board();
|
||||
|
||||
auto optSquare = Square::fromIndex(41);
|
||||
REQUIRE(optSquare.has_value());
|
||||
|
||||
auto square = optSquare.value();
|
||||
auto piece = Piece(PieceColor::White, PieceType::Rook);
|
||||
board.setPiece(square, piece);
|
||||
|
||||
auto optSetPiece = board.piece(square);
|
||||
REQUIRE(optSetPiece.has_value());
|
||||
|
||||
auto setPiece = optSetPiece.value();
|
||||
REQUIRE(setPiece == piece);
|
||||
|
||||
SECTION("Setting a piece on an occupied square overrides") {
|
||||
auto newPiece = Piece(PieceColor::Black, PieceType::King);
|
||||
board.setPiece(square, newPiece);
|
||||
|
||||
auto optNewSetPiece = board.piece(square);
|
||||
REQUIRE(optNewSetPiece.has_value());
|
||||
|
||||
auto newSetPiece = optNewSetPiece.value();
|
||||
REQUIRE(newSetPiece == newPiece);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("The turn can be set on a board", "[Board][Fundamental]") {
|
||||
auto color = GENERATE(PieceColor::Black, PieceColor::White);
|
||||
|
||||
auto board = Board();
|
||||
board.setTurn(color);
|
||||
REQUIRE(board.turn() == color);
|
||||
}
|
||||
|
||||
static void testPseudoLegalMoves(
|
||||
const char* fen,
|
||||
const std::string& fromName,
|
||||
const std::vector<std::string>& expectedTargets)
|
||||
{
|
||||
auto optBoard = Fen::createBoard(fen);
|
||||
REQUIRE(optBoard.has_value());
|
||||
|
||||
auto from = Square::Optional();
|
||||
|
||||
if (!fromName.empty()) {
|
||||
from = Square::fromName(fromName);
|
||||
REQUIRE(from.has_value());
|
||||
}
|
||||
|
||||
auto board = optBoard.value();
|
||||
|
||||
using MoveSet = std::set<Move>;
|
||||
auto expectedMoves = MoveSet();
|
||||
|
||||
for (auto targetName : expectedTargets) {
|
||||
if (targetName.length() == 2) {
|
||||
// targetName is a square
|
||||
auto optTarget = Square::fromName(targetName);
|
||||
REQUIRE(optTarget.has_value());
|
||||
REQUIRE(from.has_value());
|
||||
|
||||
auto target = optTarget.value();
|
||||
auto move = Move(from.value(), target);
|
||||
expectedMoves.insert(move);
|
||||
} else {
|
||||
// targetName is a move
|
||||
auto optMove = Move::fromUci(targetName);
|
||||
REQUIRE(optMove.has_value());
|
||||
|
||||
expectedMoves.insert(optMove.value());
|
||||
}
|
||||
}
|
||||
|
||||
auto generatedMovesVec = Board::MoveVec();
|
||||
|
||||
if (from.has_value()) {
|
||||
board.pseudoLegalMovesFrom(from.value(), generatedMovesVec);
|
||||
} else {
|
||||
board.pseudoLegalMoves(generatedMovesVec);
|
||||
}
|
||||
|
||||
auto generatedMoves = MoveSet(generatedMovesVec.begin(),
|
||||
generatedMovesVec.end());
|
||||
|
||||
auto unexpectedMoves = MoveSet();
|
||||
auto notGeneratedMoves = MoveSet();
|
||||
|
||||
std::set_difference(
|
||||
generatedMoves.begin(), generatedMoves.end(),
|
||||
expectedMoves.begin(), expectedMoves.end(),
|
||||
std::inserter(unexpectedMoves, unexpectedMoves.begin())
|
||||
);
|
||||
|
||||
std::set_difference(
|
||||
expectedMoves.begin(), expectedMoves.end(),
|
||||
generatedMoves.begin(), generatedMoves.end(),
|
||||
std::inserter(notGeneratedMoves, notGeneratedMoves.begin())
|
||||
);
|
||||
|
||||
CAPTURE(fen, from, unexpectedMoves, notGeneratedMoves);
|
||||
REQUIRE(generatedMoves == expectedMoves);
|
||||
}
|
||||
|
||||
#define TEST_CASE_PSEUDO_MOVES(name, tag) \
|
||||
TEST_CASE(name, "[Board][MoveGen]" tag)
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal knight moves, empty board", "[Knight]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3N4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/8/3N4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c6", "e6",
|
||||
"f5", "f3",
|
||||
"c2", "e2",
|
||||
"b3", "b5"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal knight moves, side of board", "[Knight]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/n7/8_b_-_-_0_1
|
||||
"8/8/8/8/8/8/n7/8 b - - 0 1",
|
||||
"a2",
|
||||
{
|
||||
"b4",
|
||||
"c3", "c1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal knight moves, captures", "[Knight]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/2p1P3/8/2PN4/2Pp4/8/8_w_-_-_0_1
|
||||
"8/8/2p1P3/8/2PN4/2Pp4/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c6",
|
||||
"f5", "f3",
|
||||
"c2", "e2",
|
||||
"b3", "b5"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal king moves, empty board", "[King]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3K4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/8/3K4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c5", "d5", "e5",
|
||||
"c4", "e4",
|
||||
"c3", "d3", "e3",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal king moves, side of board", "[King]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/8/K7_w_-_-_0_1
|
||||
"8/8/8/8/8/8/8/K7 w - - 0 1",
|
||||
"a1",
|
||||
{
|
||||
"a2", "b2",
|
||||
"b1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal king moves, captures", "[King]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/2PK4/3p4/8/8_w_-_-_0_1
|
||||
"8/8/8/8/2PK4/3p4/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c5", "d5", "e5",
|
||||
"e4",
|
||||
"c3", "d3", "e3",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal bishop moves, empty board", "[Bishop]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3B4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/8/3B4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c5", "b6", "a7",
|
||||
"e5", "f6", "g7", "h8",
|
||||
"c3", "b2", "a1",
|
||||
"e3", "f2", "g1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal bishop moves, captures", "[Bishop]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/1p3P2/8/3B4/8/8/8_w_-_-_0_1
|
||||
"8/8/1p3P2/8/3B4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c5", "b6",
|
||||
"e5",
|
||||
"c3", "b2", "a1",
|
||||
"e3", "f2", "g1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal rook moves, empty board", "[Rook]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3R4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/8/3R4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"d5", "d6", "d7", "d8",
|
||||
"d3", "d2", "d1",
|
||||
"c4", "b4", "a4",
|
||||
"e4", "f4", "g4", "h4"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal rook moves, captures", "[Rook]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/3p4/8/3R1P2/8/8/8_w_-_-_0_1
|
||||
"8/8/3p4/8/3R1P2/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"d5", "d6",
|
||||
"d3", "d2", "d1",
|
||||
"c4", "b4", "a4",
|
||||
"e4"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal queen moves, empty board", "[Queen]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3Q4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/8/3Q4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c5", "b6", "a7",
|
||||
"e5", "f6", "g7", "h8",
|
||||
"c3", "b2", "a1",
|
||||
"e3", "f2", "g1",
|
||||
"d5", "d6", "d7", "d8",
|
||||
"d3", "d2", "d1",
|
||||
"c4", "b4", "a4",
|
||||
"e4", "f4", "g4", "h4"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal queen moves, captures", "[Queen]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/1p3P2/8/1P1Q4/8/3p4/8_w_-_-_0_1
|
||||
"8/8/1p3P2/8/1P1Q4/8/3p4/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c5", "b6",
|
||||
"e5",
|
||||
"c3", "b2", "a1",
|
||||
"e3", "f2", "g1",
|
||||
"d5", "d6", "d7", "d8",
|
||||
"d3", "d2",
|
||||
"c4",
|
||||
"e4", "f4", "g4", "h4"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, step, empty board", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3P4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/8/3P4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"d5"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, step, opponent blocked", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/3p4/3P4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/3p4/3P4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, step, self blocked", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/3p4/3b4/8/8/8_b_-_-_0_1
|
||||
"8/8/8/3p4/3b4/8/8/8 b - - 0 1",
|
||||
"d5",
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, capture left", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/2ppP3/3P4/8/8/8_w_-_-_0_1
|
||||
"8/8/8/2ppP3/3P4/8/8/8 w - - 0 1",
|
||||
"d4",
|
||||
{
|
||||
"c5"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, capture both", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/3p4/2PRQ3/8/8/8_b_-_-_0_1
|
||||
"8/8/8/3p4/2PRQ3/8/8/8 b - - 0 1",
|
||||
"d5",
|
||||
{
|
||||
"c4", "e4"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, base step, empty board", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/3p4/8/8/8/8/8/8_b_-_-_0_1
|
||||
"8/3p4/8/8/8/8/8/8 b - - 0 1",
|
||||
"d7",
|
||||
{
|
||||
"d6", "d5"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, base step, blocked 1", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/3p4/3P4/8/8/8/8/8_b_-_-_0_1
|
||||
"8/3p4/3P4/8/8/8/8/8 b - - 0 1",
|
||||
"d7",
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, base step, blocked 2", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/7p/8/7P/8_w_-_-_0_1
|
||||
"8/8/8/8/7p/8/7P/8 w - - 0 1",
|
||||
"h2",
|
||||
{
|
||||
"h3"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, en passant, black left", "[Pawn][EnPassant]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3Pp3/8/8/8_b_-_d3_0_1
|
||||
"8/8/8/8/3Pp3/8/8/8 b - d3 0 1",
|
||||
"e4",
|
||||
{
|
||||
"e3", "d3"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, en passant, black right", "[Pawn][EnPassant]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/2pP4/2P5/8/8_b_-_d3_0_1
|
||||
"8/8/8/8/2pP4/2P5/8/8 b - d3 0 1",
|
||||
"c4",
|
||||
{
|
||||
"d3"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, en passant, white left", "[Pawn][EnPassant]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/2n5/pP6/8/8/8/8_w_-_a6_0_1
|
||||
"8/8/2n5/pP6/8/8/8/8 w - a6 0 1",
|
||||
"b5",
|
||||
{
|
||||
"a6", "b6", "c6"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, en passant, white right", "[Pawn][EnPassant]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/n7/Pp6/8/8/8/8_w_-_b6_0_1
|
||||
"8/8/n7/Pp6/8/8/8/8 w - b6 0 1",
|
||||
"a5",
|
||||
{
|
||||
"b6"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, en passant, black both", "[Pawn][EnPassant]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/5pPp/7P/8/8_b_-_g3_0_1
|
||||
"8/8/8/8/5pPp/7P/8/8 b - g3 0 1",
|
||||
"",
|
||||
{
|
||||
"f4f3", "f4g3",
|
||||
"h4g3"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, en passant, white both", "[Pawn][EnPassant]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/2r2n2/2PpP3/8/8/8/8_w_-_d6_0_1
|
||||
"8/8/2r2n2/2PpP3/8/8/8/8 w - d6 0 1",
|
||||
"",
|
||||
{
|
||||
"c5d6",
|
||||
"e5d6", "e5e6", "e5f6"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, en passant, wrong square", "[Pawn][EnPassant]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/3p1P2/8/8/8_b_-_f3_0_1
|
||||
"8/8/8/8/3p1P2/8/8/8 b - f3 0 1",
|
||||
"d4",
|
||||
{
|
||||
"d3"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, visual en passant, no square", "[Pawn]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/1pPp4/8/8/8/8_w_-_-_0_1
|
||||
"8/8/8/1pPp4/8/8/8/8 w - - 0 1",
|
||||
"c5",
|
||||
{
|
||||
"c6"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, white promotions", "[Pawn][Promotion]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/5r2/4P3/8/8/8/8/8/8_w_-_-_0_1
|
||||
"5r2/4P3/8/8/8/8/8/8 w - - 0 1",
|
||||
"e7",
|
||||
{
|
||||
"e7e8q", "e7e8r", "e7e8b", "e7e8n",
|
||||
"e7f8q", "e7f8r", "e7f8b", "e7f8n"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal pawn moves, black promotions", "[Pawn][Promotion]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/1p6/2Q5_b_-_-_0_1
|
||||
"8/8/8/8/8/8/1p6/2Q5 b - - 0 1",
|
||||
"b2",
|
||||
{
|
||||
"b2b1q", "b2b1r", "b2b1b", "b2b1n",
|
||||
"b2c1q", "b2c1r", "b2c1b", "b2c1n"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, white kingside", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/3PPP2/3QK2R_w_K_-_0_1
|
||||
"8/8/8/8/8/8/3PPP2/3QK2R w K - 0 1",
|
||||
"e1",
|
||||
{
|
||||
"f1", "g1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, white queenside", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/3PPP2/R3KB2_w_Q_-_0_1
|
||||
"8/8/8/8/8/8/3PPP2/R3KB2 w Q - 0 1",
|
||||
"e1",
|
||||
{
|
||||
"d1", "c1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, black kingside", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/3qk2r/3ppp2/8/8/8/8/8/8_b_k_-_0_1
|
||||
"3qk2r/3ppp2/8/8/8/8/8/8 b k - 0 1",
|
||||
"e8",
|
||||
{
|
||||
"f8", "g8"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, black queenside", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/r3kb2/3ppp2/8/8/8/8/8/8_b_q_-_0_1
|
||||
"r3kb2/3ppp2/8/8/8/8/8/8 b q - 0 1",
|
||||
"e8",
|
||||
{
|
||||
"d8", "c8"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, kingside blocked", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/3PPP2/3QK1NR_w_K_-_0_1
|
||||
"8/8/8/8/8/8/3PPP2/3QK1NR w K - 0 1",
|
||||
"e1",
|
||||
{
|
||||
"f1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, queenside blocked", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/rn2kb2/3ppp2/8/8/8/8/8/8_b_q_-_0_1
|
||||
"rn2kb2/3ppp2/8/8/8/8/8/8 b q - 0 1",
|
||||
"e8",
|
||||
{
|
||||
"d8"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, kingside attacked", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/3qk2r/3ppp2/5N2/8/8/8/8/8_b_k_-_0_1
|
||||
"3qk2r/3ppp2/5N2/8/8/8/8/8 b k - 0 1",
|
||||
"e8",
|
||||
{
|
||||
"f8"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, queenside attacked", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/5n2/3PPP2/R3KB2_w_Q_-_0_1
|
||||
"8/8/8/8/8/5n2/3PPP2/R3KB2 w Q - 0 1",
|
||||
"e1",
|
||||
{
|
||||
"d1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, queenside rook attacked", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/r3kb2/3ppp2/8/8/8/5B2/8/8_b_q_-_0_1
|
||||
"r3kb2/3ppp2/8/8/8/5B2/8/8 b q - 0 1",
|
||||
"e8",
|
||||
{
|
||||
"d8", "c8"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, kingside rook attacked", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/2q5/8/8/8/3PPP2/3QK2R_w_K_-_0_1
|
||||
"8/8/2q5/8/8/8/3PPP2/3QK2R w K - 0 1",
|
||||
"e1",
|
||||
{
|
||||
"f1", "g1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, queenside b-square attacked", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/r3kb2/3ppp2/8/8/8/8/8/1R6_b_q_-_0_1
|
||||
"r3kb2/3ppp2/8/8/8/8/8/1R6 b q - 0 1",
|
||||
"e8",
|
||||
{
|
||||
"d8", "c8"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal castling moves, no rights", "[Castling]") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/r3k2r/8/8/8/8/8/3PPP2/R3K2R_w_Kkq_-_0_1
|
||||
"r3k2r/8/8/8/8/8/3PPP2/R3K2R w Kkq - 0 1",
|
||||
"e1",
|
||||
{
|
||||
"f1", "g1",
|
||||
"d1"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static void testMakeMove(const char* fen, const Move& move) {
|
||||
auto board = Fen::createBoard(fen);
|
||||
REQUIRE(board.has_value());
|
||||
|
||||
auto piece = board->piece(move.from());
|
||||
REQUIRE(piece.has_value());
|
||||
|
||||
auto turn = board->turn();
|
||||
board->makeMove(move);
|
||||
REQUIRE(board->turn() == !turn);
|
||||
|
||||
REQUIRE_FALSE(board->piece(move.from()).has_value());
|
||||
|
||||
auto toPiece = board->piece(move.to());
|
||||
REQUIRE(toPiece.has_value());
|
||||
REQUIRE(piece == toPiece.value());
|
||||
}
|
||||
|
||||
static void testMakeMove(const char* fen, const Move& move,
|
||||
const std::map<Square, Piece>& expectedBoard) {
|
||||
CAPTURE(fen, move);
|
||||
|
||||
auto board = Fen::createBoard(fen);
|
||||
REQUIRE(board.has_value());
|
||||
|
||||
auto turn = board->turn();
|
||||
board->makeMove(move);
|
||||
REQUIRE(board->turn() == !turn);
|
||||
|
||||
for (auto [square, expectedPiece] : expectedBoard) {
|
||||
auto piece = board->piece(square);
|
||||
INFO("Expected piece not on board");
|
||||
CAPTURE(square, piece, expectedPiece);
|
||||
REQUIRE(piece.has_value());
|
||||
REQUIRE(piece.value() == expectedPiece);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 64; ++i) {
|
||||
auto square = Square::fromIndex(i);
|
||||
REQUIRE(square.has_value());
|
||||
|
||||
auto piece = board->piece(square.value());
|
||||
CAPTURE(square.value(), piece);
|
||||
auto expectedPieceIt = expectedBoard.find(square.value());
|
||||
|
||||
if (piece.has_value()) {
|
||||
INFO("Unexpected piece on board");
|
||||
REQUIRE(expectedPieceIt != expectedBoard.end());
|
||||
CAPTURE(expectedPieceIt->second);
|
||||
REQUIRE(piece.value() == expectedPieceIt->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void testMakeMove(const char* fen, const Move& move, CastlingRights cr) {
|
||||
auto board = Fen::createBoard(fen);
|
||||
REQUIRE(board.has_value());
|
||||
|
||||
auto turn = board->turn();
|
||||
board->makeMove(move);
|
||||
REQUIRE(board->turn() == !turn);
|
||||
|
||||
REQUIRE(board->castlingRights() == cr);
|
||||
}
|
||||
|
||||
static void testMakeMove(const char* fen, const Move& move,
|
||||
const Square::Optional& epSquare) {
|
||||
auto board = Fen::createBoard(fen);
|
||||
REQUIRE(board.has_value());
|
||||
|
||||
auto turn = board->turn();
|
||||
board->makeMove(move);
|
||||
REQUIRE(board->turn() == !turn);
|
||||
|
||||
REQUIRE(board->enPassantSquare() == epSquare);
|
||||
}
|
||||
|
||||
#define TEST_CASE_MAKE_MOVE(name, tag) \
|
||||
TEST_CASE(name, "[Board][MoveMaking]" tag)
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making moves a piece on the board", "") {
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/8/2Q5_w_-_-_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/8/8/8/8/2Q5 w - - 0 1",
|
||||
Move(Square::C1, Square::F4)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making supports captures", "") {
|
||||
// https://lichess.org/editor/8/8/8/2n5/8/3P4/8/8_b_-_-_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/2n5/8/3P4/8/8 b - - 0 1",
|
||||
Move(Square::C5, Square::D3)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, white promotion", "[Promotion]") {
|
||||
// https://lichess.org/editor/8/4P3/8/8/8/8/8/8_w_-_-_0_1
|
||||
testMakeMove(
|
||||
"8/4P3/8/8/8/8/8/8 w - - 0 1",
|
||||
Move(Square::E7, Square::E8, PieceType::Queen),
|
||||
{
|
||||
{Square::E8, Piece(PieceColor::White, PieceType::Queen)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, black promotion", "[Promotion]") {
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/p7/8_b_-_-_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/8/8/8/p7/8 b - - 0 1",
|
||||
Move(Square::A2, Square::A1, PieceType::Knight),
|
||||
{
|
||||
{Square::A1, Piece(PieceColor::Black, PieceType::Knight)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling white kingside", "[Castling]") {
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/8/4K2R_w_K_-_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/8/8/8/8/4K2R w K - 0 1",
|
||||
Move(Square::E1, Square::G1),
|
||||
{
|
||||
{Square::G1, Piece(PieceColor::White, PieceType::King)},
|
||||
{Square::F1, Piece(PieceColor::White, PieceType::Rook)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling white queenside", "[Castling]") {
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/8/R3K3_w_Q_-_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/8/8/8/8/R3K3 w Q - 0 1",
|
||||
Move(Square::E1, Square::C1),
|
||||
{
|
||||
{Square::C1, Piece(PieceColor::White, PieceType::King)},
|
||||
{Square::D1, Piece(PieceColor::White, PieceType::Rook)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling black kingside", "[Castling]") {
|
||||
// https://lichess.org/editor/4k2r/8/8/8/8/8/8/8_b_k_-_0_1
|
||||
testMakeMove(
|
||||
"4k2r/8/8/8/8/8/8/8 b k - 0 1",
|
||||
Move(Square::E8, Square::G8),
|
||||
{
|
||||
{Square::G8, Piece(PieceColor::Black, PieceType::King)},
|
||||
{Square::F8, Piece(PieceColor::Black, PieceType::Rook)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling black queenside", "[Castling]") {
|
||||
// https://lichess.org/editor/r3k3/8/8/8/8/8/8/8_b_q_-_0_1
|
||||
testMakeMove(
|
||||
"r3k3/8/8/8/8/8/8/8 b q - 0 1",
|
||||
Move(Square::E8, Square::C8),
|
||||
{
|
||||
{Square::C8, Piece(PieceColor::Black, PieceType::King)},
|
||||
{Square::D8, Piece(PieceColor::Black, PieceType::Rook)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling rights king move", "[Castling]") {
|
||||
// https://lichess.org/editor/r3k2r/8/8/8/8/8/8/R3K2R_w_KQkq_-_0_1
|
||||
testMakeMove(
|
||||
"r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1",
|
||||
Move(Square::E1, Square::D1),
|
||||
CastlingRights::Black
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling rights rook move", "[Castling]") {
|
||||
// https://lichess.org/editor/r3k2r/8/8/8/8/8/8/R3K2R_b_KQkq_-_0_1
|
||||
testMakeMove(
|
||||
"r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1",
|
||||
Move(Square::H8, Square::G8),
|
||||
CastlingRights::White | CastlingRights::BlackQueenside
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling rights rook capture", "[Castling]") {
|
||||
// https://lichess.org/editor/r3k2r/8/8/8/8/8/8/R3K2R_w_KQkq_-_0_1
|
||||
testMakeMove(
|
||||
"r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1",
|
||||
Move(Square::A1, Square::A8),
|
||||
CastlingRights::WhiteKingside | CastlingRights::BlackKingside
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, castling rights king back to base", "[Castling]") {
|
||||
// https://lichess.org/editor/r3k2r/8/8/8/8/8/8/R2K3R_w_kq_-_0_1
|
||||
testMakeMove(
|
||||
"r3k2r/8/8/8/8/8/8/R2K3R w kq - 0 1",
|
||||
Move(Square::D1, Square::E1),
|
||||
CastlingRights::Black
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant, black left", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/8/8/8/5Pp1/8/8/8_b_-_f3_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/8/5Pp1/8/8/8 b - f3 0 1",
|
||||
Move(Square::G4, Square::F3),
|
||||
{
|
||||
{Square::F3, Piece(PieceColor::Black, PieceType::Pawn)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant, black right", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/8/8/3P4/1PpPP3/2P5/8/8_b_-_d3_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/3P4/1PpPP3/2P5/8/8 b - d3 0 1",
|
||||
Move(Square::C4, Square::D3),
|
||||
{
|
||||
{Square::D3, Piece(PieceColor::Black, PieceType::Pawn)},
|
||||
{Square::B4, Piece(PieceColor::White, PieceType::Pawn)},
|
||||
{Square::C3, Piece(PieceColor::White, PieceType::Pawn)},
|
||||
{Square::D5, Piece(PieceColor::White, PieceType::Pawn)},
|
||||
{Square::E4, Piece(PieceColor::White, PieceType::Pawn)},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant, white left", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/8/8/3pPp2/8/8/8/8_w_-_d6_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/3pPp2/8/8/8/8 w - d6 0 1",
|
||||
Move(Square::E5, Square::D6),
|
||||
{
|
||||
{Square::D6, Piece(PieceColor::White, PieceType::Pawn)},
|
||||
{Square::F5, Piece(PieceColor::Black, PieceType::Pawn)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant, white right", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/8/8/1Pp5/8/8/8/8_w_-_c6_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/1Pp5/8/8/8/8 w - c6 0 1",
|
||||
Move(Square::B5, Square::C6),
|
||||
{
|
||||
{Square::C6, Piece(PieceColor::White, PieceType::Pawn)}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant square, white double step update", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/8/8/8/3p4/8/4P3/8_w_-_-_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/8/3p4/8/4P3/8 w - - 0 1",
|
||||
Move(Square::E2, Square::E4),
|
||||
Square::E3
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant square, black double step update", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/p7/8/1P6/8/8/8/8_b_-_-_0_1
|
||||
testMakeMove(
|
||||
"8/p7/8/1P6/8/8/8/8 b - - 0 1",
|
||||
Move(Square::A7, Square::A5),
|
||||
Square::A6
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant square, remove after capture", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/8/8/8/Pp6/8/8/8_b_-_a3_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/8/Pp6/8/8/8 b - a3 0 1",
|
||||
Move(Square::B4, Square::A3),
|
||||
std::nullopt
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_MAKE_MOVE("Move making, en passant square, remove after move", "[EnPassant]") {
|
||||
// https://lichess.org/editor/8/8/8/4pP2/8/8/1N6/8_w_-_e6_0_1
|
||||
testMakeMove(
|
||||
"8/8/8/4pP2/8/8/1N6/8 w - e6 0 1",
|
||||
Move(Square::B2, Square::C4),
|
||||
std::nullopt
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal moves, multiple pieces, white", "") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/2P5/NB6_w_-_-_0_1
|
||||
"8/8/8/8/8/8/2P5/NB6 w - - 0 1",
|
||||
"",
|
||||
{
|
||||
"a1b3",
|
||||
"b1a2",
|
||||
"c2c3", "c2c4"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_PSEUDO_MOVES("Pseudo-legal moves, multiple pieces, black", "") {
|
||||
testPseudoLegalMoves(
|
||||
// https://lichess.org/editor/7n/7b/6p1/8/8/8/8/8_b_-_-_0_1
|
||||
"7n/7b/6p1/8/8/8/8/8 b - - 0 1",
|
||||
"",
|
||||
{
|
||||
"h8f7",
|
||||
"h7g8",
|
||||
"g6g5"
|
||||
}
|
||||
);
|
||||
}
|
16
Tests/CMakeLists.txt
Normal file
16
Tests/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
add_subdirectory(Catch2/)
|
||||
|
||||
add_executable(tests
|
||||
Main.cpp
|
||||
SquareTests.cpp
|
||||
MoveTests.cpp
|
||||
PieceTests.cpp
|
||||
BoardTests.cpp
|
||||
FenTests.cpp
|
||||
EngineTests.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(tests cplchess_lib Catch2::Catch2)
|
||||
|
||||
include(Catch2/contrib/Catch.cmake)
|
||||
catch_discover_tests(tests)
|
1
Tests/Catch2
Submodule
1
Tests/Catch2
Submodule
Submodule Tests/Catch2 added at c4e3767e26
48
Tests/EngineTests.cpp
Normal file
48
Tests/EngineTests.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "TestUtils.hpp"
|
||||
|
||||
#include "EngineFactory.hpp"
|
||||
#include "Engine.hpp"
|
||||
#include "Fen.hpp"
|
||||
#include "Board.hpp"
|
||||
|
||||
static std::unique_ptr<Engine> createEngine() {
|
||||
return EngineFactory::createEngine();
|
||||
}
|
||||
|
||||
static void testGameEnd(const char* fen, bool isMate) {
|
||||
auto engine = createEngine();
|
||||
REQUIRE(engine != nullptr);
|
||||
|
||||
auto board = Fen::createBoard(fen);
|
||||
REQUIRE(board.has_value());
|
||||
|
||||
auto pv = engine->pv(board.value());
|
||||
|
||||
REQUIRE(pv.isMate() == isMate);
|
||||
REQUIRE(pv.score() == 0);
|
||||
REQUIRE(pv.length() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Engine detects checkmate", "[Engine][Checkmate]") {
|
||||
auto fen = GENERATE(
|
||||
// https://lichess.org/editor/4R2k/6pp/8/8/8/8/8/K7_b_-_-_0_1
|
||||
"4R2k/6pp/8/8/8/8/8/K7 b - - 0 1",
|
||||
// https://lichess.org/editor/7k/8/8/8/1bb5/8/r7/3BK3_w_-_-_0_1
|
||||
"7k/8/8/8/1bb5/8/r7/3BK3 w - - 0 1"
|
||||
);
|
||||
|
||||
testGameEnd(fen, true);
|
||||
}
|
||||
|
||||
TEST_CASE("Engine detects stalemate", "[Engine][Stalemate]") {
|
||||
auto fen = GENERATE(
|
||||
// https://lichess.org/editor/k7/p7/P7/8/8/8/8/KR6_b_-_-_0_1
|
||||
"k7/p7/P7/8/8/8/8/KR6 b - - 0 1",
|
||||
// https://lichess.org/editor/k7/8/8/6n1/r7/4K3/2q5/8_w_-_-_0_1
|
||||
"k7/8/8/6n1/r7/4K3/2q5/8 w - - 0 1"
|
||||
);
|
||||
|
||||
testGameEnd(fen, false);
|
||||
}
|
117
Tests/FenTests.cpp
Normal file
117
Tests/FenTests.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "TestUtils.hpp"
|
||||
|
||||
#include "Fen.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
TEST_CASE("Legal placements are correctly parsed", "[Fen]") {
|
||||
// https://lichess.org/editor/k7/2B5/7p/1q3P2/8/3N4/8/5r2_w_-_-_0_1
|
||||
auto fen = "k7/2B5/7p/1q3P2/8/3N4/8/5r2 w - - 0 1";
|
||||
|
||||
auto optBoard = Fen::createBoard(fen);
|
||||
REQUIRE(optBoard.has_value());
|
||||
|
||||
auto board = optBoard.value();
|
||||
|
||||
auto expectedPlacement = std::map<Square, Piece>{
|
||||
{Square::F1, Piece(PieceColor::Black, PieceType::Rook)},
|
||||
{Square::D3, Piece(PieceColor::White, PieceType::Knight)},
|
||||
{Square::B5, Piece(PieceColor::Black, PieceType::Queen)},
|
||||
{Square::F5, Piece(PieceColor::White, PieceType::Pawn)},
|
||||
{Square::H6, Piece(PieceColor::Black, PieceType::Pawn)},
|
||||
{Square::C7, Piece(PieceColor::White, PieceType::Bishop)},
|
||||
{Square::A8, Piece(PieceColor::Black, PieceType::King)}
|
||||
};
|
||||
|
||||
for (auto index = 0; index < 64; ++index) {
|
||||
auto optSquare = Square::fromIndex(index);
|
||||
REQUIRE(optSquare.has_value());
|
||||
|
||||
auto square = optSquare.value();
|
||||
INFO("Checking square " << square);
|
||||
|
||||
auto expectedPieceIt = expectedPlacement.find(square);
|
||||
auto optActualPiece = board.piece(square);
|
||||
|
||||
if (expectedPieceIt == expectedPlacement.end()) {
|
||||
INFO("Expecting empty")
|
||||
REQUIRE_FALSE(optActualPiece.has_value());
|
||||
} else {
|
||||
auto expectedPiece = expectedPieceIt->second;
|
||||
INFO("Expecting " << expectedPiece);
|
||||
|
||||
REQUIRE(optActualPiece.has_value());
|
||||
|
||||
auto actualPiece = optActualPiece.value();
|
||||
REQUIRE(actualPiece == expectedPiece);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Legal turns are correctly parsed", "[Fen]") {
|
||||
auto [fen, turn] = GENERATE(table<const char*, PieceColor>({
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/8/8_w_-_-_0_1
|
||||
{"8/8/8/8/8/8/8/8 w - - 0 1", PieceColor::White},
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/8/8_b_-_-_0_1
|
||||
{"8/8/8/8/8/8/8/8 b - - 0 1", PieceColor::Black}
|
||||
}));
|
||||
|
||||
auto optBoard = Fen::createBoard(fen);
|
||||
REQUIRE(optBoard.has_value());
|
||||
|
||||
auto board = optBoard.value();
|
||||
REQUIRE(board.turn() == turn);
|
||||
}
|
||||
|
||||
TEST_CASE("Castling rights are correctly parsed", "[Fen]") {
|
||||
auto [fen, cr] = GENERATE(table<const char*, CastlingRights>({
|
||||
// https://lichess.org/editor/rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR_w_KQkq_-_0_1
|
||||
{
|
||||
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
|
||||
CastlingRights::All
|
||||
},
|
||||
// https://lichess.org/editor/8/8/8/8/8/8/8/8_b_-_-_0_1
|
||||
{
|
||||
"8/8/8/8/8/8/8/8 b - - 0 1",
|
||||
CastlingRights::None
|
||||
},
|
||||
// https://lichess.org/editor/rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR_w_Qk_-_0_1
|
||||
{
|
||||
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w Qk - 0 1",
|
||||
CastlingRights::WhiteQueenside | CastlingRights::BlackKingside
|
||||
}
|
||||
}));
|
||||
|
||||
CAPTURE(fen, cr);
|
||||
|
||||
auto optBoard = Fen::createBoard(fen);
|
||||
REQUIRE(optBoard.has_value());
|
||||
|
||||
auto board = optBoard.value();
|
||||
REQUIRE(board.castlingRights() == cr);
|
||||
}
|
||||
|
||||
TEST_CASE("En passant square is correctly parsed", "[Fen][EnPassant]") {
|
||||
auto [fen, ep] = GENERATE(table<const char*, Square::Optional>({
|
||||
// https://lichess.org/editor/rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR_w_KQkq_-_0_1
|
||||
{
|
||||
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
|
||||
std::nullopt
|
||||
},
|
||||
// https://lichess.org/editor/rnbqkbnr/ppppp1pp/8/8/2PPPp2/8/PP3PPP/RNBQKBNR_b_KQkq_e3_0_3
|
||||
{
|
||||
"rnbqkbnr/ppppp1pp/8/8/2PPPp2/8/PP3PPP/RNBQKBNR b KQkq e3 0 3",
|
||||
Square::E3
|
||||
}
|
||||
}));
|
||||
|
||||
CAPTURE(fen, ep);
|
||||
|
||||
auto optBoard = Fen::createBoard(fen);
|
||||
REQUIRE(optBoard.has_value());
|
||||
|
||||
auto board = optBoard.value();
|
||||
REQUIRE(board.enPassantSquare() == ep);
|
||||
}
|
2
Tests/Main.cpp
Normal file
2
Tests/Main.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch2/catch.hpp"
|
111
Tests/MoveTests.cpp
Normal file
111
Tests/MoveTests.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "TestUtils.hpp"
|
||||
|
||||
#include "Move.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
TEST_CASE("Moves store the squares they are constructed with", "[Move][Fundamental]") {
|
||||
auto from = Square::E2;
|
||||
auto to = Square::G6;
|
||||
auto move = Move(from, to);
|
||||
REQUIRE(move.from() == from);
|
||||
REQUIRE(move.to() == to);
|
||||
REQUIRE_FALSE(move.promotion().has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("Moves store the promotion they are constructed with", "[Move][Promotion]") {
|
||||
auto from = Square::A7;
|
||||
auto to = Square::A8;
|
||||
auto promotion = PieceType::Knight;
|
||||
auto move = Move(from, to, promotion);
|
||||
REQUIRE(move.from() == from);
|
||||
REQUIRE(move.to() == to);
|
||||
REQUIRE(move.promotion().has_value());
|
||||
REQUIRE(move.promotion().value() == promotion);
|
||||
}
|
||||
|
||||
TEST_CASE("Moves support equality checks", "[Move][Fundamental]") {
|
||||
auto move1 = Move(Square::D4, Square::H7);
|
||||
auto move2 = Move(Square::D4, Square::G3);
|
||||
auto move3 = Move(Square::A3, Square::H7);
|
||||
auto move1Copy = move1;
|
||||
REQUIRE(move1 == move1Copy);
|
||||
REQUIRE_FALSE(move1 == move2);
|
||||
REQUIRE_FALSE(move1 == move3);
|
||||
}
|
||||
|
||||
TEST_CASE("Moves with promotions support equality checks", "[Move][Promotion]") {
|
||||
auto move1 = Move(Square::B2, Square::B1, PieceType::Queen);
|
||||
auto move2 = Move(Square::B2, Square::B1);
|
||||
auto move3 = Move(Square::C2, Square::C1, PieceType::Queen);
|
||||
auto move1Copy = move1;
|
||||
REQUIRE(move1 == move1Copy);
|
||||
REQUIRE_FALSE(move1 == move2);
|
||||
REQUIRE_FALSE(move1 == move3);
|
||||
}
|
||||
|
||||
TEST_CASE("Moves stream their UCI correctly", "[Move][Fundamental]") {
|
||||
auto [from, to, uci] = GENERATE(table<Square, Square, const char*>({
|
||||
{Square::A1, Square::D7, "a1d7"},
|
||||
{Square::C8, Square::F2, "c8f2"},
|
||||
{Square::H6, Square::B1, "h6b1"}
|
||||
}));
|
||||
|
||||
auto move = Move(from, to);
|
||||
auto stream = std::stringstream();
|
||||
stream << move;
|
||||
REQUIRE(stream.str() == uci);
|
||||
}
|
||||
|
||||
TEST_CASE("Moves with promotions stream their UCI correctly", "[Move][Promotion]") {
|
||||
auto [from, to, promotion, uci] = GENERATE(table<Square, Square, PieceType, const char*>({
|
||||
{Square::H7, Square::H8, PieceType::Knight, "h7h8n"},
|
||||
{Square::C2, Square::C1, PieceType::Rook, "c2c1r"},
|
||||
{Square::E7, Square::E8, PieceType::Queen, "e7e8q"}
|
||||
}));
|
||||
|
||||
auto move = Move(from, to, promotion);
|
||||
auto stream = std::stringstream();
|
||||
stream << move;
|
||||
REQUIRE(stream.str() == uci);
|
||||
}
|
||||
|
||||
TEST_CASE("Moves can be created from valid UCI notation", "[Move][Fundamental]") {
|
||||
auto [uci, from, to] = GENERATE(table<const char*, Square, Square>({
|
||||
{"a1d7", Square::A1, Square::D7},
|
||||
{"c8f2", Square::C8, Square::F2},
|
||||
{"h6b1", Square::H6, Square::B1}
|
||||
}));
|
||||
|
||||
auto move = Move::fromUci(uci);
|
||||
auto expectedMove = Move(from, to);
|
||||
|
||||
CAPTURE(uci, expectedMove, move);
|
||||
REQUIRE(move.has_value());
|
||||
REQUIRE(move.value() == expectedMove);
|
||||
}
|
||||
|
||||
TEST_CASE("Moves can be created from valid UCI notation with promotion", "[Move][Promotion]") {
|
||||
auto [uci, from, to, promotion] = GENERATE(table<const char*, Square, Square, PieceType>({
|
||||
{"a7a8n", Square::A7, Square::A8, PieceType::Knight},
|
||||
{"c2c1b", Square::C2, Square::C1, PieceType::Bishop},
|
||||
{"h7h8r", Square::H7, Square::H8, PieceType::Rook}
|
||||
}));
|
||||
|
||||
auto move = Move::fromUci(uci);
|
||||
auto expectedMove = Move(from, to, promotion);
|
||||
|
||||
CAPTURE(uci, expectedMove, move);
|
||||
REQUIRE(move.has_value());
|
||||
REQUIRE(move.value() == expectedMove);
|
||||
}
|
||||
|
||||
TEST_CASE("Moves are not created from invalid UCI notation", "[Move][Fundamental]") {
|
||||
auto uci = GENERATE("a1d7x", "a1d", "a1d7123", "a1", "", "a9d1", "a1c0");
|
||||
auto move = Move::fromUci(uci);
|
||||
|
||||
CAPTURE(uci, move);
|
||||
REQUIRE_FALSE(move.has_value());
|
||||
}
|
74
Tests/PieceTests.cpp
Normal file
74
Tests/PieceTests.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "Piece.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <cctype>
|
||||
|
||||
TEST_CASE("Pieces store color and type correctly", "[Piece][Fundamental]") {
|
||||
auto color = PieceColor::Black;
|
||||
auto type = PieceType::Queen;
|
||||
auto piece = Piece(color, type);
|
||||
REQUIRE(piece.color() == color);
|
||||
REQUIRE(piece.type() == type);
|
||||
}
|
||||
|
||||
TEST_CASE("Pieces can be created from valid symbols", "[Piece][Fundamental]") {
|
||||
auto [blackSymbol, pieceType] = GENERATE(table<char, PieceType>({
|
||||
{'p', PieceType::Pawn},
|
||||
{'n', PieceType::Knight},
|
||||
{'b', PieceType::Bishop},
|
||||
{'r', PieceType::Rook},
|
||||
{'q', PieceType::Queen},
|
||||
{'k', PieceType::King}
|
||||
}));
|
||||
|
||||
auto optBlackPiece = Piece::fromSymbol(blackSymbol);
|
||||
REQUIRE(optBlackPiece.has_value());
|
||||
|
||||
auto createdBlackPiece = optBlackPiece.value();
|
||||
REQUIRE(createdBlackPiece == Piece(PieceColor::Black, pieceType));
|
||||
|
||||
auto whiteSymbol = static_cast<char>(std::toupper(blackSymbol));
|
||||
auto optWhitePiece = Piece::fromSymbol(whiteSymbol);
|
||||
REQUIRE(optWhitePiece.has_value());
|
||||
|
||||
auto createdWhitePiece = optWhitePiece.value();
|
||||
REQUIRE(createdWhitePiece == Piece(PieceColor::White, pieceType));
|
||||
}
|
||||
|
||||
TEST_CASE("Pieces are not created from invalid symbols", "[Piece][Fundamental]") {
|
||||
auto symbol = GENERATE('x', 'Z');
|
||||
|
||||
auto optPiece = Piece::fromSymbol(symbol);
|
||||
REQUIRE_FALSE(optPiece.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("Pieces stream their symbol correctly", "[Piece][Fundamental]") {
|
||||
auto [blackSymbol, pieceType] = GENERATE(table<char, PieceType>({
|
||||
{'p', PieceType::Pawn},
|
||||
{'n', PieceType::Knight},
|
||||
{'b', PieceType::Bishop},
|
||||
{'r', PieceType::Rook},
|
||||
{'q', PieceType::Queen},
|
||||
{'k', PieceType::King}
|
||||
}));
|
||||
|
||||
auto blackStream = std::stringstream();
|
||||
auto blackPiece = Piece(PieceColor::Black, pieceType);
|
||||
blackStream << blackPiece;
|
||||
auto blackSymbolStr = std::string(1, blackSymbol);
|
||||
REQUIRE(blackStream.str() == blackSymbolStr);
|
||||
|
||||
auto whiteStream = std::stringstream();
|
||||
auto whitePiece = Piece(PieceColor::White, pieceType);
|
||||
whiteStream << whitePiece;
|
||||
auto whiteSymbol = static_cast<char>(std::toupper(blackSymbol));
|
||||
auto whiteSymbolStr = std::string(1, whiteSymbol);
|
||||
REQUIRE(whiteStream.str() == whiteSymbolStr);
|
||||
}
|
||||
|
||||
TEST_CASE("PieceColor can be inverted correctly", "[Piece][Fundamental]") {
|
||||
REQUIRE((!PieceColor::White) == PieceColor::Black);
|
||||
REQUIRE((!PieceColor::Black) == PieceColor::White);
|
||||
}
|
20
Tests/Puzzles/crushing_castling.csv
Normal file
20
Tests/Puzzles/crushing_castling.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
1nIpz,4r3/pppkn3/8/3p2R1/3P4/8/PPP2P1b/R3K3 w Q - 1 21,e1c1 h2f4 c1b1 f4g5,816,95,90,588,crushing endgame fork short,https://lichess.org/hNgUKDs9#41
|
||||
Mu4H8,2r1k2r/1p3ppp/p1nqp3/3N4/6P1/3Q3P/PPP2P2/R3R1K1 b k - 0 18,e8g8 d5f6 g7f6 d3d6,1242,76,93,2547,crushing discoveredAttack middlegame short,https://lichess.org/4TidVneQ/black#36
|
||||
tXqKo,r1b1k2r/1pB2ppp/p7/n2q4/8/3B1N2/P5PP/RN1Q1K2 b kq - 0 19,e8g8 d3h7 g8h7 d1d5,1119,83,94,746,crushing discoveredAttack kingsideAttack opening short,https://lichess.org/7uOa49aA/black#38
|
||||
w24YE,r3k1r1/pbp1qp2/1pn2bp1/1N2p3/4Q3/PP2P1P1/RBPP2B1/4K2R b Kq - 0 20,e8c8 b5a7 c6a7 e4b7,1418,76,96,4838,crushing middlegame queensideAttack short,https://lichess.org/Xg4JUKxt/black#40
|
||||
3FThf,2kr1r2/pp3ppp/2n2nq1/4p3/8/2PP1Q2/PP2NPPP/RN2K2R w KQ - 2 16,e1g1 d8d3 f3d3 g6d3,1468,76,85,526,crushing middlegame short trappedPiece,https://lichess.org/IsNuAAN9#31
|
||||
jJ2Y6,r3kb1r/ppp2ppp/1nb5/6q1/3Q4/2P2P2/PP1NN1PP/R1B1K2R w KQkq - 1 12,e1g1 f8c5 d2b3 c5d4,1251,79,92,1234,crushing opening pin short,https://lichess.org/jXD6blSd#23
|
||||
RgLWD,r4r1k/1pp1q1pp/p2b1pn1/8/P2P4/1PQ1PN2/1B3PPP/R3K2R w KQ - 3 17,d4d5 d6b4 e1g1 b4c3,1061,76,91,782,crushing middlegame pin short,https://lichess.org/5BMsYBNs#33
|
||||
Oy4HD,4r1k1/1ppnqpb1/p5pp/3p4/1n1P2PP/PPQ2P2/1B1NN3/R3K2R w KQ - 1 20,e1c1 b4a2 c1b1 a2c3,1022,75,95,2356,crushing fork middlegame short,https://lichess.org/rLlXAUHf#39
|
||||
mYVLz,r3k2r/pp1q1pp1/2n4p/3Nb3/2P1pN2/P7/R4PPP/3Q1RK1 b kq - 0 16,e8g8 d5f6 g7f6 d1d7,1228,75,96,4950,crushing discoveredAttack middlegame short,https://lichess.org/4Td7Ylh7/black#32
|
||||
Xru4M,r1b1k2r/ppp2ppp/8/2bq4/8/3B4/PPPQ1PPP/R1B2RK1 b kq - 1 11,e8g8 d3h7 g8h7 d2d5,832,80,98,741,crushing discoveredAttack kingsideAttack middlegame short,https://lichess.org/SUQJEod2/black#22
|
||||
O3mf8,r1bqk2r/pp3pp1/2nb3p/2pQ4/3P4/2P2N2/PP3PPP/RNB1K2R w KQkq - 0 11,e1g1 d6h2 f3h2 d8d5,992,77,98,503,crushing discoveredAttack kingsideAttack opening short,https://lichess.org/25COtgWc#21
|
||||
YZphi,1rb2rk1/p4p1p/4p1p1/q1ppb3/6PP/1PN5/P1PQBB2/R3K2R w KQ - 0 17,e1c1 e5c3 d2c3 a5c3,1455,76,73,794,crushing master middlegame short,https://lichess.org/YUCwTutk#33
|
||||
Xp0Fu,rnbqk2r/pp1p1ppp/2p1p3/3n4/1b1P4/1P1BPN2/P1P2PPP/RNBQK2R w KQkq - 1 6,b1d2 d5c3 e1g1 c3d1,1291,74,98,1101,crushing opening short trappedPiece,https://lichess.org/ZLMwfhTb#11
|
||||
gSuce,r3k2r/ppp2p1p/2q3p1/3N1b2/4P2P/2bQ4/P1P2PP1/1R1K1B1R b kq - 0 15,e8c8 d5e7 c8b8 e7c6,978,93,97,502,crushing fork middlegame pin short,https://lichess.org/s986T8cn/black#30
|
||||
ojyVA,r4rk1/1pp2ppp/p7/8/3nq3/P3N1Q1/BPP2PP1/R3KR2 w Q - 5 19,e1c1 d4e2 c1b1 e2g3,1467,75,99,1838,crushing fork middlegame short,https://lichess.org/9QGJ7rZV#37
|
||||
JRXLt,2q1r1k1/5pbp/r4np1/8/1B2P1b1/Pp3PN1/1P1Q3P/R2NK2R w KQ - 0 19,f3g4 f6e4 e1g1 e4d2,1400,75,93,653,crushing master middlegame short,https://lichess.org/uOcxibZc#37
|
||||
Mqwig,r3k3/pp1q4/3p3Q/2p1p3/2Pn4/2NP2P1/PP1K2BP/8 b q - 0 22,e8c8 g2h3 d7h3 h6h3,1459,74,95,7529,crushing endgame pin short,https://lichess.org/eMX64gmi/black#44
|
||||
or8tr,r1bq3r/pp2kppp/3bpn2/1B6/3Q4/2N5/PPP2PPP/R1B1K2R w KQ - 3 10,e1g1 d6h2 g1h2 d8d4,1251,75,97,2368,crushing discoveredAttack kingsideAttack opening short,https://lichess.org/aTyyc4D6#19
|
||||
ufB48,r3k2r/pp2pp2/n4b1p/1Npq2p1/8/3B1P2/2P2P1P/1R1Q1RK1 b kq - 0 16,e8g8 d3h7 g8h7 d1d5,1044,76,100,578,crushing discoveredAttack master middlegame short,https://lichess.org/4O7cI2vX/black#32
|
||||
PPhgy,2r1k2r/pp1b1ppp/4p2n/3pP3/1b1q1P2/2NB4/PP1Q2PP/R1B2R1K b k - 5 13,e8g8 d3h7 g8h7 d2d4,1120,79,89,831,crushing discoveredAttack kingsideAttack middlegame short,https://lichess.org/NDtiGLde/black#26
|
|
20
Tests/Puzzles/crushing_enPassant.csv
Normal file
20
Tests/Puzzles/crushing_enPassant.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
LjCmb,1k1r3r/pp4Rp/1n2Bp2/2pPq3/1Q2N3/P6P/1P6/K2R4 w - c6 0 29,d5c6 d8d1 a1a2 e5e6,1177,76,94,1259,crushing hangingPiece middlegame short,https://lichess.org/Yk2zG0UY#57
|
||||
04bSF,8/8/4kp2/2pPp1p1/1p4P1/1P1PK3/r1P1RP2/8 b - - 0 40,e6d5 c2c4 b4c3 e2a2,1055,78,94,722,crushing discoveredAttack endgame rookEndgame short,https://lichess.org/Ecn7TQaC/black#80
|
||||
AF9UO,2kr2nr/ppp3pp/2bb1p2/5q2/3Pp3/1BP3P1/PP3P1P/RNBQR1K1 b - d3 0 13,e4d3 b3e6 f5e6 e1e6,976,78,99,1079,crushing fork opening short,https://lichess.org/fVTwPwnn/black#26
|
||||
DeL19,2b2rk1/p4ppp/2p1p3/2qpP3/5B1P/3B1PPK/1r6/R2Q3R w - d6 0 24,e5d6 e6e5 g3g4 e5f4,1338,76,93,539,crushing discoveredAttack middlegame short,https://lichess.org/xu3NKiH3#47
|
||||
m2n9Q,2r5/B2q2bp/2Np4/1Q1Ppkp1/2P2pP1/5P2/P6P/6K1 b - g3 0 33,f4g3 c6d4 e5d4 b5d7,1426,75,97,7352,crushing discoveredAttack endgame short,https://lichess.org/mYaK9ddh/black#66
|
||||
r2rqH,1B6/7p/2p1k1p1/1pKpnpP1/8/8/PPP2P2/8 w - f6 0 33,g5f6 e5d7 c5c6 d7b8,1329,77,91,528,crushing endgame fork short,https://lichess.org/3jItgkvn#65
|
||||
t18DC,8/7p/3k2p1/8/1p1p2P1/1P1P1K2/r1P2R2/8 b - - 2 44,d6d5 c2c4 d4c3 f2a2,1060,83,83,516,crushing discoveredAttack endgame rookEndgame short,https://lichess.org/RIDIKGiy/black#88
|
||||
Gc8wi,4rk2/1p5p/2p1q1pP/1p1pPp2/rP1P2P1/P1R1QPK1/8/4R3 w - f6 0 34,e5f6 e6d6 e3e5 e8e5,1417,74,95,6861,crushing endgame short,https://lichess.org/YiVM7jB1#67
|
||||
RCtAF,r1bq1rk1/1p3pp1/p1np4/2p1pPPp/P1B1P1n1/3P4/1PP3P1/RNBQ1RK1 w - h6 0 12,g5h6 d8h4 d1g4 h4g4,1372,76,91,547,crushing middlegame short,https://lichess.org/rXkJ3qWC#23
|
||||
yLkJ8,2r2rk1/3q1ppp/3b4/2pP3b/3QP3/2N5/PP3PPP/R1B2RK1 w - c6 0 17,d5c6 d6h2 g1h2 d7d4,1278,74,90,1312,crushing discoveredAttack kingsideAttack middlegame short,https://lichess.org/zu4W2Xjc#33
|
||||
hQiNO,8/2r2p1R/pk2p3/4P3/1P1K1P2/1P6/8/8 w - - 5 45,d4e4 f7f5 e5f6 c7h7,1226,75,98,3368,crushing discoveredAttack endgame rookEndgame short,https://lichess.org/kiGIgx3X#89
|
||||
XcSfQ,8/R4pr1/2p3k1/1p4Pp/3P1P1P/P4K2/1P6/8 w - - 1 34,f3e4 f7f5 g5f6 g7a7,1435,74,95,7150,crushing discoveredAttack endgame rookEndgame short,https://lichess.org/H2FyMIZK#67
|
||||
mqUlP,r3r1k1/bb1q1pp1/p6p/2p5/1PPp4/P2BnP2/3QNBPP/R3R2K b - c3 0 22,d4c3 d3h7 g8h7 d2d7,1389,75,97,7843,crushing discoveredAttack middlegame short,https://lichess.org/2GkHylJl/black#44
|
||||
BlgSP,3r1k2/p2r1p1R/1pn1p1p1/2p3P1/2P1KP2/P1RPP3/5N2/8 w - - 1 34,f2g4 f7f5 g5f6 d7h7,1414,75,97,8105,crushing discoveredAttack endgame fork master short,https://lichess.org/SVDzwDrX#67
|
||||
8Wf37,2r3k1/8/1B1Qp1r1/8/4PPp1/P5Pp/1P5P/2q2RK1 b - f3 0 28,g4f3 f1c1 c8c1 g1f2,1131,77,93,2318,crushing endgame short,https://lichess.org/MgBKmMyw/black#56
|
||||
x1ST3,8/5p1r/r1pkp1p1/p5P1/P2PKP2/1RR4P/8/8 w - - 6 44,b3b7 f7f5 g5f6 h7b7,1206,75,88,120,crushing discoveredAttack endgame rookEndgame short,https://lichess.org/VKjN7j2C#87
|
||||
8zW22,1R6/1P6/5p2/5k2/5Pp1/1r4P1/6K1/8 b - f3 0 67,g4f3 g2f2 b3b2 f2f3,1125,77,93,2162,crushing defensiveMove endgame rookEndgame short,https://lichess.org/IGmSHmFL/black#134
|
||||
GNTiD,r1bq1rk1/2p4p/1p1p2p1/p2Pp1N1/P1P1p3/6P1/1P3P1P/R2Q1RK1 w - e6 0 17,d5e6 d8g5 d1d5 g5d5,1172,77,93,560,crushing hangingPiece middlegame short,https://lichess.org/Dvdu67gt#33
|
||||
DyB55,8/kpr2p1R/p3p1p1/2b1P1P1/4pP2/1PP2K2/3B4/8 w - - 0 34,f3e4 f7f5 e5f6 c7h7,988,76,95,633,crushing discoveredAttack endgame short,https://lichess.org/E6h2U3g9#67
|
||||
a3jh8,4r1k1/p2n2b1/1p1Br1pp/2p1Pp2/P3R3/5N1P/1PP2PP1/4R1K1 w - f6 0 28,e5f6 e6e4 e1e4 e8e4,1156,77,95,3041,crushing middlegame short,https://lichess.org/PMYUPzpW#55
|
|
18
Tests/Puzzles/crushing_simple.csv
Normal file
18
Tests/Puzzles/crushing_simple.csv
Normal file
@@ -0,0 +1,18 @@
|
||||
gjB0I,r1r3k1/p3nppp/5q2/3pp3/3nQ1P1/P1P2N1P/P2PPPB1/R1B1KR2 w Q - 0 15,e4e5 d4f3 g2f3 f6e5,1441,77,96,7017,crushing intermezzo middlegame short,https://lichess.org/NNlhThCX#29
|
||||
DjF9F,7k/1q4p1/PP2r2p/8/2Pp4/3p3P/4QPK1/RR6 w - - 0 37,e2f3 e6g6 g2h2 b7f3,1026,79,96,936,crushing deflection endgame pin short,https://lichess.org/ZRdpTIc7#73
|
||||
sdK1f,7R/N3p3/4k1b1/6p1/5bP1/1P3P1P/PKP2n2/8 w - - 5 32,h3h4 f4e5 b2b1 e5h8,1350,75,94,4319,crushing endgame fork short,https://lichess.org/jRPMtIwq#63
|
||||
c4Q2K,6k1/2pp4/1p1b2p1/p2n1rNp/7P/1P1Q3K/P4P2/5R2 w - - 2 40,g5e4 d5f4 h3h2 f4d3,964,75,100,633,crushing discoveredAttack endgame fork short,https://lichess.org/Wr2XI4kL#79
|
||||
JMggH,r1b1k1nr/pp2bBpp/1np5/8/3P4/3q1N2/PP1N1KPP/R1BQ3R b kq - 0 13,e8f7 f3e5 f7f8 e5d3,1062,76,98,531,crushing fork opening short,https://lichess.org/cV3k9SJS/black#26
|
||||
UkI37,8/7p/1b4k1/pB6/P2p1BP1/4nK2/8/8 b - - 3 48,e3c2 b5d3,1186,76,94,1406,crushing endgame oneMove,https://lichess.org/6Zy6lEOJ/black#96
|
||||
u2uUc,4r3/pp3qk1/2p2n2/3pQP1p/8/2NB4/PPP2bPP/2K2R2 w - - 2 24,e5f4 f2e3 f4e3 e8e3,1382,74,97,6750,crushing fork middlegame short,https://lichess.org/SA2OAz7V#47
|
||||
95aih,r1b1r1k1/pp3ppp/3p4/4n3/3p2B1/1P6/P1P2PPP/RN2R1K1 w - - 0 17,g4c8 e5f3 g2f3 e8e1,1180,78,94,4398,crushing discoveredAttack kingsideAttack middlegame short,https://lichess.org/OFPiuoKw#33
|
||||
TGtYY,2r2rk1/ppqb1pp1/7p/4p3/4Qn2/1P1B4/PBPP1PPP/2KR3R w - - 6 19,b2e5 f4d3 e4d3 c7e5,1257,75,94,975,crushing middlegame pin queensideAttack short,https://lichess.org/VKOdc5WH#37
|
||||
UFDsq,2R5/p2rr1pk/1p2b2p/8/5R1P/4B1P1/P4P2/6K1 w - - 11 30,a2a4 d7d1 g1h2 e6c8,1021,75,98,882,crushing discoveredAttack endgame short,https://lichess.org/6WDvsr9F#59
|
||||
EhBCM,6k1/4bp2/p3b1p1/1pp1P3/5P1p/1P2BK1P/P1B3P1/8 w - - 0 28,f3e4 e6f5 e4d5 f5c2,1106,75,99,1353,bishopEndgame crushing endgame master short skewer,https://lichess.org/kSGgZyH6#55
|
||||
1udMI,7k/1pp3p1/3p4/p7/6P1/2P1Pr1P/1P2K3/7R b - - 1 29,f3g3 e2f2 g3g4 h3g4,1165,76,90,743,crushing discoveredAttack endgame rookEndgame short trappedPiece,https://lichess.org/BwjVuezV/black#58
|
||||
TpwxP,2kr4/R1pq1ppp/8/8/8/2PPr3/P1P3P1/R2QK3 w - - 0 23,e1d2 e3d3 c2d3 d7d3,1300,75,96,3003,crushing endgame sacrifice short,https://lichess.org/wRRxYzZB#45
|
||||
v4SMo,3r2k1/5p1p/p3p1p1/2P1P1P1/5P2/P7/4p2P/2R3K1 w - - 0 31,g1f2 d8d1 f2e2 d1c1,1231,74,99,2872,crushing endgame rookEndgame short,https://lichess.org/80LcrMO8#61
|
||||
pv5iO,2kr1bnr/pp1n1p1p/2pp1q2/6p1/4Pp2/1PNP3P/1PP3PN/R1BQ1RK1 w - - 0 12,a1a7 f6d4 g1h1 d4a7,1164,75,98,835,crushing fork middlegame short,https://lichess.org/JeND0wKx#23
|
||||
IFFS9,3q2k1/3brppp/8/p1pp2N1/3P4/N7/4QPPP/1R4K1 w - - 0 25,e2e7 d8e7 b1b8 d7e8,994,75,98,970,crushing defensiveMove endgame hangingPiece short,https://lichess.org/L4E4AcIg#49
|
||||
2x4l0,5r1k/3b1pp1/p2qp2p/3p4/P1n5/2NBP1P1/1r1P1P1P/R1Q2RK1 b - - 5 24,b2d2 d3c4 d5c4 c3e4,1448,74,93,1998,crushing middlegame short,https://lichess.org/3YGuCQvX/black#48
|
||||
E9fXH,2kr4/3n1p2/2p2qp1/1p6/6P1/1Bb1P1Br/2P1QP2/1R1R2K1 w - - 2 23,d1d6 h3g3 f2g3 f6d6,1484,74,98,7693,crushing middlegame short,https://lichess.org/OhNQYiqs#45
|
|
20
Tests/Puzzles/mateIn1_castling.csv
Normal file
20
Tests/Puzzles/mateIn1_castling.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
WW5uk,r3kb1r/2p1ppp1/p1b2n1p/3qN3/3P4/4P3/PP3PPP/RNBQK2R w KQkq - 4 11,e1g1 d5g2,760,100,91,649,kingsideAttack mate mateIn1 oneMove opening,https://lichess.org/oBiCLIGA#21
|
||||
zjfWz,rnbqk2r/ppp1bppp/8/3p2P1/5PP1/3Q4/PPPP4/RNB1K1NR b KQkq - 0 10,e8g8 d3h7,1210,79,90,285,kingsideAttack mate mateIn1 oneMove opening,https://lichess.org/4kj3hdb1/black#20
|
||||
swBXg,r1b1k1nr/1p3ppp/p2bp3/4q3/8/2NBB3/PPP2PPP/R2QK2R w KQkq - 2 12,e1g1 e5h2,836,85,71,126,kingsideAttack mate mateIn1 oneMove opening,https://lichess.org/lhIYvgoM#23
|
||||
E1wln,Qn2kbnr/p1q1ppp1/2p5/5bp1/4p3/2N5/PPPPBPPP/R1B1K2R w KQk - 3 10,e1g1 c7h2,1350,77,75,146,kingsideAttack mate mateIn1 oneMove opening,https://lichess.org/mB3slcX0#19
|
||||
hIf6g,rn2k2r/2qbpp1p/2Np2p1/pB1P4/1p1Q4/7P/PPP2PP1/2KR3R b kq - 0 15,e8g8 c6e7,1020,163,82,11,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/IlQIKE1K/black#30
|
||||
PKlaU,r1bk3N/1p1pqBpQ/p1p5/2b1p3/3n4/8/PPP2nPP/RNB1K2R w KQ - 0 12,e1g1 d4e2,1427,131,86,61,mate mateIn1 middlegame oneMove,https://lichess.org/KStAa02H#23
|
||||
HgNow,r3k2r/pppn1p2/4pq1p/8/3P1Q2/P1P1P1B1/5P1P/R3K1R1 b Qkq - 2 18,e8c8 f4c7,931,76,100,195,mate mateIn1 middlegame oneMove queensideAttack,https://lichess.org/kzE6a9h5/black#36
|
||||
6b7Wt,r3kbnr/ppp4p/2p1bqp1/4Q3/3PPB2/8/PPP2PPP/RN2K2R b KQkq - 2 9,e8c8 e5c7,915,75,99,526,mate mateIn1 oneMove opening queensideAttack,https://lichess.org/rjh2BgbH/black#18
|
||||
nAfqs,r3k2r/2pq2p1/1b3p2/p6p/P1pP4/2P1PQP1/6P1/R3NRK1 b kq - 0 26,e8c8 f3a8,849,94,87,421,mate mateIn1 middlegame oneMove,https://lichess.org/65g7kSa0/black#52
|
||||
0sw4O,r3k2r/ppB2ppp/2n1pq2/6b1/4Q1P1/2PB4/PP5P/3K3R b kq - 4 19,e8g8 e4h7,1074,80,71,71,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/g1if65nb/black#38
|
||||
6kzIH,r1b1k2r/ppp2ppp/2n1pq2/8/3PQn2/P1PB1N2/1BP2PPP/R4RK1 b kq - 8 11,e8g8 e4h7,1154,93,68,44,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/NaDYEX6v/black#22
|
||||
42Bal,2r1k2r/ppq2ppp/1n2b3/4p3/2P5/P3P1P1/1KQN1PP1/3R1B1R b k - 0 19,e8g8 c2h7,835,83,85,44,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/A2xv3ghS/black#38
|
||||
AWZQ7,r2qk2r/2p1bppp/p3p3/n7/Pp1P4/3QPPP1/1PB2P2/RN3RK1 b kq - 0 15,e8g8 d3h7,602,96,60,48,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/m6RqcUwC/black#30
|
||||
P2snV,r3k2r/1p3ppp/2p1bq2/p2n2N1/PbB1Q2P/8/1P3PP1/R1B2K1R b kq - 0 16,e8g8 e4h7,1260,158,82,10,kingsideAttack master mate mateIn1 middlegame oneMove,https://lichess.org/6s9dBTUL/black#32
|
||||
DLdAn,r3k2r/p2q1p1p/b3pp2/1Nb5/P1Bp4/5Q2/1PP2PPP/R4RK1 b kq - 3 16,e8c8 f3a8,1287,76,94,3968,mate mateIn1 middlegame oneMove,https://lichess.org/l2DeuPYc/black#32
|
||||
p3Q6M,r3k2r/1b4R1/p1n1p2p/1p1q1p2/3P1Q1P/P2B1N2/1PP5/2K5 b kq - 3 21,e8c8 f4c7,1193,76,90,517,mate mateIn1 middlegame oneMove,https://lichess.org/heIdcTW0/black#42
|
||||
RsYIC,rnb1k2r/pp3ppp/4p3/q2p2N1/1b1P4/2N1P3/PPQ2PPP/2R1KB1R b Kkq - 0 10,e8g8 c2h7,847,103,89,164,kingsideAttack mate mateIn1 oneMove opening,https://lichess.org/YgEdGJ9v/black#20
|
||||
aozJ2,3bk2r/p3npp1/2q4p/4Q3/4P3/1P6/PBP2PPP/5RK1 b k - 2 19,e8g8 e5g7,846,91,70,103,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/LNWojJtd/black#38
|
||||
6PekE,r2qk2r/ppp2ppp/2n5/3p1Q2/3P3b/3B4/P2B2PP/R4K2 b kq - 0 16,e8g8 f5h7,754,82,97,188,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/Ncwq1lnM/black#32
|
||||
nfUZQ,r2qk2r/pp1n1ppp/8/3Pn3/4Qp2/2N2Bb1/PPP3P1/R1B2K1R b kq - 4 14,e8g8 e4h7,1232,92,69,45,kingsideAttack mate mateIn1 oneMove opening,https://lichess.org/04AZISKa/black#28
|
|
20
Tests/Puzzles/mateIn1_enPassant.csv
Normal file
20
Tests/Puzzles/mateIn1_enPassant.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
HDgzc,2r2r1q/5p1k/p3p2B/1p2P1Q1/4B3/P7/2p3P1/5R1K b - - 0 31,f7f5 e5f6,1990,81,65,148,discoveredAttack enPassant mate mateIn1 middlegame oneMove,https://lichess.org/ol00Tz8q/black#62
|
||||
1j5dV,7k/3rN2p/7P/p1pp3n/5pP1/5R2/PPP5/2K5 b - g3 0 38,f4g3 f3f8,890,86,67,64,endgame mate mateIn1 oneMove,https://lichess.org/zjlGPoxK/black#76
|
||||
QKy7Z,r2q1r2/1p1b1p1R/pnn1p1k1/3pP1P1/3P1P2/P2Q2P1/1P6/R1B2KN1 b - - 1 20,f7f5 e5f6,1840,77,94,5888,discoveredAttack enPassant mate mateIn1 middlegame oneMove,https://lichess.org/gowIv2fI/black#40
|
||||
4588b,r1b4k/1pq2p1p/2pp1N2/p1nPr3/2P1pP2/P3P2P/1PQ1B1P1/2KR3R b - f3 0 19,e4f3 c2h7,1051,76,82,237,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/6u9WW5eB/black#38
|
||||
1ymyA,4brk1/ppq5/4p1pQ/4PpRp/2P4r/3BR3/P1P3PP/7K w - f6 0 32,e5f6 c7h2,973,76,100,270,kingsideAttack master masterVsMaster mate mateIn1 middlegame oneMove,https://lichess.org/QVy1wixN#63
|
||||
SsgM3,r4rk1/1bq3pp/p3p3/1pb1PpN1/6n1/2NB4/PPP1Q1PP/R4R1K w - f6 0 18,e5f6 c7h2,986,75,100,597,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/j8MpBLcv#35
|
||||
YnOUq,1k1r1b1r/1pq1n3/1n2p3/1P1pPp2/3P2B1/2P5/R4PPB/1N1Q1RK1 w - f6 0 22,e5f6 c7h2,1169,205,83,8,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/uSZx2YbU#43
|
||||
94PYq,7k/pb4pp/5p2/8/2PqpP2/1P5P/P3Q1PK/8 b - f3 0 26,e4f3 e2e8,600,95,69,68,backRankMate endgame mate mateIn1 oneMove,https://lichess.org/B1rpV4bq/black#52
|
||||
iKfCM,2b1r1k1/p4p1p/B2N1b2/6pP/3P1K2/2P2P2/P5r1/RN5R w - g6 0 25,h5g6 f6g5,1434,76,85,278,mate mateIn1 middlegame oneMove,https://lichess.org/hLbfMhxx#49
|
||||
Fyn9D,2r2rk1/2b3p1/4p2p/pp2Pp2/2pPQ1Pq/P1P1P3/1P1B4/R4RK1 w - f6 0 21,e5f6 h4h2,1324,75,98,3566,mate mateIn1 middlegame oneMove,https://lichess.org/RqPUtwYt#41
|
||||
GAWcc,r4rk1/p1q3p1/bpp1p2p/4Pp2/1bpPB1nB/2N1P3/PP2Q1PP/R4RK1 w - f6 0 16,e5f6 c7h2,1098,80,92,453,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/T2TeS6dB#31
|
||||
GAhR6,r3r1k1/p7/2p4p/1p2Ppp1/6R1/1P6/PP3PPP/4R1K1 w - f6 0 22,e5f6 e8e1,1010,167,67,10,backRankMate endgame hangingPiece mate mateIn1 oneMove rookEndgame,https://lichess.org/QSZy78Od#43
|
||||
aA1q8,r7/8/p1R5/1p1P1kp1/1PnP1pP1/5K2/2P1N3/8 b - g3 0 40,f4g3 e2g3,970,78,98,633,endgame mate mateIn1 oneMove,https://lichess.org/dTA4VEdI/black#80
|
||||
4fBpP,1q6/pp6/4r1pk/2p1PpRn/3p2Q1/1P1P4/1PP3P1/4R2K w - f6 0 35,e5f6 e6e1,761,80,99,539,endgame hangingPiece mate mateIn1 oneMove,https://lichess.org/UA8Kpwh5#69
|
||||
CX1BO,8/p2n1p2/1p3b1p/3P1bk1/3pPN2/1P3KPP/PB4B1/8 b - e3 0 31,d4e3 h3h4,1177,74,97,3570,endgame mate mateIn1 oneMove,https://lichess.org/oyn66GTz/black#62
|
||||
ZQj16,r3kb1r/ppqn2p1/2p1p3/3pPpp1/N2P4/3QN3/PPP2PPP/R4RK1 w kq f6 0 14,e5f6 c7h2,1225,77,73,405,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/NaUlTKhY#27
|
||||
TfjSn,r2qk2r/ppp2pb1/3pn2p/6pn/2N1PpP1/2PB3P/PP3Q2/R1B2RK1 b kq g3 0 15,f4g3 f2f7,1033,86,88,455,attackingF2F7 mate mateIn1 oneMove opening,https://lichess.org/puM6gguU/black#30
|
||||
0Px2t,8/1b6/p4Q2/1p2Pppk/1P5p/3B3P/P1PN2PK/4q3 w - - 2 36,g2g4 h4g3,1851,78,71,230,enPassant endgame mate mateIn1 oneMove,https://lichess.org/qyXVm3Xe#71
|
||||
FSGUl,5r1k/8/3p3p/5RpP/5KP1/3Q1P2/8/4q3 w - g6 0 56,h5g6 e1e5,1541,93,84,217579,endgame mate mateIn1 oneMove pin,https://lichess.org/wFoaCTu7#111
|
||||
akDOj,r1b2rk1/p1q3pp/2p1p3/4Pp2/4Q1n1/2NB4/PPP3PP/R4RK1 w - f6 0 17,e5f6 c7h2,1244,77,41,211,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/rC5UCaJ0#33
|
|
20
Tests/Puzzles/mateIn1_simple.csv
Normal file
20
Tests/Puzzles/mateIn1_simple.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
jrJls,2bQ1k1r/1p3p2/p4b1p/4pNp1/2q5/8/PPP2PPP/1K1RR3 b - - 3 23,f6d8 d1d8,779,82,97,239,mate mateIn1 middlegame oneMove,https://lichess.org/gQa01loO/black#46
|
||||
rZoKr,5rk1/ppp5/2n4p/3b2p1/6R1/P1B1P2P/1Pq5/R4QK1 w - - 2 29,f1d1 c2f2,1467,79,81,132,mate mateIn1 middlegame oneMove,https://lichess.org/haKJxadR#57
|
||||
0urF2,2kr4/ppp2ppp/2n5/8/1P1PrP2/q4bP1/P3N2P/1R1Q1RBK w - - 1 18,f1f3 a3f3,922,80,96,245,hangingPiece kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/Ea0aHiBh#35
|
||||
2HNcF,r3rk2/5ppQ/b1ppnq1p/p7/P1P5/2N5/1PB2PP1/R3R1K1 b - - 5 23,e6g5 h7h8,1031,76,92,123,mate mateIn1 middlegame oneMove,https://lichess.org/TQl97w12/black#46
|
||||
qFJW4,r5k1/ppp1nrpp/1b2Q3/8/6n1/B1N5/P5PP/R4RqK w - - 4 21,f1g1 g4f2,935,77,98,647,mate mateIn1 middlegame oneMove smotheredMate,https://lichess.org/4IhbR7Z4#41
|
||||
07FBh,r7/1Rrk1p1P/1R2bQn1/3pP3/3P1p2/p1P3PK/5q2/3B4 w - - 0 46,b6e6 f2g3,1378,75,80,255,master masterVsMaster mate mateIn1 middlegame oneMove,https://lichess.org/N6cvrT1h#91
|
||||
BOh5N,r1b1k2r/ppp2pp1/2p5/2b1q1Bp/4P1n1/2PP4/PP2BPPP/RN1Q1RK1 w kq - 1 10,d3d4 e5h2,1110,79,87,307,kingsideAttack mate mateIn1 oneMove opening,https://lichess.org/hBADdo6I#19
|
||||
8K7dF,5Nr1/1p5k/3p1Q2/p2Pn3/4q3/1P6/P5RP/7K b - - 3 38,g8f8 f6g7,1374,78,85,113,endgame master mate mateIn1 oneMove,https://lichess.org/dVBFepDr/black#76
|
||||
pfcY2,1rq4r/pk3ppp/Q1p5/8/R7/5P2/PPP4P/2KR4 b - - 14 24,b7a8 a6a7,969,76,100,457,endgame mate mateIn1 oneMove queensideAttack,https://lichess.org/iq1SsukA/black#48
|
||||
1bg7G,r4rk1/2q4p/2p5/P1bp1pp1/4pNn1/2B1P2P/P1P2PP1/R2Q1RK1 w - - 0 18,f4d5 c7h2,1313,77,85,102,mate mateIn1 middlegame oneMove,https://lichess.org/6jomSlbK#35
|
||||
Rsm4I,3r1Q1k/6p1/7p/1p2q3/8/1P1B1R2/2P3PP/7K b - - 0 33,d8f8 f3f8,600,90,68,137,endgame mate mateIn1 oneMove,https://lichess.org/cBl2WADX/black#66
|
||||
lq6Om,5Q1k/1b2q1pp/p3B3/1pp1P3/8/2N5/PP4PP/5R1K b - - 0 28,e7f8 f1f8,629,104,84,148,backRankMate endgame mate mateIn1 oneMove,https://lichess.org/Hcocq7AO/black#56
|
||||
WOhqq,8/1b4k1/p1q2rp1/8/2PQ4/1P1p3P/1P3RP1/7K w - - 2 41,f2f6 c6g2,1000,79,81,84,endgame mate mateIn1 oneMove,https://lichess.org/s4VKQPNb#81
|
||||
1ZxkV,r1bqkb1r/p1p2ppp/2p5/3pN3/4n3/5Q2/PPPP1PPP/RNB1K2R b KQkq - 1 7,f8d6 f3f7,1102,79,100,66,attackingF2F7 mate mateIn1 oneMove opening,https://lichess.org/8mudJHJO/black#14
|
||||
ozS9D,6k1/ppp2pp1/1nn2q1p/3p4/5B2/1PPB1P2/P1P3PP/4Q1K1 b - - 0 21,f6f4 e1e8,653,89,60,146,endgame master mate mateIn1 oneMove,https://lichess.org/r5n4dpAN/black#42
|
||||
8xWfh,r4rk1/2p3pp/p2p4/1p1P1b2/5Q2/P1P1P2P/BP4q1/2KR3R w - - 0 19,h1g1 g2c2,1323,149,55,14,mate mateIn1 middlegame oneMove,https://lichess.org/nMPORUnV#37
|
||||
GLtpk,r6r/p1p1kpQ1/3p2p1/2p5/4PP2/5Pq1/PPP1R3/R1B3K1 w - - 2 22,e2g2 g3e1,1195,75,94,1852,mate mateIn1 middlegame oneMove,https://lichess.org/fGjY3KEU#43
|
||||
kOz0U,8/Q4kp1/2qB1p2/pp2p2p/n3P2P/5P2/2P3PK/8 b - - 5 36,f7e6 a7e7,805,75,100,281,endgame master mate mateIn1 oneMove,https://lichess.org/jS8203Ng/black#72
|
||||
BEUkI,r1bq1r1k/pp3pR1/1b1p3p/2p2P2/2BBP2n/2N5/PPPQ2PP/R5K1 b - - 0 15,c5d4 d2h6,1080,84,63,46,kingsideAttack mate mateIn1 middlegame oneMove,https://lichess.org/qUYVfwWj/black#30
|
||||
EAnMf,3R4/5r1k/6p1/4B3/3P1PK1/4r3/8/8 b - - 1 42,e3e1 d8h8,959,118,33,20,endgame master mate mateIn1 oneMove,https://lichess.org/7KoQ0ISo/black#84
|
|
20
Tests/Puzzles/mateIn2_castling.csv
Normal file
20
Tests/Puzzles/mateIn2_castling.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
C3LDZ,N2k1bnr/pp3ppp/8/5b2/1n1p1B2/8/PP2PPPP/R3KBNR w KQ - 3 10,e1c1 b4a2 c1d2 f8b4,946,76,98,512,mate mateIn2 opening queensideAttack short,https://lichess.org/TEvwwNPR#19
|
||||
9LFa6,N4b1r/p1p1kp2/4pp1p/1B6/4b3/8/PP3P1P/RNB1K2R w KQ - 0 17,e1g1 h8g8 c1g5 g8g5,1184,81,88,300,mate mateIn2 middlegame short,https://lichess.org/F32R0fa0#33
|
||||
1pIrM,r3k1r1/p1p1qp2/1p2b1p1/n6p/4Q3/2PB4/2P2PPP/3RR1K1 b q - 3 21,e8c8 e4a8 c8d7 d3b5,1862,77,86,105,doubleCheck mate mateIn2 middlegame queensideAttack short,https://lichess.org/pSb6NXeF/black#42
|
||||
zr2fX,r2qk2r/ppp2ppp/3p4/2b1p2n/2BnPP2/2NP3P/PPP3P1/R1BQK2R w KQkq - 3 10,e1g1 d4f3 g1h1 h5g3,1815,75,95,5210,doubleCheck kingsideAttack mate mateIn2 opening short,https://lichess.org/kjhbiYdz#19
|
||||
YpQqh,r1b1k2r/ppb2ppp/2p1p3/5Nq1/3PQ3/2P4P/PPB2PP1/3RR1K1 b kq - 6 20,e8g8 f5e7 g8h8 e4h7,1226,74,97,2181,discoveredAttack kingsideAttack mate mateIn2 middlegame short,https://lichess.org/oqBsdf2S/black#40
|
||||
DvrCz,r3k3/p1p3p1/1pP3p1/2b5/Q2NP3/4K3/PP3Pq1/R7 b q - 0 24,e8c8 a4a6 c8b8 a6b7,1169,77,93,1757,endgame mate mateIn2 queensideAttack short,https://lichess.org/Foi1kNan/black#48
|
||||
XtR9Q,r3k2r/pp1n1p2/2p1p2p/5bp1/3P4/4P1B1/P2KBPPP/2R1R3 b kq - 3 17,e8c8 c1c6 b7c6 e2a6,1189,117,70,776,bodenMate mate mateIn2 middlegame queensideAttack sacrifice short,https://lichess.org/ALOKPySz/black#34
|
||||
ELwWj,r3k3/pp1nqpb1/2p3p1/8/5BP1/5Q2/PPP1B3/2K4R b q - 2 21,e8c8 f3c6 b7c6 e2a6,1180,158,38,144,bodenMate master mate mateIn2 middlegame queensideAttack sacrifice short,https://lichess.org/MkCYHnmC/black#42
|
||||
c1OJQ,Qn2k2r/p4pp1/1q1bp1p1/8/3P4/3BP3/P2B1PPP/R3K2R w KQk - 2 18,e1c1 d6a3 c1c2 b6b2,1170,76,88,402,mate mateIn2 middlegame queensideAttack short,https://lichess.org/ze00fCw3#35
|
||||
0NplY,3r1q1r/pkp2p1p/2p2p2/7R/3bN3/1P2NQ2/P1PP2P1/R3K3 w Q - 1 19,e1c1 f8a3 c1b1 a3b2,1095,75,91,481,mate mateIn2 middlegame queensideAttack short,https://lichess.org/iqeYu7au#37
|
||||
zaghF,r3k1r1/pp1nbp1p/2p1pn2/8/P2q4/2NB1QBP/1PP2PP1/R3R1K1 b q - 1 17,e8c8 f3c6 b7c6 d3a6,1180,114,72,618,bodenMate mate mateIn2 middlegame queensideAttack sacrifice short,https://lichess.org/2QzZrb7U/black#34
|
||||
micYX,r3k2r/pp1n1pp1/2p2np1/q3p3/3P4/b1P1BQNP/PP3PP1/R3KB1R w KQkq - 1 13,e1c1 a5c3 c1b1 c3b2,1554,80,74,42,mate mateIn2 middlegame pin queensideAttack short,https://lichess.org/6fCoAKep#25
|
||||
LjS86,r1bqk2r/p3bppp/2p1n3/1p2pQ2/4N3/2PB4/P1PP1PPP/R1B2RK1 b kq - 4 12,e8g8 e4f6 g8h8 f5h7,1122,79,72,358,kingsideAttack mate mateIn2 opening short,https://lichess.org/O55hxxVw/black#24
|
||||
0uCn9,r5kr/pp2p1b1/1q1p4/2pPnbP1/4N3/5P2/PPPBQ2P/R3KBNR w KQ - 5 16,e1c1 e5d3 c1b1 b6b2,1522,76,92,1510,mate mateIn2 middlegame queensideAttack short,https://lichess.org/fpIzh0mk#31
|
||||
MLdUA,r3k2r/1p3p1p/p2p1B1b/4pP2/1PP5/q7/2K2PPP/3Q1B1R b kq - 3 21,e8g8 d1g4 h6g7 g4g7,1365,81,40,68,kingsideAttack mate mateIn2 middlegame short,https://lichess.org/eBsszmz6/black#42
|
||||
AhG2i,rn2k2r/2q2pp1/p3p1p1/2Np2N1/1P1P1n2/P7/2PQ1PPP/R3K2R w KQkq - 2 18,e1g1 f4e2 g1h1 c7h2,1345,76,85,185,discoveredAttack kingsideAttack mate mateIn2 middlegame short,https://lichess.org/eNauwPCr#35
|
||||
vnoDi,r3k3/p3q1p1/2p2n2/2P2p2/1P3Br1/5Q2/P4PPP/RN4K1 b q - 1 21,e8c8 f3c6 e7c7 c6c7,1222,79,92,1655,mate mateIn2 middlegame queensideAttack short,https://lichess.org/nq7O55Iv/black#42
|
||||
KCfTj,r1b1k2r/pppp1Npp/2n5/4p3/2B4q/5Q2/PPPP1b1P/RNB4K b kq - 0 10,e8g8 f7h6 g8h8 f3f8,1092,78,98,2011,deflection discoveredAttack doubleCheck kingsideAttack mate mateIn2 middlegame short,https://lichess.org/5aGYiZI2/black#20
|
||||
W6DEV,r3kb1r/2p2p2/p1P1p3/4P1p1/Pp1q4/8/1P1NQ1PP/R3K2R b KQkq - 1 20,e8c8 e2a6 c8b8 a6b7,1100,80,95,536,mate mateIn2 middlegame queensideAttack short,https://lichess.org/GEq3AYMj/black#40
|
||||
lo4i9,r3k2r/pp1nqppp/2p1pn2/5b2/1bB2B2/3P1QPP/PPP1NP2/2KR3R b kq - 4 13,e8c8 f3c6 b7c6 c4a6,1061,95,85,1074,bodenMate mate mateIn2 middlegame queensideAttack sacrifice short,https://lichess.org/LmkZcypC/black#26
|
|
20
Tests/Puzzles/mateIn2_enPassant.csv
Normal file
20
Tests/Puzzles/mateIn2_enPassant.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
4ealO,7R/1r6/4npp1/3p2k1/4p1PN/4P1K1/5P2/8 b - - 1 41,e6g7 f2f4 e4f3 h4f3,1570,74,93,541,endgame mate mateIn2 short,https://lichess.org/Ake1xrvX/black#82
|
||||
0jFaq,3r3R/p1r1kpp1/2pb4/6P1/3p1P2/1PnP4/PB4B1/K3R3 b - - 8 31,e7d7 g2h3 f7f5 g5f6,1473,75,95,8904,discoveredAttack enPassant mate mateIn2 middlegame short,https://lichess.org/PTxvhx4O/black#62
|
||||
IzNuV,rnbq1bkr/pppp2pp/8/4P3/8/1Q6/PPP2nPP/RNB1K1NR b KQ - 3 7,d7d5 e5d6 c8e6 b3e6,2101,76,90,597,discoveredAttack enPassant kingsideAttack mate mateIn2 opening short,https://lichess.org/jeruntSh/black#14
|
||||
pnp1m,6k1/2p2p2/7p/p4Pp1/PpP4K/1N1P3P/1PB1b1r1/R7 w - g6 0 36,f5g6 f7g6 d3d4 g6g5,1332,74,98,5203,endgame mate mateIn2 short,https://lichess.org/RqiEClbR#71
|
||||
C8vzb,7r/6R1/5P1p/3p3k/b1pP3p/4K3/r4PPP/7R b - - 1 31,h8b8 g2g4 h4g3 h2g3,1734,75,93,284,discoveredAttack endgame mate mateIn2 short,https://lichess.org/dc3gg8Ux/black#62
|
||||
m3UQp,r1bq1bkr/pppp2pp/2n5/4P3/6n1/1QN2N2/PP3PPP/R1B1K2R b KQ - 2 9,d7d5 e5d6 c8e6 b3e6,2256,77,99,139,discoveredAttack enPassant kingsideAttack mate mateIn2 opening short,https://lichess.org/VE0cPfvX/black#18
|
||||
jkW7a,r1bq1bkr/pppp2pp/2n5/4P1N1/2Q5/8/Pp3PPP/RNB1K2R b KQ - 1 10,d7d5 e5d6 c8e6 c4e6,2008,85,100,60,discoveredAttack enPassant kingsideAttack mate mateIn2 opening short,https://lichess.org/R6aKczaF/black#20
|
||||
VkFyK,3Q4/1k6/1n1b2p1/1B1p1pP1/P2Pp1K1/4q3/4N3/2R5 w - f6 0 46,g5f6 e3f3 g4g5 f3h5,2129,116,74,30,mate mateIn2 middlegame short,https://lichess.org/gcWm81m8#91
|
||||
7dd9h,6k1/1bQ2p2/p3p1p1/6Pp/2P3K1/1P2PnP1/P6r/R4R2 w - h6 0 30,g5h6 f7f5 g4f4 g6g5,1817,75,96,4785,endgame mate mateIn2 short,https://lichess.org/URt1dC1A#59
|
||||
GJfk2,5nk1/R4pp1/4p2p/2BpP2P/3P2P1/1r3K2/8/2R5 w - - 4 44,f3f4 g7g5 h5g6 f8g6,1637,74,86,400,endgame mate mateIn2 short,https://lichess.org/Rs0Ce6wy#87
|
||||
By846,6k1/5rpp/2pB2b1/2P5/4pP2/2P4P/3r2P1/2R1R1K1 b - f3 0 28,e4f3 e1e8 f7f8 e8f8,687,107,78,374,endgame mate mateIn2 short,https://lichess.org/oVDK3Vtd/black#56
|
||||
aCX4c,7k/1R2B1b1/p5pp/5p2/4p1P1/P2bP2P/5PB1/2r3K1 w - - 1 31,g1h2 g7e5 f2f4 e4f3,1611,74,95,6485,discoveredAttack enPassant endgame mate mateIn2 short,https://lichess.org/dVTQ6KQK#61
|
||||
Ksv3B,6k1/7p/R5p1/5p2/4p3/1RP1B1PP/2P2PK1/3r1r2 w - - 0 35,f2f4 e4f3 g2h2 f1h1,1923,76,92,558,enPassant endgame mate mateIn2 short,https://lichess.org/wi3kg3x1#69
|
||||
3DJKJ,8/4ppk1/3p2p1/1pnP3p/4PK1P/r4PP1/PR6/5B2 w - - 9 43,f1b5 e7e5 d5e6 c5e6,1962,74,96,4418,endgame mate mateIn2 short,https://lichess.org/RR03JsOK#85
|
||||
n4CSG,1r2r1k1/p3qpbp/1pp3p1/3pP3/5BPn/2N3Q1/PPP2P1P/3RR1K1 w - d6 0 21,e5d6 e7e1 d1e1 e8e1,742,95,47,49,kingsideAttack mate mateIn2 middlegame short,https://lichess.org/B4cIJuuo#41
|
||||
12YGT,r2qkbnr/1p2p3/2p2p2/2Pp1bp1/p2P3p/PN2BN1P/RP1KPPP1/3Q1B1R w kq - 0 15,b3c1 d8a5 b2b4 a4b3,1583,76,88,1028,discoveredAttack enPassant mate mateIn2 middlegame short,https://lichess.org/ggbXxMB9#29
|
||||
BOPKK,1r5r/p1p2pk1/4p2p/1BNnP3/6R1/P7/1P4PP/7K b - - 2 23,g7h7 b5d3 f7f5 e5f6,1723,94,85,37,discoveredAttack enPassant endgame mate mateIn2 short,https://lichess.org/9He2SHa7/black#46
|
||||
wU03v,5nk1/p4pp1/4p2p/4Pq1P/1p3P2/6PK/P1R3Q1/8 w - - 4 32,h3h4 g7g5 h5g6 f8g6,1602,75,95,386,endgame mate mateIn2 short,https://lichess.org/adh9umHR#63
|
||||
gLCiH,3r2k1/p2r1ppp/2p5/1pP5/8/1R2P3/P4PPP/1R4K1 w - b6 0 24,c5b6 d7d1 b1d1 d8d1,829,114,90,117,backRankMate endgame mate mateIn2 rookEndgame short,https://lichess.org/EPK4b73V#47
|
||||
5UCLk,8/8/p3RB2/kp3p1p/1Pp2K1P/2P5/6r1/8 b - b3 0 59,c4b3 f6d8 a5a4 e6a6,1610,75,85,304,deflection endgame mate mateIn2 short,https://lichess.org/Z6jDY4v4/black#118
|
|
20
Tests/Puzzles/mateIn2_simple.csv
Normal file
20
Tests/Puzzles/mateIn2_simple.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
uj7Uv,6k1/r4p2/6p1/4B3/p4P2/5r1p/K1R5/8 b - - 5 43,a7b7 c2c8 g8h7 c8h8,840,100,67,43,endgame mate mateIn2 short,https://lichess.org/Z3FRw1Fn/black#86
|
||||
GWKLu,3r2qr/pp2Q3/2p2P1p/7k/6R1/1P6/1PP3PP/R1B4K w - - 5 23,g4g8 d8d1 e7e1 d1e1,1038,85,96,471,backRankMate endgame mate mateIn2 short,https://lichess.org/1kIpfnCX#45
|
||||
ay0e7,1rr3k1/Q4pp1/4p2p/8/1PqPp3/P3P3/5PPP/R4RK1 w - - 3 27,f1c1 c4c1 a1c1 c8c1,630,95,69,168,backRankMate endgame mate mateIn2 short,https://lichess.org/x83fY9GE#53
|
||||
f2HsU,8/2q1kp2/4p3/p7/4Q3/7P/P1Pr1PP1/1R4K1 w - - 0 32,b1b7 d2d1 e4e1 d1e1,941,78,76,128,endgame mate mateIn2 short,https://lichess.org/NxooUJUL#63
|
||||
gEBEu,r3r2k/ppp3pp/3b4/5p2/1PQ1n2q/PnN1PB2/1B3PPP/R3K2R w KQ - 4 18,c4b3 h4f2 e1d1 f2d2,1124,80,94,862,attackingF2F7 mate mateIn2 middlegame short,https://lichess.org/prTiL04V#35
|
||||
Lk2iz,r5k1/p1p2p1Q/2pq1Bp1/3p1b2/5R2/1P1Pr3/P1P3PP/R5K1 b - - 0 21,g8h7 f4h4 h7g8 h4h8,741,91,88,90,master mate mateIn2 middlegame short,https://lichess.org/i2Wi7l4e/black#42
|
||||
t4DNf,2r3k1/5ppp/8/8/4pP2/4P2P/2Q3P1/1R3K2 b - - 0 43,c8c2 b1b8 c2c8 b8c8,655,107,82,33,backRankMate endgame mate mateIn2 rookEndgame short,https://lichess.org/r5W2eFui/black#86
|
||||
xrekH,8/p1P5/8/PP6/8/5rkp/8/6K1 w - - 2 59,c7c8q h3h2 g1h1 f3f1,990,93,96,295,advancedPawn endgame mate mateIn2 queenRookEndgame short,https://lichess.org/dQGDwh3G#117
|
||||
fBhqm,8/3R2kp/p5p1/8/5KP1/2p5/P1P4r/8 b - - 2 39,g7h6 g4g5 h6h5 d7h7,988,75,100,750,deflection endgame mate mateIn2 rookEndgame short,https://lichess.org/rfc0Wh0t/black#78
|
||||
tn9U3,8/4Q3/p2pN3/2pP4/2p1k3/8/1PPK1q2/8 w - - 1 36,d2c3 f2e1 c3c4 e1b4,1917,75,96,2210,endgame mate mateIn2 short,https://lichess.org/js3slxFD#71
|
||||
doGMx,5rk1/pp1R1pp1/4p3/8/6QP/3bB1N1/Pqr2PP1/3K3R w - - 0 22,d7d3 b2b1 e3c1 b1c1,1243,76,90,222,mate mateIn2 middlegame short,https://lichess.org/jYHnS2gv#43
|
||||
TVLHT,r1bqr1k1/p1pnbp2/1p2pn1Q/6N1/3P4/2NB4/PPP3PP/R4RK1 b - - 2 13,e7f8 d3h7 f6h7 h6h7,1274,76,94,1181,kingsideAttack mate mateIn2 middlegame short,https://lichess.org/3luk8Lyr/black#26
|
||||
EfOGc,8/5pk1/7q/3Pr3/8/3Q3P/4KRP1/8 w - - 1 45,e2f1 h6c1 d3d1 c1d1,992,76,95,561,endgame mate mateIn2 short,https://lichess.org/oWIOQKUU#89
|
||||
I7xRS,8/pp1r4/2p5/5b1p/5N2/5kP1/P4P1P/Q5K1 w - - 1 34,a1e5 d7d1 e5e1 d1e1,600,84,83,169,endgame mate mateIn2 short,https://lichess.org/D6tGkCvf#67
|
||||
CnqaD,r1bqk2r/1p6/p1n1pp1p/3p2p1/1QpPNB1P/P3P3/1PP1BPP1/R3K2R b KQkq - 0 14,g5f4 e2h5 e8d7 b4d6,1799,75,95,4247,mate mateIn2 middlegame short,https://lichess.org/gVyTK6U8/black#28
|
||||
yuqa6,4k2r/p2nppb1/6pp/1p1NP3/1P5N/K5P1/P1b2rBP/3R3R b k - 3 25,d7e5 d5c7 e8f8 d1d8,1098,78,77,313,backRankMate mate mateIn2 middlegame short,https://lichess.org/HCiRNU2E/black#50
|
||||
koa5w,r5k1/pbp1qpp1/1p2p1n1/3r2NQ/3P3P/5P2/PP4P1/R2R2K1 b - - 0 20,g6f4 h5h7 g8f8 h7h8,944,76,100,195,kingsideAttack mate mateIn2 middlegame short,https://lichess.org/TXpDhxAx/black#40
|
||||
6HZF3,2rq3r/5k2/p3p3/1pn2Np1/4P3/P1Q2P2/6b1/1KBR2N1 b - - 0 29,d8d1 c3g7 f7e8 g7e7,1524,74,98,7690,mate mateIn2 middlegame short,https://lichess.org/f4k6roov/black#58
|
||||
85ZUB,6k1/2p2pp1/p6p/3pPp2/P7/r3P3/5PPP/3R2K1 w - - 2 27,d1d5 a3a1 d5d1 a1d1,681,104,76,365,backRankMate endgame mate mateIn2 rookEndgame short,https://lichess.org/pBZZo8XP#53
|
||||
YHYZd,8/pp1R4/6pk/8/6PP/5K2/PP1prP2/4r3 b - - 0 36,d2d1q g4g5 h6h5 d7h7,1239,77,90,369,endgame mate mateIn2 queenRookEndgame short,https://lichess.org/E8UBGJ5F/black#72
|
|
80
Tests/SquareTests.cpp
Normal file
80
Tests/SquareTests.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "TestUtils.hpp"
|
||||
|
||||
#include "Square.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
TEST_CASE("Squares can be created from valid coordinates", "[Square][Fundamental]") {
|
||||
auto [file, rank] = GENERATE(table<Square::Coordinate, Square::Coordinate>({
|
||||
{4, 2}, {0, 0}, {7, 7}, {1, 7}
|
||||
}));
|
||||
|
||||
auto optSquare = Square::fromCoordinates(file, rank);
|
||||
REQUIRE(optSquare.has_value());
|
||||
|
||||
auto square = optSquare.value();
|
||||
REQUIRE(square.file() == file);
|
||||
REQUIRE(square.rank() == rank);
|
||||
REQUIRE(square.index() == rank * 8 + file);
|
||||
}
|
||||
|
||||
TEST_CASE("Squares are not created from invalid coordinates", "[Square][Fundamental]") {
|
||||
auto [file, rank] = GENERATE(table<Square::Coordinate, Square::Coordinate>({
|
||||
{4, 8}, {8, 3}, {12, 45}
|
||||
}));
|
||||
|
||||
auto optSquare = Square::fromCoordinates(file, rank);
|
||||
REQUIRE_FALSE(optSquare.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("Squares can be created from valid indices", "[Square][Fundamental]") {
|
||||
auto index = GENERATE(as<Square::Index>{}, 0, 63, 12, 41);
|
||||
|
||||
auto optSquare = Square::fromIndex(index);
|
||||
REQUIRE(optSquare.has_value());
|
||||
|
||||
auto square = optSquare.value();
|
||||
REQUIRE(square.file() == index % 8);
|
||||
REQUIRE(square.rank() == index / 8);
|
||||
REQUIRE(square.index() == index);
|
||||
}
|
||||
|
||||
TEST_CASE("Squares are not created from invalid indices", "[Square][Fundamental]") {
|
||||
auto index = GENERATE(as<Square::Index>{}, 64, 1024);
|
||||
|
||||
auto optSquare = Square::fromIndex(index);
|
||||
REQUIRE_FALSE(optSquare.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("Squares can be created from valid names", "[Square][Fundamental]") {
|
||||
auto [square, name] = GENERATE(table<Square, const char*>({
|
||||
{Square::A1, "a1"}, {Square::H8, "h8"}, {Square::D5, "d5"}
|
||||
}));
|
||||
|
||||
auto optCreatedSquare = Square::fromName(name);
|
||||
CAPTURE(square, name, optCreatedSquare);
|
||||
REQUIRE(optCreatedSquare.has_value());
|
||||
|
||||
auto createdSquare = optCreatedSquare.value();
|
||||
REQUIRE(createdSquare == square);
|
||||
}
|
||||
|
||||
TEST_CASE("Squares are not created from invalid names", "[Square][Fundamental]") {
|
||||
auto name = GENERATE("", "a", "a12", "1a", "xyz");
|
||||
|
||||
auto optSquare = Square::fromName(name);
|
||||
CAPTURE(name, optSquare);
|
||||
REQUIRE_FALSE(optSquare.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("Squares stream their name correctly", "[Square][Fundamental]") {
|
||||
auto [square, name] = GENERATE(table<Square, const char*>({
|
||||
{Square::A1, "a1"}, {Square::H8, "h8"}, {Square::E4, "e4"}
|
||||
}));
|
||||
|
||||
auto stream = std::stringstream();
|
||||
stream << square;
|
||||
REQUIRE(stream.str() == name);
|
||||
}
|
26
Tests/TestUtils.hpp
Normal file
26
Tests/TestUtils.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef CHESS_ENGINE_TESTS_TESTUTILS_HPP
|
||||
#define CHESS_ENGINE_TESTS_TESTUTILS_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
|
||||
template<typename T>
|
||||
inline std::ostream& operator<<(std::ostream& os, const std::optional<T>& opt) {
|
||||
if (opt.has_value()) {
|
||||
return os << opt.value();
|
||||
} else {
|
||||
return os << "nullopt";
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline std::ostream& operator<<(std::ostream& os, const std::unique_ptr<T>& ptr) {
|
||||
if (ptr == nullptr) {
|
||||
return os << "nullptr";
|
||||
} else {
|
||||
return os << ptr.get();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
151
Tests/puzzledb.py
Executable file
151
Tests/puzzledb.py
Executable file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import csv
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
import chess
|
||||
|
||||
|
||||
class Puzzle:
|
||||
def __init__(self, puzzle_id, fen, moves, rating, rd, popularity,
|
||||
num_plays, tags, game_url):
|
||||
self.puzzle_id = puzzle_id
|
||||
self.fen = fen
|
||||
self.moves = moves
|
||||
self.rating = rating
|
||||
self.rd = rd
|
||||
self.popularity = popularity
|
||||
self.num_plays = num_plays
|
||||
self.tags = tags
|
||||
self.game_url = game_url
|
||||
|
||||
@staticmethod
|
||||
def from_csv(row):
|
||||
puzzle_id, fen, moves, rating, rd, popularity, \
|
||||
num_plays, tags, game_url = row[:9]
|
||||
moves = [chess.Move.from_uci(m) for m in moves.split()]
|
||||
return Puzzle(puzzle_id, fen, moves, int(rating), int(rd),
|
||||
int(popularity), int(num_plays), tags.split(), game_url)
|
||||
|
||||
def to_csv(self):
|
||||
moves = ' '.join(m.uci() for m in self.moves)
|
||||
tags = ' '.join(self.tags)
|
||||
|
||||
return (
|
||||
self.puzzle_id, self.fen, moves, self.rating, self.rd,
|
||||
self.popularity, self.num_plays, tags, self.game_url
|
||||
)
|
||||
|
||||
def has_tags(self, tags):
|
||||
return all(tag in self.tags for tag in tags)
|
||||
|
||||
def has_not_tags(self, tags):
|
||||
return not any(tag in self.tags for tag in tags)
|
||||
|
||||
def _has_move(self, predicate):
|
||||
board = chess.Board(self.fen)
|
||||
|
||||
for move in self.moves:
|
||||
if predicate(board, move):
|
||||
return True
|
||||
|
||||
board.push(move)
|
||||
|
||||
return False
|
||||
|
||||
def has_castling(self):
|
||||
return self._has_move(chess.Board.is_castling)
|
||||
|
||||
def has_en_passant(self):
|
||||
return self._has_move(chess.Board.is_en_passant)
|
||||
|
||||
def num_plies(self):
|
||||
return len(self.moves) - 1
|
||||
|
||||
|
||||
class PuzzleDb:
|
||||
def __init__(self, puzzles):
|
||||
self._puzzles = puzzles
|
||||
|
||||
@staticmethod
|
||||
def from_csv(file):
|
||||
def generate_puzzles():
|
||||
csv_reader = csv.reader(file)
|
||||
|
||||
for row in csv_reader:
|
||||
puzzle = Puzzle.from_csv(row)
|
||||
yield puzzle
|
||||
|
||||
return PuzzleDb(generate_puzzles())
|
||||
|
||||
def to_csv(self, file):
|
||||
csv_writer = csv.writer(file)
|
||||
|
||||
for puzzle in self._puzzles:
|
||||
csv_writer.writerow(puzzle.to_csv())
|
||||
|
||||
def filter(self, predicate):
|
||||
return PuzzleDb(filter(predicate, self))
|
||||
|
||||
def collect(self):
|
||||
return list(self)
|
||||
|
||||
def sorted(self, key=None):
|
||||
return PuzzleDb(sorted(self._puzzles, key=key))
|
||||
|
||||
def __iter__(self):
|
||||
yield from self._puzzles
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('puzzle_db', type=Path)
|
||||
parser.add_argument('--tag', action='append', dest='tags')
|
||||
parser.add_argument('--not-tag', action='append', dest='not_tags')
|
||||
parser.add_argument('--castling', choices=['yes', 'no', 'only'],
|
||||
default='yes')
|
||||
parser.add_argument('--en-passant', choices=['yes', 'no', 'only'],
|
||||
default='yes')
|
||||
parser.add_argument('--min-plies', type=int, default=0)
|
||||
parser.add_argument('--max-plies', type=int, default=1000)
|
||||
parser.add_argument('--min-rating', type=int, default=0)
|
||||
parser.add_argument('--max-rating', type=int, default=4000)
|
||||
parser.add_argument('--min-num-plays', type=int, default=0)
|
||||
args = parser.parse_args()
|
||||
|
||||
with args.puzzle_db.open(newline='') as f:
|
||||
puzzles = PuzzleDb.from_csv(f)
|
||||
|
||||
if args.tags is not None:
|
||||
puzzles = puzzles.filter(lambda p: p.has_tags(args.tags))
|
||||
|
||||
if args.not_tags is not None:
|
||||
puzzles = puzzles.filter(lambda p: p.has_not_tags(args.not_tags))
|
||||
|
||||
if args.castling == 'no':
|
||||
puzzles = puzzles.filter(lambda p: not p.has_castling())
|
||||
elif args.castling == 'only':
|
||||
puzzles = puzzles.filter(lambda p: p.has_castling())
|
||||
|
||||
if args.en_passant == 'no':
|
||||
puzzles = puzzles.filter(lambda p: not p.has_en_passant())
|
||||
elif args.en_passant == 'only':
|
||||
puzzles = puzzles.filter(lambda p: p.has_en_passant())
|
||||
|
||||
puzzles = puzzles.filter(
|
||||
lambda p: args.min_plies <= p.num_plies() <= args.max_plies
|
||||
)
|
||||
|
||||
puzzles = puzzles.filter(
|
||||
lambda p: args.min_rating <= p.rating <= args.max_rating
|
||||
)
|
||||
|
||||
puzzles = puzzles.filter(lambda p: args.min_num_plays <= p.num_plays)
|
||||
|
||||
puzzles.to_csv(sys.stdout)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
89
Tests/puzzlerating.py
Executable file
89
Tests/puzzlerating.py
Executable file
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from puzzledb import PuzzleDb
|
||||
from puzzlerunner import run_puzzle
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from random import Random
|
||||
|
||||
import glicko2
|
||||
|
||||
|
||||
def select_puzzles(player, puzzle_db, num_puzzles, played_puzzles, random):
|
||||
rd = player.rd
|
||||
candidates = []
|
||||
|
||||
while len(candidates) < num_puzzles:
|
||||
candidates = puzzle_db \
|
||||
.filter(lambda p: p.rating >= player.rating - 2 * rd) \
|
||||
.filter(lambda p: p.rating <= player.rating + 2 * rd) \
|
||||
.filter(lambda p: p not in played_puzzles) \
|
||||
.collect()
|
||||
|
||||
rd += 50
|
||||
|
||||
return sorted(random.sample(candidates, num_puzzles),
|
||||
key=lambda p: p.rating)
|
||||
|
||||
|
||||
def play_round(player, puzzle_db, num_puzzles, engine,
|
||||
timeout, played_puzzles, random):
|
||||
puzzles = select_puzzles(player, puzzle_db, num_puzzles,
|
||||
played_puzzles, random)
|
||||
played_puzzles.update(puzzles)
|
||||
outcomes = []
|
||||
|
||||
for puzzle in puzzles:
|
||||
print(f'Running puzzle {puzzle.puzzle_id} '
|
||||
f'with rating {puzzle.rating}... ',
|
||||
end='', flush=True)
|
||||
|
||||
result = run_puzzle(puzzle, engine, timeout)
|
||||
success = result.is_success()
|
||||
outcomes.append(success)
|
||||
|
||||
if success:
|
||||
print('OK')
|
||||
else:
|
||||
print('FAIL')
|
||||
|
||||
ratings = [p.rating for p in puzzles]
|
||||
rds = [p.rd for p in puzzles]
|
||||
player.update_player(ratings, rds, outcomes)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--engine', type=Path, required=True)
|
||||
parser.add_argument('--timeout', type=float, default=60)
|
||||
parser.add_argument('--min-plays', type=int, default=500)
|
||||
parser.add_argument('--min-popularity', type=int, default=50)
|
||||
parser.add_argument('--rounds', type=int, default=20)
|
||||
parser.add_argument('--puzzles-per-round', type=int, default=5)
|
||||
parser.add_argument('--random-seed', type=int, default=0)
|
||||
parser.add_argument('--puzzle-db', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
with args.puzzle_db.open(newline='') as f:
|
||||
puzzle_db = PuzzleDb.from_csv(f) \
|
||||
.filter(lambda p: p.num_plays >= args.min_plays) \
|
||||
.filter(lambda p: p.popularity >= args.min_popularity) \
|
||||
.sorted(key=lambda p: p.rating)
|
||||
|
||||
player = glicko2.Player()
|
||||
played_puzzles = set()
|
||||
random = Random(args.random_seed)
|
||||
|
||||
for current_round in range(1, args.rounds + 1):
|
||||
print(f'=== Round {current_round}/{args.rounds}, '
|
||||
f'current rating: {round(player.rating)} '
|
||||
f'(rd: {round(player.rd)}) ===')
|
||||
play_round(player, puzzle_db, args.puzzles_per_round,
|
||||
args.engine, args.timeout, played_puzzles, random)
|
||||
|
||||
print(f'Final rating: {round(player.rating)} (rd: {round(player.rd)})')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
260
Tests/puzzlerunner.py
Executable file
260
Tests/puzzlerunner.py
Executable file
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from puzzledb import Puzzle, PuzzleDb
|
||||
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import defaultdict
|
||||
import sys
|
||||
import time
|
||||
import asyncio
|
||||
import contextlib
|
||||
|
||||
import chess
|
||||
import chess.engine
|
||||
|
||||
import junit_xml
|
||||
from junit_xml import TestSuite, TestCase
|
||||
|
||||
|
||||
class PuzzleRunResult(ABC):
|
||||
def __init__(self, puzzle):
|
||||
self.puzzle = puzzle
|
||||
self.puzzle_type = None
|
||||
self.duration_sec = None
|
||||
|
||||
@abstractmethod
|
||||
def is_success(self):
|
||||
pass
|
||||
|
||||
def to_junit_test_case(self):
|
||||
return TestCase(
|
||||
name=f'Puzzle {self.puzzle.puzzle_id}',
|
||||
status='run',
|
||||
classname=self.puzzle_type,
|
||||
elapsed_sec=self.duration_sec
|
||||
)
|
||||
|
||||
|
||||
class PuzzleRunSuccess(PuzzleRunResult):
|
||||
def __init__(self, puzzle):
|
||||
super().__init__(puzzle)
|
||||
|
||||
def is_success(self):
|
||||
return True
|
||||
|
||||
|
||||
class PuzzleRunFailure(PuzzleRunResult):
|
||||
def __init__(self, puzzle, reason, info):
|
||||
super().__init__(puzzle)
|
||||
self.reason = reason
|
||||
url = f'https://lichess.org/training/{self.puzzle.puzzle_id}'
|
||||
self.info = f'URL: {url}\n{info}'
|
||||
|
||||
def is_success(self):
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
return f'Failure reason: {self.reason}\n{self.info}'
|
||||
|
||||
def to_junit_test_case(self):
|
||||
test_case = super().to_junit_test_case()
|
||||
test_case.add_failure_info(
|
||||
message=self.reason,
|
||||
output=self.info
|
||||
)
|
||||
|
||||
return test_case
|
||||
|
||||
|
||||
class PuzzleRunWrongMove(PuzzleRunFailure):
|
||||
def __init__(self, puzzle, position, move, expected_move):
|
||||
reason = 'unexpected move'
|
||||
info = f'position={position}\n' \
|
||||
f'move={move.uci()}\n' \
|
||||
f'expected move={expected_move.uci()}'
|
||||
|
||||
super().__init__(puzzle, reason, info)
|
||||
|
||||
|
||||
class PuzzleRunTimeout(PuzzleRunFailure):
|
||||
def __init__(self, puzzle, timeout):
|
||||
reason = 'timeout'
|
||||
info = f'Puzzle timed out after {timeout} seconds'
|
||||
super().__init__(puzzle, reason, info)
|
||||
|
||||
|
||||
class PuzzleRunException(PuzzleRunFailure):
|
||||
def __init__(self, puzzle, exception):
|
||||
reason = 'exception'
|
||||
info = exception
|
||||
|
||||
super().__init__(puzzle, reason, info)
|
||||
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
async def create_engine(engine_path):
|
||||
transport, engine = \
|
||||
await chess.engine.popen_uci(engine_path)
|
||||
|
||||
try:
|
||||
yield engine
|
||||
finally:
|
||||
try:
|
||||
await asyncio.wait_for(engine.quit(), timeout=1)
|
||||
except asyncio.TimeoutError:
|
||||
try:
|
||||
transport.kill()
|
||||
transport.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
async def _run_puzzle(puzzle, engine_path, total_time):
|
||||
start_time = time.time()
|
||||
|
||||
moves = puzzle.moves[:]
|
||||
assert len(moves) >= 2
|
||||
assert len(moves) % 2 == 0
|
||||
|
||||
board = chess.Board(puzzle.fen)
|
||||
|
||||
time_limit = chess.engine.Limit()
|
||||
use_limit = total_time is not None
|
||||
|
||||
if use_limit:
|
||||
time_limit.white_clock = total_time
|
||||
time_limit.black_clock = total_time
|
||||
time_limit.white_inc = 0
|
||||
time_limit.black_inc = 0
|
||||
time_limit.remaining_moves = next_multiple(puzzle.num_plies() // 2, 5)
|
||||
time_left = total_time
|
||||
else:
|
||||
time_left = None
|
||||
|
||||
async with create_engine(engine_path) as engine:
|
||||
while len(moves) > 0:
|
||||
board.push(moves.pop(0))
|
||||
|
||||
try:
|
||||
result = await asyncio.wait_for(engine.play(board, time_limit),
|
||||
timeout=time_left)
|
||||
except asyncio.TimeoutError:
|
||||
return PuzzleRunTimeout(puzzle, total_time)
|
||||
|
||||
board.push(result.move)
|
||||
expected_move = moves.pop(0)
|
||||
|
||||
if result.move != expected_move:
|
||||
if len(moves) == 0 and board.is_checkmate():
|
||||
break
|
||||
else:
|
||||
board.pop()
|
||||
return PuzzleRunWrongMove(puzzle, board.fen(),
|
||||
result.move, expected_move)
|
||||
|
||||
if use_limit:
|
||||
time_limit.remaining_moves -= 1
|
||||
current_time = time.time()
|
||||
elapsed_time = current_time - start_time
|
||||
time_left = total_time - elapsed_time
|
||||
|
||||
if time_left < 0:
|
||||
return PuzzleRunTimeout(puzzle, total_time)
|
||||
|
||||
# The last move we made on the board was for the engine. So the
|
||||
# current turn is for the engine's opponent. We only update the
|
||||
# engine's clock to reflect the time limit.
|
||||
if board.turn == chess.WHITE:
|
||||
time_limit.black_clock = time_left
|
||||
else:
|
||||
time_limit.white_clock = time_left
|
||||
|
||||
return PuzzleRunSuccess(puzzle)
|
||||
|
||||
|
||||
def run_puzzle(puzzle, engine_path, timeout):
|
||||
async def run():
|
||||
try:
|
||||
return await _run_puzzle(puzzle, engine_path, timeout)
|
||||
except Exception as e:
|
||||
return PuzzleRunException(puzzle, e)
|
||||
|
||||
start_time = time.time()
|
||||
result = asyncio.run(run())
|
||||
end_time = time.time()
|
||||
result.duration_sec = end_time - start_time
|
||||
return result
|
||||
|
||||
|
||||
def format_duration(duration):
|
||||
return f'{duration:.3f}s'
|
||||
|
||||
|
||||
def next_multiple(n, multiple):
|
||||
return n + (multiple - n % multiple)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--engine', type=Path, required=True)
|
||||
parser.add_argument('--timeout', type=float,
|
||||
help='Timeout in seconds per puzzle')
|
||||
parser.add_argument('--junit', type=Path)
|
||||
parser.add_argument('puzzle_dbs', type=Path, nargs='+')
|
||||
args = parser.parse_args()
|
||||
|
||||
results = defaultdict(list)
|
||||
num_fails = 0
|
||||
total_duration = 0
|
||||
|
||||
for puzzle_db_path in args.puzzle_dbs:
|
||||
print(f'=== Running puzzles from {puzzle_db_path.resolve()} ===')
|
||||
|
||||
with puzzle_db_path.open(newline='') as f:
|
||||
puzzles = PuzzleDb.from_csv(f)
|
||||
|
||||
for puzzle in puzzles:
|
||||
print(f'Running puzzle {puzzle.puzzle_id} ... ',
|
||||
end='', flush=True)
|
||||
result = run_puzzle(puzzle, args.engine, args.timeout)
|
||||
total_duration += result.duration_sec
|
||||
duration_msg = f'({format_duration(result.duration_sec)})'
|
||||
|
||||
if result.is_success():
|
||||
print(f'OK {duration_msg}')
|
||||
else:
|
||||
num_fails += 1
|
||||
print(f'FAIL {duration_msg}')
|
||||
print(f'===\n{result}\n===')
|
||||
|
||||
results[puzzle_db_path].append(result)
|
||||
|
||||
if args.junit is not None:
|
||||
test_suites = []
|
||||
|
||||
for db_path, db_results in results.items():
|
||||
name = f'puzzles.{db_path.stem}'
|
||||
|
||||
def create_test_case(result):
|
||||
result.puzzle_type = name
|
||||
return result.to_junit_test_case()
|
||||
|
||||
test_cases = [create_test_case(r) for r in db_results]
|
||||
test_suite = TestSuite(name, test_cases)
|
||||
test_suites.append(test_suite)
|
||||
|
||||
xml = junit_xml.to_xml_report_string(test_suites)
|
||||
args.junit.write_text(xml)
|
||||
|
||||
print(f'Total time: {format_duration(total_duration)}')
|
||||
|
||||
if num_fails > 0:
|
||||
sys.exit(f'{num_fails} tests failed')
|
||||
else:
|
||||
print('All tests passed')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Reference in New Issue
Block a user