Skip to content
Merged
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
23 changes: 23 additions & 0 deletions .github/workflows/tournament-tools.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ on:
paths:
- 'tournament/**'
- '.github/workflows/tournament-tools.yml'
- 'CMakeLists.txt'
- 'bit_utils.h'
- 'magics.cpp'
- 'time_manager.h'
- '*.cpp'
- '*.h'

jobs:
estimator:
Expand Down Expand Up @@ -78,3 +84,20 @@ jobs:
Write-Host "Parsing $($_.Name)"
[void][ScriptBlock]::Create((Get-Content $_.FullName -Raw))
}

windows-native-build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
- name: Configure Release build
run: cmake -S . -B build-windows
- name: Build Pickle with MSVC
run: cmake --build build-windows --config Release --parallel
- name: Smoke-test UCI
shell: pwsh
run: |
$inputText = "uci`nisready`nquit`n"
$output = $inputText | & .\build-windows\Release\pickle.exe
$output | Write-Host
if ($output -notcontains 'uciok') { throw 'Missing uciok from Windows build.' }
if ($output -notcontains 'readyok') { throw 'Missing readyok from Windows build.' }
14 changes: 11 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,19 @@ set(PICKLE_CORE
zobrist.cpp
)

function(pickle_enable_release_optimizations target)
if(MSVC)
target_compile_options(${target} PRIVATE /O2)
else()
target_compile_options(${target} PRIVATE -O3)
endif()
endfunction()

if(EMSCRIPTEN)
add_executable(pickle_web ${PICKLE_CORE} wasm_api.cpp)
set_target_properties(pickle_web PROPERTIES SUFFIX ".js")

target_compile_options(pickle_web PRIVATE -O3)
pickle_enable_release_optimizations(pickle_web)
target_link_options(pickle_web PRIVATE
-O3
"-sALLOW_MEMORY_GROWTH=1"
Expand All @@ -39,9 +47,9 @@ if(EMSCRIPTEN)
)
else()
add_executable(pickle ${PICKLE_CORE} datagen.cpp main.cpp)
target_compile_options(pickle PRIVATE -O3)
pickle_enable_release_optimizations(pickle)

