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 }