Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions game.go
Original file line number Diff line number Diff line change
Expand Up @@ -828,16 +828,37 @@ func (g *Game) Clone() *Game {
// clone do not impact the parent
ret.rootMove = g.rootMove.Clone()
ret.rootMove.cloneChildren(g.rootMove.children)
mlen := len(ret.Moves())
if mlen == 0 {
if g.currentMove == nil {
ret.currentMove = ret.rootMove
} else {
ret.currentMove = ret.Moves()[mlen-1]
ret.currentMove = findClonedMove(g.rootMove, ret.rootMove, g.currentMove)
if ret.currentMove == nil {
ret.currentMove = ret.rootMove
}
}
ret.pos = ret.currentMove.position

return ret
}

func findClonedMove(original, clone, target *Move) *Move {
if original == nil || clone == nil || target == nil {
return nil
}
if original == target {
return clone
}
for i, child := range original.children {
if i >= len(clone.children) {
return nil
}
if found := findClonedMove(child, clone.children[i], target); found != nil {
return found
}
}
return nil
}

// Positions returns all positions in the game in the main line.
// This includes the starting position and all positions after each move.
func (g *Game) Positions() []*Position {
Expand Down
113 changes: 113 additions & 0 deletions game_invariants_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package chess

import (
"strings"
"testing"
)

func assertGameCurrentPositionInvariant(t *testing.T, g *Game) {
t.Helper()

if g == nil {
t.Fatal("game is nil")
}
if g.pos == nil {
t.Fatal("game current position is nil")
}
if g.Position() == nil {
t.Fatal("Position() is nil")
}
if g.CurrentPosition() == nil {
t.Fatal("CurrentPosition() is nil")
}
if g.Position().String() != g.pos.String() {
t.Fatalf("Position() = %q, want game current position %q", g.Position(), g.pos)
}
if g.CurrentPosition().String() != g.pos.String() {
t.Fatalf("CurrentPosition() = %q, want game current position %q", g.CurrentPosition(), g.pos)
}
if g.currentMove != nil && g.currentMove.position != nil && g.currentMove.position.String() != g.pos.String() {
t.Fatalf("current move position = %q, want game current position %q", g.currentMove.position, g.pos)
}
}

func TestGameCurrentPositionInvariantAfterClonePreservesCursor(t *testing.T) {
g := NewGame()
for _, move := range []string{"e4", "e5", "Nf3"} {
if err := g.PushMove(move, nil); err != nil {
t.Fatal(err)
}
}

if !g.GoBack() {
t.Fatal("expected to navigate back")
}
want := g.CurrentPosition().String()

clone := g.Clone()

assertGameCurrentPositionInvariant(t, clone)
if got := clone.CurrentPosition().String(); got != want {
t.Fatalf("clone current position = %q, want %q", got, want)
}
}

func TestGameCurrentPositionInvariantAfterDirectGameOperations(t *testing.T) {
g := NewGame()
assertGameCurrentPositionInvariant(t, g)

for _, move := range []string{"e4", "e5", "Nf3"} {
if err := g.PushMove(move, nil); err != nil {
t.Fatal(err)
}
assertGameCurrentPositionInvariant(t, g)
}

if !g.GoBack() {
t.Fatal("expected to navigate back")
}
assertGameCurrentPositionInvariant(t, g)

if !g.GoForward() {
t.Fatal("expected to navigate forward")
}
assertGameCurrentPositionInvariant(t, g)
}

func TestGameCurrentPositionInvariantAfterPGNParse(t *testing.T) {
opt, err := PGN(strings.NewReader("1. e4 e5 2. Nf3 *"))
if err != nil {
t.Fatal(err)
}

g := NewGame(opt)

assertGameCurrentPositionInvariant(t, g)
}

func TestGameCurrentPositionInvariantAfterSplitUsesLineLeaf(t *testing.T) {
g := NewGame()
for _, move := range []string{"e4", "e5", "Nf3"} {
if err := g.PushMove(move, nil); err != nil {
t.Fatal(err)
}
}

if !g.GoBack() {
t.Fatal("expected to navigate back before adding a variation")
}
if err := g.PushMove("Nc3", nil); err != nil {
t.Fatal(err)
}

splitGames := g.Split()
if len(splitGames) != 2 {
t.Fatalf("split game count = %d, want 2", len(splitGames))
}
for _, splitGame := range splitGames {
assertGameCurrentPositionInvariant(t, splitGame)
if !splitGame.IsAtEnd() {
t.Fatalf("split game current position = %q, want leaf position", splitGame.CurrentPosition())
}
}
}