add_executable(pickle_regression attacks.cpp board.cpp magics.cpp movegen.cpp zobrist.cpp tests/hash_roundtrip.cpp)
target_include_directories(pickle_regression PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_options(pickle_regression PRIVATE -O3)
pickle_enable_release_optimizations(pickle_regression)
endif()
35 changes: 31 additions & 4 deletions bit_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@

#include <cstdint>

#if defined(_MSC_VER)
#include <intrin.h>
#endif

// -----------------------------------------------------------------------------
// Bit Manipulation Utilites
// Bit Manipulation Utilities
// -----------------------------------------------------------------------------

// Set the bit at the given square index (0-63) to 1.
Expand All @@ -23,16 +27,39 @@ inline void clear_bit(std::uint64_t& bitboard, int square) {
}

// Count the number of set bits (population count) in the bitboard.
// Uses highly optimized compiler intrinsics.
inline int pop_count(std::uint64_t bitboard) {
#if defined(_MSC_VER)
// Portable SWAR popcount avoids requiring a particular MSVC /arch setting
// or CPU POPCNT feature while remaining branch-free.
bitboard -= (bitboard >> 1) & 0x5555555555555555ULL;
bitboard = (bitboard & 0x3333333333333333ULL)
+ ((bitboard >> 2) & 0x3333333333333333ULL);
bitboard = (bitboard + (bitboard >> 4)) & 0x0F0F0F0F0F0F0F0FULL;
return static_cast<int>((bitboard * 0x0101010101010101ULL) >> 56);
#else
return __builtin_popcountll(bitboard);
#endif
}

// Get the index of the Least Significant 1-Bit (LSB).
// Undefined behavior for passing 0 to __builtin_ctzll natively, so we check.
inline int lsb(std::uint64_t bitboard) {
if (bitboard == 0) return -1; // Or return 64; handled conventionally here
if (bitboard == 0) return -1;
#if defined(_MSC_VER) && defined(_M_X64)
unsigned long index = 0;
_BitScanForward64(&index, bitboard);
return static_cast<int>(index);
#elif defined(_MSC_VER)
unsigned long index = 0;
const std::uint32_t low = static_cast<std::uint32_t>(bitboard);
if (low != 0) {
_BitScanForward(&index, low);
return static_cast<int>(index);
}
_BitScanForward(&index, static_cast<std::uint32_t>(bitboard >> 32));
return static_cast<int>(index + 32);
#else
return __builtin_ctzll(bitboard);
#endif
}

#endif // BIT_UTILS_H
8 changes: 4 additions & 4 deletions datagen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ void run_datagen(int games, int target_depth, const std::string& filename) {
std::cout << "Starting Data Generation... (" << games << " games at depth " << target_depth << ")\n";

// Disable time management for raw algorithmic depth mapping
tm.depth_limit = target_depth;
tm.time_is_up = false;
search_timer.depth_limit = target_depth;
search_timer.time_is_up = false;

for (int g = 0; g < games; g++) {
Board board;
Expand Down Expand Up @@ -101,11 +101,11 @@ void run_datagen(int games, int target_depth, const std::string& filename) {
Move best = 0;
clear_heuristics();
nodes_searched = 0;
tm.time_is_up = false;
search_timer.time_is_up = false;

for (int d = 1; d <= target_depth; d++) {
final_score = negamax(d, -50000, 50000, board, 0, true);
if (!tm.time_is_up) best = best_move;
if (!search_timer.time_is_up) best = best_move;
}

// To prevent mating scores distorting NN evaluations heavily, cap mate scores
Expand Down
5 changes: 3 additions & 2 deletions magics.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "magics.h"
#include "bit_utils.h"
#include <iostream>

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -164,12 +165,12 @@ U64 rook_attacks_on_the_fly(int square, U64 block) {
// Permutation Generator
// -----------------------------------------------------------------------------

// Utility to set occupancy for a specific index.
// Utility to set occupancy for a specific index.
// Standard LSB-based permutation generator.
static U64 set_occupancy(int index, int bits_in_mask, U64 attack_mask) {
U64 occupancy = 0ULL;
for (int count = 0; count < bits_in_mask; count++) {
int square = __builtin_ctzll(attack_mask);
int square = lsb(attack_mask);
attack_mask &= ~(1ULL << square);
if (index & (1 << count)) {
occupancy |= (1ULL << square);
Expand Down
25 changes: 13 additions & 12 deletions search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ int score_move(Board& board, Move move, Move tt_move, int search_ply) {

int quiescence(int alpha, int beta, Board& board, int search_ply, int qs_ply) {
if ((nodes_searched & 511ULL) == 0) check_time();
if (tm.time_is_up) return 0;
if (search_timer.time_is_up) return 0;
++nodes_searched;

if (qs_ply >= MAX_QS_PLY) return evaluate(board);
Expand Down Expand Up @@ -176,7 +176,7 @@ int quiescence(int alpha, int beta, Board& board, int search_ply, int qs_ply) {
int score = -quiescence(-beta, -alpha, board, search_ply + 1, qs_ply + 1);
board.unmake_move(move);

if (tm.time_is_up) return 0;
if (search_timer.time_is_up) return 0;
if (score >= beta) return beta;
if (score > alpha) alpha = score;
}
Expand All @@ -187,7 +187,7 @@ int quiescence(int alpha, int beta, Board& board, int search_ply, int qs_ply) {

int negamax(int depth, int alpha, int beta, Board& board, int search_ply, bool can_null_move) {
if ((nodes_searched & 511ULL) == 0) check_time();
if (tm.time_is_up) return 0;
if (search_timer.time_is_up) return 0;
++nodes_searched;

if (search_ply >= MAX_SEARCH_PLY) return evaluate(board);
Expand Down Expand Up @@ -218,7 +218,7 @@ int negamax(int depth, int alpha, int beta, Board& board, int search_ply, bool c
int score = -negamax(depth - 1 - reduction, -beta, -beta + 1,
board, search_ply + 1, false);
board.unmake_null_move();
if (tm.time_is_up) return 0;
if (search_timer.time_is_up) return 0;
if (score >= beta) return beta;
}

Expand Down Expand Up @@ -281,7 +281,7 @@ int negamax(int depth, int alpha, int beta, Board& board, int search_ply, bool c
}

board.unmake_move(move);
if (tm.time_is_up) return 0;
if (search_timer.time_is_up) return 0;

if (score >= beta) {
if (search_ply == 0) best_move = move;
Expand Down Expand Up @@ -355,7 +355,7 @@ Move search_best_move(Board& board, int depth, bool print_info) {
return book_move;
}

int target_depth = tm.depth_limit > 0 ? tm.depth_limit : depth;
int target_depth = search_timer.depth_limit > 0 ? search_timer.depth_limit : depth;
if (target_depth <= 0) target_depth = 64;

auto wall_start = std::chrono::high_resolution_clock::now();
Expand All @@ -378,7 +378,7 @@ Move search_best_move(Board& board, int depth, bool print_info) {
while (true) {
best_move = completed_move;
score = negamax(current_depth, alpha, beta, board, 0, true);
if (tm.time_is_up || tm.stopped) break;
if (search_timer.time_is_up || search_timer.stopped) break;

if (score <= alpha && alpha > -INF) {
window *= 2;
Expand All @@ -395,7 +395,7 @@ Move search_best_move(Board& board, int depth, bool print_info) {
break;
}

if (tm.time_is_up || tm.stopped) break;
if (search_timer.time_is_up || search_timer.stopped) break;

Move iteration_move = best_move ? best_move : completed_move;
if (iteration_move == completed_move && iteration_move != 0) ++stable_best_count;
Expand Down Expand Up @@ -426,12 +426,13 @@ Move search_best_move(Board& board, int depth, bool print_info) {
std::cout << std::endl;
}

if (tm.optimum_time != -1) {
long long soft_limit = tm.optimum_time;
if (search_timer.optimum_time != -1) {
long long soft_limit = search_timer.optimum_time;
if (have_previous_score && std::abs(score - previous_score) >= 65) {
soft_limit = std::min(tm.max_time, tm.optimum_time + tm.optimum_time / 2);
soft_limit = std::min(search_timer.max_time,
search_timer.optimum_time + search_timer.optimum_time / 2);
} else if (stable_best_count >= 3) {
soft_limit = std::max(1LL, (tm.optimum_time * 3) / 4);
soft_limit = std::max(1LL, (search_timer.optimum_time * 3) / 4);
}
if (elapsed >= soft_limit) break;
}
Expand Down
46 changes: 23 additions & 23 deletions time_manager.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include <chrono>
#include "time_manager.h"

TimeManager tm;
TimeManager search_timer;

long long get_time_ms() {
auto now = std::chrono::time_point_cast<std::chrono::milliseconds>(
Expand All @@ -11,42 +11,42 @@ long long get_time_ms() {
}

void set_time_limits(long long time_left, long long inc, long long movetime, int depth) {
tm.stopped = false;
tm.time_is_up = false;
tm.start_time = get_time_ms();
tm.depth_limit = depth;
search_timer.stopped = false;
search_timer.time_is_up = false;
search_timer.start_time = get_time_ms();
search_timer.depth_limit = depth;

if (movetime > 0) {
tm.optimum_time = movetime;
tm.max_time = movetime;
tm.stop_time = tm.start_time + movetime;
search_timer.optimum_time = movetime;
search_timer.max_time = movetime;
search_timer.stop_time = search_timer.start_time + movetime;
} else if (time_left > 0) {
// Allocate roughly one move from a 30-move horizon plus part of increment.
tm.optimum_time = (time_left / 30) + (inc / 2);
tm.max_time = (time_left / 10) + inc;
search_timer.optimum_time = (time_left / 30) + (inc / 2);
search_timer.max_time = (time_left / 10) + inc;

if (tm.optimum_time > time_left - 50) tm.optimum_time = time_left - 50;
if (tm.max_time > time_left - 50) tm.max_time = time_left - 50;
if (search_timer.optimum_time > time_left - 50) search_timer.optimum_time = time_left - 50;
if (search_timer.max_time > time_left - 50) search_timer.max_time = time_left - 50;

if (tm.optimum_time < 0) tm.optimum_time = 0;
if (tm.max_time < 0) tm.max_time = 0;
if (search_timer.optimum_time < 0) search_timer.optimum_time = 0;
if (search_timer.max_time < 0) search_timer.max_time = 0;

tm.stop_time = tm.start_time + tm.max_time;
search_timer.stop_time = search_timer.start_time + search_timer.max_time;
} else {
tm.optimum_time = -1;
tm.max_time = -1;
tm.stop_time = -1;
search_timer.optimum_time = -1;
search_timer.max_time = -1;
search_timer.stop_time = -1;
}
}

void check_time() {
if (tm.stopped) {
tm.time_is_up = true;
if (search_timer.stopped) {
search_timer.time_is_up = true;
return;
}

if (tm.stop_time != -1 && get_time_ms() >= tm.stop_time) {
tm.time_is_up = true;
tm.stopped = true;
if (search_timer.stop_time != -1 && get_time_ms() >= search_timer.stop_time) {
search_timer.time_is_up = true;
search_timer.stopped = true;
}
}
5 changes: 3 additions & 2 deletions time_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ struct TimeManager {
bool stopped; // external flag to forcefully stop searching
};

// Global TimeManger instance
extern TimeManager tm;
// Global search timer state. The explicit name avoids colliding with the
// standard C/C++ `tm` time structure on MSVC.
extern TimeManager search_timer;

// Initialize the time manager with specific limits.
// Setting elements to -1 implies "infinite"
Expand Down
2 changes: 1 addition & 1 deletion uci.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ void uci_loop(Board& board) {
search_position(board, depth);
}
else if (command == "stop") {
tm.stopped = true;
search_timer.stopped = true;
}
else if (command == "quit") {
break;
Expand Down