├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── Tuning.pdf ├── logo.png └── src ├── attacks.c ├── attacks.h ├── bench.csv ├── bitboards.c ├── bitboards.h ├── board.c ├── board.h ├── cmdline.c ├── cmdline.h ├── evaluate.c ├── evaluate.h ├── history.c ├── history.h ├── incbin ├── LICENSE └── incbin.h ├── makefile ├── masks.c ├── masks.h ├── move.c ├── move.h ├── movegen.c ├── movegen.h ├── movepicker.c ├── movepicker.h ├── network.c ├── network.h ├── nnue ├── accumulator.c ├── accumulator.h ├── archs │ ├── avx.h │ ├── avx2.h │ └── ssse3.h ├── nnue.c ├── nnue.h ├── types.h └── utils.h ├── perft ├── fischer.epd ├── perft.py └── standard.epd ├── pgn.c ├── pgn.h ├── pyrrhic ├── LICENSE ├── stdendian.h ├── tbchess.c ├── tbconfig.h ├── tbprobe.c └── tbprobe.h ├── search.c ├── search.h ├── syzygy.c ├── syzygy.h ├── thread.c ├── thread.h ├── timeman.c ├── timeman.h ├── transposition.c ├── transposition.h ├── tuner.c ├── tuner.h ├── types.h ├── uci.c ├── uci.h ├── weights └── pknet_224x32x2.net ├── windows.c ├── windows.h ├── zobrist.c └── zobrist.h /.gitattributes: -------------------------------------------------------------------------------- 1 | *.h linguist-language=C 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | *.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | With the introduction of NNUE, the cost of training networks and competing with top engines, while still making an original effort, has gone up significantly. As a result, Ethereal moved to being commercial with the release of NNUE, a move which was met by support from the community, in order to ensure I could continue to work on the project. To purchase a copy of Ethereal, you can head to [the official webpage](http://chess.grantnet.us/Ethereal), to read more. 2 | 3 | # Ethereal 4 | 5 | Ethereal is a UCI-compliant chess engine operating under the alpha-beta framework, paired with a Neural Network for positional evaluations. Ethereal is inspired by a number of open source projects and aims to serve as both a high-end engine and reference for other authors. The project strives to keep the source and its ideas, however complex, clean and digestible. To read more about some of the techniques used in Ethereal, see [Ethereal's Chess Programming Wiki Page](https://www.chessprogramming.org/Ethereal) 6 | 7 | # Development 8 | 9 | The primary testing platform for Ethereal is [OpenBench](https://github.com/AndyGrant/OpenBench), a Fishtest inspired framework. OpenBench is a simplified and generalized framework, allowing many other engines to share the same framework for testing. [The primary instance of OpenBench can be found here.](http://chess.grantnet.us/) This instance houses development for a dozen or more engines, while private instances of OpenBench exist for many other engines in the open source community. 10 | 11 | All versions of Ethereal in this repository are considered official releases, and are given a unique version number which can be found in ``uci.c``, or by using the ``uci`` command inside the engine. 12 | 13 | The strength of Ethereal can be tracked by following various rating lists, including [CCRL's Blitz List](https://ccrl.chessdom.com/ccrl/404/), [CCRL's 40/15 List](https://ccrl.chessdom.com/ccrl/4040/), [CCRL's FRC List](https://ccrl.chessdom.com/ccrl/404FRC/), many of [FastGM's Lists](http://www.fastgm.de/#), as well as at [CEGT](http://cegt.net/) and [SPCC](https://www.sp-cc.de/). 14 | 15 | # A Note about the GPLv3 16 | 17 | Ethereal, as well as the projects that support Ethereal like [OpenBench](https://github.com/AndyGrant/OpenBench) and [NNTrainer](https://github.com/AndyGrant/NNTrainer) are licensed under the GPLv3. The GPLv3 gives you, the user, the right to have access to the source code of the engine, the right to redistribute the GPLv3'ed portions of the project, as well as the right to reuse the Ethereal source in any capacity so long as you continue to comply with the GPLv3's license. 18 | 19 | Open Source chess engines have accelerated the development of computer chess in immeasurable ways. If not for the early adopters of the Open Source methods, computer chess would not be what it is today. Powerful programs like Stockfish simply would not exist in their current forms. All of this is possible because the authors have empowered users by granting them rights to the code, only asking that you carry on propagating the licenses attached to their code. This is a small ask, for such a great gift, and yet we live in a time where that gift is not appreciated by some, and worse taken advantage of. 20 | 21 | Ethereal shares in the collective knowledge generated and maintained by the Computer Chess Community. However, there are three elements of Ethereal for which explicit attribution is necessary for a good faith effort at carrying out the GPLv3. This attribution has been given when the code was committed, but is restated here for clarity: Ethereal makes use of the universally adopted [Syzygy Tablebases](https://github.com/syzygy1/tb), a project under the GPLv2 and other compatible licenses. Ethereal makes use of a forked version of [Fathom](https://github.com/jdart1/Fathom), a project under the MIT license, used to implement Syzygy. Lastly, Ethereal shares a chunk of code for dealing with the Windows Operating System, which was originally written by Texel author Peter Österlund, and has since been refined and improved in various Stockfish forks, once again under GPLv3 compatible licenses. 22 | 23 | # Configuration 24 | 25 | Ethereal supports a number of relatively standard options. Definitions and recommendations are below. 26 | Most GUIs should support a method to set each option. If they do not, then refer to the UCI specification. 27 | 28 | ### Hash 29 | 30 | The size of the hash table in megabytes. For analysis the more hash given the better. For testing against other engines, just be sure to give each engine the same amount of Hash. 64MB/thread/minute is generally a good value. For testing against non-classical engines, reach out to me and I will make a recommendation. 31 | 32 | ### Threads 33 | 34 | Number of threads given to Ethereal while moving. Typically the more threads the better. There is some debate as to whether using hyper-threads provides an elo gain. I firmly believe that for Ethereal the answer is yes, and recommend all users make use of the maximum number of threads. 35 | 36 | ### MultiPV 37 | 38 | The number of lines to output for each search iteration. For best performance, MultiPV should be left at the default value of 1 in all cases. This option should only be used for analysis. 39 | 40 | ### MoveOverhead 41 | 42 | The time buffer when playing games under time constraints. If you notice any time losses you should increase the move overhead. Additionally when playing with Syzygy Table bases a larger than default overhead is recommended. 43 | 44 | ### SyzygyPath 45 | 46 | Path to Syzygy table bases. Separate multiple files paths with a semicolon on Windows, and by a colon on Unix-based systems. 47 | 48 | ### SyzygyProbeDepth 49 | 50 | Minimum depth to start probing table bases (although this depth is ignored when a position with a cardinality less than the size of the given table bases is reached). Without a strong SSD, this option may need to be increased from the default of 0. I have a SyzygyProbeDepth of 6 or 8 to be acceptable. 51 | 52 | # Special Thanks 53 | 54 | I would like to thank my previous instructor, Zachary Littrell, for all of his help in my endeavors. He was my Computer Science instructor for two semesters during my senior year of high school. His encouragement, mentoring, and assistance played a vital role in the development of my Computer Science skills. In addition to being a wonderful instructor, he is also an excellent friend. He provided the guidance I needed at such a crucial time in my life, allowing me to pursue Computer Science in a way I never imagined I could. 55 | 56 | 57 | -------------------------------------------------------------------------------- /Tuning.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndyGrant/Ethereal/0e47e9b67f345c75eb965d9fb3e2493b6a11d09a/Tuning.pdf -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndyGrant/Ethereal/0e47e9b67f345c75eb965d9fb3e2493b6a11d09a/logo.png -------------------------------------------------------------------------------- /src/attacks.c: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #ifdef USE_PEXT 23 | #include 24 | #endif 25 | 26 | #include "attacks.h" 27 | #include "bitboards.h" 28 | #include "board.h" 29 | #include "types.h" 30 | 31 | ALIGN64 uint64_t PawnAttacks[COLOUR_NB][SQUARE_NB]; 32 | ALIGN64 uint64_t KnightAttacks[SQUARE_NB]; 33 | ALIGN64 uint64_t BishopAttacks[0x1480]; 34 | ALIGN64 uint64_t RookAttacks[0x19000]; 35 | ALIGN64 uint64_t KingAttacks[SQUARE_NB]; 36 | 37 | ALIGN64 Magic BishopTable[SQUARE_NB]; 38 | ALIGN64 Magic RookTable[SQUARE_NB]; 39 | 40 | static int validCoordinate(int rank, int file) { 41 | return 0 <= rank && rank < RANK_NB 42 | && 0 <= file && file < FILE_NB; 43 | } 44 | 45 | static void setSquare(uint64_t *bb, int rank, int file) { 46 | if (validCoordinate(rank, file)) 47 | *bb |= 1ull << square(rank, file); 48 | } 49 | 50 | static int sliderIndex(uint64_t occupied, Magic *table) { 51 | #ifdef USE_PEXT 52 | return _pext_u64(occupied, table->mask); 53 | #else 54 | return ((occupied & table->mask) * table->magic) >> table->shift; 55 | #endif 56 | } 57 | 58 | static uint64_t sliderAttacks(int sq, uint64_t occupied, const int delta[4][2]) { 59 | 60 | int rank, file, dr, df; 61 | uint64_t result = 0ull; 62 | 63 | for (int i = 0; i < 4; i++) { 64 | 65 | dr = delta[i][0], df = delta[i][1]; 66 | 67 | for (rank = rankOf(sq) + dr, file = fileOf(sq) + df; validCoordinate(rank, file); rank += dr, file += df) { 68 | setBit(&result, square(rank, file)); 69 | if (testBit(occupied, square(rank, file))) 70 | break; 71 | } 72 | } 73 | 74 | return result; 75 | } 76 | 77 | static void initSliderAttacks(int sq, Magic *table, uint64_t magic, const int delta[4][2]) { 78 | 79 | uint64_t edges = ((RANK_1 | RANK_8) & ~Ranks[rankOf(sq)]) 80 | | ((FILE_A | FILE_H) & ~Files[fileOf(sq)]); 81 | 82 | uint64_t occupied = 0ull; 83 | 84 | // Init entry for the given square 85 | table[sq].magic = magic; 86 | table[sq].mask = sliderAttacks(sq, 0, delta) & ~edges; 87 | table[sq].shift = 64 - popcount(table[sq].mask); 88 | 89 | // Track the offset as we use up the table 90 | if (sq != SQUARE_NB - 1) 91 | table[sq+1].offset = table[sq].offset + (1 << popcount(table[sq].mask)); 92 | 93 | do { // Init attacks for all occupancy variations 94 | int index = sliderIndex(occupied, &table[sq]); 95 | table[sq].offset[index] = sliderAttacks(sq, occupied, delta); 96 | occupied = (occupied - table[sq].mask) & table[sq].mask; 97 | } while (occupied); 98 | } 99 | 100 | 101 | void initAttacks() { 102 | 103 | const int PawnDelta[2][2] = {{ 1,-1}, { 1, 1}}; 104 | const int KnightDelta[8][2] = {{-2,-1}, {-2, 1}, {-1,-2}, {-1, 2},{ 1,-2}, { 1, 2}, { 2,-1}, { 2, 1}}; 105 | const int KingDelta[8][2] = {{-1,-1}, {-1, 0}, {-1, 1}, { 0,-1},{ 0, 1}, { 1,-1}, { 1, 0}, { 1, 1}}; 106 | const int BishopDelta[4][2] = {{-1,-1}, {-1, 1}, { 1,-1}, { 1, 1}}; 107 | const int RookDelta[4][2] = {{-1, 0}, { 0,-1}, { 0, 1}, { 1, 0}}; 108 | 109 | // First square has initial offset 110 | BishopTable[0].offset = BishopAttacks; 111 | RookTable[0].offset = RookAttacks; 112 | 113 | // Init attack tables for Pawns 114 | for (int sq = 0; sq < 64; sq++) { 115 | for (int dir = 0; dir < 2; dir++) { 116 | setSquare(&PawnAttacks[WHITE][sq], rankOf(sq) + PawnDelta[dir][0], fileOf(sq) + PawnDelta[dir][1]); 117 | setSquare(&PawnAttacks[BLACK][sq], rankOf(sq) - PawnDelta[dir][0], fileOf(sq) - PawnDelta[dir][1]); 118 | } 119 | } 120 | 121 | // Init attack tables for Knights & Kings 122 | for (int sq = 0; sq < 64; sq++) { 123 | for (int dir = 0; dir < 8; dir++) { 124 | setSquare(&KnightAttacks[sq], rankOf(sq) + KnightDelta[dir][0], fileOf(sq) + KnightDelta[dir][1]); 125 | setSquare( &KingAttacks[sq], rankOf(sq) + KingDelta[dir][0], fileOf(sq) + KingDelta[dir][1]); 126 | } 127 | } 128 | 129 | // Init attack tables for sliding pieces 130 | for (int sq = 0; sq < 64; sq++) { 131 | initSliderAttacks(sq, BishopTable, BishopMagics[sq], BishopDelta); 132 | initSliderAttacks(sq, RookTable, RookMagics[sq], RookDelta); 133 | } 134 | } 135 | 136 | uint64_t pawnAttacks(int colour, int sq) { 137 | assert(0 <= colour && colour < COLOUR_NB); 138 | assert(0 <= sq && sq < SQUARE_NB); 139 | return PawnAttacks[colour][sq]; 140 | } 141 | 142 | uint64_t knightAttacks(int sq) { 143 | assert(0 <= sq && sq < SQUARE_NB); 144 | return KnightAttacks[sq]; 145 | } 146 | 147 | uint64_t bishopAttacks(int sq, uint64_t occupied) { 148 | assert(0 <= sq && sq < SQUARE_NB); 149 | return BishopTable[sq].offset[sliderIndex(occupied, &BishopTable[sq])]; 150 | } 151 | 152 | uint64_t rookAttacks(int sq, uint64_t occupied) { 153 | assert(0 <= sq && sq < SQUARE_NB); 154 | return RookTable[sq].offset[sliderIndex(occupied, &RookTable[sq])]; 155 | } 156 | 157 | uint64_t queenAttacks(int sq, uint64_t occupied) { 158 | assert(0 <= sq && sq < SQUARE_NB); 159 | return bishopAttacks(sq, occupied) | rookAttacks(sq, occupied); 160 | } 161 | 162 | uint64_t kingAttacks(int sq) { 163 | assert(0 <= sq && sq < SQUARE_NB); 164 | return KingAttacks[sq]; 165 | } 166 | 167 | 168 | uint64_t pawnLeftAttacks(uint64_t pawns, uint64_t targets, int colour) { 169 | return targets & (colour == WHITE ? (pawns << 7) & ~FILE_H 170 | : (pawns >> 7) & ~FILE_A); 171 | } 172 | 173 | uint64_t pawnRightAttacks(uint64_t pawns, uint64_t targets, int colour) { 174 | return targets & (colour == WHITE ? (pawns << 9) & ~FILE_A 175 | : (pawns >> 9) & ~FILE_H); 176 | } 177 | 178 | uint64_t pawnAttackSpan(uint64_t pawns, uint64_t targets, int colour) { 179 | return pawnLeftAttacks(pawns, targets, colour) 180 | | pawnRightAttacks(pawns, targets, colour); 181 | } 182 | 183 | uint64_t pawnAttackDouble(uint64_t pawns, uint64_t targets, int colour) { 184 | return pawnLeftAttacks(pawns, targets, colour) 185 | & pawnRightAttacks(pawns, targets, colour); 186 | } 187 | 188 | uint64_t pawnAdvance(uint64_t pawns, uint64_t occupied, int colour) { 189 | return ~occupied & (colour == WHITE ? (pawns << 8) : (pawns >> 8)); 190 | } 191 | 192 | uint64_t pawnEnpassCaptures(uint64_t pawns, int epsq, int colour) { 193 | return epsq == -1 ? 0ull : pawnAttacks(!colour, epsq) & pawns; 194 | } 195 | 196 | 197 | int squareIsAttacked(Board *board, int colour, int sq) { 198 | 199 | uint64_t enemy = board->colours[!colour]; 200 | uint64_t occupied = board->colours[ colour] | enemy; 201 | 202 | uint64_t enemyPawns = enemy & board->pieces[PAWN ]; 203 | uint64_t enemyKnights = enemy & board->pieces[KNIGHT]; 204 | uint64_t enemyBishops = enemy & (board->pieces[BISHOP] | board->pieces[QUEEN]); 205 | uint64_t enemyRooks = enemy & (board->pieces[ROOK ] | board->pieces[QUEEN]); 206 | uint64_t enemyKings = enemy & board->pieces[KING ]; 207 | 208 | // Check for attacks to this square. While this function has the same 209 | // result as using attackersToSquare(board, colour, sq) != 0ull, this 210 | // has a better running time by avoiding some slider move lookups. The 211 | // speed gain is easily proven using the provided PERFT suite 212 | 213 | return (pawnAttacks(colour, sq) & enemyPawns) 214 | || (knightAttacks(sq) & enemyKnights) 215 | || (enemyBishops && (bishopAttacks(sq, occupied) & enemyBishops)) 216 | || (enemyRooks && (rookAttacks(sq, occupied) & enemyRooks)) 217 | || (kingAttacks(sq) & enemyKings); 218 | } 219 | 220 | uint64_t allAttackersToSquare(Board *board, uint64_t occupied, int sq) { 221 | 222 | // When performing a static exchange evaluation we need to find all 223 | // attacks to a given square, but we also are given an updated occupied 224 | // bitboard, which will likely not match the actual board, as pieces are 225 | // removed during the iterations in the static exchange evaluation 226 | 227 | return (pawnAttacks(WHITE, sq) & board->colours[BLACK] & board->pieces[PAWN]) 228 | | (pawnAttacks(BLACK, sq) & board->colours[WHITE] & board->pieces[PAWN]) 229 | | (knightAttacks(sq) & board->pieces[KNIGHT]) 230 | | (bishopAttacks(sq, occupied) & (board->pieces[BISHOP] | board->pieces[QUEEN])) 231 | | (rookAttacks(sq, occupied) & (board->pieces[ROOK] | board->pieces[QUEEN])) 232 | | (kingAttacks(sq) & board->pieces[KING]); 233 | } 234 | 235 | uint64_t allAttackedSquares(Board *board, int colour) { 236 | 237 | uint64_t friendly = board->colours[ colour]; 238 | uint64_t occupied = board->colours[!colour] | friendly; 239 | 240 | uint64_t pawns = friendly & board->pieces[PAWN ]; 241 | uint64_t knights = friendly & board->pieces[KNIGHT]; 242 | uint64_t bishops = friendly & (board->pieces[BISHOP] | board->pieces[QUEEN]); 243 | uint64_t rooks = friendly & (board->pieces[ROOK ] | board->pieces[QUEEN]); 244 | uint64_t kings = friendly & board->pieces[KING ]; 245 | 246 | uint64_t threats = pawnAttackSpan(pawns, ~0ULL, colour); 247 | while (knights) threats |= knightAttacks(poplsb(&knights)); 248 | while (bishops) threats |= bishopAttacks(poplsb(&bishops), occupied); 249 | while (rooks) threats |= rookAttacks(poplsb(&rooks), occupied); 250 | while (kings) threats |= kingAttacks(poplsb(&kings)); 251 | 252 | return threats; 253 | } 254 | 255 | uint64_t attackersToKingSquare(Board *board) { 256 | 257 | // Wrapper for allAttackersToSquare() for use in check detection 258 | int kingsq = getlsb(board->colours[board->turn] & board->pieces[KING]); 259 | uint64_t occupied = board->colours[WHITE] | board->colours[BLACK]; 260 | return allAttackersToSquare(board, occupied, kingsq) & board->colours[!board->turn]; 261 | } 262 | 263 | uint64_t discoveredAttacks(Board *board, int sq, int US) { 264 | 265 | uint64_t enemy = board->colours[!US]; 266 | uint64_t occupied = board->colours[ US] | enemy; 267 | 268 | uint64_t rAttacks = rookAttacks(sq, occupied); 269 | uint64_t bAttacks = bishopAttacks(sq, occupied); 270 | 271 | uint64_t rooks = (enemy & board->pieces[ROOK ]) & ~rAttacks; 272 | uint64_t bishops = (enemy & board->pieces[BISHOP]) & ~bAttacks; 273 | 274 | return ( rooks & rookAttacks(sq, occupied & ~rAttacks)) 275 | | (bishops & bishopAttacks(sq, occupied & ~bAttacks)); 276 | } -------------------------------------------------------------------------------- /src/attacks.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | 23 | #include "types.h" 24 | 25 | struct Magic { 26 | uint64_t magic; 27 | uint64_t mask; 28 | uint64_t shift; 29 | uint64_t *offset; 30 | }; 31 | 32 | void initAttacks(); 33 | 34 | uint64_t pawnAttacks(int colour, int sq); 35 | uint64_t knightAttacks(int sq); 36 | uint64_t bishopAttacks(int sq, uint64_t occupied); 37 | uint64_t rookAttacks(int sq, uint64_t occupied); 38 | uint64_t queenAttacks(int sq, uint64_t occupied); 39 | uint64_t kingAttacks(int sq); 40 | 41 | uint64_t pawnLeftAttacks(uint64_t pawns, uint64_t targets, int colour); 42 | uint64_t pawnRightAttacks(uint64_t pawns, uint64_t targets, int colour); 43 | uint64_t pawnAttackSpan(uint64_t pawns, uint64_t targets, int colour); 44 | uint64_t pawnAttackDouble(uint64_t pawns, uint64_t targets, int colour); 45 | uint64_t pawnAdvance(uint64_t pawns, uint64_t occupied, int colour); 46 | uint64_t pawnEnpassCaptures(uint64_t pawns, int epsq, int colour); 47 | 48 | int squareIsAttacked(Board *board, int colour, int sq); 49 | uint64_t attackersToSquare(Board *board, int colour, int sq); 50 | uint64_t allAttackedSquares(Board *board, int colour); 51 | uint64_t allAttackersToSquare(Board *board, uint64_t occupied, int sq); 52 | uint64_t attackersToKingSquare(Board *board); 53 | 54 | uint64_t discoveredAttacks(Board *board, int sq, int US); 55 | 56 | static const uint64_t RookMagics[SQUARE_NB] = { 57 | 0xA180022080400230ull, 0x0040100040022000ull, 0x0080088020001002ull, 0x0080080280841000ull, 58 | 0x4200042010460008ull, 0x04800A0003040080ull, 0x0400110082041008ull, 0x008000A041000880ull, 59 | 0x10138001A080C010ull, 0x0000804008200480ull, 0x00010011012000C0ull, 0x0022004128102200ull, 60 | 0x000200081201200Cull, 0x202A001048460004ull, 0x0081000100420004ull, 0x4000800380004500ull, 61 | 0x0000208002904001ull, 0x0090004040026008ull, 0x0208808010002001ull, 0x2002020020704940ull, 62 | 0x8048010008110005ull, 0x6820808004002200ull, 0x0A80040008023011ull, 0x00B1460000811044ull, 63 | 0x4204400080008EA0ull, 0xB002400180200184ull, 0x2020200080100380ull, 0x0010080080100080ull, 64 | 0x2204080080800400ull, 0x0000A40080360080ull, 0x02040604002810B1ull, 0x008C218600004104ull, 65 | 0x8180004000402000ull, 0x488C402000401001ull, 0x4018A00080801004ull, 0x1230002105001008ull, 66 | 0x8904800800800400ull, 0x0042000C42003810ull, 0x008408110400B012ull, 0x0018086182000401ull, 67 | 0x2240088020C28000ull, 0x001001201040C004ull, 0x0A02008010420020ull, 0x0010003009010060ull, 68 | 0x0004008008008014ull, 0x0080020004008080ull, 0x0282020001008080ull, 0x50000181204A0004ull, 69 | 0x48FFFE99FECFAA00ull, 0x48FFFE99FECFAA00ull, 0x497FFFADFF9C2E00ull, 0x613FFFDDFFCE9200ull, 70 | 0xFFFFFFE9FFE7CE00ull, 0xFFFFFFF5FFF3E600ull, 0x0010301802830400ull, 0x510FFFF5F63C96A0ull, 71 | 0xEBFFFFB9FF9FC526ull, 0x61FFFEDDFEEDAEAEull, 0x53BFFFEDFFDEB1A2ull, 0x127FFFB9FFDFB5F6ull, 72 | 0x411FFFDDFFDBF4D6ull, 0x0801000804000603ull, 0x0003FFEF27EEBE74ull, 0x7645FFFECBFEA79Eull, 73 | }; 74 | 75 | static const uint64_t BishopMagics[SQUARE_NB] = { 76 | 0xFFEDF9FD7CFCFFFFull, 0xFC0962854A77F576ull, 0x5822022042000000ull, 0x2CA804A100200020ull, 77 | 0x0204042200000900ull, 0x2002121024000002ull, 0xFC0A66C64A7EF576ull, 0x7FFDFDFCBD79FFFFull, 78 | 0xFC0846A64A34FFF6ull, 0xFC087A874A3CF7F6ull, 0x1001080204002100ull, 0x1810080489021800ull, 79 | 0x0062040420010A00ull, 0x5028043004300020ull, 0xFC0864AE59B4FF76ull, 0x3C0860AF4B35FF76ull, 80 | 0x73C01AF56CF4CFFBull, 0x41A01CFAD64AAFFCull, 0x040C0422080A0598ull, 0x4228020082004050ull, 81 | 0x0200800400E00100ull, 0x020B001230021040ull, 0x7C0C028F5B34FF76ull, 0xFC0A028E5AB4DF76ull, 82 | 0x0020208050A42180ull, 0x001004804B280200ull, 0x2048020024040010ull, 0x0102C04004010200ull, 83 | 0x020408204C002010ull, 0x02411100020080C1ull, 0x102A008084042100ull, 0x0941030000A09846ull, 84 | 0x0244100800400200ull, 0x4000901010080696ull, 0x0000280404180020ull, 0x0800042008240100ull, 85 | 0x0220008400088020ull, 0x04020182000904C9ull, 0x0023010400020600ull, 0x0041040020110302ull, 86 | 0xDCEFD9B54BFCC09Full, 0xF95FFA765AFD602Bull, 0x1401210240484800ull, 0x0022244208010080ull, 87 | 0x1105040104000210ull, 0x2040088800C40081ull, 0x43FF9A5CF4CA0C01ull, 0x4BFFCD8E7C587601ull, 88 | 0xFC0FF2865334F576ull, 0xFC0BF6CE5924F576ull, 0x80000B0401040402ull, 0x0020004821880A00ull, 89 | 0x8200002022440100ull, 0x0009431801010068ull, 0xC3FFB7DC36CA8C89ull, 0xC3FF8A54F4CA2C89ull, 90 | 0xFFFFFCFCFD79EDFFull, 0xFC0863FCCB147576ull, 0x040C000022013020ull, 0x2000104000420600ull, 91 | 0x0400000260142410ull, 0x0800633408100500ull, 0xFC087E8E4BB2F736ull, 0x43FF9E4EF4CA2C89ull, 92 | }; 93 | -------------------------------------------------------------------------------- /src/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", 51 | -------------------------------------------------------------------------------- /src/bitboards.c: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "bitboards.h" 25 | #include "types.h" 26 | 27 | const uint64_t Files[FILE_NB] = {FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H}; 28 | const uint64_t Ranks[RANK_NB] = {RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8}; 29 | 30 | int fileOf(int sq) { 31 | assert(0 <= sq && sq < SQUARE_NB); 32 | return sq % FILE_NB; 33 | } 34 | 35 | int mirrorFile(int file) { 36 | static const int Mirror[] = {0,1,2,3,3,2,1,0}; 37 | assert(0 <= file && file < FILE_NB); 38 | return Mirror[file]; 39 | } 40 | 41 | int rankOf(int sq) { 42 | assert(0 <= sq && sq < SQUARE_NB); 43 | return sq / FILE_NB; 44 | } 45 | 46 | int relativeRankOf(int colour, int sq) { 47 | assert(0 <= colour && colour < COLOUR_NB); 48 | assert(0 <= sq && sq < SQUARE_NB); 49 | return colour == WHITE ? rankOf(sq) : 7 - rankOf(sq); 50 | } 51 | 52 | int square(int rank, int file) { 53 | assert(0 <= rank && rank < RANK_NB); 54 | assert(0 <= file && file < FILE_NB); 55 | return rank * FILE_NB + file; 56 | } 57 | 58 | int relativeSquare(int colour, int sq) { 59 | assert(0 <= colour && colour < COLOUR_NB); 60 | assert(0 <= sq && sq < SQUARE_NB); 61 | return square(relativeRankOf(colour, sq), fileOf(sq)); 62 | } 63 | 64 | int relativeSquare32(int colour, int sq) { 65 | assert(0 <= colour && colour < COLOUR_NB); 66 | assert(0 <= sq && sq < SQUARE_NB); 67 | return 4 * relativeRankOf(colour, sq) + mirrorFile(fileOf(sq)); 68 | } 69 | 70 | uint64_t squaresOfMatchingColour(int sq) { 71 | assert(0 <= sq && sq < SQUARE_NB); 72 | return testBit(WHITE_SQUARES, sq) ? WHITE_SQUARES : BLACK_SQUARES; 73 | } 74 | 75 | int frontmost(int colour, uint64_t bb) { 76 | assert(0 <= colour && colour < COLOUR_NB); 77 | return colour == WHITE ? getmsb(bb) : getlsb(bb); 78 | } 79 | 80 | int backmost(int colour, uint64_t bb) { 81 | assert(0 <= colour && colour < COLOUR_NB); 82 | return colour == WHITE ? getlsb(bb) : getmsb(bb); 83 | } 84 | 85 | int popcount(uint64_t bb) { 86 | return __builtin_popcountll(bb); 87 | } 88 | 89 | int getlsb(uint64_t bb) { 90 | assert(bb); // lsb(0) is undefined 91 | return __builtin_ctzll(bb); 92 | } 93 | 94 | int getmsb(uint64_t bb) { 95 | assert(bb); // msb(0) is undefined 96 | return __builtin_clzll(bb) ^ 63; 97 | } 98 | 99 | int poplsb(uint64_t *bb) { 100 | int lsb = getlsb(*bb); 101 | *bb &= *bb - 1; 102 | return lsb; 103 | } 104 | 105 | int popmsb(uint64_t *bb) { 106 | int msb = getmsb(*bb); 107 | *bb ^= 1ull << msb; 108 | return msb; 109 | } 110 | 111 | bool several(uint64_t bb) { 112 | return bb & (bb - 1); 113 | } 114 | 115 | bool onlyOne(uint64_t bb) { 116 | return bb && !several(bb); 117 | } 118 | 119 | void setBit(uint64_t *bb, int i) { 120 | assert(!testBit(*bb, i)); 121 | *bb ^= 1ull << i; 122 | } 123 | 124 | void clearBit(uint64_t *bb, int i) { 125 | assert(testBit(*bb, i)); 126 | *bb ^= 1ull << i; 127 | } 128 | 129 | bool testBit(uint64_t bb, int i) { 130 | assert(0 <= i && i < SQUARE_NB); 131 | return bb & (1ull << i); 132 | } 133 | 134 | void printBitboard(uint64_t bb) { 135 | 136 | for (int rank = 7; rank >= 0; rank--) { 137 | char line[] = ". . . . . . . ."; 138 | 139 | for (int file = 0; file < FILE_NB; file++) 140 | if (testBit(bb, square(rank, file))) 141 | line[2 * file] = 'X'; 142 | 143 | printf("%s\n", line); 144 | } 145 | 146 | printf("\n"); 147 | } 148 | -------------------------------------------------------------------------------- /src/bitboards.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | 24 | #include "types.h" 25 | 26 | enum { 27 | RANK_1 = 0x00000000000000FFull, 28 | RANK_2 = 0x000000000000FF00ull, 29 | RANK_3 = 0x0000000000FF0000ull, 30 | RANK_4 = 0x00000000FF000000ull, 31 | RANK_5 = 0x000000FF00000000ull, 32 | RANK_6 = 0x0000FF0000000000ull, 33 | RANK_7 = 0x00FF000000000000ull, 34 | RANK_8 = 0xFF00000000000000ull, 35 | 36 | FILE_A = 0x0101010101010101ull, 37 | FILE_B = 0x0202020202020202ull, 38 | FILE_C = 0x0404040404040404ull, 39 | FILE_D = 0x0808080808080808ull, 40 | FILE_E = 0x1010101010101010ull, 41 | FILE_F = 0x2020202020202020ull, 42 | FILE_G = 0x4040404040404040ull, 43 | FILE_H = 0x8080808080808080ull, 44 | 45 | WHITE_SQUARES = 0x55AA55AA55AA55AAull, 46 | BLACK_SQUARES = 0xAA55AA55AA55AA55ull, 47 | 48 | LONG_DIAGONALS = 0x8142241818244281ull, 49 | CENTER_SQUARES = 0x0000001818000000ull, 50 | CENTER_BIG = 0x00003C3C3C3C0000ull, 51 | 52 | LEFT_FLANK = FILE_A | FILE_B | FILE_C | FILE_D, 53 | RIGHT_FLANK = FILE_E | FILE_F | FILE_G | FILE_H, 54 | 55 | PROMOTION_RANKS = RANK_1 | RANK_8 56 | }; 57 | 58 | extern const uint64_t Files[FILE_NB]; 59 | extern const uint64_t Ranks[RANK_NB]; 60 | 61 | int fileOf(int sq); 62 | int mirrorFile(int file); 63 | int rankOf(int sq); 64 | int relativeRankOf(int colour, int sq); 65 | int square(int rank, int file); 66 | int relativeSquare(int colour, int sq); 67 | int relativeSquare32(int colour, int sq); 68 | uint64_t squaresOfMatchingColour(int sq); 69 | 70 | int frontmost(int colour, uint64_t bb); 71 | int backmost(int colour, uint64_t bb); 72 | 73 | int popcount(uint64_t bb); 74 | int getlsb(uint64_t bb); 75 | int getmsb(uint64_t bb); 76 | int poplsb(uint64_t *bb); 77 | int popmsb(uint64_t *bb); 78 | bool several(uint64_t bb); 79 | bool onlyOne(uint64_t bb); 80 | 81 | void setBit(uint64_t *bb, int i); 82 | void clearBit(uint64_t *bb, int i); 83 | bool testBit(uint64_t bb, int i); 84 | 85 | void printBitboard(uint64_t bb); 86 | -------------------------------------------------------------------------------- /src/board.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | extern const char *PieceLabel[COLOUR_NB]; 24 | 25 | struct Board { 26 | uint8_t squares[SQUARE_NB]; 27 | uint64_t pieces[8], colours[3]; 28 | uint64_t hash, pkhash, kingAttackers, threats; 29 | uint64_t castleRooks, castleMasks[SQUARE_NB]; 30 | int turn, epSquare, halfMoveCounter, fullMoveCounter; 31 | int psqtmat, numMoves, chess960; 32 | uint64_t history[8192]; 33 | Thread *thread; 34 | }; 35 | 36 | struct Undo { 37 | uint64_t hash, pkhash, kingAttackers, threats, castleRooks; 38 | int epSquare, halfMoveCounter, psqtmat, capturePiece; 39 | }; 40 | 41 | void squareToString(int sq, char *str); 42 | void boardFromFEN(Board *board, const char *fen, int chess960); 43 | void boardToFEN(Board *board, char *fen); 44 | void printBoard(Board *board); 45 | int boardHasNonPawnMaterial(Board *board, int turn); 46 | int boardIsDrawn(Board *board, int height); 47 | int boardDrawnByFiftyMoveRule(Board *board); 48 | int boardDrawnByRepetition(Board *board, int height); 49 | int boardDrawnByInsufficientMaterial(Board *board); 50 | 51 | uint64_t perft(Board *board, int depth); 52 | -------------------------------------------------------------------------------- /src/cmdline.c: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "bitboards.h" 25 | #include "board.h" 26 | #include "cmdline.h" 27 | #include "move.h" 28 | #include "pgn.h" 29 | #include "search.h" 30 | #include "thread.h" 31 | #include "timeman.h" 32 | #include "transposition.h" 33 | #include "tuner.h" 34 | #include "uci.h" 35 | 36 | #include "nnue/nnue.h" 37 | 38 | static void runBenchmark(int argc, char **argv) { 39 | 40 | static const char *Benchmarks[] = { 41 | #include "bench.csv" 42 | "" 43 | }; 44 | 45 | Board board; 46 | Thread *threads; 47 | Limits limits = {0}; 48 | 49 | int scores[256]; 50 | double times[256]; 51 | uint64_t nodes[256]; 52 | uint16_t bestMoves[256]; 53 | uint16_t ponderMoves[256]; 54 | 55 | double time; 56 | uint64_t totalNodes = 0ull; 57 | 58 | int depth = argc > 2 ? atoi(argv[2]) : 13; 59 | int nthreads = argc > 3 ? atoi(argv[3]) : 1; 60 | int megabytes = argc > 4 ? atoi(argv[4]) : 16; 61 | 62 | if (argc > 5) { 63 | nnue_init(argv[5]); 64 | printf("info string set EvalFile to %s\n", argv[5]); 65 | } 66 | 67 | tt_init(nthreads, megabytes); 68 | time = get_real_time(); 69 | threads = createThreadPool(nthreads); 70 | 71 | // Initialize a "go depth " search 72 | limits.multiPV = 1; 73 | limits.limitedByDepth = 1; 74 | limits.depthLimit = depth; 75 | 76 | for (int i = 0; strcmp(Benchmarks[i], ""); i++) { 77 | 78 | // Perform the search on the position 79 | limits.start = get_real_time(); 80 | boardFromFEN(&board, Benchmarks[i], 0); 81 | getBestMove(threads, &board, &limits, &bestMoves[i], &ponderMoves[i], &scores[i]); 82 | 83 | // Stat collection for later printing 84 | times[i] = get_real_time() - limits.start; 85 | nodes[i] = nodesSearchedThreadPool(threads); 86 | 87 | tt_clear(nthreads); // Reset TT between searches 88 | } 89 | 90 | printf("\n===============================================================================\n"); 91 | 92 | for (int i = 0; strcmp(Benchmarks[i], ""); i++) { 93 | 94 | // Convert moves to typical UCI notation 95 | char bestStr[6], ponderStr[6]; 96 | moveToString(bestMoves[i], bestStr, 0); 97 | moveToString(ponderMoves[i], ponderStr, 0); 98 | 99 | // Log all collected information for the current position 100 | printf("[# %2d] %5d cp Best:%6s Ponder:%6s %12d nodes %12d nps\n", i + 1, scores[i], 101 | bestStr, ponderStr, (int)nodes[i], (int)(1000.0f * nodes[i] / (times[i] + 1))); 102 | } 103 | 104 | printf("===============================================================================\n"); 105 | 106 | // Report the overall statistics 107 | time = get_real_time() - time; 108 | for (int i = 0; strcmp(Benchmarks[i], ""); i++) totalNodes += nodes[i]; 109 | printf("OVERALL: %47d nodes %12d nps\n", (int)totalNodes, (int)(1000.0f * totalNodes / (time + 1))); 110 | 111 | deleteThreadPool(threads); 112 | } 113 | 114 | static void runEvalBook(int argc, char **argv) { 115 | 116 | int score; 117 | Board board; 118 | char line[256]; 119 | Limits limits = {0}; 120 | uint16_t best, ponder; 121 | double start = get_real_time(); 122 | 123 | FILE *book = fopen(argv[2], "r"); 124 | int depth = argc > 3 ? atoi(argv[3]) : 12; 125 | int nthreads = argc > 4 ? atoi(argv[4]) : 1; 126 | int megabytes = argc > 5 ? atoi(argv[5]) : 2; 127 | 128 | Thread *threads = createThreadPool(nthreads); 129 | 130 | limits.multiPV = 1; 131 | limits.limitedByDepth = 1; 132 | limits.depthLimit = depth; 133 | tt_init(nthreads, megabytes); 134 | 135 | while ((fgets(line, 256, book)) != NULL) { 136 | limits.start = get_real_time(); 137 | boardFromFEN(&board, line, 0); 138 | getBestMove(threads, &board, &limits, &best, &ponder, &score); 139 | resetThreadPool(threads); tt_clear(nthreads); 140 | printf("FEN: %s", line); 141 | } 142 | 143 | printf("Time %dms\n", (int)(get_real_time() - start)); 144 | } 145 | 146 | void handleCommandLine(int argc, char **argv) { 147 | 148 | // Output all the wonderful things we can do from the Command Line 149 | if (argc > 1 && strEquals(argv[1], "--help")) { 150 | printf("\nbench [depth=13] [threads=1] [hash=16] [NNUE=None]"); 151 | printf("\n Run searches on a set of positions to compute a hash\n"); 152 | printf("\nevalbook [input-file] [depth=12] [threads=1] [hash=2]"); 153 | printf("\n Evaluate all positions in a FEN file using various options\n"); 154 | printf("\nnndata [input-file] [output-file]"); 155 | printf("\n Build an nndata from a stripped pgn file\n"); 156 | exit(EXIT_SUCCESS); 157 | } 158 | 159 | // Benchmark is being run from the command line 160 | if (argc > 1 && strEquals(argv[1], "bench")) { 161 | runBenchmark(argc, argv); 162 | exit(EXIT_SUCCESS); 163 | } 164 | 165 | // Evaluate all positions in a datafile to a given depth 166 | if (argc > 2 && strEquals(argv[1], "evalbook")) { 167 | runEvalBook(argc, argv); 168 | exit(EXIT_SUCCESS); 169 | } 170 | 171 | // Convert a PGN file to an nndata file 172 | if (argc > 3 && strEquals(argv[1], "nndata")) { 173 | process_pgn(argv[2], argv[3]); 174 | exit(EXIT_SUCCESS); 175 | } 176 | 177 | // Tuner is being run from the command line 178 | #ifdef TUNE 179 | runTuner(); 180 | exit(EXIT_SUCCESS); 181 | #endif 182 | } 183 | -------------------------------------------------------------------------------- /src/cmdline.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | void handleCommandLine(int argc, char **argv); 22 | -------------------------------------------------------------------------------- /src/evaluate.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | 23 | #include "types.h" 24 | 25 | #ifdef TUNE 26 | #define TRACE (1) 27 | #else 28 | #define TRACE (0) 29 | #endif 30 | 31 | enum { 32 | SCALE_DRAW = 0, 33 | SCALE_OCB_BISHOPS_ONLY = 64, 34 | SCALE_OCB_ONE_KNIGHT = 106, 35 | SCALE_OCB_ONE_ROOK = 96, 36 | SCALE_LONE_QUEEN = 88, 37 | SCALE_NORMAL = 128, 38 | SCALE_LARGE_PAWN_ADV = 144, 39 | }; 40 | 41 | struct EvalTrace { 42 | int PawnValue[COLOUR_NB]; 43 | int KnightValue[COLOUR_NB]; 44 | int BishopValue[COLOUR_NB]; 45 | int RookValue[COLOUR_NB]; 46 | int QueenValue[COLOUR_NB]; 47 | int KingValue[COLOUR_NB]; 48 | int PawnPSQT[SQUARE_NB][COLOUR_NB]; 49 | int KnightPSQT[SQUARE_NB][COLOUR_NB]; 50 | int BishopPSQT[SQUARE_NB][COLOUR_NB]; 51 | int RookPSQT[SQUARE_NB][COLOUR_NB]; 52 | int QueenPSQT[SQUARE_NB][COLOUR_NB]; 53 | int KingPSQT[SQUARE_NB][COLOUR_NB]; 54 | int PawnCandidatePasser[2][8][COLOUR_NB]; 55 | int PawnIsolated[8][COLOUR_NB]; 56 | int PawnStacked[2][8][COLOUR_NB]; 57 | int PawnBackwards[2][8][COLOUR_NB]; 58 | int PawnConnected32[32][COLOUR_NB]; 59 | int KnightOutpost[2][2][COLOUR_NB]; 60 | int KnightBehindPawn[COLOUR_NB]; 61 | int KnightInSiberia[4][COLOUR_NB]; 62 | int KnightMobility[9][COLOUR_NB]; 63 | int BishopPair[COLOUR_NB]; 64 | int BishopRammedPawns[COLOUR_NB]; 65 | int BishopOutpost[2][2][COLOUR_NB]; 66 | int BishopBehindPawn[COLOUR_NB]; 67 | int BishopLongDiagonal[COLOUR_NB]; 68 | int BishopMobility[14][COLOUR_NB]; 69 | int RookFile[2][COLOUR_NB]; 70 | int RookOnSeventh[COLOUR_NB]; 71 | int RookMobility[15][COLOUR_NB]; 72 | int QueenRelativePin[COLOUR_NB]; 73 | int QueenMobility[28][COLOUR_NB]; 74 | int KingPawnFileProximity[8][COLOUR_NB]; 75 | int KingDefenders[12][COLOUR_NB]; 76 | int KingShelter[2][8][8][COLOUR_NB]; 77 | int KingStorm[2][4][8][COLOUR_NB]; 78 | int SafetyKnightWeight[COLOUR_NB]; 79 | int SafetyBishopWeight[COLOUR_NB]; 80 | int SafetyRookWeight[COLOUR_NB]; 81 | int SafetyQueenWeight[COLOUR_NB]; 82 | int SafetyAttackValue[COLOUR_NB]; 83 | int SafetyWeakSquares[COLOUR_NB]; 84 | int SafetyNoEnemyQueens[COLOUR_NB]; 85 | int SafetySafeQueenCheck[COLOUR_NB]; 86 | int SafetySafeRookCheck[COLOUR_NB]; 87 | int SafetySafeBishopCheck[COLOUR_NB]; 88 | int SafetySafeKnightCheck[COLOUR_NB]; 89 | int SafetyAdjustment[COLOUR_NB]; 90 | int SafetyShelter[2][8][COLOUR_NB]; 91 | int SafetyStorm[2][8][COLOUR_NB]; 92 | int PassedPawn[2][2][8][COLOUR_NB]; 93 | int PassedFriendlyDistance[8][COLOUR_NB]; 94 | int PassedEnemyDistance[8][COLOUR_NB]; 95 | int PassedSafePromotionPath[COLOUR_NB]; 96 | int ThreatWeakPawn[COLOUR_NB]; 97 | int ThreatMinorAttackedByPawn[COLOUR_NB]; 98 | int ThreatMinorAttackedByMinor[COLOUR_NB]; 99 | int ThreatMinorAttackedByMajor[COLOUR_NB]; 100 | int ThreatRookAttackedByLesser[COLOUR_NB]; 101 | int ThreatMinorAttackedByKing[COLOUR_NB]; 102 | int ThreatRookAttackedByKing[COLOUR_NB]; 103 | int ThreatQueenAttackedByOne[COLOUR_NB]; 104 | int ThreatOverloadedPieces[COLOUR_NB]; 105 | int ThreatByPawnPush[COLOUR_NB]; 106 | int SpaceRestrictPiece[COLOUR_NB]; 107 | int SpaceRestrictEmpty[COLOUR_NB]; 108 | int SpaceCenterControl[COLOUR_NB]; 109 | int ClosednessKnightAdjustment[9][COLOUR_NB]; 110 | int ClosednessRookAdjustment[9][COLOUR_NB]; 111 | int ComplexityTotalPawns[COLOUR_NB]; 112 | int ComplexityPawnFlanks[COLOUR_NB]; 113 | int ComplexityPawnEndgame[COLOUR_NB]; 114 | int ComplexityAdjustment[COLOUR_NB]; 115 | int eval, complexity, factor, safety[COLOUR_NB]; 116 | }; 117 | 118 | struct EvalInfo { 119 | uint64_t pawnAttacks[COLOUR_NB]; 120 | uint64_t pawnAttacksBy2[COLOUR_NB]; 121 | uint64_t rammedPawns[COLOUR_NB]; 122 | uint64_t blockedPawns[COLOUR_NB]; 123 | uint64_t kingAreas[COLOUR_NB]; 124 | uint64_t mobilityAreas[COLOUR_NB]; 125 | uint64_t attacked[COLOUR_NB]; 126 | uint64_t attackedBy2[COLOUR_NB]; 127 | uint64_t attackedBy[COLOUR_NB][PIECE_NB]; 128 | uint64_t occupiedMinusBishops[COLOUR_NB]; 129 | uint64_t occupiedMinusRooks[COLOUR_NB]; 130 | uint64_t passedPawns; 131 | int kingSquare[COLOUR_NB]; 132 | int kingAttacksCount[COLOUR_NB]; 133 | int kingAttackersCount[COLOUR_NB]; 134 | int kingAttackersWeight[COLOUR_NB]; 135 | int pkeval[COLOUR_NB]; 136 | int pksafety[COLOUR_NB]; 137 | PKEntry *pkentry; 138 | }; 139 | 140 | int evaluateBoard(Thread *thread, Board *board); 141 | int evaluatePieces(EvalInfo *ei, Board *board); 142 | int evaluatePawns(EvalInfo *ei, Board *board, int colour); 143 | int evaluateKnights(EvalInfo *ei, Board *board, int colour); 144 | int evaluateBishops(EvalInfo *ei, Board *board, int colour); 145 | int evaluateRooks(EvalInfo *ei, Board *board, int colour); 146 | int evaluateQueens(EvalInfo *ei, Board *board, int colour); 147 | int evaluateKingsPawns(EvalInfo *ei, Board *board, int colour); 148 | int evaluateKings(EvalInfo *ei, Board *board, int colour); 149 | int evaluatePassed(EvalInfo *ei, Board *board, int colour); 150 | int evaluateThreats(EvalInfo *ei, Board *board, int colour); 151 | int evaluateSpace(EvalInfo *ei, Board *board, int colour); 152 | int evaluateClosedness(EvalInfo *ei, Board *board); 153 | int evaluateComplexity(EvalInfo *ei, Board *board, int eval); 154 | int evaluateScaleFactor(Board *board, int eval); 155 | void initEvalInfo(Thread *thread, Board *board, EvalInfo *ei); 156 | void initEval(); 157 | 158 | #define MakeScore(mg, eg) ((int)((unsigned int)(eg) << 16) + (mg)) 159 | #define ScoreMG(s) ((int16_t)((uint16_t)((unsigned)((s))))) 160 | #define ScoreEG(s) ((int16_t)((uint16_t)((unsigned)((s) + 0x8000) >> 16))) 161 | 162 | extern int PSQT[32][SQUARE_NB]; 163 | extern const int Tempo; 164 | -------------------------------------------------------------------------------- /src/history.c: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "bitboards.h" 24 | #include "board.h" 25 | #include "history.h" 26 | #include "move.h" 27 | #include "thread.h" 28 | #include "types.h" 29 | 30 | static int stat_bonus(int depth) { 31 | 32 | // Approximately verbatim stat bonus formula from Stockfish 33 | return depth > 13 ? 32 : 16 * depth * depth + 128 * MAX(depth - 1, 0); 34 | } 35 | 36 | static void update_history(int16_t *current, int depth, bool good) { 37 | 38 | // HistoryDivisor is essentially the max value of history 39 | const int delta = good ? stat_bonus(depth) : -stat_bonus(depth); 40 | *current += delta - *current * abs(delta) / HistoryDivisor; 41 | } 42 | 43 | 44 | static int history_captured_piece(Thread *thread, uint16_t move) { 45 | 46 | // Handle Enpassant; Consider promotions as Pawn Captures 47 | return MoveType(move) != NORMAL_MOVE ? PAWN 48 | : pieceType(thread->board.squares[MoveTo(move)]); 49 | } 50 | 51 | static int16_t* underlying_capture_history(Thread *thread, uint16_t move) { 52 | 53 | const int captured = history_captured_piece(thread, move); 54 | const int piece = pieceType(thread->board.squares[MoveFrom(move)]); 55 | 56 | // Determine if piece evades and/or enters a threat 57 | const bool threat_from = testBit(thread->board.threats, MoveFrom(move)); 58 | const bool threat_to = testBit(thread->board.threats, MoveTo(move)); 59 | 60 | assert(PAWN <= captured && captured <= QUEEN); 61 | assert(PAWN <= piece && piece <= KING); 62 | 63 | return &thread->chistory[piece][threat_from][threat_to][MoveTo(move)][captured]; 64 | } 65 | 66 | static void underlying_quiet_history(Thread *thread, uint16_t move, int16_t *histories[3]) { 67 | 68 | static int16_t NULL_HISTORY; // Always zero to handle missing CM/FM history 69 | 70 | NodeState *const ns = &thread->states[thread->height]; 71 | const uint64_t threats = thread->board.threats; 72 | 73 | // Extract information from this move 74 | const int to = MoveTo(move); 75 | const int from = MoveFrom(move); 76 | const int piece = pieceType(thread->board.squares[from]); 77 | 78 | // Determine if piece evades and/or enters a threat 79 | const bool threat_from = testBit(threats, from); 80 | const bool threat_to = testBit(threats, to); 81 | 82 | // Set Counter Move History if it exists 83 | histories[0] = (ns-1)->continuations == NULL 84 | ? &NULL_HISTORY : &(*(ns-1)->continuations)[0][piece][to]; 85 | 86 | // Set Followup Move History if it exists 87 | histories[1] = (ns-2)->continuations == NULL 88 | ? &NULL_HISTORY : &(*(ns-2)->continuations)[1][piece][to]; 89 | 90 | // Set Butterfly History, which will always exist 91 | histories[2] = &thread->history[thread->board.turn][threat_from][threat_to][from][to]; 92 | } 93 | 94 | 95 | void update_history_heuristics(Thread *thread, uint16_t *moves, int length, int depth) { 96 | 97 | NodeState *const prev = &thread->states[thread->height-1]; 98 | const int colour = thread->board.turn; 99 | 100 | update_killer_moves(thread, moves[length-1]); 101 | 102 | if (prev->move != NONE_MOVE && prev->move != NULL_MOVE) 103 | thread->cmtable[!colour][prev->movedPiece][MoveTo(prev->move)] = moves[length-1]; 104 | 105 | update_quiet_histories(thread, moves, length, depth); 106 | } 107 | 108 | void update_killer_moves(Thread *thread, uint16_t move) { 109 | 110 | // Avoid saving the same Killer Move twice 111 | if (thread->killers[thread->height][0] != move) { 112 | thread->killers[thread->height][1] = thread->killers[thread->height][0]; 113 | thread->killers[thread->height][0] = move; 114 | } 115 | } 116 | 117 | void get_refutation_moves(Thread *thread, uint16_t *killer1, uint16_t *killer2, uint16_t *counter) { 118 | 119 | // At each ply, we should have two potential Killer moves that have produced cutoffs 120 | // at the same ply in sibling nodes. Additionally, we may have a counter move, which 121 | // refutes the previously moved piece's destination square, somewhere in the search tree 122 | 123 | NodeState *const prev = &thread->states[thread->height-1]; 124 | 125 | *counter = (prev->move == NONE_MOVE || prev->move == NULL_MOVE) ? NONE_MOVE 126 | : thread->cmtable[!thread->board.turn][prev->movedPiece][MoveTo(prev->move)]; 127 | 128 | *killer1 = thread->killers[thread->height][0]; 129 | *killer2 = thread->killers[thread->height][1]; 130 | } 131 | 132 | 133 | int get_capture_history(Thread *thread, uint16_t move) { 134 | 135 | // Inflate Queen Promotions beyond the range of reductions 136 | return 64000 * (MovePromoPiece(move) == QUEEN) 137 | + *underlying_capture_history(thread, move); 138 | } 139 | 140 | void get_capture_histories(Thread *thread, uint16_t *moves, int *scores, int start, int length) { 141 | 142 | // Grab histories for all of the capture moves. Since this is used for sorting, 143 | // we include an MVV-LVA factor to improve sorting. Additionally, we add 64k to 144 | // the history score to ensure it is >= 0 to differentiate good from bad later on 145 | 146 | static const int MVVAugment[] = { 0, 2400, 2400, 4800, 9600 }; 147 | 148 | for (int i = start; i < start + length; i++) 149 | scores[i] = 64000 + get_capture_history(thread, moves[i]) 150 | + MVVAugment[history_captured_piece(thread, moves[i])]; 151 | } 152 | 153 | void update_capture_histories(Thread *thread, uint16_t best, uint16_t *moves, int length, int depth) { 154 | 155 | // Update the history for each capture move that was attempted. One of them 156 | // might have been the move which produced a cutoff, and thus earn a bonus 157 | 158 | for (int i = 0; i < length; i++) { 159 | int16_t *hist = underlying_capture_history(thread, moves[i]); 160 | update_history(hist, depth, moves[i] == best); 161 | } 162 | } 163 | 164 | 165 | int get_quiet_history(Thread *thread, uint16_t move, int *cmhist, int *fmhist) { 166 | 167 | int16_t *histories[3]; 168 | underlying_quiet_history(thread, move, histories); 169 | 170 | *cmhist = *histories[0]; *fmhist = *histories[1]; 171 | return *histories[0] + *histories[1] + *histories[2]; 172 | } 173 | 174 | void get_quiet_histories(Thread *thread, uint16_t *moves, int *scores, int start, int length) { 175 | 176 | int null_hist; // cmhist & fmhist are set, although ignored 177 | 178 | for (int i = start; i < start + length; i++) 179 | scores[i] = get_quiet_history(thread, moves[i], &null_hist, &null_hist); 180 | } 181 | 182 | void update_quiet_histories(Thread *thread, uint16_t *moves, int length, int depth) { 183 | 184 | NodeState *const ns = &thread->states[thread->height]; 185 | 186 | // We found a low-depth cutoff too easily 187 | if (!depth || (length == 1 && depth <= 3)) 188 | return; 189 | 190 | for (int i = 0; i < length; i++) { 191 | 192 | int16_t *histories[3]; 193 | underlying_quiet_history(thread, moves[i], histories); 194 | 195 | // Update Counter Move History if it exists 196 | if ((ns-1)->continuations != NULL) 197 | update_history(histories[0], depth, i == length - 1); 198 | 199 | // Update Followup Move History if it exists 200 | if ((ns-2)->continuations != NULL) 201 | update_history(histories[1], depth, i == length - 1); 202 | 203 | // Update Butterfly History, which always exists 204 | update_history(histories[2], depth, i == length - 1); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/history.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | 23 | #include "types.h" 24 | 25 | static const int HistoryDivisor = 16384; 26 | 27 | void update_history_heuristics(Thread *thread, uint16_t *moves, int length, int depth); 28 | void update_killer_moves(Thread *thread, uint16_t move); 29 | void get_refutation_moves(Thread *thread, uint16_t *killer1, uint16_t *killer2, uint16_t *counter); 30 | 31 | int get_capture_history(Thread *thread, uint16_t move); 32 | void get_capture_histories(Thread *thread, uint16_t *moves, int *scores, int start, int length); 33 | void update_capture_histories(Thread *thread, uint16_t best, uint16_t *moves, int length, int depth); 34 | 35 | int get_quiet_history(Thread *thread, uint16_t move, int *cmhist, int *fmhist); 36 | void get_quiet_histories(Thread *thread, uint16_t *moves, int *scores, int start, int length); 37 | void update_quiet_histories(Thread *thread, uint16_t *moves, int length, int depth); 38 | -------------------------------------------------------------------------------- /src/incbin/LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /src/makefile: -------------------------------------------------------------------------------- 1 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 2 | # # 3 | # Ethereal is a UCI chess playing engine authored by Andrew Grant. # 4 | # # 5 | # # 6 | # Ethereal is free software: you can redistribute it and/or modify # 7 | # it under the terms of the GNU General Public License as published by # 8 | # the Free Software Foundation, either version 3 of the License, or # 9 | # (at your option) any later version. # 10 | # # 11 | # Ethereal is distributed in the hope that it will be useful, # 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 14 | # GNU General Public License for more details. # 15 | # # 16 | # You should have received a copy of the GNU General Public License # 17 | # along with this program. If not, see . # 18 | # # 19 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 20 | 21 | ### ========================================================================= 22 | ### Section 1. Flag Declarations. Detect NNUE/Classic and Release/Native 23 | ### ========================================================================= 24 | 25 | CC = clang 26 | SRC = *.c pyrrhic/tbprobe.c 27 | LIBS = -lpthread -lm 28 | NN = -DUSE_NNUE=0 29 | EXE = Ethereal 30 | 31 | ifdef EVALFILE 32 | NN = -DUSE_NNUE=1 33 | NNFLAGS += -DEVALFILE=\"$(EVALFILE)\" 34 | SRC = *.c nnue/*.c pyrrhic/tbprobe.c 35 | endif 36 | 37 | ifdef OWNER 38 | NN = -DUSE_NNUE=1 39 | NNFLAGS += -DLICENSE_OWNER=\"$(OWNER)\" 40 | SRC = *.c nnue/*.c pyrrhic/tbprobe.c 41 | endif 42 | 43 | WFLAGS = -std=gnu11 -Wall -Wextra -Wshadow 44 | RFLAGS = -O3 $(WFLAGS) -DNDEBUG -flto $(NN) $(NNFLAGS) -static 45 | CFLAGS = -O3 $(WFLAGS) -DNDEBUG -flto $(NN) $(NNFLAGS) -march=native 46 | TFLAGS = -O3 $(WFLAGS) -DNDEBUG -flto $(NN) $(NNFLAGS) -march=native -fopenmp -DTUNE 47 | PGOFLAGS = -fno-asynchronous-unwind-tables 48 | 49 | POPCNTFLAGS = -DUSE_POPCNT -mpopcnt 50 | PEXTFLAGS = -DUSE_PEXT -mbmi2 $(POPCNTFLAGS) 51 | 52 | SSSE3FLAGS = -DUSE_SSSE3 -msse -msse2 -msse3 -mssse3 53 | AVXFLAGS = -DUSE_AVX -mavx -msse4.1 $(SSSE3FLAGS) 54 | AVX2FLAGS = -DUSE_AVX2 -mavx2 -mfma $(AVXFLAGS) 55 | 56 | ### ========================================================================= 57 | ### Section 2. Native Build Configuration [ Auto-Detection ] 58 | ### ========================================================================= 59 | 60 | PROPS = $(shell echo | $(CC) -march=native -E -dM -) 61 | 62 | # Detect POPCNT and PEXT Instruction Support 63 | 64 | ifneq ($(findstring __POPCNT__, $(PROPS)),) 65 | CFLAGS += -DUSE_POPCNT 66 | endif 67 | 68 | ifneq ($(findstring __BMI2__, $(PROPS)),) 69 | ifeq ($(findstring __znver1, $(PROPS)),) 70 | ifeq ($(findstring __znver2, $(PROPS)),) 71 | CFLAGS += -DUSE_PEXT 72 | endif 73 | endif 74 | endif 75 | 76 | # Detect AVX2, AVX, or otherwise SSSE3 Instruction Support 77 | 78 | ifneq ($(findstring __AVX2__, $(PROPS)),) 79 | CFLAGS += -DUSE_AVX2 80 | endif 81 | 82 | ifneq ($(findstring __AVX__, $(PROPS)),) 83 | CFLAGS += -DUSE_AVX 84 | endif 85 | 86 | ifneq ($(findstring __SSSE3__, $(PROPS)),) 87 | CFLAGS += -DUSE_SSSE3 88 | endif 89 | 90 | # Determine whether we are using GCC or Clang for potential PGO 91 | 92 | ifneq ($(findstring gcc, $(CC)),) 93 | PGOGEN = -fprofile-generate 94 | PGOUSE = -fprofile-use 95 | endif 96 | 97 | ifneq ($(findstring clang, $(CC)),) 98 | PGOMERGE = llvm-profdata merge -output=ethereal.profdata *.profraw 99 | PGOGEN = -fprofile-instr-generate 100 | PGOUSE = -fprofile-instr-use=ethereal.profdata 101 | endif 102 | 103 | ### ========================================================================= 104 | ### Section 3. Build Targets Optimized For Native Use 105 | ### ========================================================================= 106 | 107 | pgo: 108 | rm -f *.gcda pyrrhic/*.gcda nnue/*.gcda *.profdata *.profraw 109 | $(CC) $(PGOGEN) $(CFLAGS) $(PGOFLAGS) $(SRC) $(LIBS) -o $(EXE) 110 | ./$(EXE) bench > /dev/null 2>&1 111 | $(PGOMERGE) 112 | $(CC) $(PGOUSE) $(CFLAGS) $(PGOFLAGS) $(SRC) $(LIBS) -o $(EXE) 113 | rm -f *.gcda pyrrhic/*.gcda nnue/*.gcda *.profdata *.profraw 114 | 115 | basic: 116 | $(CC) $(CFLAGS) $(SRC) $(LIBS) -o $(EXE) 117 | 118 | tune: 119 | $(CC) $(TFLAGS) $(SRC) $(LIBS) -o $(EXE) 120 | 121 | ### ========================================================================= 122 | ### Section 4. Release Build Targets [ make release OWNER= OS= EXE= EXT= ] 123 | ### ========================================================================= 124 | 125 | builddir: 126 | mkdir -p ../$(OWNER)/$(OS) 127 | 128 | ssse3-popcnt: builddir 129 | $(CC) $(RFLAGS) $(SRC) $(LIBS) $(POPCNTFLAGS) $(SSSE3FLAGS) -o $(EXE)-ssse3$(EXT) 130 | 131 | ssse3-pext: builddir 132 | $(CC) $(RFLAGS) $(SRC) $(LIBS) $(PEXTFLAGS) $(SSSE3FLAGS) -o $(EXE)-pext-ssse3$(EXT) 133 | 134 | avx-popcnt: builddir 135 | $(CC) $(RFLAGS) $(SRC) $(LIBS) $(POPCNTFLAGS) $(AVXFLAGS) -o $(EXE)-avx$(EXT) 136 | 137 | avx-pext: builddir 138 | $(CC) $(RFLAGS) $(SRC) $(LIBS) $(PEXTFLAGS) $(AVXFLAGS) -o $(EXE)-pext-avx$(EXT) 139 | 140 | avx2-popcnt: builddir 141 | $(CC) $(RFLAGS) $(SRC) $(LIBS) $(POPCNTFLAGS) $(AVX2FLAGS) -o $(EXE)-avx2$(EXT) 142 | 143 | avx2-pext: builddir 144 | $(CC) $(RFLAGS) $(SRC) $(LIBS) $(PEXTFLAGS) $(AVX2FLAGS) -o $(EXE)-pext-avx2$(EXT) 145 | 146 | release: ssse3-popcnt avx-popcnt avx2-popcnt ssse3-pext avx-pext avx2-pext 147 | -------------------------------------------------------------------------------- /src/masks.c: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "attacks.h" 24 | #include "bitboards.h" 25 | #include "masks.h" 26 | #include "types.h" 27 | 28 | int DistanceBetween[SQUARE_NB][SQUARE_NB]; 29 | int KingPawnFileDistance[FILE_NB][1 << FILE_NB]; 30 | uint64_t BitsBetweenMasks[SQUARE_NB][SQUARE_NB]; 31 | uint64_t KingAreaMasks[COLOUR_NB][SQUARE_NB]; 32 | uint64_t ForwardRanksMasks[COLOUR_NB][RANK_NB]; 33 | uint64_t ForwardFileMasks[COLOUR_NB][SQUARE_NB]; 34 | uint64_t AdjacentFilesMasks[FILE_NB]; 35 | uint64_t PassedPawnMasks[COLOUR_NB][SQUARE_NB]; 36 | uint64_t PawnConnectedMasks[COLOUR_NB][SQUARE_NB]; 37 | uint64_t OutpostSquareMasks[COLOUR_NB][SQUARE_NB]; 38 | uint64_t OutpostRanksMasks[COLOUR_NB]; 39 | 40 | void initMasks() { 41 | 42 | // Init a table for the distance between two given squares 43 | for (int sq1 = 0; sq1 < SQUARE_NB; sq1++) 44 | for (int sq2 = 0; sq2 < SQUARE_NB; sq2++) 45 | DistanceBetween[sq1][sq2] = MAX(abs(fileOf(sq1)-fileOf(sq2)), abs(rankOf(sq1)-rankOf(sq2))); 46 | 47 | // Init a table to compute the distance between Pawns and Kings file-wise 48 | for (uint64_t mask = 0ull; mask <= 0xFF; mask++) { 49 | for (int file = 0; file < FILE_NB; file++) { 50 | 51 | int ldist, rdist, dist; 52 | uint64_t left, right; 53 | 54 | // Look at only one side at a time by shifting off the other pawns 55 | left = (0xFFull & (mask << (FILE_NB - file - 1))) >> (FILE_NB - file - 1); 56 | right = (mask >> file) << file; 57 | 58 | // Find closest Pawn on each side. If no pawn, use "max" distance 59 | ldist = left ? file - getmsb(left) : FILE_NB-1; 60 | rdist = right ? getlsb(right) - file : FILE_NB-1; 61 | 62 | // Take the min distance, unless there are no pawns, then use 0 63 | dist = (left | right) ? MIN(ldist, rdist) : 0; 64 | KingPawnFileDistance[file][mask] = dist; 65 | } 66 | } 67 | 68 | // Init a table of bitmasks for the squares between two given ones (aligned on diagonal) 69 | for (int sq1 = 0; sq1 < SQUARE_NB; sq1++) 70 | for (int sq2 = 0; sq2 < SQUARE_NB; sq2++) 71 | if (testBit(bishopAttacks(sq1, 0ull), sq2)) 72 | BitsBetweenMasks[sq1][sq2] = bishopAttacks(sq1, 1ull << sq2) 73 | & bishopAttacks(sq2, 1ull << sq1); 74 | 75 | // Init a table of bitmasks for the squares between two given ones (aligned on a straight) 76 | for (int sq1 = 0; sq1 < SQUARE_NB; sq1++) 77 | for (int sq2 = 0; sq2 < SQUARE_NB; sq2++) 78 | if (testBit(rookAttacks(sq1, 0ull), sq2)) 79 | BitsBetweenMasks[sq1][sq2] = rookAttacks(sq1, 1ull << sq2) 80 | & rookAttacks(sq2, 1ull << sq1); 81 | 82 | // Init a table for the King Areas. Use the King's square, the King's target 83 | // squares, and the squares within the pawn shield. When on the A/H files, extend 84 | // the King Area to include an additional file, namely the C and F file respectively 85 | for (int sq = 0; sq < SQUARE_NB; sq++) { 86 | 87 | KingAreaMasks[WHITE][sq] = kingAttacks(sq) | (1ull << sq) | (kingAttacks(sq) << 8); 88 | KingAreaMasks[BLACK][sq] = kingAttacks(sq) | (1ull << sq) | (kingAttacks(sq) >> 8); 89 | 90 | KingAreaMasks[WHITE][sq] |= fileOf(sq) != 0 ? 0ull : KingAreaMasks[WHITE][sq] << 1; 91 | KingAreaMasks[BLACK][sq] |= fileOf(sq) != 0 ? 0ull : KingAreaMasks[BLACK][sq] << 1; 92 | 93 | KingAreaMasks[WHITE][sq] |= fileOf(sq) != 7 ? 0ull : KingAreaMasks[WHITE][sq] >> 1; 94 | KingAreaMasks[BLACK][sq] |= fileOf(sq) != 7 ? 0ull : KingAreaMasks[BLACK][sq] >> 1; 95 | } 96 | 97 | // Init a table of bitmasks for the ranks at or above a given rank, by colour 98 | for (int rank = 0; rank < RANK_NB; rank++) { 99 | for (int i = rank; i < RANK_NB; i++) 100 | ForwardRanksMasks[WHITE][rank] |= Ranks[i]; 101 | ForwardRanksMasks[BLACK][rank] = ~ForwardRanksMasks[WHITE][rank] | Ranks[rank]; 102 | } 103 | 104 | // Init a table of bitmasks for the squares on a file above a given square, by colour 105 | for (int sq = 0; sq < SQUARE_NB; sq++) { 106 | ForwardFileMasks[WHITE][sq] = Files[fileOf(sq)] & ForwardRanksMasks[WHITE][rankOf(sq)]; 107 | ForwardFileMasks[BLACK][sq] = Files[fileOf(sq)] & ForwardRanksMasks[BLACK][rankOf(sq)]; 108 | } 109 | 110 | // Init a table of bitmasks containing the files next to a given file 111 | for (int file = 0; file < FILE_NB; file++) { 112 | AdjacentFilesMasks[file] = Files[MAX(0, file-1)]; 113 | AdjacentFilesMasks[file] |= Files[MIN(FILE_NB-1, file+1)]; 114 | AdjacentFilesMasks[file] &= ~Files[file]; 115 | } 116 | 117 | // Init a table of bitmasks to check if a given pawn has any opposition 118 | for (int colour = WHITE; colour <= BLACK; colour++) 119 | for (int sq = 0; sq < SQUARE_NB; sq++) 120 | PassedPawnMasks[colour][sq] = ~forwardRanksMasks(!colour, rankOf(sq)) 121 | & (adjacentFilesMasks(fileOf(sq)) | Files[fileOf(sq)]); 122 | 123 | // Init a table of bitmasks to check if a square is an outpost relative 124 | // to opposing pawns, such that no enemy pawn may attack the square with ease 125 | for (int colour = WHITE; colour <= BLACK; colour++) 126 | for (int sq = 0; sq < SQUARE_NB; sq++) 127 | OutpostSquareMasks[colour][sq] = PassedPawnMasks[colour][sq] & ~Files[fileOf(sq)]; 128 | 129 | // Init a pair of bitmasks to check if a square may be an outpost, by colour 130 | OutpostRanksMasks[WHITE] = RANK_4 | RANK_5 | RANK_6; 131 | OutpostRanksMasks[BLACK] = RANK_3 | RANK_4 | RANK_5; 132 | 133 | // Init a table of bitmasks to check for supports for a given pawn 134 | for (int sq = 8 ; sq < 56; sq++) { 135 | PawnConnectedMasks[WHITE][sq] = pawnAttacks(BLACK, sq) | pawnAttacks(BLACK, sq + 8); 136 | PawnConnectedMasks[BLACK][sq] = pawnAttacks(WHITE, sq) | pawnAttacks(WHITE, sq - 8); 137 | } 138 | } 139 | 140 | int distanceBetween(int s1, int s2) { 141 | assert(0 <= s1 && s1 < SQUARE_NB); 142 | assert(0 <= s2 && s2 < SQUARE_NB); 143 | return DistanceBetween[s1][s2]; 144 | } 145 | 146 | int kingPawnFileDistance(uint64_t pawns, int ksq) { 147 | pawns |= pawns >> 8; pawns |= pawns >> 16; pawns |= pawns >> 32; 148 | assert(0 <= fileOf(ksq) && fileOf(ksq) < FILE_NB); 149 | assert((pawns & 0xFF) < (1ull << FILE_NB)); 150 | return KingPawnFileDistance[fileOf(ksq)][pawns & 0xFF]; 151 | } 152 | 153 | int openFileCount(uint64_t pawns) { 154 | pawns |= pawns >> 8; pawns |= pawns >> 16; pawns |= pawns >> 32; 155 | return popcount(~pawns & 0xFF); 156 | } 157 | 158 | uint64_t bitsBetweenMasks(int s1, int s2) { 159 | assert(0 <= s1 && s1 < SQUARE_NB); 160 | assert(0 <= s2 && s2 < SQUARE_NB); 161 | return BitsBetweenMasks[s1][s2]; 162 | } 163 | 164 | uint64_t kingAreaMasks(int colour, int sq) { 165 | assert(0 <= colour && colour < COLOUR_NB); 166 | assert(0 <= sq && sq < SQUARE_NB); 167 | return KingAreaMasks[colour][sq]; 168 | } 169 | 170 | uint64_t forwardRanksMasks(int colour, int rank) { 171 | assert(0 <= colour && colour < COLOUR_NB); 172 | assert(0 <= rank && rank < RANK_NB); 173 | return ForwardRanksMasks[colour][rank]; 174 | } 175 | 176 | uint64_t forwardFileMasks(int colour, int sq) { 177 | assert(0 <= colour && colour < COLOUR_NB); 178 | assert(0 <= sq && sq < SQUARE_NB); 179 | return ForwardFileMasks[colour][sq]; 180 | } 181 | 182 | uint64_t adjacentFilesMasks(int file) { 183 | assert(0 <= file && file < FILE_NB); 184 | return AdjacentFilesMasks[file]; 185 | } 186 | 187 | uint64_t passedPawnMasks(int colour, int sq) { 188 | assert(0 <= colour && colour < COLOUR_NB); 189 | assert(0 <= sq && sq < SQUARE_NB); 190 | return PassedPawnMasks[colour][sq]; 191 | } 192 | 193 | uint64_t pawnConnectedMasks(int colour, int sq) { 194 | assert(0 <= colour && colour < COLOUR_NB); 195 | assert(0 <= sq && sq < SQUARE_NB); 196 | return PawnConnectedMasks[colour][sq]; 197 | } 198 | 199 | uint64_t outpostSquareMasks(int colour, int sq) { 200 | assert(0 <= colour && colour < COLOUR_NB); 201 | assert(0 <= sq && sq < SQUARE_NB); 202 | return OutpostSquareMasks[colour][sq]; 203 | } 204 | 205 | uint64_t outpostRanksMasks(int colour) { 206 | assert(0 <= colour && colour < COLOUR_NB); 207 | return OutpostRanksMasks[colour]; 208 | } 209 | -------------------------------------------------------------------------------- /src/masks.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | 23 | #include "types.h" 24 | 25 | void initMasks(); 26 | 27 | int distanceBetween(int sq1, int sq2); 28 | int kingPawnFileDistance(uint64_t pawns, int ksq); 29 | int openFileCount(uint64_t pawns); 30 | uint64_t bitsBetweenMasks(int sq1, int sq2); 31 | uint64_t kingAreaMasks(int colour, int sq); 32 | uint64_t forwardRanksMasks(int colour, int rank); 33 | uint64_t forwardFileMasks(int colour, int sq); 34 | uint64_t adjacentFilesMasks(int file); 35 | uint64_t passedPawnMasks(int colour, int sq); 36 | uint64_t pawnConnectedMasks(int colour, int sq); 37 | uint64_t outpostSquareMasks(int colour, int sq); 38 | uint64_t outpostRanksMasks(int colour); 39 | -------------------------------------------------------------------------------- /src/move.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | 23 | #include "types.h" 24 | 25 | enum { 26 | NONE_MOVE = 0, NULL_MOVE = 11, 27 | 28 | NORMAL_MOVE = 0 << 12, CASTLE_MOVE = 1 << 12, 29 | ENPASS_MOVE = 2 << 12, PROMOTION_MOVE = 3 << 12, 30 | 31 | PROMOTE_TO_KNIGHT = 0 << 14, PROMOTE_TO_BISHOP = 1 << 14, 32 | PROMOTE_TO_ROOK = 2 << 14, PROMOTE_TO_QUEEN = 3 << 14, 33 | 34 | KNIGHT_PROMO_MOVE = PROMOTION_MOVE | PROMOTE_TO_KNIGHT, 35 | BISHOP_PROMO_MOVE = PROMOTION_MOVE | PROMOTE_TO_BISHOP, 36 | ROOK_PROMO_MOVE = PROMOTION_MOVE | PROMOTE_TO_ROOK, 37 | QUEEN_PROMO_MOVE = PROMOTION_MOVE | PROMOTE_TO_QUEEN 38 | }; 39 | 40 | int castleKingTo(int king, int rook); 41 | int castleRookTo(int king, int rook); 42 | 43 | int apply(Thread *thread, Board *board, uint16_t move); 44 | void applyLegal(Thread *thread, Board *board, uint16_t move); 45 | void applyMove(Board *board, uint16_t move, Undo *undo); 46 | void applyNormalMove(Board *board, uint16_t move, Undo *undo); 47 | void applyCastleMove(Board *board, uint16_t move, Undo *undo); 48 | void applyEnpassMove(Board *board, uint16_t move, Undo *undo); 49 | void applyPromotionMove(Board *board, uint16_t move, Undo *undo); 50 | void applyNullMove(Board *board, Undo *undo); 51 | 52 | void revert(Thread *thread, Board *board, uint16_t move); 53 | void revertMove(Board *board, uint16_t move, Undo *undo); 54 | void revertNullMove(Board *board, Undo *undo); 55 | 56 | int legalMoveCount(Board * board); 57 | int moveExaminedByMultiPV(Thread *thread, uint16_t move); 58 | int moveIsInRootMoves(Thread *thread, uint16_t move); 59 | int moveIsTactical(Board *board, uint16_t move); 60 | int moveEstimatedValue(Board *board, uint16_t move); 61 | int moveBestCaseValue(Board *board); 62 | int moveIsLegal(Board *board, uint16_t move); 63 | int moveIsPseudoLegal(Board *board, uint16_t move); 64 | int moveWasLegal(Board *board); 65 | 66 | void printMove(uint16_t move, int chess960); 67 | void moveToString(uint16_t move, char *str, int chess960); 68 | 69 | #define MoveFrom(move) (((move) >> 0) & 63) 70 | #define MoveTo(move) (((move) >> 6) & 63) 71 | #define MoveType(move) ((move) & (3 << 12)) 72 | #define MovePromoType(move) ((move) & (3 << 14)) 73 | #define MovePromoPiece(move) (1 + ((move) >> 14)) 74 | #define MoveMake(from,to,flag) ((from) | ((to) << 6) | (flag)) 75 | -------------------------------------------------------------------------------- /src/movegen.c: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | #include "attacks.h" 22 | #include "board.h" 23 | #include "bitboards.h" 24 | #include "masks.h" 25 | #include "move.h" 26 | #include "movegen.h" 27 | #include "types.h" 28 | 29 | 30 | typedef uint64_t (*JumperFunc)(int); 31 | typedef uint64_t (*SliderFunc)(int, uint64_t); 32 | 33 | uint16_t* buildEnpassMoves(uint16_t *moves, uint64_t attacks, int epsq) { 34 | 35 | while (attacks) 36 | *(moves++) = MoveMake(poplsb(&attacks), epsq, ENPASS_MOVE); 37 | 38 | return moves; 39 | } 40 | 41 | uint16_t* buildPawnMoves(uint16_t *moves, uint64_t attacks, int delta) { 42 | 43 | while (attacks) { 44 | int sq = poplsb(&attacks); 45 | *(moves++) = MoveMake(sq + delta, sq, NORMAL_MOVE); 46 | } 47 | 48 | return moves; 49 | } 50 | 51 | uint16_t* buildPawnPromotions(uint16_t *moves, uint64_t attacks, int delta) { 52 | 53 | while (attacks) { 54 | int sq = poplsb(&attacks); 55 | *(moves++) = MoveMake(sq + delta, sq, QUEEN_PROMO_MOVE); 56 | *(moves++) = MoveMake(sq + delta, sq, ROOK_PROMO_MOVE); 57 | *(moves++) = MoveMake(sq + delta, sq, BISHOP_PROMO_MOVE); 58 | *(moves++) = MoveMake(sq + delta, sq, KNIGHT_PROMO_MOVE); 59 | } 60 | 61 | return moves; 62 | } 63 | 64 | uint16_t* buildNormalMoves(uint16_t *moves, uint64_t attacks, int sq) { 65 | 66 | while (attacks) 67 | *(moves++) = MoveMake(sq, poplsb(&attacks), NORMAL_MOVE); 68 | 69 | return moves; 70 | } 71 | 72 | uint16_t* buildJumperMoves(JumperFunc F, uint16_t *moves, uint64_t pieces, uint64_t targets) { 73 | 74 | while (pieces) { 75 | int sq = poplsb(&pieces); 76 | moves = buildNormalMoves(moves, F(sq) & targets, sq); 77 | } 78 | 79 | return moves; 80 | } 81 | 82 | uint16_t* buildSliderMoves(SliderFunc F, uint16_t *moves, uint64_t pieces, uint64_t targets, uint64_t occupied) { 83 | 84 | while (pieces) { 85 | int sq = poplsb(&pieces); 86 | moves = buildNormalMoves(moves, F(sq, occupied) & targets, sq); 87 | } 88 | 89 | return moves; 90 | } 91 | 92 | 93 | int genAllLegalMoves(Board *board, uint16_t *moves) { 94 | 95 | Undo undo[1]; 96 | int size = 0, pseudo = 0; 97 | uint16_t pseudoMoves[MAX_MOVES]; 98 | 99 | // Call genAllNoisyMoves() & genAllNoisyMoves() 100 | pseudo = genAllNoisyMoves(board, pseudoMoves); 101 | pseudo += genAllQuietMoves(board, pseudoMoves + pseudo); 102 | 103 | // Check each move for legality before copying 104 | for (int i = 0; i < pseudo; i++) { 105 | applyMove(board, pseudoMoves[i], undo); 106 | if (moveWasLegal(board)) moves[size++] = pseudoMoves[i]; 107 | revertMove(board, pseudoMoves[i], undo); 108 | } 109 | 110 | return size; 111 | } 112 | 113 | int genAllNoisyMoves(Board *board, uint16_t *moves) { 114 | 115 | const uint16_t *start = moves; 116 | 117 | const int Left = board->turn == WHITE ? -7 : 7; 118 | const int Right = board->turn == WHITE ? -9 : 9; 119 | const int Forward = board->turn == WHITE ? -8 : 8; 120 | 121 | uint64_t destinations, pawnEnpass, pawnLeft, pawnRight; 122 | uint64_t pawnPromoForward, pawnPromoLeft, pawnPromoRight; 123 | 124 | uint64_t us = board->colours[board->turn]; 125 | uint64_t them = board->colours[!board->turn]; 126 | uint64_t occupied = us | them; 127 | 128 | uint64_t pawns = us & (board->pieces[PAWN ]); 129 | uint64_t knights = us & (board->pieces[KNIGHT]); 130 | uint64_t bishops = us & (board->pieces[BISHOP]); 131 | uint64_t rooks = us & (board->pieces[ROOK ]); 132 | uint64_t kings = us & (board->pieces[KING ]); 133 | 134 | // Merge together duplicate piece ideas 135 | bishops |= us & board->pieces[QUEEN]; 136 | rooks |= us & board->pieces[QUEEN]; 137 | 138 | // Double checks can only be evaded by moving the King 139 | if (several(board->kingAttackers)) 140 | return buildJumperMoves(&kingAttacks, moves, kings, them) - start; 141 | 142 | // When checked, we may only uncheck by capturing the checker 143 | destinations = board->kingAttackers ? board->kingAttackers : them; 144 | 145 | // Compute bitboards for each type of Pawn movement 146 | pawnEnpass = pawnEnpassCaptures(pawns, board->epSquare, board->turn); 147 | pawnLeft = pawnLeftAttacks(pawns, them, board->turn); 148 | pawnRight = pawnRightAttacks(pawns, them, board->turn); 149 | pawnPromoForward = pawnAdvance(pawns, occupied, board->turn) & PROMOTION_RANKS; 150 | pawnPromoLeft = pawnLeft & PROMOTION_RANKS; pawnLeft &= ~PROMOTION_RANKS; 151 | pawnPromoRight = pawnRight & PROMOTION_RANKS; pawnRight &= ~PROMOTION_RANKS; 152 | 153 | // Generate moves for all the Pawns, so long as they are noisy 154 | moves = buildEnpassMoves(moves, pawnEnpass, board->epSquare); 155 | moves = buildPawnMoves(moves, pawnLeft & destinations, Left); 156 | moves = buildPawnMoves(moves, pawnRight & destinations, Right); 157 | moves = buildPawnPromotions(moves, pawnPromoForward, Forward); 158 | moves = buildPawnPromotions(moves, pawnPromoLeft, Left); 159 | moves = buildPawnPromotions(moves, pawnPromoRight, Right); 160 | 161 | // Generate moves for the remainder of the pieces, so long as they are noisy 162 | moves = buildJumperMoves(&knightAttacks, moves, knights, destinations); 163 | moves = buildSliderMoves(&bishopAttacks, moves, bishops, destinations, occupied); 164 | moves = buildSliderMoves(&rookAttacks, moves, rooks, destinations, occupied); 165 | moves = buildJumperMoves(&kingAttacks, moves, kings, them); 166 | 167 | return moves - start; 168 | } 169 | 170 | int genAllQuietMoves(Board *board, uint16_t *moves) { 171 | 172 | const uint16_t *start = moves; 173 | 174 | const int Forward = board->turn == WHITE ? -8 : 8; 175 | const uint64_t Rank3Relative = board->turn == WHITE ? RANK_3 : RANK_6; 176 | 177 | int rook, king, rookTo, kingTo, attacked; 178 | uint64_t destinations, pawnForwardOne, pawnForwardTwo, mask; 179 | 180 | uint64_t us = board->colours[board->turn]; 181 | uint64_t occupied = us | board->colours[!board->turn]; 182 | uint64_t castles = us & board->castleRooks; 183 | 184 | uint64_t pawns = us & (board->pieces[PAWN ]); 185 | uint64_t knights = us & (board->pieces[KNIGHT]); 186 | uint64_t bishops = us & (board->pieces[BISHOP]); 187 | uint64_t rooks = us & (board->pieces[ROOK ]); 188 | uint64_t kings = us & (board->pieces[KING ]); 189 | 190 | // Merge together duplicate piece ideas 191 | bishops |= us & board->pieces[QUEEN]; 192 | rooks |= us & board->pieces[QUEEN]; 193 | 194 | // Double checks can only be evaded by moving the King 195 | if (several(board->kingAttackers)) 196 | return buildJumperMoves(&kingAttacks, moves, kings, ~occupied) - start; 197 | 198 | // When checked, we must block the checker with non-King pieces 199 | destinations = !board->kingAttackers ? ~occupied 200 | : bitsBetweenMasks(getlsb(kings), getlsb(board->kingAttackers)); 201 | 202 | // Compute bitboards for each type of Pawn movement 203 | pawnForwardOne = pawnAdvance(pawns, occupied, board->turn) & ~PROMOTION_RANKS; 204 | pawnForwardTwo = pawnAdvance(pawnForwardOne & Rank3Relative, occupied, board->turn); 205 | 206 | // Generate moves for all the pawns, so long as they are quiet 207 | moves = buildPawnMoves(moves, pawnForwardOne & destinations, Forward); 208 | moves = buildPawnMoves(moves, pawnForwardTwo & destinations, Forward * 2); 209 | 210 | // Generate moves for the remainder of the pieces, so long as they are quiet 211 | moves = buildJumperMoves(&knightAttacks, moves, knights, destinations); 212 | moves = buildSliderMoves(&bishopAttacks, moves, bishops, destinations, occupied); 213 | moves = buildSliderMoves(&rookAttacks, moves, rooks, destinations, occupied); 214 | moves = buildJumperMoves(&kingAttacks, moves, kings, ~occupied); 215 | 216 | // Attempt to generate a castle move for each rook 217 | while (castles && !board->kingAttackers) { 218 | 219 | // Figure out which pieces are moving to which squares 220 | rook = poplsb(&castles), king = getlsb(kings); 221 | rookTo = castleRookTo(king, rook); 222 | kingTo = castleKingTo(king, rook); 223 | attacked = 0; 224 | 225 | // Castle is illegal if we would go over a piece 226 | mask = bitsBetweenMasks(king, kingTo) | (1ull << kingTo); 227 | mask |= bitsBetweenMasks(rook, rookTo) | (1ull << rookTo); 228 | mask &= ~((1ull << king) | (1ull << rook)); 229 | if (occupied & mask) continue; 230 | 231 | // Castle is illegal if we move through a checking threat 232 | mask = bitsBetweenMasks(king, kingTo); 233 | while (mask) 234 | if (squareIsAttacked(board, board->turn, poplsb(&mask))) 235 | { attacked = 1; break; } 236 | if (attacked) continue; 237 | 238 | // All conditions have been met. Identify which side we are castling to 239 | *(moves++) = MoveMake(king, rook, CASTLE_MOVE); 240 | } 241 | 242 | return moves - start; 243 | } 244 | -------------------------------------------------------------------------------- /src/movegen.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | 23 | #include "types.h" 24 | 25 | int genAllLegalMoves(Board *board, uint16_t *moves); 26 | int genAllNoisyMoves(Board *board, uint16_t *moves); 27 | int genAllQuietMoves(Board *board, uint16_t *moves); 28 | -------------------------------------------------------------------------------- /src/movepicker.c: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include "board.h" 20 | #include "history.h" 21 | #include "move.h" 22 | #include "movegen.h" 23 | #include "movepicker.h" 24 | #include "thread.h" 25 | #include "types.h" 26 | 27 | static uint16_t pop_move(int *size, uint16_t *moves, int *values, int index) { 28 | uint16_t popped = moves[index]; 29 | moves[index] = moves[--*size]; 30 | values[index] = values[*size]; 31 | return popped; 32 | } 33 | 34 | static int best_index(MovePicker *mp, int start, int end) { 35 | 36 | int best = start; 37 | 38 | for (int i = start + 1; i < end; i++) 39 | if (mp->values[i] > mp->values[best]) 40 | best = i; 41 | 42 | return best; 43 | } 44 | 45 | 46 | void init_picker(MovePicker *mp, Thread *thread, uint16_t tt_move) { 47 | 48 | // Start with the tt-move 49 | mp->stage = STAGE_TABLE; 50 | mp->tt_move = tt_move; 51 | 52 | // Lookup our refutations (killers and counter moves) 53 | get_refutation_moves(thread, &mp->killer1, &mp->killer2, &mp->counter); 54 | 55 | // General housekeeping 56 | mp->threshold = 0; 57 | mp->type = NORMAL_PICKER; 58 | 59 | // Skip over the TT-move if it is illegal 60 | mp->stage += !moveIsPseudoLegal(&thread->board, tt_move); 61 | } 62 | 63 | void init_noisy_picker(MovePicker *mp, Thread *thread, uint16_t tt_move, int threshold) { 64 | 65 | // Start with the tt-move potentially 66 | mp->stage = STAGE_TABLE; 67 | mp->tt_move = tt_move; 68 | 69 | // Skip all of the refutation moves 70 | mp->killer1 = mp->killer2 = mp->counter = NONE_MOVE; 71 | 72 | // General housekeeping 73 | mp->threshold = threshold; 74 | mp->type = NOISY_PICKER; 75 | 76 | // Skip over the TT-move unless its a threshold-winning capture 77 | mp->stage += !moveIsTactical(&thread->board, tt_move) 78 | || !moveIsPseudoLegal(&thread->board, tt_move) 79 | || !staticExchangeEvaluation(&thread->board, tt_move, threshold); 80 | } 81 | 82 | uint16_t select_next(MovePicker *mp, Thread *thread, int skip_quiets) { 83 | 84 | int best; 85 | uint16_t best_move; 86 | Board *board = &thread->board; 87 | 88 | switch (mp->stage) { 89 | 90 | case STAGE_TABLE: 91 | 92 | // Play table move ( Already shown to be legal ) 93 | mp->stage = STAGE_GENERATE_NOISY; 94 | return mp->tt_move; 95 | 96 | case STAGE_GENERATE_NOISY: 97 | 98 | // Generate and evaluate noisy moves. mp->split sets a break point 99 | // to seperate the noisy from the quiet moves, so that we can skip 100 | // some of the noisy moves during STAGE_GOOD_NOISY and return later 101 | mp->noisy_size = mp->split = genAllNoisyMoves(board, mp->moves); 102 | get_capture_histories(thread, mp->moves, mp->values, 0, mp->noisy_size); 103 | mp->stage = STAGE_GOOD_NOISY; 104 | 105 | /* fallthrough */ 106 | 107 | case STAGE_GOOD_NOISY: 108 | 109 | // Check to see if there are still more noisy moves 110 | while (mp->noisy_size) { 111 | 112 | // Grab the next best move index 113 | best = best_index(mp, 0, mp->noisy_size); 114 | 115 | // Values below zero are flagged as failing an SEE (bad noisy) 116 | if (mp->values[best] < 0) 117 | break; 118 | 119 | // Skip moves which fail to beat our SEE margin. We flag those moves 120 | // as failed with the value (-1), and then repeat the selection process 121 | if (!staticExchangeEvaluation(board, mp->moves[best], mp->threshold)) { 122 | mp->values[best] = -1; 123 | continue; 124 | } 125 | 126 | // Reduce effective move list size 127 | best_move = pop_move(&mp->noisy_size, mp->moves, mp->values, best); 128 | 129 | // Don't play the table move twice 130 | if (best_move == mp->tt_move) 131 | continue; 132 | 133 | // Don't play the refutation moves twice 134 | if (best_move == mp->killer1) mp->killer1 = NONE_MOVE; 135 | if (best_move == mp->killer2) mp->killer2 = NONE_MOVE; 136 | if (best_move == mp->counter) mp->counter = NONE_MOVE; 137 | 138 | return best_move; 139 | } 140 | 141 | // Jump to bad noisy moves when skipping quiets 142 | if (skip_quiets) { 143 | mp->stage = STAGE_BAD_NOISY; 144 | return select_next(mp, thread, skip_quiets); 145 | } 146 | 147 | mp->stage = STAGE_KILLER_1; 148 | 149 | /* fallthrough */ 150 | 151 | case STAGE_KILLER_1: 152 | 153 | // Play killer move if not yet played, and pseudo legal 154 | mp->stage = STAGE_KILLER_2; 155 | if ( !skip_quiets 156 | && mp->killer1 != mp->tt_move 157 | && moveIsPseudoLegal(board, mp->killer1)) 158 | return mp->killer1; 159 | 160 | /* fallthrough */ 161 | 162 | case STAGE_KILLER_2: 163 | 164 | // Play killer move if not yet played, and pseudo legal 165 | mp->stage = STAGE_COUNTER_MOVE; 166 | if ( !skip_quiets 167 | && mp->killer2 != mp->tt_move 168 | && moveIsPseudoLegal(board, mp->killer2)) 169 | return mp->killer2; 170 | 171 | /* fallthrough */ 172 | 173 | case STAGE_COUNTER_MOVE: 174 | 175 | // Play counter move if not yet played, and pseudo legal 176 | mp->stage = STAGE_GENERATE_QUIET; 177 | if ( !skip_quiets 178 | && mp->counter != mp->tt_move 179 | && mp->counter != mp->killer1 180 | && mp->counter != mp->killer2 181 | && moveIsPseudoLegal(board, mp->counter)) 182 | return mp->counter; 183 | 184 | /* fallthrough */ 185 | 186 | case STAGE_GENERATE_QUIET: 187 | 188 | // Generate and evaluate all quiet moves when not skipping them 189 | if (!skip_quiets) { 190 | mp->quiet_size = genAllQuietMoves(board, mp->moves + mp->split); 191 | get_quiet_histories(thread, mp->moves, mp->values, mp->split, mp->quiet_size); 192 | } 193 | 194 | mp->stage = STAGE_QUIET; 195 | 196 | /* fallthrough */ 197 | 198 | case STAGE_QUIET: 199 | 200 | // Check to see if there are still more quiet moves 201 | while (!skip_quiets && mp->quiet_size) { 202 | 203 | // Select next best quiet and reduce the effective move list size 204 | best = best_index(mp, mp->split, mp->split + mp->quiet_size) - mp->split; 205 | best_move = pop_move(&mp->quiet_size, mp->moves + mp->split, mp->values + mp->split, best); 206 | 207 | // Don't play a move more than once 208 | if ( best_move == mp->tt_move || best_move == mp->killer1 209 | || best_move == mp->killer2 || best_move == mp->counter) 210 | continue; 211 | 212 | return best_move; 213 | } 214 | 215 | // Out of quiet moves, only bad quiets remain 216 | mp->stage = STAGE_BAD_NOISY; 217 | 218 | /* fallthrough */ 219 | 220 | case STAGE_BAD_NOISY: 221 | 222 | // Check to see if there are still more noisy moves 223 | while (mp->noisy_size && mp->type != NOISY_PICKER) { 224 | 225 | // Reduce effective move list size 226 | best_move = pop_move(&mp->noisy_size, mp->moves, mp->values, 0); 227 | 228 | // Don't play a move more than once 229 | if ( best_move == mp->tt_move || best_move == mp->killer1 230 | || best_move == mp->killer2 || best_move == mp->counter) 231 | continue; 232 | 233 | return best_move; 234 | } 235 | 236 | mp->stage = STAGE_DONE; 237 | 238 | /* fallthrough */ 239 | 240 | case STAGE_DONE: 241 | return NONE_MOVE; 242 | 243 | default: 244 | return NONE_MOVE; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/movepicker.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | enum { NORMAL_PICKER, NOISY_PICKER }; 24 | 25 | enum { 26 | STAGE_TABLE, 27 | STAGE_GENERATE_NOISY, STAGE_GOOD_NOISY, 28 | STAGE_KILLER_1, STAGE_KILLER_2, STAGE_COUNTER_MOVE, 29 | STAGE_GENERATE_QUIET, STAGE_QUIET, 30 | STAGE_BAD_NOISY, 31 | STAGE_DONE, 32 | }; 33 | 34 | struct MovePicker { 35 | int split, noisy_size, quiet_size; 36 | int stage, type, threshold; 37 | int values[MAX_MOVES]; 38 | uint16_t moves[MAX_MOVES]; 39 | uint16_t tt_move, killer1, killer2, counter; 40 | }; 41 | 42 | void init_picker (MovePicker *mp, Thread *thread, uint16_t tt_move); 43 | void init_noisy_picker (MovePicker *mp, Thread *thread, uint16_t tt_move, int threshold); 44 | uint16_t select_next (MovePicker *mp, Thread *thread, int skip_quiets); 45 | -------------------------------------------------------------------------------- /src/network.c: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "bitboards.h" 26 | #include "board.h" 27 | #include "evaluate.h" 28 | #include "network.h" 29 | #include "thread.h" 30 | #include "types.h" 31 | 32 | PKNetwork PKNN; 33 | 34 | static char *PKWeights[] = { 35 | #include "weights/pknet_224x32x2.net" 36 | "" 37 | }; 38 | 39 | static int computePKNetworkIndex(int colour, int piece, int sq) { 40 | return (64 + 48) * colour 41 | + (48 * (piece == KING)) 42 | + sq - 8 * (piece == PAWN); 43 | } 44 | 45 | 46 | void initPKNetwork() { 47 | 48 | for (int i = 0; i < PKNETWORK_LAYER1; i++) { 49 | 50 | char weights[strlen(PKWeights[i]) + 1]; 51 | strcpy(weights, PKWeights[i]); 52 | strtok(weights, " "); 53 | 54 | for (int j = 0; j < PKNETWORK_INPUTS; j++) 55 | PKNN.inputWeights[j][i] = atof(strtok(NULL, " ")); 56 | PKNN.inputBiases[i] = atof(strtok(NULL, " ")); 57 | } 58 | 59 | for (int i = 0; i < PKNETWORK_OUTPUTS; i++) { 60 | 61 | char weights[strlen(PKWeights[i + PKNETWORK_LAYER1]) + 1]; 62 | strcpy(weights, PKWeights[i + PKNETWORK_LAYER1]); 63 | strtok(weights, " "); 64 | 65 | for (int j = 0; j < PKNETWORK_LAYER1; j++) 66 | PKNN.layer1Weights[i][j] = atof(strtok(NULL, " ")); 67 | PKNN.layer1Biases[i] = atof(strtok(NULL, " ")); 68 | } 69 | } 70 | 71 | int computePKNetwork(Board *board) { 72 | 73 | uint64_t pawns = board->pieces[PAWN]; 74 | uint64_t kings = board->pieces[KING]; 75 | uint64_t black = board->colours[BLACK]; 76 | 77 | float layer1Neurons[PKNETWORK_LAYER1]; 78 | float outputNeurons[PKNETWORK_OUTPUTS]; 79 | 80 | // Layer 1: Compute the values in the hidden Neurons of Layer 1 81 | // by looping over the Kings and Pawns bitboards, and applying 82 | // the weight which corresponds to each piece. We break the Kings 83 | // into two nearly duplicate steps, in order to more efficiently 84 | // set and update the Layer 1 Neurons initially 85 | 86 | { // Do one King first so we can set the Neurons 87 | int sq = poplsb(&kings); 88 | int idx = computePKNetworkIndex(testBit(black, sq), KING, sq); 89 | for (int i = 0; i < PKNETWORK_LAYER1; i++) 90 | layer1Neurons[i] = PKNN.inputBiases[i] + PKNN.inputWeights[idx][i]; 91 | } 92 | 93 | { // Do the remaining King as we would do normally 94 | int sq = poplsb(&kings); 95 | int idx = computePKNetworkIndex(testBit(black, sq), KING, sq); 96 | for (int i = 0; i < PKNETWORK_LAYER1; i++) 97 | layer1Neurons[i] += PKNN.inputWeights[idx][i]; 98 | } 99 | 100 | while (pawns) { 101 | int sq = poplsb(&pawns); 102 | int idx = computePKNetworkIndex(testBit(black, sq), PAWN, sq); 103 | for (int i = 0; i < PKNETWORK_LAYER1; i++) 104 | layer1Neurons[i] += PKNN.inputWeights[idx][i]; 105 | } 106 | 107 | // Layer 2: Trivially compute the Output layer. Apply a ReLU here. 108 | // We do not apply a ReLU in Layer 1, since we already know that all 109 | // of the Inputs in Layer 1 are going to be zeros or ones 110 | 111 | for (int i = 0; i < PKNETWORK_OUTPUTS; i++) { 112 | outputNeurons[i] = PKNN.layer1Biases[i]; 113 | for (int j = 0; j < PKNETWORK_LAYER1; j++) 114 | if (layer1Neurons[j] >= 0.0) 115 | outputNeurons[i] += layer1Neurons[j] * PKNN.layer1Weights[i][j]; 116 | } 117 | 118 | assert(PKNETWORK_OUTPUTS == PHASE_NB); 119 | return MakeScore((int) outputNeurons[MG], (int) outputNeurons[EG]); 120 | } 121 | -------------------------------------------------------------------------------- /src/network.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | 23 | #include "board.h" 24 | #include "types.h" 25 | 26 | #define PKNETWORK_INPUTS (224) 27 | #define PKNETWORK_LAYER1 ( 32) 28 | #define PKNETWORK_OUTPUTS ( 2) 29 | 30 | typedef struct PKNetwork { 31 | 32 | // PKNetworks are of the form [Input, Hidden Layer 1, Output Layer] 33 | // Our current Network is [224x32, 32x1]. The Network is trained to 34 | // output a Score in CentiPawns for the Midgame and Endgame 35 | 36 | // We transpose the Input Weights matrix in order to get better 37 | // caching and memory lookups, since when computing we iterate 38 | // over only the ~20 Inputs set out of the 224 possible Inputs 39 | 40 | ALIGN64 float inputWeights[PKNETWORK_INPUTS][PKNETWORK_LAYER1]; 41 | ALIGN64 float inputBiases[PKNETWORK_LAYER1]; 42 | 43 | ALIGN64 float layer1Weights[PKNETWORK_OUTPUTS][PKNETWORK_LAYER1]; 44 | ALIGN64 float layer1Biases[PKNETWORK_OUTPUTS]; 45 | 46 | } PKNetwork; 47 | 48 | void initPKNetwork(); 49 | int computePKNetwork(Board *board); -------------------------------------------------------------------------------- /src/nnue/accumulator.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* */ 3 | /* Ethereal is a UCI chess playing engine authored by Andrew Grant. */ 4 | /* */ 5 | /* */ 6 | /* Ethereal is free software: you can redistribute it and/or modify */ 7 | /* it under the terms of the GNU General Public License as published by */ 8 | /* the Free Software Foundation, either version 3 of the License, or */ 9 | /* (at your option) any later version. */ 10 | /* */ 11 | /* Ethereal is distributed in the hope that it will be useful, */ 12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 | /* GNU General Public License for more details. */ 15 | /* */ 16 | /* You should have received a copy of the GNU General Public License */ 17 | /* along with this program. If not, see */ 18 | /* */ 19 | /******************************************************************************/ 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include "accumulator.h" 26 | #include "nnue.h" 27 | #include "types.h" 28 | 29 | #include "../bitboards.h" 30 | #include "../thread.h" 31 | #include "../types.h" 32 | 33 | 34 | static int sq64_to_sq32(int sq) { 35 | static const int Mirror[] = { 3, 2, 1, 0, 0, 1, 2, 3 }; 36 | return ((sq >> 1) & ~0x3) + Mirror[sq & 0x7]; 37 | } 38 | 39 | static int nnue_index(int piece, int relksq, int colour, int sq) { 40 | 41 | const int ptype = pieceType(piece); 42 | const int pcolour = pieceColour(piece); 43 | const int relpsq = relativeSquare(colour, sq); 44 | 45 | const int mksq = testBit(LEFT_FLANK, relksq) ? (relksq ^ 0x7) : relksq; 46 | const int mpsq = testBit(LEFT_FLANK, relksq) ? (relpsq ^ 0x7) : relpsq; 47 | 48 | return 640 * sq64_to_sq32(mksq) + (64 * (5 * (colour == pcolour) + ptype)) + mpsq; 49 | } 50 | 51 | int nnue_can_update(NNUEAccumulator *accum, Board *board, int colour) { 52 | 53 | // Search back through the tree to find an accurate accum 54 | while (accum != board->thread->nnue->stack) { 55 | 56 | // A King move prevents the entire tree from being updated 57 | if ( accum->changes 58 | && accum->deltas[0].piece == makePiece(KING, colour)) 59 | return FALSE; 60 | 61 | // Step back, since the root can't be accurate 62 | accum = accum - 1; 63 | 64 | // We found it, so we can update the entire tree 65 | if (accum->accurate[colour]) 66 | return TRUE; 67 | } 68 | 69 | return FALSE; 70 | } 71 | 72 | void nnue_update_accumulator(NNUEAccumulator *accum, Board *board, int colour, int relksq) { 73 | 74 | int add = 0, remove = 0; 75 | int add_list[3], remove_list[3]; 76 | vepi16 *inputs, *outputs, *weights, registers[NUM_REGS]; 77 | 78 | // Recurse and update all out of our date parents 79 | if (!(accum-1)->accurate[colour]) 80 | nnue_update_accumulator((accum-1), board, colour, relksq); 81 | 82 | // Determine the features that have changed, by looping through them 83 | for (NNUEDelta *x = &accum->deltas[0]; x < &accum->deltas[0] + accum->changes; x++) { 84 | 85 | // HalfKP does not concern with KxK relations 86 | if (pieceType(x->piece) == KING) 87 | continue; 88 | 89 | // Moving or placing a Piece to a Square 90 | if (x->to != SQUARE_NB) 91 | add_list[add++] = nnue_index(x->piece, relksq, colour, x->to); 92 | 93 | // Moving or deleting a Piece from a Square 94 | if (x->from != SQUARE_NB) 95 | remove_list[remove++] = nnue_index(x->piece, relksq, colour, x->from); 96 | } 97 | 98 | for (int offset = 0; offset < KPSIZE; offset += NUM_REGS * vepi16_cnt) { 99 | 100 | outputs = (vepi16*) &(accum-0)->values[colour][offset]; 101 | inputs = (vepi16*) &(accum-1)->values[colour][offset]; 102 | 103 | for (int i = 0; i < NUM_REGS; i++) 104 | registers[i] = inputs[i]; 105 | 106 | for (int i = 0; i < add; i++) { 107 | 108 | weights = (vepi16*) &in_weights[add_list[i] * KPSIZE + offset]; 109 | 110 | for (int j = 0; j < NUM_REGS; j++) 111 | registers[j] = vepi16_add(registers[j], weights[j]); 112 | } 113 | 114 | for (int i = 0; i < remove; i++) { 115 | 116 | weights = (vepi16*) &in_weights[remove_list[i] * KPSIZE + offset]; 117 | 118 | for (int j = 0; j < NUM_REGS; j++) 119 | registers[j] = vepi16_sub(registers[j], weights[j]); 120 | } 121 | 122 | for (int i = 0; i < NUM_REGS; i++) 123 | outputs[i] = registers[i]; 124 | } 125 | 126 | accum->accurate[colour] = TRUE; 127 | return; 128 | } 129 | 130 | void nnue_refresh_accumulator(NNUEEvaluator *nnue, NNUEAccumulator *accum, Board *board, int colour, int relsq) { 131 | 132 | vepi16 *outputs, *weights, registers[NUM_REGS]; 133 | const int ksq = getlsb(board->pieces[KING] & board->colours[colour]); 134 | NNUEAccumulatorTableEntry *entry = &nnue->table[ksq]; 135 | 136 | int set_indexes[32], set_count = 0; 137 | int unset_indexes[32], unset_count = 0; 138 | 139 | for (int c = WHITE; c <= BLACK; c++) { 140 | 141 | for (int pt = PAWN; pt <= QUEEN; pt++) { 142 | 143 | uint64_t pieces = board->pieces[pt] & board->colours[c]; 144 | uint64_t to_set = pieces & ~entry->occupancy[colour][c][pt]; 145 | uint64_t to_unset = entry->occupancy[colour][c][pt] & ~pieces; 146 | 147 | while (to_set) 148 | set_indexes[set_count++] = nnue_index(makePiece(pt, c), relsq, colour, poplsb(&to_set)); 149 | 150 | while (to_unset) 151 | unset_indexes[unset_count++] = nnue_index(makePiece(pt, c), relsq, colour, poplsb(&to_unset)); 152 | 153 | entry->occupancy[colour][c][pt] = pieces; 154 | } 155 | } 156 | 157 | for (int offset = 0; offset < KPSIZE; offset += NUM_REGS * vepi16_cnt) { 158 | 159 | outputs = (vepi16*) &entry->accumulator.values[colour][offset]; 160 | 161 | for (int i = 0; i < NUM_REGS; i++) 162 | registers[i] = outputs[i]; 163 | 164 | for (int i = 0; i < set_count; i++) { 165 | 166 | weights = (vepi16*) &in_weights[set_indexes[i] * KPSIZE + offset]; 167 | 168 | for (int j = 0; j < NUM_REGS; j++) 169 | registers[j] = vepi16_add(registers[j], weights[j]); 170 | } 171 | 172 | for (int i = 0; i < unset_count; i++) { 173 | 174 | weights = (vepi16*) &in_weights[unset_indexes[i] * KPSIZE + offset]; 175 | 176 | for (int j = 0; j < NUM_REGS; j++) 177 | registers[j] = vepi16_sub(registers[j], weights[j]); 178 | } 179 | 180 | for (int i = 0; i < NUM_REGS; i++) 181 | outputs[i] = registers[i]; 182 | } 183 | 184 | memcpy(accum->values[colour], entry->accumulator.values[colour], sizeof(int16_t) * KPSIZE); 185 | accum->accurate[colour] = TRUE; 186 | } -------------------------------------------------------------------------------- /src/nnue/accumulator.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* */ 3 | /* Ethereal is a UCI chess playing engine authored by Andrew Grant. */ 4 | /* */ 5 | /* */ 6 | /* Ethereal is free software: you can redistribute it and/or modify */ 7 | /* it under the terms of the GNU General Public License as published by */ 8 | /* the Free Software Foundation, either version 3 of the License, or */ 9 | /* (at your option) any later version. */ 10 | /* */ 11 | /* Ethereal is distributed in the hope that it will be useful, */ 12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 | /* GNU General Public License for more details. */ 15 | /* */ 16 | /* You should have received a copy of the GNU General Public License */ 17 | /* along with this program. If not, see */ 18 | /* */ 19 | /******************************************************************************/ 20 | 21 | #pragma once 22 | 23 | #include "types.h" 24 | #include "utils.h" 25 | 26 | #include "../board.h" 27 | #include "../thread.h" 28 | #include "../types.h" 29 | 30 | extern ALIGN64 int16_t in_weights[INSIZE * KPSIZE]; 31 | extern ALIGN64 int16_t in_biases[KPSIZE]; 32 | 33 | INLINE NNUEEvaluator* nnue_create_evaluator() { 34 | return align_malloc(sizeof(NNUEEvaluator)); 35 | } 36 | 37 | INLINE void nnue_reset_evaluator(NNUEEvaluator* ptr) { 38 | 39 | #if USE_NNUE 40 | 41 | // Reset the Finny table Accumulators 42 | for (size_t i = 0; i < SQUARE_NB; i++) { 43 | memset(ptr->table[i].occupancy, 0, sizeof(ptr->table[i].occupancy)); 44 | memcpy(ptr->table[i].accumulator.values[WHITE], in_biases, sizeof(int16_t) * KPSIZE); 45 | memcpy(ptr->table[i].accumulator.values[BLACK], in_biases, sizeof(int16_t) * KPSIZE); 46 | } 47 | 48 | // Reset the base of the Accumulator stack 49 | ptr->current = &ptr->stack[0]; 50 | ptr->current->accurate[WHITE] = 0; 51 | ptr->current->accurate[BLACK] = 0; 52 | 53 | #endif 54 | } 55 | 56 | INLINE void nnue_delete_evaluator(NNUEEvaluator* ptr) { 57 | align_free(ptr); 58 | } 59 | 60 | 61 | INLINE void nnue_pop(Board *board) { 62 | if (USE_NNUE && board->thread != NULL) 63 | --board->thread->nnue->current; 64 | } 65 | 66 | INLINE void nnue_push(Board *board) { 67 | if (USE_NNUE && board->thread != NULL) { 68 | NNUEAccumulator *accum = ++board->thread->nnue->current; 69 | accum->accurate[WHITE] = accum->accurate[BLACK] = FALSE; 70 | accum->changes = 0; 71 | } 72 | } 73 | 74 | INLINE void nnue_move_piece(Board *board, int piece, int from, int to) { 75 | if (USE_NNUE && board->thread != NULL) { 76 | NNUEAccumulator *accum = board->thread->nnue->current; 77 | accum->deltas[accum->changes++] = (NNUEDelta) { piece, from, to }; 78 | } 79 | } 80 | 81 | INLINE void nnue_add_piece(Board *board, int piece, int sq) { 82 | nnue_move_piece(board, piece, SQUARE_NB, sq); 83 | } 84 | 85 | INLINE void nnue_remove_piece(Board *board, int piece, int sq) { 86 | if (piece != EMPTY) 87 | nnue_move_piece(board, piece, sq, SQUARE_NB); 88 | } 89 | 90 | int nnue_can_update(NNUEAccumulator *accum, Board *board, int colour); 91 | void nnue_update_accumulator(NNUEAccumulator *accum, Board *board, int colour, int relksq); 92 | void nnue_refresh_accumulator(NNUEEvaluator *nnue, NNUEAccumulator *accum, Board *board, int colour, int relksq); 93 | -------------------------------------------------------------------------------- /src/nnue/archs/avx.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* */ 3 | /* Ethereal is a UCI chess playing engine authored by Andrew Grant. */ 4 | /* */ 5 | /* */ 6 | /* Ethereal is free software: you can redistribute it and/or modify */ 7 | /* it under the terms of the GNU General Public License as published by */ 8 | /* the Free Software Foundation, either version 3 of the License, or */ 9 | /* (at your option) any later version. */ 10 | /* */ 11 | /* Ethereal is distributed in the hope that it will be useful, */ 12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 | /* GNU General Public License for more details. */ 15 | /* */ 16 | /* You should have received a copy of the GNU General Public License */ 17 | /* along with this program. If not, see */ 18 | /* */ 19 | /******************************************************************************/ 20 | 21 | #pragma once 22 | 23 | #define vepi8 __m128i 24 | #define vepi16 __m128i 25 | #define vepi32 __m128i 26 | #define vps32 __m256 27 | 28 | #define vepi8_cnt 16 29 | #define vepi16_cnt 8 30 | #define vepi32_cnt 4 31 | #define vps32_cnt 8 32 | 33 | #define vepi16_add _mm_add_epi16 34 | #define vepi16_sub _mm_sub_epi16 35 | #define vepi16_max _mm_max_epi16 36 | #define vepi16_madd _mm_madd_epi16 37 | #define vepi16_one _mm_set1_epi16(1) 38 | #define vepi16_zero _mm_setzero_si128 39 | #define vepi16_srai _mm_srai_epi16 40 | #define vepi16_packu _mm_packus_epi16 41 | #define vepi16_maubs _mm_maddubs_epi16 42 | 43 | #define vepi32_add _mm_add_epi32 44 | #define vepi32_max _mm_max_epi32 45 | #define vepi32_hadd _mm_hadd_epi32 46 | #define vepi32_zero _mm_setzero_si128 47 | 48 | #define vps32_add _mm256_add_ps 49 | #define vps32_mul _mm256_mul_ps 50 | #define vps32_max _mm256_max_ps 51 | #define vps32_hadd _mm256_hadd_ps 52 | #define vps32_zero _mm256_setzero_ps 53 | 54 | #define vps32_fma(A, B, C) vps32_add(vps32_mul(A, B), C) 55 | -------------------------------------------------------------------------------- /src/nnue/archs/avx2.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* */ 3 | /* Ethereal is a UCI chess playing engine authored by Andrew Grant. */ 4 | /* */ 5 | /* */ 6 | /* Ethereal is free software: you can redistribute it and/or modify */ 7 | /* it under the terms of the GNU General Public License as published by */ 8 | /* the Free Software Foundation, either version 3 of the License, or */ 9 | /* (at your option) any later version. */ 10 | /* */ 11 | /* Ethereal is distributed in the hope that it will be useful, */ 12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 | /* GNU General Public License for more details. */ 15 | /* */ 16 | /* You should have received a copy of the GNU General Public License */ 17 | /* along with this program. If not, see */ 18 | /* */ 19 | /******************************************************************************/ 20 | 21 | #pragma once 22 | 23 | #define vepi8 __m256i 24 | #define vepi16 __m256i 25 | #define vepi32 __m256i 26 | #define vps32 __m256 27 | 28 | #define vepi8_cnt 32 29 | #define vepi16_cnt 16 30 | #define vepi32_cnt 8 31 | #define vps32_cnt 8 32 | 33 | #define vepi16_add _mm256_add_epi16 34 | #define vepi16_sub _mm256_sub_epi16 35 | #define vepi16_max _mm256_max_epi16 36 | #define vepi16_madd _mm256_madd_epi16 37 | #define vepi16_one _mm256_set1_epi16(1) 38 | #define vepi16_zero _mm256_setzero_si256 39 | #define vepi16_srai _mm256_srai_epi16 40 | #define vepi16_packu _mm256_packus_epi16 41 | #define vepi16_maubs _mm256_maddubs_epi16 42 | 43 | #define vepi32_add _mm256_add_epi32 44 | #define vepi32_max _mm256_max_epi32 45 | #define vepi32_hadd _mm256_hadd_epi32 46 | #define vepi32_zero _mm256_setzero_si256 47 | 48 | #define vps32_add _mm256_add_ps 49 | #define vps32_mul _mm256_mul_ps 50 | #define vps32_max _mm256_max_ps 51 | #define vps32_hadd _mm256_hadd_ps 52 | #define vps32_zero _mm256_setzero_ps 53 | 54 | #define vps32_fma(A, B, C) _mm256_fmadd_ps(A, B, C) 55 | -------------------------------------------------------------------------------- /src/nnue/archs/ssse3.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* */ 3 | /* Ethereal is a UCI chess playing engine authored by Andrew Grant. */ 4 | /* */ 5 | /* */ 6 | /* Ethereal is free software: you can redistribute it and/or modify */ 7 | /* it under the terms of the GNU General Public License as published by */ 8 | /* the Free Software Foundation, either version 3 of the License, or */ 9 | /* (at your option) any later version. */ 10 | /* */ 11 | /* Ethereal is distributed in the hope that it will be useful, */ 12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 | /* GNU General Public License for more details. */ 15 | /* */ 16 | /* You should have received a copy of the GNU General Public License */ 17 | /* along with this program. If not, see */ 18 | /* */ 19 | /******************************************************************************/ 20 | 21 | #pragma once 22 | 23 | #define vepi8 __m128i 24 | #define vepi16 __m128i 25 | #define vepi32 __m128i 26 | #define vps32 __m128 27 | 28 | #define vepi8_cnt 16 29 | #define vepi16_cnt 8 30 | #define vepi32_cnt 4 31 | #define vps32_cnt 4 32 | 33 | #define vepi16_add _mm_add_epi16 34 | #define vepi16_sub _mm_sub_epi16 35 | #define vepi16_max _mm_max_epi16 36 | #define vepi16_madd _mm_madd_epi16 37 | #define vepi16_one _mm_set1_epi16(1) 38 | #define vepi16_zero _mm_setzero_si128 39 | #define vepi16_srai _mm_srai_epi16 40 | #define vepi16_packu _mm_packus_epi16 41 | #define vepi16_maubs _mm_maddubs_epi16 42 | 43 | #define vepi32_add _mm_add_epi32 44 | #define vepi32_max (void) 45 | #define vepi32_hadd _mm_hadd_epi32 46 | #define vepi32_zero _mm_setzero_si128 47 | 48 | #define vps32_add _mm_add_ps 49 | #define vps32_mul _mm_mul_ps 50 | #define vps32_max _mm_max_ps 51 | #define vps32_hadd _mm_hadd_ps 52 | #define vps32_zero _mm_setzero_ps 53 | 54 | #define vps32_fma(A, B, C) _mm_add_ps(_mm_mul_ps(A, B), C) 55 | -------------------------------------------------------------------------------- /src/nnue/nnue.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* */ 3 | /* Ethereal is a UCI chess playing engine authored by Andrew Grant. */ 4 | /* */ 5 | /* */ 6 | /* Ethereal is free software: you can redistribute it and/or modify */ 7 | /* it under the terms of the GNU General Public License as published by */ 8 | /* the Free Software Foundation, either version 3 of the License, or */ 9 | /* (at your option) any later version. */ 10 | /* */ 11 | /* Ethereal is distributed in the hope that it will be useful, */ 12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 | /* GNU General Public License for more details. */ 15 | /* */ 16 | /* You should have received a copy of the GNU General Public License */ 17 | /* along with this program. If not, see */ 18 | /* */ 19 | /******************************************************************************/ 20 | 21 | #pragma once 22 | 23 | #include "../types.h" 24 | 25 | #if USE_NNUE 26 | 27 | void nnue_init(const char* fname); 28 | void nnue_incbin_init(); 29 | int nnue_evaluate(Thread *thread, Board *board); 30 | 31 | #else 32 | 33 | INLINE void nnue_init(const char* fname) { 34 | (void) fname; printf("info string Error: NNUE is disabled for this binary\n"); 35 | } 36 | 37 | INLINE void nnue_incbin_init() { 38 | (void) 0; 39 | }; 40 | 41 | INLINE int nnue_evaluate(Thread *thread, Board * board) { 42 | (void) thread; (void) board; return 0; 43 | } 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/nnue/types.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* */ 3 | /* Ethereal is a UCI chess playing engine authored by Andrew Grant. */ 4 | /* */ 5 | /* */ 6 | /* Ethereal is free software: you can redistribute it and/or modify */ 7 | /* it under the terms of the GNU General Public License as published by */ 8 | /* the Free Software Foundation, either version 3 of the License, or */ 9 | /* (at your option) any later version. */ 10 | /* */ 11 | /* Ethereal is distributed in the hope that it will be useful, */ 12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 | /* GNU General Public License for more details. */ 15 | /* */ 16 | /* You should have received a copy of the GNU General Public License */ 17 | /* along with this program. If not, see */ 18 | /* */ 19 | /******************************************************************************/ 20 | 21 | #pragma once 22 | 23 | #include 24 | 25 | #include "../types.h" 26 | 27 | #if defined(USE_AVX2) 28 | #include "archs/avx2.h" 29 | #elif defined(USE_AVX) 30 | #include "archs/avx.h" 31 | #elif defined(USE_SSSE3) 32 | #include "archs/ssse3.h" 33 | #endif 34 | 35 | #define INSIZE 20480 36 | #define KPSIZE 768 37 | #define L1SIZE 1536 38 | #define L2SIZE 8 39 | #define L3SIZE 32 40 | #define OUTSIZE 1 41 | 42 | #define NUM_REGS 16 43 | 44 | typedef struct NNUEDelta { 45 | int piece, from, to; 46 | } NNUEDelta; 47 | 48 | typedef struct NNUEAccumulator { 49 | int changes, accurate[COLOUR_NB]; 50 | NNUEDelta deltas[3]; 51 | ALIGN64 int16_t values[COLOUR_NB][KPSIZE]; 52 | } NNUEAccumulator; 53 | 54 | typedef struct NNUEAccumulatorTableEntry { 55 | NNUEAccumulator accumulator; 56 | uint64_t occupancy[COLOUR_NB][COLOUR_NB][PIECE_NB-1]; 57 | } NNUEAccumulatorTableEntry; 58 | 59 | typedef struct NNUEEvaluator { 60 | NNUEAccumulator stack[MAX_PLY + 4]; // Each ply of search 61 | NNUEAccumulator *current; // Pointer of the current stack location 62 | NNUEAccumulatorTableEntry table[SQUARE_NB]; // Finny table with Accumulators for each square 63 | } NNUEEvaluator; 64 | -------------------------------------------------------------------------------- /src/nnue/utils.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* */ 3 | /* Ethereal is a UCI chess playing engine authored by Andrew Grant. */ 4 | /* */ 5 | /* */ 6 | /* Ethereal is free software: you can redistribute it and/or modify */ 7 | /* it under the terms of the GNU General Public License as published by */ 8 | /* the Free Software Foundation, either version 3 of the License, or */ 9 | /* (at your option) any later version. */ 10 | /* */ 11 | /* Ethereal is distributed in the hope that it will be useful, */ 12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 | /* GNU General Public License for more details. */ 15 | /* */ 16 | /* You should have received a copy of the GNU General Public License */ 17 | /* along with this program. If not, see */ 18 | /* */ 19 | /******************************************************************************/ 20 | 21 | #pragma once 22 | 23 | #include 24 | 25 | #include "../types.h" 26 | 27 | #if defined(_WIN32) || defined(_WIN64) 28 | 29 | INLINE void* align_malloc(size_t size) { 30 | return _mm_malloc(size, 64); 31 | } 32 | 33 | INLINE void align_free(void *ptr) { 34 | _mm_free(ptr); 35 | } 36 | 37 | #else 38 | 39 | INLINE void* align_malloc(size_t size) { 40 | void *mem; return posix_memalign(&mem, 64, size) ? NULL : mem; 41 | } 42 | 43 | INLINE void align_free(void *ptr) { 44 | free(ptr); 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/perft/perft.py: -------------------------------------------------------------------------------- 1 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 2 | # # 3 | # Ethereal is a UCI chess playing engine authored by Andrew Grant. # 4 | # # 5 | # # 6 | # Ethereal is free software: you can redistribute it and/or modify # 7 | # it under the terms of the GNU General Public License as published by # 8 | # the Free Software Foundation, either version 3 of the License, or # 9 | # (at your option) any later version. # 10 | # # 11 | # Ethereal is distributed in the hope that it will be useful, # 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 14 | # GNU General Public License for more details. # 15 | # # 16 | # You should have received a copy of the GNU General Public License # 17 | # along with this program. If not, see . # 18 | # # 19 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 20 | 21 | import argparse, subprocess, sys, time 22 | 23 | parser = argparse.ArgumentParser() 24 | parser.add_argument('engine', help='Path to Engine') 25 | parser.add_argument('dataset', help='PERFT data file') 26 | parser.add_argument('--depth', help='Depth of PERFT [ blank = unlimited ]', default=128) 27 | arguments = parser.parse_args() 28 | 29 | process = subprocess.Popen( 30 | arguments.engine, 31 | stderr=subprocess.STDOUT, 32 | stdout=subprocess.PIPE, 33 | stdin=subprocess.PIPE, 34 | universal_newlines=True 35 | ) 36 | 37 | with open(arguments.dataset) as fin: 38 | data = fin.readlines() 39 | 40 | SEPERATOR = '-' * 111 + '\n' 41 | HEADER = '| {0:^5} | {1:^11} | {2:^76} | {3:^6} |\n' 42 | 43 | sys.stdout.write(SEPERATOR) 44 | sys.stdout.write(HEADER.format('Depth', 'Nodes', 'FEN', 'Status')) 45 | sys.stdout.write(SEPERATOR) 46 | 47 | start = time.time() 48 | 49 | for line in data: 50 | 51 | for token in line.split(';')[1:]: 52 | 53 | fen = line.split(';')[0] 54 | depth = int(token.split(' ')[0][1:]) 55 | nodes = int(token.split(' ')[1]) 56 | if depth > arguments.depth: break 57 | 58 | sys.stdout.write('| {0:>5} | {1:>11} | {2:<76} | '.format(depth, nodes, fen)) 59 | sys.stdout.flush() 60 | 61 | process.stdin.write('position fen %s\n' % (fen)) 62 | process.stdin.write('perft %d\n' % (depth)) 63 | process.stdin.flush() 64 | 65 | found = int(process.stdout.readline().strip()) 66 | 67 | if found == nodes: sys.stdout.write (' PASS |\n') 68 | if found != nodes: sys.stdout.write (' FAIL | (%10d)\n' % (found)) 69 | 70 | process.stdin.write('quit') 71 | process.stdin.flush() 72 | 73 | sys.stdout.write(SEPERATOR) 74 | sys.stdout.write('Total Time: %dms' % (1000 * (time.time() - start))) 75 | -------------------------------------------------------------------------------- /src/perft/standard.epd: -------------------------------------------------------------------------------- 1 | rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 ;D1 20 ;D2 400 ;D3 8902 ;D4 197281 ;D5 4865609 ;D6 119060324 2 | r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1 ;D1 48 ;D2 2039 ;D3 97862 ;D4 4085603 ;D5 193690690 3 | 4k3/8/8/8/8/8/8/4K2R w K - 0 1 ;D1 15 ;D2 66 ;D3 1197 ;D4 7059 ;D5 133987 ;D6 764643 4 | 4k3/8/8/8/8/8/8/R3K3 w Q - 0 1 ;D1 16 ;D2 71 ;D3 1287 ;D4 7626 ;D5 145232 ;D6 846648 5 | 4k2r/8/8/8/8/8/8/4K3 w k - 0 1 ;D1 5 ;D2 75 ;D3 459 ;D4 8290 ;D5 47635 ;D6 899442 6 | r3k3/8/8/8/8/8/8/4K3 w q - 0 1 ;D1 5 ;D2 80 ;D3 493 ;D4 8897 ;D5 52710 ;D6 1001523 7 | 4k3/8/8/8/8/8/8/R3K2R w KQ - 0 1 ;D1 26 ;D2 112 ;D3 3189 ;D4 17945 ;D5 532933 ;D6 2788982 8 | r3k2r/8/8/8/8/8/8/4K3 w kq - 0 1 ;D1 5 ;D2 130 ;D3 782 ;D4 22180 ;D5 118882 ;D6 3517770 9 | 8/8/8/8/8/8/6k1/4K2R w K - 0 1 ;D1 12 ;D2 38 ;D3 564 ;D4 2219 ;D5 37735 ;D6 185867 10 | 8/8/8/8/8/8/1k6/R3K3 w Q - 0 1 ;D1 15 ;D2 65 ;D3 1018 ;D4 4573 ;D5 80619 ;D6 413018 11 | 4k2r/6K1/8/8/8/8/8/8 w k - 0 1 ;D1 3 ;D2 32 ;D3 134 ;D4 2073 ;D5 10485 ;D6 179869 12 | r3k3/1K6/8/8/8/8/8/8 w q - 0 1 ;D1 4 ;D2 49 ;D3 243 ;D4 3991 ;D5 20780 ;D6 367724 13 | r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1 ;D1 26 ;D2 568 ;D3 13744 ;D4 314346 ;D5 7594526 ;D6 179862938 14 | r3k2r/8/8/8/8/8/8/1R2K2R w Kkq - 0 1 ;D1 25 ;D2 567 ;D3 14095 ;D4 328965 ;D5 8153719 ;D6 195629489 15 | r3k2r/8/8/8/8/8/8/2R1K2R w Kkq - 0 1 ;D1 25 ;D2 548 ;D3 13502 ;D4 312835 ;D5 7736373 ;D6 184411439 16 | r3k2r/8/8/8/8/8/8/R3K1R1 w Qkq - 0 1 ;D1 25 ;D2 547 ;D3 13579 ;D4 316214 ;D5 7878456 ;D6 189224276 17 | 1r2k2r/8/8/8/8/8/8/R3K2R w KQk - 0 1 ;D1 26 ;D2 583 ;D3 14252 ;D4 334705 ;D5 8198901 ;D6 198328929 18 | 2r1k2r/8/8/8/8/8/8/R3K2R w KQk - 0 1 ;D1 25 ;D2 560 ;D3 13592 ;D4 317324 ;D5 7710115 ;D6 185959088 19 | r3k1r1/8/8/8/8/8/8/R3K2R w KQq - 0 1 ;D1 25 ;D2 560 ;D3 13607 ;D4 320792 ;D5 7848606 ;D6 190755813 20 | 4k3/8/8/8/8/8/8/4K2R b K - 0 1 ;D1 5 ;D2 75 ;D3 459 ;D4 8290 ;D5 47635 ;D6 899442 21 | 4k3/8/8/8/8/8/8/R3K3 b Q - 0 1 ;D1 5 ;D2 80 ;D3 493 ;D4 8897 ;D5 52710 ;D6 1001523 22 | 4k2r/8/8/8/8/8/8/4K3 b k - 0 1 ;D1 15 ;D2 66 ;D3 1197 ;D4 7059 ;D5 133987 ;D6 764643 23 | r3k3/8/8/8/8/8/8/4K3 b q - 0 1 ;D1 16 ;D2 71 ;D3 1287 ;D4 7626 ;D5 145232 ;D6 846648 24 | 4k3/8/8/8/8/8/8/R3K2R b KQ - 0 1 ;D1 5 ;D2 130 ;D3 782 ;D4 22180 ;D5 118882 ;D6 3517770 25 | r3k2r/8/8/8/8/8/8/4K3 b kq - 0 1 ;D1 26 ;D2 112 ;D3 3189 ;D4 17945 ;D5 532933 ;D6 2788982 26 | 8/8/8/8/8/8/6k1/4K2R b K - 0 1 ;D1 3 ;D2 32 ;D3 134 ;D4 2073 ;D5 10485 ;D6 179869 27 | 8/8/8/8/8/8/1k6/R3K3 b Q - 0 1 ;D1 4 ;D2 49 ;D3 243 ;D4 3991 ;D5 20780 ;D6 367724 28 | 4k2r/6K1/8/8/8/8/8/8 b k - 0 1 ;D1 12 ;D2 38 ;D3 564 ;D4 2219 ;D5 37735 ;D6 185867 29 | r3k3/1K6/8/8/8/8/8/8 b q - 0 1 ;D1 15 ;D2 65 ;D3 1018 ;D4 4573 ;D5 80619 ;D6 413018 30 | r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1 ;D1 26 ;D2 568 ;D3 13744 ;D4 314346 ;D5 7594526 ;D6 179862938 31 | r3k2r/8/8/8/8/8/8/1R2K2R b Kkq - 0 1 ;D1 26 ;D2 583 ;D3 14252 ;D4 334705 ;D5 8198901 ;D6 198328929 32 | r3k2r/8/8/8/8/8/8/2R1K2R b Kkq - 0 1 ;D1 25 ;D2 560 ;D3 13592 ;D4 317324 ;D5 7710115 ;D6 185959088 33 | r3k2r/8/8/8/8/8/8/R3K1R1 b Qkq - 0 1 ;D1 25 ;D2 560 ;D3 13607 ;D4 320792 ;D5 7848606 ;D6 190755813 34 | 1r2k2r/8/8/8/8/8/8/R3K2R b KQk - 0 1 ;D1 25 ;D2 567 ;D3 14095 ;D4 328965 ;D5 8153719 ;D6 195629489 35 | 2r1k2r/8/8/8/8/8/8/R3K2R b KQk - 0 1 ;D1 25 ;D2 548 ;D3 13502 ;D4 312835 ;D5 7736373 ;D6 184411439 36 | r3k1r1/8/8/8/8/8/8/R3K2R b KQq - 0 1 ;D1 25 ;D2 547 ;D3 13579 ;D4 316214 ;D5 7878456 ;D6 189224276 37 | 8/1n4N1/2k5/8/8/5K2/1N4n1/8 w - - 0 1 ;D1 14 ;D2 195 ;D3 2760 ;D4 38675 ;D5 570726 ;D6 8107539 38 | 8/1k6/8/5N2/8/4n3/8/2K5 w - - 0 1 ;D1 11 ;D2 156 ;D3 1636 ;D4 20534 ;D5 223507 ;D6 2594412 39 | 8/8/4k3/3Nn3/3nN3/4K3/8/8 w - - 0 1 ;D1 19 ;D2 289 ;D3 4442 ;D4 73584 ;D5 1198299 ;D6 19870403 40 | K7/8/2n5/1n6/8/8/8/k6N w - - 0 1 ;D1 3 ;D2 51 ;D3 345 ;D4 5301 ;D5 38348 ;D6 588695 41 | k7/8/2N5/1N6/8/8/8/K6n w - - 0 1 ;D1 17 ;D2 54 ;D3 835 ;D4 5910 ;D5 92250 ;D6 688780 42 | 8/1n4N1/2k5/8/8/5K2/1N4n1/8 b - - 0 1 ;D1 15 ;D2 193 ;D3 2816 ;D4 40039 ;D5 582642 ;D6 8503277 43 | 8/1k6/8/5N2/8/4n3/8/2K5 b - - 0 1 ;D1 16 ;D2 180 ;D3 2290 ;D4 24640 ;D5 288141 ;D6 3147566 44 | 8/8/3K4/3Nn3/3nN3/4k3/8/8 b - - 0 1 ;D1 4 ;D2 68 ;D3 1118 ;D4 16199 ;D5 281190 ;D6 4405103 45 | K7/8/2n5/1n6/8/8/8/k6N b - - 0 1 ;D1 17 ;D2 54 ;D3 835 ;D4 5910 ;D5 92250 ;D6 688780 46 | k7/8/2N5/1N6/8/8/8/K6n b - - 0 1 ;D1 3 ;D2 51 ;D3 345 ;D4 5301 ;D5 38348 ;D6 588695 47 | B6b/8/8/8/2K5/4k3/8/b6B w - - 0 1 ;D1 17 ;D2 278 ;D3 4607 ;D4 76778 ;D5 1320507 ;D6 22823890 48 | 8/8/1B6/7b/7k/8/2B1b3/7K w - - 0 1 ;D1 21 ;D2 316 ;D3 5744 ;D4 93338 ;D5 1713368 ;D6 28861171 49 | k7/B7/1B6/1B6/8/8/8/K6b w - - 0 1 ;D1 21 ;D2 144 ;D3 3242 ;D4 32955 ;D5 787524 ;D6 7881673 50 | K7/b7/1b6/1b6/8/8/8/k6B w - - 0 1 ;D1 7 ;D2 143 ;D3 1416 ;D4 31787 ;D5 310862 ;D6 7382896 51 | B6b/8/8/8/2K5/5k2/8/b6B b - - 0 1 ;D1 6 ;D2 106 ;D3 1829 ;D4 31151 ;D5 530585 ;D6 9250746 52 | 8/8/1B6/7b/7k/8/2B1b3/7K b - - 0 1 ;D1 17 ;D2 309 ;D3 5133 ;D4 93603 ;D5 1591064 ;D6 29027891 53 | k7/B7/1B6/1B6/8/8/8/K6b b - - 0 1 ;D1 7 ;D2 143 ;D3 1416 ;D4 31787 ;D5 310862 ;D6 7382896 54 | K7/b7/1b6/1b6/8/8/8/k6B b - - 0 1 ;D1 21 ;D2 144 ;D3 3242 ;D4 32955 ;D5 787524 ;D6 7881673 55 | 7k/RR6/8/8/8/8/rr6/7K w - - 0 1 ;D1 19 ;D2 275 ;D3 5300 ;D4 104342 ;D5 2161211 ;D6 44956585 56 | R6r/8/8/2K5/5k2/8/8/r6R w - - 0 1 ;D1 36 ;D2 1027 ;D3 29215 ;D4 771461 ;D5 20506480 ;D6 525169084 57 | 7k/RR6/8/8/8/8/rr6/7K b - - 0 1 ;D1 19 ;D2 275 ;D3 5300 ;D4 104342 ;D5 2161211 ;D6 44956585 58 | R6r/8/8/2K5/5k2/8/8/r6R b - - 0 1 ;D1 36 ;D2 1027 ;D3 29227 ;D4 771368 ;D5 20521342 ;D6 524966748 59 | 6kq/8/8/8/8/8/8/7K w - - 0 1 ;D1 2 ;D2 36 ;D3 143 ;D4 3637 ;D5 14893 ;D6 391507 60 | 6KQ/8/8/8/8/8/8/7k b - - 0 1 ;D1 2 ;D2 36 ;D3 143 ;D4 3637 ;D5 14893 ;D6 391507 61 | K7/8/8/3Q4/4q3/8/8/7k w - - 0 1 ;D1 6 ;D2 35 ;D3 495 ;D4 8349 ;D5 166741 ;D6 3370175 62 | 6qk/8/8/8/8/8/8/7K b - - 0 1 ;D1 22 ;D2 43 ;D3 1015 ;D4 4167 ;D5 105749 ;D6 419369 63 | 6KQ/8/8/8/8/8/8/7k b - - 0 1 ;D1 2 ;D2 36 ;D3 143 ;D4 3637 ;D5 14893 ;D6 391507 64 | K7/8/8/3Q4/4q3/8/8/7k b - - 0 1 ;D1 6 ;D2 35 ;D3 495 ;D4 8349 ;D5 166741 ;D6 3370175 65 | 8/8/8/8/8/K7/P7/k7 w - - 0 1 ;D1 3 ;D2 7 ;D3 43 ;D4 199 ;D5 1347 ;D6 6249 66 | 8/8/8/8/8/7K/7P/7k w - - 0 1 ;D1 3 ;D2 7 ;D3 43 ;D4 199 ;D5 1347 ;D6 6249 67 | K7/p7/k7/8/8/8/8/8 w - - 0 1 ;D1 1 ;D2 3 ;D3 12 ;D4 80 ;D5 342 ;D6 2343 68 | 7K/7p/7k/8/8/8/8/8 w - - 0 1 ;D1 1 ;D2 3 ;D3 12 ;D4 80 ;D5 342 ;D6 2343 69 | 8/2k1p3/3pP3/3P2K1/8/8/8/8 w - - 0 1 ;D1 7 ;D2 35 ;D3 210 ;D4 1091 ;D5 7028 ;D6 34834 70 | 8/8/8/8/8/K7/P7/k7 b - - 0 1 ;D1 1 ;D2 3 ;D3 12 ;D4 80 ;D5 342 ;D6 2343 71 | 8/8/8/8/8/7K/7P/7k b - - 0 1 ;D1 1 ;D2 3 ;D3 12 ;D4 80 ;D5 342 ;D6 2343 72 | K7/p7/k7/8/8/8/8/8 b - - 0 1 ;D1 3 ;D2 7 ;D3 43 ;D4 199 ;D5 1347 ;D6 6249 73 | 7K/7p/7k/8/8/8/8/8 b - - 0 1 ;D1 3 ;D2 7 ;D3 43 ;D4 199 ;D5 1347 ;D6 6249 74 | 8/2k1p3/3pP3/3P2K1/8/8/8/8 b - - 0 1 ;D1 5 ;D2 35 ;D3 182 ;D4 1091 ;D5 5408 ;D6 34822 75 | 8/8/8/8/8/4k3/4P3/4K3 w - - 0 1 ;D1 2 ;D2 8 ;D3 44 ;D4 282 ;D5 1814 ;D6 11848 76 | 4k3/4p3/4K3/8/8/8/8/8 b - - 0 1 ;D1 2 ;D2 8 ;D3 44 ;D4 282 ;D5 1814 ;D6 11848 77 | 8/8/7k/7p/7P/7K/8/8 w - - 0 1 ;D1 3 ;D2 9 ;D3 57 ;D4 360 ;D5 1969 ;D6 10724 78 | 8/8/k7/p7/P7/K7/8/8 w - - 0 1 ;D1 3 ;D2 9 ;D3 57 ;D4 360 ;D5 1969 ;D6 10724 79 | 8/8/3k4/3p4/3P4/3K4/8/8 w - - 0 1 ;D1 5 ;D2 25 ;D3 180 ;D4 1294 ;D5 8296 ;D6 53138 80 | 8/3k4/3p4/8/3P4/3K4/8/8 w - - 0 1 ;D1 8 ;D2 61 ;D3 483 ;D4 3213 ;D5 23599 ;D6 157093 81 | 8/8/3k4/3p4/8/3P4/3K4/8 w - - 0 1 ;D1 8 ;D2 61 ;D3 411 ;D4 3213 ;D5 21637 ;D6 158065 82 | k7/8/3p4/8/3P4/8/8/7K w - - 0 1 ;D1 4 ;D2 15 ;D3 90 ;D4 534 ;D5 3450 ;D6 20960 83 | 8/8/7k/7p/7P/7K/8/8 b - - 0 1 ;D1 3 ;D2 9 ;D3 57 ;D4 360 ;D5 1969 ;D6 10724 84 | 8/8/k7/p7/P7/K7/8/8 b - - 0 1 ;D1 3 ;D2 9 ;D3 57 ;D4 360 ;D5 1969 ;D6 10724 85 | 8/8/3k4/3p4/3P4/3K4/8/8 b - - 0 1 ;D1 5 ;D2 25 ;D3 180 ;D4 1294 ;D5 8296 ;D6 53138 86 | 8/3k4/3p4/8/3P4/3K4/8/8 b - - 0 1 ;D1 8 ;D2 61 ;D3 411 ;D4 3213 ;D5 21637 ;D6 158065 87 | 8/8/3k4/3p4/8/3P4/3K4/8 b - - 0 1 ;D1 8 ;D2 61 ;D3 483 ;D4 3213 ;D5 23599 ;D6 157093 88 | k7/8/3p4/8/3P4/8/8/7K b - - 0 1 ;D1 4 ;D2 15 ;D3 89 ;D4 537 ;D5 3309 ;D6 21104 89 | 7k/3p4/8/8/3P4/8/8/K7 w - - 0 1 ;D1 4 ;D2 19 ;D3 117 ;D4 720 ;D5 4661 ;D6 32191 90 | 7k/8/8/3p4/8/8/3P4/K7 w - - 0 1 ;D1 5 ;D2 19 ;D3 116 ;D4 716 ;D5 4786 ;D6 30980 91 | k7/8/8/7p/6P1/8/8/K7 w - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 92 | k7/8/7p/8/8/6P1/8/K7 w - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 93 | k7/8/8/6p1/7P/8/8/K7 w - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 94 | k7/8/6p1/8/8/7P/8/K7 w - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 95 | k7/8/8/3p4/4p3/8/8/7K w - - 0 1 ;D1 3 ;D2 15 ;D3 84 ;D4 573 ;D5 3013 ;D6 22886 96 | k7/8/3p4/8/8/4P3/8/7K w - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4271 ;D6 28662 97 | 7k/3p4/8/8/3P4/8/8/K7 b - - 0 1 ;D1 5 ;D2 19 ;D3 117 ;D4 720 ;D5 5014 ;D6 32167 98 | 7k/8/8/3p4/8/8/3P4/K7 b - - 0 1 ;D1 4 ;D2 19 ;D3 117 ;D4 712 ;D5 4658 ;D6 30749 99 | k7/8/8/7p/6P1/8/8/K7 b - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 100 | k7/8/7p/8/8/6P1/8/K7 b - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 101 | k7/8/8/6p1/7P/8/8/K7 b - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 102 | k7/8/6p1/8/8/7P/8/K7 b - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 103 | k7/8/8/3p4/4p3/8/8/7K b - - 0 1 ;D1 5 ;D2 15 ;D3 102 ;D4 569 ;D5 4337 ;D6 22579 104 | k7/8/3p4/8/8/4P3/8/7K b - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4271 ;D6 28662 105 | 7k/8/8/p7/1P6/8/8/7K w - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 106 | 7k/8/p7/8/8/1P6/8/7K w - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 107 | 7k/8/8/1p6/P7/8/8/7K w - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 108 | 7k/8/1p6/8/8/P7/8/7K w - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 109 | k7/7p/8/8/8/8/6P1/K7 w - - 0 1 ;D1 5 ;D2 25 ;D3 161 ;D4 1035 ;D5 7574 ;D6 55338 110 | k7/6p1/8/8/8/8/7P/K7 w - - 0 1 ;D1 5 ;D2 25 ;D3 161 ;D4 1035 ;D5 7574 ;D6 55338 111 | 3k4/3pp3/8/8/8/8/3PP3/3K4 w - - 0 1 ;D1 7 ;D2 49 ;D3 378 ;D4 2902 ;D5 24122 ;D6 199002 112 | 7k/8/8/p7/1P6/8/8/7K b - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 113 | 7k/8/p7/8/8/1P6/8/7K b - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 114 | 7k/8/8/1p6/P7/8/8/7K b - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 115 | 7k/8/1p6/8/8/P7/8/7K b - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 116 | k7/7p/8/8/8/8/6P1/K7 b - - 0 1 ;D1 5 ;D2 25 ;D3 161 ;D4 1035 ;D5 7574 ;D6 55338 117 | k7/6p1/8/8/8/8/7P/K7 b - - 0 1 ;D1 5 ;D2 25 ;D3 161 ;D4 1035 ;D5 7574 ;D6 55338 118 | 3k4/3pp3/8/8/8/8/3PP3/3K4 b - - 0 1 ;D1 7 ;D2 49 ;D3 378 ;D4 2902 ;D5 24122 ;D6 199002 119 | 8/Pk6/8/8/8/8/6Kp/8 w - - 0 1 ;D1 11 ;D2 97 ;D3 887 ;D4 8048 ;D5 90606 ;D6 1030499 120 | n1n5/1Pk5/8/8/8/8/5Kp1/5N1N w - - 0 1 ;D1 24 ;D2 421 ;D3 7421 ;D4 124608 ;D5 2193768 ;D6 37665329 121 | 8/PPPk4/8/8/8/8/4Kppp/8 w - - 0 1 ;D1 18 ;D2 270 ;D3 4699 ;D4 79355 ;D5 1533145 ;D6 28859283 122 | n1n5/PPPk4/8/8/8/8/4Kppp/5N1N w - - 0 1 ;D1 24 ;D2 496 ;D3 9483 ;D4 182838 ;D5 3605103 ;D6 71179139 123 | 8/Pk6/8/8/8/8/6Kp/8 b - - 0 1 ;D1 11 ;D2 97 ;D3 887 ;D4 8048 ;D5 90606 ;D6 1030499 124 | n1n5/1Pk5/8/8/8/8/5Kp1/5N1N b - - 0 1 ;D1 24 ;D2 421 ;D3 7421 ;D4 124608 ;D5 2193768 ;D6 37665329 125 | 8/PPPk4/8/8/8/8/4Kppp/8 b - - 0 1 ;D1 18 ;D2 270 ;D3 4699 ;D4 79355 ;D5 1533145 ;D6 28859283 126 | n1n5/PPPk4/8/8/8/8/4Kppp/5N1N b - - 0 1 ;D1 24 ;D2 496 ;D3 9483 ;D4 182838 ;D5 3605103 ;D6 71179139 127 | 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1 ;D4 43238 ;D5 674624 ;D6 11030083 128 | rnbqkb1r/ppppp1pp/7n/4Pp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3 ;D5 11139762 -------------------------------------------------------------------------------- /src/pgn.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | 23 | #include "types.h" 24 | 25 | enum { PGN_LOSS, PGN_DRAW, PGN_WIN, PGN_NO_RESULT, PGN_UNKNOWN_RESULT }; 26 | 27 | typedef struct PGNData { 28 | char *startpos; 29 | bool is_white, is_black; 30 | int result, plies; 31 | char buffer[65536]; 32 | } PGNData; 33 | 34 | void process_pgn(const char *fin, const char *fout); 35 | -------------------------------------------------------------------------------- /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__) || \ 30 | defined(__DragonFly__) || defined(__OpenBSD__) 31 | #include 32 | #define __ENDIAN_DEFINED 1 33 | #define __BSWAP_DEFINED 1 34 | #define __HOSTSWAP_DEFINED 1 35 | #endif /* BSD */ 36 | 37 | /* Solaris */ 38 | #if defined (sun) 39 | #include 40 | /* sun headers don't set a value for _LITTLE_ENDIAN or _BIG_ENDIAN */ 41 | #if defined(_LITTLE_ENDIAN) 42 | #undef _LITTLE_ENDIAN 43 | #define _LITTLE_ENDIAN 1234 44 | #define _BIG_ENDIAN 4321 45 | #define _BYTE_ORDER _LITTLE_ENDIAN 46 | #elif defined(_BIG_ENDIAN) 47 | #undef _BIG_ENDIAN 48 | #define _LITTLE_ENDIAN 1234 49 | #define _BIG_ENDIAN 4321 50 | #define _BYTE_ORDER _BIG_ENDIAN 51 | #endif 52 | #define __ENDIAN_DEFINED 1 53 | #endif /* sun */ 54 | 55 | /* Windows */ 56 | #if defined(_WIN32) || defined(_MSC_VER) 57 | /* assumes all Microsoft targets are little endian */ 58 | #define _LITTLE_ENDIAN 1234 59 | #define _BIG_ENDIAN 4321 60 | #define _BYTE_ORDER _LITTLE_ENDIAN 61 | #define __ENDIAN_DEFINED 1 62 | #endif /* _MSC_VER */ 63 | 64 | /* OS X */ 65 | #if defined(__APPLE__) 66 | #include 67 | #define _BYTE_ORDER BYTE_ORDER 68 | #define _LITTLE_ENDIAN LITTLE_ENDIAN 69 | #define _BIG_ENDIAN BIG_ENDIAN 70 | #define __ENDIAN_DEFINED 1 71 | #endif /* __APPLE__ */ 72 | 73 | /* OpenCL */ 74 | #if defined (__OPENCL_VERSION__) 75 | #define _LITTLE_ENDIAN 1234 76 | #define __BIG_ENDIAN 4321 77 | #if defined (__ENDIAN_LITTLE__) 78 | #define _BYTE_ORDER _LITTLE_ENDIAN 79 | #else 80 | #define _BYTE_ORDER _BIG_ENDIAN 81 | #endif 82 | #define bswap16(x) as_ushort(as_uchar2(ushort(x)).s1s0) 83 | #define bswap32(x) as_uint(as_uchar4(uint(x)).s3s2s1s0) 84 | #define bswap64(x) as_ulong(as_uchar8(ulong(x)).s7s6s5s4s3s2s1s0) 85 | #define __ENDIAN_DEFINED 1 86 | #define __BSWAP_DEFINED 1 87 | #endif 88 | 89 | /* Unknown */ 90 | #if !__ENDIAN_DEFINED 91 | #error Could not determine CPU byte order 92 | #endif 93 | 94 | /* POSIX - http://austingroupbugs.net/view.php?id=162 */ 95 | #ifndef BYTE_ORDER 96 | #define BYTE_ORDER _BYTE_ORDER 97 | #endif 98 | #ifndef LITTLE_ENDIAN 99 | #define LITTLE_ENDIAN _LITTLE_ENDIAN 100 | #endif 101 | #ifndef BIG_ENDIAN 102 | #define BIG_ENDIAN _BIG_ENDIAN 103 | #endif 104 | 105 | /* OpenCL compatibility - define __ENDIAN_LITTLE__ on little endian systems */ 106 | #if _BYTE_ORDER == _LITTLE_ENDIAN 107 | #if !defined (__ENDIAN_LITTLE__) 108 | #define __ENDIAN_LITTLE__ 1 109 | #endif 110 | #endif 111 | 112 | /* Byte swap macros */ 113 | #if !__BSWAP_DEFINED 114 | 115 | #ifndef bswap16 116 | /* handle missing __builtin_bswap16 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) | \ 122 | (((uint16_t) (x) & 0x00ff) << 8)); 123 | } 124 | #endif 125 | #endif 126 | 127 | #ifndef bswap32 128 | #if defined __GNUC__ 129 | #define bswap32(x) __builtin_bswap32(x) 130 | #else 131 | inline uint32_t bswap32(uint32_t x) { 132 | return (( x & 0xff000000) >> 24) | \ 133 | (( x & 0x00ff0000) >> 8) | \ 134 | (( x & 0x0000ff00) << 8) | \ 135 | (( x & 0x000000ff) << 24); 136 | } 137 | #endif 138 | #endif 139 | 140 | #ifndef bswap64 141 | #if defined __GNUC__ 142 | #define bswap64(x) __builtin_bswap64(x) 143 | #else 144 | inline uint64_t bswap64(uint64_t x) { 145 | return (( x & 0xff00000000000000ull) >> 56) | \ 146 | (( x & 0x00ff000000000000ull) >> 40) | \ 147 | (( x & 0x0000ff0000000000ull) >> 24) | \ 148 | (( x & 0x000000ff00000000ull) >> 8) | \ 149 | (( x & 0x00000000ff000000ull) << 8) | \ 150 | (( x & 0x0000000000ff0000ull) << 24) | \ 151 | (( x & 0x000000000000ff00ull) << 40) | \ 152 | (( x & 0x00000000000000ffull) << 56); 153 | } 154 | #endif 155 | #endif 156 | 157 | #endif 158 | 159 | /* Host swap macros */ 160 | #ifndef __HOSTSWAP_DEFINED 161 | #if __BYTE_ORDER == __LITTLE_ENDIAN 162 | #define htobe16(x) bswap16((x)) 163 | #define htole16(x) ((uint16_t)(x)) 164 | #define be16toh(x) bswap16((x)) 165 | #define le16toh(x) ((uint16_t)(x)) 166 | 167 | #define htobe32(x) bswap32((x)) 168 | #define htole32(x) ((uint32_t((x)) 169 | #define be32toh(x) bswap32((x)) 170 | #define le32toh(x) ((uint32_t)(x)) 171 | 172 | #define htobe64(x) bswap64((x)) 173 | #define htole64(x) ((uint64_t)(x)) 174 | #define be64toh(x) bswap64((x)) 175 | #define le64toh(x) ((uint64_t)(x)) 176 | #elif __BYTE_ORDER == __BIG_ENDIAN 177 | #define htobe16(x) ((uint16_t)(x)) 178 | #define htole16(x) bswap16((x)) 179 | #define be16toh(x) ((uint16_t)(x)) 180 | #define le16toh(x) bswap16((x)) 181 | 182 | #define htobe32(x) ((uint32_t)(x)) 183 | #define htole32(x) bswap32((x)) 184 | #define be32toh(x) ((uint32_t)(x)) 185 | #define le64toh(x) bswap64((x)) 186 | 187 | #define htobe64(x) ((uint64_t)(x)) 188 | #define htole64(x) bswap64((x)) 189 | #define be64toh(x) ((uint64_t)(x)) 190 | #define le32toh(x) bswap32((x)) 191 | #endif 192 | #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 all 14 | * 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 "../bitboards.h" 43 | 44 | #define PYRRHIC_POPCOUNT(x) (popcount(x)) 45 | #define PYRRHIC_LSB(x) (getlsb(x)) 46 | #define PYRRHIC_POPLSB(x) (poplsb(x)) 47 | 48 | #define PYRRHIC_PAWN_ATTACKS(sq, c) (pawnAttacks(!c, sq)) 49 | #define PYRRHIC_KNIGHT_ATTACKS(sq) (knightAttacks(sq)) 50 | #define PYRRHIC_BISHOP_ATTACKS(sq, occ) (bishopAttacks(sq, occ)) 51 | #define PYRRHIC_ROOK_ATTACKS(sq, occ) (rookAttacks(sq, occ)) 52 | #define PYRRHIC_QUEEN_ATTACKS(sq, occ) (queenAttacks(sq, occ)) 53 | #define PYRRHIC_KING_ATTACKS(sq) (kingAttacks(sq)) 54 | 55 | /* 56 | * Pyrrhic can produce scores for tablebase moves. These depend on the value 57 | * of a pawn, and the magnitude of mate scores, and will be engine specific. 58 | * 59 | * In Ethereal, I personally do not make use of these scores. They are to rank 60 | * moves. Without these values you are still able to detmine which moves Win, 61 | * Draw, and Lose. PYRRHIC_MAX_MATE_PLY should be your max search height. 62 | */ 63 | #define PYRRHIC_VALUE_PAWN ( 100) 64 | #define PYRRHIC_VALUE_MATE (32000) 65 | #define PYRRHIC_VALUE_DRAW ( 0) 66 | #define PYRRHIC_MAX_MATE_PLY ( 255) 67 | -------------------------------------------------------------------------------- /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 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 | 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) \ 62 | (((_res) & TB_RESULT_WDL_MASK) >> TB_RESULT_WDL_SHIFT) 63 | #define TB_GET_TO(_res) \ 64 | (((_res) & TB_RESULT_TO_MASK) >> TB_RESULT_TO_SHIFT) 65 | #define TB_GET_FROM(_res) \ 66 | (((_res) & TB_RESULT_FROM_MASK) >> TB_RESULT_FROM_SHIFT) 67 | #define TB_GET_PROMOTES(_res) \ 68 | (((_res) & TB_RESULT_PROMOTES_MASK) >> TB_RESULT_PROMOTES_SHIFT) 69 | #define TB_GET_EP(_res) \ 70 | (((_res) & TB_RESULT_EP_MASK) >> TB_RESULT_EP_SHIFT) 71 | #define TB_GET_DTZ(_res) \ 72 | (((_res) & TB_RESULT_DTZ_MASK) >> TB_RESULT_DTZ_SHIFT) 73 | 74 | #define TB_SET_WDL(_res, _wdl) \ 75 | (((_res) & ~TB_RESULT_WDL_MASK) | \ 76 | (((_wdl) << TB_RESULT_WDL_SHIFT) & TB_RESULT_WDL_MASK)) 77 | #define TB_SET_TO(_res, _to) \ 78 | (((_res) & ~TB_RESULT_TO_MASK) | \ 79 | (((_to) << TB_RESULT_TO_SHIFT) & TB_RESULT_TO_MASK)) 80 | #define TB_SET_FROM(_res, _from) \ 81 | (((_res) & ~TB_RESULT_FROM_MASK) | \ 82 | (((_from) << TB_RESULT_FROM_SHIFT) & TB_RESULT_FROM_MASK)) 83 | #define TB_SET_PROMOTES(_res, _promotes) \ 84 | (((_res) & ~TB_RESULT_PROMOTES_MASK) | \ 85 | (((_promotes) << TB_RESULT_PROMOTES_SHIFT) & TB_RESULT_PROMOTES_MASK)) 86 | #define TB_SET_EP(_res, _ep) \ 87 | (((_res) & ~TB_RESULT_EP_MASK) | \ 88 | (((_ep) << TB_RESULT_EP_SHIFT) & TB_RESULT_EP_MASK)) 89 | #define TB_SET_DTZ(_res, _dtz) \ 90 | (((_res) & ~TB_RESULT_DTZ_MASK) | \ 91 | (((_dtz) << TB_RESULT_DTZ_SHIFT) & TB_RESULT_DTZ_MASK)) 92 | 93 | #define TB_RESULT_CHECKMATE TB_SET_WDL(0, TB_WIN) 94 | #define TB_RESULT_STALEMATE TB_SET_WDL(0, TB_DRAW) 95 | #define TB_RESULT_FAILED 0xFFFFFFFF 96 | 97 | /* 98 | * The tablebase can be probed for any position where #pieces <= TB_LARGEST. 99 | */ 100 | extern int TB_LARGEST; 101 | 102 | /* 103 | * Initialize the tablebase. 104 | * 105 | * PARAMETERS: 106 | * - path: 107 | * The tablebase PATH string. 108 | * 109 | * RETURN: 110 | * - true=succes, false=failed. The TB_LARGEST global will also be 111 | * initialized. If no tablebase files are found, then `true' is returned 112 | * and TB_LARGEST is set to zero. 113 | */ 114 | bool tb_init(const char *_path); 115 | 116 | /* 117 | * Free any resources allocated by tb_init 118 | */ 119 | void tb_free(void); 120 | 121 | /* 122 | * Probe the Win-Draw-Loss (WDL) table. 123 | * 124 | * PARAMETERS: 125 | * - white, black, kings, queens, rooks, bishops, knights, pawns: 126 | * The current position (bitboards). 127 | * - ep: 128 | * The en passant square (if exists). Set to zero if there is no en passant 129 | * square. 130 | * - turn: 131 | * true=white, false=black 132 | * 133 | * RETURN: 134 | * - One of {TB_LOSS, TB_BLESSED_LOSS, TB_DRAW, TB_CURSED_WIN, TB_WIN}. 135 | * Otherwise returns TB_RESULT_FAILED if the probe failed. 136 | * 137 | * NOTES: 138 | * - Engines should use this function during search. 139 | * - This function is thread safe assuming TB_NO_THREADS is disabled. 140 | */ 141 | unsigned tb_probe_wdl( 142 | uint64_t white, uint64_t black, 143 | uint64_t kings, uint64_t queens, 144 | uint64_t rooks, uint64_t bishops, 145 | uint64_t knights, uint64_t pawns, 146 | unsigned ep, bool turn); 147 | 148 | /* 149 | * Probe the Distance-To-Zero (DTZ) table. 150 | * 151 | * PARAMETERS: 152 | * - white, black, kings, queens, rooks, bishops, knights, pawns: 153 | * The current position (bitboards). 154 | * - rule50: 155 | * The 50-move half-move clock. 156 | * - castling: 157 | * Castling rights. Set to zero if no castling is possible. 158 | * - ep: 159 | * The en passant square (if exists). Set to zero if there is no en passant 160 | * square. 161 | * - turn: 162 | * true=white, false=black 163 | * - results (OPTIONAL): 164 | * Alternative results, one for each possible legal move. The passed array 165 | * must be TB_MAX_MOVES in size. 166 | * If alternative results are not desired then set results=NULL. 167 | * 168 | * RETURN: 169 | * - A TB_RESULT value comprising: 170 | * 1) The WDL value (TB_GET_WDL) 171 | * 2) The suggested move (TB_GET_FROM, TB_GET_TO, TB_GET_PROMOTES, TB_GET_EP) 172 | * 3) The DTZ value (TB_GET_DTZ) 173 | * The suggested move is guaranteed to preserved the WDL value. 174 | * 175 | * Otherwise: 176 | * 1) TB_RESULT_STALEMATE is returned if the position is in stalemate. 177 | * 2) TB_RESULT_CHECKMATE is returned if the position is in checkmate. 178 | * 3) TB_RESULT_FAILED is returned if the probe failed. 179 | * 180 | * If results!=NULL, then a TB_RESULT for each legal move will be generated 181 | * and stored in the results array. The results array will be terminated 182 | * by TB_RESULT_FAILED. 183 | * 184 | * NOTES: 185 | * - Engines can use this function to probe at the root. This function should 186 | * not be used during search. 187 | * - DTZ tablebases can suggest unnatural moves, especially for losing 188 | * positions. Engines may prefer to traditional search combined with WDL 189 | * move filtering using the alternative results array. 190 | * - This function is NOT thread safe. For engines this function should only 191 | * be called once at the root per search. 192 | */ 193 | unsigned tb_probe_root( 194 | uint64_t white, uint64_t black, 195 | uint64_t kings, uint64_t queens, 196 | uint64_t rooks, uint64_t bishops, 197 | uint64_t knights, uint64_t pawns, 198 | unsigned rule50, unsigned ep, 199 | bool turn, unsigned *results); 200 | 201 | 202 | typedef uint16_t PyrrhicMove; 203 | 204 | struct TbRootMove { 205 | PyrrhicMove move; 206 | PyrrhicMove pv[TB_MAX_PLY]; 207 | unsigned pvSize; 208 | int32_t tbScore, tbRank; 209 | }; 210 | 211 | struct TbRootMoves { 212 | unsigned size; 213 | struct TbRootMove moves[TB_MAX_MOVES]; 214 | }; 215 | 216 | /* 217 | * Use the DTZ tables to rank and score all root moves. 218 | * INPUT: as for tb_probe_root 219 | * OUTPUT: TbRootMoves structure is filled in. This contains 220 | * an array of TbRootMove structures. 221 | * Each structure instance contains a rank, a score, and a 222 | * predicted principal variation. 223 | * RETURN VALUE: 224 | * non-zero if ok, 0 means not all probes were successful 225 | * 226 | */ 227 | int tb_probe_root_dtz( 228 | uint64_t white, uint64_t black, 229 | uint64_t kings, uint64_t queens, 230 | uint64_t rooks, uint64_t bishops, 231 | uint64_t knights, uint64_t pawns, 232 | unsigned rule50, unsigned ep, 233 | bool turn, bool hasRepeated, 234 | bool useRule50, struct TbRootMoves *results); 235 | 236 | /* 237 | // Use the WDL tables to rank and score all root moves. 238 | // This is a fallback for the case that some or all DTZ tables are missing. 239 | * INPUT: as for tb_probe_root 240 | * OUTPUT: TbRootMoves structure is filled in. This contains 241 | * an array of TbRootMove structures. 242 | * Each structure instance contains a rank, a score, and a 243 | * predicted principal variation. 244 | * RETURN VALUE: 245 | * non-zero if ok, 0 means not all probes were successful 246 | * 247 | */ 248 | int tb_probe_root_wdl( 249 | uint64_t white, uint64_t black, 250 | uint64_t kings, uint64_t queens, 251 | uint64_t rooks, uint64_t bishops, 252 | uint64_t knights, uint64_t pawns, 253 | unsigned rule50, unsigned ep, 254 | bool turn, bool useRule50, 255 | struct TbRootMoves *_results); 256 | 257 | #endif 258 | -------------------------------------------------------------------------------- /src/search.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | 24 | #include "types.h" 25 | 26 | struct PVariation { 27 | int length, score; 28 | uint16_t line[MAX_PLY]; 29 | }; 30 | 31 | void initSearch(); 32 | void *start_search_threads(void *arguments); 33 | void getBestMove(Thread *threads, Board *board, Limits *limits, uint16_t *best, uint16_t *ponder, int *score); 34 | void* iterativeDeepening(void *vthread); 35 | void aspirationWindow(Thread *thread); 36 | int search(Thread *thread, PVariation *pv, int alpha, int beta, int depth, bool cutnode); 37 | int qsearch(Thread *thread, PVariation *pv, int alpha, int beta); 38 | int staticExchangeEvaluation(Board *board, uint16_t move, int threshold); 39 | int singularity(Thread *thread, uint16_t ttMove, int ttValue, int depth, int PvNode, int alpha, int beta, bool cutnode); 40 | 41 | static const int WindowDepth = 4; 42 | static const int WindowSize = 10; 43 | static const int WindowTimerMS = 2500; 44 | 45 | static const int CurrmoveTimerMS = 2500; 46 | 47 | static const int TTResearchMargin = 141; 48 | 49 | static const int BetaPruningDepth = 8; 50 | static const int BetaMargin = 65; 51 | 52 | static const int AlphaPruningDepth = 4; 53 | static const int AlphaMargin = 3488; 54 | 55 | static const int NullMovePruningDepth = 2; 56 | 57 | static const int ProbCutDepth = 5; 58 | static const int ProbCutMargin = 100; 59 | 60 | static const int FutilityPruningDepth = 8; 61 | static const int FutilityMarginBase = 77; 62 | static const int FutilityMarginPerDepth = 52; 63 | static const int FutilityMarginNoHistory = 165; 64 | static const int FutilityPruningHistoryLimit[] = { 14296, 6004 }; 65 | 66 | static const int ContinuationPruningDepth[] = { 3, 2 }; 67 | static const int ContinuationPruningHistoryLimit[] = { -1000, -2500 }; 68 | 69 | static const int LateMovePruningDepth = 8; 70 | 71 | static const int SEEPruningDepth = 10; 72 | static const int SEEQuietMargin = -64; 73 | static const int SEENoisyMargin = -20; 74 | static const int SEEPieceValues[] = { 75 | 103, 422, 437, 694, 76 | 1313, 0, 0, 0, 77 | }; 78 | 79 | static const int QSSeeMargin = 123; 80 | static const int QSDeltaMargin = 142; 81 | -------------------------------------------------------------------------------- /src/syzygy.c: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "bitboards.h" 25 | #include "board.h" 26 | #include "move.h" 27 | #include "movegen.h" 28 | #include "pyrrhic/tbprobe.h" 29 | #include "types.h" 30 | #include "uci.h" 31 | 32 | unsigned TB_PROBE_DEPTH; // Set by UCI options 33 | extern int TB_LARGEST; // Set by Pyrrhic in tb_init() 34 | 35 | static uint16_t convertPyrrhicMove(Board *board, unsigned result) { 36 | 37 | // Extract Pyrhic's move representation 38 | unsigned to = TB_GET_TO(result); 39 | unsigned from = TB_GET_FROM(result); 40 | unsigned ep = TB_GET_EP(result); 41 | unsigned promo = TB_GET_PROMOTES(result); 42 | 43 | // Convert the move notation. Care that Pyrrhic's promotion flags are inverted 44 | if (ep == 0u && promo == 0u) return MoveMake(from, to, NORMAL_MOVE); 45 | else if (ep != 0u) return MoveMake(from, board->epSquare, ENPASS_MOVE); 46 | else /* if (promo != 0u) */ return MoveMake(from, to, PROMOTION_MOVE | ((4 - promo) << 14)); 47 | } 48 | 49 | static void removeBadWDL(Board *board, Limits *limits, unsigned result, unsigned *results) { 50 | 51 | // Remove for any moves that fail to maintain the ideal WDL outcome 52 | for (int i = 0; i < MAX_MOVES && results[i] != TB_RESULT_FAILED; i++) 53 | if (TB_GET_WDL(results[i]) != TB_GET_WDL(result)) 54 | limits->excludedMoves[i] = convertPyrrhicMove(board, results[i]); 55 | } 56 | 57 | 58 | void tablebasesProbeDTZ(Board *board, Limits *limits) { 59 | 60 | unsigned results[MAX_MOVES]; 61 | uint64_t white = board->colours[WHITE]; 62 | uint64_t black = board->colours[BLACK]; 63 | 64 | // We cannot probe when there are castling rights, or when 65 | // we have more pieces than our largest Tablebase has pieces 66 | if ( board->castleRooks 67 | || popcount(white | black) > TB_LARGEST) 68 | return; 69 | 70 | // Tap into Pyrrhic's API. Pyrrhic takes the board representation and the 71 | // fifty move rule counter, followed by the enpass square (0 if none set), 72 | // and the turn Pyrrhic defines WHITE as 1, and BLACK as 0, which is the 73 | // opposite of how Ethereal defines them 74 | 75 | unsigned result = tb_probe_root( 76 | board->colours[WHITE], board->colours[BLACK], 77 | board->pieces[KING ], board->pieces[QUEEN ], 78 | board->pieces[ROOK ], board->pieces[BISHOP], 79 | board->pieces[KNIGHT], board->pieces[PAWN ], 80 | board->halfMoveCounter, board->epSquare == -1 ? 0 : board->epSquare, 81 | board->turn == WHITE ? 1 : 0, results 82 | ); 83 | 84 | // Probe failed, or we are already in a finished position. 85 | if ( result == TB_RESULT_FAILED 86 | || result == TB_RESULT_CHECKMATE 87 | || result == TB_RESULT_STALEMATE) 88 | return; 89 | 90 | // Remove any moves that fail to maintain optimal WDL 91 | removeBadWDL(board, limits, result, results); 92 | } 93 | 94 | unsigned tablebasesProbeWDL(Board *board, int depth, int height) { 95 | 96 | uint64_t white = board->colours[WHITE]; 97 | uint64_t black = board->colours[BLACK]; 98 | 99 | // Never take a Syzygy Probe in a Root node, in a node with Castling rights, 100 | // in a node which was not just zero'ed by a Pawn Move or Capture, or in a 101 | // node which has more pieces than our largest found Tablebase can handle 102 | 103 | if ( height == 0 104 | || board->castleRooks 105 | || board->halfMoveCounter 106 | || popcount(white | black) > TB_LARGEST) 107 | return TB_RESULT_FAILED; 108 | 109 | 110 | // We also will avoid probing beneath the provided TB_PROBE_DEPTH, except 111 | // for when our board has even fewer pieces than the largest Tablebase is 112 | // able to handle. Namely, when we have a 7man Tablebase, we will always 113 | // probe the 6man Tablebase if possible, irregardless of TB_PROBE_DEPTH 114 | 115 | if ( depth < (int) TB_PROBE_DEPTH 116 | && popcount(white | black) == TB_LARGEST) 117 | return TB_RESULT_FAILED; 118 | 119 | 120 | // Tap into Pyrrhic's API. Pyrrhic takes the board representation, followed 121 | // by the enpass square (0 if none set), and the turn. Pyrrhic defines WHITE 122 | // as 1, and BLACK as 0, which is the opposite of how Ethereal defines them 123 | 124 | return tb_probe_wdl( 125 | board->colours[WHITE], board->colours[BLACK], 126 | board->pieces[KING ], board->pieces[QUEEN ], 127 | board->pieces[ROOK ], board->pieces[BISHOP], 128 | board->pieces[KNIGHT], board->pieces[PAWN ], 129 | board->epSquare == -1 ? 0 : board->epSquare, 130 | board->turn == WHITE ? 1 : 0 131 | ); 132 | } 133 | -------------------------------------------------------------------------------- /src/syzygy.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #include 24 | 25 | int tablebasesProbeDTZ(Board *board, Limits *limits); 26 | unsigned tablebasesProbeWDL(Board *board, int depth, int height); 27 | -------------------------------------------------------------------------------- /src/thread.c: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include "board.h" 23 | #include "history.h" 24 | #include "search.h" 25 | #include "thread.h" 26 | #include "transposition.h" 27 | #include "types.h" 28 | 29 | #include "nnue/types.h" 30 | #include "nnue/accumulator.h" 31 | #include "nnue/utils.h" 32 | 33 | Thread* createThreadPool(int nthreads) { 34 | 35 | Thread *threads = calloc(nthreads, sizeof(Thread)); 36 | 37 | for (int i = 0; i < nthreads; i++) { 38 | 39 | // Offset the Node Stack to allow looking backwards 40 | threads[i].states = &(threads[i].nodeStates[STACK_OFFSET]); 41 | 42 | // NULL out the entire continuation history 43 | for (int j = 0; j < STACK_SIZE; j++) 44 | threads[i].nodeStates[j].continuations = NULL; 45 | 46 | // Threads will know of each other 47 | threads[i].index = i; 48 | threads[i].threads = threads; 49 | threads[i].nthreads = nthreads; 50 | 51 | // Accumulator stack and table require alignment 52 | threads[i].nnue = nnue_create_evaluator(); 53 | } 54 | 55 | return threads; 56 | } 57 | 58 | void deleteThreadPool(Thread *threads) { 59 | 60 | for (int i = 0; i < threads->nthreads; i++) 61 | nnue_delete_evaluator(threads[i].nnue); 62 | 63 | free(threads); 64 | } 65 | 66 | void resetThreadPool(Thread *threads) { 67 | 68 | // Reset the per-thread tables, used for move ordering 69 | // and evaluation caching. This is needed for ucinewgame 70 | // calls in order to ensure a deterministic behaviour 71 | 72 | for (int i = 0; i < threads->nthreads; i++) { 73 | 74 | memset(&threads[i].pktable, 0, sizeof(PKTable)); 75 | 76 | memset(&threads[i].killers, 0, sizeof(KillerTable)); 77 | memset(&threads[i].cmtable, 0, sizeof(CounterMoveTable)); 78 | 79 | memset(&threads[i].history, 0, sizeof(HistoryTable)); 80 | memset(&threads[i].chistory, 0, sizeof(CaptureHistoryTable)); 81 | memset(&threads[i].continuation, 0, sizeof(ContinuationTable)); 82 | } 83 | } 84 | 85 | void newSearchThreadPool(Thread *threads, Board *board, Limits *limits, TimeManager *tm) { 86 | 87 | // Initialize each Thread in the Thread Pool. We need a reference 88 | // to the UCI seach parameters, access to the timing information, 89 | // somewhere to store the results of each iteration by the main, and 90 | // our own copy of the board. Also, we reset the seach statistics 91 | 92 | for (int i = 0; i < threads->nthreads; i++) { 93 | 94 | threads[i].limits = limits; 95 | threads[i].tm = tm; 96 | threads[i].height = 0; 97 | threads[i].nodes = 0ull; 98 | threads[i].tbhits = 0ull; 99 | 100 | memcpy(&threads[i].board, board, sizeof(Board)); 101 | threads[i].board.thread = &threads[i]; 102 | 103 | memset(threads[i].nodeStates, 0, sizeof(NodeState) * STACK_SIZE); 104 | nnue_reset_evaluator(threads[i].nnue); 105 | } 106 | } 107 | 108 | uint64_t nodesSearchedThreadPool(Thread *threads) { 109 | 110 | // Sum up the node counters across each Thread. Threads have 111 | // their own node counters to avoid true sharing the cache 112 | 113 | uint64_t nodes = 0ull; 114 | 115 | for (int i = 0; i < threads->nthreads; i++) 116 | nodes += threads->threads[i].nodes; 117 | 118 | return nodes; 119 | } 120 | 121 | uint64_t tbhitsThreadPool(Thread *threads) { 122 | 123 | // Sum up the tbhit counters across each Thread. Threads have 124 | // their own tbhit counters to avoid true sharing the cache 125 | 126 | uint64_t tbhits = 0ull; 127 | 128 | for (int i = 0; i < threads->nthreads; i++) 129 | tbhits += threads->threads[i].tbhits; 130 | 131 | return tbhits; 132 | } 133 | -------------------------------------------------------------------------------- /src/thread.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include "board.h" 26 | #include "movepicker.h" 27 | #include "network.h" 28 | #include "search.h" 29 | #include "transposition.h" 30 | #include "types.h" 31 | 32 | #include "nnue/types.h" 33 | 34 | enum { 35 | STACK_OFFSET = 4, 36 | STACK_SIZE = MAX_PLY + STACK_OFFSET 37 | }; 38 | 39 | struct NodeState { 40 | 41 | int eval; // Static evaluation of the Node 42 | int movedPiece; // Moving piece, otherwise UB 43 | int dextensions; // Number of Double Extensions 44 | bool tactical; // Cached moveIsTactical() 45 | uint16_t move; // Move applied at the Node 46 | uint16_t excluded; // Excluded move during Singular Extensions 47 | MovePicker mp; // Move Picker at each ply 48 | 49 | // Fast reference for future use for History lookups 50 | int16_t (*continuations)[CONT_NB][PIECE_NB][SQUARE_NB]; 51 | }; 52 | 53 | struct Thread { 54 | 55 | Board board; 56 | Limits *limits; 57 | TimeManager *tm; 58 | PVariation pvs[MAX_PLY]; 59 | PVariation mpvs[MAX_MOVES]; 60 | 61 | int multiPV; 62 | uint16_t bestMoves[MAX_MOVES]; 63 | 64 | uint64_t nodes, tbhits; 65 | int depth, seldepth, height, completed; 66 | 67 | NNUEEvaluator *nnue; 68 | 69 | Undo undoStack[STACK_SIZE]; 70 | NodeState *states, nodeStates[STACK_SIZE]; 71 | 72 | ALIGN64 PKTable pktable; 73 | ALIGN64 KillerTable killers; 74 | ALIGN64 CounterMoveTable cmtable; 75 | ALIGN64 HistoryTable history; 76 | ALIGN64 CaptureHistoryTable chistory; 77 | ALIGN64 ContinuationTable continuation; 78 | 79 | int index, nthreads; 80 | Thread *threads; 81 | jmp_buf jbuffer; 82 | }; 83 | 84 | 85 | Thread* createThreadPool(int nthreads); 86 | void deleteThreadPool(Thread *threads); 87 | 88 | void resetThreadPool(Thread *threads); 89 | void newSearchThreadPool(Thread *threads, Board *board, Limits *limits, TimeManager *tm); 90 | 91 | uint64_t nodesSearchedThreadPool(Thread *threads); 92 | uint64_t tbhitsThreadPool(Thread *threads); 93 | -------------------------------------------------------------------------------- /src/timeman.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* */ 3 | /* Ethereal is a UCI chess playing engine authored by Andrew Grant. */ 4 | /* */ 5 | /* */ 6 | /* Ethereal is free software: you can redistribute it and/or modify */ 7 | /* it under the terms of the GNU General Public License as published by */ 8 | /* the Free Software Foundation, either version 3 of the License, or */ 9 | /* (at your option) any later version. */ 10 | /* */ 11 | /* Ethereal is distributed in the hope that it will be useful, */ 12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 | /* GNU General Public License for more details. */ 15 | /* */ 16 | /* You should have received a copy of the GNU General Public License */ 17 | /* along with this program. If not, see */ 18 | /* */ 19 | /******************************************************************************/ 20 | 21 | #include "search.h" 22 | #include "thread.h" 23 | #include "timeman.h" 24 | #include "types.h" 25 | #include "uci.h" 26 | 27 | int MoveOverhead = 300; // Set by UCI options 28 | 29 | double get_real_time() { 30 | #if defined(_WIN32) || defined(_WIN64) 31 | return (double)(GetTickCount()); 32 | #else 33 | struct timeval tv; 34 | double secsInMilli, usecsInMilli; 35 | 36 | gettimeofday(&tv, NULL); 37 | secsInMilli = ((double)tv.tv_sec) * 1000; 38 | usecsInMilli = tv.tv_usec / 1000; 39 | 40 | return secsInMilli + usecsInMilli; 41 | #endif 42 | } 43 | 44 | double elapsed_time(const TimeManager *tm) { 45 | return get_real_time() - tm->start_time; 46 | } 47 | 48 | 49 | void tm_init(const Limits *limits, TimeManager *tm) { 50 | 51 | tm->pv_stability = 0; // Clear our stability time usage heuristic 52 | tm->start_time = limits->start; // Save off the start time of the search 53 | memset(tm->nodes, 0, sizeof(uint16_t) * 0x10000); // Clear Node counters 54 | 55 | // Allocate time if Ethereal is handling the clock 56 | if (limits->limitedBySelf) { 57 | 58 | // Playing using X / Y + Z time control 59 | if (limits->mtg >= 0) { 60 | tm->ideal_usage = 1.80 * (limits->time - MoveOverhead) / (limits->mtg + 5) + limits->inc; 61 | tm->max_usage = 10.00 * (limits->time - MoveOverhead) / (limits->mtg + 10) + limits->inc; 62 | } 63 | 64 | // Playing using X + Y time controls 65 | else { 66 | tm->ideal_usage = 2.50 * ((limits->time - MoveOverhead) + 25 * limits->inc) / 50; 67 | tm->max_usage = 10.00 * ((limits->time - MoveOverhead) + 25 * limits->inc) / 50; 68 | } 69 | 70 | // Cap time allocations using the move overhead 71 | tm->ideal_usage = MIN(tm->ideal_usage, limits->time - MoveOverhead); 72 | tm->max_usage = MIN(tm->max_usage, limits->time - MoveOverhead); 73 | } 74 | 75 | // Interface told us to search for a predefined duration 76 | if (limits->limitedByTime) { 77 | tm->ideal_usage = limits->timeLimit; 78 | tm->max_usage = limits->timeLimit; 79 | } 80 | } 81 | 82 | void tm_update(const Thread *thread, const Limits *limits, TimeManager *tm) { 83 | 84 | // Don't update our Time Managment plans at very low depths 85 | if (!limits->limitedBySelf || thread->completed < 4) 86 | return; 87 | 88 | // Track how long we've kept the same best move between iterations 89 | const uint16_t this_move = thread->pvs[thread->completed-0].line[0]; 90 | const uint16_t last_move = thread->pvs[thread->completed-1].line[0]; 91 | tm->pv_stability = (this_move == last_move) ? MIN(10, tm->pv_stability + 1) : 0; 92 | } 93 | 94 | bool tm_finished(const Thread *thread, const TimeManager *tm) { 95 | 96 | // Don't terminate early at very low depths 97 | if (thread->completed < 4) return FALSE; 98 | 99 | // Scale time between 80% and 120%, based on stable best moves 100 | const double pv_factor = 1.20 - 0.04 * tm->pv_stability; 101 | 102 | // Scale time between 75% and 125%, based on score fluctuations 103 | const double score_change = thread->pvs[thread->completed-3].score 104 | - thread->pvs[thread->completed-0].score; 105 | const double score_factor = MAX(0.75, MIN(1.25, 0.05 * score_change)); 106 | 107 | // Scale time between 50% and 240%, based on where nodes have been spent 108 | const uint64_t best_nodes = tm->nodes[thread->pvs[thread->completed-0].line[0]]; 109 | const double non_best_pct = 1.0 - ((double) best_nodes / thread->nodes); 110 | const double nodes_factor = MAX(0.50, 2 * non_best_pct + 0.4); 111 | 112 | return elapsed_time(tm) > tm->ideal_usage * pv_factor * score_factor * nodes_factor; 113 | } 114 | 115 | bool tm_stop_early(const Thread *thread) { 116 | 117 | /// Quit early IFF we've surpassed our max time or nodes, and have 118 | /// finished at least a depth 1 search to ensure we have a best move 119 | 120 | const Limits *limits = thread->limits; 121 | 122 | if (limits->limitedByNodes) 123 | return thread->depth > 1 124 | && thread->nodes >= limits->nodeLimit / thread->nthreads; 125 | 126 | return thread->depth > 1 127 | && (thread->nodes & 1023) == 1023 128 | && (limits->limitedBySelf || limits->limitedByTime) 129 | && elapsed_time(thread->tm) >= thread->tm->max_usage; 130 | } 131 | -------------------------------------------------------------------------------- /src/timeman.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* */ 3 | /* Ethereal is a UCI chess playing engine authored by Andrew Grant. */ 4 | /* */ 5 | /* */ 6 | /* Ethereal is free software: you can redistribute it and/or modify */ 7 | /* it under the terms of the GNU General Public License as published by */ 8 | /* the Free Software Foundation, either version 3 of the License, or */ 9 | /* (at your option) any later version. */ 10 | /* */ 11 | /* Ethereal is distributed in the hope that it will be useful, */ 12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 | /* GNU General Public License for more details. */ 15 | /* */ 16 | /* You should have received a copy of the GNU General Public License */ 17 | /* along with this program. If not, see */ 18 | /* */ 19 | /******************************************************************************/ 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | 26 | #if defined(_WIN32) || defined(_WIN64) 27 | #include 28 | #else 29 | #include 30 | #endif 31 | 32 | #include "types.h" 33 | 34 | struct TimeManager { 35 | int pv_stability; 36 | double start_time, ideal_usage, max_usage; 37 | uint64_t nodes[0x10000]; 38 | }; 39 | 40 | double get_real_time(); 41 | double elapsed_time(const TimeManager *tm); 42 | void tm_init(const Limits *limits, TimeManager *tm); 43 | void tm_update(const Thread *thread, const Limits *limits, TimeManager *tm); 44 | bool tm_finished(const Thread *thread, const TimeManager *tm); 45 | bool tm_stop_early(const Thread *thread); 46 | -------------------------------------------------------------------------------- /src/transposition.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* */ 3 | /* Ethereal is a UCI chess playing engine authored by Andrew Grant. */ 4 | /* */ 5 | /* */ 6 | /* Ethereal is free software: you can redistribute it and/or modify */ 7 | /* it under the terms of the GNU General Public License as published by */ 8 | /* the Free Software Foundation, either version 3 of the License, or */ 9 | /* (at your option) any later version. */ 10 | /* */ 11 | /* Ethereal is distributed in the hope that it will be useful, */ 12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 | /* GNU General Public License for more details. */ 15 | /* */ 16 | /* You should have received a copy of the GNU General Public License */ 17 | /* along with this program. If not, see */ 18 | /* */ 19 | /******************************************************************************/ 20 | 21 | #include 22 | 23 | #include "board.h" 24 | #include "evaluate.h" 25 | #include "thread.h" 26 | #include "transposition.h" 27 | #include "types.h" 28 | #include "zobrist.h" 29 | 30 | TTable Table; // Global Transposition Table 31 | 32 | /// Mate and Tablebase scores need to be adjusted relative to the Root 33 | /// when going into the Table and when coming out of the Table. Otherwise, 34 | /// we will not know when we have a "faster" vs "slower" Mate or TB Win/Loss 35 | 36 | static int tt_value_from(int value, int height) { 37 | return value == VALUE_NONE ? VALUE_NONE 38 | : value >= TBWIN_IN_MAX ? value - height 39 | : value <= -TBWIN_IN_MAX ? value + height : value; 40 | } 41 | 42 | static int tt_value_to(int value, int height) { 43 | return value == VALUE_NONE ? VALUE_NONE 44 | : value >= TBWIN_IN_MAX ? value + height 45 | : value <= -TBWIN_IN_MAX ? value - height : value; 46 | } 47 | 48 | 49 | /// Trivial helper functions to Transposition Table handleing 50 | 51 | void tt_update() { Table.generation += TT_MASK_BOUND + 1; } 52 | void tt_prefetch(uint64_t hash) { __builtin_prefetch(&Table.buckets[hash & Table.hashMask]); } 53 | 54 | 55 | int tt_init(int nthreads, int megabytes) { 56 | 57 | const uint64_t MB = 1ull << 20; 58 | uint64_t keySize = 16ull; 59 | 60 | // Cleanup memory when resizing the table 61 | if (Table.hashMask) free(Table.buckets); 62 | 63 | // Default keysize of 16 bits maps to a 2MB TTable 64 | assert((1ull << 16ull) * sizeof(TTBucket) == 2 * MB); 65 | 66 | // Find the largest keysize that is still within our given megabytes 67 | while ((1ull << keySize) * sizeof(TTBucket) <= megabytes * MB / 2) keySize++; 68 | assert((1ull << keySize) * sizeof(TTBucket) <= megabytes * MB); 69 | 70 | #if defined(__linux__) && !defined(__ANDROID__) 71 | 72 | // On Linux systems we align on 2MB boundaries and request Huge Pages 73 | Table.buckets = aligned_alloc(2 * MB, (1ull << keySize) * sizeof(TTBucket)); 74 | madvise(Table.buckets, (1ull << keySize) * sizeof(TTBucket), MADV_HUGEPAGE); 75 | #else 76 | 77 | // Otherwise, we simply allocate as usual and make no requests 78 | Table.buckets = malloc((1ull << keySize) * sizeof(TTBucket)); 79 | #endif 80 | 81 | // Save the lookup mask 82 | Table.hashMask = (1ull << keySize) - 1u; 83 | 84 | // Clear the table and load everything into the cache 85 | tt_clear(nthreads); 86 | 87 | // Return the number of MB actually allocated for the TTable 88 | return ((Table.hashMask + 1) * sizeof(TTBucket)) / MB; 89 | } 90 | 91 | int tt_hashfull() { 92 | 93 | /// Estimate the permill of the table being used, by looking at a thousand 94 | /// Buckets and seeing how many Entries contain a recent Transposition. 95 | 96 | int used = 0; 97 | 98 | for (int i = 0; i < 1000; i++) 99 | for (int j = 0; j < TT_BUCKET_NB; j++) 100 | used += (Table.buckets[i].slots[j].generation & TT_MASK_BOUND) != BOUND_NONE 101 | && (Table.buckets[i].slots[j].generation & TT_MASK_AGE) == Table.generation; 102 | 103 | return used / TT_BUCKET_NB; 104 | } 105 | 106 | bool tt_probe(uint64_t hash, int height, uint16_t *move, int *value, int *eval, int *depth, int *bound) { 107 | 108 | /// Search for a Transposition matching the provided Zobrist Hash. If one is found, 109 | /// we update its age in order to indicate that it is still relevant, before copying 110 | /// over its contents and signaling to the caller that an Entry was found. 111 | 112 | const uint16_t hash16 = hash >> 48; 113 | TTEntry *slots = Table.buckets[hash & Table.hashMask].slots; 114 | 115 | for (int i = 0; i < TT_BUCKET_NB; i++) { 116 | 117 | if (slots[i].hash16 == hash16) { 118 | 119 | slots[i].generation = Table.generation | (slots[i].generation & TT_MASK_BOUND); 120 | 121 | *move = slots[i].move; 122 | *value = tt_value_from(slots[i].value, height); 123 | *eval = slots[i].eval; 124 | *depth = slots[i].depth; 125 | *bound = slots[i].generation & TT_MASK_BOUND; 126 | return TRUE; 127 | } 128 | } 129 | 130 | return FALSE; 131 | } 132 | 133 | void tt_store(uint64_t hash, int height, uint16_t move, int value, int eval, int depth, int bound) { 134 | 135 | int i; 136 | const uint16_t hash16 = hash >> 48; 137 | TTEntry *slots = Table.buckets[hash & Table.hashMask].slots; 138 | TTEntry *replace = slots; // &slots[0] 139 | 140 | // Find a matching hash, or replace using MIN(x1, x2, x3), 141 | // where xN equals the depth minus 4 times the age difference 142 | for (i = 0; i < TT_BUCKET_NB && slots[i].hash16 != hash16; i++) 143 | if ( replace->depth - ((259 + Table.generation - replace->generation) & TT_MASK_AGE) 144 | >= slots[i].depth - ((259 + Table.generation - slots[i].generation) & TT_MASK_AGE)) 145 | replace = &slots[i]; 146 | 147 | // Prefer a matching hash, otherwise score a replacement 148 | replace = (i != TT_BUCKET_NB) ? &slots[i] : replace; 149 | 150 | // Don't overwrite an entry from the same position, unless we have 151 | // an exact bound or depth that is nearly as good as the old one 152 | if ( bound != BOUND_EXACT 153 | && hash16 == replace->hash16 154 | && depth < replace->depth - 2) 155 | return; 156 | 157 | // Don't overwrite a move if we don't have a new one 158 | if (move || hash16 != replace->hash16) 159 | replace->move = (uint16_t) move; 160 | 161 | // Finally, copy the new data into the replaced slot 162 | replace->depth = (int8_t ) depth; 163 | replace->generation = (uint8_t ) bound | Table.generation; 164 | replace->value = (int16_t ) tt_value_to(value, height); 165 | replace->eval = (int16_t ) eval; 166 | replace->hash16 = (uint16_t) hash16; 167 | } 168 | 169 | 170 | void tt_clear(int nthreads) { 171 | 172 | // Only use 1/4th of the enabled search Threads 173 | int nworkers = MAX(1, nthreads / 4); 174 | pthread_t pthreads[nworkers]; 175 | struct TTClear ttclears[nworkers]; 176 | 177 | // Initalize the data passed via a void* in pthread_create() 178 | for (int i = 0; i < nworkers; i++) 179 | ttclears[i] = (struct TTClear) { i, nworkers }; 180 | 181 | // Launch each of the helper threads to clear their sections 182 | for (int i = 1; i < nworkers; i++) 183 | pthread_create(&pthreads[i], NULL, tt_clear_threaded, &ttclears[i]); 184 | 185 | // Reuse this thread for the 0th sections of the Transposition Table 186 | tt_clear_threaded((void*) &ttclears[0]); 187 | 188 | // Join each of the helper threads after they've cleared their sections 189 | for (int i = 1; i < nworkers; i++) 190 | pthread_join(pthreads[i], NULL); 191 | } 192 | 193 | void *tt_clear_threaded(void *cargo) { 194 | 195 | const uint64_t MB = 1ull << 20; 196 | struct TTClear *ttclear = (struct TTClear*) cargo; 197 | 198 | // Logic for dividing the Table taken from Weiss and CFish 199 | const uint64_t size = (Table.hashMask + 1) * sizeof(TTBucket); 200 | const uint64_t slice = (size + ttclear->count - 1) / ttclear->count; 201 | const uint64_t blocks = (slice + 2 * MB - 1) / (2 * MB); 202 | const uint64_t begin = MIN(size, ttclear->index * blocks * 2 * MB); 203 | const uint64_t end = MIN(size, begin + blocks * 2 * MB); 204 | 205 | memset(Table.buckets + begin / sizeof(TTBucket), 0, end - begin); 206 | return NULL; 207 | } 208 | 209 | /// Simple Pawn+King Evaluation Hash Table, which also stores some additional 210 | /// safety information for use in King Safety, when not using NNUE evaluations 211 | 212 | PKEntry* getCachedPawnKingEval(Thread *thread, const Board *board) { 213 | PKEntry *pke = &thread->pktable[board->pkhash & PK_CACHE_MASK]; 214 | return pke->pkhash == board->pkhash ? pke : NULL; 215 | } 216 | 217 | void storeCachedPawnKingEval(Thread *thread, const Board *board, uint64_t passed, int eval, int safety[2]) { 218 | PKEntry *pke = &thread->pktable[board->pkhash & PK_CACHE_MASK]; 219 | *pke = (PKEntry) { board->pkhash, passed, eval, safety[WHITE], safety[BLACK] }; 220 | } 221 | -------------------------------------------------------------------------------- /src/transposition.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* */ 3 | /* Ethereal is a UCI chess playing engine authored by Andrew Grant. */ 4 | /* */ 5 | /* */ 6 | /* Ethereal is free software: you can redistribute it and/or modify */ 7 | /* it under the terms of the GNU General Public License as published by */ 8 | /* the Free Software Foundation, either version 3 of the License, or */ 9 | /* (at your option) any later version. */ 10 | /* */ 11 | /* Ethereal is distributed in the hope that it will be useful, */ 12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 | /* GNU General Public License for more details. */ 15 | /* */ 16 | /* You should have received a copy of the GNU General Public License */ 17 | /* along with this program. If not, see */ 18 | /* */ 19 | /******************************************************************************/ 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #if defined(__linux__) 30 | #include 31 | #endif 32 | 33 | #include "types.h" 34 | 35 | /// The Transposition Table contains information from positions that have been 36 | /// searched previously. Each entry contains a bound, a depth, an age, and some 37 | /// additional Zobrist bits. An Entry may also contain a static evaluation for 38 | /// the node, a search evaluation for the node, and a best move at that node. 39 | /// 40 | /// Each Entry contains 10-bytes of information. We group together entries into 41 | /// Buckets, containing three Entries a piece, as well as 2 additional bytes to 42 | /// pad out the structure to 32-bytes. This gives us multiple options when we 43 | /// run into a Zobrist collision with the Transposition Table lookup key. 44 | /// 45 | /// Generally, we prefer to replace entries that came from previous searches, 46 | /// as well as those which come from a lower depth. However, sometimes we do 47 | /// not replace any such entry, if it would be too harmful to do so. 48 | /// 49 | /// The minimum size of the Transposition Table is 2MB. This is so that we 50 | /// can lookup the table with at least 16-bits, and so that we may align the 51 | /// Table on a 2MB memory boundary, when available via the Operating System. 52 | 53 | enum { 54 | BOUND_NONE = 0, 55 | BOUND_LOWER = 1, 56 | BOUND_UPPER = 2, 57 | BOUND_EXACT = 3, 58 | 59 | TT_MASK_BOUND = 0x03, 60 | TT_MASK_AGE = 0xFC, 61 | TT_BUCKET_NB = 3, 62 | }; 63 | 64 | struct TTEntry { 65 | int8_t depth; 66 | uint8_t generation; 67 | int16_t eval, value; 68 | uint16_t move, hash16; 69 | }; 70 | 71 | struct TTBucket { 72 | TTEntry slots[TT_BUCKET_NB]; 73 | uint16_t padding; 74 | }; 75 | 76 | struct TTable { 77 | TTBucket *buckets; 78 | uint64_t hashMask; 79 | uint8_t generation; 80 | }; 81 | 82 | void tt_update(); 83 | void tt_prefetch(uint64_t hash); 84 | 85 | int tt_init(int nthreads, int megabytes); 86 | int tt_hashfull(); 87 | bool tt_probe(uint64_t hash, int height, uint16_t *move, int *value, int *eval, int *depth, int *bound); 88 | void tt_store(uint64_t hash, int height, uint16_t move, int value, int eval, int depth, int bound); 89 | 90 | struct TTClear { int index, count; }; 91 | void tt_clear(int nthreads); 92 | void *tt_clear_threaded(void *cargo); 93 | 94 | /// The Pawn King table contains saved evaluations, and additional Pawn information 95 | /// that is expensive to compute during evaluation. This includes the location of all 96 | /// passed pawns, and Pawn-Shelter / Pawn-Storm scores for use in King Safety evaluation. 97 | /// 98 | /// While this table is seldom accessed when using Ethereal NNUE, the table generally has 99 | /// an extremely high, 95%+ hit rate, generating a substantial overall speedup to Ethereal. 100 | 101 | enum { 102 | PK_CACHE_KEY_SIZE = 16, 103 | PK_CACHE_MASK = 0xFFFF, 104 | PK_CACHE_SIZE = 1 << PK_CACHE_KEY_SIZE, 105 | }; 106 | 107 | struct PKEntry { uint64_t pkhash, passed; int eval, safetyw, safetyb; }; 108 | typedef PKEntry PKTable[PK_CACHE_SIZE]; 109 | 110 | PKEntry* getCachedPawnKingEval(Thread *thread, const Board *board); 111 | void storeCachedPawnKingEval(Thread *thread, const Board *board, uint64_t passed, int eval, int safety[2]); 112 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | enum { FALSE, TRUE }; 26 | 27 | enum { MG, EG }; 28 | 29 | enum { WHITE, BLACK }; 30 | 31 | enum { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING }; 32 | 33 | enum { MAX_PLY = 128, MAX_MOVES = 256 }; 34 | 35 | enum { 36 | WHITE_PAWN = 0, BLACK_PAWN = 1, 37 | WHITE_KNIGHT = 4, BLACK_KNIGHT = 5, 38 | WHITE_BISHOP = 8, BLACK_BISHOP = 9, 39 | WHITE_ROOK = 12, BLACK_ROOK = 13, 40 | WHITE_QUEEN = 16, BLACK_QUEEN = 17, 41 | WHITE_KING = 20, BLACK_KING = 21, 42 | EMPTY = 26 43 | }; 44 | 45 | enum { 46 | MATE = 32000 + MAX_PLY, MATE_IN_MAX = MATE - MAX_PLY, 47 | TBWIN = 31000 + MAX_PLY, TBWIN_IN_MAX = TBWIN - MAX_PLY, 48 | VALUE_NONE = MATE + 1 49 | }; 50 | 51 | enum { 52 | SQUARE_NB = 64, COLOUR_NB = 2, 53 | RANK_NB = 8, FILE_NB = 8, 54 | PHASE_NB = 2, PIECE_NB = 6, 55 | CONT_NB = 2 56 | }; 57 | 58 | static inline int pieceType(int piece) { 59 | assert(0 <= piece / 4 && piece / 4 <= PIECE_NB); 60 | assert(piece % 4 <= COLOUR_NB); 61 | return piece / 4; 62 | } 63 | 64 | static inline int pieceColour(int piece) { 65 | assert(0 <= piece / 4 && piece / 4 <= PIECE_NB); 66 | assert(piece % 4 <= COLOUR_NB); 67 | return piece % 4; 68 | } 69 | 70 | static inline int makePiece(int type, int colour) { 71 | assert(0 <= type && type < PIECE_NB); 72 | assert(0 <= colour && colour <= COLOUR_NB); 73 | return type * 4 + colour; 74 | } 75 | 76 | #define MIN(A, B) ((A) < (B) ? (A) : (B)) 77 | #define MAX(A, B) ((A) > (B) ? (A) : (B)) 78 | 79 | // Forward definition of all structs 80 | 81 | typedef struct Magic Magic; 82 | typedef struct Board Board; 83 | typedef struct Undo Undo; 84 | typedef struct EvalTrace EvalTrace; 85 | typedef struct EvalInfo EvalInfo; 86 | typedef struct MovePicker MovePicker; 87 | typedef struct TimeManager TimeManager; 88 | typedef struct PVariation PVariation; 89 | typedef struct NodeState NodeState; 90 | typedef struct Thread Thread; 91 | typedef struct TTEntry TTEntry; 92 | typedef struct TTBucket TTBucket; 93 | typedef struct PKEntry PKEntry; 94 | typedef struct TTable TTable; 95 | typedef struct Limits Limits; 96 | typedef struct UCIGoStruct UCIGoStruct; 97 | 98 | // Renamings, currently for move ordering 99 | 100 | typedef uint16_t KillerTable[MAX_PLY+1][2]; 101 | typedef uint16_t CounterMoveTable[COLOUR_NB][PIECE_NB][SQUARE_NB]; 102 | 103 | typedef int16_t HistoryTable[COLOUR_NB][2][2][SQUARE_NB][SQUARE_NB]; 104 | typedef int16_t CaptureHistoryTable[PIECE_NB][2][2][SQUARE_NB][PIECE_NB-1]; 105 | typedef int16_t ContinuationTable[2][PIECE_NB][SQUARE_NB][CONT_NB][PIECE_NB][SQUARE_NB]; 106 | 107 | // Trivial alignment macros 108 | 109 | #define ALIGN64 alignas(64) 110 | #define INLINE static inline __attribute__((always_inline)) 111 | #define NOINLINE __attribute__((noinline)) -------------------------------------------------------------------------------- /src/uci.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | 24 | #include "types.h" 25 | 26 | #define VERSION_ID "14.40" 27 | 28 | #ifndef LICENSE_OWNER 29 | #define LICENSE_OWNER "Unlicensed" 30 | #endif 31 | 32 | #if USE_NNUE 33 | #define ETHEREAL_VERSION VERSION_ID" (NNUE)" 34 | #elif defined(USE_PEXT) 35 | #define ETHEREAL_VERSION VERSION_ID" (PEXT)" 36 | #elif defined(USE_POPCNT) 37 | #define ETHEREAL_VERSION VERSION_ID" (POPCNT)" 38 | #else 39 | #define ETHEREAL_VERSION VERSION_ID 40 | #endif 41 | 42 | struct Limits { 43 | double start, time, inc, mtg, timeLimit; 44 | int limitedByNone, limitedByTime, limitedBySelf; 45 | int limitedByDepth, limitedByMoves, limitedByNodes; 46 | int multiPV, depthLimit; uint64_t nodeLimit; 47 | uint16_t searchMoves[MAX_MOVES], excludedMoves[MAX_MOVES]; 48 | }; 49 | 50 | struct UCIGoStruct { 51 | Thread *threads; 52 | Board *board; 53 | Limits limits; 54 | }; 55 | 56 | void uciGo(UCIGoStruct *ucigo, pthread_t *pthread, Thread *threads, Board *board, int multiPV, char *str); 57 | void uciSetOption(char *str, Thread **threads, int *multiPV, int *chess960); 58 | void uciPosition(char *str, Board *board, int chess960); 59 | 60 | void uciReport(Thread *threads, PVariation *pv, int alpha, int beta); 61 | void uciReportCurrentMove(Board *board, uint16_t move, int currmove, int depth); 62 | 63 | int strEquals(char *str1, char *str2); 64 | int strStartsWith(char *str, char *key); 65 | int strContains(char *str, char *key); 66 | int getInput(char *str); 67 | -------------------------------------------------------------------------------- /src/windows.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndyGrant/Ethereal/0e47e9b67f345c75eb965d9fb3e2493b6a11d09a/src/windows.c -------------------------------------------------------------------------------- /src/windows.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "types.h" 22 | 23 | #ifdef _WIN32 24 | 25 | // Force to include needed API prototypes 26 | #if _WIN32_WINNT < 0x0601 27 | #undef _WIN32_WINNT 28 | #define _WIN32_WINNT 0x0601 29 | #endif 30 | 31 | #include 32 | // The needed Windows API for processor groups could be missed from old Windows 33 | // versions, so instead of calling them directly (forcing the linker to resolve 34 | // the calls at compile time), try to load them at runtime. To do this we need 35 | // first to define the corresponding function pointers. 36 | typedef int (*fun1_t) (LOGICAL_PROCESSOR_RELATIONSHIP, PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD); 37 | typedef int (*fun2_t) (USHORT, PGROUP_AFFINITY); 38 | typedef int (*fun3_t) (HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); 39 | 40 | #endif 41 | 42 | void bindThisThread(int index); 43 | -------------------------------------------------------------------------------- /src/zobrist.c: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | #include "types.h" 22 | #include "zobrist.h" 23 | 24 | uint64_t ZobristKeys[32][SQUARE_NB]; 25 | uint64_t ZobristEnpassKeys[FILE_NB]; 26 | uint64_t ZobristCastleKeys[SQUARE_NB]; 27 | uint64_t ZobristTurnKey; 28 | 29 | uint64_t rand64() { 30 | 31 | // http://vigna.di.unimi.it/ftp/papers/xorshift.pdf 32 | 33 | static uint64_t seed = 1070372ull; 34 | 35 | seed ^= seed >> 12; 36 | seed ^= seed << 25; 37 | seed ^= seed >> 27; 38 | 39 | return seed * 2685821657736338717ull; 40 | } 41 | 42 | void initZobrist() { 43 | 44 | // Init the main Zobrist keys for all pieces 45 | for (int piece = PAWN; piece <= KING; piece++) 46 | for (int sq = 0; sq < SQUARE_NB; sq++) 47 | for (int colour = WHITE; colour <= BLACK; colour++) 48 | ZobristKeys[makePiece(piece, colour)][sq] = rand64(); 49 | 50 | // Init the Zobrist keys for each enpass file 51 | for (int file = 0; file < FILE_NB; file++) 52 | ZobristEnpassKeys[file] = rand64(); 53 | 54 | // Init the Zobrist keys for each castle rook 55 | for (int sq = 0; sq < SQUARE_NB; sq++) 56 | ZobristCastleKeys[sq] = rand64(); 57 | 58 | // Init the Zobrist key for side to move 59 | ZobristTurnKey = rand64(); 60 | } 61 | -------------------------------------------------------------------------------- /src/zobrist.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ethereal is a UCI chess playing engine authored by Andrew Grant. 3 | 4 | 5 | Ethereal is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | Ethereal is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | 23 | #include "types.h" 24 | 25 | extern uint64_t ZobristKeys[32][SQUARE_NB]; 26 | extern uint64_t ZobristEnpassKeys[FILE_NB]; 27 | extern uint64_t ZobristCastleKeys[SQUARE_NB]; 28 | extern uint64_t ZobristTurnKey; 29 | 30 | uint64_t rand64(); 31 | void initZobrist(); 32 | --------------------------------------------------------------------------------