v0.11.0 harden persistence and prepare game replays
This commit is contained in:
@@ -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"`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user