v0.14.0 web replay UI, My games, CLI replay
test / test (push) Canceled after 0s
test / e2e (push) Canceled after 0s

This commit is contained in:
lixen committed 2026-10-19 16:29:01 -04:00
1 parent 87dcb3e550
commit dbe7283ca2
37 files changed
+4058 -1809

No files matched your search

+22
View File
@@ -674,6 +674,7 @@ func (p *Processor) buildGameResponse(gameID string, g game.View) core.GameRespo
State: g.State.String(),
Termination: string(g.Termination),
Moves: g.Moves,
SAN: notateLine(g.FENs, g.Moves),
Players: core.PlayersResponse{
White: g.WhitePlayer,
Black: g.BlackPlayer,
@@ -721,6 +722,27 @@ func (p *Processor) Close() error {
return errors.Join(queueErr, engineErr)
}
// notateLine derives SAN for each live move from the position before it,
// fens[i] being the position before moves[i]. Each ply stands alone, so a
// position the core cannot parse leaves only that entry empty (and is logged
// once, by the shadow check, when the move was made).
func notateLine(fens, moves []string) []string {
san := make([]string, len(moves))
for i, uci := range moves {
if i >= len(fens) {
break
}
pos, err := chess.ParseFEN(fens[i])
if err != nil {
continue
}
if m, err := pos.ParseUCI(uci); err == nil {
san[i] = pos.SAN(m)
}
}
return san
}
// rulesResult is the rules core's verdict on a move, computed alongside the
// engine's. Stockfish stays authoritative; a disagreement is logged so the
// core can be trusted, or fixed, before it validates anything.
+29
View File
@@ -2,6 +2,8 @@ package processor
import (
"errors"
"reflect"
"strings"
"testing"
"github.com/lixenwraith/chess/internal/server/chess"
@@ -25,6 +27,33 @@ func TestRulesCheck(t *testing.T) {
}
}
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...))