github.com/frankkopp/FrankyGo@v1.0.3/internal/attacks/attacks_test.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  	"os"
    31  	"path"
    32  	"runtime"
    33  	"testing"
    34  	"time"
    35  
    36  	logging2 "github.com/op/go-logging"
    37  	"github.com/stretchr/testify/assert"
    38  
    39  	"github.com/frankkopp/FrankyGo/internal/config"
    40  	"github.com/frankkopp/FrankyGo/internal/logging"
    41  	"github.com/frankkopp/FrankyGo/internal/position"
    42  	. "github.com/frankkopp/FrankyGo/internal/types"
    43  )
    44  
    45  var logTest *logging2.Logger
    46  
    47  // make tests run in the projects root directory.
    48  func init() {
    49  	_, filename, _, _ := runtime.Caller(0)
    50  	dir := path.Join(path.Dir(filename), "../..")
    51  	err := os.Chdir(dir)
    52  	if err != nil {
    53  		panic(err)
    54  	}
    55  }
    56  
    57  // Setup the tests.
    58  func TestMain(m *testing.M) {
    59  	config.Setup()
    60  	logTest = logging.GetTestLog()
    61  	code := m.Run()
    62  	os.Exit(code)
    63  }
    64  
    65  func TestAttacks(t *testing.T) {
    66  	p := position.NewPosition("r1b1k2r/pppp1ppp/2n2n2/1Bb1p2q/4P3/2NP1N2/1PP2PPP/R1BQK2R w KQkq -")
    67  	a := NewAttacks()
    68  	a.Compute(p)
    69  	assert.Equal(t, p.ZobristKey(), a.Zobrist)
    70  	assert.EqualValues(t, SqF1.Bb()|SqG1.Bb(), a.From[White][SqH1]&^p.OccupiedBb(White))
    71  	assert.EqualValues(t, SqD8.Bb()|SqE7.Bb()|SqF8.Bb(), a.From[Black][SqE8]&^p.OccupiedBb(Black))
    72  	assert.EqualValues(t, SqC6.Bb()|SqH5.Bb(), a.To[Black][SqE5]&p.OccupiedBb(Black))
    73  }
    74  
    75  func TestCompareWithPseudo(t *testing.T) {
    76  	p := position.NewPosition("r1b1k2r/pppp1ppp/2n2n2/1Bb1p2q/4P3/2NP1N2/1PP2PPP/R1BQK2R w KQkq -")
    77  	a := NewAttacks()
    78  	a.nonPawnAttacks(p)
    79  	for sq := SqA1; sq <= SqH8; sq++ {
    80  		if p.GetPiece(sq) == PieceNone || p.GetPiece(sq).TypeOf() == Pawn {
    81  			continue
    82  		}
    83  		c := p.GetPiece(sq).ColorOf()
    84  		pt := p.GetPiece(sq).TypeOf()
    85  
    86  		// compare the Attacks build with Attack and Magic bitboards
    87  		// to the attacks calculated with the lopp in the local function
    88  		magicAttacks := a.From[c][sq]
    89  		nonMagicAttacks := buildAttacks(p, pt, sq)
    90  
    91  		// out.Println("Non Magic Attacks:\n", magicStringBoard())
    92  		// out.Println("Build Attacks:\n", nonMagicStringBoard())
    93  
    94  		assert.EqualValues(t, magicAttacks, nonMagicAttacks)
    95  
    96  		// out.Println("==================================================")
    97  	}
    98  }
    99  
   100  func TestAttacksTo(t *testing.T) {
   101  	var p *position.Position
   102  	var attacksTo Bitboard
   103  
   104  	p = position.NewPosition("2brr1k1/1pq1b1p1/p1np1p1p/P1p1p2n/1PNPPP2/2P1BNP1/4Q1BP/R2R2K1 w - -")
   105  	attacksTo = AttacksTo(p, SqE5, White)
   106  	logTest.Debug("\n", attacksTo.StringBoard())
   107  	logTest.Debug(attacksTo.StringGrouped())
   108  	assert.EqualValues(t, 740294656, attacksTo)
   109  
   110  	attacksTo = AttacksTo(p, SqF1, White)
   111  	logTest.Debug("\n", attacksTo.StringBoard())
   112  	logTest.Debug(attacksTo.StringGrouped())
   113  	assert.EqualValues(t, 20552, attacksTo)
   114  
   115  	attacksTo = AttacksTo(p, SqD4, White)
   116  	logTest.Debug("\n", attacksTo.StringBoard())
   117  	logTest.Debug(attacksTo.StringGrouped())
   118  	assert.EqualValues(t, 3407880, attacksTo)
   119  
   120  	attacksTo = AttacksTo(p, SqD4, Black)
   121  	logTest.Debug("\n", attacksTo.StringBoard())
   122  	logTest.Debug(attacksTo.StringGrouped())
   123  	assert.EqualValues(t, 4483945857024, attacksTo)
   124  
   125  	attacksTo = AttacksTo(p, SqD6, Black)
   126  	logTest.Debug("\n", attacksTo.StringBoard())
   127  	logTest.Debug(attacksTo.StringGrouped())
   128  	assert.EqualValues(t, 582090251837636608, attacksTo)
   129  
   130  	attacksTo = AttacksTo(p, SqF8, Black)
   131  	logTest.Debug("\n", attacksTo.StringBoard())
   132  	logTest.Debug(attacksTo.StringGrouped())
   133  	assert.EqualValues(t, 5769111122661605376, attacksTo)
   134  
   135  	p = position.NewPosition("r3k2r/1ppn3p/2q1q1n1/4P3/2q1Pp2/6R1/pbp2PPP/1R4K1 b kq e3")
   136  	attacksTo = AttacksTo(p, SqE5, Black)
   137  	logTest.Debug("\n", attacksTo.StringBoard())
   138  	logTest.Debug(attacksTo.StringGrouped())
   139  	assert.EqualValues(t, 2339760743907840, attacksTo)
   140  
   141  	attacksTo = AttacksTo(p, SqB1, Black)
   142  	logTest.Debug("\n", attacksTo.StringBoard())
   143  	logTest.Debug(attacksTo.StringGrouped())
   144  	assert.EqualValues(t, 1280, attacksTo)
   145  
   146  	attacksTo = AttacksTo(p, SqG3, White)
   147  	logTest.Debug("\n", attacksTo.StringBoard())
   148  	logTest.Debug(attacksTo.StringGrouped())
   149  	assert.EqualValues(t, 40960, attacksTo)
   150  
   151  	attacksTo = AttacksTo(p, SqE4, Black)
   152  	logTest.Debug("\n", attacksTo.StringBoard())
   153  	logTest.Debug(attacksTo.StringGrouped())
   154  	assert.EqualValues(t, 4398113619968, attacksTo)
   155  }
   156  
   157  func TestRevealedAttacks(t *testing.T) {
   158  	p := position.NewPosition("1k1r3q/1ppn3p/p4b2/4p3/8/P2N2P1/1PP1R1BP/2K1Q3 w - -")
   159  	occ := p.OccupiedAll()
   160  
   161  	sq := SqE5
   162  
   163  	attacksTo := AttacksTo(p, sq, White) | AttacksTo(p, sq, Black)
   164  	logTest.Debug("Direct\n", attacksTo.StringBoard())
   165  	logTest.Debug(attacksTo.StringGrouped())
   166  	assert.EqualValues(t, 2286984186302464, attacksTo)
   167  
   168  	// take away bishop on f6
   169  	attacksTo.PopSquare(SqF6)
   170  	occ.PopSquare(SqF6)
   171  
   172  	attacksTo |= RevealedAttacks(p, sq, occ, White) | RevealedAttacks(p, sq, occ, Black)
   173  	logTest.Debug("Revealed\n", attacksTo.StringBoard())
   174  	logTest.Debug(attacksTo.StringGrouped())
   175  	assert.EqualValues(t, Bitboard(9225623836668989440), attacksTo)
   176  
   177  	// take away rook on e2
   178  	attacksTo.PopSquare(SqE2)
   179  	occ.PopSquare(SqE2)
   180  
   181  	attacksTo |= RevealedAttacks(p, sq, occ, White) | RevealedAttacks(p, sq, occ, Black)
   182  	logTest.Debug("Revealed\n", attacksTo.StringBoard())
   183  	logTest.Debug(attacksTo.StringGrouped())
   184  	assert.EqualValues(t, Bitboard(9225623836668985360), attacksTo)
   185  }
   186  
   187  // to compare magic bitboard attacks with loop generated attacks.
   188  func buildAttacks(p *position.Position, pt PieceType, sq Square) Bitboard {
   189  	occupiedAll := p.OccupiedAll()
   190  	attacks := BbZero
   191  	pseudoTo := GetPseudoAttacks(pt, sq) // & ^myPieces
   192  	// iterate over all target squares of the piece
   193  	if pt < Bishop { // king, knight
   194  		attacks = pseudoTo
   195  	} else {
   196  		for tmp := pseudoTo; tmp != BbZero; {
   197  			to := tmp.PopLsb()
   198  			if Intermediate(sq, to)&occupiedAll == 0 {
   199  				attacks.PushSquare(to)
   200  			}
   201  		}
   202  	}
   203  	return attacks
   204  }
   205  
   206  func Test_TimingAttacks(t *testing.T) {
   207  	// defer profile.Start(profile.CPUProfile, profile.ProfilePath("../bin")).Stop()
   208  	// go tool pprof -http=localhost:8080 FrankyGo_Test.exe cpu.pprof
   209  
   210  	p := position.NewPosition("r1b1k2r/pppp1ppp/2n2n2/1Bb1p2q/4P3/2NP1N2/1PP2PPP/R1BQK2R w KQkq -")
   211  	a := NewAttacks()
   212  
   213  	const rounds = 5
   214  	const iterations uint64 = 10_000_000
   215  
   216  	for r := 1; r <= rounds; r++ {
   217  		out.Printf("Round %d\n", r)
   218  		start := time.Now()
   219  		for i := uint64(0); i < iterations; i++ {
   220  			a.Clear()
   221  			a.Compute(p)
   222  		}
   223  		elapsed := time.Since(start)
   224  		out.Printf("Test took %s for %d iterations\n", elapsed, iterations)
   225  		out.Printf("Test took %d ns per iteration\n", elapsed.Nanoseconds()/int64(iterations))
   226  		out.Printf("Iterations per sec %d\n", int64(iterations*1e9)/elapsed.Nanoseconds())
   227  	}
   228  	_ = a
   229  }
   230  
   231  func Benchmark_NonPawnAttacks(b *testing.B) {
   232  	p := position.NewPosition("6k1/p1qb1p1p/1p3np1/2b2p2/2B5/2P3N1/PP2QPPP/4N1K1 b - -")
   233  	a := NewAttacks()
   234  
   235  	f1 := func() {
   236  		a.Clear()
   237  		a.Compute(p)
   238  	}
   239  
   240  	benchmarks := []struct {
   241  		name string
   242  		f    func()
   243  	}{
   244  		{"New Instance", f1},
   245  	}
   246  
   247  	for _, bm := range benchmarks {
   248  		b.Run(bm.name, func(b *testing.B) {
   249  			for i := 0; i < b.N; i++ {
   250  				bm.f()
   251  			}
   252  		})
   253  	}
   254  	_ = a
   255  }
   256  
   257  func BenchmarkAttacks_ClearNewVsClear(b *testing.B) {
   258  	a := NewAttacks()
   259  	f1 := func() { a = NewAttacks() }
   260  	f2 := func() { a.Clear() }
   261  	benchmarks := []struct {
   262  		name string
   263  		f    func()
   264  	}{
   265  		{"New Instance", f1},
   266  		{"Clear", f2},
   267  	}
   268  	for _, bm := range benchmarks {
   269  		b.Run(bm.name, func(b *testing.B) {
   270  			for i := 0; i < b.N; i++ {
   271  				bm.f()
   272  			}
   273  		})
   274  	}
   275  	_ = a
   276  }