127 lines
5.0 KiB
Go
127 lines
5.0 KiB
Go
package processor
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/lixenwraith/chess/internal/server/chess"
|
|
"github.com/lixenwraith/chess/internal/server/core"
|
|
"github.com/lixenwraith/chess/internal/server/game"
|
|
)
|
|
|
|
func TestRulesCheck(t *testing.T) {
|
|
const promotion = "8/5KP1/5n2/p3k3/1p3p2/1P6/P7/8 w - - 4 55"
|
|
if r := rulesCheck(promotion, "g7g8"); !errors.Is(r.err, chess.ErrPromotionRequired) {
|
|
t.Errorf("g7g8: error = %v, want ErrPromotionRequired", r.err)
|
|
}
|
|
if r := rulesCheck(promotion, "g7g8q"); r.err != nil || r.fen != "6Q1/5K2/5n2/p3k3/1p3p2/1P6/P7/8 b - - 0 55" {
|
|
t.Errorf("g7g8q: %+v", r)
|
|
}
|
|
if r := rulesCheck(chess.StartFEN, "e2e5"); !errors.Is(r.err, chess.ErrIllegalMove) {
|
|
t.Errorf("e2e5: error = %v, want ErrIllegalMove", r.err)
|
|
}
|
|
if r := rulesCheck("not a fen", "e2e4"); !errors.Is(r.err, chess.ErrInvalidFEN) {
|
|
t.Errorf("bad FEN: error = %v, want ErrInvalidFEN", r.err)
|
|
}
|
|
}
|
|
|
|
func TestNotateLine(t *testing.T) {
|
|
line, err := chess.Replay(chess.StartFEN, []string{"e2e4", "e7e5", "g1f3", "b8c6", "f1b5"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fens := make([]string, 6)
|
|
for i := range fens {
|
|
fens[i] = line.Position(i).FEN()
|
|
}
|
|
moves := []string{"e2e4", "e7e5", "g1f3", "b8c6", "f1b5"}
|
|
if got := strings.Join(notateLine(fens, moves), " "); got != "e4 e5 Nf3 Nc6 Bb5" {
|
|
t.Errorf("SAN = %q", got)
|
|
}
|
|
// A ply the core cannot notate leaves only its own entry empty.
|
|
broken := append([]string(nil), fens...)
|
|
broken[2] = "not a fen"
|
|
if got := notateLine(broken, moves); !reflect.DeepEqual(got, []string{"e4", "e5", "", "Nc6", "Bb5"}) {
|
|
t.Errorf("SAN with a broken position = %q", got)
|
|
}
|
|
if got := notateLine(nil, nil); len(got) != 0 {
|
|
t.Errorf("SAN of no moves = %q", got)
|
|
}
|
|
if got := notateLine(fens[:1], moves[:2]); !reflect.DeepEqual(got, []string{"e4", ""}) {
|
|
t.Errorf("SAN short of positions = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestAdjudicateDraw(t *testing.T) {
|
|
cycle := []string{"g1f3", "g8f6", "f3g1", "f6g8"}
|
|
line, err := chess.Replay(chess.StartFEN, append(cycle, cycle...))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fens := make([]string, 9)
|
|
for i := range fens {
|
|
fens[i] = line.Position(i).FEN()
|
|
}
|
|
|
|
for name, tc := range map[string]struct {
|
|
history []string
|
|
fen string
|
|
want core.Termination
|
|
}{
|
|
"start": {nil, chess.StartFEN, core.TermNone},
|
|
"bare kings": {nil, "4k3/8/8/8/8/8/8/4K3 w - - 0 1", core.TermInsufficientMaterial},
|
|
"knight": {nil, "4k3/8/8/8/8/8/8/3NK3 b - - 3 40", core.TermInsufficientMaterial},
|
|
"rook": {nil, "4k3/8/8/8/8/8/8/R3K3 w - - 3 40", core.TermNone},
|
|
"fifty moves": {nil, "4k3/8/8/8/8/8/8/R3K3 w - - 100 90", core.TermFiftyMoveRule},
|
|
"forty-nine": {nil, "4k3/8/8/8/8/8/8/R3K3 w - - 99 90", core.TermNone},
|
|
"second occurrence": {fens[:4], fens[4], core.TermNone},
|
|
"third occurrence": {fens[:8], fens[8], core.TermThreefoldRepetition},
|
|
"unparsable": {nil, "not a fen", core.TermNone},
|
|
} {
|
|
state, termination := adjudicateDraw(tc.history, tc.fen)
|
|
if termination != tc.want || (tc.want == core.TermNone) != (state == core.StateOngoing) {
|
|
t.Errorf("%s: got %v %q, want %q", name, state, termination, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestActingColor(t *testing.T) {
|
|
human := func(claimedBy string) *core.Player {
|
|
return &core.Player{Type: core.PlayerHuman, ClaimedBy: claimedBy}
|
|
}
|
|
computer := &core.Player{Type: core.PlayerComputer}
|
|
view := func(white, black *core.Player) game.View {
|
|
return game.View{WhitePlayer: white, BlackPlayer: black}
|
|
}
|
|
|
|
for name, tc := range map[string]struct {
|
|
view game.View
|
|
requested string
|
|
user string
|
|
want core.Color
|
|
code string
|
|
}{
|
|
"only human side": {view(human(""), computer), "", "", core.ColorWhite, ""},
|
|
"only human side, black": {view(computer, human("")), "", "", core.ColorBlack, ""},
|
|
"claimed side of caller": {view(human("u1"), human("")), "", "u1", core.ColorWhite, ""},
|
|
"explicit color": {view(human(""), human("")), "b", "", core.ColorBlack, ""},
|
|
"explicit long color": {view(human(""), human("")), "white", "", core.ColorWhite, ""},
|
|
"ambiguous hot seat": {view(human(""), human("")), "", "", 0, core.ErrInvalidRequest},
|
|
"self-play needs color": {view(human("u1"), human("u1")), "", "u1", 0, core.ErrInvalidRequest},
|
|
"computer only": {view(computer, computer), "", "", 0, core.ErrInvalidRequest},
|
|
"computer side requested": {view(human(""), computer), "b", "", 0, core.ErrInvalidRequest},
|
|
"claimed by another": {view(human("u1"), computer), "", "u2", 0, core.ErrUnauthorized},
|
|
"claimed, anonymous caller": {view(human("u1"), computer), "", "", 0, core.ErrUnauthorized},
|
|
} {
|
|
color, failure := actingColor(tc.view, tc.requested, tc.user)
|
|
switch {
|
|
case tc.code != "" && (failure == nil || failure.Error.Code != tc.code):
|
|
t.Errorf("%s: got %v %+v, want error %s", name, color, failure, tc.code)
|
|
case tc.code == "" && (failure != nil || color != tc.want):
|
|
t.Errorf("%s: got %v %+v, want %v", name, color, failure, tc.want)
|
|
}
|
|
}
|
|
}
|