v0.16.0 import PGN, delete games, two-layout web client
This commit is contained in:
1 parent
e1fa5242e3
commit
664cf9654e
34 files changed
+2007
-150
No files matched your search
@@ -254,6 +254,21 @@ func (c *Client) GetMyGames(limit int, cursor string) (*GameListResponse, error)
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
// RemoveMyGame takes a stored game off the caller's list: deleted when the
|
||||
// caller alone holds it, released when another player keeps it.
|
||||
func (c *Client) RemoveMyGame(gameID string) (*RemoveGameResponse, error) {
|
||||
var resp RemoveGameResponse
|
||||
err := c.doRequest("DELETE", "/api/users/me/games/"+gameID, nil, &resp)
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
// ImportGame stores a PGN game for the caller.
|
||||
func (c *Client) ImportGame(pgn string) (*ImportGameResponse, error) {
|
||||
var resp ImportGameResponse
|
||||
err := c.doRequest("POST", "/api/games/import", &ImportGameRequest{PGN: pgn}, &resp)
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
func (c *Client) Register(username, password, email string) (*AuthResponse, error) {
|
||||
req := &RegisterRequest{
|
||||
Username: username,
|
||||
|
||||
@@ -131,6 +131,10 @@ type GameHistoryResponse struct {
|
||||
StartTimeUTC time.Time `json:"startTimeUtc"`
|
||||
EndTimeUTC *time.Time `json:"endTimeUtc,omitempty"`
|
||||
Concession *Concession `json:"concession,omitempty"` // Result in stored form ("black_wins")
|
||||
Origin string `json:"origin,omitempty"`
|
||||
Event string `json:"event,omitempty"`
|
||||
Site string `json:"site,omitempty"`
|
||||
Round string `json:"round,omitempty"`
|
||||
Players PlayersResponse `json:"players"`
|
||||
Moves []HistoryMove `json:"moves"`
|
||||
}
|
||||
@@ -154,9 +158,26 @@ type GameSummary struct {
|
||||
StartTimeUTC time.Time `json:"startTimeUtc"`
|
||||
EndTimeUTC *time.Time `json:"endTimeUtc,omitempty"`
|
||||
MoveCount int `json:"moveCount"`
|
||||
Origin string `json:"origin,omitempty"`
|
||||
Event string `json:"event,omitempty"`
|
||||
Players PlayersResponse `json:"players"`
|
||||
}
|
||||
|
||||
type ImportGameRequest struct {
|
||||
PGN string `json:"pgn"`
|
||||
}
|
||||
|
||||
type ImportGameResponse struct {
|
||||
GameID string `json:"gameId"`
|
||||
MoveCount int `json:"moveCount"`
|
||||
Result string `json:"result,omitempty"`
|
||||
PGNResult string `json:"pgnResult"`
|
||||
}
|
||||
|
||||
type RemoveGameResponse struct {
|
||||
Removed string `json:"removed"` // "game" or "claim"
|
||||
}
|
||||
|
||||
type GameListResponse struct {
|
||||
Games []GameSummary `json:"games"`
|
||||
Limit int `json:"limit"`
|
||||
|
||||
@@ -98,8 +98,8 @@ func (r *Registry) registerGameCommands() {
|
||||
r.Register(&Command{
|
||||
Name: "games",
|
||||
ShortName: "g",
|
||||
Description: "List your stored games, newest first (login required)",
|
||||
Usage: "games [more]",
|
||||
Description: "List, or delete, your stored games (login required)",
|
||||
Usage: "games [more] | games delete <#|gameId>",
|
||||
Handler: gamesHandler,
|
||||
Paged: true,
|
||||
})
|
||||
@@ -113,6 +113,14 @@ func (r *Registry) registerGameCommands() {
|
||||
Paged: true,
|
||||
})
|
||||
|
||||
r.Register(&Command{
|
||||
Name: "import",
|
||||
ShortName: "",
|
||||
Description: "Store a PGN file as one of your games (login required)",
|
||||
Usage: "import <path>",
|
||||
Handler: importHandler,
|
||||
})
|
||||
|
||||
r.Register(&Command{
|
||||
Name: "poll",
|
||||
ShortName: "p",
|
||||
@@ -686,6 +694,9 @@ func gamesHandler(s *session.Session, args []string) error {
|
||||
if s.AuthToken == "" {
|
||||
return fmt.Errorf("login required")
|
||||
}
|
||||
if len(args) > 0 && args[0] == "delete" {
|
||||
return gamesDeleteHandler(s, args[1:])
|
||||
}
|
||||
cursor := ""
|
||||
if len(args) > 0 && args[0] == "more" {
|
||||
if s.GamesCursor == "" {
|
||||
@@ -718,6 +729,62 @@ func gamesHandler(s *session.Session, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// gamesDeleteHandler removes a game from the user's list by its number in
|
||||
// the last listing or its ID. A game another player also claims is only
|
||||
// released; one the user alone holds is deleted with its moves.
|
||||
func gamesDeleteHandler(s *session.Session, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("usage: games delete <#|gameId>")
|
||||
}
|
||||
gameID := args[0]
|
||||
if index, err := strconv.Atoi(gameID); err == nil {
|
||||
if index < 1 || index > len(s.GamesPage) {
|
||||
return fmt.Errorf("index %d is outside the last listing (1-%d)", index, len(s.GamesPage))
|
||||
}
|
||||
gameID = s.GamesPage[index-1].GameID
|
||||
}
|
||||
resp, err := s.Client.RemoveMyGame(gameID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Removed == "claim" {
|
||||
display.Println(display.Yellow, "Released your side of %s; the other player keeps the game", gameID)
|
||||
} else {
|
||||
display.Println(display.Green, "Deleted game %s", gameID)
|
||||
}
|
||||
if s.Replay != nil && s.Replay.History.GameID == gameID {
|
||||
s.Replay = nil
|
||||
}
|
||||
if s.CurrentGame == gameID && resp.Removed == "game" {
|
||||
s.CurrentGame = ""
|
||||
s.CurrentGameState = nil
|
||||
s.LastMoveCount = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// importHandler stores a PGN file (one game) under the user's account and
|
||||
// makes it the replay.
|
||||
func importHandler(s *session.Session, args []string) error {
|
||||
if s.AuthToken == "" {
|
||||
return fmt.Errorf("login required")
|
||||
}
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("usage: import <path>")
|
||||
}
|
||||
text, err := readImportFile(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := s.Client.ImportGame(text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
display.Println(display.Green, "Imported game %s (%d plies, %s)", resp.GameID, resp.MoveCount, resp.PGNResult)
|
||||
display.Println(display.Cyan, "Replay it: replay %s", resp.GameID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func playerLabel(p api.PlayerInfo) string {
|
||||
switch {
|
||||
case p.Type == 2:
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//go:build !js && !wasm
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
const maxImportFile = 64 << 10
|
||||
|
||||
// readImportFile reads a PGN file for import.
|
||||
func readImportFile(path string) (string, error) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if info.Size() > maxImportFile {
|
||||
return "", fmt.Errorf("%s is larger than %d bytes", path, maxImportFile)
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//go:build js && wasm
|
||||
|
||||
package command
|
||||
|
||||
import "fmt"
|
||||
|
||||
// readImportFile has no file system in the browser; the web board imports
|
||||
// a pasted PGN instead.
|
||||
func readImportFile(path string) (string, error) {
|
||||
return "", fmt.Errorf("files cannot be read in the browser; paste the PGN into the web board's Import instead")
|
||||
}
|
||||
@@ -152,6 +152,7 @@ func (r *Registry) helpHandler(s *session.Session, args []string) error {
|
||||
{"draw", "", ""},
|
||||
{"games", "g", ""},
|
||||
{"pgn", "", ""},
|
||||
{"import", "", ""},
|
||||
}
|
||||
|
||||
replayCommands := []cmdInfo{
|
||||
|
||||
Reference in new issue
Block a user