package chess import ( "log" "strings" "testing" ) func TestCheckmate(t *testing.T) { fenStr := "rn1qkbnr/pbpp1ppp/1p6/4p3/2B1P3/5Q2/PPPP1PPP/RNB1K1NR w KQkq - 0 1" fen, err := FEN(fenStr) if err != nil { t.Fatal(err) } g := NewGame(fen) if err := g.MoveStr("Qxf7#"); err != nil { t.Fatal(err) } if g.Method() != Checkmate { t.Fatalf("expected method %s but got %s", Checkmate, g.Method()) } if g.Outcome() != WhiteWon { t.Fatalf("expected outcome %s but got %s", WhiteWon, g.Outcome()) } } func TestCheckmateFromFen(t *testing.T) { fenStr := "rn1qkbnr/pbpp1Qpp/1p6/4p3/2B1P3/8/PPPP1PPP/RNB1K1NR b KQkq - 0 1" fen, err := FEN(fenStr) if err != nil { t.Fatal(err) } g := NewGame(fen) if g.Method() != Checkmate { t.Error(g.Position().Board().Draw()) t.Fatalf("expected method %s but got %s", Checkmate, g.Method()) } if g.Outcome() != WhiteWon { t.Fatalf("expected outcome %s but got %s", WhiteWon, g.Outcome()) } } func TestStalemate(t *testing.T) { fenStr := "k1K5/8/8/8/8/8/8/1Q6 w - - 0 1" fen, err := FEN(fenStr) if err != nil { t.Fatal(err) } g := NewGame(fen) if err := g.MoveStr("Qb6"); err != nil { t.Fatal(err) } if g.Method() != Stalemate { t.Fatalf("expected method %s but got %s", Stalemate, g.Method()) } if g.Outcome() != Draw { t.Fatalf("expected outcome %s but got %s", Draw, g.Outcome()) } } // position shouldn't result in stalemate because pawn can move http://en.lichess.org/Pc6mJDZN#138 func TestInvalidStalemate(t *testing.T) { fenStr := "8/3P4/8/8/8/7k/7p/7K w - - 2 70" fen, err := FEN(fenStr) if err != nil { t.Fatal(err) } g := NewGame(fen) if err := g.MoveStr("d8=Q"); err != nil { t.Fatal(err) } if g.Outcome() != NoOutcome { t.Fatalf("expected outcome %s but got %s", NoOutcome, g.Outcome()) } } func TestThreeFoldRepition(t *testing.T) { g := NewGame() moves := []string{ "Nf3", "Nf6", "Ng1", "Ng8", "Nf3", "Nf6", "Ng1", "Ng8", } for _, m := range moves { if err := g.MoveStr(m); err != nil { t.Fatal(err) } } if err := g.Draw(ThreefoldRepetition); err != nil { for _, pos := range g.Positions() { log.Println(pos.String()) } t.Fatalf("%s - %d reps", err.Error(), g.numOfRepitions()) } } func TestInvalidThreeFoldRepition(t *testing.T) { g := NewGame() moves := []string{ "Nf3", "Nf6", "Ng1", "Ng8", } for _, m := range moves { if err := g.MoveStr(m); err != nil { t.Fatal(err) } } if err := g.Draw(ThreefoldRepetition); err == nil { t.Fatal("should require three repeated board states") } } func TestFiveFoldRepition(t *testing.T) { g := NewGame() moves := []string{ "Nf3", "Nf6", "Ng1", "Ng8", "Nf3", "Nf6", "Ng1", "Ng8", "Nf3", "Nf6", "Ng1", "Ng8", "Nf3", "Nf6", "Ng1", "Ng8", } for _, m := range moves { if err := g.MoveStr(m); err != nil { t.Fatal(err) } } if g.Outcome() != Draw || g.Method() != FivefoldRepetition { t.Fatal("should automatically draw after five repetitions") } } func TestFiftyMoveRule(t *testing.T) { fen, _ := FEN("2r3k1/1q1nbppp/r3p3/3pP3/pPpP4/P1Q2N2/2RN1PPP/2R4K b - b3 100 60") g := NewGame(fen) if err := g.Draw(FiftyMoveRule); err != nil { t.Fatal(err) } } func TestInvalidFiftyMoveRule(t *testing.T) { fen, _ := FEN("2r3k1/1q1nbppp/r3p3/3pP3/pPpP4/P1Q2N2/2RN1PPP/2R4K b - b3 99 60") g := NewGame(fen) if err := g.Draw(FiftyMoveRule); err == nil { t.Fatal("should require fifty moves") } } func TestSeventyFiveMoveRule(t *testing.T) { fen, _ := FEN("2r3k1/1q1nbppp/r3p3/3pP3/pPpP4/P1Q2N2/2RN1PPP/2R4K b - b3 149 80") g := NewGame(fen) if err := g.MoveStr("Kf8"); err != nil { t.Fatal(err) } if g.Outcome() != Draw || g.Method() != SeventyFiveMoveRule { t.Fatal("should automatically draw after seventy five moves w/ no pawn move or capture") } } func TestInsufficentMaterial(t *testing.T) { fens := []string{ "8/2k5/8/8/8/3K4/8/8 w - - 1 1", "8/2k5/8/8/8/3K1N2/8/8 w - - 1 1", "8/2k5/8/8/8/3K1B2/8/8 w - - 1 1", "8/2k5/2b5/8/8/3K1B2/8/8 w - - 1 1", "4b3/2k5/2b5/8/8/3K1B2/8/8 w - - 1 1", } for _, f := range fens { fen, err := FEN(f) if err != nil { t.Fatal(err) } g := NewGame(fen) if g.Outcome() != Draw || g.Method() != InsufficientMaterial { log.Println(g.Position().Board().Draw()) t.Fatalf("%s should automatically draw by insufficent material", f) } } } func TestSufficentMaterial(t *testing.T) { fens := []string{ "8/2k5/8/8/8/3K1B2/4N3/8 w - - 1 1", "8/2k5/8/8/8/3KBB2/8/8 w - - 1 1", "8/2k1b3/8/8/8/3K1B2/8/8 w - - 1 1", "8/2k5/8/8/4P3/3K4/8/8 w - - 1 1", "8/2k5/8/8/8/3KQ3/8/8 w - - 1 1", "8/2k5/8/8/8/3KR3/8/8 w - - 1 1", } for _, f := range fens { fen, err := FEN(f) if err != nil { t.Fatal(err) } g := NewGame(fen) if g.Outcome() != NoOutcome { log.Println(g.Position().Board().Draw()) t.Fatalf("%s should not find insufficent material", f) } } } func TestSerializationCycle(t *testing.T) { g := NewGame() pgn, err := PGN(strings.NewReader(g.String())) if err != nil { t.Fatal(err) } cp := NewGame(pgn) if cp.String() != g.String() { t.Fatalf("expected %s but got %s", g.String(), cp.String()) } } func TestInitialNumOfValidMoves(t *testing.T) { g := NewGame() if len(g.ValidMoves()) != 20 { t.Fatal("should find 20 valid moves from the initial position") } } func TestTagPairs(t *testing.T) { g := NewGame() g.AddTagPair("Draw Offer", "White") tagPair := g.GetTagPair("Draw Offer") if tagPair == nil { t.Fatalf("expected %s but got %s", "White", "nil") } if tagPair.Value != "White" { t.Fatalf("expected %s but got %s", "White", tagPair.Value) } g.RemoveTagPair("Draw Offer") tagPair = g.GetTagPair("Draw Offer") if tagPair != nil { t.Fatalf("expected %s but got %s", "nil", "not nil") } } func TestPositionHash(t *testing.T) { g1 := NewGame() for _, s := range []string{"Nc3", "e5", "Nf3"} { g1.MoveStr(s) } g2 := NewGame() for _, s := range []string{"Nf3", "e5", "Nc3"} { g2.MoveStr(s) } if g1.Position().Hash() != g2.Position().Hash() { t.Fatalf("expected position hashes to be equal but got %s and %s", g1.Position().Hash(), g2.Position().Hash()) } } func BenchmarkStalemateStatus(b *testing.B) { fenStr := "k1K5/8/8/8/8/8/8/1Q6 w - - 0 1" fen, err := FEN(fenStr) if err != nil { b.Fatal(err) } g := NewGame(fen) if err := g.MoveStr("Qb6"); err != nil { b.Fatal(err) } b.ResetTimer() for n := 0; n < b.N; n++ { g.Position().Status() } } func BenchmarkInvalidStalemateStatus(b *testing.B) { fenStr := "8/3P4/8/8/8/7k/7p/7K w - - 2 70" fen, err := FEN(fenStr) if err != nil { b.Fatal(err) } g := NewGame(fen) if err := g.MoveStr("d8=Q"); err != nil { b.Fatal(err) } b.ResetTimer() for n := 0; n < b.N; n++ { g.Position().Status() } } func BenchmarkPositionHash(b *testing.B) { fenStr := "8/3P4/8/8/8/7k/7p/7K w - - 2 70" fen, err := FEN(fenStr) if err != nil { b.Fatal(err) } g := NewGame(fen) b.ResetTimer() for n := 0; n < b.N; n++ { g.Position().Hash() } }