├── .gitignore ├── CHANGELOG ├── LICENSE ├── Makefile ├── README ├── benchmarks ├── README ├── brilliant.dcf ├── silent.dcf ├── sts.dcf └── win300.dcf ├── bin └── .gitkeep ├── bitmask.go ├── bitmask_test.go ├── book.go ├── book_test.go ├── cmd └── donna │ └── main.go ├── data.go ├── data_bitbase.go ├── data_evaluate.go ├── engine.go ├── engine_repl.go ├── engine_uci.go ├── evaluate.go ├── evaluate_endgame.go ├── evaluate_endgame_test.go ├── evaluate_pawns.go ├── evaluate_pawns_test.go ├── evaluate_pieces.go ├── evaluate_safety.go ├── evaluate_test.go ├── evaluate_threats.go ├── expect └── expect.go ├── game.go ├── generate.go ├── generate_captures.go ├── generate_captures_test.go ├── generate_checks.go ├── generate_checks_test.go ├── generate_evasions.go ├── generate_evasions_test.go ├── generate_moves.go ├── generate_moves_test.go ├── generate_quiets.go ├── generate_quiets_test.go ├── generate_test.go ├── init.go ├── init_test.go ├── logo ├── README ├── donna1.bmp ├── donna2.bmp ├── donna3.bmp └── donna4.bmp ├── move.go ├── move_test.go ├── piece.go ├── position.go ├── position_cache.go ├── position_cache_test.go ├── position_exchange.go ├── position_exchange_test.go ├── position_moves.go ├── position_moves_test.go ├── position_targets.go ├── position_targets_test.go ├── position_test.go ├── score.go ├── scripts ├── README ├── mfl.epd ├── nebula.epd ├── play.sh └── rate.sh ├── search.go ├── search_quiescence.go ├── search_test.go ├── search_tree.go └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS 2 | .DS_Store 3 | 4 | # TextMate 5 | *.tmproj 6 | tmtags 7 | 8 | # Vim 9 | *.swp 10 | 11 | # Other 12 | ~* 13 | *~ 14 | *.*~ 15 | 16 | # Builds, logs and tools. 17 | donna 18 | cmd/donna 19 | bin/donna* 20 | logs/* 21 | tools/* 22 | games/* 23 | 24 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Oct 27, 2018: Version v4.1 2 | - Added quiet move generator. 3 | - Improved late move reduction. 4 | - Increased density of transposition table. 5 | - Added `hashfull` metrics in UCI mode and cache hits in REPL mode. 6 | - Improved endgame evaluation for positions with opposite-colored bishops. 7 | - Many small improvements and tweaks. 8 | 9 | Jan 8, 2016: Version v4.0 10 | - Improved search depth on typical time controls. 11 | - Many more optimizations and position evaluation tweaks. 12 | 13 | Oct 26, 2015: Version v3.1 14 | - Fine-tuned internal evaluation parameters and weights. 15 | - Updated mobility evaluation to take into account pinned pieces. 16 | 17 | Oct 11, 2015: Version v3.0 18 | - Improved root move ordering. 19 | - Improved late move reduction in search. 20 | - Simplified quiescence search. 21 | - Implemented King with pawn vs King with pawn endgame evaluation. 22 | - Added pawn square rule and unstoppable pawn detection. 23 | - Replaced standard Go sort with faster shell sort to speed up move ordering. 24 | - Optimized principal variation to avoid memory garbage collection. 25 | - Doubled maximum depth from 32 to 64 plies. 26 | - Imporved unit test coverage. 27 | - Fixed nasty bug in King with pawn vs King endgame. 28 | - Fixed rare condition resulting in infinite loop. 29 | 30 | May 11, 2015: Version v2.1 31 | - Fixed cache allocation to reuse existing cache (if any) when starting new game. 32 | - Added UCI Hash option to be able to specify hash size (32Mb to 1Gb, default 256Mb). 33 | 34 | May 2, 2015: Version v2.0 35 | - Refactored quiescence search. 36 | - Revamped positions cache (aka transposition table) code. 37 | - Doubled default cache size to 256Mb. 38 | - Implemented more conservative time allocation for floating time control. 39 | - Adjusted king safety evaluation for positions with opposite color bishops. 40 | - Fine-tuned some evaluation parameters. 41 | - Added "book" command to REPL. 42 | 43 | Dec 14, 2014: Version v1.0 44 | - Initial release. 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018 Michael Dvorkin. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | # Use of this source code is governed by a MIT-style license that can 3 | # be found in the LICENSE file. 4 | # 5 | # I am making my contributions/submissions to this project solely in my 6 | # personal capacity and am not conveying any rights to any intellectual 7 | # property of any third parties. 8 | 9 | VERSION = 4.1 10 | GOFLAGS = -gcflags -B 11 | PACKAGE = github.com/michaeldv/donna/cmd/donna 12 | 13 | build: 14 | go build -x -a -o ./bin/donna $(GOFLAGS) $(PACKAGE) 15 | 16 | install: 17 | go install -x $(GOFLAGS) $(PACKAGE) 18 | 19 | run: 20 | go run $(GOFLAGS) ./cmd/donna/main.go -i 21 | 22 | test: 23 | go test 24 | 25 | buildall: 26 | GOOS=darwin GOARCH=amd64 go build -a $(GOFLAGS) -o ./bin/donna-$(VERSION)-osx-64 $(PACKAGE) 27 | GOOS=freebsd GOARCH=amd64 go build -a $(GOFLAGS) -o ./bin/donna-$(VERSION)-freebsd-64 $(PACKAGE) 28 | GOOS=linux GOARCH=amd64 go build -a $(GOFLAGS) -o ./bin/donna-$(VERSION)-linux-64 $(PACKAGE) 29 | GOOS=windows GOARCH=amd64 go build -a $(GOFLAGS) -o ./bin/donna-$(VERSION)-windows-64.exe $(PACKAGE) 30 | GOOS=windows GOARCH=386 go build -a $(GOFLAGS) -o ./bin/donna-$(VERSION)-windows-32.exe $(PACKAGE) 31 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | DONNA 2 | 3 | Donna is experimental chess engine implemented in Go. Donna comes with 4 | interactive command line shell and supports standard UCI (universal chess 5 | interface) protocol. Its technical features include: 6 | 7 | Data Structures 8 | - Magic bitboards 9 | - Transposition table 10 | - Material imbalance table 11 | - Pawn cache 12 | 13 | Move Generation 14 | - All possible and all valid moves 15 | - Captures and pawn promotions 16 | - Checks and check evasions 17 | 18 | Search 19 | - Root, tree, and quiescence searches 20 | - Aspiration windows 21 | - Iterative deepening 22 | - Search depth extensions 23 | - Alpha/beta pruning 24 | - Mate distance pruning 25 | - Razoring 26 | - Futility margin pruning 27 | - Null move pruning 28 | - Late move reduction 29 | - Delta pruning for captures 30 | - Good and killer move heuristics 31 | - Insufficient material and repetition detection 32 | 33 | Position Evaluation 34 | - Piece/square bonuses 35 | - Material with imbalance adjustment 36 | - King safety and pawn shield quality 37 | - Castling rights 38 | - Piece mobility 39 | - Control of the center 40 | - Threats and hanging pieces 41 | - Passed, isolated, doubled, and backwards pawns 42 | - Trapped rooks and bishops 43 | - Known and lesser known endgames 44 | - Bitbase for King + Pawn vs. King endgames 45 | 46 | Game Controls 47 | - Maximum search depth 48 | - Time to make a move 49 | - Time control for certain number of moves 50 | - Time increment 51 | 52 | Miscellaneous 53 | - UCI protocol support 54 | - Interactive read–eval–print loop (REPL) 55 | - Polyglot opening books 56 | - Go test suite with 300+ tests 57 | - Donna Chess Format to define chess positions in human-readable way 58 | 59 | DOWNLOADS 60 | 61 | The latest versions of Donna executables are available for download at 62 | 63 | http://donnachess.github.io 64 | 65 | 64-bit builds are provided for Windows, Mac OS X, Linux, and FreeBSD. 66 | 67 | BUILDING FROM SOURCE 68 | 69 | To build Donna you will need Go v1.3 or later installed on your system (see 70 | http://golang.org/dl/). From Donna source directory run "make" or "go build" 71 | command: 72 | 73 | $ go build -o ./bin/donna -gcflags -B github.com/michaeldv/donna/cmd/donna 74 | 75 | USING DONNA 76 | 77 | Donna chess engine can be used with any chess GUI software that supports UCI 78 | protocol. You can also launch Donna as standalone command-line program and 79 | play against it in interactive mode: 80 | 81 | $ ./donna -i 82 | Donna v4.1 Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 83 | Type ? for help. 84 | 85 | donna> 86 | 87 | Donna supports Polyglot chess opening books. Free opening books are available 88 | for download at https://github.com/michaeldv/donna_opening_books. To connect 89 | the opening book set DONNA_BOOK environment variable: 90 | 91 | $ export DONNA_BOOK=~/chess/books/gm2001.bin 92 | 93 | STRENGTH 94 | 95 | Donna's chess ratings are available at Computer Chess Rating Lists site at 96 | 97 | http://www.computerchess.org.uk/ccrl/4040/cgi/engine_details.cgi?eng=Donna%201.0%2064-bit 98 | 99 | THANK YOU! 100 | 101 | Donna stands on the shoulders of the giants and it would never have been 102 | possible without them. My gratitude goes to: 103 | 104 | - Aaron Becker, Daydreamer 105 | - Fabien Letouzey, Senpai 106 | - Igor Korshunov, Murka 107 | - Jon Dart, Arasan 108 | - Steve Maughan, Maverick 109 | - Tom Kerrigan, TSCP 110 | - Tord Romstad, Marco Costalba, and Joona Kiiski, Stockfish 111 | - Vadim Demichev, Gull 112 | - Vladimir Medvedev, GreKo 113 | 114 | Special thanks goes to my old time buddy grandmaster Eduardas Rozentalis who 115 | inspired me to develop a chess engine. 116 | 117 | DEDICATION 118 | 119 | Donna chess engine is dedicated to Lithuanian grandmaster Donatas Lapienis, 120 | the man who taught me how to play chess, among other things. In the mid 121 | 1980's (pre-computer era), Donatas was the highest ranking correspondence 122 | chess player in the world with ELO rating of 2715. 123 | 124 | LICENSE 125 | 126 | Copyright (c) 2014-2018 Michael Dvorkin 127 | Donna is freely distributable under the terms of MIT license. 128 | 129 | I am making my contributions/submissions to this project solely in my personal 130 | capacity and am not conveying any rights to any intellectual property of any 131 | third parties. 132 | -------------------------------------------------------------------------------- /benchmarks/README: -------------------------------------------------------------------------------- 1 | Donna benchmark results (MacBook Air 1.3GHz Intel Core i5, 10s per position) 2 | 3 | Benchmark brilliant.dcf silent.dcf win300.dcf 4 | ------------------------------------------------------------------------------------------- 5 | Donna v4.1 +951 -50 (95.0%) +127 -7 (94.8%) +292 -8 (97.3%) 6 | Donna v4.0 +948 -53 (94.7%) +124 -10 (92.5%) +290 -10 (96.7%) 7 | Donna v3.1 +945 -56 (94.4%) +123 -11 (91.8%) +293 -7 (97.7%) 8 | Donna v3.0 +944 -57 (94.3%) +121 -13 (90.3%) +288 -12 (96.0%) 9 | Donna v2.1 +931 -70 (93.0%) +123 -11 (91.8%) +285 -15 (95.0%) 10 | Donna v2.0 +931 -70 (93.0%) +123 -11 (91.8%) +285 -15 (95.0%) 11 | Donna v1.0 +918 -83 (91.7%) +122 -12 (91.0%) +273 -27 (91.0%) 12 | -------------------------------------------------------------------------------- /bin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldv/donna/1d7facbce92a516df243d82fba66cc83e4152267/bin/.gitkeep -------------------------------------------------------------------------------- /bitmask.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`bytes`; `fmt`) 12 | 13 | type Bitmask uint64 14 | 15 | // One man's constant is another man's variable. 16 | var bit = [64]Bitmask{ 17 | 1< 127 { 44 | msbLookup[i] = 7 45 | } else if i > 63 { 46 | msbLookup[i] = 6 47 | } else if i > 31 { 48 | msbLookup[i] = 5 49 | } else if i > 15 { 50 | msbLookup[i] = 4 51 | } else if i > 7 { 52 | msbLookup[i] = 3 53 | } else if i > 3 { 54 | msbLookup[i] = 2 55 | } else if i > 1 { 56 | msbLookup[i] = 1 57 | } 58 | } 59 | } 60 | 61 | // Returns true if all bitmask bits are clear. Even if it's wrong, it's only 62 | // off by a bit. 63 | func (b Bitmask) empty() bool { 64 | return b == 0 65 | } 66 | 67 | // Returns true if at least one bit is set. 68 | func (b Bitmask) any() bool { 69 | return b != 0 70 | } 71 | 72 | // Returns true if a bit at given offset is set. 73 | func (b Bitmask) on(offset int) bool { 74 | return (b & bit[offset & 63]).any() 75 | } 76 | 77 | // Returns true if a bit at given offset is clear. 78 | func (b Bitmask) off(offset int) bool { 79 | return !b.on(offset) 80 | } 81 | 82 | // Returns true if a bitmask has single bit set. 83 | func (b Bitmask) single() bool { 84 | return b.pop().empty() 85 | } 86 | 87 | // Returns number of bits set. 88 | func (b Bitmask) count() int { 89 | if b.empty() { 90 | return 0 91 | } 92 | 93 | b -= ((b >> 1) & 0x5555555555555555) 94 | b = ((b >> 2) & 0x3333333333333333) + (b & 0x3333333333333333) 95 | b = ((b >> 4) + b) & 0x0F0F0F0F0F0F0F0F 96 | b += b >> 8 97 | b += b >> 16 98 | b += b >> 32 99 | 100 | return int(b) & 63 101 | } 102 | 103 | // Finds least significant bit set (LSB) in non-zero bitmask. Returns 104 | // an integer in 0..63 range. 105 | func (b Bitmask) first() int { 106 | return deBruijn[((b ^ (b - 1)) * 0x03F79D71B4CB0A89) >> 58] & 63 107 | } 108 | 109 | // Eugene Nalimov's bitScanReverse: finds most significant bit set (MSB). 110 | func (b Bitmask) last() (offset int) { 111 | if b > 0xFFFFFFFF { 112 | b >>= 32; offset = 32 113 | } 114 | if b > 0xFFFF { 115 | b >>= 16; offset += 16 116 | } 117 | if b > 0xFF { 118 | b >>= 8; offset += 8 119 | } 120 | 121 | return offset + msbLookup[b] 122 | } 123 | 124 | func (b Bitmask) closest(color int) int { 125 | if color == White { 126 | return b.first() 127 | } 128 | return b.last() 129 | } 130 | 131 | func (b Bitmask) farthest(color int) int { 132 | if color == White { 133 | return b.last() 134 | } 135 | return b.first() 136 | } 137 | 138 | func (b Bitmask) up(color int) Bitmask { 139 | if color == White { 140 | return b << 8 141 | } 142 | return b >> 8 143 | } 144 | 145 | // Returns bitmask with least significant bit off. 146 | func (b Bitmask) pop() Bitmask { 147 | return b & (b - 1) 148 | } 149 | 150 | // Sets a bit at given offset. 151 | func (b *Bitmask) set(offset int) *Bitmask { 152 | *b |= bit[offset] 153 | return b 154 | } 155 | 156 | // Clears a bit at given offset. 157 | func (b *Bitmask) clear(offset int) *Bitmask { 158 | *b &= ^bit[offset] 159 | return b 160 | } 161 | 162 | func (b Bitmask) shift(offset int) Bitmask { 163 | if offset > 0 { 164 | return b << uint(offset) 165 | } 166 | 167 | return b >> -uint(offset) 168 | } 169 | 170 | func (b Bitmask) charm(offset int) (bitmask Bitmask) { 171 | count := b.count() 172 | 173 | for i := 0; i < count; i++ { 174 | pop := b ^ b.pop() 175 | b = b.pop() 176 | if (bit[i] & Bitmask(offset)).any() { 177 | bitmask |= pop 178 | } 179 | } 180 | 181 | return bitmask 182 | } 183 | 184 | func (b *Bitmask) fill(square, direction int, occupied, board Bitmask) *Bitmask { 185 | for bm := (bit[square] & board).shift(direction); bm.any(); bm = bm.shift(direction) { 186 | *b |= bm 187 | if (bm & occupied).any() { 188 | break 189 | } 190 | bm &= board 191 | } 192 | 193 | return b 194 | } 195 | 196 | func (b *Bitmask) spot(square, direction int, board Bitmask) *Bitmask { 197 | *b = ^((bit[square] & board).shift(direction)) 198 | return b 199 | } 200 | 201 | func (b *Bitmask) trim(row, col int) *Bitmask { 202 | if row > 0 { 203 | *b &= 0xFFFFFFFFFFFFFF00 204 | } 205 | if row < 7 { 206 | *b &= 0x00FFFFFFFFFFFFFF 207 | } 208 | if col > 0 { 209 | *b &= 0xFEFEFEFEFEFEFEFE 210 | } 211 | if col < 7 { 212 | *b &= 0x7F7F7F7F7F7F7F7F 213 | } 214 | 215 | return b 216 | } 217 | 218 | func (b Bitmask) String() string { 219 | buffer := bytes.NewBufferString(" a b c d e f g h ") 220 | buffer.WriteString(fmt.Sprintf("0x%016X\n", uint64(b))) 221 | for row := 7; row >= 0; row-- { 222 | buffer.WriteByte('1' + byte(row)) 223 | for col := 0; col <= 7; col++ { 224 | offset := row << 3 + col 225 | buffer.WriteByte(' ') 226 | if b.on(offset) { 227 | buffer.WriteString("\u2022") // Set 228 | } else { 229 | buffer.WriteString("\u22C5") // Clear 230 | } 231 | } 232 | buffer.WriteByte('\n') 233 | } 234 | return buffer.String() 235 | } 236 | 237 | // A B C D E F G H 238 | // 7> 56 57 58 59 60 61 62 63 239 | // 6> 48 49 50 51 52 53 54 55 240 | // 5> 40 41 42 43 44 45 46 47 241 | // 4> 32 33 34 35 36 37 38 39 242 | // 3> 24 25 26 27 28 29 30 31 243 | // 2> 16 17 18 19 20 21 22 23 244 | // 1> 08 09 10 11 12 13 14 15 245 | // 0> 00 01 02 03 04 05 06 07 246 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 247 | // 0 1 2 3 4 5 6 7 248 | -------------------------------------------------------------------------------- /bitmask_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | func TestBitmask000(t *testing.T) { // maskPassed White 14 | expect.Eq(t, maskPassed[White][A2], Bitmask(0x0303030303030000)) 15 | expect.Eq(t, maskPassed[White][B2], Bitmask(0x0707070707070000)) 16 | expect.Eq(t, maskPassed[White][C2], Bitmask(0x0E0E0E0E0E0E0000)) 17 | expect.Eq(t, maskPassed[White][D2], Bitmask(0x1C1C1C1C1C1C0000)) 18 | expect.Eq(t, maskPassed[White][E2], Bitmask(0x3838383838380000)) 19 | expect.Eq(t, maskPassed[White][F2], Bitmask(0x7070707070700000)) 20 | expect.Eq(t, maskPassed[White][G2], Bitmask(0xE0E0E0E0E0E00000)) 21 | expect.Eq(t, maskPassed[White][H2], Bitmask(0xC0C0C0C0C0C00000)) 22 | 23 | expect.Eq(t, maskPassed[White][A1], Bitmask(0x0303030303030300)) 24 | expect.Eq(t, maskPassed[White][H8], Bitmask(0x0000000000000000)) 25 | expect.Eq(t, maskPassed[White][C6], Bitmask(0x0E0E000000000000)) 26 | } 27 | 28 | func TestBitmask010(t *testing.T) { // maskPassed Black 29 | expect.Eq(t, maskPassed[Black][A7], Bitmask(0x0000030303030303)) 30 | expect.Eq(t, maskPassed[Black][B7], Bitmask(0x0000070707070707)) 31 | expect.Eq(t, maskPassed[Black][C7], Bitmask(0x00000E0E0E0E0E0E)) 32 | expect.Eq(t, maskPassed[Black][D7], Bitmask(0x00001C1C1C1C1C1C)) 33 | expect.Eq(t, maskPassed[Black][E7], Bitmask(0x0000383838383838)) 34 | expect.Eq(t, maskPassed[Black][F7], Bitmask(0x0000707070707070)) 35 | expect.Eq(t, maskPassed[Black][G7], Bitmask(0x0000E0E0E0E0E0E0)) 36 | expect.Eq(t, maskPassed[Black][H7], Bitmask(0x0000C0C0C0C0C0C0)) 37 | 38 | expect.Eq(t, maskPassed[Black][A1], Bitmask(0x0000000000000000)) 39 | expect.Eq(t, maskPassed[Black][H8], Bitmask(0x00C0C0C0C0C0C0C0)) 40 | expect.Eq(t, maskPassed[Black][C6], Bitmask(0x0000000E0E0E0E0E)) 41 | } 42 | 43 | func TestBitmask020(t *testing.T) { // maskInFront White 44 | expect.Eq(t, maskInFront[0][A4], Bitmask(0x0101010100000000)) 45 | expect.Eq(t, maskInFront[0][B4], Bitmask(0x0202020200000000)) 46 | expect.Eq(t, maskInFront[0][C4], Bitmask(0x0404040400000000)) 47 | expect.Eq(t, maskInFront[0][D4], Bitmask(0x0808080800000000)) 48 | expect.Eq(t, maskInFront[0][E4], Bitmask(0x1010101000000000)) 49 | expect.Eq(t, maskInFront[0][F4], Bitmask(0x2020202000000000)) 50 | expect.Eq(t, maskInFront[0][G4], Bitmask(0x4040404000000000)) 51 | expect.Eq(t, maskInFront[0][H4], Bitmask(0x8080808000000000)) 52 | } 53 | 54 | func TestBitmask030(t *testing.T) { // maskInFront Black 55 | expect.Eq(t, maskInFront[1][A7], Bitmask(0x0000010101010101)) 56 | expect.Eq(t, maskInFront[1][B7], Bitmask(0x0000020202020202)) 57 | expect.Eq(t, maskInFront[1][C7], Bitmask(0x0000040404040404)) 58 | expect.Eq(t, maskInFront[1][D7], Bitmask(0x0000080808080808)) 59 | expect.Eq(t, maskInFront[1][E7], Bitmask(0x0000101010101010)) 60 | expect.Eq(t, maskInFront[1][F7], Bitmask(0x0000202020202020)) 61 | expect.Eq(t, maskInFront[1][G7], Bitmask(0x0000404040404040)) 62 | expect.Eq(t, maskInFront[1][H7], Bitmask(0x0000808080808080)) 63 | } 64 | 65 | func TestBitmask040(t *testing.T) { // maskBlock A1->H8 (fill) 66 | expect.Eq(t, maskBlock[A1][A1], Bitmask(0x0000000000000000)) 67 | expect.Eq(t, maskBlock[A1][B2], Bitmask(0x0000000000000200)) 68 | expect.Eq(t, maskBlock[A1][C3], Bitmask(0x0000000000040200)) 69 | expect.Eq(t, maskBlock[A1][D4], Bitmask(0x0000000008040200)) 70 | expect.Eq(t, maskBlock[A1][E5], Bitmask(0x0000001008040200)) 71 | expect.Eq(t, maskBlock[A1][F6], Bitmask(0x0000201008040200)) 72 | expect.Eq(t, maskBlock[A1][G7], Bitmask(0x0040201008040200)) 73 | expect.Eq(t, maskBlock[A1][H8], Bitmask(0x8040201008040200)) 74 | } 75 | 76 | func TestBitmask050(t *testing.T) { // maskBlock H1->A8 (fill) 77 | expect.Eq(t, maskBlock[H1][H1], Bitmask(0x0000000000000000)) 78 | expect.Eq(t, maskBlock[H1][G2], Bitmask(0x0000000000004000)) 79 | expect.Eq(t, maskBlock[H1][F3], Bitmask(0x0000000000204000)) 80 | expect.Eq(t, maskBlock[H1][E4], Bitmask(0x0000000010204000)) 81 | expect.Eq(t, maskBlock[H1][D5], Bitmask(0x0000000810204000)) 82 | expect.Eq(t, maskBlock[H1][C6], Bitmask(0x0000040810204000)) 83 | expect.Eq(t, maskBlock[H1][B7], Bitmask(0x0002040810204000)) 84 | expect.Eq(t, maskBlock[H1][A8], Bitmask(0x0102040810204000)) 85 | } 86 | 87 | func TestBitmask060(t *testing.T) { // maskEvade A1->H8 (spot) 88 | expect.Eq(t, maskEvade[A1][A1], Bitmask(0xFFFFFFFFFFFFFFFF)) 89 | expect.Eq(t, maskEvade[B2][A1], Bitmask(0xFFFFFFFFFFFBFFFF)) 90 | expect.Eq(t, maskEvade[C3][A1], Bitmask(0xFFFFFFFFF7FFFFFF)) 91 | expect.Eq(t, maskEvade[D4][A1], Bitmask(0xFFFFFFEFFFFFFFFF)) 92 | expect.Eq(t, maskEvade[E5][A1], Bitmask(0xFFFFDFFFFFFFFFFF)) 93 | expect.Eq(t, maskEvade[F6][A1], Bitmask(0xFFBFFFFFFFFFFFFF)) 94 | expect.Eq(t, maskEvade[G7][A1], Bitmask(0x7FFFFFFFFFFFFFFF)) 95 | expect.Eq(t, maskEvade[H8][A1], Bitmask(0xFFFFFFFFFFFFFFFF)) 96 | } 97 | 98 | func TestBitmask070(t *testing.T) { // maskEvade H1->A8 (spot) 99 | expect.Eq(t, maskEvade[H1][H1], Bitmask(0xFFFFFFFFFFFFFFFF)) 100 | expect.Eq(t, maskEvade[G2][H1], Bitmask(0xFFFFFFFFFFDFFFFF)) 101 | expect.Eq(t, maskEvade[F3][H1], Bitmask(0xFFFFFFFFEFFFFFFF)) 102 | expect.Eq(t, maskEvade[E4][H1], Bitmask(0xFFFFFFF7FFFFFFFF)) 103 | expect.Eq(t, maskEvade[D5][H1], Bitmask(0xFFFFFBFFFFFFFFFF)) 104 | expect.Eq(t, maskEvade[C6][H1], Bitmask(0xFFFDFFFFFFFFFFFF)) 105 | expect.Eq(t, maskEvade[B7][H1], Bitmask(0xFEFFFFFFFFFFFFFF)) 106 | expect.Eq(t, maskEvade[A8][H1], Bitmask(0xFFFFFFFFFFFFFFFF)) 107 | } 108 | 109 | func TestBitmask100(t *testing.T) { 110 | mask := Bitmask(0x0000000000000001) 111 | expect.Eq(t, mask.pop(), Bitmask(0x0000000000000000)) 112 | 113 | mask = Bitmask(0xFFFFFFFFFFFFFFF0) 114 | expect.Eq(t, mask.pop(), Bitmask(0xFFFFFFFFFFFFFFE0)) 115 | 116 | mask = Bitmask(0x8000000000000000) 117 | expect.Eq(t, mask.pop(), Bitmask(0x0000000000000000)) 118 | } 119 | -------------------------------------------------------------------------------- /book.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import ( 12 | `encoding/binary` 13 | `math/rand` 14 | `os` 15 | `sort` 16 | `time` 17 | ) 18 | 19 | // Many pages make a thick book. 20 | type Book struct { 21 | fileName string 22 | entries int64 23 | } 24 | 25 | // Opening book record: the fields are exported for binary.Read(). 26 | type Entry struct { 27 | Key uint64 28 | Move uint16 29 | Score uint16 30 | Learn uint32 31 | } 32 | 33 | func NewBook(bookFile string) (*Book, error) { 34 | book := &Book{fileName: bookFile} 35 | 36 | if fi, err := os.Stat(book.fileName); err != nil { 37 | return nil, err 38 | } else { 39 | book.entries = fi.Size() / 16 40 | } 41 | 42 | return book, nil 43 | } 44 | 45 | func (b *Book) pickMove(position *Position) Move { 46 | // The generation of random numbers is too important to be left to chance. 47 | // Returns pseudo-random integer in [0, limit] range. It panics if limit <= 0. 48 | random := func(limit int) int { 49 | rand.Seed(time.Now().Unix()); return rand.Intn(limit) 50 | } 51 | 52 | entries := b.lookup(position) 53 | switch length := len(entries); length { 54 | case 0: 55 | // TODO: set the "useless book" flag after a few misses. 56 | return Move(0) 57 | case 1: 58 | // The only move available. 59 | return b.move(position, entries[0]) 60 | default: 61 | // Sort book entries by score and pick among two best moves. 62 | sort.Sort(byBookScore{entries}) 63 | best := min(2, len(entries)) 64 | return b.move(position, entries[random(best)]) 65 | } 66 | } 67 | 68 | func (b *Book) lookup(position *Position) (entries []Entry) { 69 | var entry Entry 70 | 71 | file, err := os.Open(b.fileName) 72 | if err != nil { 73 | return entries 74 | } 75 | defer file.Close() 76 | 77 | key := position.id 78 | 79 | // Since book entries are ordered by polyglot key we can use binary 80 | // search to find *first* book entry that matches the position. 81 | first, current, last := int64(-1), int64(0), b.entries 82 | for first < last { 83 | current = (first + last) / 2 84 | file.Seek(current * 16, 0) 85 | binary.Read(file, binary.BigEndian, &entry) 86 | if key <= entry.Key { 87 | last = current 88 | } else { 89 | first = current + 1 90 | } 91 | } 92 | 93 | // Read all book entries for the given position. 94 | file.Seek(first * 16, 0) 95 | for { 96 | binary.Read(file, binary.BigEndian, &entry) 97 | if key != entry.Key { 98 | break 99 | } else { 100 | entries = append(entries, entry) 101 | } 102 | } 103 | 104 | return entries 105 | } 106 | 107 | func (b *Book) move(p *Position, entry Entry) Move { 108 | from, to := entry.from(), entry.to() 109 | 110 | // Check if this is a castle move. In Polyglot they are represented 111 | // as E1-H1, E1-A1, E8-H8, and E8-A8. 112 | if from == E1 && to == H1 { 113 | return NewCastle(p, from, G1) 114 | } else if from == E1 && to == A1 { 115 | return NewCastle(p, from, C1) 116 | } else if from == E8 && to == H8 { 117 | return NewCastle(p, from, G8) 118 | } else if from == E8 && to == A8 { 119 | return NewCastle(p, from, C8) 120 | } else { 121 | // Special treatment for non-promo pawn moves since they might 122 | // cause en-passant. 123 | if piece := p.pieces[from]; piece.isPawn() && to > H1 && to < A8 { 124 | return NewPawnMove(p, from, to) 125 | } 126 | } 127 | 128 | move := NewMove(p, from, to) 129 | if promo := entry.promoted(); promo != 0 { 130 | move.promote(promo) 131 | } 132 | 133 | return move 134 | } 135 | 136 | // Converts polyglot encoded "from" coordinate to our square. 137 | func (e *Entry) from() int { 138 | return square(int((e.Move >> 9) & 7), int((e.Move >> 6) & 7)) 139 | } 140 | 141 | // Converts polyglot encoded "to" coordinate to our square. 142 | func (e *Entry) to() int { 143 | return square(int((e.Move >> 3) & 7), int(e.Move & 7)) 144 | } 145 | 146 | // Polyglot encodes "promotion piece" as follows: 147 | // knight 1 => 4 148 | // bishop 2 => 6 149 | // rook 3 => 8 150 | // queen 4 => 10 151 | func (e *Entry) promoted() int { 152 | piece := int((e.Move >> 12) & 7) 153 | if piece == 0 { 154 | return piece 155 | } 156 | return piece * 2 + 2 157 | } 158 | 159 | type byBookScore struct { 160 | list []Entry 161 | } 162 | 163 | func (a byBookScore) Len() int { return len(a.list) } 164 | func (a byBookScore) Swap(i, j int) { a.list[i], a.list[j] = a.list[j], a.list[i] } 165 | func (a byBookScore) Less(i, j int) bool { return a.list[i].Score > a.list[j].Score } 166 | -------------------------------------------------------------------------------- /book_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | func openBook() (*Book, *Position) { 14 | return &Book{}, NewGame().start() 15 | } 16 | 17 | func polyglotEntry(source, target int) Entry { 18 | return Entry{Move: uint16(row(source)<<9) | uint16(col(source)<<6) | 19 | uint16(row(target)<<3) | uint16(col(target))} 20 | } 21 | 22 | // See test key values at http://hardy.uhasselt.be/Toga/book_format.html 23 | func TestBook000(t *testing.T) { 24 | p := NewGame().start() 25 | hash, pawnHash := p.polyglot() 26 | 27 | expect.Eq(t, hash, uint64(0x463B96181691FC9C)) 28 | expect.Eq(t, pawnHash, uint64(0x37FC40DA841E1692)) 29 | } 30 | 31 | func TestBook010(t *testing.T) { // 1. e4 32 | book, p := openBook() 33 | p = p.makeMove(book.move(p, polyglotEntry(E2, E4))) 34 | hash, pawnHash := p.polyglot() 35 | 36 | expect.Eq(t, hash, uint64(0x823C9B50FD114196)) 37 | expect.Eq(t, pawnHash, uint64(0x0B2D6B38C0B92E91)) 38 | expect.Eq(t, p.enpassant, 0) 39 | expect.Eq(t, p.castles, uint8(0x0F)) 40 | } 41 | 42 | func TestBook020(t *testing.T) { // 1. e4 d5 43 | book, p := openBook() 44 | p = p.makeMove(book.move(p, polyglotEntry(E2, E4))) 45 | p = p.makeMove(book.move(p, polyglotEntry(D7, D5))) 46 | hash, pawnHash := p.polyglot() 47 | 48 | expect.Eq(t, hash, uint64(0x0756B94461C50FB0)) 49 | expect.Eq(t, pawnHash, uint64(0x76916F86F34AE5BE)) 50 | expect.Eq(t, p.enpassant, 0) 51 | expect.Eq(t, p.castles, uint8(0x0F)) 52 | } 53 | 54 | func TestBook030(t *testing.T) { // 1. e4 d5 2. e5 55 | book, p := openBook() 56 | p = p.makeMove(book.move(p, polyglotEntry(E2, E4))) 57 | p = p.makeMove(book.move(p, polyglotEntry(D7, D5))) 58 | p = p.makeMove(book.move(p, polyglotEntry(E4, E5))) 59 | hash, pawnHash := p.polyglot() 60 | 61 | expect.Eq(t, hash, uint64(0x662FAFB965DB29D4)) 62 | expect.Eq(t, pawnHash, uint64(0xEF3E5FD1587346D3)) 63 | expect.Eq(t, p.enpassant, 0) 64 | expect.Eq(t, p.castles, uint8(0x0F)) 65 | } 66 | 67 | func TestBook040(t *testing.T) { // 1. e4 d5 2. e5 f5 <-- Enpassant 68 | book, p := openBook() 69 | p = p.makeMove(book.move(p, polyglotEntry(E2, E4))) 70 | p = p.makeMove(book.move(p, polyglotEntry(D7, D5))) 71 | p = p.makeMove(book.move(p, polyglotEntry(E4, E5))) 72 | p = p.makeMove(book.move(p, polyglotEntry(F7, F5))) 73 | hash, pawnHash := p.polyglot() 74 | 75 | expect.Eq(t, hash, uint64(0x22A48B5A8E47FF78)) 76 | expect.Eq(t, pawnHash, uint64(0x83871FE249DCEE04)) 77 | expect.Eq(t, p.enpassant, F6) 78 | expect.Eq(t, p.castles, uint8(0x0F)) 79 | } 80 | 81 | func TestBook050(t *testing.T) { // 1. e4 d5 2. e5 f5 3. Ke2 <-- White Castle 82 | book, p := openBook() 83 | p = p.makeMove(book.move(p, polyglotEntry(E2, E4))) 84 | p = p.makeMove(book.move(p, polyglotEntry(D7, D5))) 85 | p = p.makeMove(book.move(p, polyglotEntry(E4, E5))) 86 | p = p.makeMove(book.move(p, polyglotEntry(F7, F5))) 87 | p = p.makeMove(book.move(p, polyglotEntry(E1, E2))) 88 | hash, pawnHash := p.polyglot() 89 | 90 | expect.Eq(t, hash, uint64(0x652A607CA3F242C1)) 91 | expect.Eq(t, pawnHash, uint64(0x83871FE249DCEE04)) 92 | expect.Eq(t, p.enpassant, 0) 93 | expect.Eq(t, p.castles, castleKingside[Black]|castleQueenside[Black]) 94 | } 95 | 96 | func TestBook060(t *testing.T) { // 1. e4 d5 2. e5 f5 3. Ke2 Kf7 <-- Black Castle 97 | book, p := openBook() 98 | p = p.makeMove(book.move(p, polyglotEntry(E2, E4))) 99 | p = p.makeMove(book.move(p, polyglotEntry(D7, D5))) 100 | p = p.makeMove(book.move(p, polyglotEntry(E4, E5))) 101 | p = p.makeMove(book.move(p, polyglotEntry(F7, F5))) 102 | p = p.makeMove(book.move(p, polyglotEntry(E1, E2))) 103 | p = p.makeMove(book.move(p, polyglotEntry(E8, F7))) 104 | hash, pawnHash := p.polyglot() 105 | 106 | expect.Eq(t, hash, uint64(0x00FDD303C946BDD9)) 107 | expect.Eq(t, pawnHash, uint64(0x83871FE249DCEE04)) 108 | expect.Eq(t, p.enpassant, 0) 109 | expect.Eq(t, p.castles, uint8(0)) 110 | } 111 | 112 | func TestBook070(t *testing.T) { // 1. a2a4 b7b5 2. h2h4 b5b4 3. c2c4 <-- Enpassant 113 | book, p := openBook() 114 | p = p.makeMove(book.move(p, polyglotEntry(A2, A4))) 115 | p = p.makeMove(book.move(p, polyglotEntry(B7, B5))) 116 | p = p.makeMove(book.move(p, polyglotEntry(H2, H4))) 117 | p = p.makeMove(book.move(p, polyglotEntry(B5, B4))) 118 | p = p.makeMove(book.move(p, polyglotEntry(C2, C4))) 119 | hash, pawnHash := p.polyglot() 120 | 121 | expect.Eq(t, hash, uint64(0x3C8123EA7B067637)) 122 | expect.Eq(t, pawnHash, uint64(0xB5AA405AF42E7052)) 123 | expect.Eq(t, p.enpassant, C3) 124 | expect.Eq(t, p.castles, uint8(0x0F)) 125 | } 126 | 127 | func TestBook080(t *testing.T) { // 1. a2a4 b7b5 2. h2h4 b5b4 3. c2c4 b4xc3 4. Ra1a3 <-- Enpassant/Castle 128 | book, p := openBook() 129 | p = p.makeMove(book.move(p, polyglotEntry(A2, A4))) 130 | p = p.makeMove(book.move(p, polyglotEntry(B7, B5))) 131 | p = p.makeMove(book.move(p, polyglotEntry(H2, H4))) 132 | p = p.makeMove(book.move(p, polyglotEntry(B5, B4))) 133 | p = p.makeMove(book.move(p, polyglotEntry(C2, C4))) 134 | p = p.makeMove(book.move(p, polyglotEntry(B4, C3))) 135 | p = p.makeMove(book.move(p, polyglotEntry(A1, A3))) 136 | hash, pawnHash := p.polyglot() 137 | 138 | expect.Eq(t, hash, uint64(0x5C3F9B829B279560)) 139 | expect.Eq(t, pawnHash, uint64(0xE214F040EAA135A0)) 140 | expect.Eq(t, p.enpassant, 0) 141 | expect.Eq(t, p.castles, castleKingside[White]|castleKingside[Black]|castleQueenside[Black]) 142 | } 143 | 144 | func TestBook100(t *testing.T) { // 1. e4 e5 145 | book, p := openBook() 146 | p = p.makeMove(book.move(p, polyglotEntry(E2, E4))) 147 | p = p.makeMove(book.move(p, polyglotEntry(E7, E5))) 148 | hash, pawnHash := p.polyglot() 149 | 150 | expect.Eq(t, hash, uint64(0x0844931A6EF4B9A0)) 151 | expect.Eq(t, pawnHash, uint64(0x798345D8FC7B53AE)) 152 | expect.Eq(t, p.enpassant, 0) 153 | expect.Eq(t, p.castles, uint8(0x0F)) 154 | } 155 | 156 | func TestBook110(t *testing.T) { // 1. d4 d5 157 | book, p := openBook() 158 | p = p.makeMove(book.move(p, polyglotEntry(D2, D4))) 159 | p = p.makeMove(book.move(p, polyglotEntry(D7, D5))) 160 | hash, pawnHash := p.polyglot() 161 | 162 | expect.Eq(t, hash, uint64(0x06649BA69B8C9FF8)) 163 | expect.Eq(t, pawnHash, uint64(0x77A34D64090375F6)) 164 | expect.Eq(t, p.enpassant, 0) 165 | expect.Eq(t, p.castles, uint8(0x0F)) 166 | } 167 | -------------------------------------------------------------------------------- /cmd/donna/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | // This space is available for rent. 10 | package main 11 | 12 | import ( 13 | `github.com/michaeldv/donna` 14 | `os` 15 | `runtime` 16 | ) 17 | 18 | // Ignore previous comment. 19 | func main() { 20 | // Default engine settings are: 256MB transposition table, 5s per move. 21 | engine := donna.NewEngine( 22 | `fancy`, runtime.GOOS == `darwin`, 23 | `cache`, 256, 24 | `movetime`, 5000, 25 | `logfile`, os.Getenv(`DONNA_LOG`), 26 | `bookfile`, os.Getenv(`DONNA_BOOK`), 27 | ) 28 | 29 | if len(os.Args) > 1 && os.Args[1] == `-i` { 30 | engine.Repl() 31 | } else { 32 | engine.Uci() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /engine_repl.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import( 12 | `fmt` 13 | `io/ioutil` 14 | `regexp` 15 | `runtime` 16 | `strconv` 17 | `strings` 18 | `time` 19 | ) 20 | 21 | var ( 22 | ansiRed = "\033[0;31m" 23 | ansiGreen = "\033[0;32m" 24 | ansiTeal = "\033[0;36m" 25 | ansiWhite = "\033[0;37m" 26 | ansiNone = "\033[0m" 27 | ) 28 | 29 | func (e *Engine) replBestMove(move Move) *Engine { 30 | fmt.Printf(ansiTeal + "Donna's move: %s", move) 31 | if game.nodes == 0 { 32 | fmt.Printf(" (book)") 33 | } 34 | fmt.Println(ansiNone + "\n") 35 | 36 | return e 37 | } 38 | 39 | func (e *Engine) replPrincipal(depth, score, status int, duration int64) { 40 | fmt.Printf(`%2d %s %9d %9d %8.1fK %6.1f%% `, depth, ms(duration), game.nodes, game.qnodes, float32(nps(duration)) / 1000.0, float32(hashfull()) / 10.0) 41 | switch status { 42 | case WhiteWon: 43 | fmt.Println(`1-0 White Checkmates`) 44 | case BlackWon: 45 | fmt.Println(`0-1 Black Checkmates`) 46 | case Stalemate: 47 | fmt.Println(`1/2 Stalemate`) 48 | case Repetition: 49 | fmt.Println(`1/2 Repetition`) 50 | case FiftyMoves: 51 | fmt.Println(`1/2 Fifty Moves`) 52 | case WhiteWinning, BlackWinning: // Show moves till checkmate. 53 | fmt.Printf("%6dX %v Checkmate\n", (Checkmate - abs(score)) / 2 + 1, game.rootpv.moves[0:game.rootpv.size]) 54 | default: 55 | fmt.Printf("%7.2f %v\n", float32(score) / float32(onePawn), game.rootpv.moves[0:game.rootpv.size]) 56 | } 57 | } 58 | 59 | // There are two types of command interfaces in the world of computing: good 60 | // interfaces and user interfaces. -- Daniel J. Bernstein 61 | func (e *Engine) Repl() *Engine { 62 | var game *Game 63 | var position *Position 64 | 65 | // Suppress ANSI colors when running Windows. 66 | if runtime.GOOS == `windows` { 67 | ansiRed, ansiGreen, ansiTeal, ansiNone = ``, ``, ``, `` 68 | } 69 | 70 | setup := func() { 71 | if game == nil || position == nil { 72 | game = NewGame() 73 | position = game.start() 74 | fmt.Printf("%s\n", position) 75 | } 76 | } 77 | 78 | think := func() { 79 | if move := game.Think(); move != 0 { 80 | position = position.makeMove(move) 81 | fmt.Printf("%s\n", position) 82 | } 83 | } 84 | 85 | book := func(fileName string) { 86 | if e.bookFile = fileName; e.bookFile == `` { 87 | fmt.Println(`Using no opening book`) 88 | } else { 89 | fmt.Printf("Using opening book %s\n", fileName) 90 | } 91 | } 92 | 93 | benchmark := func(fileName string) { 94 | maxDepth, moveTime := e.options.maxDepth, e.options.moveTime 95 | e.options.maxDepth, e.options.moveTime = 0, 10000 96 | defer func() { 97 | e.options.maxDepth, e.options.moveTime = maxDepth, moveTime 98 | if err := recover(); err != nil { 99 | fmt.Printf("Error loading %s\n", fileName) 100 | } 101 | }() 102 | 103 | content, err := ioutil.ReadFile(fileName) 104 | if err == nil { 105 | total, solved := 0, 0 106 | re := regexp.MustCompile(`[\s\+\?!]`) 107 | 108 | NextLine: 109 | for _, line := range strings.Split(string(content), "\n") { 110 | if len(line) > 0 && line[0] != '#' { 111 | total++ 112 | game := NewGame(line) 113 | position := game.start() 114 | 115 | best := strings.Split(line, ` # `)[1] // TODO: add support for "am" (avoid move). 116 | fmt.Printf(ansiTeal + "%d) %s for %s" + ansiNone + "\n%s\n", total, best, C(position.color), position) 117 | move := game.Think() 118 | 119 | for _, nextBest := range strings.Split(best, ` `) { 120 | if move.str() == re.ReplaceAllLiteralString(nextBest, ``) { 121 | solved++ 122 | fmt.Printf(ansiGreen + "%d) Solved (%d/%d %2.1f%%)\n\n\n" + ansiNone, total, solved, total - solved, float32(solved) * 100.0 / float32(total)) 123 | continue NextLine 124 | } 125 | } 126 | fmt.Printf(ansiRed + "%d) Not solved (%d/%d %2.1f%%)\n\n\n" + ansiNone, total, solved, total - solved, float32(solved) * 100.0 / float32(total)) 127 | } 128 | } 129 | } else { 130 | fmt.Printf("Could not open benchmark file '%s'\n", fileName) 131 | } 132 | } 133 | 134 | perft := func(parameter string) { 135 | if parameter == `` { 136 | parameter = `5` 137 | } 138 | if depth, err := strconv.Atoi(parameter); err == nil { 139 | position := NewGame().start() 140 | start := time.Now() 141 | total := position.Perft(depth) 142 | finish := since(start) 143 | fmt.Printf(" Depth: %d\n", depth) 144 | fmt.Printf(" Nodes: %d\n", total) 145 | fmt.Printf("Elapsed: %s\n", ms(finish)) 146 | fmt.Printf("Nodes/s: %dK\n", total / finish) 147 | } 148 | } 149 | 150 | fmt.Printf("Donna v%s Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved.\nType ? for help.\n\n", Version) 151 | for command, parameter := ``, ``; ; command, parameter = ``, `` { 152 | fmt.Print(`donna> `) 153 | fmt.Scanln(&command, ¶meter) 154 | 155 | switch command { 156 | case ``: 157 | case `bench`: 158 | benchmark(parameter) 159 | case `book`: 160 | book(parameter) 161 | case `exit`, `quit`: 162 | return e 163 | case `go`: 164 | setup() 165 | think() 166 | case `help`, `?`: 167 | fmt.Println("The commands are:\n\n" + 168 | " bench Run benchmarks\n" + 169 | " book Use opening book\n" + 170 | " exit Exit the program\n" + 171 | " go Take side and make a move\n" + 172 | " help Display this help\n" + 173 | " new Start new game\n" + 174 | " perft [depth] Run perft test\n" + 175 | " score Show evaluation summary\n" + 176 | " undo Undo last move\n\n" + 177 | "To make a move use algebraic notation, for example e2e4, Ng1f3, or e7e8Q\n") 178 | case `new`: 179 | game, position = nil, nil 180 | setup() 181 | case `perft`: 182 | perft(parameter) 183 | case `score`: 184 | setup() 185 | _, metrics := position.EvaluateWithTrace() 186 | Summary(metrics) 187 | case `undo`: 188 | if position != nil { 189 | position = position.undoLastMove() 190 | fmt.Printf("%s\n", position) 191 | } 192 | default: 193 | setup() 194 | if move, validMoves := NewMoveFromString(position, command); move != 0 { 195 | position = position.makeMove(move) 196 | think() 197 | } else { // Invalid move or non-evasion on check. 198 | fancy := e.fancy; e.fancy = false 199 | fmt.Printf("%s appears to be an invalid move; valid moves are %v\n", command, validMoves) 200 | e.fancy = fancy 201 | } 202 | } 203 | } 204 | return e 205 | } 206 | -------------------------------------------------------------------------------- /engine_uci.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import ( 12 | `bufio` 13 | `fmt` 14 | `io` 15 | `os` 16 | `strconv` 17 | `strings` 18 | ) 19 | 20 | func (e *Engine) uciScore(depth, score, alpha, beta int) *Engine { 21 | str := fmt.Sprintf("info depth %d score", depth) 22 | 23 | if !isMate(score) { 24 | str += fmt.Sprintf(" cp %d", score * 100 / onePawn) 25 | } else { 26 | mate := -Checkmate - score 27 | if score > 0 { 28 | mate = Checkmate - score + 1 29 | } 30 | str += fmt.Sprintf(" mate %d", mate/2) 31 | } 32 | if score <= alpha { 33 | str += " upperbound" 34 | } else if score >= beta { 35 | str += " lowerbound" 36 | } 37 | 38 | return engine.reply(str + "\n") 39 | } 40 | 41 | func (e *Engine) uciMove(move Move, moveno, depth int) *Engine { 42 | return engine.reply("info depth %d currmove %s currmovenumber %d\n", depth, move.notation(), moveno) 43 | } 44 | 45 | func (e *Engine) uciBestMove(move Move, duration int64) *Engine { 46 | return engine.reply("info nodes %d time %d\nbestmove %s\n", game.nodes + game.qnodes, duration, move.notation()) 47 | } 48 | 49 | func (e *Engine) uciPrincipal(depth, score int, duration int64) *Engine { 50 | str := fmt.Sprintf("info depth %d score", depth) 51 | 52 | if !isMate(score) { 53 | str += fmt.Sprintf(" cp %d", score * 100 / onePawn) 54 | } else { 55 | mate := -Checkmate - score 56 | if score > 0 { 57 | mate = Checkmate - score + 1 58 | } 59 | str += fmt.Sprintf(" mate %d", mate / 2) 60 | } 61 | str += fmt.Sprintf(" nodes %d nps %d hashfull %d time %d pv", game.nodes + game.qnodes, nps(duration), hashfull(), duration) 62 | 63 | for i := 0; i < game.rootpv.size; i++ { 64 | str += " " + game.rootpv.moves[i].notation() 65 | } 66 | 67 | return engine.reply(str + "\n") 68 | } 69 | 70 | // Brain-damaged universal chess interface (UCI) protocol as described at 71 | // http://wbec-ridderkerk.nl/html/UCIProtocol.html 72 | func (e *Engine) Uci() *Engine { 73 | var game *Game 74 | var position *Position 75 | 76 | e.uci = true 77 | 78 | // "uci" command handler. 79 | doUci := func(args []string) { 80 | e.reply("Donna v%s Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved.\n", Version) 81 | e.reply("id name Donna %s\n", Version) 82 | e.reply("id author Michael Dvorkin\n") 83 | e.reply("option name Hash type spin default 256 min 32 max 1024\n") 84 | // e.reply("option name Mobility type spin default %d min 0 max 100\n", weightMobility.midgame) 85 | // e.reply("option name PawnStructure type spin default %d min 0 max 100\n", weightPawnStructure.midgame) 86 | // e.reply("option name PassedPawns type spin default %d min 0 max 100\n", weightPassedPawns.midgame) 87 | // e.reply("option name KingSafety type spin default %d min 0 max 100\n", weightOurKingSafety.midgame) 88 | // e.reply("option name OppositeKingSafety type spin default %d min 0 max 100\n", weightTheirKingSafety.midgame) 89 | e.reply("uciok\n") 90 | } 91 | 92 | // "ucinewgame" command handler. 93 | doUciNewGame := func(args []string) { 94 | game, position = nil, nil 95 | } 96 | 97 | // "isready" command handler. 98 | doIsReady := func(args []string) { 99 | e.reply("readyok\n") 100 | } 101 | 102 | // "position [startpos | fen ] [ moves ... ]" command handler. 103 | doPosition := func(args []string) { 104 | // Make sure we've started the game since "ucinewgame" is optional. 105 | if game == nil || position == nil { 106 | game = NewGame() 107 | } 108 | 109 | switch args[0] { 110 | case `startpos`: 111 | args = args[1:] 112 | position = game.start() 113 | case `fen`: 114 | fen := []string{} 115 | for _, token := range args[1:] { 116 | args = args[1:] // Shift the token. 117 | if token == `moves` { 118 | break 119 | } 120 | fen = append(fen, token) 121 | } 122 | game.initial = strings.Join(fen, ` `) 123 | position = game.start() 124 | default: 125 | return 126 | } 127 | 128 | if position != nil && len(args) > 0 && args[0] == `moves` { 129 | for _, move := range args[1:] { 130 | args = args[1:] // Shift the move. 131 | position = position.makeMove(NewMoveFromNotation(position, move)) 132 | } 133 | } 134 | } 135 | 136 | // "go [[wtime winc | btime binc ] movestogo] | depth | nodes | movetime" 137 | doGo := func(args []string) { 138 | think := true 139 | options := e.options 140 | 141 | for i, token := range args { 142 | // Boolen "infinite" and "ponder" commands have no arguments. 143 | if token == `infinite` { 144 | options = Options{infinite: true} 145 | } else if token == `ponder` { 146 | options = Options{ponder: true} 147 | } else if token == `test` { // <-- Custom token for use in tests. 148 | think = false 149 | } else if len(args) > i+1 { 150 | switch token { 151 | case `depth`: 152 | if n, err := strconv.Atoi(args[i+1]); err == nil { 153 | options = Options{ maxDepth: n } 154 | } 155 | case `nodes`: 156 | if n, err := strconv.Atoi(args[i+1]); err == nil { 157 | options = Options{ maxNodes: n } 158 | } 159 | case `movetime`: 160 | if n, err := strconv.Atoi(args[i+1]); err == nil { 161 | options = Options{ moveTime: int64(n) } 162 | } 163 | case `wtime`: 164 | if position.color == White { 165 | if n, err := strconv.Atoi(args[i+1]); err == nil { 166 | options.timeLeft = int64(n) 167 | } 168 | } 169 | case `btime`: 170 | if position.color == Black { 171 | if n, err := strconv.Atoi(args[i+1]); err == nil { 172 | options.timeLeft = int64(n) 173 | } 174 | } 175 | case `winc`: 176 | if position.color == White { 177 | if n, err := strconv.Atoi(args[i+1]); err == nil { 178 | options.timeInc = int64(n) 179 | } 180 | } 181 | case `binc`: 182 | if position.color == Black { 183 | if n, err := strconv.Atoi(args[i+1]); err == nil { 184 | options.timeInc = int64(n) 185 | } 186 | } 187 | case `movestogo`: 188 | if n, err := strconv.Atoi(args[i+1]); err == nil { 189 | options.movesToGo = int64(n) 190 | } 191 | } 192 | } 193 | } 194 | if options.timeLeft != 0 || options.timeInc != 0 || options.movesToGo != 0 { 195 | e.varyingLimits(options) 196 | } else { 197 | e.fixedLimit(options) 198 | } 199 | 200 | // Start "thinking" and come up with best move unless when running 201 | // tests where we verify argument parsing only. 202 | if think { 203 | game.Think() 204 | } 205 | } 206 | 207 | // Stop calculating as soon as possible. 208 | doStop := func(args []string) { 209 | e.clock.halt = true 210 | } 211 | 212 | // Set UCI option. So far we only support "setoption name Hash value 32..1024". 213 | doSetOption := func(args []string) { 214 | if len(args) == 4 && args[0] == `name` && args[1] == `Hash` && args[2] == `value` { 215 | if n, err := strconv.Atoi(args[3]); err == nil && n >= 32 && n <= 1024 { 216 | e.cacheSize = float64(n) 217 | game, position = nil, nil // Make sure the game gets restarted. 218 | } 219 | } 220 | } 221 | 222 | var commands = map[string]func([]string){ 223 | `isready`: doIsReady, 224 | `uci`: doUci, 225 | `ucinewgame`: doUciNewGame, 226 | `position`: doPosition, 227 | `go`: doGo, 228 | `stop`: doStop, 229 | `setoption`: doSetOption, 230 | } 231 | 232 | // I/O, I/O, 233 | // It's off to disk I go, 234 | // a bit or byte to read or write, 235 | // I/O, I/O, I/O, I/O 236 | // -- Dave Peacock 237 | bio := bufio.NewReader(os.Stdin) 238 | for { 239 | command, err := bio.ReadString('\n') 240 | if err != io.EOF && len(command) > 0 { 241 | //\\ e.debug("> " + command) 242 | args := strings.Split(strings.Trim(command, " \t\r\n"), ` `) 243 | if args[0] == `quit` { 244 | break 245 | } 246 | if handler, ok := commands[args[0]]; ok { 247 | handler(args[1:]) 248 | } 249 | } 250 | } 251 | return e 252 | } 253 | -------------------------------------------------------------------------------- /evaluate.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | // const = brains * looks * availability 12 | const ( 13 | whiteKingSafety = 0x01 // Should we worry about white king's safety? 14 | blackKingSafety = 0x02 // Ditto for the black king. 15 | materialDraw = 0x04 // King vs. King (with minor) 16 | knownEndgame = 0x08 // Where we calculate exact score. 17 | lesserKnownEndgame = 0x10 // Where we set score markdown value. 18 | singleBishops = 0x20 // Sides might have bishops on opposite color squares. 19 | ) 20 | 21 | // Hash containing various evaluation metrics; used only when evaluation tracing 22 | // is enabled. 23 | type Metrics map[string]interface{} 24 | 25 | // King safety information; used only in the middle game when there is enough 26 | // material to worry about the king safety. 27 | type Safety struct { 28 | fort Bitmask // Squares around the king plus one extra row in front. 29 | threats int // A sum of treats: each based on attacking piece type. 30 | attacks int // Number of attacks on squares adjacent to the king. 31 | attackers int // Number of pieces attacking king's fort. 32 | } 33 | 34 | // Helper structure used for evaluation tracking. 35 | type Total struct { 36 | white Score // Score for white. 37 | black Score // Score for black. 38 | } 39 | 40 | type Function func(*Evaluation) int 41 | type MaterialEntry struct { 42 | score Score // Score adjustment for the given material. 43 | endgame Function // Function to analyze an endgame position. 44 | phase int // Game phase based on available material. 45 | turf int // Home turf score for the game opening. 46 | flags uint8 // Evaluation flags based on material balance. 47 | } 48 | 49 | type Evaluation struct { 50 | score Score // Current score. 51 | safety [2]Safety // King safety data for both sides. 52 | attacks [14]Bitmask // Attack bitmasks for all the pieces on the board. 53 | pins [2]Bitmask // Bitmask of pinned pieces for both sides. 54 | pawns *PawnEntry // Pointer to the pawn cache entry. 55 | material *MaterialEntry // Pointer to the matrial base entry. 56 | position *Position // Pointer to the position we're evaluating. 57 | metrics Metrics // Evaluation metrics when tracking is on. 58 | } 59 | 60 | // Use single statically allocated variable to avoid garbage collection overhead. 61 | var eval Evaluation 62 | 63 | // The following statement is true. The previous statement is false. Main position 64 | // evaluation method that returns single blended score. 65 | func (p *Position) Evaluate() int { 66 | return eval.init(p).run() 67 | } 68 | 69 | // Auxiliary evaluation method that captures individual evaluation metrics. This 70 | // is useful when we want to see evaluation summary. 71 | func (p *Position) EvaluateWithTrace() (int, Metrics) { 72 | eval.init(p) 73 | eval.metrics = make(Metrics) 74 | 75 | engine.trace = true 76 | defer func() { 77 | var tempo Total 78 | var final Score 79 | 80 | if p.color == White { 81 | tempo.white.add(rightToMove) 82 | final.add(eval.score) 83 | } else { 84 | tempo.black.add(rightToMove) 85 | final.sub(eval.score) 86 | } 87 | 88 | eval.checkpoint(`Phase`, eval.material.phase) 89 | eval.checkpoint(`Imbalance`, eval.material.score) 90 | eval.checkpoint(`PST`, p.tally) 91 | eval.checkpoint(`Tempo`, tempo) 92 | eval.checkpoint(`Final`, final) 93 | engine.trace = false 94 | }() 95 | 96 | return eval.run(), eval.metrics 97 | } 98 | 99 | func (e *Evaluation) init(p *Position) *Evaluation { 100 | eval = Evaluation{} 101 | e.position = p 102 | 103 | // Initialize the score with incremental PST value and right to move. 104 | e.score = p.tally 105 | if p.color == White { 106 | e.score.add(rightToMove) 107 | } else { 108 | e.score.sub(rightToMove) 109 | } 110 | 111 | // Set up king and pawn attacks for both sides. 112 | e.attacks[King] = p.kingAttacks(White) 113 | e.attacks[Pawn] = p.pawnAttacks(White) 114 | e.attacks[BlackKing] = p.kingAttacks(Black) 115 | e.attacks[BlackPawn] = p.pawnAttacks(Black) 116 | 117 | // Overall attacks for both sides include kings and pawns so far. 118 | e.attacks[White] = e.attacks[King] | e.attacks[Pawn] 119 | e.attacks[Black] = e.attacks[BlackKing] | e.attacks[BlackPawn] 120 | 121 | // Pinned pieces for both sides that have restricted mobility. 122 | e.pins[White] = p.pins(p.king[White]) 123 | e.pins[Black] = p.pins(p.king[Black]) 124 | 125 | return e 126 | } 127 | 128 | func (e *Evaluation) run() int { 129 | e.material = &materialBase[e.position.balance] 130 | 131 | e.score.add(e.material.score) 132 | if e.material.flags & knownEndgame != 0 { 133 | return e.evaluateEndgame() 134 | } 135 | 136 | e.analyzePawns() 137 | e.analyzePieces() 138 | e.analyzeThreats() 139 | e.analyzeSafety() 140 | e.analyzePassers() 141 | e.wrapUp() 142 | 143 | return e.score.blended(e.material.phase) 144 | } 145 | 146 | func (e *Evaluation) wrapUp() { 147 | 148 | // Adjust the endgame score if we have lesser known endgame. 149 | if e.score.endgame != 0 && e.material.flags & lesserKnownEndgame != 0 { 150 | e.inspectEndgame() 151 | } 152 | 153 | // Flip the sign for black so that blended evaluation score always 154 | // represents the white side. 155 | if e.position.color == Black { 156 | e.score.midgame = -e.score.midgame 157 | e.score.endgame = -e.score.endgame 158 | } 159 | } 160 | 161 | func (e *Evaluation) checkpoint(tag string, metric interface{}) { 162 | e.metrics[tag] = metric 163 | } 164 | 165 | func (e *Evaluation) oppositeBishops() bool { 166 | bishops := e.position.outposts[Bishop] | e.position.outposts[BlackBishop] 167 | 168 | return (bishops & maskDark).any() && (bishops & ^maskDark).any() 169 | } 170 | 171 | // Returns true if material tables indicate that the king can't defend himself. 172 | func (e *Evaluation) isKingUnsafe(color int) bool { 173 | if color == White { 174 | return e.material.flags & whiteKingSafety != 0 175 | } 176 | 177 | return e.material.flags & blackKingSafety != 0 178 | } 179 | -------------------------------------------------------------------------------- /evaluate_pawns.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | type PawnEntry struct { 12 | id uint64 // Pawn hash key. 13 | score Score // Static score for the given pawn structure. 14 | king [2]int // King square for both sides. 15 | cover [2]Score // King cover penalties for both sides. 16 | passers [2]Bitmask // Passed pawn bitmasks for both sides. 17 | } 18 | 19 | type PawnCache [8192*2]PawnEntry 20 | 21 | func (e *Evaluation) analyzePawns() { 22 | key := e.position.pawnId 23 | 24 | // Since pawn hash is fairly small we can use much faster 32-bit index. 25 | index := uint32(key) % uint32(len(game.pawnCache)) 26 | e.pawns = &game.pawnCache[index] 27 | 28 | // Bypass pawns cache if evaluation tracing is enabled. 29 | if e.pawns.id != key || engine.trace { 30 | white, black := e.pawnStructure(White), e.pawnStructure(Black) 31 | e.pawns.score.clear().add(white).sub(black).apply(weightPawnStructure) 32 | e.pawns.id = key 33 | 34 | // Force full king shelter evaluation since any legit king square 35 | // will be viewed as if the king has moved. 36 | e.pawns.king[White], e.pawns.king[Black] = 0xFF, 0xFF 37 | 38 | if engine.trace { 39 | e.checkpoint(`Pawns`, Total{white, black}) 40 | } 41 | } 42 | 43 | e.score.add(e.pawns.score) 44 | } 45 | 46 | func (e *Evaluation) analyzePassers() { 47 | var white, black, score Score 48 | 49 | if engine.trace { 50 | defer func() { 51 | e.checkpoint(`Passers`, Total{white, black}) 52 | }() 53 | } 54 | 55 | white, black = e.pawnPassers(White), e.pawnPassers(Black) 56 | score.add(white).sub(black).apply(weightPassedPawns) 57 | e.score.add(score) 58 | } 59 | 60 | // Calculates extra bonus and penalty based on pawn structure. Specifically, 61 | // a bonus is awarded for passed pawns, and penalty applied for isolated and 62 | // doubled pawns. 63 | func (e *Evaluation) pawnStructure(our int) (score Score) { 64 | their := our^1 65 | ourPawns := e.position.outposts[pawn(our)] 66 | theirPawns := e.position.outposts[pawn(their)] 67 | e.pawns.passers[our] = 0 68 | 69 | for bm := ourPawns; bm.any(); bm = bm.pop() { 70 | square := bm.first() 71 | row, col := coordinate(square) 72 | 73 | isolated := (maskIsolated[col] & ourPawns).empty() 74 | exposed := (maskInFront[our][square] & theirPawns).empty() 75 | doubled := (maskInFront[our][square] & ourPawns).any() 76 | supported := (maskIsolated[col] & (maskRank[row] | maskRank[row].up(their)) & ourPawns).any() 77 | 78 | // The pawn is passed if a) there are no enemy pawns in the same 79 | // and adjacent columns; and b) there are no same our pawns in 80 | // front of us. 81 | passed := !doubled && (maskPassed[our][square] & theirPawns).empty() 82 | if passed { 83 | e.pawns.passers[our] |= bit[square] 84 | } 85 | 86 | // Penalty if the pawn is isolated, i.e. has no friendly pawns 87 | // on adjacent files. The penalty goes up if isolated pawn is 88 | // exposed on semi-open file. 89 | if isolated { 90 | if !exposed { 91 | score.sub(penaltyIsolatedPawn[col]) 92 | } else { 93 | score.sub(penaltyWeakIsolatedPawn[col]) 94 | } 95 | } else if !supported { 96 | score.sub(pawnAlone) // Small penalty if the pawn is not supported by a fiendly pawn. 97 | } 98 | 99 | // Penalty if the pawn is doubled, i.e. there is another friendly 100 | // pawn in front of us. 101 | if doubled { 102 | score.sub(penaltyDoubledPawn[col]) 103 | } 104 | 105 | // Penalty if the pawn is backward. 106 | backward := false 107 | if (!passed && !supported && !isolated) { 108 | 109 | // Backward pawn should not be attacking enemy pawns. 110 | if (pawnAttacks[our][square] & theirPawns).empty() { 111 | 112 | // Backward pawn should not have friendly pawns behind. 113 | if (maskPassed[their][square] & maskIsolated[col] & ourPawns).empty() { 114 | 115 | // Backward pawn should face enemy pawns on the next two ranks 116 | // preventing its advance. 117 | enemy := pawnAttacks[our][square].up(our) 118 | if ((enemy | enemy.up(our)) & theirPawns).any() { 119 | backward = true 120 | if !exposed { 121 | score.sub(penaltyBackwardPawn[col]) 122 | } else { 123 | score.sub(penaltyWeakBackwardPawn[col]) 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | // Bonus if the pawn has good chance to become a passed pawn. 131 | if exposed && !isolated && !passed && !backward { 132 | his := maskPassed[their][square + up[our]] & maskIsolated[col] & ourPawns 133 | her := maskPassed[our][square] & maskIsolated[col] & theirPawns 134 | if his.count() >= her.count() { 135 | score.add(bonusSemiPassedPawn[rank(our, square)]) 136 | } 137 | } 138 | } 139 | 140 | return score 141 | } 142 | 143 | func (e *Evaluation) pawnPassers(our int) (score Score) { 144 | p, their := e.position, our^1 145 | 146 | // If opposing side has no pieces other than pawns then need to check if passers are unstoppable. 147 | chase := (p.outposts[their] ^ p.outposts[pawn(their)] ^ p.outposts[king(their)]).empty() 148 | 149 | for bm := e.pawns.passers[our]; bm.any(); bm = bm.pop() { 150 | square := bm.first() 151 | rank := rank(our, square) 152 | bonus := bonusPassedPawn[rank] 153 | 154 | if rank > A2H2 { 155 | extra := extraPassedPawn[rank] 156 | nextSquare := square + up[our] 157 | 158 | // Adjust endgame bonus based on how close the kings are from the 159 | // step forward square. 160 | bonus.endgame += (distance[p.king[their]][nextSquare] * 5 - distance[p.king[our]][nextSquare] * 2) * extra 161 | 162 | // Check if the pawn can step forward. 163 | if p.board.off(nextSquare) { 164 | boost := 0 165 | 166 | // Assume all squares in front of the pawn are under attack. 167 | attacked := maskInFront[our][square] 168 | defended := attacked & e.attacks[our] 169 | 170 | // Boost the bonus if squares in front of the pawn are defended. 171 | if defended == attacked { 172 | boost += 6 // All squares. 173 | } else if defended.on(nextSquare) { 174 | boost += 4 // Next square only. 175 | } 176 | 177 | // Check who is attacking the squares in front of the pawn including 178 | // queen and rook x-ray attacks from behind. 179 | enemy := maskInFront[their][square] & (p.outposts[queen(their)] | p.outposts[rook(their)]) 180 | if enemy.empty() || (enemy & p.rookMoves(square)).empty() { 181 | 182 | // Since nobody attacks the pawn from behind adjust the attacked 183 | // bitmask to only include squares attacked or occupied by the enemy. 184 | attacked &= (e.attacks[their] | p.outposts[their]) 185 | } 186 | 187 | // Boost the bonus if passed pawn is free to advance to the 8th rank 188 | // or at least safely step forward. 189 | if attacked.empty() { 190 | boost += 15 // Remaining squares are not under attack. 191 | } else if attacked.off(nextSquare) { 192 | boost += 9 // Next square is not under attack. 193 | } 194 | 195 | if boost > 0 { 196 | bonus.adjust(extra * boost) 197 | } 198 | } 199 | } 200 | 201 | // Before chasing the unstoppable make sure own pieces are not blocking the passer. 202 | if chase && (p.outposts[our] & maskInFront[our][square]).empty() { 203 | // Pick square rule bitmask for the pawn. If defending king has the right 204 | // to move then pick extended square mask. 205 | bits := Bitmask(0) 206 | if p.color == our { 207 | bits = maskSquare[our][square] 208 | } else { 209 | bits = maskSquareEx[our][square] 210 | } 211 | if (bits & p.outposts[king(their)]).empty() { 212 | bonus.endgame += unstoppablePawn 213 | } 214 | } 215 | 216 | score.add(bonus) 217 | } 218 | 219 | return score 220 | } 221 | 222 | -------------------------------------------------------------------------------- /evaluate_pawns_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | // Doubled pawns. 14 | func TestEvaluatePawns100(t *testing.T) { 15 | p := NewGame(`Ke1,h2,h3`, `Kd8,a7,a6`).start() 16 | score := p.Evaluate() 17 | expect.Eq(t, score, rightToMove.endgame) // Right to move only. 18 | } 19 | 20 | func TestEvaluatePawns110(t *testing.T) { 21 | game := NewGame(`Ke1,h2,h3`, `Ke8,a7,h7`) 22 | score := game.start().Evaluate() 23 | 24 | expect.Eq(t, score, -10) 25 | } 26 | 27 | func TestEvaluatePawns120(t *testing.T) { 28 | game := NewGame(`Ke1,f4,f5`, `Ke8,f7,h7`) 29 | score := game.start().Evaluate() 30 | 31 | expect.Eq(t, score, -31) 32 | } 33 | 34 | // Passed pawns. 35 | func TestEvaluatePawns200(t *testing.T) { 36 | game := NewGame(`Ke1,a4,h4`, `Ke8,a5,h5`) // Blocked. 37 | score := game.start().Evaluate() 38 | 39 | expect.Eq(t, score, 10) 40 | } 41 | 42 | func TestEvaluatePawns210(t *testing.T) { 43 | game := NewGame(`Ke1,a4,h4`, `Ke8,a5,g7`) // Can't pass. 44 | score := game.start().Evaluate() 45 | 46 | expect.Eq(t, score, 8) 47 | } 48 | 49 | func TestEvaluatePawns220(t *testing.T) { 50 | game := NewGame(`Ke1,a4,e4`, `Ke8,a5,d6`) // Can't pass. 51 | score := game.start().Evaluate() 52 | 53 | expect.Eq(t, score, -2) 54 | } 55 | 56 | func TestEvaluatePawns230(t *testing.T) { 57 | game := NewGame(`Ke1,a4,e5`, `Ke8,a5,e4`) // Both passing. 58 | score := game.start().Evaluate() 59 | 60 | expect.Eq(t, score, 10) 61 | } 62 | 63 | func TestEvaluatePawns240(t *testing.T) { 64 | game := NewGame(`Kd1,e5`, `Ke8,d5`) // Both passing but white is closer. 65 | score := game.start().Evaluate() // No KPKP since the pawn crossed A4H4. 66 | 67 | expect.Eq(t, score, 32) 68 | } 69 | 70 | func TestEvaluatePawns250(t *testing.T) { 71 | game := NewGame(`Ke1,a5,b2`, `Kd8,g7,h7`) // Both passing but white is much closer. 72 | score := game.start().Evaluate() 73 | 74 | expect.Eq(t, score, 102) 75 | } 76 | 77 | // Isolated pawns. 78 | func TestEvaluatePawns300(t *testing.T) { 79 | game := NewGame(`Ke1,a5,c5`, `Kd8,f4,h4`) // All pawns are isolated. 80 | score := game.start().Evaluate() 81 | 82 | expect.Eq(t, score, 10) 83 | } 84 | 85 | func TestEvaluatePawns310(t *testing.T) { 86 | game := NewGame(`Ke1,a2,c2,e2`, `Ke8,a7,b7,c7`) // White pawns are isolated. 87 | score := game.start().Evaluate() 88 | 89 | expect.Eq(t, score, -28) 90 | } 91 | 92 | // Rooks. 93 | func TestEvaluatePawns400(t *testing.T) { 94 | game := NewGame(`Ke1,Ra7`, `Ke8,Rh3`) // White on 7th. 95 | score := game.start().Evaluate() 96 | 97 | expect.Eq(t, score, 5) 98 | } 99 | 100 | func TestEvaluatePawns410(t *testing.T) { 101 | game := NewGame(`Ke1,Rb1,Ng2,a2`, `Ke8,Rh8,Nb7,h7`) // White on open file. 102 | score := game.start().Evaluate() 103 | 104 | expect.Eq(t, score, 81) 105 | } 106 | 107 | func TestEvaluatePawns420(t *testing.T) { 108 | game := NewGame(`Ke1,Rb1,a2,g2`, `Ke8,Rh8,h7,b7`) // White on semi-open file. 109 | score := game.start().Evaluate() 110 | 111 | expect.Eq(t, score, 108) 112 | } 113 | 114 | // King shield. 115 | func TestEvaluatePawns500(t *testing.T) { 116 | game := NewGame(`Kg1,f2,g2,h2,Qa3,Na4`, `Kg8,f7,g7,h7,Qa6,Na5`) // h2,g2,h2 == f7,g7,h7 117 | score := game.start().Evaluate() 118 | 119 | expect.Eq(t, score, 10) 120 | } 121 | func TestEvaluatePawns505(t *testing.T) { 122 | game := NewGame(`Kg1,f2,g2,h2,Qa3,Na4`, `Kg8,f7,g6,h7,Qa6,Na5`) // h2,g2,h2 vs f7,G6,h7 123 | score := game.start().Evaluate() 124 | 125 | expect.Eq(t, score, 30) 126 | } 127 | 128 | func TestEvaluatePawns510(t *testing.T) { 129 | game := NewGame(`Kg1,f2,g2,h2,Qa3,Na4`, `Kg8,f5,g6,h7,Qa6,Na5`) // h2,g2,h2 vs F5,G6,h7 130 | score := game.start().Evaluate() 131 | 132 | expect.Eq(t, score, 35) 133 | } 134 | 135 | func TestEvaluatePawns520(t *testing.T) { 136 | game := NewGame(`Kg1,f2,g2,h2,Qa3,Na4`, `Kg8,a7,f7,g7,Qa6,Na5`) // h2,g2,h2 vs A7,f7,g7 137 | score := game.start().Evaluate() 138 | 139 | expect.Eq(t, score, 45) 140 | } 141 | 142 | func TestEvaluatePawns530(t *testing.T) { 143 | game := NewGame(`Kb1,a3,b2,c2,Qh3,Nh4`, `Kb8,a7,b7,c7,Qh6,Nh5`) // A3,b2,c2 vs a7,b7,c7 144 | score := game.start().Evaluate() 145 | 146 | expect.Eq(t, score, 0) 147 | } 148 | 149 | func TestEvaluatePawns540(t *testing.T) { 150 | game := NewGame(`Kb1,a3,b4,c2,Qh3,Nh4`, `Kb8,a7,b7,c7,Qh6,Nh5`) // A3,B4,c2 vs a7,b7,c7 151 | score := game.start().Evaluate() 152 | 153 | expect.Eq(t, score, -25) 154 | } 155 | 156 | func TestEvaluatePawns550(t *testing.T) { 157 | game := NewGame(`Kb1,b2,c2,h2,Qh3,Nh4`, `Kb8,a7,b7,c7,Qh6,Nh5`) // b2,c2,H2 vs a7,b7,c7 158 | score := game.start().Evaluate() 159 | 160 | expect.Eq(t, score, -25) 161 | } 162 | 163 | func TestEvaluatePawns560(t *testing.T) { 164 | game := NewGame(`Ka1,a3,b2,Qc1,Nd2`, `Kh8,g7,h6,Qf8,Ne7`) // a3,b2 == g7,h6 165 | score := game.start().Evaluate() 166 | 167 | expect.Eq(t, score, 10) 168 | } 169 | 170 | func TestEvaluatePawns570(t *testing.T) { 171 | game := NewGame(`Kb1,a2,c2,f2,g2,h2`, `Kg8,a7,c7,f7,g7,h7`) // B2 hole but not enough power to bother. 172 | score := game.start().Evaluate() 173 | 174 | expect.Eq(t, score, 10) 175 | } 176 | 177 | // Unstoppable passers. 178 | func TestEvaluatePawns600(t *testing.T) { 179 | game := NewGame(`Kd4,f2,g2,h2`, `Kg8,g7,h7,a3`) // Kd4-c3 stops A3 pawn. 180 | score := game.start().Evaluate() 181 | 182 | expect.Eq(t, score, -90) 183 | } 184 | 185 | func TestEvaluatePawns610(t *testing.T) { 186 | game := NewGame(`Kd4,f2,g2,h2`, `M99,Kg8,g7,h7,a3`) // a3-a2 makes the pawn unstoppable. 187 | score := game.start().Evaluate() 188 | 189 | expect.Eq(t, score, 1190) 190 | } 191 | 192 | func TestEvaluatePawns620(t *testing.T) { 193 | game := NewGame(`Ka1,b4,g2`, `Kg8,g7,h7`) // b4-b5 is unstoppable. 194 | score := game.start().Evaluate() 195 | 196 | expect.Eq(t, score, 1063) 197 | } 198 | 199 | func TestEvaluatePawns630(t *testing.T) { 200 | game := NewGame(`Ka1,b4,h2`, `M99,Kg8,g7,h7`) // Kg8-f8 stops B4 pawn. 201 | score := game.start().Evaluate() 202 | 203 | expect.Eq(t, score, 31) 204 | } 205 | -------------------------------------------------------------------------------- /evaluate_safety.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | func (e *Evaluation) analyzeSafety() { 12 | var score Score 13 | var cover, safety Total 14 | 15 | if engine.trace { 16 | defer func() { 17 | var our, their Score 18 | e.checkpoint(`+King`, Total{*our.add(cover.white).add(safety.white), *their.add(cover.black).add(safety.black)}) 19 | e.checkpoint(`-Cover`, cover) 20 | e.checkpoint(`-Safety`, safety) 21 | }() 22 | } 23 | 24 | // If any of the pawns or a king have moved then recalculate cover score. 25 | if e.position.king[White] != e.pawns.king[White] { 26 | e.pawns.cover[White] = e.kingCover(White) 27 | e.pawns.king[White] = e.position.king[White] 28 | } 29 | if e.position.king[Black] != e.pawns.king[Black] { 30 | e.pawns.cover[Black] = e.kingCover(Black) 31 | e.pawns.king[Black] = e.position.king[Black] 32 | } 33 | 34 | // Fetch king cover score from the pawn cache. 35 | cover.white.add(e.pawns.cover[White]) 36 | cover.black.add(e.pawns.cover[Black]) 37 | 38 | // Calculate king's safety for both sides. 39 | if e.safety[White].threats > 0 { 40 | safety.white = e.kingSafety(White) 41 | } 42 | if e.safety[Black].threats > 0 { 43 | safety.black = e.kingSafety(Black) 44 | } 45 | 46 | // Calculate total king safety and pawn cover score. 47 | score.add(safety.white).sub(safety.black).apply(weightSafety) 48 | score.add(cover.white).sub(cover.black) 49 | e.score.add(score) 50 | } 51 | 52 | func (e *Evaluation) kingSafety(our int) (score Score) { 53 | p, their := e.position, our^1 54 | safetyIndex, checkers, square := 0, 0, p.king[our] 55 | 56 | // Find squares around the king that are being attacked by the 57 | // enemy and defended by our king only. 58 | defended := e.attacks[pawn(our)] | e.attacks[knight(our)] | 59 | e.attacks[bishop(our)] | e.attacks[rook(our)] | 60 | e.attacks[queen(our)] 61 | weak := e.attacks[king(our)] & e.attacks[their] & ^defended 62 | 63 | // Find possible queen checks on weak squares around the king. 64 | // We only consider squares where the queen is protected and 65 | // can't be captured by the king. 66 | protected := e.attacks[pawn(their)] | e.attacks[knight(their)] | 67 | e.attacks[bishop(their)] | e.attacks[rook(their)] | 68 | e.attacks[king(their)] 69 | checks := weak & e.attacks[queen(their)] & protected & ^p.outposts[their] 70 | if checks.any() { 71 | checkers++ 72 | safetyIndex += queenCheck * checks.count() 73 | } 74 | 75 | // Out of all squares available for enemy pieces select the ones 76 | // that are not under our attack. 77 | safe := ^(e.attacks[our] | p.outposts[their]) 78 | 79 | // Are there any safe squares from where enemy Knight could give 80 | // us a check? 81 | if checks := knightMoves[square] & safe & e.attacks[knight(their)]; checks.any() { 82 | checkers++ 83 | safetyIndex += checks.count() 84 | } 85 | 86 | // Are there any safe squares from where enemy Bishop could give us a check? 87 | safeBishopMoves := p.bishopMoves(square) & safe 88 | if checks := safeBishopMoves & e.attacks[bishop(their)]; checks.any() { 89 | checkers++ 90 | safetyIndex += checks.count() 91 | } 92 | 93 | // Are there any safe squares from where enemy Rook could give us a check? 94 | safeRookMoves := p.rookMoves(square) & safe 95 | if checks := safeRookMoves & e.attacks[rook(their)]; checks.any() { 96 | checkers++ 97 | safetyIndex += checks.count() 98 | } 99 | 100 | // Are there any safe squares from where enemy Queen could give us a check? 101 | if checks := (safeBishopMoves | safeRookMoves) & e.attacks[queen(their)]; checks.any() { 102 | checkers++ 103 | safetyIndex += queenCheck / 2 * checks.count() 104 | } 105 | 106 | threatIndex := min(16, e.safety[our].attackers * e.safety[our].threats / 2) + 107 | (e.safety[our].attacks + weak.count()) * 3 + 108 | rank(our, square) - e.pawns.cover[our].midgame / 16 109 | safetyIndex = min(63, max(0, safetyIndex + threatIndex)) 110 | 111 | score.midgame -= kingSafety[safetyIndex] 112 | 113 | if checkers > 0 { 114 | score.add(rightToMove) 115 | if checkers > 1 { 116 | score.add(rightToMove) 117 | } 118 | } 119 | 120 | return score 121 | } 122 | 123 | func (e *Evaluation) kingCover(our int) (score Score) { 124 | p, square := e.position, e.position.king[our] 125 | 126 | // Don't bother with the cover if the king is too far out. 127 | if rank(our, square) <= A3H3 { 128 | // If we still have castle rights encourage castle pawns to stay intact 129 | // by scoring least safe castle. 130 | score.midgame = e.kingCoverBonus(our, square) 131 | if p.castles & castleKingside[our] != 0 { 132 | score.midgame = max(score.midgame, e.kingCoverBonus(our, homeKing[our] + 2)) 133 | } 134 | if p.castles & castleQueenside[our] != 0 { 135 | score.midgame = max(score.midgame, e.kingCoverBonus(our, homeKing[our] - 2)) 136 | } 137 | } 138 | 139 | score.endgame = e.kingPawnProximity(our, square) 140 | 141 | return score 142 | } 143 | 144 | func (e *Evaluation) kingCoverBonus(our int, square int) (bonus int) { 145 | bonus = onePawn + onePawn / 3 146 | 147 | // Get pawns adjacent to and in front of the king. 148 | row, col := coordinate(square) 149 | area := maskRank[row] | maskPassed[our][square] 150 | cover := e.position.outposts[pawn(our)] & area 151 | storm := e.position.outposts[pawn(our^1)] & area 152 | 153 | // For each of the cover files find the closest friendly pawn. The penalty 154 | // is carried if the pawn is missing or is too far from the king. 155 | from, to := max(B1, col) - 1, min(G1, col) + 1 156 | for c := from; c <= to; c++ { 157 | 158 | // Friendly pawns protecting the kings. 159 | closest := 0 160 | if pawns := (cover & maskFile[c]); pawns.any() { 161 | closest = rank(our, pawns.closest(our)) 162 | } 163 | bonus -= penaltyCover[closest] 164 | 165 | // Enemy pawns facing the king. 166 | if pawns := (storm & maskFile[c]); pawns.any() { 167 | farthest := rank(our, pawns.farthest(our^1)) 168 | if closest == 0 { // No opposing friendly pawn. 169 | bonus -= penaltyStorm[farthest] 170 | } else if farthest == closest + 1 { 171 | bonus -= penaltyStormBlocked[farthest] 172 | } else { 173 | bonus -= penaltyStormUnblocked[farthest] 174 | } 175 | } 176 | } 177 | 178 | return bonus 179 | } 180 | 181 | // Calculates endgame penalty to encourage a king stay closer to friendly pawns. 182 | func (e *Evaluation) kingPawnProximity(our int, square int) (penalty int) { 183 | pawns := e.position.outposts[pawn(our)] 184 | 185 | if pawns.any() && (pawns & e.attacks[king(our)]).empty() { 186 | proximity := 8 187 | 188 | for bm := pawns; bm.any(); bm = bm.pop() { 189 | proximity = min(proximity, distance[square][bm.first()]) 190 | } 191 | 192 | penalty = -kingByPawn.endgame * (proximity - 1) 193 | } 194 | 195 | return penalty 196 | } 197 | 198 | -------------------------------------------------------------------------------- /evaluate_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | // Initial position. 14 | func TestEvaluate000(t *testing.T) { 15 | p := NewGame().start() 16 | score := p.Evaluate() 17 | expect.Eq(t, score, rightToMove.midgame) // Right to move only. 18 | } 19 | 20 | // After 1. e2-e4 21 | func TestEvaluate010(t *testing.T) { 22 | p := NewGame(`Ra1,Nb1,Bc1,Qd1,Ke1,Bf1,Ng1,Rh1,a2,b2,c2,d2,e4,f2,g2,h2`, 23 | `M1,Ra8,Nb8,Bc8,Qd8,Ke8,Bf8,Ng8,Rh8,a7,b7,c7,d7,e7,f7,g7,h7`).start() 24 | score := p.Evaluate() 25 | expect.Eq(t, score, -91) // +91 for white. 26 | } 27 | 28 | // After 1. e2-e4 e7-e5 29 | func TestEvaluate020(t *testing.T) { 30 | p := NewGame(`Ra1,Nb1,Bc1,Qd1,Ke1,Bf1,Ng1,Rh1,a2,b2,c2,d2,e4,f2,g2,h2`, 31 | `Ra8,Nb8,Bc8,Qd8,Ke8,Bf8,Ng8,Rh8,a7,b7,c7,d7,e5,f7,g7,h7`).start() 32 | score := p.Evaluate() 33 | expect.Eq(t, score, rightToMove.midgame) // Right to move only. 34 | } 35 | 36 | // After 1. e2-e4 e7-e5 2. Ng1-f3 37 | func TestEvaluate030(t *testing.T) { 38 | p := NewGame(`Ra1,Nb1,Bc1,Qd1,Ke1,Bf1,Nf3,Rh1,a2,b2,c2,d2,e4,f2,g2,h2`, 39 | `M2,Ra8,Nb8,Bc8,Qd8,Ke8,Bf8,Ng8,Rh8,a7,b7,c7,d7,e5,f7,g7,h7`).start() 40 | score := p.Evaluate() 41 | expect.Eq(t, score, -95) 42 | } 43 | 44 | // After 1. e2-e4 e7-e5 2. Ng1-f3 Ng8-f6 45 | func TestEvaluate040(t *testing.T) { 46 | p := NewGame(`Ra1,Nb1,Bc1,Qd1,Ke1,Bf1,Nf3,Rh1,a2,b2,c2,d2,e4,f2,g2,h2`, 47 | `Ra8,Nb8,Bc8,Qd8,Ke8,Bf8,Nf6,Rh8,a7,b7,c7,d7,e5,f7,g7,h7`).start() 48 | score := p.Evaluate() 49 | expect.Eq(t, score, 10) 50 | } 51 | 52 | // After 1. e2-e4 e7-e5 2. Ng1-f3 Nb8-c6 53 | func TestEvaluate050(t *testing.T) { 54 | p := NewGame(`Ra1,Nb1,Bc1,Qd1,Ke1,Bf1,Nf3,Rh1,a2,b2,c2,d2,e4,f2,g2,h2`, 55 | `Ra8,Nc6,Bc8,Qd8,Ke8,Bf8,Ng8,Rh8,a7,b7,c7,d7,e5,f7,g7,h7`).start() 56 | score := p.Evaluate() 57 | expect.Eq(t, score, 3) 58 | } 59 | 60 | // After 1. e2-e4 e7-e5 2. Ng1-f3 Nb8-c6 3. Nb1-c3 Ng8-f6 61 | func TestEvaluate060(t *testing.T) { 62 | p := NewGame(`Ra1,Nc3,Bc1,Qd1,Ke1,Bf1,Nf3,Rh1,a2,b2,c2,d2,e4,f2,g2,h2`, 63 | `Ra8,Nc6,Bc8,Qd8,Ke8,Bf8,Nf6,Rh8,a7,b7,c7,d7,e5,f7,g7,h7`).start() 64 | score := p.Evaluate() 65 | expect.Eq(t, score, rightToMove.midgame) // Right to move only. 66 | } 67 | 68 | // Opposite-colored bishops. 69 | func TestEvaluate070(t *testing.T) { 70 | p := NewGame(`Ke1,Bc1`, `Ke8,Bc8`).start() 71 | eval.init(p) 72 | expect.True(t, eval.oppositeBishops()) 73 | } 74 | 75 | func TestEvaluate071(t *testing.T) { 76 | p := NewGame(`Kc4,Bd4`, `Ke8,Bd5`).start() 77 | eval.init(p) 78 | expect.True(t, eval.oppositeBishops()) 79 | } 80 | 81 | func TestEvaluate072(t *testing.T) { 82 | p := NewGame(`Kc4,Bd4`, `Ke8,Be5`).start() 83 | eval.init(p) 84 | expect.False(t, eval.oppositeBishops()) 85 | } 86 | 87 | func TestEvaluate073(t *testing.T) { 88 | p := NewGame(`Ke1,Bc1`, `Ke8,Bf8`).start() 89 | eval.init(p) 90 | expect.False(t, eval.oppositeBishops()) 91 | } 92 | -------------------------------------------------------------------------------- /evaluate_threats.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | func (e *Evaluation) analyzeThreats() { 12 | var score Score 13 | var threats, center Total 14 | 15 | if engine.trace { 16 | defer func() { 17 | e.checkpoint(`Threats`, threats) 18 | e.checkpoint(`Center`, center) 19 | }() 20 | } 21 | 22 | threats.white = e.threats(White) 23 | threats.black = e.threats(Black) 24 | score.add(threats.white).sub(threats.black).apply(weightThreats) 25 | e.score.add(score) 26 | 27 | if e.material.turf != 0 && e.material.flags & (whiteKingSafety | blackKingSafety) != 0 { 28 | center.white = e.center(White) 29 | center.black = e.center(Black) 30 | score.clear().add(center.white).sub(center.black).apply(weightCenter) 31 | e.score.add(score) 32 | } 33 | } 34 | 35 | func (e *Evaluation) threats(our int) (score Score) { 36 | p, their := e.position, our^1 37 | 38 | // Get our protected and non-hanging pawns. 39 | pawns := p.outposts[pawn(our)] & (e.attacks[our] | ^e.attacks[their]) 40 | 41 | // Find enemy pieces attacked by our protected/non-hanging pawns. 42 | pieces := p.outposts[their] ^ p.outposts[king(their)] // All pieces except king. 43 | majors := pieces ^ p.outposts[pawn(their)] // All pieces except king and pawns. 44 | 45 | // Bonus for each enemy piece attacked by our pawn. 46 | for bm := majors & p.pawnTargets(our, pawns); bm.any(); bm = bm.pop() { 47 | piece := p.pieces[bm.first()] 48 | score.add(bonusPawnThreat[piece.id()]) 49 | } 50 | 51 | // Find enemy pieces that might be our likely targets: major pieces 52 | // attacked by our pawns and all attacked pieces not defended by pawns. 53 | defended := majors & e.attacks[pawn(their)] 54 | undefended := pieces & ^e.attacks[pawn(their)] & e.attacks[our] 55 | 56 | if likely := defended | undefended; likely.any() { 57 | // Bonus for enemy pieces attacked by knights and bishops. 58 | for bm := likely & (e.attacks[knight(our)] | e.attacks[bishop(our)]); bm.any(); bm = bm.pop() { 59 | piece := p.pieces[bm.first()] 60 | score.add(bonusMinorThreat[piece.id()]) 61 | } 62 | 63 | // Bonus for enemy pieces attacked by rooks. 64 | for bm := (undefended | p.outposts[queen(their)]) & e.attacks[rook(our)]; bm.any(); bm = bm.pop() { 65 | piece := p.pieces[bm.first()] 66 | score.add(bonusRookThreat[piece.id()]) 67 | } 68 | 69 | // Bonus for enemy pieces attacked by the king. 70 | if bm := undefended & e.attacks[king(our)]; bm.any() { 71 | if count := bm.count(); count > 0 { 72 | score.add(kingAttack) 73 | if count > 1 { 74 | score.add(kingAttack) 75 | } 76 | } 77 | } 78 | 79 | // Extra bonus when attacking enemy pieces that are hanging. 80 | if bm := undefended & ^e.attacks[their]; bm.any() { 81 | if count := bm.count(); count > 0 { 82 | score.add(hangingAttack.times(count)) 83 | } 84 | } 85 | } 86 | 87 | return score 88 | } 89 | 90 | func (e *Evaluation) center(our int) (score Score) { 91 | p, their := e.position, our^1 92 | 93 | turf := p.outposts[pawn(our)] 94 | safe := homeTurf[our] & ^turf & ^e.attacks[pawn(their)] & (e.attacks[our] | ^e.attacks[their]) 95 | 96 | if our == White { 97 | turf |= turf >> 8 // A4..H4 -> A3..H3 98 | turf |= turf >> 16 // A4..H4 | A3..H3 -> A2..H2 | A1..H1 99 | turf &= safe // Keep safe squares only. 100 | safe <<= 32 // Move up to black's half of the board. 101 | } else { 102 | turf |= turf << 8 // A5..H5 -> A6..H6 103 | turf |= turf << 16 // A5..H5 | A6..H6 -> A7..H7 | A8..H8 104 | turf &= safe // Keep safe squares only. 105 | safe >>= 32 // Move down to white's half of the board. 106 | } 107 | 108 | score.midgame = (safe | turf).count() * e.material.turf / 2 109 | 110 | return score 111 | } 112 | -------------------------------------------------------------------------------- /expect/expect.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package expect 10 | 11 | import(`fmt`; `runtime`; `path/filepath`; `strings`; `testing`) 12 | 13 | func Eq(t *testing.T, actual, expected interface{}) { 14 | log(t, actual, expected, equal(actual, expected)) 15 | } 16 | 17 | func Ne(t *testing.T, actual, expected interface{}) { 18 | log(t, actual, expected, !equal(actual, expected)) 19 | } 20 | 21 | func True(t *testing.T, actual interface{}) { 22 | log(t, actual, true, equal(actual, true)) 23 | } 24 | 25 | func False(t *testing.T, actual interface{}) { 26 | log(t, actual, false, equal(actual, false)) 27 | } 28 | 29 | func Contain(t *testing.T, actual interface{}, expected string) { 30 | match(t, fmt.Sprintf(`%+v`, actual), expected, true) 31 | } 32 | 33 | func NotContain(t *testing.T, actual interface{}, expected string) { 34 | match(t, fmt.Sprintf(`%+v`, actual), expected, false) 35 | } 36 | 37 | func equal(actual, expected interface{}) (passed bool) { 38 | switch expected.(type) { 39 | case bool: 40 | if assertion, ok := actual.(bool); ok { 41 | passed = (assertion == expected) 42 | } 43 | case int: 44 | if assertion, ok := actual.(int); ok { 45 | passed = (assertion == expected) 46 | } 47 | case uint64: 48 | if assertion, ok := actual.(uint64); ok { 49 | passed = (assertion == expected) 50 | } 51 | default: 52 | passed = (fmt.Sprintf(`%v`, actual) == fmt.Sprintf(`%v`, expected)) 53 | } 54 | return 55 | } 56 | 57 | // Simple success/failure logger that assumes source test file is at Caller(2). 58 | func log(t *testing.T, actual, expected interface{}, passed bool) { 59 | _, file, line, _ := runtime.Caller(2) // Get the calling file path and line number. 60 | file = filepath.Base(file) // Keep file name only. 61 | 62 | // Serious error. 63 | // All shortcuts have disappeared. 64 | // Screen. Mind. Both are blank. 65 | if !passed { 66 | t.Errorf("\r \x1B[31m%s line %d\nExpected: %v\n Actual: %v\x1B[0m", file, line, expected, actual) 67 | } else if (testing.Verbose()) { 68 | t.Logf("\r \x1B[32m%s line %d: %v\x1B[0m", file, line, actual) 69 | } 70 | } 71 | 72 | func match(t *testing.T, actual, expected string, contains bool) { 73 | passed := (contains == strings.Contains(actual, expected)) 74 | 75 | _, file, line, _ := runtime.Caller(2) 76 | file = filepath.Base(file) 77 | 78 | // Yesterday it worked. 79 | // Today it is not working. 80 | // Windows is like that. 81 | if !passed { 82 | t.Errorf("\r \x1B[31m%s line %d\nContains: %s\n Actual: %s\x1B[0m", file, line, expected, actual) 83 | } else if (testing.Verbose()) { 84 | t.Logf("\r \x1B[32m%s line %d: %v\x1B[0m", file, line, actual) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /generate.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | type MoveWithScore struct { 12 | move Move 13 | score int 14 | } 15 | 16 | type MoveGen struct { 17 | p *Position 18 | list [128]MoveWithScore 19 | ply int 20 | head int 21 | tail int 22 | pins Bitmask 23 | } 24 | 25 | // Pre-allocate move generator array (one entry per ply) to avoid garbage 26 | // collection overhead. Last entry serves for utility move generation, ex. when 27 | // converting string notations or determining a stalemate. 28 | var moveList [MaxPly+1]MoveGen 29 | 30 | // Returns "new" move generator for the given ply. Since move generator array 31 | // has been pre-allocated already we simply return a pointer to the existing 32 | // array element re-initializing all its data. 33 | func NewGen(p *Position, ply int) (gen *MoveGen) { 34 | gen = &moveList[ply] 35 | gen.p = p 36 | gen.list = [128]MoveWithScore{} 37 | gen.ply = ply 38 | gen.head, gen.tail = 0, 0 39 | gen.pins = p.pins(p.king[p.color]) 40 | 41 | return gen 42 | } 43 | 44 | // Convenience method to return move generator for the current ply. 45 | func NewMoveGen(p *Position) *MoveGen { 46 | return NewGen(p, ply()) 47 | } 48 | 49 | // Returns new move generator for the initial step of iterative deepening 50 | // (depth == 1) and existing one for subsequent iterations (depth > 1). 51 | func NewRootGen(p *Position, depth int) *MoveGen { 52 | if depth == 1 { 53 | return NewGen(p, 0) // Zero ply. 54 | } 55 | 56 | return &moveList[0] 57 | } 58 | 59 | func (gen *MoveGen) reset() *MoveGen { 60 | gen.head = 0 61 | 62 | return gen 63 | } 64 | 65 | func (gen *MoveGen) size() int { 66 | return gen.tail 67 | } 68 | 69 | func (gen *MoveGen) onlyMove() bool { 70 | return gen.tail == 1 71 | } 72 | 73 | func (gen *MoveGen) nextMove() (move Move) { 74 | move = gen.list[gen.head].move 75 | gen.head++ 76 | 77 | return move 78 | } 79 | 80 | // Removes invalid moves from the generated list. We use in iterative deepening 81 | // to avoid filtering out invalid moves on each iteration. 82 | func (gen *MoveGen) validOnly() *MoveGen { 83 | for move := gen.nextMove(); move.some(); move = gen.nextMove() { 84 | if !move.valid(gen.p, gen.pins) { 85 | gen.remove() 86 | } 87 | } 88 | 89 | return gen.reset() 90 | } 91 | 92 | // Probes a list of generated moves and returns true if it contains at least 93 | // one valid move. 94 | func (gen *MoveGen) anyValid() bool { 95 | for move := gen.nextMove(); move.some(); move = gen.nextMove() { 96 | if move.valid(gen.p, gen.pins) { 97 | return true 98 | } 99 | } 100 | 101 | return false 102 | } 103 | 104 | // Probes valid-only list of generated moves and returns true if the given move 105 | // is one of them. 106 | func (gen *MoveGen) amongValid(someMove Move) bool { 107 | for move := gen.nextMove(); move.some(); move = gen.nextMove() { 108 | if someMove == move { 109 | return true 110 | } 111 | } 112 | 113 | return false 114 | } 115 | 116 | // Assigns given score to the last move returned by the gen.nextMove(). 117 | func (gen *MoveGen) scoreMove(depth, score int) *MoveGen { 118 | current := &gen.list[gen.head - 1] 119 | 120 | if depth == 1 || current.score == score + 1 { 121 | current.score = score 122 | } else if score != -depth || (score == -depth && current.score != score) { 123 | current.score += score // Fix up aspiration search drop. 124 | } 125 | 126 | return gen 127 | } 128 | 129 | // Shell sort that is somewhat faster that standard Go sort. It also seems 130 | // to outperform: 131 | // loop { 132 | // gen.shuffleRandomly() 133 | // if gen.isSorted() { 134 | // break 135 | // } 136 | // } 137 | func (gen *MoveGen) sort() *MoveGen { 138 | total := gen.tail - gen.head 139 | count := total 140 | pocket := MoveWithScore{} 141 | ever := true 142 | 143 | for (ever) { 144 | count = (count + 1) / 2 145 | ever = count > 1 146 | for i := 0; i < total - count; i++ { 147 | if this := gen.list[i + count]; this.score > gen.list[i].score { 148 | pocket = this 149 | gen.list[i + count] = gen.list[i] 150 | gen.list[i] = pocket 151 | ever = true 152 | } 153 | } 154 | } 155 | 156 | return gen 157 | } 158 | 159 | func (gen *MoveGen) rank(bestMove Move) *MoveGen { 160 | if gen.size() < 2 { 161 | return gen 162 | } 163 | 164 | for i := gen.head; i < gen.tail; i++ { 165 | move := gen.list[i].move 166 | if move == bestMove { 167 | gen.list[i].score = 0xFFFF 168 | } else if !move.isQuiet() || move.isEnpassant() { 169 | gen.list[i].score = 8192 + move.value() 170 | } else if move == game.killers[gen.ply][0] { 171 | gen.list[i].score = 4096 172 | } else if move == game.killers[gen.ply][1] { 173 | gen.list[i].score = 2048 174 | } else { 175 | gen.list[i].score = game.good(move) 176 | } 177 | } 178 | 179 | return gen.sort() 180 | } 181 | 182 | func (gen *MoveGen) quickRank() *MoveGen { 183 | if gen.size() < 2 { 184 | return gen 185 | } 186 | 187 | for i := gen.head; i < gen.tail; i++ { 188 | if move := gen.list[i].move; !move.isQuiet() || move.isEnpassant() { 189 | gen.list[i].score = 8192 + move.value() 190 | } else { 191 | gen.list[i].score = game.good(move) 192 | } 193 | } 194 | 195 | return gen.sort() 196 | } 197 | 198 | func (gen *MoveGen) add(move Move) *MoveGen { 199 | gen.list[gen.tail].move = move 200 | gen.tail++ 201 | 202 | return gen 203 | } 204 | 205 | // Removes current move from the list by copying over the ramaining moves. Head and 206 | // tail pointers get decremented so that calling NexMove() works as expected. 207 | func (gen *MoveGen) remove() *MoveGen { 208 | copy(gen.list[gen.head-1:], gen.list[gen.head:]) 209 | gen.head-- 210 | gen.tail-- 211 | 212 | return gen 213 | } 214 | 215 | // Returns an array of generated moves by continuously appending the nextMove() 216 | // until the list is empty. 217 | func (gen *MoveGen) allMoves() (moves []Move) { 218 | for move := gen.nextMove(); move.some(); move = gen.nextMove() { 219 | moves = append(moves, move) 220 | } 221 | gen.reset() 222 | 223 | return moves 224 | } 225 | -------------------------------------------------------------------------------- /generate_captures.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | func (gen *MoveGen) generateCaptures() *MoveGen { 12 | color := gen.p.color 13 | return gen.pawnCaptures(color).pieceCaptures(color) 14 | } 15 | 16 | // Generates all pseudo-legal pawn captures and promotions. 17 | func (gen *MoveGen) pawnCaptures(color int) *MoveGen { 18 | enemy := gen.p.outposts[color^1] 19 | 20 | for pawns := gen.p.outposts[pawn(color)]; pawns.any(); pawns = pawns.pop() { 21 | square := pawns.first() 22 | 23 | // For pawns on files 2-6 the moves include captures only, 24 | // while for pawns on the 7th file the moves include captures 25 | // as well as queen promotion. 26 | if rank(color, square) != A7H7 { 27 | gen.movePawn(square, gen.p.targets(square) & enemy) 28 | } else { 29 | for bm := gen.p.targets(square); bm.any(); bm = bm.pop() { 30 | target := bm.first() 31 | mQ, _, _, _ := NewPromotion(gen.p, square, target) 32 | gen.add(mQ) 33 | } 34 | } 35 | } 36 | 37 | return gen 38 | } 39 | 40 | // Generates all pseudo-legal captures by pieces other than pawn. 41 | func (gen *MoveGen) pieceCaptures(color int) *MoveGen { 42 | enemy := gen.p.outposts[color^1] 43 | 44 | for bm := gen.p.outposts[color] ^ gen.p.outposts[pawn(color)] ^ gen.p.outposts[king(color)]; bm.any(); bm = bm.pop() { 45 | square := bm.first() 46 | gen.movePiece(square, gen.p.targets(square) & enemy) 47 | } 48 | if gen.p.outposts[king(color)].any() { 49 | square := gen.p.king[color] 50 | gen.moveKing(square, gen.p.targets(square) & enemy) 51 | } 52 | 53 | return gen 54 | } 55 | -------------------------------------------------------------------------------- /generate_captures_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | // Piece captures. 14 | func TestGenCaptures000(t *testing.T) { 15 | p := NewGame(`Ka1,Qd1,Rh1,Bb3,Ne5`, `Ka8,Qd8,Rh8,Be6,Ng6`).start() 16 | white := NewMoveGen(p).generateCaptures() 17 | expect.Eq(t, white.allMoves(), `[Qd1xd8 Rh1xh8 Bb3xe6 Ne5xg6]`) 18 | 19 | p.color = Black 20 | black := NewMoveGen(p).generateCaptures() 21 | expect.Eq(t, black.allMoves(), `[Be6xb3 Ng6xe5 Qd8xd1 Rh8xh1]`) 22 | } 23 | 24 | // Pawn captures on rows 2-6 (no promotions). 25 | func TestGenCaptures010(t *testing.T) { 26 | p := NewGame(`Ka1,b3,c4,d5`, `Ka8,a4,b5,c6,e6`).start() 27 | white := NewMoveGen(p).generateCaptures() 28 | expect.Eq(t, white.allMoves(), `[b3xa4 c4xb5 d5xc6 d5xe6]`) 29 | 30 | p.color = Black 31 | black := NewMoveGen(p).generateCaptures() 32 | expect.Eq(t, black.allMoves(), `[a4xb3 b5xc4 c6xd5 e6xd5]`) 33 | } 34 | 35 | // Pawn captures with promotion, rows 1-7 (queen promotions only when generating captures). 36 | func TestGenCaptures020(t *testing.T) { 37 | p := NewGame(`Ka1,Bh1,Ng1,a2,b7,e7`, `Kb8,Rd8,Be8,Rf8,h2`).start() 38 | white := NewMoveGen(p).generateCaptures() 39 | expect.Eq(t, white.allMoves(), `[e7xd8Q e7xf8Q]`) 40 | 41 | p.color = Black 42 | black := NewMoveGen(p).generateCaptures() 43 | expect.Eq(t, black.allMoves(), `[h2xg1Q Kb8xb7]`) 44 | } 45 | 46 | // Pawn promotions without capture, rows 1-7 (queen promotions only when generating captures). 47 | func TestGenCaptures030(t *testing.T) { 48 | p := NewGame(`Ka1,a2,e7`, `Ka8,Rd8,a7,h2`).start() 49 | white := NewMoveGen(p).generateCaptures() 50 | expect.Eq(t, white.allMoves(), `[e7xd8Q e7-e8Q]`) 51 | 52 | p.color = Black 53 | black := NewMoveGen(p).generateCaptures() 54 | expect.Eq(t, black.allMoves(), `[h2-h1Q]`) 55 | } 56 | 57 | // Captures/promotions sort order. 58 | func TestGenCaptures100(t *testing.T) { 59 | p := NewGame(`Kg1,Qh4,Rg2,Rf1,Nd6,f7`, `Kh8,Qc3,Rd8,Rd7,Ne6,a3,h7`).start() 60 | gen := NewMoveGen(p).generateCaptures() 61 | expect.Eq(t, gen.allMoves(), `[f7-f8Q Qh4xh7 Qh4xd8]`) 62 | } 63 | 64 | func TestGenCaptures110(t *testing.T) { 65 | p := NewGame(`Kg1,Qh4,Rg2,Rf1,Nd6,f7`, `Kh8,Qc3,Rd8,Rd7,Ne6,a3,h7`).start() 66 | gen := NewMoveGen(p).generateCaptures().rank(Move(0)) 67 | expect.Eq(t, gen.allMoves(), `[f7-f8Q Qh4xd8 Qh4xh7]`) 68 | } 69 | 70 | func TestGenCaptures120(t *testing.T) { 71 | p := NewGame(`Kg1,Qh4,Rg2,Rf1,Nd6,f7`, `Kh8,Qc3,Rd8,Rd7,Ne6,a3,h7`).start() 72 | gen := NewMoveGen(p).generateCaptures().quickRank() 73 | expect.Eq(t, gen.allMoves(), `[f7-f8Q Qh4xd8 Qh4xh7]`) 74 | } 75 | 76 | // Move legality. 77 | func TestGenCaptures130(t *testing.T) { 78 | p := NewGame(`Ka1,Rd8,Nb1,f6,h6`, `Kh8,Re1,Ng8,a3,c3`).start() 79 | white := NewMoveGen(p).generateCaptures().quickRank() 80 | expect.Eq(t, white.allMoves(), `[Rd8xg8 Nb1xc3 Nb1xa3]`) // Nb1 is pinned but nevertheless. 81 | 82 | p.color = Black 83 | black := NewMoveGen(p).generateCaptures() 84 | expect.Eq(t, black.allMoves(), `[Re1xb1 Ng8xf6 Ng8xh6]`) // Ng8 is pinned but nevertheless. 85 | } 86 | -------------------------------------------------------------------------------- /generate_checks.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | // Non-capturing checks. 12 | func (gen *MoveGen) generateChecks() *MoveGen { 13 | p := gen.p 14 | color, enemy := p.color, p.color^1 15 | square := p.king[enemy] 16 | r, c := coordinate(square) 17 | prohibit := Bitmask(0) 18 | empty := ^p.board 19 | friendly := ^p.outposts[enemy] 20 | 21 | // Non-capturing Knight checks. 22 | checks := knightMoves[square] 23 | for bm := p.outposts[knight(color)]; bm.any(); bm = bm.pop() { 24 | from := bm.first() 25 | gen.movePiece(from, knightMoves[from] & checks & empty) 26 | } 27 | 28 | // Non-capturing Bishop or Queen checks. 29 | checks = p.bishopAttacksAt(square, enemy) 30 | for bm := p.outposts[bishop(color)] | p.outposts[queen(color)]; bm.any(); bm = bm.pop() { 31 | from := bm.first() 32 | diagonal := (r != row(from) && c != col(from)) 33 | for bm := p.bishopAttacksAt(from, enemy) & checks & friendly; bm.any(); bm = bm.pop() { 34 | to := bm.first() 35 | if piece := p.pieces[to]; piece == 0 { 36 | // Empty square: simply move a bishop to check. 37 | gen.add(NewMove(p, from, to)) 38 | } else if diagonal && piece.color() == color && maskLine[from][square].any() { 39 | // Non-empty square occupied by friendly piece on the same 40 | // diagonal: moving the piece away causes discovered check. 41 | switch piece.kind() { 42 | case Pawn: 43 | // Block pawn promotions (since they are treated as 44 | // captures) and en-passant captures. 45 | prohibit = maskRank[0] | maskRank[7] 46 | if p.enpassant != 0 { 47 | prohibit.set(p.enpassant) 48 | } 49 | gen.movePawn(to, p.targets(to) & empty & ^prohibit) 50 | case King: 51 | // Make sure the king steps out of attack diaginal. 52 | gen.moveKing(to, p.targets(to) & empty & ^maskBlock[from][square]) 53 | default: 54 | gen.movePiece(to, p.targets(to) & empty) 55 | } 56 | } 57 | } 58 | if p.pieces[from].isQueen() { 59 | // Queen could move straight as a rook and check diagonally as a bishop 60 | // or move diagonally as a bishop and check straight as a rook. 61 | targets := (p.rookAttacksAt(from, color) & checks) | 62 | (p.bishopAttacksAt(from, color) & p.rookAttacksAt(square, color)) 63 | gen.movePiece(from, targets & empty) 64 | } 65 | } 66 | 67 | // Non-capturing Rook or Queen checks. 68 | checks = p.rookAttacksAt(square, enemy) 69 | for bm := p.outposts[rook(color)] | p.outposts[queen(color)]; bm.any(); bm = bm.pop() { 70 | from := bm.first() 71 | straight := (r == row(from) || c == col(from)) 72 | for bm := p.rookAttacksAt(from, enemy) & checks & friendly; bm.any(); bm = bm.pop() { 73 | to := bm.first() 74 | if piece := p.pieces[to]; piece == 0 { 75 | // Empty square: simply move a rook to check. 76 | gen.add(NewMove(p, from, to)) 77 | } else if straight && piece.color() == color && maskLine[from][square].any() { 78 | // Non-empty square occupied by friendly piece on the same 79 | // file or rank: moving the piece away causes discovered check. 80 | switch piece.kind() { 81 | case Pawn: 82 | // If pawn and rook share the same file then non-capturing 83 | // discovered check is not possible since the pawn is going 84 | // to stay on the same file no matter what. 85 | if col(from) == col(to) { 86 | continue 87 | } 88 | // Block pawn promotions (since they are treated as captures) 89 | // and en-passant captures. 90 | prohibit = maskRank[0] | maskRank[7] 91 | if p.enpassant != 0 { 92 | prohibit.set(p.enpassant) 93 | } 94 | gen.movePawn(to, p.targets(to) & empty & ^prohibit) 95 | case King: 96 | // Make sure the king steps out of attack file or rank. 97 | if row(from) == r { 98 | prohibit = maskRank[r] 99 | } else { 100 | prohibit = maskFile[c] 101 | } 102 | gen.moveKing(to, p.targets(to) & empty & ^prohibit) 103 | default: 104 | gen.movePiece(to, p.targets(to) & empty) 105 | } 106 | } 107 | } 108 | } 109 | 110 | // Non-capturing Pawn checks. 111 | for bm := p.outposts[pawn(color)] & maskIsolated[c]; bm.any(); bm = bm.pop() { 112 | from := bm.first() 113 | if bm := maskPawn[color][square] & p.targets(from); bm.any() { 114 | gen.add(NewPawnMove(p, from, bm.first())) 115 | } 116 | } 117 | 118 | return gen 119 | } 120 | -------------------------------------------------------------------------------- /generate_checks_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | // Knight. 14 | func TestGenChecks000(t *testing.T) { 15 | p := NewGame(`Ka1,Nd7,Nf3,b3`, `Kh7,Nd4,f6`).start() 16 | white := NewMoveGen(p).generateChecks() 17 | expect.Eq(t, white.allMoves(), `[Nf3-g5 Nd7-f8]`) 18 | 19 | p.color = Black 20 | black := NewMoveGen(p).generateChecks() 21 | expect.Eq(t, black.allMoves(), `[Nd4-c2]`) 22 | } 23 | 24 | // Bishop. 25 | func TestGenChecks010(t *testing.T) { 26 | p := NewGame(`Kh2,Ba2`, `Kh7,Ba7`).start() 27 | white := NewMoveGen(p).generateChecks() 28 | expect.Eq(t, white.allMoves(), `[Ba2-b1 Ba2-g8]`) 29 | 30 | p.color = Black 31 | black := NewMoveGen(p).generateChecks() 32 | expect.Eq(t, black.allMoves(), `[Ba7-g1 Ba7-b8]`) 33 | } 34 | 35 | func TestGenChecks020(t *testing.T) { 36 | p := NewGame(`Kf4,Bc1`, `Kc5,Bf8,h6,e3`).start() 37 | white := NewMoveGen(p).generateChecks() 38 | expect.Eq(t, white.allMoves(), `[Bc1-a3]`) 39 | 40 | p.color = Black 41 | black := NewMoveGen(p).generateChecks() 42 | expect.Eq(t, black.allMoves(), `[Bf8-d6]`) 43 | } 44 | 45 | // Bishop: discovered non-capturing check with blocked diaginal. 46 | func TestGenChecks030(t *testing.T) { 47 | p := NewGame(`Ka8,Ba1,Nb2,c3,f3`, `Kh8,Bh1,Ng2`).start() 48 | white := NewMoveGen(p).generateChecks() 49 | expect.Eq(t, white.allMoves(), `[]`) 50 | 51 | p.color = Black 52 | black := NewMoveGen(p).generateChecks() 53 | expect.Eq(t, black.allMoves(), `[]`) 54 | } 55 | 56 | // Bishop: discovered non-capturing check: Knight. 57 | func TestGenChecks040(t *testing.T) { 58 | p := NewGame(`Ka8,Ba1,Nb2,a4,h4`, `Kh8,Bh1,Ng2,c4,f4`).start() 59 | white := NewMoveGen(p).generateChecks() 60 | expect.Eq(t, white.allMoves(), `[Nb2-d1 Nb2-d3]`) 61 | 62 | p.color = Black 63 | black := NewMoveGen(p).generateChecks() 64 | expect.Eq(t, black.allMoves(), `[Ng2-e1 Ng2-e3]`) 65 | } 66 | 67 | // Bishop: discovered non-capturing check: Rook. 68 | func TestGenChecks050(t *testing.T) { 69 | p := NewGame(`Ka8,Qa1,Nb1,Rb2,b4,d2,e2`, `Kh8,Qh1,Rg2,g4`).start() 70 | white := NewMoveGen(p).generateChecks() 71 | expect.Eq(t, white.allMoves(), `[Rb2-a2 Rb2-c2 Rb2-b3]`) 72 | 73 | p.color = Black 74 | black := NewMoveGen(p).generateChecks() 75 | expect.Eq(t, black.allMoves(), `[Rg2-g1 Rg2-f2 Rg2-h2 Rg2-g3]`) 76 | } 77 | 78 | // Bishop: discovered non-capturing check: King. 79 | func TestGenChecks060(t *testing.T) { 80 | p := NewGame(`Ke5,Qc3,c4,d3,e4`, `Kh8,e6`).start() 81 | white := NewMoveGen(p).generateChecks() 82 | expect.Eq(t, white.allMoves(), `[Ke5-f4 Ke5-d5 Ke5-f5 Ke5-d6]`) 83 | } 84 | 85 | // Bishop: discovered non-capturing check: Pawn move. 86 | func TestGenChecks070(t *testing.T) { 87 | p := NewGame(`Ka8,Ba1,c3`, `Kh8,Bg2,e4`).start() 88 | white := NewMoveGen(p).generateChecks() 89 | expect.Eq(t, white.allMoves(), `[c3-c4]`) 90 | 91 | p.color = Black 92 | black := NewMoveGen(p).generateChecks() 93 | expect.Eq(t, black.allMoves(), `[e4-e3]`) 94 | } 95 | 96 | // Bishop: discovered non-capturing check: Pawn jump. 97 | func TestGenChecks080(t *testing.T) { 98 | p := NewGame(`Kh2,Bb1,c2`, `Kh7,Bb8,c7`).start() 99 | white := NewMoveGen(p).generateChecks() 100 | expect.Eq(t, white.allMoves(), `[c2-c3 c2-c4]`) 101 | 102 | p.color = Black 103 | black := NewMoveGen(p).generateChecks() 104 | expect.Eq(t, black.allMoves(), `[c7-c5 c7-c6]`) 105 | } 106 | 107 | // Bishop: discovered non-capturing check: no pawn promotions. 108 | func TestGenChecks090(t *testing.T) { 109 | p := NewGame(`Kh7,Bb8,c7`, `Kh2,Bb1,c2`).start() 110 | white := NewMoveGen(p).generateChecks() 111 | expect.Eq(t, white.allMoves(), `[]`) 112 | 113 | p.color = Black 114 | black := NewMoveGen(p).generateChecks() 115 | expect.Eq(t, black.allMoves(), `[]`) 116 | } 117 | 118 | // Bishop: discovered non-capturing check: no enpassant captures. 119 | func TestGenChecks100(t *testing.T) { 120 | p := NewGame(`Ka1,Bf4,e5`, `M,Kb8,f7`).start() 121 | white := NewMoveGen(p.makeMove(NewEnpassant(p, F7, F5))).generateChecks() 122 | expect.Eq(t, white.allMoves(), `[e5-e6]`) 123 | 124 | p = NewGame(`Ka1,e2`, `Kb8,Be5,d4`).start() 125 | black := NewMoveGen(p.makeMove(NewEnpassant(p, E2, E4))).generateChecks() 126 | expect.Eq(t, black.allMoves(), `[d4-d3]`) 127 | } 128 | 129 | // Bishop: extra Rook moves for Queen. 130 | func TestGenChecks110(t *testing.T) { 131 | p := NewGame(`Kb1,Qa1,f2,a2`, `Kh1,Qa7,Nb8,a6`).start() 132 | white := NewMoveGen(p).generateChecks() 133 | expect.Eq(t, white.allMoves(), `[Qa1-h8 Kb1-b2 Kb1-c2]`) 134 | 135 | p.color = Black 136 | black := NewMoveGen(p).generateChecks() 137 | expect.Eq(t, black.allMoves(), `[Qa7-b6 Qa7-h7 Qa7-b7]`) 138 | } 139 | 140 | // Pawns. 141 | func TestGenChecks120(t *testing.T) { 142 | p := NewGame(`Kb5,f2,g2,h2`, `Kg4,a7,b7,c7`).start() 143 | white := NewMoveGen(p).generateChecks() 144 | expect.Eq(t, white.allMoves(), `[f2-f3 h2-h3]`) 145 | 146 | p.color = Black 147 | black := NewMoveGen(p).generateChecks() 148 | expect.Eq(t, black.allMoves(), `[a7-a6 c7-c6]`) 149 | } 150 | 151 | func TestGenChecks130(t *testing.T) { 152 | p := NewGame(`Kb4,f2,g2,h2`, `Kg5,a7,b7,c7`).start() 153 | white := NewMoveGen(p).generateChecks() 154 | expect.Eq(t, white.allMoves(), `[f2-f4 h2-h4]`) 155 | 156 | p.color = Black 157 | black := NewMoveGen(p).generateChecks() 158 | expect.Eq(t, black.allMoves(), `[a7-a5 c7-c5]`) 159 | } 160 | 161 | func TestGenChecks140(t *testing.T) { 162 | p := NewGame(`Kb4,c5,f2,g2,h2`, `Kg5,a7,b7,c7,h4`).start() 163 | white := NewMoveGen(p).generateChecks() 164 | expect.Eq(t, white.allMoves(), `[f2-f4]`) 165 | 166 | p.color = Black 167 | black := NewMoveGen(p).generateChecks() 168 | expect.Eq(t, black.allMoves(), `[a7-a5]`) 169 | } 170 | 171 | // Rook with pawn on the same rank (discovered check). 172 | func TestGenChecks150(t *testing.T) { 173 | p := NewGame(`Ka4,Ra5,e5`, `Kh5,Rh4,c4`).start() 174 | white := NewMoveGen(p).generateChecks() 175 | expect.Eq(t, white.allMoves(), `[e5-e6]`) 176 | 177 | p.color = Black 178 | black := NewMoveGen(p).generateChecks() 179 | expect.Eq(t, black.allMoves(), `[c4-c3]`) 180 | } 181 | 182 | // Rook with pawn on the same file (no check). 183 | func TestGenChecks160(t *testing.T) { 184 | p := NewGame(`Kh8,Ra8,a6`, `Ka3,Rh1,h5`).start() 185 | white := NewMoveGen(p).generateChecks() 186 | expect.Eq(t, white.allMoves(), `[]`) 187 | 188 | p.color = Black 189 | black := NewMoveGen(p).generateChecks() 190 | expect.Eq(t, black.allMoves(), `[]`) 191 | } 192 | 193 | // Rook with king on the same rank (discovered check). 194 | func TestGenChecks170(t *testing.T) { 195 | p := NewGame(`Ke5,Ra5,d4,e4,f4`, `Kh5`).start() 196 | white := NewMoveGen(p).generateChecks() 197 | expect.Eq(t, white.allMoves(), `[Ke5-d6 Ke5-e6 Ke5-f6]`) 198 | } 199 | 200 | // Rook with king on the same file (discovered check). 201 | func TestGenChecks180(t *testing.T) { 202 | p := NewGame(`Kb5,Rb8,c4,c5,c6`, `Kb1`).start() 203 | white := NewMoveGen(p).generateChecks() 204 | expect.Eq(t, white.allMoves(), `[Kb5-a4 Kb5-a5 Kb5-a6]`) 205 | } 206 | -------------------------------------------------------------------------------- /generate_evasions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | func (gen *MoveGen) generateEvasions() *MoveGen { 12 | p := gen.p 13 | color, enemy := p.color, p.color^1 14 | square := p.king[color] 15 | 16 | // Find out what pieces are checking the king. Usually it's a single 17 | // piece but double check is also a possibility. 18 | checkers := maskPawn[enemy][square] & p.outposts[pawn(enemy)] 19 | checkers |= p.knightAttacksAt(square, color) & p.outposts[knight(enemy)] 20 | checkers |= p.bishopAttacksAt(square, color) & (p.outposts[bishop(enemy)] | p.outposts[queen(enemy)]) 21 | checkers |= p.rookAttacksAt(square, color) & (p.outposts[rook(enemy)] | p.outposts[queen(enemy)]) 22 | 23 | // Generate possible king retreats first, i.e. moves to squares not 24 | // occupied by friendly pieces and not attacked by the opponent. 25 | retreats := p.targets(square) & ^p.allAttacks(enemy) 26 | 27 | // If the attacking piece is bishop, rook, or queen then exclude the 28 | // square behind the king using evasion mask. Note that knight's 29 | // evasion mask is full board so we only check if the attacking piece 30 | // is not a pawn. 31 | attackSquare := checkers.first() 32 | if p.pieces[attackSquare] != pawn(enemy) { 33 | retreats &= maskEvade[square][attackSquare] 34 | } 35 | 36 | // If checkers mask is not empty then we've got double check and 37 | // retreat is the only option. 38 | if checkers = checkers.pop(); checkers.any() { 39 | attackSquare = checkers.first() 40 | if p.pieces[attackSquare] != pawn(enemy) { 41 | retreats &= maskEvade[square][attackSquare] 42 | } 43 | return gen.movePiece(square, retreats) 44 | } 45 | 46 | // Generate king retreats. Since castle is not an option there is no 47 | // reason to use moveKing(). 48 | gen.movePiece(square, retreats) 49 | 50 | // Pawn captures: do we have any pawns available that could capture 51 | // the attacking piece? 52 | for bm := maskPawn[color][attackSquare] & p.outposts[pawn(color)]; bm.any(); bm = bm.pop() { 53 | move := NewMove(p, bm.first(), attackSquare) 54 | if attackSquare >= A8 || attackSquare <= H1 { 55 | move = move.promote(Queen) 56 | } 57 | gen.add(move) 58 | } 59 | 60 | // Rare case when the check could be avoided by en-passant capture. 61 | // For example: Ke4, c5, e5 vs. Ke8, d7. Black's d7-d5+ could be 62 | // evaded by c5xd6 or e5xd6 en-passant captures. 63 | if p.enpassant != 0 { 64 | if enpassant := attackSquare + up[color]; enpassant == p.enpassant { 65 | for bm := maskPawn[color][enpassant] & p.outposts[pawn(color)]; bm.any(); bm = bm.pop() { 66 | gen.add(NewMove(p, bm.first(), enpassant)) 67 | } 68 | } 69 | } 70 | 71 | // See if the check could be blocked or the attacked piece captured. 72 | block := maskBlock[square][attackSquare] | bit[attackSquare] 73 | 74 | // Create masks for one-square pawn pushes and two-square jumps. 75 | pawns, jumps := Bitmask(0), ^p.board 76 | if color == White { 77 | pawns = (p.outposts[Pawn] << 8) & ^p.board 78 | jumps &= maskRank[3] & (pawns << 8) 79 | } else { 80 | pawns = (p.outposts[BlackPawn] >> 8) & ^p.board 81 | jumps &= maskRank[4] & (pawns >> 8) 82 | } 83 | 84 | // Handle one-square pawn pushes: promote to Queen if reached last rank. 85 | for bm := pawns & block; bm.any(); bm = bm.pop() { 86 | to := bm.first() 87 | from := to - up[color] 88 | move := NewMove(p, from, to) // Can't cause en-passant. 89 | if to >= A8 || to <= H1 { 90 | move = move.promote(Queen) 91 | } 92 | gen.add(move) 93 | } 94 | 95 | // Handle two-square pawn jumps that can cause en-passant. 96 | for bm := jumps & block; bm.any(); bm = bm.pop() { 97 | to := bm.first() 98 | from := to - 2 * up[color] 99 | gen.add(NewPawnMove(p, from, to)) 100 | } 101 | 102 | // What's left is to generate all possible knight, bishop, rook, and 103 | // queen moves that evade the check. 104 | for bm := p.outposts[color] & ^p.outposts[pawn(color)] & ^p.outposts[king(color)]; bm.any(); bm = bm.pop() { 105 | from := bm.first() 106 | gen.movePiece(from, p.targets(from) & block) 107 | } 108 | 109 | return gen 110 | } 111 | -------------------------------------------------------------------------------- /generate_evasions_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | // Check evasions (king retreats). 14 | func TestGenEvasions270(t *testing.T) { 15 | game := NewGame(`Kh6,g7`, `M,Kf8`) 16 | black := NewMoveGen(game.start()).generateEvasions() 17 | expect.Eq(t, black.allMoves(), `[Kf8-e7 Kf8-f7 Kf8-e8 Kf8-g8]`) 18 | } 19 | 20 | // Check evasions (king retreats). 21 | func TestGenEvasions280(t *testing.T) { 22 | game := NewGame(`Ka1`, `Kf8,Nc4,b2`) 23 | white := NewMoveGen(game.start()).generateEvasions() 24 | expect.Eq(t, white.allMoves(), `[Ka1-b1 Ka1-a2]`) 25 | } 26 | 27 | // Check evasions (king retreats). 28 | func TestGenEvasions290(t *testing.T) { 29 | game := NewGame(`Ka1,h6,g7`, `M,Kf8`) 30 | black := NewMoveGen(game.start()).generateEvasions() 31 | expect.Eq(t, black.allMoves(), `[Kf8-e7 Kf8-f7 Kf8-e8 Kf8-g8]`) 32 | } 33 | 34 | // Check evasions (captures/blocks by major pieces). 35 | func TestGenEvasions300(t *testing.T) { 36 | game := NewGame(`Ka5,Ra8`, `M,Kg8,Qf6,Re6,Bc6,Bg7,Na6,Nb6,f7,g6,h5`) 37 | black := NewMoveGen(game.start()).generateEvasions() 38 | expect.Eq(t, black.allMoves(), `[Kg8-h7 Na6-b8 Nb6xa8 Nb6-c8 Bc6xa8 Bc6-e8 Re6-e8 Qf6-d8 Bg7-f8]`) 39 | } 40 | 41 | // Check evasions (double check). 42 | func TestGenEvasions310(t *testing.T) { 43 | game := NewGame(`Ka1,Ra8,Nf6`, `M,Kg8,Qc6,Bb7,Nb6,f7,g6,h7`) 44 | black := NewMoveGen(game.start()).generateEvasions() 45 | expect.Eq(t, black.allMoves(), `[Kg8-g7]`) 46 | } 47 | 48 | // Check evasions (double check). 49 | func TestGenEvasions320(t *testing.T) { 50 | game := NewGame(`Ka1,Ra8,Be5`, `M,Kh8,Qd5,Bb7,Nb6`) 51 | black := NewMoveGen(game.start()).generateEvasions() 52 | expect.Eq(t, black.allMoves(), `[Kh8-h7]`) 53 | } 54 | 55 | // Check evasions (pawn captures). 56 | func TestGenEvasions330(t *testing.T) { 57 | game := NewGame(`Kh6,Be5`, `M,Kh8,d6`) 58 | black := NewMoveGen(game.start()).generateEvasions() 59 | expect.Eq(t, black.allMoves(), `[Kh8-g8 d6xe5]`) 60 | } 61 | 62 | // Check evasions (pawn captures). 63 | func TestGenEvasions340(t *testing.T) { 64 | game := NewGame(`Ke1,e2,f2,g2`, `Kc3,Nf3`) 65 | white := NewMoveGen(game.start()).generateEvasions() 66 | expect.Eq(t, white.allMoves(), `[Ke1-d1 Ke1-f1 e2xf3 g2xf3]`) 67 | } 68 | 69 | // Check evasions (pawn blocks). 70 | func TestGenEvasions350(t *testing.T) { 71 | game := NewGame(`Kf8,Ba1`, `M,Kh8,b3,c4,d5,e6,f7`) 72 | black := NewMoveGen(game.start()).generateEvasions() 73 | expect.Eq(t, black.allMoves(), `[Kh8-h7 b3-b2 c4-c3 d5-d4 e6-e5 f7-f6]`) 74 | } 75 | 76 | // Check evasions (pawn blocks). 77 | func TestGenEvasions360(t *testing.T) { 78 | game := NewGame(`Ka5,a4,b4,c4,d4,e4,f4,h4`, `Kh8,Qh5`) 79 | white := NewMoveGen(game.start()).generateEvasions() 80 | expect.Eq(t, white.allMoves(), `[Ka5-a6 Ka5-b6 b4-b5 c4-c5 d4-d5 e4-e5 f4-f5]`) 81 | } 82 | 83 | // Check evasions (pawn jumps). 84 | func TestGenEvasions365(t *testing.T) { 85 | game := NewGame(`Ke1,Rh4`, `M,Kh6,h7`) 86 | white := NewMoveGen(game.start()).generateEvasions() 87 | expect.Eq(t, white.allMoves(), `[Kh6-g5 Kh6-g6 Kh6-g7]`) // No h7-h5 jump. 88 | } 89 | 90 | // Check evasions (en-passant pawn capture). 91 | func TestGenEvasions370(t *testing.T) { 92 | game := NewGame(`Kd4,d5,f5`, `M,Kd8,e7`) 93 | black := game.start() 94 | white := NewMoveGen(black.makeMove(NewEnpassant(black, E7, E5))).generateEvasions() 95 | expect.Eq(t, white.allMoves(), `[Kd4-c3 Kd4-d3 Kd4-e3 Kd4-c4 Kd4-e4 Kd4-c5 Kd4xe5 d5xe6 f5xe6]`) 96 | } 97 | 98 | // Check evasions (en-passant pawn capture). 99 | func TestGenEvasions380(t *testing.T) { 100 | game := NewGame(`Kb1,b2`, `Ka5,a4,c5,c4`) 101 | white := game.start() 102 | black := NewMoveGen(white.makeMove(NewEnpassant(white, B2, B4))).generateEvasions() 103 | expect.Eq(t, black.allMoves(), `[Ka5xb4 Ka5-b5 Ka5-a6 Ka5-b6 c5xb4 a4xb3 c4xb3]`) 104 | } 105 | 106 | // Check evasions (en-passant pawn capture). 107 | func TestGenEvasions385(t *testing.T) { 108 | game := NewGame(`Ke4,c5,e5`, `M,Ke7,d7`) 109 | black := game.start() 110 | white := NewMoveGen(black.makeMove(NewEnpassant(black, D7, D5))).generateEvasions() 111 | for move := white.nextMove(); move != 0; move = white.nextMove() { 112 | if move.piece() == Pawn { 113 | expect.Eq(t, move.to(), D6) 114 | expect.Eq(t, move.color(), uint8(White)) 115 | expect.Eq(t, Piece(move.capture()), Piece(BlackPawn)) 116 | expect.Eq(t, Piece(move.promo()), Piece(0)) 117 | } 118 | } 119 | } 120 | 121 | // Check evasions (en-passant pawn capture). 122 | func TestGenEvasions386(t *testing.T) { 123 | game := NewGame(`Kf1,Qa2`, `M,Kf7,b2`) 124 | black := NewMoveGen(game.start()).generateEvasions() 125 | expect.NotContain(t, black.allMoves(), `b2-a1`) 126 | } 127 | 128 | // Check evasions (pawn jumps). 129 | func TestGenEvasions390(t *testing.T) { 130 | game := NewGame(`Kh4,a2,b2,c2,d2,e2,f2,g2`, `Kd8,Ra4`) 131 | white := NewMoveGen(game.start()).generateEvasions() 132 | expect.Eq(t, white.allMoves(), `[Kh4-g3 Kh4-h3 Kh4-g5 Kh4-h5 b2-b4 c2-c4 d2-d4 e2-e4 f2-f4 g2-g4]`) 133 | } 134 | 135 | // Check evasions (pawn jumps). 136 | func TestGenEvasions400(t *testing.T) { 137 | game := NewGame(`Kd8,Rh5`, `M,Ka5,b7,c7,d7,e7,f7,g7,h7`) 138 | black := NewMoveGen(game.start()).generateEvasions() 139 | expect.Eq(t, black.allMoves(), `[Ka5-a4 Ka5-b4 Ka5-a6 Ka5-b6 b7-b5 c7-c5 d7-d5 e7-e5 f7-f5 g7-g5]`) 140 | } 141 | 142 | // Check evasions (pawn jump, sets en-passant). 143 | func TestGenEvasions405(t *testing.T) { 144 | p := NewGame(`Ke1,Qd4,d5`, `M,Kg7,e7,g6,h7`).start() 145 | black := NewMoveGen(p).generateEvasions() 146 | e7e5 := black.list[black.head + 4].move 147 | expect.Eq(t, black.allMoves(), `[Kg7-h6 Kg7-f7 Kg7-f8 Kg7-g8 e7-e5]`) 148 | p = p.makeMove(e7e5) 149 | expect.Eq(t, p.enpassant, uint8(E6)) 150 | } 151 | 152 | // Check evasions (piece x piece captures). 153 | func TestGenEvasions410(t *testing.T) { 154 | game := NewGame(`Ke1,Qd1,Rc7,Bd3,Nd4,c2,f2`, `M,Kh8,Nb4`) 155 | black := game.start() 156 | white := NewMoveGen(black.makeMove(NewMove(black, B4, C2))).generateEvasions() 157 | expect.Eq(t, white.allMoves(), `[Ke1-f1 Ke1-d2 Ke1-e2 Qd1xc2 Bd3xc2 Nd4xc2 Rc7xc2]`) 158 | } 159 | 160 | // Check evasions (pawn x piece captures). 161 | func TestGenEvasions420(t *testing.T) { 162 | game := NewGame(`Ke1,Qd1,Rd7,Bf1,Nc1,c2,d3,f2`, `M,Kh8,Nb4`) 163 | black := game.start() 164 | white := NewMoveGen(black.makeMove(NewMove(black, B4, D3))).generateEvasions() 165 | expect.Eq(t, white.allMoves(), `[Ke1-d2 Ke1-e2 c2xd3 Nc1xd3 Qd1xd3 Bf1xd3 Rd7xd3]`) 166 | } 167 | 168 | // Check evasions (king x piece captures). 169 | func TestGenEvasions430(t *testing.T) { 170 | game := NewGame(`Ke1,Qf7,f2`, `M,Kh8,Qh4`) 171 | black := game.start() 172 | white := NewMoveGen(black.makeMove(NewMove(black, H4, F2))).generateEvasions() 173 | expect.Eq(t, white.allMoves(), `[Ke1-d1 Ke1xf2 Qf7xf2]`) 174 | } 175 | 176 | // Pawn promotion to block. 177 | func TestGenEvasions440(t *testing.T) { 178 | game := NewGame(`Kf1,Qf3,Nf2`, `Ka1,b2`) 179 | white := game.start() 180 | black := NewMoveGen(white.makeMove(NewMove(white, F3, D1))).generateEvasions() 181 | expect.Eq(t, black.allMoves(), `[Ka1-a2 b2-b1Q]`) 182 | } 183 | 184 | // Pawn promotion to block or capture. 185 | func TestGenEvasions450(t *testing.T) { 186 | game := NewGame(`Kf1,Qf3,Nf2`, `Ka1,b2,c2`) 187 | white := game.start() 188 | black := NewMoveGen(white.makeMove(NewMove(white, F3, D1))).generateEvasions() 189 | expect.Eq(t, black.allMoves(), `[Ka1-a2 c2xd1Q b2-b1Q c2-c1Q]`) 190 | } 191 | 192 | // Pawn promotion to capture. 193 | func TestGenEvasions460(t *testing.T) { 194 | game := NewGame(`Kf1,Qf3,Nf2`, `Kc1,c2,d2`) 195 | white := game.start() 196 | black := NewMoveGen(white.makeMove(NewMove(white, F3, D1))).generateEvasions() 197 | expect.Eq(t, black.allMoves(), `[Kc1-b2 c2xd1Q]`) 198 | } 199 | -------------------------------------------------------------------------------- /generate_moves.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | func (gen *MoveGen) generateRootMoves() *MoveGen { 12 | gen.generateAllMoves() 13 | 14 | if !gen.onlyMove() { 15 | gen.validOnly().rank(Move(0)) 16 | } 17 | 18 | return gen 19 | } 20 | 21 | // Copies last move returned by nextMove() to the top of the list shifting 22 | // remaining moves down. Head/tail pointers remain unchanged. 23 | func (gen *MoveGen) rearrangeRootMoves() *MoveGen { 24 | if gen.head > 0 { 25 | best := gen.list[gen.head - 1] 26 | copy(gen.list[1:], gen.list[0:gen.head - 1]) 27 | gen.list[0] = best 28 | } 29 | 30 | return gen 31 | } 32 | 33 | func (gen *MoveGen) generateAllMoves() *MoveGen { 34 | if gen.p.isInCheck(gen.p.color) { 35 | return gen.generateEvasions() 36 | } 37 | 38 | return gen.generateMoves() 39 | } 40 | 41 | func (gen *MoveGen) generateMoves() *MoveGen { 42 | color := gen.p.color 43 | 44 | return gen.pawnMoves(color).pieceMoves(color).kingMoves(color) 45 | } 46 | 47 | func (gen *MoveGen) pawnMoves(color int) *MoveGen { 48 | for bm := gen.p.outposts[pawn(color)]; bm.any(); bm = bm.pop() { 49 | square := bm.first() 50 | gen.movePawn(square, gen.p.targets(square)) 51 | } 52 | 53 | return gen 54 | } 55 | 56 | // Go over all pieces except pawns and the king. 57 | func (gen *MoveGen) pieceMoves(color int) *MoveGen { 58 | for bm := gen.p.outposts[color] ^ gen.p.outposts[pawn(color)] ^ gen.p.outposts[king(color)]; bm.any(); bm = bm.pop() { 59 | square := bm.first() 60 | gen.movePiece(square, gen.p.targets(square)) 61 | } 62 | 63 | return gen 64 | } 65 | 66 | func (gen *MoveGen) kingMoves(color int) *MoveGen { 67 | if gen.p.outposts[king(color)].any() { 68 | square := gen.p.king[color] 69 | gen.moveKing(square, gen.p.targets(square)) 70 | 71 | kingside, queenside := gen.p.canCastle(color) 72 | if kingside { 73 | gen.moveKing(square, bit[G1 + 56 * color]) 74 | } 75 | if queenside { 76 | gen.moveKing(square, bit[C1 + 56 * color]) 77 | } 78 | } 79 | 80 | return gen 81 | } 82 | 83 | func (gen *MoveGen) movePawn(square int, targets Bitmask) *MoveGen { 84 | for bm := targets; bm.any(); bm = bm.pop() { 85 | target := bm.first() 86 | if target > H1 && target < A8 { 87 | gen.add(NewPawnMove(gen.p, square, target)) 88 | } else { // Promotion. 89 | mQ, mR, mB, mN := NewPromotion(gen.p, square, target) 90 | gen.add(mQ).add(mN).add(mR).add(mB) 91 | } 92 | } 93 | 94 | return gen 95 | } 96 | 97 | func (gen *MoveGen) moveKing(square int, targets Bitmask) *MoveGen { 98 | for bm := targets; bm.any(); bm = bm.pop() { 99 | target := bm.first() 100 | if abs(square - target) == 2 { 101 | gen.add(NewCastle(gen.p, square, target)) 102 | } else { 103 | gen.add(NewMove(gen.p, square, target)) 104 | } 105 | } 106 | 107 | return gen 108 | } 109 | 110 | func (gen *MoveGen) movePiece(square int, targets Bitmask) *MoveGen { 111 | for bm := targets; bm.any(); bm = bm.pop() { 112 | gen.add(NewMove(gen.p, square, bm.first())) 113 | } 114 | 115 | return gen 116 | } 117 | -------------------------------------------------------------------------------- /generate_moves_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | func TestGenerateMoves000(t *testing.T) { 14 | gen := NewMoveGen(NewGame().start()).generateMoves() 15 | 16 | // All possible moves in the initial position, pawn-to-queen, left-to right, unsorted. 17 | expect.Eq(t, gen.allMoves(), `[a2-a3 a2-a4 b2-b3 b2-b4 c2-c3 c2-c4 d2-d3 d2-d4 e2-e3 e2-e4 f2-f3 f2-f4 g2-g3 g2-g4 h2-h3 h2-h4 Nb1-a3 Nb1-c3 Ng1-f3 Ng1-h3]`) 18 | } 19 | 20 | func TestGenerateMoves020(t *testing.T) { 21 | game := NewGame(`a2,b3,c4,d2,e6,f5,g4,h3`, `a3,b4,c5,e7,f6,g5,h4,Kg8`) 22 | gen := NewMoveGen(game.start()).generateMoves() 23 | 24 | // All possible moves, left-to right, unsorted. 25 | expect.Eq(t, gen.allMoves(), `[d2-d3 d2-d4]`) 26 | } 27 | 28 | func TestGenerateMoves030(t *testing.T) { 29 | game := NewGame(`a2,e4,g2`, `b3,f5,f3,h3,Kg8`) 30 | gen := NewMoveGen(game.start()).generateMoves() 31 | 32 | // All possible moves, left-to right, unsorted. 33 | expect.Eq(t, gen.allMoves(), `[a2-a3 a2xb3 a2-a4 g2xf3 g2-g3 g2xh3 g2-g4 e4-e5 e4xf5]`) 34 | } 35 | 36 | // Castles. 37 | func TestGenerateMoves031(t *testing.T) { 38 | p := NewGame(`Ke1,Rh1,h2`, `Ke8,Ra8,a7`).start() 39 | white := NewMoveGen(p).generateMoves() 40 | expect.Contain(t, white.allMoves(), `0-0`) 41 | 42 | p.color = Black 43 | black := NewMoveGen(p).generateMoves() 44 | expect.Contain(t, black.allMoves(), `0-0-0`) 45 | } 46 | 47 | // Should not include castles when rook has moved. 48 | func TestGenerateMoves040(t *testing.T) { 49 | p := NewGame(`Ke1,Rf1,g2`, `Ke8`).start() 50 | white := NewMoveGen(p).generateMoves() 51 | expect.NotContain(t, white.allMoves(), `0-0`) 52 | } 53 | 54 | func TestGenerateMoves050(t *testing.T) { 55 | p := NewGame(`Ke1,Rb1,b2`, `Ke8`).start() 56 | white := NewMoveGen(p).generateMoves() 57 | expect.NotContain(t, white.allMoves(), `0-0`) 58 | } 59 | 60 | // Should not include castles when king has moved. 61 | func TestGenerateMoves060(t *testing.T) { 62 | p := NewGame(`Kf1,Ra1,a2,Rh1,h2`, `Ke8`).start() 63 | white := NewMoveGen(p).generateMoves() 64 | expect.NotContain(t, white.allMoves(), `0-0`) 65 | } 66 | 67 | // Should not include castles when rooks are not there. 68 | func TestGenerateMoves070(t *testing.T) { 69 | p := NewGame(`Ke1`, `Ke8`).start() 70 | white := NewMoveGen(p).generateMoves() 71 | expect.NotContain(t, white.allMoves(), `0-0`) 72 | } 73 | 74 | // Should not include castles when king is in check. 75 | func TestGenerateMoves080(t *testing.T) { 76 | p := NewGame(`Ke1,Ra1,Rf1`, `Ke8,Re7`).start() 77 | white := NewMoveGen(p).generateMoves() 78 | expect.NotContain(t, white.allMoves(), `0-0`) 79 | } 80 | 81 | // Should not include castles when target square is a capture. 82 | func TestGenerateMoves090(t *testing.T) { 83 | p := NewGame(`Ke1,Ra1,Rf1`, `Ke8,Nc1,Ng1`).start() 84 | white := NewMoveGen(p).generateMoves() 85 | expect.NotContain(t, white.allMoves(), `0-0`) 86 | } 87 | 88 | // Should not include castles when king is to jump over attacked square. 89 | func TestGenerateMoves100(t *testing.T) { 90 | p := NewGame(`Ke1,Ra1,Rf1`, `Ke8,Bc4,Bf4`).start() 91 | white := NewMoveGen(p).generateMoves() 92 | expect.NotContain(t, white.allMoves(), `0-0`) 93 | } 94 | 95 | // Pawn moves that include promotions. 96 | func TestGenerateMoves200(t *testing.T) { 97 | p := NewGame(`Ka1,a6,b7`, `Kh8,g3,h2`).start() 98 | white := NewMoveGen(p).pawnMoves(White) 99 | expect.Eq(t, white.allMoves(), `[a6-a7 b7-b8Q b7-b8N b7-b8R b7-b8B]`) 100 | 101 | p.color = Black 102 | black := NewMoveGen(p).pawnMoves(Black) 103 | expect.Eq(t, black.allMoves(), `[h2-h1Q h2-h1N h2-h1R h2-h1B g3-g2]`) 104 | } 105 | 106 | // Pawn moves that include jumps. 107 | func TestGenerateMoves210(t *testing.T) { 108 | p := NewGame(`Ka1,a6`, `Kh8,a7,g7,h6`).start() 109 | white := NewMoveGen(p).pawnMoves(White) 110 | expect.Eq(t, white.allMoves(), `[]`) 111 | 112 | p.color = Black 113 | black := NewMoveGen(p).pawnMoves(Black) 114 | expect.Eq(t, black.allMoves(), `[h6-h5 g7-g5 g7-g6]`) 115 | } 116 | 117 | // Pawn captures without promotions. 118 | func TestGenerateMoves220(t *testing.T) { 119 | p := NewGame(`Ka1,a6,f6,g5`, `Kh8,b7,g7,h6`).start() 120 | white := NewMoveGen(p).pawnCaptures(White) 121 | expect.Eq(t, white.allMoves(), `[g5xh6 a6xb7 f6xg7]`) 122 | 123 | p.color = Black 124 | black := NewMoveGen(p).pawnCaptures(Black) 125 | expect.Eq(t, black.allMoves(), `[h6xg5 b7xa6 g7xf6]`) 126 | } 127 | 128 | // Pawn captures with Queen promotion (captures generate queen promotions only). 129 | func TestGenerateMoves230(t *testing.T) { 130 | p := NewGame(`Ka1,Rh1,Bf1,c7`, `Kh8,Nb8,Qd8,g2`).start() 131 | white := NewMoveGen(p).pawnCaptures(White) 132 | expect.Eq(t, white.allMoves(), `[c7xb8Q c7-c8Q c7xd8Q]`) 133 | 134 | p.color = Black 135 | black := NewMoveGen(p).pawnCaptures(Black) 136 | expect.Eq(t, black.allMoves(), `[g2xf1Q g2-g1Q g2xh1Q]`) 137 | } 138 | 139 | // Rearrange root moves. 140 | func TestGenerateMoves300(t *testing.T) { 141 | p := NewGame().start() 142 | gen := NewMoveGen(p).generateMoves().validOnly() 143 | 144 | // Pick from the middle. 145 | gen.head = 10 // e2-e4 146 | gen.rearrangeRootMoves().reset() 147 | expect.Eq(t, gen.allMoves(), `[e2-e4 a2-a3 a2-a4 b2-b3 b2-b4 c2-c3 c2-c4 d2-d3 d2-d4 e2-e3 f2-f3 f2-f4 g2-g3 g2-g4 h2-h3 h2-h4 Nb1-a3 Nb1-c3 Ng1-f3 Ng1-h3]`) 148 | 149 | // Pick first one. 150 | gen.head = 1 151 | gen.rearrangeRootMoves().reset() 152 | expect.Eq(t, gen.allMoves(), `[e2-e4 a2-a3 a2-a4 b2-b3 b2-b4 c2-c3 c2-c4 d2-d3 d2-d4 e2-e3 f2-f3 f2-f4 g2-g3 g2-g4 h2-h3 h2-h4 Nb1-a3 Nb1-c3 Ng1-f3 Ng1-h3]`) 153 | 154 | // Pick last one. 155 | gen.head = gen.tail 156 | gen.rearrangeRootMoves().reset() 157 | expect.Eq(t, gen.allMoves(), `[Ng1-h3 e2-e4 a2-a3 a2-a4 b2-b3 b2-b4 c2-c3 c2-c4 d2-d3 d2-d4 e2-e3 f2-f3 f2-f4 g2-g3 g2-g4 h2-h3 h2-h4 Nb1-a3 Nb1-c3 Ng1-f3]`) 158 | } 159 | -------------------------------------------------------------------------------- /generate_quiets.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | func (gen *MoveGen) addQuiet(move Move) *MoveGen { 12 | gen.list[gen.tail].move = move 13 | gen.list[gen.tail].score = game.good(move) 14 | gen.tail++ 15 | 16 | return gen 17 | } 18 | 19 | // Generates pseudo-legal moves that preserve material balance, i.e. 20 | // no captures or pawn promotions are allowed. 21 | func (gen *MoveGen) generateQuiets() *MoveGen { 22 | p := gen.p 23 | color := p.color 24 | empty := ^p.board 25 | 26 | // Castles. 27 | if !p.isInCheck(color) { 28 | home := homeKing[color] 29 | kingside, queenside := p.canCastle(color) 30 | if kingside { 31 | gen.addQuiet(NewCastle(p, home, home + 2)) 32 | } 33 | if queenside { 34 | gen.addQuiet(NewCastle(p, home, home - 2)) 35 | } 36 | } 37 | 38 | // Pawns. 39 | last := let(color == White, 7, 0) 40 | for bm := p.outposts[pawn(color)].up(color) & empty & ^maskRank[last]; bm.any(); bm = bm.pop() { 41 | square := bm.first() 42 | forward, backward := square + up[color], square - up[color] 43 | if rank(color, square) == 2 && p.pieces[forward].none() { 44 | gen.addQuiet(NewPawnMove(gen.p, backward, forward)) // Jump. 45 | } 46 | gen.addQuiet(NewPawnMove(gen.p, backward, square)) // Push. 47 | } 48 | 49 | // Knights. 50 | for bm := p.outposts[knight(color)]; bm.any(); bm = bm.pop() { 51 | square := bm.first() 52 | for bm := knightMoves[square] & empty; bm.any(); bm = bm.pop() { 53 | gen.addQuiet(NewMove(p, square, bm.first())) 54 | } 55 | } 56 | 57 | // Bishops. 58 | for bm := p.outposts[bishop(color)]; bm.any(); bm = bm.pop() { 59 | square := bm.first() 60 | for bm := p.bishopMoves(square) & empty; bm.any(); bm = bm.pop() { 61 | gen.addQuiet(NewMove(p, square, bm.first())) 62 | } 63 | } 64 | 65 | // Rooks. 66 | for bm := p.outposts[rook(color)]; bm.any(); bm = bm.pop() { 67 | square := bm.first() 68 | for bm := p.rookMoves(square) & empty; bm.any(); bm = bm.pop() { 69 | gen.addQuiet(NewMove(p, square, bm.first())) 70 | } 71 | } 72 | 73 | // Queens. 74 | for bm := p.outposts[queen(color)]; bm.any(); bm = bm.pop() { 75 | square := bm.first() 76 | for bm := (p.bishopMoves(square) | p.rookMoves(square)) & empty; bm.any(); bm = bm.pop() { 77 | gen.addQuiet(NewMove(p, square, bm.first())) 78 | } 79 | } 80 | 81 | // King. 82 | square := p.king[color] 83 | for bm := (kingMoves[square] & empty); bm.any(); bm = bm.pop() { 84 | gen.addQuiet(NewMove(p, square, bm.first())) 85 | } 86 | 87 | return gen 88 | } 89 | -------------------------------------------------------------------------------- /generate_quiets_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | // Pawns. 14 | func TestGenQuiets000(t *testing.T) { 15 | p := NewGame(`Ka1,Rb1,a2,b2,c4,d7`, `Kh8,Rc1,Ne8,a3,b5`).start() 16 | gen := NewMoveGen(p).generateQuiets() 17 | expect.Eq(t, gen.allMoves(), `[b2-b4 b2-b3 c4-c5]`) 18 | } 19 | 20 | func TestGenQuiets001(t *testing.T) { 21 | p := NewGame(`Kh1,Rc8,Ne1,a6,b4`, `M,Ka8,Rb8,a7,b7,c5,d2`).start() 22 | gen := NewMoveGen(p).generateQuiets() 23 | expect.Eq(t, gen.allMoves(), `[c5-c4 b7-b5 b7-b6]`) 24 | } 25 | 26 | // Knights. 27 | func TestGenQuiets010(t *testing.T) { 28 | p := NewGame(`Ka1,Nb1,a2`, `Kh8,Rb2,a3,d2`).start() 29 | gen := NewMoveGen(p).generateQuiets() 30 | expect.Eq(t, gen.allMoves(), `[Nb1-c3]`) 31 | } 32 | 33 | func TestGenQuiets011(t *testing.T) { 34 | p := NewGame(`Kh1,Rb7,a6,d7`, `M,Ka8,Nb8,a7`).start() 35 | gen := NewMoveGen(p).generateQuiets() 36 | expect.Eq(t, gen.allMoves(), `[Nb8-c6]`) 37 | } 38 | 39 | // Bishops. 40 | func TestGenQuiets020(t *testing.T) { 41 | p := NewGame(`Ka1,Bb1`, `Kh8,Rb2,a2,e4`).start() 42 | gen := NewMoveGen(p).generateQuiets() 43 | expect.Eq(t, gen.allMoves(), `[Bb1-c2 Bb1-d3]`) 44 | } 45 | 46 | func TestGenQuiets021(t *testing.T) { 47 | p := NewGame(`Kh1,Rb7,a7,e5`, `M,Ka8,Bb8`).start() 48 | gen := NewMoveGen(p).generateQuiets() 49 | expect.Eq(t, gen.allMoves(), `[Bb8-d6 Bb8-c7]`) 50 | } 51 | 52 | // Rooks. 53 | func TestGenQuiets030(t *testing.T) { 54 | p := NewGame(`Ka1,Rb1`, `Kh8,Rb2,Re1,a2`).start() 55 | gen := NewMoveGen(p).generateQuiets() 56 | expect.Eq(t, gen.allMoves(), `[Rb1-c1 Rb1-d1]`) 57 | } 58 | 59 | func TestGenQuiets031(t *testing.T) { 60 | p := NewGame(`Kh1,Rb7,Re8,a7`, `M,Ka8,Rb8`).start() 61 | gen := NewMoveGen(p).generateQuiets() 62 | expect.Eq(t, gen.allMoves(), `[Rb8-c8 Rb8-d8]`) 63 | } 64 | 65 | // Queens. 66 | func TestGenQuiets040(t *testing.T) { 67 | p := NewGame(`Ka1,Qb1`, `Kh8,Rb2,Re1,a2,e4`).start() 68 | gen := NewMoveGen(p).generateQuiets() 69 | expect.Eq(t, gen.allMoves(), `[Qb1-c1 Qb1-d1 Qb1-c2 Qb1-d3]`) 70 | } 71 | 72 | func TestGenQuiets041(t *testing.T) { 73 | p := NewGame(`Kh1,Rb7,Re8,a7,e5`, `M,Ka8,Qb8`).start() 74 | gen := NewMoveGen(p).generateQuiets() 75 | expect.Eq(t, gen.allMoves(), `[Qb8-d6 Qb8-c7 Qb8-c8 Qb8-d8]`) 76 | } 77 | 78 | // 0-0, King. 79 | func TestGenQuiets050(t *testing.T) { 80 | p := NewGame(`Ke1,Rh1,h2`, `Ka8,h3`).start() 81 | gen := NewMoveGen(p).generateQuiets() 82 | expect.Eq(t, gen.allMoves(), `[0-0 Rh1-f1 Rh1-g1 Ke1-d1 Ke1-f1 Ke1-d2 Ke1-e2 Ke1-f2]`) 83 | } 84 | 85 | func TestGenQuiets051(t *testing.T) { 86 | p := NewGame(`Ke1,Rh1,h2`, `Ka8,e2,h3`).start() 87 | gen := NewMoveGen(p).generateQuiets() 88 | expect.Eq(t, gen.allMoves(), `[Rh1-f1 Rh1-g1 Ke1-d1 Ke1-f1 Ke1-d2 Ke1-f2]`) 89 | } 90 | 91 | func TestGenQuiets052(t *testing.T) { 92 | p := NewGame(`Ka1,h6`, `M,Ke8,Rh8,h7`).start() 93 | gen := NewMoveGen(p).generateQuiets() 94 | expect.Eq(t, gen.allMoves(), `[0-0 Rh8-f8 Rh8-g8 Ke8-d7 Ke8-e7 Ke8-f7 Ke8-d8 Ke8-f8]`) 95 | } 96 | 97 | func TestGenQuiets053(t *testing.T) { 98 | p := NewGame(`Ka1,e7,h6`, `M,Ke8,Rh8,h7`).start() 99 | gen := NewMoveGen(p).generateQuiets() 100 | expect.Eq(t, gen.allMoves(), `[Rh8-f8 Rh8-g8 Ke8-d7 Ke8-f7 Ke8-d8 Ke8-f8]`) 101 | } 102 | 103 | // 0-0-0, King. 104 | func TestGenQuiets060(t *testing.T) { 105 | p := NewGame(`Ke1,Ra1,a2`, `Ka8,a3`).start() 106 | gen := NewMoveGen(p).generateQuiets() 107 | expect.Eq(t, gen.allMoves(), `[0-0-0 Ra1-b1 Ra1-c1 Ra1-d1 Ke1-d1 Ke1-f1 Ke1-d2 Ke1-e2 Ke1-f2]`) 108 | } 109 | 110 | func TestGenQuiets061(t *testing.T) { 111 | p := NewGame(`Ke1,Ra1,a2`, `Ka8,a3,e2`).start() 112 | gen := NewMoveGen(p).generateQuiets() 113 | expect.Eq(t, gen.allMoves(), `[Ra1-b1 Ra1-c1 Ra1-d1 Ke1-d1 Ke1-f1 Ke1-d2 Ke1-f2]`) 114 | } 115 | 116 | func TestGenQuiets62(t *testing.T) { 117 | p := NewGame(`Ka1,a6`, `M,Ke8,Ra8,a7`).start() 118 | gen := NewMoveGen(p).generateQuiets() 119 | expect.Eq(t, gen.allMoves(), `[0-0-0 Ra8-b8 Ra8-c8 Ra8-d8 Ke8-d7 Ke8-e7 Ke8-f7 Ke8-d8 Ke8-f8]`) 120 | } 121 | 122 | func TestGenQuiets063(t *testing.T) { 123 | p := NewGame(`Ka1,a6,e7`, `M,Ke8,Ra8,a7`).start() 124 | gen := NewMoveGen(p).generateQuiets() 125 | expect.Eq(t, gen.allMoves(), `[Ra8-b8 Ra8-c8 Ra8-d8 Ke8-d7 Ke8-f7 Ke8-d8 Ke8-f8]`) 126 | } 127 | 128 | // Move legality. 129 | func TestGenQuiets070(t *testing.T) { 130 | p := NewGame(`Ka1,Bb1,Nb2`, `Ka8,Rd1,Bd4,a4,b3,c4,e4`).start() // Bb1,Nb2 both pinned, a2 under attack. 131 | gen := NewMoveGen(p).generateQuiets() 132 | expect.Eq(t, gen.allMoves(), `[Nb2-d3 Bb1-a2 Bb1-c2 Bb1-d3 Ka1-a2]`) 133 | } 134 | 135 | func TestGenQuiets071(t *testing.T) { 136 | p := NewGame(`Ka1,Rd8,Bd5,a5,b6,c5,e5`, `M,Ka8,Bb8,Nb7`).start() // Bb8,Nb7 both pinned, a7 under attack. 137 | gen := NewMoveGen(p).generateQuiets() 138 | expect.Eq(t, gen.allMoves(), `[Nb7-d6 Bb8-d6 Bb8-a7 Bb8-c7 Ka8-a7]`) 139 | } 140 | -------------------------------------------------------------------------------- /generate_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | // Default move ordering. 14 | func TestGenerate010(t *testing.T) { 15 | game := NewGame(`Ka1,a2,b3,c4,d2,e6,f5,g4,h3`, `Kc1`) 16 | gen := NewMoveGen(game.start()).generateMoves().rank(Move(0)) 17 | 18 | expect.Eq(t, gen.allMoves(), `[a2-a3 a2-a4 d2-d3 d2-d4 b3-b4 h3-h4 c4-c5 g4-g5 f5-f6 e6-e7 Ka1-b1 Ka1-b2]`) 19 | } 20 | 21 | // LVA/MVV capture ordering. 22 | func TestGenerate110(t *testing.T) { 23 | game := NewGame(`Kd4,e4,Nf4,Bc4,Ra5,Qh5`, `Kd8,Qd5`) 24 | gen := NewMoveGen(game.start()).generateCaptures().rank(Move(0)) 25 | 26 | expect.Eq(t, gen.allMoves(), `[e4xd5 Nf4xd5 Bc4xd5 Ra5xd5 Qh5xd5 Kd4xd5]`) 27 | } 28 | 29 | func TestGenerate120(t *testing.T) { 30 | game := NewGame(`Kd4,e4,Nf4,Bc4,Ra5,Qh5`, `Kd8,Qd5,Rf5`) 31 | gen := NewMoveGen(game.start()).generateCaptures().rank(Move(0)) 32 | 33 | expect.Eq(t, gen.allMoves(), `[e4xd5 Nf4xd5 Bc4xd5 Ra5xd5 Kd4xd5 e4xf5 Qh5xf5]`) 34 | } 35 | 36 | func TestGenerate130(t *testing.T) { 37 | game := NewGame(`Kd4,e4,Nf4,Bc4,Ra5,Qh5`, `Kd8,Qd5,Rf5,Bg6`) 38 | gen := NewMoveGen(game.start()).generateCaptures().rank(Move(0)) 39 | 40 | expect.Eq(t, gen.allMoves(), `[e4xd5 Nf4xd5 Bc4xd5 Ra5xd5 Kd4xd5 e4xf5 Qh5xf5 Nf4xg6 Qh5xg6]`) 41 | } 42 | 43 | func TestGenerate140(t *testing.T) { 44 | game := NewGame(`Kd4,e4,Nf4,Bc4,Ra5,Qh5`, `Kd8,Qd5,Rf5,Bg6,Nh3`) 45 | gen := NewMoveGen(game.start()).generateCaptures().rank(Move(0)) 46 | 47 | expect.Eq(t, gen.allMoves(), `[e4xd5 Nf4xd5 Bc4xd5 Ra5xd5 Kd4xd5 e4xf5 Qh5xf5 Nf4xg6 Qh5xg6 Nf4xh3 Qh5xh3]`) 48 | } 49 | 50 | func TestGenerate150(t *testing.T) { 51 | game := NewGame(`Kd4,e4,Nf4,Bc4,Ra5,Qh5`, `Kd8,Qd5,Rf5,Bg6,Nh3,e2`) 52 | gen := NewMoveGen(game.start()).generateCaptures().rank(Move(0)) 53 | 54 | expect.Eq(t, gen.allMoves(), `[e4xd5 Nf4xd5 Bc4xd5 Ra5xd5 Kd4xd5 e4xf5 Qh5xf5 Nf4xg6 Qh5xg6 Nf4xh3 Qh5xh3 Nf4xe2 Bc4xe2 Qh5xe2]`) 55 | } 56 | 57 | // Vaditaing generated moves. 58 | func TestGenerate200(t *testing.T) { 59 | p := NewGame(`Ke1,Qe2,d2`, `Ke8,e4`).start() 60 | p = p.makeMove(NewEnpassant(p, D2, D4)) 61 | 62 | // No e4xd3 en-passant capture. 63 | black := NewMoveGen(p).generateMoves().validOnly() 64 | expect.Eq(t, black.allMoves(), `[e4-e3 Ke8-d7 Ke8-e7 Ke8-f7 Ke8-d8 Ke8-f8]`) 65 | } 66 | 67 | func TestGenerate210(t *testing.T) { 68 | p := NewGame(`Ke1,Qg2,d2`, `Ka8,e4`).start() 69 | p = p.makeMove(NewEnpassant(p, D2, D4)) 70 | 71 | // Neither e4-e3 nor e4xd3 en-passant capture. 72 | black := NewMoveGen(p).generateMoves().validOnly() 73 | expect.Eq(t, black.allMoves(), `[Ka8-a7 Ka8-b7 Ka8-b8]`) 74 | } 75 | -------------------------------------------------------------------------------- /logo/README: -------------------------------------------------------------------------------- 1 | Donna logo images were graciously provided by computer chess enthusiasts 2 | Norbert Raimund Leisner (Germany) and Graham Banks (New Zealand). 3 | -------------------------------------------------------------------------------- /logo/donna1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldv/donna/1d7facbce92a516df243d82fba66cc83e4152267/logo/donna1.bmp -------------------------------------------------------------------------------- /logo/donna2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldv/donna/1d7facbce92a516df243d82fba66cc83e4152267/logo/donna2.bmp -------------------------------------------------------------------------------- /logo/donna3.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldv/donna/1d7facbce92a516df243d82fba66cc83e4152267/logo/donna3.bmp -------------------------------------------------------------------------------- /logo/donna4.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldv/donna/1d7facbce92a516df243d82fba66cc83e4152267/logo/donna4.bmp -------------------------------------------------------------------------------- /piece.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | type Piece int 12 | 13 | const ( 14 | White = iota // 0 15 | Black // 1 16 | Pawn // 2 17 | BlackPawn // 3 18 | Knight // 4 19 | BlackKnight // 5 20 | Bishop // 6 21 | BlackBishop // 7 22 | Rook // 8 23 | BlackRook // 9 24 | Queen // 10 25 | BlackQueen // 11 26 | King // 12 27 | BlackKing // 13 28 | ) 29 | 30 | func king(color int) Piece { 31 | return Piece(color | King) 32 | } 33 | 34 | func queen(color int) Piece { 35 | return Piece(color | Queen) 36 | } 37 | 38 | func rook(color int) Piece { 39 | return Piece(color | Rook) 40 | } 41 | 42 | func bishop(color int) Piece { 43 | return Piece(color | Bishop) 44 | } 45 | 46 | func knight(color int) Piece { 47 | return Piece(color | Knight) 48 | } 49 | 50 | func pawn(color int) Piece { 51 | return Piece(color | Pawn) 52 | } 53 | 54 | func (p Piece) none() bool { 55 | return p == Piece(0) 56 | } 57 | 58 | func (p Piece) some() bool { 59 | return p != Piece(0) 60 | } 61 | 62 | func (p Piece) polyglot(square int) uint64 { 63 | return polyglotRandom[polyglotBase[p] + square] 64 | } 65 | 66 | func (p Piece) color() int { 67 | return int(p) & 1 68 | } 69 | 70 | func (p Piece) id() int { 71 | return int(p) >> 1 72 | } 73 | 74 | func (p Piece) kind() int { 75 | return int(p) & 0xFE 76 | } 77 | 78 | func (p Piece) value() int { 79 | return pieceValue[p.id()] 80 | } 81 | 82 | func (p Piece) isWhite() bool { 83 | return p & 1 == 0 84 | } 85 | 86 | func (p Piece) isBlack() bool { 87 | return p & 1 == 1 88 | } 89 | 90 | func (p Piece) isKing() bool { 91 | return p & 0xFE == King 92 | } 93 | 94 | func (p Piece) isQueen() bool { 95 | return p & 0xFE == Queen 96 | } 97 | 98 | func (p Piece) isRook() bool { 99 | return p & 0xFE == Rook 100 | } 101 | 102 | func (p Piece) isBishop() bool { 103 | return p & 0xFE == Bishop 104 | } 105 | 106 | func (p Piece) isKnight() bool { 107 | return p & 0xFE == Knight 108 | } 109 | 110 | func (p Piece) isPawn() bool { 111 | return p & 0xFE == Pawn 112 | } 113 | 114 | // Returns colorless ASCII code for the piece. 115 | func (p Piece) char() byte { 116 | return []byte{ 0, 0, 0, 0, 'N', 'N', 'B', 'B', 'R', 'R', 'Q', 'Q', 'K', 'K' }[p] 117 | } 118 | 119 | func (p Piece) String() string { 120 | plain := []string{ ` `, ` `, `P`, `p`, `N`, `n`, `B`, `b`, `R`, `r`, `Q`, `q`, `K`, `k` } 121 | fancy := []string{ ` `, ` `, "\u2659", "\u265F", "\u2658", "\u265E", "\u2657", "\u265D", "\u2656", "\u265C", "\u2655", "\u265B", "\u2654", "\u265A" } 122 | 123 | if engine.fancy { 124 | return fancy[p] 125 | } 126 | return plain[p] 127 | } 128 | -------------------------------------------------------------------------------- /position_cache.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import `unsafe` 12 | 13 | const ( 14 | cacheNone = uint8(0) 15 | cacheAlpha = uint8(1) // Upper bound. 16 | cacheBeta = uint8(2) // Lower bound. 17 | cacheExact = uint8(cacheAlpha | cacheBeta) 18 | cacheEntrySize = int(unsafe.Sizeof(CacheEntry{})) 19 | ) 20 | 21 | type CacheEntry struct { 22 | id uint16 // 2 23 | move Move // +4 = 6 24 | xscore int16 // +2 = 8 25 | xdepth int8 // +1 = 9 26 | flags uint8 // +1 = 10 27 | padding uint8 // +1 = 11 28 | } 29 | 30 | type Cache []CacheEntry 31 | 32 | func cacheUsage() (hits int) { 33 | for i := 0; i < len(game.cache); i++ { 34 | if game.cache[i].id != uint16(0) { 35 | hits++ 36 | } 37 | } 38 | 39 | return hits 40 | } 41 | 42 | func (ce *CacheEntry) score(ply int) int { 43 | score := int(ce.xscore) 44 | 45 | if score >= matingIn(MaxPly) { 46 | return score - ply 47 | } else if score <= matedIn(MaxPly) { 48 | return score + ply 49 | } 50 | 51 | return score 52 | } 53 | 54 | func (ce *CacheEntry) depth() int { 55 | return int(ce.xdepth) 56 | } 57 | 58 | func (ce *CacheEntry) token() uint8 { 59 | return ce.flags & 0xFC 60 | } 61 | 62 | func (ce *CacheEntry) bounds() uint8 { 63 | return ce.flags & 3 64 | } 65 | 66 | // Creates new or resets existing game cache (aka transposition table). 67 | func NewCache(megaBytes float64) Cache { 68 | if megaBytes > 0.0 { 69 | cacheSize := int(1024 * 1024 * megaBytes) / cacheEntrySize 70 | // Cache size has changed. 71 | if existing := len(game.cache); cacheSize != existing { 72 | if existing == 0 { 73 | // Create brand new zero-initialized cache. 74 | return make(Cache, cacheSize) 75 | } else { 76 | // Reallocate existing cache (shrink or expand). 77 | game.cache = append([]CacheEntry{}, game.cache[:cacheSize]...) 78 | } 79 | } 80 | // Make sure the cache is all clear. 81 | for i := 0; i < len(game.cache); i++ { 82 | game.cache[i] = CacheEntry{} 83 | } 84 | return game.cache 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func (p *Position) cache(move Move, score, depth, ply int, flags uint8) *Position { 91 | if cacheSize := len(game.cache); cacheSize > 0 { 92 | index := p.id & uint64(cacheSize - 1) 93 | entry := &game.cache[index] 94 | 95 | if depth > entry.depth() || game.token != entry.token() { 96 | if score >= matingIn(MaxPly) { 97 | entry.xscore = int16(score + ply) 98 | } else if score <= matedIn(MaxPly) { 99 | entry.xscore = int16(score - ply) 100 | } else { 101 | entry.xscore = int16(score) 102 | } 103 | id := uint16(p.id >> 48) 104 | if move.some() || id != entry.id { 105 | entry.move = move 106 | } 107 | entry.xdepth = int8(depth) 108 | entry.flags = flags | game.token 109 | entry.id = id 110 | } 111 | } 112 | 113 | return p 114 | } 115 | 116 | func (p *Position) probeCache() *CacheEntry { 117 | if cacheSize := len(game.cache); cacheSize > 0 { 118 | index := p.id & uint64(cacheSize - 1) 119 | if entry := &game.cache[index]; entry.id == uint16(p.id >> 48) { 120 | return entry 121 | } 122 | } 123 | 124 | return nil 125 | } 126 | -------------------------------------------------------------------------------- /position_cache_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | func TestCache000(t *testing.T) { 14 | engine.cacheSize = 0.5 15 | p := NewGame().start() 16 | move := NewMove(p, E2, E4) 17 | p = p.makeMove(move).cache(move, 42, 1, 0, cacheExact) 18 | 19 | cached := p.probeCache() 20 | expect.Eq(t, cached.move, move) 21 | expect.Eq(t, cached.xscore, int16(42)) 22 | expect.Eq(t, cached.xdepth, int8(1)) 23 | expect.Eq(t, cached.flags, uint8(cacheExact | game.token)) 24 | expect.Eq(t, cached.id, uint16(p.id >> 48)) 25 | } 26 | -------------------------------------------------------------------------------- /position_exchange.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | var exchangeScores = []int{ 12 | 0, 0, // Zero score for non-capture moves. 13 | valuePawn.midgame, valuePawn.midgame, // Pawn/BlackPawn captures. 14 | valueKnight.midgame, valueKnight.midgame, // Knight/BlackKinght captures. 15 | valueBishop.midgame, valueBishop.midgame, // Bishop/BlackBishop captures. 16 | valueRook.midgame, valueRook.midgame, // Rook/BlackRook captures. 17 | valueQueen.midgame, valueQueen.midgame, // Queen/BlackQueen captures. 18 | valueQueen.midgame * 8, valueQueen.midgame * 8, // King/BlackKing specials. 19 | } 20 | 21 | // Static exchange evaluation. 22 | func (p *Position) exchange(move Move) int { 23 | from, to, piece, capture := move.split() 24 | 25 | score := exchangeScores[capture] 26 | if promo := move.promo(); promo.some() { 27 | score += exchangeScores[promo] - exchangeScores[Pawn] 28 | piece = promo 29 | } 30 | 31 | board := p.board ^ bit[from] 32 | return -p.exchangeScore(piece.color()^1, to, -score, exchangeScores[piece], board) 33 | } 34 | 35 | // Recursive helper method for the static exchange evaluation. 36 | func (p *Position) exchangeScore(color int, to, score, extra int, board Bitmask) int { 37 | attackers := p.attackers(color, to, board) & board 38 | if attackers.empty() { 39 | return score 40 | } 41 | 42 | from, best := 0, Checkmate 43 | for bm := attackers; bm.any(); bm = bm.pop() { 44 | square := bm.first() 45 | if index := p.pieces[square]; exchangeScores[index] < best { 46 | from = square 47 | best = exchangeScores[index] 48 | } 49 | } 50 | 51 | if best != Checkmate { 52 | board ^= bit[from] 53 | } 54 | 55 | return max(score, -p.exchangeScore(color^1, to, -(score + extra), best, board)) 56 | } 57 | -------------------------------------------------------------------------------- /position_exchange_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | func TestExchange000(t *testing.T) { // c4,d4 vs. d5,e6 (protected). 14 | p := NewGame(`Kg1,c4,d4`, `Kg8,d5,e6`).start() 15 | exchange := p.exchange(NewMove(p, C4, D5)) 16 | expect.Eq(t, exchange, 0) 17 | } 18 | 19 | func TestExchange010(t *testing.T) { // c4,d4,e4 vs. c6,d5,e6 (protected). 20 | p := NewGame(`Kg1,Qb3,Nc3,a2,b2,c4,d4,e4,f2,g2,h2`, `Kg8,Qd8,Nf6,a7,b6,c6,d5,e6,f7,g7,h7`).start() 21 | exchange := p.exchange(NewMove(p, E4, D5)) 22 | expect.Eq(t, exchange, 0) 23 | } 24 | 25 | func TestExchange020(t *testing.T) { // c4,d4,e4 vs. c6,d5,e6 (white wins a pawn). 26 | p := NewGame(`Kg1,Qb3,Nc3,Nf3,a2,b2,c4,d4,e4,f2,g2,h2`, `Kg8,Qd8,Nd7,Nf6,a7,b6,c6,d5,e6,f7,g7,h7`).start() 27 | exchange := p.exchange(NewMove(p, E4, D5)) 28 | expect.Eq(t, exchange, valuePawn.midgame) 29 | } 30 | -------------------------------------------------------------------------------- /position_moves.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | func (p *Position) movePiece(piece Piece, from, to int) *Position { 12 | p.pieces[from], p.pieces[to] = 0, piece 13 | p.outposts[piece] ^= bit[from] | bit[to] 14 | p.outposts[piece.color()] ^= bit[from] | bit[to] 15 | 16 | // Update position's hash values. 17 | random := piece.polyglot(from) ^ piece.polyglot(to) 18 | p.id ^= random 19 | if piece.isPawn() { 20 | p.pawnId ^= random 21 | } 22 | 23 | // Update positional score. 24 | p.tally.sub(pst[piece][from]).add(pst[piece][to]) 25 | 26 | return p 27 | } 28 | 29 | func (p *Position) promotePawn(pawn Piece, from, to int, promo Piece) *Position { 30 | p.pieces[from], p.pieces[to] = 0, promo 31 | p.outposts[pawn] ^= bit[from] 32 | p.outposts[promo] ^= bit[to] 33 | p.outposts[pawn.color()] ^= bit[from] | bit[to] 34 | 35 | // Update position's hash values and material balance. 36 | random := pawn.polyglot(from) 37 | p.id ^= random ^ promo.polyglot(to) 38 | p.pawnId ^= random 39 | p.balance += materialBalance[promo] - materialBalance[pawn] 40 | 41 | // Update positional score. 42 | p.tally.sub(pst[pawn][from]).add(pst[promo][to]) 43 | 44 | return p 45 | } 46 | 47 | func (p *Position) capturePiece(capture Piece, from, to int) *Position { 48 | p.outposts[capture] ^= bit[to] 49 | p.outposts[capture.color()] ^= bit[to] 50 | 51 | // Update position's hash values and material balance. 52 | random := capture.polyglot(to) 53 | p.id ^= random 54 | if capture.isPawn() { 55 | p.pawnId ^= random 56 | } 57 | p.balance -= materialBalance[capture] 58 | 59 | // Update positional score. 60 | p.tally.sub(pst[capture][to]) 61 | 62 | return p 63 | } 64 | 65 | func (p *Position) captureEnpassant(capture Piece, from, to int) *Position { 66 | enpassant := to - up[capture.color()^1] 67 | 68 | p.pieces[enpassant] = 0 69 | p.outposts[capture] ^= bit[enpassant] 70 | p.outposts[capture.color()] ^= bit[enpassant] 71 | 72 | // Update position's hash values and material balance. 73 | random := capture.polyglot(enpassant) 74 | p.id ^= random 75 | p.pawnId ^= random 76 | p.balance -= materialBalance[capture] 77 | 78 | // Update positional score. 79 | p.tally.sub(pst[capture][enpassant]) 80 | 81 | return p 82 | } 83 | 84 | func (p *Position) makeMove(move Move) *Position { 85 | color := move.color() 86 | from, to, piece, capture := move.split() 87 | 88 | // Copy over the contents of previous tree node to the current one. 89 | node++ 90 | tree[node] = *p // => tree[node] = tree[node - 1] 91 | pp := &tree[node] 92 | 93 | pp.enpassant, pp.reversible = 0, true 94 | 95 | if capture != 0 && (to == 0 || to != p.enpassant) { 96 | pp.count50, pp.reversible = 0, false 97 | pp.capturePiece(capture, from, to) 98 | } 99 | 100 | if piece.isPawn() { 101 | pp.count50, pp.reversible = 0, false 102 | if to != 0 && to == p.enpassant { 103 | pp.captureEnpassant(pawn(color^1), from, to) 104 | pp.id ^= hashEnpassant[p.enpassant & 7] // p.enpassant column. 105 | } 106 | if promo := move.promo(); promo != 0 { 107 | pp.promotePawn(piece, from, to, promo) 108 | } else { 109 | pp.movePiece(piece, from, to) 110 | if move.isEnpassant() { 111 | pp.enpassant = from + up[color] // Save the en-passant square. 112 | pp.id ^= hashEnpassant[pp.enpassant & 7] 113 | } 114 | } 115 | } else if piece.isKing() { 116 | pp.movePiece(piece, from, to) 117 | pp.count50++ 118 | pp.king[color] = to 119 | if move.isCastle() { 120 | pp.reversible = false 121 | if to == from + 2 { 122 | pp.movePiece(rook(color), to + 1, to - 1) 123 | } else if to == from - 2 { 124 | pp.movePiece(rook(color), to - 2, to + 1) 125 | } 126 | } 127 | } else { 128 | pp.movePiece(piece, from, to) 129 | pp.count50++ 130 | } 131 | 132 | // Set up the board bitmask, update castle rights, finish off incremental 133 | // hash value, and flip the color. 134 | pp.board = pp.outposts[White] | pp.outposts[Black] 135 | pp.castles &= castleRights[from] & castleRights[to] 136 | pp.id ^= hashCastle[p.castles] ^ hashCastle[pp.castles] 137 | pp.id ^= polyglotRandomWhite 138 | pp.color ^= 1 // <-- Flip side to move. 139 | pp.score = Unknown 140 | 141 | return &tree[node] // pp 142 | } 143 | 144 | // Makes "null" move by copying over previous node position (i.e. preserving all pieces 145 | // intact) and flipping the color. 146 | func (p *Position) makeNullMove() *Position { 147 | node++ 148 | tree[node] = *p // => tree[node] = tree[node - 1] 149 | pp := &tree[node] 150 | 151 | // Flipping side to move obviously invalidates the enpassant square. 152 | if pp.enpassant != 0 { 153 | pp.id ^= hashEnpassant[pp.enpassant & 7] 154 | pp.enpassant = 0 155 | } 156 | pp.id ^= polyglotRandomWhite 157 | pp.color ^= 1 // <-- Flip side to move. 158 | pp.count50++ 159 | 160 | return &tree[node] // pp 161 | } 162 | 163 | // Restores previous position effectively taking back the last move made. 164 | func (p *Position) undoLastMove() *Position { 165 | if node > 0 { 166 | node-- 167 | } 168 | return &tree[node] 169 | } 170 | 171 | func (p *Position) isInCheck(color int) bool { 172 | return p.isAttacked(color ^ 1, p.king[color]) 173 | } 174 | 175 | func (p *Position) isNull() bool { 176 | return node > 0 && tree[node].board == tree[node-1].board 177 | } 178 | 179 | func (p *Position) fifty() bool { 180 | return p.count50 >= 100 181 | } 182 | 183 | func (p *Position) repetition() bool { 184 | if !p.reversible || node < 1 { 185 | return false 186 | } 187 | 188 | for previous := node - 1; previous >= 0; previous-- { 189 | if !tree[previous].reversible { 190 | return false 191 | } 192 | if tree[previous].id == p.id { 193 | return true 194 | } 195 | } 196 | 197 | return false 198 | } 199 | 200 | func (p *Position) thirdRepetition() bool { 201 | if !p.reversible || node < 4 { 202 | return false 203 | } 204 | 205 | for previous, repetitions := node - 2, 1; previous >= 0; previous -= 2 { 206 | if !tree[previous].reversible || !tree[previous + 1].reversible { 207 | return false 208 | } 209 | if tree[previous].id == p.id { 210 | repetitions++ 211 | if repetitions == 3 { 212 | return true 213 | } 214 | } 215 | } 216 | 217 | return false 218 | } 219 | 220 | // Returns a pair of booleans that indicate whether given side is allowed to 221 | // castle kingside and queenside. 222 | func (p *Position) canCastle(color int) (kingside, queenside bool) { 223 | 224 | // Start off with simple checks. 225 | kingside = (p.castles & castleKingside[color] != 0) && (gapKing[color] & p.board).empty() 226 | queenside = (p.castles & castleQueenside[color] != 0) && (gapQueen[color] & p.board).empty() 227 | 228 | // If it still looks like the castles are possible perform more expensive 229 | // final check. 230 | if kingside || queenside { 231 | attacks := p.allAttacks(color^1) 232 | kingside = kingside && (castleKing[color] & attacks).empty() 233 | queenside = queenside && (castleQueen[color] & attacks).empty() 234 | } 235 | 236 | return kingside, queenside 237 | } 238 | 239 | // Returns a bitmask of all pinned pieces preventing a check for the king on 240 | // given square. The color of the pieces match the color of the king. 241 | func (p *Position) pins(square int) (bitmask Bitmask) { 242 | our := p.pieces[square].color() 243 | their := our^1 244 | 245 | attackers := (p.outposts[bishop(their)] | p.outposts[queen(their)]) & bishopMagicMoves[square][0] 246 | attackers |= (p.outposts[rook(their)] | p.outposts[queen(their)]) & rookMagicMoves[square][0] 247 | 248 | for bm := attackers; bm.any(); bm = bm.pop() { 249 | attackSquare := bm.first() 250 | blockers := maskBlock[square][attackSquare] & ^bit[attackSquare] & p.board 251 | 252 | if blockers.single() { 253 | bitmask |= blockers & p.outposts[our] // Only friendly pieces are pinned. 254 | } 255 | } 256 | 257 | return bitmask 258 | } 259 | -------------------------------------------------------------------------------- /position_targets.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | // Returns a bitmask of possible Bishop moves from the given square. 12 | func (p *Position) bishopMoves(square int) Bitmask { 13 | return p.bishopMovesAt(square, p.board) 14 | } 15 | 16 | // Ditto for Rook. 17 | func (p *Position) rookMoves(square int) Bitmask { 18 | return p.rookMovesAt(square, p.board) 19 | } 20 | 21 | // Ditto for Queen. 22 | func (p *Position) queenMoves(square int) Bitmask { 23 | return p.bishopMovesAt(square, p.board) | p.rookMovesAt(square, p.board) 24 | } 25 | 26 | // Returns a bitmask of possible Bishop moves from the given square whereas 27 | // other pieces on the board are represented by the explicit parameter. 28 | func (p *Position) bishopMovesAt(square int, board Bitmask) Bitmask { 29 | magic := ((bishopMagic[square].mask & board) * bishopMagic[square].magic) >> 55 30 | return bishopMagicMoves[square][magic] 31 | } 32 | 33 | // Ditto for Rook. 34 | func (p *Position) rookMovesAt(square int, board Bitmask) Bitmask { 35 | magic := ((rookMagic[square].mask & board) * rookMagic[square].magic) >> 52 36 | return rookMagicMoves[square][magic] 37 | } 38 | 39 | // Ditto for Queen. 40 | func (p *Position) queenMovesAt(square int, board Bitmask) Bitmask { 41 | return p.bishopMovesAt(square, board) | p.rookMovesAt(square, board) 42 | } 43 | 44 | func (p *Position) targets(square int) (bitmask Bitmask) { 45 | piece := p.pieces[square] 46 | color := piece.color() 47 | if piece.isPawn() { 48 | // Start with one square push, then try the second square. 49 | empty := ^p.board 50 | bitmask = bit[square].up(color) & empty 51 | bitmask |= bitmask.up(color) & empty & maskRank[A4H4 + color] 52 | bitmask |= pawnAttacks[color][square] & p.outposts[color^1] 53 | 54 | // If the last move set the en-passant square and it is diagonally adjacent 55 | // to the current pawn, then add en-passant to the pawn's attack targets. 56 | if p.enpassant != 0 && maskPawn[color][p.enpassant].on(square) { 57 | bitmask.set(p.enpassant) 58 | } 59 | } else { 60 | bitmask = p.attacksFor(square, piece) & ^p.outposts[color] 61 | } 62 | 63 | return bitmask 64 | } 65 | 66 | func (p *Position) attacks(square int) Bitmask { 67 | return p.attacksFor(square, p.pieces[square]) 68 | } 69 | 70 | func (p *Position) attacksFor(square int, piece Piece) (bitmask Bitmask) { 71 | switch piece.kind() { 72 | case Pawn: 73 | return pawnAttacks[piece.color()][square] 74 | case Knight: 75 | return knightMoves[square] 76 | case Bishop: 77 | return p.bishopMoves(square) 78 | case Rook: 79 | return p.rookMoves(square) 80 | case Queen: 81 | return p.queenMoves(square) 82 | case King: 83 | return kingMoves[square] 84 | } 85 | 86 | return bitmask 87 | } 88 | 89 | func (p *Position) xrayAttacks(square int) Bitmask { 90 | return p.xrayAttacksFor(square, p.pieces[square]) 91 | } 92 | 93 | func (p *Position) xrayAttacksFor(square int, piece Piece) (bitmask Bitmask) { 94 | switch kind, color := piece.kind(), piece.color(); kind { 95 | case Bishop: 96 | board := p.board ^ p.outposts[queen(color)] 97 | return p.bishopMovesAt(square, board) 98 | case Rook: 99 | board := p.board ^ p.outposts[rook(color)] ^ p.outposts[queen(color)] 100 | return p.rookMovesAt(square, board) 101 | } 102 | 103 | return p.attacksFor(square, piece) 104 | } 105 | 106 | func (p *Position) allAttacks(color int) (bitmask Bitmask) { 107 | bitmask = p.pawnAttacks(color) | p.knightAttacks(color) | p.kingAttacks(color) 108 | 109 | for bm := p.outposts[bishop(color)] | p.outposts[queen(color)]; bm.any(); bm = bm.pop() { 110 | bitmask |= p.bishopMoves(bm.first()) 111 | } 112 | 113 | for bm := p.outposts[rook(color)] | p.outposts[queen(color)]; bm.any(); bm = bm.pop() { 114 | bitmask |= p.rookMoves(bm.first()) 115 | } 116 | 117 | return bitmask 118 | } 119 | 120 | // Returns a bitmask of pieces that attack given square. The resulting bitmask 121 | // only counts pieces of requested color. 122 | // 123 | // This method is used in static exchange evaluation so instead of using current 124 | // board bitmask (p.board) we pass the one that gets continuously updated during 125 | // the evaluation. 126 | func (p *Position) attackers(color int, square int, board Bitmask) (bitmask Bitmask) { 127 | bitmask = knightMoves[square] & p.outposts[knight(color)] 128 | bitmask |= maskPawn[color][square] & p.outposts[pawn(color)] 129 | bitmask |= kingMoves[square] & p.outposts[king(color)] 130 | bitmask |= p.rookMovesAt(square, board) & (p.outposts[rook(color)] | p.outposts[queen(color)]) 131 | bitmask |= p.bishopMovesAt(square, board) & (p.outposts[bishop(color)] | p.outposts[queen(color)]) 132 | 133 | return bitmask 134 | } 135 | 136 | func (p *Position) isAttacked(color int, square int) bool { 137 | return (knightMoves[square] & p.outposts[knight(color)]).any() || 138 | (maskPawn[color][square] & p.outposts[pawn(color)]).any() || 139 | (kingMoves[square] & p.outposts[king(color)]).any() || 140 | (p.rookMoves(square) & (p.outposts[rook(color)] | p.outposts[queen(color)])).any() || 141 | (p.bishopMoves(square) & (p.outposts[bishop(color)] | p.outposts[queen(color)])).any() 142 | } 143 | 144 | func (p *Position) pawnTargets(color int, pawns Bitmask) Bitmask { 145 | if color == White { 146 | return ((pawns & ^maskFile[0]) << 7) | ((pawns & ^maskFile[7]) << 9) 147 | } 148 | 149 | return ((pawns & ^maskFile[0]) >> 9) | ((pawns & ^maskFile[7]) >> 7) 150 | } 151 | 152 | func (p *Position) pawnAttacks(color int) Bitmask { 153 | return p.pawnTargets(color, p.outposts[pawn(color)]) 154 | } 155 | 156 | func (p *Position) knightAttacks(color int) (bitmask Bitmask) { 157 | for bm := p.outposts[knight(color)]; bm.any(); bm = bm.pop() { 158 | bitmask |= knightMoves[bm.first()] 159 | } 160 | 161 | return bitmask 162 | } 163 | 164 | func (p *Position) bishopAttacks(color int) (bitmask Bitmask) { 165 | for bm := p.outposts[bishop(color)]; bm.any(); bm = bm.pop() { 166 | bitmask |= p.bishopMoves(bm.first()) 167 | } 168 | 169 | return bitmask 170 | } 171 | 172 | func (p *Position) rookAttacks(color int) (bitmask Bitmask) { 173 | for bm := p.outposts[rook(color)]; bm.any(); bm = bm.pop() { 174 | bitmask |= p.rookMoves(bm.first()) 175 | } 176 | 177 | return bitmask 178 | } 179 | 180 | func (p *Position) queenAttacks(color int) (bitmask Bitmask) { 181 | for bm := p.outposts[queen(color)]; bm.any(); bm = bm.pop() { 182 | bitmask |= p.queenMoves(bm.first()) 183 | } 184 | 185 | return bitmask 186 | } 187 | 188 | func (p *Position) kingAttacks(color int) Bitmask { 189 | return kingMoves[p.king[color]] 190 | } 191 | 192 | func (p *Position) knightAttacksAt(square int, color int) (bitmask Bitmask) { 193 | return knightMoves[square] & ^p.outposts[color] 194 | } 195 | 196 | func (p *Position) bishopAttacksAt(square int, color int) (bitmask Bitmask) { 197 | return p.bishopMoves(square) & ^p.outposts[color] 198 | } 199 | 200 | func (p *Position) rookAttacksAt(square int, color int) (bitmask Bitmask) { 201 | return p.rookMoves(square) & ^p.outposts[color] 202 | } 203 | 204 | func (p *Position) queenAttacksAt(square int, color int) (bitmask Bitmask) { 205 | return p.queenMoves(square) & ^p.outposts[color] 206 | } 207 | 208 | func (p *Position) kingAttacksAt(square int, color int) Bitmask { 209 | return kingMoves[square] & ^p.outposts[color] 210 | } 211 | 212 | -------------------------------------------------------------------------------- /position_targets_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | // Pawn targets. 14 | func TestTargets000(t *testing.T) { 15 | game := NewGame(`Kd1,e2`, `Ke8,d4`) 16 | position := game.start() 17 | 18 | expect.Eq(t, position.targets(E2), bit[E3]|bit[E4]) // e3,e4 19 | expect.Eq(t, position.targets(D4), bit[D3]) // d3 20 | } 21 | 22 | func TestTargets010(t *testing.T) { 23 | game := NewGame(`Kd1,e2,d3`, `Ke8,d4,e4`) 24 | position := game.start() 25 | 26 | expect.Eq(t, position.targets(E2), bit[E3]) // e3 27 | expect.Eq(t, position.targets(D3), bit[E4]) // e4 28 | expect.Eq(t, position.targets(D4), Bitmask(0)) // None. 29 | expect.Eq(t, position.targets(E4), bit[D3]|bit[E3]) // d3,e3 30 | } 31 | 32 | func TestTargets020(t *testing.T) { 33 | game := NewGame(`Kd1,e2`, `Ke8,d3,f3`) 34 | position := game.start() 35 | 36 | expect.Eq(t, position.targets(E2), bit[D3]|bit[E3]|bit[E4]|bit[F3]) // d3,e3,e4,f3 37 | expect.Eq(t, position.targets(D3), bit[E2]|bit[D2]) // e2,d2 38 | expect.Eq(t, position.targets(F3), bit[E2]|bit[F2]) // e2,f2 39 | } 40 | 41 | func TestTargets030(t *testing.T) { 42 | game := NewGame(`Kd1,e2`, `Ke8,d4`) 43 | position := game.start() 44 | position = position.makeMove(NewEnpassant(position, E2, E4)) // Creates en-passant on e3. 45 | 46 | expect.Eq(t, position.targets(E4), bit[E5]) // e5 47 | expect.Eq(t, position.targets(D4), bit[D3]|bit[E3]) // d3, e3 (en-passant). 48 | } 49 | 50 | // Pawn attacks. 51 | func TestTargets040(t *testing.T) { 52 | game := NewGame(`Ke1,a3,b3,c3,d3,e3,f3,g3,h3`, `Ke8,a6,b6,c6,d6,e6,f6,g6,h6`) 53 | position := game.start() 54 | expect.Eq(t, position.pawnAttacks(White), bit[A4]|bit[B4]|bit[C4]|bit[D4]|bit[E4]|bit[F4]|bit[G4]|bit[H4]) 55 | expect.Eq(t, position.pawnAttacks(Black), bit[A5]|bit[B5]|bit[C5]|bit[D5]|bit[E5]|bit[F5]|bit[G5]|bit[H5]) 56 | } 57 | -------------------------------------------------------------------------------- /position_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | // Initial position: castles, no en-passant. 14 | func TestPosition000(t *testing.T) { 15 | p := NewGame(`rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1`).start() 16 | expect.Eq(t, p.id, uint64(0x463B96181691FC9C)) 17 | expect.Eq(t, p.pawnId, uint64(0x37FC40DA841E1692)) 18 | expect.Eq(t, p.color, White) 19 | expect.Eq(t, p.enpassant, 0) 20 | expect.Eq(t, p.king[White], E1) 21 | expect.Eq(t, p.king[Black], E8) 22 | expect.Eq(t, p.castles, uint(0x0F)) 23 | expect.Eq(t, p.outposts[Pawn], bit[A2]|bit[B2]|bit[C2]|bit[D2]|bit[E2]|bit[F2]|bit[G2]|bit[H2]) 24 | expect.Eq(t, p.outposts[Knight], bit[B1]|bit[G1]) 25 | expect.Eq(t, p.outposts[Bishop], bit[C1]|bit[F1]) 26 | expect.Eq(t, p.outposts[Rook], bit[A1]|bit[H1]) 27 | expect.Eq(t, p.outposts[Queen], bit[D1]) 28 | expect.Eq(t, p.outposts[King], bit[E1]) 29 | expect.Eq(t, p.outposts[BlackPawn], bit[A7]|bit[B7]|bit[C7]|bit[D7]|bit[E7]|bit[F7]|bit[G7]|bit[H7]) 30 | expect.Eq(t, p.outposts[BlackKnight], bit[B8]|bit[G8]) 31 | expect.Eq(t, p.outposts[BlackBishop], bit[C8]|bit[F8]) 32 | expect.Eq(t, p.outposts[BlackRook], bit[A8]|bit[H8]) 33 | expect.Eq(t, p.outposts[BlackQueen], bit[D8]) 34 | expect.Eq(t, p.outposts[BlackKing], bit[E8]) 35 | } 36 | 37 | // Castles, no en-passant. 38 | func TestPosition010(t *testing.T) { 39 | p := NewGame(`2r1kb1r/pp3ppp/2n1b3/1q1N2B1/1P2Q3/8/P4PPP/3RK1NR w Kk - 42 42`).start() 40 | expect.Eq(t, p.color, White) 41 | expect.Eq(t, p.castles, castleKingside[White] | castleKingside[Black]) 42 | expect.Eq(t, p.enpassant, 0) 43 | expect.Eq(t, p.king[White], E1) 44 | expect.Eq(t, p.king[Black], E8) 45 | expect.Eq(t, p.outposts[Pawn], bit[A2]|bit[B4]|bit[F2]|bit[G2]|bit[H2]) 46 | expect.Eq(t, p.outposts[Knight], bit[D5]|bit[G1]) 47 | expect.Eq(t, p.outposts[Bishop], bit[G5]) 48 | expect.Eq(t, p.outposts[Rook], bit[D1]|bit[H1]) 49 | expect.Eq(t, p.outposts[Queen], bit[E4]) 50 | expect.Eq(t, p.outposts[King], bit[E1]) 51 | expect.Eq(t, p.outposts[BlackPawn], bit[A7]|bit[B7]|bit[F7]|bit[G7]|bit[H7]) 52 | expect.Eq(t, p.outposts[BlackKnight], bit[C6]) 53 | expect.Eq(t, p.outposts[BlackBishop], bit[E6]|bit[F8]) 54 | expect.Eq(t, p.outposts[BlackRook], bit[C8]|bit[H8]) 55 | expect.Eq(t, p.outposts[BlackQueen], bit[B5]) 56 | expect.Eq(t, p.outposts[BlackKing], bit[E8]) 57 | } 58 | 59 | // No castles, en-passant. 60 | func TestPosition020(t *testing.T) { 61 | p := NewGame(`1rr2k2/p1q5/3p2Q1/3Pp2p/8/1P3P2/1KPRN3/8 w - e6 42 42`).start() 62 | expect.Eq(t, p.color, White) 63 | expect.Eq(t, p.castles, uint8(0)) 64 | expect.Eq(t, p.enpassant, E6) 65 | expect.Eq(t, p.king[White], B2) 66 | expect.Eq(t, p.king[Black], F8) 67 | expect.Eq(t, p.outposts[Pawn], bit[B3]|bit[C2]|bit[D5]|bit[F3]) 68 | expect.Eq(t, p.outposts[Knight], bit[E2]) 69 | expect.Eq(t, p.outposts[Bishop], Bitmask(0)) 70 | expect.Eq(t, p.outposts[Rook], bit[D2]) 71 | expect.Eq(t, p.outposts[Queen], bit[G6]) 72 | expect.Eq(t, p.outposts[King], bit[B2]) 73 | expect.Eq(t, p.outposts[BlackPawn], bit[A7]|bit[D6]|bit[E5]|bit[H5]) 74 | expect.Eq(t, p.outposts[BlackKnight], Bitmask(0)) 75 | expect.Eq(t, p.outposts[BlackBishop], Bitmask(0)) 76 | expect.Eq(t, p.outposts[BlackRook], bit[B8]|bit[C8]) 77 | expect.Eq(t, p.outposts[BlackQueen], bit[C7]) 78 | expect.Eq(t, p.outposts[BlackKing], bit[F8]) 79 | } 80 | 81 | //\\ Position to FEN tests. 82 | // Initial position: castles, no en-passant. 83 | func TestPosition100(t *testing.T) { 84 | p := NewGame(`rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1`).start() 85 | expect.Eq(t, p.fen(), `rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1`) 86 | } 87 | 88 | // Castles, no en-passant. 89 | func TestPosition110(t *testing.T) { 90 | p := NewGame(`2r1kb1r/pp3ppp/2n1b3/1q1N2B1/1P2Q3/8/P4PPP/3RK1NR w Kk - 42 42`).start() 91 | expect.Eq(t, p.fen(), `2r1kb1r/pp3ppp/2n1b3/1q1N2B1/1P2Q3/8/P4PPP/3RK1NR w Kk - 42 1`) 92 | } 93 | 94 | // No castles, en-passant. 95 | func TestPosition120(t *testing.T) { 96 | p := NewGame(`1rr2k2/p1q5/3p2Q1/3Pp2p/8/1P3P2/1KPRN3/8 w - e6 42 42`).start() 97 | expect.Eq(t, p.fen(), `1rr2k2/p1q5/3p2Q1/3Pp2p/8/1P3P2/1KPRN3/8 w - e6 42 1`) 98 | } 99 | 100 | //\\ Donna Chess Format (DCF) tests. 101 | // Initial position: castles, no en-passant. 102 | func TestPosition130(t *testing.T) { 103 | p := NewGame(`rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1`).start() 104 | expect.Eq(t, p.dcf(), `Ke1,Qd1,Ra1,Rh1,Bc1,Bf1,Nb1,Ng1,a2,b2,c2,d2,e2,f2,g2,h2 : Ke8,Qd8,Ra8,Rh8,Bc8,Bf8,Nb8,Ng8,a7,b7,c7,d7,e7,f7,g7,h7`) 105 | } 106 | 107 | // Castles, no en-passant. 108 | func TestPosition140(t *testing.T) { 109 | p := NewGame(`2r1kb1r/pp3ppp/2n1b3/1q1N2B1/1P2Q3/8/P4PPP/3RK1NR w Kk - 42 42`).start() 110 | expect.Eq(t, p.dcf(), `Ke1,Qe4,Rd1,Rh1,Bg5,Ng1,Nd5,Cg1,a2,f2,g2,h2,b4 : Ke8,Qb5,Rc8,Rh8,Be6,Bf8,Nc6,Cg8,a7,b7,f7,g7,h7`) 111 | } 112 | 113 | // No castles, en-passant. 114 | func TestPosition150(t *testing.T) { 115 | p := NewGame(`1rr2k2/p1q5/3p2Q1/3Pp2p/8/1P3P2/1KPRN3/8 w - e6 42 42`).start() 116 | expect.Eq(t, p.dcf(), `Kb2,Qg6,Rd2,Ne2,Ee6,c2,b3,f3,d5 : Kf8,Qc7,Rb8,Rc8,e5,h5,d6,a7`) 117 | 118 | pp := NewGame(`M,Kb2,Qg6,Rd2,Ne2,Ee6,c2,b3,f3,d5`, `Kf8,Qc7,Rb8,Rc8,e5,h5,d6,a7`).start() 119 | expect.Eq(t, pp.fen(), `1rr2k2/p1q5/3p2Q1/3Pp2p/8/1P3P2/1KPRN3/8 w - e6 0 1`) 120 | } 121 | 122 | // Position status. 123 | func TestPosition200(t *testing.T) { 124 | p := NewGame().start() 125 | expect.Eq(t, p.status(NewMove(p, E2, E4), p.Evaluate()), InProgress) 126 | } 127 | 128 | // Mate in 1 move. 129 | func TestPosition210(t *testing.T) { 130 | p := NewGame(`Kf8,Rh1,g6`, `Kh8,Bg8,g7,h7`).start() 131 | rootNode = node // Reset ply(). 132 | expect.Eq(t, p.status(NewMove(p, H1, H6), Checkmate - ply()), WhiteWinning) 133 | } 134 | 135 | // Forced stalemate. 136 | func TestPosition220(t *testing.T) { 137 | p := NewGame(`Kf7,b2,b4,h6`, `Kh8,Ba4,b3,b5,h7`).start() 138 | expect.Eq(t, p.status(NewMove(p, F7, F8), 0), Stalemate) 139 | } 140 | 141 | // Self-imposed stalemate. 142 | func TestPosition230(t *testing.T) { 143 | p := NewGame(`Ka1,g3,h2`, `M,Kh5,h3,g4,g5,g6,h7`).start() 144 | p = p.makeMove(NewMove(p, H7, H6)) 145 | expect.Eq(t, p.status(NewMove(p, A1, B2), 0), Stalemate) 146 | } 147 | 148 | // Draw by repetition. 149 | func TestPosition240(t *testing.T) { 150 | p := NewGame(`Ka1,g3,h2`, `M,Kh5,h3,g4,g5,g6,h7`).start() // Initial. 151 | 152 | p = p.makeMove(NewMove(p, H5, H6)) 153 | p = p.makeMove(NewMove(p, A1, A2)) 154 | p = p.makeMove(NewMove(p, H6, H5)) 155 | p = p.makeMove(NewMove(p, A2, A1)) // Rep #2. 156 | expect.Eq(t, p.status(NewMove(p, H5, H6), 0), InProgress) 157 | 158 | p = p.makeMove(NewMove(p, H5, H6)) 159 | p = p.makeMove(NewMove(p, A1, A2)) 160 | p = p.makeMove(NewMove(p, H6, H5)) // -- No NewMove(p, A2, A1) here -- 161 | 162 | rootNode = node // Reset ply(). 163 | expect.Eq(t, p.status(NewMove(p, A2, A1), 0), Repetition) // <-- Ka2-a1 causes rep #3. 164 | } 165 | 166 | // Insufficient material. 167 | func TestPosition250(t *testing.T) { 168 | p := NewGame(`Ka1,Bb2`, `Kh5`).start() 169 | expect.True(t, p.insufficient()) 170 | } 171 | 172 | // Restricted mobility for pinned pieces. 173 | func TestPosition300(t *testing.T) { 174 | p := NewGame(`Ka1,a2,Nc3`, `Kh8,h7,Bg8`).start() // Nc3 vs Bishop, no pin. 175 | expect.Eq(t, p.Evaluate(), -12) 176 | p = NewGame(`Ka1,a2,Nc3`, `Kh8,h7,Bg7`).start() // Nc3 vs Bishop, pin on C3-G7 diagonal. 177 | expect.Eq(t, p.Evaluate(), -63) 178 | 179 | } 180 | 181 | func TestPosition310(t *testing.T) { 182 | p := NewGame(`Ka1,a2,Bc3`, `Kg8,h7,Bg6`).start() // Bc3 vs Bishop, no pin. 183 | expect.Eq(t, p.Evaluate(), 0) 184 | p = NewGame(`Ka1,a2,Bc3`, `Kg8,h7,Bg7`).start() // Bc3 vs Bishop, pin on C3-G7 diagonal. 185 | expect.Eq(t, p.Evaluate(), -25) 186 | p = NewGame(`Ka3,a2,Bc3`, `Kh8,h7,Rh1`).start() // Bc3 vs Rook, no pin. 187 | expect.Eq(t, p.Evaluate(), -206) 188 | p = NewGame(`Ka3,a2,Bc3`, `Kh8,h7,Rh3`).start() // Bc3 vs Rook, pin on C3-H3 file. 189 | expect.Eq(t, p.Evaluate(), -319) 190 | 191 | } 192 | -------------------------------------------------------------------------------- /score.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | type Score struct { 12 | midgame int 13 | endgame int 14 | } 15 | 16 | // Reference methods that change the score receiver in place and return a 17 | // pointer to the updated score. 18 | func (s *Score) clear() *Score { 19 | s.midgame, s.endgame = 0, 0 20 | 21 | return s 22 | } 23 | 24 | func (s *Score) add(score Score) *Score { 25 | s.midgame += score.midgame 26 | s.endgame += score.endgame 27 | 28 | return s 29 | } 30 | 31 | func (s *Score) sub(score Score) *Score { 32 | s.midgame -= score.midgame 33 | s.endgame -= score.endgame 34 | 35 | return s 36 | } 37 | 38 | func (s *Score) apply(weight Score) *Score { 39 | s.midgame = s.midgame * weight.midgame / 100 40 | s.endgame = s.endgame * weight.endgame / 100 41 | 42 | return s 43 | } 44 | 45 | 46 | func (s *Score) adjust(n int) *Score { 47 | s.midgame += n 48 | s.endgame += n 49 | 50 | return s 51 | } 52 | 53 | func (s *Score) scale(n int) *Score { 54 | s.midgame = s.midgame * n / 100 55 | s.endgame = s.endgame * n / 100 56 | 57 | return s 58 | } 59 | 60 | // Value methods that return newly updated score value. 61 | func (s Score) plus(score Score) Score { 62 | s.midgame += score.midgame 63 | s.endgame += score.endgame 64 | 65 | return s 66 | } 67 | 68 | func (s Score) minus(score Score) Score { 69 | s.midgame -= score.midgame 70 | s.endgame -= score.endgame 71 | 72 | return s 73 | } 74 | 75 | func (s Score) times(n int) Score { 76 | s.midgame *= n 77 | s.endgame *= n 78 | 79 | return s 80 | } 81 | 82 | // Calculates normalized score based on the game phase. 83 | func (s Score) blended(phase int) int { 84 | return (s.midgame * phase + s.endgame * (256 - phase)) / 256 85 | } 86 | -------------------------------------------------------------------------------- /scripts/README: -------------------------------------------------------------------------------- 1 | play.sh 2 | Shell script to start a match between two chess engines using Cute Chess CLI. 3 | 4 | rate.sh 5 | Shell script to compute ELO rating based on PGN games. 6 | 7 | mfl.epd 8 | Most frequest lines opening book for fast engine testing as described at 9 | https://sites.google.com/site/chessbazaar/2007-01-2008-fast-engine-testing-the-mlmfl-test 10 | 11 | nebula.epd 12 | Nebula 100 long opening book from https://sites.google.com/site/nebulachess/testsets 13 | -------------------------------------------------------------------------------- /scripts/mfl.epd: -------------------------------------------------------------------------------- 1 | rnbqkb1r/1p3ppp/p2ppn2/8/3NP3/2N1BP2/PPP3PP/R2QKB1R b KQkq - 0 7 2 | rn1qkb1r/1p3ppp/p2pbn2/4p3/4P3/1NN1BP2/PPP3PP/R2QKB1R b KQkq - 0 8 3 | rnbqkb1r/1p3ppp/p2ppn2/8/3NP3/2N5/PPP1BPPP/R1BQK2R w KQkq - 0 7 4 | r1bqk2r/1p2bpp1/p1nppn1p/8/3NP3/2N1B3/PPPQ1PPP/2KR1B1R w kq - 0 10 5 | r1bqkb1r/5ppp/p1np1n2/1p2p1B1/4P3/N1N5/PPP2PPP/R2QKB1R w KQkq b6 0 9 6 | r1b1kbnr/ppqp1ppp/2n1p3/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 0 6 7 | rnbqkbnr/1p1p1ppp/p3p3/8/3NP3/8/PPP2PPP/RNBQKB1R w KQkq - 0 5 8 | rnbqkbnr/pp1ppppp/8/2p5/4P3/2P5/PP1P1PPP/RNBQKBNR b KQkq - 0 2 9 | rnbqkbnr/pp1ppppp/8/2p5/4P3/2N5/PPPP1PPP/R1BQKBNR b KQkq - 0 2 10 | r1bq1rk1/4bppp/p2p1n2/npp1p3/3PP3/2P2N1P/PPB2PP1/RNBQR1K1 b - d3 0 11 11 | rnbqkb1r/ppp2ppp/8/3p4/3Pn3/3B1N2/PPP2PPP/RNBQK2R b KQkq - 0 6 12 | rnbqk1nr/pp3ppp/4p3/2ppP3/3P4/P1P5/2P2PPP/R1BQKBNR b KQkq - 0 6 13 | rnbqkbnr/pp3ppp/4p3/2pp4/3PP3/8/PPPN1PPP/R1BQKBNR w KQkq c6 0 4 14 | rnbqkbnr/pp2pppp/2p5/3p4/3PP3/8/PPP2PPP/RNBQKBNR w KQkq d6 0 3 15 | rnbqkb1r/ppp1pp1p/3p1np1/8/3PP3/2N5/PPP2PPP/R1BQKBNR w KQkq - 0 4 16 | r1bqkb1r/p2n1ppp/2p1pn2/1p6/3P4/2NBPN2/PP3PPP/R1BQK2R b KQkq - 0 8 17 | r1bqk2r/pp1n1ppp/2pbpn2/3p4/2PP4/2N1PN2/PPQ2PPP/R1B1KB1R w KQkq - 0 7 18 | rnbqkb1r/pp3ppp/2p1pn2/3p2B1/2PP4/2N2N2/PP2PPPP/R2QKB1R b KQkq - 0 5 19 | rnbqkb1r/ppp2ppp/4pn2/3p4/2PP4/5NP1/PP2PP1P/RNBQKB1R b KQkq - 0 4 20 | rn1qk2r/p3bppp/bpp1pn2/3p4/2PP4/1PB2NP1/P3PPBP/RN1QK2R w KQkq d6 0 9 21 | rnbqkb1r/p1pp1ppp/1p2pn2/8/2PP4/P4N2/1P2PPPP/RNBQKB1R b KQkq - 0 4 22 | rnbq1rk1/pppp1ppp/4pn2/8/1bPP4/2NBP3/PP3PPP/R1BQK1NR b KQ - 0 5 23 | rnbq1rk1/p1pp1ppp/1p2pn2/8/2PP4/P1Q5/1P2PPPP/R1B1KBNR w KQ - 0 7 24 | r1bq1rk1/ppp1npbp/3p1np1/3Pp3/2P1P3/2N2N2/PP2BPPP/R1BQ1RK1 w - - 0 9 25 | rnbqk2r/ppp1ppbp/5np1/3p4/2PP4/2N2N2/PP2PPPP/R1BQKB1R w KQkq d6 0 5 26 | rnbqk2r/ppp1ppbp/6p1/8/3PP3/2P5/P4PPP/R1BQKBNR w KQkq - 0 7 27 | rnbq1rk1/ppp1ppbp/3p1np1/8/2PP4/5NP1/PP2PPBP/RNBQK2R w KQ - 0 6 28 | rn1qkb1r/pp2pppp/2p2n2/5b2/P1pP4/2N2N2/1P2PPPP/R1BQKB1R w KQkq - 0 6 29 | rnbqkb1r/ppp1pppp/5n2/8/2pP4/5N2/PP2PPPP/RNBQKB1R w KQkq - 0 4 30 | r1bqkb1r/pppp1ppp/2n2n2/4p3/2P5/2N2N2/PP1PPPPP/R1BQKB1R w KQkq - 0 4 31 | rnbqkb1r/pp1ppppp/5n2/2p5/2P5/2N2N2/PP1PPPPP/R1BQKB1R b KQkq - 0 3 32 | rnbqkbnr/ppp1pppp/8/3p4/8/5NP1/PPPPPP1P/RNBQKB1R b KQkq - 0 2 33 | -------------------------------------------------------------------------------- /scripts/nebula.epd: -------------------------------------------------------------------------------- 1 | rn1qkb1r/1p3ppp/p2pbn2/4p3/4P3/1NN1BP2/PPP3PP/R2QKB1R b KQkq - 0 8 2 | r2qkb1r/1p1n1p2/p2p1np1/3Pp2p/8/1N2BP2/PPPQB1PP/R3K2R b KQkq - 1 12 3 | r1bq1rk1/2p1bppp/p1np1n2/1p2p3/4P3/1BP2N2/PP1P1PPP/RNBQR1K1 w - - 2 10 4 | r1bk1b1r/ppp2ppp/2p5/4Pn2/8/5N2/PPP2PPP/RNB2RK1 w - - 0 10 5 | rnb1kb1r/1p3ppp/p2ppn2/6B1/3NPP2/2N5/PqPQ2PP/1R2KB1R b Kkq - 1 9 6 | rn1q1rk1/1p2bppp/p2pbn2/4p3/4P3/1NNQ4/PPP1BPPP/R1B2RK1 w - - 8 11 7 | r2q1rk1/1bpnbppp/p2p1n2/1p2p3/3PP3/2P2N1P/PPBN1PP1/R1BQR1K1 b - - 4 12 8 | rnb1kb1r/1p3p2/p3pP1p/8/3N3p/q1N5/P1PQB1PP/1R2K2R b Kkq - 1 14 9 | r2qkb1r/1p1b1ppp/p1nppn2/6B1/3NP3/2N5/PPPQ1PPP/2KR1B1R w kq - 3 10 10 | r1bqkb1r/1p3ppp/p1np1n2/4p1B1/4P3/N1N5/PPP2PPP/R2QKB1R b KQkq - 1 8 11 | r2qk2r/1bp1bppp/p1np1n2/1p2p3/4P3/1BNP1N2/PPP2PPP/R1BQ1RK1 w kq - 0 10 12 | r3kb1r/ppp1nppp/2p3b1/4P3/3N4/2N4P/PPP2PP1/R1BR2K1 w - - 3 14 13 | rn1qk2r/1p2bppp/p2pbn2/3Np3/4P3/1N2B3/PPP1BPPP/R2QK2R b KQkq - 5 9 14 | r1bq1rk1/pp1n1ppp/2pbpn2/8/2pP4/2NBPN2/PPQ2PPP/R1B2RK1 w - - 0 10 15 | r2qr1k1/1bpnbppp/p2p4/np6/3PP3/5N1P/PPB2PP1/R1BQRNK1 b - - 4 14 16 | rnbqkb1r/1p2pp2/p2p3p/6p1/3NP1n1/2N3B1/PPP2PPP/R2QKB1R b KQkq - 1 9 17 | r2qkbnr/pp1npppb/2p4p/7P/3P4/5NN1/PPP2PP1/R1BQKB1R w KQkq - 1 10 18 | r2qkb1r/1p1n1pp1/p2pbn2/4p2p/4P3/1NN1BP2/PPPQB1PP/R3K2R b KQkq - 3 10 19 | r2qkb1r/pb1n1ppp/2p1pn2/1p6/3P4/2NBPN2/PP3PPP/R1BQK2R w KQkq - 2 10 20 | rnbqkb1r/p4p2/2p1pn1p/1p4p1/2pPP3/2N2NB1/PP3PPP/R2QKB1R w KQkq b6 0 10 21 | r3k2r/1p1nbpp1/p2p1q2/3Ppb1p/8/1N1B1P2/PPPQ1BPP/R4RK1 b kq - 9 15 22 | rn1q1rk1/1p2bppp/p2p4/3Ppb2/8/1N2B3/PPPQBPPP/R4RK1 b - - 6 12 23 | r2q1rk1/4b1pp/p2p1p2/Np1PpbPn/8/4BP2/PPPQ3P/1K1R1B1R w - - 0 18 24 | r1bq1rk1/pp2ppbp/2np1np1/8/3NP3/2N1BP2/PPPQ2PP/R3KB1R w KQ - 4 10 25 | r1bqk2r/1p2bpp1/p1nppn1p/8/3NP1P1/2N1B2P/PPP2PB1/R2QK2R w KQkq - 4 11 26 | r2q1rk1/1b1n1ppp/p1pbpn2/1p4N1/3P4/2NBP3/PPQ2PPP/R1BR2K1 b - - 3 12 27 | r2q1rk1/1bp1bppp/p1np1n2/4p3/2B1P3/2NP1N2/PP3PPP/R1BQ1RK1 w - - 0 12 28 | r1b1k2r/1pqp1ppp/p1n1pn2/8/1b1NP3/2N1B3/PPP1BPPP/R2Q1RK1 w kq - 5 10 29 | rnbqk2r/1pp1bp1p/p3p3/5p2/3P4/2N2N2/PPP2PPP/R2QKB1R w KQkq - 0 10 30 | r1bqkb1r/3n1ppp/p2ppn2/8/1p1NP1P1/2N1BP2/PPPQ3P/R3KB1R w KQkq - 0 11 31 | r1bq1rk1/5ppp/p1np4/3Np1b1/R3P3/2P5/1PN2PPP/3QKB1R b K - 0 14 32 | r1b1k2r/1pqpbppp/p1n1pn2/8/N2NP3/4B3/PPP1BPPP/R2Q1RK1 w kq - 7 11 33 | r3kb1r/ppqnnpp1/2p1p2p/3pPb2/3P4/2P2N2/PP1NBPPP/R1BQ1RK1 w kq - 2 10 34 | 2rq1rk1/1p1nbppp/p2p1n2/3Pp3/8/1N1QB3/PPP1BPPP/R4RK1 w - - 1 14 35 | r1b1kb1r/1p1n1ppp/pq1p1n2/4pNB1/4PP2/2N5/PPPQ2PP/R3KB1R b KQkq - 3 9 36 | r2qk2r/1bpp1ppp/pbn2n2/1p2p3/4P3/1BP2N2/PP1P1PPP/RNBQR1K1 w kq - 1 10 37 | r1bqkb1r/1p3ppp/p1np1n2/1N2p1B1/4P3/2N5/PPP2PPP/R2QKB1R w KQkq - 0 10 38 | r1bqkb1r/3n1ppp/p1n1p3/1pppP3/3P1P2/2N1BN2/PPPQ2PP/R3KB1R w KQkq b6 0 10 39 | 2rq1rk1/3nbpp1/p2pbn2/1p2p1Bp/4P2P/PNN2P2/1PPQ2P1/1K1R1B1R b - - 3 14 40 | r2qk2r/3nbppp/p2pbn2/3Np3/1p2P1P1/1N2BP2/PPPQ3P/2KR1B1R b kq - 1 12 41 | r2qkb1r/1p1n1pp1/p2p4/3Ppb1p/8/1N2BP2/PPP1B1PP/R2QK2R w KQkq - 1 13 42 | r3k2r/1pqn1pb1/p2p1np1/N2Pp2p/8/4BP2/PPPQB1PP/R4RK1 b kq - 6 14 43 | r3kbnr/pp1n1pp1/2p1p2p/q6P/3P4/3Q1NN1/PPPB1PP1/R3K2R b KQkq - 3 12 44 | r2qkb1r/3b1ppp/p1nppn2/1p4B1/3NPP2/2N5/PPPQ2PP/2KR1B1R w kq b6 0 11 45 | r1bq1rk1/ppp2pp1/1bnp1n1p/1B2p3/3PP2B/2P2N2/PP3PPP/RN1Q1RK1 w - - 0 10 46 | r2qkb1r/3b1p2/p2ppn1p/4n1p1/NQ1NP1P1/5P2/PPPB3P/2KR1B1R b kq - 1 14 47 | r2qkb1r/2p2ppp/p1n1b3/1p1pP3/4n3/1B3N2/PPP2PPP/RNBQ1RK1 w kq - 1 10 48 | r1bqk2r/ppp1bppp/8/3p4/1nPPn3/3B1N2/PP3PPP/RNBQ1RK1 w kq - 1 10 49 | r1bqkb1r/5ppp/p1np1n2/1p1Np3/4P3/N7/PPP2PPP/R1BQKB1R w KQkq - 2 10 50 | r1b1kb1r/pp1n1pp1/2p1pq1p/8/2pP4/2NBPN2/PP3PPP/R2QK2R w KQkq - 0 10 51 | r2q1rk1/pb3ppp/1pnbpn2/2ppN3/3P1P2/2PBP1B1/PP1N2PP/R2QK2R b KQ f3 0 10 52 | rn1qk2r/p3bppp/bpp1pn2/3p4/2PP4/1PB2NP1/P3PPBP/RN1QK2R w KQkq d6 0 10 53 | 1r1qk2r/1bp1bppp/p2p1n2/n3p3/Pp2P3/3P1NN1/BPP2PPP/R1BQ1RK1 b k - 3 12 54 | 2rqnrk1/1p1nb1pp/p2p4/3Ppp2/2P5/1N2B1P1/PP1QBP1P/R4RK1 b - - 0 15 55 | r1bbk2r/2p2ppp/p1n2n2/4p3/Pp2P3/1B3N2/1PP2PPP/RNB2RK1 w kq - 0 12 56 | r1bq1rk1/ppp1bppp/1nn5/4p3/8/2NP1NP1/PP2PPBP/R1BQ1RK1 w - - 2 10 57 | r2qk2r/1pp1bppp/p1p2n2/4p2b/4P3/2N2N1P/PPPP1PP1/R1BQ1RK1 w kq - 1 10 58 | r3kb1r/3b1p2/pqnppp1p/1p6/3NPP2/2N5/PPPQ2PP/1K1R1B1R w kq - 2 14 59 | r1bq1rk1/ppppbppp/3n4/4R3/8/8/PPPP1PPP/RNBQ1BK1 w - - 2 10 60 | r2qk2r/1p1nbpp1/p2p4/3Ppb1p/8/1N2BPP1/PPPQB2P/2KR3R w kq - 4 16 61 | r1bq1rk1/2p2pp1/p1pp1n1p/2b1p3/4P3/2PP1N1P/PP1N1PP1/R1BQ1RK1 w - - 0 11 62 | r1bq1rk1/pp2nppp/2n1p3/2ppP3/3P2Q1/P1PB4/2P2PPP/R1B1K1NR w KQ - 5 10 63 | 1rq1k2r/3pbppp/p3pn2/2p1B3/4P3/8/PPP1BPPP/R2Q1RK1 b k - 1 14 64 | r1bq1rk1/ppp1bppp/1nn5/4p3/8/P1N2NP1/1P1PPPBP/R1BQ1RK1 w - - 2 10 65 | r2q1rk1/1bppbppp/p1n2n2/1p2p3/P3P3/1B1P1N2/1PP2PPP/RNBQR1K1 b - - 0 9 66 | rn1qkb1r/1p3pp1/p2p4/3Pp2p/8/1N1QBP2/PPP3PP/R3K2R b KQkq - 0 12 67 | r1bq1rk1/pp1n1ppp/2n1p3/2bpP3/3N1P2/2N1B3/PPPQ2PP/2KR1B1R b - - 6 10 68 | r3kb1r/1bqn2pp/p3pn2/1pp5/4P3/2NB1N2/PP3PPP/R1BQ1RK1 w kq - 0 14 69 | r3kb1r/1pp1npp1/p1p2q2/4p2p/4P1b1/3P1N1P/PPPN1PP1/R1BQ1RK1 w kq - 3 10 70 | r1bqk2r/1p2pp2/p2p2np/6p1/3QP3/2N2P1P/PPP3P1/R3KB1R b KQkq - 0 15 71 | r1bq1rk1/ppp3pp/2np1n2/1Bb1p1B1/4P3/2N2N2/PPP2PPP/R2Q1RK1 w - - 3 10 72 | r1b2rk1/5ppp/p1pb4/1p1n4/3P4/1BP3Pq/PP2QP1P/RNB1R1K1 b - - 2 15 73 | r1b1kb1r/1p1n1ppp/p4n2/2q1p1B1/2B1P3/2N1N3/P1PQ2PP/1R3RK1 b kq - 6 14 74 | rnbq1rk1/pp2ppbp/6p1/2p5/3PP3/2P2N2/P4PPP/1RBQKB1R w K - 3 10 75 | r2qkb1r/1b1n1ppp/p2ppn2/1p6/3NP3/P1N1BP2/1PPQ2PP/2KR1B1R b kq - 0 10 76 | r1bqk2r/ppp2ppp/2n1p3/8/QnpP4/5NP1/PP2PPBP/RN2K2R w KQkq - 0 10 77 | r2q1rk1/p2nbppp/bpp1p3/3p4/2PP4/1PB3P1/P2NPPBP/R2Q1RK1 b - - 5 12 78 | rn1qkb1r/pb3p2/2p1pn1p/1p4p1/2pPP3/2N2NB1/PP2BPPP/R2Q1RK1 b kq - 4 10 79 | r1b1k2r/1pqp1ppp/p1n1pn2/8/1b1NP3/2N1BP2/PPPQ2PP/2KR1B1R b kq - 0 9 80 | r4rk1/1pqnbppp/p2p4/N2Ppb2/2P5/4B3/PP1QBPPP/R4RK1 b - c3 0 14 81 | r1bq1rk1/pp2ppbp/2np1np1/8/3NP3/1BN1B3/PPP2PPP/R2QK2R w KQ - 0 10 82 | r2qkb1r/pp1nnppp/4p3/2ppP3/3P4/2PQ4/PP2NPPP/RNB2RK1 w kq - 0 10 83 | 2rq1rk1/1p1nbppp/p2pbn2/P3p3/4P3/1NNQB3/1PP1BPPP/R4RK1 b - - 0 12 84 | rn1qk2r/1p2bppp/p2p4/3Ppb2/8/1N2B3/PPP1BPPP/R2Q1RK1 b kq - 3 11 85 | rn1qkb1r/1b1n1ppp/p2pp3/1p6/3NP1P1/2N4P/PPP2PB1/R1BQ1RK1 w kq - 5 11 86 | r2q1rk1/1p4b1/p1n1p2p/3pn1p1/8/4N1BP/PPP1BPP1/R2QK2R w KQ - 0 18 87 | r1bq1rk1/1p1nb1pp/p2p4/3Ppp2/P1P5/1N1QB3/1P2BPPP/R4RK1 b - c3 0 14 88 | r2qkb1r/pb1n1p2/2p1pn1p/1p4p1/2pPP3/2N2NB1/PP2BPPP/R2Q1RK1 w kq - 5 12 89 | 1rbqk2r/5ppp/p1np4/1p1Np1b1/4P3/2P5/PPN2PPP/R2QKB1R w KQk - 3 14 90 | rn1q1rk1/1p2bppp/p2pBn2/4p3/4P3/2N1BN2/PPP2PPP/R2Q1RK1 b - - 0 10 91 | r1b1kb1r/3n1ppp/p4n2/1pq1p1B1/2B1P3/2N1N3/P1PQ2PP/1R3RK1 w kq b6 0 16 92 | rnb1k2r/4bppp/pq1ppn2/1p6/3NP3/1BN2Q2/PPP2PPP/R1B2RK1 w kq - 4 11 93 | rnb1k2r/pp2pp1p/1bp2np1/8/2B1PN2/5N2/PPP2PPP/R1B1K2R w KQkq - 4 10 94 | r3kb1r/ppqn1ppp/2p2n2/4pb2/P1NP4/2N3P1/1P2PP1P/R1BQKB1R w KQkq e6 0 10 95 | r2qkbr1/pb3p2/2p1pn1p/1p2B3/2pPP2P/2N2B2/PP3PP1/R2QK2R b KQq - 2 14 96 | rnbqk1nr/5ppp/2p1p3/pP6/2pP4/2b1PN2/1P1B1PPP/R2QKB1R w KQkq - 0 10 97 | 1rbq1rk1/pp2ppbp/n2p1np1/2pP4/4PP2/2NB1N2/PPP3PP/R1BQ1RK1 w - - 1 10 98 | r3kb1r/ppqn2pp/2p2p2/4nb2/P1N2B2/2N3P1/1P2PPBP/R2Q1RK1 b kq - 2 12 99 | rn1qk2r/pb2bppp/1p1ppn2/6B1/2PQ4/2N2NP1/PP2PPBP/R4RK1 b kq - 1 9 100 | r1bqk2r/pp2bppp/2n2n2/3p4/2pP4/2N2NP1/PP2PPBP/R1BQ1RK1 w kq - 0 10 101 | -------------------------------------------------------------------------------- /scripts/play.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | usage() { 4 | echo "Usage: ./play.sh \$1 \$2 \$3 \$4 \$5 [\$6]" 5 | echo "Where \$1 - number of games to play" 6 | echo " \$2 - command to launch Donna" 7 | echo " \$3 - command to launch opponent engine" 8 | echo " \$4 - time control string" 9 | echo " \$5 - output .pgn file name to save the games" 10 | echo " \$6 - openings .epd file name (optional)" 11 | echo "Example:" 12 | echo "\$ ./play.sh 200 ../donna ../donna 40/60+1 /tmp/test.pgn ./mfl.epd" 13 | echo 14 | } 15 | 16 | if [ -z "$CUTE" ]; then 17 | echo "Please set CUTE environment variable to point to cutechess-cli executable." 18 | echo "See https://github.com/cutechess/cutechess/blob/master/projects/cli/res/doc/help.txt for details." 19 | exit 1 20 | fi 21 | 22 | if [ $# -lt 5 ] || [ $# -gt 6 ]; then 23 | usage 24 | else 25 | if [ $# -eq 5 ]; then 26 | cmd="$CUTE -games $1 -engine cmd=$3 -engine cmd=$2 -each tc=$4 proto=uci -draw movenumber=40 movecount=8 score=0 -resign movecount=8 score=350 -pgnout $5 -repeat" 27 | else 28 | cmd="$CUTE -games $1 -engine cmd=$3 -engine cmd=$2 -each tc=$4 proto=uci -draw movenumber=40 movecount=8 score=0 -resign movecount=8 score=350 -pgnout $5 -repeat -openings file=$6 format=epd" 29 | fi 30 | echo $cmd 31 | eval $cmd 32 | fi 33 | -------------------------------------------------------------------------------- /scripts/rate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | usage() { 4 | echo "Usage: ./rate.sh \$1 \$2 \$3" 5 | echo "Where \$1 - engine name" 6 | echo " \$2 - engine's ELO rating" 7 | echo " \$3 - PGN file to calculate ratings" 8 | echo "Example:" 9 | echo "\$ ./rate.sh \"GreKo 12.0\" 2539 donna-0.9_vs_greko-12.0_40_60_1_mfl.pgn" 10 | echo 11 | } 12 | 13 | if [ -z "$ORDO" ]; then 14 | echo "Please set ORDO environment variable to point to ordo executable." 15 | echo "See https://sites.google.com/site/gaviotachessengine/ordo for details." 16 | exit 1 17 | fi 18 | 19 | if [ $# -ne 3 ]; then 20 | usage 21 | else 22 | cmd="$ORDO -A '$1' -a $2 -j /dev/stdout $3" 23 | echo $cmd 24 | eval $cmd 25 | fi 26 | -------------------------------------------------------------------------------- /search.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | // Root node search. Basic principle is expressed by Boob's Law: you always find 12 | // something in the last place you look. 13 | func (p *Position) search(alpha, beta, depth int) (score int) { 14 | ply, inCheck := ply(), p.isInCheck(p.color) 15 | 16 | // Root move generator makes sure all generated moves are valid. The 17 | // best move found so far is always the first one we search. 18 | gen := NewRootGen(p, depth) 19 | if depth == 1 { 20 | gen.generateRootMoves() 21 | } else { 22 | gen.reset() 23 | } 24 | 25 | bestAlpha, bestScore := alpha, alpha 26 | bestMove, moveCount := Move(0), 0 27 | for move := gen.nextMove(); move.some(); move = gen.nextMove() { 28 | position := p.makeMove(move) 29 | moveCount++; game.nodes++ 30 | if engine.uci { 31 | engine.uciMove(move, moveCount, depth) 32 | } 33 | 34 | // Reduce search depth if we're not checking. 35 | giveCheck := position.isInCheck(position.color) 36 | newDepth := let(giveCheck && p.exchange(move) >= 0, depth, depth - 1) 37 | 38 | // Start search with full window. 39 | game.deepening = (moveCount == 1) 40 | if moveCount == 1 { 41 | score = -position.searchTree(-beta, -alpha, newDepth) 42 | } else { 43 | reduction := 0 44 | if !inCheck && !giveCheck && depth > 2 && move.isQuiet() && !move.isKiller(ply) && !move.isPawnAdvance() { 45 | reduction = lateMoveReductions[(moveCount-1) & 63][depth & 63] 46 | if game.history[move.piece()][move.to()] < 0 { 47 | reduction++ 48 | } 49 | } 50 | 51 | score = -position.searchTree(-alpha - 1, -alpha, max(0, newDepth - reduction)) 52 | 53 | // Verify late move reduction and re-run the search if necessary. 54 | if reduction > 0 && score > alpha { 55 | score = -position.searchTree(-alpha - 1, -alpha, newDepth) 56 | } 57 | 58 | // If zero window fails then try full window. 59 | if score > alpha { 60 | score = -position.searchTree(-beta, -alpha, newDepth) 61 | } 62 | } 63 | position.undoLastMove() 64 | 65 | // Don't touch anything if the time has elapsed and we need to abort th search. 66 | if engine.clock.halt { 67 | return alpha 68 | } 69 | 70 | if moveCount == 1 || score > alpha { 71 | bestMove = move 72 | game.saveBest(0, move) 73 | gen.scoreMove(depth, score).rearrangeRootMoves() 74 | if moveCount > 1 { 75 | game.volatility++ 76 | } 77 | } else { 78 | gen.scoreMove(depth, -depth) 79 | } 80 | 81 | if score > bestScore { 82 | bestScore = score 83 | if score > alpha { 84 | game.saveBest(ply, move) 85 | if score < beta { 86 | alpha = score 87 | bestMove = move 88 | } else { 89 | p.cache(move, score, depth, ply, cacheBeta) 90 | if !inCheck && alpha > bestAlpha { 91 | game.saveGood(depth, bestMove).updatePoor(depth, bestMove, gen.reset()) 92 | } 93 | return score 94 | } 95 | } 96 | } 97 | } 98 | 99 | 100 | if moveCount == 0 { 101 | score = let(inCheck, -Checkmate, 0) // Mate if in check, stalemate otherwise. 102 | if engine.uci { 103 | engine.uciScore(depth, score, alpha, beta) 104 | } 105 | return score 106 | } 107 | score = bestScore 108 | 109 | if !inCheck && alpha > bestAlpha { 110 | game.saveGood(depth, bestMove).updatePoor(depth, bestMove, gen.reset()) 111 | } 112 | 113 | cacheFlags := cacheAlpha 114 | if score >= beta { 115 | cacheFlags = cacheBeta 116 | } else if bestMove.some() { 117 | cacheFlags = cacheExact 118 | } 119 | p.cache(bestMove, score, depth, ply, cacheFlags) 120 | if engine.uci { 121 | engine.uciScore(depth, score, alpha, beta) 122 | } 123 | 124 | return 125 | } 126 | 127 | // Testing helper method to test root search. 128 | func (p *Position) solve(depth int) Move { 129 | if depth != 1 { 130 | NewRootGen(p, 1).generateRootMoves() 131 | } 132 | p.search(-Checkmate, Checkmate, depth) 133 | return game.pv[0].moves[0] 134 | } 135 | 136 | func (p *Position) Perft(depth int) (total int64) { 137 | if depth == 0 { 138 | return 1 139 | } 140 | 141 | gen := NewGen(p, depth).generateAllMoves() 142 | for move := gen.nextMove(); move != 0; move = gen.nextMove() { 143 | if !move.valid(p, gen.pins) { 144 | continue 145 | } 146 | position := p.makeMove(move) 147 | total += position.Perft(depth - 1) 148 | position.undoLastMove() 149 | } 150 | return 151 | } 152 | -------------------------------------------------------------------------------- /search_quiescence.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | // Quiescence search. 12 | func (p *Position) searchQuiescence(alpha, beta, depth int, inCheck bool) (score int) { 13 | ply := ply() 14 | 15 | // Return if it's time to stop search. 16 | if ply >= MaxPly || engine.clock.halt { 17 | return p.Evaluate() 18 | } 19 | 20 | // Insufficient material and repetition/perpetual check pruning. 21 | if p.fifty() || p.insufficient() || p.repetition() { 22 | return 0 23 | } 24 | 25 | // Checkmate distance pruning. 26 | alpha, beta = mateDistance(alpha, beta, ply) 27 | if alpha >= beta { 28 | return alpha 29 | } 30 | 31 | // If you pick up a starving dog and make him prosperous, he will not 32 | // bite you. This is the principal difference between a dog and a man. 33 | // ―- Mark Twain 34 | isNull := p.isNull() 35 | isPrincipal := (beta - alpha > 1) 36 | if isPrincipal { 37 | game.pv[ply].size = 0 // Reset principal variation. 38 | } 39 | 40 | // Use fixed depth for caching. 41 | newDepth := let(inCheck || depth >= 0, 0, -1) 42 | 43 | // Probe cache. 44 | cached, cachedMove := p.probeCache(), Move(0) 45 | if cached != nil { 46 | cachedMove = cached.move 47 | if !isPrincipal && cached.depth() >= newDepth { 48 | bounds, score := cached.bounds(), cached.score(ply) 49 | if (bounds & cacheBeta != 0 && score >= beta) || (bounds & cacheAlpha != 0 && score <= alpha) { 50 | return score 51 | } 52 | } 53 | } 54 | 55 | if inCheck { 56 | p.score = Unknown 57 | } else { 58 | if cached != nil { 59 | if p.score == Unknown { 60 | p.score = p.Evaluate() 61 | } 62 | bounds, score := cached.bounds(), cached.score(ply) 63 | if (score > p.score && (bounds & cacheBeta != 0)) || (score <= p.score && (bounds & cacheAlpha != 0)) { 64 | p.score = score 65 | } 66 | } else if isNull { 67 | p.score = rightToMove.midgame * 2 - tree[node-1].score 68 | } else { 69 | p.score = p.Evaluate() 70 | } 71 | 72 | if p.score >= beta { 73 | return p.score 74 | } 75 | if isPrincipal { 76 | alpha = max(alpha, p.score) 77 | } 78 | } 79 | 80 | // Generate check evasions or captures. 81 | gen := NewGen(p, ply) 82 | if inCheck { 83 | gen.generateEvasions().quickRank() 84 | } else { 85 | gen.generateCaptures() 86 | if depth == 0 { 87 | gen.generateChecks() 88 | } 89 | gen.rank(cachedMove) 90 | } 91 | 92 | bestAlpha := alpha 93 | bestScore := let(p.score != Unknown, p.score, matedIn(ply)) 94 | bestMove, moveCount := Move(0), 0 95 | for move := gen.nextMove(); move.some(); move = gen.nextMove() { 96 | capture := move.capture() 97 | if (!inCheck && capture.some() && p.exchange(move) < 0) || !move.valid(p, gen.pins) { 98 | continue 99 | } 100 | 101 | position := p.makeMove(move) 102 | moveCount++; game.qnodes++ 103 | giveCheck := position.isInCheck(position.color) 104 | 105 | // Prune useless captures -- but make sure it's not a capture move that checks. 106 | if !inCheck && !giveCheck && !isPrincipal && capture != 0 && !move.isPromo() && p.score + pieceValue[capture.id()] + 72 < alpha { 107 | position.undoLastMove() 108 | continue 109 | } 110 | score = -position.searchQuiescence(-beta, -alpha, depth - 1, giveCheck) 111 | position.undoLastMove() 112 | 113 | // Don't touch anything if the time has elapsed and we need to abort th search. 114 | if engine.clock.halt { 115 | return alpha 116 | } 117 | 118 | if score > bestScore { 119 | bestScore = score 120 | if score > alpha { 121 | if isPrincipal { 122 | game.saveBest(ply, move) 123 | } 124 | if isPrincipal && score < beta { 125 | alpha = score 126 | bestMove = move 127 | } else { 128 | p.cache(move, score, newDepth, ply, cacheBeta) 129 | return score 130 | } 131 | } 132 | } 133 | } 134 | 135 | score = let(inCheck && moveCount == 0, matedIn(ply), bestScore) 136 | 137 | cacheFlags := cacheAlpha 138 | if isPrincipal && score > bestAlpha { 139 | cacheFlags = cacheExact 140 | } 141 | p.cache(bestMove, score, newDepth, ply, cacheFlags) 142 | 143 | return score 144 | } 145 | -------------------------------------------------------------------------------- /search_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import(`github.com/michaeldv/donna/expect`; `testing`) 12 | 13 | // Mate in 2. 14 | 15 | // The very first chess puzzle I had solved as a kid. 16 | func TestSearch000(t *testing.T) { 17 | move := NewGame(`Kf8,Rh1,g6`, `Kh8,Bg8,g7,h7`).start().solve(3) 18 | expect.Eq(t, move, `Rh1-h6`) 19 | } 20 | 21 | func TestSearch020(t *testing.T) { 22 | move := NewGame(`Kf4,Qc2,Nc5`, `Kd4`).start().solve(3) 23 | expect.Eq(t, move, `Nc5-b7`) 24 | } 25 | 26 | func TestSearch030(t *testing.T) { 27 | move := NewGame(`Kf2,Qf7,Nf3`, `Kg4`).start().solve(3) 28 | expect.Eq(t, move, `Qf7-f6`) 29 | } 30 | 31 | func TestSearch040(t *testing.T) { 32 | move := NewGame(`Kc3,Qc2,Ra4`, `Kb5`).start().solve(3) 33 | expect.Eq(t, move, `Qc2-g6`) 34 | } 35 | 36 | func TestSearch050(t *testing.T) { 37 | move := NewGame(`Ke5,Qc1,Rf3,Bg2`, `Ke2,Nd5,Nb1`).start().solve(3) 38 | expect.Eq(t, move, `Rf3-d3`) 39 | } 40 | 41 | func TestSearch060(t *testing.T) { 42 | move := NewGame(`Kf1,Qa8,Bf7,Ng2`, `Kg4`).start().solve(3) 43 | expect.Eq(t, move, `Qa8-b8`) 44 | } 45 | 46 | func TestSearch070(t *testing.T) { 47 | move := NewGame(`Ke5,Rd3,Bb1`, `Kh7`).start().solve(3) 48 | expect.Eq(t, move, `Ke5-f6`) 49 | } 50 | 51 | // Puzzles with pawns. 52 | 53 | func TestSearch080(t *testing.T) { 54 | move := NewGame(`Kg3,Bc1,Nc3,Bg2`, `Kg1,Re1,e3`).start().solve(3) 55 | expect.Eq(t, move, `Bc1-a3`) 56 | } 57 | 58 | func TestSearch090(t *testing.T) { 59 | move := NewGame(`Kf2,Qb8,Be7,f3`, `Kh5,h6,g5`).start().solve(3) 60 | expect.Eq(t, move, `Qb8-b1`) 61 | } 62 | 63 | func TestSearch100(t *testing.T) { 64 | move := NewGame(`Ke6,Qg3,b3,c2`, `Ke4,e7,f5`).start().solve(3) 65 | expect.Eq(t, move, `b3-b4`) 66 | } 67 | 68 | func TestSearch110(t *testing.T) { 69 | move := NewGame(`Kf1,Qh6,Nd2,Nf2`, `Kc1,c2,c3`).start().solve(3) 70 | expect.Eq(t, move, `Qh6-a6`) 71 | } 72 | 73 | func TestSearch120(t *testing.T) { // En-passant 74 | move := NewGame(`Kd5,Qc8,c5,e5,g6`, `Ke7,d7`).start().solve(3) 75 | expect.Eq(t, move, `Kd5-e4`) 76 | } 77 | 78 | func TestSearch130(t *testing.T) { // En-passant 79 | move := NewGame(`Ke7,Rf8,Ba3,Bc2,e5,g5`, `Kg7,c3,h7`).start().solve(3) 80 | expect.Eq(t, move, `Ba3-c1`) 81 | } 82 | 83 | func TestSearch140(t *testing.T) { // En-passant, stalemate 84 | move := NewGame(`Kc6,Rh4,Bb5,a3,c2,d3`, `Ka5,c5,d4,h5`).start().solve(3) 85 | expect.Eq(t, move, `c2-c4`) 86 | } 87 | 88 | func TestSearch150(t *testing.T) { // Stalemate after Qg7-c3 89 | move := NewGame(`Kb4,Qg7,Nc1`, `Kb1`).start().solve(3) 90 | expect.Eq(t, move, `Kb4-c3`) 91 | } 92 | 93 | func TestSearch160(t *testing.T) { // Pawn promotion 94 | move := NewGame(`Ka8,Qc4,b7`, `Ka5`).start().solve(3) 95 | expect.Eq(t, move, `b7-b8B`) 96 | } 97 | 98 | func TestSearch170(t *testing.T) { // Pawn promotion 99 | move := NewGame(`Kf8,Rc6,Be4,Nd7,c7`, `Ke6,d6`).start().solve(3) 100 | expect.Eq(t, move, `c7-c8R`) 101 | } 102 | 103 | func TestSearch180(t *testing.T) { // Pawn promotion 104 | move := NewGame(`Kc6,c7`, `Ka7`).start().solve(3) 105 | expect.Eq(t, move, `c7-c8R`) 106 | } 107 | 108 | func TestSearch190(t *testing.T) { // Pawn promotion 109 | move := NewGame(`Kc4,a7,c7`, `Ka5`).start().solve(3) 110 | expect.Eq(t, move, `c7-c8N`) 111 | } 112 | 113 | func TestSearch195(t *testing.T) { // King-side castle 114 | move := NewGame(`Ke1,Rf1,Rh1`, `Ka1`).start().solve(3) 115 | expect.Eq(t, move, `Rf1-f2`) 116 | } 117 | 118 | func TestSearch196(t *testing.T) { // Queen-side castle 119 | move := NewGame(`Ke1,Ra1,Rb1`, `Kg1`).start().solve(3) 120 | expect.Eq(t, move, `Rb1-b2`) 121 | } 122 | 123 | // Mate in 3. 124 | 125 | func TestSearch200(t *testing.T) { 126 | move := NewGame(`Kf8,Re7,Nd5`, `Kh8,Bh5`).start().solve(5) 127 | expect.Eq(t, move, `Re7-g7`) 128 | } 129 | 130 | func TestSearch210(t *testing.T) { 131 | move := NewGame(`Kf8,Bf7,Nf3,e5`, `Kh8,e6,h7`).start().solve(5) 132 | expect.Eq(t, move, `Bf7-g8`) 133 | } 134 | 135 | func TestSearch220(t *testing.T) { // Pawn promotion 136 | move := NewGame(`Kf3,h7`, `Kh1,h3`).start().solve(5) 137 | expect.Eq(t, move, `h7-h8R`) 138 | } 139 | 140 | func TestSearch230(t *testing.T) { // Pawn promotion 141 | move := NewGame(`Kd8,c7,e4,f7`, `Ke6,e5`).start().solve(5) 142 | expect.Eq(t, move, `f7-f8R`) 143 | } 144 | 145 | func TestSearch240(t *testing.T) { // Pawn promotion 146 | move := NewGame(`Kh3,f7,g7`, `Kh6`).start().solve(5) 147 | expect.Eq(t, move, `g7-g8Q`) 148 | } 149 | 150 | func TestSearch250(t *testing.T) { // Pawn promotion 151 | move := NewGame(`Ke4,c7,d6,e7,f6,g7`, `Ke6`).start().solve(5) 152 | expect.Eq(t, move, `e7-e8B`) 153 | } 154 | 155 | // Mate in 4. 156 | 157 | func TestSearch260(t *testing.T) { // Pawn promotion 158 | move := NewGame(`Kf6,Nf8,Nh6`, `Kh8,f7,h7`).start().solve(7) 159 | expect.Eq(t, move, `Nf8-e6`) 160 | } 161 | 162 | func TestSearch270(t *testing.T) { // Pawn promotion/stalemate 163 | move := NewGame(`Kf2,e7`, `Kh1,d2`).start().solve(7) 164 | expect.Eq(t, move, `e7-e8R`) 165 | } 166 | 167 | func TestSearch280(t *testing.T) { // Stalemate 168 | move := NewGame(`Kc1,Nb4,a2`, `Ka1,b5`).start().solve(7) 169 | expect.Eq(t, move, `a2-a4`) 170 | } 171 | 172 | func TestSearch290(t *testing.T) { // Stalemate 173 | move := NewGame(`Kh6,Rd3,h7`, `Kh8,Bd7`).start().solve(7) 174 | expect.Eq(t, move, `Rd3-d6`) 175 | } 176 | 177 | func TestSearch300(t *testing.T) { 178 | move := NewGame(`Kc6,Bc1,Ne5`, `Kc8,Ra8,a7,a6`).start().solve(7) 179 | expect.Eq(t, move, `Ne5-f7`) 180 | } 181 | 182 | // Perft. 183 | func TestSearch400(t *testing.T) { 184 | position := NewGame().start() 185 | expect.Eq(t, position.Perft(0), int64(1)) 186 | } 187 | 188 | func TestSearch410(t *testing.T) { 189 | position := NewGame().start() 190 | expect.Eq(t, position.Perft(1), int64(20)) 191 | } 192 | 193 | func TestSearch420(t *testing.T) { 194 | position := NewGame().start() 195 | expect.Eq(t, position.Perft(2), int64(400)) 196 | } 197 | 198 | func TestSearch430(t *testing.T) { 199 | position := NewGame().start() 200 | expect.Eq(t, position.Perft(3), int64(8902)) 201 | } 202 | 203 | func TestSearch440(t *testing.T) { 204 | position := NewGame().start() 205 | expect.Eq(t, position.Perft(4), int64(197281)) 206 | } 207 | 208 | func TestSearch450(t *testing.T) { 209 | position := NewGame().start() 210 | expect.Eq(t, position.Perft(5), int64(4865609)) 211 | } 212 | -------------------------------------------------------------------------------- /search_tree.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | func (p *Position) searchTree(alpha, beta, depth int) (score int) { 12 | ply := ply() 13 | 14 | // Return if it's time to stop search. 15 | if ply >= MaxPly || engine.clock.halt { 16 | return p.Evaluate() 17 | } 18 | 19 | // Reset principal variation. 20 | game.pv[ply].size = 0 21 | 22 | // Insufficient material and repetition/perpetual check pruning. 23 | if p.fifty() || p.insufficient() || p.repetition() { 24 | return 0 25 | } 26 | 27 | // Checkmate distance pruning. 28 | alpha, beta = mateDistance(alpha, beta, ply) 29 | if alpha >= beta { 30 | return alpha 31 | } 32 | 33 | // Initialize node search conditions. 34 | isNull := p.isNull() 35 | inCheck := p.isInCheck(p.color) 36 | isPrincipal := (beta - alpha > 1) 37 | 38 | // Probe cache. 39 | cached, cachedMove := p.probeCache(), Move(0) 40 | if cached != nil { 41 | cachedMove = cached.move 42 | if !isPrincipal && cached.depth() >= depth { 43 | bounds, score := cached.bounds(), cached.score(ply) 44 | if (score >= beta && (bounds & cacheBeta != 0)) || (score <= alpha && (bounds & cacheAlpha != 0)) { 45 | if score >= beta && !inCheck && cachedMove.some() { 46 | game.saveGood(depth, cachedMove) 47 | } 48 | return score 49 | } 50 | } 51 | } 52 | 53 | if !inCheck { 54 | if depth < 1 { 55 | return p.searchQuiescence(alpha, beta, 0, inCheck) 56 | } 57 | if cached != nil { 58 | if p.score == Unknown { 59 | p.score = p.Evaluate() 60 | } 61 | bounds, score := cached.bounds(), cached.score(ply) 62 | if (score > p.score && (bounds & cacheBeta != 0)) || (score <= p.score && (bounds & cacheAlpha != 0)) { 63 | p.score = score 64 | } 65 | } else if isNull { 66 | p.score = rightToMove.midgame * 2 - tree[node-1].score 67 | } else { 68 | p.score = p.Evaluate() 69 | } 70 | } 71 | 72 | // Razoring and futility margin pruning. 73 | if !inCheck && !isPrincipal { 74 | 75 | // No razoring if pawns are on 7th rank. 76 | if cachedMove.null() && depth < 3 && p.outposts[pawn(p.color)] & mask7th[p.color] == 0 { 77 | razoringMargin := func(depth int) int { 78 | return 96 + 64 * (depth - 1) 79 | } 80 | 81 | // Special case for razoring at low depths. 82 | if p.score <= alpha - razoringMargin(5) { 83 | return p.searchQuiescence(alpha, beta, 0, inCheck) 84 | } 85 | 86 | margin := alpha - razoringMargin(depth) 87 | if score := p.searchQuiescence(margin, margin + 1, 0, inCheck); score <= margin { 88 | return score 89 | } 90 | } 91 | 92 | // Futility pruning is only applicable if we don't have winning score 93 | // yet and there are pieces other than pawns. 94 | if !isNull && depth < 14 && !isMate(beta) && 95 | (p.outposts[p.color] & ^(p.outposts[king(p.color)] | p.outposts[pawn(p.color)])).any() { 96 | // Largest conceivable positional gain. 97 | if gain := p.score - 256 * depth; gain >= beta { 98 | return gain 99 | } 100 | } 101 | 102 | // Null move pruning. 103 | if !isNull && depth > 1 && p.outposts[p.color].count() > 5 { 104 | position := p.makeNullMove() 105 | game.nodes++ 106 | nullScore := -position.searchTree(-beta, -beta + 1, depth - 1 - 3) 107 | position.undoLastMove() 108 | 109 | if nullScore >= beta { 110 | if isMate(nullScore) { 111 | return beta 112 | } 113 | return nullScore 114 | } 115 | } 116 | } 117 | 118 | // Internal iterative deepening. 119 | if !inCheck && cachedMove.null() && depth > 4 { 120 | newDepth := depth / 2 121 | if isPrincipal { 122 | newDepth = depth - 2 123 | } 124 | p.searchTree(alpha, beta, newDepth) 125 | if cached := p.probeCache(); cached != nil { 126 | cachedMove = cached.move 127 | } 128 | } 129 | 130 | gen := NewGen(p, ply) 131 | if inCheck { 132 | gen.generateEvasions().quickRank() 133 | } else { 134 | gen.generateMoves().rank(cachedMove) 135 | } 136 | 137 | bestScore := alpha 138 | bestMove, moveCount := Move(0), 0 139 | for move := gen.nextMove(); move.some(); move = gen.nextMove() { 140 | if !move.valid(p, gen.pins) { 141 | continue 142 | } 143 | 144 | position := p.makeMove(move) 145 | moveCount++; game.nodes++ 146 | 147 | // Reduce search depth if we're not checking. 148 | giveCheck := position.isInCheck(position.color) 149 | newDepth := let(giveCheck && p.exchange(move) >= 0, depth, depth - 1) 150 | 151 | // Start search with full window. 152 | if isPrincipal && moveCount == 1 { 153 | score = -position.searchTree(-beta, -alpha, newDepth) 154 | } else { 155 | reduction := 0 156 | if !inCheck && !giveCheck && depth > 2 && move.isQuiet() && !move.isKiller(ply) && !move.isPawnAdvance() { 157 | reduction = lateMoveReductions[(moveCount-1) & 63][depth & 63] 158 | if isPrincipal { 159 | reduction /= 2 160 | } else { 161 | // Reduce more if the score is not improving. 162 | if node > 1 && bestScore < tree[node-2].score && tree[node-2].score != Unknown { 163 | reduction++ 164 | } 165 | // Reduce more for weak queit moves. 166 | if move.isQuiet() && game.history[move.piece()][move.to()] < 0 { 167 | reduction++ 168 | } 169 | } 170 | } 171 | 172 | score = -position.searchTree(-alpha - 1, -alpha, max(0, newDepth - reduction)) 173 | 174 | // Verify late move reduction and re-run the search if necessary. 175 | if reduction > 0 && score > alpha { 176 | score = -position.searchTree(-alpha - 1, -alpha, newDepth) 177 | } 178 | 179 | // If zero window failed try full window. 180 | if isPrincipal && score > alpha && score < beta { 181 | score = -position.searchTree(-beta, -alpha, newDepth) 182 | } 183 | } 184 | position.undoLastMove() 185 | 186 | // Don't touch anything if the time has elapsed and we need to abort th search. 187 | if engine.clock.halt { 188 | return alpha 189 | } 190 | 191 | if score > bestScore { 192 | bestScore = score 193 | if score > alpha { 194 | if isPrincipal { 195 | game.saveBest(ply, move) 196 | } 197 | if isPrincipal && score < beta { 198 | alpha = score 199 | bestMove = move 200 | } else { 201 | p.cache(move, score, depth, ply, cacheBeta) 202 | return score 203 | } 204 | } 205 | } 206 | } 207 | 208 | if moveCount == 0 { 209 | score = let(inCheck, matedIn(ply), 0) 210 | } else { 211 | score = bestScore 212 | if !inCheck { 213 | game.saveGood(depth, bestMove) 214 | } 215 | } 216 | 217 | cacheFlags := cacheAlpha 218 | if score >= beta { 219 | cacheFlags = cacheBeta 220 | } else if isPrincipal && bestMove.some() { 221 | cacheFlags = cacheExact 222 | } 223 | p.cache(bestMove, score, depth, ply, cacheFlags) 224 | 225 | return score 226 | } 227 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2018 by Michael Dvorkin. All Rights Reserved. 2 | // Use of this source code is governed by a MIT-style license that can 3 | // be found in the LICENSE file. 4 | // 5 | // I am making my contributions/submissions to this project solely in my 6 | // personal capacity and am not conveying any rights to any intellectual 7 | // property of any third parties. 8 | 9 | package donna 10 | 11 | import ( 12 | `fmt` 13 | `time` 14 | ) 15 | 16 | // Returns row number in 0..7 range for the given square. 17 | func row(square int) int { 18 | return square >> 3 19 | } 20 | 21 | // Returns column number in 0..7 range for the given square. 22 | func col(square int) int { 23 | return square & 7 24 | } 25 | 26 | // Returns both row and column numbers for the given square. 27 | func coordinate(square int) (int, int) { 28 | return row(square), col(square) 29 | } 30 | 31 | // Returns relative rank for the square in 0..7 range. For example E2 is rank 1 32 | // for white and rank 6 for black. 33 | func rank(color int, square int) int { 34 | return row(square) ^ (color * 7) 35 | } 36 | 37 | // Returns 0..63 square number for the given row/column coordinate. 38 | func square(row, column int) int { 39 | return (row << 3) + column 40 | } 41 | 42 | // Poor man's ternary. Works best with scalar yes and no. 43 | func let(ok bool, yes, no int) int { 44 | if ok { 45 | return yes 46 | } 47 | 48 | return no 49 | } 50 | 51 | // Flips the square verically for white (ex. E2 becomes E7). 52 | func flip(color int, square int) int { 53 | if color == White { 54 | return square ^ 56 55 | } 56 | return square 57 | } 58 | 59 | // Returns a bitmask with light or dark squares set matching the color of the 60 | // square. 61 | func same(square int) Bitmask { 62 | if (bit[square] & maskDark).any() { 63 | return maskDark 64 | } 65 | 66 | return ^maskDark 67 | } 68 | 69 | // Returns a distance between current node and the root one. 70 | func ply() int { 71 | return node - rootNode 72 | } 73 | 74 | // Returns a score of getting mated in given number of plies. 75 | func matedIn(ply int) int { 76 | return ply - Checkmate 77 | } 78 | 79 | // Returns a score of mating an opponent in given number of plies. 80 | func matingIn(ply int) int { 81 | return Checkmate - ply 82 | } 83 | 84 | // Adjusts values of alpha and beta based on how close we are 85 | // to checkmate or be checkmated. 86 | func mateDistance(alpha, beta, ply int) (int, int) { 87 | return max(matedIn(ply), alpha), min(matingIn(ply + 1), beta) 88 | } 89 | 90 | func isMate(score int) bool { 91 | return abs(score) >= Checkmate - MaxPly 92 | } 93 | 94 | // Integer version of math/abs. 95 | func abs(n int) int { 96 | if n < 0 { 97 | return -n 98 | } 99 | return n 100 | } 101 | 102 | func min(x, y int) int { 103 | if x < y { 104 | return x 105 | } 106 | return y 107 | } 108 | 109 | func max(x, y int) int { 110 | if x > y { 111 | return x 112 | } 113 | return y 114 | } 115 | 116 | func min64(x, y int64) int64 { 117 | if x < y { 118 | return x 119 | } 120 | return y 121 | } 122 | 123 | func max64(x, y int64) int64 { 124 | if x > y { 125 | return x 126 | } 127 | return y 128 | } 129 | 130 | // Returns time in milliseconds elapsed since the given start time. 131 | func since(start time.Time) int64 { 132 | return time.Since(start).Nanoseconds() / 1000000 133 | } 134 | 135 | // Returns nodes per second search speed for the given time duration. 136 | func nps(duration int64) int64 { 137 | nodes := int64(game.nodes + game.qnodes) * 1000 138 | if duration != 0 { 139 | return nodes / duration 140 | } 141 | return nodes 142 | } 143 | 144 | // Returns a number of used items in a sample of 1000 cache entries. 145 | func hashfull() int { 146 | count := 0 147 | 148 | if cacheSize := len(game.cache); cacheSize > 1000 { 149 | start := (game.nodes + game.qnodes) % (cacheSize - 1000) // 0 <= start < cacheSize - 1000. 150 | for i := start; i < start + 1000; i++ { 151 | if game.cache[i].token() == game.token { 152 | count++ 153 | } 154 | } 155 | } 156 | 157 | return count 158 | } 159 | 160 | // Formats time duration in milliseconds in human readable form (MM:SS.XXX). 161 | func ms(duration int64) string { 162 | mm := duration / 1000 / 60 163 | ss := duration / 1000 % 60 164 | xx := duration - mm * 1000 * 60 - ss * 1000 165 | 166 | return fmt.Sprintf(`%02d:%02d.%03d`, mm, ss, xx) 167 | } 168 | 169 | func C(color int) string { 170 | return [2]string{`white`, `black`}[color] 171 | } 172 | 173 | func Summary(metrics map[string]interface{}) { 174 | phase := metrics[`Phase`].(int) 175 | tally := metrics[`PST`].(Score) 176 | material := metrics[`Imbalance`].(Score) 177 | final := metrics[`Final`].(Score) 178 | units := float32(onePawn) 179 | 180 | fmt.Println() 181 | fmt.Printf("Metric MidGame | EndGame | Blended\n") 182 | fmt.Printf(" W B W-B | W B W-B | (%d) \n", phase) 183 | fmt.Printf("-----------------------------------+-----------------------+--------\n") 184 | fmt.Printf("%-12s - - %5.2f | - - %5.2f > %5.2f\n", `PST`, 185 | float32(tally.midgame)/units, float32(tally.endgame)/units, float32(tally.blended(phase))/units) 186 | fmt.Printf("%-12s - - %5.2f | - - %5.2f > %5.2f\n", `Imbalance`, 187 | float32(material.midgame)/units, float32(material.endgame)/units, float32(material.blended(phase))/units) 188 | 189 | for _, tag := range([]string{`Tempo`, `Center`, `Threats`, `Pawns`, `Passers`, `Mobility`, `+Pieces`, `-Knights`, `-Bishops`, `-Rooks`, `-Queens`, `+King`, `-Cover`, `-Safety`}) { 190 | white := metrics[tag].(Total).white 191 | black := metrics[tag].(Total).black 192 | 193 | var score Score 194 | score.add(white).sub(black) 195 | 196 | if tag[0:1] == `+` { 197 | tag = tag[1:] 198 | } else if tag[0:1] == `-` { 199 | tag = ` ` + tag[1:] 200 | } 201 | 202 | fmt.Printf("%-12s %5.2f %5.2f %5.2f | %5.2f %5.2f %5.2f > %5.2f\n", tag, 203 | float32(white.midgame)/units, float32(black.midgame)/units, float32(score.midgame)/units, 204 | float32(white.endgame)/units, float32(black.endgame)/units, float32(score.endgame)/units, 205 | float32(score.blended(phase))/units) 206 | } 207 | fmt.Printf("%-12s - - %5.2f | - - %5.2f > %5.2f\n\n", `Final Score`, 208 | float32(final.midgame)/units, float32(final.endgame)/units, float32(final.blended(phase))/units) 209 | } 210 | 211 | // Logging wrapper around fmt.Printf() that could be turned on as needed. Typical 212 | // usage is Log(); defer Log() in tests. 213 | func Log(args ...interface{}) { 214 | switch len(args) { 215 | case 0: 216 | // Calling Log() with no arguments flips the logging setting. 217 | engine.log = !engine.log 218 | engine.fancy = !engine.fancy 219 | case 1: 220 | switch args[0].(type) { 221 | case bool: 222 | engine.log = args[0].(bool) 223 | engine.fancy = args[0].(bool) 224 | default: 225 | if engine.log { 226 | fmt.Println(args...) 227 | } 228 | } 229 | default: 230 | if engine.log { 231 | fmt.Printf(args[0].(string), args[1:]...) 232 | } 233 | } 234 | } 235 | --------------------------------------------------------------------------------