github.com/frankkopp/FrankyGo@v1.0.3/internal/attacks/attacks.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 attacks
    28  
    29  import (
    30  	"github.com/op/go-logging"
    31  	"golang.org/x/text/language"
    32  	"golang.org/x/text/message"
    33  
    34  	myLogging "github.com/frankkopp/FrankyGo/internal/logging"
    35  	"github.com/frankkopp/FrankyGo/internal/position"
    36  	. "github.com/frankkopp/FrankyGo/internal/types"
    37  )
    38  
    39  var out = message.NewPrinter(language.German)
    40  
    41  // Attacks is a data structure to store all attacks and defends of a position.
    42  type Attacks struct {
    43  	log *logging.Logger
    44  
    45  	// the position key for which the attacks have been calculated
    46  	Zobrist position.Key
    47  	// bitboards of attacked/defended squares for each color and each from square
    48  	// to get attackers us &^ ownPieces or & ownPieces for defenders
    49  	From [ColorLength][SqLength]Bitboard
    50  	// bitboards of attackers/defenders for each color and to square
    51  	// to get attackers us &^ ownPieces or & ownPieces for defenders
    52  	To [ColorLength][SqLength]Bitboard
    53  	// bitboards for all attacked/defended squares of a color
    54  	// to get attackers us &^ ownPieces or & ownPieces for defenders
    55  	All [ColorLength]Bitboard
    56  	// bitboards of attacked/defended squares for each color and each piece type
    57  	// to get attackers us &^ ownPieces or & ownPieces for defenders
    58  	Piece [ColorLength][PtLength]Bitboard
    59  	// sum of possible moves for each color (moves to ownPieces already excluded)
    60  	Mobility [ColorLength]int
    61  	// pawn attacks - squares attacked by pawn of the given color
    62  	Pawns [ColorLength]Bitboard
    63  	// pawn double - squares which are attacked twice by pawns of the given color
    64  	PawnsDouble [ColorLength]Bitboard
    65  }
    66  
    67  // NewAttacks creates a new instance of Attacks.
    68  func NewAttacks() *Attacks {
    69  	return &Attacks{
    70  		log: myLogging.GetLog(),
    71  	}
    72  }
    73  
    74  // Clear resets all fields of the Attacks instance without
    75  // new allocation by looping through all fields
    76  // This is considerably faster than creating a new instance
    77  // Benchmark/New_Instance-8   1.904.764  691.0 ns/op
    78  // Benchmark/Clear-8         13.043.875   91.7 ns/op.
    79  func (a *Attacks) Clear() {
    80  	a.Zobrist = 0
    81  	for sq := 0; sq < SqLength; sq++ {
    82  		a.From[White][sq] = BbZero
    83  		a.From[Black][sq] = BbZero
    84  		a.To[White][sq] = BbZero
    85  		a.To[Black][sq] = BbZero
    86  	}
    87  	for pt := PtNone; pt < PtLength; pt++ {
    88  		a.Piece[White][pt] = BbZero
    89  		a.Piece[Black][pt] = BbZero
    90  	}
    91  	a.All[White] = BbZero
    92  	a.All[Black] = BbZero
    93  	a.Mobility[White] = 0
    94  	a.Mobility[Black] = 0
    95  	a.Pawns[White] = 0
    96  	a.Pawns[Black] = 0
    97  	a.PawnsDouble[White] = 0
    98  	a.PawnsDouble[Black] = 0
    99  }
   100  
   101  // Compute calculates all attacks on the position.
   102  // Stores the positions zobrist key to be able to
   103  // check if the position is already computed.
   104  // if a position is called twice the already
   105  // stored attacks are untouched.
   106  func (a *Attacks) Compute(p *position.Position) {
   107  	if p.ZobristKey() == a.Zobrist {
   108  		a.log.Debugf("attacks compute: position was already computed")
   109  		return
   110  	}
   111  	a.Zobrist = p.ZobristKey()
   112  	a.nonPawnAttacks(p)
   113  	// TODO safe time with pawn hash table?
   114  	a.pawnAttacks(p)
   115  }
   116  
   117  // nonPawnAttacks calculates all attacks of non pawn pieces including king.
   118  func (a *Attacks) nonPawnAttacks(p *position.Position) {
   119  	ptList := [5]PieceType{King, Knight, Bishop, Rook, Queen}
   120  	var attacks Bitboard
   121  	allPieces := p.OccupiedAll()
   122  
   123  	// iterate over colors
   124  	for c := White; c <= Black; c++ {
   125  		myPieces := p.OccupiedBb(c)
   126  		// iterate over all piece types
   127  		for _, pt := range ptList {
   128  			// iterate over pieces of piece type
   129  			for pieces := p.PiecesBb(c, pt); pieces != BbZero; {
   130  				psq := pieces.PopLsb() // piece square
   131  				// attacks will include attacks to opponents pieces
   132  				// and defending own pieces
   133  				attacks = GetAttacksBb(pt, psq, allPieces)
   134  				// accumulate all attacks of this piece type for the color
   135  				a.From[c][psq] = attacks
   136  				a.Piece[c][pt] |= attacks
   137  				a.All[c] |= attacks
   138  				// store all attacks to the square
   139  				tmp := attacks
   140  				for tmp != BbZero {
   141  					toSq := tmp.PopLsb() // attacked square
   142  					a.To[c][toSq].PushSquare(psq)
   143  				}
   144  				a.Mobility[c] += (attacks &^ myPieces).PopCount()
   145  			}
   146  		}
   147  	}
   148  }
   149  
   150  // pawnAttacks calculate all attacks for pawns.
   151  func (a *Attacks) pawnAttacks(p *position.Position) {
   152  	a.Pawns[White] = ShiftBitboard(p.PiecesBb(White, Pawn), Northwest) | ShiftBitboard(p.PiecesBb(White, Pawn), Northeast)
   153  	a.Pawns[Black] = ShiftBitboard(p.PiecesBb(Black, Pawn), Northwest) | ShiftBitboard(p.PiecesBb(Black, Pawn), Northeast)
   154  	a.PawnsDouble[White] = ShiftBitboard(p.PiecesBb(White, Pawn), Northwest) & ShiftBitboard(p.PiecesBb(White, Pawn), Northeast)
   155  	a.PawnsDouble[Black] = ShiftBitboard(p.PiecesBb(Black, Pawn), Northwest) & ShiftBitboard(p.PiecesBb(Black, Pawn), Northeast)
   156  }
   157  
   158  // AttacksTo determines all attacks to the given square for the given color.
   159  func AttacksTo(p *position.Position, square Square, color Color) Bitboard {
   160  	// prepare en passant attacks
   161  	epAttacks := BbZero
   162  	enPassantSquare := p.GetEnPassantSquare()
   163  	if enPassantSquare != SqNone && enPassantSquare == square {
   164  		pawnSquare := enPassantSquare.To(color.Flip().MoveDirection())
   165  		epAttacker := pawnSquare.NeighbourFilesMask() & pawnSquare.RankOf().Bb() & p.PiecesBb(color, Pawn)
   166  		if epAttacker != BbZero {
   167  			epAttacks |= pawnSquare.Bb()
   168  		}
   169  	}
   170  
   171  	occupiedAll := p.OccupiedAll()
   172  
   173  	// this uses a reverse approach - it uses the target square as from square
   174  	// to generate attacks for each type and then intersects the result with
   175  	// the piece bitboard.
   176  
   177  	//      Pawns
   178  	return (GetPawnAttacks(color.Flip(), square) & p.PiecesBb(color, Pawn)) |
   179  		// Knight
   180  		(GetAttacksBb(Knight, square, occupiedAll) & p.PiecesBb(color, Knight)) |
   181  		// King
   182  		(GetAttacksBb(King, square, occupiedAll) & p.PiecesBb(color, King)) |
   183  		// Sliding rooks and queens
   184  		(GetAttacksBb(Rook, square, occupiedAll) & (p.PiecesBb(color, Rook) | p.PiecesBb(color, Queen))) |
   185  		// Sliding bishops and queens
   186  		(GetAttacksBb(Bishop, square, occupiedAll) & (p.PiecesBb(color, Bishop) | p.PiecesBb(color, Queen))) |
   187  		// consider en passant attacks
   188  		epAttacks
   189  }
   190  
   191  // RevealedAttacks returns sliding attacks after a piece has been removed to reveal new attacks.
   192  // It is only necessary to look at slider pieces as only their attacks can be revealed.
   193  func RevealedAttacks(p *position.Position, square Square, occupied Bitboard, color Color) Bitboard {
   194  	// Sliding rooks and queens
   195  	return (GetAttacksBb(Rook, square, occupied) & (p.PiecesBb(color, Rook) | p.PiecesBb(color, Queen)) & occupied) |
   196  		// Sliding bishops and queens
   197  		(GetAttacksBb(Bishop, square, occupied) & (p.PiecesBb(color, Bishop) | p.PiecesBb(color, Queen)) & occupied)
   198  }