v0.11.0 harden persistence and prepare game replays

This commit is contained in:
2026-09-07 14:39:00 -04:00
parent 21dea47694
commit 5d2be4abb2
42 changed files with 3591 additions and 693 deletions
+44 -2
View File
@@ -1,5 +1,7 @@
package core
import "time"
// Request types
type CreateGameRequest struct {
@@ -27,7 +29,7 @@ type GameResponse struct {
GameID string `json:"gameId"`
FEN string `json:"fen"`
Turn string `json:"turn"` // "w" or "b"
State string `json:"state"` // "ongoing", "white_wins", etc
State string `json:"state"` // "ongoing", "white wins", etc
Moves []string `json:"moves"`
Players PlayersResponse `json:"players"`
LastMove *MoveInfo `json:"lastMove,omitempty"`
@@ -45,8 +47,48 @@ type BoardResponse struct {
Board string `json:"board"` // ASCII representation
}
// GameHistoryResponse is the durable replay representation of a game. Moves
// are ordered and include the resulting FEN so clients do not need an engine
// to replay a stored game.
type GameHistoryResponse struct {
GameID string `json:"gameId"`
InitialFEN string `json:"initialFen"`
Result string `json:"result,omitempty"`
StartTimeUTC time.Time `json:"startTimeUtc"`
EndTimeUTC *time.Time `json:"endTimeUtc,omitempty"`
Players PlayersResponse `json:"players"`
Moves []HistoryMove `json:"moves"`
}
type HistoryMove struct {
MoveNumber int `json:"moveNumber"`
MoveUCI string `json:"moveUci"`
FENAfterMove string `json:"fenAfterMove"`
PlayerColor string `json:"playerColor"`
MoveTimeUTC time.Time `json:"moveTimeUtc"`
}
// GameSummary is intentionally sufficient for a client-side game picker; the
// full move list remains on the per-game history endpoint.
type GameSummary struct {
GameID string `json:"gameId"`
InitialFEN string `json:"initialFen"`
Result string `json:"result,omitempty"`
StartTimeUTC time.Time `json:"startTimeUtc"`
EndTimeUTC *time.Time `json:"endTimeUtc,omitempty"`
MoveCount int `json:"moveCount"`
Players PlayersResponse `json:"players"`
}
type GameListResponse struct {
Games []GameSummary `json:"games"`
Limit int `json:"limit"`
Offset int `json:"offset"`
NextOffset *int `json:"nextOffset,omitempty"`
}
type ErrorResponse struct {
Error string `json:"error"`
Code string `json:"code"`
Details string `json:"details,omitempty"`
}
}
+14 -12
View File
@@ -2,15 +2,17 @@ package core
// Error codes
const (
ErrGameNotFound = "GAME_NOT_FOUND"
ErrInvalidMove = "INVALID_MOVE"
ErrNotHumanTurn = "NOT_HUMAN_TURN"
ErrGameOver = "GAME_OVER"
ErrRateLimitExceeded = "RATE_LIMIT_EXCEEDED"
ErrInvalidContent = "INVALID_CONTENT_TYPE"
ErrInvalidRequest = "INVALID_REQUEST"
ErrInvalidFEN = "INVALID_FEN"
ErrInternalError = "INTERNAL_ERROR"
ErrResourceLimit = "RESOURCE_LIMIT"
ErrUnauthorized = "UNAUTHORIZED"
)
ErrGameNotFound = "GAME_NOT_FOUND"
ErrInvalidMove = "INVALID_MOVE"
ErrNotHumanTurn = "NOT_HUMAN_TURN"
ErrGameOver = "GAME_OVER"
ErrRateLimitExceeded = "RATE_LIMIT_EXCEEDED"
ErrInvalidContent = "INVALID_CONTENT_TYPE"
ErrInvalidRequest = "INVALID_REQUEST"
ErrInvalidFEN = "INVALID_FEN"
ErrInternalError = "INTERNAL_ERROR"
ErrResourceLimit = "RESOURCE_LIMIT"
ErrUnauthorized = "UNAUTHORIZED"
ErrConflict = "GAME_CONFLICT"
ErrStorageUnavailable = "STORAGE_UNAVAILABLE"
)
+29 -2
View File
@@ -5,7 +5,7 @@ type State int
const (
StateOngoing State = iota
StatePending // Computer is calculating a move
StateStuck // Computer is calculating a move
StateStuck // Engine work failed and requires recovery or undo
StateWhiteWins
StateBlackWins
StateDraw
@@ -31,4 +31,31 @@ func (s State) String() string {
default:
return "unknown"
}
}
}
// IsTerminal reports whether the game has a durable result. Pending and stuck
// are operational states and must not be archived as completed games.
func (s State) IsTerminal() bool {
switch s {
case StateWhiteWins, StateBlackWins, StateDraw, StateStalemate:
return true
default:
return false
}
}
// Result returns the stable persistence/API value for a terminal state.
func (s State) Result() (string, bool) {
switch s {
case StateWhiteWins:
return "white_wins", true
case StateBlackWins:
return "black_wins", true
case StateDraw:
return "draw", true
case StateStalemate:
return "stalemate", true
default:
return "", false
}
}