github.com/frankkopp/FrankyGo@v1.0.3/internal/movegen/movegen.go (about) 1 // 2 // FrankyGo - UCI chess engine in GO for learning purposes 3 // 4 // MIT License 5 // 6 // Copyright (c) 2018-2020 Frank Kopp 7 // 8 // Permission is hereby granted, free of charge, to any person obtaining a copy 9 // of this software and associated documentation files (the "Software"), to deal 10 // in the Software without restriction, including without limitation the rights 11 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 // copies of the Software, and to permit persons to whom the Software is 13 // furnished to do so, subject to the following conditions: 14 // 15 // The above copyright notice and this permission notice shall be included in all 16 // copies or substantial portions of the Software. 17 // 18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 // SOFTWARE. 25 // 26 27 // Package movegen contains functionality to create moves on a 28 // chess position. It implements several variants like 29 // generate pseudo legal moves, legal moves or on demand 30 // generation of pseudo legal moves. 31 package movegen 32 33 import ( 34 "fmt" 35 "regexp" 36 "strings" 37 38 "github.com/op/go-logging" 39 40 "github.com/frankkopp/FrankyGo/internal/attacks" 41 "github.com/frankkopp/FrankyGo/internal/config" 42 "github.com/frankkopp/FrankyGo/internal/history" 43 myLogging "github.com/frankkopp/FrankyGo/internal/logging" 44 "github.com/frankkopp/FrankyGo/internal/moveslice" 45 "github.com/frankkopp/FrankyGo/internal/position" 46 . "github.com/frankkopp/FrankyGo/internal/types" 47 ) 48 49 var log *logging.Logger 50 51 // Movegen data structure. Create new move generator via 52 // movegen.NewMoveGen() 53 // Creating this directly will not work. 54 type Movegen struct { 55 pseudoLegalMoves *moveslice.MoveSlice 56 legalMoves *moveslice.MoveSlice 57 58 onDemandMoves *moveslice.MoveSlice 59 currentODZobrist position.Key 60 onDemandEvasionTargets Bitboard 61 currentODStage int8 62 takeIndex int 63 64 killerMoves [2]Move 65 pvMove Move 66 pvMovePushed bool 67 historyData *history.History 68 } 69 70 // ////////////////////////////////////////////////////// 71 // // Public 72 // ////////////////////////////////////////////////////// 73 74 // GenMode generation modes for on demand move generation. 75 type GenMode int 76 77 // GenMode generation modes for on demand move generation. 78 const ( 79 GenZero GenMode = 0b00 80 GenNonQuiet GenMode = 0b01 81 GenQuiet GenMode = 0b10 82 GenAll GenMode = 0b11 83 ) 84 85 // NewMoveGen creates a new instance of a move generator 86 // This is the only time when we allocate new memory. The instance 87 // will not create any move lists during normal move generation 88 // as it will reuse pre-created internal lists which will 89 // be returned via pointer to a caller. 90 // OBS: Be careful when trying to store the list of generated 91 // moves as the underlying list will be changed when move gen 92 // is called again. A deep copy is necessary if you need a 93 // copy of the move list. 94 // For a deep copy use: 95 // moveslice.MoveSlice.Clone() 96 func NewMoveGen() *Movegen { 97 if log == nil { 98 log = myLogging.GetLog() 99 } 100 tmpMg := &Movegen{ 101 pseudoLegalMoves: moveslice.NewMoveSlice(MaxMoves), 102 legalMoves: moveslice.NewMoveSlice(MaxMoves), 103 104 onDemandMoves: moveslice.NewMoveSlice(MaxMoves), 105 currentODZobrist: 0, 106 onDemandEvasionTargets: BbZero, 107 currentODStage: odNew, 108 takeIndex: 0, 109 110 killerMoves: [2]Move{MoveNone, MoveNone}, 111 pvMove: MoveNone, 112 pvMovePushed: false, 113 historyData: nil, 114 } 115 return tmpMg 116 } 117 118 // GeneratePseudoLegalMoves generates pseudo moves for the next player. Does not check if 119 // king is left in check or if it passes an attacked square when castling or has been in check 120 // before castling. 121 // 122 // If a PV move is set with setPV(Move pv) this move will be returned first and will 123 // not be returned at its normal place. 124 // 125 // Killer moves will be played as soon as possible after non quiet moves. As Killer moves 126 // are stored for the whole ply a Killer move might not be valid for the current position. 127 // Therefore we need to wait until they are generated. Killer moves will then be pushed 128 // to the top of the the quiet moves. 129 // 130 // Evasion is a parameter given when the position is in check and only evasion moves should 131 // be generated. For testing purposes this is a parameter but obviously we could determine 132 // checks very quickly internally in this function. 133 // The idea of evasion is to avoid generating moves which are obviously not getting the 134 // king out of check. This may reduce the total number of generated moves but there might 135 // still be a few non legal moves. This is the case if considering and calculating all 136 // possible scenarios is more expensive than to just generate the move and dismiss is later. 137 // Because of beta cuts off we quite often will never have to check the full legality 138 // of these moves anyway. 139 func (mg *Movegen) GeneratePseudoLegalMoves(p *position.Position, mode GenMode, evasion bool) *moveslice.MoveSlice { 140 // re-use move list 141 mg.pseudoLegalMoves.Clear() 142 143 // when in check only generate moves either blocking or capturing the attacker 144 if evasion { 145 mg.onDemandEvasionTargets = mg.getEvasionTargets(p) 146 } 147 148 // first generate all non quiet moves 149 if mode&GenNonQuiet != 0 { 150 mg.generatePawnMoves(p, GenNonQuiet, evasion, mg.onDemandEvasionTargets, mg.pseudoLegalMoves) 151 // castling never captures 152 mg.generateKingMoves(p, GenNonQuiet, evasion, mg.onDemandEvasionTargets, mg.pseudoLegalMoves) 153 mg.generateMoves(p, GenNonQuiet, evasion, mg.onDemandEvasionTargets, mg.pseudoLegalMoves) 154 } 155 // second generate all other moves 156 if mode&GenQuiet != 0 { 157 mg.generatePawnMoves(p, GenQuiet, evasion, mg.onDemandEvasionTargets, mg.pseudoLegalMoves) 158 if !evasion { // no castling when in check 159 mg.generateCastling(p, GenQuiet, mg.pseudoLegalMoves) 160 } 161 mg.generateKingMoves(p, GenQuiet, evasion, mg.onDemandEvasionTargets, mg.pseudoLegalMoves) 162 mg.generateMoves(p, GenQuiet, evasion, mg.onDemandEvasionTargets, mg.pseudoLegalMoves) 163 } 164 165 // PV, Killer and history handling 166 mg.updateSortValues(p, mg.pseudoLegalMoves) 167 168 // sort moves 169 mg.pseudoLegalMoves.Sort() 170 171 // remove internal sort value 172 mg.pseudoLegalMoves.ForEach(func(i int) { 173 mg.pseudoLegalMoves.Set(i, mg.pseudoLegalMoves.At(i).MoveOf()) 174 }) 175 176 return mg.pseudoLegalMoves 177 } 178 179 // GenerateLegalMoves generates legal moves for the next player. 180 // Uses GeneratePseudoLegalMoves and filters out illegal moves. 181 // Usually only used for root moves generation as this is expensive. During 182 // the AlphaBeta search we will only use pseudo legal move generation. 183 func (mg *Movegen) GenerateLegalMoves(position *position.Position, mode GenMode) *moveslice.MoveSlice { 184 mg.legalMoves.Clear() 185 mg.GeneratePseudoLegalMoves(position, mode, false) 186 mg.pseudoLegalMoves.FilterCopy(mg.legalMoves, func(i int) bool { 187 return position.IsLegalMove(mg.pseudoLegalMoves.At(i)) 188 }) 189 return mg.legalMoves 190 } 191 192 // GetNextMove is the main function for phased generation of pseudo legal moves. 193 // It returns the next move for the given position and will usually be called in a 194 // loop during search. As we hope for an early beta cut this will save time as not 195 // all moves will have been generated. 196 // 197 // To reuse this on the same position a call to ResetOnDemand() is necessary. This 198 // is not necessary when a different position is called as this func will reset it self 199 // in this case. 200 // 201 // If a PV move is set with setPV(Move pv) this will be returned first 202 // and will not be returned at its normal place. 203 // 204 // Killer moves will be played as soon as possible. As Killer moves are stored for 205 // the whole ply a Killer move might not be valid for the current position. Therefore 206 // we need to wait until they are generated by the phased move generation. Killers will 207 // then be pushed to the top of the list of the generation stage. 208 // 209 // Evasion is a parameter given when the position is in check and only evasion moves should 210 // be generated. For testing purposes this is a parameter for now but obviously we could 211 // determine checks very quickly internally in this function. 212 // The idea of evasion is to avoid generating moves which are obviously not getting the 213 // king out of check. This may reduce the total number of generated moves but there might 214 // still be a few non legal moves. This is the case if considering and calculating all 215 // possible scenarios is more expensive than to just generate the move and dismiss is later. 216 // Because of beta cuts off we quite often will never have to check the full legality 217 // of these moves anyway. 218 func (mg *Movegen) GetNextMove(p *position.Position, mode GenMode, evasion bool) Move { 219 220 // if the position changes during iteration the iteration 221 // will be reset and generation will be restart with the 222 // new position. 223 if p.ZobristKey() != mg.currentODZobrist { 224 mg.onDemandMoves.Clear() 225 mg.onDemandEvasionTargets = BbZero 226 mg.currentODStage = odNew 227 mg.pvMovePushed = false 228 mg.takeIndex = 0 229 mg.currentODZobrist = p.ZobristKey() 230 } 231 232 // when in check only generate moves either blocking or capturing the attacker 233 if evasion && mg.onDemandEvasionTargets == BbZero { 234 mg.onDemandEvasionTargets = mg.getEvasionTargets(p) 235 } 236 237 // ad takeIndex 238 // With the takeIndex we can take from the front of the vector 239 // without removing the element from the vector which would 240 // be expensive as all elements would have to be shifted. 241 // (although our Moveslice class can handle this efficiently 242 // through a similar mechanism) 243 244 // If the list is currently empty and we have not generated all moves yet 245 // generate the next batch until we have new moves or there are no more 246 // moves to generate 247 if mg.onDemandMoves.Len() == 0 { 248 mg.fillOnDemandMoveList(p, mode, evasion) 249 } 250 251 // If we have generated moves we will return the first move and 252 // increase the takeIndex to the next move. If the list is empty 253 // even after all stages of generating we have no more moves 254 // and return MOVE_NONE 255 // If we have pushed a pvMove into the list we will need to 256 // skip this pvMove for each subsequent phases. 257 if mg.onDemandMoves.Len() != 0 { 258 259 // Handle PvMove 260 // if we pushed a pv move and the list is not empty we 261 // check if the pv is the next move in list and skip it. 262 if mg.currentODStage != od1 && 263 mg.pvMovePushed && 264 (*mg.onDemandMoves)[mg.takeIndex].MoveOf() == mg.pvMove.MoveOf() { 265 266 // skip pv move 267 mg.takeIndex++ 268 269 // We found the pv move and skipped it. 270 // No need to check this for this generation cycle 271 mg.pvMovePushed = false 272 273 // PV move last in move list 274 if mg.takeIndex >= mg.onDemandMoves.Len() { 275 // The pv move was the last move in this iterations list. 276 // We will try to generate more moves. If no more moves 277 // can be generated we will return MOVE_NONE. 278 // Otherwise we return the move below. 279 mg.takeIndex = 0 280 mg.onDemandMoves.Clear() 281 mg.fillOnDemandMoveList(p, mode, false) 282 // no more moves - return MOVE_NONE 283 if mg.onDemandMoves.Len() == 0 { 284 return MoveNone 285 } 286 } 287 } 288 289 // we have at least one move in the list and 290 // it is not the pvMove. Increase the takeIndex 291 // and return the move 292 // (remove internal sort value) 293 move := (*mg.onDemandMoves)[mg.takeIndex].MoveOf() 294 mg.takeIndex++ 295 if mg.takeIndex >= mg.onDemandMoves.Len() { 296 mg.takeIndex = 0 297 mg.onDemandMoves.Clear() 298 } 299 return move 300 } 301 302 // no more moves to be generated 303 mg.takeIndex = 0 304 mg.pvMovePushed = false 305 return MoveNone 306 } 307 308 // ResetOnDemand resets the move on demand generator to start fresh. 309 // Also deletes Killer and PV moves. 310 func (mg *Movegen) ResetOnDemand() { 311 mg.onDemandMoves.Clear() 312 mg.onDemandEvasionTargets = BbZero 313 mg.currentODStage = odNew 314 mg.currentODZobrist = 0 315 mg.pvMove = MoveNone 316 mg.pvMovePushed = false 317 mg.takeIndex = 0 318 } 319 320 // SetPvMove sets a PV move which should be returned first by 321 // the OnDemand MoveGenerator. 322 func (mg *Movegen) SetPvMove(move Move) { 323 mg.pvMove = move.MoveOf() 324 } 325 326 // StoreKiller provides the on demand move generator with a new killer move 327 // which should be returned as soon as possible when generating moves with 328 // the on demand generator. 329 func (mg *Movegen) StoreKiller(move Move) { 330 // check if already stored in first slot - if so return 331 moveOf := move.MoveOf() 332 if mg.killerMoves[0] == moveOf { 333 return 334 } else if mg.killerMoves[1] == moveOf { // if in second slot move it to first 335 mg.killerMoves[1] = mg.killerMoves[0] 336 mg.killerMoves[0] = moveOf 337 } else { 338 // add it to first slot und move first to second 339 mg.killerMoves[1] = mg.killerMoves[0] 340 mg.killerMoves[0] = moveOf 341 } 342 } 343 344 // SetHistoryData provides a pointer to the search's history data 345 // for the move generator so it can optimize sorting. 346 func (mg *Movegen) SetHistoryData(historyData *history.History) { 347 mg.historyData = historyData 348 } 349 350 // HasLegalMove determines if we have at least one legal move. We only have to find 351 // one legal move. We search for any KING, PAWN, KNIGHT, BISHOP, ROOK, QUEEN move 352 // and return immediately if we found one. 353 // The order of our search is approx from the most likely to the least likely. 354 func (mg *Movegen) HasLegalMove(position *position.Position) bool { 355 356 us := position.NextPlayer() 357 usBb := position.OccupiedBb(us) 358 359 // KING 360 // We do not need to check castling as possible castling implies King or Rook moves 361 kingSquare := position.KingSquare(us) 362 tmpMoves := GetAttacksBb(King, kingSquare, BbZero) &^ usBb 363 for tmpMoves != 0 { 364 toSquare := tmpMoves.PopLsb() 365 if position.IsLegalMove(CreateMove(kingSquare, toSquare, Normal, PtNone)) { 366 return true 367 } 368 } 369 370 myPawns := position.PiecesBb(us, Pawn) 371 occupiedBb := position.OccupiedAll() 372 opponentBb := position.OccupiedBb(us.Flip()) 373 374 // PAWN 375 // pawns - check step one to unoccupied squares 376 tmpMoves = ShiftBitboard(myPawns, us.MoveDirection()) & ^position.OccupiedAll() 377 // pawns double - check step two to unoccupied squares 378 tmpMovesDouble := ShiftBitboard(tmpMoves&us.PawnDoubleRank(), us.MoveDirection()) & ^position.OccupiedAll() 379 // double pawn steps 380 for tmpMovesDouble != 0 { 381 toSquare := tmpMovesDouble.PopLsb() 382 fromSquare := toSquare.To(us.Flip().MoveDirection()).To(us.Flip().MoveDirection()) 383 if position.IsLegalMove(CreateMove(fromSquare, toSquare, Normal, PtNone)) { 384 return true 385 } 386 } 387 // normal single pawn steps 388 tmpMoves &= ^us.PromotionRankBb() 389 for tmpMoves != 0 { 390 toSquare := tmpMoves.PopLsb() 391 fromSquare := toSquare.To(us.Flip().MoveDirection()) 392 if position.IsLegalMove(CreateMove(fromSquare, toSquare, Normal, PtNone)) { 393 return true 394 } 395 } 396 397 // normal pawn captures to the west (includes promotions) 398 tmpMoves = ShiftBitboard(myPawns, us.MoveDirection()+West) & opponentBb 399 for tmpMoves != 0 { 400 toSquare := tmpMoves.PopLsb() 401 fromSquare := toSquare.To(us.Flip().MoveDirection() + East) 402 if position.IsLegalMove(CreateMove(fromSquare, toSquare, Normal, PtNone)) { 403 return true 404 } 405 } 406 407 // normal pawn captures to the east - promotions first 408 tmpMoves = ShiftBitboard(myPawns, us.MoveDirection()+East) & opponentBb 409 for tmpMoves != 0 { 410 toSquare := tmpMoves.PopLsb() 411 fromSquare := toSquare.To(us.Flip().MoveDirection() + West) 412 if position.IsLegalMove(CreateMove(fromSquare, toSquare, Normal, PtNone)) { 413 return true 414 } 415 } 416 417 // OFFICERS 418 for pt := Knight; pt <= Queen; pt++ { 419 pieces := position.PiecesBb(us, pt) 420 for pieces != 0 { 421 fromSquare := pieces.PopLsb() 422 moves := GetAttacksBb(pt, fromSquare, occupiedBb) 423 for moves != 0 { 424 toSquare := moves.PopLsb() 425 if position.IsLegalMove(CreateMove(fromSquare, toSquare, Normal, PtNone)) { 426 return true 427 } 428 } 429 } 430 } 431 432 // en passant captures 433 enPassantSquare := position.GetEnPassantSquare() 434 if enPassantSquare != SqNone { 435 // left 436 tmpMoves = ShiftBitboard(enPassantSquare.Bb(), us.Flip().MoveDirection()+West) & myPawns 437 if tmpMoves != 0 { 438 fromSquare := tmpMoves.PopLsb() 439 if position.IsLegalMove(CreateMove(fromSquare, fromSquare.To(us.MoveDirection()+East), EnPassant, PtNone)) { 440 return true 441 } 442 } 443 // right 444 tmpMoves = ShiftBitboard(enPassantSquare.Bb(), us.Flip().MoveDirection()+East) & myPawns 445 if tmpMoves != 0 { 446 fromSquare := tmpMoves.PopLsb() 447 if position.IsLegalMove(CreateMove(fromSquare, fromSquare.To(us.MoveDirection()+West), EnPassant, PtNone)) { 448 return true 449 } 450 } 451 } 452 453 // no move found 454 return false 455 } 456 457 // Regex for UCI notation (UCI). 458 var regexUciMove = regexp.MustCompile("([a-h][1-8][a-h][1-8])([NBRQnbrq])?") 459 460 // GetMoveFromUci Generates all legal moves and matches the given UCI 461 // move string against them. If there is a match the actual move is returned. 462 // Otherwise MoveNone is returned. 463 // 464 // As this uses string creation and comparison this is not very efficient. 465 // Use only when performance is not critical. 466 func (mg *Movegen) GetMoveFromUci(posPtr *position.Position, uciMove string) Move { 467 matches := regexUciMove.FindStringSubmatch(uciMove) 468 if matches == nil { 469 return MoveNone 470 } 471 472 // get the parts from the pattern match 473 movePart := matches[1] 474 promotionPart := "" 475 if len(matches) == 3 { 476 // we allow lower case promotion letters 477 // not really UCI but many input files have this wrong 478 promotionPart = strings.ToUpper(matches[2]) 479 } 480 481 // check against all legal moves on position 482 mg.GenerateLegalMoves(posPtr, GenAll) 483 for _, m := range *mg.legalMoves { 484 if m.StringUci() == movePart+promotionPart { 485 // move found 486 return m 487 } 488 } 489 // move not found 490 return MoveNone 491 } 492 493 var regexSanMove = regexp.MustCompile("([NBRQK])?([a-h])?([1-8])?x?([a-h][1-8]|O-O-O|O-O)(=?([NBRQ]))?([!?+#]*)?") 494 495 // GetMoveFromSan Generates all legal moves and matches the given SAN 496 // move string against them. If there is a match the actual move is returned. 497 // Otherwise MoveNone is returned. 498 // 499 // As this uses string creation and comparison this is not very efficient. 500 // Use only when performance is not critical. 501 func (mg *Movegen) GetMoveFromSan(posPtr *position.Position, sanMove string) Move { 502 matches := regexSanMove.FindStringSubmatch(sanMove) 503 if matches == nil { 504 return MoveNone 505 } 506 507 // get parts 508 pieceType := matches[1] 509 disambFile := matches[2] 510 disambRank := matches[3] 511 toSquare := matches[4] 512 promotion := matches[6] 513 // checkSign := matches[7] - ignore 514 515 movesFound := 0 516 moveFromSAN := MoveNone 517 518 // check against all legal moves on position 519 mg.GenerateLegalMoves(posPtr, GenAll) 520 for _, genMove := range *mg.legalMoves { 521 522 // castling moves 523 if genMove.MoveType() == Castling { 524 kingToSquare := genMove.To() 525 var castlingString string 526 switch kingToSquare { 527 case SqG1: // white king side 528 fallthrough 529 case SqG8: // black king side 530 castlingString = "O-O" 531 case SqC1: // white queen side 532 fallthrough 533 case SqC8: // black queen side 534 castlingString = "O-O-O" 535 default: 536 log.Error("Move type CASTLING but wrong to square: %s %s", castlingString, kingToSquare.String()) 537 continue 538 } 539 if castlingString == toSquare { 540 moveFromSAN = genMove 541 movesFound++ 542 continue 543 } 544 } 545 546 // normal moves 547 moveTarget := genMove.To().String() 548 if moveTarget == toSquare { 549 550 // determine if piece types match - if not skip 551 legalPt := posPtr.GetPiece(genMove.From()).TypeOf() 552 legalPtChar := legalPt.Char() 553 if (len(pieceType) == 0 || legalPtChar != pieceType) && 554 (len(pieceType) != 0 || legalPt != Pawn) { 555 continue 556 } 557 558 // Disambiguation File 559 if len(disambFile) != 0 && genMove.From().FileOf().String() != disambFile { 560 continue 561 } 562 563 // Disambiguation Rank 564 if len(disambRank) != 0 && genMove.From().RankOf().String() != disambRank { 565 continue 566 } 567 568 // promotion 569 if (len(promotion) != 0 && genMove.PromotionType().Char() != promotion) || 570 (len(promotion) == 0 && genMove.MoveType() == Promotion) { 571 continue 572 } 573 574 // we should have our move if we end up here 575 moveFromSAN = genMove 576 movesFound++ 577 } 578 } 579 580 // we should only have one move here 581 if movesFound > 1 { 582 log.Warningf("SAN move %s is ambiguous (%d matches) on %s!", sanMove, movesFound, posPtr.StringFen()) 583 } else if movesFound == 0 || !moveFromSAN.IsValid() { 584 log.Warningf("SAN move not valid! SAN move %s not found on position: %s", sanMove, posPtr.StringFen()) 585 } else { 586 return moveFromSAN 587 } 588 // no move found 589 return MoveNone 590 } 591 592 // ValidateMove validates if a move is a valid legal move on the given position 593 func (mg *Movegen) ValidateMove(p *position.Position, move Move) bool { 594 if move == MoveNone { 595 return false 596 } 597 ml := mg.GenerateLegalMoves(p, GenAll) 598 for _, m := range *ml { 599 if move.MoveOf() == m { 600 return true 601 } 602 } 603 return false 604 } 605 606 // PvMove returns the current PV move 607 func (mg *Movegen) PvMove() Move { 608 return mg.pvMove 609 } 610 611 // KillerMoves returns a pointer to the killer moves array 612 func (mg *Movegen) KillerMoves() *[2]Move { 613 return &mg.killerMoves 614 } 615 616 // String returns a string representation of a MoveGen instance 617 func (mg *Movegen) String() string { 618 return fmt.Sprintf("MoveGen: { OnDemand Stage: { %d }, PV Move: %s Killer Move 1: %s Killer Move 2: %s }", 619 mg.currentODStage, mg.pvMove.String(), mg.killerMoves[0].String(), mg.killerMoves[1].String()) 620 } 621 622 // ////////////////////////////////////////////////////// 623 // // Private 624 // ////////////////////////////////////////////////////// 625 626 // States for the on demand move generator 627 const ( 628 odNew = iota 629 odPv = iota 630 od1 = iota 631 od2 = iota 632 od3 = iota 633 od4 = iota 634 od5 = iota 635 od6 = iota 636 od7 = iota 637 od8 = iota 638 odEnd = iota 639 ) 640 641 // This calls the actual generation of moves in phases. The phases match roughly 642 // the order of most promising moves first. 643 func (mg *Movegen) fillOnDemandMoveList(p *position.Position, mode GenMode, evasion bool) { 644 for mg.onDemandMoves.Len() == 0 && mg.currentODStage < odEnd { 645 switch mg.currentODStage { 646 case odNew: 647 mg.currentODStage = odPv 648 fallthrough 649 case odPv: 650 // If a pvMove is set we return it first and filter it out before 651 // returning a move 652 if mg.pvMove != MoveNone { 653 switch mode { 654 case GenAll: 655 mg.pvMovePushed = true 656 mg.onDemandMoves.PushBack(mg.pvMove) 657 case GenNonQuiet: 658 if p.IsCapturingMove(mg.pvMove) { 659 mg.pvMovePushed = true 660 mg.onDemandMoves.PushBack(mg.pvMove) 661 } 662 case GenQuiet: 663 if !p.IsCapturingMove(mg.pvMove) { 664 mg.pvMovePushed = true 665 mg.onDemandMoves.PushBack(mg.pvMove) 666 } 667 } 668 } 669 // decide which state we should continue with 670 // captures or non captures or both 671 if mode&GenNonQuiet != 0 { 672 mg.currentODStage = od1 673 } else { 674 mg.currentODStage = od4 675 } 676 case od1: // capture 677 mg.generatePawnMoves(p, GenNonQuiet, evasion, mg.onDemandEvasionTargets, mg.onDemandMoves) 678 mg.updateSortValues(p, mg.onDemandMoves) 679 mg.currentODStage = od2 680 case od2: 681 mg.generateMoves(p, GenNonQuiet, evasion, mg.onDemandEvasionTargets, mg.onDemandMoves) 682 mg.updateSortValues(p, mg.onDemandMoves) 683 mg.currentODStage = od3 684 case od3: 685 mg.generateKingMoves(p, GenNonQuiet, evasion, mg.onDemandEvasionTargets, mg.onDemandMoves) 686 mg.currentODStage = od4 687 case od4: 688 if mode&GenQuiet != 0 { 689 mg.currentODStage = od5 690 } else { 691 mg.currentODStage = odEnd 692 } 693 case od5: // non capture 694 mg.generatePawnMoves(p, GenQuiet, evasion, mg.onDemandEvasionTargets, mg.onDemandMoves) 695 mg.updateSortValues(p, mg.onDemandMoves) 696 mg.currentODStage = od6 697 case od6: 698 if !evasion { // no castlings when in check 699 mg.generateCastling(p, GenQuiet, mg.onDemandMoves) 700 mg.updateSortValues(p, mg.onDemandMoves) 701 } 702 mg.currentODStage = od7 703 case od7: 704 mg.generateMoves(p, GenQuiet, evasion, mg.onDemandEvasionTargets, mg.onDemandMoves) 705 mg.updateSortValues(p, mg.onDemandMoves) 706 mg.currentODStage = od8 707 case od8: 708 mg.generateKingMoves(p, GenQuiet, evasion, mg.onDemandEvasionTargets, mg.onDemandMoves) 709 mg.updateSortValues(p, mg.onDemandMoves) 710 mg.currentODStage = odEnd 711 case odEnd: 712 break 713 } 714 // sort the list according to sort values encoded in the move 715 if mg.onDemandMoves.Len() > 0 { 716 mg.onDemandMoves.Sort() 717 } 718 } // while onDemandMoves.empty() 719 } 720 721 // Move order heuristics based on history data. 722 func (mg *Movegen) updateSortValues(p *position.Position, moveList *moveslice.MoveSlice) { 723 us := p.NextPlayer() 724 for i := 0; i < len(*moveList); i++ { 725 move := &(*moveList)[i] 726 switch { 727 case move.MoveOf() == mg.pvMove: // PV move 728 (*move).SetValue(ValueMax) 729 case move.MoveOf() == mg.killerMoves[1]: // Killer 2 730 (*move).SetValue(-4001) 731 case move.MoveOf() == mg.killerMoves[0]: // Killer 1 732 (*move).SetValue(-4000) 733 case mg.historyData != nil: // historical search data 734 735 // History Count 736 // Moves that cause a beta cut in the search get an increasing value 737 // which favors many repetitions and deep searches. 738 // We use the history count to improve the sort value of a move 739 // If and how much a sort value has to be improved for a move is 740 // difficult to predict - this needs testing and experimentation. 741 // The current way is a hard cut for values <1000 and then 1 point 742 // per 1000 count points. 743 // It is also yet unclear if the history count table should be 744 // reused for several consecutive searches or just for one search. 745 // TODO: Testing 746 count := mg.historyData.HistoryCount[us][move.From()][move.To()] 747 value := Value(count / 100) 748 749 // Counter Move History 750 // When we have a counter move which caused a beta cut off before we 751 // bump up its sort value 752 // TODO: Testing 753 if mg.historyData.CounterMoves[p.LastMove().From()][p.LastMove().To()] == move.MoveOf() { 754 value += 500 755 } 756 757 // update move sort value 758 if value > 0 { // only touch the value if it would be improved 759 preValue := move.ValueOf() 760 (*move).SetValue(preValue + value) 761 // out.Printf("HistoryCount: %s = %d / %d ==> %d \n", move.StringUci(), count, preValue, preValue+value) 762 } 763 } 764 } 765 } 766 767 // getEvasionTargets returns the number of attackers and a Bitboard with target 768 // squares for generated moves when the position has check against the next 769 // player. Most of the moves will not even be generated as they will not 770 // have these target squares. These target squares cover the attacking 771 // (checker) piece and any squares in between the attacker and the king 772 // in case of the attacker being a slider. 773 // If we have more than one attacker we can skip everything apart from 774 // king moves. 775 func (mg *Movegen) getEvasionTargets(p *position.Position) Bitboard { 776 us := p.NextPlayer() 777 ourKing := p.KingSquare(us) 778 // find all target squares which either capture or block the attacker 779 evasionTargets := attacks.AttacksTo(p, ourKing, us.Flip()) 780 // we can only block attacks of sliders of there is not more 781 // than one attacker 782 if evasionTargets.PopCount() == 1 { 783 atck := evasionTargets.Lsb() 784 // sliding pieces 785 if p.GetPiece(atck).TypeOf() > Knight { 786 evasionTargets |= Intermediate(atck, ourKing) 787 } 788 } 789 return evasionTargets 790 } 791 792 func (mg *Movegen) generatePawnMoves(position *position.Position, mode GenMode, evasion bool, evasionTargets Bitboard, ml *moveslice.MoveSlice) { 793 794 nextPlayer := position.NextPlayer() 795 myPawns := position.PiecesBb(nextPlayer, Pawn) 796 oppPieces := position.OccupiedBb(nextPlayer.Flip()) 797 gamePhase := position.GamePhase() 798 piece := MakePiece(nextPlayer, Pawn) 799 800 // captures 801 if mode&GenNonQuiet != 0 { 802 803 // This algorithm shifts the own pawn bitboard in the direction of pawn captures 804 // and ANDs it with the opponents pieces. With this we get all possible captures 805 // and can easily create the moves by using a loop over all captures and using 806 // the backward shift for the from-Square. 807 // All moves get sort values so that sort order should be: 808 // captures: most value victim least value attacker - promotion piece value 809 // non captures: killer (TBD), promotions, castling, normal moves (position value) 810 // Values for sorting are descending - the most valuable move has the highest value. 811 // Values are not compatible to position evaluation values outside of the move 812 // generator. 813 814 // When we are in check only evasion moves are generated. E.g. all moves need to 815 // target these evasion squares. That is either capturing the attacker or blocking 816 // a sliding attacker. 817 818 var tmpCaptures, promCaptures Bitboard 819 820 for _, dir := range []Direction{West, East} { 821 // normal pawn captures - promotions first 822 tmpCaptures = ShiftBitboard(myPawns, nextPlayer.MoveDirection()+dir) & oppPieces 823 if evasion { 824 tmpCaptures &= evasionTargets 825 } 826 827 promCaptures = tmpCaptures & nextPlayer.PromotionRankBb() 828 // promotion captures 829 for promCaptures != 0 { 830 toSquare := promCaptures.PopLsb() 831 fromSquare := toSquare.To(nextPlayer.Flip().MoveDirection() - dir) 832 // value is the delta of values from the two pieces involved minus the promoted pawn 833 value := position.GetPiece(toSquare).ValueOf() - (2 * Pawn.ValueOf()) 834 // add the possible promotion moves to the move list and also add value of the promoted piece type 835 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Promotion, Queen, value+Queen.ValueOf())) 836 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Promotion, Knight, value+Knight.ValueOf())) 837 // rook and bishops are usually redundant to queen promotion (except in stale mate situations) 838 // therefore we give them lower sort order 839 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Promotion, Rook, value+Rook.ValueOf()-Value(2000))) 840 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Promotion, Bishop, value+Bishop.ValueOf()-Value(2000))) 841 } 842 843 // non promotion pawn captures 844 tmpCaptures &= ^nextPlayer.PromotionRankBb() 845 for tmpCaptures != 0 { 846 toSquare := tmpCaptures.PopLsb() 847 fromSquare := toSquare.To(nextPlayer.Flip().MoveDirection() - dir) 848 // value is the delta of values from the two pieces involved plus the positional value 849 value := position.GetPiece(toSquare).ValueOf() - position.GetPiece(fromSquare).ValueOf() + 850 PosValue(piece, toSquare, gamePhase) 851 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Normal, PtNone, value)) 852 } 853 } 854 855 // en passant captures 856 enPassantSquare := position.GetEnPassantSquare() 857 if enPassantSquare != SqNone { 858 for _, dir := range []Direction{West, East} { 859 tmpCaptures = ShiftBitboard(enPassantSquare.Bb(), nextPlayer.Flip().MoveDirection()+dir) & myPawns 860 if tmpCaptures != 0 { 861 fromSquare := tmpCaptures.PopLsb() 862 toSquare := fromSquare.To(nextPlayer.MoveDirection() - dir) 863 // value is the positional value of the piece at this game phase 864 value := PosValue(piece, toSquare, gamePhase) 865 ml.PushBack(CreateMoveValue(fromSquare, toSquare, EnPassant, PtNone, value)) 866 } 867 } 868 } 869 870 // if this is enabled we treat Queen and Knight promotions as non quiet moves 871 if config.Settings.Search.UsePromNonQuiet { 872 promMoves := ShiftBitboard(myPawns, nextPlayer.MoveDirection()) & 873 ^position.OccupiedAll() & 874 nextPlayer.PromotionRankBb() 875 if evasion { 876 promMoves &= evasionTargets 877 } 878 for promMoves != 0 { 879 toSquare := promMoves.PopLsb() 880 fromSquare := toSquare.To(nextPlayer.Flip().MoveDirection()) 881 // value for non captures is lowered by 10k 882 value := -Pawn.ValueOf() 883 // add the possible promotion moves to the move list and also add value of the promoted piece type 884 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Promotion, Queen, value+Queen.ValueOf())) 885 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Promotion, Knight, value+Knight.ValueOf())) 886 } 887 } 888 } 889 890 // non captures 891 if mode&GenQuiet != 0 { 892 893 // Move my pawns forward one step and keep all on not occupied squares 894 // Move pawns now on rank 3 (rank 6) another square forward to check for pawn doubles. 895 // Loop over pawns remaining on unoccupied squares and add moves. 896 897 // When we are in check only evasion moves are generated. E.g. all moves need to 898 // target these evasion squares. That is either capturing the attacker or blocking 899 // a sliding attacker. 900 901 // pawns - check step one to unoccupied squares 902 tmpMoves := ShiftBitboard(myPawns, nextPlayer.MoveDirection()) & ^position.OccupiedAll() 903 // pawns double - check step two to unoccupied squares 904 tmpMovesDouble := ShiftBitboard(tmpMoves&nextPlayer.PawnDoubleRank(), nextPlayer.MoveDirection()) & ^position.OccupiedAll() 905 906 if evasion { 907 tmpMoves &= evasionTargets 908 tmpMovesDouble &= evasionTargets 909 } 910 911 // single pawn steps - promotions first 912 promMoves := tmpMoves & nextPlayer.PromotionRankBb() 913 for promMoves != 0 { 914 toSquare := promMoves.PopLsb() 915 fromSquare := toSquare.To(nextPlayer.Flip().MoveDirection()) 916 // value for non captures is lowered by 10k 917 value := Value(-10_000) 918 // if this is enabled we treat Queen and Knight promotions as non quiet moves 919 // and they are generated there 920 if !config.Settings.Search.UsePromNonQuiet { 921 // add the possible promotion moves to the move list and also add value of the promoted piece type 922 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Promotion, Queen, value+Queen.ValueOf())) 923 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Promotion, Knight, value+Knight.ValueOf())) 924 } 925 // rook and bishops are usually redundant to queen promotion (except in stale mate situations) 926 // therefore we give them lower sort order 927 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Promotion, Rook, value+Rook.ValueOf()-Value(2000))) 928 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Promotion, Bishop, value+Bishop.ValueOf()-Value(2000))) 929 } 930 // double pawn steps 931 for tmpMovesDouble != 0 { 932 toSquare := tmpMovesDouble.PopLsb() 933 fromSquare := toSquare.To(nextPlayer.Flip().MoveDirection()). 934 To(nextPlayer.Flip().MoveDirection()) 935 value := Value(-10_000) + PosValue(piece, toSquare, gamePhase) 936 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Normal, PtNone, value)) 937 } 938 // normal single pawn steps 939 tmpMoves &= ^nextPlayer.PromotionRankBb() 940 for tmpMoves != 0 { 941 toSquare := tmpMoves.PopLsb() 942 fromSquare := toSquare.To(nextPlayer.Flip().MoveDirection()) 943 value := Value(-10_000) + PosValue(piece, toSquare, gamePhase) 944 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Normal, PtNone, value)) 945 } 946 } 947 } 948 949 func (mg *Movegen) generateCastling(position *position.Position, mode GenMode, ml *moveslice.MoveSlice) { 950 nextPlayer := position.NextPlayer() 951 occupiedBB := position.OccupiedAll() 952 953 // castling - pseudo castling - we will not check if we are in check after the move 954 // or if we have passed an attacked square with the king or if the king has been in check 955 956 if mode&GenQuiet != 0 && position.CastlingRights() != CastlingNone { 957 cr := position.CastlingRights() 958 if nextPlayer == White { // white 959 if cr.Has(CastlingWhiteOO) && Intermediate(SqE1, SqH1)&occupiedBB == 0 { 960 ml.PushBack(CreateMoveValue(SqE1, SqG1, Castling, PtNone, Value(-5000))) 961 } 962 if cr.Has(CastlingWhiteOOO) && Intermediate(SqE1, SqA1)&occupiedBB == 0 { 963 ml.PushBack(CreateMoveValue(SqE1, SqC1, Castling, PtNone, Value(-5000))) 964 } 965 } else { // black 966 if cr.Has(CastlingBlackOO) && Intermediate(SqE8, SqH8)&occupiedBB == 0 { 967 ml.PushBack(CreateMoveValue(SqE8, SqG8, Castling, PtNone, Value(-5000))) 968 } 969 if cr.Has(CastlingBlackOOO) && Intermediate(SqE8, SqA8)&occupiedBB == 0 { 970 ml.PushBack(CreateMoveValue(SqE8, SqC8, Castling, PtNone, Value(-5000))) 971 } 972 } 973 } 974 } 975 976 func (mg *Movegen) generateKingMoves(p *position.Position, mode GenMode, evasion bool, evasionTargets Bitboard, ml *moveslice.MoveSlice) { 977 us := p.NextPlayer() 978 them := us.Flip() 979 piece := MakePiece(us, King) 980 gamePhase := p.GamePhase() 981 kingSquareBb := p.PiecesBb(us, King) 982 fromSquare := kingSquareBb.PopLsb() 983 984 // pseudo attacks include all moves no matter if the king would be in check 985 pseudoMoves := GetAttacksBb(King, fromSquare, BbZero) 986 987 // captures 988 if mode&GenNonQuiet != 0 { 989 captures := pseudoMoves & p.OccupiedBb(them) 990 for captures != 0 { 991 toSquare := captures.PopLsb() 992 // in case we are in check we only generate king moves to target squares which 993 // are not attacked by the opponent 994 if !evasion || attacks.AttacksTo(p, toSquare, them).PopCount() == 0 { 995 value := p.GetPiece(toSquare).ValueOf() - p.GetPiece(fromSquare).ValueOf() + PosValue(piece, toSquare, gamePhase) 996 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Normal, PtNone, value)) 997 } 998 } 999 } 1000 1001 // non captures 1002 if mode&GenQuiet != 0 { 1003 nonCaptures := pseudoMoves &^ p.OccupiedAll() 1004 for nonCaptures != 0 { 1005 toSquare := nonCaptures.PopLsb() 1006 // in case we are in check we only generate king moves to target squares which 1007 // are not attacked by the opponent 1008 if !evasion || attacks.AttacksTo(p, toSquare, them).PopCount() == 0 { 1009 value := Value(-10_000) + PosValue(piece, toSquare, gamePhase) 1010 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Normal, PtNone, value)) 1011 } 1012 } 1013 } 1014 } 1015 1016 // generates officers moves using the attacks pre-computed with magic bitboards 1017 // Performance improvement to the previous loop based version: 1018 // Old version: 1019 // Test took 2.0049508s for 10.000.000 iterations 1020 // Test took 200 ns per iteration 1021 // Iterations per sec 4.987.653 1022 // This version: 1023 // Test took 1.516326s for 10.000.000 iterations 1024 // Test took 151 ns per iteration 1025 // Iterations per sec 6.594.887 1026 // Improvement: +32% 1027 func (mg *Movegen) generateMoves(position *position.Position, mode GenMode, evasion bool, evasionTargets Bitboard, ml *moveslice.MoveSlice) { 1028 nextPlayer := position.NextPlayer() 1029 gamePhase := position.GamePhase() 1030 occupiedBb := position.OccupiedAll() 1031 1032 // loop through all piece types, get pseudo attacks for the piece and 1033 // AND it with the opponents pieces. 1034 // For sliding pieces check if there are other pieces in between the 1035 // piece and the target square. If free this is a valid move (or 1036 // capture) 1037 // When we are in check (evasion=true) only evasion moves are generated. E.g. all 1038 // moves need to target these evasion squares. That is either capturing the 1039 // attacker or blocking a sliding attacker. 1040 1041 for pt := Knight; pt <= Queen; pt++ { 1042 pieces := position.PiecesBb(nextPlayer, pt) 1043 piece := MakePiece(nextPlayer, pt) 1044 1045 for pieces != 0 { 1046 fromSquare := pieces.PopLsb() 1047 1048 moves := GetAttacksBb(pt, fromSquare, occupiedBb) 1049 1050 // captures 1051 if mode&GenNonQuiet != 0 { 1052 captures := moves & position.OccupiedBb(nextPlayer.Flip()) 1053 if evasion { 1054 captures &= evasionTargets 1055 } 1056 for captures != 0 { 1057 toSquare := captures.PopLsb() 1058 value := position.GetPiece(toSquare).ValueOf() - position.GetPiece(fromSquare).ValueOf() + PosValue(piece, toSquare, gamePhase) 1059 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Normal, PtNone, value)) 1060 } 1061 } 1062 1063 // non captures 1064 if mode&GenQuiet != 0 { 1065 nonCaptures := moves &^ occupiedBb 1066 if evasion { 1067 nonCaptures &= evasionTargets 1068 } 1069 for nonCaptures != 0 { 1070 toSquare := nonCaptures.PopLsb() 1071 value := Value(-10_000) + PosValue(piece, toSquare, gamePhase) 1072 ml.PushBack(CreateMoveValue(fromSquare, toSquare, Normal, PtNone, value)) 1073 } 1074 } 1075 } 1076 } 1077 }