├── .gitignore ├── Godeps ├── Godeps.json └── Readme ├── LICENSE ├── attack.go ├── balancer.go ├── bitboard.go ├── bitboard_magic.go ├── bitboard_setup.go ├── bitwise_math.go ├── bitwise_math_test.go ├── board.go ├── create_binaries.sh ├── eval.go ├── eval_pawns.go ├── game_timer.go ├── history.go ├── killer.go ├── magics.json ├── main.go ├── make.go ├── memory.go ├── move.go ├── move_gen.go ├── move_gen_test.go ├── move_validation.go ├── notation.go ├── notation_test.go ├── notes.txt ├── pawn_hash.go ├── piece.go ├── pv.go ├── rand.go ├── readme.md ├── recycler.go ├── recycler_test.go ├── repetitions.go ├── search.go ├── search_test.go ├── selector.go ├── sort.go ├── split.go ├── stack.go ├── test_suites ├── all.epd ├── bratko_kopek.epd ├── challenging.epd ├── kaufman.epd ├── null_move.epd ├── perftsuite.epd ├── wac_150.epd ├── wac_300.epd └── wac_75.epd ├── uci.go ├── utilities.go ├── vendor └── github.com │ └── pkg │ └── profile │ ├── .travis.yml │ ├── AUTHORS │ ├── LICENSE │ ├── README.md │ └── profile.go ├── worker.go └── zobrist.go /.gitignore: -------------------------------------------------------------------------------- 1 | ./chess 2 | ./gopher_check 3 | ./log.txt 4 | ./*.pprof 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/stephenjlovell/gopher_check", 3 | "GoVersion": "go1.7", 4 | "GodepVersion": "v74", 5 | "Deps": [ 6 | { 7 | "ImportPath": "github.com/pkg/profile", 8 | "Comment": "v1.0.0-9-g8a808a6", 9 | "Rev": "8a808a6967b79da66deacfe508b26d398a69518f" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | // this software and associated documentation files (the "Software"), to deal in 8 | // the Software without restriction, including without limitation the rights to 9 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | // the Software, and to permit persons to whom the Software is furnished to do so, 11 | // subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | //----------------------------------------------------------------------------------- 23 | -------------------------------------------------------------------------------- /attack.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import "fmt" 9 | 10 | func attackMap(brd *Board, occ BB, sq int) BB { 11 | bb := ((pawnAttackMasks[BLACK][sq] & brd.pieces[WHITE][PAWN]) | 12 | (pawnAttackMasks[WHITE][sq] & brd.pieces[BLACK][PAWN])) | // Pawns 13 | (knightMasks[sq] & (brd.pieces[WHITE][KNIGHT] | brd.pieces[BLACK][KNIGHT])) | // Knights 14 | (kingMasks[sq] & (brd.pieces[WHITE][KING] | brd.pieces[BLACK][KING])) // Kings 15 | if bSliders := (brd.pieces[WHITE][BISHOP] | brd.pieces[BLACK][BISHOP] | brd.pieces[WHITE][QUEEN] | brd.pieces[BLACK][QUEEN]); bSliders&bishopMasks[sq] > 0 { 16 | bb |= (bishopAttacks(occ, sq) & bSliders) // Bishops and Queens 17 | } 18 | if rSliders := (brd.pieces[WHITE][ROOK] | brd.pieces[BLACK][ROOK] | brd.pieces[WHITE][QUEEN] | brd.pieces[BLACK][QUEEN]); rSliders&rookMasks[sq] > 0 { 19 | bb |= (rookAttacks(occ, sq) & rSliders) // Rooks and Queens 20 | } 21 | return bb 22 | } 23 | 24 | func colorAttackMap(brd *Board, occ BB, sq int, c, e uint8) BB { 25 | bb := (pawnAttackMasks[e][sq] & brd.pieces[c][PAWN]) | // Pawns 26 | (knightMasks[sq] & brd.pieces[c][KNIGHT]) | // Knights 27 | (kingMasks[sq] & brd.pieces[c][KING]) // Kings 28 | if bSliders := (brd.pieces[c][BISHOP] | brd.pieces[c][QUEEN]); bSliders&bishopMasks[sq] > 0 { 29 | bb |= (bishopAttacks(occ, sq) & bSliders) // Bishops and Queens 30 | } 31 | if rSliders := (brd.pieces[c][ROOK] | brd.pieces[c][QUEEN]); rSliders&rookMasks[sq] > 0 { 32 | bb |= (rookAttacks(occ, sq) & rSliders) // Rooks and Queens 33 | } 34 | return bb 35 | } 36 | 37 | func isAttackedBy(brd *Board, occ BB, sq int, attacker, defender uint8) bool { 38 | return (pawnAttackMasks[defender][sq]&brd.pieces[attacker][PAWN] > 0) || // Pawns 39 | (knightMasks[sq]&(brd.pieces[attacker][KNIGHT]) > 0) || // Knights 40 | (kingMasks[sq]&(brd.pieces[attacker][KING]) > 0) || // Kings 41 | (bishopAttacks(occ, sq)&(brd.pieces[attacker][BISHOP]|brd.pieces[attacker][QUEEN]) > 0) || // Bishops and Queens 42 | (rookAttacks(occ, sq)&(brd.pieces[attacker][ROOK]|brd.pieces[attacker][QUEEN]) > 0) // Rooks and Queens 43 | } 44 | 45 | func pinnedCanMove(brd *Board, from, to int, c, e uint8) bool { 46 | return isPinned(brd, brd.AllOccupied(), from, c, e)&sqMaskOn[to] > 0 47 | } 48 | 49 | // Determines if a piece is blocking a ray attack to its king, and cannot move off this ray 50 | // without placing its king in check. 51 | // Returns the area to which the piece can move without leaving its king in check. 52 | // 1. Find the displacement vector between the piece at sq and its own king and determine if it 53 | // lies along a valid ray attack. If the vector is a valid ray attack: 54 | // 2. Scan toward the king to see if there are any other pieces blocking this route to the king. 55 | // 3. Scan in the opposite direction to see detect any potential threats along this ray. 56 | 57 | // Return a bitboard of locations the piece at sq can move to without leaving the king in check. 58 | 59 | func isPinned(brd *Board, occ BB, sq int, c, e uint8) BB { 60 | var line, attacks, threat BB 61 | kingSq := brd.KingSq(c) 62 | line = lineMasks[sq][kingSq] 63 | if line > 0 { // can only be pinned if on a ray to the king. 64 | if directions[sq][kingSq] < NORTH { // direction toward king 65 | attacks = bishopAttacks(occ, sq) 66 | threat = line & attacks & (brd.pieces[e][BISHOP] | brd.pieces[e][QUEEN]) 67 | } else { 68 | attacks = rookAttacks(occ, sq) 69 | threat = line & attacks & (brd.pieces[e][ROOK] | brd.pieces[e][QUEEN]) 70 | } 71 | if threat > 0 && (attacks&brd.pieces[c][KING]) > 0 { 72 | return line & attacks 73 | } 74 | } 75 | return BB(ANY_SQUARE_MASK) 76 | } 77 | 78 | // The Static Exchange Evaluation (SEE) heuristic provides a way to determine if a capture 79 | // is a 'winning' or 'losing' capture. 80 | // 1. When a capture results in an exchange of pieces by both sides, SEE is used to determine the 81 | // net gain/loss in material for the side initiating the exchange. 82 | // 2. SEE scoring of moves is used for move ordering of captures at critical nodes. 83 | // 3. During s.quiescence search, SEE is used to prune losing captures. This provides a very low-risk 84 | // way of reducing the size of the q-search without impacting playing strength. 85 | const ( 86 | SEE_MIN = -780 // worst possible outcome (trading a queen for a pawn) 87 | // SEE_MAX = 880 // best outcome (capturing an undefended queen) 88 | ) 89 | 90 | func getSee(brd *Board, from, to int, capturedPiece Piece) int { 91 | var nextVictim int 92 | var t Piece 93 | // var t, last_t Piece 94 | tempColor := brd.Enemy() 95 | // get initial map of all squares directly attacking this square (does not include 'discovered'/hidden attacks) 96 | bAttackers := brd.pieces[WHITE][BISHOP] | brd.pieces[BLACK][BISHOP] | 97 | brd.pieces[WHITE][QUEEN] | brd.pieces[BLACK][QUEEN] 98 | rAttackers := brd.pieces[WHITE][ROOK] | brd.pieces[BLACK][ROOK] | 99 | brd.pieces[WHITE][QUEEN] | brd.pieces[BLACK][QUEEN] 100 | 101 | tempOcc := brd.AllOccupied() 102 | tempMap := attackMap(brd, tempOcc, to) 103 | 104 | var tempPieces BB 105 | 106 | var pieceList [20]int 107 | count := 1 108 | 109 | if capturedPiece == KING { 110 | // this move is illegal and will be discarded by the move selector. Return the lowest possible 111 | // SEE value so that this move will be put at end of list. If cutoff occurs before then, 112 | // the cost of detecting the illegal move will be saved. 113 | fmt.Println("info string king capture detected in getSee()!") 114 | fmt.Printf("info string %s%s x %s", SquareString(from), SquareString(to), sanChars[capturedPiece]) 115 | brd.Print() 116 | panic("king capture detected in getSee()!") 117 | // return SEE_MIN 118 | } 119 | t = brd.TypeAt(from) 120 | if t == KING { // Only commit to the attack if target piece is undefended. 121 | if tempMap&brd.occupied[tempColor] > 0 { 122 | return SEE_MIN 123 | } else { 124 | return pieceValues[capturedPiece] 125 | } 126 | } 127 | // before entering the main loop, perform each step once for the initial attacking piece. 128 | // This ensures that the moved piece is the first to capture. 129 | pieceList[0] = pieceValues[capturedPiece] 130 | nextVictim = brd.ValueAt(from) 131 | 132 | tempOcc.Clear(from) 133 | if t != KNIGHT && t != KING { // if the attacker was a pawn, bishop, rook, or queen, re-scan for hidden attacks: 134 | if t == PAWN || t == BISHOP || t == QUEEN { 135 | tempMap |= bishopAttacks(tempOcc, to) & bAttackers 136 | } 137 | if t == PAWN || t == ROOK || t == QUEEN { 138 | tempMap |= rookAttacks(tempOcc, to) & rAttackers 139 | } 140 | } 141 | 142 | for tempMap &= tempOcc; tempMap > 0; tempMap &= tempOcc { 143 | for t = PAWN; t <= KING; t++ { // loop over piece ts in order of value. 144 | tempPieces = brd.pieces[tempColor][t] & tempMap 145 | if tempPieces > 0 { 146 | break 147 | } // stop as soon as a match is found. 148 | } 149 | if t >= KING { 150 | if t == KING { 151 | if tempMap&brd.occupied[tempColor^1] > 0 { 152 | break // only commit a king to the attack if the other side has no defenders left. 153 | } 154 | } 155 | break 156 | } 157 | 158 | pieceList[count] = nextVictim - pieceList[count-1] 159 | nextVictim = pieceValues[t] 160 | 161 | count++ 162 | 163 | if (pieceList[count-1] - nextVictim) > 0 { // validate this. 164 | break 165 | } 166 | 167 | tempOcc ^= (tempPieces & -tempPieces) // merge the first set bit of temp_pieces into temp_occ 168 | if t != KNIGHT && t != KING { 169 | if t == PAWN || t == BISHOP || t == QUEEN { 170 | tempMap |= (bishopAttacks(tempOcc, to) & bAttackers) 171 | } 172 | if t == ROOK || t == QUEEN { 173 | tempMap |= (rookAttacks(tempOcc, to) & rAttackers) 174 | } 175 | } 176 | tempColor ^= 1 177 | } 178 | 179 | for count-1 > 0 { 180 | count-- 181 | pieceList[count-1] = -max(-pieceList[count-1], pieceList[count]) 182 | } 183 | // fmt.Printf(" %d ", piece_list[0]) 184 | return pieceList[0] 185 | } 186 | 187 | func isCheckmate(brd *Board, inCheck bool) bool { 188 | if !inCheck { 189 | return false 190 | } 191 | c := brd.c 192 | e := brd.Enemy() 193 | var to int 194 | from := brd.KingSq(c) 195 | occ := brd.AllOccupied() 196 | for t := kingMasks[from] & (^brd.occupied[c]); t > 0; t.Clear(to) { // generate to squares 197 | to = furthestForward(c, t) 198 | if !isAttackedBy(brd, occAfterMove(occ, from, to), to, e, c) { 199 | return false 200 | } 201 | } 202 | return true 203 | } 204 | 205 | func occAfterMove(occ BB, from, to int) BB { 206 | return (occ | sqMaskOn[to]) & sqMaskOff[from] 207 | } 208 | -------------------------------------------------------------------------------- /balancer.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "sync" 11 | // "time" 12 | ) 13 | 14 | // 2-Level Locking Scheme 15 | // 16 | // Load Balancer (Global-level) Lock 17 | // 18 | // - Protects integrity of SP lists maintained by workers (Adding / Removing SPs) 19 | // USAGE: 20 | // - When a worker self-assigns (searches for an SP from available SP lists), it should lock 21 | // the load balancer. Load balancer should only be unlocked after the worker is registered 22 | // with the SP. 23 | // - Finding the best SP should ideally be encapsulated by the load balancer. 24 | // 25 | // Split Point (local) Lock 26 | // 27 | // - Protects search state for workers collaborating on same SP. 28 | // - Protects info on which workers are collaborating at this SP. 29 | // USAGE: 30 | // - When the master directly assigns a worker, it should register the worker immediately 31 | // with the SP before sending the SP to the worker to avoid a data race with the SP's 32 | // WaitGroup. 33 | 34 | var loadBalancer *Balancer 35 | 36 | func setupLoadBalancer(numCPU int) { 37 | loadBalancer = NewLoadBalancer(uint8(numCPU)) 38 | loadBalancer.Start(numCPU) 39 | } 40 | 41 | func NewLoadBalancer(numWorkers uint8) *Balancer { 42 | b := &Balancer{ 43 | workers: make([]*Worker, numWorkers), 44 | done: make(chan *Worker, numWorkers), 45 | } 46 | for i := uint8(0); i < numWorkers; i++ { 47 | b.workers[i] = &Worker{ 48 | mask: 1 << i, 49 | index: i, 50 | spList: make(SPList, 0, MAX_DEPTH), 51 | stk: NewStack(), 52 | ptt: NewPawnTT(), 53 | assignSp: make(chan *SplitPoint, 1), 54 | recycler: NewRecycler(512), 55 | } 56 | } 57 | return b 58 | } 59 | 60 | type Balancer struct { 61 | workers []*Worker 62 | // sync.Mutex 63 | once sync.Once 64 | done chan *Worker 65 | } 66 | 67 | func (b *Balancer) Start(numCPU int) { 68 | b.once.Do(func() { 69 | // fmt.Printf("Initializing %d workers\n", numCPU) 70 | for _, w := range b.workers[1:] { 71 | w.Help(b) // Start each worker except for the root worker. 72 | } 73 | }) 74 | } 75 | 76 | func (b *Balancer) Overhead() int { 77 | overhead := 0 78 | for _, w := range b.workers { 79 | overhead += w.searchOverhead 80 | } 81 | return overhead 82 | } 83 | 84 | func (b *Balancer) RootWorker() *Worker { 85 | return b.workers[0] 86 | } 87 | 88 | func (b *Balancer) AddSP(w *Worker, sp *SplitPoint) { 89 | w.Lock() 90 | w.spList.Push(sp) 91 | w.Unlock() 92 | w.currentSp = sp 93 | 94 | FlushIdle: // If there are any idle workers, assign them now. 95 | for { 96 | select { 97 | case idle := <-b.done: 98 | sp.AddServant(idle.mask) 99 | idle.assignSp <- sp 100 | default: 101 | break FlushIdle 102 | } 103 | } 104 | } 105 | 106 | // RemoveSP prevents new workers from being assigned to w.current_sp without cancelling 107 | // any ongoing work at this SP. 108 | func (b *Balancer) RemoveSP(w *Worker) { 109 | w.Lock() 110 | w.spList.Pop() 111 | w.Unlock() 112 | w.currentSp = w.currentSp.parent 113 | } 114 | 115 | func (b *Balancer) Print() { 116 | for i, w := range b.workers { 117 | if len(w.spList) > 0 { 118 | w.Lock() 119 | fmt.Printf("w%d: ", i) 120 | for _, sp := range w.spList { 121 | fmt.Printf("%d, ", (sp.brd.hashKey >> 48)) 122 | } 123 | fmt.Printf("\n") 124 | w.Unlock() 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /bitboard.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | ) 11 | 12 | const ( 13 | ANY_SQUARE_MASK = (1 << 64) - 1 14 | ) 15 | 16 | type BB uint64 17 | 18 | func (b *BB) Clear(sq int) { 19 | *b &= sqMaskOff[sq] 20 | } 21 | 22 | func (b *BB) Add(sq int) { 23 | *b |= sqMaskOn[sq] 24 | } 25 | 26 | func (b BB) Print() { 27 | var row, sq string 28 | fmt.Println(" A B C D E F G H") 29 | for i := 63; i >= 0; i-- { 30 | if sqMaskOn[i]&b > 0 { 31 | sq = " 1" 32 | } else { 33 | sq = " 0" 34 | } 35 | row = sq + row 36 | if i%8 == 0 { 37 | fmt.Printf("%d%s\n", (i/8)+1, row) 38 | row = "" 39 | } 40 | } 41 | fmt.Printf(" A B C D E F G H\n\n") 42 | } 43 | 44 | func slidingAttacks(piece Piece, occ BB, sq int) BB { 45 | switch piece { 46 | case BISHOP: 47 | return bishopAttacks(occ, sq) 48 | case ROOK: 49 | return rookAttacks(occ, sq) 50 | case QUEEN: 51 | return queenAttacks(occ, sq) 52 | default: 53 | return BB(0) 54 | } 55 | } 56 | 57 | // TODO: incorporate pawn_attacks() into movegen 58 | 59 | func pawnAttacks(brd *Board, c uint8) (BB, BB) { // returns (left_attacks, right_attacks) separately 60 | if c == WHITE { 61 | return ((brd.pieces[WHITE][PAWN] & (^columnMasks[0])) << 7), ((brd.pieces[WHITE][PAWN] & (^columnMasks[7])) << 9) 62 | } else { 63 | return ((brd.pieces[BLACK][PAWN] & (^columnMasks[7])) >> 7), ((brd.pieces[BLACK][PAWN] & (^columnMasks[0])) >> 9) 64 | } 65 | } 66 | 67 | func generateBishopAttacks(occ BB, sq int) BB { 68 | return scanUp(occ, NW, sq) | scanUp(occ, NE, sq) | scanDown(occ, SE, sq) | scanDown(occ, SW, sq) 69 | } 70 | 71 | func generateRookAttacks(occ BB, sq int) BB { 72 | return scanUp(occ, NORTH, sq) | scanUp(occ, EAST, sq) | scanDown(occ, SOUTH, sq) | scanDown(occ, WEST, sq) 73 | } 74 | 75 | func scanDown(occ BB, dir, sq int) BB { 76 | ray := rayMasks[dir][sq] 77 | blockers := (ray & occ) 78 | if blockers > 0 { 79 | ray ^= (rayMasks[dir][msb(blockers)]) // chop off end of ray after first blocking piece. 80 | } 81 | return ray 82 | } 83 | 84 | func scanUp(occ BB, dir, sq int) BB { 85 | ray := rayMasks[dir][sq] 86 | blockers := (ray & occ) 87 | if blockers > 0 { 88 | ray ^= (rayMasks[dir][lsb(blockers)]) // chop off end of ray after first blocking piece. 89 | } 90 | return ray 91 | } 92 | -------------------------------------------------------------------------------- /bitboard_magic.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "encoding/json" 10 | "fmt" 11 | "io/ioutil" 12 | "os" 13 | "sync" 14 | ) 15 | 16 | const ( 17 | MAGIC_INDEX_SIZE = 12 18 | MAGIC_DB_SIZE = 1 << MAGIC_INDEX_SIZE 19 | MAGICS_JSON = "magics.json" 20 | ) 21 | 22 | type MagicData struct { 23 | BishopMagics [64]BB `json:"bishopMagics"` 24 | RookMagics [64]BB `json:"rookMagics"` 25 | } 26 | 27 | // In testing, homogenous array move DB actually outperformed a 'Fancy' 28 | // magic bitboard approach implemented using slices. 29 | var bishopMagicMoves, rookMagicMoves [64][MAGIC_DB_SIZE]BB 30 | 31 | var bishopMagics, rookMagics [64]BB 32 | var bishopMagicMasks, rookMagicMasks [64]BB 33 | 34 | func bishopAttacks(occ BB, sq int) BB { 35 | return bishopMagicMoves[sq][magicIndex(occ, bishopMagicMasks[sq], bishopMagics[sq])] 36 | } 37 | 38 | func rookAttacks(occ BB, sq int) BB { 39 | return rookMagicMoves[sq][magicIndex(occ, rookMagicMasks[sq], rookMagics[sq])] 40 | } 41 | 42 | func queenAttacks(occ BB, sq int) BB { 43 | return (bishopAttacks(occ, sq) | rookAttacks(occ, sq)) 44 | } 45 | 46 | func magicIndex(occ, sqMask, magic BB) int { 47 | return int(((occ & sqMask) * magic) >> (64 - MAGIC_INDEX_SIZE)) 48 | } 49 | 50 | // if magics have already been generated, just fetch them from 'magics.json'. 51 | // otherwise, generate the magics and write them to disk. 52 | func setupMagicMoveGen() { 53 | var wg sync.WaitGroup 54 | 55 | magicsNeeded := false 56 | if _, err := os.Stat(MAGICS_JSON); err == nil { 57 | if !loadMagics() { // if magics failed to load for any reason, we'll have to generate them. 58 | magicsNeeded = true 59 | } 60 | } else { 61 | magicsNeeded = true 62 | } 63 | if magicsNeeded { 64 | fmt.Printf("Calculating magics") 65 | wg.Add(64 * 2) 66 | } 67 | 68 | setupMagicsForPiece(magicsNeeded, &wg, &bishopMagicMasks, &bishopMasks, &bishopMagics, 69 | &bishopMagicMoves, generateBishopAttacks) 70 | setupMagicsForPiece(magicsNeeded, &wg, &rookMagicMasks, &rookMasks, &rookMagics, 71 | &rookMagicMoves, generateRookAttacks) 72 | 73 | if magicsNeeded { 74 | wg.Wait() 75 | writeMagicsToDisk() 76 | fmt.Printf("done!\n\n") 77 | } 78 | } 79 | 80 | func checkError(e error) { 81 | if e != nil { 82 | panic(e) 83 | } 84 | } 85 | 86 | func writeMagicsToDisk() { 87 | magics := MagicData{ 88 | BishopMagics: bishopMagics, 89 | RookMagics: rookMagics, 90 | } 91 | 92 | f, err := os.Create(MAGICS_JSON) 93 | checkError(err) 94 | defer f.Close() 95 | 96 | data, err := json.Marshal(magics) 97 | checkError(err) 98 | 99 | _, err = f.Write(data) // write the magics to disk as JSON. 100 | checkError(err) 101 | } 102 | 103 | func loadMagics() (success bool) { 104 | defer func() { 105 | if r := recover(); r != nil { 106 | fmt.Printf("Failure reading magics from disk.") 107 | success = false // recover any panic 108 | } 109 | }() 110 | 111 | data, err := ioutil.ReadFile(MAGICS_JSON) 112 | checkError(err) 113 | 114 | magics := &MagicData{} 115 | 116 | err = json.Unmarshal(data, magics) 117 | checkError(err) 118 | 119 | bishopMagics = magics.BishopMagics 120 | rookMagics = magics.RookMagics 121 | fmt.Printf("Magics read from disk.\n") 122 | return true 123 | } 124 | 125 | func setupMagicsForPiece(magicsNeeded bool, wg *sync.WaitGroup, magicMasks, masks, magics *[64]BB, 126 | moves *[64][MAGIC_DB_SIZE]BB, genFn func(BB, int) BB) { 127 | 128 | for sq := 0; sq < 64; sq++ { 129 | edgeMask := (columnMasks[0]|columnMasks[7])&(^columnMasks[column(sq)]) | 130 | (rowMasks[0]|rowMasks[7])&(^rowMasks[row(sq)]) 131 | magicMasks[sq] = masks[sq] & (^edgeMask) 132 | 133 | // Enumerate all subsets of the sq_mask using the Carry-Rippler technique: 134 | // https://chessprogramming.wikispaces.com/Traversing+Subsets+of+a+Set#Enumerating%20All%20Subsets-All%20Subsets%20of%20any%20Set 135 | refAttacks, occupied := [MAGIC_DB_SIZE]BB{}, [MAGIC_DB_SIZE]BB{} 136 | n := 0 137 | for occ := BB(0); occ != 0 || n == 0; occ = (occ - magicMasks[sq]) & magicMasks[sq] { 138 | refAttacks[n] = genFn(occ, sq) // save the attack bitboard for each subset for later use. 139 | occupied[n] = occ 140 | n++ // count the number of subsets 141 | } 142 | 143 | if magicsNeeded { 144 | go func(sq int) { // Calculate a magic for square sq in parallel 145 | randGenerator := NewRngKiss(73) // random number generator optimized for finding magics 146 | i := 0 147 | for i < n { 148 | // try random numbers until a suitable candidate is found. 149 | for magics[sq] = randGenerator.RandomMagic(sq); popCount((magicMasks[sq]*magics[sq])>>(64-MAGIC_INDEX_SIZE)) < MAGIC_INDEX_SIZE; { 150 | magics[sq] = randGenerator.RandomMagic(sq) 151 | } 152 | // if the last candidate magic failed, clear out any attack maps already placed in the moves DB 153 | moves[sq] = [MAGIC_DB_SIZE]BB{} 154 | for i = 0; i < n; i++ { 155 | // verify the candidate magic will index each possible occupancy subset to either a new slot, 156 | // or a slot with the same attack map (only benign collisions are allowed). 157 | attack := &moves[sq][magicIndex(occupied[i], magicMasks[sq], magics[sq])] 158 | 159 | if *attack != BB(0) && *attack != refAttacks[i] { 160 | break // keep going unless we hit a harmful collision 161 | } 162 | *attack = refAttacks[i] // populate the moves DB so we can detect collisions. 163 | } 164 | } // if every possible occupancy has been mapped to the correct attack set, we are done. 165 | fmt.Printf(".") 166 | wg.Done() 167 | }(sq) 168 | } else { 169 | for i := 0; i < n; i++ { 170 | moves[sq][magicIndex(occupied[i], magicMasks[sq], magics[sq])] = refAttacks[i] 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /bitboard_setup.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | // "fmt" 9 | 10 | const ( // direction codes (0...8) 11 | NW = iota 12 | NE 13 | SE 14 | SW 15 | NORTH // 4 16 | EAST 17 | SOUTH 18 | WEST // 7 19 | DIR_INVALID 20 | ) 21 | 22 | const ( 23 | OFF_SINGLE = iota 24 | OFF_DOUBLE 25 | OFF_LEFT 26 | OFF_RIGHT 27 | ) 28 | 29 | var pawnFromOffsets = [2][4]int{{8, 16, 9, 7}, {-8, -16, -7, -9}} 30 | var knightOffsets = [8]int{-17, -15, -10, -6, 6, 10, 15, 17} 31 | var bishopOffsets = [4]int{7, 9, -7, -9} 32 | var rookOffsets = [4]int{8, 1, -8, -1} 33 | var kingOffsets = [8]int{-9, -7, 7, 9, -8, -1, 1, 8} 34 | var pawnAttackOffsets = [4]int{9, 7, -9, -7} 35 | 36 | var directions [64][64]int 37 | 38 | var oppositeDir = [16]int{SE, SW, NW, NE, SOUTH, WEST, NORTH, EAST, DIR_INVALID} 39 | 40 | var rowMasks, columnMasks [8]BB 41 | 42 | var castleMasks [16]BB 43 | 44 | var pawnIsolatedMasks, pawnSideMasks, pawnDoubledMasks, knightMasks, bishopMasks, rookMasks, 45 | queenMasks, kingMasks, sqMaskOn, sqMaskOff [64]BB 46 | 47 | var intervening, lineMasks [64][64]BB 48 | 49 | var castleQueensideIntervening, castleKingsideIntervening [2]BB 50 | 51 | var pawnAttackMasks, pawnPassedMasks, pawnAttackSpans, pawnBackwardSpans, pawnFrontSpans, 52 | pawnStopMasks, kingZoneMasks, kingShieldMasks [2][64]BB 53 | 54 | var rayMasks [8][64]BB 55 | 56 | var pawnStopSq, pawnPromoteSq [2][64]int 57 | 58 | func manhattanDistance(from, to int) int { 59 | return abs(row(from)-row(to)) + abs(column(from)-column(to)) 60 | } 61 | 62 | func setupSquareMasks() { 63 | for i := 0; i < 64; i++ { 64 | sqMaskOn[i] = BB(1 << uint(i)) 65 | sqMaskOff[i] = (^sqMaskOn[i]) 66 | } 67 | } 68 | 69 | func setupPawnMasks() { 70 | var sq int 71 | for i := 0; i < 64; i++ { 72 | pawnSideMasks[i] = (kingMasks[i] & rowMasks[row(i)]) 73 | if i < 56 { 74 | pawnStopMasks[WHITE][i] = sqMaskOn[i] << 8 75 | pawnStopSq[WHITE][i] = i + 8 76 | for j := 0; j < 2; j++ { 77 | sq = i + pawnAttackOffsets[j] 78 | if manhattanDistance(sq, i) == 2 { 79 | pawnAttackMasks[WHITE][i].Add(sq) 80 | } 81 | } 82 | } 83 | if i > 7 { 84 | pawnStopMasks[BLACK][i] = sqMaskOn[i] >> 8 85 | pawnStopSq[BLACK][i] = i - 8 86 | for j := 2; j < 4; j++ { 87 | sq = i + pawnAttackOffsets[j] 88 | if manhattanDistance(sq, i) == 2 { 89 | pawnAttackMasks[BLACK][i].Add(sq) 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | func setupKnightMasks() { 97 | var sq int 98 | for i := 0; i < 64; i++ { 99 | for j := 0; j < 8; j++ { 100 | sq = i + knightOffsets[j] 101 | if onBoard(sq) && manhattanDistance(sq, i) == 3 { 102 | knightMasks[i] |= sqMaskOn[sq] 103 | } 104 | } 105 | } 106 | } 107 | 108 | func setupBishopMasks() { 109 | var previous, current, offset int 110 | for i := 0; i < 64; i++ { 111 | for j := 0; j < 4; j++ { 112 | previous = i 113 | offset = bishopOffsets[j] 114 | current = i + offset 115 | for onBoard(current) && manhattanDistance(current, previous) == 2 { 116 | rayMasks[j][i].Add(current) 117 | previous = current 118 | current += offset 119 | } 120 | } 121 | bishopMasks[i] = rayMasks[NW][i] | rayMasks[NE][i] | rayMasks[SE][i] | rayMasks[SW][i] 122 | } 123 | } 124 | 125 | func setupRookMasks() { 126 | var previous, current, offset int 127 | for i := 0; i < 64; i++ { 128 | for j := 0; j < 4; j++ { 129 | previous = i 130 | offset = rookOffsets[j] 131 | current = i + offset 132 | for onBoard(current) && manhattanDistance(current, previous) == 1 { 133 | rayMasks[j+4][i].Add(current) 134 | previous = current 135 | current += offset 136 | } 137 | } 138 | rookMasks[i] = rayMasks[NORTH][i] | rayMasks[SOUTH][i] | rayMasks[EAST][i] | rayMasks[WEST][i] 139 | } 140 | } 141 | 142 | func setupQueenMasks() { 143 | for i := 0; i < 64; i++ { 144 | queenMasks[i] = bishopMasks[i] | rookMasks[i] 145 | } 146 | } 147 | 148 | func setupKingMasks() { 149 | var sq int 150 | var center BB 151 | for i := 0; i < 64; i++ { 152 | for j := 0; j < 8; j++ { 153 | sq = i + kingOffsets[j] 154 | if onBoard(sq) && manhattanDistance(sq, i) <= 2 { 155 | kingMasks[i].Add(sq) 156 | } 157 | } 158 | center = kingMasks[i] | sqMaskOn[i] 159 | // The king zone is the 3 x 4 square area consisting of the squares around the king and 160 | // the squares facing the enemy side. 161 | kingZoneMasks[WHITE][i] = center | (center << 8) 162 | kingZoneMasks[BLACK][i] = center | (center >> 8) 163 | // The king shield is the three squares adjacent to the king and closest to the enemy side. 164 | kingShieldMasks[WHITE][i] = (kingZoneMasks[WHITE][i] ^ center) >> 8 165 | kingShieldMasks[BLACK][i] = (kingZoneMasks[BLACK][i] ^ center) << 8 166 | } 167 | 168 | } 169 | 170 | func setupRowMasks() { 171 | rowMasks[0] = 0xff // set the first row to binary 11111111, or 255. 172 | for i := 1; i < 8; i++ { 173 | rowMasks[i] = (rowMasks[i-1] << 8) // create the remaining rows by shifting the previous 174 | } // row up by 8 squares. 175 | // middle_rows = row_masks[2] | row_masks[3] | row_masks[4] | row_masks[5] 176 | } 177 | 178 | func setupColumnMasks() { 179 | columnMasks[0] = 1 180 | for i := 0; i < 8; i++ { // create the first column 181 | columnMasks[0] |= columnMasks[0] << 8 182 | } 183 | for i := 1; i < 8; i++ { // create the remaining columns by transposing the first column rightward. 184 | columnMasks[i] = (columnMasks[i-1] << 1) 185 | } 186 | } 187 | 188 | func setupDirections() { 189 | var ray BB 190 | for i := 0; i < 64; i++ { 191 | for j := 0; j < 64; j++ { 192 | directions[i][j] = DIR_INVALID // initialize array. 193 | } 194 | } 195 | for i := 0; i < 64; i++ { 196 | for j := 0; j < 64; j++ { 197 | for dir := 0; dir < 8; dir++ { 198 | ray = rayMasks[dir][i] 199 | if sqMaskOn[j]&ray > 0 { 200 | directions[i][j] = dir 201 | intervening[i][j] = ray ^ (rayMasks[dir][j] | sqMaskOn[j]) 202 | lineMasks[i][j] = ray | rayMasks[oppositeDir[dir]][j] 203 | } 204 | } 205 | } 206 | } 207 | } 208 | 209 | func setupPawnStructureMasks() { 210 | var col int 211 | for i := 0; i < 64; i++ { 212 | col = column(i) 213 | pawnIsolatedMasks[i] = (kingMasks[i] & (^columnMasks[col])) 214 | 215 | pawnPassedMasks[WHITE][i] = rayMasks[NORTH][i] 216 | pawnPassedMasks[BLACK][i] = rayMasks[SOUTH][i] 217 | if col < 7 { 218 | pawnPassedMasks[WHITE][i] |= pawnPassedMasks[WHITE][i] << BB(1) 219 | pawnPassedMasks[BLACK][i] |= pawnPassedMasks[BLACK][i] << BB(1) 220 | } 221 | if col > 0 { 222 | pawnPassedMasks[WHITE][i] |= pawnPassedMasks[WHITE][i] >> BB(1) 223 | pawnPassedMasks[BLACK][i] |= pawnPassedMasks[BLACK][i] >> BB(1) 224 | } 225 | 226 | pawnAttackSpans[WHITE][i] = pawnPassedMasks[WHITE][i] & (^columnMasks[col]) 227 | pawnAttackSpans[BLACK][i] = pawnPassedMasks[BLACK][i] & (^columnMasks[col]) 228 | 229 | pawnBackwardSpans[WHITE][i] = pawnAttackSpans[BLACK][i] | pawnSideMasks[i] 230 | pawnBackwardSpans[BLACK][i] = pawnAttackSpans[WHITE][i] | pawnSideMasks[i] 231 | 232 | pawnFrontSpans[WHITE][i] = pawnPassedMasks[WHITE][i] & (columnMasks[col]) 233 | pawnFrontSpans[BLACK][i] = pawnPassedMasks[BLACK][i] & (columnMasks[col]) 234 | 235 | pawnDoubledMasks[i] = pawnFrontSpans[WHITE][i] | pawnFrontSpans[BLACK][i] 236 | 237 | pawnPromoteSq[WHITE][i] = msb(pawnFrontSpans[WHITE][i]) 238 | pawnPromoteSq[BLACK][i] = lsb(pawnFrontSpans[BLACK][i]) 239 | } 240 | } 241 | 242 | func setupCastleMasks() { 243 | castleQueensideIntervening[WHITE] |= (sqMaskOn[B1] | sqMaskOn[C1] | sqMaskOn[D1]) 244 | castleKingsideIntervening[WHITE] |= (sqMaskOn[F1] | sqMaskOn[G1]) 245 | castleQueensideIntervening[BLACK] = (castleQueensideIntervening[WHITE] << 56) 246 | castleKingsideIntervening[BLACK] = (castleKingsideIntervening[WHITE] << 56) 247 | 248 | for i := uint8(0); i < 16; i++ { 249 | if i&C_WQ > 0 { 250 | castleMasks[i] |= (sqMaskOn[A1] | sqMaskOn[E1]) 251 | } 252 | if i&C_WK > 0 { 253 | castleMasks[i] |= (sqMaskOn[E1] | sqMaskOn[H1]) 254 | } 255 | if i&C_BQ > 0 { 256 | castleMasks[i] |= (sqMaskOn[A8] | sqMaskOn[E8]) 257 | } 258 | if i&C_BK > 0 { 259 | castleMasks[i] |= (sqMaskOn[E8] | sqMaskOn[H8]) 260 | } 261 | } 262 | } 263 | 264 | func setupMasks() { 265 | setupRowMasks() // Create bitboard masks for each row and column. 266 | setupColumnMasks() 267 | setupSquareMasks() // First set up masks used to add/remove bits by their index. 268 | setupKnightMasks() // For each square, calculate bitboard attack maps showing 269 | setupBishopMasks() // the squares to which the given piece type may move. These are 270 | setupRookMasks() // used as bitmasks during move generation to find pseudolegal moves. 271 | setupQueenMasks() 272 | setupKingMasks() 273 | setupDirections() 274 | setupPawnMasks() 275 | setupPawnStructureMasks() 276 | setupCastleMasks() 277 | } 278 | -------------------------------------------------------------------------------- /bitwise_math.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | // Bit manipulation resources: 7 | // https://chessprogramming.wikispaces.com/Bit-Twiddling 8 | 9 | package main 10 | 11 | // "fmt" 12 | 13 | const ( 14 | DEBRUIJN = 285870213051386505 15 | ) 16 | 17 | func furthestForward(c uint8, b BB) int { 18 | if c == WHITE { 19 | return lsb(b) 20 | } else { 21 | return msb(b) 22 | } 23 | } 24 | 25 | func msb(b BB) int { 26 | b |= b >> 1 27 | b |= b >> 2 28 | b |= b >> 4 29 | b |= b >> 8 30 | b |= b >> 16 31 | b |= b >> 32 32 | return debruijnMsbTable[(b*DEBRUIJN)>>58] 33 | } 34 | 35 | func lsb(b BB) int { 36 | return debruijnLsbTable[((b&-b)*DEBRUIJN)>>58] 37 | } 38 | 39 | func popCount(b BB) int { 40 | b = b - ((b >> 1) & 0x5555555555555555) 41 | b = (b & 0x3333333333333333) + ((b >> 2) & 0x3333333333333333) 42 | b = (b + (b >> 4)) & 0x0f0f0f0f0f0f0f0f 43 | b = b + (b >> 8) 44 | b = b + (b >> 16) 45 | b = b + (b >> 32) 46 | return int(b & 0x7f) 47 | } 48 | 49 | var debruijnLsbTable = [64]int{ 50 | 0, 1, 48, 2, 57, 49, 28, 3, 51 | 61, 58, 50, 42, 38, 29, 17, 4, 52 | 62, 55, 59, 36, 53, 51, 43, 22, 53 | 45, 39, 33, 30, 24, 18, 12, 5, 54 | 63, 47, 56, 27, 60, 41, 37, 16, 55 | 54, 35, 52, 21, 44, 32, 23, 11, 56 | 46, 26, 40, 15, 34, 20, 31, 10, 57 | 25, 14, 19, 9, 13, 8, 7, 6, 58 | } 59 | 60 | var debruijnMsbTable = [64]int{ 61 | 0, 47, 1, 56, 48, 27, 2, 60, 62 | 57, 49, 41, 37, 28, 16, 3, 61, 63 | 54, 58, 35, 52, 50, 42, 21, 44, 64 | 38, 32, 29, 23, 17, 11, 4, 62, 65 | 46, 55, 26, 59, 40, 36, 15, 53, 66 | 34, 51, 20, 43, 31, 22, 10, 45, 67 | 25, 39, 14, 33, 19, 30, 9, 24, 68 | 13, 18, 8, 12, 7, 6, 5, 63, 69 | } 70 | -------------------------------------------------------------------------------- /bitwise_math_test.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "testing" 11 | ) 12 | 13 | var result int // hacks to make sure compiler doesn't eliminate func under test. 14 | 15 | func BenchmarkPopCount(b *testing.B) { 16 | var bb BB 17 | test, err := loadEpdFile("test_suites/wac_300.epd") 18 | if err != nil { 19 | fmt.Print(err) 20 | return 21 | } 22 | b.ResetTimer() 23 | for _, epd := range test { 24 | bb = epd.brd.occupied[WHITE] 25 | for i := 0; i < b.N; i++ { 26 | result = popCount(bb) 27 | } 28 | } 29 | 30 | } 31 | 32 | func BenchmarkLSB(b *testing.B) { 33 | var bb BB 34 | test, err := loadEpdFile("test_suites/wac_300.epd") 35 | if err != nil { 36 | fmt.Print(err) 37 | return 38 | } 39 | b.ResetTimer() 40 | for _, epd := range test { 41 | bb = epd.brd.occupied[WHITE] 42 | for i := 0; i < b.N; i++ { 43 | result = lsb(bb) 44 | } 45 | } 46 | } 47 | 48 | func BenchmarkLSBRand(b *testing.B) { 49 | rng := NewRngKiss(74) 50 | bb := rng.RandomBB(BB((1 << 32) - 1)) 51 | for i := 0; i < b.N; i++ { 52 | result = lsb(bb) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /board.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "sync" 11 | ) 12 | 13 | const ( // color 14 | BLACK = iota 15 | WHITE 16 | ) 17 | 18 | var printMutex sync.Mutex 19 | 20 | // When spawning new goroutines for subtree search, a deep copy of the Board struct will have to be made 21 | // and passed to the new goroutine. Keep this struct as small as possible. 22 | type Board struct { 23 | pieces [2][8]BB // 1024 bits 24 | squares [64]Piece // 512 bits 25 | occupied [2]BB // 128 bits 26 | hashKey uint64 // 64 bits 27 | worker *Worker // 64 bits 28 | material [2]int16 // 32 bits 29 | pawnHashKey uint32 // 32 bits 30 | c uint8 // 8 bits 31 | castle uint8 // 8 bits 32 | enpTarget uint8 // 8 bits 33 | halfmoveClock uint8 // 8 bits 34 | endgameCounter uint8 // 8 bits 35 | // ...24 bits padding 36 | } 37 | 38 | type BoardMemento struct { // memento object used to store board state to unmake later. 39 | hashKey uint64 40 | pawnHashKey uint32 41 | castle uint8 42 | enpTarget uint8 43 | halfmoveClock uint8 44 | } 45 | 46 | func (brd *Board) NewMemento() *BoardMemento { 47 | return &BoardMemento{ 48 | hashKey: brd.hashKey, 49 | pawnHashKey: brd.pawnHashKey, 50 | castle: brd.castle, 51 | enpTarget: brd.enpTarget, 52 | halfmoveClock: brd.halfmoveClock, 53 | } 54 | } 55 | 56 | func (brd *Board) InCheck() bool { // determines if side to move is in check 57 | return isAttackedBy(brd, brd.AllOccupied(), brd.KingSq(brd.c), brd.Enemy(), brd.c) 58 | } 59 | 60 | func (brd *Board) KingSq(c uint8) int { 61 | return furthestForward(c, brd.pieces[c][KING]) 62 | } 63 | 64 | func (brd *Board) MayPromote(m Move) bool { 65 | if m.Piece() != PAWN { 66 | return false 67 | } 68 | if m.IsPromotion() { 69 | return true 70 | } 71 | if brd.c == WHITE { 72 | return m.To() >= A5 || brd.isPassedPawn(m) 73 | } else { 74 | return m.To() < A5 || brd.isPassedPawn(m) 75 | } 76 | } 77 | 78 | func (brd *Board) isPassedPawn(m Move) bool { 79 | return pawnPassedMasks[brd.c][m.To()]&brd.pieces[brd.Enemy()][PAWN] == 0 80 | } 81 | 82 | func (brd *Board) ValueAt(sq int) int { 83 | return brd.squares[sq].Value() 84 | } 85 | 86 | func (brd *Board) TypeAt(sq int) Piece { 87 | return brd.squares[sq] 88 | } 89 | 90 | func (brd *Board) Enemy() uint8 { 91 | return brd.c ^ 1 92 | } 93 | 94 | func (brd *Board) AllOccupied() BB { return brd.occupied[0] | brd.occupied[1] } 95 | 96 | func (brd *Board) Placement(c uint8) BB { return brd.occupied[c] } 97 | 98 | func (brd *Board) PawnsOnly() bool { 99 | return brd.occupied[brd.c] == brd.pieces[brd.c][PAWN]|brd.pieces[brd.c][KING] 100 | } 101 | 102 | func (brd *Board) ColorPawnsOnly(c uint8) bool { 103 | return brd.occupied[c] == brd.pieces[c][PAWN]|brd.pieces[c][KING] 104 | } 105 | 106 | func (brd *Board) Copy() *Board { 107 | return &Board{ 108 | pieces: brd.pieces, 109 | squares: brd.squares, 110 | occupied: brd.occupied, 111 | material: brd.material, 112 | hashKey: brd.hashKey, 113 | pawnHashKey: brd.pawnHashKey, 114 | c: brd.c, 115 | castle: brd.castle, 116 | enpTarget: brd.enpTarget, 117 | halfmoveClock: brd.halfmoveClock, 118 | endgameCounter: brd.endgameCounter, 119 | } 120 | } 121 | 122 | func (brd *Board) PrintDetails() { 123 | pieceNames := [6]string{"Pawn", "Knight", "Bishop", "Rook", "Queen", "KING"} 124 | sideNames := [2]string{"White", "Black"} 125 | printMutex.Lock() 126 | 127 | fmt.Printf("hashKey: %x, pawnHashKey: %x\n", brd.hashKey, brd.pawnHashKey) 128 | fmt.Printf("castle: %d, enpTarget: %d, halfmoveClock: %d\noccupied:\n", brd.castle, brd.enpTarget, brd.halfmoveClock) 129 | for i := 0; i < 2; i++ { 130 | fmt.Printf("side: %s, material: %d\n", sideNames[i], brd.material[i]) 131 | brd.occupied[i].Print() 132 | for pc := 0; pc < 6; pc++ { 133 | fmt.Printf("%s\n", pieceNames[pc]) 134 | brd.pieces[i][pc].Print() 135 | } 136 | } 137 | printMutex.Unlock() 138 | brd.Print() 139 | } 140 | 141 | func (brd *Board) Print() { 142 | printMutex.Lock() 143 | if brd.c == WHITE { 144 | fmt.Println("\nSide to move: WHITE") 145 | } else { 146 | fmt.Println("\nSide to move: BLACK") 147 | } 148 | fmt.Printf(" A B C D E F G H\n") 149 | fmt.Printf(" ---------------------------------\n") 150 | row := brd.squares[56:] 151 | fmt.Printf("8 ") 152 | brd.PrintRow(56, row) 153 | 154 | for i := 48; i >= 0; i -= 8 { 155 | row = brd.squares[i : i+8] 156 | fmt.Printf("%v ", 1+(i/8)) 157 | brd.PrintRow(i, row) 158 | } 159 | fmt.Printf(" A B C D E F G H\n") 160 | printMutex.Unlock() 161 | } 162 | 163 | func (brd *Board) PrintRow(start int, row []Piece) { 164 | fmt.Printf("| ") 165 | for i, piece := range row { 166 | if piece == EMPTY { 167 | fmt.Printf(" | ") 168 | } else { 169 | if brd.occupied[WHITE]&sqMaskOn[start+i] > 0 { 170 | fmt.Printf("%v | ", pieceGraphics[WHITE][piece]) 171 | } else { 172 | fmt.Printf("%v | ", pieceGraphics[BLACK][piece]) 173 | } 174 | } 175 | } 176 | fmt.Printf("\n ---------------------------------\n") 177 | } 178 | 179 | func EmptyBoard() *Board { 180 | brd := &Board{ 181 | enpTarget: SQ_INVALID, 182 | } 183 | for sq := 0; sq < 64; sq++ { 184 | brd.squares[sq] = EMPTY 185 | } 186 | return brd 187 | } 188 | 189 | func onBoard(sq int) bool { return 0 <= sq && sq <= 63 } 190 | func row(sq int) int { return sq >> 3 } 191 | func column(sq int) int { return sq & 7 } 192 | 193 | var pieceGraphics = [2][6]string{ 194 | {"\u265F", "\u265E", "\u265D", "\u265C", "\u265B", "\u265A"}, 195 | {"\u2659", "\u2658", "\u2657", "\u2656", "\u2655", "\u2654"}, 196 | } 197 | 198 | const ( 199 | A1 = iota 200 | B1 201 | C1 202 | D1 203 | E1 204 | F1 205 | G1 206 | H1 207 | A2 208 | B2 209 | C2 210 | D2 211 | E2 212 | F2 213 | G2 214 | H2 215 | A3 216 | B3 217 | C3 218 | D3 219 | E3 220 | F3 221 | G3 222 | H3 223 | A4 224 | B4 225 | C4 226 | D4 227 | E4 228 | F4 229 | G4 230 | H4 231 | A5 232 | B5 233 | C5 234 | D5 235 | E5 236 | F5 237 | G5 238 | H5 239 | A6 240 | B6 241 | C6 242 | D6 243 | E6 244 | F6 245 | G6 246 | H6 247 | A7 248 | B7 249 | C7 250 | D7 251 | E7 252 | F7 253 | G7 254 | H7 255 | A8 256 | B8 257 | C8 258 | D8 259 | E8 260 | F8 261 | G8 262 | H8 263 | SQ_INVALID 264 | ) 265 | -------------------------------------------------------------------------------- /create_binaries.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script partly automates creation of gopher_check binaries for new releases. 4 | 5 | createBinaries() { 6 |   read -p "enter the current semantic version number: " version 7 |   echo "creating binaries for version: ${version}" 8 |   mkdir "./binaries" 9 |   versionPath="./binaries/${version}" 10 |   mkdir "${versionPath}" 11 | 12 |   mkdir "${versionPath}/mac" 13 |   env GOARCH=amd64 GOOS=darwin go build -o "${versionPath}/mac/gopher_check" 14 |   zip "${versionPath}/mac/gopher_check-${version}-mac-amd64.zip" "${versionPath}/mac/gopher_check" 15 | 16 |   mkdir "${versionPath}/windows" 17 |   env GOARCH=amd64 GOOS=windows go build -o "${versionPath}/windows/gopher_check.exe" 18 |   zip "${versionPath}/windows/gopher_check-${version}-windows-amd64.zip" "${versionPath}/windows/gopher_check.exe" 19 | 20 |   mkdir "${versionPath}/linux" 21 |   env GOARCH=amd64 GOOS=linux go build -o "${versionPath}/linux/gopher_check" 22 |   zip "${versionPath}/linux/gopher_check-${version}-linux-amd64.zip" "${versionPath}/linux/gopher_check" 23 | } 24 | 25 | createBinaries -------------------------------------------------------------------------------- /eval.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | const ( // TODO: expose these options via UCI interface. 9 | LAZY_EVAL_MARGIN = BISHOP_VALUE 10 | TEMPO_BONUS = 5 11 | ) 12 | 13 | const ( 14 | MAX_ENDGAME_COUNT = 24 15 | ) 16 | 17 | const ( 18 | MIDGAME = iota 19 | ENDGAME 20 | ) 21 | 22 | var chebyshevDistanceTable [64][64]int 23 | 24 | func chebyshevDistance(from, to int) int { 25 | return chebyshevDistanceTable[from][to] 26 | } 27 | 28 | func setupChebyshevDistance() { 29 | for from := 0; from < 64; from++ { 30 | for to := 0; to < 64; to++ { 31 | chebyshevDistanceTable[from][to] = max(abs(row(from)-row(to)), abs(column(from)-column(to))) 32 | } 33 | } 34 | } 35 | 36 | // 0 indicates endgame. Initial position phase is 24. Maximum possible is 48. 37 | var endgamePhase [64]int 38 | 39 | // piece values used to determine endgame status. 0-12 per side, 40 | var endgameCountValues = [8]uint8{0, 1, 1, 2, 4, 0} 41 | 42 | var mainPst = [2][8][64]int{ // Black. White PST will be set in setup_eval. 43 | { // Pawn 44 | {0, 0, 0, 0, 0, 0, 0, 0, 45 | -11, 1, 1, 1, 1, 1, 1, -11, 46 | -12, 0, 1, 2, 2, 1, 0, -12, 47 | -13, -1, 2, 10, 10, 2, -1, -13, 48 | -14, -2, 4, 14, 14, 4, -2, -14, 49 | -15, -3, 0, 9, 9, 0, -3, -15, 50 | -16, -4, 0, -20, -20, 0, -4, -16, 51 | 0, 0, 0, 0, 0, 0, 0, 0}, 52 | 53 | // Knight 54 | {-8, -8, -6, -6, -6, -6, -8, -8, 55 | -8, 0, 0, 0, 0, 0, 0, -8, 56 | -6, 0, 4, 4, 4, 4, 0, -6, 57 | -6, 0, 4, 8, 8, 4, 0, -6, 58 | -6, 0, 4, 8, 8, 4, 0, -6, 59 | -6, 0, 4, 4, 4, 4, 0, -6, 60 | -8, 0, 1, 2, 2, 1, 0, -8, 61 | -10, -12, -6, -6, -6, -6, -12, -10}, 62 | // Bishop 63 | {-3, -3, -3, -3, -3, -3, -3, -3, 64 | -3, 0, 0, 0, 0, 0, 0, -3, 65 | -3, 0, 2, 4, 4, 2, 0, -3, 66 | -3, 0, 4, 5, 5, 4, 0, -3, 67 | -3, 0, 4, 5, 5, 4, 0, -3, 68 | -3, 1, 2, 4, 4, 2, 1, -3, 69 | -3, 2, 1, 1, 1, 1, 2, -3, 70 | -3, -3, -10, -3, -3, -10, -3, -3}, 71 | // Rook 72 | {4, 4, 4, 4, 4, 4, 4, 4, 73 | 16, 16, 16, 16, 16, 16, 16, 16, 74 | -4, 0, 0, 0, 0, 0, 0, -4, 75 | -4, 0, 0, 0, 0, 0, 0, -4, 76 | -4, 0, 0, 0, 0, 0, 0, -4, 77 | -4, 0, 0, 0, 0, 0, 0, -4, 78 | -4, 0, 0, 0, 0, 0, 0, -4, 79 | 0, 0, 0, 2, 2, 0, 0, 0}, 80 | // Queen 81 | {0, 0, 0, 1, 1, 0, 0, 0, 82 | 0, 0, 1, 2, 2, 1, 0, 0, 83 | 0, 1, 2, 2, 2, 2, 1, 0, 84 | 0, 1, 2, 3, 3, 2, 1, 0, 85 | 0, 1, 2, 3, 3, 2, 1, 0, 86 | 0, 1, 1, 2, 2, 1, 1, 0, 87 | 0, 0, 1, 1, 1, 1, 0, 0, 88 | -6, -6, -6, -6, -6, -6, -6, -6}, 89 | }, 90 | } 91 | 92 | var kingPst = [2][2][64]int{ // Black 93 | { // Early game 94 | { 95 | -52, -50, -50, -50, -50, -50, -50, -52, // In early game, encourage the king to stay on back 96 | -50, -48, -48, -48, -48, -48, -48, -50, // row defended by friendly pieces. 97 | -48, -46, -46, -46, -46, -46, -46, -48, 98 | -46, -44, -44, -44, -44, -44, -44, -46, 99 | -44, -42, -42, -42, -42, -42, -42, -44, 100 | -42, -40, -40, -40, -40, -40, -40, -42, 101 | -16, -15, -20, -20, -20, -20, -15, -16, 102 | 0, 20, 30, -30, 0, -20, 30, 20, 103 | }, 104 | { // Endgame 105 | -30, -20, -10, 0, 0, -10, -20, -30, // In end game (when few friendly pieces are available 106 | -20, -10, 0, 10, 10, 0, -10, -20, // to protect king), the king should move toward the center 107 | -10, 0, 10, 20, 20, 10, 0, -10, // and avoid getting trapped in corners. 108 | 0, 10, 20, 30, 30, 20, 10, 0, 109 | 0, 10, 20, 30, 30, 20, 10, 0, 110 | -10, 0, 10, 20, 20, 10, 0, -10, 111 | -20, -10, 0, 10, 10, 0, -10, -20, 112 | -30, -20, -10, 0, 0, -10, -20, -30, 113 | }, 114 | }, 115 | } 116 | 117 | var squareMirror = [64]int{ 118 | A8, B8, C8, D8, E8, F8, G8, H8, 119 | A7, B7, C7, D7, E7, F7, G7, H7, 120 | A6, B6, C6, D6, E6, F6, G6, H6, 121 | A5, B5, C5, D5, E5, F5, G5, H5, 122 | A4, B4, C4, D4, E4, F4, G4, H4, 123 | A3, B3, C3, D3, E3, F3, G3, H3, 124 | A2, B2, C2, D2, E2, F2, G2, H2, 125 | A1, B1, C1, D1, E1, F1, G1, H1, 126 | } 127 | 128 | var kingThreatBonus = [64]int{ 129 | 0, 2, 3, 5, 9, 15, 24, 37, 130 | 55, 79, 111, 150, 195, 244, 293, 337, 131 | 370, 389, 389, 389, 389, 389, 389, 389, 132 | 389, 389, 389, 389, 389, 389, 389, 389, 133 | 389, 389, 389, 389, 389, 389, 389, 389, 134 | 389, 389, 389, 389, 389, 389, 389, 389, 135 | 389, 389, 389, 389, 389, 389, 389, 389, 136 | 389, 389, 389, 389, 389, 389, 389, 389, 137 | } 138 | 139 | var kingSafteyBase = [2][64]int{ 140 | { // Black 141 | 4, 4, 4, 4, 4, 4, 4, 4, 142 | 4, 4, 4, 4, 4, 4, 4, 4, 143 | 4, 4, 4, 4, 4, 4, 4, 4, 144 | 4, 4, 4, 4, 4, 4, 4, 4, 145 | 4, 4, 4, 4, 4, 4, 4, 4, 146 | 4, 3, 3, 3, 3, 3, 3, 4, 147 | 3, 1, 1, 1, 1, 1, 1, 3, 148 | 2, 0, 0, 0, 0, 0, 0, 2, 149 | }, 150 | } 151 | 152 | // adjusts value of knights and rooks based on number of own pawns in play. 153 | var knightPawns = [16]int{-20, -16, -12, -8, -4, 0, 4, 8, 12} 154 | var rookPawns = [16]int{16, 12, 8, 4, 2, 0, -2, -4, -8} 155 | 156 | // adjusts the value of bishop pairs based on number of enemy pawns in play. 157 | var bishopPairPawns = [16]int{10, 10, 9, 8, 6, 4, 2, 0, -2} 158 | 159 | var knightMobility = [16]int{-16, -12, -6, -3, 0, 1, 3, 5, 6, 0, 0, 0, 0, 0, 0} 160 | 161 | var bishopMobility = [16]int{-24, -16, -8, -4, -2, 0, 2, 4, 6, 7, 8, 9, 10, 11, 12, 13} 162 | 163 | var rookMobility = [16]int{-12, -8, -4, -2, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} 164 | 165 | var queenMobility = [32]int{-24, -18, -12, -6, -3, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 166 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 24, 24, 24} 167 | 168 | var queenTropismBonus = [8]int{0, 12, 9, 6, 3, 0, -3, -6} 169 | 170 | func evaluate(brd *Board, alpha, beta int) int { 171 | c, e := brd.c, brd.Enemy() 172 | // lazy evaluation: if material balance is already outside the search window by an amount that outweighs 173 | // the largest likely placement evaluation, return the material as an approximate evaluation. 174 | // This prevents the engine from wasting a lot of time evaluating unrealistic positions. 175 | score := int(brd.material[c]-brd.material[e]) + TEMPO_BONUS 176 | if score+LAZY_EVAL_MARGIN < alpha || score-LAZY_EVAL_MARGIN > beta { 177 | return score 178 | } 179 | 180 | pentry := brd.worker.ptt.Probe(brd.pawnHashKey) 181 | if pentry.key != brd.pawnHashKey { // pawn hash table miss. 182 | // collisions can occur, but are too infrequent to matter much (1 / 20+ million) 183 | setPawnStructure(brd, pentry) // evaluate pawn structure and save to pentry. 184 | } 185 | 186 | score += netPawnPlacement(brd, pentry, c, e) 187 | score += netMajorPlacement(brd, pentry, c, e) // 3x as expensive as pawn eval... 188 | 189 | return score 190 | } 191 | 192 | func netMajorPlacement(brd *Board, pentry *PawnEntry, c, e uint8) int { 193 | kingSq, enemyKingSq := brd.KingSq(c), brd.KingSq(e) 194 | return majorPlacement(brd, pentry, c, e, kingSq, enemyKingSq) - 195 | majorPlacement(brd, pentry, e, c, enemyKingSq, kingSq) 196 | } 197 | 198 | var pawnShieldBonus = [4]int{-9, -3, 3, 9} 199 | 200 | func majorPlacement(brd *Board, pentry *PawnEntry, c, e uint8, kingSq, 201 | enemyKingSq int) (totalPlacement int) { 202 | 203 | friendly := brd.Placement(c) 204 | occ := brd.AllOccupied() 205 | 206 | available := (^friendly) & (^(pentry.allAttacks[e])) 207 | 208 | var sq, mobility, placement, kingThreats int 209 | var b, attacks BB 210 | 211 | enemyKingZone := kingZoneMasks[e][enemyKingSq] 212 | 213 | pawnCount := pentry.count[c] 214 | 215 | for b = brd.pieces[c][KNIGHT]; b > 0; b.Clear(sq) { 216 | sq = furthestForward(c, b) 217 | placement += knightPawns[pawnCount] 218 | attacks = knightMasks[sq] & available 219 | kingThreats += popCount(attacks & enemyKingZone) 220 | mobility += knightMobility[popCount(attacks)] 221 | } 222 | 223 | for b = brd.pieces[c][BISHOP]; b > 0; b.Clear(sq) { 224 | sq = furthestForward(c, b) 225 | attacks = bishopAttacks(occ, sq) & available 226 | kingThreats += popCount(attacks & enemyKingZone) 227 | mobility += bishopMobility[popCount(attacks)] 228 | } 229 | if popCount(brd.pieces[c][BISHOP]) > 1 { // bishop pairs 230 | placement += 40 + bishopPairPawns[pentry.count[e]] 231 | } 232 | 233 | phase := endgamePhase[brd.endgameCounter] 234 | 235 | for b = brd.pieces[c][ROOK]; b > 0; b.Clear(sq) { 236 | sq = furthestForward(c, b) 237 | placement += rookPawns[pawnCount] 238 | attacks = rookAttacks(occ, sq) & available 239 | kingThreats += popCount(attacks & enemyKingZone) 240 | // only reward rook mobility in the late-game. 241 | mobility += weightScore(phase, 0, rookMobility[popCount(attacks)]) 242 | } 243 | 244 | for b = brd.pieces[c][QUEEN]; b > 0; b.Clear(sq) { 245 | sq = furthestForward(c, b) 246 | attacks = queenAttacks(occ, sq) & available 247 | kingThreats += popCount(attacks & enemyKingZone) 248 | mobility += queenMobility[popCount(attacks)] 249 | placement += weightScore(phase, 0, // encourage queen to move toward enemy king in the late-game. 250 | queenTropismBonus[chebyshevDistance(sq, enemyKingSq)]) 251 | } 252 | 253 | placement += weightScore(phase, 254 | pawnShieldBonus[popCount(brd.pieces[c][PAWN]&kingShieldMasks[c][kingSq])], 0) 255 | 256 | placement += weightScore(phase, 257 | kingPst[c][MIDGAME][kingSq], kingPst[c][ENDGAME][kingSq]) 258 | 259 | placement += weightScore(phase, 260 | kingThreatBonus[kingThreats+kingSafteyBase[e][enemyKingSq]], 0) 261 | 262 | return placement + mobility 263 | } 264 | 265 | // Tapered Evaluation: adjust the score based on how close we are to the endgame. 266 | // This prevents 'evaluation discontinuity' where the score changes significantly when moving from 267 | // mid-game to end-game, causing the search to chase after changes in endgame status instead of real 268 | // positional gain. 269 | // Uses the scaling function first implemented in Fruit and described here: 270 | // https://chessprogramming.wikispaces.com/Tapered+Eval 271 | func weightScore(phase, mgScore, egScore int) int { 272 | return ((mgScore * (256 - phase)) + (egScore * phase)) / 256 273 | } 274 | 275 | func setupEval() { 276 | for piece := PAWN; piece < KING; piece++ { // Main PST 277 | for sq := 0; sq < 64; sq++ { 278 | mainPst[WHITE][piece][sq] = mainPst[BLACK][piece][squareMirror[sq]] 279 | } 280 | } 281 | for endgame := 0; endgame < 2; endgame++ { // King PST 282 | for sq := 0; sq < 64; sq++ { 283 | kingPst[WHITE][endgame][sq] = kingPst[BLACK][endgame][squareMirror[sq]] 284 | } 285 | } 286 | for sq := 0; sq < 64; sq++ { // King saftey counters 287 | kingSafteyBase[WHITE][sq] = kingSafteyBase[BLACK][squareMirror[sq]] 288 | } 289 | for i := 0; i <= 24; i++ { // Endgame phase scaling factor 290 | endgamePhase[i] = (((MAX_ENDGAME_COUNT - i) * 256) + (MAX_ENDGAME_COUNT / 2)) / MAX_ENDGAME_COUNT 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /eval_pawns.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | // "fmt" 9 | 10 | const ( 11 | DOUBLED_PENALTY = 20 12 | ISOLATED_PENALTY = 12 13 | BACKWARD_PENALTY = 4 14 | ) 15 | 16 | var passedPawnBonus = [2][8]int{ 17 | {0, 192, 96, 48, 24, 12, 6, 0}, 18 | {0, 6, 12, 24, 48, 96, 192, 0}, 19 | } 20 | var tarraschBonus = [2][8]int{ 21 | {0, 12, 8, 4, 2, 0, 0, 0}, 22 | {0, 0, 0, 2, 4, 8, 12, 0}, 23 | } 24 | var defenseBonus = [2][8]int{ 25 | {0, 12, 8, 6, 5, 4, 3, 0}, 26 | {0, 3, 4, 5, 6, 8, 12, 0}, 27 | } 28 | var duoBonus = [2][8]int{ 29 | {0, 0, 2, 1, 1, 1, 0, 0}, 30 | {0, 0, 1, 1, 1, 2, 0, 0}, 31 | } 32 | 33 | // var promoteRow = [2][2]int{ 34 | // {1, 2}, 35 | // {6, 5}, 36 | // } 37 | 38 | // PAWN EVALUATION 39 | // Good structures: 40 | // -Passed pawns - Bonus for pawns unblocked by an enemy pawn on the same or adjacent file. 41 | // May eventually get promoted. 42 | // -Pawn duos - Pawns side by side to another friendly pawn receive a small bonus 43 | // Bad structures: 44 | // -Isolated pawns - Penalty for any pawn without friendly pawns on adjacent files. 45 | // -Double/tripled pawns - Penalty for having multiple pawns on the same file. 46 | // -Backward pawns 47 | 48 | func setPawnStructure(brd *Board, pentry *PawnEntry) { 49 | pentry.key = brd.pawnHashKey 50 | setPawnMaps(brd, pentry, WHITE) 51 | setPawnMaps(brd, pentry, BLACK) 52 | pentry.value[WHITE] = pawnStructure(brd, pentry, WHITE, BLACK) - 53 | pawnStructure(brd, pentry, BLACK, WHITE) 54 | pentry.value[BLACK] = -pentry.value[WHITE] 55 | } 56 | 57 | func setPawnMaps(brd *Board, pentry *PawnEntry, c uint8) { 58 | pentry.leftAttacks[c], pentry.rightAttacks[c] = pawnAttacks(brd, c) 59 | pentry.allAttacks[c] = pentry.leftAttacks[c] | pentry.rightAttacks[c] 60 | pentry.count[c] = uint8(popCount(brd.pieces[c][PAWN])) 61 | pentry.passedPawns[c] = 0 62 | } 63 | 64 | // pawn_structure() sets the remaining pentry attributes for side c 65 | func pawnStructure(brd *Board, pentry *PawnEntry, c, e uint8) int { 66 | 67 | var value, sq, sqRow int 68 | ownPawns, enemyPawns := brd.pieces[c][PAWN], brd.pieces[e][PAWN] 69 | for b := ownPawns; b > 0; b.Clear(sq) { 70 | sq = furthestForward(c, b) 71 | sqRow = row(sq) 72 | 73 | if (pawnAttackMasks[e][sq])&ownPawns > 0 { // defended pawns 74 | value += defenseBonus[c][sqRow] 75 | } 76 | if (pawnSideMasks[sq] & ownPawns) > 0 { // pawn duos 77 | value += duoBonus[c][sqRow] 78 | } 79 | 80 | if pawnDoubledMasks[sq]&ownPawns > 0 { // doubled or tripled pawns 81 | value -= DOUBLED_PENALTY 82 | } 83 | 84 | if pawnPassedMasks[c][sq]&enemyPawns == 0 { // passed pawns 85 | value += passedPawnBonus[c][sqRow] 86 | pentry.passedPawns[c].Add(sq) // note the passed pawn location in the pawn hash entry. 87 | } else { // don't penalize passed pawns for being isolated. 88 | if pawnIsolatedMasks[sq]&ownPawns == 0 { 89 | value -= ISOLATED_PENALTY // isolated pawns 90 | } 91 | } 92 | 93 | // https://chessprogramming.wikispaces.com/Backward+Pawn 94 | // backward pawns: 95 | // 1. cannot be defended by friendly pawns, 96 | // 2. their stop square is defended by an enemy sentry pawn, 97 | // 3. their stop square is not defended by a friendly pawn 98 | if (pawnBackwardSpans[c][sq]&ownPawns == 0) && 99 | (pentry.allAttacks[e]&pawnStopMasks[c][sq] > 0) { 100 | value -= BACKWARD_PENALTY 101 | } 102 | } 103 | return value 104 | } 105 | 106 | func netPawnPlacement(brd *Board, pentry *PawnEntry, c, e uint8) int { 107 | return pentry.value[c] + netPassedPawns(brd, pentry, c, e) 108 | } 109 | 110 | func netPassedPawns(brd *Board, pentry *PawnEntry, c, e uint8) int { 111 | return evalPassedPawns(brd, c, e, pentry.passedPawns[c]) - 112 | evalPassedPawns(brd, e, c, pentry.passedPawns[e]) 113 | } 114 | 115 | func evalPassedPawns(brd *Board, c, e uint8, passedPawns BB) int { 116 | var value, sq int 117 | enemyKingSq := brd.KingSq(e) 118 | for ; passedPawns > 0; passedPawns.Clear(sq) { 119 | sq = furthestForward(c, passedPawns) 120 | // Tarrasch rule: assign small bonus for friendly rook behind the passed pawn 121 | if pawnFrontSpans[e][sq]&brd.pieces[c][ROOK] > 0 { 122 | value += tarraschBonus[c][row(sq)] 123 | } 124 | // pawn race: Assign a bonus if the pawn is closer to its promote square than the enemy king. 125 | promoteSquare := pawnPromoteSq[c][sq] 126 | if brd.c == c { 127 | if chebyshevDistance(sq, promoteSquare) < (chebyshevDistance(enemyKingSq, promoteSquare)) { 128 | value += passedPawnBonus[c][row(sq)] 129 | } 130 | } else { 131 | if chebyshevDistance(sq, promoteSquare) < (chebyshevDistance(enemyKingSq, promoteSquare) - 1) { 132 | value += passedPawnBonus[c][row(sq)] 133 | } 134 | } 135 | } 136 | return value 137 | } 138 | -------------------------------------------------------------------------------- /game_timer.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | // Implements time management features for different time rules: 7 | // https://chessprogramming.wikispaces.com/Time+Management 8 | 9 | // Time control can be per-move, or per-game. 10 | // Per-game time control consists of a base amount of time, plus an increment of additional 11 | // time granted at the beginning of each move. 12 | 13 | package main 14 | 15 | import ( 16 | "time" 17 | ) 18 | 19 | const ( 20 | AVG_MOVES_PER_GAME = 55 21 | MIN_MOVES_REMAINING = 15 22 | MAX_TIME = time.Duration(8) * time.Hour // default search time limit 23 | SAFETY_MARGIN = time.Duration(5) * time.Millisecond // minimal amount of time to keep on clock 24 | ) 25 | 26 | type GameTimer struct { 27 | inc [2]time.Duration 28 | remaining [2]time.Duration 29 | movesRemaining int 30 | startTime time.Time 31 | timer *time.Timer 32 | s *Search 33 | sideToMove uint8 34 | } 35 | 36 | func NewGameTimer(movesPlayed int, sideToMove uint8) *GameTimer { 37 | return &GameTimer{ 38 | movesRemaining: max(MIN_MOVES_REMAINING, AVG_MOVES_PER_GAME-movesPlayed), 39 | remaining: [2]time.Duration{MAX_TIME, MAX_TIME}, 40 | sideToMove: sideToMove, 41 | startTime: time.Now(), 42 | } 43 | } 44 | 45 | func (gt *GameTimer) SetMoveTime(timeLimit time.Duration) { 46 | gt.remaining = [2]time.Duration{timeLimit, timeLimit} 47 | gt.inc = [2]time.Duration{0, 0} 48 | gt.movesRemaining = 1 49 | } 50 | 51 | func (gt *GameTimer) Start() { 52 | gt.timer = time.AfterFunc(gt.TimeLimit(), gt.s.Abort) 53 | } 54 | 55 | func (gt *GameTimer) TimeLimit() time.Duration { 56 | return (gt.remaining[gt.sideToMove] - SAFETY_MARGIN) / time.Duration(gt.movesRemaining) 57 | } 58 | 59 | func (gt *GameTimer) Elapsed() time.Duration { 60 | return time.Since(gt.startTime) 61 | } 62 | 63 | func (gt *GameTimer) Stop() { 64 | if gt.timer != nil { 65 | gt.timer.Stop() 66 | } 67 | } 68 | 69 | // 70 | -------------------------------------------------------------------------------- /history.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "sync/atomic" 11 | ) 12 | 13 | type HistoryTable [2][8][64]uint32 14 | 15 | // Store atomically adds count to the history table h. 16 | func (h *HistoryTable) Store(m Move, c uint8, count int) { 17 | atomic.AddUint32(&h[c][m.Piece()][m.To()], uint32((count>>3)|1)) 18 | } 19 | 20 | // Probe atomically reads the history table h. 21 | func (h *HistoryTable) Probe(pc Piece, c uint8, to int) uint32 { 22 | if v := atomic.LoadUint32(&h[c][pc][to]); v > 0 { 23 | return ((v >> 3) & (uint32(1<<22) - 1)) | 1 24 | } 25 | return 0 26 | } 27 | 28 | func (h *HistoryTable) PrintMax() { 29 | var val uint32 30 | for i := 0; i < 2; i++ { 31 | for j := 0; j < 8; j++ { 32 | for k := 0; k < 64; k++ { 33 | if h[i][j][k] > val { 34 | val = h[i][j][k] 35 | } 36 | } 37 | } 38 | } 39 | fmt.Printf("%d\n", val) 40 | } 41 | -------------------------------------------------------------------------------- /killer.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | const ( 9 | KILLER_COUNT = 3 10 | ) 11 | 12 | type KEntry [4]Move // keep power of 2 array size 13 | 14 | func (s *StackItem) StoreKiller(m Move) { 15 | killers := &s.killers 16 | switch m { 17 | case killers[0]: 18 | // no update needed. 19 | case killers[1]: 20 | killers[0], killers[1] = killers[1], killers[0] 21 | default: 22 | killers[0], killers[1], killers[2] = m, killers[0], killers[1] 23 | } 24 | } 25 | 26 | func (s *StackItem) IsKiller(m Move) bool { 27 | killers := &s.killers 28 | return m == killers[0] || m == killers[1] || m == killers[2] 29 | } 30 | -------------------------------------------------------------------------------- /magics.json: -------------------------------------------------------------------------------- 1 | {"bishopMagics":[326515105195884672,9351232053886256656,1153554887999095568,1279391854758232840,2508575584797397643,10377490222769296512,9297699067995095360,2630157366951389193,8802687094852,7152280545523016708,3521817125150812672,1299295098307760257,9946270217809069328,4830112838100091072,8577110183787390088,8577110183787390088,6362469745186783772,27050485722976403,8856406803464638976,9225628295129731408,9940573590046773312,1170962292505319700,4686137352995758592,58547432158403584,9525887268097917955,12114762319265153580,2603241826299741453,871449450668925074,2543533783746352128,2852685241740,288531170235424840,1159993838274746502,2356392094547095634,2356392094547095634,288215995048530,2817032547733516,18054015824953376,5207112282836042064,363678052623975559,9241533839749709864,12360551463890538518,12360551463890538518,14233772891828548174,16287602324397948945,36031010705526808,2298408852045952,2333147469255086209,2423218246404587938,4611756845164986408,8381269559958603032,10993287420690171968,2956647083704587,2596888249488638081,9229054485693774856,36943661648381968,1461745738023469073,3674972516353442307,3468106406993106954,168304530694439478,11584454862638784516,7233134497237177864,11532035339632379936,9451930268312642048,13835208826628089136],"rookMagics":[5512410616834429472,90074192108601353,45036133784494177,1297107224636686342,180146493364371457,14992488932053188612,2458983092883587586,2053646377888260352,2456572912965009416,2774221793136487686,576502538107897890,3143516390372147720,14268810998723020802,9802717963571167442,14258115104468943232,1442735246310154368,2748709250460570304,1590052419337585738,2469764802480734920,4882464980394337396,2382421797218877451,4148237507996682560,2398324031804690580,1748419201242153377,509050321604214900,74313810101415936,9232981768625201188,2549055054292520968,147220466729746674,9228179239399851144,2738754549757204485,4707334948699259013,5476412332366103568,5199476138719611904,3175179067492139300,11004556824452005976,578757082606534720,9290891509531137,10443994872376762369,102493312912508930,9583730380397086720,4730194405340283981,38285819781529606,4683754616775573760,580968958149018000,1191237295877948417,434679140361831184,9799976904674000905,12718869045100298368,1442009091817669808,945767055108538390,1152939096977966112,9331528951476257793,10092590955283890210,9367529294270693504,9374859507322063488,2598031902657118722,17654251551660716265,2477780536947376385,11530624688696853513,3464569956992026242,10955643821066422275,9489166130917705990,3170552989404922882]} -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "bufio" 10 | "flag" 11 | "fmt" 12 | "os" 13 | "runtime" 14 | 15 | "github.com/pkg/profile" 16 | ) 17 | 18 | var version = "0.2.3" 19 | 20 | func max(a, b int) int { 21 | if a > b { 22 | return a 23 | } else { 24 | return b 25 | } 26 | } 27 | func min(a, b int) int { 28 | if a > b { 29 | return b 30 | } else { 31 | return a 32 | } 33 | } 34 | func abs(x int) int { 35 | if x < 0 { 36 | return -x 37 | } else { 38 | return x 39 | } 40 | } 41 | 42 | func assert(statement bool, failureMessage string) { 43 | if !statement { 44 | panic("\nassertion failed: " + failureMessage + "\n") 45 | } 46 | } 47 | 48 | func init() { 49 | numCPU := runtime.NumCPU() 50 | runtime.GOMAXPROCS(numCPU) 51 | setupChebyshevDistance() 52 | setupMasks() 53 | setupMagicMoveGen() 54 | setupEval() 55 | setupRand() 56 | setupZobrist() 57 | resetMainTt() 58 | setupLoadBalancer(numCPU) 59 | } 60 | 61 | func printName() { 62 | fmt.Printf("\n---------------------------------------\n") 63 | fmt.Printf(" \u265B GopherCheck v.%s \u265B\n", version) 64 | fmt.Printf(" Copyright \u00A9 2014 Stephen J. Lovell\n") 65 | fmt.Printf("---------------------------------------\n\n") 66 | } 67 | 68 | var cpuProfileFlag = flag.Bool("cpuprofile", false, "Runs cpu profiler on test suite.") 69 | var memProfileFlag = flag.Bool("memprofile", false, "Runs memory profiler on test suite.") 70 | var versionFlag = flag.Bool("version", false, "Prints version number and exits.") 71 | 72 | func main() { 73 | flag.Parse() 74 | if *versionFlag { 75 | printName() 76 | } else { 77 | if *cpuProfileFlag { 78 | printName() 79 | defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop() 80 | RunTestSuite("test_suites/wac_300.epd", MAX_DEPTH, 5000) 81 | // run 'go tool pprof -text gopher_check cpu.pprof > cpu_prof.txt' to output profile to text 82 | } else if *memProfileFlag { 83 | printName() 84 | defer profile.Start(profile.MemProfileRate(64), profile.ProfilePath(".")).Stop() 85 | // run 'go tool pprof -text --alloc_objects gopher_check mem.pprof > mem_profile.txt' to output profile to text 86 | RunTestSuite("test_suites/wac_150.epd", MAX_DEPTH, 5000) 87 | } else { 88 | uci := NewUCIAdapter() 89 | uci.Read(bufio.NewReader(os.Stdin)) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /make.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | const ( 9 | C_WQ = 8 // White castle queen side 10 | C_WK = 4 // White castle king side 11 | C_BQ = 2 // Black castle queen side 12 | C_BK = 1 // Black castle king side 13 | ) 14 | 15 | func makeMove(brd *Board, move Move) { 16 | from := move.From() 17 | to := move.To() 18 | updateCastleRights(brd, from, to) 19 | 20 | capturedPiece := move.CapturedPiece() 21 | c := brd.c 22 | piece := move.Piece() 23 | enpTarget := brd.enpTarget 24 | brd.hashKey ^= enpZobrist(enpTarget) // XOR out old en passant target. 25 | brd.enpTarget = SQ_INVALID 26 | 27 | // assert(captured_piece != KING, "Illegal king capture detected during make_move()") 28 | 29 | switch piece { 30 | case PAWN: 31 | brd.halfmoveClock = 0 // All pawn moves are irreversible. 32 | brd.pawnHashKey ^= pawnZobrist(from, c) 33 | switch capturedPiece { 34 | case EMPTY: 35 | if abs(to-from) == 16 { // handle en passant advances 36 | brd.enpTarget = uint8(to) 37 | brd.hashKey ^= enpZobrist(uint8(to)) // XOR in new en passant target 38 | } 39 | case PAWN: // Destination square will be empty if en passant capture 40 | if enpTarget != SQ_INVALID && brd.TypeAt(to) == EMPTY { 41 | brd.pawnHashKey ^= pawnZobrist(int(enpTarget), brd.Enemy()) 42 | removePiece(brd, PAWN, int(enpTarget), brd.Enemy()) 43 | brd.squares[enpTarget] = EMPTY 44 | } else { 45 | brd.pawnHashKey ^= pawnZobrist(to, brd.Enemy()) 46 | removePiece(brd, PAWN, to, brd.Enemy()) 47 | } 48 | default: // any non-pawn piece is captured 49 | removePiece(brd, capturedPiece, to, brd.Enemy()) 50 | } 51 | promotedPiece := move.PromotedTo() 52 | if promotedPiece != EMPTY { 53 | removePiece(brd, PAWN, from, c) 54 | brd.squares[from] = EMPTY 55 | addPiece(brd, promotedPiece, to, c) 56 | } else { 57 | brd.pawnHashKey ^= pawnZobrist(to, c) 58 | relocatePiece(brd, PAWN, from, to, c) 59 | } 60 | 61 | case KING: 62 | switch capturedPiece { 63 | case EMPTY: 64 | brd.halfmoveClock += 1 65 | if abs(to-from) == 2 { // king is castling. 66 | brd.halfmoveClock = 0 67 | if c == WHITE { 68 | if to == G1 { 69 | relocatePiece(brd, ROOK, H1, F1, c) 70 | } else { 71 | relocatePiece(brd, ROOK, A1, D1, c) 72 | } 73 | } else { 74 | if to == G8 { 75 | relocatePiece(brd, ROOK, H8, F8, c) 76 | } else { 77 | relocatePiece(brd, ROOK, A8, D8, c) 78 | } 79 | } 80 | } 81 | case PAWN: 82 | removePiece(brd, capturedPiece, to, brd.Enemy()) 83 | brd.pawnHashKey ^= pawnZobrist(to, brd.Enemy()) 84 | brd.halfmoveClock = 0 // All capture moves are irreversible. 85 | default: 86 | removePiece(brd, capturedPiece, to, brd.Enemy()) 87 | brd.halfmoveClock = 0 // All capture moves are irreversible. 88 | } 89 | relocateKing(brd, KING, capturedPiece, from, to, c) 90 | 91 | case ROOK: 92 | switch capturedPiece { 93 | case ROOK: 94 | removePiece(brd, capturedPiece, to, brd.Enemy()) 95 | brd.halfmoveClock = 0 // All capture moves are irreversible. 96 | case EMPTY: 97 | brd.halfmoveClock += 1 98 | case PAWN: 99 | removePiece(brd, capturedPiece, to, brd.Enemy()) 100 | brd.halfmoveClock = 0 // All capture moves are irreversible. 101 | brd.pawnHashKey ^= pawnZobrist(to, brd.Enemy()) 102 | default: 103 | removePiece(brd, capturedPiece, to, brd.Enemy()) 104 | brd.halfmoveClock = 0 // All capture moves are irreversible. 105 | } 106 | relocatePiece(brd, ROOK, from, to, c) 107 | 108 | default: 109 | switch capturedPiece { 110 | case ROOK: 111 | removePiece(brd, capturedPiece, to, brd.Enemy()) 112 | brd.halfmoveClock = 0 // All capture moves are irreversible. 113 | case EMPTY: 114 | brd.halfmoveClock += 1 115 | case PAWN: 116 | removePiece(brd, capturedPiece, to, brd.Enemy()) 117 | brd.halfmoveClock = 0 // All capture moves are irreversible. 118 | brd.pawnHashKey ^= pawnZobrist(to, brd.Enemy()) 119 | default: 120 | removePiece(brd, capturedPiece, to, brd.Enemy()) 121 | brd.halfmoveClock = 0 // All capture moves are irreversible. 122 | } 123 | relocatePiece(brd, piece, from, to, c) 124 | } 125 | 126 | brd.c ^= 1 // flip the current side to move. 127 | brd.hashKey ^= sideKey64 128 | } 129 | 130 | // Castle flag, enp target, hash key, pawn hash key, and halfmove clock are all restored during search 131 | func unmakeMove(brd *Board, move Move, memento *BoardMemento) { 132 | brd.c ^= 1 // flip the current side to move. 133 | 134 | c := brd.c 135 | piece := move.Piece() 136 | from := move.From() 137 | to := move.To() 138 | capturedPiece := move.CapturedPiece() 139 | enpTarget := memento.enpTarget 140 | 141 | switch piece { 142 | case PAWN: 143 | if move.PromotedTo() != EMPTY { 144 | unmakeRemovePiece(brd, move.PromotedTo(), to, c) 145 | brd.squares[to] = capturedPiece 146 | unmakeAddPiece(brd, piece, from, c) 147 | } else { 148 | unmakeRelocatePiece(brd, piece, to, from, c) 149 | } 150 | switch capturedPiece { 151 | case PAWN: 152 | if enpTarget != SQ_INVALID { 153 | if c == WHITE { 154 | if to == int(enpTarget)+8 { 155 | unmakeAddPiece(brd, PAWN, int(enpTarget), brd.Enemy()) 156 | } else { 157 | unmakeAddPiece(brd, PAWN, to, brd.Enemy()) 158 | } 159 | } else { 160 | if to == int(enpTarget)-8 { 161 | unmakeAddPiece(brd, PAWN, int(enpTarget), brd.Enemy()) 162 | } else { 163 | unmakeAddPiece(brd, PAWN, to, brd.Enemy()) 164 | } 165 | } 166 | } else { 167 | unmakeAddPiece(brd, PAWN, to, brd.Enemy()) 168 | } 169 | case EMPTY: 170 | default: // any non-pawn piece was captured 171 | unmakeAddPiece(brd, capturedPiece, to, brd.Enemy()) 172 | } 173 | 174 | case KING: 175 | unmakeRelocateKing(brd, piece, capturedPiece, to, from, c) 176 | if capturedPiece != EMPTY { 177 | unmakeAddPiece(brd, capturedPiece, to, brd.Enemy()) 178 | } else if abs(to-from) == 2 { // king castled. 179 | if c == WHITE { 180 | if to == G1 { 181 | unmakeRelocatePiece(brd, ROOK, F1, H1, WHITE) 182 | } else { 183 | unmakeRelocatePiece(brd, ROOK, D1, A1, WHITE) 184 | } 185 | } else { 186 | if to == G8 { 187 | unmakeRelocatePiece(brd, ROOK, F8, H8, BLACK) 188 | } else { 189 | unmakeRelocatePiece(brd, ROOK, D8, A8, BLACK) 190 | } 191 | } 192 | } 193 | 194 | default: 195 | unmakeRelocatePiece(brd, piece, to, from, c) 196 | if capturedPiece != EMPTY { 197 | unmakeAddPiece(brd, capturedPiece, to, brd.Enemy()) 198 | } 199 | } 200 | 201 | brd.hashKey, brd.pawnHashKey = memento.hashKey, memento.pawnHashKey 202 | brd.castle, brd.enpTarget = memento.castle, memento.enpTarget 203 | brd.halfmoveClock = memento.halfmoveClock 204 | } 205 | 206 | // Update castling rights whenever a piece moves from or to a square associated with the 207 | // current castling rights. 208 | func updateCastleRights(brd *Board, from, to int) { 209 | if castle := brd.castle; castle > 0 && (sqMaskOn[from]|sqMaskOn[to])&castleMasks[castle] > 0 { 210 | updateCasleRightsForSq(brd, from) 211 | updateCasleRightsForSq(brd, to) 212 | brd.hashKey ^= castleZobrist(castle) 213 | brd.hashKey ^= castleZobrist(brd.castle) 214 | } 215 | } 216 | 217 | func updateCasleRightsForSq(brd *Board, sq int) { 218 | switch sq { // if brd.castle remains unchanged, hash key will be unchanged. 219 | case A1: 220 | brd.castle &= (^uint8(C_WQ)) 221 | case E1: // white king starting position 222 | brd.castle &= (^uint8(C_WK | C_WQ)) 223 | case H1: 224 | brd.castle &= (^uint8(C_WK)) 225 | case A8: 226 | brd.castle &= (^uint8(C_BQ)) 227 | case E8: // black king starting position 228 | brd.castle &= (^uint8(C_BK | C_BQ)) 229 | case H8: 230 | brd.castle &= (^uint8(C_BK)) 231 | default: 232 | } 233 | } 234 | 235 | func removePiece(brd *Board, removedPiece Piece, sq int, e uint8) { 236 | unmakeRemovePiece(brd, removedPiece, sq, e) 237 | brd.hashKey ^= zobrist(removedPiece, sq, e) // XOR out the captured piece 238 | } 239 | 240 | func unmakeRemovePiece(brd *Board, removedPiece Piece, sq int, e uint8) { 241 | brd.pieces[e][removedPiece].Clear(sq) 242 | brd.occupied[e].Clear(sq) 243 | brd.material[e] -= int16(removedPiece.Value() + mainPst[e][removedPiece][sq]) 244 | brd.endgameCounter -= endgameCountValues[removedPiece] 245 | } 246 | 247 | func addPiece(brd *Board, addedPiece Piece, sq int, c uint8) { 248 | unmakeAddPiece(brd, addedPiece, sq, c) 249 | brd.hashKey ^= zobrist(addedPiece, sq, c) // XOR in key for added_piece 250 | } 251 | 252 | func unmakeAddPiece(brd *Board, addedPiece Piece, sq int, c uint8) { 253 | brd.pieces[c][addedPiece].Add(sq) 254 | brd.squares[sq] = addedPiece 255 | brd.occupied[c].Add(sq) 256 | brd.material[c] += int16(addedPiece.Value() + mainPst[c][addedPiece][sq]) 257 | brd.endgameCounter += endgameCountValues[addedPiece] 258 | } 259 | 260 | func relocatePiece(brd *Board, piece Piece, from, to int, c uint8) { 261 | unmakeRelocatePiece(brd, piece, from, to, c) 262 | // XOR out the key for piece at from, and XOR in the key for piece at to. 263 | brd.hashKey ^= (zobrist(piece, from, c) ^ zobrist(piece, to, c)) 264 | } 265 | 266 | func unmakeRelocatePiece(brd *Board, piece Piece, from, to int, c uint8) { 267 | fromTo := (sqMaskOn[from] | sqMaskOn[to]) 268 | brd.pieces[c][piece] ^= fromTo 269 | brd.occupied[c] ^= fromTo 270 | brd.squares[from] = EMPTY 271 | brd.squares[to] = piece 272 | brd.material[c] += int16(mainPst[c][piece][to] - mainPst[c][piece][from]) 273 | } 274 | 275 | func relocateKing(brd *Board, piece, capturedPiece Piece, from, to int, c uint8) { 276 | unmakeRelocateKing(brd, piece, capturedPiece, from, to, c) 277 | // XOR out the key for piece at from, and XOR in the key for piece at to. 278 | brd.hashKey ^= (zobrist(piece, from, c) ^ zobrist(piece, to, c)) 279 | } 280 | 281 | func unmakeRelocateKing(brd *Board, piece, capturedPiece Piece, from, to int, c uint8) { 282 | fromTo := (sqMaskOn[from] | sqMaskOn[to]) 283 | brd.pieces[c][piece] ^= fromTo 284 | brd.occupied[c] ^= fromTo 285 | brd.squares[from] = EMPTY 286 | brd.squares[to] = piece 287 | } 288 | -------------------------------------------------------------------------------- /memory.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "sync/atomic" 10 | ) 11 | 12 | const ( 13 | SLOT_COUNT = 1048576 // number of main TT slots. 4 buckets per slot. 14 | TT_MASK = SLOT_COUNT - 1 // a set bitmask used to index into TT. 15 | ) 16 | 17 | const ( 18 | NO_MATCH = 1 << iota // 0000001 19 | ORDERING_ONLY // 0000010 20 | AVOID_NULL // 0000100 21 | ALPHA_FOUND // 0001000 22 | BETA_FOUND // 0010000 23 | EXACT_FOUND // 0100000 24 | CUTOFF_FOUND // 1000000 25 | ) 26 | 27 | const ( 28 | LOWER_BOUND = iota 29 | EXACT 30 | UPPER_BOUND 31 | ) 32 | 33 | var mainTt TT 34 | 35 | func resetMainTt() { 36 | for i := 0; i < SLOT_COUNT; i++ { 37 | for j := 0; j < 4; j++ { 38 | // main_tt[i][j].key = uint64(0) 39 | // main_tt[i][j].data = uint64(NewData(NO_MOVE, 0, EXACT, NO_SCORE, 511)) 40 | mainTt[i][j].Store(NewData(NO_MOVE, 0, EXACT, NO_SCORE, 511), uint64(0)) 41 | } 42 | } 43 | } 44 | 45 | type TT [SLOT_COUNT]Slot 46 | type Slot [4]Bucket // sized to fit in a single cache line 47 | 48 | // data stores the following: (54 bits total) 49 | // depth remaining - 5 bits 50 | // move - 21 bits 51 | // bound/node type (exact, upper, lower) - 2 bits 52 | // value - 17 bits 53 | // search id (age of entry) - 9 bits 54 | type Bucket struct { 55 | key uint64 56 | data uint64 57 | } 58 | 59 | func NewData(move Move, depth, entryType, value, id int) BucketData { 60 | return BucketData(depth) | (BucketData(move) << 5) | (BucketData(entryType) << 26) | 61 | (BucketData(value+INF) << 28) | (BucketData(id) << 45) 62 | } 63 | 64 | func (b *Bucket) Store(newData BucketData, hashKey uint64) { 65 | atomic.StoreUint64(&b.data, uint64(newData)) 66 | atomic.StoreUint64(&b.key, uint64(newData)^hashKey) 67 | // b.data = uint64(new_data) 68 | // b.key = (uint64(new_data) ^ hash_key) 69 | } 70 | 71 | func (b *Bucket) Load() (BucketData, BucketData) { 72 | return BucketData(atomic.LoadUint64(&b.data)), BucketData(atomic.LoadUint64(&b.key)) 73 | // return BucketData(b.data), BucketData(b.key) 74 | } 75 | 76 | type BucketData uint64 77 | 78 | func (data BucketData) Depth() int { 79 | return int(uint64(data) & uint64(31)) 80 | } 81 | func (data BucketData) Move() Move { 82 | return Move((uint64(data) >> 5) & uint64(2097151)) 83 | } 84 | func (data BucketData) Type() int { 85 | return int((uint64(data) >> 26) & uint64(3)) 86 | } 87 | func (data BucketData) Value() int { 88 | return int(((uint64(data) >> 28) & uint64(131071)) - INF) 89 | } 90 | func (data BucketData) Id() int { 91 | return int((uint64(data) >> 45) & uint64(511)) 92 | } 93 | 94 | func (data BucketData) NewID(id int) BucketData { 95 | return (data & BucketData(35184372088831)) | (BucketData(id) << 45) 96 | } 97 | 98 | func (tt *TT) getSlot(hashKey uint64) *Slot { 99 | return &tt[hashKey&TT_MASK] 100 | } 101 | 102 | // Use Hyatt's lockless hashing approach to avoid having to lock/unlock shared TT memory 103 | // during parallel search: https://cis.uab.edu/hyatt/hashing.html 104 | func (tt *TT) probe(brd *Board, depth, nullDepth, alpha, beta int, score *int) (Move, int) { 105 | 106 | // return NO_MOVE, NO_MATCH // uncomment to disable transposition table 107 | 108 | var data, key BucketData 109 | hashKey := brd.hashKey 110 | slot := tt.getSlot(hashKey) 111 | 112 | for i := 0; i < 4; i++ { 113 | data, key = slot[i].Load() 114 | 115 | // XOR out data to return the original hash key. If data has been modified by another goroutine 116 | // due to a data race, the key returned will no longer match and probe() will reject the entry. 117 | if hashKey == uint64(data^key) { // look for an entry uncorrupted by lockless access. 118 | 119 | slot[i].Store(data.NewID(searchId), hashKey) // update age (search id) of entry. 120 | 121 | entryValue := data.Value() 122 | *score = entryValue // set the current search score 123 | 124 | entryDepth := data.Depth() 125 | if entryDepth >= depth { 126 | switch data.Type() { 127 | case LOWER_BOUND: // failed high last time (at CUT node) 128 | if entryValue >= beta { 129 | return data.Move(), (CUTOFF_FOUND | BETA_FOUND) 130 | } 131 | return data.Move(), BETA_FOUND 132 | case UPPER_BOUND: // failed low last time. (at ALL node) 133 | if entryValue <= alpha { 134 | return data.Move(), (CUTOFF_FOUND | ALPHA_FOUND) 135 | } 136 | return data.Move(), ALPHA_FOUND 137 | case EXACT: // score was inside bounds. (at PV node) 138 | if entryValue > alpha && entryValue < beta { 139 | return data.Move(), (CUTOFF_FOUND | EXACT_FOUND) 140 | } 141 | return data.Move(), EXACT_FOUND 142 | } 143 | } else if entryDepth >= nullDepth { 144 | // if the entry is too shallow for an immediate cutoff but at least as deep as a potential 145 | // null-move search, check if a null move search would have any chance of causing a beta cutoff. 146 | if data.Type() == UPPER_BOUND && data.Value() < beta { 147 | return data.Move(), AVOID_NULL 148 | } 149 | } 150 | return data.Move(), ORDERING_ONLY 151 | } 152 | } 153 | return NO_MOVE, NO_MATCH 154 | } 155 | 156 | // use lockless storing to avoid concurrent write issues without incurring locking overhead. 157 | func (tt *TT) store(brd *Board, move Move, depth, entryType, value int) { 158 | hashKey := brd.hashKey 159 | slot := tt.getSlot(hashKey) 160 | var key BucketData 161 | var data [4]BucketData 162 | 163 | newData := NewData(move, depth, entryType, value, searchId) 164 | 165 | for i := 0; i < 4; i++ { 166 | data[i], key = slot[i].Load() 167 | if hashKey == uint64(data[i]^key) { 168 | slot[i].Store(newData, hashKey) // exact match found. Always replace. 169 | return 170 | } 171 | } 172 | // If entries from a previous search exist, find/replace shallowest old entry. 173 | replaceIndex, replaceDepth := 4, 32 174 | for i := 0; i < 4; i++ { 175 | if searchId != data[i].Id() { // entry is not from the current search. 176 | if data[i].Depth() < replaceDepth { 177 | replaceIndex, replaceDepth = i, data[i].Depth() 178 | } 179 | } 180 | } 181 | if replaceIndex != 4 { 182 | slot[replaceIndex].Store(newData, hashKey) 183 | return 184 | } 185 | // No exact match or entry from previous search found. Replace the shallowest entry. 186 | replaceIndex, replaceDepth = 4, 32 187 | for i := 0; i < 4; i++ { 188 | if data[i].Depth() < replaceDepth { 189 | replaceIndex, replaceDepth = i, data[i].Depth() 190 | } 191 | } 192 | slot[replaceIndex].Store(newData, hashKey) 193 | } 194 | -------------------------------------------------------------------------------- /move.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | ) 11 | 12 | const ( 13 | NO_MOVE = (Move(EMPTY) << 15) | (Move(EMPTY) << 18) 14 | ) 15 | 16 | type Move uint32 17 | 18 | // To to fit into transposition table entries, moves are encoded using 21 bits as follows (in LSB order): 19 | // From square - first 6 bits 20 | // To square - next 6 bits 21 | // Piece - next 3 bits 22 | // Captured piece - next 3 bits 23 | // promoted to - next 3 bits 24 | 25 | func (m Move) From() int { 26 | return int(uint32(m) & uint32(63)) 27 | } 28 | 29 | func (m Move) To() int { 30 | return int((uint32(m) >> 6) & uint32(63)) 31 | } 32 | 33 | func (m Move) Piece() Piece { 34 | return Piece((uint32(m) >> 12) & uint32(7)) 35 | } 36 | 37 | func (m Move) CapturedPiece() Piece { 38 | return Piece((uint32(m) >> 15) & uint32(7)) 39 | } 40 | 41 | func (m Move) PromotedTo() Piece { 42 | return Piece((uint32(m) >> 18) & uint32(7)) 43 | } 44 | 45 | func (m Move) IsCapture() bool { 46 | return m.CapturedPiece() != EMPTY 47 | } 48 | 49 | func (m Move) IsPromotion() bool { 50 | return m.PromotedTo() != EMPTY 51 | } 52 | 53 | func (m Move) IsQuiet() bool { 54 | return !(m.IsCapture() || m.IsPromotion()) 55 | } 56 | 57 | func (m Move) IsMove() bool { 58 | return m != 0 && m != NO_MOVE 59 | } 60 | 61 | var pieceChars = [6]string{"p", "n", "b", "r", "q", "k"} 62 | 63 | func (m Move) Print() { 64 | fmt.Println(m.ToString()) 65 | } 66 | 67 | func (m Move) ToString() string { // string representation used for debugging only. 68 | var str string 69 | if !m.IsMove() { 70 | return "NO_MOVE" 71 | } 72 | str += pieceChars[m.Piece()] + " " 73 | str += ParseCoordinates(row(m.From()), column(m.From())) 74 | str += ParseCoordinates(row(m.To()), column(m.To())) 75 | if m.IsCapture() { 76 | str += " x " + pieceChars[m.CapturedPiece()] 77 | } 78 | if m.IsPromotion() { 79 | str += " promoted to " + pieceChars[m.PromotedTo()] 80 | } 81 | return str 82 | } 83 | 84 | func (m Move) ToUCI() string { 85 | if !m.IsMove() { 86 | return "0000" 87 | } 88 | str := ParseCoordinates(row(m.From()), column(m.From())) + 89 | ParseCoordinates(row(m.To()), column(m.To())) 90 | if m.PromotedTo() != EMPTY { 91 | str += pieceChars[m.PromotedTo()] 92 | } 93 | return str 94 | } 95 | 96 | func NewMove(from, to int, piece, capturedPiece, promotedTo Piece) Move { 97 | return Move(from) | (Move(to) << 6) | (Move(piece) << 12) | 98 | (Move(capturedPiece) << 15) | (Move(promotedTo) << 18) 99 | } 100 | 101 | func NewRegularMove(from, to int, piece Piece) Move { 102 | return Move(from) | (Move(to) << 6) | (Move(piece) << 12) | 103 | (Move(EMPTY) << 15) | (Move(EMPTY) << 18) 104 | } 105 | 106 | func NewCapture(from, to int, piece, capturedPiece Piece) Move { 107 | return Move(from) | (Move(to) << 6) | (Move(piece) << 12) | 108 | (Move(capturedPiece) << 15) | (Move(EMPTY) << 18) 109 | } 110 | 111 | // // since moving piece is always PAWN (0) for promotions, no need to merge in the moving piece. 112 | // func NewPromotion(from, to int, piece, promotedTo Piece) Move { 113 | // return Move(from) | (Move(to) << 6) | (Move(piece) << 12) | 114 | // (Move(EMPTY) << 15) | (Move(promotedTo) << 18) 115 | // } 116 | -------------------------------------------------------------------------------- /move_gen_test.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "strconv" 11 | "testing" 12 | // "testing" 13 | "time" 14 | ) 15 | 16 | var legalMaxTree = [10]int{1, 20, 400, 8902, 197281, 4865609, 119060324, 3195901860, 84998978956, 2439530234167} 17 | 18 | func TestLegalMoveGen(t *testing.T) { 19 | depth := 5 20 | legalMovegen(Perft, StartPos(), depth, legalMaxTree[depth], true) 21 | } 22 | 23 | // func TestMoveValidation(t *testing.T) { 24 | // depth := 5 25 | // legal_movegen(PerftValidation, StartPos(), depth, legal_max_tree[depth], true) 26 | // } 27 | 28 | // func TestPerftSuite(t *testing.T) { 29 | // depth := 6 30 | // testPositions, err := loadEpdFile("test_suites/perftsuite.epd") // http://www.rocechess.ch/perft.html 31 | // if err != nil { 32 | // panic("could not load epd file") 33 | // } 34 | // 35 | // for i, epd := range testPositions { 36 | // if expected, ok := epd.nodeCount[depth]; ok { 37 | // fmt.Printf("%d.", i+1) 38 | // epd.brd.Print() 39 | // fmt.Println(epd.fen) 40 | // legalMovegen(Perft, epd.brd, depth, expected, false) 41 | // } 42 | // } 43 | // } 44 | 45 | // TODO: add parallelism 46 | 47 | func legalMovegen(fn func(*Board, *HistoryTable, Stack, int, int) int, brd *Board, depth, expected int, verbose bool) { 48 | htable := new(HistoryTable) 49 | copy := brd.Copy() 50 | start := time.Now() 51 | stk := make(Stack, MAX_STACK, MAX_STACK) 52 | 53 | sum := fn(brd, htable, stk, depth, 0) 54 | 55 | if verbose { 56 | elapsed := time.Since(start) 57 | nps := int64(float64(sum) / elapsed.Seconds()) 58 | fmt.Printf("%d nodes at depth %d. %d NPS\n", sum, depth, nps) 59 | CompareBoards(copy, brd) 60 | } 61 | assert(*brd == *copy, "move generation did not return to initial board state.") 62 | assert(sum == expected, "Expected "+strconv.Itoa(expected)+" nodes, got "+strconv.Itoa(sum)) 63 | } 64 | 65 | func Perft(brd *Board, htable *HistoryTable, stk Stack, depth, ply int) int { 66 | sum := 0 67 | inCheck := brd.InCheck() 68 | thisStk := stk[ply] 69 | memento := brd.NewMemento() 70 | recycler := loadBalancer.RootWorker().recycler 71 | generator := NewMoveSelector(brd, &thisStk, htable, inCheck, NO_MOVE) 72 | for m, _ := generator.Next(recycler, SP_NONE); m != NO_MOVE; m, _ = generator.Next(recycler, SP_NONE) { 73 | if depth > 1 { 74 | makeMove(brd, m) 75 | sum += Perft(brd, htable, stk, depth-1, ply+1) 76 | unmakeMove(brd, m, memento) 77 | } else { 78 | sum += 1 79 | } 80 | } 81 | 82 | return sum 83 | } 84 | 85 | func PerftValidation(brd *Board, htable *HistoryTable, stk Stack, depth, ply int) int { 86 | if depth == 0 { 87 | return 1 88 | } 89 | sum := 0 90 | thisStk := stk[ply] 91 | memento := brd.NewMemento() 92 | // intentionally disregard whether king is in check while generating moves. 93 | recycler := loadBalancer.RootWorker().recycler 94 | generator := NewMoveSelector(brd, &thisStk, htable, false, NO_MOVE) 95 | for m, _ := generator.Next(recycler, SP_NONE); m != NO_MOVE; m, _ = generator.Next(recycler, SP_NONE) { 96 | inCheck := brd.InCheck() 97 | if !brd.ValidMove(m, inCheck) || !brd.LegalMove(m, inCheck) { 98 | continue // rely on validation to prevent illegal moves... 99 | } 100 | makeMove(brd, m) 101 | sum += PerftValidation(brd, htable, stk, depth-1, ply+1) 102 | unmakeMove(brd, m, memento) 103 | } 104 | return sum 105 | } 106 | -------------------------------------------------------------------------------- /move_validation.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import "fmt" 9 | 10 | // Used to verify that a killer or hash move is legal. 11 | func (brd *Board) LegalMove(m Move, inCheck bool) bool { 12 | if inCheck { 13 | return brd.EvadesCheck(m) 14 | } else { 15 | return brd.PseudolegalAvoidsCheck(m) 16 | } 17 | } 18 | 19 | // Moves generated while in check should already be legal, since we determine this 20 | // as a side-effect of generating evasions. 21 | func (brd *Board) AvoidsCheck(m Move, inCheck bool) bool { 22 | return inCheck || brd.PseudolegalAvoidsCheck(m) 23 | } 24 | 25 | func (brd *Board) PseudolegalAvoidsCheck(m Move) bool { 26 | switch m.Piece() { 27 | case PAWN: 28 | if m.CapturedPiece() == PAWN && brd.TypeAt(m.To()) == EMPTY { // En-passant 29 | // detect if the moving pawn would be pinned in the absence of the captured pawn. 30 | return isPinned(brd, brd.AllOccupied()&sqMaskOff[brd.enpTarget], 31 | m.From(), brd.c, brd.Enemy())&sqMaskOn[m.To()] > 0 32 | } else { 33 | return pinnedCanMove(brd, m.From(), m.To(), brd.c, brd.Enemy()) 34 | } 35 | case KNIGHT: // Knights can never move when pinned. 36 | return isPinned(brd, brd.AllOccupied(), m.From(), brd.c, brd.Enemy()) == BB(ANY_SQUARE_MASK) 37 | case KING: 38 | return !isAttackedBy(brd, brd.AllOccupied(), m.To(), brd.Enemy(), brd.c) 39 | default: 40 | return pinnedCanMove(brd, m.From(), m.To(), brd.c, brd.Enemy()) 41 | } 42 | } 43 | 44 | // Only called when in check 45 | func (brd *Board) EvadesCheck(m Move) bool { 46 | piece, from, to := m.Piece(), m.From(), m.To() 47 | c, e := brd.c, brd.Enemy() 48 | 49 | if piece == KING { 50 | return !isAttackedBy(brd, occAfterMove(brd.AllOccupied(), from, to), to, e, c) 51 | } 52 | occ := brd.AllOccupied() 53 | kingSq := brd.KingSq(c) 54 | threats := colorAttackMap(brd, occ, kingSq, e, c) 55 | 56 | // TODO: EvadesCheck() called from non-check position in rare cases. Examples: 57 | // 5r1k/1b3p1p/pp3p1q/3n4/1P2R3/P2B1PP1/7P/6K1 w - - 0 1 58 | // 8/PPKR4/1Bn4P/3P3R/8/2p4r/pp4p1/r6k w - - 5 2 (r h3h5 x r)...? 59 | 60 | if threats == 0 { 61 | fmt.Println("info string EvadesCheck() called from non-check position!") 62 | // brd.Print() 63 | // m.Print() 64 | // fmt.Printf("King sq: %d\n", kingSq) 65 | // fmt.Println(brd.InCheck()) 66 | // if !isBoardConsistent(brd) { 67 | // panic("inconsistent board state") 68 | // } 69 | return brd.PseudolegalAvoidsCheck(m) 70 | } 71 | 72 | if popCount(threats) > 1 { 73 | return false // only king moves can escape from double check. 74 | } 75 | if (threats|intervening[furthestForward(e, threats)][kingSq])&sqMaskOn[to] == 0 { 76 | return false // the moving piece must kill or block the attacking piece. 77 | } 78 | if brd.enpTarget != SQ_INVALID && piece == PAWN && m.CapturedPiece() == PAWN && // En-passant 79 | brd.TypeAt(to) == EMPTY { 80 | return isPinned(brd, occ&sqMaskOff[brd.enpTarget], from, c, e)&sqMaskOn[to] > 0 81 | } 82 | return pinnedCanMove(brd, from, to, c, e) // the moving piece can't be pinned to the king. 83 | } 84 | 85 | // Determines if a move is otherwise legal for brd, without considering king safety. 86 | func (brd *Board) ValidMove(m Move, inCheck bool) bool { 87 | if !m.IsMove() { 88 | return false 89 | } 90 | c, e := brd.c, brd.Enemy() 91 | piece, from, to, capturedPiece := m.Piece(), m.From(), m.To(), m.CapturedPiece() 92 | // Check that the piece is of the correct type and color. 93 | if brd.TypeAt(from) != piece || brd.pieces[c][piece]&sqMaskOn[from] == 0 { 94 | // fmt.Printf("No piece of this type available at from square!{%s}", m.ToString()) 95 | return false 96 | } 97 | if sqMaskOn[to]&brd.occupied[c] > 0 { 98 | // fmt.Printf("To square occupied by own piece!{%s}", m.ToString()) 99 | return false 100 | } 101 | if capturedPiece == KING { 102 | fmt.Printf("info string King capture detected in ValidMove! (%s)\n", m.ToString()) 103 | return false 104 | } 105 | switch piece { 106 | case PAWN: 107 | var diff int 108 | if c == WHITE { 109 | diff = to - from 110 | } else { 111 | diff = from - to 112 | } 113 | if diff < 0 { 114 | // fmt.Printf("Invalid pawn movement direction!{%s}", m.ToString()) 115 | return false 116 | } else if diff == 8 { 117 | return brd.TypeAt(to) == EMPTY 118 | } else if diff == 16 { 119 | return brd.TypeAt(to) == EMPTY && brd.TypeAt(pawnStopSq[c][from]) == EMPTY 120 | } else if capturedPiece == EMPTY { 121 | // fmt.Printf("Invalid pawn move!{%s}", m.ToString()) 122 | return false 123 | } else if capturedPiece == PAWN && brd.TypeAt(to) == EMPTY { 124 | if c == WHITE { 125 | return brd.enpTarget != SQ_INVALID && pawnSideMasks[brd.enpTarget]&sqMaskOn[from] > 0 && 126 | int(brd.enpTarget)+8 == to 127 | } else { 128 | return brd.enpTarget != SQ_INVALID && pawnSideMasks[brd.enpTarget]&sqMaskOn[from] > 0 && 129 | int(brd.enpTarget)-8 == to 130 | } 131 | } else { 132 | return brd.TypeAt(to) == capturedPiece 133 | } 134 | 135 | case KING: 136 | 137 | if abs(to-from) == 2 { // validate castle moves 138 | if inCheck { 139 | return false 140 | } 141 | occ := brd.AllOccupied() 142 | castle := brd.castle 143 | if c == WHITE { 144 | switch to { 145 | case C1: 146 | if (castle&C_WQ > uint8(0)) && castleQueensideIntervening[WHITE]&occ == 0 && 147 | !isAttackedBy(brd, occ, C1, e, c) && !isAttackedBy(brd, occ, D1, e, c) { 148 | return true 149 | } 150 | case G1: 151 | if (castle&C_WK > uint8(0)) && castleKingsideIntervening[WHITE]&occ == 0 && 152 | !isAttackedBy(brd, occ, F1, e, c) && !isAttackedBy(brd, occ, G1, e, c) { 153 | return true 154 | } 155 | } 156 | } else { 157 | switch to { 158 | case C8: 159 | if (castle&C_BQ > uint8(0)) && castleQueensideIntervening[BLACK]&occ == 0 && 160 | !isAttackedBy(brd, occ, C8, e, c) && !isAttackedBy(brd, occ, D8, e, c) { 161 | return true 162 | } 163 | case G8: 164 | if (castle&C_BK > uint8(0)) && castleKingsideIntervening[BLACK]&occ == 0 && 165 | !isAttackedBy(brd, occ, F8, e, c) && !isAttackedBy(brd, occ, G8, e, c) { 166 | return true 167 | } 168 | } 169 | } 170 | return false 171 | } 172 | case KNIGHT: 173 | // no special treatment needed for knights. 174 | default: 175 | if slidingAttacks(piece, brd.AllOccupied(), from)&sqMaskOn[to] == 0 { 176 | return false 177 | } 178 | } 179 | return brd.TypeAt(to) == capturedPiece 180 | } 181 | -------------------------------------------------------------------------------- /notation.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | // UCI Protocol specification: http://wbec-ridderkerk.nl/html/UCIProtocol.html 7 | 8 | package main 9 | 10 | import ( 11 | "bufio" 12 | "errors" 13 | "fmt" 14 | "os" 15 | "regexp" 16 | "strconv" 17 | "strings" 18 | ) 19 | 20 | type EPD struct { 21 | brd *Board 22 | bestMoves []string 23 | avoidMoves []string 24 | nodeCount map[int]int 25 | id string 26 | fen string 27 | } 28 | 29 | func (epd *EPD) Print() { 30 | fmt.Println(epd.id) 31 | } 32 | 33 | func (epd *EPD) PrintDetails() { 34 | epd.Print() 35 | epd.brd.PrintDetails() 36 | fmt.Println("Best moves:") 37 | fmt.Println(epd.bestMoves) 38 | fmt.Println("Avoid moves:") 39 | fmt.Println(epd.avoidMoves) 40 | } 41 | 42 | func loadEpdFile(dir string) ([]*EPD, error) { 43 | epdFile, err := os.Open(dir) 44 | if err != nil { 45 | return nil, errors.New(fmt.Sprintf("The specified EPD file could not be loaded.:\n%s\n", dir)) 46 | } 47 | var testPositions []*EPD 48 | scanner := bufio.NewScanner(epdFile) 49 | for scanner.Scan() { 50 | epd := ParseEPDString(scanner.Text()) 51 | testPositions = append(testPositions, epd) 52 | } 53 | return testPositions, err 54 | } 55 | 56 | // 2k4B/bpp1qp2/p1b5/7p/1PN1n1p1/2Pr4/P5PP/R3QR1K b - - bm Ng3+ g3; id "WAC.273"; 57 | func ParseEPDString(str string) *EPD { 58 | epd := &EPD{ 59 | nodeCount: make(map[int]int), 60 | } 61 | epdFields := strings.Split(str, ";") 62 | fenFields := strings.Split(epdFields[0], " ") 63 | 64 | epd.brd = ParseFENSlice(fenFields[:4]) 65 | epd.fen = strings.Join(fenFields[:4], " ") 66 | 67 | bm := regexp.MustCompile("bm") 68 | am := regexp.MustCompile("am") 69 | id := regexp.MustCompile("id") 70 | depth := regexp.MustCompile("D[1-9][0-9]?") 71 | var loc []int 72 | var subFields []string 73 | var d, nodeCount int64 74 | for _, field := range epdFields { 75 | loc = bm.FindStringIndex(field) 76 | if loc != nil { 77 | field = field[loc[1]:] 78 | subFields = strings.Split(field, " ") 79 | epd.bestMoves = append(epd.bestMoves, subFields...) 80 | continue 81 | } 82 | loc = am.FindStringIndex(field) 83 | if loc != nil { 84 | field = field[:loc[1]+1] 85 | subFields = strings.Split(field, " ") 86 | epd.avoidMoves = append(epd.avoidMoves, subFields...) 87 | continue 88 | } 89 | loc = id.FindStringIndex(field) 90 | if loc != nil { 91 | subFields = strings.Split(field, " ") 92 | epd.id = strings.Join(subFields[2:], "") 93 | } 94 | 95 | loc = depth.FindStringIndex(field) 96 | if loc != nil { // map each depth to expected node count 97 | // fmt.Println(field) 98 | subFields = strings.Split(field, " ") 99 | d, _ = strconv.ParseInt(subFields[0][1:], 10, 64) 100 | nodeCount, _ = strconv.ParseInt(subFields[1], 10, 64) 101 | epd.nodeCount[int(d)] = int(nodeCount) 102 | } 103 | } 104 | return epd 105 | } 106 | 107 | var sanChars = [8]string{"P", "N", "B", "R", "Q", "K"} 108 | 109 | func ToSAN(brd *Board, m Move) string { // convert move to Standard Algebraic Notation (SAN) 110 | piece, from, to := m.Piece(), m.From(), m.To() 111 | san := SquareString(to) 112 | 113 | if piece == PAWN { 114 | return PawnSAN(brd, m, san) 115 | } 116 | 117 | if piece == KING { 118 | if to-from == 2 { // kingside castling 119 | return "O-O" 120 | } else if to-from == -2 { // queenside castling 121 | return "O-O-O" 122 | } 123 | } 124 | 125 | if m.IsCapture() { 126 | san = "x" + san 127 | } 128 | 129 | // disambiguate moving piece 130 | if popCount(brd.pieces[brd.c][piece]) > 1 { 131 | occ := brd.AllOccupied() 132 | c := brd.c 133 | var t BB 134 | switch piece { 135 | case KNIGHT: 136 | t = knightMasks[to] & brd.pieces[c][piece] 137 | case BISHOP: 138 | t = bishopAttacks(occ, to) & brd.pieces[c][piece] 139 | case ROOK: 140 | t = rookAttacks(occ, to) & brd.pieces[c][piece] 141 | case QUEEN: 142 | t = queenAttacks(occ, to) & brd.pieces[c][piece] 143 | } 144 | if popCount(t) > 1 { 145 | if popCount(columnMasks[column(from)]&t) == 1 { 146 | san = columnNames[column(from)] + san 147 | } else if popCount(rowMasks[row(from)]&t) == 1 { 148 | san = strconv.Itoa(row(from)+1) + san 149 | } else { 150 | san = SquareString(from) + san 151 | } 152 | } 153 | } 154 | 155 | if GivesCheck(brd, m) { 156 | san += "+" 157 | } 158 | san = sanChars[piece] + san 159 | return san 160 | } 161 | 162 | func PawnSAN(brd *Board, m Move, san string) string { 163 | if m.IsPromotion() { 164 | san += sanChars[m.PromotedTo()] 165 | } 166 | if m.IsCapture() { 167 | from, to := m.From(), m.To() 168 | san = "x" + san 169 | // disambiguate capturing pawn 170 | if brd.TypeAt(to) == EMPTY { // en passant 171 | san = columnNames[column(from)] + san 172 | } else { 173 | t := pawnAttackMasks[brd.Enemy()][to] & brd.pieces[brd.c][PAWN] 174 | if popCount(t) > 1 { 175 | san = columnNames[column(from)] + san 176 | } 177 | } 178 | } 179 | if GivesCheck(brd, m) { 180 | san += "+" 181 | } 182 | return san 183 | } 184 | 185 | func GivesCheck(brd *Board, m Move) bool { 186 | memento := brd.NewMemento() 187 | makeMove(brd, m) 188 | inCheck := brd.InCheck() 189 | unmakeMove(brd, m, memento) 190 | return inCheck 191 | } 192 | 193 | func ParseFENSlice(fenFields []string) *Board { 194 | brd := EmptyBoard() 195 | 196 | ParsePlacement(brd, fenFields[0]) 197 | brd.c = ParseSide(fenFields[1]) 198 | brd.castle = ParseCastleRights(brd, fenFields[2]) 199 | brd.hashKey ^= castleZobrist(brd.castle) 200 | if len(fenFields) > 3 { 201 | brd.enpTarget = ParseEnpTarget(fenFields[3]) 202 | if len(fenFields) > 4 { 203 | brd.halfmoveClock = ParseHalfmoveClock(fenFields[4]) 204 | } 205 | } 206 | brd.hashKey ^= enpZobrist(brd.enpTarget) 207 | return brd 208 | } 209 | 210 | func ParseFENString(str string) *Board { 211 | brd := EmptyBoard() 212 | fenFields := strings.Split(str, " ") 213 | 214 | ParsePlacement(brd, fenFields[0]) 215 | brd.c = ParseSide(fenFields[1]) 216 | brd.castle = ParseCastleRights(brd, fenFields[2]) 217 | brd.hashKey ^= castleZobrist(brd.castle) 218 | 219 | brd.enpTarget = ParseEnpTarget(fenFields[3]) 220 | brd.hashKey ^= enpZobrist(brd.enpTarget) 221 | 222 | if len(fenFields) > 4 { 223 | brd.halfmoveClock = ParseHalfmoveClock(fenFields[4]) 224 | } 225 | return brd 226 | } 227 | 228 | var fenPieceChars = map[string]int{ 229 | "p": 0, 230 | "n": 1, 231 | "b": 2, 232 | "r": 3, 233 | "q": 4, 234 | "k": 5, 235 | "P": 8, 236 | "N": 9, 237 | "B": 10, 238 | "R": 11, 239 | "Q": 12, 240 | "K": 13, 241 | } 242 | 243 | func ParsePlacement(brd *Board, str string) { 244 | var rowStr string 245 | rowFields := strings.Split(str, "/") 246 | sq := 0 247 | matchDigit, _ := regexp.Compile(`\d`) 248 | for row := len(rowFields) - 1; row >= 0; row-- { 249 | rowStr = rowFields[row] 250 | for _, r := range rowStr { 251 | chr := string(r) 252 | if matchDigit.MatchString(chr) { 253 | digit, _ := strconv.ParseInt(chr, 10, 5) 254 | sq += int(digit) 255 | } else { 256 | c := uint8(fenPieceChars[chr] >> 3) 257 | pieceType := Piece(fenPieceChars[chr] & 7) 258 | addPiece(brd, pieceType, sq, c) // place the piece on the board. 259 | if pieceType == PAWN { 260 | brd.pawnHashKey ^= pawnZobrist(sq, c) 261 | } 262 | sq += 1 263 | } 264 | } 265 | } 266 | } 267 | 268 | func ParseSide(str string) uint8 { 269 | if str == "w" { 270 | return 1 271 | } else if str == "b" { 272 | return 0 273 | } else { 274 | // something's wrong. 275 | return 1 276 | } 277 | } 278 | 279 | func ParseCastleRights(brd *Board, str string) uint8 { 280 | var castle uint8 281 | if str != "-" { 282 | match, _ := regexp.MatchString("K", str) 283 | if match { 284 | castle |= C_WK 285 | } 286 | match, _ = regexp.MatchString("Q", str) 287 | if match { 288 | castle |= C_WQ 289 | } 290 | match, _ = regexp.MatchString("k", str) 291 | if match { 292 | castle |= C_BK 293 | } 294 | match, _ = regexp.MatchString("q", str) 295 | if match { 296 | castle |= C_BQ 297 | } 298 | } 299 | return castle 300 | } 301 | 302 | func ParseEnpTarget(str string) uint8 { 303 | if str == "-" { 304 | return SQ_INVALID 305 | } else { 306 | return uint8(ParseSquare(str)) 307 | } 308 | } 309 | 310 | func ParseHalfmoveClock(str string) uint8 { 311 | nonNumeric, _ := regexp.MatchString("\\D", str) 312 | if nonNumeric { 313 | return 0 314 | } else { 315 | halmoveClock, _ := strconv.ParseInt(str, 10, 8) 316 | return uint8(halmoveClock) 317 | } 318 | } 319 | 320 | func ParseMove(brd *Board, str string) Move { 321 | // make sure the move is valid. 322 | if !IsMove(str) { 323 | return NO_MOVE 324 | } 325 | 326 | from := ParseSquare(str[:2]) 327 | to := ParseSquare(str[2:4]) 328 | piece := brd.TypeAt(from) 329 | capturedPiece := brd.TypeAt(to) 330 | if piece == PAWN && capturedPiece == EMPTY { // check for en-passant capture 331 | if abs(to-from) == 9 || abs(to-from) == 7 { 332 | capturedPiece = PAWN // en-passant capture detected. 333 | } 334 | } 335 | var promotedTo Piece 336 | if len(str) == 5 { // check for promotion. 337 | promotedTo = Piece(fenPieceChars[string(str[4])]) // will always be lowercase. 338 | } else { 339 | promotedTo = Piece(EMPTY) 340 | } 341 | return NewMove(from, to, piece, capturedPiece, promotedTo) 342 | } 343 | 344 | // A1 through H8. test with Regexp. 345 | 346 | var columnChars = map[string]int{ 347 | "a": 0, 348 | "b": 1, 349 | "c": 2, 350 | "d": 3, 351 | "e": 4, 352 | "f": 5, 353 | "g": 6, 354 | "h": 7, 355 | } 356 | 357 | var columnNames = [8]string{"a", "b", "c", "d", "e", "f", "g", "h"} 358 | 359 | // create regular expression to match valid move string. 360 | func IsMove(str string) bool { 361 | match, _ := regexp.MatchString("[a-h][1-8][a-h][1-8][nbrq]?", str) 362 | return match 363 | } 364 | 365 | func Square(row, column int) int { return (row << 3) + column } 366 | 367 | func ParseSquare(str string) int { 368 | column := columnChars[string(str[0])] 369 | row, _ := strconv.ParseInt(string(str[1]), 10, 5) 370 | return Square(int(row-1), column) 371 | } 372 | 373 | func SquareString(sq int) string { 374 | return ParseCoordinates(row(sq), column(sq)) 375 | } 376 | 377 | func ParseCoordinates(row, col int) string { 378 | return columnNames[col] + strconv.Itoa(row+1) 379 | } 380 | -------------------------------------------------------------------------------- /notation_test.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "testing" 11 | ) 12 | 13 | func TestEPDParsing(t *testing.T) { 14 | test, err := loadEpdFile("test_suites/wac_300.epd") 15 | if err != nil { 16 | fmt.Print(err) 17 | return 18 | } 19 | for _, epd := range test { 20 | epd.Print() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | To Do: 4 | 5 | TODO: add proper error handling in UCI adapter. 6 | 7 | BUG: 'orphaned' workers occasionally still processing at completion of search - can interfere 8 | with next search. 9 | 10 | Reduce GC pressure: decrease object allocation during parallel search. 11 | 12 | Tune Tapered Eval. 13 | 14 | 15 | Performance: 16 | 17 | 5/28/16, 2.9 GHz Core i5 (2x physical cores, 4 hyper-threads) 18 | 657.6419m nodes searched in 408.8506s (1.6085m NPS) 19 | Total score: 290/300 20 | Overhead: 31.9396m 21 | Timeout: 1.5s 22 | 23 | 8/21/16, 2.9 GHz Core i5 (2x physical cores, 4 hyper-threads) 24 | Upgraded to Go 1.7 25 | 779.9704m nodes searched in 410.1461s (1.9017m NPS) 26 | Total score: 290/300 27 | Overhead: 45.4335m 28 | Timeout: 2.0s 29 | PASS 30 | 31 | 1/31/17, 2.9 GHz Core i5 (2x physical cores, 4 hyper-threads) 32 | Implemented memory recycling for move lists 33 | 1034.6923m nodes searched in 408.7054s (2.5316m NPS) 34 | Total score: 289/300 35 | Overhead: 53.8827m 36 | Timeout: 2.0s 37 | -------------------------------------------------------------------------------- /pawn_hash.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | const ( 9 | PAWN_ENTRY_COUNT = 16384 10 | PAWN_TT_MASK = PAWN_ENTRY_COUNT - 1 11 | ) 12 | 13 | type PawnTT [PAWN_ENTRY_COUNT]PawnEntry 14 | 15 | type PawnEntry struct { 16 | leftAttacks [2]BB 17 | rightAttacks [2]BB 18 | allAttacks [2]BB 19 | passedPawns [2]BB 20 | value [2]int 21 | key uint32 22 | count [2]uint8 23 | } 24 | 25 | func NewPawnTT() *PawnTT { 26 | return new(PawnTT) 27 | } 28 | 29 | // Typical hit rate is around 97 % 30 | func (ptt *PawnTT) Probe(key uint32) *PawnEntry { 31 | return &ptt[key&PAWN_TT_MASK] 32 | } 33 | -------------------------------------------------------------------------------- /piece.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | const ( 9 | PAWN = iota 10 | KNIGHT // 1 11 | BISHOP // 2 12 | ROOK // 3 13 | QUEEN // 4 14 | KING // 5 15 | EMPTY // 6 no piece located at this square 16 | ) 17 | 18 | const ( 19 | PAWN_VALUE = 100 // piece values are given in centipawns 20 | KNIGHT_VALUE = 320 21 | BISHOP_VALUE = 333 22 | ROOK_VALUE = 510 23 | QUEEN_VALUE = 880 24 | KING_VALUE = 5000 25 | ) 26 | 27 | type Piece uint8 28 | 29 | var pieceValues = [8]int{PAWN_VALUE, KNIGHT_VALUE, BISHOP_VALUE, ROOK_VALUE, QUEEN_VALUE, KING_VALUE} // default piece values 30 | 31 | var promoteValues = [8]int{0, KNIGHT_VALUE - PAWN_VALUE, BISHOP_VALUE - PAWN_VALUE, ROOK_VALUE - PAWN_VALUE, 32 | QUEEN_VALUE - PAWN_VALUE} 33 | 34 | func (pc Piece) Value() int { return pieceValues[pc] } 35 | 36 | func (pc Piece) PromoteValue() int { return promoteValues[pc] } 37 | -------------------------------------------------------------------------------- /pv.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | // "fmt" 9 | 10 | type PV struct { 11 | m Move 12 | value int 13 | depth int 14 | next *PV 15 | } 16 | 17 | func (pv *PV) ToUCI() string { 18 | if pv == nil || !pv.m.IsMove() { 19 | return "" 20 | } 21 | str := pv.m.ToUCI() 22 | for currPv := pv.next; currPv != nil; currPv = currPv.next { 23 | if !currPv.m.IsMove() { 24 | break 25 | } 26 | str += " " + currPv.m.ToUCI() 27 | } 28 | return str 29 | } 30 | 31 | func (pv *PV) SavePV(brd *Board, value, depth int) { 32 | var m Move 33 | var inCheck bool 34 | copy := brd.Copy() // create a local copy of the board to avoid having to unmake moves. 35 | // fmt.Printf("\n%s\n", pv.ToUCI()) 36 | for pv != nil { 37 | m = pv.m 38 | inCheck = copy.InCheck() 39 | if !copy.ValidMove(m, inCheck) || !copy.LegalMove(m, inCheck) { 40 | break 41 | } 42 | // fmt.Printf("%d, ", pv.depth) 43 | mainTt.store(copy, m, pv.depth, EXACT, pv.value) 44 | 45 | makeMove(copy, m) 46 | pv = pv.next 47 | } 48 | // fmt.Printf("\n") 49 | 50 | } 51 | 52 | // Node criteria as defined by Onno Garms: 53 | // http://www.talkchess.com/forum/viewtopic.php?t=38408&postdays=0&postorder=asc&topic_view=flat&start=10 54 | 55 | // The root node is a PV node. 56 | // The first child of a PV node is a PV node. 57 | // The further children are searched by a scout search as CUT nodes. 58 | 59 | // Research is done as PV nodes. 60 | 61 | // The node after a null move is a CUT node. 62 | // Internal iterative deeping does not change the node type. 63 | 64 | // The first child of a CUT node is an ALL node. 65 | // Further children of a CUT node are CUT nodes. 66 | // Children of ALL nodes are CUT nodes. 67 | 68 | // The first node of bad pruning is a CUT node. 69 | // The first node of null move verification is a CUT node 70 | -------------------------------------------------------------------------------- /rand.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "math/rand" 10 | ) 11 | 12 | func setupRand() { 13 | rand.Seed(4129246945) // keep the same seed each time for debugging purposes. 14 | } 15 | 16 | // RngKiss uses Bob Jenkins' pseudorandom number approach, which is well-suited for generating 17 | // magic number candidates: https://chessprogramming.wikispaces.com/Bob+Jenkins 18 | type RngKiss struct { 19 | a BB 20 | b BB 21 | c BB 22 | d BB 23 | boosters [8]BB 24 | } 25 | 26 | func NewRngKiss(seed int) *RngKiss { 27 | r := &RngKiss{} 28 | r.Setup(seed) 29 | return r 30 | } 31 | 32 | func (r *RngKiss) Setup(seed int) { 33 | r.boosters = [8]BB{3101, 552, 3555, 926, 834, 26, 2131, 1117} 34 | r.a = 0xF1EA5EED 35 | r.b, r.c, r.d = 0xD4E12C77, 0xD4E12C77, 0xD4E12C77 36 | for i := 0; i < seed; i++ { 37 | _ = r.rand() 38 | } 39 | } 40 | 41 | func (r *RngKiss) RandomMagic(sq int) BB { 42 | return r.RandomBB(r.boosters[row(sq)]) 43 | } 44 | 45 | func (r *RngKiss) RandomUint64(sq int) uint64 { 46 | return uint64(r.RandomBB(r.boosters[row(sq)])) 47 | } 48 | 49 | func (r *RngKiss) RandomUint32(sq int) uint32 { 50 | return uint32(r.RandomBB(r.boosters[row(sq)])) 51 | } 52 | 53 | func (r *RngKiss) RandomBB(booster BB) BB { 54 | return r.rotate((r.rotate(r.rand(), booster&63) & r.rand()), ((booster >> 6) & 63 & r.rand())) 55 | } 56 | 57 | func (r *RngKiss) rand() BB { 58 | e := r.a - r.rotate(r.b, 7) 59 | r.a = r.b ^ r.rotate(r.c, 13) 60 | r.b = r.c + r.rotate(r.d, 37) 61 | r.c = r.d + e 62 | r.d = e + r.a 63 | return r.d 64 | } 65 | 66 | func (r *RngKiss) rotate(x, k BB) BB { 67 | return (x << k) | (x >> (64 - k)) 68 | } 69 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # GopherCheck 2 | 3 | An open-source, UCI chess engine written in Go! 4 | 5 | GopherCheck supports a subset of the Universal Chess Interface (UCI) protocol. To use GopherCheck, you'll need a UCI-compatible chess GUI such as [Arena Chess](http://www.playwitharena.com/ "Arena Chess") or [Scid vs. PC](http://scidvspc.sourceforge.net/ "Scid vs. PC"). 6 | 7 | ## Installation 8 | 9 | Binaries are available for Windows and Mac. You can get the latest stable release from the [releases page](https://github.com/stephenjlovell/gopher_check/releases). 10 | 11 | To compile from source, you'll need the [latest version of Go](https://golang.org/doc/install). Once you've set up your Go workspace, run [go get](https://golang.org/cmd/go/#hdr-Download_and_install_packages_and_dependencies) to download and install GopherCheck: 12 | 13 | $ go get -u github.com/stephenjlovell/gopher_check 14 | 15 | ## Usage 16 | 17 | ``` 18 | $ gopher_check --help 19 | Usage of gopher_check: 20 | -cpuprofile 21 | Runs cpu profiler on test suite. 22 | -memprofile 23 | Runs memory profiler on test suite. 24 | -version 25 | Prints version number and exits. 26 | ``` 27 | Starting GopherCheck without any arguments will start the engine in UCI (command-line) mode: 28 | ``` 29 | $ gopher_check 30 | Magics read from disk. 31 | 32 | $ uci 33 | id name GopherCheck 0.2.0 34 | id author Steve Lovell 35 | option name Ponder type check default false 36 | option name CPU type spin default 0 min 1 max 4 37 | uciok 38 | 39 | $ position startpos 40 | readyok 41 | 42 | $ print 43 | Side to move: WHITE 44 | A B C D E F G H 45 | --------------------------------- 46 | 8 | ♜ | ♞ | ♝ | ♛ | ♚ | ♝ | ♞ | ♜ | 47 | --------------------------------- 48 | 7 | ♟ | ♟ | ♟ | ♟ | ♟ | ♟ | ♟ | ♟ | 49 | --------------------------------- 50 | 6 | | | | | | | | | 51 | --------------------------------- 52 | 5 | | | | | | | | | 53 | --------------------------------- 54 | 4 | | | | | | | | | 55 | --------------------------------- 56 | 3 | | | | | | | | | 57 | --------------------------------- 58 | 2 | ♙ | ♙ | ♙ | ♙ | ♙ | ♙ | ♙ | ♙ | 59 | --------------------------------- 60 | 1 | ♖ | ♘ | ♗ | ♕ | ♔ | ♗ | ♘ | ♖ | 61 | --------------------------------- 62 | A B C D E F G H 63 | 64 | $ go movetime 2000 65 | info score cp 16 depth 7 nodes 9531 nps 1188663 time 8 pv e2e4 d7d5 e4e5 c8f5 d2d4 e7e6 b1c3 66 | info score cp 13 depth 8 nodes 55569 nps 1173934 time 47 pv d2d4 d7d5 c1f4 e7e6 e2e3 b8c6 b1c3 b7b6 67 | info score cp 19 depth 9 nodes 129949 nps 1282122 time 101 pv e2e4 e7e6 d2d4 d7d5 e4e5 d5e4 b1c3 d8g5 c1g5 68 | info score cp 12 depth 10 nodes 213058 nps 1194635 time 178 pv e2e4 e7e6 d2d4 d7d5 e4e5 c7c5 c2c3 d8a5 g2g3 c5d4 69 | info score cp 18 depth 11 nodes 917781 nps 1528904 time 600 pv e2e4 e7e6 f2f4 d7d5 e4e5 c7c5 d2d4 d8b6 b1c3 f8b4 f1a6 70 | info score cp 11 depth 12 nodes 2394738 nps 1661600 time 1441 pv e2e4 e7e6 f2f4 d7d5 e4e5 c7c5 g2g3 b8c6 c2c3 d8b6 b2b3 g7g6 71 | bestmove e2e4 ponder e7e6 72 | 73 | $ quit 74 | ``` 75 | ## Search Features 76 | 77 | GopherCheck supports [parallel search](https://chessprogramming.wikispaces.com/Parallel+Search "Parallel Search"), defaulting to one search process (goroutine) per logical core. You can set the number of search goroutines via the options panel in your GUI, or by using ```setoption name CPU value ``` when in command-line mode. 78 | 79 | GopherCheck uses a version of iterative deepening, nega-max search known as [Principal Variation Search (PVS)](https://chessprogramming.wikispaces.com/Principal+Variation+Search "Principal Variation Search"). Notable search features include: 80 | 81 | - Shared hash table 82 | - Young-brothers wait concept (YBWC) 83 | - Null-move pruning with verification search 84 | - Mate-distance pruning 85 | - Internal iterative deepening (IID) 86 | - Search extensions: 87 | - Singular extensions 88 | - Check extensions 89 | - Promotion extensions 90 | - Search reductions: 91 | - Late-move reductions 92 | - Pruning: 93 | - Futility pruning 94 | 95 | ## Evaluation Features 96 | 97 | Evaluation in GopherCheck is symmetric: values for each heuristic are calculated for both sides, and a net score is returned for the current side to move. GopherCheck uses the following evaluation heuristics: 98 | 99 | - Material balance - material is a simple sum of the value of each non-king piece in play. This is the largest evaluation factor. 100 | - Lazy evaluation - if the material balance is well outside the search window, evaluation is cut short and returns the material balance. This prevents the engine from wasting a lot of time evaluating unrealistic positions. 101 | - Piece-square tables - Small static bonuses/penalties are applied based on the type of piece and its location on the board. 102 | - Mobility - major pieces are awarded bonuses based on the type of piece and the available moves from its current location (excluding squares guarded by enemy pawns). GopherCheck will generally prefer to position its major pieces where they can control the largest amount of space on the board. 103 | - King safety - Each side receives a scaled bonus for the number of attacks it can make into squares adjacent to the enemy king. 104 | - Tapered evaluation - Some heuristics are adjusted based on how close we are to the endgame. This prevents 'evaluation discontinuity' where the score changes significantly when moving from mid-game to end-game, causing the search to chase after changes in endgame status instead of real positional gain. 105 | - Pawn structure - Pawn values are adjusted by looking for several structures considered in chess to be particularly strong/weak. 106 | - Passed pawns - If no enemy pawns can block a pawn's advance, it is considered 'passed' and is more likely to eventually get promoted. A bonus is awarded for each passed pawn based on how close it is to promotion. 107 | - Defended/chained pawns - Pawns that are defended by at least one other pawn are awarded a bonus. 108 | - Isolated pawns - Pawns that are separated from other friendly pawns are vulnerable and may tie down valuable resources in defending them. A small penalty is given for each isolated pawn. 109 | - Pawn duos - Pawns that are side by side to one another create an interlocking wall of defended squares. A small bonus is given to each pawn that has at least one other pawn directly to its left or right. 110 | - Doubled/tripled pawns - A penalty is given for each pawn on the same file (column) as another friendly pawn. Having multiple pawns on the same file (column) limits their ability to advance, as they can easily be blocked by a single enemy piece and cannot defend one another. 111 | - Backward pawns - A small penalty is given to backward pawns, i.e.: 112 | - they cannot be defended by friendly pawns (no friendly pawn can move up to defend them), 113 | - their stop square is defended by an enemy sentry pawn, 114 | - their stop square is not defended by a friendly pawn 115 | - Pawn hash table - Evaluation features that depend only on the location of each side's pawns are cached in a special pawn hash table. 116 | 117 | ## Contributing 118 | 119 | Pull requests are welcome! To contribute to GopherCheck, you'll need to do the following: 120 | 121 | 1. Make sure you have [Go (>= 1.7.0)](https://golang.org/doc/install) installed. 122 | - Install a UCI-compatible chess GUI such as [Arena Chess](http://www.playwitharena.com/ "Arena Chess") or [Scid vs. PC](http://scidvspc.sourceforge.net/ "Scid vs. PC"). 123 | - Fork this repo. 124 | - Run ```go install``` and ```gopher_check --version``` to ensure GopherCheck installed correctly. 125 | - Hack on your changes. 126 | - Run tests frequently to make sure everything is still working: 127 | - Run ```go test -run=TestPlayingStrength``` to benchmark GopherCheck's performance on your hardware. This takes about 10 minutes. 128 | - Use your chess GUI to pit GopherCheck against other engines, or against older versions of GopherCheck. 129 | - Document the reasoning behind your changes along with any test results in your pull request. 130 | 131 | ## License 132 | 133 | GopherCheck is available under the MIT License. 134 | -------------------------------------------------------------------------------- /recycler.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import "sync" 9 | 10 | type Recycler struct { 11 | stack []MoveList 12 | sync.Mutex 13 | } 14 | 15 | func NewRecycler(capacity uint64) *Recycler { 16 | r := &Recycler{ 17 | stack: make([]MoveList, capacity/2, capacity), 18 | } 19 | r.init() 20 | return r 21 | } 22 | 23 | func (r *Recycler) init() { 24 | for i := 0; i < len(r.stack); i++ { 25 | r.stack[0] = NewMoveList() 26 | } 27 | } 28 | 29 | func (r *Recycler) Recycle(moves MoveList) { 30 | if len(r.stack) < cap(r.stack) { 31 | r.stack = append(r.stack, moves) 32 | } 33 | } 34 | 35 | func (r *Recycler) AttemptReuse() MoveList { 36 | var moves MoveList 37 | if len(r.stack) > 0 { 38 | moves, r.stack = r.stack[len(r.stack)-1], r.stack[:len(r.stack)-1] 39 | } 40 | if moves == nil { 41 | moves = NewMoveList() 42 | } 43 | return moves 44 | } 45 | -------------------------------------------------------------------------------- /recycler_test.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "math/rand" 11 | "runtime" 12 | "sync" 13 | "testing" 14 | "time" 15 | ) 16 | 17 | func TestRecyclerThreadSafety(t *testing.T) { 18 | r := NewRecycler(512) 19 | count := runtime.NumCPU() 20 | var wg sync.WaitGroup 21 | wg.Add(count) 22 | for i := 0; i < count; i++ { 23 | go func(r *Recycler) { 24 | defer wg.Done() 25 | var moves MoveList 26 | for j := 0; j < 100; j++ { 27 | moves = r.AttemptReuse() 28 | time.Sleep(time.Microsecond * time.Duration(rand.Intn(1000))) 29 | fmt.Println(len(moves)) 30 | // r.g.Dump() 31 | r.Recycle(moves) 32 | } 33 | }(r) 34 | } 35 | wg.Wait() 36 | } 37 | -------------------------------------------------------------------------------- /repetitions.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | // "fmt" 9 | 10 | func (stk Stack) IsRepetition(ply int, halfmoveClock uint8) bool { 11 | hashKey := stk[ply].hashKey 12 | if halfmoveClock < 4 { 13 | return false 14 | } 15 | for repetitionCount := 0; ply >= 2; ply -= 2 { 16 | if stk[ply-2].hashKey == hashKey { 17 | repetitionCount += 1 18 | if repetitionCount == 2 { 19 | return true 20 | } 21 | } 22 | } 23 | return false 24 | } 25 | -------------------------------------------------------------------------------- /search_test.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import "testing" 9 | 10 | func TestPlayingStrength(t *testing.T) { 11 | printName() 12 | timeout := 2000 13 | RunTestSuite("test_suites/wac_300.epd", MAX_DEPTH, timeout) 14 | } 15 | -------------------------------------------------------------------------------- /selector.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | // Current search stages: 9 | // 1. Hash move if available 10 | // 2. IID move if no hash move available. 11 | // 3. Evasions or Winning captures/promotions via get_best_moves(). No pruning - extensions only. 12 | // 4. All other moves via get_remaining_moves(). Futility pruning and Late-move reductions applied. 13 | // Q-search stages 14 | // 1. Evasions or winning captures/promotions get_best_moves(). Specialized futility pruning. 15 | // 2. Non-captures that give check via get_checks(). 16 | 17 | import "sync" 18 | 19 | const ( 20 | STAGE_FIRST = iota 21 | STAGE_WINNING 22 | STAGE_KILLER 23 | STAGE_LOSING 24 | STAGE_REMAINING 25 | ) 26 | const ( 27 | Q_STAGE_WINNING = iota 28 | Q_STAGE_LOSING 29 | Q_STAGE_REMAINING 30 | Q_STAGE_CHECKS 31 | ) 32 | 33 | type AbstractSelector struct { 34 | sync.Mutex 35 | stage int 36 | index int 37 | finished int 38 | inCheck bool 39 | winning MoveList 40 | losing MoveList 41 | remainingMoves MoveList 42 | brd *Board 43 | thisStk *StackItem 44 | htable *HistoryTable 45 | } 46 | 47 | func (s *AbstractSelector) CurrentStage() int { 48 | return s.stage - 1 49 | } 50 | 51 | func (s *AbstractSelector) recycleList(recycler *Recycler, moves MoveList) { 52 | if moves != nil { 53 | recycler.Recycle(moves[0:0]) 54 | } 55 | } 56 | 57 | type MoveSelector struct { 58 | AbstractSelector 59 | firstMove Move 60 | } 61 | 62 | type QMoveSelector struct { 63 | AbstractSelector 64 | checks MoveList 65 | recycler *Recycler 66 | canCheck bool 67 | } 68 | 69 | func NewMoveSelector(brd *Board, thisStk *StackItem, htable *HistoryTable, inCheck bool, firstMove Move) *MoveSelector { 70 | return &MoveSelector{ 71 | AbstractSelector: AbstractSelector{ 72 | brd: brd, 73 | thisStk: thisStk, 74 | htable: htable, 75 | inCheck: inCheck, 76 | }, 77 | firstMove: firstMove, 78 | } 79 | } 80 | 81 | func NewQMoveSelector(brd *Board, thisStk *StackItem, htable *HistoryTable, recycler *Recycler, inCheck, canCheck bool) *QMoveSelector { 82 | return &QMoveSelector{ 83 | AbstractSelector: AbstractSelector{ 84 | brd: brd, 85 | thisStk: thisStk, 86 | htable: htable, 87 | inCheck: inCheck, 88 | }, 89 | canCheck: canCheck, 90 | recycler: recycler, 91 | } 92 | } 93 | 94 | func (s *MoveSelector) Next(recycler *Recycler, spType int) (Move, int) { 95 | if spType == SP_NONE { 96 | return s.NextMove(recycler) 97 | } else { 98 | return s.NextSPMove(recycler) 99 | } 100 | } 101 | 102 | func (s *MoveSelector) NextSPMove(recycler *Recycler) (Move, int) { 103 | s.Lock() 104 | m, stage := s.NextMove(recycler) 105 | s.Unlock() 106 | return m, stage 107 | } 108 | 109 | func (s *MoveSelector) NextMove(recycler *Recycler) (Move, int) { 110 | for { 111 | for s.index == s.finished { 112 | if s.NextBatch(recycler) { 113 | return NO_MOVE, s.CurrentStage() 114 | } 115 | } 116 | switch s.CurrentStage() { 117 | case STAGE_FIRST: 118 | s.index++ 119 | if s.brd.ValidMove(s.firstMove, s.inCheck) && s.brd.LegalMove(s.firstMove, s.inCheck) { 120 | return s.firstMove, STAGE_FIRST 121 | } 122 | case STAGE_WINNING: 123 | m := s.winning[s.index].move 124 | s.index++ 125 | if m != s.firstMove && s.brd.AvoidsCheck(m, s.inCheck) { 126 | return m, STAGE_WINNING 127 | } 128 | case STAGE_KILLER: 129 | m := s.thisStk.killers[s.index] 130 | s.index++ 131 | if m != s.firstMove && s.brd.ValidMove(m, s.inCheck) && s.brd.LegalMove(m, s.inCheck) { 132 | return m, STAGE_KILLER 133 | } 134 | case STAGE_LOSING: 135 | m := s.losing[s.index].move 136 | s.index++ 137 | if m != s.firstMove && s.brd.AvoidsCheck(m, s.inCheck) { 138 | return m, STAGE_LOSING 139 | } 140 | case STAGE_REMAINING: 141 | m := s.remainingMoves[s.index].move 142 | s.index++ 143 | if m != s.firstMove && !s.thisStk.IsKiller(m) && s.brd.AvoidsCheck(m, s.inCheck) { 144 | return m, STAGE_REMAINING 145 | } 146 | default: 147 | } 148 | } 149 | } 150 | 151 | func (s *MoveSelector) NextBatch(recycler *Recycler) bool { 152 | done := false 153 | s.index = 0 154 | switch s.stage { 155 | case STAGE_FIRST: 156 | s.finished = 1 157 | case STAGE_WINNING: 158 | if s.inCheck { 159 | s.winning = recycler.AttemptReuse() 160 | s.losing = recycler.AttemptReuse() 161 | s.remainingMoves = recycler.AttemptReuse() 162 | getEvasions(s.brd, s.htable, &s.winning, &s.losing, &s.remainingMoves) 163 | // fmt.Printf("%t,%t,%t,", len(s.winning) > 8, len(s.losing) > 8, len(s.remaining_moves) > 8) 164 | } else { 165 | s.winning = recycler.AttemptReuse() 166 | s.losing = recycler.AttemptReuse() 167 | getCaptures(s.brd, s.htable, &s.winning, &s.losing) 168 | // fmt.Printf("%t,%t,", len(s.winning) > 8, len(s.losing) > 8) 169 | } 170 | s.winning.Sort() 171 | s.finished = len(s.winning) 172 | case STAGE_KILLER: 173 | s.finished = KILLER_COUNT 174 | case STAGE_LOSING: 175 | s.losing.Sort() 176 | s.finished = len(s.losing) 177 | case STAGE_REMAINING: 178 | if !s.inCheck { 179 | s.remainingMoves = recycler.AttemptReuse() 180 | getNonCaptures(s.brd, s.htable, &s.remainingMoves) 181 | // fmt.Printf("%t,", len(s.remaining_moves) > 8) 182 | } 183 | s.remainingMoves.Sort() 184 | s.finished = len(s.remainingMoves) 185 | default: 186 | s.finished = 0 187 | done = true 188 | } 189 | s.stage++ 190 | return done 191 | } 192 | 193 | func (s *MoveSelector) Recycle(recycler *Recycler) { 194 | s.recycleList(recycler, s.winning) 195 | s.recycleList(recycler, s.losing) 196 | s.recycleList(recycler, s.remainingMoves) 197 | s.winning, s.losing, s.remainingMoves = nil, nil, nil 198 | } 199 | 200 | func (s *QMoveSelector) Next() Move { 201 | for { 202 | for s.index == s.finished { 203 | if s.NextBatch() { 204 | return NO_MOVE 205 | } 206 | } 207 | switch s.CurrentStage() { 208 | case Q_STAGE_WINNING: 209 | m := s.winning[s.index].move 210 | s.index++ 211 | if s.brd.AvoidsCheck(m, s.inCheck) { 212 | return m 213 | } 214 | case Q_STAGE_LOSING: 215 | m := s.losing[s.index].move 216 | s.index++ 217 | if s.brd.AvoidsCheck(m, s.inCheck) { 218 | return m 219 | } 220 | case Q_STAGE_REMAINING: 221 | m := s.remainingMoves[s.index].move 222 | s.index++ 223 | if s.brd.AvoidsCheck(m, s.inCheck) { 224 | return m 225 | } 226 | case Q_STAGE_CHECKS: 227 | m := s.checks[s.index].move 228 | s.index++ 229 | if s.brd.AvoidsCheck(m, s.inCheck) { 230 | return m 231 | } 232 | default: 233 | } 234 | } 235 | } 236 | 237 | func (s *QMoveSelector) NextBatch() bool { 238 | done := false 239 | s.index = 0 240 | switch s.stage { 241 | case Q_STAGE_WINNING: 242 | if s.inCheck { 243 | s.winning = s.recycler.AttemptReuse() 244 | s.losing = s.recycler.AttemptReuse() 245 | s.remainingMoves = s.recycler.AttemptReuse() 246 | getEvasions(s.brd, s.htable, &s.winning, &s.losing, &s.remainingMoves) 247 | } else { 248 | s.winning = s.recycler.AttemptReuse() 249 | getWinningCaptures(s.brd, s.htable, &s.winning) 250 | } 251 | s.winning.Sort() 252 | s.finished = len(s.winning) 253 | case Q_STAGE_LOSING: 254 | s.losing.Sort() 255 | s.finished = len(s.losing) 256 | case Q_STAGE_REMAINING: 257 | s.remainingMoves.Sort() 258 | s.finished = len(s.remainingMoves) 259 | case Q_STAGE_CHECKS: 260 | if !s.inCheck && s.canCheck { 261 | s.checks = s.recycler.AttemptReuse() 262 | getChecks(s.brd, s.htable, &s.checks) 263 | s.checks.Sort() 264 | } 265 | s.finished = len(s.checks) 266 | default: 267 | done = true 268 | } 269 | s.stage++ 270 | return done 271 | } 272 | 273 | func (s *QMoveSelector) Recycle() { 274 | s.recycleList(s.recycler, s.winning) 275 | s.recycleList(s.recycler, s.losing) 276 | s.recycleList(s.recycler, s.remainingMoves) 277 | s.recycleList(s.recycler, s.checks) 278 | s.winning, s.losing, s.remainingMoves, s.checks = nil, nil, nil, nil 279 | } 280 | -------------------------------------------------------------------------------- /sort.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import "sort" 9 | 10 | // Root Sorting 11 | // At root, moves should be sorted based on subtree value rather than standard sorting. 12 | 13 | // bit pos. (LSB order) 14 | // 31 Winning promotions (1 bits) 15 | // 30 <> (1 bit) 16 | // 29 Losing promotions (1 bits) 17 | // 28 <> (1 bit) 18 | // 22 MVV/LVA (6 bits) - Used to choose between captures of equal material gain/loss 19 | // 0 History heuristic : (22 bits). Castles will always have the first bit set. 20 | 21 | const ( 22 | SORT_WINNING_PROMOTION = (1 << 31) 23 | SORT_LOSING_PROMOTION = (1 << 29) 24 | ) 25 | 26 | // Promotion Captures: 27 | // if undefended, gain is promote_values[promoted_piece] + piece_values[captured_piece] 28 | // is defended, gain is SEE score. 29 | // Non-capture promotions: 30 | // if square undefended, gain is promote_values[promoted_piece]. 31 | // If defended, gain is SEE score where captured_piece == EMPTY 32 | 33 | func SortPromotionAdvances(brd *Board, from, to int, promotedTo Piece) uint32 { 34 | if isAttackedBy(brd, brd.AllOccupied()&sqMaskOff[from], 35 | to, brd.Enemy(), brd.c) { // defended 36 | see := getSee(brd, from, to, EMPTY) 37 | if see >= 0 { 38 | return SORT_WINNING_PROMOTION | uint32(see) 39 | } else { 40 | return uint32(SORT_LOSING_PROMOTION + see) 41 | } 42 | } else { // undefended 43 | return SORT_WINNING_PROMOTION | uint32(promotedTo.PromoteValue()) 44 | } 45 | } 46 | 47 | func SortPromotionCaptures(brd *Board, from, to int, capturedPiece, promotedTo Piece) uint32 { 48 | if isAttackedBy(brd, brd.AllOccupied()&sqMaskOff[from], to, brd.Enemy(), brd.c) { // defended 49 | return uint32(SORT_WINNING_PROMOTION + getSee(brd, from, to, capturedPiece)) 50 | } else { // undefended 51 | return SORT_WINNING_PROMOTION | uint32(promotedTo.PromoteValue()+capturedPiece.Value()) 52 | } 53 | } 54 | 55 | func SortCapture(victim, attacker Piece, see int) uint32 { 56 | return (MVVLVA(victim, attacker) << 22) + uint32(see-SEE_MIN) 57 | } 58 | 59 | func MVVLVA(victim, attacker Piece) uint32 { 60 | return uint32(((victim + 1) << 3) - attacker) 61 | } 62 | 63 | type SortItem struct { 64 | order uint32 65 | move Move 66 | } 67 | 68 | type MoveList []SortItem 69 | 70 | func NewMoveList() MoveList { 71 | return make(MoveList, 0, 8) 72 | } 73 | 74 | func (l *MoveList) Sort() { 75 | sort.Sort(l) 76 | } 77 | 78 | func (l MoveList) Len() int { return len(l) } 79 | 80 | func (l MoveList) Less(i, j int) bool { return l[i].order > l[j].order } 81 | 82 | func (l MoveList) Swap(i, j int) { 83 | l[i], l[j] = l[j], l[i] 84 | } 85 | 86 | func (l *MoveList) Push(item SortItem) { 87 | *l = append(*l, item) 88 | } 89 | -------------------------------------------------------------------------------- /split.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "sync" 10 | ) 11 | 12 | const ( 13 | SP_NONE = iota 14 | SP_SERVANT 15 | SP_MASTER 16 | ) 17 | 18 | type SplitPoint struct { 19 | sync.RWMutex // 24 (bytes) 20 | stk Stack // 12 21 | depth, ply, nodeType, nodeCount int // 8 x 8 22 | alpha, beta, best, legalSearched int 23 | s *Search // 8 x 7 24 | selector *MoveSelector 25 | parent *SplitPoint 26 | master *Worker 27 | brd *Board 28 | thisStk *StackItem 29 | cond *sync.Cond 30 | bestMove Move // 4 31 | servantMask uint8 32 | cancel, workerFinished, checked bool 33 | // extensionsLeft int // TODO: verify if extension counter needs lock protection. 34 | // canNull bool 35 | // wg sync.WaitGroup 36 | } 37 | 38 | func (sp *SplitPoint) Wait() { 39 | sp.cond.L.Lock() 40 | for sp.servantMask > 0 { 41 | sp.cond.Wait() // unlocks, sleeps thread, then locks sp.cond.L 42 | } 43 | sp.cond.L.Unlock() 44 | } 45 | 46 | func (sp *SplitPoint) Order() int { 47 | sp.RLock() 48 | searched := sp.legalSearched 49 | nodeType := sp.nodeType 50 | sp.RUnlock() 51 | return (max(searched, 16) << 3) | nodeType 52 | } 53 | 54 | func (sp *SplitPoint) WorkerFinished() bool { 55 | sp.RLock() 56 | finished := sp.workerFinished 57 | sp.RUnlock() 58 | return finished 59 | } 60 | 61 | func (sp *SplitPoint) Cancel() bool { 62 | sp.RLock() 63 | cancel := sp.cancel 64 | sp.RUnlock() 65 | return cancel 66 | } 67 | 68 | func (sp *SplitPoint) HelpWanted() bool { 69 | return !sp.Cancel() && sp.ServantMask() > 0 70 | } 71 | 72 | func (sp *SplitPoint) ServantMask() uint8 { 73 | sp.cond.L.Lock() 74 | servantMask := sp.servantMask 75 | sp.cond.L.Unlock() 76 | return servantMask 77 | } 78 | 79 | func (sp *SplitPoint) AddServant(wMask uint8) { 80 | sp.cond.L.Lock() 81 | sp.servantMask |= wMask 82 | sp.cond.L.Unlock() 83 | } 84 | 85 | func (sp *SplitPoint) RemoveServant(wMask uint8) { 86 | sp.cond.L.Lock() 87 | sp.servantMask &= (^wMask) 88 | sp.cond.L.Unlock() 89 | 90 | sp.Lock() 91 | sp.workerFinished = true 92 | sp.Unlock() 93 | 94 | sp.cond.Signal() 95 | } 96 | 97 | func CreateSP(s *Search, brd *Board, stk Stack, ms *MoveSelector, bestMove Move, alpha, beta, best, 98 | depth, ply, legalSearched, nodeType, sum int, checked bool) *SplitPoint { 99 | 100 | sp := &SplitPoint{ 101 | cond: sync.NewCond(new(sync.Mutex)), 102 | selector: ms, 103 | master: brd.worker, 104 | parent: brd.worker.currentSp, 105 | 106 | brd: brd.Copy(), 107 | thisStk: stk[ply].Copy(), 108 | s: s, 109 | 110 | depth: depth, 111 | ply: ply, 112 | 113 | nodeType: nodeType, 114 | 115 | alpha: alpha, 116 | beta: beta, 117 | best: best, 118 | bestMove: bestMove, 119 | 120 | checked: checked, 121 | 122 | nodeCount: sum, 123 | legalSearched: legalSearched, 124 | cancel: false, 125 | } 126 | 127 | sp.stk = make(Stack, ply, ply) 128 | stk.CopyUpTo(sp.stk, ply) 129 | 130 | ms.brd = sp.brd // make sure the move selector points to the static SP board. 131 | ms.thisStk = sp.thisStk 132 | stk[ply].sp = sp 133 | 134 | return sp 135 | } 136 | 137 | type SPList []*SplitPoint 138 | 139 | func (l *SPList) Push(sp *SplitPoint) { 140 | *l = append(*l, sp) 141 | } 142 | 143 | func (l *SPList) Pop() *SplitPoint { 144 | old := *l 145 | n := len(old) 146 | sp := old[n-1] 147 | *l = old[0 : n-1] 148 | return sp 149 | } 150 | -------------------------------------------------------------------------------- /stack.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | // "fmt" 9 | 10 | const ( 11 | MAX_STACK = 128 12 | ) 13 | 14 | type Stack []StackItem 15 | 16 | type StackItem struct { 17 | hashKey uint64 // use hash key to search for repetitions 18 | killers KEntry 19 | singularMove Move 20 | 21 | sp *SplitPoint 22 | pv *PV 23 | eval int16 24 | inCheck bool 25 | canNull bool 26 | } 27 | 28 | func (thisStk *StackItem) Copy() *StackItem { 29 | return &StackItem{ 30 | // split point is not copied over. 31 | pv: thisStk.pv, 32 | killers: thisStk.killers, 33 | singularMove: thisStk.singularMove, 34 | eval: thisStk.eval, 35 | hashKey: thisStk.hashKey, 36 | inCheck: thisStk.inCheck, 37 | canNull: thisStk.canNull, 38 | } 39 | } 40 | 41 | func NewStack() Stack { 42 | stk := make(Stack, MAX_STACK, MAX_STACK) 43 | for i := 0; i < MAX_STACK; i++ { 44 | stk[i].canNull = true 45 | stk[i].singularMove = NO_MOVE 46 | } 47 | return stk 48 | } 49 | 50 | func (stk Stack) CopyUpTo(otherStk Stack, ply int) { 51 | for i := 0; i < ply; i++ { 52 | // other_stk[i].sp = stk[i].sp 53 | // other_stk[i].value = stk[i].value 54 | // other_stk[i].eval = stk[i].eval 55 | // other_stk[i].pv_move = stk[i].pv_move 56 | // other_stk[i].killers = stk[i].killers 57 | otherStk[i].hashKey = stk[i].hashKey 58 | // other_stk[i].depth = stk[i].depth 59 | // other_stk[i].in_check = stk[i].in_check 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test_suites/bratko_kopek.epd: -------------------------------------------------------------------------------- 1 | 1k1r4/pp1b1R2/3q2pp/4p3/2B5/4Q3/PPP2B2/2K5 b - - bm Qd1+; id "BK.01"; 2 | 3r1k2/4npp1/1ppr3p/p6P/P2PPPP1/1NR5/5K2/2R5 w - - bm d5; id "BK.02"; 3 | 2q1rr1k/3bbnnp/p2p1pp1/2pPp3/PpP1P1P1/1P2BNNP/2BQ1PRK/7R b - - bm f5; id "BK.03"; 4 | rnbqkb1r/p3pppp/1p6/2ppP3/3N4/2P5/PPP1QPPP/R1B1KB1R w KQkq - bm e6; id "BK.04"; 5 | r1b2rk1/2q1b1pp/p2ppn2/1p6/3QP3/1BN1B3/PPP3PP/R4RK1 w - - bm Nd5 a4; id "BK.05"; 6 | 2r3k1/pppR1pp1/4p3/4P1P1/5P2/1P4K1/P1P5/8 w - - bm g6; id "BK.06"; 7 | 1nk1r1r1/pp2n1pp/4p3/q2pPp1N/b1pP1P2/B1P2R2/2P1B1PP/R2Q2K1 w - - bm Nf6; id "BK.07"; 8 | 4b3/p3kp2/6p1/3pP2p/2pP1P2/4K1P1/P3N2P/8 w - - bm f5; id "BK.08"; 9 | 2kr1bnr/pbpq4/2n1pp2/3p3p/3P1P1B/2N2N1Q/PPP3PP/2KR1B1R w - - bm f5; id "BK.09"; 10 | 3rr1k1/pp3pp1/1qn2np1/8/3p4/PP1R1P2/2P1NQPP/R1B3K1 b - - bm Ne5; id "BK.10"; 11 | 2r1nrk1/p2q1ppp/bp1p4/n1pPp3/P1P1P3/2PBB1N1/4QPPP/R4RK1 w - - bm f4; id "BK.11"; 12 | r3r1k1/ppqb1ppp/8/4p1NQ/8/2P5/PP3PPP/R3R1K1 b - - bm Bf5; id "BK.12"; 13 | r2q1rk1/4bppp/p2p4/2pP4/3pP3/3Q4/PP1B1PPP/R3R1K1 w - - bm b4; id "BK.13"; 14 | rnb2r1k/pp2p2p/2pp2p1/q2P1p2/8/1Pb2NP1/PB2PPBP/R2Q1RK1 w - - bm Qd2 Qe1; id "BK.14"; 15 | 2r3k1/1p2q1pp/2b1pr2/p1pp4/6Q1/1P1PP1R1/P1PN2PP/5RK1 w - - bm Qxg7+; id "BK.15"; 16 | r1bqkb1r/4npp1/p1p4p/1p1pP1B1/8/1B6/PPPN1PPP/R2Q1RK1 w kq - bm Ne4; id "BK.16"; 17 | r2q1rk1/1ppnbppp/p2p1nb1/3Pp3/2P1P1P1/2N2N1P/PPB1QP2/R1B2RK1 b - - bm h5; id "BK.17"; 18 | r1bq1rk1/pp2ppbp/2np2p1/2n5/P3PP2/N1P2N2/1PB3PP/R1B1QRK1 b - - bm Nb3; id "BK.18"; 19 | 3rr3/2pq2pk/p2p1pnp/8/2QBPP2/1P6/P5PP/4RRK1 b - - bm Rxe4; id "BK.19"; 20 | r4k2/pb2bp1r/1p1qp2p/3pNp2/3P1P2/2N3P1/PPP1Q2P/2KRR3 w - - bm g4; id "BK.20"; 21 | 3rn2k/ppb2rpp/2ppqp2/5N2/2P1P3/1P5Q/PB3PPP/3RR1K1 w - - bm Nh6; id "BK.21"; 22 | 2r2rk1/1bqnbpp1/1p1ppn1p/pP6/N1P1P3/P2B1N1P/1B2QPP1/R2R2K1 b - - bm Bxe4; id "BK.22"; 23 | r1bqk2r/pp2bppp/2p5/3pP3/P2Q1P2/2N1B3/1PP3PP/R4RK1 b kq - bm f6; id "BK.23"; 24 | r2qnrnk/p2b2b1/1p1p2pp/2pPpp2/1PP1P3/PRNBB3/3QNPPP/5RK1 w - - bm f4; id "BK.24"; -------------------------------------------------------------------------------- /test_suites/challenging.epd: -------------------------------------------------------------------------------- 1 | 8/7p/5k2/5p2/p1p2P2/Pr1pPK2/1P1R3P/8 b - - bm Rxb2; id "WAC.002"; 2 | r4q1k/p2bR1rp/2p2Q1N/5p2/5p2/2P5/PP3PPP/R5K1 w - - bm Rf7; id "WAC.008"; 3 | rb3qk1/pQ3ppp/4p3/3P4/8/1P3N2/1P3PPP/3R2K1 w - - bm Qxa8 d6 dxe6 g3; id "WAC.031"; 4 | 1k6/5RP1/1P6/1K6/6r1/8/8/8 w - - bm Ka5 Kc5 b7; id "WAC.041"; 5 | 3rb1k1/pq3pbp/4n1p1/3p4/2N5/2P2QB1/PP3PPP/1B1R2K1 b - - bm dxc4; id "WAC.044"; 6 | 2kr3r/pp1q1ppp/5n2/1Nb5/2Pp1B2/7Q/P4PPP/1R3RK1 w - - bm Nxa7+; id "WAC.071"; 7 | 8/p7/1ppk1n2/5ppp/P1PP4/2P1K1P1/5N1P/8 b - - bm Ng4+; id "WAC.086"; 8 | 8/p3k1p1/4r3/2ppNpp1/PP1P4/2P3KP/5P2/8 b - - bm Rxe5; id "WAC.087"; 9 | r4rk1/1p2ppbp/p2pbnp1/q7/3BPPP1/2N2B2/PPP4P/R2Q1RK1 b - - bm Bxg4; id "WAC.092"; 10 | 8/k1b5/P4p2/1Pp2p1p/K1P2P1P/8/3B4/8 w - - bm Be3 b6+; id "WAC.100"; 11 | 5rk1/p5pp/8/8/2Pbp3/1P4P1/7P/4RN1K b - - bm Bc3; id "WAC.101"; 12 | 8/8/2Kp4/3P1B2/2P2k2/5p2/8/8 w - - bm Bc8 Bd3 Bh3; id "WAC.146"; 13 | 8/3b2kp/4p1p1/pr1n4/N1N4P/1P4P1/1K3P2/3R4 w - - bm Nc3; id "WAC.151"; 14 | 1br2rk1/1pqb1ppp/p3pn2/8/1P6/P1N1PN1P/1B3PP1/1QRR2K1 w - - bm Ne4; id "WAC.152"; 15 | 5rk1/2p4p/2p4r/3P4/4p1b1/1Q2NqPp/PP3P1K/R4R2 b - - bm Qg2+; id "WAC.163"; 16 | 1r2k1r1/5p2/b3p3/1p2b1B1/3p3P/3B4/PP2KP2/2R3R1 w - - bm Bf6; id "WAC.183"; 17 | rr4k1/p1pq2pp/Q1n1pn2/2bpp3/4P3/2PP1NN1/PP3PPP/R1B1K2R b KQ - bm Nb4; id "WAC.196"; 18 | 3r1r1k/1b4pp/ppn1p3/4Pp1R/Pn5P/3P4/4QP2/1qB1NKR1 w - - bm Rxh7+; id "WAC.213"; 19 | r3k3/P5bp/2N1bp2/4p3/2p5/6NP/1PP2PP1/3R2K1 w q - bm Rd8+; id "WAC.221"; 20 | 2r1r2k/1q3ppp/p2Rp3/2p1P3/6QB/p3P3/bP3PPP/3R2K1 w - - bm Bf6; id "WAC.222"; 21 | 8/8/8/1p5r/p1p1k1pN/P2pBpP1/1P1K1P2/8 b - - bm Rxh4 b4; id "WAC.229"; 22 | 2b5/1r6/2kBp1p1/p2pP1P1/2pP4/1pP3K1/1R3P2/8 b - - bm Rb4; id "WAC.230"; 23 | r5k1/pQp2qpp/8/4pbN1/3P4/6P1/PPr4P/1K1R3R b - - bm Rc1+; id "WAC.237"; 24 | 2rq1rk1/pp3ppp/2n2b2/4NR2/3P4/PB5Q/1P4PP/3R2K1 w - - bm Qxh7+; id "WAC.241"; 25 | 1r3r1k/3p4/1p1Nn1R1/4Pp1q/pP3P1p/P7/5Q1P/6RK w - - bm Qe2; id "WAC.243"; 26 | 2k1r3/1p2Bq2/p2Qp3/Pb1p1p1P/2pP1P2/2P5/2P2KP1/1R6 w - - bm Rxb5; id "WAC.247"; 27 | 5r1k/1p4pp/3q4/3Pp1R1/8/8/PP4PP/4Q1K1 b - - bm Qc5+; id "WAC.248"; 28 | 3r1rk1/1pb1qp1p/2p3p1/p7/P2Np2R/1P5P/1BP2PP1/3Q1BK1 w - - bm Nf5; id "WAC.256"; 29 | 6k1/p1B1b2p/2b3r1/2p5/4p3/1PP1N1Pq/P2R1P2/3Q2K1 b - - bm Rh6; id "WAC.262"; 30 | 2r1k2r/2pn1pp1/1p3n1p/p3PP2/4q2B/P1P5/2Q1N1PP/R4RK1 w k - bm exf6; id "WAC.265"; 31 | 2kr2nr/pp1n1ppp/2p1p3/q7/1b1P1B2/P1N2Q1P/1PP1BPP1/R3K2R w KQ - bm axb4; id "WAC.269"; 32 | 1nbq1r1k/3rbp1p/p1p1pp1Q/1p6/P1pPN3/5NP1/1P2PPBP/R4RK1 w - - bm Nfg5; id "WAC.293"; 33 | 3r3k/1r3p1p/p1pB1p2/8/p1qNP1Q1/P6P/1P4P1/3R3K w - - bm Bf8 Nf5 Qf4; id "WAC.294"; 34 | 3r2k1/1p3ppp/2pq4/p1n5/P6P/1P6/1PB2QP1/1K2R3 w - - am Rd1; id "position 03"; 35 | r1b1r1k1/1ppn1p1p/3pnqp1/8/p1P1P3/5P2/PbNQNBPP/1R2RB1K w - - bm Rxb2; id "position 04"; 36 | r5k1/3n1ppp/1p6/3p1p2/3P1B2/r3P2P/PR3PP1/2R3K1 b - - am Rxa2; id "position 06"; 37 | 6k1/p3q2p/1nr3pB/8/3Q1P2/6P1/PP5P/3R2K1 b - - bm Rd6; id "position 12"; 38 | 8/5p2/pk2p3/4P2p/2b1pP1P/P3P2B/8/7K w - - bm Bg4; id "position 20"; 39 | 8/2k5/4p3/1nb2p2/2K5/8/6B1/8 w - - bm Kxb5; id "position 21"; 40 | 1B1b4/7K/1p6/1k6/8/8/8/8 w - - bm Ba7; id "position 22"; 41 | 8/3nk3/3pp3/1B6/8/3PPP2/4K3/8 w - - bm Bxd7; id "position 25"; 42 | -------------------------------------------------------------------------------- /test_suites/kaufman.epd: -------------------------------------------------------------------------------- 1 | 1rbq1rk1/p1b1nppp/1p2p3/8/1B1pN3/P2B4/1P3PPP/2RQ1R1K w - - bm Nf6+; id "position 01"; 2 | 3r2k1/p2r1p1p/1p2p1p1/q4n2/3P4/PQ5P/1P1RNPP1/3R2K1 b - - bm Nxd4; id "position 02"; 3 | 3r2k1/1p3ppp/2pq4/p1n5/P6P/1P6/1PB2QP1/1K2R3 w - - am Rd1; id "position 03"; 4 | r1b1r1k1/1ppn1p1p/3pnqp1/8/p1P1P3/5P2/PbNQNBPP/1R2RB1K w - - bm Rxb2; id "position 04"; 5 | 2r4k/pB4bp/1p4p1/6q1/1P1n4/2N5/P4PPP/2R1Q1K1 b - - bm Qxc1; id "position 05"; 6 | r5k1/3n1ppp/1p6/3p1p2/3P1B2/r3P2P/PR3PP1/2R3K1 b - - am Rxa2; id "position 06"; 7 | 2r2rk1/1bqnbpp1/1p1ppn1p/pP6/N1P1P3/P2B1N1P/1B2QPP1/R2R2K1 b - - bm Bxe4; id "position 07"; 8 | 5r1k/6pp/1n2Q3/4p3/8/7P/PP4PK/R1B1q3 b - - bm h6; id "position 08"; 9 | r3k2r/pbn2ppp/8/1P1pP3/P1qP4/5B2/3Q1PPP/R3K2R w KQkq - bm Be2; id "position 09"; 10 | 3r2k1/ppq2pp1/4p2p/3n3P/3N2P1/2P5/PP2QP2/K2R4 b - - bm Nxc3; id "position 10"; 11 | q3rn1k/2QR4/pp2pp2/8/P1P5/1P4N1/6n1/6K1 w - - bm Nf5; id "position 11"; 12 | 6k1/p3q2p/1nr3pB/8/3Q1P2/6P1/PP5P/3R2K1 b - - bm Rd6; id "position 12"; 13 | 1r4k1/7p/5np1/3p3n/8/2NB4/7P/3N1RK1 w - - bm Nxd5; id "position 13"; 14 | 1r2r1k1/p4p1p/6pB/q7/8/3Q2P1/PbP2PKP/1R3R2 w - - bm Rxb2; id "position 14"; 15 | r2q1r1k/pb3p1p/2n1p2Q/5p2/8/3B2N1/PP3PPP/R3R1K1 w - - bm Bxf5; id "position 15"; 16 | 8/4p3/p2p4/2pP4/2P1P3/1P4k1/1P1K4/8 w - - bm b4; id "position 16"; 17 | 1r1q1rk1/p1p2pbp/2pp1np1/6B1/4P3/2NQ4/PPP2PPP/3R1RK1 w - - bm e5; id "position 17"; 18 | q4rk1/1n1Qbppp/2p5/1p2p3/1P2P3/2P4P/6P1/2B1NRK1 b - - bm Qc8; id "position 18"; 19 | r2q1r1k/1b1nN2p/pp3pp1/8/Q7/PP5P/1BP2RPN/7K w - - bm Qxd7; id "position 19"; 20 | 8/5p2/pk2p3/4P2p/2b1pP1P/P3P2B/8/7K w - - bm Bg4; id "position 20"; 21 | 8/2k5/4p3/1nb2p2/2K5/8/6B1/8 w - - bm Kxb5; id "position 21"; 22 | 1B1b4/7K/1p6/1k6/8/8/8/8 w - - bm Ba7; id "position 22"; 23 | rn1q1rk1/1b2bppp/1pn1p3/p2pP3/3P4/P2BBN1P/1P1N1PP1/R2Q1RK1 b - - bm Ba6; id "position 23"; 24 | 8/p1ppk1p1/2n2p2/8/4B3/2P1KPP1/1P5P/8 w - - bm Bxc6; id "position 24"; 25 | 8/3nk3/3pp3/1B6/8/3PPP2/4K3/8 w - - bm Bxd7; id "position 25"; -------------------------------------------------------------------------------- /test_suites/null_move.epd: -------------------------------------------------------------------------------- 1 | 8/8/p1p5/1p5p/1P5p/8/PPP2K1p/4R1rk w - - 0 1 bm Rf1; id "zugzwang.001"; 2 | 1q1k4/2Rr4/8/2Q3K1/8/8/8/8 w - - 0 1 bm Kh6; id "zugzwang.002"; 3 | 7k/5K2/5P1p/3p4/6P1/3p4/8/8 w - - 0 1 bm g5; id "zugzwang.003"; 4 | 8/6B1/p5p1/Pp4kp/1P5r/5P1Q/4q1PK/8 w - - 0 32 bm Qxh4; id "zugzwang.004"; 5 | 8/8/1p1r1k2/p1pPN1p1/P3KnP1/1P6/8/3R4 b - - 0 1 bm Nxd5; id "zugzwang.005"; -------------------------------------------------------------------------------- /test_suites/perftsuite.epd: -------------------------------------------------------------------------------- 1 | rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 ;D1 20 ;D2 400 ;D3 8902 ;D4 197281 ;D5 4865609 ;D6 119060324 2 | r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1 ;D1 48 ;D2 2039 ;D3 97862 ;D4 4085603 ;D5 193690690 3 | 4k3/8/8/8/8/8/8/4K2R w K - 0 1 ;D1 15 ;D2 66 ;D3 1197 ;D4 7059 ;D5 133987 ;D6 764643 4 | 4k3/8/8/8/8/8/8/R3K3 w Q - 0 1 ;D1 16 ;D2 71 ;D3 1287 ;D4 7626 ;D5 145232 ;D6 846648 5 | 4k2r/8/8/8/8/8/8/4K3 w k - 0 1 ;D1 5 ;D2 75 ;D3 459 ;D4 8290 ;D5 47635 ;D6 899442 6 | r3k3/8/8/8/8/8/8/4K3 w q - 0 1 ;D1 5 ;D2 80 ;D3 493 ;D4 8897 ;D5 52710 ;D6 1001523 7 | 4k3/8/8/8/8/8/8/R3K2R w KQ - 0 1 ;D1 26 ;D2 112 ;D3 3189 ;D4 17945 ;D5 532933 ;D6 2788982 8 | r3k2r/8/8/8/8/8/8/4K3 w kq - 0 1 ;D1 5 ;D2 130 ;D3 782 ;D4 22180 ;D5 118882 ;D6 3517770 9 | 8/8/8/8/8/8/6k1/4K2R w K - 0 1 ;D1 12 ;D2 38 ;D3 564 ;D4 2219 ;D5 37735 ;D6 185867 10 | 8/8/8/8/8/8/1k6/R3K3 w Q - 0 1 ;D1 15 ;D2 65 ;D3 1018 ;D4 4573 ;D5 80619 ;D6 413018 11 | 4k2r/6K1/8/8/8/8/8/8 w k - 0 1 ;D1 3 ;D2 32 ;D3 134 ;D4 2073 ;D5 10485 ;D6 179869 12 | r3k3/1K6/8/8/8/8/8/8 w q - 0 1 ;D1 4 ;D2 49 ;D3 243 ;D4 3991 ;D5 20780 ;D6 367724 13 | r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1 ;D1 26 ;D2 568 ;D3 13744 ;D4 314346 ;D5 7594526 ;D6 179862938 14 | r3k2r/8/8/8/8/8/8/1R2K2R w Kkq - 0 1 ;D1 25 ;D2 567 ;D3 14095 ;D4 328965 ;D5 8153719 ;D6 195629489 15 | r3k2r/8/8/8/8/8/8/2R1K2R w Kkq - 0 1 ;D1 25 ;D2 548 ;D3 13502 ;D4 312835 ;D5 7736373 ;D6 184411439 16 | r3k2r/8/8/8/8/8/8/R3K1R1 w Qkq - 0 1 ;D1 25 ;D2 547 ;D3 13579 ;D4 316214 ;D5 7878456 ;D6 189224276 17 | 1r2k2r/8/8/8/8/8/8/R3K2R w KQk - 0 1 ;D1 26 ;D2 583 ;D3 14252 ;D4 334705 ;D5 8198901 ;D6 198328929 18 | 2r1k2r/8/8/8/8/8/8/R3K2R w KQk - 0 1 ;D1 25 ;D2 560 ;D3 13592 ;D4 317324 ;D5 7710115 ;D6 185959088 19 | r3k1r1/8/8/8/8/8/8/R3K2R w KQq - 0 1 ;D1 25 ;D2 560 ;D3 13607 ;D4 320792 ;D5 7848606 ;D6 190755813 20 | 4k3/8/8/8/8/8/8/4K2R b K - 0 1 ;D1 5 ;D2 75 ;D3 459 ;D4 8290 ;D5 47635 ;D6 899442 21 | 4k3/8/8/8/8/8/8/R3K3 b Q - 0 1 ;D1 5 ;D2 80 ;D3 493 ;D4 8897 ;D5 52710 ;D6 1001523 22 | 4k2r/8/8/8/8/8/8/4K3 b k - 0 1 ;D1 15 ;D2 66 ;D3 1197 ;D4 7059 ;D5 133987 ;D6 764643 23 | r3k3/8/8/8/8/8/8/4K3 b q - 0 1 ;D1 16 ;D2 71 ;D3 1287 ;D4 7626 ;D5 145232 ;D6 846648 24 | 4k3/8/8/8/8/8/8/R3K2R b KQ - 0 1 ;D1 5 ;D2 130 ;D3 782 ;D4 22180 ;D5 118882 ;D6 3517770 25 | r3k2r/8/8/8/8/8/8/4K3 b kq - 0 1 ;D1 26 ;D2 112 ;D3 3189 ;D4 17945 ;D5 532933 ;D6 2788982 26 | 8/8/8/8/8/8/6k1/4K2R b K - 0 1 ;D1 3 ;D2 32 ;D3 134 ;D4 2073 ;D5 10485 ;D6 179869 27 | 8/8/8/8/8/8/1k6/R3K3 b Q - 0 1 ;D1 4 ;D2 49 ;D3 243 ;D4 3991 ;D5 20780 ;D6 367724 28 | 4k2r/6K1/8/8/8/8/8/8 b k - 0 1 ;D1 12 ;D2 38 ;D3 564 ;D4 2219 ;D5 37735 ;D6 185867 29 | r3k3/1K6/8/8/8/8/8/8 b q - 0 1 ;D1 15 ;D2 65 ;D3 1018 ;D4 4573 ;D5 80619 ;D6 413018 30 | r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1 ;D1 26 ;D2 568 ;D3 13744 ;D4 314346 ;D5 7594526 ;D6 179862938 31 | r3k2r/8/8/8/8/8/8/1R2K2R b Kkq - 0 1 ;D1 26 ;D2 583 ;D3 14252 ;D4 334705 ;D5 8198901 ;D6 198328929 32 | r3k2r/8/8/8/8/8/8/2R1K2R b Kkq - 0 1 ;D1 25 ;D2 560 ;D3 13592 ;D4 317324 ;D5 7710115 ;D6 185959088 33 | r3k2r/8/8/8/8/8/8/R3K1R1 b Qkq - 0 1 ;D1 25 ;D2 560 ;D3 13607 ;D4 320792 ;D5 7848606 ;D6 190755813 34 | 1r2k2r/8/8/8/8/8/8/R3K2R b KQk - 0 1 ;D1 25 ;D2 567 ;D3 14095 ;D4 328965 ;D5 8153719 ;D6 195629489 35 | 2r1k2r/8/8/8/8/8/8/R3K2R b KQk - 0 1 ;D1 25 ;D2 548 ;D3 13502 ;D4 312835 ;D5 7736373 ;D6 184411439 36 | r3k1r1/8/8/8/8/8/8/R3K2R b KQq - 0 1 ;D1 25 ;D2 547 ;D3 13579 ;D4 316214 ;D5 7878456 ;D6 189224276 37 | 8/1n4N1/2k5/8/8/5K2/1N4n1/8 w - - 0 1 ;D1 14 ;D2 195 ;D3 2760 ;D4 38675 ;D5 570726 ;D6 8107539 38 | 8/1k6/8/5N2/8/4n3/8/2K5 w - - 0 1 ;D1 11 ;D2 156 ;D3 1636 ;D4 20534 ;D5 223507 ;D6 2594412 39 | 8/8/4k3/3Nn3/3nN3/4K3/8/8 w - - 0 1 ;D1 19 ;D2 289 ;D3 4442 ;D4 73584 ;D5 1198299 ;D6 19870403 40 | K7/8/2n5/1n6/8/8/8/k6N w - - 0 1 ;D1 3 ;D2 51 ;D3 345 ;D4 5301 ;D5 38348 ;D6 588695 41 | k7/8/2N5/1N6/8/8/8/K6n w - - 0 1 ;D1 17 ;D2 54 ;D3 835 ;D4 5910 ;D5 92250 ;D6 688780 42 | 8/1n4N1/2k5/8/8/5K2/1N4n1/8 b - - 0 1 ;D1 15 ;D2 193 ;D3 2816 ;D4 40039 ;D5 582642 ;D6 8503277 43 | 8/1k6/8/5N2/8/4n3/8/2K5 b - - 0 1 ;D1 16 ;D2 180 ;D3 2290 ;D4 24640 ;D5 288141 ;D6 3147566 44 | 8/8/3K4/3Nn3/3nN3/4k3/8/8 b - - 0 1 ;D1 4 ;D2 68 ;D3 1118 ;D4 16199 ;D5 281190 ;D6 4405103 45 | K7/8/2n5/1n6/8/8/8/k6N b - - 0 1 ;D1 17 ;D2 54 ;D3 835 ;D4 5910 ;D5 92250 ;D6 688780 46 | k7/8/2N5/1N6/8/8/8/K6n b - - 0 1 ;D1 3 ;D2 51 ;D3 345 ;D4 5301 ;D5 38348 ;D6 588695 47 | B6b/8/8/8/2K5/4k3/8/b6B w - - 0 1 ;D1 17 ;D2 278 ;D3 4607 ;D4 76778 ;D5 1320507 ;D6 22823890 48 | 8/8/1B6/7b/7k/8/2B1b3/7K w - - 0 1 ;D1 21 ;D2 316 ;D3 5744 ;D4 93338 ;D5 1713368 ;D6 28861171 49 | k7/B7/1B6/1B6/8/8/8/K6b w - - 0 1 ;D1 21 ;D2 144 ;D3 3242 ;D4 32955 ;D5 787524 ;D6 7881673 50 | K7/b7/1b6/1b6/8/8/8/k6B w - - 0 1 ;D1 7 ;D2 143 ;D3 1416 ;D4 31787 ;D5 310862 ;D6 7382896 51 | B6b/8/8/8/2K5/5k2/8/b6B b - - 0 1 ;D1 6 ;D2 106 ;D3 1829 ;D4 31151 ;D5 530585 ;D6 9250746 52 | 8/8/1B6/7b/7k/8/2B1b3/7K b - - 0 1 ;D1 17 ;D2 309 ;D3 5133 ;D4 93603 ;D5 1591064 ;D6 29027891 53 | k7/B7/1B6/1B6/8/8/8/K6b b - - 0 1 ;D1 7 ;D2 143 ;D3 1416 ;D4 31787 ;D5 310862 ;D6 7382896 54 | K7/b7/1b6/1b6/8/8/8/k6B b - - 0 1 ;D1 21 ;D2 144 ;D3 3242 ;D4 32955 ;D5 787524 ;D6 7881673 55 | 7k/RR6/8/8/8/8/rr6/7K w - - 0 1 ;D1 19 ;D2 275 ;D3 5300 ;D4 104342 ;D5 2161211 ;D6 44956585 56 | R6r/8/8/2K5/5k2/8/8/r6R w - - 0 1 ;D1 36 ;D2 1027 ;D3 29215 ;D4 771461 ;D5 20506480 ;D6 525169084 57 | 7k/RR6/8/8/8/8/rr6/7K b - - 0 1 ;D1 19 ;D2 275 ;D3 5300 ;D4 104342 ;D5 2161211 ;D6 44956585 58 | R6r/8/8/2K5/5k2/8/8/r6R b - - 0 1 ;D1 36 ;D2 1027 ;D3 29227 ;D4 771368 ;D5 20521342 ;D6 524966748 59 | 6kq/8/8/8/8/8/8/7K w - - 0 1 ;D1 2 ;D2 36 ;D3 143 ;D4 3637 ;D5 14893 ;D6 391507 60 | 6KQ/8/8/8/8/8/8/7k b - - 0 1 ;D1 2 ;D2 36 ;D3 143 ;D4 3637 ;D5 14893 ;D6 391507 61 | K7/8/8/3Q4/4q3/8/8/7k w - - 0 1 ;D1 6 ;D2 35 ;D3 495 ;D4 8349 ;D5 166741 ;D6 3370175 62 | 6qk/8/8/8/8/8/8/7K b - - 0 1 ;D1 22 ;D2 43 ;D3 1015 ;D4 4167 ;D5 105749 ;D6 419369 63 | 6KQ/8/8/8/8/8/8/7k b - - 0 1 ;D1 2 ;D2 36 ;D3 143 ;D4 3637 ;D5 14893 ;D6 391507 64 | K7/8/8/3Q4/4q3/8/8/7k b - - 0 1 ;D1 6 ;D2 35 ;D3 495 ;D4 8349 ;D5 166741 ;D6 3370175 65 | 8/8/8/8/8/K7/P7/k7 w - - 0 1 ;D1 3 ;D2 7 ;D3 43 ;D4 199 ;D5 1347 ;D6 6249 66 | 8/8/8/8/8/7K/7P/7k w - - 0 1 ;D1 3 ;D2 7 ;D3 43 ;D4 199 ;D5 1347 ;D6 6249 67 | K7/p7/k7/8/8/8/8/8 w - - 0 1 ;D1 1 ;D2 3 ;D3 12 ;D4 80 ;D5 342 ;D6 2343 68 | 7K/7p/7k/8/8/8/8/8 w - - 0 1 ;D1 1 ;D2 3 ;D3 12 ;D4 80 ;D5 342 ;D6 2343 69 | 8/2k1p3/3pP3/3P2K1/8/8/8/8 w - - 0 1 ;D1 7 ;D2 35 ;D3 210 ;D4 1091 ;D5 7028 ;D6 34834 70 | 8/8/8/8/8/K7/P7/k7 b - - 0 1 ;D1 1 ;D2 3 ;D3 12 ;D4 80 ;D5 342 ;D6 2343 71 | 8/8/8/8/8/7K/7P/7k b - - 0 1 ;D1 1 ;D2 3 ;D3 12 ;D4 80 ;D5 342 ;D6 2343 72 | K7/p7/k7/8/8/8/8/8 b - - 0 1 ;D1 3 ;D2 7 ;D3 43 ;D4 199 ;D5 1347 ;D6 6249 73 | 7K/7p/7k/8/8/8/8/8 b - - 0 1 ;D1 3 ;D2 7 ;D3 43 ;D4 199 ;D5 1347 ;D6 6249 74 | 8/2k1p3/3pP3/3P2K1/8/8/8/8 b - - 0 1 ;D1 5 ;D2 35 ;D3 182 ;D4 1091 ;D5 5408 ;D6 34822 75 | 8/8/8/8/8/4k3/4P3/4K3 w - - 0 1 ;D1 2 ;D2 8 ;D3 44 ;D4 282 ;D5 1814 ;D6 11848 76 | 4k3/4p3/4K3/8/8/8/8/8 b - - 0 1 ;D1 2 ;D2 8 ;D3 44 ;D4 282 ;D5 1814 ;D6 11848 77 | 8/8/7k/7p/7P/7K/8/8 w - - 0 1 ;D1 3 ;D2 9 ;D3 57 ;D4 360 ;D5 1969 ;D6 10724 78 | 8/8/k7/p7/P7/K7/8/8 w - - 0 1 ;D1 3 ;D2 9 ;D3 57 ;D4 360 ;D5 1969 ;D6 10724 79 | 8/8/3k4/3p4/3P4/3K4/8/8 w - - 0 1 ;D1 5 ;D2 25 ;D3 180 ;D4 1294 ;D5 8296 ;D6 53138 80 | 8/3k4/3p4/8/3P4/3K4/8/8 w - - 0 1 ;D1 8 ;D2 61 ;D3 483 ;D4 3213 ;D5 23599 ;D6 157093 81 | 8/8/3k4/3p4/8/3P4/3K4/8 w - - 0 1 ;D1 8 ;D2 61 ;D3 411 ;D4 3213 ;D5 21637 ;D6 158065 82 | k7/8/3p4/8/3P4/8/8/7K w - - 0 1 ;D1 4 ;D2 15 ;D3 90 ;D4 534 ;D5 3450 ;D6 20960 83 | 8/8/7k/7p/7P/7K/8/8 b - - 0 1 ;D1 3 ;D2 9 ;D3 57 ;D4 360 ;D5 1969 ;D6 10724 84 | 8/8/k7/p7/P7/K7/8/8 b - - 0 1 ;D1 3 ;D2 9 ;D3 57 ;D4 360 ;D5 1969 ;D6 10724 85 | 8/8/3k4/3p4/3P4/3K4/8/8 b - - 0 1 ;D1 5 ;D2 25 ;D3 180 ;D4 1294 ;D5 8296 ;D6 53138 86 | 8/3k4/3p4/8/3P4/3K4/8/8 b - - 0 1 ;D1 8 ;D2 61 ;D3 411 ;D4 3213 ;D5 21637 ;D6 158065 87 | 8/8/3k4/3p4/8/3P4/3K4/8 b - - 0 1 ;D1 8 ;D2 61 ;D3 483 ;D4 3213 ;D5 23599 ;D6 157093 88 | k7/8/3p4/8/3P4/8/8/7K b - - 0 1 ;D1 4 ;D2 15 ;D3 89 ;D4 537 ;D5 3309 ;D6 21104 89 | 7k/3p4/8/8/3P4/8/8/K7 w - - 0 1 ;D1 4 ;D2 19 ;D3 117 ;D4 720 ;D5 4661 ;D6 32191 90 | 7k/8/8/3p4/8/8/3P4/K7 w - - 0 1 ;D1 5 ;D2 19 ;D3 116 ;D4 716 ;D5 4786 ;D6 30980 91 | k7/8/8/7p/6P1/8/8/K7 w - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 92 | k7/8/7p/8/8/6P1/8/K7 w - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 93 | k7/8/8/6p1/7P/8/8/K7 w - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 94 | k7/8/6p1/8/8/7P/8/K7 w - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 95 | k7/8/8/3p4/4p3/8/8/7K w - - 0 1 ;D1 3 ;D2 15 ;D3 84 ;D4 573 ;D5 3013 ;D6 22886 96 | k7/8/3p4/8/8/4P3/8/7K w - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4271 ;D6 28662 97 | 7k/3p4/8/8/3P4/8/8/K7 b - - 0 1 ;D1 5 ;D2 19 ;D3 117 ;D4 720 ;D5 5014 ;D6 32167 98 | 7k/8/8/3p4/8/8/3P4/K7 b - - 0 1 ;D1 4 ;D2 19 ;D3 117 ;D4 712 ;D5 4658 ;D6 30749 99 | k7/8/8/7p/6P1/8/8/K7 b - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 100 | k7/8/7p/8/8/6P1/8/K7 b - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 101 | k7/8/8/6p1/7P/8/8/K7 b - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 102 | k7/8/6p1/8/8/7P/8/K7 b - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 103 | k7/8/8/3p4/4p3/8/8/7K b - - 0 1 ;D1 5 ;D2 15 ;D3 102 ;D4 569 ;D5 4337 ;D6 22579 104 | k7/8/3p4/8/8/4P3/8/7K b - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4271 ;D6 28662 105 | 7k/8/8/p7/1P6/8/8/7K w - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 106 | 7k/8/p7/8/8/1P6/8/7K w - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 107 | 7k/8/8/1p6/P7/8/8/7K w - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 108 | 7k/8/1p6/8/8/P7/8/7K w - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 109 | k7/7p/8/8/8/8/6P1/K7 w - - 0 1 ;D1 5 ;D2 25 ;D3 161 ;D4 1035 ;D5 7574 ;D6 55338 110 | k7/6p1/8/8/8/8/7P/K7 w - - 0 1 ;D1 5 ;D2 25 ;D3 161 ;D4 1035 ;D5 7574 ;D6 55338 111 | 3k4/3pp3/8/8/8/8/3PP3/3K4 w - - 0 1 ;D1 7 ;D2 49 ;D3 378 ;D4 2902 ;D5 24122 ;D6 199002 112 | 7k/8/8/p7/1P6/8/8/7K b - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 113 | 7k/8/p7/8/8/1P6/8/7K b - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 114 | 7k/8/8/1p6/P7/8/8/7K b - - 0 1 ;D1 5 ;D2 22 ;D3 139 ;D4 877 ;D5 6112 ;D6 41874 115 | 7k/8/1p6/8/8/P7/8/7K b - - 0 1 ;D1 4 ;D2 16 ;D3 101 ;D4 637 ;D5 4354 ;D6 29679 116 | k7/7p/8/8/8/8/6P1/K7 b - - 0 1 ;D1 5 ;D2 25 ;D3 161 ;D4 1035 ;D5 7574 ;D6 55338 117 | k7/6p1/8/8/8/8/7P/K7 b - - 0 1 ;D1 5 ;D2 25 ;D3 161 ;D4 1035 ;D5 7574 ;D6 55338 118 | 3k4/3pp3/8/8/8/8/3PP3/3K4 b - - 0 1 ;D1 7 ;D2 49 ;D3 378 ;D4 2902 ;D5 24122 ;D6 199002 119 | 8/Pk6/8/8/8/8/6Kp/8 w - - 0 1 ;D1 11 ;D2 97 ;D3 887 ;D4 8048 ;D5 90606 ;D6 1030499 120 | n1n5/1Pk5/8/8/8/8/5Kp1/5N1N w - - 0 1 ;D1 24 ;D2 421 ;D3 7421 ;D4 124608 ;D5 2193768 ;D6 37665329 121 | 8/PPPk4/8/8/8/8/4Kppp/8 w - - 0 1 ;D1 18 ;D2 270 ;D3 4699 ;D4 79355 ;D5 1533145 ;D6 28859283 122 | n1n5/PPPk4/8/8/8/8/4Kppp/5N1N w - - 0 1 ;D1 24 ;D2 496 ;D3 9483 ;D4 182838 ;D5 3605103 ;D6 71179139 123 | 8/Pk6/8/8/8/8/6Kp/8 b - - 0 1 ;D1 11 ;D2 97 ;D3 887 ;D4 8048 ;D5 90606 ;D6 1030499 124 | n1n5/1Pk5/8/8/8/8/5Kp1/5N1N b - - 0 1 ;D1 24 ;D2 421 ;D3 7421 ;D4 124608 ;D5 2193768 ;D6 37665329 125 | 8/PPPk4/8/8/8/8/4Kppp/8 b - - 0 1 ;D1 18 ;D2 270 ;D3 4699 ;D4 79355 ;D5 1533145 ;D6 28859283 126 | n1n5/PPPk4/8/8/8/8/4Kppp/5N1N b - - 0 1 ;D1 24 ;D2 496 ;D3 9483 ;D4 182838 ;D5 3605103 ;D6 71179139 127 | -------------------------------------------------------------------------------- /test_suites/wac_150.epd: -------------------------------------------------------------------------------- 1 | 2rr3k/pp3pp1/1nnqbN1p/3pN3/2pP4/2P3Q1/PPB4P/R4RK1 w - - bm Qg6; id "WAC.001"; 2 | 5rk1/1ppb3p/p1pb4/6q1/3P1p1r/2P1R2P/PP1BQ1P1/5RKN w - - bm Rg3; id "WAC.003"; 3 | 5k2/6pp/p1qN4/1p1p4/3P4/2PKP2Q/PP3r2/3R4 b - - bm Qc4+; id "WAC.005"; 4 | rnbqkb1r/pppp1ppp/8/4P3/6n1/7P/PPPNPPP1/R1BQKBNR b KQkq - bm Ne3; id "WAC.007"; 5 | 3q1rk1/p4pp1/2pb3p/3p4/6Pr/1PNQ4/P1PB1PP1/4RRK1 b - - bm Bh2+; id "WAC.009"; 6 | r1b1kb1r/3q1ppp/pBp1pn2/8/Np3P2/5B2/PPP3PP/R2Q1RK1 w kq - bm Bxc6; id "WAC.011"; 7 | 5rk1/pp4p1/2n1p2p/2Npq3/2p5/6P1/P3P1BP/R4Q1K w - - bm Qxf8+; id "WAC.013"; 8 | 1R6/1brk2p1/4p2p/p1P1Pp2/P7/6P1/1P4P1/2R3K1 w - - bm Rxb7; id "WAC.015"; 9 | 1k5r/pppbn1pp/4q1r1/1P3p2/2NPp3/1QP5/P4PPP/R1B1R1K1 w - - bm Ne5; id "WAC.017"; 10 | r1b2rk1/ppbn1ppp/4p3/1QP4q/3P4/N4N2/5PPP/R1B2RK1 w - - bm c6; id "WAC.019"; 11 | 5rk1/1b3p1p/pp3p2/3n1N2/1P6/P1qB1PP1/3Q3P/4R1K1 w - - bm Qh6; id "WAC.021"; 12 | r3nrk1/2p2p1p/p1p1b1p1/2NpPq2/3R4/P1N1Q3/1PP2PPP/4R1K1 w - - bm g4; id "WAC.023"; 13 | 3R1rk1/8/5Qpp/2p5/2P1p1q1/P3P3/1P2PK2/8 b - - bm Qh4+; id "WAC.025"; 14 | 7k/pp4np/2p3p1/3pN1q1/3P4/Q7/1r3rPP/2R2RK1 w - - bm Qf8+; id "WAC.027"; 15 | r2q2k1/pp1rbppp/4pn2/2P5/1P3B2/6P1/P3QPBP/1R3RK1 w - - bm c6; id "WAC.029"; 16 | rb3qk1/pQ3ppp/4p3/3P4/8/1P3N2/1P3PPP/3R2K1 w - - bm Qxa8 d6 dxe6 g3; id "WAC.031"; 17 | 8/p1q2pkp/2Pr2p1/8/P3Q3/6P1/5P1P/2R3K1 w - - bm Qe5+ Qf4; id "WAC.033"; 18 | r3r2k/2R3pp/pp1q1p2/8/3P3R/7P/PP3PP1/3Q2K1 w - - bm Rxh7+; id "WAC.035"; 19 | 2r5/2rk2pp/1pn1pb2/pN1p4/P2P4/1N2B3/nPR1KPPP/3R4 b - - bm Nxd4+; id "WAC.037"; 20 | r1br2k1/pp2bppp/2nppn2/8/2P1PB2/2N2P2/PqN1B1PP/R2Q1R1K w - - bm Na4; id "WAC.039"; 21 | 1k6/5RP1/1P6/1K6/6r1/8/8/8 w - - bm Ka5 Kc5 b7; id "WAC.041"; 22 | r2q3k/p2P3p/1p3p2/3QP1r1/8/B7/P5PP/2R3K1 w - - bm Be7 Qxa8; id "WAC.043"; 23 | 7k/2p1b1pp/8/1p2P3/1P3r2/2P3Q1/1P5P/R4qBK b - - bm Qxa1; id "WAC.045"; 24 | r1b2rk1/pp2bppp/2n1pn2/q5B1/2BP4/2N2N2/PP2QPPP/2R2RK1 b - - bm Nxd4; id "WAC.047"; 25 | 2b3k1/4rrpp/p2p4/2pP2RQ/1pP1Pp1N/1P3P1P/1q6/6RK w - - bm Qxh7+; id "WAC.049"; 26 | r1bq1r2/pp4k1/4p2p/3pPp1Q/3N1R1P/2PB4/6P1/6K1 w - - bm Rg4+; id "WAC.051"; 27 | 6k1/6p1/p7/3Pn3/5p2/4rBqP/P4RP1/5QK1 b - - bm Re1; id "WAC.053"; 28 | r3r1k1/pp1q1pp1/4b1p1/3p2B1/3Q1R2/8/PPP3PP/4R1K1 w - - bm Qxg7+; id "WAC.055"; 29 | r3q1kr/ppp5/3p2pQ/8/3PP1b1/5R2/PPP3P1/5RK1 w - - bm Rf8+; id "WAC.057"; 30 | r1b2rk1/2p1qnbp/p1pp2p1/5p2/2PQP3/1PN2N1P/PB3PP1/3R1RK1 w - - bm Nd5; id "WAC.059"; 31 | 3qrbk1/ppp1r2n/3pP2p/3P4/2P4P/1P3Q2/PB6/R4R1K w - - bm Qf7+; id "WAC.061"; 32 | r1brnbk1/ppq2pp1/4p2p/4N3/3P4/P1PB1Q2/3B1PPP/R3R1K1 w - - bm Nxf7; id "WAC.063"; 33 | 1r1r1qk1/p2n1p1p/bp1Pn1pQ/2pNp3/2P2P1N/1P5B/P6P/3R1RK1 w - - bm Ne7+; id "WAC.065"; 34 | 3r2k1/p2q4/1p4p1/3rRp1p/5P1P/6PK/P3R3/3Q4 w - - bm Rxd5; id "WAC.067"; 35 | 2k5/pppr4/4R3/4Q3/2pp2q1/8/PPP2PPP/6K1 w - - bm f3 h3; id "WAC.069"; 36 | 2kr3r/pp1q1ppp/5n2/1Nb5/2Pp1B2/7Q/P4PPP/1R3RK1 w - - bm Nxa7+; id "WAC.071"; 37 | r1q3rk/1ppbb1p1/4Np1p/p3pP2/P3P3/2N4R/1PP1Q1PP/3R2K1 w - - bm Qd2; id "WAC.073"; 38 | r3r1k1/pppq1ppp/8/8/1Q4n1/7P/PPP2PP1/RNB1R1K1 b - - bm Qd6; id "WAC.075"; 39 | 3r2k1/ppp2ppp/6q1/b4n2/3nQB2/2p5/P4PPP/RN3RK1 b - - bm Ng3; id "WAC.077"; 40 | r3k2r/pbp2pp1/3b1n2/1p6/3P3p/1B2N1Pq/PP1PQP1P/R1B2RK1 b kq - bm Qxh2+; id "WAC.079"; 41 | r4rk1/1bR1bppp/4pn2/1p2N3/1P6/P3P3/4BPPP/3R2K1 b - - bm Bd6; id "WAC.081"; 42 | 3rr1k1/ppqbRppp/2p5/8/3Q1n2/2P3N1/PPB2PPP/3R2K1 w - - bm Qxd7; id "WAC.083"; 43 | kr2R3/p4r2/2pq4/2N2p1p/3P2p1/Q5P1/5P1P/5BK1 w - - bm Na6; id "WAC.085"; 44 | 8/p3k1p1/4r3/2ppNpp1/PP1P4/2P3KP/5P2/8 b - - bm Rxe5; id "WAC.087"; 45 | 1r3b1k/p4rpp/4pp2/3q4/2ppbPPQ/6RK/PP5P/2B1NR2 b - - bm g5; id "WAC.089"; 46 | 2qr2k1/4b1p1/2p2p1p/1pP1p3/p2nP3/PbQNB1PP/1P3PK1/4RB2 b - - bm Be6; id "WAC.091"; 47 | r1b1k1nr/pp3pQp/4pq2/3pn3/8/P1P5/2P2PPP/R1B1KBNR w KQkq - bm Bh6; id "WAC.093"; 48 | 2r5/1r6/4pNpk/3pP1qp/8/2P1QP2/5PK1/R7 w - - bm Ng4+; id "WAC.095"; 49 | 6k1/5p2/p5np/4B3/3P4/1PP1q3/P3r1QP/6RK w - - bm Qa8+; id "WAC.097"; 50 | r1bq1r1k/1pp1Np1p/p2p2pQ/4R3/n7/8/PPPP1PPP/R1B3K1 w - - bm Rh5; id "WAC.099"; 51 | 5rk1/p5pp/8/8/2Pbp3/1P4P1/7P/4RN1K b - - bm Bc3; id "WAC.101"; 52 | 6k1/2pb1r1p/3p1PpQ/p1nPp3/1q2P3/2N2P2/PrB5/2K3RR w - - bm Qxg6+; id "WAC.103"; 53 | r2r2k1/pb3ppp/1p1bp3/7q/3n2nP/PP1B2P1/1B1N1P2/RQ2NRK1 b - - bm Bxg3 Qxh4; id "WAC.105"; 54 | 5n2/pRrk2p1/P4p1p/4p3/3N4/5P2/6PP/6K1 w - - bm Nb5; id "WAC.107"; 55 | rn2k1nr/pbp2ppp/3q4/1p2N3/2p5/QP6/PB1PPPPP/R3KB1R b KQkq - bm c3; id "WAC.109"; 56 | 6k1/p5p1/5p2/2P2Q2/3pN2p/3PbK1P/7P/6q1 b - - bm Qf1+; id "WAC.111"; 57 | rnbqkb1r/1p3ppp/5N2/1p2p1B1/2P5/8/PP2PPPP/R2QKB1R b KQkq - bm Qxf6; id "WAC.113"; 58 | 4N2k/5rpp/1Q6/p3q3/8/P5P1/1P3P1P/5K2 w - - bm Nd6; id "WAC.115"; 59 | 3r1rk1/q4ppp/p1Rnp3/8/1p6/1N3P2/PP3QPP/3R2K1 b - - bm Ne4; id "WAC.117"; 60 | r2qr1k1/p1p2ppp/2p5/2b5/4nPQ1/3B4/PPP3PP/R1B2R1K b - - bm Qxd3; id "WAC.119"; 61 | 6k1/5p1p/2bP2pb/4p3/2P5/1p1pNPPP/1P1Q1BK1/1q6 b - - bm Bxf3+; id "WAC.121"; 62 | 6k1/1b2rp2/1p4p1/3P4/PQ4P1/2N2q2/5P2/3R2K1 b - - bm Bxd5 Rc7 Re6; id "WAC.123"; 63 | r1bqr1k1/pp3ppp/1bp5/3n4/3B4/2N2P1P/PPP1B1P1/R2Q1RK1 b - - bm Bxd4+; id "WAC.125"; 64 | 2k4r/1pr1n3/p1p1q2p/5pp1/3P1P2/P1P1P3/1R2Q1PP/1RB3K1 w - - bm Rxb7; id "WAC.127"; 65 | 3r1r1k/1b2b1p1/1p5p/2p1Pp2/q1B2P2/4P2P/1BR1Q2K/6R1 b - - bm Bf3; id "WAC.129"; 66 | 2rq1bk1/p4p1p/1p4p1/3b4/3B1Q2/8/P4PpP/3RR1K1 w - - bm Re8; id "WAC.131"; 67 | r1b1k2r/1pp1q2p/p1n3p1/3QPp2/8/1BP3B1/P5PP/3R1RK1 w kq - bm Bh4; id "WAC.133"; 68 | 3r1r1k/N2qn1pp/1p2np2/2p5/2Q1P2N/3P4/PP4PP/3R1RK1 b - - bm Nd4; id "WAC.135"; 69 | 3b1rk1/1bq3pp/5pn1/1p2rN2/2p1p3/2P1B2Q/1PB2PPP/R2R2K1 w - - bm Rd7; id "WAC.137"; 70 | rnb3kr/ppp2ppp/1b6/3q4/3pN3/Q4N2/PPP2KPP/R1B1R3 w - - bm Nf6+; id "WAC.139"; 71 | 4r1k1/p1qr1p2/2pb1Bp1/1p5p/3P1n1R/1B3P2/PP3PK1/2Q4R w - - bm Qxf4; id "WAC.141"; 72 | 5b2/pp2r1pk/2pp1pRp/4rP1N/2P1P3/1P4QP/P3q1P1/5R1K w - - bm Rxh6+; id "WAC.143"; 73 | r1bq4/1p4kp/3p1n2/p4pB1/2pQ4/8/1P4PP/4RRK1 w - - bm Re8; id "WAC.145"; 74 | r2r2k1/ppqbppbp/2n2np1/2pp4/6P1/1P1PPNNP/PBP2PB1/R2QK2R b KQ - bm Nxg4; id "WAC.147"; 75 | 6k1/6p1/2p4p/4Pp2/4b1qP/2Br4/1P2RQPK/8 b - - bm Bxg2; id "WAC.149"; 76 | 8/3b2kp/4p1p1/pr1n4/N1N4P/1P4P1/1K3P2/3R4 w - - bm Nc3; id "WAC.151"; 77 | 2r3k1/q4ppp/p3p3/pnNp4/2rP4/2P2P2/4R1PP/2R1Q1K1 b - - bm Nxd4; id "WAC.153"; 78 | 5bk1/1rQ4p/5pp1/2pP4/3n1PP1/7P/1q3BB1/4R1K1 w - - bm d6; id "WAC.155"; 79 | 5rk1/p4ppp/2p1b3/3Nq3/4P1n1/1p1B2QP/1PPr2P1/1K2R2R w - - bm Ne7+; id "WAC.157"; 80 | r1b2r2/5P1p/ppn3pk/2p1p1Nq/1bP1PQ2/3P4/PB4BP/1R3RK1 w - - bm Ne6+; id "WAC.159"; 81 | 3r3k/3r1P1p/pp1Nn3/2pp4/7Q/6R1/Pq4PP/5RK1 w - - bm Qxd8+; id "WAC.161"; 82 | 5rk1/2p4p/2p4r/3P4/4p1b1/1Q2NqPp/PP3P1K/R4R2 b - - bm Qg2+; id "WAC.163"; 83 | 1r5k/p1p3pp/8/8/4p3/P1P1R3/1P1Q1qr1/2KR4 w - - bm Re2; id "WAC.165"; 84 | 7Q/ppp2q2/3p2k1/P2Ppr1N/1PP5/7R/5rP1/6K1 b - - bm Rxg2+; id "WAC.167"; 85 | 5rk1/1pp3bp/3p2p1/2PPp3/1P2P3/2Q1B3/4q1PP/R5K1 b - - bm Bh6; id "WAC.169"; 86 | 2rq4/1b2b1kp/p3p1p1/1p1nNp2/7P/1B2B1Q1/PP3PP1/3R2K1 w - - bm Bh6+; id "WAC.171"; 87 | 2r1b3/1pp1qrk1/p1n1P1p1/7R/2B1p3/4Q1P1/PP3PP1/3R2K1 w - - bm Qh6+; id "WAC.173"; 88 | r5k1/pppb3p/2np1n2/8/3PqNpP/3Q2P1/PPP5/R4RK1 w - - bm Nh5; id "WAC.175"; 89 | r1b3r1/4qk2/1nn1p1p1/3pPp1P/p4P2/1p3BQN/PKPBN3/3R3R b - - bm Qa3+; id "WAC.177"; 90 | r1b2r1k/pp4pp/3p4/3B4/8/1QN3Pn/PP3q1P/R3R2K b - - bm Qg1+; id "WAC.179"; 91 | r3k2r/2p2p2/p2p1n2/1p2p3/4P2p/1PPPPp1q/1P5P/R1N2QRK b kq - bm Ng4; id "WAC.181"; 92 | 1r2k1r1/5p2/b3p3/1p2b1B1/3p3P/3B4/PP2KP2/2R3R1 w - - bm Bf6; id "WAC.183"; 93 | 1r1rb1k1/2p3pp/p2q1p2/3PpP1Q/Pp1bP2N/1B5R/1P4PP/2B4K w - - bm Qxh7+; id "WAC.185"; 94 | 6k1/5p2/p3p3/1p3qp1/2p1Qn2/2P1R3/PP1r1PPP/4R1K1 b - - bm Nh3+; id "WAC.187"; 95 | 3r1k2/1ppPR1n1/p2p1rP1/3P3p/4Rp1N/5K2/P1P2P2/8 w - - bm Re8+; id "WAC.189"; 96 | 2r1Rn1k/1p1q2pp/p7/5p2/3P4/1B4P1/P1P1QP1P/6K1 w - - bm Qc4; id "WAC.191"; 97 | 5bk1/p4ppp/Qp6/4B3/1P6/Pq2P1P1/2rr1P1P/R4RK1 b - - bm Qxe3; id "WAC.193"; 98 | 3r1rk1/1p3p2/p3pnnp/2p3p1/2P2q2/1P5P/PB2QPPN/3RR1K1 w - - bm g3; id "WAC.195"; 99 | 7k/1p4p1/7p/3P1n2/4Q3/2P2P2/PP3qRP/7K b - - bm Qf1+; id "WAC.197"; 100 | r1br2k1/pp2nppp/2n5/1B1q4/Q7/4BN2/PP3PPP/2R2RK1 w - - bm Bxc6 Rcd1 Rfd1; id "WAC.199"; 101 | 2b2r1k/4q2p/3p2pQ/2pBp3/8/6P1/1PP2P1P/R5K1 w - - bm Ra7; id "WAC.201"; 102 | r4rk1/5ppp/p3q1n1/2p2NQ1/4n3/P3P3/1B3PPP/1R3RK1 w - - bm Qh6; id "WAC.203"; 103 | r3rnk1/1pq2bb1/p4p2/3p1Pp1/3B2P1/1NP4R/P1PQB3/2K4R w - - bm Qxg5; id "WAC.205"; 104 | r1bq2kr/p1pp1ppp/1pn1p3/4P3/2Pb2Q1/BR6/P4PPP/3K1BNR w - - bm Qxg7+; id "WAC.207"; 105 | 4kb1r/2q2p2/r2p4/pppBn1B1/P6P/6Q1/1PP5/2KRR3 w k - bm Rxe5+; id "WAC.209"; 106 | r1bqrk2/pp1n1n1p/3p1p2/P1pP1P1Q/2PpP1NP/6R1/2PB4/4RBK1 w - - bm Qxf7+; id "WAC.211"; 107 | 3r1r1k/1b4pp/ppn1p3/4Pp1R/Pn5P/3P4/4QP2/1qB1NKR1 w - - bm Rxh7+; id "WAC.213"; 108 | 3r2k1/pb1q1pp1/1p2pb1p/8/3N4/P2QB3/1P3PPP/1Br1R1K1 w - - bm Qh7+; id "WAC.215"; 109 | r3kb1r/1pp3p1/p3bp1p/5q2/3QN3/1P6/PBP3P1/3RR1K1 w kq - bm Qd7+; id "WAC.217"; 110 | 7k/p4q1p/1pb5/2p5/4B2Q/2P1B3/P6P/7K b - - bm Qf1+; id "WAC.219"; 111 | r3k3/P5bp/2N1bp2/4p3/2p5/6NP/1PP2PP1/3R2K1 w q - bm Rd8+; id "WAC.221"; 112 | r1bqk2r/pp3ppp/5n2/8/1b1npB2/2N5/PP1Q2PP/1K2RBNR w kq - bm Nxe4; id "WAC.223"; 113 | 4R3/4q1kp/6p1/1Q3b2/1P1b1P2/6KP/8/8 b - - bm Qh4+; id "WAC.225"; 114 | 2k1rb1r/ppp3pp/2np1q2/5b2/2B2P2/2P1BQ2/PP1N1P1P/2KR3R b - - bm d5; id "WAC.227"; 115 | 8/8/8/1p5r/p1p1k1pN/P2pBpP1/1P1K1P2/8 b - - bm Rxh4 b4; id "WAC.229"; 116 | r4rk1/1b1nqp1p/p5p1/1p2PQ2/2p5/5N2/PP3PPP/R1BR2K1 w - - bm Bg5; id "WAC.231"; 117 | 5rk1/p1p2r1p/2pp2p1/4p3/PPPnP3/3Pq1P1/1Q1R1R1P/4NK2 b - - bm Nb3; id "WAC.233"; 118 | 5r2/1p1RRrk1/4Qq1p/1PP3p1/8/4B3/1b3P1P/6K1 w - - bm Qe4 Qxf7+ Rxf7+; id "WAC.235"; 119 | r5k1/pQp2qpp/8/4pbN1/3P4/6P1/PPr4P/1K1R3R b - - bm Rc1+; id "WAC.237"; 120 | 8/6k1/5pp1/Q6p/5P2/6PK/P4q1P/8 b - - bm Qf1+; id "WAC.239"; 121 | 2rq1rk1/pp3ppp/2n2b2/4NR2/3P4/PB5Q/1P4PP/3R2K1 w - - bm Qxh7+; id "WAC.241"; 122 | 1r3r1k/3p4/1p1Nn1R1/4Pp1q/pP3P1p/P7/5Q1P/6RK w - - bm Qe2; id "WAC.243"; 123 | 4rrn1/ppq3bk/3pPnpp/2p5/2PB4/2NQ1RPB/PP5P/5R1K w - - bm Qxg6+; id "WAC.245"; 124 | 2k1r3/1p2Bq2/p2Qp3/Pb1p1p1P/2pP1P2/2P5/2P2KP1/1R6 w - - bm Rxb5; id "WAC.247"; 125 | r4rk1/pbq2pp1/1ppbpn1p/8/2PP4/1P1Q1N2/PBB2PPP/R3R1K1 w - - bm c5 d5; id "WAC.249"; 126 | k7/p4p2/P1q1b1p1/3p3p/3Q4/7P/5PP1/1R4K1 w - - bm Qe5 Qf4; id "WAC.251"; 127 | k5r1/p4b2/2P5/5p2/3P1P2/4QBrq/P5P1/4R1K1 w - - bm Qe8+; id "WAC.253"; 128 | 3r3r/p4pk1/5Rp1/3q4/1p1P2RQ/5N2/P1P4P/2b4K w - - bm Rfxg6+; id "WAC.255"; 129 | 4r1k1/pq3p1p/2p1r1p1/2Q1p3/3nN1P1/1P6/P1P2P1P/3RR1K1 w - - bm Rxd4; id "WAC.257"; 130 | r1bq1rk1/ppp2ppp/2np4/2bN1PN1/2B1P3/3p4/PPP2nPP/R1BQ1K1R w - - bm Qh5; id "WAC.259"; 131 | r5k1/1bp3pp/p2p4/1p6/5p2/1PBP1nqP/1PP3Q1/R4R1K b - - bm Nd4; id "WAC.261"; 132 | rnbqr2k/pppp1Qpp/8/b2NN3/2B1n3/8/PPPP1PPP/R1B1K2R w KQ - bm Qg8+; id "WAC.263"; 133 | 2r1k2r/2pn1pp1/1p3n1p/p3PP2/4q2B/P1P5/2Q1N1PP/R4RK1 w k - bm exf6; id "WAC.265"; 134 | 2r1kb1r/pp3ppp/2n1b3/1q1N2B1/1P2Q3/8/P4PPP/3RK1NR w Kk - bm Nc7+; id "WAC.267"; 135 | 2kr2nr/pp1n1ppp/2p1p3/q7/1b1P1B2/P1N2Q1P/1PP1BPP1/R3K2R w KQ - bm axb4; id "WAC.269"; 136 | 2kr4/ppp3Pp/4RP1B/2r5/5P2/1P6/P2p4/3K4 w - - bm Rd6; id "WAC.271"; 137 | 2k4B/bpp1qp2/p1b5/7p/1PN1n1p1/2Pr4/P5PP/R3QR1K b - - bm Ng3+ g3; id "WAC.273"; 138 | r1b2rk1/1p1n1ppp/p1p2q2/4p3/P1B1Pn2/1QN2N2/1P3PPP/3R1RK1 b - - bm Nc5 Nxg2 b5; id "WAC.275"; 139 | 1r4r1/p2kb2p/bq2p3/3p1p2/5P2/2BB3Q/PP4PP/3RKR2 b - - bm Rg3 Rxg2; id "WAC.277"; 140 | r7/4b3/2p1r1k1/1p1pPp1q/1P1P1P1p/PR2NRpP/2Q3K1/8 w - - bm Nxf5 Rc3; id "WAC.279"; 141 | 2R5/2R4p/5p1k/6n1/8/1P2QPPq/r7/6K1 w - - bm Rxh7+; id "WAC.281"; 142 | 3q1rk1/4bp1p/1n2P2Q/3p1p2/6r1/Pp2R2N/1B4PP/7K w - - bm Ng5; id "WAC.283"; 143 | 2rr3k/1b2bppP/p2p1n2/R7/3P4/1qB2P2/1P4Q1/1K5R w - - bm Qxg7+; id "WAC.285"; 144 | rn3k1r/pp2bBpp/2p2n2/q5N1/3P4/1P6/P1P3PP/R1BQ1RK1 w - - bm Qg4 Qh5; id "WAC.287"; 145 | 2r3k1/5p1p/p3q1p1/2n3P1/1p1QP2P/1P4N1/PK6/2R5 b - - bm Qe5; id "WAC.289"; 146 | 5r1k/3b2p1/p6p/1pRpR3/1P1P2q1/P4pP1/5QnP/1B4K1 w - - bm h3; id "WAC.291"; 147 | 1nbq1r1k/3rbp1p/p1p1pp1Q/1p6/P1pPN3/5NP1/1P2PPBP/R4RK1 w - - bm Nfg5; id "WAC.293"; 148 | 4r3/p4r1p/R1p2pp1/1p1bk3/4pNPP/2P1K3/2P2P2/3R4 w - - bm Rxd5+; id "WAC.295"; 149 | 3r1rk1/p3qp1p/2bb2p1/2p5/3P4/1P6/PBQN1PPP/2R2RK1 b - - bm Bxg2 Bxh2+; id "WAC.297"; 150 | 1n2rr2/1pk3pp/pNn2p2/2N1p3/8/6P1/PP2PPKP/2RR4 w - - bm Nca4; id "WAC.299"; 151 | -------------------------------------------------------------------------------- /test_suites/wac_75.epd: -------------------------------------------------------------------------------- 1 | 2rr3k/pp3pp1/1nnqbN1p/3pN3/2pP4/2P3Q1/PPB4P/R4RK1 w - - bm Qg6; id "WAC.001"; 2 | 5k2/6pp/p1qN4/1p1p4/3P4/2PKP2Q/PP3r2/3R4 b - - bm Qc4+; id "WAC.005"; 3 | 3q1rk1/p4pp1/2pb3p/3p4/6Pr/1PNQ4/P1PB1PP1/4RRK1 b - - bm Bh2+; id "WAC.009"; 4 | 5rk1/pp4p1/2n1p2p/2Npq3/2p5/6P1/P3P1BP/R4Q1K w - - bm Qxf8+; id "WAC.013"; 5 | 1k5r/pppbn1pp/4q1r1/1P3p2/2NPp3/1QP5/P4PPP/R1B1R1K1 w - - bm Ne5; id "WAC.017"; 6 | 5rk1/1b3p1p/pp3p2/3n1N2/1P6/P1qB1PP1/3Q3P/4R1K1 w - - bm Qh6; id "WAC.021"; 7 | 3R1rk1/8/5Qpp/2p5/2P1p1q1/P3P3/1P2PK2/8 b - - bm Qh4+; id "WAC.025"; 8 | r2q2k1/pp1rbppp/4pn2/2P5/1P3B2/6P1/P3QPBP/1R3RK1 w - - bm c6; id "WAC.029"; 9 | 8/p1q2pkp/2Pr2p1/8/P3Q3/6P1/5P1P/2R3K1 w - - bm Qe5+ Qf4; id "WAC.033"; 10 | 2r5/2rk2pp/1pn1pb2/pN1p4/P2P4/1N2B3/nPR1KPPP/3R4 b - - bm Nxd4+; id "WAC.037"; 11 | 1k6/5RP1/1P6/1K6/6r1/8/8/8 w - - bm Ka5 Kc5 b7; id "WAC.041"; 12 | 7k/2p1b1pp/8/1p2P3/1P3r2/2P3Q1/1P5P/R4qBK b - - bm Qxa1; id "WAC.045"; 13 | 2b3k1/4rrpp/p2p4/2pP2RQ/1pP1Pp1N/1P3P1P/1q6/6RK w - - bm Qxh7+; id "WAC.049"; 14 | 6k1/6p1/p7/3Pn3/5p2/4rBqP/P4RP1/5QK1 b - - bm Re1; id "WAC.053"; 15 | r3q1kr/ppp5/3p2pQ/8/3PP1b1/5R2/PPP3P1/5RK1 w - - bm Rf8+; id "WAC.057"; 16 | 3qrbk1/ppp1r2n/3pP2p/3P4/2P4P/1P3Q2/PB6/R4R1K w - - bm Qf7+; id "WAC.061"; 17 | 1r1r1qk1/p2n1p1p/bp1Pn1pQ/2pNp3/2P2P1N/1P5B/P6P/3R1RK1 w - - bm Ne7+; id "WAC.065"; 18 | 2k5/pppr4/4R3/4Q3/2pp2q1/8/PPP2PPP/6K1 w - - bm f3 h3; id "WAC.069"; 19 | r1q3rk/1ppbb1p1/4Np1p/p3pP2/P3P3/2N4R/1PP1Q1PP/3R2K1 w - - bm Qd2; id "WAC.073"; 20 | 3r2k1/ppp2ppp/6q1/b4n2/3nQB2/2p5/P4PPP/RN3RK1 b - - bm Ng3; id "WAC.077"; 21 | r4rk1/1bR1bppp/4pn2/1p2N3/1P6/P3P3/4BPPP/3R2K1 b - - bm Bd6; id "WAC.081"; 22 | kr2R3/p4r2/2pq4/2N2p1p/3P2p1/Q5P1/5P1P/5BK1 w - - bm Na6; id "WAC.085"; 23 | 1r3b1k/p4rpp/4pp2/3q4/2ppbPPQ/6RK/PP5P/2B1NR2 b - - bm g5; id "WAC.089"; 24 | r1b1k1nr/pp3pQp/4pq2/3pn3/8/P1P5/2P2PPP/R1B1KBNR w KQkq - bm Bh6; id "WAC.093"; 25 | 6k1/5p2/p5np/4B3/3P4/1PP1q3/P3r1QP/6RK w - - bm Qa8+; id "WAC.097"; 26 | 5rk1/p5pp/8/8/2Pbp3/1P4P1/7P/4RN1K b - - bm Bc3; id "WAC.101"; 27 | r2r2k1/pb3ppp/1p1bp3/7q/3n2nP/PP1B2P1/1B1N1P2/RQ2NRK1 b - - bm Bxg3 Qxh4; id "WAC.105"; 28 | rn2k1nr/pbp2ppp/3q4/1p2N3/2p5/QP6/PB1PPPPP/R3KB1R b KQkq - bm c3; id "WAC.109"; 29 | rnbqkb1r/1p3ppp/5N2/1p2p1B1/2P5/8/PP2PPPP/R2QKB1R b KQkq - bm Qxf6; id "WAC.113"; 30 | 3r1rk1/q4ppp/p1Rnp3/8/1p6/1N3P2/PP3QPP/3R2K1 b - - bm Ne4; id "WAC.117"; 31 | 6k1/5p1p/2bP2pb/4p3/2P5/1p1pNPPP/1P1Q1BK1/1q6 b - - bm Bxf3+; id "WAC.121"; 32 | r1bqr1k1/pp3ppp/1bp5/3n4/3B4/2N2P1P/PPP1B1P1/R2Q1RK1 b - - bm Bxd4+; id "WAC.125"; 33 | 3r1r1k/1b2b1p1/1p5p/2p1Pp2/q1B2P2/4P2P/1BR1Q2K/6R1 b - - bm Bf3; id "WAC.129"; 34 | r1b1k2r/1pp1q2p/p1n3p1/3QPp2/8/1BP3B1/P5PP/3R1RK1 w kq - bm Bh4; id "WAC.133"; 35 | 3b1rk1/1bq3pp/5pn1/1p2rN2/2p1p3/2P1B2Q/1PB2PPP/R2R2K1 w - - bm Rd7; id "WAC.137"; 36 | 4r1k1/p1qr1p2/2pb1Bp1/1p5p/3P1n1R/1B3P2/PP3PK1/2Q4R w - - bm Qxf4; id "WAC.141"; 37 | r1bq4/1p4kp/3p1n2/p4pB1/2pQ4/8/1P4PP/4RRK1 w - - bm Re8; id "WAC.145"; 38 | 6k1/6p1/2p4p/4Pp2/4b1qP/2Br4/1P2RQPK/8 b - - bm Bxg2; id "WAC.149"; 39 | 2r3k1/q4ppp/p3p3/pnNp4/2rP4/2P2P2/4R1PP/2R1Q1K1 b - - bm Nxd4; id "WAC.153"; 40 | 5rk1/p4ppp/2p1b3/3Nq3/4P1n1/1p1B2QP/1PPr2P1/1K2R2R w - - bm Ne7+; id "WAC.157"; 41 | 3r3k/3r1P1p/pp1Nn3/2pp4/7Q/6R1/Pq4PP/5RK1 w - - bm Qxd8+; id "WAC.161"; 42 | 1r5k/p1p3pp/8/8/4p3/P1P1R3/1P1Q1qr1/2KR4 w - - bm Re2; id "WAC.165"; 43 | 5rk1/1pp3bp/3p2p1/2PPp3/1P2P3/2Q1B3/4q1PP/R5K1 b - - bm Bh6; id "WAC.169"; 44 | 2r1b3/1pp1qrk1/p1n1P1p1/7R/2B1p3/4Q1P1/PP3PP1/3R2K1 w - - bm Qh6+; id "WAC.173"; 45 | r1b3r1/4qk2/1nn1p1p1/3pPp1P/p4P2/1p3BQN/PKPBN3/3R3R b - - bm Qa3+; id "WAC.177"; 46 | r3k2r/2p2p2/p2p1n2/1p2p3/4P2p/1PPPPp1q/1P5P/R1N2QRK b kq - bm Ng4; id "WAC.181"; 47 | 1r1rb1k1/2p3pp/p2q1p2/3PpP1Q/Pp1bP2N/1B5R/1P4PP/2B4K w - - bm Qxh7+; id "WAC.185"; 48 | 3r1k2/1ppPR1n1/p2p1rP1/3P3p/4Rp1N/5K2/P1P2P2/8 w - - bm Re8+; id "WAC.189"; 49 | 5bk1/p4ppp/Qp6/4B3/1P6/Pq2P1P1/2rr1P1P/R4RK1 b - - bm Qxe3; id "WAC.193"; 50 | 7k/1p4p1/7p/3P1n2/4Q3/2P2P2/PP3qRP/7K b - - bm Qf1+; id "WAC.197"; 51 | 2b2r1k/4q2p/3p2pQ/2pBp3/8/6P1/1PP2P1P/R5K1 w - - bm Ra7; id "WAC.201"; 52 | r3rnk1/1pq2bb1/p4p2/3p1Pp1/3B2P1/1NP4R/P1PQB3/2K4R w - - bm Qxg5; id "WAC.205"; 53 | 4kb1r/2q2p2/r2p4/pppBn1B1/P6P/6Q1/1PP5/2KRR3 w k - bm Rxe5+; id "WAC.209"; 54 | 3r1r1k/1b4pp/ppn1p3/4Pp1R/Pn5P/3P4/4QP2/1qB1NKR1 w - - bm Rxh7+; id "WAC.213"; 55 | r3kb1r/1pp3p1/p3bp1p/5q2/3QN3/1P6/PBP3P1/3RR1K1 w kq - bm Qd7+; id "WAC.217"; 56 | r3k3/P5bp/2N1bp2/4p3/2p5/6NP/1PP2PP1/3R2K1 w q - bm Rd8+; id "WAC.221"; 57 | 4R3/4q1kp/6p1/1Q3b2/1P1b1P2/6KP/8/8 b - - bm Qh4+; id "WAC.225"; 58 | 8/8/8/1p5r/p1p1k1pN/P2pBpP1/1P1K1P2/8 b - - bm Rxh4 b4; id "WAC.229"; 59 | 5rk1/p1p2r1p/2pp2p1/4p3/PPPnP3/3Pq1P1/1Q1R1R1P/4NK2 b - - bm Nb3; id "WAC.233"; 60 | r5k1/pQp2qpp/8/4pbN1/3P4/6P1/PPr4P/1K1R3R b - - bm Rc1+; id "WAC.237"; 61 | 2rq1rk1/pp3ppp/2n2b2/4NR2/3P4/PB5Q/1P4PP/3R2K1 w - - bm Qxh7+; id "WAC.241"; 62 | 4rrn1/ppq3bk/3pPnpp/2p5/2PB4/2NQ1RPB/PP5P/5R1K w - - bm Qxg6+; id "WAC.245"; 63 | r4rk1/pbq2pp1/1ppbpn1p/8/2PP4/1P1Q1N2/PBB2PPP/R3R1K1 w - - bm c5 d5; id "WAC.249"; 64 | k5r1/p4b2/2P5/5p2/3P1P2/4QBrq/P5P1/4R1K1 w - - bm Qe8+; id "WAC.253"; 65 | 4r1k1/pq3p1p/2p1r1p1/2Q1p3/3nN1P1/1P6/P1P2P1P/3RR1K1 w - - bm Rxd4; id "WAC.257"; 66 | r5k1/1bp3pp/p2p4/1p6/5p2/1PBP1nqP/1PP3Q1/R4R1K b - - bm Nd4; id "WAC.261"; 67 | 2r1k2r/2pn1pp1/1p3n1p/p3PP2/4q2B/P1P5/2Q1N1PP/R4RK1 w k - bm exf6; id "WAC.265"; 68 | 2kr2nr/pp1n1ppp/2p1p3/q7/1b1P1B2/P1N2Q1P/1PP1BPP1/R3K2R w KQ - bm axb4; id "WAC.269"; 69 | 2k4B/bpp1qp2/p1b5/7p/1PN1n1p1/2Pr4/P5PP/R3QR1K b - - bm Ng3+ g3; id "WAC.273"; 70 | 1r4r1/p2kb2p/bq2p3/3p1p2/5P2/2BB3Q/PP4PP/3RKR2 b - - bm Rg3 Rxg2; id "WAC.277"; 71 | 2R5/2R4p/5p1k/6n1/8/1P2QPPq/r7/6K1 w - - bm Rxh7+; id "WAC.281"; 72 | 2rr3k/1b2bppP/p2p1n2/R7/3P4/1qB2P2/1P4Q1/1K5R w - - bm Qxg7+; id "WAC.285"; 73 | 2r3k1/5p1p/p3q1p1/2n3P1/1p1QP2P/1P4N1/PK6/2R5 b - - bm Qe5; id "WAC.289"; 74 | 1nbq1r1k/3rbp1p/p1p1pp1Q/1p6/P1pPN3/5NP1/1P2PPBP/R4RK1 w - - bm Nfg5; id "WAC.293"; 75 | 1n2rr2/1pk3pp/pNn2p2/2N1p3/8/6P1/PP2PPKP/2RR4 w - - bm Nca4; id "WAC.299"; 76 | -------------------------------------------------------------------------------- /uci.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | // This module implements communication over standard I/O using the Universal Chess 7 | // Interface (UCI) protocol. This allows the engine to communicate with any other 8 | // chess software that also implements UCI. 9 | 10 | // UCI Protocol specification: http://wbec-ridderkerk.nl/html/UCIProtocol.html 11 | 12 | package main 13 | 14 | import ( 15 | "bufio" 16 | "fmt" 17 | "log" 18 | "os" 19 | "path/filepath" 20 | "runtime" 21 | "strconv" 22 | "strings" 23 | "sync" 24 | "time" 25 | ) 26 | 27 | // Info 28 | type Info struct { 29 | score, depth, nodeCount int 30 | t time.Duration // time elapsed 31 | stk Stack 32 | } 33 | 34 | // TODO: add proper error handling in UCI adapter. 35 | type UCIAdapter struct { 36 | brd *Board 37 | search *Search 38 | wg *sync.WaitGroup 39 | result chan SearchResult 40 | 41 | moveCounter int 42 | 43 | optionPonder bool 44 | optionDebug bool 45 | } 46 | 47 | func NewUCIAdapter() *UCIAdapter { 48 | return &UCIAdapter{ 49 | wg: new(sync.WaitGroup), 50 | result: make(chan SearchResult), 51 | } 52 | } 53 | 54 | func (uci *UCIAdapter) Send(s string) { // log the UCI command s and print to standard I/O. 55 | log.Printf("engine: " + s) 56 | fmt.Print(s) 57 | } 58 | 59 | func (uci *UCIAdapter) BestMove(result SearchResult) { 60 | uci.Send(fmt.Sprintf("bestmove %s ponder %s\n", result.bestMove.ToUCI(), 61 | result.ponderMove.ToUCI())) 62 | } 63 | 64 | // Printed to standard output at end of each non-trivial iterative deepening pass. 65 | // Score given in centipawns. Time given in milliseconds. PV given as list of moves. 66 | // Example: info score cp 13 depth 1 nodes 13 time 15 pv f1b5 h1h2 67 | func (uci *UCIAdapter) Info(info Info) { 68 | nps := int64(float64(info.nodeCount) / info.t.Seconds()) 69 | uci.Send(fmt.Sprintf("info score cp %d depth %d nodes %d nps %d time %d pv %s\n", info.score, 70 | info.depth, info.nodeCount, nps, int(info.t/time.Millisecond), info.stk[0].pv.ToUCI())) 71 | } 72 | 73 | func (uci *UCIAdapter) InfoString(s string) { 74 | uci.Send("info string " + s) 75 | } 76 | 77 | func (uci *UCIAdapter) Read(reader *bufio.Reader) { 78 | var input string 79 | var uciFields []string 80 | 81 | f, err := os.OpenFile("./log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) 82 | if err != nil { 83 | fmt.Printf("info string error opening file: %v\n", err) 84 | } else { 85 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 86 | if err != nil { 87 | // log.Fatal(err) 88 | } 89 | fmt.Printf("info string log file created: %s\n", dir) 90 | } 91 | 92 | defer f.Close() 93 | log.SetOutput(f) 94 | 95 | ponder := false 96 | 97 | for { 98 | input, _ = reader.ReadString('\n') 99 | log.Println("gui: " + input) 100 | uciFields = strings.Fields(input) 101 | 102 | if len(uciFields) > 0 { 103 | switch uciFields[0] { 104 | case "": 105 | continue 106 | // uci 107 | // tell engine to use the uci (universal chess interface), 108 | // this will be send once as a first command after program boot 109 | // to tell the engine to switch to uci mode. 110 | // After receiving the uci command the engine must identify itself with the "id" command 111 | // and sent the "option" commands to tell the GUI which engine settings the engine supports if any. 112 | // After that the engine should sent "uciok" to acknowledge the uci mode. 113 | // If no uciok is sent within a certain time period, the engine task will be killed by the GUI. 114 | case "uci": 115 | uci.identify() 116 | // * debug [ on | off ] 117 | // switch the debug mode of the engine on and off. 118 | // In debug mode the engine should sent additional infos to the GUI, e.g. with the "info string" command, 119 | // to help debugging, e.g. the commands that the engine has received etc. 120 | // This mode should be switched off by default and this command can be sent 121 | // any time, also when the engine is thinking. 122 | case "debug": 123 | if len(uciFields) > 1 { 124 | uci.optionDebug = uci.debug(uciFields[1:]) 125 | } 126 | uci.Send("readyok\n") 127 | // * isready 128 | // this is used to synchronize the engine with the GUI. When the GUI has sent a command or 129 | // multiple commands that can take some time to complete, 130 | // this command can be used to wait for the engine to be ready again or 131 | // to ping the engine to find out if it is still alive. 132 | // E.g. this should be sent after setting the path to the tablebases as this can take some time. 133 | // This command is also required once before the engine is asked to do any search 134 | // to wait for the engine to finish initializing. 135 | // This command must always be answered with "readyok" and can be sent also when the engine is calculating 136 | // in which case the engine should also immediately answer with "readyok" without stopping the search. 137 | case "isready": 138 | uci.wg.Wait() 139 | uci.Send("readyok\n") 140 | // * setoption name [value ] 141 | // this is sent to the engine when the user wants to change the internal parameters 142 | // of the engine. For the "button" type no value is needed. 143 | // One string will be sent for each parameter and this will only be sent when the engine is waiting. 144 | // The name of the option in should not be case sensitive and can inludes spaces like also the value. 145 | // The substrings "value" and "name" should be avoided in and to allow unambiguous parsing, 146 | // for example do not use = "draw value". 147 | // Here are some strings for the example below: 148 | // "setoption name Nullmove value true\n" 149 | // "setoption name Selectivity value 3\n" 150 | // "setoption name Style value Risky\n" 151 | // "setoption name Clear Hash\n" 152 | // "setoption name NalimovPath value c:\chess\tb\4;c:\chess\tb\5\n" 153 | case "setoption": // setoption name option_name 154 | if len(uciFields) > 2 && uciFields[1] == "name" { 155 | uci.setOption(uciFields[2:]) 156 | } 157 | uci.Send("readyok\n") 158 | // * register 159 | // this is the command to try to register an engine or to tell the engine that registration 160 | // will be done later. This command should always be sent if the engine has send "registration error" 161 | // at program startup. 162 | // The following tokens are allowed: 163 | // * later 164 | // the user doesn't want to register the engine now. 165 | // * name 166 | // the engine should be registered with the name 167 | // * code 168 | // the engine should be registered with the code 169 | // Example: 170 | // "register later" 171 | // "register name Stefan MK code 4359874324" 172 | // 173 | case "register": 174 | if len(uciFields) > 1 { 175 | uci.register(uciFields[1:]) 176 | } 177 | uci.Send("readyok\n") 178 | // * ucinewgame 179 | // this is sent to the engine when the next search (started with "position" and "go") will be from 180 | // a different game. This can be a new game the engine should play or a new game it should analyse but 181 | // also the next position from a testsuite with positions only. 182 | // If the GUI hasn't sent a "ucinewgame" before the first "position" command, the engine shouldn't 183 | // expect any further ucinewgame commands as the GUI is probably not supporting the ucinewgame command. 184 | // So the engine should not rely on this command even though all new GUIs should support it. 185 | // As the engine's reaction to "ucinewgame" can take some time the GUI should always send "isready" 186 | // after "ucinewgame" to wait for the engine to finish its operation. 187 | case "ucinewgame": 188 | resetMainTt() 189 | uci.brd = StartPos() 190 | uci.Send("readyok\n") 191 | // * position [fen | startpos ] moves .... 192 | // set up the position described in fenstring on the internal board and 193 | // play the moves on the internal chess board. 194 | // if the game was played from the start position the string "startpos" will be sent 195 | // Note: no "new" command is needed. However, if this position is from a different game than 196 | // the last position sent to the engine, the GUI should have sent a "ucinewgame" inbetween. 197 | case "position": 198 | uci.wg.Wait() 199 | uci.position(uciFields[1:]) 200 | uci.Send("readyok\n") 201 | // * go 202 | // start calculating on the current position set up with the "position" command. 203 | // There are a number of commands that can follow this command, all will be sent in the same string. 204 | // If one command is not send its value should be interpreted as it would not influence the search. 205 | case "go": 206 | if uci.brd != nil { 207 | ponder = uci.start(uciFields[1:]) // parse any parameters given by GUI and begin searching. 208 | if !uci.optionPonder || !ponder { 209 | uci.moveCounter++ 210 | } 211 | } else { 212 | uci.InfoString("You must set the current position via the position command before searching.\n") 213 | } 214 | // * stop 215 | // stop calculating as soon as possible, 216 | // don't forget the "bestmove" and possibly the "ponder" token when finishing the search 217 | case "stop": // stop calculating and return a result as soon as possible. 218 | if uci.search != nil { 219 | uci.search.Abort() 220 | if ponder { 221 | uci.BestMove(<-uci.result) 222 | } 223 | } 224 | // * ponderhit 225 | // the user has played the expected move. This will be sent if the engine was told to ponder on the same move 226 | // the user has played. The engine should continue searching but switch from pondering to normal search. 227 | case "ponderhit": 228 | if uci.search != nil && ponder { 229 | uci.search.gt.Start() 230 | uci.BestMove(<-uci.result) 231 | } 232 | uci.moveCounter++ 233 | case "quit": // quit the program as soon as possible 234 | return 235 | 236 | case "print": // Not a UCI command. Used to print the board for debugging from console 237 | uci.brd.Print() // while in UCI mode. 238 | default: 239 | uci.invalid(uciFields) 240 | } 241 | } 242 | } 243 | } 244 | 245 | func (uci *UCIAdapter) debug(uciFields []string) bool { 246 | switch uciFields[0] { 247 | case "on": 248 | return true 249 | case "off": 250 | default: 251 | uci.invalid(uciFields) 252 | } 253 | return false 254 | } 255 | 256 | func (uci *UCIAdapter) invalid(uciFields []string) { 257 | uci.InfoString("invalid command.\n") 258 | } 259 | 260 | func (uci *UCIAdapter) identify() { 261 | uci.Send(fmt.Sprintf("id name GopherCheck %s\n", version)) 262 | uci.Send("id author Steve Lovell\n") 263 | uci.option() 264 | uci.Send("uciok\n") 265 | } 266 | 267 | func (uci *UCIAdapter) option() { // option name option_name [ parameters ] 268 | // tells the GUI which parameters can be changed in the engine. 269 | uci.Send("option name Ponder type check default false\n") 270 | numCPU := runtime.NumCPU() 271 | uci.Send(fmt.Sprintf("option name CPU type spin default %d min 1 max %d\n", numCPU, numCPU)) 272 | } 273 | 274 | // some example options from Toga 1.3.1: 275 | 276 | // Engine: option name Hash type spin default 16 min 4 max 1024 277 | // Engine: option name Search Time type spin default 0 min 0 max 3600 278 | // Engine: option name Search Depth type spin default 0 min 0 max 20 279 | // Engine: option name Ponder type check default false 280 | // Engine: option name OwnBook type check default true 281 | // Engine: option name BookFile type string default performance.bin 282 | // Engine: option name MultiPV type spin default 1 min 1 max 10 283 | // Engine: option name NullMove Pruning type combo default Always var Always var Fail High var Never 284 | // Engine: option name NullMove Reduction type spin default 3 min 1 max 4 285 | // Engine: option name Verification Search type combo default Always var Always var Endgame var Never 286 | // Engine: option name Verification Reduction type spin default 5 min 1 max 6 287 | // Engine: option name History Pruning type check default true 288 | // Engine: option name History Threshold type spin default 70 min 0 max 100 289 | // Engine: option name Futility Pruning type check default true 290 | // Engine: option name Futility Margin type spin default 100 min 0 max 500 291 | // Engine: option name Extended Futility Margin type spin default 300 min 0 max 900 292 | // Engine: option name Delta Pruning type check default true 293 | // Engine: option name Delta Margin type spin default 50 min 0 max 500 294 | // Engine: option name Quiescence Check Plies type spin default 1 min 0 max 2 295 | // Engine: option name Material type spin default 100 min 0 max 400 296 | // Engine: option name Piece Activity type spin default 100 min 0 max 400 297 | // Engine: option name King Safety type spin default 100 min 0 max 400 298 | // Engine: option name Pawn Structure type spin default 100 min 0 max 400 299 | // Engine: option name Passed Pawns type spin default 100 min 0 max 400 300 | // Engine: option name Toga Lazy Eval type check default true 301 | // Engine: option name Toga Lazy Eval Margin type spin default 200 min 0 max 900 302 | // Engine: option name Toga King Safety type check default false 303 | // Engine: option name Toga King Safety Margin type spin default 1700 min 500 max 3000 304 | // Engine: option name Toga Extended History Pruning type check default false 305 | 306 | func (uci *UCIAdapter) setOption(uciFields []string) { 307 | switch uciFields[0] { 308 | case "Ponder": // example: setoption name Ponder value true 309 | if len(uciFields) == 3 { 310 | switch uciFields[2] { 311 | case "true": 312 | uci.optionPonder = true 313 | case "false": 314 | uci.optionPonder = false 315 | default: 316 | uci.invalid(uciFields) 317 | } 318 | } 319 | // option name CPU type spin default 0 min 1 max numCPU 320 | case "CPU": 321 | if len(uciFields) == 3 { 322 | numCPU, err := strconv.Atoi(uciFields[2]) 323 | if err != nil { 324 | uci.invalid(uciFields) 325 | return 326 | } 327 | if numCPU > 0 && runtime.NumCPU() >= numCPU { 328 | if uci.optionDebug { 329 | uci.InfoString(fmt.Sprintf("setting up load balancer for %d CPU\n", numCPU)) 330 | } 331 | setupLoadBalancer(numCPU) 332 | } 333 | } 334 | default: 335 | } 336 | } 337 | 338 | func (uci *UCIAdapter) register(uciFields []string) { 339 | // The following tokens are allowed: 340 | // * later - the user doesn't want to register the engine now. 341 | // * name - the engine should be registered with the name 342 | // * code - the engine should be registered with the code 343 | // Examples: "register later" "register name Stefan MK code 4359874324" 344 | } 345 | 346 | // * go 347 | // start calculating on the current position set up with the "position" command. 348 | // There are a number of commands that can follow this command, all will be sent in the same string. 349 | // If one command is not send its value should be interpreted as it would not influence the search. 350 | func (uci *UCIAdapter) start(uciFields []string) bool { 351 | var timeLimit int 352 | maxDepth := MAX_DEPTH 353 | gt := NewGameTimer(uci.moveCounter, uci.brd.c) // TODO: this will be inaccurate in pondering mode. 354 | ponder := false 355 | var allowedMoves []Move 356 | for len(uciFields) > 0 { 357 | // fmt.Println(uci_fields[0]) 358 | switch uciFields[0] { 359 | 360 | // * searchmoves .... 361 | // restrict search to this moves only 362 | // Example: After "position startpos" and "go infinite searchmoves e2e4 d2d4" 363 | // the engine should only search the two moves e2e4 and d2d4 in the initial position. 364 | case "searchmoves": 365 | uciFields = uciFields[1:] 366 | for len(uciFields) > 0 && IsMove(uciFields[0]) { 367 | allowedMoves = append(allowedMoves, ParseMove(uci.brd, uciFields[0])) 368 | uciFields = uciFields[1:] 369 | } 370 | 371 | // * ponder - start searching in pondering mode. 372 | case "ponder": 373 | if uci.optionPonder { 374 | ponder = true 375 | } 376 | uciFields = uciFields[1:] 377 | 378 | case "wtime": // white has x msec left on the clock 379 | timeLimit, _ = strconv.Atoi(uciFields[1]) 380 | gt.remaining[WHITE] = time.Duration(timeLimit) * time.Millisecond 381 | uciFields = uciFields[2:] 382 | 383 | case "btime": // black has x msec left on the clock 384 | timeLimit, _ = strconv.Atoi(uciFields[1]) 385 | gt.remaining[BLACK] = time.Duration(timeLimit) * time.Millisecond 386 | uciFields = uciFields[2:] 387 | 388 | case "winc": // white increment per move in mseconds if x > 0 389 | timeLimit, _ = strconv.Atoi(uciFields[1]) 390 | gt.inc[WHITE] = time.Duration(timeLimit) * time.Millisecond 391 | uciFields = uciFields[2:] 392 | 393 | case "binc": // black increment per move in mseconds if x > 0 394 | timeLimit, _ = strconv.Atoi(uciFields[1]) 395 | gt.inc[BLACK] = time.Duration(timeLimit) * time.Millisecond 396 | uciFields = uciFields[2:] 397 | 398 | // * movestogo: there are x moves to the next time control, this will only be sent if x > 0, 399 | // if you don't get this and get the wtime and btime it's sudden death 400 | case "movestogo": 401 | remaining, _ := strconv.Atoi(uciFields[1]) 402 | gt.movesRemaining = remaining 403 | uciFields = uciFields[2:] 404 | 405 | case "depth": // search x plies only 406 | maxDepth, _ = strconv.Atoi(uciFields[1]) 407 | uciFields = uciFields[2:] 408 | 409 | case "nodes": // search x nodes only 410 | uci.invalid(uciFields) 411 | uciFields = uciFields[2:] 412 | 413 | case "mate": // search for a mate in x moves 414 | uci.invalid(uciFields) 415 | uciFields = uciFields[2:] 416 | 417 | case "movetime": // search exactly x mseconds 418 | timeLimit, _ = strconv.Atoi(uciFields[1]) 419 | gt.SetMoveTime(time.Duration(timeLimit) * time.Millisecond) 420 | uciFields = uciFields[2:] 421 | // * infinite: search until the "stop" command. Do not exit the search without being 422 | // told so in this mode! 423 | case "infinite": 424 | gt.SetMoveTime(MAX_TIME) 425 | uciFields = uciFields[1:] 426 | default: 427 | uciFields = uciFields[1:] 428 | } 429 | } 430 | uci.wg.Add(1) 431 | 432 | // type SearchParams struct { 433 | // max_depth int 434 | // verbose, ponder, restrict_search bool 435 | // } 436 | uci.search = NewSearch(SearchParams{maxDepth, uci.optionDebug, ponder, len(allowedMoves) > 0}, 437 | gt, uci, allowedMoves) 438 | go uci.search.Start(uci.brd.Copy()) // starting the search also starts the clock 439 | return ponder 440 | } 441 | 442 | // position [fen | startpos ] moves .... 443 | func (uci *UCIAdapter) position(uciFields []string) { 444 | if len(uciFields) == 0 { 445 | uci.brd = StartPos() 446 | } else if uciFields[0] == "startpos" { 447 | uci.brd = StartPos() 448 | uciFields = uciFields[1:] 449 | if len(uciFields) > 1 && uciFields[0] == "moves" { 450 | uci.playMoveSequence(uciFields[1:]) 451 | } 452 | } else if uciFields[0] == "fen" { 453 | uci.brd = ParseFENSlice(uciFields[1:]) 454 | if len(uciFields) > 7 { 455 | uci.playMoveSequence(uciFields[7:]) 456 | } 457 | } else { 458 | uci.invalid(uciFields) 459 | } 460 | } 461 | 462 | func (uci *UCIAdapter) playMoveSequence(uciFields []string) { 463 | var move Move 464 | if uciFields[0] == "moves" { 465 | uciFields = uciFields[1:] 466 | } 467 | for _, moveStr := range uciFields { 468 | move = ParseMove(uci.brd, moveStr) 469 | makeMove(uci.brd, move) 470 | } 471 | } 472 | 473 | func StartPos() *Board { 474 | return ParseFENString("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") 475 | } 476 | -------------------------------------------------------------------------------- /utilities.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "time" 11 | ) 12 | 13 | func RunTestSuite(testSuite string, depth, timeout int) { 14 | test, err := loadEpdFile(testSuite) 15 | if err != nil { 16 | fmt.Println(err) 17 | return 18 | } 19 | var moveStr string 20 | sum, score := 0, 0 21 | var gt *GameTimer 22 | var search *Search 23 | 24 | start := time.Now() 25 | for i, epd := range test { 26 | gt = NewGameTimer(0, epd.brd.c) 27 | gt.SetMoveTime(time.Duration(timeout) * time.Millisecond) 28 | search = NewSearch(SearchParams{depth, false, false, false}, gt, nil, nil) 29 | search.Start(epd.brd) 30 | 31 | moveStr = ToSAN(epd.brd, search.bestMove) 32 | if correctMove(epd, moveStr) { 33 | score += 1 34 | fmt.Printf("-") 35 | } else { 36 | fmt.Printf("%d.", i+1) 37 | } 38 | sum += search.nodes 39 | // search.htable.PrintMax() 40 | } 41 | secondsElapsed := time.Since(start).Seconds() 42 | mNodes := float64(sum) / 1000000.0 43 | fmt.Printf("\n%.4fm nodes searched in %.4fs (%.4fm NPS)\n", 44 | mNodes, secondsElapsed, mNodes/secondsElapsed) 45 | fmt.Printf("Total score: %d/%d\n", score, len(test)) 46 | fmt.Printf("Overhead: %.4fm\n", float64(loadBalancer.Overhead())/1000000.0) 47 | fmt.Printf("Timeout: %.1fs\n", float64(timeout)/1000.0) 48 | } 49 | 50 | func correctMove(epd *EPD, moveStr string) bool { 51 | for _, a := range epd.avoidMoves { 52 | if moveStr == a { 53 | return false 54 | } 55 | } 56 | for _, b := range epd.bestMoves { 57 | if moveStr == b { 58 | return true 59 | } 60 | } 61 | return false 62 | } 63 | 64 | func CompareBoards(brd, other *Board) bool { 65 | equal := true 66 | if brd.pieces != other.pieces { 67 | fmt.Println("Board.pieces unequal") 68 | equal = false 69 | } 70 | if brd.squares != other.squares { 71 | fmt.Println("Board.squares unequal") 72 | fmt.Println("original:") 73 | brd.Print() 74 | fmt.Println("new board:") 75 | other.Print() 76 | equal = false 77 | } 78 | if brd.occupied != other.occupied { 79 | fmt.Println("Board.occupied unequal") 80 | for i := 0; i < 2; i++ { 81 | fmt.Printf("side: %d\n", i) 82 | fmt.Println("original:") 83 | brd.occupied[i].Print() 84 | fmt.Println("new board:") 85 | other.occupied[i].Print() 86 | } 87 | equal = false 88 | } 89 | if brd.material != other.material { 90 | fmt.Println("Board.material unequal") 91 | equal = false 92 | } 93 | if brd.hashKey != other.hashKey { 94 | fmt.Println("Board.hashKey unequal") 95 | equal = false 96 | } 97 | if brd.pawnHashKey != other.pawnHashKey { 98 | fmt.Println("Board.pawnHashKey unequal") 99 | equal = false 100 | } 101 | if brd.c != other.c { 102 | fmt.Println("Board.c unequal") 103 | equal = false 104 | } 105 | if brd.castle != other.castle { 106 | fmt.Println("Board.castle unequal") 107 | equal = false 108 | } 109 | if brd.enpTarget != other.enpTarget { 110 | fmt.Println("Board.enpTarget unequal") 111 | equal = false 112 | } 113 | if brd.halfmoveClock != other.halfmoveClock { 114 | fmt.Println("Board.halfmoveClock unequal") 115 | equal = false 116 | } 117 | if brd.endgameCounter != other.endgameCounter { 118 | fmt.Println("Board.endgameCounter unequal") 119 | equal = false 120 | } 121 | return equal 122 | } 123 | 124 | func isBoardConsistent(brd *Board) bool { 125 | var squares [64]Piece 126 | var occupied [2]BB 127 | var material [2]int16 128 | 129 | var sq int 130 | for sq = 0; sq < 64; sq++ { 131 | squares[sq] = EMPTY 132 | } 133 | consistent := true 134 | 135 | for c := uint8(BLACK); c <= WHITE; c++ { 136 | for pc := Piece(PAWN); pc <= KING; pc++ { 137 | if occupied[c]&brd.pieces[c][pc] > 0 { 138 | fmt.Printf("brd.pieces[%d][%d] overlaps with another pieces bitboard.\n", c, pc) 139 | consistent = false 140 | } 141 | occupied[c] |= brd.pieces[c][pc] 142 | 143 | for bb := brd.pieces[c][pc]; bb > 0; bb.Clear(sq) { 144 | sq = furthestForward(c, bb) 145 | material[c] += int16(pc.Value() + mainPst[c][pc][sq]) 146 | if squares[sq] != EMPTY { 147 | fmt.Printf("brd.pieces[%d][%d] overlaps with another pieces bitboard at %s.\n", c, pc, SquareString(sq)) 148 | consistent = false 149 | } 150 | squares[sq] = pc 151 | } 152 | } 153 | } 154 | 155 | if squares != brd.squares { 156 | fmt.Println("brd.squares inconsistent") 157 | consistent = false 158 | } 159 | if occupied != brd.occupied { 160 | fmt.Println("brd.occupied inconsistent") 161 | consistent = false 162 | } 163 | if material != brd.material { 164 | fmt.Println("brd.material inconsistent") 165 | consistent = false 166 | } 167 | 168 | return consistent 169 | } 170 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/profile/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go_import_path: github.com/pkg/profile 3 | go: 4 | - 1.4.3 5 | - 1.5.2 6 | - 1.6.2 7 | - tip 8 | 9 | script: 10 | - go test github.com/pkg/profile 11 | - go test -race github.com/pkg/profile 12 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/profile/AUTHORS: -------------------------------------------------------------------------------- 1 | Dave Cheney 2 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/profile/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Dave Cheney. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/profile/README.md: -------------------------------------------------------------------------------- 1 | profile 2 | ======= 3 | 4 | Simple profiling support package for Go 5 | 6 | [![Build Status](https://travis-ci.org/pkg/profile.svg?branch=master)](https://travis-ci.org/pkg/profile) [![GoDoc](http://godoc.org/github.com/pkg/profile?status.svg)](http://godoc.org/github.com/pkg/profile) 7 | 8 | 9 | installation 10 | ------------ 11 | 12 | go get github.com/pkg/profile 13 | 14 | usage 15 | ----- 16 | 17 | Enabling profiling in your application is as simple as one line at the top of your main function 18 | 19 | ```go 20 | import "github.com/pkg/profile" 21 | 22 | func main() { 23 | defer profile.Start().Stop() 24 | ... 25 | } 26 | ``` 27 | 28 | options 29 | ------- 30 | 31 | What to profile is controlled by config value passed to profile.Start. 32 | By default CPU profiling is enabled. 33 | 34 | ```go 35 | import "github.com/pkg/profile" 36 | 37 | func main() { 38 | // p.Stop() must be called before the program exits to 39 | // ensure profiling information is written to disk. 40 | p := profile.Start(profile.MemProfile, profile.ProfilePath("."), profile.NoShutdownHook) 41 | ... 42 | } 43 | ``` 44 | 45 | Several convenience package level values are provided for cpu, memory, and block (contention) profiling. 46 | 47 | For more complex options, consult the [documentation](http://godoc.org/github.com/pkg/profile). 48 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/profile/profile.go: -------------------------------------------------------------------------------- 1 | // Package profile provides a simple way to manage runtime/pprof 2 | // profiling of your Go application. 3 | package profile 4 | 5 | import ( 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "os/signal" 10 | "path/filepath" 11 | "runtime" 12 | "runtime/pprof" 13 | "sync/atomic" 14 | ) 15 | 16 | const ( 17 | cpuMode = iota 18 | memMode 19 | blockMode 20 | ) 21 | 22 | type profile struct { 23 | // quiet suppresses informational messages during profiling. 24 | quiet bool 25 | 26 | // noShutdownHook controls whether the profiling package should 27 | // hook SIGINT to write profiles cleanly. 28 | noShutdownHook bool 29 | 30 | // mode holds the type of profiling that will be made 31 | mode int 32 | 33 | // path holds the base path where various profiling files are written. 34 | // If blank, the base path will be generated by ioutil.TempDir. 35 | path string 36 | 37 | // memProfileRate holds the rate for the memory profile. 38 | memProfileRate int 39 | 40 | // closer holds a cleanup function that run after each profile 41 | closer func() 42 | 43 | // stopped records if a call to profile.Stop has been made 44 | stopped uint32 45 | } 46 | 47 | // NoShutdownHook controls whether the profiling package should 48 | // hook SIGINT to write profiles cleanly. 49 | // Programs with more sophisticated signal handling should set 50 | // this to true and ensure the Stop() function returned from Start() 51 | // is called during shutdown. 52 | func NoShutdownHook(p *profile) { p.noShutdownHook = true } 53 | 54 | // Quiet suppresses informational messages during profiling. 55 | func Quiet(p *profile) { p.quiet = true } 56 | 57 | // CPUProfile controls if cpu profiling will be enabled. It disables any previous profiling settings. 58 | func CPUProfile(p *profile) { p.mode = cpuMode } 59 | 60 | // DefaultMemProfileRate is the default memory profiling rate. 61 | // See also http://golang.org/pkg/runtime/#pkg-variables 62 | const DefaultMemProfileRate = 4096 63 | 64 | // MemProfile controls if memory profiling will be enabled. It disables any previous profiling settings. 65 | func MemProfile(p *profile) { 66 | p.memProfileRate = DefaultMemProfileRate 67 | p.mode = memMode 68 | } 69 | 70 | // MemProfileRate controls if memory profiling will be enabled. Additionally, it takes a parameter which 71 | // allows the setting of the memory profile rate. 72 | func MemProfileRate(rate int) func(*profile) { 73 | return func(p *profile) { 74 | p.memProfileRate = rate 75 | p.mode = memMode 76 | } 77 | } 78 | 79 | // BlockProfile controls if block (contention) profiling will be enabled. It disables any previous profiling settings. 80 | func BlockProfile(p *profile) { p.mode = blockMode } 81 | 82 | // ProfilePath controls the base path where various profiling 83 | // files are written. If blank, the base path will be generated 84 | // by ioutil.TempDir. 85 | func ProfilePath(path string) func(*profile) { 86 | return func(p *profile) { 87 | p.path = path 88 | } 89 | } 90 | 91 | // Stop stops the profile and flushes any unwritten data. 92 | func (p *profile) Stop() { 93 | if !atomic.CompareAndSwapUint32(&p.stopped, 0, 1) { 94 | // someone has already called close 95 | return 96 | } 97 | p.closer() 98 | atomic.StoreUint32(&started, 0) 99 | } 100 | 101 | // started is non zero if a profile is running. 102 | var started uint32 103 | 104 | // Start starts a new profiling session. 105 | // The caller should call the Stop method on the value returned 106 | // to cleanly stop profiling. 107 | func Start(options ...func(*profile)) interface { 108 | Stop() 109 | } { 110 | if !atomic.CompareAndSwapUint32(&started, 0, 1) { 111 | log.Fatal("profile: Start() already called") 112 | } 113 | 114 | var prof profile 115 | for _, option := range options { 116 | option(&prof) 117 | } 118 | 119 | path, err := func() (string, error) { 120 | if p := prof.path; p != "" { 121 | return p, os.MkdirAll(p, 0777) 122 | } 123 | return ioutil.TempDir("", "profile") 124 | }() 125 | 126 | if err != nil { 127 | log.Fatalf("profile: could not create initial output directory: %v", err) 128 | } 129 | 130 | logf := func(format string, args ...interface{}) { 131 | if !prof.quiet { 132 | log.Printf(format, args...) 133 | } 134 | } 135 | 136 | switch prof.mode { 137 | case cpuMode: 138 | fn := filepath.Join(path, "cpu.pprof") 139 | f, err := os.Create(fn) 140 | if err != nil { 141 | log.Fatalf("profile: could not create cpu profile %q: %v", fn, err) 142 | } 143 | logf("profile: cpu profiling enabled, %s", fn) 144 | pprof.StartCPUProfile(f) 145 | prof.closer = func() { 146 | pprof.StopCPUProfile() 147 | f.Close() 148 | logf("profile: cpu profiling disabled, %s", fn) 149 | } 150 | 151 | case memMode: 152 | fn := filepath.Join(path, "mem.pprof") 153 | f, err := os.Create(fn) 154 | if err != nil { 155 | log.Fatalf("profile: could not create memory profile %q: %v", fn, err) 156 | } 157 | old := runtime.MemProfileRate 158 | runtime.MemProfileRate = prof.memProfileRate 159 | logf("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, fn) 160 | prof.closer = func() { 161 | pprof.Lookup("heap").WriteTo(f, 0) 162 | f.Close() 163 | runtime.MemProfileRate = old 164 | logf("profile: memory profiling disabled, %s", fn) 165 | } 166 | 167 | case blockMode: 168 | fn := filepath.Join(path, "block.pprof") 169 | f, err := os.Create(fn) 170 | if err != nil { 171 | log.Fatalf("profile: could not create block profile %q: %v", fn, err) 172 | } 173 | runtime.SetBlockProfileRate(1) 174 | logf("profile: block profiling enabled, %s", fn) 175 | prof.closer = func() { 176 | pprof.Lookup("block").WriteTo(f, 0) 177 | f.Close() 178 | runtime.SetBlockProfileRate(0) 179 | logf("profile: block profiling disabled, %s", fn) 180 | } 181 | } 182 | 183 | if !prof.noShutdownHook { 184 | go func() { 185 | c := make(chan os.Signal, 1) 186 | signal.Notify(c, os.Interrupt) 187 | <-c 188 | 189 | log.Println("profile: caught interrupt, stopping profiles") 190 | prof.Stop() 191 | 192 | os.Exit(0) 193 | }() 194 | } 195 | 196 | return &prof 197 | } 198 | -------------------------------------------------------------------------------- /worker.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | import ( 9 | // "fmt" 10 | "sync" 11 | ) 12 | 13 | // Each worker maintains a list of active split points for which it is responsible. 14 | 15 | // When a worker's search reaches a new SP node, it creates a new SP struct, (including the current 16 | // []Stack info for nodes above the SP) and adds the SP to its active SP list. 17 | 18 | // When workers are idle (they've finished searching and have no split points of their own), 19 | // they request more work from the load balancer. The load balancer selects the best 20 | // available SP and assigns the worker to the SP. 21 | 22 | // The assigned worker begins a search rooted at the chosen SP node. Each worker searching the SP 23 | // node requests moves from the SP node's move generator. 24 | 25 | // Cancellation: 26 | 27 | // When a beta cutoff occurs at an SP node, the worker sends a cancellation signal on a channel 28 | // read by the other workers collaborating on the current split point. 29 | // If there are more SPs below the current one, the cancellation signal will be fanned out to 30 | // each child SP. 31 | 32 | type Worker struct { 33 | sync.RWMutex 34 | searchOverhead int 35 | 36 | spList SPList 37 | stk Stack 38 | 39 | assignSp chan *SplitPoint 40 | 41 | ptt *PawnTT 42 | recycler *Recycler 43 | currentSp *SplitPoint 44 | 45 | mask uint8 46 | index uint8 47 | } 48 | 49 | func (w *Worker) IsCancelled() bool { 50 | for sp := w.currentSp; sp != nil; sp = sp.parent { 51 | if sp.Cancel() { 52 | return true 53 | } 54 | } 55 | return false 56 | } 57 | 58 | func (w *Worker) HelpServants(currentSp *SplitPoint) { 59 | var bestSp *SplitPoint 60 | var worker *Worker 61 | // Check for SPs underneath current_sp 62 | 63 | // assert(w.current_sp == current_sp.parent, "not current sp") 64 | 65 | for mask := currentSp.ServantMask(); mask > 0; mask = currentSp.ServantMask() { 66 | bestSp = nil 67 | 68 | for tempMask := mask; tempMask > 0; tempMask &= (^worker.mask) { 69 | worker = loadBalancer.workers[lsb(BB(tempMask))] 70 | worker.RLock() 71 | for _, thisSp := range worker.spList { 72 | // If a worker has already finished searching, then either a beta cutoff has already 73 | // occurred at sp, or no moves are left to search. 74 | if !thisSp.WorkerFinished() && (bestSp == nil || thisSp.Order() > bestSp.Order()) { 75 | bestSp = thisSp 76 | tempMask |= thisSp.ServantMask() // If this SP has servants of its own, check them as well. 77 | } 78 | } 79 | worker.RUnlock() 80 | } 81 | 82 | if bestSp == nil || bestSp.WorkerFinished() { 83 | break 84 | } else { 85 | bestSp.AddServant(w.mask) 86 | w.currentSp = bestSp 87 | w.SearchSP(bestSp) 88 | } 89 | } 90 | 91 | w.currentSp = currentSp.parent 92 | 93 | // If at any point we can't find another viable servant SP, wait for remaining servants to complete. 94 | // This prevents us from continually acquiring the worker locks. 95 | currentSp.Wait() 96 | } 97 | 98 | func (w *Worker) Help(b *Balancer) { 99 | go func() { 100 | var bestSp *SplitPoint 101 | for { 102 | bestSp = nil 103 | for _, master := range b.workers { // try to find a good SP 104 | if master.index == w.index { 105 | continue 106 | } 107 | master.RLock() 108 | for _, thisSp := range master.spList { 109 | if !thisSp.WorkerFinished() && (bestSp == nil || thisSp.Order() > bestSp.Order()) { 110 | bestSp = thisSp 111 | } 112 | } 113 | master.RUnlock() 114 | } 115 | 116 | if bestSp == nil || bestSp.WorkerFinished() { // No best SP was available. 117 | b.done <- w // Worker is completely idle and available to help any processor. 118 | bestSp = <-w.assignSp // Wait for the next SP to be discovered. 119 | } else { 120 | bestSp.AddServant(w.mask) 121 | } 122 | 123 | w.currentSp = bestSp 124 | w.SearchSP(bestSp) 125 | w.currentSp = nil 126 | 127 | } 128 | }() 129 | } 130 | 131 | func (w *Worker) SearchSP(sp *SplitPoint) { 132 | brd := sp.brd.Copy() 133 | brd.worker = w 134 | 135 | sp.stk.CopyUpTo(w.stk, sp.ply) 136 | w.stk[sp.ply].sp = sp 137 | 138 | sp.RLock() 139 | alpha, beta := sp.alpha, sp.beta 140 | sp.RUnlock() 141 | 142 | // Once the SP is fully evaluated, The SP master will handle returning its value to parent node. 143 | _, total := sp.s.ybw(brd, w.stk, alpha, beta, sp.depth, sp.ply, sp.nodeType, SP_SERVANT, sp.checked) 144 | w.searchOverhead += total 145 | 146 | sp.RemoveServant(w.mask) 147 | // At this point, any additional SPs found by the worker during the search rooted at sp 148 | // should be fully resolved. The SP list for this worker should be empty again. 149 | } 150 | -------------------------------------------------------------------------------- /zobrist.go: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------- 2 | // ♛ GopherCheck ♛ 3 | // Copyright © 2014 Stephen J. Lovell 4 | //----------------------------------------------------------------------------------- 5 | 6 | package main 7 | 8 | // Zobrist Hashing - 9 | // Each possible square and piece combination is assigned a unique 64-bit integer key at startup. 10 | // A unique hash key for a chess position can be generated by merging (via XOR) the keys for each 11 | // piece/square combination, and merging in keys representing the side to move, castling rights, 12 | // and any en-passant target square. 13 | var pawnZobristTable [2][64]uint32 14 | var zobristTable [2][8][64]uint64 15 | 16 | // integer keys representing the en-passant target square, if any. 17 | var enpTable [128]uint64 18 | var castleTable [16]uint64 19 | var sideKey64 uint64 // keys representing a change in side-to-move. 20 | 21 | func setupZobrist() { 22 | rng := NewRngKiss(148) // sparsely populated rands produce fewer collisions. 23 | for c := 0; c < 2; c++ { 24 | for sq := 0; sq < 64; sq++ { 25 | pawnZobristTable[c][sq] = rng.RandomUint32(sq) 26 | for pc := 0; pc < 6; pc++ { 27 | zobristTable[c][pc][sq] = rng.RandomUint64(sq) 28 | } 29 | } 30 | } 31 | for i := 0; i < 16; i++ { 32 | castleTable[i] = rng.RandomUint64((i << 2)) 33 | } 34 | for sq := 0; sq < 64; sq++ { 35 | enpTable[sq] = rng.RandomUint64(sq) 36 | } 37 | enpTable[64] = 0 38 | sideKey64 = rng.RandomUint64(63) 39 | } 40 | 41 | func zobrist(pc Piece, sq int, c uint8) uint64 { 42 | return zobristTable[c][pc][sq] 43 | } 44 | 45 | func pawnZobrist(sq int, c uint8) uint32 { 46 | return pawnZobristTable[c][sq] 47 | } 48 | 49 | func enpZobrist(sq uint8) uint64 { 50 | return enpTable[sq] 51 | } 52 | 53 | func castleZobrist(castle uint8) uint64 { 54 | return castleTable[castle] 55 | } 56 | --------------------------------------------------------------------------------