├── .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 | 
2 |
3 | # Berserk Chess Engine
4 |
5 |
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"
--------------------------------------------------------------------------------