diff --git a/diag/perft.go b/diag/perft.go index 8a3bbe2..9acab77 100644 --- a/diag/perft.go +++ b/diag/perft.go @@ -57,7 +57,7 @@ func perftBreakdown(G *chess.Game, depth int) (nodes, checks, castles, mates, ca } toMove := G.ActiveColor() - notToMove := []piece.Color{game.Black, game.White}[toMove] + notToMove := piece.OtherColor[toMove] isChecked := G.Check(toMove) ml := G.LegalMoves() diff --git a/diag/perftsuite_test.go b/diag/perftsuite_test.go index 0a872cb..888b8d8 100644 --- a/diag/perftsuite_test.go +++ b/diag/perftsuite_test.go @@ -3,13 +3,14 @@ package diag import ( "errors" "fmt" - "github.com/andrewbackes/chess/epd" - "github.com/andrewbackes/chess/position" "os" "strconv" "strings" "testing" "time" + + "github.com/andrewbackes/chess/epd" + "github.com/andrewbackes/chess/position" ) func TestPerftSuite(t *testing.T) { @@ -127,3 +128,48 @@ func checkPerft(p *position.Position, depth int, nodes uint64) error { } return nil } + +func BenchmarkPerftSuite(b *testing.B) { + f, err := os.Open("perftsuite.epd") + if err != nil { + b.Fatal(err) + } + tests, err := epd.Read(f) + if err != nil { + b.Fatal(err) + } + + maxdepth := 3 + tests = tests[:10] + if strings.ToLower(os.Getenv("BENCH_DEEP_PERFT_SUITE")) == "true" { + maxdepth = 5 + } else if testing.Short() { + tests = tests[:5] + maxdepth = 1 + } + for i, test := range tests { + if len(test.Operations) > maxdepth+1 { + test.Operations = test.Operations[:maxdepth+1] + } + + b.Run(fmt.Sprintf("EPD_%02d", i+1), func(b *testing.B) { + benchPerftSuite(b, test) + }) + } +} + +func benchPerftSuite(b *testing.B, test *epd.EPD) { + for depth := range test.Operations { + if depth == 0 { + // Perft with any position and depth 0 always returns 1. Don't need to bench. + continue + } + + b.Run(fmt.Sprint("Depth ", depth), func(b *testing.B) { + for i := 0; i < b.N; i++ { + // No need to check result, this is done in TestPerftSuite. + Perft(test.Position, depth) + } + }) + } +} diff --git a/game/game_test.go b/game/game_test.go index f750dd2..f3ad138 100644 --- a/game/game_test.go +++ b/game/game_test.go @@ -3,14 +3,14 @@ package game import ( "errors" "fmt" + "testing" + "time" + + "github.com/andrewbackes/chess/fen" "github.com/andrewbackes/chess/piece" "github.com/andrewbackes/chess/position" "github.com/andrewbackes/chess/position/move" "github.com/andrewbackes/chess/position/square" - "strconv" - "strings" - "testing" - "time" ) // This is an example of how you might play a game. @@ -38,7 +38,7 @@ func ExampleLegalMoves() { game, _ := gameFromFEN("8/8/1KP5/3r4/8/8/8/k7 w - - 0 1") moves := game.LegalMoves() fmt.Println(moves) - // Output : map[b6b7:{} b6a7:{} c6c7:{} b6a6:{} b6c7:{}] + // Output: map[c6c7:{} b6a6:{} b6c7:{} b6b7:{} b6a7:{}] } func TestGamePrint(t *testing.T) { @@ -223,57 +223,142 @@ func TestStalemate(t *testing.T) { } } -func gameFromFEN(fen string) (*Game, error) { +func TestGameResultInProgress(t *testing.T) { g := New() - p, err := fromFEN(fen) - g.Positions[0] = p - return g, err + if g.Result() != "*" { + t.Fail() + } } -func fromFEN(board string) (*position.Position, error) { - b := position.New() - b.Clear() - // remove the /'s and replace the numbers with that many spaces - // so that there is a 1-1 mapping from bytes to squares. - justBoard := strings.Split(board, " ")[0] - parsedBoard := strings.Replace(justBoard, "/", "", 9) - for i := 1; i < 9; i++ { - parsedBoard = strings.Replace(parsedBoard, strconv.Itoa(i), strings.Repeat(" ", i), -1) +func gameFromFEN(FEN string) (*Game, error) { + p, err := fen.Decode(FEN) + if err != nil { + return nil, err } - if len(parsedBoard) < 64 { - return nil, errors.New("fen: could not parse position") + return &Game{ + control: nil, + Tags: map[string]string{ + "FEN": FEN, + "Setup": "1", + }, + Positions: []*position.Position{p}, + }, nil +} + +// BenchmarkGame benchmarks full games of various lengths using moves as move.Move struct and strings in PCN and SAN notation. +func BenchmarkGame(b *testing.B) { + benchCases := []struct { + Name string + PCNMoves []string + }{ + { + "2-move-fools-mate", + []string{"f3", "e5", "g4", "Qh4#"}, + }, + { + "Random-game-#1338_half-moves-11", + []string{"e4", "Na6", "a3", "Nf6", "Nc3", "Rg8", "Bc4", "Nxe4", "Qh5", "Nec5", "Bxf7#"}, + }, + { + "Random-game-#6589_half-moves-25", + []string{"c4", "f5", "Qc2", "g5", "h3", "h6", "h4", "g4", "b3", "d5", "cxd5", "a6", "Qc4", "c5", "h5", "Qc7", "Qxa6", "Kf7", "Qd3", "Kg7", "Qe3", "Qd8", "Qe6", "g3", "Qg6#"}, + }, + { + "Random-game-#8159_half-moves-50", + []string{"b3", "c5", "Nf3", "d5", "Ng5", "Na6", "Nxf7", "Kd7", "Nc3", "Qb6", "Nxd5", "g6", "g3", "Qd6", "e3", "Nc7", "a3", "Ne8", "Be2", "Qc7", "h4", "b5", "Bf3", "Qf4", "Rb1", "b4", "h5", "Bg7", "Bg2", "Qe5", "c4", "Qxc3", "Be4", "e5", "Rh2", "Nef6", "Qe2", "Qxb3", "a4", "Ne8", "Qf3", "Qxc4", "Nh6", "Bxh6", "Qf6", "Ba6", "Ra1", "Qb5", "Qg5", "Qe2#"}, + }, + { + "Random-game-#2537_half-moves-100", + []string{"c4", "f5", "h4", "Na6", "b4", "h5", "g4", "b6", "Nh3", "c6", "d3", "Nf6", "a3", "Rb8", "Rh2", "Rh7", "Ng5", "Bb7", "b5", "Bc8", "Nh3", "hxg4", "Ng1", "Qc7", "d4", "Nh5", "Rh1", "Ra8", "Ra2", "Rb8", "bxa6", "Qg3", "Kd2", "Qf4+", "Kd3", "d5", "Nc3", "c5", "Qd2", "Qh2", "Qb2", "Nf4+", "Kd2", "b5", "h5", "Qxf2", "Nh3", "e5", "cxd5", "e4", "Qb3", "Bb7", "Qxb5+", "Kf7", "Rb2", "e3+", "Kc2", "Re8", "axb7", "Re5", "Rb1", "Rxh5", "Ng5+", "Kf6", "Qc6+", "Kxg5", "Nb5", "c4", "Rg1", "Qxg1", "Nxa7", "Ng2", "Qb5", "Bb4", "Bb2", "Kg6", "d6", "Rd5", "Qc6", "Ba5", "b8=R", "Rh4", "Qc8", "Qf2", "Bxg2", "Qxe2+", "Kc1", "Qxb2+", "Kd1", "Qc3", "R1b7", "Qxa3", "Qd8", "Qc3", "Bf3", "Kh5", "Rc8", "gxf3", "Rc6", "Rhxd4#"}, + }, + { + "Random-game-#177_half-moves-250", + []string{"h3", "a6", "a4", "b6", "e3", "h5", "a5", "d5", "Ke2", "bxa5", "Ra4", "g5", "Rxa5", "Nh6", "d3", "Bf5", "Ra1", "Ng4", "h4", "Ne5", "Ra4", "e6", "Rb4", "Nbc6", "Rc4", "Ng4", "g3", "Nxf2", "Rg4", "Rh7", "Kf3", "Nb8", "Bd2", "Bd6", "Rb4", "g4+", "Kxf2", "Kf8", "Bc1", "Ra7", "Rb7", "Ke7", "Qf3", "Be5", "Qxg4", "Bd4", "b4", "Kd6", "b5", "Ke7", "Ke2", "Bh8", "Bg2", "Bd4", "Qg6", "a5", "c3", "Rg7", "c4", "Qe8", "Rh2", "Qf8", "Bh3", "Rxg6", "Bf1", "Nc6", "bxc6", "Qc8", "Bh3", "Bh8", "Nd2", "d4", "Kf1", "Rxg3", "Rb6", "Be4", "Rb7", "e5", "Kf2", "Rf3+", "Ke1", "Qf5", "Ndxf3", "Bf6", "Rb8", "dxe3", "Bxf5", "Ra8", "Nh3", "Bg7", "Kd1", "Kf6", "Bh7", "Bxh7", "Rd2", "Ra6", "Rb5", "e2+", "Kc2", "e1=Q", "c5", "Rb6", "Nfg5", "Ke7", "Kb1", "Qxd2", "Bxd2", "Bf6", "Ka2", "Bh8", "Be3", "Bxd3", "Rxa5", "Rxc6", "Nf4", "exf4", "Ra6", "Bc3", "Kb3", "Bc4+", "Kc2", "Kf6", "Ne4+", "Kg7", "Ng3", "Ba1", "Nxh5+", "Kg8", "Kc1", "Bb3", "Ra5", "fxe3", "Ra3", "Rf6", "Ra8+", "Kh7", "Ra2", "Bc3", "Rf2", "e2", "Rxf6", "Bd4", "Rh6+", "Kg8", "Ra6", "Ba2", "Rg6+", "Kh7", "Rg7+", "Kh8", "Rg5", "e1=R+", "Kc2", "f6", "Rg3", "Re6", "Rd3", "Re2+", "Rd2", "Rf2", "Nf4", "Ba1", "Nd5", "Bb2", "Nc3", "Bxc3", "Kxc3", "Rh2", "Re2", "Bc4", "Kd2", "Bb3", "Ke1", "Bd1", "Re5", "c6", "Rh5+", "Kg7", "Kxd1", "Rc2", "Rh7+", "Kg6", "h5+", "Kxh7", "Kxc2", "Kg7", "h6+", "Kh7", "Kb2", "f5", "Kb1", "Kg8", "Kc2", "Kh8", "Kb3", "Kg8", "Kb2", "Kh8", "Kb3", "Kh7", "Ka2", "Kxh6", "Ka1", "Kg7", "Kb2", "f4", "Kc1", "Kh6", "Kb1", "Kg7", "Kc2", "Kf6", "Kc3", "Kf5", "Kc4", "Ke5", "Kb4", "f3", "Ka4", "Ke6", "Ka5", "Ke5", "Ka6", "Kf6", "Kb7", "Kg6", "Kxc6", "Kg7", "Kd5", "Kf8", "Ke6", "Ke8", "Kf6", "Kd8", "Kg5", "Kc8", "Kg4", "Kc7", "Kg3", "f2", "Kg4", "f1=B", "Kf5", "Bh3+", "Kf4", "Bf5", "Ke3", "Bg4", "c6", "Kxc6"}, + }, + { + "Random-game-#577_half-moves-500", + []string{"b4", "c6", "e3", "e6", "Qe2", "Na6", "d4", "Nc7", "Bb2", "a6", "a3", "g6", "h4", "Bc5", "e4", "e5", "Nd2", "f6", "Qd1", "Qe7", "Ke2", "g5", "f3", "f5", "hxg5", "h6", "Rh2", "Bxd4", "Rh4", "Rb8", "Rf4", "b6", "Ke1", "Qc5", "Rc1", "Qd6", "Bc4", "Nb5", "Bd5", "Qe6", "Bc4", "hxg5", "Nb1", "Qf6", "Rxf5", "Kf8", "Bxd4", "Nc3", "Bxe5", "Rb7", "Bh2", "Qxf5", "Ba2", "Qxf3", "Qd6+", "Ne7", "Nh3", "Qf7", "Be6", "Qf5", "Kd2", "Na4", "Nc3", "Ke8", "Rh1", "Qd5+", "Qxd5", "Rxh3", "Nb5", "Rg3", "Bf5", "Nc5", "Rd1", "Rxg2+", "Ke1", "d6", "Bh7", "Rc7", "Qxg5", "Ng8", "Qf6", "Rg1+", "Kf2", "Rcg7", "Bxd6", "Rd7", "Rc1", "Nd3+", "cxd3", "Rh1", "Qf4", "Rxd6", "Nxd6+", "Kd7", "d4", "Kc7", "Qf7+", "Kb8", "Bf5", "Bxf5", "exf5", "Nh6", "Qe7", "Ka8", "Nb7", "Rh4", "Na5", "Nxf5", "Rd1", "Kb8", "Rb1", "Nh6", "Qh7", "Nf5", "Kg1", "b5", "Qa7+", "Kxa7", "d5", "Rg4+", "Kf1", "Nh6", "Nb7", "Rc4", "Kf2", "Ng8", "Kg1", "Rc2", "Rf1", "Rc1", "Kh1", "cxd5", "Na5", "Kb6", "a4", "Rb1", "axb5", "Ra1", "Rb1", "Nf6", "Rc1", "Ng8", "Kg1", "Rxa5", "Rc2", "Ra3", "Re2", "Ra4", "Re5", "Ra2", "Re7", "Kxb5", "Kh1", "Nh6", "Rb7+", "Kc4", "Rg7", "Kc3", "Rd7", "Kb3", "Rb7", "Ka3", "Ra7", "Ra1+", "Kg2", "Ka4", "Rf7", "Nf5", "b5", "Ra2+", "Kf1", "Nd6", "Rf5", "Rf2+", "Ke1", "axb5", "Rf3", "Rb2", "Rh3", "Ne4", "Kf1", "Rc2", "Rh4", "Kb3", "Rh3+", "Ka2", "Rd3", "Rc6", "Rd1", "Ng5", "Kg2", "Nh7", "Re1", "Rf6", "Rc1", "Ka3", "Rc4", "Ka2", "Rb4", "Ka3", "Rf4", "Ka2", "Rxf6", "Ng5", "Ra6+", "Kb3", "Kg3", "Kb4", "Kg2", "Kb3", "Kh2", "Ne4", "Rg6", "Ka4", "Rg1", "Nf2", "Re1", "Ka3", "Re6", "Kb4", "Rg6", "Nh3", "Rg4+", "Kc5", "Ra4", "Nf4", "Re4", "b4", "Rd4", "Ne6", "Kg2", "Kd6", "Rg4", "b3", "Rd4", "Nd8", "Kf3", "Nf7", "Kg2", "Ke7", "Kh2", "Nh8", "Rxd5", "Kf6", "Rd1", "Nf7", "Rb1", "Nh8", "Rd1", "b2", "Kg3", "b1=B", "Rh1", "Kg5", "Rd1", "Nf7", "Rd2", "Bf5", "Rb2", "Nd6", "Rb1", "Kh6", "Rb2", "Bc2", "Rb5", "Bf5", "Kf4", "Bh7", "Rb2", "Kg6", "Kg4", "Nf5", "Rg2", "Ng3", "Rc2", "Bg8", "Rd2", "Bd5", "Rd3", "Nh5", "Rc3", "Bb3", "Kh3", "Ba2", "Rc2", "Ng7", "Rd2", "Kh6", "Rc2", "Kh5", "Rf2", "Kg6", "Re2", "Be6+", "Kh2", "Kf5", "Rd2", "Kf6", "Rb2", "Kg6", "Rc2", "Kf6", "Rd2", "Kg6", "Rd4", "Kh6", "Re4", "Bh3", "Re8", "Be6", "Re7", "Bb3", "Re1", "Ba4", "Kh3", "Bc2", "Rh1", "Kg5", "Kg2", "Kg4", "Rc1", "Bb3", "Rc5", "Bf7", "Rg5+", "Kf4", "Kh1", "Nh5", "Rg3", "Be8", "Rc3", "Bd7", "Rc1", "Be6", "Rc4+", "Kf5", "Ra4", "Ng7", "Ra7", "Kg6", "Ra1", "Kf5", "Kg2", "Kg5", "Rf1", "Nf5", "Kh1", "Nh4", "Kg1", "Ng2", "Kxg2", "Bb3", "Rf7", "Kh6", "Rg7", "Ba4", "Rf7", "Kg6", "Kf3", "Be8", "Ra7", "Bb5", "Ra2", "Be8", "Kf4", "Kh7", "Ra6", "Bb5", "Ke5", "Bc6", "Kd4", "Be4", "Ra2", "Bg2", "Rb2", "Kg8", "Rb6", "Kg7", "Kc4", "Kf7", "Rc6", "Ke8", "Kb3", "Bf3", "Rc7", "Bg4", "Kc2", "Be2", "Rc6", "Bc4", "Rh6", "Bb3+", "Kb1", "Bd5", "Rg6", "Ba8", "Kc1", "Bf3", "Ra6", "Bb7", "Ra7", "Ba8", "Ra3", "Bb7", "Kc2", "Bh1", "Kc3", "Kf7", "Kb4", "Kg7", "Rc3", "Kh6", "Kb3", "Kg7", "Rh3", "Kf7", "Rxh1", "Ke7", "Ka3", "Kf8", "Rh5", "Ke8", "Kb2", "Kd8", "Rh3", "Kc8", "Rh7", "Kd8", "Kc2", "Ke8", "Rb7", "Kd8", "Kd3", "Kc8", "Rb8+", "Kd7", "Rb6", "Ke8", "Rb4", "Kd8", "Ke3", "Ke7", "Rd4", "Kf8", "Rd6", "Ke7", "Rd7+", "Ke8", "Ra7", "Kf8", "Kf3", "Kg8", "Ra4", "Kg7", "Re4", "Kh8", "Ke2", "Kg7", "Kf2", "Kh7", "Rh4+", "Kg6", "Rh7", "Kf6", "Rb7", "Kg6", "Kg1", "Kg5", "Ra7", "Kg6", "Rb7", "Kh5", "Rb3", "Kg4", "Rb8", "Kf3", "Rh8", "Kf4", "Re8", "Kg5", "Rf8", "Kh4", "Rf4+", "Kh5", "Rf2", "Kg4", "Kh1", "Kh5", "Rf4", "Kg5", "Rd4", "Kg6", "Kg2", "Kg5", "Kg1", "Kg6", "Rd6+", "Kg7", "Rh6", "Kxh6"}, + }, + { + "Random-game-#1077_half-moves-724", + []string{"b4", "a5", "c3", "b6", "e4", "Ra7", "g3", "b5", "Qg4", "c5", "Bh3", "h6", "Qe2", "g5", "Bxd7+", "Bxd7", "c4", "g4", "a4", "Bf5", "bxc5", "f6", "d4", "Bd7", "axb5", "e6", "Nh3", "Ra6", "Qf1", "Rc6", "f4", "Qe7", "Nd2", "Kd8", "Ba3", "Rb6", "Qf2", "Kc7", "Nb1", "Qg7", "Qe3", "Na6", "Ng5", "Qg6", "Kf2", "Qxe4", "Qe2", "Bd6", "d5", "h5", "Kf1", "Qf3+", "Qxf3", "Be5", "Ke2", "Kc8", "Kf2", "Bb8", "d6", "f5", "c6", "Nf6", "Qc3", "Rh7", "Ke2", "Rf7", "Qd2", "Re7", "Qc1", "Rxb5", "h4", "Rb2+", "Kd3", "Ne8", "Qc3", "Nc5+", "Ke3", "Na6", "Bxb2", "Bxc6", "d7+", "Kc7", "Rd1", "Bb5", "Nh7", "Nd6", "cxb5+", "Nc5", "Ra4", "Nc8", "Qc4", "Kb6", "dxc8=R", "Nd3", "Qc2", "Nc5", "Kd2", "Nb7", "Qd3", "Rf7", "Kc3", "Rd7", "Qe3+", "Rd4", "Bc1", "Ka7", "Ba3", "Nc5", "Rd8", "Nd7", "Bb2", "Nc5", "Ra3", "Rxd8", "b6+", "Ka6", "Rh1", "Nd3", "Qd2", "Rc8+", "Kb3", "Kb7", "Re1", "Rc6", "Ng5", "Rc1", "Ba1", "Rc7", "bxc7", "Kb6", "Bh8", "Ka7", "Rd1", "Kb7", "Qe2", "Ba7", "Qh2", "Ka8", "Ra1", "Ne5", "Ka4", "Bg1", "Qc2", "Nd7", "Qc4", "Ka7", "Qd3", "Bb6", "c8=N+", "Kb7", "Nd6+", "Kc6", "Qa6", "Nc5+", "Ka3", "Nb7", "Kb3", "e5", "Rxa5", "e4", "Re5", "Nc5+", "Kc4", "Kc7", "Rg1", "Nb7", "Nb5+", "Kd8", "Qa7", "Ba5", "Qd4+", "Nd6+", "Kc5", "Bb6+", "Kc6", "Kc8", "Qd1", "Bxg1", "Na7+", "Kd8", "Qxg1", "Nc4", "Na3", "e3", "Nf3", "Nd6", "Nd2", "e2", "Qc5", "Nf7", "Re4", "e1=R", "Nc2", "Re3", "Nc4", "Rb3", "Qb6+", "Rxb6+", "Kc5", "Nh6", "Rd4+", "Rd6", "Rd5", "Kc7", "Rd3", "Rd4", "Nb6", "Rd6", "Bd4", "Re6", "Be3", "Re7", "Nbc8", "Re8", "Kb4", "Rxc8", "Na1", "Nf7", "Rc3+", "Kd6", "Kb3", "Ke6", "Ka2", "Ne5", "Rc7", "Nd7", "Bd4", "Ra8", "Bg1", "Rh8", "Rb7", "Kf7", "Bc5", "Rh7", "Nc8", "Kg6", "Rb8", "Nf8", "Rb6+", "Kg7", "Rb1", "Kf6", "Ne7", "Rf7", "Ba7", "Nd7", "Nc8", "Rf8", "Ne7", "Rh8", "Be3", "Ne5", "Bc1", "Nd7", "Kb2", "Rd8", "Ng8+", "Rxg8", "Be3", "Kg7", "Re1", "Nf6", "Ka3", "Rf8", "Nc2", "Ne4", "Bb6", "Kh6", "Rxe4", "Kg7", "Bg1", "Rf7", "Re7", "Kg8", "Re1", "Kg7", "Bb6", "Re7", "Ka2", "Re2", "Ba5", "Rh2", "Rc1", "Kh8", "Bc7", "Rh3", "Bd8", "Kh7", "Nb4", "Rh2+", "Rc2", "Rf2", "Be7", "Kg6", "Bf8", "Rxf4", "Kb1", "Rf2", "Be7", "Rh2", "Bf6", "f4", "Rc8", "Rf2", "Rc6", "Ra2", "Nxa2", "Kf5", "Bh8", "Ke4", "Rb6", "Kf5", "Rb7", "f3", "Ka1", "f2", "Nc3", "Kg6", "Ne2", "Kh6", "Bg7+", "Kh7", "Rb8", "f1=N", "Bb2", "Ne3", "Ba3", "Nd1", "Rb1", "Nb2", "Nc1", "Kg6", "Bf8", "Nc4", "Bb4", "Na3", "Na2", "Kg7", "Ba5", "Kh6", "Rb4", "Kg6", "Nc1", "Nc2+", "Kb2", "Na3", "Kc3", "Kg7", "Nb3", "Nc4", "Nc1", "Ne5", "Bd8", "Kh7", "Bc7", "Kg6", "Bb8", "Nd7", "Rc4", "Kf7", "Kd4", "Nf8", "Nb3", "Kg6", "Ke5", "Nd7+", "Kd4", "Kh7", "Rc1", "Kg6", "Bf4", "Kf7", "Kd5", "Nf8", "Rc2", "Kf6", "Rc3", "Kf5", "Be3", "Ne6", "Bh6", "Nf8", "Bg7", "Kg6", "Kc4", "Kxg7", "Na5", "Kh6", "Nb7", "Nd7", "Kb3", "Ne5", "Ka3", "Nf3", "Ka4", "Ng5", "Rc1", "Kg7", "Re1", "Kg8", "Re4", "Kf7", "Re1", "Nh7", "Rf1+", "Nf6", "Kb5", "Kf8", "Rh1", "Nd7", "Nd8", "Ne5", "Nb7", "Nd7", "Rd1", "Kg7", "Re1", "Kf7", "Nd8+", "Kg6", "Re8", "Nc5", "Re4", "Kf6", "Ka5", "Kg7", "Kb5", "Kf8", "Ra4", "Nd3", "Ra8", "Ke7", "Ra2", "Kxd8", "Rh2", "Ne5", "Ra2", "Nd3", "Kc6", "Nc5", "Kd5", "Kc7", "Ra6", "Nxa6", "Kc4", "Kd8", "Kb3", "Nc7", "Kb4", "Ke8", "Kb3", "Nb5", "Ka2", "Nc3+", "Ka3", "Nd1", "Kb3", "Nf2", "Kb4", "Kf8", "Ka3", "Nd1", "Ka2", "Kf7", "Kb1", "Kf8", "Kc2", "Nc3", "Kc1", "Kg7", "Kc2", "Nd5", "Kd1", "Kg6", "Kd2", "Nf4", "Kc2", "Kh7", "Kd2", "Kh6", "Ke3", "Kg7", "Kd4", "Kf8", "Ke3", "Nh3", "Kd2", "Kg7", "Kc1", "Nf2", "Kb1", "Kf7", "Kc1", "Nh3", "Kb1", "Ke8", "Ka2", "Kd7", "Kb1", "Kd8", "Kc2", "Ng1", "Kc3", "Ke8", "Kc4", "Nh3", "Kb5", "Nf4", "gxf4", "Kf8", "Ka4", "Kf7", "Ka3", "Kg8", "Kb2", "Kf7", "Kc2", "Kg7", "Kd2", "Kf7", "f5", "Kf6", "Ke1", "Ke5", "Kd1", "g3", "Ke2", "Ke4", "Ke1", "Ke3", "Kd1", "Kd4", "Kc2", "Ke5", "Kb3", "Kd5", "Ka2", "Ke5", "Ka1", "g2", "Kb1", "Kf4", "Kc2", "g1=Q", "Kd3", "Kf3", "Kc2", "Qf1", "f6", "Qa1", "f7", "Kg2", "f8=R", "Qa3", "Kd1", "Kh3", "Rc8", "Kg2", "Rf8", "Qa7", "Rf5", "Qg7", "Rf1", "Qb7", "Rh1", "Qh7", "Kd2", "Kf3", "Ra1", "Qh8", "Kc2", "Qb2+", "Kxb2", "Ke3", "Rc1", "Kd4", "Rg1", "Kc5", "Rg3", "Kd6", "Kb1", "Kc5", "Rg6", "Kb4", "Rg7", "Ka3", "Rg8", "Kb3", "Kc1", "Kb4", "Rg7", "Ka3", "Ra7+", "Kb3", "Kd1", "Kc4", "Kd2", "Kd4", "Rg7", "Ke4", "Kc3", "Kf3", "Kd3", "Kf2", "Rb7", "Ke1", "Kc4", "Ke2", "Rb8", "Kf3", "Ra8", "Ke4", "Rf8", "Ke5", "Ra8", "Kf5", "Ra1", "Ke6", "Ra7", "Ke5", "Rb7", "Ke6", "Re7+", "Kxe7", "Kc5", "Kf8", "Kb4", "Ke8", "Ka5", "Kf7", "Kb5", "Kg7", "Kc5", "Kh7", "Kd5", "Kh8", "Ke6", "Kg8", "Kf6", "Kf8", "Kg6", "Kg8", "Kf5", "Kh7", "Ke5", "Kg6", "Ke6", "Kh7", "Kd5", "Kg6", "Ke5", "Kh7", "Ke4", "Kh8", "Kd5", "Kg7", "Ke6", "Kf8", "Kd5", "Kf7", "Ke5", "Ke7", "Kd5", "Ke8", "Kd4", "Kd8", "Ke4", "Kc7", "Kd4", "Kb8", "Kc3", "Kc7", "Kb4", "Kb6", "Ka3", "Kc7", "Kb3", "Kd6", "Kc4", "Kc7", "Kb4", "Kd6", "Kb3", "Kc5", "Ka3", "Kc4", "Ka4", "Kd4", "Ka5", "Kd5", "Ka4", "Ke6", "Kb3", "Ke5", "Ka4", "Kd5", "Kb3", "Kd6", "Kc3", "Kc6", "Kc2", "Kd6", "Kc3", "Ke5", "Kd3", "Kd6", "Ke3", "Kd7", "Kf3", "Kc6", "Kf2", "Kd6", "Ke2", "Kc7", "Kd1", "Kb6", "Kc1", "Ka6", "Kd1", "Ka7", "Kc2", "Kb7", "Kc1", "Kc8"}, + }, } - p := map[rune]piece.Type{ - 'P': piece.Pawn, 'p': piece.Pawn, - 'N': piece.Knight, 'n': piece.Knight, - 'B': piece.Bishop, 'b': piece.Bishop, - 'R': piece.Rook, 'r': piece.Rook, - 'Q': piece.Queen, 'q': piece.Queen, - 'K': piece.King, 'k': piece.King} - color := map[rune]piece.Color{ - 'P': piece.White, 'p': piece.Black, - 'N': piece.White, 'n': piece.Black, - 'B': piece.White, 'b': piece.Black, - 'R': piece.White, 'r': piece.Black, - 'Q': piece.White, 'q': piece.Black, - 'K': piece.White, 'k': piece.Black} - // adjust the bitboards: - for pos := 0; pos < len(parsedBoard); pos++ { - if pos > 64 { - break + for i, bc := range benchCases { + if testing.Short() && i%2 == 0 { + // Skip every even benchmark if short flag. + continue } - k := rune(parsedBoard[pos]) - if _, ok := p[k]; ok { - b.Put(piece.New(color[k], p[k]), square.Square(63-pos)) - //b.bitBoard[color[k]][p[k]] |= (1 << uint(63-pos)) - } - } - return b, nil -} + b.Run(bc.Name, func(b *testing.B) { + // Check if benchmark game is valid. + g := New() + for n := 0; n < len(bc.PCNMoves); n++ { + m, err := g.Position().ParseMove(bc.PCNMoves[n]) + if err != nil { + b.Fatalf("Error parsing move #%d: %v", n+1, err) + } + _, err = g.MakeMove(m) + if err != nil { + b.Fatalf("Error making move #%d: %v", n+1, err) + } + } + if g.Status() == InProgress { + b.Fatal("Game status after last benchmark move should not be InProgress") + } + b.Logf("GameStatus after %d half-moves: %v", len(g.Positions)-1, g.Status()) -func TestGameResultInProgress(t *testing.T) { - g := New() - if g.Result() != "*" { - t.Fail() + // Generate slices for PCN and struct moves benchmarks. + PCNMoves := make([]string, 0, len(bc.PCNMoves)) + StructMoves := make([]move.Move, 0, len(bc.PCNMoves)) + for _, p := range g.Positions[1:] { + PCNMoves = append(PCNMoves, p.LastMove.String()) + StructMoves = append(StructMoves, p.LastMove) + } + + b.Run("SAN-Moves", func(b *testing.B) { + for i := 0; i < b.N; i++ { + g := New() + for n := 0; n < len(bc.PCNMoves); n++ { + m, err := g.Position().ParseMove(bc.PCNMoves[n]) + if err != nil { + b.Fatalf("Error parsing move #%d: %v", n+1, err) + } + _, err = g.MakeMove(m) + if err != nil { + b.Fatalf("Error making move #%d: %v", n+1, err) + } + } + } + }) + + if testing.Short() { + // Skip other benchmarks if short flag. + return + } + + b.Run("PCN-Moves", func(b *testing.B) { + for i := 0; i < b.N; i++ { + g := New() + for n := 0; n < len(PCNMoves); n++ { + _, err := g.MakeMove(move.Parse(PCNMoves[n])) + if err != nil { + b.Fatalf("Error making move #%d: %v", n+1, err) + } + } + } + }) + + b.Run("Struct-Moves", func(b *testing.B) { + for i := 0; i < b.N; i++ { + g := New() + for n := 0; n < len(StructMoves); n++ { + _, err := g.MakeMove(StructMoves[n]) + if err != nil { + b.Fatalf("Error making move #%d: %v", n+1, err) + } + } + } + }) + }) } } diff --git a/piece/color.go b/piece/color.go index 5d7f18b..db15841 100644 --- a/piece/color.go +++ b/piece/color.go @@ -18,14 +18,21 @@ const ( NoColor Color = 2 ) +const COLOR_COUNT = 2 + // Colors can be used to loop through the colors via range. -var Colors = [2]Color{White, Black} +var Colors = [COLOR_COUNT]Color{White, Black} + +// OtherColor can be used to get opponent's color. E.g. `oponentColor := piece.OtherColor[position.ActiveColor]`. +var OtherColor = [COLOR_COUNT]Color{Black, White} + +var colorStrings = [COLOR_COUNT]string{"White", "Black"} func (c Color) String() string { - return map[Color]string{ - White: "White", - Black: "Black", - }[c] + if c >= COLOR_COUNT { + return "" + } + return colorStrings[c] } func (c Color) MarshalJSON() ([]byte, error) { diff --git a/piece/color_test.go b/piece/color_test.go new file mode 100644 index 0000000..6009cc3 --- /dev/null +++ b/piece/color_test.go @@ -0,0 +1,47 @@ +package piece + +import "testing" + +func TestColorString(t *testing.T) { + testCases := []struct { + name string + c Color + want string + }{ + {"White", White, "White"}, + {"Black", Black, "Black"}, + {"NoColor", NoColor, ""}, + {"Color(4)", Color(4), ""}, + //{"Color(-1)", Color(-1), ""}, // Build error: constant -1 overflows Color. + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if s := tc.c.String(); s != tc.want { + t.Errorf("%#v.String() = %s, want %s", tc.c, s, tc.want) + } + }) + } +} + +const benchValidColorCount = 2 // White, Black. + +func BenchmarkColorString(b *testing.B) { + b.Run("Valid-Colors", func(b *testing.B) { + for i := 0; i < b.N; i++ { + // Is Color(0), Color(1), Color(0), Color(1), ... + s := Color(i % benchValidColorCount).String() + if s == "" { + b.Fatal("Valid color should not have a empty string output") + } + } + }) + b.Run("Invalid-Colors", func(b *testing.B) { + for i := 0; i < b.N; i++ { + // Is Color(2), Color(3), Color(2), Color(3), ... + s := Color(benchValidColorCount + i%benchValidColorCount).String() + if s != "" { + b.Fatal("Invalid color should have a empty string output") + } + } + }) +} diff --git a/piece/piece.go b/piece/piece.go index 6c9bf9e..0ab791b 100644 --- a/piece/piece.go +++ b/piece/piece.go @@ -8,6 +8,8 @@ import ( // Type is a player's piece. Ex: King, Queen, etc. type Type uint8 +const TYPE_COUNT = 7 // Including piece.None. Improves code readability. + // Possible pieces. const ( None Type = iota @@ -19,16 +21,13 @@ const ( King ) +var typeStrings = [TYPE_COUNT]string{" ", "p", "n", "b", "r", "q", "k"} + func (T Type) String() string { - return map[Type]string{ - None: " ", - Pawn: "p", - Knight: "n", - Bishop: "b", - Rook: "r", - Queen: "q", - King: "k", - }[T] + if T >= TYPE_COUNT { + return "" + } + return typeStrings[T] } // Piece represents a chess piece. @@ -46,15 +45,20 @@ func (P Piece) String() string { return t } +var figurines = [COLOR_COUNT][TYPE_COUNT]rune{ + [TYPE_COUNT]rune{' ', 0x2659, 0x2658, 0x2657, 0x2656, 0x2655, 0x2654}, + [TYPE_COUNT]rune{' ', 0x265F, 0x265E, 0x265D, 0x265C, 0x265B, 0x265A}, +} + // Figurine returns a chess piece icon func (P Piece) Figurine() string { if P.Color == NoColor || P.Type == None { return " " } - return string(map[Color]map[Type]rune{ - White: {Pawn: 0x2659, Knight: 0x2658, Bishop: 0x2657, Rook: 0x2656, Queen: 0x2655, King: 0x2654}, - Black: {Pawn: 0x265F, Knight: 0x265E, Bishop: 0x265D, Rook: 0x265C, Queen: 0x265B, King: 0x265A}, - }[P.Color][P.Type]) + if P.Color >= COLOR_COUNT || P.Type >= TYPE_COUNT { + return "\x00" + } + return string(figurines[P.Color][P.Type]) } // New returns a new chess piece type. diff --git a/piece/piece_test.go b/piece/piece_test.go index 2b217ba..580addf 100644 --- a/piece/piece_test.go +++ b/piece/piece_test.go @@ -3,27 +3,181 @@ package piece import ( "encoding/json" "fmt" - "github.com/stretchr/testify/assert" + "reflect" "testing" ) func TestPiecePrint(t *testing.T) { - p := New(White, Pawn) - if fmt.Sprint(p) != "P" { - t.Fail() + testCases := []struct { + name string + p Piece + want string + }{ + {"White-Pawn", New(White, Pawn), "P"}, + {"Black-Pawn", New(Black, Pawn), "p"}, + {"NoColor-Pawn", New(NoColor, Pawn), "p"}, + {"Color(4)-Pawn", New(Color(4), Pawn), "p"}, + {"White-None", New(White, None), " "}, + {"Black-None", New(Black, None), " "}, + {"NoColor-None", New(NoColor, None), " "}, + {"Color(4)-None", New(Color(4), None), " "}, + {"White-Rook", New(White, Rook), "R"}, + {"Black-Queen", New(Black, Queen), "q"}, + {"NoColor-Knight", New(NoColor, Knight), "n"}, + {"Color(4)-King", New(Color(4), King), "k"}, + {"White-Type(10)", New(White, Type(10)), ""}, + {"Black-Type(10)", New(Black, Type(10)), ""}, + {"NoColor-Type(10)", New(NoColor, Type(10)), ""}, + {"Color(4)-Type(10)", New(Color(4), Type(10)), ""}, + //{"Color(-1)-Type(-1)", New(Color(-1), Type(-1)), ""}, // Build error: constant -1 overflows Color/Type. + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if s := fmt.Sprint(tc.p); s != tc.want { + t.Errorf("%#v.String() = %#v, want %#v", tc.p, s, tc.want) + } + }) } } func TestColorUnmarshalJson(t *testing.T) { blob := `["White"]` + want := []Color{White} var c []Color - json.Unmarshal([]byte(blob), &c) - assert.Equal(t, []Color{White}, c) + err := json.Unmarshal([]byte(blob), &c) + if err != nil || !reflect.DeepEqual(c, want) { + t.Fatalf("json.Unmarshal(%#v, &c) = %#v, want %#v", blob, err, error(nil)) + } else if !reflect.DeepEqual(c, want) { + t.Fatalf("json.Unmarshal(%#v, &c); c = %#v, want %#v", blob, c, want) + } } func TestColorMarshalJson(t *testing.T) { j := []Color{White} + want := `["White"]` result, err := json.Marshal(j) - assert.Equal(t, nil, err) - assert.Equal(t, `["White"]`, string(result)) + if err != nil || string(result) != want { + t.Fatalf("json.Marshal(%#v) = (%s, %#v), want (%s, %#v)", j, result, err, want, error(nil)) + } +} + +func TestTypeString(t *testing.T) { + testCases := []struct { + name string + ty Type + want string + }{ + {"Pawn", Pawn, "p"}, + {"Rook", Rook, "r"}, + {"King", King, "k"}, + {"None", None, " "}, + {"Type(10)", Type(10), ""}, + //{"Type(-1)", Type(-1), ""}, // Build error: constant -1 overflows Type. + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if s := tc.ty.String(); s != tc.want { + t.Errorf("%#v.String() = %#v, want %#v", tc.ty, s, tc.want) + } + }) + } +} + +func TestPieceFigurine(t *testing.T) { + testCases := []struct { + name string + p Piece + want string + }{ + {"White-Pawn", New(White, Pawn), "♙"}, + {"Black-Pawn", New(Black, Pawn), "♟"}, + {"NoColor-Pawn", New(NoColor, Pawn), " "}, + {"Black-None", New(Black, None), " "}, + {"NoColor-None", New(NoColor, None), " "}, + {"Color(4)-Pawn", New(Color(4), Pawn), "\x00"}, + {"Black-Type(10)", New(Black, Type(10)), "\x00"}, + {"Color(4)-Type(10)", New(Color(4), Type(10)), "\x00"}, + //{"Color(-1)-Type(-1)", New(Color(-1), Type(-1)), ""}, // Build error: constant -1 overflows Color/Type. + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if f := tc.p.Figurine(); f != tc.want { + t.Errorf("%#v.Figurine() = %#v, want %#v", tc.p, f, tc.want) + } + }) + } +} + +const benchValidTypesCount = 7 // None, Pawn, ..., King. + +func BenchmarkTypeString(b *testing.B) { + b.Run("Valid-Types", func(b *testing.B) { + for i := 0; i < b.N; i++ { + // Is Type(0), Type(1), ..., Type(6), Type(0), Type(1), ... + s := Type(i % benchValidTypesCount).String() + if s == "" { + b.Fatal("Valid type should not have a empty string output") + } + } + }) + b.Run("Invalid-Types", func(b *testing.B) { + for i := 0; i < b.N; i++ { + // Is Type(7), Type(8), ..., Type(13), Type(7), Type(8), ... + s := Type(benchValidTypesCount + i%benchValidTypesCount).String() + if s != "" { + b.Fatal("Invalid type should have a empty string output") + } + } + }) +} + +func BenchmarkPieceFigurine(b *testing.B) { + b.Run("Valid-Colors_Valid-Types", func(b *testing.B) { + for i := 0; i < b.N; i++ { + // In the piece following colors and types are present. + // Color(0), Color(1), Color(2), Color(0), Color(1), Color(2), ... + // Type(0), Type(1), ..., Type(6), Type(0), Type(1), ... + p := Piece{Color(i % benchValidColorCount), Type(i % benchValidTypesCount)} + s := p.Figurine() + if s == "\x00" { + b.Fatalf("Figurine of piece with valid color and type %#v should not have an empty string output", p) + } + } + }) + b.Run("Invalid-Colors_Valid-Types", func(b *testing.B) { + for i := 0; i < b.N; i++ { + // In the piece following colors and types are present. + // Color(3), Color(4), Color(5), Color(3), Color(4), Color(5), ... + // Type(0), Type(1), ..., Type(6), Type(0), Type(1), ... + p := Piece{Color(benchValidColorCount + i%benchValidColorCount), Type(i % benchValidTypesCount)} + s := p.Figurine() + if s != "\x00" && s != " " { + b.Fatalf("Figurine of piece with invalid color and valid type %#v, should have an empty string output, got: %#v", p, s) + } + } + }) + b.Run("Valid-Colors_Invalid-Types", func(b *testing.B) { + for i := 0; i < b.N; i++ { + // In the piece following colors and types are present. + // Color(0), Color(1), Color(2), Color(0), Color(1), Color(2), ... + // Type(7), Type(8), ..., Type(13), Type(7), Type(8), ... + p := Piece{Color(i % benchValidColorCount), Type(benchValidTypesCount + i%benchValidTypesCount)} + s := p.Figurine() + if s != "\x00" && s != " " { + b.Fatalf("Figurine of piece with valid color and invalid type %#v should have an empty string output, got: %#v", p, s) + } + } + }) + b.Run("Invalid-Colors_Invalid-Types", func(b *testing.B) { + for i := 0; i < b.N; i++ { + // In the piece following colors and types are present. + // Color(3), Color(4), Color(5), Color(0), Color(1), Color(2), ... + // Type(7), Type(8), ..., Type(13), Type(7), Type(8), ... + p := Piece{Color(benchValidColorCount + i%benchValidColorCount), Type(benchValidTypesCount + i%benchValidTypesCount)} + s := p.Figurine() + if s != "\x00" && s != " " { + b.Fatalf("Figurine of piece with invalid color and type %#v should have an empty string output, got: %#v", p, s) + } + } + }) } diff --git a/position/bitboards.go b/position/bitboards.go index 77d6790..e5b3a4e 100644 --- a/position/bitboards.go +++ b/position/bitboards.go @@ -2,6 +2,7 @@ package position import ( "fmt" + "github.com/andrewbackes/chess/piece" "github.com/andrewbackes/chess/position/square" ) diff --git a/position/json.go b/position/json.go index 9b80b40..5481af3 100644 --- a/position/json.go +++ b/position/json.go @@ -11,7 +11,7 @@ func (p *Position) MarshalJSON() ([]byte, error) { Board string *Alias }{ - Board: BitBoards(p.bitBoard).MailBox(), + Board: p.bitBoards().MailBox(), Alias: (*Alias)(p), }) } diff --git a/position/json_test.go b/position/json_test.go new file mode 100644 index 0000000..c764d34 --- /dev/null +++ b/position/json_test.go @@ -0,0 +1,28 @@ +package position + +import ( + "testing" +) + +func TestMarshalJSON(t *testing.T) { + testCases := []struct { + name string + position testPosition + want string + }{ + {"Empty", testPosition{}, `{"Board":" ","moveNumber":1,"enPassant":64,"castlingRights":{"0":{"0":true,"1":true},"1":{"0":true,"1":true}},"activeColor":"White","movesLeft":{},"clock":{},"lastMove":{"source":64,"destination":64}}`}, + {"Initial", nil, `{"Board":"RNBKQBNRPPPPPPPP pppppppprnbkqbnr","moveNumber":1,"enPassant":64,"castlingRights":{"0":{"0":true,"1":true},"1":{"0":true,"1":true}},"activeColor":"White","movesLeft":{},"clock":{},"lastMove":{"source":64,"destination":64}}`}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + p := tc.position.Position() + jsonBytes, err := p.MarshalJSON() + json := string(jsonBytes) + if json != tc.want || err != nil { + t.Logf("Position:\n%v", p) + t.Errorf("*Position.MarshalJSON() =\n\t([]byte(\"%s\"), %v)\n, want\n\t([]byte(\"%s\"), %v)", json, err, tc.want, error(nil)) + } + }) + } +} diff --git a/position/move/move.go b/position/move/move.go index 6bb7ff3..5a04cb1 100644 --- a/position/move/move.go +++ b/position/move/move.go @@ -2,9 +2,10 @@ package move import ( + "time" + "github.com/andrewbackes/chess/piece" "github.com/andrewbackes/chess/position/square" - "time" ) // Move represents the action that transitions one chess position to another. @@ -30,12 +31,16 @@ func Parse(algebraic string) Move { to := square.Parse(algebraic[2:4]) promote := piece.None if len(algebraic) > 4 { - p := make(map[string]piece.Type) - p = map[string]piece.Type{ - "Q": piece.Queen, "N": piece.Knight, "B": piece.Bishop, "R": piece.Rook, - "q": piece.Queen, "n": piece.Knight, "b": piece.Bishop, "r": piece.Rook, + switch string(algebraic[len(algebraic)-1]) { + case "Q", "q": + promote = piece.Queen + case "R", "r": + promote = piece.Rook + case "B", "b": + promote = piece.Bishop + case "N", "n": + promote = piece.Knight } - promote = p[string(algebraic[len(algebraic)-1])] } return Move{ Source: from, diff --git a/position/movegen.go b/position/movegen.go index 693a445..69d1519 100644 --- a/position/movegen.go +++ b/position/movegen.go @@ -25,10 +25,13 @@ func (p *Position) LegalMoves() map[move.Move]struct{} { // an attacked square are not included. func (p *Position) Moves() map[move.Move]struct{} { moves := make(map[move.Move]struct{}) + if p.ActiveColor >= piece.NoColor { + return moves + } add := func(m move.Move) { moves[m] = struct{}{} } - notToMove := piece.Color((p.ActiveColor + 1) % 2) + notToMove := piece.Color((p.ActiveColor + 1) % piece.COLOR_COUNT) p.genPawnMoves(p.ActiveColor, notToMove, p.EnPassant, add) p.genKnightMoves(p.ActiveColor, notToMove, add) p.genDiagnalMoves(p.ActiveColor, notToMove, add) @@ -37,6 +40,7 @@ func (p *Position) Moves() map[move.Move]struct{} { return moves } +// Panics if toMove or notToMove is not White or Black. func (p *Position) genKnightMoves(toMove, notToMove piece.Color, add func(move.Move)) { //piece.Knights: pieces := p.bitBoard[toMove][piece.Knight] @@ -52,6 +56,7 @@ func (p *Position) genKnightMoves(toMove, notToMove piece.Color, add func(move.M } } +// Panics if toMove or notToMove is not White or Black. func (p *Position) genDiagnalMoves(toMove, notToMove piece.Color, add func(move.Move)) { // piece.Bishops/piece.Queens: pieces := p.bitBoard[toMove][piece.Bishop] | p.bitBoard[toMove][piece.Queen] @@ -74,6 +79,7 @@ func (p *Position) genDiagnalMoves(toMove, notToMove piece.Color, add func(move. } } +// Panics if toMove or notToMove is not White or Black. func (p *Position) genStraightMoves(toMove, notToMove piece.Color, add func(move.Move)) { // Rooks/piece.Queens: pieces := p.bitBoard[toMove][piece.Rook] | p.bitBoard[toMove][piece.Queen] @@ -96,6 +102,7 @@ func (p *Position) genStraightMoves(toMove, notToMove piece.Color, add func(move } } +// Panics if toMove or notToMove is not White or Black. func (p *Position) genKingMoves(toMove, notToMove piece.Color, castlingRights map[piece.Color]map[board.Side]bool, add func(move.Move)) { pieces := p.bitBoard[toMove][piece.King] { @@ -128,6 +135,7 @@ func (p *Position) genKingMoves(toMove, notToMove piece.Color, castlingRights ma } } +// Panics if toMove or notToMove is not White or Black. func (p *Position) genPawnMoves(toMove, notToMove piece.Color, enPassant square.Square, add func(move.Move)) { pieces := p.bitBoard[toMove][piece.Pawn] &^ pawns_spawn[notToMove] //&^ = AND_NOT for pieces != 0 { @@ -178,28 +186,30 @@ func (p *Position) genPawnMoves(toMove, notToMove piece.Color, enPassant square. // Threatened returns whether or not the specified square is under attack // by the specified color. -func (p *Position) Threatened(square square.Square, byWho piece.Color) bool { - defender := []piece.Color{piece.Black, piece.White}[byWho] +func (p *Position) Threatened(sq square.Square, byWho piece.Color) bool { + if byWho >= piece.NoColor || sq > square.LastSquare { + return false + } // other king attacks: - if (king_moves[square] & p.bitBoard[byWho][piece.King]) != 0 { + if (king_moves[sq] & p.bitBoard[byWho][piece.King]) != 0 { return true } // pawn attacks: - if pawn_captures[defender][square]&p.bitBoard[byWho][piece.Pawn] != 0 { + if pawn_captures[piece.OtherColor[byWho]][sq]&p.bitBoard[byWho][piece.Pawn] != 0 { return true } // knight attacks: - if knight_moves[square]&p.bitBoard[byWho][piece.Knight] != 0 { + if knight_moves[sq]&p.bitBoard[byWho][piece.Knight] != 0 { return true } // diagonal attacks: direction := [4][65]uint64{nw, ne, sw, se} scan := [4]func(uint64) uint{bsf, bsf, bsr, bsr} for i := 0; i < 4; i++ { - blockerIndex := scan[i](direction[i][square] & p.occupied(piece.BothColors)) + blockerIndex := scan[i](direction[i][sq] & p.occupied(piece.BothColors)) if (1<= 1; r-- { + str += "\n" + strconv.Itoa(int(r)) + "│ " + for f := uint(1); f <= 8; f++ { + sq := square.New(f, r) + pc := p.OnSquare(sq) + if pc.Type == piece.None { + str += ". " + } else { + str += pc.String() + " " } } - if noPiece { - str += " " + str += "│" + strconv.Itoa(int(r)) + } + str += "\n" + str += " └─────────────────┘\n" + str += " a b c d e f g h\n" + str += "\n" + str += "MoveNumber: " + strconv.Itoa(p.MoveNumber) + "\n" + str += "ActiveColor: " + p.ActiveColor.String() + "\n" + + str += "CastlingRights:\n" + for _, c := range piece.Colors { + crqs, crks := "", "" + if p.CastlingRights[c][board.LongSide] { + crqs = " O-O-O" } - if sq%8 == 0 { - str += "|\n" - str += "+---+---+---+---+---+---+---+---+" - if sq < square.LastSquare { - str += "\n" - } + if p.CastlingRights[c][board.ShortSide] { + crks = " O-O" } + str += " " + c.String() + ":" + crqs + crks + "\n" + } + + eps := "" + if p.EnPassant != square.NoSquare { + eps = " " + p.EnPassant.String() + } + str += "EnPassant:" + eps + "\n" + + lms := "" + if p.LastMove != move.Null { + lms = " " + p.LastMove.String() } - return + str += "LastMove:" + lms + "\n" + + str += "FiftyMoveCount: " + strconv.Itoa(int(p.FiftyMoveCount)) + "\n" + str += "ThreeFoldCount: " + strconv.Itoa(p.ThreeFoldCount[p.Polyglot()]) + "\n" + + str += "MovesLeft:\n" + for _, c := range piece.Colors { + str += " " + c.String() + ": " + strconv.Itoa(p.MovesLeft[c]) + "\n" + } + + str += "Clocks:" + for _, c := range piece.Colors { + str += "\n " + c.String() + ": " + p.Clocks[c].String() + } + + return str } -*/ + // Clear empties the Board. func (p *Position) Clear() { - p.bitBoard = newBitboards() + p.bitBoard = [piece.COLOR_COUNT][piece.TYPE_COUNT]uint64{} } // Reset puts the pieces in the new game position. func (p *Position) Reset() { // puts the pieces in their starting/newgame positions - for color := piece.Color(0); color < 2; color = color + 1 { + for color := range piece.Colors { //Pawns first: p.bitBoard[color][piece.Pawn] = 255 << (8 + (color * 8 * 5)) //Then the rest of the pieces: @@ -199,10 +229,18 @@ func (p *Position) Reset() { // OnSquare returns the piece that is on the specified square. func (p *Position) OnSquare(s square.Square) piece.Piece { - return BitBoards(p.bitBoard).OnSquare(s) + for c := piece.White; c <= piece.Black; c++ { + for pc := piece.Pawn; pc <= piece.King; pc++ { + if (p.bitBoard[c][pc] & (1 << s)) != 0 { + return piece.New(c, pc) + } + } + } + return piece.New(piece.Neither, piece.None) } // Occupied returns a bitBoard with all of the specified colors pieces. +// Panics when `c` is greater than piece.BothColors. func (p *Position) occupied(c piece.Color) uint64 { var mask uint64 for pc := piece.Pawn; pc <= piece.King; pc++ { @@ -232,7 +270,7 @@ func (p *Position) MakeMove(m move.Move) *Position { q.adjustBoard(m, from, to, movingPiece, capturedPiece) q.Clocks[q.ActiveColor] -= m.Duration q.MovesLeft[q.ActiveColor]-- - q.ActiveColor = (q.ActiveColor + 1) % 2 + q.ActiveColor = (q.ActiveColor + 1) % piece.COLOR_COUNT if q.ActiveColor == piece.White { q.MoveNumber++ } @@ -282,7 +320,7 @@ func (p *Position) adjustCastlingRights(movingPiece piece.Piece, from, to square p.CastlingRights[movingPiece.Color][side] = false } if to == [2][2]square.Square{{square.H8, square.A8}, {square.H1, square.A1}}[movingPiece.Color][side] { - p.CastlingRights[[]piece.Color{piece.Black, piece.White}[movingPiece.Color]][side] = false + p.CastlingRights[piece.OtherColor[movingPiece.Color]][side] = false } } } @@ -323,25 +361,37 @@ func (p *Position) adjustBoard(m move.Move, from, to square.Square, movingPiece, } } -// Put places a piece on the square and removes any other piece -// that may be on that square. +// Put places a piece on the square and removes any other piece that may be on that square. +// If the placed piece has a non-standard color or type, no piece is placed (just the removing part is done). func (p *Position) Put(pp piece.Piece, s square.Square) { + if s > square.LastSquare { + return + } pc := p.OnSquare(s) if pc.Type != piece.None { p.bitBoard[pc.Color][pc.Type] ^= (1 << s) } + if pp.Type == piece.None || pp.Type > piece.King || pp.Color > piece.Black { + return + } p.bitBoard[pp.Color][pp.Type] |= (1 << s) } // QuickPut places a piece on the square without removing // any piece that may already be on that square. func (p *Position) QuickPut(pc piece.Piece, s square.Square) { + if pc.Type > piece.King || pc.Color > piece.Black || s > square.LastSquare { + return + } p.bitBoard[pc.Color][pc.Type] |= (1 << s) } // Find returns the squares that hold the specified piece. func (p *Position) Find(pc piece.Piece) map[square.Square]struct{} { s := make(map[square.Square]struct{}) + if pc.Type == piece.None || pc.Type >= piece.TYPE_COUNT || pc.Color >= piece.COLOR_COUNT { + return s + } bits := p.bitBoard[pc.Color][pc.Type] for bits != 0 { sq := bitscan(bits) @@ -365,8 +415,8 @@ func (p *Position) InsufficientMaterial() bool { return false } - for color := piece.White; color <= piece.Black; color++ { - otherColor := []piece.Color{piece.Black, piece.White}[color] + for _, color := range piece.Colors { + otherColor := piece.OtherColor[color] if loneKing[color] { // King vs King: if loneKing[otherColor] { @@ -395,8 +445,8 @@ func (p *Position) InsufficientMaterial() bool { mask := p.bitBoard[otherColor][piece.King] | p.bitBoard[otherColor][piece.Bishop] occuppied := p.occupied(otherColor) if (occuppied&mask == occuppied) && (popcount(p.bitBoard[otherColor][piece.Bishop]) == 1) { - color1 := bitscan(p.bitBoard[color][piece.Bishop]) % 2 - color2 := bitscan(p.bitBoard[otherColor][piece.Bishop]) % 2 + color1 := bitscan(p.bitBoard[color][piece.Bishop]) % piece.COLOR_COUNT + color2 := bitscan(p.bitBoard[otherColor][piece.Bishop]) % piece.COLOR_COUNT if color1 == color2 { return true } @@ -409,9 +459,11 @@ func (p *Position) InsufficientMaterial() bool { // Check returns whether or not the specified color is in check. func (p *Position) Check(color piece.Color) bool { - opponent := []piece.Color{piece.Black, piece.White}[color] + if color > piece.Black { + return false + } kingsq := square.Square(bitscan(p.bitBoard[color][piece.King])) - return p.Threatened(kingsq, opponent) + return p.Threatened(kingsq, piece.OtherColor[color]) } // Returns a string that is a SAN representation of the input move from the current position. diff --git a/position/position_test.go b/position/position_test.go index 3477827..caf8e65 100644 --- a/position/position_test.go +++ b/position/position_test.go @@ -2,13 +2,38 @@ package position import ( "fmt" + "reflect" "testing" + "time" "github.com/andrewbackes/chess/piece" + "github.com/andrewbackes/chess/position/board" "github.com/andrewbackes/chess/position/move" "github.com/andrewbackes/chess/position/square" ) +func TestMailBox(t *testing.T) { + testCases := []struct { + name string + position testPosition + want string + }{ + {"Empty", testPosition{}, " "}, + {"Initial", nil, "RNBKQBNRPPPPPPPP pppppppprnbkqbnr"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + p := tc.position.Position() + mbx := p.MailBox() + if mbx != tc.want { + t.Logf("Position:\n%v", p) + t.Errorf("*Position.MailBox() =\n\t\"%s\"\n, want\n\t\"%s\"", mbx, tc.want) + } + }) + } +} + func piecesOnSquare(b *Position, s square.Square) int { count := 0 for c := piece.White; c <= piece.Black; c++ { @@ -21,36 +46,2214 @@ func piecesOnSquare(b *Position, s square.Square) int { return count } -func changedBitBoards(before, after *Position) map[piece.Piece]struct{} { - changed := make(map[piece.Piece]struct{}) +func changedBitBoards(before, after *Position) map[piece.Piece]struct{} { + changed := make(map[piece.Piece]struct{}) + + for c := range before.bitBoard { + for p := range before.bitBoard[piece.Color(c)] { + if p == int(piece.None) { // Skip piece.None. + continue + } + if before.bitBoard[piece.Color(c)][p] != after.bitBoard[piece.Color(c)][p] { + changed[piece.New(piece.Color(c), piece.Type(p))] = struct{}{} + } + } + } + return changed +} + +func TestBitBoards(t *testing.T) { + testCases := []struct { + name string + bitBoard [piece.COLOR_COUNT][piece.TYPE_COUNT]uint64 + want BitBoards + }{ + {"Empty", + [piece.COLOR_COUNT][piece.TYPE_COUNT]uint64{}, + BitBoards{ + piece.White: map[piece.Type]uint64{ + piece.Pawn: 0, + piece.Knight: 0, + piece.Bishop: 0, + piece.Rook: 0, + piece.Queen: 0, + piece.King: 0, + }, + piece.Black: map[piece.Type]uint64{ + piece.Pawn: 0, + piece.Knight: 0, + piece.Bishop: 0, + piece.Rook: 0, + piece.Queen: 0, + piece.King: 0, + }, + }, + }, + {"Initial", + [piece.COLOR_COUNT][piece.TYPE_COUNT]uint64{ + {0, 65280, 66, 36, 129, 16, 8}, + {0, 71776119061217280, 4755801206503243776, 2594073385365405696, 9295429630892703744, 1152921504606846976, 576460752303423488}, + }, + BitBoards{ + piece.White: map[piece.Type]uint64{ + piece.Pawn: 65280, + piece.Knight: 66, + piece.Bishop: 36, + piece.Rook: 129, + piece.Queen: 16, + piece.King: 8, + }, + piece.Black: map[piece.Type]uint64{ + piece.Pawn: 71776119061217280, + piece.Knight: 4755801206503243776, + piece.Bishop: 2594073385365405696, + piece.Rook: 9295429630892703744, + piece.Queen: 1152921504606846976, + piece.King: 576460752303423488, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + p := &Position{bitBoard: tc.bitBoard} + bbs := p.bitBoards() + if !reflect.DeepEqual(bbs, tc.want) { + t.Errorf("&Position{bitBoard: %v}.bitBoards() = %v, want %v", tc.bitBoard, bbs, tc.want) + } + }) + } +} + +func TestMovePawn(t *testing.T) { + beforeMove := New() + afterMove := beforeMove.MakeMove(move.Parse("e2e4")) + changed := changedBitBoards(beforeMove, afterMove) + t.Log("Changed: ", changed) + if _, c := changed[piece.New(piece.White, piece.Pawn)]; !c || len(changed) != 1 { + t.Fail() + } +} + +func TestMoveKnight(t *testing.T) { + beforeMove := New() + afterMove := beforeMove.MakeMove(move.Parse("b1c3")) + changed := changedBitBoards(beforeMove, afterMove) + t.Log("Changed: ", changed) + if _, c := changed[piece.New(piece.White, piece.Knight)]; !c || len(changed) != 1 { + t.Fail() + } +} + +// Stores information about various position changes. +type positionChanges struct { + squares squareChanges + castling castlingChanges + enPassant *square.Square +} + +// Stores information about changes on a chess board for testing purposes. It considers, that here can be multiple piece types on one square. +// Values are: +// false - Piece was removed from square. +// true - Piece was added to square. +type squareChanges map[square.Square]pieceChanges +type pieceChanges map[piece.Piece]bool + +// Stores information about castling rights changes. +// Values are: +// false - Castling right for color and side was changed from true to false +// true - Castling right for color and side was changed from false to true (should never happen). +type castlingChanges map[piece.Color]map[board.Side]bool + +func squarePtr(sq square.Square) *square.Square { + return &sq +} + +// Returns board changes between two positions. +func changedSquares(before, after *Position) squareChanges { + changed := make(squareChanges) + for sq := square.Square(0); sq <= square.LastSquare; sq += 1 { + for _, col := range piece.Colors { + for tpe := piece.Pawn; tpe <= piece.King; tpe += 1 { + sqb := uint64(1 << sq) + bsqpc := (before.bitBoard[col][tpe] & sqb) > 0 + asqpc := (after.bitBoard[col][tpe] & sqb) > 0 + if bsqpc != asqpc { + if _, ok := changed[sq]; !ok { + changed[sq] = pieceChanges{} + } + changed[sq][piece.New(col, tpe)] = asqpc && !bsqpc + } + } + } + } + + return changed +} + +// Returns castling changes between two positions. +func changedCastlingRights(before, after *Position) castlingChanges { + changed := make(castlingChanges) + for _, col := range piece.Colors { + for _, side := range board.Sides { + if before.CastlingRights[col][side] != after.CastlingRights[col][side] { + if _, ok := changed[col]; !ok { + changed[col] = map[board.Side]bool{} + } + changed[col][side] = after.CastlingRights[col][side] + } + } + } + + if len(changed) == 0 { + return nil + } + return changed +} + +func TestPositionMakeMove(t *testing.T) { + testCases := []struct { + name string + p testPosition + pc positionChanger + m move.Move + wantChanged positionChanges + }{ + // Move. + {"InitialBoard-ActiveWhite-WhitePawn-e2e3", + InitialTestPosition, nil, + move.Move{square.E2, square.E3, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.E2: pieceChanges{piece.New(piece.White, piece.Pawn): false}, + square.E3: pieceChanges{piece.New(piece.White, piece.Pawn): true}, + }, + }, + }, + {"InitialBoard-ActiveBlack-BlackPawn-e7e6", + InitialTestPosition, active(piece.Black), + move.Move{square.E7, square.E6, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.E7: pieceChanges{piece.New(piece.Black, piece.Pawn): false}, + square.E6: pieceChanges{piece.New(piece.Black, piece.Pawn): true}, + }, + }, + }, + {"InitialBoard-ActiveWhite-WhitePawn-e2e3-Time5", + InitialTestPosition, nil, + move.Move{square.E2, square.E3, piece.None, 5}, + positionChanges{ + squares: squareChanges{ + square.E2: pieceChanges{piece.New(piece.White, piece.Pawn): false}, + square.E3: pieceChanges{piece.New(piece.White, piece.Pawn): true}, + }, + }, + }, + {"InitialBoard-ActiveBlack-BlackPawn-e7e6-Time5", + InitialTestPosition, active(piece.Black), + move.Move{square.E7, square.E6, piece.None, 5}, + positionChanges{ + squares: squareChanges{ + square.E7: pieceChanges{piece.New(piece.Black, piece.Pawn): false}, + square.E6: pieceChanges{piece.New(piece.Black, piece.Pawn): true}, + }, + }, + }, + {"InitialBoard-ActiveWhite-WhitePawn-e2e4-StoreEnPassant", + InitialTestPosition, nil, + move.Move{square.E2, square.E4, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.E2: pieceChanges{piece.New(piece.White, piece.Pawn): false}, + square.E4: pieceChanges{piece.New(piece.White, piece.Pawn): true}, + }, + enPassant: squarePtr(square.E3), + }, + }, + {"InitialBoard-ActiveBlack-BlackPawn-e7e5-StoreEnPassant", + InitialTestPosition, active(piece.Black), + move.Move{square.E7, square.E5, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.E7: pieceChanges{piece.New(piece.Black, piece.Pawn): false}, + square.E5: pieceChanges{piece.New(piece.Black, piece.Pawn): true}, + }, + enPassant: squarePtr(square.E6), + }, + }, + {"InitialBoard-ActiveWhite-WhiteKnight-b1c3", + InitialTestPosition, nil, + move.Move{square.B1, square.C3, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.B1: pieceChanges{piece.New(piece.White, piece.Knight): false}, + square.C3: pieceChanges{piece.New(piece.White, piece.Knight): true}, + }, + }, + }, + {"InitialBoard-ActiveBlack-BlackKnight-b8c6", + InitialTestPosition, active(piece.Black), + move.Move{square.B8, square.C6, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.B8: pieceChanges{piece.New(piece.Black, piece.Knight): false}, + square.C6: pieceChanges{piece.New(piece.Black, piece.Knight): true}, + }, + }, + }, + {"TwoPawnsAtTwoOpositeKings-ActiveWhite-WhiteKing-e1f2", + TwoPawnsAtTwoOpositeKingsTestPosition, nil, + move.Move{square.E1, square.F2, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.E1: pieceChanges{piece.New(piece.White, piece.King): false}, + square.F2: pieceChanges{piece.New(piece.White, piece.King): true}, + }, + castling: castlingChanges{piece.White: map[board.Side]bool{board.ShortSide: false, board.LongSide: false}}, + }, + }, + {"TwoPawnsAtTwoOpositeKings-ActiveBlack-BlackKing-e8f7", + TwoPawnsAtTwoOpositeKingsTestPosition, active(piece.Black), + move.Move{square.E8, square.F7, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.E8: pieceChanges{piece.New(piece.Black, piece.King): false}, + square.F7: pieceChanges{piece.New(piece.Black, piece.King): true}, + }, + castling: castlingChanges{piece.Black: map[board.Side]bool{board.ShortSide: false, board.LongSide: false}}, + }, + }, + // Capture. + {"TwoPawnsAtTwoOpositeKings-ActiveWhite-WhiteKing-e1e2-CaptureBlackPawn", + TwoPawnsAtTwoOpositeKingsTestPosition, nil, + move.Move{square.E1, square.E2, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.E1: pieceChanges{piece.New(piece.White, piece.King): false}, + square.E2: pieceChanges{ + piece.New(piece.Black, piece.Pawn): false, + piece.New(piece.White, piece.King): true, + }, + }, + castling: castlingChanges{piece.White: map[board.Side]bool{board.ShortSide: false, board.LongSide: false}}, + }, + }, + {"TwoPawnsAtTwoOpositeKings-ActiveBlack-BlackKing-e8e7-CaptureWhitePawn", + TwoPawnsAtTwoOpositeKingsTestPosition, active(piece.Black), + move.Move{square.E8, square.E7, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.E8: pieceChanges{piece.New(piece.Black, piece.King): false}, + square.E7: pieceChanges{ + piece.New(piece.White, piece.Pawn): false, + piece.New(piece.Black, piece.King): true, + }, + }, + castling: castlingChanges{piece.Black: map[board.Side]bool{board.ShortSide: false, board.LongSide: false}}, + }, + }, + // Castle. + {"TwoKingsFourRooks-ActiveWhite-WhiteKing-e1g1-ShortCastle", + TwoKingsFourRooksTestPosition, nil, + move.Move{square.E1, square.G1, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.E1: pieceChanges{piece.New(piece.White, piece.King): false}, + square.G1: pieceChanges{piece.New(piece.White, piece.King): true}, + square.H1: pieceChanges{piece.New(piece.White, piece.Rook): false}, + square.F1: pieceChanges{piece.New(piece.White, piece.Rook): true}, + }, + castling: castlingChanges{piece.White: map[board.Side]bool{board.ShortSide: false, board.LongSide: false}}, + }, + }, + {"TwoKingsFourRooks-ActiveBlack-BlackKing-e8g8-ShortCastle", + TwoKingsFourRooksTestPosition, active(piece.Black), + move.Move{square.E8, square.G8, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.E8: pieceChanges{piece.New(piece.Black, piece.King): false}, + square.G8: pieceChanges{piece.New(piece.Black, piece.King): true}, + square.H8: pieceChanges{piece.New(piece.Black, piece.Rook): false}, + square.F8: pieceChanges{piece.New(piece.Black, piece.Rook): true}, + }, + castling: castlingChanges{piece.Black: map[board.Side]bool{board.ShortSide: false, board.LongSide: false}}, + }, + }, + {"TwoKingsFourRooks-ActiveWhite-WhiteKing-e1c1-LongCastle", + TwoKingsFourRooksTestPosition, nil, + move.Move{square.E1, square.C1, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.E1: pieceChanges{piece.New(piece.White, piece.King): false}, + square.C1: pieceChanges{piece.New(piece.White, piece.King): true}, + square.A1: pieceChanges{piece.New(piece.White, piece.Rook): false}, + square.D1: pieceChanges{piece.New(piece.White, piece.Rook): true}, + }, + castling: castlingChanges{piece.White: map[board.Side]bool{board.ShortSide: false, board.LongSide: false}}, + }, + }, + {"TwoKingsFourRooks-ActiveBlack-BlackKing-e8c8-LongCastle", + TwoKingsFourRooksTestPosition, active(piece.Black), + move.Move{square.E8, square.C8, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.E8: pieceChanges{piece.New(piece.Black, piece.King): false}, + square.C8: pieceChanges{piece.New(piece.Black, piece.King): true}, + square.A8: pieceChanges{piece.New(piece.Black, piece.Rook): false}, + square.D8: pieceChanges{piece.New(piece.Black, piece.Rook): true}, + }, + castling: castlingChanges{piece.Black: map[board.Side]bool{board.ShortSide: false, board.LongSide: false}}, + }, + }, + // Castling rights. + {"TwoKingsFourRooks-ActiveWhite-WhiteRook-a1a2", + TwoKingsFourRooksTestPosition, nil, + move.Move{square.A1, square.A2, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.A1: pieceChanges{piece.New(piece.White, piece.Rook): false}, + square.A2: pieceChanges{piece.New(piece.White, piece.Rook): true}, + }, + castling: castlingChanges{piece.White: map[board.Side]bool{board.LongSide: false}}, + }, + }, + {"TwoKingsFourRooks-ActiveBlack-BlackRook-a8a7", + TwoKingsFourRooksTestPosition, active(piece.Black), + move.Move{square.A8, square.A7, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.A8: pieceChanges{piece.New(piece.Black, piece.Rook): false}, + square.A7: pieceChanges{piece.New(piece.Black, piece.Rook): true}, + }, + castling: castlingChanges{piece.Black: map[board.Side]bool{board.LongSide: false}}, + }, + }, + {"TwoKingsFourRooks-ActiveWhite-WhiteRook-h1h3", + TwoKingsFourRooksTestPosition, nil, + move.Move{square.H1, square.H3, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.H1: pieceChanges{piece.New(piece.White, piece.Rook): false}, + square.H3: pieceChanges{piece.New(piece.White, piece.Rook): true}, + }, + castling: castlingChanges{piece.White: map[board.Side]bool{board.ShortSide: false}}, + }, + }, + {"TwoKingsFourRooks-ActiveBlack-BlackRook-h8h6", + TwoKingsFourRooksTestPosition, active(piece.Black), + move.Move{square.H8, square.H6, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.H8: pieceChanges{piece.New(piece.Black, piece.Rook): false}, + square.H6: pieceChanges{piece.New(piece.Black, piece.Rook): true}, + }, + castling: castlingChanges{piece.Black: map[board.Side]bool{board.ShortSide: false}}, + }, + }, + // En-passant. + {"EnPassantCapture-ActiveWhite-EnPassantOnB6-WhitePawn-b5c6-CaptureBlackPawnEnPassant", + EnPassantCaptureTestPosition, enPassant(square.C6), + move.Move{square.B5, square.C6, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.B5: pieceChanges{piece.New(piece.White, piece.Pawn): false}, + square.C6: pieceChanges{piece.New(piece.White, piece.Pawn): true}, + square.C5: pieceChanges{piece.New(piece.Black, piece.Pawn): false}, + }, + }, + }, + {"EnPassantCapture-ActiveBlack-EnPassantOnB6-BlackPawn-g4f3-CaptureWhitePawnEnPassant", + EnPassantCaptureTestPosition, multi(active(piece.Black), enPassant(square.F3)), + move.Move{square.G4, square.F3, piece.None, 0}, + positionChanges{ + squares: squareChanges{ + square.G4: pieceChanges{piece.New(piece.Black, piece.Pawn): false}, + square.F3: pieceChanges{piece.New(piece.Black, piece.Pawn): true}, + square.F4: pieceChanges{piece.New(piece.White, piece.Pawn): false}, + }, + }, + }, + // Promotion. + {"Promotion-ActiveWhite-WhitePawn-b7b8-PromoteToQueen", + PromotionTestPosition, nil, + move.Move{square.B7, square.B8, piece.Queen, 0}, + positionChanges{ + squares: squareChanges{ + square.B7: pieceChanges{piece.New(piece.White, piece.Pawn): false}, + square.B8: pieceChanges{piece.New(piece.White, piece.Queen): true}, + }, + }, + }, + {"Promotion-ActiveBlack-BlackPawn-b2b1-PromoteToQueen", + PromotionTestPosition, active(piece.Black), + move.Move{square.B2, square.B1, piece.Queen, 0}, + positionChanges{ + squares: squareChanges{ + square.B2: pieceChanges{piece.New(piece.Black, piece.Pawn): false}, + square.B1: pieceChanges{piece.New(piece.Black, piece.Queen): true}, + }, + }, + }, + {"Promotion-ActiveWhite-WhitePawn-b7a8-CaptureAndPromoteToRook", + PromotionTestPosition, nil, + move.Move{square.B7, square.A8, piece.Rook, 0}, + positionChanges{ + squares: squareChanges{ + square.B7: pieceChanges{piece.New(piece.White, piece.Pawn): false}, + square.A8: pieceChanges{ + piece.New(piece.White, piece.Rook): true, + piece.New(piece.Black, piece.Rook): false, + }, + }, + castling: castlingChanges{piece.Black: map[board.Side]bool{board.LongSide: false}}, + }, + }, + {"Promotion-ActiveBlack-BlackPawn-b2a1-CaptureAndPromoteToRook", + PromotionTestPosition, active(piece.Black), + move.Move{square.B2, square.A1, piece.Rook, 0}, + positionChanges{ + squares: squareChanges{ + square.B2: pieceChanges{piece.New(piece.Black, piece.Pawn): false}, + square.A1: pieceChanges{ + piece.New(piece.Black, piece.Rook): true, + piece.New(piece.White, piece.Rook): false, + }, + }, + castling: castlingChanges{piece.White: map[board.Side]bool{board.LongSide: false}}, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Prepare position. + beforeMove, err := testCasePosition(tc.p, tc.pc) + if err != nil { + t.Fatal(err) + } + + // Catch panics. + defer func() { + if err := recover(); err != nil { + t.Logf("Position:\n%v", beforeMove) + t.Errorf("*Position.MakeMove(%v) should not panic, but panicked with: %v", tc.m, err) + } + }() + + { // Check if test move is a legal move. + legalMoves := beforeMove.LegalMoves() + moveNoDuration := tc.m + moveNoDuration.Duration = time.Duration(0) + if _, ok := legalMoves[moveNoDuration]; !ok { + t.Logf("Position:\n%v", beforeMove) + t.Fatalf("Move %v is not in Position.LegalMoves() result: %v", moveNoDuration, legalMoves) + } + } + + // Make move. + afterMove := beforeMove.MakeMove(tc.m) + + { // Check bitBoards changes. + changed := changedSquares(beforeMove, afterMove) + if !reflect.DeepEqual(changed, tc.wantChanged.squares) { + t.Logf("Position:\n%v", beforeMove) + t.Errorf("After *Position.MakeMove(%v), board changes are %v, want %v", tc.m, changed, tc.wantChanged.squares) + } + } + + { // Check castling rights changes, if any. + changed := changedCastlingRights(beforeMove, afterMove) + if !reflect.DeepEqual(changed, tc.wantChanged.castling) { + t.Logf("Position:\n%v", beforeMove) + t.Errorf("After *Position.MakeMove(%v), castling changes are %v, want %v", tc.m, changed, tc.wantChanged.castling) + } + } + + { // Check EnPassant after move. + want := square.NoSquare + if tc.wantChanged.enPassant != nil { + want = *tc.wantChanged.enPassant + } + if afterMove.EnPassant != want { + t.Logf("Position:\n%v", beforeMove) + t.Errorf("After *Position.MakeMove(%v), EnPassant is %v, want %v", tc.m, afterMove.EnPassant, want) + } + } + + // Check ActiveColor after move. + if afterMove.ActiveColor != (beforeMove.ActiveColor+1)%2 { + t.Logf("Position:\n%v", beforeMove) + t.Fatalf("After *Position.MakeMove(%v), board ActiveColor is %v, want %v", tc.m, afterMove.ActiveColor, (beforeMove.ActiveColor+1)%2) + } + + // Do additional checks only if ActiveColor before move is a valid color. + if beforeMove.ActiveColor == piece.White || beforeMove.ActiveColor == piece.Black { + // Check Clocks for both colors after move. + wantClock := beforeMove.Clocks[beforeMove.ActiveColor] - tc.m.Duration + if afterMove.Clocks[beforeMove.ActiveColor] != wantClock { + t.Logf("Position:\n%v", beforeMove) + t.Errorf("After *Position.MakeMove(%v), Clock[%v] is %v, want %v", tc.m, beforeMove.ActiveColor, afterMove.Clocks[beforeMove.ActiveColor], wantClock) + } + if afterMove.Clocks[afterMove.ActiveColor] != beforeMove.Clocks[afterMove.ActiveColor] { + t.Logf("Position:\n%v", beforeMove) + t.Errorf("After *Position.MakeMove(%v), Clock[%v] is %v, want %v", tc.m, afterMove.ActiveColor, afterMove.Clocks[afterMove.ActiveColor], beforeMove.Clocks[afterMove.ActiveColor]) + } + + // Check MovesLeft for both colors after move. + wantML := beforeMove.MovesLeft[beforeMove.ActiveColor] - 1 + if afterMove.MovesLeft[beforeMove.ActiveColor] != wantML { + t.Logf("Position:\n%v", beforeMove) + t.Errorf("After *Position.MakeMove(%v), MovesLeft[%v] is %v, want %v", tc.m, beforeMove.ActiveColor, afterMove.MovesLeft[beforeMove.ActiveColor], wantML) + } + if afterMove.MovesLeft[afterMove.ActiveColor] != beforeMove.MovesLeft[afterMove.ActiveColor] { + t.Logf("Position:\n%v", beforeMove) + t.Errorf("After *Position.MakeMove(%v), MovesLeft[%v] is %v, want %v", tc.m, afterMove.ActiveColor, afterMove.MovesLeft[afterMove.ActiveColor], beforeMove.MovesLeft[afterMove.ActiveColor]) + } + + // Check MoveNumber after move. + if beforeMove.ActiveColor == piece.Black { + wantMN := beforeMove.MoveNumber + 1 + if afterMove.MoveNumber != wantMN { + t.Logf("Position:\n%v", beforeMove) + t.Errorf("After *Position.MakeMove(%v), MoveNumber is %v, want %v", tc.m, afterMove.MoveNumber, wantMN) + } + } else if afterMove.MoveNumber != beforeMove.MoveNumber { + t.Logf("Position:\n%v", beforeMove) + t.Errorf("After *Position.MakeMove(%v), MoveNumber is %v, want %v", tc.m, afterMove.MoveNumber, beforeMove.MoveNumber) + } + } + + // Check LastMove after move. + if afterMove.LastMove != tc.m { + t.Logf("Position:\n%v", beforeMove) + t.Errorf("After *Position.MakeMove(%v), LastMove is %v, want %v", tc.m, afterMove.LastMove, tc.m) + } + + // Check ThreeFoldCount update after move. + hash := afterMove.Polyglot() + beforeHC := 0 + if hc, exists := beforeMove.ThreeFoldCount[hash]; exists { + beforeHC = hc + } + afterHC := afterMove.ThreeFoldCount[hash] + if afterHC != beforeHC+1 { + t.Logf("Position:\n%v", beforeMove) + t.Errorf("After *Position.MakeMove(%v), ThreeFoldCount[hash] is %v, want %v", tc.m, afterHC, beforeHC+1) + } + }) + } +} + +func TestPositionPut(t *testing.T) { + testCases := []struct { + name string + sq square.Square + pc piece.Piece + wantChanged squareChanges + }{ + // Pawns. + {"White-Pawn-A3", square.A3, piece.New(piece.White, piece.Pawn), + squareChanges{square.A3: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-A6", square.A6, piece.New(piece.Black, piece.Pawn), + squareChanges{square.A6: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + {"White-Pawn-A2", square.A2, piece.New(piece.White, piece.Pawn), + squareChanges{}, + }, + {"Black-Pawn-A7", square.A7, piece.New(piece.Black, piece.Pawn), + squareChanges{}, + }, + {"White-Pawn-A1", square.A1, piece.New(piece.White, piece.Pawn), + squareChanges{square.A1: pieceChanges{ + piece.New(piece.White, piece.Rook): false, + piece.New(piece.White, piece.Pawn): true, + }}, + }, + {"Black-Pawn-A8", square.A8, piece.New(piece.Black, piece.Pawn), + squareChanges{square.A8: pieceChanges{ + piece.New(piece.Black, piece.Rook): false, + piece.New(piece.Black, piece.Pawn): true, + }}, + }, + {"White-Pawn-B1", square.B1, piece.New(piece.White, piece.Pawn), + squareChanges{square.B1: pieceChanges{ + piece.New(piece.White, piece.Knight): false, + piece.New(piece.White, piece.Pawn): true, + }}, + }, + {"Black-Pawn-B8", square.B8, piece.New(piece.Black, piece.Pawn), + squareChanges{square.B8: pieceChanges{ + piece.New(piece.Black, piece.Knight): false, + piece.New(piece.Black, piece.Pawn): true, + }}, + }, + {"White-Pawn-C1", square.C1, piece.New(piece.White, piece.Pawn), + squareChanges{square.C1: pieceChanges{ + piece.New(piece.White, piece.Bishop): false, + piece.New(piece.White, piece.Pawn): true, + }}, + }, + {"Black-Pawn-C8", square.C8, piece.New(piece.Black, piece.Pawn), + squareChanges{square.C8: pieceChanges{ + piece.New(piece.Black, piece.Bishop): false, + piece.New(piece.Black, piece.Pawn): true, + }}, + }, + {"White-Pawn-D1", square.D1, piece.New(piece.White, piece.Pawn), + squareChanges{square.D1: pieceChanges{ + piece.New(piece.White, piece.Queen): false, + piece.New(piece.White, piece.Pawn): true, + }}, + }, + {"Black-Pawn-D8", square.D8, piece.New(piece.Black, piece.Pawn), + squareChanges{square.D8: pieceChanges{ + piece.New(piece.Black, piece.Queen): false, + piece.New(piece.Black, piece.Pawn): true, + }}, + }, + {"White-Pawn-E1", square.E1, piece.New(piece.White, piece.Pawn), + squareChanges{square.E1: pieceChanges{ + piece.New(piece.White, piece.King): false, + piece.New(piece.White, piece.Pawn): true, + }}, + }, + {"Black-Pawn-E8", square.E8, piece.New(piece.Black, piece.Pawn), + squareChanges{square.E8: pieceChanges{ + piece.New(piece.Black, piece.King): false, + piece.New(piece.Black, piece.Pawn): true, + }}, + }, + {"White-Pawn-H6", square.H6, piece.New(piece.White, piece.Pawn), + squareChanges{square.H6: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-H3", square.H3, piece.New(piece.Black, piece.Pawn), + squareChanges{square.H3: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + {"White-Pawn-H7", square.H7, piece.New(piece.White, piece.Pawn), + squareChanges{square.H7: pieceChanges{ + piece.New(piece.Black, piece.Pawn): false, + piece.New(piece.White, piece.Pawn): true, + }}, + }, + {"Black-Pawn-H2", square.H2, piece.New(piece.Black, piece.Pawn), + squareChanges{square.H2: pieceChanges{ + piece.New(piece.White, piece.Pawn): false, + piece.New(piece.Black, piece.Pawn): true, + }}, + }, + {"White-Pawn-H8", square.H8, piece.New(piece.White, piece.Pawn), + squareChanges{square.H8: pieceChanges{ + piece.New(piece.Black, piece.Rook): false, + piece.New(piece.White, piece.Pawn): true, + }}, + }, + {"Black-Pawn-H1", square.H1, piece.New(piece.Black, piece.Pawn), + squareChanges{square.H1: pieceChanges{ + piece.New(piece.White, piece.Rook): false, + piece.New(piece.Black, piece.Pawn): true, + }}, + }, + {"White-Pawn-G8", square.G8, piece.New(piece.White, piece.Pawn), + squareChanges{square.G8: pieceChanges{ + piece.New(piece.Black, piece.Knight): false, + piece.New(piece.White, piece.Pawn): true, + }}, + }, + {"Black-Pawn-G1", square.G1, piece.New(piece.Black, piece.Pawn), + squareChanges{square.G1: pieceChanges{ + piece.New(piece.White, piece.Knight): false, + piece.New(piece.Black, piece.Pawn): true, + }}, + }, + {"White-Pawn-F8", square.F8, piece.New(piece.White, piece.Pawn), + squareChanges{square.F8: pieceChanges{ + piece.New(piece.Black, piece.Bishop): false, + piece.New(piece.White, piece.Pawn): true, + }}, + }, + {"Black-Pawn-F1", square.F1, piece.New(piece.Black, piece.Pawn), + squareChanges{square.F1: pieceChanges{ + piece.New(piece.White, piece.Bishop): false, + piece.New(piece.Black, piece.Pawn): true, + }}, + }, + {"White-Pawn-E8", square.E8, piece.New(piece.White, piece.Pawn), + squareChanges{square.E8: pieceChanges{ + piece.New(piece.Black, piece.King): false, + piece.New(piece.White, piece.Pawn): true, + }}, + }, + {"Black-Pawn-E1", square.E1, piece.New(piece.Black, piece.Pawn), + squareChanges{square.E1: pieceChanges{ + piece.New(piece.White, piece.King): false, + piece.New(piece.Black, piece.Pawn): true, + }}, + }, + {"White-Pawn-D8", square.D8, piece.New(piece.White, piece.Pawn), + squareChanges{square.D8: pieceChanges{ + piece.New(piece.Black, piece.Queen): false, + piece.New(piece.White, piece.Pawn): true, + }}, + }, + {"Black-Pawn-D1", square.D1, piece.New(piece.Black, piece.Pawn), + squareChanges{square.D1: pieceChanges{ + piece.New(piece.White, piece.Queen): false, + piece.New(piece.Black, piece.Pawn): true, + }}, + }, + + // Rooks. + {"White-Rook-A3", square.A3, piece.New(piece.White, piece.Rook), + squareChanges{square.A3: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-A6", square.A6, piece.New(piece.Black, piece.Rook), + squareChanges{square.A6: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + {"White-Rook-A2", square.A2, piece.New(piece.White, piece.Rook), + squareChanges{square.A2: pieceChanges{ + piece.New(piece.White, piece.Pawn): false, + piece.New(piece.White, piece.Rook): true, + }}, + }, + {"Black-Rook-A7", square.A7, piece.New(piece.Black, piece.Rook), + squareChanges{square.A7: pieceChanges{ + piece.New(piece.Black, piece.Pawn): false, + piece.New(piece.Black, piece.Rook): true, + }}, + }, + {"White-Rook-A1", square.A1, piece.New(piece.White, piece.Rook), + squareChanges{}, + }, + {"Black-Rook-A8", square.A8, piece.New(piece.Black, piece.Rook), + squareChanges{}, + }, + {"White-Rook-B1", square.B1, piece.New(piece.White, piece.Rook), + squareChanges{square.B1: pieceChanges{ + piece.New(piece.White, piece.Knight): false, + piece.New(piece.White, piece.Rook): true, + }}, + }, + {"Black-Rook-B8", square.B8, piece.New(piece.Black, piece.Rook), + squareChanges{square.B8: pieceChanges{ + piece.New(piece.Black, piece.Knight): false, + piece.New(piece.Black, piece.Rook): true, + }}, + }, + {"White-Rook-C1", square.C1, piece.New(piece.White, piece.Rook), + squareChanges{square.C1: pieceChanges{ + piece.New(piece.White, piece.Bishop): false, + piece.New(piece.White, piece.Rook): true, + }}, + }, + {"Black-Rook-C8", square.C8, piece.New(piece.Black, piece.Rook), + squareChanges{square.C8: pieceChanges{ + piece.New(piece.Black, piece.Bishop): false, + piece.New(piece.Black, piece.Rook): true, + }}, + }, + {"White-Rook-D1", square.D1, piece.New(piece.White, piece.Rook), + squareChanges{square.D1: pieceChanges{ + piece.New(piece.White, piece.Queen): false, + piece.New(piece.White, piece.Rook): true, + }}, + }, + {"Black-Rook-D8", square.D8, piece.New(piece.Black, piece.Rook), + squareChanges{square.D8: pieceChanges{ + piece.New(piece.Black, piece.Queen): false, + piece.New(piece.Black, piece.Rook): true, + }}, + }, + {"White-Rook-E1", square.E1, piece.New(piece.White, piece.Rook), + squareChanges{square.E1: pieceChanges{ + piece.New(piece.White, piece.King): false, + piece.New(piece.White, piece.Rook): true, + }}, + }, + {"Black-Rook-E8", square.E8, piece.New(piece.Black, piece.Rook), + squareChanges{square.E8: pieceChanges{ + piece.New(piece.Black, piece.King): false, + piece.New(piece.Black, piece.Rook): true, + }}, + }, + {"White-Rook-H6", square.H6, piece.New(piece.White, piece.Rook), + squareChanges{square.H6: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-H3", square.H3, piece.New(piece.Black, piece.Rook), + squareChanges{square.H3: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + {"White-Rook-H7", square.H7, piece.New(piece.White, piece.Rook), + squareChanges{square.H7: pieceChanges{ + piece.New(piece.Black, piece.Pawn): false, + piece.New(piece.White, piece.Rook): true, + }}, + }, + {"Black-Rook-H2", square.H2, piece.New(piece.Black, piece.Rook), + squareChanges{square.H2: pieceChanges{ + piece.New(piece.White, piece.Pawn): false, + piece.New(piece.Black, piece.Rook): true, + }}, + }, + {"White-Rook-H8", square.H8, piece.New(piece.White, piece.Rook), + squareChanges{square.H8: pieceChanges{ + piece.New(piece.Black, piece.Rook): false, + piece.New(piece.White, piece.Rook): true, + }}, + }, + {"Black-Rook-H1", square.H1, piece.New(piece.Black, piece.Rook), + squareChanges{square.H1: pieceChanges{ + piece.New(piece.White, piece.Rook): false, + piece.New(piece.Black, piece.Rook): true, + }}, + }, + {"White-Rook-G8", square.G8, piece.New(piece.White, piece.Rook), + squareChanges{square.G8: pieceChanges{ + piece.New(piece.Black, piece.Knight): false, + piece.New(piece.White, piece.Rook): true, + }}, + }, + {"Black-Rook-G1", square.G1, piece.New(piece.Black, piece.Rook), + squareChanges{square.G1: pieceChanges{ + piece.New(piece.White, piece.Knight): false, + piece.New(piece.Black, piece.Rook): true, + }}, + }, + {"White-Rook-F8", square.F8, piece.New(piece.White, piece.Rook), + squareChanges{square.F8: pieceChanges{ + piece.New(piece.Black, piece.Bishop): false, + piece.New(piece.White, piece.Rook): true, + }}, + }, + {"Black-Rook-F1", square.F1, piece.New(piece.Black, piece.Rook), + squareChanges{square.F1: pieceChanges{ + piece.New(piece.White, piece.Bishop): false, + piece.New(piece.Black, piece.Rook): true, + }}, + }, + {"White-Rook-E8", square.E8, piece.New(piece.White, piece.Rook), + squareChanges{square.E8: pieceChanges{ + piece.New(piece.Black, piece.King): false, + piece.New(piece.White, piece.Rook): true, + }}, + }, + {"Black-Rook-E1", square.E1, piece.New(piece.Black, piece.Rook), + squareChanges{square.E1: pieceChanges{ + piece.New(piece.White, piece.King): false, + piece.New(piece.Black, piece.Rook): true, + }}, + }, + {"White-Rook-D8", square.D8, piece.New(piece.White, piece.Rook), + squareChanges{square.D8: pieceChanges{ + piece.New(piece.Black, piece.Queen): false, + piece.New(piece.White, piece.Rook): true, + }}, + }, + {"Black-Rook-D1", square.D1, piece.New(piece.Black, piece.Rook), + squareChanges{square.D1: pieceChanges{ + piece.New(piece.White, piece.Queen): false, + piece.New(piece.Black, piece.Rook): true, + }}, + }, + + // Knights. + {"White-Knight-B3", square.B3, piece.New(piece.White, piece.Knight), + squareChanges{square.B3: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-B6", square.B6, piece.New(piece.Black, piece.Knight), + squareChanges{square.B6: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, + {"White-Knight-B2", square.B2, piece.New(piece.White, piece.Knight), + squareChanges{square.B2: pieceChanges{ + piece.New(piece.White, piece.Pawn): false, + piece.New(piece.White, piece.Knight): true, + }}, + }, + {"Black-Knight-B7", square.B7, piece.New(piece.Black, piece.Knight), + squareChanges{square.B7: pieceChanges{ + piece.New(piece.Black, piece.Pawn): false, + piece.New(piece.Black, piece.Knight): true, + }}, + }, + {"White-Knight-A1", square.A1, piece.New(piece.White, piece.Knight), + squareChanges{square.A1: pieceChanges{ + piece.New(piece.White, piece.Rook): false, + piece.New(piece.White, piece.Knight): true, + }}, + }, + {"Black-Knight-A8", square.A8, piece.New(piece.Black, piece.Knight), + squareChanges{square.A8: pieceChanges{ + piece.New(piece.Black, piece.Rook): false, + piece.New(piece.Black, piece.Knight): true, + }}, + }, + {"White-Knight-B1", square.B1, piece.New(piece.White, piece.Knight), + squareChanges{}, + }, + {"Black-Knight-B8", square.B8, piece.New(piece.Black, piece.Knight), + squareChanges{}, + }, + {"White-Knight-C1", square.C1, piece.New(piece.White, piece.Knight), + squareChanges{square.C1: pieceChanges{ + piece.New(piece.White, piece.Bishop): false, + piece.New(piece.White, piece.Knight): true, + }}, + }, + {"Black-Knight-C8", square.C8, piece.New(piece.Black, piece.Knight), + squareChanges{square.C8: pieceChanges{ + piece.New(piece.Black, piece.Bishop): false, + piece.New(piece.Black, piece.Knight): true, + }}, + }, + {"White-Knight-D1", square.D1, piece.New(piece.White, piece.Knight), + squareChanges{square.D1: pieceChanges{ + piece.New(piece.White, piece.Queen): false, + piece.New(piece.White, piece.Knight): true, + }}, + }, + {"Black-Knight-D8", square.D8, piece.New(piece.Black, piece.Knight), + squareChanges{square.D8: pieceChanges{ + piece.New(piece.Black, piece.Queen): false, + piece.New(piece.Black, piece.Knight): true, + }}, + }, + {"White-Knight-E1", square.E1, piece.New(piece.White, piece.Knight), + squareChanges{square.E1: pieceChanges{ + piece.New(piece.White, piece.King): false, + piece.New(piece.White, piece.Knight): true, + }}, + }, + {"Black-Knight-E8", square.E8, piece.New(piece.Black, piece.Knight), + squareChanges{square.E8: pieceChanges{ + piece.New(piece.Black, piece.King): false, + piece.New(piece.Black, piece.Knight): true, + }}, + }, + {"White-Knight-G6", square.G6, piece.New(piece.White, piece.Knight), + squareChanges{square.G6: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-G3", square.G3, piece.New(piece.Black, piece.Knight), + squareChanges{square.G3: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, + {"White-Knight-G7", square.G7, piece.New(piece.White, piece.Knight), + squareChanges{square.G7: pieceChanges{ + piece.New(piece.Black, piece.Pawn): false, + piece.New(piece.White, piece.Knight): true, + }}, + }, + {"Black-Knight-G2", square.G2, piece.New(piece.Black, piece.Knight), + squareChanges{square.G2: pieceChanges{ + piece.New(piece.White, piece.Pawn): false, + piece.New(piece.Black, piece.Knight): true, + }}, + }, + {"White-Knight-H8", square.H8, piece.New(piece.White, piece.Knight), + squareChanges{square.H8: pieceChanges{ + piece.New(piece.Black, piece.Rook): false, + piece.New(piece.White, piece.Knight): true, + }}, + }, + {"Black-Knight-H1", square.H1, piece.New(piece.Black, piece.Knight), + squareChanges{square.H1: pieceChanges{ + piece.New(piece.White, piece.Rook): false, + piece.New(piece.Black, piece.Knight): true, + }}, + }, + {"White-Knight-G8", square.G8, piece.New(piece.White, piece.Knight), + squareChanges{square.G8: pieceChanges{ + piece.New(piece.Black, piece.Knight): false, + piece.New(piece.White, piece.Knight): true, + }}, + }, + {"Black-Knight-G1", square.G1, piece.New(piece.Black, piece.Knight), + squareChanges{square.G1: pieceChanges{ + piece.New(piece.White, piece.Knight): false, + piece.New(piece.Black, piece.Knight): true, + }}, + }, + {"White-Knight-F8", square.F8, piece.New(piece.White, piece.Knight), + squareChanges{square.F8: pieceChanges{ + piece.New(piece.Black, piece.Bishop): false, + piece.New(piece.White, piece.Knight): true, + }}, + }, + {"Black-Knight-F1", square.F1, piece.New(piece.Black, piece.Knight), + squareChanges{square.F1: pieceChanges{ + piece.New(piece.White, piece.Bishop): false, + piece.New(piece.Black, piece.Knight): true, + }}, + }, + {"White-Knight-E8", square.E8, piece.New(piece.White, piece.Knight), + squareChanges{square.E8: pieceChanges{ + piece.New(piece.Black, piece.King): false, + piece.New(piece.White, piece.Knight): true, + }}, + }, + {"Black-Knight-E1", square.E1, piece.New(piece.Black, piece.Knight), + squareChanges{square.E1: pieceChanges{ + piece.New(piece.White, piece.King): false, + piece.New(piece.Black, piece.Knight): true, + }}, + }, + {"White-Knight-D8", square.D8, piece.New(piece.White, piece.Knight), + squareChanges{square.D8: pieceChanges{ + piece.New(piece.Black, piece.Queen): false, + piece.New(piece.White, piece.Knight): true, + }}, + }, + {"Black-Knight-D1", square.D1, piece.New(piece.Black, piece.Knight), + squareChanges{square.D1: pieceChanges{ + piece.New(piece.White, piece.Queen): false, + piece.New(piece.Black, piece.Knight): true, + }}, + }, + + // Bishops. + {"White-Bishop-C3", square.C3, piece.New(piece.White, piece.Bishop), + squareChanges{square.C3: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-C6", square.C6, piece.New(piece.Black, piece.Bishop), + squareChanges{square.C6: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, + {"White-Bishop-C2", square.C2, piece.New(piece.White, piece.Bishop), + squareChanges{square.C2: pieceChanges{ + piece.New(piece.White, piece.Pawn): false, + piece.New(piece.White, piece.Bishop): true, + }}, + }, + {"Black-Bishop-C7", square.C7, piece.New(piece.Black, piece.Bishop), + squareChanges{square.C7: pieceChanges{ + piece.New(piece.Black, piece.Pawn): false, + piece.New(piece.Black, piece.Bishop): true, + }}, + }, + {"White-Bishop-A1", square.A1, piece.New(piece.White, piece.Bishop), + squareChanges{square.A1: pieceChanges{ + piece.New(piece.White, piece.Rook): false, + piece.New(piece.White, piece.Bishop): true, + }}, + }, + {"Black-Bishop-A8", square.A8, piece.New(piece.Black, piece.Bishop), + squareChanges{square.A8: pieceChanges{ + piece.New(piece.Black, piece.Rook): false, + piece.New(piece.Black, piece.Bishop): true, + }}, + }, + {"White-Bishop-B1", square.B1, piece.New(piece.White, piece.Bishop), + squareChanges{square.B1: pieceChanges{ + piece.New(piece.White, piece.Knight): false, + piece.New(piece.White, piece.Bishop): true, + }}, + }, + {"Black-Bishop-B8", square.B8, piece.New(piece.Black, piece.Bishop), + squareChanges{square.B8: pieceChanges{ + piece.New(piece.Black, piece.Knight): false, + piece.New(piece.Black, piece.Bishop): true, + }}, + }, + {"White-Bishop-C1", square.C1, piece.New(piece.White, piece.Bishop), + squareChanges{}, + }, + {"Black-Bishop-C8", square.C8, piece.New(piece.Black, piece.Bishop), + squareChanges{}, + }, + {"White-Bishop-D1", square.D1, piece.New(piece.White, piece.Bishop), + squareChanges{square.D1: pieceChanges{ + piece.New(piece.White, piece.Queen): false, + piece.New(piece.White, piece.Bishop): true, + }}, + }, + {"Black-Bishop-D8", square.D8, piece.New(piece.Black, piece.Bishop), + squareChanges{square.D8: pieceChanges{ + piece.New(piece.Black, piece.Queen): false, + piece.New(piece.Black, piece.Bishop): true, + }}, + }, + {"White-Bishop-E1", square.E1, piece.New(piece.White, piece.Bishop), + squareChanges{square.E1: pieceChanges{ + piece.New(piece.White, piece.King): false, + piece.New(piece.White, piece.Bishop): true, + }}, + }, + {"Black-Bishop-E8", square.E8, piece.New(piece.Black, piece.Bishop), + squareChanges{square.E8: pieceChanges{ + piece.New(piece.Black, piece.King): false, + piece.New(piece.Black, piece.Bishop): true, + }}, + }, + {"White-Bishop-F6", square.F6, piece.New(piece.White, piece.Bishop), + squareChanges{square.F6: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-F3", square.F3, piece.New(piece.Black, piece.Bishop), + squareChanges{square.F3: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, + {"White-Bishop-F7", square.F7, piece.New(piece.White, piece.Bishop), + squareChanges{square.F7: pieceChanges{ + piece.New(piece.Black, piece.Pawn): false, + piece.New(piece.White, piece.Bishop): true, + }}, + }, + {"Black-Bishop-F2", square.F2, piece.New(piece.Black, piece.Bishop), + squareChanges{square.F2: pieceChanges{ + piece.New(piece.White, piece.Pawn): false, + piece.New(piece.Black, piece.Bishop): true, + }}, + }, + {"White-Bishop-H8", square.H8, piece.New(piece.White, piece.Bishop), + squareChanges{square.H8: pieceChanges{ + piece.New(piece.Black, piece.Rook): false, + piece.New(piece.White, piece.Bishop): true, + }}, + }, + {"Black-Bishop-H1", square.H1, piece.New(piece.Black, piece.Bishop), + squareChanges{square.H1: pieceChanges{ + piece.New(piece.White, piece.Rook): false, + piece.New(piece.Black, piece.Bishop): true, + }}, + }, + {"White-Bishop-G8", square.G8, piece.New(piece.White, piece.Bishop), + squareChanges{square.G8: pieceChanges{ + piece.New(piece.Black, piece.Knight): false, + piece.New(piece.White, piece.Bishop): true, + }}, + }, + {"Black-Bishop-G1", square.G1, piece.New(piece.Black, piece.Bishop), + squareChanges{square.G1: pieceChanges{ + piece.New(piece.White, piece.Knight): false, + piece.New(piece.Black, piece.Bishop): true, + }}, + }, + {"White-Bishop-F8", square.F8, piece.New(piece.White, piece.Bishop), + squareChanges{square.F8: pieceChanges{ + piece.New(piece.Black, piece.Bishop): false, + piece.New(piece.White, piece.Bishop): true, + }}, + }, + {"Black-Bishop-F1", square.F1, piece.New(piece.Black, piece.Bishop), + squareChanges{square.F1: pieceChanges{ + piece.New(piece.White, piece.Bishop): false, + piece.New(piece.Black, piece.Bishop): true, + }}, + }, + {"White-Bishop-E8", square.E8, piece.New(piece.White, piece.Bishop), + squareChanges{square.E8: pieceChanges{ + piece.New(piece.Black, piece.King): false, + piece.New(piece.White, piece.Bishop): true, + }}, + }, + {"Black-Bishop-E1", square.E1, piece.New(piece.Black, piece.Bishop), + squareChanges{square.E1: pieceChanges{ + piece.New(piece.White, piece.King): false, + piece.New(piece.Black, piece.Bishop): true, + }}, + }, + {"White-Bishop-D8", square.D8, piece.New(piece.White, piece.Bishop), + squareChanges{square.D8: pieceChanges{ + piece.New(piece.Black, piece.Queen): false, + piece.New(piece.White, piece.Bishop): true, + }}, + }, + {"Black-Bishop-D1", square.D1, piece.New(piece.Black, piece.Bishop), + squareChanges{square.D1: pieceChanges{ + piece.New(piece.White, piece.Queen): false, + piece.New(piece.Black, piece.Bishop): true, + }}, + }, + + // Queens. + {"White-Queen-D3", square.D3, piece.New(piece.White, piece.Queen), + squareChanges{square.D3: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-D6", square.D6, piece.New(piece.Black, piece.Queen), + squareChanges{square.D6: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, + {"White-Queen-D2", square.D2, piece.New(piece.White, piece.Queen), + squareChanges{square.D2: pieceChanges{ + piece.New(piece.White, piece.Pawn): false, + piece.New(piece.White, piece.Queen): true, + }}, + }, + {"Black-Queen-D7", square.D7, piece.New(piece.Black, piece.Queen), + squareChanges{square.D7: pieceChanges{ + piece.New(piece.Black, piece.Pawn): false, + piece.New(piece.Black, piece.Queen): true, + }}, + }, + {"White-Queen-A1", square.A1, piece.New(piece.White, piece.Queen), + squareChanges{square.A1: pieceChanges{ + piece.New(piece.White, piece.Rook): false, + piece.New(piece.White, piece.Queen): true, + }}, + }, + {"Black-Queen-A8", square.A8, piece.New(piece.Black, piece.Queen), + squareChanges{square.A8: pieceChanges{ + piece.New(piece.Black, piece.Rook): false, + piece.New(piece.Black, piece.Queen): true, + }}, + }, + {"White-Queen-B1", square.B1, piece.New(piece.White, piece.Queen), + squareChanges{square.B1: pieceChanges{ + piece.New(piece.White, piece.Knight): false, + piece.New(piece.White, piece.Queen): true, + }}, + }, + {"Black-Queen-B8", square.B8, piece.New(piece.Black, piece.Queen), + squareChanges{square.B8: pieceChanges{ + piece.New(piece.Black, piece.Knight): false, + piece.New(piece.Black, piece.Queen): true, + }}, + }, + {"White-Queen-C1", square.C1, piece.New(piece.White, piece.Queen), + squareChanges{square.C1: pieceChanges{ + piece.New(piece.White, piece.Bishop): false, + piece.New(piece.White, piece.Queen): true, + }}, + }, + {"Black-Queen-C8", square.C8, piece.New(piece.Black, piece.Queen), + squareChanges{square.C8: pieceChanges{ + piece.New(piece.Black, piece.Bishop): false, + piece.New(piece.Black, piece.Queen): true, + }}, + }, + {"White-Queen-D1", square.D1, piece.New(piece.White, piece.Queen), + squareChanges{}, + }, + {"Black-Queen-D8", square.D8, piece.New(piece.Black, piece.Queen), + squareChanges{}, + }, + {"White-Queen-E1", square.E1, piece.New(piece.White, piece.Queen), + squareChanges{square.E1: pieceChanges{ + piece.New(piece.White, piece.King): false, + piece.New(piece.White, piece.Queen): true, + }}, + }, + {"Black-Queen-E8", square.E8, piece.New(piece.Black, piece.Queen), + squareChanges{square.E8: pieceChanges{ + piece.New(piece.Black, piece.King): false, + piece.New(piece.Black, piece.Queen): true, + }}, + }, + {"White-Queen-D6", square.D6, piece.New(piece.White, piece.Queen), + squareChanges{square.D6: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-D3", square.D3, piece.New(piece.Black, piece.Queen), + squareChanges{square.D3: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, + {"White-Queen-D7", square.D7, piece.New(piece.White, piece.Queen), + squareChanges{square.D7: pieceChanges{ + piece.New(piece.Black, piece.Pawn): false, + piece.New(piece.White, piece.Queen): true, + }}, + }, + {"Black-Queen-D2", square.D2, piece.New(piece.Black, piece.Queen), + squareChanges{square.D2: pieceChanges{ + piece.New(piece.White, piece.Pawn): false, + piece.New(piece.Black, piece.Queen): true, + }}, + }, + {"White-Queen-H8", square.H8, piece.New(piece.White, piece.Queen), + squareChanges{square.H8: pieceChanges{ + piece.New(piece.Black, piece.Rook): false, + piece.New(piece.White, piece.Queen): true, + }}, + }, + {"Black-Queen-H1", square.H1, piece.New(piece.Black, piece.Queen), + squareChanges{square.H1: pieceChanges{ + piece.New(piece.White, piece.Rook): false, + piece.New(piece.Black, piece.Queen): true, + }}, + }, + {"White-Queen-G8", square.G8, piece.New(piece.White, piece.Queen), + squareChanges{square.G8: pieceChanges{ + piece.New(piece.Black, piece.Knight): false, + piece.New(piece.White, piece.Queen): true, + }}, + }, + {"Black-Queen-G1", square.G1, piece.New(piece.Black, piece.Queen), + squareChanges{square.G1: pieceChanges{ + piece.New(piece.White, piece.Knight): false, + piece.New(piece.Black, piece.Queen): true, + }}, + }, + {"White-Queen-F8", square.F8, piece.New(piece.White, piece.Queen), + squareChanges{square.F8: pieceChanges{ + piece.New(piece.Black, piece.Bishop): false, + piece.New(piece.White, piece.Queen): true, + }}, + }, + {"Black-Queen-F1", square.F1, piece.New(piece.Black, piece.Queen), + squareChanges{square.F1: pieceChanges{ + piece.New(piece.White, piece.Bishop): false, + piece.New(piece.Black, piece.Queen): true, + }}, + }, + {"White-Queen-E8", square.E8, piece.New(piece.White, piece.Queen), + squareChanges{square.E8: pieceChanges{ + piece.New(piece.Black, piece.King): false, + piece.New(piece.White, piece.Queen): true, + }}, + }, + {"Black-Queen-E1", square.E1, piece.New(piece.Black, piece.Queen), + squareChanges{square.E1: pieceChanges{ + piece.New(piece.White, piece.King): false, + piece.New(piece.Black, piece.Queen): true, + }}, + }, + {"White-Queen-D8", square.D8, piece.New(piece.White, piece.Queen), + squareChanges{square.D8: pieceChanges{ + piece.New(piece.Black, piece.Queen): false, + piece.New(piece.White, piece.Queen): true, + }}, + }, + {"Black-Queen-D1", square.D1, piece.New(piece.Black, piece.Queen), + squareChanges{square.D1: pieceChanges{ + piece.New(piece.White, piece.Queen): false, + piece.New(piece.Black, piece.Queen): true, + }}, + }, + + // Kings. + {"White-King-E3", square.E3, piece.New(piece.White, piece.King), + squareChanges{square.E3: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-E6", square.E6, piece.New(piece.Black, piece.King), + squareChanges{square.E6: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + {"White-King-E2", square.E2, piece.New(piece.White, piece.King), + squareChanges{square.E2: pieceChanges{ + piece.New(piece.White, piece.Pawn): false, + piece.New(piece.White, piece.King): true, + }}, + }, + {"Black-King-E7", square.E7, piece.New(piece.Black, piece.King), + squareChanges{square.E7: pieceChanges{ + piece.New(piece.Black, piece.Pawn): false, + piece.New(piece.Black, piece.King): true, + }}, + }, + {"White-King-A1", square.A1, piece.New(piece.White, piece.King), + squareChanges{square.A1: pieceChanges{ + piece.New(piece.White, piece.Rook): false, + piece.New(piece.White, piece.King): true, + }}, + }, + {"Black-King-A8", square.A8, piece.New(piece.Black, piece.King), + squareChanges{square.A8: pieceChanges{ + piece.New(piece.Black, piece.Rook): false, + piece.New(piece.Black, piece.King): true, + }}, + }, + {"White-King-B1", square.B1, piece.New(piece.White, piece.King), + squareChanges{square.B1: pieceChanges{ + piece.New(piece.White, piece.Knight): false, + piece.New(piece.White, piece.King): true, + }}, + }, + {"Black-King-B8", square.B8, piece.New(piece.Black, piece.King), + squareChanges{square.B8: pieceChanges{ + piece.New(piece.Black, piece.Knight): false, + piece.New(piece.Black, piece.King): true, + }}, + }, + {"White-King-C1", square.C1, piece.New(piece.White, piece.King), + squareChanges{square.C1: pieceChanges{ + piece.New(piece.White, piece.Bishop): false, + piece.New(piece.White, piece.King): true, + }}, + }, + {"Black-King-C8", square.C8, piece.New(piece.Black, piece.King), + squareChanges{square.C8: pieceChanges{ + piece.New(piece.Black, piece.Bishop): false, + piece.New(piece.Black, piece.King): true, + }}, + }, + {"White-King-D1", square.D1, piece.New(piece.White, piece.King), + squareChanges{square.D1: pieceChanges{ + piece.New(piece.White, piece.Queen): false, + piece.New(piece.White, piece.King): true, + }}, + }, + {"Black-King-D8", square.D8, piece.New(piece.Black, piece.King), + squareChanges{square.D8: pieceChanges{ + piece.New(piece.Black, piece.Queen): false, + piece.New(piece.Black, piece.King): true, + }}, + }, + {"White-King-E1", square.E1, piece.New(piece.White, piece.King), + squareChanges{}, + }, + {"Black-King-E8", square.E8, piece.New(piece.Black, piece.King), + squareChanges{}, + }, + {"White-King-E6", square.E6, piece.New(piece.White, piece.King), + squareChanges{square.E6: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-E3", square.E3, piece.New(piece.Black, piece.King), + squareChanges{square.E3: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + {"White-King-E7", square.E7, piece.New(piece.White, piece.King), + squareChanges{square.E7: pieceChanges{ + piece.New(piece.Black, piece.Pawn): false, + piece.New(piece.White, piece.King): true, + }}, + }, + {"Black-King-E2", square.E2, piece.New(piece.Black, piece.King), + squareChanges{square.E2: pieceChanges{ + piece.New(piece.White, piece.Pawn): false, + piece.New(piece.Black, piece.King): true, + }}, + }, + {"White-King-H8", square.H8, piece.New(piece.White, piece.King), + squareChanges{square.H8: pieceChanges{ + piece.New(piece.Black, piece.Rook): false, + piece.New(piece.White, piece.King): true, + }}, + }, + {"Black-King-H1", square.H1, piece.New(piece.Black, piece.King), + squareChanges{square.H1: pieceChanges{ + piece.New(piece.White, piece.Rook): false, + piece.New(piece.Black, piece.King): true, + }}, + }, + {"White-King-G8", square.G8, piece.New(piece.White, piece.King), + squareChanges{square.G8: pieceChanges{ + piece.New(piece.Black, piece.Knight): false, + piece.New(piece.White, piece.King): true, + }}, + }, + {"Black-King-G1", square.G1, piece.New(piece.Black, piece.King), + squareChanges{square.G1: pieceChanges{ + piece.New(piece.White, piece.Knight): false, + piece.New(piece.Black, piece.King): true, + }}, + }, + {"White-King-F8", square.F8, piece.New(piece.White, piece.King), + squareChanges{square.F8: pieceChanges{ + piece.New(piece.Black, piece.Bishop): false, + piece.New(piece.White, piece.King): true, + }}, + }, + {"Black-King-F1", square.F1, piece.New(piece.Black, piece.King), + squareChanges{square.F1: pieceChanges{ + piece.New(piece.White, piece.Bishop): false, + piece.New(piece.Black, piece.King): true, + }}, + }, + {"White-King-E8", square.E8, piece.New(piece.White, piece.King), + squareChanges{square.E8: pieceChanges{ + piece.New(piece.Black, piece.King): false, + piece.New(piece.White, piece.King): true, + }}, + }, + {"Black-King-E1", square.E1, piece.New(piece.Black, piece.King), + squareChanges{square.E1: pieceChanges{ + piece.New(piece.White, piece.King): false, + piece.New(piece.Black, piece.King): true, + }}, + }, + {"White-King-D8", square.D8, piece.New(piece.White, piece.King), + squareChanges{square.D8: pieceChanges{ + piece.New(piece.Black, piece.Queen): false, + piece.New(piece.White, piece.King): true, + }}, + }, + {"Black-King-D1", square.D1, piece.New(piece.Black, piece.King), + squareChanges{square.D1: pieceChanges{ + piece.New(piece.White, piece.Queen): false, + piece.New(piece.Black, piece.King): true, + }}, + }, + + // Non-standard squares and pieces. + {"White-Pawn-NoSquare", square.NoSquare, piece.New(piece.White, piece.Pawn), + squareChanges{}, + }, + {"Black-Pawn-NoSquare", square.NoSquare, piece.New(piece.Black, piece.Pawn), + squareChanges{}, + }, + {"White-Pawn-Square(100)", square.Square(100), piece.New(piece.White, piece.Pawn), + squareChanges{}, + }, + {"Black-Pawn-Square(100)", square.Square(100), piece.New(piece.Black, piece.Pawn), + squareChanges{}, + }, + {"White-None-A1", square.A1, piece.New(piece.White, piece.None), + squareChanges{square.A1: pieceChanges{piece.New(piece.White, piece.Rook): false}}, + }, + {"Black-None-A8", square.A8, piece.New(piece.Black, piece.None), + squareChanges{square.A8: pieceChanges{piece.New(piece.Black, piece.Rook): false}}, + }, + {"White-Type(10)-A1", square.A1, piece.New(piece.White, piece.Type(10)), + squareChanges{square.A1: pieceChanges{piece.New(piece.White, piece.Rook): false}}, + }, + {"Black-Type(10)-A8", square.A8, piece.New(piece.Black, piece.Type(10)), + squareChanges{square.A8: pieceChanges{piece.New(piece.Black, piece.Rook): false}}, + }, + {"NoColor-Pawn-A1", square.A1, piece.New(piece.NoColor, piece.Pawn), + squareChanges{square.A1: pieceChanges{piece.New(piece.White, piece.Rook): false}}, + }, + {"NoColor-Pawn-A8", square.A8, piece.New(piece.NoColor, piece.Pawn), + squareChanges{square.A8: pieceChanges{piece.New(piece.Black, piece.Rook): false}}, + }, + {"Color(5)-Pawn-A1", square.A1, piece.New(piece.Color(5), piece.Pawn), + squareChanges{square.A1: pieceChanges{piece.New(piece.White, piece.Rook): false}}, + }, + {"Color(5)-Pawn-A8", square.A8, piece.New(piece.Color(5), piece.Pawn), + squareChanges{square.A8: pieceChanges{piece.New(piece.Black, piece.Rook): false}}, + }, + {"NoColor-None-A1", square.A1, piece.New(piece.NoColor, piece.None), + squareChanges{square.A1: pieceChanges{piece.New(piece.White, piece.Rook): false}}, + }, + {"NoColor-None-A8", square.A8, piece.New(piece.NoColor, piece.None), + squareChanges{square.A8: pieceChanges{piece.New(piece.Black, piece.Rook): false}}, + }, + {"NoColor-Type(10)-A1", square.A1, piece.New(piece.NoColor, piece.Type(10)), + squareChanges{square.A1: pieceChanges{piece.New(piece.White, piece.Rook): false}}, + }, + {"NoColor-Type(10)-A8", square.A8, piece.New(piece.NoColor, piece.Type(10)), + squareChanges{square.A8: pieceChanges{piece.New(piece.Black, piece.Rook): false}}, + }, + {"Color(5)-None-A1", square.A1, piece.New(piece.Color(5), piece.None), + squareChanges{square.A1: pieceChanges{piece.New(piece.White, piece.Rook): false}}, + }, + {"Color(5)-None-A8", square.A8, piece.New(piece.Color(5), piece.None), + squareChanges{square.A8: pieceChanges{piece.New(piece.Black, piece.Rook): false}}, + }, + {"Color(5)-Type(10)-A1", square.A1, piece.New(piece.Color(5), piece.Type(10)), + squareChanges{square.A1: pieceChanges{piece.New(piece.White, piece.Rook): false}}, + }, + {"Color(5)-Type(10)-A8", square.A8, piece.New(piece.Color(5), piece.Type(10)), + squareChanges{square.A8: pieceChanges{piece.New(piece.Black, piece.Rook): false}}, + }, + {"NoColor-None-NoSquare", square.NoSquare, piece.New(piece.NoColor, piece.None), + squareChanges{}, + }, + {"NoColor-None-NoSquare", square.NoSquare, piece.New(piece.NoColor, piece.None), + squareChanges{}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + p := New() + defer func() { + if err := recover(); err != nil { + t.Errorf("For initial board, *Position.Put(%v, %v) should not panic, but panicked with: %v", tc.pc, tc.sq, err) + } + }() + p.Put(tc.pc, tc.sq) + ch := changedSquares(New(), p) + if !reflect.DeepEqual(ch, tc.wantChanged) { + t.Errorf("For initial board and *Position.Put(%v, %v), board changes are %v, want %v", tc.pc, tc.sq, ch, tc.wantChanged) + } + }) + } +} + +func TestPositionQuickPut(t *testing.T) { + testCases := []struct { + name string + sq square.Square + pc piece.Piece + wantChanged squareChanges + }{ + // Pawns. + {"White-Pawn-A3", square.A3, piece.New(piece.White, piece.Pawn), + squareChanges{square.A3: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-A6", square.A6, piece.New(piece.Black, piece.Pawn), + squareChanges{square.A6: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + {"White-Pawn-A2", square.A2, piece.New(piece.White, piece.Pawn), + squareChanges{}, + }, + {"Black-Pawn-A7", square.A7, piece.New(piece.Black, piece.Pawn), + squareChanges{}, + }, + {"White-Pawn-A1", square.A1, piece.New(piece.White, piece.Pawn), + squareChanges{square.A1: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-A8", square.A8, piece.New(piece.Black, piece.Pawn), + squareChanges{square.A8: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + {"White-Pawn-B1", square.B1, piece.New(piece.White, piece.Pawn), + squareChanges{square.B1: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-B8", square.B8, piece.New(piece.Black, piece.Pawn), + squareChanges{square.B8: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + {"White-Pawn-C1", square.C1, piece.New(piece.White, piece.Pawn), + squareChanges{square.C1: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-C8", square.C8, piece.New(piece.Black, piece.Pawn), + squareChanges{square.C8: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + {"White-Pawn-D1", square.D1, piece.New(piece.White, piece.Pawn), + squareChanges{square.D1: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-D8", square.D8, piece.New(piece.Black, piece.Pawn), + squareChanges{square.D8: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + {"White-Pawn-E1", square.E1, piece.New(piece.White, piece.Pawn), + squareChanges{square.E1: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-E8", square.E8, piece.New(piece.Black, piece.Pawn), + squareChanges{square.E8: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + {"White-Pawn-H6", square.H6, piece.New(piece.White, piece.Pawn), + squareChanges{square.H6: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-H3", square.H3, piece.New(piece.Black, piece.Pawn), + squareChanges{square.H3: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + {"White-Pawn-H7", square.H7, piece.New(piece.White, piece.Pawn), + squareChanges{square.H7: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-H2", square.H2, piece.New(piece.Black, piece.Pawn), + squareChanges{square.H2: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + {"White-Pawn-H8", square.H8, piece.New(piece.White, piece.Pawn), + squareChanges{square.H8: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-H1", square.H1, piece.New(piece.Black, piece.Pawn), + squareChanges{square.H1: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + {"White-Pawn-G8", square.G8, piece.New(piece.White, piece.Pawn), + squareChanges{square.G8: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-G1", square.G1, piece.New(piece.Black, piece.Pawn), + squareChanges{square.G1: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + {"White-Pawn-F8", square.F8, piece.New(piece.White, piece.Pawn), + squareChanges{square.F8: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-F1", square.F1, piece.New(piece.Black, piece.Pawn), + squareChanges{square.F1: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + {"White-Pawn-E8", square.E8, piece.New(piece.White, piece.Pawn), + squareChanges{square.E8: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-E1", square.E1, piece.New(piece.Black, piece.Pawn), + squareChanges{square.E1: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + {"White-Pawn-D8", square.D8, piece.New(piece.White, piece.Pawn), + squareChanges{square.D8: pieceChanges{piece.New(piece.White, piece.Pawn): true}}, + }, + {"Black-Pawn-D1", square.D1, piece.New(piece.Black, piece.Pawn), + squareChanges{square.D1: pieceChanges{piece.New(piece.Black, piece.Pawn): true}}, + }, + + // Rooks. + {"White-Rook-A3", square.A3, piece.New(piece.White, piece.Rook), + squareChanges{square.A3: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-A6", square.A6, piece.New(piece.Black, piece.Rook), + squareChanges{square.A6: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + {"White-Rook-A2", square.A2, piece.New(piece.White, piece.Rook), + squareChanges{square.A2: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-A7", square.A7, piece.New(piece.Black, piece.Rook), + squareChanges{square.A7: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + {"White-Rook-A1", square.A1, piece.New(piece.White, piece.Rook), + squareChanges{}, + }, + {"Black-Rook-A8", square.A8, piece.New(piece.Black, piece.Rook), + squareChanges{}, + }, + {"White-Rook-B1", square.B1, piece.New(piece.White, piece.Rook), + squareChanges{square.B1: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-B8", square.B8, piece.New(piece.Black, piece.Rook), + squareChanges{square.B8: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + {"White-Rook-C1", square.C1, piece.New(piece.White, piece.Rook), + squareChanges{square.C1: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-C8", square.C8, piece.New(piece.Black, piece.Rook), + squareChanges{square.C8: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + {"White-Rook-D1", square.D1, piece.New(piece.White, piece.Rook), + squareChanges{square.D1: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-D8", square.D8, piece.New(piece.Black, piece.Rook), + squareChanges{square.D8: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + {"White-Rook-E1", square.E1, piece.New(piece.White, piece.Rook), + squareChanges{square.E1: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-E8", square.E8, piece.New(piece.Black, piece.Rook), + squareChanges{square.E8: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + {"White-Rook-H6", square.H6, piece.New(piece.White, piece.Rook), + squareChanges{square.H6: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-H3", square.H3, piece.New(piece.Black, piece.Rook), + squareChanges{square.H3: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + {"White-Rook-H7", square.H7, piece.New(piece.White, piece.Rook), + squareChanges{square.H7: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-H2", square.H2, piece.New(piece.Black, piece.Rook), + squareChanges{square.H2: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + {"White-Rook-H8", square.H8, piece.New(piece.White, piece.Rook), + squareChanges{square.H8: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-H1", square.H1, piece.New(piece.Black, piece.Rook), + squareChanges{square.H1: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + {"White-Rook-G8", square.G8, piece.New(piece.White, piece.Rook), + squareChanges{square.G8: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-G1", square.G1, piece.New(piece.Black, piece.Rook), + squareChanges{square.G1: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + {"White-Rook-F8", square.F8, piece.New(piece.White, piece.Rook), + squareChanges{square.F8: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-F1", square.F1, piece.New(piece.Black, piece.Rook), + squareChanges{square.F1: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + {"White-Rook-E8", square.E8, piece.New(piece.White, piece.Rook), + squareChanges{square.E8: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-E1", square.E1, piece.New(piece.Black, piece.Rook), + squareChanges{square.E1: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + {"White-Rook-D8", square.D8, piece.New(piece.White, piece.Rook), + squareChanges{square.D8: pieceChanges{piece.New(piece.White, piece.Rook): true}}, + }, + {"Black-Rook-D1", square.D1, piece.New(piece.Black, piece.Rook), + squareChanges{square.D1: pieceChanges{piece.New(piece.Black, piece.Rook): true}}, + }, + + // Knights. + {"White-Knight-B3", square.B3, piece.New(piece.White, piece.Knight), + squareChanges{square.B3: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-B6", square.B6, piece.New(piece.Black, piece.Knight), + squareChanges{square.B6: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, + {"White-Knight-B2", square.B2, piece.New(piece.White, piece.Knight), + squareChanges{square.B2: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-B7", square.B7, piece.New(piece.Black, piece.Knight), + squareChanges{square.B7: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, + {"White-Knight-A1", square.A1, piece.New(piece.White, piece.Knight), + squareChanges{square.A1: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-A8", square.A8, piece.New(piece.Black, piece.Knight), + squareChanges{square.A8: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, + {"White-Knight-B1", square.B1, piece.New(piece.White, piece.Knight), + squareChanges{}, + }, + {"Black-Knight-B8", square.B8, piece.New(piece.Black, piece.Knight), + squareChanges{}, + }, + {"White-Knight-C1", square.C1, piece.New(piece.White, piece.Knight), + squareChanges{square.C1: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-C8", square.C8, piece.New(piece.Black, piece.Knight), + squareChanges{square.C8: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, + {"White-Knight-D1", square.D1, piece.New(piece.White, piece.Knight), + squareChanges{square.D1: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-D8", square.D8, piece.New(piece.Black, piece.Knight), + squareChanges{square.D8: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, + {"White-Knight-E1", square.E1, piece.New(piece.White, piece.Knight), + squareChanges{square.E1: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-E8", square.E8, piece.New(piece.Black, piece.Knight), + squareChanges{square.E8: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, + {"White-Knight-G6", square.G6, piece.New(piece.White, piece.Knight), + squareChanges{square.G6: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-G3", square.G3, piece.New(piece.Black, piece.Knight), + squareChanges{square.G3: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, + {"White-Knight-G7", square.G7, piece.New(piece.White, piece.Knight), + squareChanges{square.G7: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-G2", square.G2, piece.New(piece.Black, piece.Knight), + squareChanges{square.G2: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, + {"White-Knight-H8", square.H8, piece.New(piece.White, piece.Knight), + squareChanges{square.H8: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-H1", square.H1, piece.New(piece.Black, piece.Knight), + squareChanges{square.H1: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, + {"White-Knight-G8", square.G8, piece.New(piece.White, piece.Knight), + squareChanges{square.G8: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-G1", square.G1, piece.New(piece.Black, piece.Knight), + squareChanges{square.G1: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, + {"White-Knight-F8", square.F8, piece.New(piece.White, piece.Knight), + squareChanges{square.F8: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-F1", square.F1, piece.New(piece.Black, piece.Knight), + squareChanges{square.F1: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, + {"White-Knight-E8", square.E8, piece.New(piece.White, piece.Knight), + squareChanges{square.E8: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-E1", square.E1, piece.New(piece.Black, piece.Knight), + squareChanges{square.E1: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, + {"White-Knight-D8", square.D8, piece.New(piece.White, piece.Knight), + squareChanges{square.D8: pieceChanges{piece.New(piece.White, piece.Knight): true}}, + }, + {"Black-Knight-D1", square.D1, piece.New(piece.Black, piece.Knight), + squareChanges{square.D1: pieceChanges{piece.New(piece.Black, piece.Knight): true}}, + }, - for c := range before.bitBoard { - for p := range before.bitBoard[piece.Color(c)] { - if before.bitBoard[piece.Color(c)][p] != after.bitBoard[piece.Color(c)][p] { - changed[piece.New(piece.Color(c), piece.Type(p))] = struct{}{} - } - } - } - return changed -} + // Bishops. + {"White-Bishop-C3", square.C3, piece.New(piece.White, piece.Bishop), + squareChanges{square.C3: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-C6", square.C6, piece.New(piece.Black, piece.Bishop), + squareChanges{square.C6: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, + {"White-Bishop-C2", square.C2, piece.New(piece.White, piece.Bishop), + squareChanges{square.C2: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-C7", square.C7, piece.New(piece.Black, piece.Bishop), + squareChanges{square.C7: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, + {"White-Bishop-A1", square.A1, piece.New(piece.White, piece.Bishop), + squareChanges{square.A1: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-A8", square.A8, piece.New(piece.Black, piece.Bishop), + squareChanges{square.A8: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, + {"White-Bishop-B1", square.B1, piece.New(piece.White, piece.Bishop), + squareChanges{square.B1: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-B8", square.B8, piece.New(piece.Black, piece.Bishop), + squareChanges{square.B8: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, + {"White-Bishop-C1", square.C1, piece.New(piece.White, piece.Bishop), + squareChanges{}, + }, + {"Black-Bishop-C8", square.C8, piece.New(piece.Black, piece.Bishop), + squareChanges{}, + }, + {"White-Bishop-D1", square.D1, piece.New(piece.White, piece.Bishop), + squareChanges{square.D1: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-D8", square.D8, piece.New(piece.Black, piece.Bishop), + squareChanges{square.D8: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, + {"White-Bishop-E1", square.E1, piece.New(piece.White, piece.Bishop), + squareChanges{square.E1: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-E8", square.E8, piece.New(piece.Black, piece.Bishop), + squareChanges{square.E8: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, + {"White-Bishop-F6", square.F6, piece.New(piece.White, piece.Bishop), + squareChanges{square.F6: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-F3", square.F3, piece.New(piece.Black, piece.Bishop), + squareChanges{square.F3: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, + {"White-Bishop-F7", square.F7, piece.New(piece.White, piece.Bishop), + squareChanges{square.F7: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-F2", square.F2, piece.New(piece.Black, piece.Bishop), + squareChanges{square.F2: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, + {"White-Bishop-H8", square.H8, piece.New(piece.White, piece.Bishop), + squareChanges{square.H8: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-H1", square.H1, piece.New(piece.Black, piece.Bishop), + squareChanges{square.H1: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, + {"White-Bishop-G8", square.G8, piece.New(piece.White, piece.Bishop), + squareChanges{square.G8: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-G1", square.G1, piece.New(piece.Black, piece.Bishop), + squareChanges{square.G1: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, + {"White-Bishop-F8", square.F8, piece.New(piece.White, piece.Bishop), + squareChanges{square.F8: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-F1", square.F1, piece.New(piece.Black, piece.Bishop), + squareChanges{square.F1: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, + {"White-Bishop-E8", square.E8, piece.New(piece.White, piece.Bishop), + squareChanges{square.E8: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-E1", square.E1, piece.New(piece.Black, piece.Bishop), + squareChanges{square.E1: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, + {"White-Bishop-D8", square.D8, piece.New(piece.White, piece.Bishop), + squareChanges{square.D8: pieceChanges{piece.New(piece.White, piece.Bishop): true}}, + }, + {"Black-Bishop-D1", square.D1, piece.New(piece.Black, piece.Bishop), + squareChanges{square.D1: pieceChanges{piece.New(piece.Black, piece.Bishop): true}}, + }, -func TestMovePawn(t *testing.T) { - beforeMove := New() - afterMove := beforeMove.MakeMove(move.Parse("e2e4")) - changed := changedBitBoards(beforeMove, afterMove) - t.Log("Changed: ", changed) - if _, c := changed[piece.New(piece.White, piece.Pawn)]; !c || len(changed) != 1 { - t.Fail() - } -} + // Queens. + {"White-Queen-D3", square.D3, piece.New(piece.White, piece.Queen), + squareChanges{square.D3: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-D6", square.D6, piece.New(piece.Black, piece.Queen), + squareChanges{square.D6: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, + {"White-Queen-D2", square.D2, piece.New(piece.White, piece.Queen), + squareChanges{square.D2: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-D7", square.D7, piece.New(piece.Black, piece.Queen), + squareChanges{square.D7: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, + {"White-Queen-A1", square.A1, piece.New(piece.White, piece.Queen), + squareChanges{square.A1: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-A8", square.A8, piece.New(piece.Black, piece.Queen), + squareChanges{square.A8: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, + {"White-Queen-B1", square.B1, piece.New(piece.White, piece.Queen), + squareChanges{square.B1: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-B8", square.B8, piece.New(piece.Black, piece.Queen), + squareChanges{square.B8: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, + {"White-Queen-C1", square.C1, piece.New(piece.White, piece.Queen), + squareChanges{square.C1: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-C8", square.C8, piece.New(piece.Black, piece.Queen), + squareChanges{square.C8: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, + {"White-Queen-D1", square.D1, piece.New(piece.White, piece.Queen), + squareChanges{}, + }, + {"Black-Queen-D8", square.D8, piece.New(piece.Black, piece.Queen), + squareChanges{}, + }, + {"White-Queen-E1", square.E1, piece.New(piece.White, piece.Queen), + squareChanges{square.E1: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-E8", square.E8, piece.New(piece.Black, piece.Queen), + squareChanges{square.E8: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, + {"White-Queen-D6", square.D6, piece.New(piece.White, piece.Queen), + squareChanges{square.D6: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-D3", square.D3, piece.New(piece.Black, piece.Queen), + squareChanges{square.D3: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, + {"White-Queen-D7", square.D7, piece.New(piece.White, piece.Queen), + squareChanges{square.D7: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-D2", square.D2, piece.New(piece.Black, piece.Queen), + squareChanges{square.D2: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, + {"White-Queen-H8", square.H8, piece.New(piece.White, piece.Queen), + squareChanges{square.H8: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-H1", square.H1, piece.New(piece.Black, piece.Queen), + squareChanges{square.H1: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, + {"White-Queen-G8", square.G8, piece.New(piece.White, piece.Queen), + squareChanges{square.G8: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-G1", square.G1, piece.New(piece.Black, piece.Queen), + squareChanges{square.G1: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, + {"White-Queen-F8", square.F8, piece.New(piece.White, piece.Queen), + squareChanges{square.F8: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-F1", square.F1, piece.New(piece.Black, piece.Queen), + squareChanges{square.F1: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, + {"White-Queen-E8", square.E8, piece.New(piece.White, piece.Queen), + squareChanges{square.E8: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-E1", square.E1, piece.New(piece.Black, piece.Queen), + squareChanges{square.E1: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, + {"White-Queen-D8", square.D8, piece.New(piece.White, piece.Queen), + squareChanges{square.D8: pieceChanges{piece.New(piece.White, piece.Queen): true}}, + }, + {"Black-Queen-D1", square.D1, piece.New(piece.Black, piece.Queen), + squareChanges{square.D1: pieceChanges{piece.New(piece.Black, piece.Queen): true}}, + }, -func TestMoveKnight(t *testing.T) { - beforeMove := New() - afterMove := beforeMove.MakeMove(move.Parse("b1c3")) - changed := changedBitBoards(beforeMove, afterMove) - t.Log("Changed: ", changed) - if _, c := changed[piece.New(piece.White, piece.Knight)]; !c || len(changed) != 1 { - t.Fail() + // Kings. + {"White-King-E3", square.E3, piece.New(piece.White, piece.King), + squareChanges{square.E3: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-E6", square.E6, piece.New(piece.Black, piece.King), + squareChanges{square.E6: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + {"White-King-E2", square.E2, piece.New(piece.White, piece.King), + squareChanges{square.E2: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-E7", square.E7, piece.New(piece.Black, piece.King), + squareChanges{square.E7: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + {"White-King-A1", square.A1, piece.New(piece.White, piece.King), + squareChanges{square.A1: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-A8", square.A8, piece.New(piece.Black, piece.King), + squareChanges{square.A8: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + {"White-King-B1", square.B1, piece.New(piece.White, piece.King), + squareChanges{square.B1: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-B8", square.B8, piece.New(piece.Black, piece.King), + squareChanges{square.B8: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + {"White-King-C1", square.C1, piece.New(piece.White, piece.King), + squareChanges{square.C1: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-C8", square.C8, piece.New(piece.Black, piece.King), + squareChanges{square.C8: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + {"White-King-D1", square.D1, piece.New(piece.White, piece.King), + squareChanges{square.D1: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-D8", square.D8, piece.New(piece.Black, piece.King), + squareChanges{square.D8: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + {"White-King-E1", square.E1, piece.New(piece.White, piece.King), + squareChanges{}, + }, + {"Black-King-E8", square.E8, piece.New(piece.Black, piece.King), + squareChanges{}, + }, + {"White-King-E6", square.E6, piece.New(piece.White, piece.King), + squareChanges{square.E6: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-E3", square.E3, piece.New(piece.Black, piece.King), + squareChanges{square.E3: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + {"White-King-E7", square.E7, piece.New(piece.White, piece.King), + squareChanges{square.E7: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-E2", square.E2, piece.New(piece.Black, piece.King), + squareChanges{square.E2: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + {"White-King-H8", square.H8, piece.New(piece.White, piece.King), + squareChanges{square.H8: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-H1", square.H1, piece.New(piece.Black, piece.King), + squareChanges{square.H1: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + {"White-King-G8", square.G8, piece.New(piece.White, piece.King), + squareChanges{square.G8: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-G1", square.G1, piece.New(piece.Black, piece.King), + squareChanges{square.G1: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + {"White-King-F8", square.F8, piece.New(piece.White, piece.King), + squareChanges{square.F8: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-F1", square.F1, piece.New(piece.Black, piece.King), + squareChanges{square.F1: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + {"White-King-E8", square.E8, piece.New(piece.White, piece.King), + squareChanges{square.E8: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-E1", square.E1, piece.New(piece.Black, piece.King), + squareChanges{square.E1: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + {"White-King-D8", square.D8, piece.New(piece.White, piece.King), + squareChanges{square.D8: pieceChanges{piece.New(piece.White, piece.King): true}}, + }, + {"Black-King-D1", square.D1, piece.New(piece.Black, piece.King), + squareChanges{square.D1: pieceChanges{piece.New(piece.Black, piece.King): true}}, + }, + + // Non-standard squares and pieces. + {"White-Pawn-NoSquare", square.NoSquare, piece.New(piece.White, piece.Pawn), + squareChanges{}, + }, + {"Black-Pawn-NoSquare", square.NoSquare, piece.New(piece.Black, piece.Pawn), + squareChanges{}, + }, + {"White-Pawn-Square(100)", square.Square(100), piece.New(piece.White, piece.Pawn), + squareChanges{}, + }, + {"Black-Pawn-Square(100)", square.Square(100), piece.New(piece.Black, piece.Pawn), + squareChanges{}, + }, + {"White-None-A1", square.A1, piece.New(piece.White, piece.None), + squareChanges{}, + }, + {"Black-None-A8", square.A8, piece.New(piece.Black, piece.None), + squareChanges{}, + }, + {"White-Type(10)-A1", square.A1, piece.New(piece.White, piece.Type(10)), + squareChanges{}, + }, + {"Black-Type(10)-A8", square.A8, piece.New(piece.Black, piece.Type(10)), + squareChanges{}, + }, + {"NoColor-Pawn-A1", square.A1, piece.New(piece.NoColor, piece.Pawn), + squareChanges{}, + }, + {"NoColor-Pawn-A8", square.A8, piece.New(piece.NoColor, piece.Pawn), + squareChanges{}, + }, + {"Color(5)-Pawn-A1", square.A1, piece.New(piece.Color(5), piece.Pawn), + squareChanges{}, + }, + {"Color(5)-Pawn-A8", square.A8, piece.New(piece.Color(5), piece.Pawn), + squareChanges{}, + }, + {"NoColor-None-A1", square.A1, piece.New(piece.NoColor, piece.None), + squareChanges{}, + }, + {"NoColor-None-A8", square.A8, piece.New(piece.NoColor, piece.None), + squareChanges{}, + }, + {"NoColor-Type(10)-A1", square.A1, piece.New(piece.NoColor, piece.Type(10)), + squareChanges{}, + }, + {"NoColor-Type(10)-A8", square.A8, piece.New(piece.NoColor, piece.Type(10)), + squareChanges{}, + }, + {"Color(5)-None-A1", square.A1, piece.New(piece.Color(5), piece.None), + squareChanges{}, + }, + {"Color(5)-None-A8", square.A8, piece.New(piece.Color(5), piece.None), + squareChanges{}, + }, + {"Color(5)-Type(10)-A1", square.A1, piece.New(piece.Color(5), piece.Type(10)), + squareChanges{}, + }, + {"Color(5)-Type(10)-A8", square.A8, piece.New(piece.Color(5), piece.Type(10)), + squareChanges{}, + }, + {"NoColor-None-NoSquare", square.NoSquare, piece.New(piece.NoColor, piece.None), + squareChanges{}, + }, + {"NoColor-None-Square(100)", square.Square(100), piece.New(piece.NoColor, piece.None), + squareChanges{}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + p := New() + defer func() { + if err := recover(); err != nil { + t.Errorf("For initial board, *Position.QuickPut(%v, %v) should not panic, but panicked with: %v", tc.pc, tc.sq, err) + } + }() + p.QuickPut(tc.pc, tc.sq) + ch := changedSquares(New(), p) + if !reflect.DeepEqual(ch, tc.wantChanged) { + t.Errorf("For initial board and *Position.QuickPut(%v, %v), board changes are %v, want %v", tc.pc, tc.sq, ch, tc.wantChanged) + } + }) } } @@ -64,14 +2267,99 @@ func TestPutOnOccSquare(t *testing.T) { } } +type testFindGroup struct { + Name string + Position testPosition + TestCases []testFindTestCase +} + +type testFindTestCase struct { + Name string + Piece piece.Piece + Want map[square.Square]struct{} +} + func TestFind(t *testing.T) { - b := New() - s := b.Find(piece.New(piece.White, piece.King)) - if len(s) != 1 { - t.Fail() + testFindGroups := []testFindGroup{ + { + "ZeroOrOneResult", + testPosition{ + square.E1: piece.New(piece.White, piece.King), + square.E2: piece.New(piece.White, piece.Pawn), + square.E8: piece.New(piece.Black, piece.King), + square.D7: piece.New(piece.Black, piece.Pawn), + }, + []testFindTestCase{ + {"WhiteQueen", piece.New(piece.White, piece.Queen), map[square.Square]struct{}{}}, + {"BlackKnight", piece.New(piece.Black, piece.Knight), map[square.Square]struct{}{}}, + {"WhiteBishop", piece.New(piece.White, piece.Bishop), map[square.Square]struct{}{}}, + {"BlackRook", piece.New(piece.Black, piece.Rook), map[square.Square]struct{}{}}, + {"WhiteKnight", piece.New(piece.White, piece.Knight), map[square.Square]struct{}{}}, + {"WhiteKing", piece.New(piece.White, piece.King), map[square.Square]struct{}{square.E1: struct{}{}}}, + {"WhitePawn", piece.New(piece.White, piece.Pawn), map[square.Square]struct{}{square.E2: struct{}{}}}, + }, + }, + { + "TwoOrFourResults", + testPosition{ + square.A2: piece.New(piece.White, piece.Pawn), + square.B2: piece.New(piece.White, piece.Pawn), + square.C2: piece.New(piece.White, piece.Pawn), + square.D2: piece.New(piece.White, piece.Pawn), + square.G7: piece.New(piece.Black, piece.Pawn), + square.H7: piece.New(piece.Black, piece.Pawn), + }, + []testFindTestCase{ + {"WhitePawns", piece.New(piece.White, piece.Pawn), map[square.Square]struct{}{square.D2: struct{}{}, square.C2: struct{}{}, square.B2: struct{}{}, square.A2: struct{}{}}}, + {"BlackPawns", piece.New(piece.Black, piece.Pawn), map[square.Square]struct{}{square.H7: struct{}{}, square.G7: struct{}{}}}, + }, + }, + { + "EightOrOneResults", + testPosition(nil), // Initial chess board. + []testFindTestCase{ + {"WhitePawns", piece.New(piece.White, piece.Pawn), map[square.Square]struct{}{square.H2: struct{}{}, square.G2: struct{}{}, square.F2: struct{}{}, square.E2: struct{}{}, square.D2: struct{}{}, square.C2: struct{}{}, square.B2: struct{}{}, square.A2: struct{}{}}}, + {"BlackPawns", piece.New(piece.Black, piece.Pawn), map[square.Square]struct{}{square.H7: struct{}{}, square.G7: struct{}{}, square.F7: struct{}{}, square.E7: struct{}{}, square.D7: struct{}{}, square.C7: struct{}{}, square.B7: struct{}{}, square.A7: struct{}{}}}, + {"WhiteKing", piece.New(piece.White, piece.King), map[square.Square]struct{}{square.E1: struct{}{}}}, + }, + }, + { + "FindNonStandardPieces", + testPosition(nil), // Initial chess board. + []testFindTestCase{ + {"WhiteTypeNone", piece.New(piece.White, piece.None), map[square.Square]struct{}{}}, + {"WhiteType(piece.TYPE_COUNT)", piece.New(piece.White, piece.Type(piece.TYPE_COUNT)), map[square.Square]struct{}{}}, + {"BlackTypeNone", piece.New(piece.Black, piece.None), map[square.Square]struct{}{}}, + {"BlackType(piece.TYPE_COUNT)", piece.New(piece.Black, piece.Type(piece.TYPE_COUNT)), map[square.Square]struct{}{}}, + {"NoColorTypeNone", piece.New(piece.NoColor, piece.None), map[square.Square]struct{}{}}, + {"NoColorTypeKing", piece.New(piece.NoColor, piece.King), map[square.Square]struct{}{}}, + {"NoColorType(piece.TYPE_COUNT)", piece.New(piece.NoColor, piece.Type(piece.TYPE_COUNT)), map[square.Square]struct{}{}}, + {"Color(5)TypeNone", piece.New(piece.Color(5), piece.None), map[square.Square]struct{}{}}, + {"Color(5)TypeQueen", piece.New(piece.Color(5), piece.Queen), map[square.Square]struct{}{}}, + {"Color(5)Type(piece.TYPE_COUNT)", piece.New(piece.Color(5), piece.Type(piece.TYPE_COUNT)), map[square.Square]struct{}{}}, + }, + }, } - if _, ok := s[square.E1]; !ok { - t.Fail() + for _, group := range testFindGroups { + t.Run(group.Name, func(t *testing.T) { + // Get position. + p, err := testCasePosition(group.Position, nil) + if err != nil { + t.Fatalf("Position preparation error: %s", err) + } + + for _, tc := range group.TestCases { + t.Run(tc.Name, func(t *testing.T) { + // Call Find function with test case input on Position. + s := p.Find(tc.Piece) + + // Compare results with expected values. + if !reflect.DeepEqual(s, tc.Want) { + t.Errorf("*Position.Find(%s): got \n%#v,\n\twant \n%#v\n", tc.Piece, s, tc.Want) + } + }) + } + }) } } @@ -79,6 +2367,9 @@ func TestFind(t *testing.T) { func (b *Position) printBitBoards() { for c := range b.bitBoard { for j := range b.bitBoard[c] { + if j == int(piece.None) { // Skip piece.None. + continue + } fmt.Println(piece.New(piece.Color(c), piece.Type(j))) fmt.Println(BitBoard(b.bitBoard[c][j])) } @@ -113,32 +2404,254 @@ func TestKandBvKandOpB(t *testing.T) { } } -/* -func TestBoardPrint(t *testing.T) { - expected := `+---+---+---+---+---+---+---+---+ -| r | n | b | q | k | b | n | r | -+---+---+---+---+---+---+---+---+ -| p | p | p | p | p | p | p | p | -+---+---+---+---+---+---+---+---+ -| | | | | | | | | -+---+---+---+---+---+---+---+---+ -| | | | | | | | | -+---+---+---+---+---+---+---+---+ -| | | | | | | | | -+---+---+---+---+---+---+---+---+ -| | | | | | | | | -+---+---+---+---+---+---+---+---+ -| P | P | P | P | P | P | P | P | -+---+---+---+---+---+---+---+---+ -| R | N | B | Q | K | B | N | R | -+---+---+---+---+---+---+---+---+ -` - got := New().String() - if got != expected { - t.Error(got) +func TestString(t *testing.T) { + + testCases := []struct { + Name string + Position testPosition + Changes positionChanger + Want string + }{ + {"InitialPosition", nil, nil, ` a b c d e f g h + ┌─────────────────┐ +8│ r n b q k b n r │8 +7│ p p p p p p p p │7 +6│ . . . . . . . . │6 +5│ . . . . . . . . │5 +4│ . . . . . . . . │4 +3│ . . . . . . . . │3 +2│ P P P P P P P P │2 +1│ R N B Q K B N R │1 + └─────────────────┘ + a b c d e f g h + +MoveNumber: 1 +ActiveColor: White +CastlingRights: + White: O-O-O O-O + Black: O-O-O O-O +EnPassant: +LastMove: +FiftyMoveCount: 0 +ThreeFoldCount: 0 +MovesLeft: + White: 0 + Black: 0 +Clocks: + White: 0s + Black: 0s`, + }, + {"InitialPosition-ShortPawnMove-e2e3", nil, makeMove("e2e3"), ` a b c d e f g h + ┌─────────────────┐ +8│ r n b q k b n r │8 +7│ p p p p p p p p │7 +6│ . . . . . . . . │6 +5│ . . . . . . . . │5 +4│ . . . . . . . . │4 +3│ . . . . P . . . │3 +2│ P P P P . P P P │2 +1│ R N B Q K B N R │1 + └─────────────────┘ + a b c d e f g h + +MoveNumber: 1 +ActiveColor: Black +CastlingRights: + White: O-O-O O-O + Black: O-O-O O-O +EnPassant: +LastMove: e2e3 +FiftyMoveCount: 0 +ThreeFoldCount: 1 +MovesLeft: + White: -1 + Black: 0 +Clocks: + White: 0s + Black: 0s`, + }, + {"InitialPosition-LongPawnMove-e2e4", nil, makeMove("e2e4"), ` a b c d e f g h + ┌─────────────────┐ +8│ r n b q k b n r │8 +7│ p p p p p p p p │7 +6│ . . . . . . . . │6 +5│ . . . . . . . . │5 +4│ . . . . P . . . │4 +3│ . . . . . . . . │3 +2│ P P P P . P P P │2 +1│ R N B Q K B N R │1 + └─────────────────┘ + a b c d e f g h + +MoveNumber: 1 +ActiveColor: Black +CastlingRights: + White: O-O-O O-O + Black: O-O-O O-O +EnPassant: e3 +LastMove: e2e4 +FiftyMoveCount: 0 +ThreeFoldCount: 1 +MovesLeft: + White: -1 + Black: 0 +Clocks: + White: 0s + Black: 0s`, + }, + {"InitialPosition-2xLongPawnMove-e2e4-e7e5", nil, + multi( + makeMove("e2e4"), + makeMove("e7e5"), + ), + ` a b c d e f g h + ┌─────────────────┐ +8│ r n b q k b n r │8 +7│ p p p p . p p p │7 +6│ . . . . . . . . │6 +5│ . . . . p . . . │5 +4│ . . . . P . . . │4 +3│ . . . . . . . . │3 +2│ P P P P . P P P │2 +1│ R N B Q K B N R │1 + └─────────────────┘ + a b c d e f g h + +MoveNumber: 2 +ActiveColor: White +CastlingRights: + White: O-O-O O-O + Black: O-O-O O-O +EnPassant: e6 +LastMove: e7e5 +FiftyMoveCount: 0 +ThreeFoldCount: 1 +MovesLeft: + White: -1 + Black: -1 +Clocks: + White: 0s + Black: 0s`, + }, + {"InitialPosition-CastlingRights", nil, + multi( + castling(piece.White, board.ShortSide, false), + castling(piece.White, board.LongSide, false), + castling(piece.Black, board.LongSide, false), + ), + ` a b c d e f g h + ┌─────────────────┐ +8│ r n b q k b n r │8 +7│ p p p p p p p p │7 +6│ . . . . . . . . │6 +5│ . . . . . . . . │5 +4│ . . . . . . . . │4 +3│ . . . . . . . . │3 +2│ P P P P P P P P │2 +1│ R N B Q K B N R │1 + └─────────────────┘ + a b c d e f g h + +MoveNumber: 1 +ActiveColor: White +CastlingRights: + White: + Black: O-O +EnPassant: +LastMove: +FiftyMoveCount: 0 +ThreeFoldCount: 0 +MovesLeft: + White: 0 + Black: 0 +Clocks: + White: 0s + Black: 0s`, + }, + {"InitialPosition-Clocks", nil, + multi( + clock(piece.White, 10*time.Minute), + clock(piece.Black, 5*time.Second), + ), + ` a b c d e f g h + ┌─────────────────┐ +8│ r n b q k b n r │8 +7│ p p p p p p p p │7 +6│ . . . . . . . . │6 +5│ . . . . . . . . │5 +4│ . . . . . . . . │4 +3│ . . . . . . . . │3 +2│ P P P P P P P P │2 +1│ R N B Q K B N R │1 + └─────────────────┘ + a b c d e f g h + +MoveNumber: 1 +ActiveColor: White +CastlingRights: + White: O-O-O O-O + Black: O-O-O O-O +EnPassant: +LastMove: +FiftyMoveCount: 0 +ThreeFoldCount: 0 +MovesLeft: + White: 0 + Black: 0 +Clocks: + White: 10m0s + Black: 5s`, + }, + {"InitialPosition-FiftyMoveCount-10", nil, func(p Position) (Position, error) { + out := *Copy(&p) + out.FiftyMoveCount = 10 + return out, nil + }, + ` a b c d e f g h + ┌─────────────────┐ +8│ r n b q k b n r │8 +7│ p p p p p p p p │7 +6│ . . . . . . . . │6 +5│ . . . . . . . . │5 +4│ . . . . . . . . │4 +3│ . . . . . . . . │3 +2│ P P P P P P P P │2 +1│ R N B Q K B N R │1 + └─────────────────┘ + a b c d e f g h + +MoveNumber: 1 +ActiveColor: White +CastlingRights: + White: O-O-O O-O + Black: O-O-O O-O +EnPassant: +LastMove: +FiftyMoveCount: 10 +ThreeFoldCount: 0 +MovesLeft: + White: 0 + Black: 0 +Clocks: + White: 0s + Black: 0s`, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + // Get position. + p, err := testCasePosition(tc.Position, tc.Changes) + if err != nil { + t.Fatalf("Position preparation error: %s", err) + } + + if s := p.String(); s != tc.Want { + t.Errorf("Position.String() =\n%s\n.\nWant\n%s\n.", s, tc.Want) + } + }) } } -*/ func TestCapture(t *testing.T) { b := New() @@ -208,6 +2721,59 @@ func TestBitBoardPrint(t *testing.T) { } +func TestCheck(t *testing.T) { + onlyWhiteCheckTestPosition := testPosition{ + square.E1: piece.New(piece.White, piece.King), + square.E8: piece.New(piece.Black, piece.King), + square.E7: piece.New(piece.Black, piece.Rook), + } + onlyBlackCheckTestPosition := testPosition{ + square.E1: piece.New(piece.White, piece.King), + square.E2: piece.New(piece.White, piece.Rook), + square.E8: piece.New(piece.Black, piece.King), + } + bothChecksTestPosition := testPosition{ + square.E1: piece.New(piece.White, piece.King), + square.G6: piece.New(piece.White, piece.Bishop), + square.E8: piece.New(piece.Black, piece.King), + square.C3: piece.New(piece.Black, piece.Bishop), + } + + testCases := []struct { + Name string + Position testPosition + col piece.Color + want bool + }{ + {"InitialTestPosition-White", InitialTestPosition, piece.White, false}, + {"InitialTestPosition-Black", InitialTestPosition, piece.Black, false}, + {"InitialTestPosition-NoColor", InitialTestPosition, piece.NoColor, false}, + {"InitialTestPosition-Color(5)", InitialTestPosition, piece.Color(5), false}, + {"onlyWhiteCheckTestPosition-White", onlyWhiteCheckTestPosition, piece.White, true}, + {"onlyWhiteCheckTestPosition-Black", onlyWhiteCheckTestPosition, piece.Black, false}, + {"onlyBlackCheckTestPosition-White", onlyBlackCheckTestPosition, piece.White, false}, + {"onlyBlackCheckTestPosition-Black", onlyBlackCheckTestPosition, piece.Black, true}, + {"bothChecksTestPosition-White", bothChecksTestPosition, piece.White, true}, + {"bothChecksTestPosition-Black", bothChecksTestPosition, piece.Black, true}, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + p := tc.Position.Position() + defer func() { + if err := recover(); err != nil { + t.Logf("Position:\n%v", p) + t.Errorf("Position.Check(%#v) should not panic, but panicked with: %v", tc.col, err) + } + }() + if ch := p.Check(tc.col); ch != tc.want { + t.Logf("Position:\n%v", p) + t.Errorf("Position.Check(%v) = %v, want %v", tc.col, ch, tc.want) + } + }) + } +} + func TestSAN(t *testing.T) { for _, group := range testSANGroups { t.Run(group.Name, func(t *testing.T) { @@ -648,3 +3214,78 @@ var testSANGroups = []testSANGroup{ }, }, } + +// Various boards for testing purposes. +var ( + InitialTestPosition = testPosition(nil) + TwoPawnsAtTwoOpositeKingsTestPosition = testPosition{ + square.E1: piece.New(piece.White, piece.King), + square.E7: piece.New(piece.White, piece.Pawn), + square.E8: piece.New(piece.Black, piece.King), + square.E2: piece.New(piece.Black, piece.Pawn), + } + TwoKingsFourRooksTestPosition = testPosition{ + square.E1: piece.New(piece.White, piece.King), + square.A1: piece.New(piece.White, piece.Rook), + square.H1: piece.New(piece.White, piece.Rook), + square.E8: piece.New(piece.Black, piece.King), + square.A8: piece.New(piece.Black, piece.Rook), + square.H8: piece.New(piece.Black, piece.Rook), + } + EnPassantCaptureTestPosition = testPosition{ + square.E1: piece.New(piece.White, piece.King), + square.B5: piece.New(piece.White, piece.Pawn), + square.F4: piece.New(piece.White, piece.Pawn), + square.E8: piece.New(piece.Black, piece.King), + square.C5: piece.New(piece.Black, piece.Pawn), + square.G4: piece.New(piece.Black, piece.Pawn), + } + PromotionTestPosition = testPosition{ + square.E1: piece.New(piece.White, piece.King), + square.A1: piece.New(piece.White, piece.Rook), + square.B7: piece.New(piece.White, piece.Pawn), + square.C6: piece.New(piece.White, piece.Pawn), + square.G2: piece.New(piece.White, piece.Pawn), + square.E8: piece.New(piece.Black, piece.King), + square.A8: piece.New(piece.Black, piece.Rook), + square.B2: piece.New(piece.Black, piece.Pawn), + square.C3: piece.New(piece.Black, piece.Pawn), + square.G7: piece.New(piece.Black, piece.Pawn), + } +) + +func BenchmarkPositionPut(b *testing.B) { + newSquares := [square.LastSquare + 1]piece.Piece{} + tpe, col := piece.Type(0), piece.Color(0) + for sq := square.Square(0); sq <= square.LastSquare; sq += 1 { + newSquares[sq] = piece.New(col, tpe) + tpe, col = (tpe+1)%(piece.TYPE_COUNT), (col+1)%(piece.Black+1) + } + + pos := New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + pos = New() + for sq := square.Square(0); sq <= square.LastSquare; sq += 1 { + pos.Put(newSquares[sq], sq) + } + } +} + +func BenchmarkPositionQuickPut(b *testing.B) { + newSquares := [square.LastSquare + 1]piece.Piece{} + tpe, col := piece.Type(0), piece.Color(0) + for sq := square.Square(0); sq <= square.LastSquare; sq += 1 { + newSquares[sq] = piece.New(col, tpe) + tpe, col = (tpe+1)%(piece.TYPE_COUNT), (col+1)%(piece.Black+1) + } + + pos := New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + pos = New() + for sq := square.Square(0); sq <= square.LastSquare; sq += 1 { + pos.QuickPut(newSquares[sq], sq) + } + } +}