├── .clang-format ├── .github └── workflows │ └── berserk.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── resources ├── berserk.jpg └── update.sh ├── src ├── attacks.c ├── attacks.h ├── bench.c ├── bench.h ├── berserk.c ├── bits.c ├── bits.h ├── board.c ├── board.h ├── eval.c ├── eval.h ├── files │ └── bench.csv ├── history.c ├── history.h ├── incbin.h ├── makefile ├── move.c ├── move.h ├── movegen.c ├── movegen.h ├── movepick.c ├── movepick.h ├── nn │ ├── accumulator.c │ ├── accumulator.h │ ├── evaluate.c │ └── evaluate.h ├── perft.c ├── perft.h ├── pyrrhic │ ├── LICENSE │ ├── stdendian.h │ ├── tbchess.c │ ├── tbconfig.h │ ├── tbprobe.c │ └── tbprobe.h ├── random.c ├── random.h ├── search.c ├── search.h ├── see.c ├── see.h ├── tb.c ├── tb.h ├── thread.c ├── thread.h ├── transposition.c ├── transposition.h ├── types.h ├── uci.c ├── uci.h ├── util.c ├── util.h ├── zobrist.c └── zobrist.h └── tests ├── mate-in-1.sh └── perft.sh /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: google 2 | 3 | AlignAfterOpenBracket: Align 4 | AlignConsecutiveAssignments: Consecutive 5 | AlignConsecutiveBitFields: Consecutive 6 | AlignConsecutiveMacros: Consecutive 7 | AlignOperands: true 8 | AlignTrailingComments: true 9 | 10 | AllowAllArgumentsOnNextLine: false 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: true 14 | AllowShortEnumsOnASingleLine: false 15 | AllowShortFunctionsOnASingleLine: false 16 | AllowShortIfStatementsOnASingleLine: false 17 | AllowShortLambdasOnASingleLine: true 18 | AllowShortLoopsOnASingleLine: false 19 | 20 | AlwaysBreakAfterReturnType: None 21 | AlwaysBreakBeforeMultilineStrings: true 22 | AlwaysBreakTemplateDeclarations: Yes 23 | 24 | BinPackArguments: false 25 | BinPackParameters: false 26 | BitFieldColonSpacing: Both 27 | 28 | BreakBeforeBinaryOperators: None 29 | BreakBeforeBraces: Attach 30 | BreakBeforeTernaryOperators: false 31 | BreakConstructorInitializers: AfterColon 32 | BreakInheritanceList: AfterColon 33 | BreakStringLiterals: true 34 | 35 | ColumnLimit: 120 36 | 37 | CompactNamespaces: false 38 | 39 | ContinuationIndentWidth: 2 40 | 41 | EmptyLineAfterAccessModifier: Never 42 | EmptyLineBeforeAccessModifier: Always 43 | 44 | FixNamespaceComments: false 45 | 46 | IndentWidth: 2 47 | InsertTrailingCommas: Wrapped 48 | 49 | KeepEmptyLinesAtTheStartOfBlocks: false 50 | 51 | Language: Cpp 52 | 53 | MaxEmptyLinesToKeep: 1 54 | 55 | PointerAlignment: Left 56 | 57 | ReflowComments: true 58 | 59 | SortIncludes: CaseInsensitive 60 | 61 | SpaceAfterCStyleCast: true 62 | SpaceAfterLogicalNot: false 63 | SpaceAfterTemplateKeyword: false 64 | SpaceBeforeAssignmentOperators: true 65 | SpaceBeforeCaseColon: false 66 | SpaceBeforeCpp11BracedList: true 67 | SpaceBeforeInheritanceColon: true 68 | SpaceBeforeParens: true 69 | SpaceBeforeRangeBasedForLoopColon: true 70 | SpaceBeforeSquareBrackets: false 71 | SpaceInEmptyBlock: false 72 | SpaceInEmptyParentheses: false 73 | SpacesBeforeTrailingComments: 1 74 | SpacesInAngles: false 75 | SpacesInCStyleCastParentheses: false 76 | SpacesInConditionalStatement: false 77 | SpacesInContainerLiterals: false 78 | SpacesInParentheses: false 79 | SpacesInSquareBrackets: false 80 | -------------------------------------------------------------------------------- /.github/workflows/berserk.yml: -------------------------------------------------------------------------------- 1 | name: Berserk Validation 2 | on: 3 | push: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | engine: 8 | name: Berserk on ${{matrix.os}} with gcc-${{matrix.version}} 9 | runs-on: ${{matrix.os}} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-20.04] 13 | version: [11] 14 | 15 | steps: 16 | - name: Set up GCC ${{matrix.version}} 17 | uses: egor-tensin/setup-gcc@v1 18 | with: 19 | version: ${{matrix.version}} 20 | platform: x64 21 | 22 | - name: Clone Berserk 23 | uses: actions/checkout@v2 24 | 25 | - name: Compile 26 | run: cd src && make build ARCH=avx2 27 | 28 | - name: Bench 29 | run: | 30 | bench=$(git show --summary | grep -Po '(?<=Bench: )[0-9]+?(?=$)') 31 | ./src/berserk bench > output 32 | real=$(grep 'Results:' output | grep -Po '(?<=[\s]{5})[0-9]+?(?= nodes)') 33 | if [[ "$bench" != "$real" ]]; then echo "got $real, expected $bench" && exit 1; fi 34 | 35 | - name: Install Expect 36 | run: sudo apt-get install -y expect 37 | 38 | - name: Perft Test 39 | run: ./tests/perft.sh 1>/dev/null 40 | 41 | clang-format-checking: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v2 45 | 46 | - uses: RafikFarhad/clang-format-github-action@v2.1.0 47 | with: 48 | sources: 'src/*.c,src/*.h' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | berserk 55 | berserk-popcnt 56 | .vscode 57 | .idea 58 | dist 59 | 60 | *.log 61 | *.nn -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhonnold/berserk/64f258bcf3671d2555df274feb893ca9bf71465b/.gitmodules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![AUR version](https://img.shields.io/aur/version/berserk) 2 | 3 | # Berserk Chess Engine 4 | 5 | Berserk 6 | 7 | A UCI chess engine written in C. Feel free to challenge me on [Lichess](https://lichess.org/@/BerserkEngine)! 8 | 9 | ## Strength 10 | 11 | ### Rating Lists + Elo 12 | 13 | Many websites use an [Elo rating system](https://en.wikipedia.org/wiki/Elo_rating_system) to present relative skill amongst engines. 14 | Below is a list of many chess engine lists throughout the web (*variance in Elo is due to different conditions for each list*) 15 | 16 | * [CCRL 40/15](https://ccrl.chessdom.com/ccrl/4040/) - **3514 4CPU, 3480 1CPU** 17 | * [CCRL 40/2](https://ccrl.chessdom.com/ccrl/404/) - **3667 1CPU** 18 | * [IpMan Chess](https://ipmanchess.yolasite.com/r9-7945hx.php) - **3547 1CPU** 19 | * [CEGT](http://www.cegt.net/40_4_Ratinglist/40_4_single/rangliste.html) - **3598 1CPU** 20 | * [SPCC](https://www.sp-cc.de/) - **3733 1CPU** 21 | * ~~[FGRL](www.fastgm.de/60-0.60.html) - **3518 1CPU**~~ 22 | * List no longer maintained 23 | 24 | ### Tournaments/Events with Berserk 25 | 26 | - [TCEC](https://tcec-chess.com/) 27 | - [CCC](https://www.chess.com/computer-chess-championship) 28 | - [Graham's Broadcasts](https://ccrl.live/) 29 | 30 | ## Functional Details 31 | 32 | ### Board Representation and Move Generation 33 | 34 | - [Bitboards](https://www.chessprogramming.org/Bitboards) 35 | - [Magic Bitboards](https://www.chessprogramming.org/Magic_Bitboards) 36 | - [Staged Move Gen](https://www.chessprogramming.org/Move_Generation#Staged_move_generation) 37 | 38 | ### Search 39 | 40 | - [Negamax](https://www.chessprogramming.org/Negamax) 41 | - [PVS](https://www.chessprogramming.org/Principal_Variation_Search) 42 | - [Quiescence](https://www.chessprogramming.org/Quiescence_Search) 43 | - [Iterative Deepening](https://www.chessprogramming.org/Iterative_Deepening) 44 | - [Transposition Table](https://www.chessprogramming.org/Transposition_Table) 45 | - [Aspiration Windows](https://www.chessprogramming.org/Aspiration_Windows) 46 | - [Internal Iterative Reductions](https://www.talkchess.com/forum3/viewtopic.php?f=7&t=74769) 47 | - [Reverse Futility Pruning](https://www.chessprogramming.org/Reverse_Futility_Pruning) 48 | - [Razoring](https://www.chessprogramming.org/Razoring) 49 | - [Null Move Pruning](https://www.chessprogramming.org/Null_Move_Pruning) 50 | - [ProbCut](https://www.chessprogramming.org/ProbCut) 51 | - [FutilityPruning](https://www.chessprogramming.org/Futility_Pruning) 52 | - [LMP](https://www.chessprogramming.org/Futility_Pruning#MoveCountBasedPruning) 53 | - History Pruning 54 | - [SEE](https://www.chessprogramming.org/Static_Exchange_Evaluation) 55 | - Static Exchange Evaluation Pruning 56 | - [LMR](https://www.chessprogramming.org/Late_Move_Reductions) 57 | - [Killer Heuristic](https://www.chessprogramming.org/Killer_Heuristic) 58 | - [Countermove Heuristic](https://www.chessprogramming.org/Countermove_Heuristic) 59 | - [Extensions](https://www.chessprogramming.org/Extensions) 60 | - [Singular](https://www.chessprogramming.org/Singular_Extensions) 61 | 62 | ### Evaluation 63 | 64 | - [NNUE](https://www.chessprogramming.org/NNUE) 65 | - Horizontally Mirrored 16 Buckets 66 | - 2x(12288 -> 512) -> 1 67 | - [Berserk FenGen](https://github.com/jhonnold/berserk/tree/fen-gen) 68 | - [Grapheus](https://github.com/Luecx/Grapheus) 69 | - ~~[Koivisto's CUDA Trainer](https://github.com/Luecx/CudAD)~~ 70 | - This has been deprecated in favor of an even newer trainer written by Luecx, Grapheus. 71 | - ~~[Berserk Trainer](https://github.com/jhonnold/berserk-trainer)~~ 72 | - This has been deprecated in favor of Koivisto's trainer, but trained all networks through Berserk 8.5.1+ 73 | 74 | ## Building 75 | 76 | ```bash 77 | git clone https://github.com/jhonnold/berserk && \ 78 | cd berserk/src && \ 79 | make pgo CC=clang && \ 80 | ./berserk 81 | ``` 82 | 83 | ## Credit 84 | 85 | This engine could not be written without some influence and they are... 86 | 87 | ### Engine Influences 88 | 89 | - [Stockfish](https://github.com/official-stockfish/Stockfish) 90 | - [Ethereal](https://github.com/AndyGrant/Ethereal) 91 | - [Koivisto](https://github.com/Luecx/Koivisto) 92 | - [Weiss](https://github.com/TerjeKir/weiss) 93 | - [Chess22k](https://github.com/sandermvdb/chess22k) 94 | - [BBC](https://github.com/maksimKorzh/chess_programming) 95 | - [Cheng](https://www.chessprogramming.org/Cheng) 96 | 97 | ### Additional Resources 98 | 99 | - [Grapheus](https://github.com/Luecx/Grapheus) 100 | - [Koivisto's CUDA Trainer](https://github.com/Luecx/CudAD) 101 | - [OpenBench](https://github.com/AndyGrant/OpenBench) 102 | - [TalkChess Forum](http://talkchess.com/forum3/viewforum.php?f=7) 103 | - [CCRL](https://kirill-kryukov.com/chess/discussion-board/viewforum.php?f=7) 104 | - [JCER](https://chessengines.blogspot.com/p/rating-jcer.html) 105 | - [Cute Chess](https://cutechess.com/) 106 | - [Arena](http://www.playwitharena.de/) 107 | - [CPW](https://www.chessprogramming.org/Main_Page) 108 | - Lars in Grahams Broadcast rooms 109 | 110 | -------------------------------------------------------------------------------- /resources/berserk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhonnold/berserk/64f258bcf3671d2555df274feb893ca9bf71465b/resources/berserk.jpg -------------------------------------------------------------------------------- /resources/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -uex 4 | 5 | # Select a compiler, prioritize Clang 6 | if hash clang 2>/dev/null; then 7 | cc=clang 8 | elif hash gcc 2>/dev/null; then 9 | cc=gcc 10 | else 11 | echo "Please install clang or gcc to compile Berserk!" 12 | exit 1 13 | fi 14 | 15 | # Clone Berserk 16 | git clone --depth 1 --branch main https://github.com/jhonnold/berserk.git 17 | success=$? 18 | 19 | if [[ success -eq 0 ]]; then 20 | echo "Cloning completed successfully" 21 | else 22 | echo "Unable to clone Berserk!" 23 | exit 1 24 | fi; 25 | 26 | cd berserk/src 27 | make build CC=$cc ARCH=native 28 | 29 | if test -f berserk; then 30 | echo "Compilation complete..." 31 | else 32 | echo "Compilation failed!" 33 | exit 1 34 | fi 35 | 36 | EXE=$PWD/berserk 37 | 38 | $EXE bench 13 > bench.log 2 >&1 39 | grep Results bench.log 40 | -------------------------------------------------------------------------------- /src/attacks.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include 18 | #include 19 | #include 20 | #ifdef USE_PEXT 21 | #include 22 | #endif 23 | 24 | #include "attacks.h" 25 | #include "bits.h" 26 | #include "board.h" 27 | #include "movegen.h" 28 | #include "random.h" 29 | 30 | // This file was built using all the logic found in the BBC video guide on 31 | // youtube I highly recommend it to understand how magic bitboards 32 | // work/generated 33 | // https://www.youtube.com/channel/UCB9-prLkPwgvlKKqDgXhsMQ/videos 34 | // OTF is abbr for On The Fly 35 | 36 | const int BISHOP_RELEVANT_BITS[64] = {6, 5, 5, 5, 5, 5, 5, 6, // 37 | 5, 5, 5, 5, 5, 5, 5, 5, // 38 | 5, 5, 7, 7, 7, 7, 5, 5, // 39 | 5, 5, 7, 9, 9, 7, 5, 5, // 40 | 5, 5, 7, 9, 9, 7, 5, 5, // 41 | 5, 5, 7, 7, 7, 7, 5, 5, // 42 | 5, 5, 5, 5, 5, 5, 5, 5, // 43 | 6, 5, 5, 5, 5, 5, 5, 6}; 44 | 45 | const int ROOK_RELEVANT_BITS[64] = {12, 11, 11, 11, 11, 11, 11, 12, // 46 | 11, 10, 10, 10, 10, 10, 10, 11, // 47 | 11, 10, 10, 10, 10, 10, 10, 11, // 48 | 11, 10, 10, 10, 10, 10, 10, 11, // 49 | 11, 10, 10, 10, 10, 10, 10, 11, // 50 | 11, 10, 10, 10, 10, 10, 10, 11, // 51 | 11, 10, 10, 10, 10, 10, 10, 11, // 52 | 12, 11, 11, 11, 11, 11, 11, 12}; 53 | 54 | BitBoard BETWEEN_SQS[64][64]; 55 | BitBoard PINNED_MOVES[64][64]; 56 | 57 | BitBoard PAWN_ATTACKS[2][64]; 58 | BitBoard KNIGHT_ATTACKS[64]; 59 | BitBoard BISHOP_ATTACKS[64][512]; 60 | BitBoard ROOK_ATTACKS[64][4096]; 61 | BitBoard KING_ATTACKS[64]; 62 | BitBoard ROOK_MASKS[64]; 63 | BitBoard BISHOP_MASKS[64]; 64 | 65 | uint64_t ROOK_MAGICS[64]; 66 | uint64_t BISHOP_MAGICS[64]; 67 | 68 | void InitBetweenSquares() { 69 | int i; 70 | for (int f = 0; f < 64; f++) { 71 | for (int t = f + 1; t < 64; t++) { 72 | if (Rank(f) == Rank(t)) { 73 | i = t + W; 74 | while (i > f) { 75 | BETWEEN_SQS[f][t] |= (1ULL << i); 76 | i += W; 77 | } 78 | } else if (File(f) == File(t)) { 79 | i = t + N; 80 | while (i > f) { 81 | BETWEEN_SQS[f][t] |= (1ULL << i); 82 | i += N; 83 | } 84 | } else if ((t - f) % 9 == 0 && (File(t) > File(f))) { 85 | i = t + NW; 86 | while (i > f) { 87 | BETWEEN_SQS[f][t] |= (1ULL << i); 88 | i += NW; 89 | } 90 | } else if ((t - f) % 7 == 0 && (File(t) < File(f))) { 91 | i = t + NE; 92 | while (i > f) { 93 | BETWEEN_SQS[f][t] |= (1ULL << i); 94 | i += NE; 95 | } 96 | } 97 | } 98 | } 99 | 100 | for (int f = 0; f < 64; f++) 101 | for (int t = 0; t < f; t++) 102 | BETWEEN_SQS[f][t] = BETWEEN_SQS[t][f]; 103 | } 104 | 105 | void InitPinnedMovementSquares() { 106 | int dirs[] = {W, NE, N, NW, E, SW, S, SE}; 107 | 108 | for (int pSq = 0; pSq < 64; pSq++) { 109 | for (int kSq = 0; kSq < 64; kSq++) { 110 | int dir = 0; 111 | for (int i = 0; i < 8; i++) { 112 | if (dir) 113 | break; 114 | 115 | for (int xray = kSq + dirs[i]; xray >= 0 && xray < 64; xray += dirs[i]) { 116 | if (dirs[i] == E || dirs[i] == SE || dirs[i] == NE) 117 | if (File(xray) == 0) 118 | break; 119 | 120 | if (dirs[i] == W || dirs[i] == NW || dirs[i] == SW) 121 | if (File(xray) == 7) 122 | break; 123 | 124 | if (xray == pSq) { 125 | dir = dirs[i]; 126 | break; 127 | } 128 | } 129 | } 130 | 131 | if (dir) { 132 | for (int xray = kSq + dir; xray >= 0 && xray < 64; xray += dir) { 133 | PINNED_MOVES[pSq][kSq] |= (1ULL << xray); 134 | 135 | if (dir == E || dir == SE || dir == NE) 136 | if (File(xray) == 7) 137 | break; 138 | 139 | if (dir == W || dir == SW || dir == NW) 140 | if (File(xray) == 0) 141 | break; 142 | } 143 | } 144 | } 145 | } 146 | } 147 | 148 | inline BitBoard BetweenSquares(int from, int to) { 149 | return BETWEEN_SQS[from][to]; 150 | } 151 | 152 | inline BitBoard PinnedMoves(int p, int k) { 153 | return PINNED_MOVES[p][k]; 154 | } 155 | 156 | BitBoard GetGeneratedPawnAttacks(int sq, int color) { 157 | BitBoard attacks = 0, board = 0; 158 | 159 | SetBit(board, sq); 160 | 161 | if (color == WHITE) { 162 | attacks |= ShiftNW(board); 163 | attacks |= ShiftNE(board); 164 | } else { 165 | attacks |= ShiftSE(board); 166 | attacks |= ShiftSW(board); 167 | } 168 | 169 | return attacks; 170 | } 171 | 172 | void InitPawnAttacks() { 173 | for (int i = 0; i < 64; i++) { 174 | PAWN_ATTACKS[WHITE][i] = GetGeneratedPawnAttacks(i, WHITE); 175 | PAWN_ATTACKS[BLACK][i] = GetGeneratedPawnAttacks(i, BLACK); 176 | } 177 | } 178 | 179 | BitBoard GetGeneratedKnightAttacks(int sq) { 180 | BitBoard attacks = 0, board = 0; 181 | 182 | SetBit(board, sq); 183 | 184 | if ((board >> 17) & ~H_FILE) 185 | attacks |= (board >> 17); 186 | if ((board >> 15) & ~A_FILE) 187 | attacks |= (board >> 15); 188 | if ((board >> 10) & ~(G_FILE | H_FILE)) 189 | attacks |= (board >> 10); 190 | if ((board >> 6) & ~(A_FILE | B_FILE)) 191 | attacks |= (board >> 6); 192 | 193 | if ((board << 17) & ~A_FILE) 194 | attacks |= (board << 17); 195 | if ((board << 15) & ~H_FILE) 196 | attacks |= (board << 15); 197 | if ((board << 10) & ~(A_FILE | B_FILE)) 198 | attacks |= (board << 10); 199 | if ((board << 6) & ~(G_FILE | H_FILE)) 200 | attacks |= (board << 6); 201 | 202 | return attacks; 203 | } 204 | 205 | void InitKnightAttacks() { 206 | for (int i = 0; i < 64; i++) 207 | KNIGHT_ATTACKS[i] = GetGeneratedKnightAttacks(i); 208 | } 209 | 210 | BitBoard GetGeneratedKingAttacks(int sq) { 211 | BitBoard attacks = 0, board = 0; 212 | 213 | SetBit(board, sq); 214 | 215 | attacks |= ShiftN(board); 216 | attacks |= ShiftNE(board); 217 | attacks |= ShiftE(board); 218 | attacks |= ShiftSE(board); 219 | attacks |= ShiftS(board); 220 | attacks |= ShiftSW(board); 221 | attacks |= ShiftW(board); 222 | attacks |= ShiftNW(board); 223 | 224 | return attacks; 225 | } 226 | 227 | void InitKingAttacks() { 228 | for (int i = 0; i < 64; i++) 229 | KING_ATTACKS[i] = GetGeneratedKingAttacks(i); 230 | } 231 | 232 | BitBoard GetBishopMask(int sq) { 233 | BitBoard attacks = 0; 234 | 235 | int sr = Rank(sq); 236 | int sf = File(sq); 237 | 238 | for (int r = sr + 1, f = sf + 1; r <= 6 && f <= 6; r++, f++) 239 | attacks |= (1ULL << (r * 8 + f)); 240 | for (int r = sr - 1, f = sf + 1; r >= 1 && f <= 6; r--, f++) 241 | attacks |= (1ULL << (r * 8 + f)); 242 | for (int r = sr + 1, f = sf - 1; r <= 6 && f >= 1; r++, f--) 243 | attacks |= (1ULL << (r * 8 + f)); 244 | for (int r = sr - 1, f = sf - 1; r >= 1 && f >= 1; r--, f--) 245 | attacks |= (1ULL << (r * 8 + f)); 246 | 247 | return attacks; 248 | } 249 | 250 | void InitBishopMasks() { 251 | for (int i = 0; i < 64; i++) 252 | BISHOP_MASKS[i] = GetBishopMask(i); 253 | } 254 | 255 | BitBoard GetBishopAttacksOTF(int sq, BitBoard blockers) { 256 | BitBoard attacks = 0; 257 | 258 | int sr = Rank(sq); 259 | int sf = File(sq); 260 | 261 | for (int r = sr + 1, f = sf + 1; r <= 7 && f <= 7; r++, f++) { 262 | attacks |= (1ULL << (r * 8 + f)); 263 | if (GetBit(blockers, r * 8 + f)) 264 | break; 265 | } 266 | 267 | for (int r = sr - 1, f = sf + 1; r >= 0 && f <= 7; r--, f++) { 268 | attacks |= (1ULL << (r * 8 + f)); 269 | if (GetBit(blockers, r * 8 + f)) 270 | break; 271 | } 272 | 273 | for (int r = sr + 1, f = sf - 1; r <= 7 && f >= 0; r++, f--) { 274 | attacks |= (1ULL << (r * 8 + f)); 275 | if (GetBit(blockers, r * 8 + f)) 276 | break; 277 | } 278 | 279 | for (int r = sr - 1, f = sf - 1; r >= 0 && f >= 0; r--, f--) { 280 | attacks |= (1ULL << (r * 8 + f)); 281 | if (GetBit(blockers, r * 8 + f)) 282 | break; 283 | } 284 | 285 | return attacks; 286 | } 287 | 288 | BitBoard GetRookMask(int sq) { 289 | BitBoard attacks = 0; 290 | 291 | int sr = Rank(sq); 292 | int sf = File(sq); 293 | 294 | for (int r = sr + 1; r <= 6; r++) 295 | attacks |= (1ULL << (r * 8 + sf)); 296 | for (int r = sr - 1; r >= 1; r--) 297 | attacks |= (1ULL << (r * 8 + sf)); 298 | for (int f = sf + 1; f <= 6; f++) 299 | attacks |= (1ULL << (sr * 8 + f)); 300 | for (int f = sf - 1; f >= 1; f--) 301 | attacks |= (1ULL << (sr * 8 + f)); 302 | 303 | return attacks; 304 | } 305 | 306 | void InitRookMasks() { 307 | for (int i = 0; i < 64; i++) 308 | ROOK_MASKS[i] = GetRookMask(i); 309 | } 310 | 311 | BitBoard GetRookAttacksOTF(int sq, BitBoard blockers) { 312 | BitBoard attacks = 0; 313 | 314 | int sr = Rank(sq); 315 | int sf = File(sq); 316 | 317 | for (int r = sr + 1; r <= 7; r++) { 318 | attacks |= (1ULL << (r * 8 + sf)); 319 | if (GetBit(blockers, r * 8 + sf)) 320 | break; 321 | } 322 | 323 | for (int r = sr - 1; r >= 0; r--) { 324 | attacks |= (1ULL << (r * 8 + sf)); 325 | if (GetBit(blockers, r * 8 + sf)) 326 | break; 327 | } 328 | 329 | for (int f = sf + 1; f <= 7; f++) { 330 | attacks |= (1ULL << (sr * 8 + f)); 331 | if (GetBit(blockers, sr * 8 + f)) 332 | break; 333 | } 334 | 335 | for (int f = sf - 1; f >= 0; f--) { 336 | attacks |= (1ULL << (sr * 8 + f)); 337 | if (GetBit(blockers, sr * 8 + f)) 338 | break; 339 | } 340 | 341 | return attacks; 342 | } 343 | 344 | BitBoard SetPieceLayoutOccupancy(int idx, int bits, BitBoard attacks) { 345 | BitBoard occupany = 0; 346 | 347 | for (int i = 0; i < bits; i++) { 348 | int sq = PopLSB(&attacks); 349 | 350 | if (idx & (1 << i)) 351 | occupany |= (1ULL << sq); 352 | } 353 | 354 | return occupany; 355 | } 356 | 357 | uint64_t FindMagicNumber(int sq, int n, int isBishop) { 358 | int numOccupancies = 1 << n; 359 | 360 | BitBoard occupancies[4096]; 361 | BitBoard attacks[4096]; 362 | BitBoard usedAttacks[4096]; 363 | 364 | BitBoard mask = isBishop ? BISHOP_MASKS[sq] : ROOK_MASKS[sq]; 365 | 366 | for (int i = 0; i < numOccupancies; i++) { 367 | occupancies[i] = SetPieceLayoutOccupancy(i, n, mask); 368 | attacks[i] = isBishop ? GetBishopAttacksOTF(sq, occupancies[i]) : GetRookAttacksOTF(sq, occupancies[i]); 369 | } 370 | 371 | for (int count = 0; count < 10000000; count++) { 372 | uint64_t magic = RandomMagic(); 373 | 374 | if (BitCount((mask * magic) & 0xFF00000000000000) < 6) 375 | continue; 376 | 377 | memset(usedAttacks, 0UL, sizeof(usedAttacks)); 378 | 379 | int failed = 0; 380 | for (int i = 0; !failed && i < numOccupancies; i++) { 381 | int idx = (occupancies[i] * magic) >> (64 - n); 382 | 383 | if (!usedAttacks[idx]) 384 | usedAttacks[idx] = attacks[i]; 385 | else if (usedAttacks[idx] != attacks[i]) 386 | failed = 1; 387 | } 388 | 389 | if (!failed) 390 | return magic; 391 | } 392 | 393 | printf("failed to find magic number"); 394 | return 0; 395 | } 396 | 397 | void InitBishopMagics() { 398 | for (int i = 0; i < 64; i++) 399 | BISHOP_MAGICS[i] = FindMagicNumber(i, BISHOP_RELEVANT_BITS[i], 1); 400 | } 401 | 402 | void InitRookMagics() { 403 | for (int i = 0; i < 64; i++) 404 | ROOK_MAGICS[i] = FindMagicNumber(i, ROOK_RELEVANT_BITS[i], 0); 405 | } 406 | 407 | void InitBishopAttacks() { 408 | for (int sq = 0; sq < 64; sq++) { 409 | BitBoard mask = BISHOP_MASKS[sq]; 410 | int bits = BISHOP_RELEVANT_BITS[sq]; 411 | int n = (1 << bits); 412 | 413 | for (int i = 0; i < n; i++) { 414 | BitBoard occupancy = SetPieceLayoutOccupancy(i, bits, mask); 415 | 416 | #ifndef USE_PEXT 417 | int idx = (occupancy * BISHOP_MAGICS[sq]) >> (64 - bits); 418 | BISHOP_ATTACKS[sq][idx] = GetBishopAttacksOTF(sq, occupancy); 419 | #else 420 | BISHOP_ATTACKS[sq][_pext_u64(occupancy, mask)] = GetBishopAttacksOTF(sq, occupancy); 421 | #endif 422 | } 423 | } 424 | } 425 | 426 | void InitRookAttacks() { 427 | for (int sq = 0; sq < 64; sq++) { 428 | BitBoard mask = ROOK_MASKS[sq]; 429 | int bits = ROOK_RELEVANT_BITS[sq]; 430 | int n = (1 << bits); 431 | 432 | for (int i = 0; i < n; i++) { 433 | BitBoard occupancy = SetPieceLayoutOccupancy(i, bits, mask); 434 | 435 | #ifndef USE_PEXT 436 | int idx = (occupancy * ROOK_MAGICS[sq]) >> (64 - bits); 437 | ROOK_ATTACKS[sq][idx] = GetRookAttacksOTF(sq, occupancy); 438 | #else 439 | ROOK_ATTACKS[sq][_pext_u64(occupancy, mask)] = GetRookAttacksOTF(sq, occupancy); 440 | #endif 441 | } 442 | } 443 | } 444 | 445 | void InitAttacks() { 446 | InitBetweenSquares(); 447 | InitPinnedMovementSquares(); 448 | 449 | InitPawnAttacks(); 450 | InitKnightAttacks(); 451 | InitKingAttacks(); 452 | 453 | InitBishopMasks(); 454 | InitRookMasks(); 455 | 456 | #ifndef USE_PEXT 457 | InitBishopMagics(); 458 | InitRookMagics(); 459 | #endif 460 | 461 | InitBishopAttacks(); 462 | InitRookAttacks(); 463 | } 464 | 465 | inline BitBoard GetPawnAttacks(int sq, int color) { 466 | return PAWN_ATTACKS[color][sq]; 467 | } 468 | 469 | inline BitBoard GetKnightAttacks(int sq) { 470 | return KNIGHT_ATTACKS[sq]; 471 | } 472 | 473 | inline BitBoard GetBishopAttacks(int sq, BitBoard occupancy) { 474 | #ifndef USE_PEXT 475 | occupancy &= BISHOP_MASKS[sq]; 476 | occupancy *= BISHOP_MAGICS[sq]; 477 | occupancy >>= 64 - BISHOP_RELEVANT_BITS[sq]; 478 | 479 | return BISHOP_ATTACKS[sq][occupancy]; 480 | #else 481 | return BISHOP_ATTACKS[sq][_pext_u64(occupancy, BISHOP_MASKS[sq])]; 482 | #endif 483 | } 484 | 485 | inline BitBoard GetRookAttacks(int sq, BitBoard occupancy) { 486 | #ifndef USE_PEXT 487 | occupancy &= ROOK_MASKS[sq]; 488 | occupancy *= ROOK_MAGICS[sq]; 489 | occupancy >>= 64 - ROOK_RELEVANT_BITS[sq]; 490 | 491 | return ROOK_ATTACKS[sq][occupancy]; 492 | #else 493 | return ROOK_ATTACKS[sq][_pext_u64(occupancy, ROOK_MASKS[sq])]; 494 | #endif 495 | } 496 | 497 | inline BitBoard GetQueenAttacks(int sq, BitBoard occupancy) { 498 | return GetBishopAttacks(sq, occupancy) | GetRookAttacks(sq, occupancy); 499 | } 500 | 501 | inline BitBoard GetKingAttacks(int sq) { 502 | return KING_ATTACKS[sq]; 503 | } 504 | 505 | inline BitBoard GetPieceAttacks(int sq, BitBoard occupancy, const int type) { 506 | switch (type) { 507 | case KNIGHT: return GetKnightAttacks(sq); 508 | case BISHOP: return GetBishopAttacks(sq, occupancy); 509 | case ROOK: return GetRookAttacks(sq, occupancy); 510 | case QUEEN: return GetQueenAttacks(sq, occupancy); 511 | case KING: return GetKingAttacks(sq); 512 | } 513 | 514 | return 0; 515 | } 516 | 517 | // get a bitboard of ALL pieces attacking a given square 518 | inline BitBoard AttacksToSquare(Board* board, int sq, BitBoard occ) { 519 | return (GetPawnAttacks(sq, WHITE) & PieceBB(PAWN, BLACK)) | // White and Black Pawn atx 520 | (GetPawnAttacks(sq, BLACK) & PieceBB(PAWN, WHITE)) | // 521 | (GetKnightAttacks(sq) & (PieceBB(KNIGHT, WHITE) | PieceBB(KNIGHT, BLACK))) | // Knights 522 | (GetKingAttacks(sq) & (PieceBB(KING, WHITE) | PieceBB(KING, BLACK))) | // Kings 523 | (GetBishopAttacks(sq, occ) & (PieceBB(BISHOP, WHITE) | PieceBB(BISHOP, BLACK) | // Bishop + Queen 524 | PieceBB(QUEEN, WHITE) | PieceBB(QUEEN, BLACK))) | // 525 | (GetRookAttacks(sq, occ) & (PieceBB(ROOK, WHITE) | PieceBB(ROOK, BLACK) | // Rook + Queen 526 | PieceBB(QUEEN, WHITE) | PieceBB(QUEEN, BLACK))); 527 | } -------------------------------------------------------------------------------- /src/attacks.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef ATTACKS_H 18 | #define ATTACKS_H 19 | 20 | #include "types.h" 21 | 22 | extern BitBoard BETWEEN_SQS[64][64]; 23 | extern BitBoard PINNED_MOVES[64][64]; 24 | 25 | extern BitBoard PAWN_ATTACKS[2][64]; 26 | extern BitBoard KNIGHT_ATTACKS[64]; 27 | extern BitBoard BISHOP_ATTACKS[64][512]; 28 | extern BitBoard ROOK_ATTACKS[64][4096]; 29 | extern BitBoard KING_ATTACKS[64]; 30 | extern BitBoard ROOK_MASKS[64]; 31 | extern BitBoard BISHOP_MASKS[64]; 32 | 33 | extern uint64_t ROOK_MAGICS[64]; 34 | extern uint64_t BISHOP_MAGICS[64]; 35 | 36 | void InitBetweenSquares(); 37 | void InitPinnedMovementSquares(); 38 | void initPawnSpans(); 39 | void InitPawnAttacks(); 40 | void InitKnightAttacks(); 41 | void InitBishopMasks(); 42 | void InitBishopMagics(); 43 | void InitBishopAttacks(); 44 | void InitRookMasks(); 45 | void InitRookMagics(); 46 | void InitRookAttacks(); 47 | void InitKingAttacks(); 48 | void InitAttacks(); 49 | 50 | BitBoard GetGeneratedPawnAttacks(int sq, int color); 51 | BitBoard GetGeneratedKnightAttacks(int sq); 52 | BitBoard GetBishopMask(int sq); 53 | BitBoard GetBishopAttacksOTF(int sq, BitBoard blockers); 54 | BitBoard GetRookMask(int sq); 55 | BitBoard GetRookAttacksOTF(int sq, BitBoard blockers); 56 | BitBoard GetGeneratedKingAttacks(int sq); 57 | BitBoard SetPieceLayoutOccupancy(int idx, int bits, BitBoard attacks); 58 | 59 | uint64_t FindMagicNumber(int sq, int n, int bishop); 60 | 61 | BitBoard BetweenSquares(int from, int to); 62 | BitBoard PinnedMoves(int p, int k); 63 | 64 | BitBoard GetPawnAttacks(int sq, int color); 65 | BitBoard GetKnightAttacks(int sq); 66 | BitBoard GetBishopAttacks(int sq, BitBoard occupancy); 67 | BitBoard GetRookAttacks(int sq, BitBoard occupancy); 68 | BitBoard GetQueenAttacks(int sq, BitBoard occupancy); 69 | BitBoard GetKingAttacks(int sq); 70 | BitBoard GetPieceAttacks(int sq, BitBoard occupancy, const int type); 71 | BitBoard AttacksToSquare(Board* board, int sq, BitBoard occ); 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /src/bench.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "bench.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "board.h" 25 | #include "move.h" 26 | #include "search.h" 27 | #include "thread.h" 28 | #include "transposition.h" 29 | #include "types.h" 30 | #include "uci.h" 31 | #include "util.h" 32 | 33 | // Ethereal's bench set 34 | const int NUM_BENCH_POSITIONS = 50; 35 | char* benchmarks[] = { 36 | #include "files/bench.csv" 37 | }; 38 | 39 | void Bench(int depth) { 40 | Board board; 41 | 42 | Limits.depth = depth; 43 | Limits.multiPV = 1; 44 | Limits.hitrate = INT_MAX; 45 | Limits.max = INT_MAX; 46 | Limits.timeset = 0; 47 | 48 | Move bestMoves[NUM_BENCH_POSITIONS]; 49 | int scores[NUM_BENCH_POSITIONS]; 50 | uint64_t nodes[NUM_BENCH_POSITIONS]; 51 | long times[NUM_BENCH_POSITIONS]; 52 | 53 | long startTime = GetTimeMS(); 54 | for (int i = 0; i < NUM_BENCH_POSITIONS; i++) { 55 | ParseFen(benchmarks[i], &board); 56 | 57 | TTClear(); 58 | SearchClear(); 59 | 60 | Limits.start = GetTimeMS(); 61 | StartSearch(&board, 0); 62 | ThreadWaitUntilSleep(Threads.threads[0]); 63 | times[i] = GetTimeMS() - Limits.start; 64 | 65 | bestMoves[i] = Threads.threads[0]->rootMoves[0].move; 66 | scores[i] = Threads.threads[0]->rootMoves[0].score; 67 | nodes[i] = Threads.threads[0]->nodes; 68 | } 69 | long totalTime = GetTimeMS() - startTime; 70 | 71 | printf("\n\n"); 72 | for (int i = 0; i < NUM_BENCH_POSITIONS; i++) { 73 | printf("Bench [#%2d]: bestmove %5s score %5d %12" PRIu64 " nodes %8d nps | %71s\n", 74 | i + 1, 75 | MoveToStr(bestMoves[i], &board), 76 | (int) Normalize(scores[i]), 77 | nodes[i], 78 | (int) (1000.0 * nodes[i] / (times[i] + 1)), 79 | benchmarks[i]); 80 | } 81 | 82 | uint64_t totalNodes = 0; 83 | for (int i = 0; i < NUM_BENCH_POSITIONS; i++) 84 | totalNodes += nodes[i]; 85 | 86 | printf("\nResults: %43" PRIu64 " nodes %8d nps\n\n", totalNodes, (int) (1000.0 * totalNodes / (totalTime + 1))); 87 | } -------------------------------------------------------------------------------- /src/bench.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef BENCH_H 18 | #define BENCH_H 19 | 20 | #define DEFAULT_BENCH_DEPTH 13 21 | 22 | void Bench(int depth); 23 | 24 | #endif -------------------------------------------------------------------------------- /src/berserk.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include 18 | 19 | #include "attacks.h" 20 | #include "bench.h" 21 | #include "bits.h" 22 | #include "eval.h" 23 | #include "nn/evaluate.h" 24 | #include "random.h" 25 | #include "search.h" 26 | #include "thread.h" 27 | #include "transposition.h" 28 | #include "types.h" 29 | #include "uci.h" 30 | #include "util.h" 31 | #include "zobrist.h" 32 | 33 | // Welcome to berserk 34 | int main(int argc, char** argv) { 35 | SeedRandom(0); 36 | 37 | InitZobristKeys(); 38 | InitPruningAndReductionTables(); 39 | InitAttacks(); 40 | InitCuckoo(); 41 | 42 | LoadDefaultNN(); 43 | ThreadsInit(); 44 | TTInit(16); 45 | 46 | // Compliance for OpenBench 47 | if (argc > 1 && !strncmp(argv[1], "bench", 5)) { 48 | int depth = DEFAULT_BENCH_DEPTH; 49 | if (argc > 2) 50 | depth = Max(1, atoi(argv[2])); 51 | 52 | Bench(depth); 53 | } else { 54 | UCILoop(); 55 | } 56 | 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /src/bits.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "bits.h" 18 | -------------------------------------------------------------------------------- /src/bits.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef BITS_H 18 | #define BITS_H 19 | 20 | #include 21 | 22 | #include "types.h" 23 | #include "util.h" 24 | 25 | #define A_FILE 0x0101010101010101ULL 26 | #define B_FILE 0x0202020202020202ULL 27 | #define C_FILE 0x0404040404040404ULL 28 | #define D_FILE 0x0808080808080808ULL 29 | #define E_FILE 0x1010101010101010ULL 30 | #define F_FILE 0x2020202020202020ULL 31 | #define G_FILE 0x4040404040404040ULL 32 | #define H_FILE 0x8080808080808080ULL 33 | 34 | #define RANK_1 0xFF00000000000000ULL 35 | #define RANK_2 0x00FF000000000000ULL 36 | #define RANK_3 0x0000FF0000000000ULL 37 | #define RANK_4 0x000000FF00000000ULL 38 | #define RANK_5 0x00000000FF000000ULL 39 | #define RANK_6 0x0000000000FF0000ULL 40 | #define RANK_7 0x000000000000FF00ULL 41 | #define RANK_8 0x00000000000000FFULL 42 | 43 | #define DARK_SQS 0x55AA55AA55AA55AAULL 44 | 45 | #define Bit(sq) (1ULL << (sq)) 46 | #define BitCount(bb) (__builtin_popcountll(bb)) 47 | #define SetBit(bb, sq) ((bb) |= Bit(sq)) 48 | #define GetBit(bb, sq) ((bb) &Bit(sq)) 49 | #define PopBit(bb, sq) ((bb) &= ~Bit(sq)) 50 | #define FlipBit(bb, sq) ((bb) ^= Bit(sq)) 51 | #define FlipBits(bb, sq1, sq2) ((bb) ^= Bit(sq1) ^ Bit(sq2)) 52 | #define LSB(bb) (__builtin_ctzll(bb)) 53 | #define MSB(bb) (63 ^ __builtin_clzll(bb)) 54 | 55 | #define ShiftN(bb) ((bb) >> 8) 56 | #define ShiftS(bb) ((bb) << 8) 57 | #define ShiftNN(bb) ((bb) >> 16) 58 | #define ShiftSS(bb) ((bb) << 16) 59 | #define ShiftW(bb) (((bb) & ~A_FILE) >> 1) 60 | #define ShiftE(bb) (((bb) & ~H_FILE) << 1) 61 | #define ShiftNE(bb) (((bb) & ~H_FILE) >> 7) 62 | #define ShiftSW(bb) (((bb) & ~A_FILE) << 7) 63 | #define ShiftNW(bb) (((bb) & ~A_FILE) >> 9) 64 | #define ShiftSE(bb) (((bb) & ~H_FILE) << 9) 65 | 66 | INLINE BitBoard ShiftPawnDir(BitBoard bb, const int c) { 67 | return c == WHITE ? ShiftN(bb) : ShiftS(bb); 68 | } 69 | 70 | INLINE BitBoard ShiftPawnCapW(BitBoard bb, const int c) { 71 | return c == WHITE ? ShiftNW(bb) : ShiftSW(bb); 72 | } 73 | 74 | INLINE BitBoard ShiftPawnCapE(BitBoard bb, const int c) { 75 | return c == WHITE ? ShiftNE(bb) : ShiftSE(bb); 76 | } 77 | 78 | INLINE uint8_t PawnFiles(BitBoard pawns) { 79 | pawns |= (pawns >> 8); 80 | pawns |= (pawns >> 16); 81 | return (uint8_t) ((pawns | (pawns >> 32)) & 0xFF); 82 | } 83 | 84 | INLINE int PopLSB(BitBoard* bb) { 85 | int sq = LSB(*bb); 86 | *bb &= *bb - 1; 87 | return sq; 88 | } 89 | 90 | INLINE void PrintBB(BitBoard bitboard) { 91 | for (int i = 0; i < 64; i++) { 92 | if ((i & 7) == 0) 93 | printf(" %d ", 8 - (i >> 3)); 94 | 95 | printf(" %d", GetBit(bitboard, i) ? 1 : 0); 96 | 97 | if ((i & 7) == 7) 98 | printf("\n"); 99 | } 100 | 101 | printf("\n a b c d e f g h\n\n"); 102 | printf(" Value: %" PRIu64 "\n\n", bitboard); 103 | } 104 | 105 | #endif -------------------------------------------------------------------------------- /src/board.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef BOARD_H 18 | #define BOARD_H 19 | 20 | #include "types.h" 21 | #include "util.h" 22 | 23 | #define NO_PIECE 12 24 | 25 | #define Piece(pc, c) (((pc) << 1) + c) 26 | #define PieceType(pc) ((pc) >> 1) 27 | #define PPieceBB(pc) (board->pieces[pc]) 28 | #define PieceBB(pc, c) (board->pieces[Piece(pc, (c))]) 29 | #define OccBB(c) (board->occupancies[c]) 30 | 31 | #define File(sq) ((sq) &7) 32 | #define Rank(sq) ((sq) >> 3) 33 | #define Sq(r, f) ((r) *8 + (f)) 34 | #define Distance(a, b) Max(abs(Rank(a) - Rank(b)), abs(File(a) - File(b))) 35 | #define MDistance(a, b) (abs(Rank(a) - Rank(b)) + abs(File(a) - File(b))) 36 | #define PieceCount(pc) (1ull << (pc * 4)) 37 | 38 | extern const uint16_t KING_BUCKETS[64]; 39 | 40 | void ClearBoard(Board* board); 41 | void ParseFen(char* fen, Board* board); 42 | void BoardToFen(char* fen, Board* board); 43 | void PrintBoard(Board* board); 44 | 45 | void SetSpecialPieces(Board* board); 46 | void SetThreats(Board* board); 47 | 48 | int DoesMoveCheck(Move move, Board* board); 49 | 50 | int IsDraw(Board* board, int ply); 51 | int IsRepetition(Board* board, int ply); 52 | int IsMaterialDraw(Board* board); 53 | int IsFiftyMoveRule(Board* board); 54 | 55 | void MakeNullMove(Board* board); 56 | void UndoNullMove(Board* board); 57 | void MakeMove(Move move, Board* board); 58 | void MakeMoveUpdate(Move move, Board* board, int update); 59 | void UndoMove(Move move, Board* board); 60 | 61 | int IsPseudoLegal(Move move, Board* board); 62 | int IsLegal(Move move, Board* board); 63 | 64 | void InitCuckoo(); 65 | int HasCycle(Board* board, int ply); 66 | 67 | INLINE BitBoard OpponentsEasyCaptures(Board* board) { 68 | const int stm = board->stm; 69 | const BitBoard queens = PieceBB(QUEEN, stm); 70 | const BitBoard rooks = queens | PieceBB(ROOK, stm); 71 | const BitBoard minors = rooks | PieceBB(BISHOP, stm) | PieceBB(KNIGHT, stm); 72 | 73 | const BitBoard pawnThreats = board->threatenedBy[PAWN]; 74 | const BitBoard minorThreats = pawnThreats | board->threatenedBy[KNIGHT] | board->threatenedBy[BISHOP]; 75 | const BitBoard rookThreats = minorThreats | board->threatenedBy[ROOK]; 76 | 77 | return (queens & rookThreats) | (rooks & minorThreats) | (minors & pawnThreats); 78 | } 79 | 80 | INLINE int HasNonPawn(Board* board, const int color) { 81 | return !!(OccBB(color) ^ PieceBB(KING, color) ^ PieceBB(PAWN, color)); 82 | } 83 | 84 | INLINE int MoveRequiresRefresh(int piece, int from, int to) { 85 | if (PieceType(piece) != KING) 86 | return 0; 87 | 88 | if ((from & 4) != (to & 4)) 89 | return 1; 90 | return KING_BUCKETS[from] != KING_BUCKETS[to]; 91 | } 92 | 93 | INLINE int FeatureIdx(int piece, int sq, int kingsq, const int view) { 94 | int oP = 6 * ((piece ^ view) & 0x1) + PieceType(piece); 95 | int oK = (7 * !(kingsq & 4)) ^ (56 * view) ^ kingsq; 96 | int oSq = (7 * !(kingsq & 4)) ^ (56 * view) ^ sq; 97 | 98 | return KING_BUCKETS[oK] * 12 * 64 + oP * 64 + oSq; 99 | } 100 | 101 | #endif -------------------------------------------------------------------------------- /src/eval.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "eval.h" 18 | 19 | #include 20 | 21 | #include "attacks.h" 22 | #include "bits.h" 23 | #include "board.h" 24 | #include "move.h" 25 | #include "nn/accumulator.h" 26 | #include "nn/evaluate.h" 27 | #include "uci.h" 28 | #include "util.h" 29 | 30 | const int PHASE_VALUES[6] = {0, 3, 3, 5, 10, 0}; 31 | const int MAX_PHASE = 64; 32 | 33 | void SetContempt(int* dest, int stm) { 34 | int contempt = CONTEMPT; 35 | 36 | dest[stm] = contempt; 37 | dest[stm ^ 1] = -contempt; 38 | } 39 | 40 | // Main evalution method 41 | Score Evaluate(Board* board, ThreadData* thread) { 42 | if (IsMaterialDraw(board)) 43 | return 0; 44 | 45 | Accumulator* acc = board->accumulators; 46 | for (int c = WHITE; c <= BLACK; c++) { 47 | if (!acc->correct[c]) { 48 | if (CanEfficientlyUpdate(acc, c)) 49 | ApplyLazyUpdates(acc, board, c); 50 | else 51 | RefreshAccumulator(acc, board, c); 52 | } 53 | } 54 | 55 | int score = board->stm == WHITE ? Propagate(acc, WHITE) : Propagate(acc, BLACK); 56 | 57 | // scaled based on phase [1, 1.5] 58 | score = (128 + board->phase) * score / 128; 59 | score += board->phase * thread->contempt[board->stm] / 64; 60 | 61 | return Min(EVAL_UNKNOWN - 1, Max(-EVAL_UNKNOWN + 1, score)); 62 | } 63 | 64 | void EvaluateTrace(Board* board) { 65 | // The UCI board has no guarantee of accumulator allocation 66 | // so we have to set that up here. 67 | board->accumulators = AlignedMalloc(sizeof(Accumulator), 64); 68 | ResetAccumulator(board->accumulators, board, WHITE); 69 | ResetAccumulator(board->accumulators, board, BLACK); 70 | 71 | int base = Propagate(board->accumulators, board->stm); 72 | base = board->stm == WHITE ? base : -base; 73 | int scaled = (128 + board->phase) * base / 128; 74 | 75 | printf("\nNNUE derived piece values:\n"); 76 | 77 | for (int r = 0; r < 8; r++) { 78 | printf("+-------+-------+-------+-------+-------+-------+-------+-------+\n"); 79 | printf("|"); 80 | for (int f = 0; f < 16; f++) { 81 | if (f == 8) 82 | printf("\n|"); 83 | 84 | int sq = r * 8 + (f > 7 ? f - 8 : f); 85 | int pc = board->squares[sq]; 86 | 87 | if (pc == NO_PIECE) { 88 | printf(" |"); 89 | } else if (f < 8) { 90 | printf(" %c |", PIECE_TO_CHAR[pc]); 91 | } else if (PieceType(pc) == KING) { 92 | printf(" |"); 93 | } else { 94 | // To calculate the piece value, we pop it 95 | // reset the accumulators and take a diff 96 | PopBit(OccBB(BOTH), sq); 97 | ResetAccumulator(board->accumulators, board, WHITE); 98 | ResetAccumulator(board->accumulators, board, BLACK); 99 | int new = Propagate(board->accumulators, board->stm); 100 | new = board->stm == WHITE ? new : -new; 101 | SetBit(OccBB(BOTH), sq); 102 | 103 | int diff = base - new; 104 | int normalized = Normalize(diff); 105 | int v = abs(normalized); 106 | 107 | char buffer[6]; 108 | buffer[5] = '\0'; 109 | buffer[0] = diff > 0 ? '+' : diff < 0 ? '-' : ' '; 110 | if (v >= 1000) { 111 | buffer[1] = '0' + v / 1000; 112 | v %= 1000; 113 | buffer[2] = '0' + v / 100; 114 | v %= 100; 115 | buffer[3] = '.'; 116 | buffer[4] = '0' + v / 10; 117 | } else { 118 | buffer[1] = '0' + v / 100; 119 | v %= 100; 120 | buffer[2] = '.'; 121 | buffer[3] = '0' + v / 10; 122 | v %= 10; 123 | buffer[4] = '0' + v; 124 | } 125 | printf(" %s |", buffer); 126 | } 127 | } 128 | 129 | printf("\n"); 130 | } 131 | 132 | printf("+-------+-------+-------+-------+-------+-------+-------+-------+\n\n"); 133 | 134 | printf(" NNUE Score: %dcp (white)\n", (int) Normalize(base)); 135 | printf("Final Score: %dcp (white)\n", (int) Normalize(scaled)); 136 | 137 | AlignedFree(board->accumulators); 138 | } -------------------------------------------------------------------------------- /src/eval.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef EVAL_H 18 | #define EVAL_H 19 | 20 | #include 21 | 22 | #include "types.h" 23 | #include "util.h" 24 | 25 | #define EVAL_UNKNOWN 2046 26 | 27 | INLINE int ClampEval(int eval) { 28 | return Min(EVAL_UNKNOWN - 1, Max(-EVAL_UNKNOWN + 1, eval)); 29 | } 30 | 31 | extern const int PHASE_VALUES[6]; 32 | extern const int MAX_PHASE; 33 | 34 | void SetContempt(int* dest, int stm); 35 | Score Evaluate(Board* board, ThreadData* thread); 36 | void EvaluateTrace(Board* board); 37 | 38 | #endif -------------------------------------------------------------------------------- /src/files/bench.csv: -------------------------------------------------------------------------------- 1 | "r3k2r/2pb1ppp/2pp1q2/p7/1nP1B3/1P2P3/P2N1PPP/R2QK2R w KQkq a6 0 14", 2 | "4rrk1/2p1b1p1/p1p3q1/4p3/2P2n1p/1P1NR2P/PB3PP1/3R1QK1 b - - 2 24", 3 | "r3qbrk/6p1/2b2pPp/p3pP1Q/PpPpP2P/3P1B2/2PB3K/R5R1 w - - 16 42", 4 | "6k1/1R3p2/6p1/2Bp3p/3P2q1/P7/1P2rQ1K/5R2 b - - 4 44", 5 | "8/8/1p2k1p1/3p3p/1p1P1P1P/1P2PK2/8/8 w - - 3 54", 6 | "7r/2p3k1/1p1p1qp1/1P1Bp3/p1P2r1P/P7/4R3/Q4RK1 w - - 0 36", 7 | "r1bq1rk1/pp2b1pp/n1pp1n2/3P1p2/2P1p3/2N1P2N/PP2BPPP/R1BQ1RK1 b - - 2 10", 8 | "3r3k/2r4p/1p1b3q/p4P2/P2Pp3/1B2P3/3BQ1RP/6K1 w - - 3 87", 9 | "2r4r/1p4k1/1Pnp4/3Qb1pq/8/4BpPp/5P2/2RR1BK1 w - - 0 42", 10 | "4q1bk/6b1/7p/p1p4p/PNPpP2P/KN4P1/3Q4/4R3 b - - 0 37", 11 | "2q3r1/1r2pk2/pp3pp1/2pP3p/P1Pb1BbP/1P4Q1/R3NPP1/4R1K1 w - - 2 34", 12 | "1r2r2k/1b4q1/pp5p/2pPp1p1/P3Pn2/1P1B1Q1P/2R3P1/4BR1K b - - 1 37", 13 | "r3kbbr/pp1n1p1P/3ppnp1/q5N1/1P1pP3/P1N1B3/2P1QP2/R3KB1R b KQkq b3 0 17", 14 | "8/6pk/2b1Rp2/3r4/1R1B2PP/P5K1/8/2r5 b - - 16 42", 15 | "1r4k1/4ppb1/2n1b1qp/pB4p1/1n1BP1P1/7P/2PNQPK1/3RN3 w - - 8 29", 16 | "8/p2B4/PkP5/4p1pK/4Pb1p/5P2/8/8 w - - 29 68", 17 | "3r4/ppq1ppkp/4bnp1/2pN4/2P1P3/1P4P1/PQ3PBP/R4K2 b - - 2 20", 18 | "5rr1/4n2k/4q2P/P1P2n2/3B1p2/4pP2/2N1P3/1RR1K2Q w - - 1 49", 19 | "1r5k/2pq2p1/3p3p/p1pP4/4QP2/PP1R3P/6PK/8 w - - 1 51", 20 | "q5k1/5ppp/1r3bn1/1B6/P1N2P2/BQ2P1P1/5K1P/8 b - - 2 34", 21 | "r1b2k1r/5n2/p4q2/1ppn1Pp1/3pp1p1/NP2P3/P1PPBK2/1RQN2R1 w - - 0 22", 22 | "r1bqk2r/pppp1ppp/5n2/4b3/4P3/P1N5/1PP2PPP/R1BQKB1R w KQkq - 0 5", 23 | "r1bqr1k1/pp1p1ppp/2p5/8/3N1Q2/P2BB3/1PP2PPP/R3K2n b Q - 1 12", 24 | "r1bq2k1/p4r1p/1pp2pp1/3p4/1P1B3Q/P2B1N2/2P3PP/4R1K1 b - - 2 19", 25 | "r4qk1/6r1/1p4p1/2ppBbN1/1p5Q/P7/2P3PP/5RK1 w - - 2 25", 26 | "r7/6k1/1p6/2pp1p2/7Q/8/p1P2K1P/8 w - - 0 32", 27 | "r3k2r/ppp1pp1p/2nqb1pn/3p4/4P3/2PP4/PP1NBPPP/R2QK1NR w KQkq - 1 5", 28 | "3r1rk1/1pp1pn1p/p1n1q1p1/3p4/Q3P3/2P5/PP1NBPPP/4RRK1 w - - 0 12", 29 | "5rk1/1pp1pn1p/p3Brp1/8/1n6/5N2/PP3PPP/2R2RK1 w - - 2 20", 30 | "8/1p2pk1p/p1p1r1p1/3n4/8/5R2/PP3PPP/4R1K1 b - - 3 27", 31 | "8/4pk2/1p1r2p1/p1p4p/Pn5P/3R4/1P3PP1/4RK2 w - - 1 33", 32 | "8/5k2/1pnrp1p1/p1p4p/P6P/4R1PK/1P3P2/4R3 b - - 1 38", 33 | "8/8/1p1kp1p1/p1pr1n1p/P6P/1R4P1/1P3PK1/1R6 b - - 15 45", 34 | "8/8/1p1k2p1/p1prp2p/P2n3P/6P1/1P1R1PK1/4R3 b - - 5 49", 35 | "8/8/1p4p1/p1p2k1p/P2npP1P/4K1P1/1P6/3R4 w - - 6 54", 36 | "8/8/1p4p1/p1p2k1p/P2n1P1P/4K1P1/1P6/6R1 b - - 6 59", 37 | "8/5k2/1p4p1/p1pK3p/P2n1P1P/6P1/1P6/4R3 b - - 14 63", 38 | "8/1R6/1p1K1kp1/p6p/P1p2P1P/6P1/1Pn5/8 w - - 0 67", 39 | "1rb1rn1k/p3q1bp/2p3p1/2p1p3/2P1P2N/PP1RQNP1/1B3P2/4R1K1 b - - 4 23", 40 | "4rrk1/pp1n1pp1/q5p1/P1pP4/2n3P1/7P/1P3PB1/R1BQ1RK1 w - - 3 22", 41 | "r2qr1k1/pb1nbppp/1pn1p3/2ppP3/3P4/2PB1NN1/PP3PPP/R1BQR1K1 w - - 4 12", 42 | "2r2k2/8/4P1R1/1p6/8/P4K1N/7b/2B5 b - - 0 55", 43 | "6k1/5pp1/8/2bKP2P/2P5/p4PNb/B7/8 b - - 1 44", 44 | "2rqr1k1/1p3p1p/p2p2p1/P1nPb3/2B1P3/5P2/1PQ2NPP/R1R4K w - - 3 25", 45 | "r1b2rk1/p1q1ppbp/6p1/2Q5/8/4BP2/PPP3PP/2KR1B1R b - - 2 14", 46 | "6r1/5k2/p1b1r2p/1pB1p1p1/1Pp3PP/2P1R1K1/2P2P2/3R4 w - - 1 36", 47 | "rnbqkb1r/pppppppp/5n2/8/2PP4/8/PP2PPPP/RNBQKBNR b KQkq c3 0 2", 48 | "2rr2k1/1p4bp/p1q1p1p1/4Pp1n/2PB4/1PN3P1/P3Q2P/2RR2K1 w - f6 0 20", 49 | "3br1k1/p1pn3p/1p3n2/5pNq/2P1p3/1PN3PP/P2Q1PB1/4R1K1 w - - 0 23", 50 | "2r2b2/5p2/5k2/p1r1pP2/P2pB3/1P3P2/K1P3R1/7R w - - 23 93" -------------------------------------------------------------------------------- /src/history.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "history.h" 18 | 19 | #include 20 | #include 21 | 22 | #include "board.h" 23 | #include "move.h" 24 | #include "util.h" 25 | 26 | void UpdateHistories(SearchStack* ss, 27 | ThreadData* thread, 28 | Move bestMove, 29 | int depth, 30 | Move quiets[], 31 | int nQ, 32 | Move captures[], 33 | int nC) { 34 | Board* board = &thread->board; 35 | int stm = board->stm; 36 | 37 | int16_t inc = HistoryBonus(depth); 38 | 39 | if (!IsCap(bestMove)) { 40 | if (PromoPT(bestMove) != QUEEN) { 41 | AddKillerMove(ss, bestMove); 42 | 43 | if ((ss - 1)->move) 44 | AddCounterMove(thread, bestMove, (ss - 1)->move); 45 | } 46 | 47 | // Only increase the best move history when it 48 | // wasn't trivial. This idea was first thought of 49 | // by Alayan in Ethereal 50 | if (nQ > 1 || depth > 5) { 51 | AddHistoryHeuristic(&HH(stm, bestMove, board->threatened), inc); 52 | UpdateCH(ss, bestMove, inc); 53 | } 54 | } else { 55 | int piece = Moving(bestMove); 56 | int to = To(bestMove); 57 | int defended = !GetBit(board->threatened, to); 58 | int captured = IsEP(bestMove) ? PAWN : PieceType(board->squares[to]); 59 | 60 | AddHistoryHeuristic(&TH(piece, to, defended, captured), inc); 61 | } 62 | 63 | // Update quiets 64 | if (!IsCap(bestMove)) { 65 | const int malus = Min(0, -inc + 36 * (nQ - 1)); 66 | 67 | for (int i = 0; i < nQ; i++) { 68 | Move m = quiets[i]; 69 | if (m == bestMove) 70 | continue; 71 | 72 | AddHistoryHeuristic(&HH(stm, m, board->threatened), malus); 73 | UpdateCH(ss, m, malus); 74 | } 75 | } 76 | 77 | // Update captures 78 | for (int i = 0; i < nC; i++) { 79 | Move m = captures[i]; 80 | if (m == bestMove) 81 | continue; 82 | 83 | int piece = Moving(m); 84 | int to = To(m); 85 | int defended = !GetBit(board->threatened, to); 86 | int captured = IsEP(m) ? PAWN : PieceType(board->squares[to]); 87 | 88 | AddHistoryHeuristic(&TH(piece, to, defended, captured), -inc); 89 | } 90 | } 91 | 92 | void UpdatePawnCorrection(int raw, int real, int depth, Board* board, ThreadData* thread) { 93 | const int16_t correction = Min(4096, Max(-4096, 3 * (real - raw) * depth)); 94 | int16_t* pawnCorrection = &thread->pawnCorrection[board->pawnZobrist & PAWN_CORRECTION_MASK]; 95 | AddHistoryHeuristic(pawnCorrection, correction); 96 | } 97 | 98 | void UpdateContCorrection(int raw, int real, int depth, SearchStack* ss) { 99 | if ((ss - 1)->move && (ss - 2)->move) { 100 | const int16_t correction = Min(4096, Max(-4096, 4 * (real - raw) * depth)); 101 | int16_t* contCorrection = &(*(ss - 2)->cont)[Moving((ss - 1)->move)][To((ss - 1)->move)]; 102 | AddHistoryHeuristic(contCorrection, correction); 103 | } 104 | 105 | if ((ss - 1)->move && (ss - 3)->move) { 106 | const int16_t correction = Min(4096, Max(-4096, 4 * (real - raw) * depth)); 107 | int16_t* contCorrection = &(*(ss - 3)->cont)[Moving((ss - 1)->move)][To((ss - 1)->move)]; 108 | AddHistoryHeuristic(contCorrection, correction); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/history.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef HISTORY_H 18 | #define HISTORY_H 19 | 20 | #include "bits.h" 21 | #include "board.h" 22 | #include "move.h" 23 | #include "types.h" 24 | #include "util.h" 25 | 26 | #define HH(stm, m, threats) (thread->hh[stm][!GetBit(threats, From(m))][!GetBit(threats, To(m))][FromTo(m)]) 27 | #define TH(p, e, d, c) (thread->caph[p][e][d][c]) 28 | 29 | INLINE int GetQuietHistory(SearchStack* ss, ThreadData* thread, Move move) { 30 | return (int) HH(thread->board.stm, move, thread->board.threatened) + // 31 | (int) (*(ss - 1)->ch)[Moving(move)][To(move)] + // 32 | (int) (*(ss - 2)->ch)[Moving(move)][To(move)] + // 33 | (int) (*(ss - 4)->ch)[Moving(move)][To(move)]; 34 | } 35 | 36 | INLINE int GetCaptureHistory(ThreadData* thread, Move move) { 37 | Board* board = &thread->board; 38 | 39 | return TH(Moving(move), 40 | To(move), 41 | !GetBit(board->threatened, To(move)), 42 | IsEP(move) ? PAWN : PieceType(board->squares[To(move)])); 43 | } 44 | 45 | INLINE int GetHistory(SearchStack* ss, ThreadData* thread, Move move) { 46 | return IsCap(move) ? GetCaptureHistory(thread, move) : GetQuietHistory(ss, thread, move); 47 | } 48 | 49 | INLINE void AddKillerMove(SearchStack* ss, Move move) { 50 | if (ss->killers[0] != move) { 51 | ss->killers[1] = ss->killers[0]; 52 | ss->killers[0] = move; 53 | } 54 | } 55 | 56 | INLINE void AddCounterMove(ThreadData* thread, Move move, Move parent) { 57 | thread->counters[Moving(parent)][To(parent)] = move; 58 | } 59 | 60 | INLINE int16_t HistoryBonus(int depth) { 61 | return Min(1708, 4 * depth * depth + 191 * depth - 118); 62 | } 63 | 64 | INLINE void AddHistoryHeuristic(int16_t* entry, int16_t inc) { 65 | *entry += inc - *entry * abs(inc) / 16384; 66 | } 67 | 68 | INLINE void UpdateCH(SearchStack* ss, Move move, int16_t bonus) { 69 | if ((ss - 1)->move) 70 | AddHistoryHeuristic(&(*(ss - 1)->ch)[Moving(move)][To(move)], bonus); 71 | if ((ss - 2)->move) 72 | AddHistoryHeuristic(&(*(ss - 2)->ch)[Moving(move)][To(move)], bonus); 73 | if ((ss - 4)->move) 74 | AddHistoryHeuristic(&(*(ss - 4)->ch)[Moving(move)][To(move)], bonus); 75 | if ((ss - 6)->move) 76 | AddHistoryHeuristic(&(*(ss - 6)->ch)[Moving(move)][To(move)], bonus); 77 | } 78 | 79 | INLINE int GetCorrectionScore(Board* board, ThreadData* thread, SearchStack* ss) { 80 | const int pawn = thread->pawnCorrection[board->pawnZobrist & PAWN_CORRECTION_MASK]; 81 | const int cont1 = (*(ss - 3)->cont)[Moving((ss - 1)->move)][To((ss - 1)->move)]; 82 | const int cont2 = (*(ss - 2)->cont)[Moving((ss - 1)->move)][To((ss - 1)->move)]; 83 | 84 | return (31 * pawn + 17 * cont1 + 46 * cont2) / 8192; 85 | } 86 | 87 | void UpdateHistories(SearchStack* ss, 88 | ThreadData* thread, 89 | Move bestMove, 90 | int depth, 91 | Move quiets[], 92 | int nQ, 93 | Move captures[], 94 | int nC); 95 | 96 | void UpdatePawnCorrection(int raw, int real, int depth, Board* board, ThreadData* thread); 97 | void UpdateContCorrection(int raw, int real, int depth, SearchStack* ss); 98 | 99 | #endif 100 | -------------------------------------------------------------------------------- /src/incbin.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file incbin.h 3 | * @author Dale Weiler 4 | * @brief Utility for including binary files 5 | * 6 | * Facilities for including binary files into the current translation unit and 7 | * making use from them externally in other translation units. 8 | */ 9 | 10 | #pragma once 11 | 12 | #ifndef INCBIN_HDR 13 | #define INCBIN_HDR 14 | #include 15 | #if defined(__AVX512BW__) || defined(__AVX512CD__) || defined(__AVX512DQ__) || defined(__AVX512ER__) || \ 16 | defined(__AVX512PF__) || defined(__AVX512VL__) || defined(__AVX512F__) 17 | #define INCBIN_ALIGNMENT_INDEX 6 18 | #elif defined(__AVX__) || defined(__AVX2__) 19 | #define INCBIN_ALIGNMENT_INDEX 5 20 | #elif defined(__SSE__) || defined(__SSE2__) || defined(__SSE3__) || defined(__SSSE3__) || defined(__SSE4_1__) || \ 21 | defined(__SSE4_2__) || defined(__neon__) 22 | #define INCBIN_ALIGNMENT_INDEX 4 23 | #elif ULONG_MAX != 0xffffffffu 24 | #define INCBIN_ALIGNMENT_INDEX 3 25 | #else 26 | #define INCBIN_ALIGNMENT_INDEX 2 27 | #endif 28 | 29 | /* Lookup table of (1 << n) where `n' is `INCBIN_ALIGNMENT_INDEX' */ 30 | #define INCBIN_ALIGN_SHIFT_0 1 31 | #define INCBIN_ALIGN_SHIFT_1 2 32 | #define INCBIN_ALIGN_SHIFT_2 4 33 | #define INCBIN_ALIGN_SHIFT_3 8 34 | #define INCBIN_ALIGN_SHIFT_4 16 35 | #define INCBIN_ALIGN_SHIFT_5 32 36 | #define INCBIN_ALIGN_SHIFT_6 64 37 | 38 | /* Actual alignment value */ 39 | #define INCBIN_ALIGNMENT INCBIN_CONCATENATE(INCBIN_CONCATENATE(INCBIN_ALIGN_SHIFT, _), INCBIN_ALIGNMENT_INDEX) 40 | 41 | /* Stringize */ 42 | #define INCBIN_STR(X) #X 43 | #define INCBIN_STRINGIZE(X) INCBIN_STR(X) 44 | /* Concatenate */ 45 | #define INCBIN_CAT(X, Y) X##Y 46 | #define INCBIN_CONCATENATE(X, Y) INCBIN_CAT(X, Y) 47 | /* Deferred macro expansion */ 48 | #define INCBIN_EVAL(X) X 49 | #define INCBIN_INVOKE(N, ...) INCBIN_EVAL(N(__VA_ARGS__)) 50 | 51 | /* Green Hills uses a different directive for including binary data */ 52 | #if defined(__ghs__) 53 | #if (__ghs_asm == 2) 54 | #define INCBIN_MACRO ".file" 55 | /* Or consider the ".myrawdata" entry in the ld file */ 56 | #else 57 | #define INCBIN_MACRO "\tINCBIN" 58 | #endif 59 | #else 60 | #define INCBIN_MACRO ".incbin" 61 | #endif 62 | 63 | #ifndef _MSC_VER 64 | #define INCBIN_ALIGN __attribute__((aligned(INCBIN_ALIGNMENT))) 65 | #else 66 | #define INCBIN_ALIGN __declspec(align(INCBIN_ALIGNMENT)) 67 | #endif 68 | 69 | #if defined(__arm__) || /* GNU C and RealView */ \ 70 | defined(__arm) || /* Diab */ \ 71 | defined(_ARM) /* ImageCraft */ 72 | #define INCBIN_ARM 73 | #endif 74 | 75 | #ifdef __GNUC__ 76 | /* Utilize .balign where supported */ 77 | #define INCBIN_ALIGN_HOST ".balign " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n" 78 | #define INCBIN_ALIGN_BYTE ".balign 1\n" 79 | #elif defined(INCBIN_ARM) 80 | /* 81 | * On arm assemblers, the alignment value is calculated as (1 << n) where `n' is 82 | * the shift count. This is the value passed to `.align' 83 | */ 84 | #define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT_INDEX) "\n" 85 | #define INCBIN_ALIGN_BYTE ".align 0\n" 86 | #else 87 | /* We assume other inline assembler's treat `.align' as `.balign' */ 88 | #define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n" 89 | #define INCBIN_ALIGN_BYTE ".align 1\n" 90 | #endif 91 | 92 | /* INCBIN_CONST is used by incbin.c generated files */ 93 | #if defined(__cplusplus) 94 | #define INCBIN_EXTERNAL extern "C" 95 | #define INCBIN_CONST extern const 96 | #else 97 | #define INCBIN_EXTERNAL extern 98 | #define INCBIN_CONST const 99 | #endif 100 | 101 | /** 102 | * @brief Optionally override the linker section into which data is emitted. 103 | * 104 | * @warning If you use this facility, you'll have to deal with platform-specific 105 | * linker output section naming on your own 106 | * 107 | * Overriding the default linker output section, e.g for esp8266/Arduino: 108 | * @code 109 | * #define INCBIN_OUTPUT_SECTION ".irom.text" 110 | * #include "incbin.h" 111 | * INCBIN(Foo, "foo.txt"); 112 | * // Data is emitted into program memory that never gets copied to RAM 113 | * @endcode 114 | */ 115 | #if !defined(INCBIN_OUTPUT_SECTION) 116 | #if defined(__APPLE__) 117 | #define INCBIN_OUTPUT_SECTION ".const_data" 118 | #else 119 | #define INCBIN_OUTPUT_SECTION ".rodata" 120 | #endif 121 | #endif 122 | 123 | #if defined(__APPLE__) 124 | /* The directives are different for Apple branded compilers */ 125 | #define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n" 126 | #define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" 127 | #define INCBIN_INT ".long " 128 | #define INCBIN_MANGLE "_" 129 | #define INCBIN_BYTE ".byte " 130 | #define INCBIN_TYPE(...) 131 | #else 132 | #define INCBIN_SECTION ".section " INCBIN_OUTPUT_SECTION "\n" 133 | #define INCBIN_GLOBAL(NAME) ".global " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" 134 | #if defined(__ghs__) 135 | #define INCBIN_INT ".word " 136 | #else 137 | #define INCBIN_INT ".int " 138 | #endif 139 | #if defined(__USER_LABEL_PREFIX__) 140 | #define INCBIN_MANGLE INCBIN_STRINGIZE(__USER_LABEL_PREFIX__) 141 | #else 142 | #define INCBIN_MANGLE "" 143 | #endif 144 | #if defined(INCBIN_ARM) 145 | /* On arm assemblers, `@' is used as a line comment token */ 146 | #define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", %object\n" 147 | #elif defined(__MINGW32__) || defined(__MINGW64__) 148 | /* Mingw doesn't support this directive either */ 149 | #define INCBIN_TYPE(NAME) 150 | #else 151 | /* It's safe to use `@' on other architectures */ 152 | #define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", @object\n" 153 | #endif 154 | #define INCBIN_BYTE ".byte " 155 | #endif 156 | 157 | /* List of style types used for symbol names */ 158 | #define INCBIN_STYLE_CAMEL 0 159 | #define INCBIN_STYLE_SNAKE 1 160 | 161 | /** 162 | * @brief Specify the prefix to use for symbol names. 163 | * 164 | * By default this is `g', producing symbols of the form: 165 | * @code 166 | * #include "incbin.h" 167 | * INCBIN(Foo, "foo.txt"); 168 | * 169 | * // Now you have the following symbols: 170 | * // const unsigned char gFooData[]; 171 | * // const unsigned char *const gFooEnd; 172 | * // const unsigned int gFooSize; 173 | * @endcode 174 | * 175 | * If however you specify a prefix before including: e.g: 176 | * @code 177 | * #define INCBIN_PREFIX incbin 178 | * #include "incbin.h" 179 | * INCBIN(Foo, "foo.txt"); 180 | * 181 | * // Now you have the following symbols instead: 182 | * // const unsigned char incbinFooData[]; 183 | * // const unsigned char *const incbinFooEnd; 184 | * // const unsigned int incbinFooSize; 185 | * @endcode 186 | */ 187 | #if !defined(INCBIN_PREFIX) 188 | #define INCBIN_PREFIX g 189 | #endif 190 | 191 | /** 192 | * @brief Specify the style used for symbol names. 193 | * 194 | * Possible options are 195 | * - INCBIN_STYLE_CAMEL "CamelCase" 196 | * - INCBIN_STYLE_SNAKE "snake_case" 197 | * 198 | * Default option is *INCBIN_STYLE_CAMEL* producing symbols of the form: 199 | * @code 200 | * #include "incbin.h" 201 | * INCBIN(Foo, "foo.txt"); 202 | * 203 | * // Now you have the following symbols: 204 | * // const unsigned char FooData[]; 205 | * // const unsigned char *const FooEnd; 206 | * // const unsigned int FooSize; 207 | * @endcode 208 | * 209 | * If however you specify a style before including: e.g: 210 | * @code 211 | * #define INCBIN_STYLE INCBIN_STYLE_SNAKE 212 | * #include "incbin.h" 213 | * INCBIN(foo, "foo.txt"); 214 | * 215 | * // Now you have the following symbols: 216 | * // const unsigned char foo_data[]; 217 | * // const unsigned char *const foo_end; 218 | * // const unsigned int foo_size; 219 | * @endcode 220 | */ 221 | #if !defined(INCBIN_STYLE) 222 | #define INCBIN_STYLE INCBIN_STYLE_CAMEL 223 | #endif 224 | 225 | /* Style lookup tables */ 226 | #define INCBIN_STYLE_0_DATA Data 227 | #define INCBIN_STYLE_0_END End 228 | #define INCBIN_STYLE_0_SIZE Size 229 | #define INCBIN_STYLE_1_DATA _data 230 | #define INCBIN_STYLE_1_END _end 231 | #define INCBIN_STYLE_1_SIZE _size 232 | 233 | /* Style lookup: returning identifier */ 234 | #define INCBIN_STYLE_IDENT(TYPE) \ 235 | INCBIN_CONCATENATE(INCBIN_STYLE_, INCBIN_CONCATENATE(INCBIN_EVAL(INCBIN_STYLE), INCBIN_CONCATENATE(_, TYPE))) 236 | 237 | /* Style lookup: returning string literal */ 238 | #define INCBIN_STYLE_STRING(TYPE) INCBIN_STRINGIZE(INCBIN_STYLE_IDENT(TYPE)) 239 | 240 | /* Generate the global labels by indirectly invoking the macro with our style 241 | * type and concatenating the name against them. */ 242 | #define INCBIN_GLOBAL_LABELS(NAME, TYPE) \ 243 | INCBIN_INVOKE(INCBIN_GLOBAL, INCBIN_CONCATENATE(NAME, INCBIN_INVOKE(INCBIN_STYLE_IDENT, TYPE))) \ 244 | INCBIN_INVOKE(INCBIN_TYPE, INCBIN_CONCATENATE(NAME, INCBIN_INVOKE(INCBIN_STYLE_IDENT, TYPE))) 245 | 246 | /** 247 | * @brief Externally reference binary data included in another translation unit. 248 | * 249 | * Produces three external symbols that reference the binary data included in 250 | * another translation unit. 251 | * 252 | * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with 253 | * "Data", as well as "End" and "Size" after. An example is provided below. 254 | * 255 | * @param NAME The name given for the binary data 256 | * 257 | * @code 258 | * INCBIN_EXTERN(Foo); 259 | * 260 | * // Now you have the following symbols: 261 | * // extern const unsigned char FooData[]; 262 | * // extern const unsigned char *const FooEnd; 263 | * // extern const unsigned int FooSize; 264 | * @endcode 265 | */ 266 | #define INCBIN_EXTERN(NAME) \ 267 | INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char INCBIN_CONCATENATE(INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ 268 | INCBIN_STYLE_IDENT(DATA))[]; \ 269 | INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char* const INCBIN_CONCATENATE(INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ 270 | INCBIN_STYLE_IDENT(END)); \ 271 | INCBIN_EXTERNAL const unsigned int INCBIN_CONCATENATE(INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ 272 | INCBIN_STYLE_IDENT(SIZE)) 273 | 274 | /** 275 | * @brief Include a binary file into the current translation unit. 276 | * 277 | * Includes a binary file into the current translation unit, producing three 278 | * symbols for objects that encode the data and size respectively. 279 | * 280 | * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with 281 | * "Data", as well as "End" and "Size" after. An example is provided below. 282 | * 283 | * @param NAME The name to associate with this binary data (as an identifier.) 284 | * @param FILENAME The file to include (as a string literal.) 285 | * 286 | * @code 287 | * INCBIN(Icon, "icon.png"); 288 | * 289 | * // Now you have the following symbols: 290 | * // const unsigned char IconData[]; 291 | * // const unsigned char *const IconEnd; 292 | * // const unsigned int IconSize; 293 | * @endcode 294 | * 295 | * @warning This must be used in global scope 296 | * @warning The identifiers may be different if INCBIN_STYLE is not default 297 | * 298 | * To externally reference the data included by this in another translation unit 299 | * please @see INCBIN_EXTERN. 300 | */ 301 | #ifdef _MSC_VER 302 | #define INCBIN(NAME, FILENAME) INCBIN_EXTERN(NAME) 303 | #else 304 | #define INCBIN(NAME, FILENAME) \ 305 | __asm__(INCBIN_SECTION INCBIN_GLOBAL_LABELS(NAME, DATA) \ 306 | INCBIN_ALIGN_HOST INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING( \ 307 | DATA) ":\n" INCBIN_MACRO " \"" FILENAME "\"\n" INCBIN_GLOBAL_LABELS(NAME, END) \ 308 | INCBIN_ALIGN_BYTE INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING( \ 309 | END) ":\n" INCBIN_BYTE "1\n" INCBIN_GLOBAL_LABELS(NAME, SIZE) \ 310 | INCBIN_ALIGN_HOST INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING( \ 311 | SIZE) ":\n" INCBIN_INT INCBIN_MANGLE \ 312 | INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) " - " INCBIN_MANGLE INCBIN_STRINGIZE( \ 313 | INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) "\n" INCBIN_ALIGN_HOST ".text\n"); \ 314 | INCBIN_EXTERN(NAME) 315 | 316 | #endif 317 | #endif -------------------------------------------------------------------------------- /src/makefile: -------------------------------------------------------------------------------- 1 | # General 2 | EXE = berserk 3 | SRC = attacks.c bench.c berserk.c bits.c board.c eval.c history.c move.c movegen.c movepick.c perft.c random.c \ 4 | search.c see.c tb.c thread.c transposition.c uci.c util.c zobrist.c nn/accumulator.c nn/evaluate.c pyrrhic/tbprobe.c 5 | CC = clang 6 | VERSION = 20250525 7 | MAIN_NETWORK = berserk-deb80bf0ba9d.nn 8 | EVALFILE = $(MAIN_NETWORK) 9 | DEFS = -DVERSION=\"$(VERSION)\" -DEVALFILE=\"$(EVALFILE)\" -DNDEBUG 10 | 11 | # Flags 12 | STD = -std=gnu11 13 | LIBS = -pthread -lm 14 | WARN = -Wall -Wextra -Wshadow 15 | 16 | FLAGS = $(STD) $(WARN) -g -O3 -flto $(PGOFLAGS) $(DEFS) 17 | M64 = -m64 -mpopcnt 18 | MSSE41 = $(M64) -msse -msse2 -mssse3 -msse4.1 19 | MAVX2 = $(MSSE41) -mbmi -mfma -mavx2 20 | MAVX512 = $(MAVX2) -mavx512f -mavx512bw 21 | ARM64 = -arch arm64 22 | 23 | # Detecting windows 24 | ifeq ($(shell echo "test"), "test") 25 | FLAGS += -static 26 | endif 27 | 28 | # Detecting Apple Silicon (ARM64) 29 | UNAME := $(shell uname -m) 30 | ifeq ($(UNAME), arm64) 31 | ARCH = arm64 32 | endif 33 | 34 | # Setup arch 35 | ifeq ($(ARCH), ) 36 | ARCH = native 37 | endif 38 | 39 | ifeq ($(ARCH), native) 40 | CFLAGS = $(FLAGS) -march=native 41 | else ifeq ($(ARCH), arm64) 42 | CFLAGS = $(FLAGS) $(ARM64) 43 | else ifeq ($(findstring x86-64, $(ARCH)), x86-64) 44 | CFLAGS = $(FLAGS) $(M64) 45 | else ifeq ($(findstring sse41, $(ARCH)), sse41) 46 | CFLAGS = $(FLAGS) $(MSSE41) 47 | else ifeq ($(findstring avx2, $(ARCH)), avx2) 48 | CFLAGS = $(FLAGS) $(MAVX2) 49 | else ifeq ($(findstring avx512, $(ARCH)), avx512) 50 | CFLAGS = $(FLAGS) $(MAVX512) 51 | endif 52 | 53 | ifeq ($(ARCH), native) 54 | PROPS = $(shell echo | $(CC) -march=native -E -dM -) 55 | ifneq ($(findstring __BMI2__, $(PROPS)),) 56 | ifeq ($(findstring __znver1, $(PROPS)),) 57 | ifeq ($(findstring __znver2, $(PROPS)),) 58 | CFLAGS += -DUSE_PEXT 59 | endif 60 | endif 61 | endif 62 | else ifeq ($(findstring -pext, $(ARCH)), -pext) 63 | CFLAGS += -DUSE_PEXT -mbmi2 64 | endif 65 | 66 | openbench: download-network 67 | $(MAKE) ARCH=avx2 all 68 | 69 | build: download-network 70 | $(MAKE) ARCH=$(ARCH) all 71 | 72 | pgo: download-network 73 | ifeq ($(findstring gcc, $(CC)), gcc) 74 | $(MAKE) ARCH=$(ARCH) PGOFLAGS="-fprofile-generate=pgo" all 75 | 76 | ./$(EXE) bench 13 > pgo.out 2>&1 77 | grep Results pgo.out 78 | 79 | $(MAKE) ARCH=$(ARCH) PGOFLAGS="-fprofile-use=pgo" all 80 | 81 | @rm -rf pgo pgo.out 82 | else ifeq ($(findstring clang, $(CC)), clang) 83 | $(MAKE) ARCH=$(ARCH) PGOFLAGS="-fprofile-instr-generate" all 84 | 85 | ./$(EXE) bench 13 > pgo.out 2>&1 86 | grep Results pgo.out 87 | 88 | llvm-profdata merge -output=berserk.profdata *.profraw 89 | $(MAKE) ARCH=$(ARCH) PGOFLAGS="-fprofile-instr-use=berserk.profdata" all 90 | 91 | @rm -rf pgo pgo.out berserk.profdata *.profraw 92 | else 93 | @echo "PGO builds not supported for $(CC)" 94 | endif 95 | 96 | all: 97 | $(CC) $(CFLAGS) $(SRC) $(LIBS) -o $(EXE) 98 | 99 | download-network: 100 | @if [ "$(EVALFILE)" = "$(MAIN_NETWORK)" ]; then \ 101 | echo "Using the current best network: $(EVALFILE)"; \ 102 | if test -f "$(EVALFILE)"; then \ 103 | echo "File already downloaded"; \ 104 | elif hash wget 2>/dev/null; then \ 105 | echo "Downloading $(EVALFILE) with wget"; wget -qO- https://berserk-networks.s3.amazonaws.com/$(EVALFILE) > $(EVALFILE); \ 106 | elif hash curl 2>/dev/null; then \ 107 | echo "Downloading $(EVALFILE) with curl"; curl -skL https://berserk-networks.s3.amazonaws.com/$(EVALFILE) > $(EVALFILE); \ 108 | fi; \ 109 | if test -f "$(EVALFILE)"; then \ 110 | if hash shasum 2>/dev/null; then \ 111 | if [ "$(EVALFILE)" = "berserk-"`shasum -a 256 $(EVALFILE) | cut -c1-12`".nn" ]; then \ 112 | echo "Downloaded network $(EVALFILE) and verified"; \ 113 | else \ 114 | echo "Downloaded network $(EVALFILE) failed validation"; \ 115 | exit 1; \ 116 | fi; \ 117 | elif hash sha256sum 2>/dev/null; then \ 118 | if [ "$(EVALFILE)" = "berserk-"`sha256sum $(EVALFILE) | cut -c1-12`".nn" ]; then \ 119 | echo "Downloaded network $(EVALFILE) and verified"; \ 120 | else \ 121 | echo "Downloaded network $(EVALFILE) failed validation"; \ 122 | exit 1; \ 123 | fi; \ 124 | else \ 125 | echo "Downloaded network $(EVALFILE), but unable to verify"; \ 126 | fi; \ 127 | else \ 128 | echo "Unable to download network: $(EVALFILE)"; \ 129 | exit 1; \ 130 | fi; \ 131 | elif test -f "$(EVALFILE)"; then \ 132 | echo "Using network: $(EVALFILE)"; \ 133 | else \ 134 | echo "Unknown network: $(EVALFILE)"; \ 135 | fi; 136 | 137 | clean: 138 | rm -f $(EXE) 139 | -------------------------------------------------------------------------------- /src/move.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "move.h" 18 | 19 | #include 20 | #include 21 | 22 | #include "movegen.h" 23 | #include "movepick.h" 24 | #include "types.h" 25 | #include "uci.h" 26 | 27 | const char* PIECE_TO_CHAR = "PpNnBbRrQqKk"; 28 | 29 | const char* PROMOTION_TO_CHAR = "-nbrq-"; 30 | 31 | const int CHAR_TO_PIECE[] = { 32 | ['P'] = WHITE_PAWN, // 33 | ['N'] = WHITE_KNIGHT, // 34 | ['B'] = WHITE_BISHOP, // 35 | ['R'] = WHITE_ROOK, // 36 | ['Q'] = WHITE_QUEEN, // 37 | ['K'] = WHITE_KING, // 38 | ['p'] = BLACK_PAWN, // 39 | ['n'] = BLACK_KNIGHT, // 40 | ['b'] = BLACK_BISHOP, // 41 | ['r'] = BLACK_ROOK, // 42 | ['q'] = BLACK_QUEEN, // 43 | ['k'] = BLACK_KING, // 44 | }; 45 | 46 | const char* SQ_TO_COORD[64] = { 47 | "a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8", // 48 | "a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7", // 49 | "a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6", // 50 | "a5", "b5", "c5", "d5", "e5", "f5", "g5", "h5", // 51 | "a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4", // 52 | "a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3", // 53 | "a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2", // 54 | "a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1", // 55 | }; 56 | 57 | const int CASTLING_ROOK[64] = { 58 | [G1] = 0, 59 | [C1] = 1, 60 | [G8] = 2, 61 | [C8] = 3, 62 | }; 63 | 64 | const int CASTLE_ROOK_DEST[64] = { 65 | [G1] = F1, 66 | [C1] = D1, 67 | [G8] = F8, 68 | [C8] = D8, 69 | }; 70 | 71 | Move ParseMove(char* moveStr, Board* board) { 72 | SimpleMoveList rootMoves; 73 | RootMoves(&rootMoves, board); 74 | 75 | for (int i = 0; i < rootMoves.count; i++) { 76 | if (!strcmp("O-O", moveStr) && IsCas(rootMoves.moves[i]) && 77 | To(rootMoves.moves[i]) == (board->stm == WHITE ? G1 : G8)) 78 | return rootMoves.moves[i]; 79 | else if (!strcmp("O-O-O", moveStr) && IsCas(rootMoves.moves[i]) && 80 | To(rootMoves.moves[i]) == (board->stm == WHITE ? C1 : C8)) 81 | return rootMoves.moves[i]; 82 | else if (!strcmp(MoveToStr(rootMoves.moves[i], board), moveStr)) 83 | return rootMoves.moves[i]; 84 | } 85 | 86 | return NULL_MOVE; 87 | } 88 | 89 | char* MoveToStr(Move move, Board* board) { 90 | static char buffer[6]; 91 | 92 | int from = From(move); 93 | int to = To(move); 94 | 95 | if (CHESS_960 && IsCas(move)) 96 | to = board->cr[CASTLING_ROOK[to]]; 97 | 98 | if (IsPromo(move)) { 99 | sprintf(buffer, "%s%s%c", SQ_TO_COORD[from], SQ_TO_COORD[to], PROMOTION_TO_CHAR[PromoPT(move)]); 100 | } else { 101 | sprintf(buffer, "%s%s", SQ_TO_COORD[from], SQ_TO_COORD[to]); 102 | } 103 | 104 | return buffer; 105 | } 106 | -------------------------------------------------------------------------------- /src/move.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef MOVE_H 18 | #define MOVE_H 19 | 20 | #include "types.h" 21 | 22 | #define NULL_MOVE 0 23 | 24 | #define QUIET_FLAG 0b0000 25 | #define CASTLE_FLAG 0b0001 26 | #define CAPTURE_FLAG 0b0100 27 | #define EP_FLAG 0b0110 28 | #define PROMO_FLAG 0b1000 29 | #define KNIGHT_PROMO_FLAG 0b1000 30 | #define BISHOP_PROMO_FLAG 0b1001 31 | #define ROOK_PROMO_FLAG 0b1010 32 | #define QUEEN_PROMO_FLAG 0b1011 33 | 34 | extern const int CHAR_TO_PIECE[]; 35 | extern const char* PIECE_TO_CHAR; 36 | extern const char* PROMOTION_TO_CHAR; 37 | extern const char* SQ_TO_COORD[64]; 38 | extern const int CASTLE_ROOK_DEST[64]; 39 | extern const int CASTLING_ROOK[64]; 40 | 41 | #define BuildMove(from, to, piece, flags) (from) | ((to) << 6) | ((piece) << 12) | ((flags) << 16) 42 | #define FromTo(move) (((int) (move) &0x00fff) >> 0) 43 | #define From(move) (((int) (move) &0x0003f) >> 0) 44 | #define To(move) (((int) (move) &0x00fc0) >> 6) 45 | #define Moving(move) (((int) (move) &0x0f000) >> 12) 46 | #define Flags(move) (((int) (move) &0xf0000) >> 16) 47 | 48 | #define IsCap(move) (!!(Flags(move) & CAPTURE_FLAG)) 49 | #define IsEP(move) (Flags(move) == EP_FLAG) 50 | #define IsCas(move) (Flags(move) == CASTLE_FLAG) 51 | 52 | #define IsPromo(move) (!!(Flags(move) & PROMO_FLAG)) 53 | #define PromoPT(move) ((Flags(move) & 0x3) + KNIGHT) 54 | #define PromoPiece(move, stm) (Piece(PromoPT(move), stm)) 55 | 56 | Move ParseMove(char* moveStr, Board* board); 57 | char* MoveToStr(Move move, Board* board); 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /src/movegen.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "movegen.h" 18 | 19 | const int CASTLE_MAP[4][3] = { 20 | {WHITE_KS, G1, F1}, 21 | {WHITE_QS, C1, D1}, 22 | {BLACK_KS, G8, F8}, 23 | {BLACK_QS, C8, D8}, 24 | }; 25 | 26 | ScoredMove* AddNoisyMoves(ScoredMove* moves, Board* board) { 27 | return board->stm == WHITE ? AddPseudoLegalMoves(moves, board, GT_CAPTURE, WHITE) : // 28 | AddPseudoLegalMoves(moves, board, GT_CAPTURE, BLACK); 29 | } 30 | 31 | ScoredMove* AddQuietMoves(ScoredMove* moves, Board* board) { 32 | return board->stm == WHITE ? AddPseudoLegalMoves(moves, board, GT_QUIET, WHITE) : // 33 | AddPseudoLegalMoves(moves, board, GT_QUIET, BLACK); 34 | } 35 | 36 | ScoredMove* AddEvasionMoves(ScoredMove* moves, Board* board) { 37 | return board->stm == WHITE ? AddPseudoLegalMoves(moves, board, GT_LEGAL, WHITE) : // 38 | AddPseudoLegalMoves(moves, board, GT_LEGAL, BLACK); 39 | } 40 | 41 | ScoredMove* AddQuietCheckMoves(ScoredMove* moves, Board* board) { 42 | return board->stm == WHITE ? AddQuietChecks(moves, board, WHITE) : // 43 | AddQuietChecks(moves, board, BLACK); 44 | } 45 | 46 | ScoredMove* AddPerftMoves(ScoredMove* moves, Board* board) { 47 | return board->stm == WHITE ? AddLegalMoves(moves, board, WHITE) : // 48 | AddLegalMoves(moves, board, BLACK); 49 | } 50 | -------------------------------------------------------------------------------- /src/movegen.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef MOVEGEN_H 18 | #define MOVEGEN_H 19 | 20 | #include "attacks.h" 21 | #include "bits.h" 22 | #include "board.h" 23 | #include "eval.h" 24 | #include "move.h" 25 | #include "search.h" 26 | #include "see.h" 27 | #include "transposition.h" 28 | #include "types.h" 29 | #include "util.h" 30 | 31 | extern const int CASTLE_MAP[4][3]; 32 | 33 | #define PawnDir(c) ((c) == WHITE ? N : S) 34 | #define PromoRank(c) ((c) == WHITE ? RANK_7 : RANK_2) 35 | #define DPRank(c) ((c) == WHITE ? RANK_3 : RANK_6) 36 | 37 | #define ALL -1ULL 38 | 39 | #define WHITE_KS 0x8 40 | #define WHITE_QS 0x4 41 | #define BLACK_KS 0x2 42 | #define BLACK_QS 0x1 43 | 44 | #define CanCastle(dir) (board->castling & (dir)) 45 | 46 | // Note: Any usage of & that matches 1 or 2 will match 3 47 | // thus making GT_LEGAL generate all. 48 | enum { 49 | GT_QUIET = 0b01, 50 | GT_CAPTURE = 0b10, 51 | GT_LEGAL = 0b11, 52 | }; 53 | 54 | INLINE ScoredMove* AddMove(ScoredMove* moves, int from, int to, int moving, int flags) { 55 | *moves++ = (ScoredMove) {.move = BuildMove(from, to, moving, flags), .score = 0}; 56 | return moves; 57 | } 58 | 59 | INLINE ScoredMove* AddPromotions(ScoredMove* moves, int from, int to, int moving, const int baseFlag, const int type) { 60 | if (type & GT_CAPTURE) 61 | moves = AddMove(moves, from, to, moving, baseFlag | QUEEN_PROMO_FLAG); 62 | 63 | if (type & GT_QUIET) { 64 | moves = AddMove(moves, from, to, moving, baseFlag | ROOK_PROMO_FLAG); 65 | moves = AddMove(moves, from, to, moving, baseFlag | BISHOP_PROMO_FLAG); 66 | moves = AddMove(moves, from, to, moving, baseFlag | KNIGHT_PROMO_FLAG); 67 | } 68 | 69 | return moves; 70 | } 71 | 72 | INLINE ScoredMove* AddPawnMoves(ScoredMove* moves, BitBoard opts, Board* board, const int stm, const int type) { 73 | const int xstm = stm ^ 1; 74 | 75 | if (type & GT_QUIET) { 76 | // Quiet non-promotions 77 | BitBoard valid = PieceBB(PAWN, stm) & ~PromoRank(stm); 78 | BitBoard targets = ShiftPawnDir(valid, stm) & ~OccBB(BOTH); 79 | BitBoard dpTargets = ShiftPawnDir(targets & DPRank(stm), stm) & ~OccBB(BOTH); 80 | 81 | targets &= opts, dpTargets &= opts; 82 | 83 | while (targets) { 84 | int to = PopLSB(&targets); 85 | moves = AddMove(moves, to - PawnDir(stm), to, Piece(PAWN, stm), QUIET_FLAG); 86 | } 87 | 88 | while (dpTargets) { 89 | int to = PopLSB(&dpTargets); 90 | moves = AddMove(moves, to - PawnDir(stm) - PawnDir(stm), to, Piece(PAWN, stm), QUIET_FLAG); 91 | } 92 | } 93 | 94 | if (type & GT_CAPTURE) { 95 | // Captures non-promotions 96 | BitBoard valid = PieceBB(PAWN, stm) & ~PromoRank(stm); 97 | BitBoard eTargets = ShiftPawnCapE(valid, stm) & OccBB(xstm) & opts; 98 | BitBoard wTargets = ShiftPawnCapW(valid, stm) & OccBB(xstm) & opts; 99 | 100 | while (eTargets) { 101 | int to = PopLSB(&eTargets); 102 | moves = AddMove(moves, to - (PawnDir(stm) + E), to, Piece(PAWN, stm), CAPTURE_FLAG); 103 | } 104 | 105 | while (wTargets) { 106 | int to = PopLSB(&wTargets); 107 | moves = AddMove(moves, to - (PawnDir(stm) + W), to, Piece(PAWN, stm), CAPTURE_FLAG); 108 | } 109 | 110 | if (board->epSquare) { 111 | BitBoard movers = GetPawnAttacks(board->epSquare, xstm) & valid; 112 | 113 | while (movers) { 114 | int from = PopLSB(&movers); 115 | moves = AddMove(moves, from, board->epSquare, Piece(PAWN, stm), EP_FLAG); 116 | } 117 | } 118 | } 119 | 120 | // Promotions (both capture and non-capture) 121 | BitBoard valid = PieceBB(PAWN, stm) & PromoRank(stm); 122 | BitBoard sTargets = ShiftPawnDir(valid, stm) & ~OccBB(BOTH) & opts; 123 | BitBoard eTargets = ShiftPawnCapE(valid, stm) & OccBB(xstm) & opts; 124 | BitBoard wTargets = ShiftPawnCapW(valid, stm) & OccBB(xstm) & opts; 125 | 126 | while (sTargets) { 127 | int to = PopLSB(&sTargets); 128 | moves = AddPromotions(moves, to - PawnDir(stm), to, Piece(PAWN, stm), QUIET_FLAG, type); 129 | } 130 | 131 | while (eTargets) { 132 | int to = PopLSB(&eTargets); 133 | moves = AddPromotions(moves, to - (PawnDir(stm) + E), to, Piece(PAWN, stm), CAPTURE_FLAG, type); 134 | } 135 | 136 | while (wTargets) { 137 | int to = PopLSB(&wTargets); 138 | moves = AddPromotions(moves, to - (PawnDir(stm) + W), to, Piece(PAWN, stm), CAPTURE_FLAG, type); 139 | } 140 | 141 | return moves; 142 | } 143 | 144 | INLINE ScoredMove* AddPieceMoves(ScoredMove* moves, 145 | BitBoard opts, 146 | Board* board, 147 | const int stm, 148 | const int type, 149 | const int piece) { 150 | const int xstm = stm ^ 1; 151 | 152 | BitBoard movers = PieceBB(piece, stm); 153 | while (movers) { 154 | int from = PopLSB(&movers); 155 | BitBoard valid = GetPieceAttacks(from, OccBB(BOTH), piece) & opts; 156 | 157 | if (type & GT_CAPTURE) { 158 | BitBoard targets = valid & OccBB(xstm); 159 | 160 | while (targets) { 161 | int to = PopLSB(&targets); 162 | 163 | moves = AddMove(moves, from, to, Piece(piece, stm), CAPTURE_FLAG); 164 | } 165 | } 166 | 167 | if (type & GT_QUIET) { 168 | BitBoard targets = valid & ~OccBB(BOTH); 169 | 170 | while (targets) { 171 | int to = PopLSB(&targets); 172 | 173 | moves = AddMove(moves, from, to, Piece(piece, stm), QUIET_FLAG); 174 | } 175 | } 176 | } 177 | 178 | return moves; 179 | } 180 | 181 | INLINE ScoredMove* AddCastles(ScoredMove* moves, Board* board, const int stm) { 182 | const int rookStart = stm == WHITE ? 0 : 2; 183 | const int rookEnd = stm == WHITE ? 2 : 4; 184 | const int from = LSB(PieceBB(KING, stm)); 185 | 186 | for (int rk = rookStart; rk < rookEnd; rk++) { 187 | if (!CanCastle(CASTLE_MAP[rk][0])) 188 | continue; 189 | 190 | int rookFrom = board->cr[rk]; 191 | if (GetBit(board->pinned, rookFrom)) 192 | continue; 193 | 194 | int to = CASTLE_MAP[rk][1], rookTo = CASTLE_MAP[rk][2]; 195 | 196 | BitBoard kingCrossing = BetweenSquares(from, to) | Bit(to); 197 | BitBoard rookCrossing = BetweenSquares(rookFrom, rookTo) | Bit(rookTo); 198 | BitBoard between = kingCrossing | rookCrossing; 199 | 200 | if (!((OccBB(BOTH) ^ Bit(from) ^ Bit(rookFrom)) & between)) 201 | if (!(kingCrossing & board->threatened)) 202 | moves = AddMove(moves, from, to, Piece(KING, stm), CASTLE_FLAG); 203 | } 204 | 205 | return moves; 206 | } 207 | 208 | INLINE ScoredMove* AddQuietChecks(ScoredMove* moves, Board* board, const int stm) { 209 | const int xstm = !stm; 210 | const int oppKingSq = LSB(PieceBB(KING, xstm)); 211 | const BitBoard bishopMask = GetBishopAttacks(oppKingSq, OccBB(BOTH)); 212 | const BitBoard rookMask = GetRookAttacks(oppKingSq, OccBB(BOTH)); 213 | 214 | moves = AddPieceMoves(moves, bishopMask | rookMask, board, stm, GT_QUIET, QUEEN); 215 | moves = AddPieceMoves(moves, rookMask, board, stm, GT_QUIET, ROOK); 216 | moves = AddPieceMoves(moves, bishopMask, board, stm, GT_QUIET, BISHOP); 217 | moves = AddPieceMoves(moves, GetKnightAttacks(oppKingSq), board, stm, GT_QUIET, KNIGHT); 218 | 219 | return AddPawnMoves(moves, GetPawnAttacks(oppKingSq, xstm), board, stm, GT_QUIET); 220 | } 221 | 222 | INLINE ScoredMove* AddPseudoLegalMoves(ScoredMove* moves, Board* board, const int type, const int color) { 223 | if (BitCount(board->checkers) > 1) 224 | return AddPieceMoves(moves, ~board->threatened, board, color, type, KING); 225 | 226 | BitBoard opts = 227 | !board->checkers ? ALL : BetweenSquares(LSB(PieceBB(KING, color)), LSB(board->checkers)) | board->checkers; 228 | 229 | moves = AddPawnMoves(moves, opts, board, color, type); 230 | moves = AddPieceMoves(moves, opts, board, color, type, KNIGHT); 231 | moves = AddPieceMoves(moves, opts, board, color, type, BISHOP); 232 | moves = AddPieceMoves(moves, opts, board, color, type, ROOK); 233 | moves = AddPieceMoves(moves, opts, board, color, type, QUEEN); 234 | moves = AddPieceMoves(moves, ~board->threatened, board, color, type, KING); 235 | if ((type & GT_QUIET) && !board->checkers) 236 | moves = AddCastles(moves, board, color); 237 | 238 | return moves; 239 | } 240 | 241 | INLINE ScoredMove* AddLegalMoves(ScoredMove* moves, Board* board, const int color) { 242 | ScoredMove* curr = moves; 243 | BitBoard pinned = board->pinned; 244 | 245 | moves = AddPseudoLegalMoves(moves, board, GT_LEGAL, color); 246 | 247 | while (curr != moves) { 248 | if (((pinned && GetBit(pinned, From(curr->move))) || IsEP(curr->move)) && !IsLegal(curr->move, board)) 249 | curr->move = (--moves)->move; 250 | else 251 | curr++; 252 | } 253 | 254 | return moves; 255 | } 256 | 257 | ScoredMove* AddNoisyMoves(ScoredMove* moves, Board* board); 258 | ScoredMove* AddQuietMoves(ScoredMove* moves, Board* board); 259 | ScoredMove* AddEvasionMoves(ScoredMove* moves, Board* board); 260 | ScoredMove* AddQuietCheckMoves(ScoredMove* moves, Board* board); 261 | ScoredMove* AddPerftMoves(ScoredMove* moves, Board* board); 262 | 263 | #endif 264 | -------------------------------------------------------------------------------- /src/movepick.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "movepick.h" 18 | 19 | #include 20 | 21 | #include "board.h" 22 | #include "eval.h" 23 | #include "history.h" 24 | #include "move.h" 25 | #include "movegen.h" 26 | #include "see.h" 27 | #include "transposition.h" 28 | #include "types.h" 29 | 30 | INLINE Move Best(ScoredMove* current, ScoredMove* end) { 31 | ScoredMove* orig = current; 32 | ScoredMove* max = current; 33 | 34 | while (++current < end) 35 | if (current->score > max->score) 36 | max = current; 37 | 38 | ScoredMove temp = *orig; 39 | *orig = *max; 40 | *max = temp; 41 | 42 | return orig->move; 43 | } 44 | 45 | INLINE void ScoreMoves(MovePicker* picker, Board* board, const int type) { 46 | ScoredMove* current = picker->current; 47 | ThreadData* thread = picker->thread; 48 | SearchStack* ss = picker->ss; 49 | 50 | const BitBoard pawnThreats = board->threatenedBy[PAWN]; 51 | const BitBoard minorThreats = pawnThreats | board->threatenedBy[KNIGHT] | board->threatenedBy[BISHOP]; 52 | const BitBoard rookThreats = minorThreats | board->threatenedBy[ROOK]; 53 | const BitBoard threats[3] = {pawnThreats, minorThreats, rookThreats}; 54 | 55 | while (current < picker->end) { 56 | const Move move = current->move; 57 | const int from = From(move); 58 | const int to = To(move); 59 | const int pc = Moving(move); 60 | const int pt = PieceType(pc); 61 | const int captured = IsEP(move) ? PAWN : PieceType(board->squares[to]); 62 | 63 | if (type == ST_QUIET || type == ST_EVASION_QT) { 64 | current->score = ((int) HH(board->stm, move, board->threatened) * 26 + // 65 | (int) (*(ss - 1)->ch)[pc][to] * 36 + // 66 | (int) (*(ss - 2)->ch)[pc][to] * 35 + // 67 | (int) (*(ss - 4)->ch)[pc][to] * 19 + // 68 | (int) (*(ss - 6)->ch)[pc][to] * 17) / 69 | 16; 70 | 71 | if (pt != PAWN && pt != KING) { 72 | const BitBoard danger = threats[Max(0, pt - BISHOP)]; 73 | 74 | if (GetBit(danger, from)) 75 | current->score += 16863; 76 | if (GetBit(danger, to)) 77 | current->score -= 13955; 78 | } 79 | } else if (type == ST_CAPTURE) 80 | current->score = GetCaptureHistory(picker->thread, move) / 16 + SEE_VALUE[PieceType(board->squares[To(move)])]; 81 | 82 | else if (type == ST_EVASION_CAP) 83 | current->score = 1e7 + SEE_VALUE[captured]; 84 | 85 | else if (type == ST_MVV) 86 | current->score = SEE_VALUE[captured] + 2000 * IsPromo(move); 87 | 88 | current++; 89 | } 90 | } 91 | 92 | Move NextMove(MovePicker* picker, Board* board, int skipQuiets) { 93 | switch (picker->phase) { 94 | case HASH_MOVE: 95 | picker->phase = GEN_NOISY_MOVES; 96 | if (IsPseudoLegal(picker->hashMove, board)) 97 | return picker->hashMove; 98 | // fallthrough 99 | case GEN_NOISY_MOVES: 100 | picker->current = picker->endBad = picker->moves; 101 | picker->end = AddNoisyMoves(picker->current, board); 102 | 103 | ScoreMoves(picker, board, ST_CAPTURE); 104 | 105 | picker->phase = PLAY_GOOD_NOISY; 106 | // fallthrough 107 | case PLAY_GOOD_NOISY: 108 | while (picker->current != picker->end) { 109 | Move move = Best(picker->current, picker->end); 110 | int score = picker->current->score; 111 | picker->current++; 112 | 113 | if (move == picker->hashMove) 114 | continue; 115 | 116 | else if (SEE(board, move, -score / 2)) 117 | return move; 118 | 119 | *picker->endBad++ = *(picker->current - 1); 120 | } 121 | 122 | picker->phase = PLAY_KILLER_1; 123 | // fallthrough 124 | case PLAY_KILLER_1: 125 | picker->phase = PLAY_KILLER_2; 126 | if (!skipQuiets && picker->killer1 != picker->hashMove && IsPseudoLegal(picker->killer1, board)) 127 | return picker->killer1; 128 | // fallthrough 129 | case PLAY_KILLER_2: 130 | picker->phase = PLAY_COUNTER; 131 | if (!skipQuiets && picker->killer2 != picker->hashMove && IsPseudoLegal(picker->killer2, board)) 132 | return picker->killer2; 133 | // fallthrough 134 | case PLAY_COUNTER: 135 | picker->phase = GEN_QUIET_MOVES; 136 | if (!skipQuiets && picker->counter != picker->hashMove && picker->counter != picker->killer1 && 137 | picker->counter != picker->killer2 && IsPseudoLegal(picker->counter, board)) 138 | return picker->counter; 139 | // fallthrough 140 | case GEN_QUIET_MOVES: 141 | if (!skipQuiets) { 142 | picker->current = picker->endBad; 143 | picker->end = AddQuietMoves(picker->current, board); 144 | 145 | ScoreMoves(picker, board, ST_QUIET); 146 | } 147 | 148 | picker->phase = PLAY_QUIETS; 149 | // fallthrough 150 | case PLAY_QUIETS: 151 | while (picker->current != picker->end && !skipQuiets) { 152 | Move move = Best(picker->current++, picker->end); 153 | 154 | if (move != picker->hashMove && // 155 | move != picker->killer1 && // 156 | move != picker->killer2 && // 157 | move != picker->counter) 158 | return move; 159 | } 160 | 161 | picker->current = picker->moves; 162 | picker->end = picker->endBad; 163 | 164 | picker->phase = PLAY_BAD_NOISY; 165 | // fallthrough 166 | case PLAY_BAD_NOISY: 167 | while (picker->current != picker->end) { 168 | Move move = (picker->current++)->move; 169 | 170 | if (move != picker->hashMove) 171 | return move; 172 | } 173 | 174 | picker->phase = -1; 175 | return NULL_MOVE; 176 | 177 | // Probcut MP Steps 178 | case PC_GEN_NOISY_MOVES: 179 | picker->current = picker->endBad = picker->moves; 180 | picker->end = AddNoisyMoves(picker->current, board); 181 | 182 | ScoreMoves(picker, board, ST_MVV); 183 | 184 | picker->phase = PC_PLAY_GOOD_NOISY; 185 | // fallthrough 186 | case PC_PLAY_GOOD_NOISY: 187 | while (picker->current != picker->end) { 188 | Move move = Best(picker->current++, picker->end); 189 | 190 | if (SEE(board, move, picker->seeCutoff)) 191 | return move; 192 | } 193 | 194 | picker->phase = -1; 195 | return NULL_MOVE; 196 | 197 | // QSearch MP Steps 198 | case QS_GEN_NOISY_MOVES: 199 | picker->current = picker->moves; 200 | picker->end = AddNoisyMoves(picker->current, board); 201 | 202 | ScoreMoves(picker, board, ST_CAPTURE); 203 | 204 | picker->phase = QS_PLAY_NOISY_MOVES; 205 | // fallthrough 206 | case QS_PLAY_NOISY_MOVES: 207 | while (picker->current != picker->end) 208 | return Best(picker->current++, picker->end); 209 | 210 | if (!picker->genChecks) { 211 | picker->phase = -1; 212 | return NULL_MOVE; 213 | } 214 | 215 | picker->phase = QS_GEN_QUIET_CHECKS; 216 | 217 | // fallthrough 218 | case QS_GEN_QUIET_CHECKS: 219 | picker->current = picker->moves; 220 | picker->end = AddQuietCheckMoves(picker->current, board); 221 | 222 | picker->phase = QS_PLAY_QUIET_CHECKS; 223 | 224 | // fallthrough 225 | case QS_PLAY_QUIET_CHECKS: 226 | while (picker->current != picker->end) 227 | return (picker->current++)->move; 228 | 229 | picker->phase = -1; 230 | return NULL_MOVE; 231 | 232 | // QSearch Evasion Steps 233 | case QS_EVASION_HASH_MOVE: 234 | picker->phase = QS_EVASION_GEN_NOISY; 235 | if (IsPseudoLegal(picker->hashMove, board)) 236 | return picker->hashMove; 237 | // fallthrough 238 | case QS_EVASION_GEN_NOISY: 239 | picker->current = picker->moves; 240 | picker->end = AddNoisyMoves(picker->current, board); 241 | 242 | ScoreMoves(picker, board, ST_EVASION_CAP); 243 | 244 | picker->phase = QS_EVASION_PLAY_NOISY; 245 | 246 | // fallthrough 247 | case QS_EVASION_PLAY_NOISY: 248 | while (picker->current != picker->end) { 249 | Move move = Best(picker->current++, picker->end); 250 | 251 | if (move != picker->hashMove) 252 | return move; 253 | } 254 | 255 | picker->phase = QS_EVASION_GEN_QUIET; 256 | 257 | // fallthrough 258 | case QS_EVASION_GEN_QUIET: 259 | if (!skipQuiets) { 260 | picker->current = picker->moves; 261 | picker->end = AddQuietMoves(picker->current, board); 262 | 263 | ScoreMoves(picker, board, ST_EVASION_QT); 264 | } 265 | 266 | picker->phase = QS_EVASION_PLAY_QUIET; 267 | 268 | // fallthrough 269 | case QS_EVASION_PLAY_QUIET: 270 | while (picker->current != picker->end && !skipQuiets) { 271 | Move move = Best(picker->current++, picker->end); 272 | 273 | if (move != picker->hashMove) 274 | return move; 275 | } 276 | 277 | picker->phase = -1; 278 | return NULL_MOVE; 279 | 280 | case PERFT_MOVES: 281 | while (picker->current != picker->end) 282 | return (picker->current++)->move; 283 | 284 | picker->phase = -1; 285 | return NULL_MOVE; 286 | default: return NULL_MOVE; 287 | } 288 | } 289 | 290 | char* PhaseName(MovePicker* picker) { 291 | switch (picker->phase) { 292 | case HASH_MOVE: return "HASH_MOVE"; 293 | case PLAY_GOOD_NOISY: return "PLAY_GOOD_NOISY"; 294 | case PLAY_KILLER_1: return "PLAY_KILLER_1"; 295 | case PLAY_KILLER_2: return "PLAY_KILLER_2"; 296 | case PLAY_COUNTER: return "PLAY_COUNTER"; 297 | case PLAY_QUIETS: return "PLAY_QUIETS"; 298 | case PLAY_BAD_NOISY: return "PLAY_BAD_NOISY"; 299 | default: return "UNKNOWN"; 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/movepick.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef MOVEPICK_H 18 | #define MOVEPICK_H 19 | 20 | #include "move.h" 21 | #include "movegen.h" 22 | #include "types.h" 23 | #include "util.h" 24 | 25 | enum { 26 | ST_QUIET, 27 | ST_CAPTURE, 28 | ST_EVASION_CAP, 29 | ST_EVASION_QT, 30 | ST_MVV 31 | }; 32 | 33 | INLINE void InitNormalMovePicker(MovePicker* picker, Move hashMove, ThreadData* thread, SearchStack* ss) { 34 | picker->phase = HASH_MOVE; 35 | 36 | picker->hashMove = hashMove; 37 | picker->killer1 = ss->killers[0]; 38 | picker->killer2 = ss->killers[1]; 39 | if ((ss - 1)->move) 40 | picker->counter = thread->counters[Moving((ss - 1)->move)][To((ss - 1)->move)]; 41 | else 42 | picker->counter = NULL_MOVE; 43 | 44 | picker->thread = thread; 45 | picker->ss = ss; 46 | } 47 | 48 | INLINE void InitPCMovePicker(MovePicker* picker, ThreadData* thread, int threshold) { 49 | picker->phase = PC_GEN_NOISY_MOVES; 50 | picker->thread = thread; 51 | picker->seeCutoff = threshold; 52 | } 53 | 54 | INLINE void InitQSMovePicker(MovePicker* picker, ThreadData* thread, int genChecks) { 55 | picker->phase = QS_GEN_NOISY_MOVES; 56 | picker->thread = thread; 57 | picker->genChecks = genChecks; 58 | } 59 | 60 | INLINE void InitQSEvasionsPicker(MovePicker* picker, Move hashMove, ThreadData* thread, SearchStack* ss) { 61 | picker->phase = QS_EVASION_HASH_MOVE; 62 | 63 | picker->hashMove = hashMove; 64 | 65 | picker->thread = thread; 66 | picker->ss = ss; 67 | } 68 | 69 | INLINE void InitPerftMovePicker(MovePicker* picker, Board* board) { 70 | picker->phase = PERFT_MOVES; 71 | picker->current = picker->moves; 72 | 73 | picker->end = AddPerftMoves(picker->moves, board); 74 | } 75 | 76 | Move NextMove(MovePicker* picker, Board* board, int skipQuiets); 77 | 78 | #endif -------------------------------------------------------------------------------- /src/nn/accumulator.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "accumulator.h" 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "../bits.h" 24 | #include "../board.h" 25 | #include "../move.h" 26 | #include "../movegen.h" 27 | #include "../util.h" 28 | 29 | void ResetRefreshTable(AccumulatorKingState* refreshTable) { 30 | for (size_t b = 0; b < 2 * 2 * N_KING_BUCKETS; b++) { 31 | AccumulatorKingState* state = refreshTable + b; 32 | 33 | memcpy(state->values, INPUT_BIASES, sizeof(acc_t) * N_HIDDEN); 34 | memset(state->pcs, 0, sizeof(BitBoard) * 12); 35 | } 36 | } 37 | 38 | // Refreshes an accumulator using a diff from the last known board state 39 | // with proper king bucketing 40 | void RefreshAccumulator(Accumulator* dest, Board* board, const int perspective) { 41 | Delta delta[1]; 42 | delta->r = delta->a = 0; 43 | 44 | int kingSq = LSB(PieceBB(KING, perspective)); 45 | int pBucket = perspective == WHITE ? 0 : 2 * N_KING_BUCKETS; 46 | int kingBucket = KING_BUCKETS[kingSq ^ (56 * perspective)] + N_KING_BUCKETS * (File(kingSq) > 3); 47 | 48 | AccumulatorKingState* state = &board->refreshTable[pBucket + kingBucket]; 49 | 50 | for (int pc = WHITE_PAWN; pc <= BLACK_KING; pc++) { 51 | BitBoard curr = board->pieces[pc]; 52 | BitBoard prev = state->pcs[pc]; 53 | 54 | BitBoard rem = prev & ~curr; 55 | BitBoard add = curr & ~prev; 56 | 57 | while (rem) { 58 | int sq = PopLSB(&rem); 59 | delta->rem[delta->r++] = FeatureIdx(pc, sq, kingSq, perspective); 60 | } 61 | 62 | while (add) { 63 | int sq = PopLSB(&add); 64 | delta->add[delta->a++] = FeatureIdx(pc, sq, kingSq, perspective); 65 | } 66 | 67 | state->pcs[pc] = curr; 68 | } 69 | 70 | ApplyDelta(state->values, state->values, delta); 71 | 72 | // Copy in state 73 | memcpy(dest->values[perspective], state->values, sizeof(acc_t) * N_HIDDEN); 74 | dest->correct[perspective] = 1; 75 | } 76 | 77 | // Resets an accumulator from pieces on the board 78 | void ResetAccumulator(Accumulator* dest, Board* board, const int perspective) { 79 | Delta delta[1]; 80 | delta->r = delta->a = 0; 81 | 82 | int kingSq = LSB(PieceBB(KING, perspective)); 83 | 84 | BitBoard occ = OccBB(BOTH); 85 | while (occ) { 86 | int sq = PopLSB(&occ); 87 | int pc = board->squares[sq]; 88 | delta->add[delta->a++] = FeatureIdx(pc, sq, kingSq, perspective); 89 | } 90 | 91 | acc_t* values = dest->values[perspective]; 92 | memcpy(values, INPUT_BIASES, sizeof(acc_t) * N_HIDDEN); 93 | ApplyDelta(values, values, delta); 94 | dest->correct[perspective] = 1; 95 | } 96 | 97 | void ApplyUpdates(acc_t* output, acc_t* prev, Board* board, const Move move, const int captured, const int view) { 98 | const int king = LSB(PieceBB(KING, view)); 99 | const int movingSide = Moving(move) & 1; 100 | 101 | int from = FeatureIdx(Moving(move), From(move), king, view); 102 | int to = FeatureIdx(IsPromo(move) ? PromoPiece(move, movingSide) : Moving(move), To(move), king, view); 103 | 104 | if (IsCas(move)) { 105 | int rookFrom = FeatureIdx(Piece(ROOK, movingSide), board->cr[CASTLING_ROOK[To(move)]], king, view); 106 | int rookTo = FeatureIdx(Piece(ROOK, movingSide), CASTLE_ROOK_DEST[To(move)], king, view); 107 | 108 | ApplySubSubAddAdd(output, prev, from, rookFrom, to, rookTo); 109 | } else if (IsCap(move)) { 110 | int capSq = IsEP(move) ? To(move) - PawnDir(movingSide) : To(move); 111 | int capturedTo = FeatureIdx(captured, capSq, king, view); 112 | 113 | ApplySubSubAdd(output, prev, from, capturedTo, to); 114 | } else { 115 | ApplySubAdd(output, prev, from, to); 116 | } 117 | } 118 | 119 | void ApplyLazyUpdates(Accumulator* live, Board* board, const int view) { 120 | Accumulator* curr = live; 121 | while (!(--curr)->correct[view]) 122 | ; // go back to the latest correct accumulator 123 | 124 | do { 125 | ApplyUpdates((curr + 1)->values[view], curr->values[view], board, curr->move, curr->captured, view); 126 | (curr + 1)->correct[view] = 1; 127 | } while (++curr != live); 128 | } 129 | 130 | int CanEfficientlyUpdate(Accumulator* live, const int view) { 131 | Accumulator* curr = live; 132 | 133 | while (1) { 134 | curr--; 135 | 136 | int from = From(curr->move) ^ (56 * view); // invert for black 137 | int to = To(curr->move) ^ (56 * view); // invert for black 138 | int piece = Moving(curr->move); 139 | 140 | if ((piece & 1) == view && MoveRequiresRefresh(piece, from, to)) 141 | return 0; // refresh only necessary for our view 142 | if (curr->correct[view]) 143 | return 1; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/nn/accumulator.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef ACCUMULATOR_H 18 | #define ACCUMULATOR_H 19 | 20 | #include "../board.h" 21 | #include "../types.h" 22 | #include "../util.h" 23 | 24 | #if defined(__AVX512F__) && defined(__AVX512BW__) 25 | #include 26 | #define UNROLL 512 27 | #define NUM_REGS 16 28 | #define regi_t __m512i 29 | #define regi_load _mm512_load_si512 30 | #define regi_sub _mm512_sub_epi16 31 | #define regi_add _mm512_add_epi16 32 | #define regi_store _mm512_store_si512 33 | #elif defined(__AVX2__) 34 | #include 35 | #define UNROLL 256 36 | #define NUM_REGS 16 37 | #define regi_t __m256i 38 | #define regi_load _mm256_load_si256 39 | #define regi_sub _mm256_sub_epi16 40 | #define regi_add _mm256_add_epi16 41 | #define regi_store _mm256_store_si256 42 | #elif defined(__SSE4_1__) 43 | #include 44 | #define UNROLL 128 45 | #define NUM_REGS 16 46 | #define regi_t __m128i 47 | #define regi_load _mm_load_si128 48 | #define regi_sub _mm_sub_epi16 49 | #define regi_add _mm_add_epi16 50 | #define regi_store _mm_store_si128 51 | #elif defined(__ARM_NEON__) 52 | #include 53 | #define UNROLL 128 54 | #define NUM_REGS 16 55 | #define regi_t int16x8_t 56 | #define regi_load(a) vld1q_s16((int16_t*) (a)) 57 | #define regi_sub(a, b) vsubq_s16(a, b) 58 | #define regi_add(a, b) vaddq_s16(a, b) 59 | #define regi_store(a, b) vst1q_s16((int16_t*) (a), b) 60 | #else 61 | #define UNROLL 16 62 | #define NUM_REGS 16 63 | #define regi_t acc_t 64 | #define regi_load(a) (*(a)) 65 | #define regi_sub(a, b) ((a) - (b)) 66 | #define regi_add(a, b) ((a) + (b)) 67 | #define regi_store(a, b) (*(a) = (b)) 68 | #endif 69 | 70 | extern int16_t INPUT_WEIGHTS[N_FEATURES * N_HIDDEN]; 71 | extern int16_t INPUT_BIASES[N_HIDDEN]; 72 | 73 | typedef struct { 74 | uint8_t r, a; 75 | int rem[32]; 76 | int add[32]; 77 | } Delta; 78 | 79 | INLINE void ApplyDelta(acc_t* dest, acc_t* src, Delta* delta) { 80 | regi_t regs[NUM_REGS]; 81 | 82 | for (size_t c = 0; c < N_HIDDEN / UNROLL; ++c) { 83 | const size_t unrollOffset = c * UNROLL; 84 | 85 | const regi_t* inputs = (regi_t*) &src[unrollOffset]; 86 | regi_t* outputs = (regi_t*) &dest[unrollOffset]; 87 | 88 | for (size_t i = 0; i < NUM_REGS; i++) 89 | regs[i] = regi_load(&inputs[i]); 90 | 91 | for (size_t r = 0; r < delta->r; r++) { 92 | const size_t offset = delta->rem[r] * N_HIDDEN + unrollOffset; 93 | const regi_t* weights = (regi_t*) &INPUT_WEIGHTS[offset]; 94 | for (size_t i = 0; i < NUM_REGS; i++) 95 | regs[i] = regi_sub(regs[i], weights[i]); 96 | } 97 | 98 | for (size_t a = 0; a < delta->a; a++) { 99 | const size_t offset = delta->add[a] * N_HIDDEN + unrollOffset; 100 | const regi_t* weights = (regi_t*) &INPUT_WEIGHTS[offset]; 101 | for (size_t i = 0; i < NUM_REGS; i++) 102 | regs[i] = regi_add(regs[i], weights[i]); 103 | } 104 | 105 | for (size_t i = 0; i < NUM_REGS; i++) 106 | regi_store(&outputs[i], regs[i]); 107 | } 108 | } 109 | 110 | INLINE void ApplySubAdd(acc_t* dest, acc_t* src, int f1, int f2) { 111 | regi_t regs[NUM_REGS]; 112 | 113 | for (size_t c = 0; c < N_HIDDEN / UNROLL; ++c) { 114 | const size_t unrollOffset = c * UNROLL; 115 | 116 | const regi_t* inputs = (regi_t*) &src[unrollOffset]; 117 | regi_t* outputs = (regi_t*) &dest[unrollOffset]; 118 | 119 | for (size_t i = 0; i < NUM_REGS; i++) 120 | regs[i] = regi_load(&inputs[i]); 121 | 122 | const size_t o1 = f1 * N_HIDDEN + unrollOffset; 123 | const regi_t* w1 = (regi_t*) &INPUT_WEIGHTS[o1]; 124 | for (size_t i = 0; i < NUM_REGS; i++) 125 | regs[i] = regi_sub(regs[i], w1[i]); 126 | 127 | const size_t o2 = f2 * N_HIDDEN + unrollOffset; 128 | const regi_t* w2 = (regi_t*) &INPUT_WEIGHTS[o2]; 129 | for (size_t i = 0; i < NUM_REGS; i++) 130 | regs[i] = regi_add(regs[i], w2[i]); 131 | 132 | for (size_t i = 0; i < NUM_REGS; i++) 133 | regi_store(&outputs[i], regs[i]); 134 | } 135 | } 136 | 137 | INLINE void ApplySubSubAdd(acc_t* dest, acc_t* src, int f1, int f2, int f3) { 138 | regi_t regs[NUM_REGS]; 139 | 140 | for (size_t c = 0; c < N_HIDDEN / UNROLL; ++c) { 141 | const size_t unrollOffset = c * UNROLL; 142 | 143 | const regi_t* inputs = (regi_t*) &src[unrollOffset]; 144 | regi_t* outputs = (regi_t*) &dest[unrollOffset]; 145 | 146 | for (size_t i = 0; i < NUM_REGS; i++) 147 | regs[i] = regi_load(&inputs[i]); 148 | 149 | const size_t o1 = f1 * N_HIDDEN + unrollOffset; 150 | const regi_t* w1 = (regi_t*) &INPUT_WEIGHTS[o1]; 151 | for (size_t i = 0; i < NUM_REGS; i++) 152 | regs[i] = regi_sub(regs[i], w1[i]); 153 | 154 | const size_t o2 = f2 * N_HIDDEN + unrollOffset; 155 | const regi_t* w2 = (regi_t*) &INPUT_WEIGHTS[o2]; 156 | for (size_t i = 0; i < NUM_REGS; i++) 157 | regs[i] = regi_sub(regs[i], w2[i]); 158 | 159 | const size_t o3 = f3 * N_HIDDEN + unrollOffset; 160 | const regi_t* w3 = (regi_t*) &INPUT_WEIGHTS[o3]; 161 | for (size_t i = 0; i < NUM_REGS; i++) 162 | regs[i] = regi_add(regs[i], w3[i]); 163 | 164 | for (size_t i = 0; i < NUM_REGS; i++) 165 | regi_store(&outputs[i], regs[i]); 166 | } 167 | } 168 | 169 | INLINE void ApplySubSubAddAdd(acc_t* dest, acc_t* src, int f1, int f2, int f3, int f4) { 170 | regi_t regs[NUM_REGS]; 171 | 172 | for (size_t c = 0; c < N_HIDDEN / UNROLL; ++c) { 173 | const size_t unrollOffset = c * UNROLL; 174 | 175 | const regi_t* inputs = (regi_t*) &src[unrollOffset]; 176 | regi_t* outputs = (regi_t*) &dest[unrollOffset]; 177 | 178 | for (size_t i = 0; i < NUM_REGS; i++) 179 | regs[i] = regi_load(&inputs[i]); 180 | 181 | const size_t o1 = f1 * N_HIDDEN + unrollOffset; 182 | const regi_t* w1 = (regi_t*) &INPUT_WEIGHTS[o1]; 183 | for (size_t i = 0; i < NUM_REGS; i++) 184 | regs[i] = regi_sub(regs[i], w1[i]); 185 | 186 | const size_t o2 = f2 * N_HIDDEN + unrollOffset; 187 | const regi_t* w2 = (regi_t*) &INPUT_WEIGHTS[o2]; 188 | for (size_t i = 0; i < NUM_REGS; i++) 189 | regs[i] = regi_sub(regs[i], w2[i]); 190 | 191 | const size_t o3 = f3 * N_HIDDEN + unrollOffset; 192 | const regi_t* w3 = (regi_t*) &INPUT_WEIGHTS[o3]; 193 | for (size_t i = 0; i < NUM_REGS; i++) 194 | regs[i] = regi_add(regs[i], w3[i]); 195 | 196 | const size_t o4 = f4 * N_HIDDEN + unrollOffset; 197 | const regi_t* w4 = (regi_t*) &INPUT_WEIGHTS[o4]; 198 | for (size_t i = 0; i < NUM_REGS; i++) 199 | regs[i] = regi_add(regs[i], w4[i]); 200 | 201 | for (size_t i = 0; i < NUM_REGS; i++) 202 | regi_store(&outputs[i], regs[i]); 203 | } 204 | } 205 | 206 | void ResetRefreshTable(AccumulatorKingState* refreshTable); 207 | void RefreshAccumulator(Accumulator* dest, Board* board, const int perspective); 208 | 209 | void ResetAccumulator(Accumulator* dest, Board* board, const int perspective); 210 | 211 | void ApplyLazyUpdates(Accumulator* live, Board* board, const int view); 212 | int CanEfficientlyUpdate(Accumulator* live, const int view); 213 | 214 | void LoadDefaultNN(); 215 | int LoadNetwork(char* path); 216 | 217 | #endif 218 | -------------------------------------------------------------------------------- /src/nn/evaluate.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef EVALUATE_H 18 | #define EVALUATE_H 19 | 20 | #include "../types.h" 21 | 22 | #define SPARSE_CHUNK_SIZE 4 23 | 24 | int Predict(Board* board); 25 | int Propagate(Accumulator* accumulator, const int stm); 26 | 27 | void LoadDefaultNN(); 28 | int LoadNetwork(char* path); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/perft.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include 18 | #include 19 | 20 | #include "board.h" 21 | #include "move.h" 22 | #include "movegen.h" 23 | #include "movepick.h" 24 | #include "types.h" 25 | #include "util.h" 26 | 27 | uint64_t Perft(int depth, Board* board) { 28 | if (depth == 0) 29 | return 1; 30 | 31 | Move move; 32 | MovePicker mp; 33 | InitPerftMovePicker(&mp, board); 34 | 35 | if (depth == 1) 36 | return mp.end - mp.moves; 37 | 38 | uint64_t nodes = 0; 39 | while ((move = NextMove(&mp, board, 0))) { 40 | MakeMoveUpdate(move, board, 0); 41 | nodes += Perft(depth - 1, board); 42 | UndoMove(move, board); 43 | } 44 | 45 | return nodes; 46 | } 47 | 48 | void PerftTest(int depth, Board* board) { 49 | uint64_t total = 0; 50 | 51 | printf("\nRunning performance test to depth %d\n\n", depth); 52 | 53 | long startTime = GetTimeMS(); 54 | 55 | Move move; 56 | MovePicker mp; 57 | InitPerftMovePicker(&mp, board); 58 | 59 | while ((move = NextMove(&mp, board, 0))) { 60 | MakeMoveUpdate(move, board, 0); 61 | uint64_t nodes = Perft(depth - 1, board); 62 | UndoMove(move, board); 63 | 64 | printf("%5s: %" PRIu64 "\n", MoveToStr(move, board), nodes); 65 | total += nodes; 66 | } 67 | 68 | long endTime = GetTimeMS(); 69 | 70 | printf("\nNodes: %" PRIu64 "\n", total); 71 | printf("Time: %ldms\n", (endTime - startTime)); 72 | printf("NPS: %" PRIu64 "\n\n", total / Max(1, (endTime - startTime)) * 1000); 73 | } -------------------------------------------------------------------------------- /src/perft.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef PERFT_H 18 | #define PERFT_H 19 | 20 | #include "types.h" 21 | 22 | int Perft(int depth, Board* board); 23 | void PerftTest(int depth, Board* board); 24 | 25 | #endif -------------------------------------------------------------------------------- /src/pyrrhic/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | (c) 2015 basil, all rights reserved, 4 | Modifications Copyright (c) 2016-2019 by Jon Dart 5 | Modifications Copyright (c) 2020-2020 by Andrew Grant 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | -------------------------------------------------------------------------------- /src/pyrrhic/stdendian.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* requires C11 or C++11 */ 4 | #if defined(__cplusplus) 5 | #include 6 | #elif !defined(__OPENCL_VERSION__) 7 | #include 8 | #endif 9 | 10 | /* Linux / GLIBC */ 11 | #if defined(__linux__) || defined(__GLIBC__) || defined(__CYGWIN__) 12 | #include 13 | #include 14 | #define __ENDIAN_DEFINED 1 15 | #define __BSWAP_DEFINED 1 16 | #define __HOSTSWAP_DEFINED 1 17 | // NDK defines _BYTE_ORDER etc 18 | #ifndef _BYTE_ORDER 19 | #define _BYTE_ORDER __BYTE_ORDER 20 | #define _LITTLE_ENDIAN __LITTLE_ENDIAN 21 | #define _BIG_ENDIAN __BIG_ENDIAN 22 | #endif 23 | #define bswap16(x) bswap_16(x) 24 | #define bswap32(x) bswap_32(x) 25 | #define bswap64(x) bswap_64(x) 26 | #endif /* __linux__ || __GLIBC__ */ 27 | 28 | /* BSD */ 29 | #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) 30 | #include 31 | #define __ENDIAN_DEFINED 1 32 | #define __BSWAP_DEFINED 1 33 | #define __HOSTSWAP_DEFINED 1 34 | #endif /* BSD */ 35 | 36 | /* Solaris */ 37 | #if defined(sun) 38 | #include 39 | /* sun headers don't set a value for _LITTLE_ENDIAN or _BIG_ENDIAN */ 40 | #if defined(_LITTLE_ENDIAN) 41 | #undef _LITTLE_ENDIAN 42 | #define _LITTLE_ENDIAN 1234 43 | #define _BIG_ENDIAN 4321 44 | #define _BYTE_ORDER _LITTLE_ENDIAN 45 | #elif defined(_BIG_ENDIAN) 46 | #undef _BIG_ENDIAN 47 | #define _LITTLE_ENDIAN 1234 48 | #define _BIG_ENDIAN 4321 49 | #define _BYTE_ORDER _BIG_ENDIAN 50 | #endif 51 | #define __ENDIAN_DEFINED 1 52 | #endif /* sun */ 53 | 54 | /* Windows */ 55 | #if defined(_WIN32) || defined(_MSC_VER) 56 | /* assumes all Microsoft targets are little endian */ 57 | #define _LITTLE_ENDIAN 1234 58 | #define _BIG_ENDIAN 4321 59 | #define _BYTE_ORDER _LITTLE_ENDIAN 60 | #define __ENDIAN_DEFINED 1 61 | #endif /* _MSC_VER */ 62 | 63 | /* OS X */ 64 | #if defined(__APPLE__) 65 | #include 66 | #define _BYTE_ORDER BYTE_ORDER 67 | #define _LITTLE_ENDIAN LITTLE_ENDIAN 68 | #define _BIG_ENDIAN BIG_ENDIAN 69 | #define __ENDIAN_DEFINED 1 70 | #endif /* __APPLE__ */ 71 | 72 | /* OpenCL */ 73 | #if defined(__OPENCL_VERSION__) 74 | #define _LITTLE_ENDIAN 1234 75 | #define __BIG_ENDIAN 4321 76 | #if defined(__ENDIAN_LITTLE__) 77 | #define _BYTE_ORDER _LITTLE_ENDIAN 78 | #else 79 | #define _BYTE_ORDER _BIG_ENDIAN 80 | #endif 81 | #define bswap16(x) as_ushort(as_uchar2(ushort(x)).s1s0) 82 | #define bswap32(x) as_uint(as_uchar4(uint(x)).s3s2s1s0) 83 | #define bswap64(x) as_ulong(as_uchar8(ulong(x)).s7s6s5s4s3s2s1s0) 84 | #define __ENDIAN_DEFINED 1 85 | #define __BSWAP_DEFINED 1 86 | #endif 87 | 88 | /* Unknown */ 89 | #if !__ENDIAN_DEFINED 90 | #error Could not determine CPU byte order 91 | #endif 92 | 93 | /* POSIX - http://austingroupbugs.net/view.php?id=162 */ 94 | #ifndef BYTE_ORDER 95 | #define BYTE_ORDER _BYTE_ORDER 96 | #endif 97 | #ifndef LITTLE_ENDIAN 98 | #define LITTLE_ENDIAN _LITTLE_ENDIAN 99 | #endif 100 | #ifndef BIG_ENDIAN 101 | #define BIG_ENDIAN _BIG_ENDIAN 102 | #endif 103 | 104 | /* OpenCL compatibility - define __ENDIAN_LITTLE__ on little endian systems */ 105 | #if _BYTE_ORDER == _LITTLE_ENDIAN 106 | #if !defined(__ENDIAN_LITTLE__) 107 | #define __ENDIAN_LITTLE__ 1 108 | #endif 109 | #endif 110 | 111 | /* Byte swap macros */ 112 | #if !__BSWAP_DEFINED 113 | 114 | #ifndef bswap16 115 | /* handle missing __builtin_bswap16 116 | * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52624 */ 117 | #if defined __GNUC__ 118 | #define bswap16(x) __builtin_bswap16(x) 119 | #else 120 | inline uint16_t bswap16(uint16_t x) { 121 | return (uint16_t) ((((uint16_t) (x) &0xff00) >> 8) | (((uint16_t) (x) &0x00ff) << 8)); 122 | } 123 | #endif 124 | #endif 125 | 126 | #ifndef bswap32 127 | #if defined __GNUC__ 128 | #define bswap32(x) __builtin_bswap32(x) 129 | #else 130 | inline uint32_t bswap32(uint32_t x) { 131 | return ((x & 0xff000000) >> 24) | ((x & 0x00ff0000) >> 8) | ((x & 0x0000ff00) << 8) | ((x & 0x000000ff) << 24); 132 | } 133 | #endif 134 | #endif 135 | 136 | #ifndef bswap64 137 | #if defined __GNUC__ 138 | #define bswap64(x) __builtin_bswap64(x) 139 | #else 140 | inline uint64_t bswap64(uint64_t x) { 141 | return ((x & 0xff00000000000000ull) >> 56) | ((x & 0x00ff000000000000ull) >> 40) | 142 | ((x & 0x0000ff0000000000ull) >> 24) | ((x & 0x000000ff00000000ull) >> 8) | ((x & 0x00000000ff000000ull) << 8) | 143 | ((x & 0x0000000000ff0000ull) << 24) | ((x & 0x000000000000ff00ull) << 40) | 144 | ((x & 0x00000000000000ffull) << 56); 145 | } 146 | #endif 147 | #endif 148 | 149 | #endif 150 | 151 | /* Host swap macros */ 152 | #ifndef __HOSTSWAP_DEFINED 153 | #if __BYTE_ORDER == __LITTLE_ENDIAN 154 | #define htobe16(x) bswap16((x)) 155 | #define htole16(x) ((uint16_t) (x)) 156 | #define be16toh(x) bswap16((x)) 157 | #define le16toh(x) ((uint16_t) (x)) 158 | 159 | #define htobe32(x) bswap32((x)) 160 | #define htole32(x) ((uint32_t((x)) 161 | #define be32toh(x) bswap32((x)) 162 | #define le32toh(x) ((uint32_t) (x)) 163 | 164 | #define htobe64(x) bswap64((x)) 165 | #define htole64(x) ((uint64_t) (x)) 166 | #define be64toh(x) bswap64((x)) 167 | #define le64toh(x) ((uint64_t) (x)) 168 | #elif __BYTE_ORDER == __BIG_ENDIAN 169 | #define htobe16(x) ((uint16_t) (x)) 170 | #define htole16(x) bswap16((x)) 171 | #define be16toh(x) ((uint16_t) (x)) 172 | #define le16toh(x) bswap16((x)) 173 | 174 | #define htobe32(x) ((uint32_t) (x)) 175 | #define htole32(x) bswap32((x)) 176 | #define be32toh(x) ((uint32_t) (x)) 177 | #define le64toh(x) bswap64((x)) 178 | 179 | #define htobe64(x) ((uint64_t) (x)) 180 | #define htole64(x) bswap64((x)) 181 | #define be64toh(x) ((uint64_t) (x)) 182 | #define le32toh(x) bswap32((x)) 183 | #endif 184 | #endif -------------------------------------------------------------------------------- /src/pyrrhic/tbconfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) 2015 basil, all rights reserved, 3 | * Modifications Copyright (c) 2016-2019 by Jon Dart 4 | * Modifications Copyright (c) 2020-2020 by Andrew Grant 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | /* 28 | * You are in charge of defining each of these macros. The macros already 29 | * defined here are simply an example of what to do. This configuration is 30 | * used by Ethereal to implement Pyrrhic. 31 | * 32 | * See Ethereal's source if it is 33 | * not readily clear what these definfitions mean. The relevant files are 34 | * are the ones included below. 35 | * 36 | * Note that for the Pawn Attacks, we invert the colour. This is because 37 | * Pyrrhic defines White as 1, where as Ethereal (any many others) choose 38 | * to define White as 0 and Black as 1. 39 | */ 40 | 41 | #include "../attacks.h" 42 | #include "../bits.h" 43 | #include "../search.h" 44 | #include "../types.h" 45 | 46 | #define PYRRHIC_POPCOUNT(x) (BitCount(x)) 47 | #define PYRRHIC_LSB(x) (LSB(x)) 48 | #define PYRRHIC_POPLSB(x) (PopLSB(x)) 49 | 50 | #define PYRRHIC_PAWN_ATTACKS(sq, c) (GetPawnAttacks(sq, c)) 51 | #define PYRRHIC_KNIGHT_ATTACKS(sq) (GetKnightAttacks(sq)) 52 | #define PYRRHIC_BISHOP_ATTACKS(sq, occ) (GetBishopAttacks(sq, occ)) 53 | #define PYRRHIC_ROOK_ATTACKS(sq, occ) (GetRookAttacks(sq, occ)) 54 | #define PYRRHIC_QUEEN_ATTACKS(sq, occ) (GetQueenAttacks(sq, occ)) 55 | #define PYRRHIC_KING_ATTACKS(sq) (GetKingAttacks(sq)) 56 | 57 | /* 58 | * Pyrrhic can produce scores for tablebase moves. These depend on the value 59 | * of a pawn, and the magnitude of mate scores, and will be engine specific. 60 | * 61 | * In Ethereal, I personally do not make use of these scores. They are to rank 62 | * moves. Without these values you are still able to detmine which moves Win, 63 | * Draw, and Lose. PYRRHIC_MAX_MATE_PLY should be your max search height. 64 | */ 65 | #define PYRRHIC_VALUE_PAWN (100) 66 | #define PYRRHIC_VALUE_MATE (CHECKMATE) 67 | #define PYRRHIC_VALUE_DRAW (0) 68 | #define PYRRHIC_MAX_MATE_PLY (MAX_SEARCH_PLY) 69 | -------------------------------------------------------------------------------- /src/pyrrhic/tbprobe.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2020 Ronald de Man 3 | * Copyright (c) 2015 Basil, all rights reserved, 4 | * Modifications Copyright (c) 2016-2019 by Jon Dart 5 | * Modifications Copyright (c) 2020-2020 by Andrew Grant 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #ifndef TBPROBE_H 27 | #define TBPROBE_H 28 | 29 | #include 30 | #include 31 | 32 | #include "tbconfig.h" 33 | 34 | /****************************************************************************/ 35 | /* MAIN API */ 36 | /****************************************************************************/ 37 | 38 | #define TB_MAX_MOVES 256 39 | #define TB_MAX_CAPTURES 64 40 | #define TB_MAX_PLY 256 41 | 42 | #define TB_LOSS 0 /* LOSS */ 43 | #define TB_BLESSED_LOSS 1 /* LOSS but 50-move draw */ 44 | #define TB_DRAW 2 /* DRAW */ 45 | #define TB_CURSED_WIN 3 /* WIN but 50-move draw */ 46 | #define TB_WIN 4 /* WIN */ 47 | 48 | #define TB_RESULT_WDL_MASK 0x0000000F 49 | #define TB_RESULT_TO_MASK 0x000003F0 50 | #define TB_RESULT_FROM_MASK 0x0000FC00 51 | #define TB_RESULT_PROMOTES_MASK 0x00070000 52 | #define TB_RESULT_EP_MASK 0x00080000 53 | #define TB_RESULT_DTZ_MASK 0xFFF00000 54 | #define TB_RESULT_WDL_SHIFT 0 55 | #define TB_RESULT_TO_SHIFT 4 56 | #define TB_RESULT_FROM_SHIFT 10 57 | #define TB_RESULT_PROMOTES_SHIFT 16 58 | #define TB_RESULT_EP_SHIFT 19 59 | #define TB_RESULT_DTZ_SHIFT 20 60 | 61 | #define TB_GET_WDL(_res) (((_res) &TB_RESULT_WDL_MASK) >> TB_RESULT_WDL_SHIFT) 62 | #define TB_GET_TO(_res) (((_res) &TB_RESULT_TO_MASK) >> TB_RESULT_TO_SHIFT) 63 | #define TB_GET_FROM(_res) (((_res) &TB_RESULT_FROM_MASK) >> TB_RESULT_FROM_SHIFT) 64 | #define TB_GET_PROMOTES(_res) (((_res) &TB_RESULT_PROMOTES_MASK) >> TB_RESULT_PROMOTES_SHIFT) 65 | #define TB_GET_EP(_res) (((_res) &TB_RESULT_EP_MASK) >> TB_RESULT_EP_SHIFT) 66 | #define TB_GET_DTZ(_res) (((_res) &TB_RESULT_DTZ_MASK) >> TB_RESULT_DTZ_SHIFT) 67 | 68 | #define TB_SET_WDL(_res, _wdl) (((_res) & ~TB_RESULT_WDL_MASK) | (((_wdl) << TB_RESULT_WDL_SHIFT) & TB_RESULT_WDL_MASK)) 69 | #define TB_SET_TO(_res, _to) (((_res) & ~TB_RESULT_TO_MASK) | (((_to) << TB_RESULT_TO_SHIFT) & TB_RESULT_TO_MASK)) 70 | #define TB_SET_FROM(_res, _from) \ 71 | (((_res) & ~TB_RESULT_FROM_MASK) | (((_from) << TB_RESULT_FROM_SHIFT) & TB_RESULT_FROM_MASK)) 72 | #define TB_SET_PROMOTES(_res, _promotes) \ 73 | (((_res) & ~TB_RESULT_PROMOTES_MASK) | (((_promotes) << TB_RESULT_PROMOTES_SHIFT) & TB_RESULT_PROMOTES_MASK)) 74 | #define TB_SET_EP(_res, _ep) (((_res) & ~TB_RESULT_EP_MASK) | (((_ep) << TB_RESULT_EP_SHIFT) & TB_RESULT_EP_MASK)) 75 | #define TB_SET_DTZ(_res, _dtz) (((_res) & ~TB_RESULT_DTZ_MASK) | (((_dtz) << TB_RESULT_DTZ_SHIFT) & TB_RESULT_DTZ_MASK)) 76 | 77 | #define TB_RESULT_CHECKMATE TB_SET_WDL(0, TB_WIN) 78 | #define TB_RESULT_STALEMATE TB_SET_WDL(0, TB_DRAW) 79 | #define TB_RESULT_FAILED 0xFFFFFFFF 80 | 81 | /* 82 | * The tablebase can be probed for any position where #pieces <= TB_LARGEST. 83 | */ 84 | extern int TB_LARGEST; 85 | 86 | /* 87 | * Initialize the tablebase. 88 | * 89 | * PARAMETERS: 90 | * - path: 91 | * The tablebase PATH string. 92 | * 93 | * RETURN: 94 | * - true=succes, false=failed. The TB_LARGEST global will also be 95 | * initialized. If no tablebase files are found, then `true' is returned 96 | * and TB_LARGEST is set to zero. 97 | */ 98 | bool tb_init(const char *_path); 99 | 100 | /* 101 | * Free any resources allocated by tb_init 102 | */ 103 | void tb_free(void); 104 | 105 | /* 106 | * Probe the Win-Draw-Loss (WDL) table. 107 | * 108 | * PARAMETERS: 109 | * - white, black, kings, queens, rooks, bishops, knights, pawns: 110 | * The current position (bitboards). 111 | * - ep: 112 | * The en passant square (if exists). Set to zero if there is no en passant 113 | * square. 114 | * - turn: 115 | * true=white, false=black 116 | * 117 | * RETURN: 118 | * - One of {TB_LOSS, TB_BLESSED_LOSS, TB_DRAW, TB_CURSED_WIN, TB_WIN}. 119 | * Otherwise returns TB_RESULT_FAILED if the probe failed. 120 | * 121 | * NOTES: 122 | * - Engines should use this function during search. 123 | * - This function is thread safe assuming TB_NO_THREADS is disabled. 124 | */ 125 | unsigned tb_probe_wdl(uint64_t white, 126 | uint64_t black, 127 | uint64_t kings, 128 | uint64_t queens, 129 | uint64_t rooks, 130 | uint64_t bishops, 131 | uint64_t knights, 132 | uint64_t pawns, 133 | unsigned ep, 134 | bool turn); 135 | 136 | /* 137 | * Probe the Distance-To-Zero (DTZ) table. 138 | * 139 | * PARAMETERS: 140 | * - white, black, kings, queens, rooks, bishops, knights, pawns: 141 | * The current position (bitboards). 142 | * - rule50: 143 | * The 50-move half-move clock. 144 | * - castling: 145 | * Castling rights. Set to zero if no castling is possible. 146 | * - ep: 147 | * The en passant square (if exists). Set to zero if there is no en passant 148 | * square. 149 | * - turn: 150 | * true=white, false=black 151 | * - results (OPTIONAL): 152 | * Alternative results, one for each possible legal move. The passed array 153 | * must be TB_MAX_MOVES in size. 154 | * If alternative results are not desired then set results=NULL. 155 | * 156 | * RETURN: 157 | * - A TB_RESULT value comprising: 158 | * 1) The WDL value (TB_GET_WDL) 159 | * 2) The suggested move (TB_GET_FROM, TB_GET_TO, TB_GET_PROMOTES, TB_GET_EP) 160 | * 3) The DTZ value (TB_GET_DTZ) 161 | * The suggested move is guaranteed to preserved the WDL value. 162 | * 163 | * Otherwise: 164 | * 1) TB_RESULT_STALEMATE is returned if the position is in stalemate. 165 | * 2) TB_RESULT_CHECKMATE is returned if the position is in checkmate. 166 | * 3) TB_RESULT_FAILED is returned if the probe failed. 167 | * 168 | * If results!=NULL, then a TB_RESULT for each legal move will be generated 169 | * and stored in the results array. The results array will be terminated 170 | * by TB_RESULT_FAILED. 171 | * 172 | * NOTES: 173 | * - Engines can use this function to probe at the root. This function should 174 | * not be used during search. 175 | * - DTZ tablebases can suggest unnatural moves, especially for losing 176 | * positions. Engines may prefer to traditional search combined with WDL 177 | * move filtering using the alternative results array. 178 | * - This function is NOT thread safe. For engines this function should only 179 | * be called once at the root per search. 180 | */ 181 | unsigned tb_probe_root(uint64_t white, 182 | uint64_t black, 183 | uint64_t kings, 184 | uint64_t queens, 185 | uint64_t rooks, 186 | uint64_t bishops, 187 | uint64_t knights, 188 | uint64_t pawns, 189 | unsigned rule50, 190 | unsigned ep, 191 | bool turn, 192 | unsigned *results); 193 | 194 | typedef uint16_t PyrrhicMove; 195 | 196 | struct TbRootMove { 197 | PyrrhicMove move; 198 | PyrrhicMove pv[TB_MAX_PLY]; 199 | unsigned pvSize; 200 | int32_t tbScore, tbRank; 201 | }; 202 | 203 | struct TbRootMoves { 204 | unsigned size; 205 | struct TbRootMove moves[TB_MAX_MOVES]; 206 | }; 207 | 208 | /* 209 | * Use the DTZ tables to rank and score all root moves. 210 | * INPUT: as for tb_probe_root 211 | * OUTPUT: TbRootMoves structure is filled in. This contains 212 | * an array of TbRootMove structures. 213 | * Each structure instance contains a rank, a score, and a 214 | * predicted principal variation. 215 | * RETURN VALUE: 216 | * non-zero if ok, 0 means not all probes were successful 217 | * 218 | */ 219 | int tb_probe_root_dtz(uint64_t white, 220 | uint64_t black, 221 | uint64_t kings, 222 | uint64_t queens, 223 | uint64_t rooks, 224 | uint64_t bishops, 225 | uint64_t knights, 226 | uint64_t pawns, 227 | unsigned rule50, 228 | unsigned ep, 229 | bool turn, 230 | bool hasRepeated, 231 | bool useRule50, 232 | struct TbRootMoves *results); 233 | 234 | /* 235 | // Use the WDL tables to rank and score all root moves. 236 | // This is a fallback for the case that some or all DTZ tables are missing. 237 | * INPUT: as for tb_probe_root 238 | * OUTPUT: TbRootMoves structure is filled in. This contains 239 | * an array of TbRootMove structures. 240 | * Each structure instance contains a rank, a score, and a 241 | * predicted principal variation. 242 | * RETURN VALUE: 243 | * non-zero if ok, 0 means not all probes were successful 244 | * 245 | */ 246 | int tb_probe_root_wdl(uint64_t white, 247 | uint64_t black, 248 | uint64_t kings, 249 | uint64_t queens, 250 | uint64_t rooks, 251 | uint64_t bishops, 252 | uint64_t knights, 253 | uint64_t pawns, 254 | unsigned rule50, 255 | unsigned ep, 256 | bool turn, 257 | bool useRule50, 258 | struct TbRootMoves *_results); 259 | 260 | #endif 261 | -------------------------------------------------------------------------------- /src/random.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | #include "random.h" 17 | 18 | #include 19 | 20 | // I dunno anything about random number generators, and had a bad one for a 21 | // while Thanks to Martin Sedlák (author of Cheng) this one is really cool and 22 | // works :) http://www.vlasak.biz/cheng/ 23 | 24 | uint64_t keys[2]; 25 | 26 | inline uint64_t rotate(uint64_t v, uint8_t s) { 27 | return (v >> s) | (v << (64 - s)); 28 | } 29 | 30 | inline uint64_t RandomUInt64() { 31 | uint64_t tmp = keys[0]; 32 | keys[0] += rotate(keys[1] ^ 0xc5462216u ^ ((uint64_t) 0xcf14f4ebu << 32), 1); 33 | return keys[1] += rotate(tmp ^ 0x75ecfc58u ^ ((uint64_t) 0x9576080cu << 32), 9); 34 | } 35 | 36 | void SeedRandom(uint64_t seed) { 37 | keys[0] = keys[1] = seed; 38 | 39 | for (int i = 0; i < 64; i++) 40 | RandomUInt64(); 41 | } 42 | 43 | // Magic's are combined to try and get one with few bits 44 | uint64_t RandomMagic() { 45 | return RandomUInt64() & RandomUInt64() & RandomUInt64(); 46 | } -------------------------------------------------------------------------------- /src/random.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef RANDOM_H 18 | #define RANDOM_H 19 | 20 | #include 21 | 22 | uint64_t rotate(uint64_t v, uint8_t s); 23 | uint64_t RandomUInt64(); 24 | void SeedRandom(uint64_t seed); 25 | uint64_t RandomMagic(); 26 | 27 | #endif -------------------------------------------------------------------------------- /src/search.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef SEARCH_H 18 | #define SEARCH_H 19 | 20 | #include "types.h" 21 | 22 | // search specific score evals 23 | #define UNKNOWN \ 24 | 32257 // this must be higher than CHECKMATE (some conditional logic relies on 25 | // this) 26 | #define CHECKMATE 32200 27 | #define MATE_BOUND (32200 - MAX_SEARCH_PLY) 28 | #define TB_WIN_SCORE MATE_BOUND 29 | #define TB_WIN_BOUND (TB_WIN_SCORE - MAX_SEARCH_PLY) 30 | 31 | void InitPruningAndReductionTables(); 32 | 33 | void StartSearch(Board* board, uint8_t ponder); 34 | void MainSearch(); 35 | void Search(ThreadData* thread); 36 | int Negamax(int alpha, int beta, int depth, int cutnode, ThreadData* thread, PV* pv, SearchStack* ss); 37 | int Quiesce(int alpha, int beta, int depth, ThreadData* thread, SearchStack* ss); 38 | 39 | void PrintUCI(ThreadData* thread, int alpha, int beta, Board* board); 40 | void PrintPV(PV* pv, Board* board); 41 | 42 | void SortRootMoves(ThreadData* thread, int offset); 43 | int ValidRootMove(ThreadData* thread, Move move); 44 | 45 | void SearchClearThread(ThreadData* thread); 46 | void SearchClear(); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/see.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "see.h" 18 | 19 | #include "attacks.h" 20 | #include "bits.h" 21 | #include "board.h" 22 | #include "move.h" 23 | #include "movegen.h" 24 | #include "types.h" 25 | #include "util.h" 26 | 27 | const int SEE_VALUE[7] = {86, 326, 394, 593, 1047, 30000, 0}; 28 | 29 | // Static exchange evaluation using The Swap Algorithm - 30 | // https://www.chessprogramming.org/SEE_-_The_Swap_Algorithm 31 | inline int SEE(Board* board, Move move, int threshold) { 32 | if (IsCas(move) || IsEP(move) || IsPromo(move)) 33 | return 1; 34 | 35 | int from = From(move); 36 | int to = To(move); 37 | 38 | int v = SEE_VALUE[PieceType(board->squares[to])] - threshold; 39 | if (v < 0) 40 | return 0; 41 | 42 | v = SEE_VALUE[PieceType(Moving(move))] - v; 43 | if (v <= 0) 44 | return 1; 45 | 46 | int stm = board->stm; 47 | BitBoard occ = OccBB(BOTH) ^ Bit(from) ^ Bit(to); 48 | BitBoard attackers = AttacksToSquare(board, to, occ); 49 | BitBoard mine, leastAttacker; 50 | 51 | const BitBoard diag = PieceBB(BISHOP, WHITE) | PieceBB(BISHOP, BLACK) | PieceBB(QUEEN, WHITE) | PieceBB(QUEEN, BLACK); 52 | const BitBoard straight = PieceBB(ROOK, WHITE) | PieceBB(ROOK, BLACK) | PieceBB(QUEEN, WHITE) | PieceBB(QUEEN, BLACK); 53 | 54 | int result = 1; 55 | 56 | while (1) { 57 | stm ^= 1; 58 | attackers &= occ; 59 | 60 | if (!(mine = (attackers & OccBB(stm)))) 61 | break; 62 | 63 | result ^= 1; 64 | 65 | if ((leastAttacker = mine & PieceBB(PAWN, stm))) { 66 | if ((v = SEE_VALUE[PAWN] - v) < result) 67 | break; 68 | 69 | occ ^= (leastAttacker & -leastAttacker); 70 | attackers |= GetBishopAttacks(to, occ) & diag; 71 | } else if ((leastAttacker = mine & PieceBB(KNIGHT, stm))) { 72 | if ((v = SEE_VALUE[KNIGHT] - v) < result) 73 | break; 74 | 75 | occ ^= (leastAttacker & -leastAttacker); 76 | } else if ((leastAttacker = mine & PieceBB(BISHOP, stm))) { 77 | if ((v = SEE_VALUE[BISHOP] - v) < result) 78 | break; 79 | 80 | occ ^= (leastAttacker & -leastAttacker); 81 | attackers |= GetBishopAttacks(to, occ) & diag; 82 | } else if ((leastAttacker = mine & PieceBB(ROOK, stm))) { 83 | if ((v = SEE_VALUE[ROOK] - v) < result) 84 | break; 85 | 86 | occ ^= (leastAttacker & -leastAttacker); 87 | attackers |= GetRookAttacks(to, occ) & straight; 88 | } else if ((leastAttacker = mine & PieceBB(QUEEN, stm))) { 89 | if ((v = SEE_VALUE[QUEEN] - v) < result) 90 | break; 91 | 92 | occ ^= (leastAttacker & -leastAttacker); 93 | attackers |= (GetBishopAttacks(to, occ) & diag) | (GetRookAttacks(to, occ) & straight); 94 | } else 95 | return (attackers & ~OccBB(stm)) ? result ^ 1 : result; 96 | } 97 | 98 | return result; 99 | } 100 | -------------------------------------------------------------------------------- /src/see.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef SEE_H 18 | #define SEE_H 19 | 20 | #include "types.h" 21 | 22 | extern const int SEE_VALUE[7]; 23 | 24 | int SEE(Board* board, Move move, int threshold); 25 | 26 | #endif -------------------------------------------------------------------------------- /src/tb.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "tb.h" 18 | 19 | #include 20 | #include 21 | 22 | #include "board.h" 23 | #include "move.h" 24 | #include "movegen.h" 25 | #include "pyrrhic/tbprobe.h" 26 | 27 | #define ByteSwap(bb) __builtin_bswap64((bb)) 28 | 29 | void TBRootMoves(SimpleMoveList* moves, Board* board) { 30 | moves->count = 0; 31 | 32 | unsigned results[MAX_MOVES]; 33 | unsigned result = TBRootProbe(board, results); 34 | 35 | if (result == TB_RESULT_FAILED || result == TB_RESULT_CHECKMATE || result == TB_RESULT_STALEMATE) 36 | return; 37 | 38 | const unsigned wdl = TB_GET_WDL(result); 39 | 40 | for (int i = 0; i < MAX_MOVES && results[i] != TB_RESULT_FAILED; i++) 41 | if (wdl == TB_GET_WDL(results[i])) 42 | moves->moves[moves->count++] = TBMoveFromResult(results[i], board); 43 | } 44 | 45 | unsigned TBRootProbe(Board* board, unsigned* results) { 46 | if (board->castling || BitCount(OccBB(BOTH)) > TB_LARGEST) 47 | return TB_RESULT_FAILED; 48 | 49 | return tb_probe_root(ByteSwap(OccBB(WHITE)), 50 | ByteSwap(OccBB(BLACK)), 51 | ByteSwap(PieceBB(KING, WHITE) | PieceBB(KING, BLACK)), 52 | ByteSwap(PieceBB(QUEEN, WHITE) | PieceBB(QUEEN, BLACK)), 53 | ByteSwap(PieceBB(ROOK, WHITE) | PieceBB(ROOK, BLACK)), 54 | ByteSwap(PieceBB(BISHOP, WHITE) | PieceBB(BISHOP, BLACK)), 55 | ByteSwap(PieceBB(KNIGHT, WHITE) | PieceBB(KNIGHT, BLACK)), 56 | ByteSwap(PieceBB(PAWN, WHITE) | PieceBB(PAWN, BLACK)), 57 | board->fmr, 58 | board->epSquare ? (board->epSquare ^ 56) : 0, 59 | board->stm == WHITE ? 1 : 0, 60 | results); 61 | } 62 | 63 | unsigned TBProbe(Board* board) { 64 | if (board->castling || board->fmr || BitCount(OccBB(BOTH)) > TB_LARGEST) 65 | return TB_RESULT_FAILED; 66 | 67 | return tb_probe_wdl(ByteSwap(OccBB(WHITE)), 68 | ByteSwap(OccBB(BLACK)), 69 | ByteSwap(PieceBB(KING, WHITE) | PieceBB(KING, BLACK)), 70 | ByteSwap(PieceBB(QUEEN, WHITE) | PieceBB(QUEEN, BLACK)), 71 | ByteSwap(PieceBB(ROOK, WHITE) | PieceBB(ROOK, BLACK)), 72 | ByteSwap(PieceBB(BISHOP, WHITE) | PieceBB(BISHOP, BLACK)), 73 | ByteSwap(PieceBB(KNIGHT, WHITE) | PieceBB(KNIGHT, BLACK)), 74 | ByteSwap(PieceBB(PAWN, WHITE) | PieceBB(PAWN, BLACK)), 75 | board->epSquare ? (board->epSquare ^ 56) : 0, 76 | board->stm == WHITE ? 1 : 0); 77 | } -------------------------------------------------------------------------------- /src/tb.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef TB_H 18 | #define TB_H 19 | 20 | #include "move.h" 21 | #include "movegen.h" 22 | #include "pyrrhic/tbprobe.h" 23 | #include "types.h" 24 | #include "util.h" 25 | 26 | INLINE Move TBMoveFromResult(unsigned res, Board* board) { 27 | unsigned from = TB_GET_FROM(res) ^ 56; 28 | unsigned to = TB_GET_TO(res) ^ 56; 29 | unsigned ep = TB_GET_EP(res); 30 | unsigned promo = TB_GET_PROMOTES(res); 31 | int piece = board->squares[from]; 32 | int capture = board->squares[to] != NO_PIECE; 33 | 34 | int flags = QUIET_FLAG; 35 | if (ep) 36 | flags = EP_FLAG; 37 | else if (capture) 38 | flags = CAPTURE_FLAG; 39 | 40 | if (promo) { 41 | switch (KING - promo) { 42 | case KNIGHT: flags |= KNIGHT_PROMO_FLAG; break; 43 | case BISHOP: flags |= BISHOP_PROMO_FLAG; break; 44 | case ROOK: flags |= ROOK_PROMO_FLAG; break; 45 | case QUEEN: flags |= QUEEN_PROMO_FLAG; break; 46 | } 47 | } 48 | 49 | return BuildMove(from, to, piece, flags); 50 | } 51 | 52 | void TBRootMoves(SimpleMoveList* moves, Board* board); 53 | unsigned TBRootProbe(Board* board, unsigned* results); 54 | unsigned TBProbe(Board* board); 55 | 56 | #endif -------------------------------------------------------------------------------- /src/thread.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "thread.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "eval.h" 26 | #include "nn/accumulator.h" 27 | #include "search.h" 28 | #include "tb.h" 29 | #include "transposition.h" 30 | #include "types.h" 31 | #include "uci.h" 32 | #include "util.h" 33 | 34 | // Below is an implementation of CFish's Sleeping Threads 35 | // This is implemented entirely with pThreads 36 | ThreadPool Threads; 37 | 38 | // Block until requested thread is sleeping 39 | void ThreadWaitUntilSleep(ThreadData* thread) { 40 | pthread_mutex_lock(&thread->mutex); 41 | 42 | while (thread->action != THREAD_SLEEP) 43 | pthread_cond_wait(&thread->sleep, &thread->mutex); 44 | 45 | pthread_mutex_unlock(&thread->mutex); 46 | 47 | if (thread->idx == 0) 48 | Threads.searching = 0; 49 | } 50 | 51 | // Block thread until on condition 52 | void ThreadWait(ThreadData* thread, atomic_uchar* cond) { 53 | pthread_mutex_lock(&thread->mutex); 54 | 55 | while (!atomic_load(cond)) 56 | pthread_cond_wait(&thread->sleep, &thread->mutex); 57 | 58 | pthread_mutex_unlock(&thread->mutex); 59 | } 60 | 61 | // Wake a thread up with an action 62 | void ThreadWake(ThreadData* thread, int action) { 63 | pthread_mutex_lock(&thread->mutex); 64 | 65 | if (action != THREAD_RESUME) 66 | thread->action = action; 67 | 68 | pthread_cond_signal(&thread->sleep); 69 | pthread_mutex_unlock(&thread->mutex); 70 | } 71 | 72 | // Idle loop that wakes into an action 73 | void ThreadIdle(ThreadData* thread) { 74 | while (1) { 75 | pthread_mutex_lock(&thread->mutex); 76 | 77 | while (thread->action == THREAD_SLEEP) { 78 | pthread_cond_signal(&thread->sleep); 79 | pthread_cond_wait(&thread->sleep, &thread->mutex); 80 | } 81 | 82 | pthread_mutex_unlock(&thread->mutex); 83 | 84 | if (thread->action == THREAD_EXIT) 85 | break; 86 | else if (thread->action == THREAD_TT_CLEAR) { 87 | TTClearPart(thread->idx); 88 | } else if (thread->action == THREAD_SEARCH_CLEAR) { 89 | SearchClearThread(thread); 90 | } else { 91 | if (thread->idx) 92 | Search(thread); 93 | else 94 | MainSearch(); 95 | } 96 | 97 | thread->action = THREAD_SLEEP; 98 | } 99 | } 100 | 101 | // Build a thread 102 | void* ThreadInit(void* arg) { 103 | int i = (intptr_t) arg; 104 | 105 | ThreadData* thread = calloc(1, sizeof(ThreadData)); 106 | thread->idx = i; 107 | 108 | #if defined(__linux__) 109 | const size_t alignment = MEGABYTE * 2; 110 | #else 111 | const size_t alignment = 4096; 112 | #endif 113 | 114 | // Alloc all the necessary accumulators 115 | thread->accumulators = (Accumulator*) AlignedMalloc(sizeof(Accumulator) * (MAX_SEARCH_PLY + 1), alignment); 116 | thread->refreshTable = 117 | (AccumulatorKingState*) AlignedMalloc(sizeof(AccumulatorKingState) * 2 * 2 * N_KING_BUCKETS, alignment); 118 | ResetRefreshTable(thread->refreshTable); 119 | 120 | // Copy these onto the board for easier access within the engine 121 | thread->board.accumulators = thread->accumulators; 122 | thread->board.refreshTable = thread->refreshTable; 123 | 124 | pthread_mutex_init(&thread->mutex, NULL); 125 | pthread_cond_init(&thread->sleep, NULL); 126 | 127 | Threads.threads[i] = thread; 128 | 129 | pthread_mutex_lock(&Threads.mutex); 130 | Threads.init = 0; 131 | pthread_cond_signal(&Threads.sleep); 132 | pthread_mutex_unlock(&Threads.mutex); 133 | 134 | ThreadIdle(thread); 135 | 136 | return NULL; 137 | } 138 | 139 | // Create a thread with idx i 140 | void ThreadCreate(int i) { 141 | pthread_t thread; 142 | 143 | Threads.init = 1; 144 | pthread_mutex_lock(&Threads.mutex); 145 | pthread_create(&thread, NULL, ThreadInit, (void*) (intptr_t) i); 146 | 147 | while (Threads.init) 148 | pthread_cond_wait(&Threads.sleep, &Threads.mutex); 149 | pthread_mutex_unlock(&Threads.mutex); 150 | 151 | Threads.threads[i]->nativeThread = thread; 152 | } 153 | 154 | // Teardown and free a thread 155 | void ThreadDestroy(ThreadData* thread) { 156 | pthread_mutex_lock(&thread->mutex); 157 | thread->action = THREAD_EXIT; 158 | pthread_cond_signal(&thread->sleep); 159 | pthread_mutex_unlock(&thread->mutex); 160 | 161 | pthread_join(thread->nativeThread, NULL); 162 | pthread_cond_destroy(&thread->sleep); 163 | pthread_mutex_destroy(&thread->mutex); 164 | 165 | AlignedFree(thread->accumulators); 166 | AlignedFree(thread->refreshTable); 167 | 168 | free(thread); 169 | } 170 | 171 | // Build the pool to a certain amnt 172 | void ThreadsSetNumber(int n) { 173 | while (Threads.count < n) 174 | ThreadCreate(Threads.count++); 175 | while (Threads.count > n) 176 | ThreadDestroy(Threads.threads[--Threads.count]); 177 | 178 | if (n == 0) 179 | Threads.searching = 0; 180 | } 181 | 182 | // End 183 | void ThreadsExit() { 184 | ThreadsSetNumber(0); 185 | 186 | pthread_cond_destroy(&Threads.sleep); 187 | pthread_mutex_destroy(&Threads.mutex); 188 | } 189 | 190 | // Start 191 | void ThreadsInit() { 192 | pthread_mutex_init(&Threads.mutex, NULL); 193 | pthread_cond_init(&Threads.sleep, NULL); 194 | 195 | Threads.count = 1; 196 | ThreadCreate(0); 197 | } 198 | 199 | INLINE void InitRootMove(RootMove* rm, Move move) { 200 | rm->move = move; 201 | 202 | rm->previousScore = rm->score = rm->avgScore = -CHECKMATE; 203 | 204 | rm->pv.moves[0] = move; 205 | rm->pv.count = 1; 206 | 207 | rm->nodes = 0; 208 | } 209 | 210 | void SetupMainThread(Board* board) { 211 | ThreadData* mainThread = Threads.threads[0]; 212 | mainThread->calls = Limits.hitrate; 213 | mainThread->nodes = 0; 214 | mainThread->tbhits = 0; 215 | mainThread->nmpMinPly = 0; 216 | 217 | memcpy(&mainThread->board, board, offsetof(Board, accumulators)); 218 | 219 | if (Limits.searchMoves) { 220 | for (int i = 0; i < Limits.searchable.count; i++) 221 | InitRootMove(&mainThread->rootMoves[i], Limits.searchable.moves[i]); 222 | 223 | mainThread->numRootMoves = Limits.searchable.count; 224 | } else { 225 | SimpleMoveList ml[1]; 226 | TBRootMoves(ml, board); 227 | 228 | if (!ml->count) 229 | RootMoves(ml, board); 230 | 231 | for (int i = 0; i < ml->count; i++) 232 | InitRootMove(&mainThread->rootMoves[i], ml->moves[i]); 233 | 234 | mainThread->numRootMoves = ml->count; 235 | } 236 | } 237 | 238 | void SetupOtherThreads(Board* board) { 239 | ThreadData* mainThread = Threads.threads[0]; 240 | 241 | for (int i = 1; i < Threads.count; i++) { 242 | ThreadData* thread = Threads.threads[i]; 243 | thread->calls = Limits.hitrate; 244 | thread->nodes = 0; 245 | thread->tbhits = 0; 246 | thread->nmpMinPly = 0; 247 | 248 | for (int j = 0; j < mainThread->numRootMoves; j++) 249 | InitRootMove(&thread->rootMoves[j], mainThread->rootMoves[j].move); 250 | 251 | thread->numRootMoves = mainThread->numRootMoves; 252 | 253 | memcpy(&thread->board, board, offsetof(Board, accumulators)); 254 | } 255 | } 256 | 257 | // sum node counts 258 | uint64_t NodesSearched() { 259 | uint64_t nodes = 0; 260 | for (int i = 0; i < Threads.count; i++) 261 | nodes += LoadRlx(Threads.threads[i]->nodes); 262 | 263 | return nodes; 264 | } 265 | 266 | uint64_t TBHits() { 267 | uint64_t tbhits = 0; 268 | for (int i = 0; i < Threads.count; i++) 269 | tbhits += LoadRlx(Threads.threads[i]->tbhits); 270 | 271 | return tbhits; 272 | } 273 | -------------------------------------------------------------------------------- /src/thread.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef THREAD_H 18 | #define THREAD_H 19 | 20 | #include 21 | #include 22 | 23 | #include "types.h" 24 | 25 | typedef struct { 26 | ThreadData* threads[2048]; 27 | int count; 28 | 29 | pthread_mutex_t mutex, lock; 30 | pthread_cond_t sleep; 31 | 32 | uint8_t init, searching, sleeping, stopOnPonderHit; 33 | atomic_uchar ponder, stop; 34 | } ThreadPool; 35 | 36 | extern ThreadPool Threads; 37 | 38 | void ThreadWaitUntilSleep(ThreadData* thread); 39 | void ThreadWait(ThreadData* thread, atomic_uchar* cond); 40 | void ThreadWake(ThreadData* thread, int action); 41 | void ThreadIdle(ThreadData* thread); 42 | void* ThreadInit(void* arg); 43 | void ThreadCreate(int i); 44 | void ThreadDestroy(ThreadData* thread); 45 | void ThreadsSetNumber(int n); 46 | void ThreadsExit(); 47 | void ThreadsInit(); 48 | 49 | void SetupMainThread(Board* board); 50 | void SetupOtherThreads(Board* board); 51 | 52 | uint64_t NodesSearched(); 53 | uint64_t TBHits(); 54 | 55 | #endif -------------------------------------------------------------------------------- /src/transposition.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #if defined(__linux__) 25 | #include 26 | #endif 27 | 28 | #include "bits.h" 29 | #include "search.h" 30 | #include "thread.h" 31 | #include "transposition.h" 32 | #include "types.h" 33 | 34 | const int DEPTH_OFFSET = -2; 35 | 36 | // Global TT 37 | TTTable TT = {0}; 38 | 39 | size_t TTInit(int mb) { 40 | if (TT.mem) 41 | TTFree(); 42 | 43 | uint64_t size = (uint64_t) mb * MEGABYTE; 44 | 45 | #if defined(__linux__) 46 | const size_t alignment = 2 * MEGABYTE; 47 | #else 48 | const size_t alignment = 4096; 49 | #endif 50 | 51 | TT.mem = AlignedMalloc(size, alignment); 52 | 53 | #if defined(MADV_HUGEPAGE) 54 | madvise(TT.mem, size, MADV_HUGEPAGE); 55 | #endif 56 | 57 | TT.buckets = (TTBucket*) TT.mem; 58 | TT.count = size / sizeof(TTBucket); 59 | 60 | TTClear(); 61 | return size; 62 | } 63 | 64 | void TTFree() { 65 | AlignedFree(TT.mem); 66 | } 67 | 68 | void TTClearPart(int idx) { 69 | int count = Threads.count; 70 | 71 | const uint64_t size = TT.count * sizeof(TTBucket); 72 | const uint64_t slice = (size + count - 1) / count; 73 | const uint64_t blocks = (slice + 2 * MEGABYTE - 1) / (2 * MEGABYTE); 74 | const uint64_t begin = Min(size, idx * blocks * 2 * MEGABYTE); 75 | const uint64_t end = Min(size, begin + blocks * 2 * MEGABYTE); 76 | 77 | memset(TT.buckets + begin / sizeof(TTBucket), 0, end - begin); 78 | } 79 | 80 | inline void TTClear() { 81 | for (int i = 0; i < Threads.count; i++) 82 | ThreadWake(Threads.threads[i], THREAD_TT_CLEAR); 83 | for (int i = 0; i < Threads.count; i++) 84 | ThreadWaitUntilSleep(Threads.threads[i]); 85 | } 86 | 87 | inline void TTUpdate() { 88 | TT.age += AGE_INC; 89 | } 90 | 91 | inline uint64_t TTIdx(uint64_t hash) { 92 | return ((unsigned __int128) hash * (unsigned __int128) TT.count) >> 64; 93 | } 94 | 95 | inline void TTPrefetch(uint64_t hash) { 96 | __builtin_prefetch(&TT.buckets[TTIdx(hash)]); 97 | } 98 | 99 | inline TTEntry* TTProbe(uint64_t hash, 100 | int ply, 101 | int* hit, 102 | Move* hashMove, 103 | int* ttScore, 104 | int* ttEval, 105 | int* ttDepth, 106 | int* ttBound, 107 | int* pv) { 108 | TTEntry* const bucket = TT.buckets[TTIdx(hash)].entries; 109 | const uint16_t shortHash = (uint16_t) hash; 110 | 111 | for (int i = 0; i < BUCKET_SIZE; i++) { 112 | if (bucket[i].hash == shortHash || !bucket[i].depth) { 113 | *hit = !!bucket[i].depth; 114 | 115 | if (*hit) { 116 | *hashMove = TTMove(&bucket[i]); 117 | *ttEval = TTEval(&bucket[i]); 118 | *ttScore = TTScore(&bucket[i], ply); 119 | *ttDepth = TTDepth(&bucket[i]); 120 | *ttBound = TTBound(&bucket[i]); 121 | *pv = *pv || TTPV(&bucket[i]); 122 | } 123 | 124 | return &bucket[i]; 125 | } 126 | } 127 | 128 | *hit = 0; 129 | 130 | TTEntry* replace = bucket; 131 | for (int i = 1; i < BUCKET_SIZE; i++) 132 | if (replace->depth - TTAge(replace) / 2 > bucket[i].depth - TTAge(bucket + i) / 2) 133 | replace = &bucket[i]; 134 | 135 | return replace; 136 | } 137 | 138 | inline void 139 | TTPut(TTEntry* tt, uint64_t hash, int depth, int16_t score, uint8_t bound, Move move, int ply, int16_t eval, int pv) { 140 | uint16_t shortHash = (uint16_t) hash; 141 | 142 | if (score >= TB_WIN_BOUND) 143 | score += ply; 144 | else if (score <= -TB_WIN_BOUND) 145 | score -= ply; 146 | 147 | if (move || shortHash != tt->hash) 148 | TTStoreMove(tt, move); 149 | 150 | if ((bound == BOUND_EXACT) || shortHash != tt->hash || depth + 4 > TTDepth(tt) || TTAge(tt)) { 151 | tt->hash = shortHash; 152 | tt->score = score; 153 | tt->depth = (uint8_t) (depth - DEPTH_OFFSET); 154 | tt->agePvBound = (uint8_t) (TT.age | (pv << 2) | bound); 155 | TTStoreEval(tt, eval); 156 | } 157 | } 158 | 159 | int TTFull() { 160 | int c = 0; 161 | 162 | for (int i = 0; i < 1000; i++) 163 | for (int j = 0; j < BUCKET_SIZE; j++) 164 | c += TT.buckets[i].entries[j].depth && (TT.buckets[i].entries[j].agePvBound & AGE_MASK) == TT.age; 165 | 166 | return c / BUCKET_SIZE; 167 | } 168 | -------------------------------------------------------------------------------- /src/transposition.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef TRANSPOSITION_H 18 | #define TRANSPOSITION_H 19 | 20 | #include "types.h" 21 | #include "util.h" 22 | 23 | #define NO_ENTRY 0ULL 24 | #define MEGABYTE (1024ull * 1024ull) 25 | #define BUCKET_SIZE 3 26 | 27 | #define BOUND_MASK (0x3) 28 | #define PV_MASK (0x4) 29 | #define AGE_MASK (0xF8) 30 | #define AGE_INC (0x8) 31 | #define AGE_CYCLE (255 + AGE_INC) 32 | 33 | typedef struct __attribute__((packed)) { 34 | uint16_t hash; 35 | uint8_t depth; 36 | uint8_t agePvBound; 37 | uint32_t evalAndMove; 38 | int16_t score; 39 | } TTEntry; 40 | 41 | typedef struct { 42 | TTEntry entries[BUCKET_SIZE]; 43 | uint16_t padding; 44 | } TTBucket; 45 | 46 | typedef struct { 47 | void* mem; 48 | TTBucket* buckets; 49 | uint64_t count; 50 | uint8_t age; 51 | } TTTable; 52 | 53 | enum { 54 | BOUND_UNKNOWN = 0, 55 | BOUND_LOWER = 1, 56 | BOUND_UPPER = 2, 57 | BOUND_EXACT = 3 58 | }; 59 | 60 | extern TTTable TT; 61 | 62 | size_t TTInit(int mb); 63 | void TTFree(); 64 | void TTClearPart(int idx); 65 | void TTClear(); 66 | void TTUpdate(); 67 | void TTPrefetch(uint64_t hash); 68 | TTEntry* TTProbe(uint64_t hash, 69 | int ply, 70 | int* hit, 71 | Move* hashMove, 72 | int* ttScore, 73 | int* ttEval, 74 | int* ttDepth, 75 | int* ttBound, 76 | int* pv); 77 | void TTPut(TTEntry* tt, 78 | uint64_t hash, 79 | int depth, 80 | int16_t score, 81 | uint8_t bound, 82 | Move move, 83 | int ply, 84 | int16_t eval, 85 | int pv); 86 | int TTFull(); 87 | 88 | #define HASH_MAX ((int) (pow(2, 40) * sizeof(TTBucket) / MEGABYTE)) 89 | 90 | INLINE int TTAge(TTEntry* e) { 91 | return ((AGE_CYCLE + TT.age - e->agePvBound) & AGE_MASK); 92 | } 93 | 94 | INLINE Move TTMove(TTEntry* e) { 95 | // Lower 20 bits for move 96 | return (e->evalAndMove & 0xfffff); 97 | } 98 | 99 | INLINE int TTEval(TTEntry* e) { 100 | // Top 12 bits for eval offset by 2048 101 | return ((e->evalAndMove >> 20) & 0xfff) - 2048; 102 | } 103 | 104 | INLINE void TTStoreMove(TTEntry* e, Move move) { 105 | e->evalAndMove = (e->evalAndMove & 0xfff00000) | move; 106 | } 107 | 108 | INLINE void TTStoreEval(TTEntry* e, int eval) { 109 | uint32_t ueval = eval + 2048; 110 | e->evalAndMove = (ueval << 20) | (e->evalAndMove & 0x000fffff); 111 | } 112 | 113 | INLINE int TTScore(TTEntry* e, int ply) { 114 | if (e->score == UNKNOWN) 115 | return UNKNOWN; 116 | 117 | return e->score >= TB_WIN_BOUND ? e->score - ply : e->score <= -TB_WIN_BOUND ? e->score + ply : e->score; 118 | } 119 | 120 | extern const int DEPTH_OFFSET; 121 | 122 | INLINE int TTDepth(TTEntry* e) { 123 | return e->depth + DEPTH_OFFSET; 124 | } 125 | 126 | INLINE int TTBound(TTEntry* e) { 127 | return e->agePvBound & BOUND_MASK; // 2 bottom bits 128 | } 129 | 130 | INLINE int TTPV(TTEntry* e) { 131 | return e->agePvBound & PV_MASK; // 3rd to bottom bit 132 | } 133 | 134 | #endif -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef TYPES_H 18 | #define TYPES_H 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #define MAX_SEARCH_PLY 201 // effective max depth 250 27 | #define MAX_MOVES 128 28 | 29 | #define N_KING_BUCKETS 16 30 | 31 | #define N_FEATURES (N_KING_BUCKETS * 12 * 64) 32 | #define N_HIDDEN 1024 33 | #define N_L1 (2 * N_HIDDEN) 34 | #define N_L2 16 35 | #define N_L3 32 36 | #define N_OUTPUT 1 37 | 38 | #define ALIGN_ON 64 39 | #define ALIGN __attribute__((aligned(ALIGN_ON))) 40 | 41 | #define CORRECTION_GRAIN 256 42 | 43 | #define PAWN_CORRECTION_SIZE 131072 44 | #define PAWN_CORRECTION_MASK (PAWN_CORRECTION_SIZE - 1) 45 | 46 | typedef int Score; 47 | typedef uint64_t BitBoard; 48 | typedef uint32_t Move; 49 | 50 | enum { 51 | SUB = 0, 52 | ADD = 1 53 | }; 54 | 55 | typedef int16_t acc_t; 56 | 57 | typedef struct { 58 | uint8_t correct[2]; 59 | uint16_t captured; 60 | Move move; 61 | acc_t values[2][N_HIDDEN] ALIGN; 62 | } Accumulator; 63 | 64 | typedef struct { 65 | acc_t values[N_HIDDEN] ALIGN; 66 | BitBoard pcs[12]; 67 | } AccumulatorKingState; 68 | 69 | typedef struct { 70 | int castling; 71 | int ep; 72 | int fmr; 73 | int nullply; 74 | uint64_t zobrist; 75 | uint64_t pawnZobrist; 76 | BitBoard checkers; 77 | BitBoard pinned; 78 | BitBoard threatened; 79 | BitBoard threatenedBy[6]; 80 | int capture; 81 | } BoardHistory; 82 | 83 | typedef struct { 84 | // The below are in order of BoardHistory above for copies 85 | int castling; // castling mask e.g. 1111 = KQkq, 1001 = Kq 86 | int epSquare; // en passant square (a8 or 0 is not valid so that marks no 87 | // active ep) 88 | int fmr; // half move count for 50 move rule 89 | int nullply; // distance from last nullmove 90 | 91 | uint64_t zobrist; // zobrist hash of the position 92 | uint64_t pawnZobrist; // pawn zobrist hash of the position (pawns + stm) 93 | 94 | BitBoard checkers; // checking piece squares 95 | BitBoard pinned; // pinned pieces 96 | 97 | BitBoard threatened; // opponent "threatening" these squares 98 | BitBoard threatenedBy[6]; 99 | 100 | int stm; // side to move 101 | int xstm; // not side to move 102 | int histPly; // ply for historical state 103 | int moveNo; // game move number 104 | int phase; // efficiently updated phase for scaling 105 | 106 | uint64_t piecesCounts; // "material key" - pieces left on the board 107 | 108 | int squares[64]; // piece per square 109 | BitBoard occupancies[3]; // 0 - white pieces, 1 - black pieces, 2 - both 110 | BitBoard pieces[12]; // individual piece data 111 | 112 | int cr[4]; 113 | int castlingRights[64]; 114 | 115 | BoardHistory history[MAX_SEARCH_PLY + 100]; 116 | 117 | Accumulator* accumulators; 118 | AccumulatorKingState* refreshTable; 119 | } Board; 120 | 121 | typedef struct { 122 | int count; 123 | Move moves[MAX_MOVES]; 124 | } SimpleMoveList; 125 | 126 | // Tracking the principal variation 127 | typedef struct { 128 | int count; 129 | Move moves[MAX_SEARCH_PLY]; 130 | } PV; 131 | 132 | typedef int16_t PieceTo[12][64]; 133 | 134 | typedef struct { 135 | int ply, staticEval, de; 136 | int reduction; 137 | PieceTo* ch; 138 | PieceTo* cont; 139 | Move move, skip; 140 | Move killers[2]; 141 | } SearchStack; 142 | 143 | typedef struct { 144 | long start; 145 | int alloc; 146 | int max; 147 | 148 | uint64_t nodes; 149 | int hitrate; 150 | 151 | int timeset; 152 | int depth; 153 | int mate; 154 | int movesToGo; 155 | int stopped; 156 | int quit; 157 | int multiPV; 158 | int infinite; 159 | int searchMoves; 160 | SimpleMoveList searchable; 161 | } SearchParams; 162 | 163 | typedef struct { 164 | Move move; 165 | int seldepth; 166 | int score, previousScore, avgScore; 167 | uint64_t nodes; 168 | PV pv; 169 | } RootMove; 170 | 171 | enum { 172 | THREAD_SLEEP, 173 | THREAD_SEARCH, 174 | THREAD_TT_CLEAR, 175 | THREAD_SEARCH_CLEAR, 176 | THREAD_EXIT, 177 | THREAD_RESUME 178 | }; 179 | 180 | typedef struct ThreadData ThreadData; 181 | 182 | struct ThreadData { 183 | int idx, multiPV, depth, seldepth; 184 | atomic_uint_fast64_t nodes, tbhits; 185 | 186 | int nmpMinPly, npmColor; 187 | 188 | Accumulator* accumulators; 189 | AccumulatorKingState* refreshTable; 190 | 191 | Board board; 192 | 193 | int contempt[2]; 194 | int previousScore; 195 | int numRootMoves; 196 | RootMove rootMoves[MAX_MOVES]; 197 | 198 | Move counters[12][64]; // counter move butterfly table 199 | int16_t hh[2][2][2][64 * 64]; // history heuristic butterfly table (stm / threatened) 200 | int16_t ch[2][12][64][12][64]; // continuation move history table 201 | int16_t caph[12][64][2][7]; // capture history (piece - to - defeneded - captured_type) 202 | 203 | int16_t pawnCorrection[PAWN_CORRECTION_SIZE]; 204 | int16_t contCorrection[12][64][12][64]; 205 | 206 | int action, calls; 207 | pthread_t nativeThread; 208 | pthread_mutex_t mutex; 209 | pthread_cond_t sleep; 210 | jmp_buf exit; 211 | }; 212 | 213 | typedef struct { 214 | Board* board; 215 | } SearchArgs; 216 | 217 | // Move generation storage 218 | // moves/scores idx's match 219 | enum { 220 | ALL_MOVES, 221 | NOISY_MOVES 222 | }; 223 | 224 | enum { 225 | HASH_MOVE, 226 | GEN_NOISY_MOVES, 227 | PLAY_GOOD_NOISY, 228 | PLAY_KILLER_1, 229 | PLAY_KILLER_2, 230 | PLAY_COUNTER, 231 | GEN_QUIET_MOVES, 232 | PLAY_QUIETS, 233 | PLAY_BAD_NOISY, 234 | // ProbCut 235 | PC_GEN_NOISY_MOVES, 236 | PC_PLAY_GOOD_NOISY, 237 | // QSearch 238 | QS_GEN_NOISY_MOVES, 239 | QS_PLAY_NOISY_MOVES, 240 | QS_GEN_QUIET_CHECKS, 241 | QS_PLAY_QUIET_CHECKS, 242 | // QSearch Evasions 243 | QS_EVASION_HASH_MOVE, 244 | QS_EVASION_GEN_NOISY, 245 | QS_EVASION_PLAY_NOISY, 246 | QS_EVASION_GEN_QUIET, 247 | QS_EVASION_PLAY_QUIET, 248 | // ... 249 | NO_MORE_MOVES, 250 | PERFT_MOVES, 251 | }; 252 | 253 | typedef struct { 254 | int score; 255 | Move move; 256 | } ScoredMove; 257 | 258 | typedef struct { 259 | ThreadData* thread; 260 | SearchStack* ss; 261 | Move hashMove, killer1, killer2, counter; 262 | int seeCutoff, phase, genChecks; 263 | 264 | ScoredMove *current, *end, *endBad; 265 | ScoredMove moves[MAX_MOVES]; 266 | } MovePicker; 267 | 268 | enum { 269 | WHITE, 270 | BLACK, 271 | BOTH 272 | }; 273 | 274 | enum { 275 | A8, 276 | B8, 277 | C8, 278 | D8, 279 | E8, 280 | F8, 281 | G8, 282 | H8, 283 | A7, 284 | B7, 285 | C7, 286 | D7, 287 | E7, 288 | F7, 289 | G7, 290 | H7, 291 | A6, 292 | B6, 293 | C6, 294 | D6, 295 | E6, 296 | F6, 297 | G6, 298 | H6, 299 | A5, 300 | B5, 301 | C5, 302 | D5, 303 | E5, 304 | F5, 305 | G5, 306 | H5, 307 | A4, 308 | B4, 309 | C4, 310 | D4, 311 | E4, 312 | F4, 313 | G4, 314 | H4, 315 | A3, 316 | B3, 317 | C3, 318 | D3, 319 | E3, 320 | F3, 321 | G3, 322 | H3, 323 | A2, 324 | B2, 325 | C2, 326 | D2, 327 | E2, 328 | F2, 329 | G2, 330 | H2, 331 | A1, 332 | B1, 333 | C1, 334 | D1, 335 | E1, 336 | F1, 337 | G1, 338 | H1, 339 | }; 340 | 341 | enum { 342 | N = -8, 343 | E = 1, 344 | S = 8, 345 | W = -1, 346 | NE = -7, 347 | SE = 9, 348 | SW = 7, 349 | NW = -9 350 | }; 351 | 352 | enum { 353 | WHITE_PAWN, 354 | BLACK_PAWN, 355 | WHITE_KNIGHT, 356 | BLACK_KNIGHT, 357 | WHITE_BISHOP, 358 | BLACK_BISHOP, 359 | WHITE_ROOK, 360 | BLACK_ROOK, 361 | WHITE_QUEEN, 362 | BLACK_QUEEN, 363 | WHITE_KING, 364 | BLACK_KING 365 | }; 366 | 367 | enum { 368 | PAWN, 369 | KNIGHT, 370 | BISHOP, 371 | ROOK, 372 | QUEEN, 373 | KING 374 | }; 375 | 376 | enum { 377 | MG, 378 | EG 379 | }; 380 | 381 | #endif 382 | -------------------------------------------------------------------------------- /src/uci.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "uci.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "bench.h" 27 | #include "board.h" 28 | #include "eval.h" 29 | #include "move.h" 30 | #include "movegen.h" 31 | #include "movepick.h" 32 | #include "nn/accumulator.h" 33 | #include "nn/evaluate.h" 34 | #include "perft.h" 35 | #include "pyrrhic/tbprobe.h" 36 | #include "search.h" 37 | #include "see.h" 38 | #include "thread.h" 39 | #include "transposition.h" 40 | #include "util.h" 41 | 42 | #define START_FEN "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" 43 | 44 | int MOVE_OVERHEAD = 50; 45 | int MULTI_PV = 1; 46 | int PONDER_ENABLED = 0; 47 | int CHESS_960 = 0; 48 | int CONTEMPT = 0; 49 | int SHOW_WDL = 1; 50 | 51 | SearchParams Limits; 52 | 53 | // All WDL work below is thanks to the work of vondele@ and 54 | // this repo: https://github.com/vondele/WLD_model 55 | 56 | // Third order polynomial fit of Berserk data 57 | const double as[4] = {-2.02923586, 16.87641200, -27.06230207, 182.53858835}; 58 | const double bs[4] = {-6.15230497, 42.37548361, -80.19006222, 77.75994970}; 59 | 60 | // win% as permilli given score and ply 61 | int WRModel(Score s, int ply) { 62 | double m = Min(240, ply) / 64.0; 63 | 64 | double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; 65 | double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; 66 | 67 | double x = Min(4000.0, Max(-4000.0, s)); 68 | 69 | return 0.5 + 1000 / (1 + exp((a - x) / b)); 70 | } 71 | 72 | void RootMoves(SimpleMoveList* moves, Board* board) { 73 | moves->count = 0; 74 | 75 | MovePicker mp; 76 | InitPerftMovePicker(&mp, board); 77 | 78 | Move mv; 79 | while ((mv = NextMove(&mp, board, 0))) 80 | moves->moves[moves->count++] = mv; 81 | } 82 | 83 | // uci "go" command 84 | void ParseGo(char* in, Board* board) { 85 | in += 3; 86 | 87 | Limits.depth = MAX_SEARCH_PLY; 88 | Limits.start = GetTimeMS(); 89 | Limits.timeset = 0; 90 | Limits.stopped = 0; 91 | Limits.quit = 0; 92 | Limits.multiPV = MULTI_PV; 93 | Limits.searchMoves = 0; 94 | Limits.searchable.count = 0; 95 | Limits.infinite = 0; 96 | Limits.mate = 0; 97 | 98 | char* ptrChar = in; 99 | int perft = 0, movesToGo = -1, moveTime = -1, time = -1, inc = 0, depth = -1, nodes = 0, ponder = 0, mate = 0; 100 | 101 | SimpleMoveList rootMoves; 102 | RootMoves(&rootMoves, board); 103 | 104 | if ((ptrChar = strstr(in, "infinite"))) 105 | Limits.infinite = 1; 106 | 107 | if ((ptrChar = strstr(in, "perft"))) 108 | perft = atoi(ptrChar + 6); 109 | 110 | if ((ptrChar = strstr(in, "binc")) && board->stm == BLACK) 111 | inc = atoi(ptrChar + 5); 112 | 113 | if ((ptrChar = strstr(in, "winc")) && board->stm == WHITE) 114 | inc = atoi(ptrChar + 5); 115 | 116 | if ((ptrChar = strstr(in, "wtime")) && board->stm == WHITE) 117 | time = atoi(ptrChar + 6); 118 | 119 | if ((ptrChar = strstr(in, "btime")) && board->stm == BLACK) 120 | time = atoi(ptrChar + 6); 121 | 122 | if ((ptrChar = strstr(in, "movestogo"))) 123 | movesToGo = Max(1, Min(50, atoi(ptrChar + 10))); 124 | 125 | if ((ptrChar = strstr(in, "movetime"))) 126 | moveTime = atoi(ptrChar + 9); 127 | 128 | if ((ptrChar = strstr(in, "depth"))) 129 | depth = Min(MAX_SEARCH_PLY - 1, atoi(ptrChar + 6)); 130 | 131 | if ((ptrChar = strstr(in, "mate"))) 132 | mate = Min(MAX_SEARCH_PLY - 1, atoi(ptrChar + 5)); 133 | 134 | if ((ptrChar = strstr(in, "nodes"))) 135 | nodes = Max(1, atol(ptrChar + 6)); 136 | 137 | if ((ptrChar = strstr(in, "ponder"))) { 138 | if (PONDER_ENABLED) 139 | ponder = 1; 140 | else { 141 | printf("info string Enable option Ponder to use 'ponder'\n"); 142 | return; 143 | } 144 | } 145 | 146 | if ((ptrChar = strstr(in, "searchmoves"))) { 147 | Limits.searchMoves = 1; 148 | 149 | for (char* moves = strtok(ptrChar + 12, " "); moves != NULL; moves = strtok(NULL, " ")) 150 | for (int i = 0; i < rootMoves.count; i++) 151 | if (!strcmp(MoveToStr(rootMoves.moves[i], board), moves)) 152 | Limits.searchable.moves[Limits.searchable.count++] = rootMoves.moves[i]; 153 | } 154 | 155 | if (perft) { 156 | PerftTest(perft, board); 157 | return; 158 | } 159 | 160 | Limits.depth = depth; 161 | Limits.mate = mate; 162 | Limits.nodes = nodes; 163 | 164 | if (Limits.nodes) 165 | Limits.hitrate = Min(1000, Max(1, Limits.nodes / 100)); 166 | else 167 | Limits.hitrate = 1000; 168 | 169 | // "movetime" is essentially making a move with 1 to go for TC 170 | if (moveTime != -1) { 171 | Limits.timeset = 1; 172 | Limits.alloc = INT32_MAX; 173 | Limits.max = moveTime; 174 | } else { 175 | if (time != -1) { 176 | Limits.timeset = 1; 177 | 178 | if (movesToGo == -1) { 179 | int total = Max(1, time + 50 * inc - 50 * MOVE_OVERHEAD); 180 | 181 | Limits.alloc = Min(time * 0.4193, total * 0.0575); 182 | Limits.max = Min(time * 0.9221 - MOVE_OVERHEAD, Limits.alloc * 5.9280) - 10; 183 | } else { 184 | int total = Max(1, time + movesToGo * inc - MOVE_OVERHEAD); 185 | 186 | Limits.alloc = Min(time * 0.9, (0.9 * total) / Max(1, movesToGo / 2.5)); 187 | Limits.max = Min(time * 0.8 - MOVE_OVERHEAD, Limits.alloc * 5.5) - 10; 188 | } 189 | } else { 190 | // no time control 191 | Limits.timeset = 0; 192 | Limits.max = INT_MAX; 193 | } 194 | } 195 | 196 | Limits.multiPV = Min(Limits.multiPV, Limits.searchMoves ? Limits.searchable.count : rootMoves.count); 197 | if (rootMoves.count == 1 && Limits.timeset) 198 | Limits.max = Min(250, Limits.max); 199 | 200 | if (depth <= 0) 201 | Limits.depth = MAX_SEARCH_PLY - 1; 202 | 203 | printf( 204 | "info string time %d start %ld alloc %d max %d depth %d timeset %d " 205 | "searchmoves %d\n", 206 | time, 207 | Limits.start, 208 | Limits.alloc, 209 | Limits.max, 210 | Limits.depth, 211 | Limits.timeset, 212 | Limits.searchable.count); 213 | 214 | StartSearch(board, ponder); 215 | } 216 | 217 | // uci "position" command 218 | void ParsePosition(char* in, Board* board) { 219 | in += 9; 220 | char* ptrChar = in; 221 | 222 | if (strncmp(in, "startpos", 8) == 0) { 223 | ParseFen(START_FEN, board); 224 | } else { 225 | ptrChar = strstr(in, "fen"); 226 | if (ptrChar == NULL) 227 | ParseFen(START_FEN, board); 228 | else { 229 | ptrChar += 4; 230 | ParseFen(ptrChar, board); 231 | } 232 | } 233 | 234 | ptrChar = strstr(in, "moves"); 235 | 236 | if (ptrChar == NULL) 237 | return; 238 | 239 | ptrChar += 6; 240 | 241 | for (char* moves = strtok(ptrChar, " "); moves != NULL; moves = strtok(NULL, " ")) { 242 | Move enteredMove = ParseMove(moves, board); 243 | if (!enteredMove) 244 | break; 245 | 246 | MakeMoveUpdate(enteredMove, board, 0); 247 | 248 | if (board->fmr == 0) 249 | board->histPly = board->nullply = 0; 250 | } 251 | } 252 | 253 | void PrintUCIOptions() { 254 | printf("id name Berserk " VERSION "\n"); 255 | printf("id author Jay Honnold\n"); 256 | printf("option name Hash type spin default 16 min 2 max %d\n", HASH_MAX); 257 | printf("option name Threads type spin default 1 min 1 max 2048\n"); 258 | printf("option name SyzygyPath type string default \n"); 259 | printf("option name MultiPV type spin default 1 min 1 max 256\n"); 260 | printf("option name Ponder type check default false\n"); 261 | printf("option name UCI_ShowWDL type check default true\n"); 262 | printf("option name UCI_Chess960 type check default false\n"); 263 | printf("option name MoveOverhead type spin default 50 min 0 max 10000\n"); 264 | printf("option name Contempt type spin default 0 min -100 max 100\n"); 265 | printf("option name EvalFile type string default \n"); 266 | printf("uciok\n"); 267 | } 268 | 269 | int ReadLine(char* in) { 270 | if (fgets(in, 8192, stdin) == NULL) 271 | return 0; 272 | 273 | size_t c = strcspn(in, "\r\n"); 274 | if (c < strlen(in)) 275 | in[c] = '\0'; 276 | 277 | return 1; 278 | } 279 | 280 | void UCILoop() { 281 | static char in[8192]; 282 | 283 | Board board; 284 | ParseFen(START_FEN, &board); 285 | 286 | setbuf(stdout, NULL); 287 | 288 | Threads.searching = Threads.sleeping = 0; 289 | 290 | while (ReadLine(in)) { 291 | if (in[0] == '\n') 292 | continue; 293 | 294 | if (!strncmp(in, "isready", 7)) { 295 | printf("readyok\n"); 296 | } else if (!strncmp(in, "position", 8)) { 297 | ParsePosition(in, &board); 298 | } else if (!strncmp(in, "ucinewgame", 10)) { 299 | ParsePosition("position startpos\n", &board); 300 | TTClear(); 301 | SearchClear(); 302 | } else if (!strncmp(in, "go", 2)) { 303 | ParseGo(in, &board); 304 | } else if (!strncmp(in, "stop", 4)) { 305 | if (Threads.searching) { 306 | Threads.stop = 1; 307 | pthread_mutex_lock(&Threads.lock); 308 | if (Threads.sleeping) 309 | ThreadWake(Threads.threads[0], THREAD_RESUME); 310 | Threads.sleeping = 0; 311 | pthread_mutex_unlock(&Threads.lock); 312 | } 313 | } else if (!strncmp(in, "quit", 4)) { 314 | if (Threads.searching) { 315 | Threads.stop = 1; 316 | pthread_mutex_lock(&Threads.lock); 317 | if (Threads.sleeping) 318 | ThreadWake(Threads.threads[0], THREAD_RESUME); 319 | Threads.sleeping = 0; 320 | pthread_mutex_unlock(&Threads.lock); 321 | } 322 | break; 323 | } else if (!strncmp(in, "uci", 3)) { 324 | PrintUCIOptions(); 325 | } else if (!strncmp(in, "ponderhit", 9)) { 326 | Threads.ponder = 0; 327 | if (Threads.stopOnPonderHit) 328 | Threads.stop = 1; 329 | pthread_mutex_lock(&Threads.lock); 330 | if (Threads.sleeping) { 331 | Threads.stop = 1; 332 | ThreadWake(Threads.threads[0], THREAD_RESUME); 333 | Threads.sleeping = 0; 334 | } 335 | pthread_mutex_unlock(&Threads.lock); 336 | } else if (!strncmp(in, "board", 5)) { 337 | PrintBoard(&board); 338 | } else if (!strncmp(in, "cycle", 5)) { 339 | int cycle = HasCycle(&board, MAX_SEARCH_PLY); 340 | printf(cycle ? "yes\n" : "no\n"); 341 | } else if (!strncmp(in, "perft", 5)) { 342 | strtok(in, " "); 343 | char* d = strtok(NULL, " ") ?: "5"; 344 | char* fen = strtok(NULL, "\0") ?: START_FEN; 345 | 346 | int depth = atoi(d); 347 | ParseFen(fen, &board); 348 | 349 | PerftTest(depth, &board); 350 | } else if (!strncmp(in, "bench", 5)) { 351 | strtok(in, " "); 352 | char* d = strtok(NULL, " ") ?: "13"; 353 | 354 | int depth = atoi(d); 355 | Bench(depth); 356 | } else if (!strncmp(in, "threats", 7)) { 357 | PrintBB(board.threatened); 358 | } else if (!strncmp(in, "eval", 4)) { 359 | EvaluateTrace(&board); 360 | } else if (!strncmp(in, "see ", 4)) { 361 | Move m = ParseMove(in + 4, &board); 362 | if (m) 363 | printf("info string SEE result: %d\n", SEE(&board, m, 0)); 364 | else 365 | printf("info string Invalid move!\n"); 366 | } else if (!strncmp(in, "apply ", 6)) { 367 | Move m = ParseMove(in + 6, &board); 368 | if (m) { 369 | MakeMoveUpdate(m, &board, 0); 370 | PrintBoard(&board); 371 | } else 372 | printf("info string Invalid move!\n"); 373 | } else if (!strncmp(in, "setoption name Hash value ", 26)) { 374 | int mb = GetOptionIntValue(in); 375 | mb = Max(2, Min(HASH_MAX, mb)); 376 | uint64_t bytesAllocated = TTInit(mb); 377 | uint64_t totalEntries = BUCKET_SIZE * bytesAllocated / sizeof(TTBucket); 378 | printf("info string set Hash to value %d (%" PRIu64 " bytes) (%" PRIu64 " entries)\n", 379 | mb, 380 | bytesAllocated, 381 | totalEntries); 382 | } else if (!strncmp(in, "setoption name Threads value ", 29)) { 383 | int n = GetOptionIntValue(in); 384 | ThreadsSetNumber(Max(1, Min(2048, n))); 385 | printf("info string set Threads to value %d\n", Threads.count); 386 | } else if (!strncmp(in, "setoption name SyzygyPath value ", 32)) { 387 | int success = tb_init(in + 32); 388 | if (success) 389 | printf("info string set SyzygyPath to value %s\n", in + 32); 390 | else 391 | printf("info string FAILED!\n"); 392 | } else if (!strncmp(in, "setoption name MultiPV value ", 29)) { 393 | int n = GetOptionIntValue(in); 394 | 395 | MULTI_PV = Max(1, Min(256, n)); 396 | printf("info string set MultiPV to value %d\n", MULTI_PV); 397 | } else if (!strncmp(in, "setoption name Ponder value ", 28)) { 398 | char opt[6]; 399 | sscanf(in, "%*s %*s %*s %*s %5s", opt); 400 | 401 | PONDER_ENABLED = !strncmp(opt, "true", 4); 402 | printf("info string set Ponder to value %s\n", PONDER_ENABLED ? "true" : "false"); 403 | } else if (!strncmp(in, "setoption name UCI_ShowWDL value ", 33)) { 404 | char opt[6]; 405 | sscanf(in, "%*s %*s %*s %*s %5s", opt); 406 | 407 | SHOW_WDL = !strncmp(opt, "true", 4); 408 | printf("info string set SHOW_WDL to value %s\n", SHOW_WDL ? "true" : "false"); 409 | } else if (!strncmp(in, "setoption name UCI_Chess960 value ", 34)) { 410 | char opt[6]; 411 | sscanf(in, "%*s %*s %*s %*s %5s", opt); 412 | 413 | CHESS_960 = !strncmp(opt, "true", 4); 414 | printf("info string set UCI_Chess960 to value %s\n", CHESS_960 ? "true" : "false"); 415 | printf("info string Resetting board...\n"); 416 | 417 | ParsePosition("position startpos\n", &board); 418 | TTClear(); 419 | SearchClear(); 420 | } else if (!strncmp(in, "setoption name MoveOverhead value ", 34)) { 421 | MOVE_OVERHEAD = Min(10000, Max(0, GetOptionIntValue(in))); 422 | } else if (!strncmp(in, "setoption name Contempt value ", 30)) { 423 | CONTEMPT = Min(100, Max(-100, GetOptionIntValue(in))); 424 | } else if (!strncmp(in, "setoption name EvalFile value ", 30)) { 425 | char* path = in + 30; 426 | int success = 0; 427 | 428 | if (strncmp(path, "", 7)) 429 | success = LoadNetwork(path); 430 | else { 431 | LoadDefaultNN(); 432 | success = 1; 433 | } 434 | 435 | if (success) 436 | printf("info string set EvalFile to value %s\n", path); 437 | } else 438 | printf("Unknown command: %s \n", in); 439 | } 440 | 441 | if (Threads.searching) 442 | ThreadWaitUntilSleep(Threads.threads[0]); 443 | 444 | pthread_mutex_destroy(&Threads.lock); 445 | ThreadsExit(); 446 | } 447 | 448 | int GetOptionIntValue(char* in) { 449 | int n; 450 | sscanf(in, "%*s %*s %*s %*s %d", &n); 451 | 452 | return n; 453 | } 454 | -------------------------------------------------------------------------------- /src/uci.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef UCI_H 18 | #define UCI_H 19 | 20 | #include "types.h" 21 | 22 | extern int SHOW_WDL; 23 | extern int CHESS_960; 24 | extern int CONTEMPT; 25 | extern SearchParams Limits; 26 | 27 | // Normalization of a score to 50% WR at 100cp 28 | #define Normalize(s) ((s) / 1.70) 29 | 30 | int WRModel(Score s, int ply); 31 | 32 | void RootMoves(SimpleMoveList* moves, Board* board); 33 | 34 | void ParseGo(char* in, Board* board); 35 | void ParsePosition(char* in, Board* board); 36 | void PrintUCIOptions(); 37 | 38 | int ReadLine(char* in); 39 | void UCILoop(); 40 | 41 | int GetOptionIntValue(char* in); 42 | 43 | #endif -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "types.h" 18 | 19 | #ifdef WIN32 20 | #include 21 | 22 | long GetTimeMS() { 23 | return GetTickCount(); 24 | } 25 | 26 | #else 27 | #include 28 | #include 29 | 30 | long GetTimeMS() { 31 | struct timeval time; 32 | gettimeofday(&time, NULL); 33 | 34 | return time.tv_sec * 1000 + time.tv_usec / 1000; 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef UTIL_H 18 | #define UTIL_H 19 | 20 | #include 21 | #include 22 | 23 | #include "types.h" 24 | 25 | #define Min(a, b) (((a) < (b)) ? (a) : (b)) 26 | #define Max(a, b) (((a) > (b)) ? (a) : (b)) 27 | 28 | #define INLINE static inline __attribute__((always_inline)) 29 | 30 | #define LoadRlx(x) atomic_load_explicit(&(x), memory_order_relaxed) 31 | #define IncRlx(x) atomic_fetch_add_explicit(&(x), 1, memory_order_relaxed) 32 | #define DecRlx(x) atomic_fetch_sub_explicit(&(x), 1, memory_order_relaxed) 33 | 34 | long GetTimeMS(); 35 | 36 | INLINE void* AlignedMalloc(uint64_t size, const size_t on) { 37 | void* mem = malloc(size + on + sizeof(void*)); 38 | void** ptr = (void**) ((uintptr_t) (mem + on + sizeof(void*)) & ~(on - 1)); 39 | ptr[-1] = mem; 40 | return ptr; 41 | } 42 | 43 | INLINE void AlignedFree(void* ptr) { 44 | free(((void**) ptr)[-1]); 45 | } 46 | 47 | #endif -------------------------------------------------------------------------------- /src/zobrist.c: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "zobrist.h" 18 | 19 | #include "bits.h" 20 | #include "random.h" 21 | #include "types.h" 22 | 23 | uint64_t ZOBRIST_PIECES[12][64]; 24 | uint64_t ZOBRIST_EP_KEYS[64]; 25 | uint64_t ZOBRIST_CASTLE_KEYS[16]; 26 | uint64_t ZOBRIST_SIDE_KEY; 27 | 28 | void InitZobristKeys() { 29 | for (int i = 0; i < 12; i++) 30 | for (int j = 0; j < 64; j++) 31 | ZOBRIST_PIECES[i][j] = RandomUInt64(); 32 | 33 | for (int i = 0; i < 64; i++) 34 | ZOBRIST_EP_KEYS[i] = RandomUInt64(); 35 | 36 | for (int i = 0; i < 16; i++) 37 | ZOBRIST_CASTLE_KEYS[i] = RandomUInt64(); 38 | 39 | ZOBRIST_SIDE_KEY = RandomUInt64(); 40 | } 41 | 42 | // Generate a Zobrist key for the current board state 43 | uint64_t Zobrist(Board* board) { 44 | uint64_t hash = 0ULL; 45 | 46 | for (int piece = WHITE_PAWN; piece <= BLACK_KING; piece++) { 47 | BitBoard pcs = board->pieces[piece]; 48 | while (pcs) 49 | hash ^= ZOBRIST_PIECES[piece][PopLSB(&pcs)]; 50 | } 51 | 52 | if (board->epSquare) 53 | hash ^= ZOBRIST_EP_KEYS[board->epSquare]; 54 | 55 | hash ^= ZOBRIST_CASTLE_KEYS[board->castling]; 56 | 57 | if (board->stm) 58 | hash ^= ZOBRIST_SIDE_KEY; 59 | 60 | return hash; 61 | } 62 | 63 | // Generate a Pawn Zobrist key for the current board state 64 | uint64_t PawnZobrist(Board* board) { 65 | uint64_t hash = 0ULL; 66 | 67 | for (int piece = WHITE_PAWN; piece <= BLACK_PAWN; piece++) { 68 | BitBoard pcs = board->pieces[piece]; 69 | while (pcs) 70 | hash ^= ZOBRIST_PIECES[piece][PopLSB(&pcs)]; 71 | } 72 | 73 | if (board->stm) 74 | hash ^= ZOBRIST_SIDE_KEY; 75 | 76 | return hash; 77 | } 78 | -------------------------------------------------------------------------------- /src/zobrist.h: -------------------------------------------------------------------------------- 1 | // Berserk is a UCI compliant chess engine written in C 2 | // Copyright (C) 2024 Jay Honnold 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef ZOBRIST_H 18 | #define ZOBRIST_H 19 | 20 | #include "board.h" 21 | #include "move.h" 22 | #include "types.h" 23 | #include "util.h" 24 | 25 | extern uint64_t ZOBRIST_PIECES[12][64]; 26 | extern uint64_t ZOBRIST_EP_KEYS[64]; 27 | extern uint64_t ZOBRIST_CASTLE_KEYS[16]; 28 | extern uint64_t ZOBRIST_SIDE_KEY; 29 | 30 | void InitZobristKeys(); 31 | uint64_t Zobrist(Board* board); 32 | uint64_t PawnZobrist(Board* board); 33 | 34 | INLINE uint64_t KeyAfter(Board* board, const Move move) { 35 | if (!move) 36 | return board->zobrist ^ ZOBRIST_SIDE_KEY; 37 | 38 | const int from = From(move); 39 | const int to = To(move); 40 | const int moving = Moving(move); 41 | 42 | uint64_t newKey = board->zobrist ^ ZOBRIST_SIDE_KEY ^ ZOBRIST_PIECES[moving][from] ^ ZOBRIST_PIECES[moving][to]; 43 | 44 | if (board->squares[to] != NO_PIECE) 45 | newKey ^= ZOBRIST_PIECES[board->squares[to]][to]; 46 | 47 | return newKey; 48 | } 49 | 50 | #endif -------------------------------------------------------------------------------- /tests/mate-in-1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | error() { 4 | >&2 echo "mate testing failed on line $1" 5 | exit 1 6 | } 7 | trap 'error ${LINENO}' ERR 8 | 9 | echo "mate testing started" 10 | 11 | cat << EOF > mate.exp 12 | set timeout 2 13 | lassign \$argv pos result 14 | spawn ./src/berserk 15 | send "position fen \$pos\\ngo\\n" 16 | expect "bestmove \$result" {} timeout {exit 1} 17 | send "quit\\n" 18 | expect eof 19 | EOF 20 | 21 | expect mate.exp "3k3B/7p/p1Q1p3/2n5/6P1/K3b3/PP5q/R7 w - -" h8f6 22 | expect mate.exp "4bk2/ppp3p1/2np3p/2b5/2B2Bnq/2N5/PP4PP/4RR1K w - -" f4d6 23 | expect mate.exp "4rkr1/1p1Rn1pp/p3p2B/4Qp2/8/8/PPq2PPP/3R2K1 w - -" e5f6 24 | expect mate.exp "5r2/p1n3k1/1p3qr1/7R/8/1BP1Q3/P5R1/6K1 w - -" e3h6 25 | expect mate.exp "5rkr/ppp2p1p/8/3qp3/2pN4/8/PPPQ1PPP/4R1K1 w - -" d2g5 26 | expect mate.exp "6n1/5P1k/7p/np4b1/3B4/1pP4P/5PP1/1b4K1 w - -" f7f8n 27 | expect mate.exp "6q1/R2Q3p/1p1p1ppk/1P1N4/1P2rP2/6P1/7P/6K1 w - -" d7h3 28 | expect mate.exp "8/6P1/5K1k/6N1/5N2/8/8/8 w - -" g7g8n 29 | expect mate.exp "r1b2rk1/pppp2p1/8/3qPN1Q/8/8/P5PP/b1B2R1K w - -" f5e7 30 | expect mate.exp "r1bk3r/p1q1b1p1/7p/nB1pp1N1/8/3PB3/PPP2PPP/R3K2R w KQ -" g5f7 31 | expect mate.exp "r1bknb1r/pppnp1p1/3Np3/3p4/3P1B2/2P5/P3KPPP/7q w - -" d6f7 32 | expect mate.exp "r1bq2kr/pnpp3p/2pP1ppB/8/3Q4/8/PPP2PPP/RN2R1K1 w - -" d4c4 33 | expect mate.exp "r1bqkb1r/pp1npppp/2p2n2/8/3PN3/8/PPP1QPPP/R1B1KBNR w KQkq -" e4d6 34 | expect mate.exp "r1bqr3/pp1nbk1p/2p2ppB/8/3P4/5Q2/PPP1NPPP/R3K2R w KQ -" f3b3 35 | expect mate.exp "r1q1r3/ppp1bpp1/2np4/5b1P/2k1NQP1/2P1B3/PPP2P2/2KR3R w - -" e4d6 36 | expect mate.exp "r2q1bnr/pp1bk1pp/4p3/3pPp1B/3n4/6Q1/PPP2PPP/R1B1K2R w KQ -" g3a3 37 | expect mate.exp "r2q1nr1/1b5k/p5p1/2pP1BPp/8/1P3N1Q/PB5P/4R1K1 w - -" h3h5 38 | expect mate.exp "r2qk2r/pp1n2p1/2p1pn1p/3p4/3P4/B1PB1N2/P1P2PPP/R2Q2K1 w kq -" d3g6 39 | expect mate.exp "r2qkb1r/1bp2ppp/p4n2/3p4/8/5p2/PPP1BPPP/RNBQR1K1 w kq -" e2b5 40 | expect mate.exp "r3kb1r/1p3ppp/8/3np1B1/1p6/8/PP3PPP/R3KB1R w KQkq -" f1b5 41 | expect mate.exp "r3rqkb/pp1b1pnp/2p1p1p1/4P1B1/2B1N1P1/5N1P/PPP2P2/2KR3R w - -" e4f6 42 | expect mate.exp "r5r1/pQ5p/1qp2R2/2k1p3/P3P3/2PP4/2P3PP/6K1 w - -" b7e7 43 | expect mate.exp "r5r1/pppb1p2/3npkNp/8/3P2P1/2PB4/P1P1Q2P/6K1 w - -" e2e5 44 | expect mate.exp "r7/1p4b1/p3Bp2/6pp/1PNN4/1P1k4/KB4P1/6q1 w - -" e6f5 45 | expect mate.exp "rb6/k1p4R/P1P5/PpK5/8/8/8/5B2 w - b6" a5b6 46 | expect mate.exp "rk5r/p1q2ppp/Qp1B1n2/2p5/2P5/6P1/PP3PBP/4R1K1 w - -" a6b7 47 | expect mate.exp "rn1qkbnr/ppp2ppp/8/8/4Np2/5b2/PPPPQ1PP/R1B1KB1R w KQkq -" e4f6 48 | expect mate.exp "rn6/pQ5p/6r1/k1N1P3/3P4/4b1p1/1PP1K1P1/8 w - -" b2b4 49 | expect mate.exp "rnb3r1/pp1pb2p/2pk1nq1/6BQ/8/8/PPP3PP/4RRK1 w - -" g5f4 50 | expect mate.exp "rnbq3r/pppp2pp/1b6/8/1P2k3/8/PBPP1PPP/R2QK2R w KQ -" d1f3 51 | expect mate.exp "rnbqkb1r/ppp2ppp/8/3p4/8/2n2N2/PP2BPPP/R1B1R1K1 w kq -" e2b5 52 | expect mate.exp "rnbqkr2/pp1pbN1p/8/3p4/2B5/2p5/P4PPP/R3R1K1 w q -" f7d6 53 | expect mate.exp "1k1r4/2p2ppp/8/8/Qb6/2R1Pn2/PP2KPPP/3r4 b - -" f3g1 54 | expect mate.exp "1n4rk/1bp2Q1p/p2p4/1p2p3/5N1N/1P1P3P/1PP2p1K/8 b - -" f2f1n 55 | expect mate.exp "1r3r1k/6pp/6b1/pBp3B1/Pn1N2P1/4p2P/1P6/2KR3R b - -" b4a2 56 | expect mate.exp "2B1nrk1/p5bp/1p1p4/4p3/8/1NPKnq1P/PP1Q4/R6R b - -" e5e4 57 | expect mate.exp "2k2r2/1pp4P/p2n4/2Nn2R1/1P1P4/P1RK2Q1/1r4b1/8 b - -" g2f1 58 | expect mate.exp "2k5/pp4pp/1b6/2nP4/5pb1/P7/1P2QKPP/5R2 b - -" c5d3 59 | expect mate.exp "2k5/ppp2p2/7q/6p1/2Nb1p2/1B3Kn1/PP2Q1P1/8 b - -" h6h5 60 | expect mate.exp "3r2k1/ppp2ppp/6Q1/b7/3n1B2/2p3n1/P4PPP/RN3RK1 b - -" d4e2 61 | expect mate.exp "3rkr2/5p2/b1p2p2/4pP1P/p3P1Q1/b1P5/B1K2RP1/2RNq3 b - -" a6d3 62 | expect mate.exp "4r1k1/pp3ppp/6q1/3p4/2bP1n2/P1Q2B2/1P3PPP/6KR b - -" f4h3 63 | expect mate.exp "5rk1/5ppp/p7/1pb1P3/7R/7P/PP2b2P/R1B4K b - -" e2f3 64 | expect mate.exp "6k1/5qpp/pn1p2N1/B1p2p1P/Q3p3/2K1P2R/1r2BPP1/1r5R b - -" b6a4 65 | expect mate.exp "6rk/8/8/8/6q1/6Qb/7P/5BKR b - -" g4d4 66 | expect mate.exp "7r/6k1/8/7q/6p1/6P1/5PK1/3R2Q1 b - -" h5h3 67 | expect mate.exp "8/1k5q/8/4Q3/8/1n1P4/NP6/1K6 b - -" h7d3 68 | expect mate.exp "8/2N3p1/5b2/k1B2P2/pP4R1/8/K1nn4/8 b - b3" a4b3 69 | expect mate.exp "8/8/pp3Q2/7k/5Pp1/P1P3K1/3r3p/8 b - -" h2h1n 70 | expect mate.exp "8/p2k4/1p5R/2pp2R1/4n3/P2K4/1PP1N3/5r2 b - -" f1f3 71 | expect mate.exp "8/p4pkp/8/3B1b2/3b1ppP/P1N1r1n1/1PP3PR/R4QK1 b - -" e3e1 72 | expect mate.exp "r1b1k2r/ppp1qppp/5B2/3Pn3/8/8/PPP2PPP/RN1QKB1R b KQkq -" e5f3 73 | expect mate.exp "r1b1kbnr/pppp1Npp/8/8/3nq3/8/PPPPBP1P/RNBQKR2 b Qkq -" d4f3 74 | expect mate.exp "r1b1q1kr/ppNnb1pp/5n2/8/3P4/8/PPP2PPP/R1BQKB1R b KQ -" e7b4 75 | expect mate.exp "r1b3k1/pppn3p/3p2rb/3P1K2/2P1P3/2N2P2/PP1QB3/R4R2 b - -" d7b8 76 | expect mate.exp "r1b3r1/5k2/1nn1p1p1/3pPp1P/p4P2/Kp3BQN/P1PBN1P1/3R3R b - -" b6c4 77 | expect mate.exp "r1bqk1nr/pppp1ppp/8/2b1P3/3nP3/6P1/PPP1N2P/RNBQKB1R b KQkq -" d4f3 78 | expect mate.exp "r2Bk2r/ppp2p2/3b3p/8/1n1PK1b1/4P3/PPP2pPP/RN1Q1B1R b kq -" f7f5 79 | expect mate.exp "r2qk2r/pp3ppp/2p1p3/5P2/2Qn4/2n5/P2N1PPP/R1B1KB1R b KQkq -" d4c2 80 | expect mate.exp "r2r2k1/ppp2pp1/5q1p/4p3/4bn2/2PB2N1/P1PQ1P1P/R4RK1 b - -" f4h3 81 | expect mate.exp "r3k1nr/p1p2p1p/2pP4/8/7q/7b/PPPP3P/RNBQ2KR b kq -" h4d4 82 | expect mate.exp "r3k3/bppbq2r/p2p3p/3Pp2n/P1N1Pp2/2P2P1P/1PB3PN/R2QR2K b q -" h5g3 83 | expect mate.exp "r4k1N/2p3pp/p7/1pbPn3/6b1/1P1P3P/1PP2qPK/RNB4Q b - -" e5f3 84 | expect mate.exp "r6r/pppk1ppp/8/2b5/2P5/2Nb1N2/PPnK1nPP/1RB2B1R b - -" c5e3 85 | 86 | rm mate.exp 87 | 88 | echo "mate testing OK" --------------------------------------------------------------------------------