├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── network.txt ├── src ├── 3rdparty │ ├── fathom │ │ ├── stdendian.h │ │ ├── tbchess.c │ │ ├── tbconfig.h │ │ ├── tbprobe.cpp │ │ └── tbprobe.h │ ├── incbin.h │ └── zstd │ │ ├── COPYING │ │ ├── zstd.h │ │ ├── zstd_errors.h │ │ └── zstddeclib.c ├── arch.h ├── attacks │ ├── attacks.h │ ├── black_magic │ │ ├── attacks.cpp │ │ ├── attacks.h │ │ └── data.h │ ├── bmi2 │ │ ├── attacks.cpp │ │ ├── attacks.h │ │ └── data.h │ └── util.h ├── bench.cpp ├── bench.h ├── bitboard.h ├── core.h ├── correction.h ├── cuckoo.cpp ├── cuckoo.h ├── datagen │ ├── common.h │ ├── datagen.cpp │ ├── datagen.h │ ├── fen.cpp │ ├── fen.h │ ├── format.h │ ├── marlinformat.cpp │ ├── marlinformat.h │ ├── viriformat.cpp │ └── viriformat.h ├── eval │ ├── arch.h │ ├── eval.h │ ├── nnue.cpp │ ├── nnue.h │ └── nnue │ │ ├── activation.h │ │ ├── arch │ │ ├── multilayer.h │ │ └── singlelayer.h │ │ ├── features.h │ │ ├── input.h │ │ ├── io.h │ │ ├── io_impl.cpp │ │ ├── io_impl.h │ │ ├── network.h │ │ └── output.h ├── history.h ├── keys.h ├── limit │ ├── compound.h │ ├── limit.h │ ├── time.cpp │ ├── time.h │ └── trivial.h ├── main.cpp ├── move.h ├── movegen.cpp ├── movegen.h ├── movepick.h ├── opts.cpp ├── opts.h ├── perft.cpp ├── perft.h ├── position │ ├── boards.h │ ├── position.cpp │ └── position.h ├── pretty.cpp ├── pretty.h ├── rays.h ├── search.cpp ├── search.h ├── search_fwd.h ├── see.h ├── stats.cpp ├── stats.h ├── tb.cpp ├── tb.h ├── ttable.cpp ├── ttable.h ├── tunable.cpp ├── tunable.h ├── types.h ├── uci.cpp ├── uci.h ├── util │ ├── align.h │ ├── aligned_array.h │ ├── barrier.h │ ├── bitfield.h │ ├── bits.h │ ├── cemath.h │ ├── ctrlc.cpp │ ├── ctrlc.h │ ├── memstream.h │ ├── multi_array.h │ ├── parse.h │ ├── range.h │ ├── rng.h │ ├── simd.h │ ├── simd │ │ ├── avx2.h │ │ ├── avx512.h │ │ └── neon.h │ ├── split.cpp │ ├── split.h │ ├── static_vector.h │ ├── timer.cpp │ ├── timer.h │ └── u4array.h ├── wdl.cpp └── wdl.h └── version.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /cmake-*/ 2 | /build/ 3 | /.idea/ 4 | /*.exe 5 | /*.profraw 6 | *.nnue 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | file(STRINGS version.txt SP_VERSION LIMIT_COUNT 1) 4 | project(stormphrax VERSION ${SP_VERSION}) 5 | 6 | set(CMAKE_CXX_STANDARD 20) 7 | 8 | file(STRINGS network.txt SP_DEFAULT_NET_NAME LIMIT_COUNT 1) 9 | 10 | set(SP_CLANG CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 11 | 12 | if(MSVC AND NOT SP_CLANG) 13 | message(FATAL_ERROR Stormphrax does not support building with MSVC) 14 | endif() 15 | 16 | option(SP_EMBED_COMMIT_HASH "whether to include the current git commit in the UCI version string" ON) 17 | 18 | if(SP_EMBED_COMMIT_HASH) 19 | set(SP_COMMIT_HASH "unknown") 20 | find_package(Git QUIET) 21 | if(GIT_FOUND) 22 | execute_process( 23 | COMMAND git log -1 --pretty=format:%h 24 | OUTPUT_VARIABLE SP_COMMIT_HASH 25 | OUTPUT_STRIP_TRAILING_WHITESPACE 26 | ERROR_QUIET 27 | ) 28 | endif() 29 | endif() 30 | 31 | # for fathom 32 | add_compile_definitions(_SILENCE_CXX20_ATOMIC_INIT_DEPRECATION_WARNING) 33 | 34 | if(MSVC) 35 | add_compile_options(/EHsc) 36 | # for fathom 37 | add_compile_definitions(_CRT_SECURE_NO_WARNINGS) 38 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:DebugDLL>") 39 | endif() 40 | 41 | find_package(Threads REQUIRED) 42 | 43 | # cmake forces thin lto on clang for CMAKE_INTERPROCEDURAL_OPTIMIZATION, thanks cmake 44 | add_compile_options($<$:-flto>) 45 | 46 | option(SP_FAST_PEXT "whether pext and pdep are usably fast on this architecture, for building native binaries" ON) 47 | option(SP_DISABLE_AVX512 "whether to disable AVX-512 (zen 4)" OFF) 48 | option(SP_DISABLE_NEON_DOTPROD "whether to disable NEON dotprod on ARM machines" OFF) 49 | 50 | set(STORMPHRAX_COMMON_SRC src/types.h src/main.cpp src/uci.h src/uci.cpp src/core.h src/util/bitfield.h src/util/bits.h 51 | src/util/parse.h src/util/split.h src/util/split.cpp src/util/rng.h src/util/static_vector.h src/bitboard.h 52 | src/move.h src/keys.h src/position/position.h src/position/position.cpp src/search.h src/search.cpp src/movegen.h 53 | src/movegen.cpp src/attacks/util.h src/attacks/attacks.h src/util/timer.h src/util/timer.cpp src/pretty.h 54 | src/pretty.cpp src/rays.h src/ttable.h src/ttable.cpp src/limit/limit.h src/limit/trivial.h src/limit/time.h 55 | src/limit/time.cpp src/util/cemath.h src/eval/nnue.h src/eval/nnue.cpp src/util/range.h src/arch.h src/perft.h 56 | src/perft.cpp src/search_fwd.h src/see.h src/bench.h src/bench.cpp src/tunable.h src/tunable.cpp src/opts.h 57 | src/opts.cpp src/position/boards.h src/3rdparty/fathom/stdendian.h src/3rdparty/fathom/tbconfig.h 58 | src/3rdparty/fathom/tbprobe.h src/3rdparty/fathom/tbprobe.cpp src/datagen/datagen.h src/datagen/datagen.cpp 59 | src/util/u4array.h src/eval/eval.h src/util/barrier.h src/util/simd.h src/wdl.h src/wdl.cpp src/eval/arch.h 60 | src/cuckoo.h src/cuckoo.cpp src/eval/nnue/network.h src/eval/nnue/activation.h src/eval/nnue/output.h 61 | src/eval/nnue/input.h src/util/memstream.h src/util/aligned_array.h src/eval/nnue/io.h src/eval/nnue/features.h 62 | src/datagen/format.h src/datagen/common.h src/datagen/marlinformat.h src/datagen/marlinformat.cpp 63 | src/datagen/viriformat.h src/datagen/viriformat.cpp src/tb.h src/tb.cpp src/movepick.h src/history.h 64 | src/util/multi_array.h src/correction.h src/limit/compound.h src/util/simd/avx512.h 65 | src/util/simd/avx2.h src/util/simd/neon.h src/util/align.h src/3rdparty/zstd/zstddeclib.c 66 | src/eval/nnue/io_impl.h src/eval/nnue/io_impl.cpp src/datagen/fen.h src/datagen/fen.cpp src/util/ctrlc.h 67 | src/util/ctrlc.cpp src/eval/nnue/arch/singlelayer.h src/eval/nnue/arch/multilayer.h src/stats.h src/stats.cpp) 68 | 69 | set(STORMPHRAX_BMI2_SRC src/attacks/bmi2/data.h src/attacks/bmi2/attacks.h src/attacks/bmi2/attacks.cpp) 70 | set(STORMPHRAX_NON_BMI2_SRC src/attacks/black_magic/data.h src/attacks/black_magic/attacks.h 71 | src/attacks/black_magic/attacks.cpp) 72 | 73 | add_executable(stormphrax-native ${STORMPHRAX_COMMON_SRC} ${STORMPHRAX_BMI2_SRC} ${STORMPHRAX_NON_BMI2_SRC}) 74 | add_executable(stormphrax-vnni512 ${STORMPHRAX_COMMON_SRC} ${STORMPHRAX_BMI2_SRC}) 75 | add_executable(stormphrax-avx2-bmi2 ${STORMPHRAX_COMMON_SRC} ${STORMPHRAX_BMI2_SRC}) 76 | add_executable(stormphrax-avx2 ${STORMPHRAX_COMMON_SRC} ${STORMPHRAX_NON_BMI2_SRC}) 77 | 78 | target_compile_options(stormphrax-native PUBLIC -march=native) 79 | target_compile_options(stormphrax-vnni512 PUBLIC -march=znver5) 80 | target_compile_options(stormphrax-avx2-bmi2 PUBLIC -march=haswell) 81 | # excavator without amd-specific extensions and bmi2 82 | target_compile_options(stormphrax-avx2 PUBLIC -march=bdver4 -mno-tbm -mno-sse4a -mno-bmi2) 83 | 84 | if(NOT MSVC) 85 | target_compile_options(stormphrax-native PUBLIC -mtune=native) 86 | target_compile_options(stormphrax-vnni512 PUBLIC -mtune=znver5) 87 | target_compile_options(stormphrax-avx2-bmi2 PUBLIC -mtune=haswell) 88 | target_compile_options(stormphrax-avx2 PUBLIC -mtune=znver2) # zen 2 89 | else() # clang 90 | target_compile_options(stormphrax-native PUBLIC /tune:native) 91 | target_compile_options(stormphrax-vnni512 PUBLIC /tune:znver5) 92 | target_compile_options(stormphrax-avx2-bmi2 PUBLIC /tune:znver3) 93 | target_compile_options(stormphrax-avx2 PUBLIC /tune:znver2) # zen 2 94 | endif() 95 | 96 | if(SP_FAST_PEXT) 97 | target_compile_definitions(stormphrax-native PUBLIC SP_FAST_PEXT) 98 | endif() 99 | 100 | if(SP_DISABLE_AVX512) 101 | target_compile_definitions(stormphrax-native PUBLIC SP_DISABLE_AVX512) 102 | endif() 103 | 104 | if(SP_DISABLE_NEON_DOTPROD) 105 | target_compile_definitions(stormphrax-native PUBLIC SP_DISABLE_NEON_DOTPROD) 106 | endif() 107 | 108 | get_directory_property(TARGETS BUILDSYSTEM_TARGETS) 109 | 110 | foreach(TARGET ${TARGETS}) 111 | string(REPLACE "stormphrax-" "" ARCH_NAME "${TARGET}") 112 | string(REPLACE "-" "_" ARCH_NAME "${ARCH_NAME}") 113 | string(TOUPPER ${ARCH_NAME} ARCH_NAME) 114 | 115 | target_compile_definitions(${TARGET} PUBLIC SP_VERSION=${CMAKE_PROJECT_VERSION} 116 | SP_${ARCH_NAME} SP_NETWORK_FILE="${PROJECT_SOURCE_DIR}/${SP_DEFAULT_NET_NAME}.nnue") 117 | 118 | string(REPLACE "stormphrax-" "stormphrax-${CMAKE_PROJECT_VERSION}-" TARGET_NAME "${TARGET}") 119 | set_property(TARGET ${TARGET} PROPERTY OUTPUT_NAME "${TARGET_NAME}") 120 | 121 | if(SP_EMBED_COMMIT_HASH) 122 | target_compile_definitions(${TARGET} PUBLIC SP_COMMIT_HASH=${SP_COMMIT_HASH}) 123 | endif() 124 | 125 | target_link_libraries(${TARGET} Threads::Threads) 126 | endforeach() 127 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(OS), Windows_NT) 2 | DETECTED_OS := Windows 3 | else 4 | DETECTED_OS := $(shell uname -s) 5 | endif 6 | 7 | ifeq ($(DETECTED_OS),Darwin) 8 | VERSION := $(shell cat version.txt) 9 | DEFAULT_NET := $(shell cat network.txt) 10 | else 11 | VERSION := $(file < version.txt) 12 | DEFAULT_NET := $(file < network.txt) 13 | endif 14 | 15 | ifndef EXE 16 | EXE = stormphrax-$(VERSION) 17 | NO_EXE_SET = true 18 | endif 19 | 20 | ifndef EVALFILE 21 | EVALFILE = $(DEFAULT_NET).nnue 22 | NO_EVALFILE_SET = true 23 | endif 24 | 25 | PGO = off 26 | COMMIT_HASH = off 27 | DISABLE_NEON_DOTPROD = off 28 | 29 | SOURCES_COMMON := src/main.cpp src/uci.cpp src/util/split.cpp src/position/position.cpp src/movegen.cpp src/search.cpp src/util/timer.cpp src/pretty.cpp src/ttable.cpp src/limit/time.cpp src/eval/nnue.cpp src/perft.cpp src/bench.cpp src/tunable.cpp src/opts.cpp src/3rdparty/fathom/tbprobe.cpp src/datagen/datagen.cpp src/wdl.cpp src/cuckoo.cpp src/datagen/marlinformat.cpp src/datagen/viriformat.cpp src/datagen/fen.cpp src/tb.cpp src/3rdparty/zstd/zstddeclib.c src/eval/nnue/io_impl.cpp src/util/ctrlc.cpp src/stats.cpp 30 | SOURCES_BMI2 := src/attacks/bmi2/attacks.cpp 31 | SOURCES_BLACK_MAGIC := src/attacks/black_magic/attacks.cpp 32 | 33 | SUFFIX := 34 | 35 | CXX := clang++ 36 | # silence warning for fathom 37 | CXXFLAGS := -std=c++20 -O3 -flto -DNDEBUG -DSP_NETWORK_FILE=\"$(EVALFILE)\" -DSP_VERSION=$(VERSION) -D_SILENCE_CXX20_ATOMIC_INIT_DEPRECATION_WARNING 38 | 39 | CXXFLAGS_NATIVE := -DSP_NATIVE -march=native 40 | CXXFLAGS_TUNABLE := -DSP_NATIVE -march=native -DSP_EXTERNAL_TUNE=1 41 | CXXFLAGS_VNNI512 := -DSP_VNNI512 -DSP_FAST_PEXT -march=znver5 -mtune=znver5 42 | CXXFLAGS_AVX2_BMI2 := -DSP_AVX2_BMI2 -DSP_FAST_PEXT -march=haswell -mtune=znver3 43 | CXXFLAGS_AVX2 := -DSP_AVX2 -march=bdver4 -mno-tbm -mno-sse4a -mno-bmi2 -mtune=znver2 44 | 45 | LDFLAGS := 46 | 47 | COMPILER_VERSION := $(shell $(CXX) --version) 48 | 49 | ifeq (, $(findstring clang,$(COMPILER_VERSION))) 50 | ifeq (, $(findstring gcc,$(COMPILER_VERSION))) 51 | ifeq (, $(findstring g++,$(COMPILER_VERSION))) 52 | $(error Only Clang and GCC supported) 53 | endif 54 | endif 55 | endif 56 | 57 | ifeq ($(DETECTED_OS), Windows) 58 | SUFFIX := .exe 59 | # for fathom 60 | CXXFLAGS += -D_CRT_SECURE_NO_WARNINGS 61 | RM := del 62 | else 63 | SUFFIX := 64 | LDFLAGS += -pthread 65 | # don't ask 66 | ifdef IS_COSMO 67 | CXXFLAGS += -stdlib=libc++ 68 | endif 69 | RM := rm 70 | endif 71 | 72 | ifneq (, $(findstring clang,$(COMPILER_VERSION))) 73 | ifneq ($(DETECTED_OS),Darwin) 74 | LDFLAGS += -fuse-ld=lld 75 | endif 76 | ifeq ($(DETECTED_OS),Windows) 77 | ifeq (,$(shell where llvm-profdata)) 78 | $(warning llvm-profdata not found, disabling PGO) 79 | override PGO := off 80 | endif 81 | else 82 | ifeq (,$(shell which llvm-profdata)) 83 | $(warning llvm-profdata not found, disabling PGO) 84 | override PGO := off 85 | endif 86 | endif 87 | PGO_GENERATE := -DSP_PGO_PROFILE -fprofile-instr-generate 88 | PGO_MERGE := llvm-profdata merge -output=sp.profdata *.profraw 89 | PGO_USE := -fprofile-instr-use=sp.profdata 90 | else 91 | $(warning GCC currently produces very slow binaries for Stormphrax) 92 | PGO_GENERATE := -DSP_PGO_PROFILE -fprofile-generate 93 | PGO_MERGE := 94 | PGO_USE := -fprofile-use 95 | endif 96 | 97 | ARCH_DEFINES := $(shell echo | $(CXX) -march=native -E -dM -) 98 | 99 | ifneq ($(findstring __BMI2__, $(ARCH_DEFINES)),) 100 | ifeq ($(findstring __znver1, $(ARCH_DEFINES)),) 101 | ifeq ($(findstring __znver2, $(ARCH_DEFINES)),) 102 | ifeq ($(findstring __bdver, $(ARCH_DEFINES)),) 103 | CXXFLAGS_NATIVE += -DSP_FAST_PEXT 104 | endif 105 | endif 106 | endif 107 | endif 108 | 109 | # AVX-512 as a whole is slower on zen 4 110 | ifneq ($(findstring __znver4, $(ARCH_DEFINES)),) 111 | CXXFLAGS_NATIVE += -DSP_DISABLE_AVX512 112 | endif 113 | 114 | ifneq ($(findstring __ARM_ARCH, $(ARCH_DEFINES)),) 115 | ifeq ($(DISABLE_NEON_DOTPROD),on) 116 | CXXFLAGS_NATIVE += -DSP_DISABLE_NEON_DOTPROD 117 | endif 118 | endif 119 | 120 | ifeq ($(COMMIT_HASH),on) 121 | CXXFLAGS += -DSP_COMMIT_HASH=$(shell git log -1 --pretty=format:%h) 122 | endif 123 | 124 | PROFILE_OUT = sp_profile$(SUFFIX) 125 | 126 | ifneq ($(PGO),on) 127 | define build 128 | $(CXX) $(CXXFLAGS) $(CXXFLAGS_$1) $(LDFLAGS) -o $(EXE)$(if $(NO_EXE_SET),-$2)$(SUFFIX) $(filter-out $(EVALFILE),$^) 129 | endef 130 | else 131 | define build 132 | $(CXX) $(CXXFLAGS) $(CXXFLAGS_$1) $(LDFLAGS) -o $(PROFILE_OUT) $(PGO_GENERATE) $(filter-out $(EVALFILE),$^) 133 | ./$(PROFILE_OUT) bench 134 | $(RM) $(PROFILE_OUT) 135 | $(PGO_MERGE) 136 | $(CXX) $(CXXFLAGS) $(CXXFLAGS_$1) $(LDFLAGS) -o $(EXE)$(if $(NO_EXE_SET),-$2)$(SUFFIX) $(PGO_USE) $(filter-out $(EVALFILE),$^) 137 | $(RM) *.profraw 138 | $(RM) sp.profdata 139 | endef 140 | endif 141 | 142 | release: vnni512 avx2-bmi2 avx2 143 | all: native release 144 | 145 | .PHONY: all 146 | 147 | .DEFAULT_GOAL := native 148 | 149 | ifdef NO_EVALFILE_SET 150 | $(EVALFILE): 151 | $(info Downloading default network $(DEFAULT_NET).nnue) 152 | curl -sOL https://github.com/Ciekce/stormphrax-nets/releases/download/$(DEFAULT_NET)/$(DEFAULT_NET).nnue 153 | 154 | download-net: $(EVALFILE) 155 | endif 156 | 157 | $(EXE): $(EVALFILE) $(SOURCES_COMMON) $(SOURCES_BLACK_MAGIC) $(SOURCES_BMI2) 158 | $(call build,NATIVE,native) 159 | 160 | native: $(EXE) 161 | 162 | tunable: $(EVALFILE) $(SOURCES_COMMON) $(SOURCES_BLACK_MAGIC) $(SOURCES_BMI2) 163 | $(call build,TUNABLE,tunable) 164 | 165 | vnni512: $(EVALFILE) $(SOURCES_COMMON) $(SOURCES_BMI2) 166 | $(call build,VNNI512,vnni512) 167 | 168 | avx2-bmi2: $(EVALFILE) $(SOURCES_COMMON) $(SOURCES_BMI2) 169 | $(call build,AVX2_BMI2,avx2-bmi2) 170 | 171 | avx2: $(EVALFILE) $(SOURCES_COMMON) $(SOURCES_BLACK_MAGIC) 172 | $(call build,AVX2,avx2) 173 | 174 | clean: 175 | 176 | -------------------------------------------------------------------------------- /network.txt: -------------------------------------------------------------------------------- 1 | net047_255_128_q6 2 | -------------------------------------------------------------------------------- /src/3rdparty/zstd/zstd_errors.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under both the BSD-style license (found in the 6 | * LICENSE file in the root directory of this source tree) and the GPLv2 (found 7 | * in the COPYING file in the root directory of this source tree). 8 | * You may select, at your option, one of the above-listed licenses. 9 | */ 10 | 11 | #ifndef ZSTD_ERRORS_H_398273423 12 | #define ZSTD_ERRORS_H_398273423 13 | 14 | #if defined (__cplusplus) 15 | extern "C" { 16 | #endif 17 | 18 | /*===== dependency =====*/ 19 | #include /* size_t */ 20 | 21 | 22 | /* ===== ZSTDERRORLIB_API : control library symbols visibility ===== */ 23 | #ifndef ZSTDERRORLIB_VISIBLE 24 | /* Backwards compatibility with old macro name */ 25 | # ifdef ZSTDERRORLIB_VISIBILITY 26 | # define ZSTDERRORLIB_VISIBLE ZSTDERRORLIB_VISIBILITY 27 | # elif defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__) 28 | # define ZSTDERRORLIB_VISIBLE __attribute__ ((visibility ("default"))) 29 | # else 30 | # define ZSTDERRORLIB_VISIBLE 31 | # endif 32 | #endif 33 | 34 | #ifndef ZSTDERRORLIB_HIDDEN 35 | # if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__) 36 | # define ZSTDERRORLIB_HIDDEN __attribute__ ((visibility ("hidden"))) 37 | # else 38 | # define ZSTDERRORLIB_HIDDEN 39 | # endif 40 | #endif 41 | 42 | #if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1) 43 | # define ZSTDERRORLIB_API __declspec(dllexport) ZSTDERRORLIB_VISIBLE 44 | #elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1) 45 | # define ZSTDERRORLIB_API __declspec(dllimport) ZSTDERRORLIB_VISIBLE /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ 46 | #else 47 | # define ZSTDERRORLIB_API ZSTDERRORLIB_VISIBLE 48 | #endif 49 | 50 | /*-********************************************* 51 | * Error codes list 52 | *-********************************************* 53 | * Error codes _values_ are pinned down since v1.3.1 only. 54 | * Therefore, don't rely on values if you may link to any version < v1.3.1. 55 | * 56 | * Only values < 100 are considered stable. 57 | * 58 | * note 1 : this API shall be used with static linking only. 59 | * dynamic linking is not yet officially supported. 60 | * note 2 : Prefer relying on the enum than on its value whenever possible 61 | * This is the only supported way to use the error list < v1.3.1 62 | * note 3 : ZSTD_isError() is always correct, whatever the library version. 63 | **********************************************/ 64 | typedef enum { 65 | ZSTD_error_no_error = 0, 66 | ZSTD_error_GENERIC = 1, 67 | ZSTD_error_prefix_unknown = 10, 68 | ZSTD_error_version_unsupported = 12, 69 | ZSTD_error_frameParameter_unsupported = 14, 70 | ZSTD_error_frameParameter_windowTooLarge = 16, 71 | ZSTD_error_corruption_detected = 20, 72 | ZSTD_error_checksum_wrong = 22, 73 | ZSTD_error_literals_headerWrong = 24, 74 | ZSTD_error_dictionary_corrupted = 30, 75 | ZSTD_error_dictionary_wrong = 32, 76 | ZSTD_error_dictionaryCreation_failed = 34, 77 | ZSTD_error_parameter_unsupported = 40, 78 | ZSTD_error_parameter_combination_unsupported = 41, 79 | ZSTD_error_parameter_outOfBound = 42, 80 | ZSTD_error_tableLog_tooLarge = 44, 81 | ZSTD_error_maxSymbolValue_tooLarge = 46, 82 | ZSTD_error_maxSymbolValue_tooSmall = 48, 83 | ZSTD_error_stabilityCondition_notRespected = 50, 84 | ZSTD_error_stage_wrong = 60, 85 | ZSTD_error_init_missing = 62, 86 | ZSTD_error_memory_allocation = 64, 87 | ZSTD_error_workSpace_tooSmall= 66, 88 | ZSTD_error_dstSize_tooSmall = 70, 89 | ZSTD_error_srcSize_wrong = 72, 90 | ZSTD_error_dstBuffer_null = 74, 91 | ZSTD_error_noForwardProgress_destFull = 80, 92 | ZSTD_error_noForwardProgress_inputEmpty = 82, 93 | /* following error codes are __NOT STABLE__, they can be removed or changed in future versions */ 94 | ZSTD_error_frameIndex_tooLarge = 100, 95 | ZSTD_error_seekableIO = 102, 96 | ZSTD_error_dstBuffer_wrong = 104, 97 | ZSTD_error_srcBuffer_wrong = 105, 98 | ZSTD_error_sequenceProducer_failed = 106, 99 | ZSTD_error_externalSequences_invalid = 107, 100 | ZSTD_error_maxCode = 120 /* never EVER use this value directly, it can change in future versions! Use ZSTD_isError() instead */ 101 | } ZSTD_ErrorCode; 102 | 103 | /*! ZSTD_getErrorCode() : 104 | convert a `size_t` function result into a `ZSTD_ErrorCode` enum type, 105 | which can be used to compare with enum list published above */ 106 | ZSTDERRORLIB_API ZSTD_ErrorCode ZSTD_getErrorCode(size_t functionResult); 107 | ZSTDERRORLIB_API const char* ZSTD_getErrorString(ZSTD_ErrorCode code); /**< Same as ZSTD_getErrorName, but using a `ZSTD_ErrorCode` enum argument */ 108 | 109 | 110 | #if defined (__cplusplus) 111 | } 112 | #endif 113 | 114 | #endif /* ZSTD_ERRORS_H_398273423 */ 115 | -------------------------------------------------------------------------------- /src/arch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include 24 | 25 | #if defined(SP_NATIVE) 26 | // cannot expand a macro to defined() 27 | #if __BMI2__ && defined(SP_FAST_PEXT) 28 | #define SP_HAS_BMI2 1 29 | #else 30 | #define SP_HAS_BMI2 0 31 | #endif 32 | #if !defined(SP_DISABLE_AVX512) 33 | #define SP_HAS_VNNI512 __AVX512VNNI__ 34 | #define SP_HAS_AVX512 (__AVX512F__ && (__AVX512BW__ || __AVX512VNNI__)) 35 | #else 36 | #define SP_HAS_VNNI512 0 37 | #define SP_HAS_AVX512 0 38 | #endif 39 | #define SP_HAS_VNNI256 0 // slowdown on any cpu that would use it 40 | #define SP_HAS_AVX2 __AVX2__ 41 | #define SP_HAS_POPCNT __POPCNT__ 42 | #define SP_HAS_NEON __ARM_NEON 43 | #if !defined(SP_DISABLE_NEON_DOTPROD) 44 | #define SP_HAS_NEON_DOTPROD (__ARM_ARCH >= 8) 45 | #else 46 | #define SP_HAS_NEON_DOTPROD 0 47 | #endif 48 | #elif defined(SP_VNNI512) 49 | #define SP_HAS_BMI2 1 50 | #define SP_HAS_VNNI512 1 51 | #define SP_HAS_AVX512 1 52 | #define SP_HAS_VNNI256 1 53 | #define SP_HAS_AVX2 1 54 | #define SP_HAS_POPCNT 1 55 | #define SP_HAS_NEON 0 56 | #define SP_HAS_NEON_DOTPROD 0 57 | #elif defined(SP_AVX2_BMI2) 58 | #define SP_HAS_BMI2 1 59 | #define SP_HAS_VNNI512 0 60 | #define SP_HAS_AVX512 0 61 | #define SP_HAS_VNNI256 0 62 | #define SP_HAS_AVX2 1 63 | #define SP_HAS_POPCNT 1 64 | #define SP_HAS_NEON 0 65 | #define SP_HAS_NEON_DOTPROD 0 66 | #elif defined(SP_AVX2) 67 | #define SP_HAS_BMI2 0 68 | #define SP_HAS_VNNI512 0 69 | #define SP_HAS_AVX512 0 70 | #define SP_HAS_VNNI256 0 71 | #define SP_HAS_AVX2 1 72 | #define SP_HAS_POPCNT 1 73 | #define SP_HAS_NEON 0 74 | #define SP_HAS_NEON_DOTPROD 0 75 | #else 76 | #error no arch specified 77 | #endif 78 | 79 | namespace stormphrax 80 | { 81 | #ifdef __cpp_lib_hardware_interference_size 82 | constexpr auto CacheLineSize = std::hardware_destructive_interference_size; 83 | #else 84 | constexpr usize CacheLineSize = 64; 85 | #endif 86 | } 87 | -------------------------------------------------------------------------------- /src/attacks/attacks.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | #include 25 | 26 | #include "../core.h" 27 | #include "../bitboard.h" 28 | #include "util.h" 29 | #include "../util/bits.h" 30 | 31 | #if SP_HAS_BMI2 32 | #include "bmi2/attacks.h" 33 | #else 34 | #include "black_magic/attacks.h" 35 | #endif 36 | 37 | namespace stormphrax::attacks 38 | { 39 | constexpr auto KnightAttacks = [] 40 | { 41 | std::array dst{}; 42 | 43 | for (usize i = 0; i < dst.size(); ++i) 44 | { 45 | const auto bit = Bitboard::fromSquare(static_cast(i)); 46 | 47 | auto &attacks = dst[i]; 48 | 49 | attacks |= bit.shiftUpUpLeft(); 50 | attacks |= bit.shiftUpUpRight(); 51 | attacks |= bit.shiftUpLeftLeft(); 52 | attacks |= bit.shiftUpRightRight(); 53 | attacks |= bit.shiftDownLeftLeft(); 54 | attacks |= bit.shiftDownRightRight(); 55 | attacks |= bit.shiftDownDownLeft(); 56 | attacks |= bit.shiftDownDownRight(); 57 | } 58 | 59 | return dst; 60 | }(); 61 | 62 | constexpr auto KingAttacks = [] 63 | { 64 | std::array dst{}; 65 | 66 | for (usize i = 0; i < dst.size(); ++i) 67 | { 68 | const auto bit = Bitboard::fromSquare(static_cast(i)); 69 | 70 | auto &attacks = dst[i]; 71 | 72 | attacks |= bit.shiftUp(); 73 | attacks |= bit.shiftDown(); 74 | attacks |= bit.shiftLeft(); 75 | attacks |= bit.shiftRight(); 76 | attacks |= bit.shiftUpLeft(); 77 | attacks |= bit.shiftUpRight(); 78 | attacks |= bit.shiftDownLeft(); 79 | attacks |= bit.shiftDownRight(); 80 | } 81 | 82 | return dst; 83 | }(); 84 | 85 | template 86 | consteval auto generatePawnAttacks() 87 | { 88 | std::array dst{}; 89 | 90 | for (usize i = 0; i < dst.size(); ++i) 91 | { 92 | const auto bit = Bitboard::fromSquare(static_cast(i)); 93 | 94 | dst[i] |= bit.shiftUpLeftRelative(); 95 | dst[i] |= bit.shiftUpRightRelative(); 96 | } 97 | 98 | return dst; 99 | } 100 | 101 | constexpr auto BlackPawnAttacks = generatePawnAttacks(); 102 | constexpr auto WhitePawnAttacks = generatePawnAttacks(); 103 | 104 | constexpr auto getKnightAttacks(Square src) 105 | { 106 | return KnightAttacks[static_cast(src)]; 107 | } 108 | 109 | constexpr auto getKingAttacks(Square src) 110 | { 111 | return KingAttacks[static_cast(src)]; 112 | } 113 | 114 | constexpr auto getPawnAttacks(Square src, Color color) 115 | { 116 | const auto &attacks = color == Color::White ? WhitePawnAttacks : BlackPawnAttacks; 117 | return attacks[static_cast(src)]; 118 | } 119 | 120 | inline auto getQueenAttacks(Square src, Bitboard occupancy) 121 | { 122 | return getRookAttacks(src, occupancy) 123 | | getBishopAttacks(src, occupancy); 124 | } 125 | 126 | inline auto getNonPawnPieceAttacks(PieceType piece, Square src, Bitboard occupancy = Bitboard{}) 127 | { 128 | assert(piece != PieceType::None); 129 | assert(piece != PieceType::Pawn); 130 | 131 | switch (piece) 132 | { 133 | case PieceType::Knight: return getKnightAttacks(src); 134 | case PieceType::Bishop: return getBishopAttacks(src, occupancy); 135 | case PieceType::Rook: return getRookAttacks(src, occupancy); 136 | case PieceType::Queen: return getQueenAttacks(src, occupancy); 137 | case PieceType::King: return getKingAttacks(src); 138 | default: __builtin_unreachable(); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/attacks/black_magic/attacks.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "../attacks.h" 20 | 21 | #if !SP_HAS_BMI2 22 | namespace stormphrax::attacks 23 | { 24 | using namespace black_magic; 25 | 26 | namespace 27 | { 28 | auto generateRookAttacks() 29 | { 30 | std::array dst{}; 31 | 32 | for (u32 square = 0; square < 64; ++square) 33 | { 34 | const auto &data = RookData.data[square]; 35 | 36 | const auto invMask = ~data.mask; 37 | const auto maxEntries = 1 << invMask.popcount(); 38 | 39 | for (u32 i = 0; i < maxEntries; ++i) 40 | { 41 | const auto occupancy = util::pdep(i, invMask); 42 | const auto idx = getRookIdx(occupancy, static_cast(square)); 43 | 44 | if (!dst[data.offset + idx].empty()) 45 | continue; 46 | 47 | for (const auto dir : {offsets::Up, offsets::Down, offsets::Left, offsets::Right}) 48 | { 49 | dst[data.offset + idx] 50 | |= internal::generateSlidingAttacks(static_cast(square), dir, occupancy); 51 | } 52 | } 53 | } 54 | 55 | return dst; 56 | } 57 | 58 | auto generateBishopAttacks() 59 | { 60 | std::array dst{}; 61 | 62 | for (u32 square = 0; square < 64; ++square) 63 | { 64 | const auto &data = BishopData.data[square]; 65 | 66 | const auto invMask = ~data.mask; 67 | const auto maxEntries = 1 << invMask.popcount(); 68 | 69 | for (u32 i = 0; i < maxEntries; ++i) 70 | { 71 | const auto occupancy = util::pdep(i, invMask); 72 | const auto idx = getBishopIdx(occupancy, static_cast(square)); 73 | 74 | if (!dst[data.offset + idx].empty()) 75 | continue; 76 | 77 | for (const auto dir : { 78 | offsets::UpLeft, offsets::UpRight, offsets::DownLeft, offsets::DownRight 79 | }) 80 | { 81 | dst[data.offset + idx] 82 | |= internal::generateSlidingAttacks(static_cast(square), dir, occupancy); 83 | } 84 | } 85 | } 86 | 87 | return dst; 88 | } 89 | } 90 | 91 | const std::array RookAttacks = generateRookAttacks(); 92 | const std::array BishopAttacks = generateBishopAttacks(); 93 | } 94 | #endif // !SP_HAS_BMI2 95 | -------------------------------------------------------------------------------- /src/attacks/black_magic/attacks.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../../types.h" 22 | 23 | #include 24 | 25 | #include "../../core.h" 26 | #include "../../bitboard.h" 27 | #include "../util.h" 28 | #include "data.h" 29 | #include "../../util/bits.h" 30 | 31 | namespace stormphrax::attacks 32 | { 33 | extern const std::array RookAttacks; 34 | extern const std::array BishopAttacks; 35 | 36 | [[nodiscard]] inline auto getRookIdx(Bitboard occupancy, Square src) 37 | { 38 | const auto s = static_cast(src); 39 | 40 | const auto &data = black_magic::RookData.data[s]; 41 | 42 | const auto magic = black_magic::RookMagics[s]; 43 | const auto shift = black_magic::RookShifts[s]; 44 | 45 | return ((occupancy | data.mask) * magic) >> shift; 46 | } 47 | 48 | [[nodiscard]] inline auto getBishopIdx(Bitboard occupancy, Square src) 49 | { 50 | const auto s = static_cast(src); 51 | 52 | const auto &data = black_magic::BishopData.data[s]; 53 | 54 | const auto magic = black_magic::BishopMagics[s]; 55 | const auto shift = black_magic::BishopShifts[s]; 56 | 57 | return ((occupancy | data.mask) * magic) >> shift; 58 | } 59 | 60 | [[nodiscard]] inline auto getRookAttacks(Square src, Bitboard occupancy) 61 | { 62 | const auto s = static_cast(src); 63 | 64 | const auto &data = black_magic::RookData.data[s]; 65 | const auto idx = getRookIdx(occupancy, src); 66 | 67 | return RookAttacks[data.offset + idx]; 68 | } 69 | 70 | [[nodiscard]] inline auto getBishopAttacks(Square src, Bitboard occupancy) 71 | { 72 | const auto s = static_cast(src); 73 | 74 | const auto &data = black_magic::BishopData.data[s]; 75 | const auto idx = getBishopIdx(occupancy, src); 76 | 77 | return BishopAttacks[data.offset + idx]; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/attacks/bmi2/attacks.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "../attacks.h" 20 | 21 | #if SP_HAS_BMI2 22 | namespace stormphrax::attacks 23 | { 24 | using namespace bmi2; 25 | 26 | namespace 27 | { 28 | auto generateRookAttacks() 29 | { 30 | std::array dst{}; 31 | 32 | for (u32 square = 0; square < 64; ++square) 33 | { 34 | const auto &data = RookData.data[square]; 35 | const auto entries = 1 << data.srcMask.popcount(); 36 | 37 | for (u32 i = 0; i < entries; ++i) 38 | { 39 | const auto occupancy = util::pdep(i, data.srcMask); 40 | 41 | Bitboard attacks{}; 42 | 43 | for (const auto dir : {offsets::Up, offsets::Down, offsets::Left, offsets::Right}) 44 | { 45 | attacks |= internal::generateSlidingAttacks(static_cast(square), dir, occupancy); 46 | } 47 | 48 | dst[data.offset + i] = static_cast(util::pext(attacks, data.dstMask)); 49 | } 50 | } 51 | 52 | return dst; 53 | } 54 | 55 | auto generateBishopAttacks() 56 | { 57 | std::array dst{}; 58 | 59 | for (u32 square = 0; square < 64; ++square) 60 | { 61 | const auto &data = BishopData.data[square]; 62 | const auto entries = 1 << data.mask.popcount(); 63 | 64 | for (u32 i = 0; i < entries; ++i) 65 | { 66 | const auto occupancy = util::pdep(i, data.mask); 67 | 68 | for (const auto dir 69 | : {offsets::UpLeft, offsets::UpRight, offsets::DownLeft, offsets::DownRight}) 70 | { 71 | dst[data.offset + i] 72 | |= internal::generateSlidingAttacks(static_cast(square), dir, occupancy); 73 | } 74 | } 75 | } 76 | 77 | return dst; 78 | } 79 | } 80 | 81 | const std::array< u16, RookData.tableSize> RookAttacks = generateRookAttacks(); 82 | const std::array BishopAttacks = generateBishopAttacks(); 83 | } 84 | #endif // SP_HAS_BMI2 85 | -------------------------------------------------------------------------------- /src/attacks/bmi2/attacks.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../../types.h" 22 | 23 | #include 24 | 25 | #include "../../core.h" 26 | #include "../../bitboard.h" 27 | #include "../util.h" 28 | #include "data.h" 29 | #include "../../util/bits.h" 30 | 31 | namespace stormphrax::attacks 32 | { 33 | extern const std::array< u16, bmi2:: RookData.tableSize> RookAttacks; 34 | extern const std::array BishopAttacks; 35 | 36 | inline auto getRookAttacks(Square src, Bitboard occupancy) -> Bitboard 37 | { 38 | const auto s = static_cast(src); 39 | 40 | const auto &data = bmi2::RookData.data[s]; 41 | 42 | const auto idx = util::pext(occupancy, data.srcMask); 43 | const auto attacks = util::pdep(RookAttacks[data.offset + idx], data.dstMask); 44 | 45 | return attacks; 46 | } 47 | 48 | inline auto getBishopAttacks(Square src, Bitboard occupancy) 49 | { 50 | const auto s = static_cast(src); 51 | 52 | const auto &data = bmi2::BishopData.data[s]; 53 | const auto idx = util::pext(occupancy, data.mask); 54 | 55 | return BishopAttacks[data.offset + idx]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/attacks/bmi2/data.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../../types.h" 22 | 23 | #include 24 | 25 | #include "../../core.h" 26 | #include "../../bitboard.h" 27 | #include "../util.h" 28 | 29 | // ignore the duplication pls ty :3 30 | namespace stormphrax::attacks::bmi2 31 | { 32 | struct RookSquareData 33 | { 34 | Bitboard srcMask; 35 | Bitboard dstMask; 36 | u32 offset; 37 | }; 38 | 39 | struct RookData_ 40 | { 41 | std::array data; 42 | u32 tableSize; 43 | }; 44 | 45 | struct BishopSquareData 46 | { 47 | Bitboard mask; 48 | u32 offset; 49 | }; 50 | 51 | struct BishopData_ 52 | { 53 | std::array data; 54 | u32 tableSize; 55 | }; 56 | 57 | constexpr auto RookData = [] 58 | { 59 | RookData_ dst{}; 60 | 61 | for (u32 i = 0; i < 64; ++i) 62 | { 63 | const auto square = static_cast(i); 64 | 65 | for (const auto dir : { 66 | offsets::Up, 67 | offsets::Down, 68 | offsets::Left, 69 | offsets::Right 70 | }) 71 | { 72 | const auto attacks = internal::generateSlidingAttacks(square, dir, 0); 73 | 74 | dst.data[i].srcMask |= attacks & ~internal::edges(dir); 75 | dst.data[i].dstMask |= attacks; 76 | } 77 | 78 | dst.data[i].offset = dst.tableSize; 79 | dst.tableSize += 1 << dst.data[i].srcMask.popcount(); 80 | } 81 | 82 | return dst; 83 | }(); 84 | 85 | constexpr auto BishopData = [] 86 | { 87 | BishopData_ dst{}; 88 | 89 | for (u32 i = 0; i < 64; ++i) 90 | { 91 | const auto square = static_cast(i); 92 | 93 | for (const auto dir : { 94 | offsets::UpLeft, 95 | offsets::UpRight, 96 | offsets::DownLeft, 97 | offsets::DownRight 98 | }) 99 | { 100 | const auto attacks = internal::generateSlidingAttacks(square, dir, 0); 101 | dst.data[i].mask |= attacks & ~internal::edges(dir); 102 | } 103 | 104 | dst.data[i].offset = dst.tableSize; 105 | dst.tableSize += 1 << dst.data[i].mask.popcount(); 106 | } 107 | 108 | return dst; 109 | }(); 110 | } 111 | -------------------------------------------------------------------------------- /src/attacks/util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | 25 | #include "../core.h" 26 | #include "../bitboard.h" 27 | #include "../util/cemath.h" 28 | 29 | namespace stormphrax::attacks 30 | { 31 | namespace internal 32 | { 33 | constexpr auto edges(i32 dir) 34 | { 35 | switch (dir) 36 | { 37 | case offsets:: Up: return boards::Rank8 ; 38 | case offsets:: Down: return boards::Rank1 ; 39 | case offsets:: Left: return boards::FileA ; 40 | case offsets:: Right: return boards::FileH ; 41 | case offsets:: UpLeft: return boards::FileA | boards::Rank8; 42 | case offsets:: UpRight: return boards::FileH | boards::Rank8; 43 | case offsets:: DownLeft: return boards::FileA | boards::Rank1; 44 | case offsets::DownRight: return boards::FileH | boards::Rank1; 45 | default: __builtin_unreachable(); // don't 46 | } 47 | } 48 | 49 | constexpr auto generateSlidingAttacks(Square src, i32 dir, Bitboard occupancy) 50 | { 51 | Bitboard dst{}; 52 | 53 | auto blockers = edges(dir); 54 | 55 | const bool right = dir < 0; 56 | const auto shift = util::abs(dir); 57 | 58 | auto bit = squareBit(src); 59 | 60 | if (!(blockers & bit).empty()) 61 | return dst; 62 | 63 | blockers |= occupancy; 64 | 65 | do 66 | { 67 | if (right) 68 | dst |= bit >>= shift; 69 | else dst |= bit <<= shift; 70 | } while (!(bit & blockers)); 71 | 72 | return dst; 73 | } 74 | } 75 | 76 | template 77 | consteval auto generateEmptyBoardAttacks() 78 | { 79 | std::array dst{}; 80 | 81 | for (i32 square = 0; square < 64; ++square) 82 | { 83 | for (const auto dir : {Dirs...}) 84 | { 85 | const auto attacks = internal::generateSlidingAttacks(static_cast(square), dir, 0); 86 | dst[square] |= attacks; 87 | } 88 | } 89 | 90 | return dst; 91 | } 92 | 93 | constexpr auto EmptyBoardRooks 94 | = generateEmptyBoardAttacks(); 95 | constexpr auto EmptyBoardBishops 96 | = generateEmptyBoardAttacks(); 97 | 98 | template 99 | consteval auto genAllSlidingAttacks(Square src, Bitboard occupancy) 100 | { 101 | Bitboard dst{}; 102 | 103 | for (const auto dir : {Dirs...}) 104 | { 105 | dst |= internal::generateSlidingAttacks(src, dir, occupancy); 106 | } 107 | 108 | return dst; 109 | } 110 | 111 | consteval auto genRookAttacks(Square src, Bitboard occupancy) 112 | { 113 | return genAllSlidingAttacks< 114 | offsets::Up, 115 | offsets::Down, 116 | offsets::Left, 117 | offsets::Right 118 | >(src, occupancy); 119 | } 120 | 121 | consteval auto genBishopAttacks(Square src, Bitboard occupancy) 122 | { 123 | return genAllSlidingAttacks< 124 | offsets::UpLeft, 125 | offsets::UpRight, 126 | offsets::DownLeft, 127 | offsets::DownRight 128 | >(src, occupancy); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/bench.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "bench.h" 20 | 21 | #include 22 | 23 | #include "position/position.h" 24 | #include "stats.h" 25 | 26 | namespace stormphrax::bench 27 | { 28 | auto run(search::Searcher &searcher, i32 depth) -> void 29 | { 30 | const std::array Fens { // fens from alexandria, ultimately from bitgenie 31 | "r3k2r/2pb1ppp/2pp1q2/p7/1nP1B3/1P2P3/P2N1PPP/R2QK2R w KQkq - 0 14", 32 | "4rrk1/2p1b1p1/p1p3q1/4p3/2P2n1p/1P1NR2P/PB3PP1/3R1QK1 b - - 2 24", 33 | "r3qbrk/6p1/2b2pPp/p3pP1Q/PpPpP2P/3P1B2/2PB3K/R5R1 w - - 16 42", 34 | "6k1/1R3p2/6p1/2Bp3p/3P2q1/P7/1P2rQ1K/5R2 b - - 4 44", 35 | "8/8/1p2k1p1/3p3p/1p1P1P1P/1P2PK2/8/8 w - - 3 54", 36 | "7r/2p3k1/1p1p1qp1/1P1Bp3/p1P2r1P/P7/4R3/Q4RK1 w - - 0 36", 37 | "r1bq1rk1/pp2b1pp/n1pp1n2/3P1p2/2P1p3/2N1P2N/PP2BPPP/R1BQ1RK1 b - - 2 10", 38 | "3r3k/2r4p/1p1b3q/p4P2/P2Pp3/1B2P3/3BQ1RP/6K1 w - - 3 87", 39 | "2r4r/1p4k1/1Pnp4/3Qb1pq/8/4BpPp/5P2/2RR1BK1 w - - 0 42", 40 | "4q1bk/6b1/7p/p1p4p/PNPpP2P/KN4P1/3Q4/4R3 b - - 0 37", 41 | "2q3r1/1r2pk2/pp3pp1/2pP3p/P1Pb1BbP/1P4Q1/R3NPP1/4R1K1 w - - 2 34", 42 | "1r2r2k/1b4q1/pp5p/2pPp1p1/P3Pn2/1P1B1Q1P/2R3P1/4BR1K b - - 1 37", 43 | "r3kbbr/pp1n1p1P/3ppnp1/q5N1/1P1pP3/P1N1B3/2P1QP2/R3KB1R b KQkq - 0 17", 44 | "8/6pk/2b1Rp2/3r4/1R1B2PP/P5K1/8/2r5 b - - 16 42", 45 | "1r4k1/4ppb1/2n1b1qp/pB4p1/1n1BP1P1/7P/2PNQPK1/3RN3 w - - 8 29", 46 | "8/p2B4/PkP5/4p1pK/4Pb1p/5P2/8/8 w - - 29 68", 47 | "3r4/ppq1ppkp/4bnp1/2pN4/2P1P3/1P4P1/PQ3PBP/R4K2 b - - 2 20", 48 | "5rr1/4n2k/4q2P/P1P2n2/3B1p2/4pP2/2N1P3/1RR1K2Q w - - 1 49", 49 | "1r5k/2pq2p1/3p3p/p1pP4/4QP2/PP1R3P/6PK/8 w - - 1 51", 50 | "q5k1/5ppp/1r3bn1/1B6/P1N2P2/BQ2P1P1/5K1P/8 b - - 2 34", 51 | "r1b2k1r/5n2/p4q2/1ppn1Pp1/3pp1p1/NP2P3/P1PPBK2/1RQN2R1 w - - 0 22", 52 | "r1bqk2r/pppp1ppp/5n2/4b3/4P3/P1N5/1PP2PPP/R1BQKB1R w KQkq - 0 5", 53 | "r1bqr1k1/pp1p1ppp/2p5/8/3N1Q2/P2BB3/1PP2PPP/R3K2n b Q - 1 12", 54 | "r1bq2k1/p4r1p/1pp2pp1/3p4/1P1B3Q/P2B1N2/2P3PP/4R1K1 b - - 2 19", 55 | "r4qk1/6r1/1p4p1/2ppBbN1/1p5Q/P7/2P3PP/5RK1 w - - 2 25", 56 | "r7/6k1/1p6/2pp1p2/7Q/8/p1P2K1P/8 w - - 0 32", 57 | "r3k2r/ppp1pp1p/2nqb1pn/3p4/4P3/2PP4/PP1NBPPP/R2QK1NR w KQkq - 1 5", 58 | "3r1rk1/1pp1pn1p/p1n1q1p1/3p4/Q3P3/2P5/PP1NBPPP/4RRK1 w - - 0 12", 59 | "5rk1/1pp1pn1p/p3Brp1/8/1n6/5N2/PP3PPP/2R2RK1 w - - 2 20", 60 | "8/1p2pk1p/p1p1r1p1/3n4/8/5R2/PP3PPP/4R1K1 b - - 3 27", 61 | "8/4pk2/1p1r2p1/p1p4p/Pn5P/3R4/1P3PP1/4RK2 w - - 1 33", 62 | "8/5k2/1pnrp1p1/p1p4p/P6P/4R1PK/1P3P2/4R3 b - - 1 38", 63 | "8/8/1p1kp1p1/p1pr1n1p/P6P/1R4P1/1P3PK1/1R6 b - - 15 45", 64 | "8/8/1p1k2p1/p1prp2p/P2n3P/6P1/1P1R1PK1/4R3 b - - 5 49", 65 | "8/8/1p4p1/p1p2k1p/P2npP1P/4K1P1/1P6/3R4 w - - 6 54", 66 | "8/8/1p4p1/p1p2k1p/P2n1P1P/4K1P1/1P6/6R1 b - - 6 59", 67 | "8/5k2/1p4p1/p1pK3p/P2n1P1P/6P1/1P6/4R3 b - - 14 63", 68 | "8/1R6/1p1K1kp1/p6p/P1p2P1P/6P1/1Pn5/8 w - - 0 67", 69 | "1rb1rn1k/p3q1bp/2p3p1/2p1p3/2P1P2N/PP1RQNP1/1B3P2/4R1K1 b - - 4 23", 70 | "4rrk1/pp1n1pp1/q5p1/P1pP4/2n3P1/7P/1P3PB1/R1BQ1RK1 w - - 3 22", 71 | "r2qr1k1/pb1nbppp/1pn1p3/2ppP3/3P4/2PB1NN1/PP3PPP/R1BQR1K1 w - - 4 12", 72 | "2r2k2/8/4P1R1/1p6/8/P4K1N/7b/2B5 b - - 0 55", 73 | "6k1/5pp1/8/2bKP2P/2P5/p4PNb/B7/8 b - - 1 44", 74 | "2rqr1k1/1p3p1p/p2p2p1/P1nPb3/2B1P3/5P2/1PQ2NPP/R1R4K w - - 3 25", 75 | "r1b2rk1/p1q1ppbp/6p1/2Q5/8/4BP2/PPP3PP/2KR1B1R b - - 2 14", 76 | "6r1/5k2/p1b1r2p/1pB1p1p1/1Pp3PP/2P1R1K1/2P2P2/3R4 w - - 1 36", 77 | "rnbqkb1r/pppppppp/5n2/8/2PP4/8/PP2PPPP/RNBQKBNR b KQkq - 0 2", 78 | "2rr2k1/1p4bp/p1q1p1p1/4Pp1n/2PB4/1PN3P1/P3Q2P/2RR2K1 w - f6 0 20", 79 | "3br1k1/p1pn3p/1p3n2/5pNq/2P1p3/1PN3PP/P2Q1PB1/4R1K1 w - - 0 23", 80 | "2r2b2/5p2/5k2/p1r1pP2/P2pB3/1P3P2/K1P3R1/7R w - - 23 93" 81 | }; 82 | 83 | searcher.newGame(); 84 | 85 | usize nodes{}; 86 | f64 time{}; 87 | 88 | for (const auto &fen : Fens) 89 | { 90 | const auto pos = *Position::fromFen(fen); 91 | 92 | search::BenchData data{}; 93 | searcher.runBench(data, pos, depth); 94 | 95 | nodes += data.search.nodes; 96 | time += data.time; 97 | } 98 | 99 | std::cout << "info string " << time << " seconds" << std::endl; 100 | std::cout << nodes << " nodes " << static_cast(static_cast(nodes) / time) << " nps" << std::endl; 101 | 102 | stats::print(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/bench.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include "search.h" 24 | 25 | namespace stormphrax::bench 26 | { 27 | #ifdef SP_PGO_PROFILE 28 | constexpr i32 DefaultBenchDepth = 10; 29 | #else 30 | constexpr i32 DefaultBenchDepth = 14; 31 | #endif 32 | 33 | constexpr usize DefaultBenchTtSize = 16; 34 | 35 | auto run(search::Searcher &searcher, i32 depth = DefaultBenchDepth) -> void; 36 | } 37 | -------------------------------------------------------------------------------- /src/correction.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include 24 | #include 25 | 26 | #include "core.h" 27 | #include "position/position.h" 28 | #include "util/multi_array.h" 29 | #include "util/cemath.h" 30 | #include "search_fwd.h" 31 | #include "tunable.h" 32 | 33 | namespace stormphrax 34 | { 35 | class CorrectionHistoryTable 36 | { 37 | public: 38 | CorrectionHistoryTable() = default; 39 | ~CorrectionHistoryTable() = default; 40 | 41 | inline auto clear() 42 | { 43 | std::memset(&m_pawnTable, 0, sizeof(m_pawnTable)); 44 | std::memset(&m_blackNonPawnTable, 0, sizeof(m_blackNonPawnTable)); 45 | std::memset(&m_whiteNonPawnTable, 0, sizeof(m_whiteNonPawnTable)); 46 | std::memset(&m_majorTable, 0, sizeof(m_majorTable)); 47 | std::memset(&m_contTable, 0, sizeof(m_contTable)); 48 | } 49 | 50 | inline auto update(const Position &pos, std::span moves, 51 | i32 ply, i32 depth, Score searchScore, Score staticEval) 52 | { 53 | const auto bonus = std::clamp((searchScore - staticEval) * depth / 8, -MaxBonus, MaxBonus); 54 | 55 | const auto stm = static_cast(pos.toMove()); 56 | 57 | m_pawnTable[stm][pos.pawnKey() % Entries].update(bonus); 58 | m_blackNonPawnTable[stm][pos.blackNonPawnKey() % Entries].update(bonus); 59 | m_whiteNonPawnTable[stm][pos.whiteNonPawnKey() % Entries].update(bonus); 60 | m_majorTable[stm][pos.majorKey() % Entries].update(bonus); 61 | 62 | if (ply >= 2) 63 | { 64 | const auto [moving2, dst2] = moves[ply - 2]; 65 | const auto [moving1, dst1] = moves[ply - 1]; 66 | 67 | if (moving2 != Piece::None && moving1 != Piece::None) 68 | m_contTable[stm][static_cast(pieceType(moving2))][static_cast(dst2)] 69 | [static_cast(pieceType(moving1))][static_cast(dst1)].update(bonus); 70 | } 71 | } 72 | 73 | [[nodiscard]] inline auto correct(const Position &pos, 74 | std::span moves, i32 ply, Score score) const 75 | { 76 | using namespace tunable; 77 | 78 | const auto stm = static_cast(pos.toMove()); 79 | 80 | const auto [blackNpWeight, whiteNpWeight] = pos.toMove() == Color::Black 81 | ? std::pair{ stmNonPawnCorrhistWeight(), nstmNonPawnCorrhistWeight()} 82 | : std::pair{nstmNonPawnCorrhistWeight(), stmNonPawnCorrhistWeight()}; 83 | 84 | i32 correction{}; 85 | 86 | correction += pawnCorrhistWeight() * m_pawnTable[stm][pos.pawnKey() % Entries]; 87 | correction += blackNpWeight * m_blackNonPawnTable[stm][pos.blackNonPawnKey() % Entries]; 88 | correction += whiteNpWeight * m_whiteNonPawnTable[stm][pos.whiteNonPawnKey() % Entries]; 89 | correction += majorCorrhistWeight() * m_majorTable[stm][pos.majorKey() % Entries]; 90 | 91 | if (ply >= 2) 92 | { 93 | const auto [moving2, dst2] = moves[ply - 2]; 94 | const auto [moving1, dst1] = moves[ply - 1]; 95 | 96 | if (moving2 != Piece::None && moving1 != Piece::None) 97 | correction += contCorrhistWeight() * m_contTable[stm] 98 | [static_cast(pieceType(moving2))][static_cast(dst2)] 99 | [static_cast(pieceType(moving1))][static_cast(dst1)]; 100 | } 101 | 102 | score += correction / 2048; 103 | 104 | return std::clamp(score, -ScoreWin + 1, ScoreWin - 1); 105 | } 106 | 107 | private: 108 | static constexpr usize Entries = 16384; 109 | 110 | static constexpr i32 Limit = 1024; 111 | static constexpr i32 MaxBonus = Limit / 4; 112 | 113 | struct Entry 114 | { 115 | i16 value{}; 116 | 117 | inline auto update(i32 bonus) -> void 118 | { 119 | value += bonus - value * std::abs(bonus) / Limit; 120 | } 121 | 122 | [[nodiscard]] inline operator i32() const 123 | { 124 | return value; 125 | } 126 | }; 127 | 128 | util::MultiArray m_pawnTable{}; 129 | util::MultiArray m_blackNonPawnTable{}; 130 | util::MultiArray m_whiteNonPawnTable{}; 131 | util::MultiArray m_majorTable{}; 132 | util::MultiArray m_contTable{}; 133 | }; 134 | } 135 | -------------------------------------------------------------------------------- /src/cuckoo.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "cuckoo.h" 20 | 21 | #include 22 | #include 23 | 24 | #include "keys.h" 25 | #include "attacks/attacks.h" 26 | 27 | namespace stormphrax::cuckoo 28 | { 29 | // https://web.archive.org/web/20201107002606/https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf 30 | // Implementation based on Stockfish's 31 | 32 | std::array keys{}; 33 | std::array moves{}; 34 | 35 | void init() 36 | { 37 | u32 count = 0; 38 | 39 | // skip pawns 40 | for (u32 p = static_cast(Piece::BlackKnight); 41 | p < static_cast(Piece::None); 42 | ++p) 43 | { 44 | const auto piece = static_cast(p); 45 | 46 | for (u32 s0 = 0; s0 < 64; ++s0) 47 | { 48 | const auto square0 = static_cast(s0); 49 | 50 | for (u32 s1 = s0 + 1; s1 < 64; ++s1) 51 | { 52 | const auto square1 = static_cast(s1); 53 | 54 | if (!attacks::getNonPawnPieceAttacks(pieceType(piece), square0)[square1]) 55 | continue; 56 | 57 | auto move = Move::standard(square0, square1); 58 | auto key = keys::pieceSquare(piece, square0) 59 | ^ keys::pieceSquare(piece, square1) 60 | ^ keys::color(); 61 | 62 | u32 slot = h1(key); 63 | 64 | while (true) 65 | { 66 | std::swap(keys[slot], key); 67 | std::swap(moves[slot], move); 68 | 69 | if (move == NullMove) 70 | break; 71 | 72 | slot = slot == h1(key) ? h2(key) : h1(key); 73 | } 74 | 75 | ++count; 76 | } 77 | } 78 | } 79 | 80 | assert(count == 3668); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/cuckoo.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include 24 | 25 | #include "move.h" 26 | 27 | namespace stormphrax::cuckoo 28 | { 29 | constexpr auto h1(u64 key) 30 | { 31 | return static_cast(key & 0x1FFF); 32 | } 33 | 34 | constexpr auto h2(u64 key) 35 | { 36 | return static_cast((key >> 16) & 0x1FFF); 37 | } 38 | 39 | extern std::array keys; 40 | extern std::array moves; 41 | 42 | void init(); 43 | } 44 | -------------------------------------------------------------------------------- /src/datagen/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | namespace stormphrax::datagen 24 | { 25 | enum class Outcome : u8 26 | { 27 | WhiteLoss = 0, 28 | Draw, 29 | WhiteWin 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/datagen/datagen.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | namespace stormphrax::datagen 29 | { 30 | auto run(const std::function &printUsage, const std::string &format, 31 | bool dfrc, const std::string &output, i32 threads, std::optional tbPath) -> i32; 32 | } 33 | -------------------------------------------------------------------------------- /src/datagen/fen.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "fen.h" 20 | 21 | namespace stormphrax::datagen 22 | { 23 | Fen::Fen() 24 | { 25 | m_positions.reserve(256); 26 | } 27 | 28 | auto Fen::start(const Position &initialPosition) -> void 29 | { 30 | m_positions.clear(); 31 | m_curr = initialPosition; 32 | } 33 | 34 | auto Fen::push(bool filtered, Move move, Score score) -> void 35 | { 36 | if (!filtered) 37 | m_positions.push_back(m_curr.toFen() + " | " + std::to_string(score)); 38 | m_curr = m_curr.applyMove(move); 39 | } 40 | 41 | auto Fen::writeAllWithOutcome(std::ostream &stream, Outcome outcome) -> usize 42 | { 43 | for (const auto &fen : m_positions) 44 | { 45 | stream << fen << " | "; 46 | 47 | switch (outcome) 48 | { 49 | case Outcome::WhiteLoss: 50 | stream << "0.0"; 51 | break; 52 | case Outcome::Draw: 53 | stream << "0.5"; 54 | break; 55 | case Outcome::WhiteWin: 56 | stream << "1.0"; 57 | break; 58 | } 59 | 60 | stream << '\n'; 61 | } 62 | 63 | return m_positions.size(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/datagen/fen.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | 25 | #include "format.h" 26 | #include "../position/position.h" 27 | #include "../util/u4array.h" 28 | 29 | namespace stormphrax::datagen 30 | { 31 | class Fen 32 | { 33 | public: 34 | Fen(); 35 | ~Fen() = default; 36 | 37 | static constexpr auto Extension = "txt"; 38 | 39 | auto start(const Position &initialPosition) -> void; 40 | auto push(bool filtered, Move move, Score score) -> void; 41 | auto writeAllWithOutcome(std::ostream &stream, Outcome outcome) -> usize; 42 | 43 | private: 44 | std::vector m_positions{}; 45 | Position m_curr; 46 | }; 47 | 48 | static_assert(OutputFormat); 49 | } 50 | -------------------------------------------------------------------------------- /src/datagen/format.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "common.h" 28 | #include "../core.h" 29 | #include "../position/position.h" 30 | #include "../move.h" 31 | 32 | namespace stormphrax::datagen 33 | { 34 | template 35 | concept OutputFormat = requires (T t, const Position &initialPosition, 36 | bool filtered, Move move, Score score, Outcome outcome, std::ostream &stream) 37 | { 38 | { T::Extension } -> std::convertible_to; 39 | t.start(initialPosition); 40 | t.push(filtered, move, score); 41 | { t.writeAllWithOutcome(stream, outcome) } -> std::same_as; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /src/datagen/marlinformat.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "marlinformat.h" 20 | 21 | namespace stormphrax::datagen 22 | { 23 | Marlinformat::Marlinformat() 24 | { 25 | m_positions.reserve(256); 26 | } 27 | 28 | auto Marlinformat::start(const Position &initialPosition) -> void 29 | { 30 | m_positions.clear(); 31 | m_curr = initialPosition; 32 | } 33 | 34 | auto Marlinformat::push(bool filtered, Move move, Score score) -> void 35 | { 36 | if (!filtered) 37 | m_positions.push_back(marlinformat::PackedBoard::pack(m_curr, static_cast(score))); 38 | m_curr = m_curr.applyMove(move); 39 | } 40 | 41 | auto Marlinformat::writeAllWithOutcome(std::ostream &stream, Outcome outcome) -> usize 42 | { 43 | for (auto &board : m_positions) 44 | { 45 | board.wdl = outcome; 46 | } 47 | 48 | stream.write(reinterpret_cast(m_positions.data()), 49 | static_cast(m_positions.size() * sizeof(marlinformat::PackedBoard))); 50 | 51 | return m_positions.size(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/datagen/marlinformat.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | 25 | #include "format.h" 26 | #include "../position/position.h" 27 | #include "../util/u4array.h" 28 | 29 | namespace stormphrax::datagen 30 | { 31 | namespace marlinformat 32 | { 33 | // https://github.com/jnlt3/marlinflow/blob/main/marlinformat/src/lib.rs 34 | struct __attribute__((packed)) PackedBoard 35 | { 36 | u64 occupancy; 37 | util::U4Array<32> pieces; 38 | u8 stmEpSquare; 39 | u8 halfmoveClock; 40 | u16 fullmoveNumber; 41 | i16 eval; 42 | Outcome wdl; 43 | [[maybe_unused]] u8 extra; 44 | 45 | [[nodiscard]] static auto pack(const Position &pos, i16 score) 46 | { 47 | static constexpr u8 UnmovedRook = 6; 48 | 49 | PackedBoard board{}; 50 | 51 | const auto castlingRooks = pos.castlingRooks(); 52 | const auto &boards = pos.boards(); 53 | 54 | auto occupancy = boards.bbs().occupancy(); 55 | board.occupancy = occupancy; 56 | 57 | usize i = 0; 58 | while (occupancy) 59 | { 60 | const auto square = occupancy.popLowestSquare(); 61 | const auto piece = boards.pieceAt(square); 62 | 63 | auto pieceId = static_cast(pieceType(piece)); 64 | 65 | if (pieceType(piece) == PieceType::Rook 66 | && (square == castlingRooks.black().kingside 67 | || square == castlingRooks.black().queenside 68 | || square == castlingRooks.white().kingside 69 | || square == castlingRooks.white().queenside)) 70 | pieceId = UnmovedRook; 71 | 72 | const u8 colorId = pieceColor(piece) == Color::Black ? (1 << 3) : 0; 73 | 74 | board.pieces[i++] = pieceId | colorId; 75 | } 76 | 77 | const u8 stm = pos.toMove() == Color::Black ? (1 << 7) : 0; 78 | 79 | const Square relativeEpSquare = pos.enPassant() == Square::None ? Square::None 80 | : toSquare(pos.toMove() == Color::Black ? 2 : 5, squareFile(pos.enPassant())); 81 | 82 | board.stmEpSquare = stm | static_cast(relativeEpSquare); 83 | board.halfmoveClock = pos.halfmove(); 84 | board.fullmoveNumber = pos.fullmove(); 85 | board.eval = score; 86 | 87 | return board; 88 | } 89 | }; 90 | } 91 | 92 | class Marlinformat 93 | { 94 | public: 95 | Marlinformat(); 96 | ~Marlinformat() = default; 97 | 98 | static constexpr auto Extension = "bin"; 99 | 100 | auto start(const Position &initialPosition) -> void; 101 | auto push(bool filtered, Move move, Score score) -> void; 102 | auto writeAllWithOutcome(std::ostream &stream, Outcome outcome) -> usize; 103 | 104 | private: 105 | std::vector m_positions{}; 106 | Position m_curr; 107 | }; 108 | 109 | static_assert(OutputFormat); 110 | } 111 | -------------------------------------------------------------------------------- /src/datagen/viriformat.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "viriformat.h" 20 | 21 | #include 22 | 23 | namespace stormphrax::datagen 24 | { 25 | Viriformat::Viriformat() 26 | { 27 | m_moves.reserve(256); 28 | } 29 | 30 | auto Viriformat::start(const Position &initialPosition) -> void 31 | { 32 | m_initial = marlinformat::PackedBoard::pack(initialPosition, 0); 33 | m_moves.clear(); 34 | } 35 | 36 | auto Viriformat::push([[maybe_unused]] bool filtered, Move move, Score score) -> void 37 | { 38 | static constexpr auto MoveTypes = std::array{ 39 | static_cast(0x0000), // normal 40 | static_cast(0xC000), // promo 41 | static_cast(0x8000), // castling 42 | static_cast(0x4000) // ep 43 | }; 44 | 45 | u16 viriMove{}; 46 | 47 | viriMove |= move.srcIdx(); 48 | viriMove |= move.dstIdx() << 6; 49 | viriMove |= move.promoIdx() << 12; 50 | viriMove |= MoveTypes[static_cast(move.type())]; 51 | 52 | m_moves.push_back({viriMove, static_cast(score)}); 53 | } 54 | 55 | auto Viriformat::writeAllWithOutcome(std::ostream &stream, Outcome outcome) -> usize 56 | { 57 | static constexpr std::array NullTerminator{}; 58 | 59 | m_initial.wdl = outcome; 60 | 61 | stream.write(reinterpret_cast(&m_initial), sizeof(marlinformat::PackedBoard)); 62 | stream.write(reinterpret_cast(m_moves.data()), sizeof(ScoredMove) * m_moves.size()); 63 | stream.write(reinterpret_cast(NullTerminator.data()), sizeof(ScoredMove)); 64 | 65 | return m_moves.size() + 1; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/datagen/viriformat.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include "common.h" 24 | #include "format.h" 25 | #include "marlinformat.h" 26 | 27 | namespace stormphrax::datagen 28 | { 29 | // Format originally from Viridithas 30 | // https://github.com/cosmobobak/viridithas/blob/029672a/src/datagen/dataformat.rs 31 | class Viriformat 32 | { 33 | public: 34 | Viriformat(); 35 | ~Viriformat() = default; 36 | 37 | static constexpr auto Extension = "vf"; 38 | 39 | auto start(const Position &initialPosition) -> void; 40 | auto push(bool filtered, Move move, Score score) -> void; 41 | auto writeAllWithOutcome(std::ostream &stream, Outcome outcome) -> usize; 42 | 43 | private: 44 | using ScoredMove = std::pair; 45 | static_assert(sizeof(ScoredMove) == sizeof(u16) + sizeof(i16)); 46 | 47 | marlinformat::PackedBoard m_initial{}; 48 | std::vector m_moves{}; 49 | }; 50 | 51 | static_assert(OutputFormat); 52 | } 53 | -------------------------------------------------------------------------------- /src/eval/arch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | 25 | #include "nnue/activation.h" 26 | #include "nnue/output.h" 27 | #include "nnue/features.h" 28 | #include "nnue/arch/singlelayer.h" 29 | #include "nnue/arch/multilayer.h" 30 | 31 | namespace stormphrax::eval 32 | { 33 | // current arch: (704x16hm->1792)x2->(16->32->1)x8 34 | // pairwise clipped ReLU -> clipped squared ReLU -> clipped ReLU 35 | 36 | constexpr u32 FtQBits = 8; 37 | constexpr u32 L1QBits = 7; 38 | 39 | constexpr u32 FtScaleBits = 7; 40 | 41 | constexpr u32 L1Size = 1792; 42 | constexpr u32 L2Size = 16; 43 | constexpr u32 L3Size = 32; 44 | 45 | using L1Activation = nnue::activation::ClippedReLU; 46 | 47 | constexpr i32 Scale = 400; 48 | 49 | // visually flipped upside down, a1 = 0 50 | using InputFeatureSet = nnue::features::KingBucketsMergedMirrored< 51 | nnue::features::MirroredKingSide::Abcd, 52 | 0, 1, 2, 3, 53 | 4, 5, 6, 7, 54 | 8, 9, 10, 11, 55 | 8, 9, 10, 11, 56 | 12, 12, 13, 13, 57 | 12, 12, 13, 13, 58 | 14, 14, 15, 15, 59 | 14, 14, 15, 15 60 | >; 61 | 62 | using OutputBucketing = nnue::output::MaterialCount<8>; 63 | 64 | using LayeredArch = nnue::arch::PairwiseMultilayerCReLUSCReLUCReLU< 65 | L1Size, L2Size, L3Size, FtScaleBits, FtQBits, L1QBits, OutputBucketing, Scale>; 66 | } 67 | -------------------------------------------------------------------------------- /src/eval/eval.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | 25 | #include "nnue.h" 26 | #include "../position/position.h" 27 | #include "../core.h" 28 | #include "../tunable.h" 29 | #include "../correction.h" 30 | #include "../see.h" 31 | 32 | namespace stormphrax::eval 33 | { 34 | // black, white 35 | using Contempt = std::array; 36 | 37 | template 38 | inline auto adjustEval(const Position &pos, std::span moves, 39 | i32 ply, const CorrectionHistoryTable *correction, i32 eval, i32 *corrDelta = nullptr) 40 | { 41 | eval = eval * (200 - pos.halfmove()) / 200; 42 | 43 | if constexpr (Correct) 44 | { 45 | const auto corrected = correction->correct(pos, moves, ply, eval); 46 | 47 | if (corrDelta) 48 | *corrDelta = std::abs(eval - corrected); 49 | 50 | eval = corrected; 51 | } 52 | 53 | return std::clamp(eval, -ScoreWin + 1, ScoreWin - 1); 54 | } 55 | 56 | template 57 | inline auto adjustStatic(const Position &pos, const Contempt &contempt, Score eval) 58 | { 59 | using namespace tunable; 60 | 61 | if constexpr (Scale) 62 | { 63 | const auto bbs = pos.bbs(); 64 | 65 | const auto npMaterial 66 | = scalingValueKnight() * bbs.knights().popcount() 67 | + scalingValueBishop() * bbs.bishops().popcount() 68 | + scalingValueRook() * bbs.rooks ().popcount() 69 | + scalingValueQueen() * bbs.queens ().popcount(); 70 | 71 | eval = eval * (materialScalingBase() + npMaterial) / 32768; 72 | } 73 | 74 | eval += contempt[static_cast(pos.toMove())]; 75 | 76 | return std::clamp(eval, -ScoreWin + 1, ScoreWin - 1); 77 | } 78 | 79 | template 80 | inline auto staticEval(const Position &pos, NnueState &nnueState, const Contempt &contempt = {}) 81 | { 82 | auto eval = nnueState.evaluate(pos.bbs(), pos.kings(), pos.toMove()); 83 | return adjustStatic(pos, contempt, eval); 84 | } 85 | 86 | template 87 | inline auto adjustedStaticEval(const Position &pos, 88 | std::span moves, i32 ply, NnueState &nnueState, 89 | const CorrectionHistoryTable *correction, const Contempt &contempt = {}) 90 | { 91 | const auto eval = staticEval(pos, nnueState, contempt); 92 | return adjustEval(pos, moves, ply, correction, eval); 93 | } 94 | 95 | template 96 | inline auto staticEvalOnce(const Position &pos, const Contempt &contempt = {}) 97 | { 98 | auto eval = NnueState::evaluateOnce(pos.bbs(), pos.kings(), pos.toMove()); 99 | return adjustStatic(pos, contempt, eval); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/eval/nnue/activation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../../types.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "../../util/simd.h" 30 | 31 | namespace stormphrax::eval::nnue::activation 32 | { 33 | struct [[maybe_unused]] Identity 34 | { 35 | static constexpr u8 Id = 3; 36 | 37 | template 38 | SP_ALWAYS_INLINE_NDEBUG static inline auto activateDotAccumulate( 39 | util::simd::PromotedVector sum, util::simd::Vector inputs, util::simd::Vector weights) 40 | { 41 | using namespace util::simd; 42 | 43 | return mulAddAdjAcc(sum, inputs, weights); 44 | } 45 | 46 | template 47 | SP_ALWAYS_INLINE_NDEBUG static inline auto activateDotAccumulate( 48 | util::simd::PromotedVector sum, util::simd::Vector inputs1, 49 | util::simd::Vector inputs2, util::simd::Vector weights) 50 | { 51 | using namespace util::simd; 52 | 53 | const auto products = mulLo(inputs1, weights); 54 | return mulAddAdjAcc(sum, products, inputs2); 55 | } 56 | 57 | template 58 | SP_ALWAYS_INLINE_NDEBUG static inline auto output(OutputType value) 59 | { 60 | return value; 61 | } 62 | }; 63 | 64 | struct [[maybe_unused]] ReLU 65 | { 66 | static constexpr u8 Id = 2; 67 | 68 | template 69 | SP_ALWAYS_INLINE_NDEBUG static inline auto activateDotAccumulate( 70 | util::simd::PromotedVector sum, util::simd::Vector inputs, util::simd::Vector weights) 71 | { 72 | using namespace util::simd; 73 | 74 | const auto activated = max(inputs, zero()); 75 | return mulAddAdjAcc(sum, activated, weights); 76 | } 77 | 78 | template 79 | SP_ALWAYS_INLINE_NDEBUG static inline auto activateDotAccumulate( 80 | util::simd::PromotedVector sum, util::simd::Vector inputs1, 81 | util::simd::Vector inputs2, util::simd::Vector weights) 82 | { 83 | using namespace util::simd; 84 | 85 | const auto activated1 = max(inputs1, zero()); 86 | const auto activated2 = max(inputs2, zero()); 87 | 88 | const auto products = mulLo(activated1, weights); 89 | return mulAddAdjAcc(sum, products, activated2); 90 | } 91 | 92 | template 93 | SP_ALWAYS_INLINE_NDEBUG static inline auto output(T value) 94 | { 95 | return value; 96 | } 97 | }; 98 | 99 | struct [[maybe_unused]] ClippedReLU 100 | { 101 | static constexpr u8 Id = 0; 102 | 103 | template 104 | SP_ALWAYS_INLINE_NDEBUG static inline auto activateDotAccumulate( 105 | util::simd::PromotedVector sum, util::simd::Vector inputs, util::simd::Vector weights) 106 | { 107 | using namespace util::simd; 108 | 109 | static const auto max = set1(Max); 110 | 111 | const auto clipped = clamp(inputs, zero(), max); 112 | return mulAddAdjAcc(sum, clipped, weights); 113 | } 114 | 115 | template 116 | SP_ALWAYS_INLINE_NDEBUG static inline auto activateDotAccumulate( 117 | util::simd::PromotedVector sum, util::simd::Vector inputs1, 118 | util::simd::Vector inputs2, util::simd::Vector weights) 119 | { 120 | using namespace util::simd; 121 | 122 | static const auto max = set1(Max); 123 | 124 | const auto clipped1 = clamp(inputs1, zero(), max); 125 | const auto clipped2 = clamp(inputs2, zero(), max); 126 | 127 | const auto products = mulLo(clipped1, weights); 128 | return mulAddAdjAcc(sum, clipped2, products); 129 | } 130 | 131 | template 132 | SP_ALWAYS_INLINE_NDEBUG static inline auto output(T value) 133 | { 134 | return value; 135 | } 136 | }; 137 | 138 | struct [[maybe_unused]] SquaredClippedReLU 139 | { 140 | static constexpr u8 Id = 1; 141 | 142 | template 143 | SP_ALWAYS_INLINE_NDEBUG static inline auto activateDotAccumulate( 144 | util::simd::PromotedVector sum, util::simd::Vector inputs, util::simd::Vector weights) 145 | { 146 | using namespace util::simd; 147 | 148 | static const auto max = set1(Max); 149 | 150 | const auto clipped = clamp(inputs, zero(), max); 151 | const auto crelu = mulLo(clipped, weights); 152 | return mulAddAdjAcc(sum, crelu, clipped); 153 | } 154 | 155 | template 156 | SP_ALWAYS_INLINE_NDEBUG static inline auto output(T value) 157 | { 158 | return value / Max; 159 | } 160 | }; 161 | } 162 | -------------------------------------------------------------------------------- /src/eval/nnue/arch/singlelayer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../../../types.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "../activation.h" 30 | #include "../output.h" 31 | #include "../../../util/simd.h" 32 | #include "../io.h" 33 | #include "../../../util/multi_array.h" 34 | 35 | namespace stormphrax::eval::nnue::arch 36 | { 37 | // implements an (inputs->L1)x2->1xN network, with configurable activation 38 | template 40 | struct SingleLayer 41 | { 42 | using OutputType = i32; 43 | static constexpr u32 OutputCount = 1; 44 | 45 | static constexpr bool Pairwise = false; 46 | static constexpr bool RequiresFtPermute = false; 47 | 48 | private: 49 | static constexpr auto OutputBucketCount = OutputBucketing::BucketCount; 50 | 51 | SP_SIMD_ALIGNAS std::array l1Weights{}; 52 | SP_SIMD_ALIGNAS std::array l1Biases{}; 53 | 54 | public: 55 | inline auto propagate(u32 bucket, 56 | std::span stmInputs, 57 | std::span nstmInputs, 58 | std::span outputs) const 59 | { 60 | using namespace util::simd; 61 | 62 | static constexpr i32 Q = FtQ * L1Q; 63 | 64 | assert(isAligned( stmInputs.data())); 65 | assert(isAligned(nstmInputs.data())); 66 | assert(isAligned( outputs.data())); 67 | 68 | const auto weightOffset = bucket * L1Size * 2; 69 | const auto biasOffset = bucket; 70 | 71 | auto sum = zero(); 72 | 73 | // stm perspective 74 | for (u32 inputIdx = 0; inputIdx < L1Size; inputIdx += ChunkSize) 75 | { 76 | const auto inputs = load(&stmInputs[inputIdx]); 77 | const auto weights = load(&l1Weights[weightOffset + inputIdx]); 78 | 79 | sum = Activation::template activateDotAccumulate(sum, inputs, weights); 80 | } 81 | 82 | // nstm perspective 83 | for (u32 inputIdx = 0; inputIdx < L1Size; inputIdx += ChunkSize) 84 | { 85 | const auto inputs = load(&nstmInputs[inputIdx]); 86 | const auto weights = load(&l1Weights[L1Size + weightOffset + inputIdx]); 87 | 88 | sum = Activation::template activateDotAccumulate(sum, inputs, weights); 89 | } 90 | 91 | const auto output = hsum(sum); 92 | 93 | const auto bias = static_cast(l1Biases[biasOffset]); 94 | const auto out = bias + Activation::template output(output); 95 | 96 | outputs[0] = out * Scale / Q; 97 | } 98 | 99 | inline auto readFrom(IParamStream &stream) -> bool 100 | { 101 | return stream.read(l1Weights) 102 | && stream.read(l1Biases); 103 | } 104 | 105 | inline auto writeTo(IParamStream &stream) const -> bool 106 | { 107 | return stream.write(l1Weights) 108 | && stream.write(l1Biases); 109 | } 110 | }; 111 | } 112 | -------------------------------------------------------------------------------- /src/eval/nnue/io.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../../types.h" 22 | 23 | #include 24 | #include 25 | 26 | namespace stormphrax::eval::nnue 27 | { 28 | class IParamStream 29 | { 30 | public: 31 | virtual ~IParamStream() = default; 32 | 33 | template 34 | inline auto read(std::span dst) -> bool = delete; 35 | 36 | template <> 37 | inline auto read(std::span dst) -> bool 38 | { 39 | return readI8s(dst); 40 | } 41 | 42 | template <> 43 | inline auto read(std::span dst) -> bool 44 | { 45 | return readI16s(dst); 46 | } 47 | 48 | template <> 49 | inline auto read(std::span dst) -> bool 50 | { 51 | return readI32s(dst); 52 | } 53 | 54 | template 55 | inline auto read(std::array &dst) 56 | { 57 | return read(std::span{dst}); 58 | } 59 | 60 | template 61 | inline auto write(std::span src) -> bool = delete; 62 | 63 | template <> 64 | inline auto write(std::span src) -> bool 65 | { 66 | return writeI8s(src); 67 | } 68 | 69 | template <> 70 | inline auto write(std::span src) -> bool 71 | { 72 | return writeI16s(src); 73 | } 74 | 75 | template <> 76 | inline auto write(std::span src) -> bool 77 | { 78 | return writeI32s(src); 79 | } 80 | 81 | template 82 | inline auto write(const std::array &src) 83 | { 84 | return write(std::span{src}); 85 | } 86 | 87 | protected: 88 | virtual auto readI8s(std::span dst) -> bool = 0; 89 | virtual auto writeI8s(std::span src) -> bool = 0; 90 | 91 | virtual auto readI16s(std::span dst) -> bool = 0; 92 | virtual auto writeI16s(std::span src) -> bool = 0; 93 | 94 | virtual auto readI32s(std::span dst) -> bool = 0; 95 | virtual auto writeI32s(std::span src) -> bool = 0; 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /src/eval/nnue/io_impl.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "io_impl.h" 20 | 21 | #include 22 | 23 | #include "../../3rdparty/zstd/zstd_errors.h" 24 | 25 | namespace stormphrax::eval::nnue 26 | { 27 | ZstdParamStream::ZstdParamStream(std::istream &in) 28 | : m_stream{in} 29 | { 30 | m_inBuf.resize(ZSTD_DStreamInSize()); 31 | m_outBuf.resize(ZSTD_DStreamOutSize()); 32 | 33 | m_dStream = ZSTD_createDStream(); 34 | 35 | m_input.src = m_inBuf.data(); 36 | m_output.dst = m_outBuf.data(); 37 | } 38 | 39 | ZstdParamStream::~ZstdParamStream() 40 | { 41 | ZSTD_freeDStream(m_dStream); 42 | } 43 | 44 | auto ZstdParamStream::fillBuffer() -> bool 45 | { 46 | while (m_pos >= m_end 47 | && (m_input.pos < m_input.size || !m_stream.fail())) 48 | { 49 | if (m_input.pos == m_input.size) 50 | { 51 | if (!m_stream) 52 | { 53 | m_fail = true; 54 | return false; 55 | } 56 | 57 | m_stream.read(reinterpret_cast(m_inBuf.data()), 58 | static_cast(m_inBuf.size())); 59 | 60 | m_input.size = m_stream.gcount(); 61 | m_input.pos = 0; 62 | } 63 | 64 | if (m_result == 0 && m_input.pos < m_input.size) 65 | ZSTD_initDStream(m_dStream); 66 | 67 | m_output.size = m_outBuf.size(); 68 | m_output.pos = 0; 69 | 70 | m_result = ZSTD_decompressStream(m_dStream, &m_output, &m_input); 71 | 72 | if (ZSTD_isError(m_result)) 73 | { 74 | const auto code = ZSTD_getErrorCode(m_result); 75 | std::cerr << "zstd error: " << ZSTD_getErrorString(code) << std::endl; 76 | 77 | m_fail = true; 78 | return false; 79 | } 80 | 81 | m_pos = 0; 82 | m_end = m_output.pos; 83 | } 84 | 85 | return m_pos < m_end; 86 | } 87 | 88 | auto ZstdParamStream::read(std::byte *dst, usize n) -> bool 89 | { 90 | if (m_fail) 91 | return false; 92 | 93 | while (n > 0) 94 | { 95 | if (m_pos >= m_end && !fillBuffer()) 96 | return false; 97 | 98 | const auto remaining = m_end - m_pos; 99 | const auto count = std::min(n, remaining); 100 | 101 | std::memcpy(dst, &m_outBuf[m_pos], count); 102 | 103 | m_pos += count; 104 | dst += count; 105 | 106 | n -= count; 107 | } 108 | 109 | return true; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/eval/nnue/io_impl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../../types.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "io.h" 32 | #include "../../util/cemath.h" 33 | #include "../../util/memstream.h" 34 | #include "../../3rdparty/zstd/zstd.h" 35 | 36 | namespace stormphrax::eval::nnue 37 | { 38 | template 39 | class PaddedParamStream final : public IParamStream 40 | { 41 | public: 42 | explicit PaddedParamStream(std::istream &in) 43 | : m_stream{&in} {} 44 | explicit PaddedParamStream(std::ostream &out) 45 | : m_stream{&out} {} 46 | 47 | ~PaddedParamStream() final = default; 48 | 49 | protected: 50 | inline auto readI8s(std::span dst) -> bool final 51 | { 52 | return read(reinterpret_cast(dst.data()), dst.size_bytes()); 53 | } 54 | 55 | inline auto writeI8s(std::span src) -> bool final 56 | { 57 | return write(reinterpret_cast(src.data()), src.size_bytes()); 58 | } 59 | 60 | inline auto readI16s(std::span dst) -> bool final 61 | { 62 | return read(reinterpret_cast(dst.data()), dst.size_bytes()); 63 | } 64 | 65 | inline auto writeI16s(std::span src) -> bool final 66 | { 67 | return write(reinterpret_cast(src.data()), src.size_bytes()); 68 | } 69 | 70 | inline auto readI32s(std::span dst) -> bool final 71 | { 72 | return read(reinterpret_cast(dst.data()), dst.size_bytes()); 73 | } 74 | 75 | inline auto writeI32s(std::span src) -> bool final 76 | { 77 | return write(reinterpret_cast(src.data()), src.size_bytes()); 78 | } 79 | 80 | private: 81 | std::variant m_stream; 82 | 83 | inline auto read(std::byte *dst, usize n) -> bool 84 | { 85 | if (!std::holds_alternative(m_stream)) 86 | { 87 | assert(false); 88 | return false; 89 | } 90 | 91 | auto &stream = *std::get(m_stream); 92 | 93 | const auto padding = calcPadding(n); 94 | 95 | stream.read(reinterpret_cast(dst), static_cast(n)); 96 | stream.ignore(static_cast(padding)); 97 | 98 | return !stream.fail(); 99 | } 100 | 101 | inline auto write(const std::byte *src, usize n) -> bool 102 | { 103 | if (!std::holds_alternative(m_stream)) 104 | { 105 | assert(false); 106 | return false; 107 | } 108 | 109 | static constexpr std::array Empty{}; 110 | 111 | auto &stream = *std::get(m_stream); 112 | 113 | const auto padding = calcPadding(n); 114 | 115 | stream.write(reinterpret_cast(src), static_cast(n)); 116 | stream.write(reinterpret_cast(Empty.data()), padding); 117 | 118 | return !stream.fail(); 119 | } 120 | 121 | [[nodiscard]] static constexpr auto calcPadding(usize v) -> usize 122 | { 123 | return v - util::pad(v); 124 | } 125 | }; 126 | 127 | class ZstdParamStream final : public IParamStream 128 | { 129 | public: 130 | explicit ZstdParamStream(std::istream &in); 131 | 132 | ~ZstdParamStream() final; 133 | 134 | protected: 135 | inline auto readI8s(std::span dst) -> bool final 136 | { 137 | return read(reinterpret_cast(dst.data()), dst.size_bytes()); 138 | } 139 | 140 | inline auto writeI8s(std::span src) -> bool final 141 | { 142 | std::cerr << "ZstdParamStream::writeI8s" << std::endl; 143 | std::terminate(); 144 | } 145 | 146 | inline auto readI16s(std::span dst) -> bool final 147 | { 148 | return read(reinterpret_cast(dst.data()), dst.size_bytes()); 149 | } 150 | 151 | inline auto writeI16s(std::span src) -> bool final 152 | { 153 | std::cerr << "ZstdParamStream::writeI16s" << std::endl; 154 | std::terminate(); 155 | } 156 | 157 | inline auto readI32s(std::span dst) -> bool final 158 | { 159 | return read(reinterpret_cast(dst.data()), dst.size_bytes()); 160 | } 161 | 162 | inline auto writeI32s(std::span src) -> bool final 163 | { 164 | std::cerr << "ZstdParamStream::writeI32s" << std::endl; 165 | std::terminate(); 166 | } 167 | 168 | private: 169 | std::istream &m_stream; 170 | 171 | std::vector m_inBuf{}; 172 | std::vector m_outBuf{}; 173 | 174 | usize m_pos{}; 175 | usize m_end{}; 176 | 177 | usize m_result{}; 178 | 179 | ZSTD_DStream *m_dStream; 180 | 181 | ZSTD_inBuffer m_input{}; 182 | ZSTD_outBuffer m_output{}; 183 | 184 | bool m_fail{false}; 185 | 186 | auto fillBuffer() -> bool; 187 | auto read(std::byte *dst, usize n) -> bool; 188 | }; 189 | } 190 | -------------------------------------------------------------------------------- /src/eval/nnue/network.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../../types.h" 22 | 23 | #include 24 | 25 | #include "../../position/boards.h" 26 | #include "../../util/aligned_array.h" 27 | 28 | namespace stormphrax::eval::nnue 29 | { 30 | template 31 | class PerspectiveNetwork 32 | { 33 | public: 34 | using FeatureTransformer = Ft; 35 | 36 | [[nodiscard]] inline auto featureTransformer() const -> const auto & 37 | { 38 | return m_featureTransformer; 39 | } 40 | 41 | inline auto propagate(const BitboardSet &bbs, 42 | std::span stmInputs, 43 | std::span nstmInputs) const 44 | { 45 | util::simd::Array outputs; 46 | 47 | const auto bucket = OutputBucketing::getBucket(bbs); 48 | m_arch.propagate(bucket, stmInputs, nstmInputs, outputs); 49 | 50 | return outputs; 51 | } 52 | 53 | inline auto readFrom(IParamStream &stream) -> bool 54 | { 55 | if (!m_featureTransformer.readFrom(stream) || !m_arch.readFrom(stream)) 56 | return false; 57 | 58 | if constexpr (Arch::RequiresFtPermute) 59 | Arch::template permuteFt( 60 | m_featureTransformer.weights, m_featureTransformer.biases); 61 | 62 | return true; 63 | } 64 | 65 | inline auto writeTo(IParamStream &stream) const -> bool 66 | { 67 | return m_featureTransformer.writeTo(stream) 68 | && m_arch.writeTo(stream); 69 | } 70 | 71 | private: 72 | FeatureTransformer m_featureTransformer{}; 73 | Arch m_arch{}; 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /src/eval/nnue/output.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../../types.h" 22 | 23 | #include 24 | #include 25 | 26 | #include "../../position/boards.h" 27 | #include "../../util/bits.h" 28 | 29 | namespace stormphrax::eval::nnue::output 30 | { 31 | template 32 | concept OutputBucketing = requires 33 | { 34 | { T::BucketCount } -> std::same_as; 35 | { T::getBucket(BitboardSet{}) } -> std::same_as; 36 | }; 37 | 38 | struct [[maybe_unused]] Single 39 | { 40 | static constexpr u32 BucketCount = 1; 41 | 42 | static constexpr auto getBucket(const BitboardSet &) -> u32 43 | { 44 | return 0; 45 | } 46 | }; 47 | 48 | template 49 | struct [[maybe_unused]] MaterialCount 50 | { 51 | static_assert(Count > 0 && util::resetLsb(Count) == 0); 52 | static_assert(Count <= 32); 53 | 54 | static constexpr u32 BucketCount = Count; 55 | 56 | static inline auto getBucket(const BitboardSet &bbs) -> u32 57 | { 58 | constexpr auto Div = 32 / Count; 59 | return (bbs.occupancy().popcount() - 2) / Div; 60 | } 61 | }; 62 | 63 | struct [[maybe_unused]] Ocb 64 | { 65 | static constexpr u32 BucketCount = 2; 66 | 67 | static inline auto getBucket(const BitboardSet &bbs) -> u32 68 | { 69 | return (!bbs.blackBishops().empty() 70 | && !bbs.whiteBishops().empty() 71 | && (bbs.blackBishops() & boards::LightSquares).empty() 72 | != (bbs.whiteBishops() & boards::LightSquares).empty()) 73 | ? 1 : 0; 74 | } 75 | }; 76 | 77 | template 78 | requires (!std::is_same_v && !std::is_same_v) 79 | struct [[maybe_unused]] Combo 80 | { 81 | static constexpr u32 BucketCount = L::BucketCount * R::BucketCount; 82 | 83 | static inline auto getBucket(const BitboardSet &bbs) -> u32 84 | { 85 | return L::getBucket(bbs) * R::BucketCount + R::getBucket(bbs); 86 | } 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /src/keys.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include 24 | 25 | #include "core.h" 26 | #include "util/rng.h" 27 | 28 | namespace stormphrax::keys 29 | { 30 | namespace sizes 31 | { 32 | constexpr usize PieceSquares = 12 * 64; 33 | constexpr usize Color = 1; 34 | constexpr usize Castling = 16; 35 | constexpr usize EnPassant = 8; 36 | 37 | constexpr auto Total = PieceSquares + Color + Castling + EnPassant; 38 | } 39 | 40 | namespace offsets 41 | { 42 | constexpr usize PieceSquares = 0; 43 | constexpr auto Color = PieceSquares + sizes::PieceSquares; 44 | constexpr auto Castling = Color + sizes::Color; 45 | constexpr auto EnPassant = Castling + sizes::Castling; 46 | } 47 | 48 | constexpr auto Keys = [] 49 | { 50 | constexpr auto Seed = U64(0xD06C659954EC904A); 51 | 52 | std::array keys{}; 53 | 54 | util::rng::Jsf64Rng rng{Seed}; 55 | 56 | for (auto &key : keys) 57 | { 58 | key = rng.nextU64(); 59 | } 60 | 61 | return keys; 62 | }(); 63 | 64 | inline auto pieceSquare(Piece piece, Square square) -> u64 65 | { 66 | if (piece == Piece::None || square == Square::None) 67 | return 0; 68 | 69 | return Keys[offsets::PieceSquares + static_cast(square) * 12 + static_cast(piece)]; 70 | } 71 | 72 | // for flipping 73 | inline auto color() 74 | { 75 | return Keys[offsets::Color]; 76 | } 77 | 78 | inline auto color(Color c) 79 | { 80 | return c == Color::White ? 0 : color(); 81 | } 82 | 83 | inline auto castling(const CastlingRooks &castlingRooks) 84 | { 85 | constexpr usize BlackShort = 0x01; 86 | constexpr usize BlackLong = 0x02; 87 | constexpr usize WhiteShort = 0x04; 88 | constexpr usize WhiteLong = 0x08; 89 | 90 | usize flags{}; 91 | 92 | if (castlingRooks.black().kingside != Square::None) 93 | flags |= BlackShort; 94 | if (castlingRooks.black().queenside != Square::None) 95 | flags |= BlackLong; 96 | if (castlingRooks.white().kingside != Square::None) 97 | flags |= WhiteShort; 98 | if (castlingRooks.white().queenside != Square::None) 99 | flags |= WhiteLong; 100 | 101 | return Keys[offsets::Castling + flags]; 102 | } 103 | 104 | inline auto enPassant(u32 file) 105 | { 106 | return Keys[offsets::EnPassant + file]; 107 | } 108 | 109 | inline auto enPassant(Square square) -> u64 110 | { 111 | if (square == Square::None) 112 | return 0; 113 | 114 | return Keys[offsets::EnPassant + squareFile(square)]; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/limit/compound.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | #include 25 | 26 | #include "limit.h" 27 | 28 | namespace stormphrax::limit 29 | { 30 | class CompoundLimiter final : public ISearchLimiter 31 | { 32 | public: 33 | CompoundLimiter() = default; 34 | ~CompoundLimiter() final = default; 35 | 36 | template 37 | inline auto addLimiter(Args &&...args) 38 | { 39 | m_limiters.push_back(std::make_unique(std::forward(args)...)); 40 | } 41 | 42 | inline auto update(const search::SearchData &data, Score score, Move bestMove, usize totalNodes) -> void final 43 | { 44 | for (const auto &limiter : m_limiters) 45 | { 46 | limiter->update(data, score, bestMove, totalNodes); 47 | } 48 | } 49 | 50 | inline auto updateMoveNodes(Move move, usize nodes) -> void final 51 | { 52 | for (const auto &limiter : m_limiters) 53 | { 54 | limiter->updateMoveNodes(move, nodes); 55 | } 56 | } 57 | 58 | [[nodiscard]] inline auto stop(const search::SearchData &data, bool allowSoftTimeout) -> bool final 59 | { 60 | return std::ranges::any_of(m_limiters, [&](const auto &limiter) 61 | { 62 | return limiter->stop(data, allowSoftTimeout); 63 | }); 64 | } 65 | 66 | [[nodiscard]] inline auto stopped() const -> bool final 67 | { 68 | return std::ranges::any_of(m_limiters, [&](const auto &limiter) 69 | { 70 | return limiter->stopped(); 71 | }); 72 | } 73 | 74 | private: 75 | std::vector> m_limiters{}; 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /src/limit/limit.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include "../search_fwd.h" 24 | 25 | namespace stormphrax::limit 26 | { 27 | class ISearchLimiter 28 | { 29 | public: 30 | virtual ~ISearchLimiter() = default; 31 | 32 | virtual auto update(const search::SearchData &data, Score score, Move bestMove, usize totalNodes) -> void {} 33 | virtual auto updateMoveNodes(Move move, usize nodes) -> void {} 34 | 35 | [[nodiscard]] virtual auto stop(const search::SearchData &data, bool allowSoftTimeout) -> bool = 0; 36 | 37 | [[nodiscard]] virtual auto stopped() const -> bool = 0; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/limit/time.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "time.h" 20 | 21 | #include 22 | 23 | #include "../tunable.h" 24 | 25 | namespace stormphrax::limit 26 | { 27 | using namespace stormphrax::tunable; 28 | 29 | using util::Instant; 30 | 31 | MoveTimeLimiter::MoveTimeLimiter(i64 time, i64 overhead) 32 | : m_endTime{Instant::now() + static_cast(std::max(1, time - overhead)) / 1000.0} {} 33 | 34 | auto MoveTimeLimiter::stop(const search::SearchData &data, bool allowSoftTimeout) -> bool 35 | { 36 | if (data.rootDepth > 2 37 | && data.nodes > 0 38 | && (data.nodes % 1024) == 0 39 | && Instant::now() >= m_endTime) 40 | { 41 | m_stopped.store(true, std::memory_order_release); 42 | return true; 43 | } 44 | 45 | return false; 46 | } 47 | 48 | auto MoveTimeLimiter::stopped() const -> bool 49 | { 50 | return m_stopped.load(std::memory_order_acquire); 51 | } 52 | 53 | TimeManager::TimeManager(Instant start, f64 remaining, f64 increment, i32 toGo, f64 overhead) 54 | : m_startTime{start} 55 | { 56 | assert(toGo >= 0); 57 | 58 | const auto limit = std::max(0.001, remaining - overhead); 59 | 60 | if (toGo == 0) 61 | toGo = defaultMovesToGo(); 62 | 63 | const auto baseTime = limit / static_cast(toGo) + increment * incrementScale(); 64 | 65 | m_maxTime = limit * hardTimeScale(); 66 | m_softTime = std::min(baseTime * softTimeScale(), m_maxTime); 67 | } 68 | 69 | auto TimeManager::update(const search::SearchData &data, Score score, Move bestMove, usize totalNodes) -> void 70 | { 71 | assert(bestMove != NullMove); 72 | assert(totalNodes > 0); 73 | 74 | if (bestMove == m_prevBestMove) 75 | ++m_stability; 76 | else 77 | { 78 | m_stability = 1; 79 | m_prevBestMove = bestMove; 80 | } 81 | 82 | auto scale = 1.0; 83 | 84 | const auto bestMoveNodeFraction = static_cast(m_moveNodeCounts[bestMove.srcIdx()][bestMove.dstIdx()]) 85 | / static_cast(totalNodes); 86 | scale *= std::max(nodeTmBase() - bestMoveNodeFraction * nodeTmScale(), nodeTmScaleMin()); 87 | 88 | if (data.rootDepth >= 6) 89 | { 90 | const auto stability = static_cast(m_stability); 91 | scale *= std::min( 92 | bmStabilityTmMax(), 93 | bmStabilityTmMin() + bmStabilityTmScale() 94 | * std::pow(stability + bmStabilityTmOffset(), bmStabilityTmPower()) 95 | ); 96 | } 97 | 98 | if (m_avgScore) 99 | { 100 | const auto avgScore = *m_avgScore; 101 | 102 | const auto scoreChange = static_cast(score - avgScore) / scoreTrendTmScoreScale(); 103 | const auto invScale = scoreChange * scoreTrendTmScale() / (std::abs(scoreChange) + scoreTrendTmStretch()) 104 | * (scoreChange > 0 ? scoreTrendTmPositiveScale() : scoreTrendTmNegativeScale()); 105 | 106 | scale *= std::clamp(1.0 - invScale, scoreTrendTmMin(), scoreTrendTmMax()); 107 | 108 | m_avgScore = util::ilerp<8>(avgScore, score, 1); 109 | } 110 | else m_avgScore = score; 111 | 112 | m_scale = std::max(scale, timeScaleMin()); 113 | } 114 | 115 | auto TimeManager::updateMoveNodes(Move move, usize nodes) -> void 116 | { 117 | assert(move != NullMove); 118 | m_moveNodeCounts[move.srcIdx()][move.dstIdx()] += nodes; 119 | } 120 | 121 | auto TimeManager::stop(const search::SearchData &data, bool allowSoftTimeout) -> bool 122 | { 123 | if (data.nodes == 0 124 | || (!allowSoftTimeout && (data.nodes % 1024) != 0)) 125 | return false; 126 | 127 | const auto elapsed = m_startTime.elapsed(); 128 | 129 | if (elapsed > m_maxTime || (allowSoftTimeout && elapsed > m_softTime * m_scale)) 130 | { 131 | m_stopped.store(true, std::memory_order_release); 132 | return true; 133 | } 134 | 135 | return false; 136 | } 137 | 138 | auto TimeManager::stopped() const -> bool 139 | { 140 | return m_stopped.load(std::memory_order_acquire); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/limit/time.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "limit.h" 28 | #include "../util/timer.h" 29 | #include "../util/range.h" 30 | #include "../util/multi_array.h" 31 | 32 | namespace stormphrax::limit 33 | { 34 | constexpr i32 DefaultMoveOverhead = 10; 35 | constexpr util::Range MoveOverheadRange{0, 50000}; 36 | 37 | class MoveTimeLimiter final : public ISearchLimiter 38 | { 39 | public: 40 | explicit MoveTimeLimiter(i64 time, i64 overhead = 0); 41 | ~MoveTimeLimiter() final = default; 42 | 43 | [[nodiscard]] auto stop(const search::SearchData &data, bool allowSoftTimeout) -> bool final; 44 | 45 | [[nodiscard]] auto stopped() const -> bool final; 46 | 47 | private: 48 | util::Instant m_endTime; 49 | std::atomic_bool m_stopped{false}; 50 | }; 51 | 52 | class TimeManager final : public ISearchLimiter 53 | { 54 | public: 55 | TimeManager(util::Instant start, f64 remaining, f64 increment, i32 toGo, f64 overhead); 56 | ~TimeManager() final = default; 57 | 58 | auto update(const search::SearchData &data, Score score, Move bestMove, usize totalNodes) -> void final; 59 | auto updateMoveNodes(Move move, usize nodes) -> void final; 60 | 61 | [[nodiscard]] auto stop(const search::SearchData &data, bool allowSoftTimeout) -> bool final; 62 | 63 | [[nodiscard]] auto stopped() const -> bool final; 64 | 65 | private: 66 | util::Instant m_startTime; 67 | 68 | f64 m_softTime{}; 69 | f64 m_maxTime{}; 70 | 71 | f64 m_scale{1.0}; 72 | 73 | util::MultiArray m_moveNodeCounts{}; 74 | 75 | Move m_prevBestMove{}; 76 | u32 m_stability{}; 77 | 78 | std::optional m_avgScore{}; 79 | 80 | std::atomic_bool m_stopped{false}; 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /src/limit/trivial.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | 25 | #include "limit.h" 26 | #include "../opts.h" 27 | 28 | namespace stormphrax::limit 29 | { 30 | class InfiniteLimiter final : public ISearchLimiter 31 | { 32 | public: 33 | InfiniteLimiter() = default; 34 | ~InfiniteLimiter() final = default; 35 | 36 | [[nodiscard]] inline auto stop(const search::SearchData &data, bool allowSoftTimeout) -> bool final 37 | { 38 | return false; 39 | } 40 | 41 | [[nodiscard]] inline auto stopped() const -> bool final 42 | { 43 | return false; 44 | } 45 | }; 46 | 47 | constexpr auto SoftNodeHardLimitMultiplierRange = util::Range{1, 5000}; 48 | 49 | class NodeLimiter final : public ISearchLimiter 50 | { 51 | public: 52 | explicit NodeLimiter(usize maxNodes) : m_maxNodes{maxNodes} {} 53 | 54 | ~NodeLimiter() final = default; 55 | 56 | [[nodiscard]] inline auto stop(const search::SearchData &data, bool allowSoftTimeout) -> bool final 57 | { 58 | // if softLimit is enabled: 59 | // - soft limit: m_maxNodes 60 | // - hard limit: m_maxNodes * softNodeHardLimitMultiplier 61 | // otherwise: 62 | // - no soft limit 63 | // - hard limit: m_maxNodes 64 | 65 | const auto hardLimit = m_maxNodes 66 | * (g_opts.softNodes ? g_opts.softNodeHardLimitMultiplier : 1); 67 | 68 | if (data.nodes >= hardLimit || (g_opts.softNodes && allowSoftTimeout && data.nodes >= m_maxNodes)) 69 | { 70 | m_stopped.store(true, std::memory_order_release); 71 | return true; 72 | } 73 | 74 | return false; 75 | } 76 | 77 | [[nodiscard]] inline auto stopped() const -> bool final 78 | { 79 | return m_stopped.load(std::memory_order_acquire); 80 | } 81 | 82 | private: 83 | usize m_maxNodes; // hard limit when softNodes is disabled, soft limit when enabled 84 | std::atomic_bool m_stopped{false}; 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "uci.h" 20 | #include "bench.h" 21 | #include "datagen/datagen.h" 22 | #include "util/parse.h" 23 | #include "eval/nnue.h" 24 | #include "tunable.h" 25 | #include "cuckoo.h" 26 | #include "util/ctrlc.h" 27 | 28 | #if SP_EXTERNAL_TUNE 29 | #include "util/split.h" 30 | #endif 31 | 32 | using namespace stormphrax; 33 | 34 | auto main(i32 argc, const char *argv[]) -> i32 35 | { 36 | util::signal::init(); 37 | 38 | tunable::init(); 39 | cuckoo::init(); 40 | 41 | eval::loadDefaultNetwork(); 42 | 43 | if (argc > 1) 44 | { 45 | const std::string mode{argv[1]}; 46 | 47 | if (mode == "bench") 48 | { 49 | search::Searcher searcher{bench::DefaultBenchTtSize}; 50 | bench::run(searcher); 51 | 52 | return 0; 53 | } 54 | else if (mode == "datagen") 55 | { 56 | const auto printUsage = [&]() 57 | { 58 | std::cerr << "usage: " << argv[0] 59 | << " datagen [threads] [syzygy path]" 60 | << std::endl; 61 | }; 62 | 63 | if (argc < 5) 64 | { 65 | printUsage(); 66 | return 1; 67 | } 68 | 69 | bool dfrc = false; 70 | 71 | if (std::string{argv[3]} == "dfrc") 72 | dfrc = true; 73 | else if (std::string{argv[3]} != "standard") 74 | { 75 | std::cerr << "invalid variant " << argv[3] << std::endl; 76 | printUsage(); 77 | return 1; 78 | } 79 | 80 | u32 threads = 1; 81 | if (argc > 5 && !util::tryParseU32(threads, argv[5])) 82 | { 83 | std::cerr << "invalid number of threads " << argv[5] << std::endl; 84 | printUsage(); 85 | return 1; 86 | } 87 | 88 | std::optional tbPath{}; 89 | if (argc > 6) 90 | tbPath = std::string{argv[6]}; 91 | 92 | return datagen::run(printUsage, argv[2], dfrc, argv[4], static_cast(threads), tbPath); 93 | } 94 | #if SP_EXTERNAL_TUNE 95 | else if (mode == "printwf" 96 | || mode == "printctt" 97 | || mode == "printob") 98 | { 99 | if (argc == 2) 100 | return 0; 101 | 102 | const auto params = split::split(argv[2], ','); 103 | 104 | if (mode == "printwf") 105 | uci::printWfTuningParams(params); 106 | else if (mode == "printctt") 107 | uci::printCttTuningParams(params); 108 | else if (mode == "printob") 109 | uci::printObTuningParams(params); 110 | 111 | return 0; 112 | } 113 | #endif 114 | } 115 | 116 | return uci::run(); 117 | } 118 | -------------------------------------------------------------------------------- /src/move.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include 24 | #include 25 | 26 | #include "core.h" 27 | #include "util/static_vector.h" 28 | #include "opts.h" 29 | 30 | namespace stormphrax 31 | { 32 | enum class MoveType 33 | { 34 | Standard = 0, 35 | Promotion, 36 | Castling, 37 | EnPassant 38 | }; 39 | 40 | class Move 41 | { 42 | public: 43 | constexpr Move() = default; 44 | constexpr ~Move() = default; 45 | 46 | [[nodiscard]] constexpr auto srcIdx() const { return m_move >> 10; } 47 | [[nodiscard]] constexpr auto src() const { return static_cast(srcIdx()); } 48 | 49 | [[nodiscard]] constexpr auto srcRank() const { return m_move >> 13; } 50 | [[nodiscard]] constexpr auto srcFile() const { return (m_move >> 10) & 0x7; } 51 | 52 | [[nodiscard]] constexpr auto dstIdx() const { return (m_move >> 4) & 0x3F; } 53 | [[nodiscard]] constexpr auto dst() const { return static_cast(dstIdx()); } 54 | 55 | [[nodiscard]] constexpr auto dstRank() const { return (m_move >> 7) & 0x7; } 56 | [[nodiscard]] constexpr auto dstFile() const { return (m_move >> 4) & 0x7; } 57 | 58 | [[nodiscard]] constexpr auto promoIdx() const { return (m_move >> 2) & 0x3; } 59 | [[nodiscard]] constexpr auto promo() const { return static_cast(promoIdx() + 1); } 60 | 61 | [[nodiscard]] constexpr auto type() const { return static_cast(m_move & 0x3); } 62 | 63 | [[nodiscard]] constexpr auto isNull() const { return /*src() == dst()*/ m_move == 0; } 64 | 65 | [[nodiscard]] constexpr auto data() const { return m_move; } 66 | 67 | // returns the king's actual destination square for non-FRC 68 | // castling moves, otherwise just the move's destination 69 | // used to avoid inflating the history of the generally 70 | // bad moves of putting the king in a corner when castling 71 | [[nodiscard]] constexpr auto historyDst() const 72 | { 73 | if (type() == MoveType::Castling && !g_opts.chess960) 74 | return toSquare(srcRank(), srcFile() < dstFile() ? 6 : 2); 75 | else return dst(); 76 | } 77 | 78 | [[nodiscard]] explicit constexpr operator bool() const { return !isNull(); } 79 | 80 | constexpr auto operator==(Move other) const { return m_move == other.m_move; } 81 | 82 | [[nodiscard]] static constexpr auto standard(Square src, Square dst) 83 | { 84 | return Move{static_cast( 85 | (static_cast(src) << 10) 86 | | (static_cast(dst) << 4) 87 | | static_cast(MoveType::Standard) 88 | )}; 89 | } 90 | 91 | [[nodiscard]] static constexpr auto promotion(Square src, Square dst, PieceType promo) 92 | { 93 | return Move{static_cast( 94 | (static_cast(src) << 10) 95 | | (static_cast(dst) << 4) 96 | | ((static_cast(promo) - 1) << 2) 97 | | static_cast(MoveType::Promotion) 98 | )}; 99 | } 100 | 101 | [[nodiscard]] static constexpr auto castling(Square src, Square dst) 102 | { 103 | return Move{static_cast( 104 | (static_cast(src) << 10) 105 | | (static_cast(dst) << 4) 106 | | static_cast(MoveType::Castling) 107 | )}; 108 | } 109 | 110 | [[nodiscard]] static constexpr auto enPassant(Square src, Square dst) 111 | { 112 | return Move{static_cast( 113 | (static_cast(src) << 10) 114 | | (static_cast(dst) << 4) 115 | | static_cast(MoveType::EnPassant) 116 | )}; 117 | } 118 | 119 | private: 120 | explicit constexpr Move(u16 move) : m_move{move} {} 121 | 122 | u16 m_move{}; 123 | }; 124 | 125 | constexpr Move NullMove{}; 126 | 127 | // assumed upper bound for number of possible moves is 218 128 | constexpr usize DefaultMoveListCapacity = 256; 129 | 130 | using MoveList = StaticVector; 131 | } 132 | -------------------------------------------------------------------------------- /src/movegen.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include "move.h" 24 | #include "position/position.h" 25 | 26 | namespace stormphrax 27 | { 28 | struct ScoredMove 29 | { 30 | Move move; 31 | i32 score; 32 | }; 33 | 34 | using ScoredMoveList = StaticVector; 35 | 36 | auto generateNoisy(ScoredMoveList &noisy, const Position &pos) -> void; 37 | auto generateQuiet(ScoredMoveList &quiet, const Position &pos) -> void; 38 | 39 | auto generateAll(ScoredMoveList &dst, const Position &pos) -> void; 40 | } 41 | -------------------------------------------------------------------------------- /src/opts.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "opts.h" 20 | 21 | namespace stormphrax 22 | { 23 | namespace 24 | { 25 | opts::GlobalOptions s_opts{}; 26 | } 27 | 28 | namespace opts 29 | { 30 | auto mutableOpts() -> GlobalOptions & 31 | { 32 | return s_opts; 33 | } 34 | } 35 | 36 | const opts::GlobalOptions &g_opts = s_opts; 37 | } 38 | -------------------------------------------------------------------------------- /src/opts.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include "wdl.h" 24 | #include "util/range.h" 25 | 26 | namespace stormphrax 27 | { 28 | namespace opts 29 | { 30 | constexpr u32 DefaultThreadCount = 1; 31 | constexpr auto ThreadCountRange = util::Range{1, 2048}; 32 | 33 | constexpr i32 DefaultNormalizedContempt = 0; 34 | 35 | struct GlobalOptions 36 | { 37 | u32 threads{DefaultThreadCount}; 38 | 39 | bool chess960{false}; 40 | bool showWdl{true}; 41 | bool showCurrMove{false}; 42 | 43 | bool softNodes{false}; 44 | i32 softNodeHardLimitMultiplier{1678}; 45 | 46 | bool enableWeirdTcs{false}; 47 | 48 | bool syzygyEnabled{false}; 49 | i32 syzygyProbeDepth{1}; 50 | i32 syzygyProbeLimit{7}; 51 | 52 | i32 contempt{wdl::unnormalizeScoreMaterial58(DefaultNormalizedContempt)}; 53 | }; 54 | 55 | auto mutableOpts() -> GlobalOptions &; 56 | } 57 | 58 | extern const opts::GlobalOptions &g_opts; 59 | } 60 | -------------------------------------------------------------------------------- /src/perft.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "perft.h" 20 | 21 | #include 22 | 23 | #include "movegen.h" 24 | #include "uci.h" 25 | #include "util/timer.h" 26 | 27 | namespace stormphrax 28 | { 29 | using util::Instant; 30 | 31 | namespace 32 | { 33 | auto doPerft(const Position &pos, i32 depth) -> usize 34 | { 35 | if (depth == 0) 36 | return 1; 37 | 38 | --depth; 39 | 40 | ScoredMoveList moves{}; 41 | generateAll(moves, pos); 42 | 43 | usize total{}; 44 | 45 | for (const auto [move, score] : moves) 46 | { 47 | if (!pos.isLegal(move)) 48 | continue; 49 | 50 | if (depth == 0) 51 | ++total; 52 | else 53 | { 54 | const auto newPos = pos.applyMove(move); 55 | total += doPerft(newPos, depth); 56 | } 57 | } 58 | 59 | return total; 60 | } 61 | } 62 | 63 | auto perft(const Position &pos, i32 depth) -> void 64 | { 65 | std::cout << doPerft(pos, depth) << std::endl; 66 | } 67 | 68 | auto splitPerft(const Position &pos, i32 depth) -> void 69 | { 70 | --depth; 71 | 72 | const auto start = Instant::now(); 73 | 74 | ScoredMoveList moves{}; 75 | generateAll(moves, pos); 76 | 77 | usize total{}; 78 | 79 | for (const auto [move, score] : moves) 80 | { 81 | if (!pos.isLegal(move)) 82 | continue; 83 | 84 | const auto newPos = pos.applyMove(move); 85 | const auto value = doPerft(newPos, depth); 86 | 87 | total += value; 88 | std::cout << uci::moveToString(move) << '\t' << value << '\n'; 89 | } 90 | 91 | const auto nps = static_cast(static_cast(total) / start.elapsed()); 92 | 93 | std::cout << "\ntotal " << total << '\n'; 94 | std::cout << nps << " nps" << std::endl; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/perft.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include "core.h" 24 | #include "position/position.h" 25 | 26 | namespace stormphrax 27 | { 28 | auto perft(const Position &pos, i32 depth) -> void; 29 | auto splitPerft(const Position &pos, i32 depth) -> void; 30 | } 31 | -------------------------------------------------------------------------------- /src/pretty.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "pretty.h" 20 | 21 | namespace stormphrax 22 | { 23 | auto printBitboard(std::ostream &out, Bitboard board) -> void 24 | { 25 | for (i32 rank = 7; rank >= 0; --rank) 26 | { 27 | out << " +---+---+---+---+---+---+---+---+\n"; 28 | 29 | for (usize file = 0; file < 8; ++file) 30 | { 31 | out << " | " << (board[toSquare(rank, file)] ? '1' : ' '); 32 | } 33 | 34 | out << " | " << (rank + 1) << "\n"; 35 | } 36 | 37 | out << " +---+---+---+---+---+---+---+---+\n"; 38 | out << " a b c d e f g h\n\n"; 39 | } 40 | 41 | auto printBitboardCompact(std::ostream &out, Bitboard board) -> void 42 | { 43 | for (i32 rank = 7; rank >= 0; --rank) 44 | { 45 | for (usize file = 0; file < 8; ++file) 46 | { 47 | if (file > 0) 48 | out << ' '; 49 | 50 | out << (board[toSquare(rank, file)] ? '1' : '.'); 51 | } 52 | 53 | out << "\n"; 54 | } 55 | } 56 | 57 | auto printBoard(std::ostream &out, const Position &position) -> void 58 | { 59 | const auto &boards = position.boards(); 60 | 61 | for (i32 rank = 7; rank >= 0; --rank) 62 | { 63 | out << " +---+---+---+---+---+---+---+---+\n"; 64 | 65 | for (usize file = 0; file < 8; ++file) 66 | { 67 | out << " | " << pieceToChar(boards.pieceAt(rank, file)); 68 | } 69 | 70 | out << " | " << (rank + 1) << "\n"; 71 | } 72 | 73 | out << " +---+---+---+---+---+---+---+---+\n"; 74 | out << " a b c d e f g h\n\n"; 75 | 76 | out << (position.toMove() == Color::White ? "White" : "Black") << " to move\n"; 77 | } 78 | 79 | auto printScore(std::ostream &out, Score score) -> void 80 | { 81 | if (score == 0) 82 | { 83 | out << "0.00"; 84 | return; 85 | } 86 | 87 | out << (score < 0 ? '-' : '+'); 88 | 89 | if (score < 0) 90 | score = -score; 91 | 92 | out << (score / 100) << '.'; 93 | 94 | const auto d = score % 100; 95 | 96 | if (d < 10) 97 | out << '0'; 98 | 99 | out << d; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/pretty.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include 24 | 25 | #include "bitboard.h" 26 | #include "position/position.h" 27 | 28 | namespace stormphrax 29 | { 30 | auto printBitboard(std::ostream &out, Bitboard board) -> void; 31 | auto printBitboardCompact(std::ostream &out, Bitboard board) -> void; 32 | 33 | auto printBoard(std::ostream &out, const Position &position) -> void; 34 | 35 | auto printScore(std::ostream &out, Score score) -> void; 36 | } 37 | -------------------------------------------------------------------------------- /src/rays.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include 24 | 25 | #include "core.h" 26 | #include "bitboard.h" 27 | #include "attacks/util.h" 28 | #include "util/multi_array.h" 29 | 30 | namespace stormphrax 31 | { 32 | consteval auto generateBetweenRays() 33 | { 34 | util::MultiArray dst{}; 35 | 36 | for (i32 from = 0; from < 64; ++from) 37 | { 38 | const auto srcSquare = static_cast(from); 39 | const auto srcMask = squareBit(srcSquare); 40 | 41 | const auto rookAttacks = attacks::EmptyBoardRooks [from]; 42 | const auto bishopAttacks = attacks::EmptyBoardBishops[from]; 43 | 44 | for (i32 to = 0; to < 64; ++to) 45 | { 46 | if (from == to) 47 | continue; 48 | 49 | const auto dstSquare = static_cast(to); 50 | const auto dstMask = squareBit(dstSquare); 51 | 52 | if (rookAttacks[dstSquare]) 53 | dst[from][to] 54 | = attacks::genRookAttacks(srcSquare, dstMask) 55 | & attacks::genRookAttacks(dstSquare, srcMask); 56 | else if (bishopAttacks[dstSquare]) 57 | dst[from][to] 58 | = attacks::genBishopAttacks(srcSquare, dstMask) 59 | & attacks::genBishopAttacks(dstSquare, srcMask); 60 | } 61 | } 62 | 63 | return dst; 64 | } 65 | 66 | consteval auto generateIntersectingRays() 67 | { 68 | util::MultiArray dst{}; 69 | 70 | for (i32 from = 0; from < 64; ++from) 71 | { 72 | const auto srcSquare = static_cast(from); 73 | const auto srcMask = squareBit(srcSquare); 74 | 75 | const auto rookAttacks = attacks::EmptyBoardRooks [from]; 76 | const auto bishopAttacks = attacks::EmptyBoardBishops[from]; 77 | 78 | for (i32 to = 0; to < 64; ++to) 79 | { 80 | if (from == to) 81 | continue; 82 | 83 | const auto dstSquare = static_cast(to); 84 | const auto dstMask = squareBit(dstSquare); 85 | 86 | if (rookAttacks[dstSquare]) 87 | dst[from][to] 88 | = (srcMask | attacks::genRookAttacks(srcSquare, Bitboard{})) 89 | & (dstMask | attacks::genRookAttacks(dstSquare, Bitboard{})); 90 | else if (bishopAttacks[dstSquare]) 91 | dst[from][to] 92 | = (srcMask | attacks::genBishopAttacks(srcSquare, Bitboard{})) 93 | & (dstMask | attacks::genBishopAttacks(dstSquare, Bitboard{})); 94 | } 95 | } 96 | 97 | return dst; 98 | } 99 | 100 | constexpr auto BetweenRays = generateBetweenRays(); 101 | constexpr auto IntersectingRays = generateIntersectingRays(); 102 | 103 | constexpr auto rayBetween(Square src, Square dst) 104 | { 105 | return BetweenRays[static_cast(src)][static_cast(dst)]; 106 | } 107 | 108 | constexpr auto rayIntersecting(Square src, Square dst) 109 | { 110 | return IntersectingRays[static_cast(src)][static_cast(dst)]; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/search_fwd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include 24 | 25 | #include "move.h" 26 | 27 | namespace stormphrax::search 28 | { 29 | struct SearchData 30 | { 31 | i32 rootDepth{}; 32 | 33 | std::atomic seldepth{}; 34 | std::atomic nodes{}; 35 | std::atomic tbhits{}; 36 | 37 | SearchData() = default; 38 | 39 | SearchData(const SearchData &other) 40 | { 41 | *this = other; 42 | } 43 | 44 | [[nodiscard]] inline auto loadSeldepth() const 45 | { 46 | return seldepth.load(std::memory_order::relaxed); 47 | } 48 | 49 | inline auto updateSeldepth(i32 v) 50 | { 51 | if (v > loadSeldepth()) 52 | seldepth.store(v, std::memory_order::relaxed); 53 | } 54 | 55 | [[nodiscard]] inline auto loadNodes() const 56 | { 57 | return nodes.load(std::memory_order::relaxed); 58 | } 59 | 60 | inline auto incNodes() 61 | { 62 | nodes.fetch_add(1, std::memory_order::relaxed); 63 | } 64 | 65 | [[nodiscard]] inline auto loadTbHits() const 66 | { 67 | return tbhits.load(std::memory_order::relaxed); 68 | } 69 | 70 | inline auto incTbHits() 71 | { 72 | tbhits.fetch_add(1, std::memory_order::relaxed); 73 | } 74 | 75 | auto operator=(const SearchData &other) -> SearchData & 76 | { 77 | rootDepth = other.rootDepth; 78 | 79 | seldepth.store(other.seldepth.load()); 80 | nodes.store(other.nodes.load()); 81 | tbhits.store(other.tbhits.load()); 82 | 83 | return *this; 84 | } 85 | }; 86 | 87 | struct PlayedMove 88 | { 89 | Piece moving; 90 | Square dst; 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /src/see.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include 24 | 25 | #include "core.h" 26 | #include "tunable.h" 27 | #include "position/position.h" 28 | #include "attacks/attacks.h" 29 | #include "rays.h" 30 | 31 | namespace stormphrax::see 32 | { 33 | constexpr auto value(Piece piece) 34 | { 35 | return tunable::g_seeValues[static_cast(piece)]; 36 | } 37 | 38 | constexpr auto value(PieceType piece) 39 | { 40 | return tunable::g_seeValues[static_cast(piece) * 2]; 41 | } 42 | 43 | inline auto gain(const PositionBoards &boards, Move move) 44 | { 45 | const auto type = move.type(); 46 | 47 | if (type == MoveType::Castling) 48 | return 0; 49 | else if (type == MoveType::EnPassant) 50 | return value(PieceType::Pawn); 51 | 52 | auto score = value(boards.pieceAt(move.dst())); 53 | 54 | if (type == MoveType::Promotion) 55 | score += value(move.promo()) - value(PieceType::Pawn); 56 | 57 | return score; 58 | } 59 | 60 | [[nodiscard]] inline auto popLeastValuable(const BitboardSet &bbs, 61 | Bitboard &occ, Bitboard attackers, Color color) 62 | { 63 | for (i32 i = 0; i < 6; ++i) 64 | { 65 | const auto piece = static_cast(i); 66 | auto board = attackers & bbs.forPiece(piece, color); 67 | 68 | if (!board.empty()) 69 | { 70 | occ ^= board.lowestBit(); 71 | return piece; 72 | } 73 | } 74 | 75 | return PieceType::None; 76 | } 77 | 78 | // basically ported from ethereal and weiss (their implementation is the same) 79 | inline auto see(const Position &pos, Move move, Score threshold) 80 | { 81 | const auto &boards = pos.boards(); 82 | const auto &bbs = boards.bbs(); 83 | 84 | const auto color = pos.toMove(); 85 | 86 | auto score = gain(boards, move) - threshold; 87 | 88 | if (score < 0) 89 | return false; 90 | 91 | auto next = move.type() == MoveType::Promotion 92 | ? move.promo() 93 | : pieceType(boards.pieceAt(move.src())); 94 | 95 | score -= value(next); 96 | 97 | if (score >= 0) 98 | return true; 99 | 100 | const auto square = move.dst(); 101 | 102 | auto occupancy = bbs.occupancy() 103 | ^ squareBit(move.src()) 104 | ^ squareBit(square); 105 | 106 | const auto queens = bbs.queens(); 107 | 108 | const auto bishops = queens | bbs.bishops(); 109 | const auto rooks = queens | bbs.rooks(); 110 | 111 | const auto blackPinned = pos.pinned(Color::Black); 112 | const auto whitePinned = pos.pinned(Color::White); 113 | 114 | const auto blackKingRay = rayIntersecting(pos.blackKing(), square); 115 | const auto whiteKingRay = rayIntersecting(pos.whiteKing(), square); 116 | 117 | const auto allowed = ~(blackPinned | whitePinned) | (blackPinned & blackKingRay) | (whitePinned & whiteKingRay); 118 | 119 | auto attackers = pos.allAttackersTo(square, occupancy) & allowed; 120 | 121 | auto us = oppColor(color); 122 | 123 | while (true) 124 | { 125 | const auto ourAttackers = attackers & bbs.forColor(us); 126 | 127 | if (ourAttackers.empty()) 128 | break; 129 | 130 | next = popLeastValuable(bbs, occupancy, ourAttackers, us); 131 | 132 | if (next == PieceType::Pawn 133 | || next == PieceType::Bishop 134 | || next == PieceType::Queen) 135 | attackers |= attacks::getBishopAttacks(square, occupancy) & bishops; 136 | 137 | if (next == PieceType::Rook 138 | || next == PieceType::Queen) 139 | attackers |= attacks::getRookAttacks(square, occupancy) & rooks; 140 | 141 | attackers &= occupancy; 142 | 143 | score = -score - 1 - value(next); 144 | us = oppColor(us); 145 | 146 | if (score >= 0) 147 | { 148 | // our only attacker is our king, but the opponent still has defenders 149 | if (next == PieceType::King 150 | && !(attackers & bbs.forColor(us)).empty()) 151 | us = oppColor(us); 152 | break; 153 | } 154 | } 155 | 156 | return color != us; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/stats.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "stats.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "util/multi_array.h" 27 | 28 | namespace stormphrax::stats 29 | { 30 | namespace 31 | { 32 | constexpr usize Slots = 32; 33 | 34 | struct Range 35 | { 36 | std::atomic min{std::numeric_limits::max()}; 37 | std::atomic max{std::numeric_limits::min()}; 38 | }; 39 | 40 | util::MultiArray, Slots, 2> s_conditionHits{}; 41 | util::MultiArray s_ranges{}; 42 | util::MultiArray, std::atomic>, Slots> s_means{}; 43 | 44 | std::atomic_bool s_anyUsed{false}; 45 | 46 | template 47 | inline auto atomicMin(std::atomic &v, T x) 48 | { 49 | auto curr = v.load(); 50 | while (x < curr && v.compare_exchange_weak(curr, x)) {} 51 | } 52 | 53 | template 54 | inline auto atomicMax(std::atomic &v, T x) 55 | { 56 | auto curr = v.load(); 57 | while (x > curr && v.compare_exchange_weak(curr, x)) {} 58 | } 59 | } 60 | 61 | auto conditionHit(bool condition, usize slot) -> void 62 | { 63 | if (slot >= Slots) 64 | { 65 | std::cerr << "tried to hit condition " << slot << " (max " << (Slots - 1) << ")" << std::endl; 66 | return; 67 | } 68 | 69 | ++s_conditionHits[slot][condition]; 70 | 71 | s_anyUsed = true; 72 | } 73 | 74 | auto range(i64 v, usize slot) -> void 75 | { 76 | if (slot >= Slots) 77 | { 78 | std::cerr << "tried to hit range " << slot << " (max " << (Slots - 1) << ")" << std::endl; 79 | return; 80 | } 81 | 82 | atomicMin(s_ranges[slot].min, v); 83 | atomicMax(s_ranges[slot].max, v); 84 | 85 | s_anyUsed = true; 86 | } 87 | 88 | auto mean(i64 v, usize slot) -> void 89 | { 90 | if (slot >= Slots) 91 | { 92 | std::cerr << "tried to hit mean " << slot << " (max " << (Slots - 1) << ")" << std::endl; 93 | return; 94 | } 95 | 96 | s_means[slot].first += v; 97 | ++s_means[slot].second; 98 | 99 | s_anyUsed = true; 100 | } 101 | 102 | auto print() -> void 103 | { 104 | if (!s_anyUsed.load()) 105 | return; 106 | 107 | for (usize slot = 0; slot < Slots; ++slot) 108 | { 109 | const auto hits = s_conditionHits[slot][1].load(); 110 | const auto misses = s_conditionHits[slot][0].load(); 111 | 112 | if (hits == 0 && misses == 0) 113 | continue; 114 | 115 | const auto hitrate = static_cast(hits) / static_cast(hits + misses); 116 | 117 | std::cout << "condition " << slot << ":\n"; 118 | std::cout << " hits: " << hits << "\n"; 119 | std::cout << " misses: " << misses << "\n"; 120 | std::cout << " hitrate: " << (hitrate * 100) << "%" << std::endl; 121 | } 122 | 123 | for (usize slot = 0; slot < Slots; ++slot) 124 | { 125 | const auto min = s_ranges[slot].min.load(); 126 | const auto max = s_ranges[slot].max.load(); 127 | 128 | if (min == std::numeric_limits::max()) 129 | continue; 130 | 131 | std::cout << "range " << slot << ":\n"; 132 | std::cout << " min: " << min << "\n"; 133 | std::cout << " max: " << max << std::endl; 134 | } 135 | 136 | for (usize slot = 0; slot < Slots; ++slot) 137 | { 138 | const auto total = s_means[slot].first.load(); 139 | const auto count = s_means[slot].second.load(); 140 | 141 | if (count == 0) 142 | continue; 143 | 144 | const auto mean = static_cast(total) / static_cast(count); 145 | 146 | std::cout << "mean " << slot << ":\n"; 147 | std::cout << " mean: " << mean << "\n"; 148 | std::cout << " total: " << total << "\n"; 149 | std::cout << " count: " << count << std::endl; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/stats.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | namespace stormphrax::stats 24 | { 25 | auto conditionHit(bool condition, usize slot = 0) -> void; 26 | auto range(i64 value, usize slot = 0) -> void; 27 | auto mean(i64 value, usize slot = 0) -> void; 28 | 29 | auto print() -> void; 30 | } 31 | -------------------------------------------------------------------------------- /src/tb.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "tb.h" 20 | 21 | #include "move.h" 22 | #include "3rdparty/fathom/tbprobe.h" 23 | 24 | namespace stormphrax::tb 25 | { 26 | auto probeRoot(MoveList &rootMoves, const Position &pos) -> ProbeResult 27 | { 28 | const auto moveFromTb = [&pos](auto tbMove) 29 | { 30 | static constexpr auto PromoPieces = std::array { 31 | PieceType::None, 32 | PieceType::Queen, 33 | PieceType::Rook, 34 | PieceType::Bishop, 35 | PieceType::Knight 36 | }; 37 | 38 | const auto src = static_cast(TB_MOVE_FROM(tbMove)); 39 | const auto dst = static_cast(TB_MOVE_TO (tbMove)); 40 | const auto promo = PromoPieces[TB_MOVE_PROMOTES(tbMove)]; 41 | 42 | if (promo != PieceType::None) 43 | return Move::promotion(src, dst, promo); 44 | else if (dst == pos.enPassant() 45 | && pos.boards().pieceTypeAt(src) == PieceType::Pawn) 46 | return Move::enPassant(src, dst); 47 | // Syzygy TBs do not encode positions with castling rights 48 | else return Move::standard(src, dst); 49 | }; 50 | 51 | const auto &bbs = pos.bbs(); 52 | 53 | TbRootMoves tbRootMoves{}; 54 | 55 | const auto epSq = pos.enPassant(); 56 | auto result = tb_probe_root_dtz( 57 | bbs.whiteOccupancy(), 58 | bbs.blackOccupancy(), 59 | bbs.kings(), 60 | bbs.queens(), 61 | bbs.rooks(), 62 | bbs.bishops(), 63 | bbs.knights(), 64 | bbs.pawns(), 65 | pos.halfmove(), 0, 66 | epSq == Square::None ? 0 : static_cast(epSq), 67 | pos.toMove() == Color::White, 68 | false /*TODO*/, true, &tbRootMoves 69 | ); 70 | 71 | if (!result) // DTZ tables unavailable, fall back to WDL 72 | result = tb_probe_root_wdl( 73 | bbs.whiteOccupancy(), 74 | bbs.blackOccupancy(), 75 | bbs.kings(), 76 | bbs.queens(), 77 | bbs.rooks(), 78 | bbs.bishops(), 79 | bbs.knights(), 80 | bbs.pawns(), 81 | pos.halfmove(), 0, 82 | epSq == Square::None ? 0 : static_cast(epSq), 83 | pos.toMove() == Color::White, 84 | true, &tbRootMoves 85 | ); 86 | 87 | if (!result 88 | || tbRootMoves.size == 0) // mate or stalemate at root, handled by search 89 | return ProbeResult::Failed; 90 | 91 | std::sort(&tbRootMoves.moves[0], &tbRootMoves.moves[tbRootMoves.size], [](auto a, auto b) 92 | { 93 | return a.tbRank > b.tbRank; 94 | }); 95 | 96 | const auto [wdl, minRank] = [&]() -> std::pair 97 | { 98 | const auto best = tbRootMoves.moves[0]; 99 | 100 | if (best.tbRank >= 900) 101 | return {ProbeResult::Win, 900}; 102 | else if (best.tbRank >= -899) // includes cursed wins and blessed losses 103 | return {ProbeResult::Draw, -899}; 104 | else return {ProbeResult::Loss, -1000}; 105 | }(); 106 | 107 | for (u32 i = 0; i < tbRootMoves.size; ++i) 108 | { 109 | const auto move = tbRootMoves.moves[i]; 110 | 111 | if (move.tbRank < minRank) 112 | break; 113 | 114 | rootMoves.push(moveFromTb(move.move)); 115 | } 116 | 117 | return wdl; 118 | } 119 | 120 | auto probe(const Position &pos) -> ProbeResult 121 | { 122 | const auto &bbs = pos.bbs(); 123 | 124 | const auto epSq = pos.enPassant(); 125 | const auto wdl = tb_probe_wdl( 126 | bbs.whiteOccupancy(), 127 | bbs.blackOccupancy(), 128 | bbs.kings(), 129 | bbs.queens(), 130 | bbs.rooks(), 131 | bbs.bishops(), 132 | bbs.knights(), 133 | bbs.pawns(), 134 | 0, 0, 135 | epSq == Square::None ? 0 : static_cast(epSq), 136 | pos.toMove() == Color::White 137 | ); 138 | 139 | if (wdl == TB_RESULT_FAILED) 140 | return ProbeResult::Failed; 141 | 142 | if (wdl == TB_WIN) 143 | return ProbeResult::Win; 144 | else if (wdl == TB_LOSS) 145 | return ProbeResult::Loss; 146 | else return ProbeResult::Draw; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/tb.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include "position/position.h" 24 | #include "movegen.h" 25 | #include "ttable.h" 26 | 27 | namespace stormphrax::tb 28 | { 29 | enum class ProbeResult 30 | { 31 | Failed, 32 | Win, 33 | Draw, 34 | Loss 35 | }; 36 | 37 | auto probeRoot(MoveList &rootMoves, const Position &pos) -> ProbeResult; 38 | auto probe(const Position &pos) -> ProbeResult; 39 | } 40 | -------------------------------------------------------------------------------- /src/ttable.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "core.h" 29 | #include "move.h" 30 | #include "util/range.h" 31 | #include "arch.h" 32 | 33 | namespace stormphrax 34 | { 35 | constexpr usize DefaultTtSizeMib = 64; 36 | constexpr util::Range TtSizeMibRange{1, 67108864}; 37 | 38 | enum class TtFlag : u8 39 | { 40 | None = 0, 41 | UpperBound, 42 | LowerBound, 43 | Exact 44 | }; 45 | 46 | struct ProbedTTableEntry 47 | { 48 | Score score; 49 | Score staticEval; 50 | i32 depth; 51 | Move move; 52 | bool wasPv; 53 | TtFlag flag; 54 | }; 55 | 56 | class TTable 57 | { 58 | public: 59 | explicit TTable(usize mib = DefaultTtSizeMib); 60 | ~TTable(); 61 | 62 | auto resize(usize mib) -> void; 63 | auto finalize() -> bool; 64 | 65 | auto probe(ProbedTTableEntry &dst, u64 key, i32 ply) const -> bool; 66 | auto put(u64 key, Score score, Score staticEval, Move move, i32 depth, i32 ply, TtFlag flag, bool pv) -> void; 67 | 68 | inline auto age() 69 | { 70 | m_age = (m_age + 1) % (1 << Entry::AgeBits); 71 | } 72 | 73 | auto clear() -> void; 74 | 75 | [[nodiscard]] auto full() const -> u32; 76 | 77 | inline auto prefetch(u64 key) 78 | { 79 | __builtin_prefetch(&m_clusters[index(key)]); 80 | } 81 | 82 | private: 83 | struct Entry 84 | { 85 | static constexpr u32 AgeBits = 5; 86 | 87 | static constexpr u32 AgeCycle = 1 << AgeBits; 88 | static constexpr u32 AgeMask = AgeCycle - 1; 89 | 90 | u16 key; 91 | i16 score; 92 | i16 staticEval; 93 | Move move; 94 | u8 depth; 95 | u8 agePvFlag; 96 | 97 | [[nodiscard]] inline auto age() const 98 | { 99 | return static_cast(agePvFlag >> 3); 100 | } 101 | 102 | [[nodiscard]] inline auto pv() const 103 | { 104 | return (static_cast(agePvFlag >> 2) & 1) != 0; 105 | } 106 | 107 | [[nodiscard]] inline auto flag() const 108 | { 109 | return static_cast(agePvFlag & 0x3); 110 | } 111 | 112 | inline auto setAgePvFlag(u32 age, bool pv, TtFlag flag) 113 | { 114 | assert(age < (1 << AgeBits)); 115 | agePvFlag = (age << 3) | (static_cast(pv) << 2) | static_cast(flag); 116 | } 117 | }; 118 | 119 | static_assert(sizeof(Entry) == 10); 120 | 121 | static constexpr usize ClusterAlignment = 32; 122 | static constexpr auto StorageAlignment = std::max(CacheLineSize, ClusterAlignment); 123 | 124 | struct alignas(32) Cluster 125 | { 126 | static constexpr usize EntriesPerCluster = 3; 127 | 128 | std::array entries{}; 129 | 130 | // round up to nearest power of 2 bytes 131 | [[maybe_unused]] std::array padding{}; 134 | }; 135 | 136 | [[nodiscard]] inline auto index(u64 key) const -> u64 137 | { 138 | // this emits a single mul on both x64 and arm64 139 | return static_cast((static_cast(key) * static_cast(m_clusterCount)) >> 64); 140 | } 141 | 142 | // Only accessed from UCI thread 143 | bool m_pendingInit{}; 144 | 145 | Cluster *m_clusters{}; 146 | usize m_clusterCount{}; 147 | 148 | u32 m_age{}; 149 | }; 150 | } 151 | -------------------------------------------------------------------------------- /src/tunable.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "tunable.h" 20 | 21 | #include 22 | 23 | namespace stormphrax::tunable 24 | { 25 | namespace 26 | { 27 | inline auto lmrReduction(f64 base, f64 divisor, i32 depth, i32 moves) 28 | { 29 | const auto lnDepth = std::log(static_cast(depth)); 30 | const auto lnMoves = std::log(static_cast(moves)); 31 | return static_cast(128.0 * (base + lnDepth * lnMoves / divisor)); 32 | } 33 | } 34 | 35 | util::MultiArray g_lmrTable{}; 36 | std::array g_seeValues{}; 37 | 38 | auto updateQuietLmrTable() -> void 39 | { 40 | const auto base = static_cast(quietLmrBase()) / 100.0; 41 | const auto divisor = static_cast(quietLmrDivisor()) / 100.0; 42 | 43 | for (i32 depth = 1; depth < 256; ++depth) 44 | { 45 | for (i32 moves = 1; moves < 256; ++moves) 46 | { 47 | g_lmrTable[0][depth][moves] = lmrReduction(base, divisor, depth, moves); 48 | } 49 | } 50 | } 51 | 52 | auto updateNoisyLmrTable() -> void 53 | { 54 | const auto base = static_cast(noisyLmrBase()) / 100.0; 55 | const auto divisor = static_cast(noisyLmrDivisor()) / 100.0; 56 | 57 | for (i32 depth = 1; depth < 256; ++depth) 58 | { 59 | for (i32 moves = 1; moves < 256; ++moves) 60 | { 61 | g_lmrTable[1][depth][moves] = lmrReduction(base, divisor, depth, moves); 62 | } 63 | } 64 | } 65 | 66 | auto updateSeeValueTable() -> void 67 | { 68 | // king and none 69 | g_seeValues.fill(0); 70 | 71 | const auto scores = std::array { 72 | seeValuePawn(), 73 | seeValueKnight(), 74 | seeValueBishop(), 75 | seeValueRook(), 76 | seeValueQueen(), 77 | }; 78 | 79 | for (usize i = 0; i < scores.size(); ++i) 80 | { 81 | g_seeValues[i * 2 + 0] = scores[i]; 82 | g_seeValues[i * 2 + 1] = scores[i]; 83 | } 84 | } 85 | 86 | auto init() -> void 87 | { 88 | updateQuietLmrTable(); 89 | updateNoisyLmrTable(); 90 | 91 | updateSeeValueTable(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | namespace stormphrax 26 | { 27 | using u8 = std::uint8_t; 28 | using u16 = std::uint16_t; 29 | using u32 = std::uint32_t; 30 | using u64 = std::uint64_t; 31 | using u128 = unsigned __int128; 32 | 33 | using i8 = std::int8_t; 34 | using i16 = std::int16_t; 35 | using i32 = std::int32_t; 36 | using i64 = std::int64_t; 37 | using i128 = __int128; 38 | 39 | using f32 = float; 40 | using f64 = double; 41 | 42 | using usize = std::size_t; 43 | 44 | [[noreturn]] inline auto unimplemented() 45 | { 46 | std::terminate(); 47 | } 48 | } 49 | 50 | #define I64(V) INT64_C(V) 51 | #define U64(V) UINT64_C(V) 52 | 53 | #define SP_STRINGIFY_(S) #S 54 | #define SP_STRINGIFY(S) SP_STRINGIFY_(S) 55 | 56 | #ifndef NDEBUG 57 | #define SP_ALWAYS_INLINE_NDEBUG 58 | #else 59 | #define SP_ALWAYS_INLINE_NDEBUG __attribute__((always_inline)) 60 | #endif 61 | -------------------------------------------------------------------------------- /src/uci.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include 24 | #include 25 | 26 | #include "core.h" 27 | #include "move.h" 28 | #include "tunable.h" 29 | 30 | namespace stormphrax::uci 31 | { 32 | constexpr auto ContemptRange = util::Range{-1000, 1000}; 33 | 34 | auto run() -> i32; 35 | 36 | [[nodiscard]] auto moveToString(Move move) -> std::string; 37 | 38 | #if SP_EXTERNAL_TUNE 39 | auto printWfTuningParams(std::span params) -> void; 40 | auto printCttTuningParams(std::span params) -> void; 41 | auto printObTuningParams(std::span params) -> void; 42 | #endif 43 | } 44 | -------------------------------------------------------------------------------- /src/util/align.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | 25 | namespace stormphrax::util 26 | { 27 | template 28 | constexpr auto isAligned(const T *ptr) 29 | { 30 | return (reinterpret_cast(ptr) % Alignment) == 0; 31 | } 32 | 33 | template 34 | inline auto alignedAlloc(usize alignment, usize count) 35 | { 36 | const auto size = count * sizeof(T); 37 | 38 | #ifdef _WIN32 39 | return static_cast(_aligned_malloc(size, alignment)); 40 | #else 41 | return static_cast(std::aligned_alloc(alignment, size)); 42 | #endif 43 | } 44 | 45 | inline auto alignedFree(void *ptr) 46 | { 47 | if (!ptr) 48 | return; 49 | 50 | #ifdef _WIN32 51 | _aligned_free(ptr); 52 | #else 53 | std::free(ptr); 54 | #endif 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/util/aligned_array.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | #include 25 | 26 | namespace stormphrax::util 27 | { 28 | template 29 | class AlignedArray 30 | { 31 | public: 32 | [[nodiscard]] constexpr auto at(usize idx) -> auto & 33 | { 34 | return m_array.at(idx); 35 | } 36 | 37 | [[nodiscard]] constexpr auto at(usize idx) const -> const auto & 38 | { 39 | return m_array.at(idx); 40 | } 41 | 42 | [[nodiscard]] constexpr auto operator[](usize idx) -> auto & 43 | { 44 | return m_array[idx]; 45 | } 46 | 47 | [[nodiscard]] constexpr auto operator[](usize idx) const -> const auto & 48 | { 49 | return m_array[idx]; 50 | } 51 | 52 | [[nodiscard]] constexpr auto front() -> auto & 53 | { 54 | return m_array.front(); 55 | } 56 | 57 | [[nodiscard]] constexpr auto front() const -> const auto & 58 | { 59 | return m_array.front(); 60 | } 61 | 62 | [[nodiscard]] constexpr auto back() -> auto & 63 | { 64 | return m_array.back(); 65 | } 66 | 67 | [[nodiscard]] constexpr auto back() const -> const auto & 68 | { 69 | return m_array.back(); 70 | } 71 | 72 | [[nodiscard]] constexpr auto data() 73 | { 74 | return m_array.data(); 75 | } 76 | 77 | [[nodiscard]] constexpr auto data() const 78 | { 79 | return m_array.data(); 80 | } 81 | 82 | [[nodiscard]] constexpr auto begin() 83 | { 84 | return m_array.begin(); 85 | } 86 | 87 | [[nodiscard]] constexpr auto begin() const 88 | { 89 | return m_array.begin(); 90 | } 91 | 92 | [[nodiscard]] constexpr auto cbegin() const 93 | { 94 | return m_array.cbegin(); 95 | } 96 | 97 | [[nodiscard]] constexpr auto end() 98 | { 99 | return m_array.end(); 100 | } 101 | 102 | [[nodiscard]] constexpr auto end() const 103 | { 104 | return m_array.end(); 105 | } 106 | 107 | [[nodiscard]] constexpr auto cend() const 108 | { 109 | return m_array.cend(); 110 | } 111 | 112 | [[nodiscard]] constexpr auto rbegin() 113 | { 114 | return m_array.rbegin(); 115 | } 116 | 117 | [[nodiscard]] constexpr auto rbegin() const 118 | { 119 | return m_array.rbegin(); 120 | } 121 | 122 | [[nodiscard]] constexpr auto crbegin() const 123 | { 124 | return m_array.crbegin(); 125 | } 126 | 127 | [[nodiscard]] constexpr auto rend() 128 | { 129 | return m_array.rend(); 130 | } 131 | 132 | [[nodiscard]] constexpr auto rend() const 133 | { 134 | return m_array.rend(); 135 | } 136 | 137 | [[nodiscard]] constexpr auto crend() const 138 | { 139 | return m_array.crend(); 140 | } 141 | 142 | [[nodiscard]] constexpr auto empty() const 143 | { 144 | return m_array.empty(); 145 | } 146 | 147 | [[nodiscard]] constexpr auto size() const 148 | { 149 | return m_array.size(); 150 | } 151 | 152 | [[nodiscard]] constexpr auto max_size() const 153 | { 154 | return m_array.max_size(); 155 | } 156 | 157 | constexpr auto fill(const T &value) 158 | { 159 | m_array.fill(value); 160 | } 161 | 162 | constexpr auto swap(AlignedArray &other) 163 | { 164 | m_array.swap(other.m_array); 165 | } 166 | 167 | [[nodiscard]] constexpr auto array() -> auto & 168 | { 169 | return m_array; 170 | } 171 | 172 | [[nodiscard]] constexpr auto array() const -> const auto & 173 | { 174 | return m_array; 175 | } 176 | 177 | constexpr operator std::array &() 178 | { 179 | return m_array; 180 | } 181 | 182 | constexpr operator const std::array &() const 183 | { 184 | return m_array; 185 | } 186 | 187 | constexpr operator std::span() 188 | { 189 | return m_array; 190 | } 191 | 192 | constexpr operator std::span() const 193 | { 194 | return m_array; 195 | } 196 | 197 | private: 198 | alignas(Alignment) std::array m_array; 199 | }; 200 | 201 | template 202 | auto swap(AlignedArray &a, AlignedArray &b) 203 | { 204 | a.swap(b); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/util/barrier.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | namespace stormphrax::util 29 | { 30 | class Barrier 31 | { 32 | public: 33 | explicit Barrier(i64 expected) 34 | { 35 | reset(expected); 36 | } 37 | 38 | auto reset(i64 expected) -> void 39 | { 40 | assert(expected > 0); 41 | assert(m_current.load() == m_total.load()); 42 | 43 | m_total.store(expected, std::memory_order::seq_cst); 44 | m_current.store(expected, std::memory_order::seq_cst); 45 | } 46 | 47 | auto arriveAndWait() 48 | { 49 | std::unique_lock lock{m_waitMutex}; 50 | 51 | const auto current = --m_current; 52 | 53 | if (current > 0) 54 | { 55 | const auto phase = m_phase.load(std::memory_order::relaxed); 56 | m_waitSignal.wait(lock, [this, phase] 57 | { 58 | return (phase - m_phase.load(std::memory_order::acquire)) < 0; 59 | }); 60 | } 61 | else 62 | { 63 | const auto total = m_total.load(std::memory_order::acquire); 64 | m_current.store(total, std::memory_order::release); 65 | 66 | ++m_phase; 67 | 68 | m_waitSignal.notify_all(); 69 | } 70 | } 71 | 72 | private: 73 | std::atomic m_total{}; 74 | std::atomic m_current{}; 75 | std::atomic m_phase{}; 76 | 77 | std::mutex m_waitMutex{}; 78 | std::condition_variable m_waitSignal{}; 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /src/util/bitfield.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #define SP_ENUM_FLAG_OPERATOR(T,UT,O) \ 24 | [[maybe_unused]] inline constexpr auto operator O(T lhs, T rhs) \ 25 | { \ 26 | return static_cast(static_cast(lhs) O static_cast(rhs)); \ 27 | } \ 28 | [[maybe_unused]] inline constexpr auto operator O##=(T &lhs, T rhs) \ 29 | { \ 30 | return lhs = static_cast(static_cast(lhs) O static_cast(rhs)); \ 31 | } 32 | 33 | #define SP_ENUM_FLAGS(UT,T) \ 34 | enum class T : UT; \ 35 | [[maybe_unused]] inline constexpr auto operator ~(T t) { return static_cast(~static_cast(t)); } \ 36 | SP_ENUM_FLAG_OPERATOR(T, UT, |) \ 37 | SP_ENUM_FLAG_OPERATOR(T, UT, ^) \ 38 | SP_ENUM_FLAG_OPERATOR(T, UT, &) \ 39 | enum class T : UT 40 | 41 | namespace stormphrax 42 | { 43 | template 44 | constexpr auto testFlags(T field, T flags) 45 | { 46 | return (field & flags) != T::None; 47 | } 48 | 49 | template 50 | constexpr auto setFlags(T field, T flags, bool v) 51 | { 52 | if (v) 53 | field |= flags; 54 | else field &= ~flags; 55 | 56 | return field; 57 | } 58 | 59 | template 60 | constexpr auto flipFlags(T field, T flags) 61 | { 62 | return field ^ flags; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/util/bits.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | #include 25 | 26 | #include "../arch.h" 27 | 28 | #if SP_HAS_BMI2 29 | #include 30 | #endif 31 | 32 | namespace stormphrax::util 33 | { 34 | namespace fallback 35 | { 36 | [[nodiscard]] constexpr auto pext(u64 v, u64 mask) 37 | { 38 | u64 dst{}; 39 | 40 | for (u64 bit = 1; mask != 0; bit <<= 1) 41 | { 42 | if ((v & mask & -mask) != 0) 43 | dst |= bit; 44 | mask &= mask - 1; 45 | } 46 | 47 | return dst; 48 | } 49 | 50 | [[nodiscard]] constexpr auto pdep(u64 v, u64 mask) 51 | { 52 | u64 dst{}; 53 | 54 | for (u64 bit = 1; mask != 0; bit <<= 1) 55 | { 56 | if ((v & bit) != 0) 57 | dst |= mask & -mask; 58 | mask &= mask - 1; 59 | } 60 | 61 | return dst; 62 | } 63 | } 64 | 65 | [[nodiscard]] constexpr auto isolateLsb(u64 v) -> u64 66 | { 67 | return v & -v; 68 | } 69 | 70 | [[nodiscard]] constexpr auto resetLsb(u64 v) -> u64 71 | { 72 | return v & (v - 1); 73 | } 74 | 75 | [[nodiscard]] constexpr auto ctz(u64 v) -> i32 76 | { 77 | if (std::is_constant_evaluated()) 78 | return std::countr_zero(v); 79 | 80 | return __builtin_ctzll(v); 81 | } 82 | 83 | [[nodiscard]] constexpr auto pext(u64 v, u64 mask) -> u64 84 | { 85 | #if SP_HAS_BMI2 86 | if (std::is_constant_evaluated()) 87 | return fallback::pext(v, mask); 88 | 89 | return _pext_u64(v, mask); 90 | #else 91 | return fallback::pext(v, mask); 92 | #endif 93 | } 94 | 95 | [[nodiscard]] constexpr auto pdep(u64 v, u64 mask) -> u64 96 | { 97 | #if SP_HAS_BMI2 98 | if (std::is_constant_evaluated()) 99 | return fallback::pdep(v, mask); 100 | 101 | return _pdep_u64(v, mask); 102 | #else 103 | return fallback::pdep(v, mask); 104 | #endif 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/util/cemath.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | 25 | namespace stormphrax::util 26 | { 27 | template 28 | constexpr auto abs(T v) 29 | { 30 | return v < T{0} ? -v : v; 31 | } 32 | 33 | template 34 | constexpr auto ilerp(decltype(One) a, decltype(One) b, decltype(One) t) -> decltype(One) 35 | { 36 | return (a * (One - t) + b * t) / One; 37 | } 38 | 39 | template 40 | constexpr auto ceilDiv(T a, T b) 41 | { 42 | return (a + b - 1) / b; 43 | } 44 | 45 | template 46 | [[nodiscard]] inline auto pad(decltype(Block) v) 47 | { 48 | return ceilDiv(v, Block) * Block; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/util/ctrlc.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "ctrlc.h" 20 | 21 | #include 22 | 23 | #ifdef _WIN32 24 | #define WIN32_LEAN_AND_MEAN 25 | #define NOMINMAX 26 | #include 27 | #else 28 | #include 29 | #endif 30 | 31 | namespace stormphrax::util::signal 32 | { 33 | namespace 34 | { 35 | std::vector s_handlers{}; 36 | } 37 | 38 | auto addCtrlCHandler(CtrlCHandler handler) -> void 39 | { 40 | s_handlers.push_back(std::move(handler)); 41 | } 42 | 43 | auto init() -> void 44 | { 45 | #ifdef _WIN32 46 | const auto result = SetConsoleCtrlHandler([](DWORD dwCtrlType) -> BOOL 47 | { 48 | if (dwCtrlType == CTRL_BREAK_EVENT) 49 | return FALSE; 50 | 51 | for (auto &handler : s_handlers) 52 | { 53 | handler(); 54 | } 55 | 56 | return TRUE; 57 | }, TRUE); 58 | 59 | if (!result) 60 | std::cerr << "failed to set ctrl+c handler" << std::endl; 61 | #else 62 | struct sigaction action { 63 | .sa_flags = SA_RESTART 64 | }; 65 | 66 | // on some platforms this is a union, and C++ doesn't support nested designated initialisers 67 | action.sa_handler = [](int signal) 68 | { 69 | for (auto &handler : s_handlers) 70 | { 71 | handler(); 72 | } 73 | }; 74 | 75 | if (sigaction(SIGINT, &action, nullptr)) 76 | std::cerr << "failed to set SIGINT handler" << std::endl; 77 | 78 | if (sigaction(SIGTERM, &action, nullptr)) 79 | std::cerr << "failed to set SIGTERM handler" << std::endl; 80 | #endif 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/util/ctrlc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | 25 | namespace stormphrax::util::signal 26 | { 27 | using CtrlCHandler = std::function; 28 | auto addCtrlCHandler(CtrlCHandler handler) -> void; 29 | 30 | auto init() -> void; 31 | } 32 | -------------------------------------------------------------------------------- /src/util/memstream.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | namespace stormphrax::util 29 | { 30 | class MemoryBuffer : public std::streambuf 31 | { 32 | public: 33 | explicit MemoryBuffer(std::span data) 34 | : m_begin{reinterpret_cast(const_cast(data.data()))}, 35 | m_end{m_begin + data.size_bytes()} 36 | { 37 | setg(m_begin, m_begin, m_end); 38 | } 39 | 40 | auto seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which) -> pos_type override 41 | { 42 | assert(off <= std::numeric_limits::max()); 43 | 44 | switch (dir) 45 | { 46 | case std::ios_base::cur: 47 | gbump(static_cast(off)); 48 | break; 49 | case std::ios_base::end: 50 | setg(m_begin, m_end + off, m_end); 51 | break; 52 | case std::ios_base::beg: 53 | setg(m_begin, m_begin + off, m_end); 54 | break; 55 | default: 56 | break; 57 | } 58 | 59 | return gptr() - eback(); 60 | } 61 | 62 | auto seekpos(std::streampos pos, std::ios_base::openmode mode) -> pos_type override 63 | { 64 | return seekoff(pos, std::ios_base::beg, mode); 65 | } 66 | 67 | private: 68 | char *m_begin; 69 | char *m_end; 70 | }; 71 | 72 | class MemoryIstream : public std::istream 73 | { 74 | public: 75 | explicit MemoryIstream(std::span data) 76 | : m_buf{data}, 77 | std::istream{&m_buf} {} 78 | 79 | private: 80 | MemoryBuffer m_buf; 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /src/util/multi_array.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | 25 | namespace stormphrax::util 26 | { 27 | namespace internal 28 | { 29 | template 30 | struct MultiArrayImpl 31 | { 32 | using Type = std::array::Type, N>; 33 | }; 34 | 35 | template 36 | struct MultiArrayImpl 37 | { 38 | using Type = std::array; 39 | }; 40 | } 41 | 42 | template 43 | using MultiArray = typename internal::MultiArrayImpl::Type; 44 | } 45 | -------------------------------------------------------------------------------- /src/util/parse.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | #include 25 | 26 | namespace stormphrax::util 27 | { 28 | [[nodiscard]] inline auto tryParseDigit(char c) -> std::optional 29 | { 30 | if (c >= '0' && c <= '9') 31 | return static_cast(c - '0'); 32 | else return {}; 33 | } 34 | 35 | [[nodiscard]] inline auto tryParseU32(const std::string &value, u32 radix = 10) -> std::optional 36 | { 37 | try 38 | { 39 | return std::stoul(value, nullptr, static_cast(radix)); 40 | } 41 | catch (...) 42 | { 43 | return {}; 44 | } 45 | } 46 | 47 | inline auto tryParseU32(u32 &dst, const std::string &value, u32 radix = 10) 48 | { 49 | if (const auto parsed = tryParseU32(value, radix)) 50 | { 51 | dst = *parsed; 52 | return true; 53 | } 54 | else return false; 55 | } 56 | 57 | [[nodiscard]] inline auto tryParseI32(const std::string &value, u32 radix = 10) -> std::optional 58 | { 59 | try 60 | { 61 | return std::stol(value, nullptr, static_cast(radix)); 62 | } 63 | catch (...) 64 | { 65 | return {}; 66 | } 67 | } 68 | 69 | inline auto tryParseI32(i32 &dst, const std::string &value, u32 radix = 10) 70 | { 71 | if (const auto parsed = tryParseI32(value, radix)) 72 | { 73 | dst = *parsed; 74 | return true; 75 | } 76 | else return false; 77 | } 78 | 79 | [[nodiscard]] inline auto tryParseU64(const std::string &value, u32 radix = 10) -> std::optional 80 | { 81 | try 82 | { 83 | return std::stoull(value, nullptr, static_cast(radix)); 84 | } 85 | catch (...) 86 | { 87 | return {}; 88 | } 89 | } 90 | 91 | inline auto tryParseU64(u64 &dst, const std::string &value, u32 radix = 10) 92 | { 93 | if (const auto parsed = tryParseU64(value, radix)) 94 | { 95 | dst = *parsed; 96 | return true; 97 | } 98 | else return false; 99 | } 100 | 101 | [[nodiscard]] inline auto tryParseI64(const std::string &value, u32 radix = 10) -> std::optional 102 | { 103 | try 104 | { 105 | return std::stoll(value, nullptr, static_cast(radix)); 106 | } 107 | catch (...) 108 | { 109 | return {}; 110 | } 111 | } 112 | 113 | inline auto tryParseI64(i64 &dst, const std::string &value, u32 radix = 10) 114 | { 115 | if (const auto parsed = tryParseI64(value, radix)) 116 | { 117 | dst = *parsed; 118 | return true; 119 | } 120 | else return false; 121 | } 122 | 123 | [[nodiscard]] inline auto tryParseSize(const std::string &value, u32 radix = 10) -> std::optional 124 | { 125 | if constexpr (sizeof(usize) == 8) 126 | return tryParseU64(value, radix); 127 | else return tryParseU32(value, radix); 128 | } 129 | 130 | inline auto tryParseSize(usize &dst, const std::string &value, u32 radix = 10) 131 | { 132 | if (const auto parsed = tryParseSize(value, radix)) 133 | { 134 | dst = *parsed; 135 | return true; 136 | } 137 | else return false; 138 | } 139 | 140 | [[nodiscard]] inline auto tryParseBool(const std::string &value) -> std::optional 141 | { 142 | if (value == "true") 143 | return true; 144 | else if (value == "false") 145 | return false; 146 | else return {}; 147 | } 148 | 149 | inline auto tryParseBool(bool &dst, const std::string &value) 150 | { 151 | if (const auto parsed = tryParseBool(value)) 152 | { 153 | dst = *parsed; 154 | return true; 155 | } 156 | else return false; 157 | } 158 | 159 | [[nodiscard]] inline auto tryParseF32(const std::string &value) -> std::optional 160 | { 161 | try 162 | { 163 | return std::stof(value, nullptr); 164 | } 165 | catch (...) 166 | { 167 | return {}; 168 | } 169 | } 170 | 171 | inline auto tryParseF32(f32 &dst, const std::string &value) 172 | { 173 | if (const auto parsed = tryParseF32(value)) 174 | { 175 | dst = *parsed; 176 | return true; 177 | } 178 | else return false; 179 | } 180 | 181 | [[nodiscard]] inline auto tryParseF64(const std::string &value) -> std::optional 182 | { 183 | try 184 | { 185 | return std::stod(value, nullptr); 186 | } 187 | catch (...) 188 | { 189 | return {}; 190 | } 191 | } 192 | 193 | inline auto tryParseF64(f64 &dst, const std::string &value) 194 | { 195 | if (const auto parsed = tryParseF64(value)) 196 | { 197 | dst = *parsed; 198 | return true; 199 | } 200 | else return false; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/util/range.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | 25 | namespace stormphrax::util 26 | { 27 | template 28 | class Range 29 | { 30 | public: 31 | constexpr Range(T min, T max) 32 | : m_min{min}, m_max{max} {} 33 | 34 | [[nodiscard]] constexpr auto contains(T v) const 35 | { 36 | return m_min <= v && v <= m_max; 37 | } 38 | 39 | [[nodiscard]] constexpr auto clamp(T v) const 40 | { 41 | return std::clamp(v, m_min, m_max); 42 | } 43 | 44 | [[nodiscard]] constexpr auto min() const { return m_min; } 45 | [[nodiscard]] constexpr auto max() const { return m_max; } 46 | 47 | private: 48 | T m_min; 49 | T m_max; 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /src/util/rng.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | namespace stormphrax::util::rng 28 | { 29 | class Jsf64Rng 30 | { 31 | public: 32 | using result_type = u64; 33 | 34 | explicit constexpr Jsf64Rng(u64 seed) 35 | : m_b{seed}, m_c{seed}, m_d{seed} 36 | { 37 | for (usize i = 0; i < 20; ++i) 38 | { 39 | nextU64(); 40 | } 41 | } 42 | 43 | ~Jsf64Rng() = default; 44 | 45 | constexpr auto nextU64() -> u64 46 | { 47 | const auto e = m_a - std::rotl(m_b, 7); 48 | m_a = m_b ^ std::rotl(m_c, 13); 49 | m_b = m_c + std::rotl(m_d, 37); 50 | m_c = m_d + e; 51 | m_d = e + m_a; 52 | return m_d; 53 | } 54 | 55 | constexpr auto nextU32() 56 | { 57 | return static_cast(nextU64() >> 32); 58 | } 59 | 60 | constexpr auto nextU32(u32 bound) -> u32 61 | { 62 | if (bound == 0) 63 | return 0; 64 | 65 | auto x = nextU32(); 66 | auto m = static_cast(x) * static_cast(bound); 67 | auto l = static_cast(m); 68 | 69 | if (l < bound) 70 | { 71 | auto t = -bound; 72 | 73 | if (t >= bound) 74 | { 75 | t -= bound; 76 | 77 | if (t >= bound) 78 | t %= bound; 79 | } 80 | 81 | while (l < t) 82 | { 83 | x = nextU32(); 84 | m = static_cast(x) * static_cast(bound); 85 | l = static_cast(m); 86 | } 87 | } 88 | 89 | return static_cast(m >> 32); 90 | } 91 | 92 | constexpr auto operator()() { return nextU64(); } 93 | 94 | static constexpr auto min() { return std::numeric_limits::min(); } 95 | static constexpr auto max() { return std::numeric_limits::max(); } 96 | 97 | private: 98 | u64 m_a{0xF1EA5EED}; 99 | u64 m_b; 100 | u64 m_c; 101 | u64 m_d; 102 | }; 103 | 104 | inline auto generateSingleSeed() 105 | { 106 | std::random_device generator{}; 107 | return static_cast(generator()) << 32 | generator(); 108 | } 109 | 110 | // splitmix64, suitable for seeding jsf64 111 | class SeedGenerator 112 | { 113 | public: 114 | explicit SeedGenerator(u64 seed = generateSingleSeed()) 115 | : m_state{seed} {} 116 | 117 | ~SeedGenerator() = default; 118 | 119 | constexpr auto nextSeed() 120 | { 121 | m_state += U64(0x9E3779B97F4A7C15); 122 | 123 | auto z = m_state; 124 | 125 | z = (z ^ (z >> 30)) * U64(0xBF58476D1CE4E5B9); 126 | z = (z ^ (z >> 27)) * U64(0x94D049BB133111EB); 127 | 128 | return z ^ (z >> 31); 129 | } 130 | 131 | private: 132 | u64 m_state{}; 133 | }; 134 | } 135 | -------------------------------------------------------------------------------- /src/util/split.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "split.h" 20 | 21 | #include 22 | 23 | namespace stormphrax::split 24 | { 25 | auto split(const std::string &str, char delim) -> std::vector 26 | { 27 | std::vector result{}; 28 | 29 | std::istringstream stream{str}; 30 | 31 | for (std::string token{}; std::getline(stream, token, delim);) 32 | { 33 | if (token.empty()) 34 | continue; 35 | 36 | result.push_back(token); 37 | } 38 | 39 | return result; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/util/split.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | #include 25 | 26 | namespace stormphrax::split 27 | { 28 | auto split(const std::string &str, char delim) -> std::vector; 29 | } 30 | -------------------------------------------------------------------------------- /src/util/static_vector.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | namespace stormphrax 28 | { 29 | template 30 | class StaticVector 31 | { 32 | public: 33 | StaticVector() = default; 34 | ~StaticVector() = default; 35 | 36 | StaticVector(const StaticVector &other) 37 | { 38 | *this = other; 39 | } 40 | 41 | inline auto push(const T &elem) 42 | { 43 | assert(m_size < Capacity); 44 | m_data[m_size++] = elem; 45 | } 46 | 47 | inline auto push(T &&elem) 48 | { 49 | assert(m_size < Capacity); 50 | m_data[m_size++] = std::move(elem); 51 | } 52 | 53 | inline T pop() 54 | { 55 | assert(m_size > 0); 56 | return std::move(m_data[--m_size]); 57 | } 58 | 59 | inline auto clear() { m_size = 0; } 60 | 61 | inline auto fill(const T &v) { m_data.fill(v); } 62 | 63 | [[nodiscard]] inline auto size() const { return m_size; } 64 | 65 | [[nodiscard]] inline auto empty() const { return m_size == 0; } 66 | 67 | [[nodiscard]] inline auto operator[](usize i) const -> const auto & 68 | { 69 | assert(i < m_size); 70 | return m_data[i]; 71 | } 72 | 73 | [[nodiscard]] inline auto begin() { return m_data.begin(); } 74 | [[nodiscard]] inline auto end() { return m_data.begin() + static_cast(m_size); } 75 | 76 | [[nodiscard]] inline auto operator[](usize i) -> auto & 77 | { 78 | assert(i < m_size); 79 | return m_data[i]; 80 | } 81 | 82 | [[nodiscard]] inline auto begin() const { return m_data.begin(); } 83 | [[nodiscard]] inline auto end() const { return m_data.begin() + static_cast(m_size); } 84 | 85 | inline auto resize(usize size) 86 | { 87 | assert(size <= Capacity); 88 | m_size = size; 89 | } 90 | 91 | inline auto operator=(const StaticVector &other) -> auto & 92 | { 93 | std::copy(other.begin(), other.end(), begin()); 94 | m_size = other.m_size; 95 | return *this; 96 | } 97 | 98 | private: 99 | std::array m_data{}; 100 | usize m_size{0}; 101 | }; 102 | } 103 | -------------------------------------------------------------------------------- /src/util/timer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "timer.h" 20 | 21 | #ifdef _WIN32 22 | #define WIN32_LEAN_AND_MEAN 23 | #include 24 | #else // posix 25 | #include 26 | #endif 27 | 28 | namespace stormphrax::util 29 | { 30 | namespace 31 | { 32 | class Timer 33 | { 34 | public: 35 | Timer(); 36 | ~Timer() = default; 37 | 38 | [[nodiscard]] auto time() const -> f64; 39 | 40 | private: 41 | #ifdef _WIN32 42 | u64 m_initTime{}; 43 | f64 m_frequency; 44 | #else 45 | f64 m_initTime; 46 | #endif 47 | }; 48 | 49 | #ifdef _WIN32 50 | Timer::Timer() 51 | { 52 | LARGE_INTEGER freq{}; 53 | QueryPerformanceFrequency(&freq); 54 | 55 | LARGE_INTEGER initTime{}; 56 | QueryPerformanceCounter(&initTime); 57 | 58 | m_frequency = static_cast(freq.QuadPart); 59 | m_initTime = initTime.QuadPart; 60 | } 61 | 62 | auto Timer::time() const -> f64 63 | { 64 | LARGE_INTEGER time{}; 65 | QueryPerformanceCounter(&time); 66 | 67 | return static_cast(time.QuadPart - m_initTime) / m_frequency; 68 | } 69 | #else 70 | Timer::Timer() 71 | { 72 | struct timespec time; 73 | clock_gettime(CLOCK_MONOTONIC, &time); 74 | 75 | m_initTime = static_cast(time.tv_sec) + static_cast(time.tv_nsec) / 1000000000.0; 76 | } 77 | 78 | auto Timer::time() const -> f64 79 | { 80 | struct timespec time{}; 81 | clock_gettime(CLOCK_MONOTONIC, &time); 82 | 83 | return (static_cast(time.tv_sec) + static_cast(time.tv_nsec) / 1000000000.0) - m_initTime; 84 | } 85 | #endif 86 | 87 | const Timer s_timer{}; 88 | } 89 | 90 | auto Instant::elapsed() const -> f64 91 | { 92 | return s_timer.time() - m_time; 93 | } 94 | 95 | auto Instant::now() -> Instant 96 | { 97 | return Instant{s_timer.time()}; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/util/timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | 25 | namespace stormphrax::util 26 | { 27 | class Instant 28 | { 29 | public: 30 | [[nodiscard]] auto elapsed() const -> f64; 31 | 32 | inline auto operator+(f64 time) const 33 | { 34 | return Instant{m_time + time}; 35 | } 36 | 37 | inline auto operator-(f64 time) const 38 | { 39 | return Instant{m_time - time}; 40 | } 41 | 42 | [[nodiscard]] inline auto operator<=>(const Instant &other) const 43 | { 44 | return m_time <=> other.m_time; 45 | } 46 | 47 | [[nodiscard]] static auto now() -> Instant; 48 | 49 | private: 50 | explicit Instant(f64 time) : m_time{time} {} 51 | 52 | f64 m_time; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /src/util/u4array.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "../types.h" 22 | 23 | #include 24 | #include 25 | 26 | namespace stormphrax::util 27 | { 28 | class IndexedU4 29 | { 30 | public: 31 | constexpr inline operator u8() const // NOLINT 32 | { 33 | return m_high ? (m_value >> 4) : (m_value & 0xF); 34 | } 35 | 36 | constexpr inline auto operator=(u8 v) -> IndexedU4 & 37 | { 38 | assert(v <= 0xF); 39 | 40 | if (m_high) 41 | m_value = (m_value & 0x0F) | (v << 4); 42 | else m_value = (m_value & 0xF0) | (v & 0x0F); 43 | 44 | return *this; 45 | } 46 | 47 | private: 48 | constexpr IndexedU4(u8 &value, bool high) 49 | : m_value{value}, m_high{high} {} 50 | 51 | u8 &m_value; 52 | bool m_high; 53 | 54 | template 55 | friend class U4Array; 56 | }; 57 | 58 | template 59 | class U4Array 60 | { 61 | static_assert(Size % 2 == 0); 62 | 63 | public: 64 | U4Array() = default; 65 | ~U4Array() = default; 66 | 67 | constexpr auto operator[](usize i) const 68 | { 69 | assert(i < Size); 70 | return m_data[i / 2] >> ((i % 2) * 4); 71 | } 72 | 73 | constexpr auto operator[](usize i) 74 | { 75 | assert(i < Size); 76 | return IndexedU4{m_data[i / 2], (i % 2) == 1}; 77 | } 78 | 79 | private: 80 | std::array m_data{}; 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /src/wdl.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #include "wdl.h" 20 | 21 | #include 22 | #include 23 | 24 | namespace stormphrax::wdl 25 | { 26 | auto wdlParams(i32 material) -> std::pair 27 | { 28 | static constexpr auto As = std::array { 29 | -21.62471742, 57.27533161, -196.12706447, 419.75122408 30 | }; 31 | 32 | static constexpr auto Bs = std::array { 33 | -41.67494726, 81.27819509, 10.83073229, 48.03832274 34 | }; 35 | 36 | static_assert(Material58NormalizationK == static_cast(std::reduce(As.begin(), As.end()))); 37 | 38 | const auto m = static_cast(std::clamp(material, 17, 78)) / 58.0; 39 | 40 | return { 41 | (((As[0] * m + As[1]) * m + As[2]) * m) + As[3], 42 | (((Bs[0] * m + Bs[1]) * m + Bs[2]) * m) + Bs[3] 43 | }; 44 | } 45 | 46 | auto wdlModel(Score povScore, i32 material) -> std::pair 47 | { 48 | const auto [a, b] = wdlParams(material); 49 | 50 | const auto x = static_cast(povScore); 51 | 52 | return { 53 | static_cast(std::round(1000.0 / (1.0 + std::exp((a - x) / b)))), 54 | static_cast(std::round(1000.0 / (1.0 + std::exp((a + x) / b)))) 55 | }; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/wdl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Stormphrax, a UCI chess engine 3 | * Copyright (C) 2024 Ciekce 4 | * 5 | * Stormphrax is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Stormphrax is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with Stormphrax. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include 24 | #include 25 | 26 | #include "core.h" 27 | 28 | namespace stormphrax::wdl 29 | { 30 | // Only used for unnormalisation, as a kind of best effort attempt 31 | // Normalisation goes through the wdl model so as to be independent of material 32 | constexpr Score Material58NormalizationK = 259; 33 | 34 | [[nodiscard]] auto wdlParams(i32 material) -> std::pair; 35 | [[nodiscard]] auto wdlModel(Score povScore, i32 material) -> std::pair; // [win, loss] 36 | 37 | inline auto normalizeScore(Score score, i32 material) 38 | { 39 | // don't normalise wins/losses, or zeroes that are pointless to normalise 40 | if (score == 0 || std::abs(score) > ScoreWin) 41 | return score; 42 | 43 | const auto [a, b] = wdlParams(material); 44 | return static_cast(std::round(100.0 * static_cast(score) / a)); 45 | } 46 | 47 | inline auto unnormalizeScoreMaterial58(Score score) 48 | { 49 | return score == 0 || std::abs(score) > ScoreWin 50 | ? score : score * Material58NormalizationK / 100; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 6.0.40 2 | --------------------------------------------------------------------------------