Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions .github/workflows/clang-format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,4 @@ jobs:
clang-format -i $(git ls-files '*.cpp' '*.hpp' '*.h' '*.c')

- name: Commit and push changes
run: |
if ! git diff --quiet; then
git config user.email "actions@github.com"
git config user.name "GitHub Actions"
git commit -am "Apply clang-format"
git push
fi
uses: stefanzweifel/git-auto-commit-action@v7
5 changes: 0 additions & 5 deletions .github/workflows/try_compile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,10 @@ jobs:
- name: Configure CMake
shell: bash
run: |
CXX_FLAGS=""
LINK_FLAGS=""

cmake -B "${{ steps.vars.outputs.dir }}" \
-DCMAKE_C_COMPILER=${{ matrix.c_compiler }} \
-DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} \
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
-DCMAKE_CXX_FLAGS_DEBUG="$CXX_FLAGS" \
-DCMAKE_EXE_LINKER_FLAGS="$LINK_FLAGS" \
-S "${{ github.workspace }}"

- name: Build
Expand Down
23 changes: 20 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
cmake_minimum_required(VERSION 3.16)
# a chess library (bonus: you can integrate more piece types!) which
# supports Chess960 and is decently fast enough
# Copyright (C) 2025-2026 winapiadmin
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
cmake_minimum_required(VERSION 3.14)
project(chesslib LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
Expand Down Expand Up @@ -51,7 +68,7 @@ if(BUILD_TESTING)
FetchContent_Declare(
doctest
GIT_REPOSITORY https://github.com/doctest/doctest.git
GIT_TAG v2.4.12
GIT_TAG v2.5.2
)
FetchContent_MakeAvailable(doctest)

Expand All @@ -76,4 +93,4 @@ if(BUILD_TESTING)
endif()
endif()

endif()
endif()
18 changes: 18 additions & 0 deletions attacks.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/*
a chess library (bonus: you can integrate more piece types!) which
supports Chess960 and is decently fast enough
Copyright (C) 2025-2026 winapiadmin

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "attacks.h"

namespace chess::_chess {
Expand Down
54 changes: 53 additions & 1 deletion attacks.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
/*
a chess library (bonus: you can integrate more piece types!) which
supports Chess960 and is decently fast enough
Copyright (C) 2025-2026 winapiadmin

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once
#include "bitboard.h"
#include "fwd_decl.h"
Expand Down Expand Up @@ -126,7 +145,7 @@ struct Magic {
struct Magic {
Bitboard mask;
Bitboard magic;
int index;
size_t index;
Bitboard shift;
constexpr Bitboard operator()(Bitboard b) const { return (((b & mask)) * magic) >> shift; }
};
Expand Down Expand Up @@ -162,6 +181,39 @@ extern const std::array<Bitboard, 0x1480> BishopAttacks;
return (b & ~MASK_FILE[7]) << 1;
case Direction::SOUTH_EAST:
return (b & ~MASK_FILE[7]) >> 7;
case DOUBLE_NORTH:
return b << 16;

case DOUBLE_SOUTH:
return b >> 16;

case DOUBLE_EAST:
return (b & ~MASK_FILE[7] & ~(MASK_FILE[7] >> 1)) << 2;

case DOUBLE_WEST:
return (b & ~MASK_FILE[0] & ~(MASK_FILE[0] << 1)) >> 2;

case DOUBLE_NORTH_EAST: {
Bitboard t = (b & ~MASK_FILE[7]) << 9;
return (t & ~MASK_FILE[7]) << 9;
}

case DOUBLE_NORTH_WEST: {
Bitboard t = (b & ~MASK_FILE[0]) << 7;
return (t & ~MASK_FILE[0]) << 7;
}

case DOUBLE_SOUTH_EAST: {
Bitboard t = (b & ~MASK_FILE[7]) >> 7;
return (t & ~MASK_FILE[7]) >> 7;
}

case DOUBLE_SOUTH_WEST: {
Bitboard t = (b & ~MASK_FILE[0]) >> 9;
return (t & ~MASK_FILE[0]) >> 9;
}
case DIR_NONE:
return b;
default:
UNREACHABLE();
return 0;
Expand Down
18 changes: 18 additions & 0 deletions bitboard.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/*
a chess library (bonus: you can integrate more piece types!) which
supports Chess960 and is decently fast enough
Copyright (C) 2025-2026 winapiadmin

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "types.h"

Expand Down
106 changes: 86 additions & 20 deletions chess960_tests.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/*
a chess library (bonus: you can integrate more piece types!) which
supports Chess960 and is decently fast enough
Copyright (C) 2025-2026 winapiadmin

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#define DOCTEST_CONFIG_IMPLEMENT
#define DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
#include "moves_io.h"
Expand Down Expand Up @@ -37,7 +55,37 @@ template <typename T, MoveGenType mt, bool EnableDiv = false> uint64_t perft(_Po
uint64_t total = 0;
for (const Move &m : moves) {
pos.template doMove<false>(m);
#if !IS_RELEASE
{
const auto pre_nm_hash_1 = pos.hash();
const auto pre_nm_fen_1 = pos.fen();
if (pos.zobrist() != pos.hash())
REQUIRE(pos.zobrist() == pos.hash());
pos.doNullMove();
pos.undoMove();
if (!(pos.hash() == pre_nm_hash_1 && pos.fen() == pre_nm_fen_1 && pos.zobrist() == pre_nm_hash_1)) {
REQUIRE(pos.hash() == pre_nm_hash_1);
REQUIRE(pos.fen() == pre_nm_fen_1);
REQUIRE(pos.zobrist() == pre_nm_hash_1);
}
}
#endif
const uint64_t nodes = perft<T, mt, false>(pos, depth - 1);
#if !IS_RELEASE
{
const auto pre_nm_hash_1 = pos.hash();
const auto pre_nm_fen_1 = pos.fen();
if (pos.zobrist() != pos.hash())
REQUIRE(pos.zobrist() == pos.hash());
pos.doNullMove();
pos.undoMove();
if (!(pos.hash() == pre_nm_hash_1 && pos.fen() == pre_nm_fen_1 && pos.zobrist() == pre_nm_hash_1)) {
REQUIRE(pos.hash() == pre_nm_hash_1);
REQUIRE(pos.fen() == pre_nm_fen_1);
REQUIRE(pos.zobrist() == pre_nm_hash_1);
}
}
#endif
pos.undoMove();
if constexpr (EnableDiv)
std::cout << m << ": " << nodes << '\n';
Expand All @@ -48,78 +96,96 @@ template <typename T, MoveGenType mt, bool EnableDiv = false> uint64_t perft(_Po
return total;
}
}
#if !IS_RELEASE
auto split_testcases(std::vector<TestEntry<std::string, perft_t>> &entries) {
std::vector<TestEntry<std::string, perft_t>> optimized;

std::sort(entries.begin(), entries.end(), [](const auto &a, const auto &b) { return a.info.nodes < b.info.nodes; });

std::vector<TestEntry<std::string, perft_t>> bucket1, bucket2, bucket3;
for (const auto &e : entries) {
if (e.info.nodes <= 1'000'000)
bucket1.push_back(e);
else if (e.info.nodes <= 10'000'000)
bucket2.push_back(e);
else if (e.info.nodes <= 1'000'000'000)
bucket3.push_back(e);
}

size_t n1 = std::min(bucket1.size(), size_t(2000));
optimized.insert(optimized.end(), bucket1.begin(), bucket1.begin() + n1);

size_t n2 = std::min(bucket2.size(), size_t(30));
optimized.insert(optimized.end(), bucket2.begin(), bucket2.begin() + n2);

size_t n3 = std::min(bucket3.size(), size_t(5));
if (n3 > 0) {
optimized.insert(optimized.end(), bucket3.end() - n3, bucket3.end());
}

return optimized;
}
#endif
template <MoveGenType mt = MoveGenType::ALL, bool EnableDiv = false>
void check_perfts(const std::vector<TestEntry<std::string, perft_t>> &entries) {
void check_perfts(std::vector<TestEntry<std::string, perft_t>> &entries) {
uint64_t nodes = 0;
double elapsed = 0;
using namespace std::chrono;
#if !IS_RELEASE
entries = split_testcases(entries);
#endif
auto start_time = high_resolution_clock::now();
for (auto &entry : entries) {
std::cerr << entry.input << " (chess960=true) " << entry.info.depth;
#if !IS_RELEASE
if (entry.info.nodes > 2e7) {
if (entry.info.nodes > 1e6) {
std::cerr << "(skipped)\n";
continue;
}
#endif
std::cerr << '\n';
{
_Position<PolyglotPiece> pos(entry.input, true);
auto start_time = high_resolution_clock::now();
REQUIRE(perft<PolyglotPiece, mt, EnableDiv>(pos, entry.info.depth) == entry.info.nodes);
auto end_time = high_resolution_clock::now();
elapsed += duration<double>(end_time - start_time).count();
nodes += entry.info.nodes;
if (entry.info.nodes < 5e6) {
_Position<PolyglotPiece> pos2 = pos;
REQUIRE(pos.fen() == pos2.fen());
auto start_time = high_resolution_clock::now();
REQUIRE(perft<PolyglotPiece, mt, EnableDiv>(pos2, entry.info.depth) == entry.info.nodes);
auto end_time = high_resolution_clock::now();
elapsed += duration<double>(end_time - start_time).count();
nodes += entry.info.nodes;
} else {
std::cerr << "\n(skipped copying test)\n";
}
}
{
_Position<EnginePiece> pos(entry.input, true);
auto start_time = high_resolution_clock::now();
REQUIRE(perft<EnginePiece, mt, EnableDiv>(pos, entry.info.depth) == entry.info.nodes);
auto end_time = high_resolution_clock::now();
elapsed += duration<double>(end_time - start_time).count();
nodes += entry.info.nodes;
if (entry.info.nodes < 5e6) {
_Position<EnginePiece> pos2 = pos;
REQUIRE(pos.fen() == pos2.fen());
auto start_time = high_resolution_clock::now();
REQUIRE(perft<EnginePiece, mt, EnableDiv>(pos2, entry.info.depth) == entry.info.nodes);
auto end_time = high_resolution_clock::now();
elapsed += duration<double>(end_time - start_time).count();
nodes += entry.info.nodes;
} else {
std::cerr << "\n(skipped copying test)\n";
}
}
{
_Position<ContiguousMappingPiece> pos(entry.input, true);
auto start_time = high_resolution_clock::now();
REQUIRE(perft<ContiguousMappingPiece, mt, EnableDiv>(pos, entry.info.depth) == entry.info.nodes);
auto end_time = high_resolution_clock::now();
elapsed += duration<double>(end_time - start_time).count();
nodes += entry.info.nodes;
if (entry.info.nodes < 5e6) {
_Position<ContiguousMappingPiece> pos2 = pos;
REQUIRE(pos.fen() == pos2.fen());
auto start_time = high_resolution_clock::now();
REQUIRE(perft<ContiguousMappingPiece, mt, EnableDiv>(pos2, entry.info.depth) == entry.info.nodes);
auto end_time = high_resolution_clock::now();
elapsed += duration<double>(end_time - start_time).count();
nodes += entry.info.nodes;
} else {
std::cerr << "\n(skipped copying test)\n";
}
}
}
auto end_time = high_resolution_clock::now();
elapsed = duration<double>(end_time - start_time).count();
double mnps = (nodes / elapsed) / 1'000'000.0;
std::cout << "Speed: " << mnps << "Mnps\n";
}
Expand Down
18 changes: 18 additions & 0 deletions fwd_decl.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/*
a chess library (bonus: you can integrate more piece types!) which
supports Chess960 and is decently fast enough
Copyright (C) 2025-2026 winapiadmin

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <cstdint>
#include <type_traits>
Expand Down
Loading
Loading