From 66891ab5c45e296258e1ec91e3563a5cd0c591bd Mon Sep 17 00:00:00 2001 From: Mariano Gappa Date: Sat, 28 Dec 2019 14:03:58 +1300 Subject: [PATCH] Adds /defaultGame and /doAction API calls. --- api/api.go | 92 ++++++++++++++++++++++++++++++++++++++------- api/api_entities.go | 16 ++++---- main.go | 67 +++++++++++++++++++++++++++++++-- 3 files changed, 149 insertions(+), 26 deletions(-) diff --git a/api/api.go b/api/api.go index 4175802..89986f5 100644 --- a/api/api.go +++ b/api/api.go @@ -2,28 +2,45 @@ package api import ( "fmt" + "strings" ) -// // Cli -// // Server -// // Library - type API struct{} func New() API { return API{} } -// func (a API) DoAction(game InputGame, action InputAction, options DoActionInputOptions) (OutputGame, OutputAction, error) { - -// } - -// func (a API) DefaultGame(options DefaultGameInputOptions) (OutputGame, error) { - -// } var ( - errInvalidInputGame = fmt.Errorf("invalid input game: please supply a valid fenString or a board") + errInvalidInputGame = fmt.Errorf("invalid input game: please supply a valid fenString or a board") + errAlgebraicSquareInvalidOrOutOfBounds = fmt.Errorf("invalid algebraic square: empty or out of bounds") + errInvalidPieceTypeName = fmt.Errorf("invalid piece type name: please use one of {Queen|King|Bishop|Knight|Rook|Pawn} or empty string") + errInvalidActionForGivenGame = fmt.Errorf("the specified action is invalid for the specified game") ) +func (a API) DefaultGame() OutputGame { + return mapGameToOutputGame(defaultGame) +} + func (a API) ParseGame(g InputGame) (OutputGame, error) { + parsedGame, err := a.parseGame(g) + if err != nil { + return OutputGame{}, err + } + return mapGameToOutputGame(parsedGame), nil +} + +func (a API) DoAction(game InputGame, action InputAction) (OutputGame, OutputAction, error) { + parsedGame, err := a.parseGame(game) + if err != nil { + return OutputGame{}, OutputAction{}, err + } + parsedAction, err := a.parseAction(action, parsedGame) + if err != nil { + return OutputGame{}, OutputAction{}, err + } + return mapGameToOutputGame(parsedGame.doAction(parsedAction)), mapInternalActionToAction(parsedAction), nil +} + +func (a API) parseGame(g InputGame) (game, error) { var ( parsedGame game err error @@ -37,8 +54,55 @@ func (a API) ParseGame(g InputGame) (OutputGame, error) { err = errInvalidInputGame } if err != nil { - return OutputGame{}, err + return game{}, err } + return parsedGame, nil +} - return mapGameToOutputGame(parsedGame), nil +func (a API) parseAction(ia InputAction, g game) (action, error) { + fromXY, err := a.algebraicToXY(strings.ToLower(ia.FromSquare)) + if err != nil { + return action{}, err + } + toXY, err := a.algebraicToXY(strings.ToLower(ia.ToSquare)) + if err != nil { + return action{}, err + } + promotePieceType, err := a.stringToPieceType(ia.PromotePieceType) + if err != nil { + return action{}, err + } + + for _, action := range g.actions { + if action.fromPiece.xy != fromXY || action.toXY != toXY || (action.isPromotion && action.promotionPieceType != promotePieceType) { + continue + } + return action, nil + } + + return action{}, errInvalidActionForGivenGame +} + +func (a API) algebraicToXY(sq string) (xy, error) { + if len(sq) != 2 || sq[0] < 'a' || sq[0] > 'h' || sq[1] < '1' || sq[1] > '8' { + return xy{}, errAlgebraicSquareInvalidOrOutOfBounds + } + return xy{int(sq[0] - 'a'), int('8' - sq[1])}, nil +} + +func (a API) stringToPieceType(s string) (pieceType, error) { + m := map[string]pieceType{ + "Queen": pieceQueen, + "King": pieceKing, + "Bishop": pieceBishop, + "Knight": pieceKnight, + "Rook": pieceRook, + "Pawn": piecePawn, + "": pieceNone, + } + pt, ok := m[s] + if !ok { + return pieceNone, errInvalidPieceTypeName + } + return pt, nil } diff --git a/api/api_entities.go b/api/api_entities.go index 463a6d4..bb64603 100644 --- a/api/api_entities.go +++ b/api/api_entities.go @@ -1,14 +1,15 @@ package api type InputGame struct { - FENString string - Board Board + FENString string `json:"fenString"` + Board Board `json:"board"` } -// type InputAction struct { -// ActionType ActionType -// Action string -// } +type InputAction struct { + FromSquare string `json:"fromSquare"` + ToSquare string `json:"toSquare"` + PromotePieceType string `json:"promotePieceType"` +} type Board struct { Board []string `json:"board"` @@ -67,9 +68,6 @@ type OutputAction struct { CapturedPieceType string `json:"capturedPieceType"` } -// type OutputGameSteps struct { -// } - func mapGameToOutputGame(g game) OutputGame { var o OutputGame diff --git a/main.go b/main.go index 154e77d..babb161 100644 --- a/main.go +++ b/main.go @@ -11,9 +11,11 @@ import ( ) var ( - flagServe = flag.Int("serve", 0, "Start a server on the specified port.") - flagParseGame = flag.String("parseGame", "", "ParseGame api call. Requires a JSON string with arguments. Please review spec.") - a = api.New() + flagServe = flag.Int("serve", 0, "Start a server on the specified port.") + flagDefaultGame = flag.Bool("defaultGame", false, "Default API call. Returns a default game.") + flagParseGame = flag.String("parseGame", "", "ParseGame API call. Requires a JSON string with arguments. Please review spec.") + flagDoAction = flag.String("doAction", "", "DoAction API call. Requires a JSON string with arguments. Please review spec.") + a = api.New() ) // api.InputGame{FENString: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"}, @@ -21,14 +23,29 @@ var ( func main() { flag.Parse() http.HandleFunc("/parseGame", handleServerParseGame) + http.HandleFunc("/defaultGame", handleServerDefaultGame) + http.HandleFunc("/doAction", handleServerDoAction) switch { case *flagServe != 0: http.ListenAndServe(fmt.Sprintf(":%v", *flagServe), nil) + case *flagDefaultGame: + handleCliDefaultGame() case *flagParseGame != "": handleCliParseGame(flagParseGame) + case *flagDoAction != "": + handleCliDoAction(flagDoAction) } } +func handleServerDefaultGame(w http.ResponseWriter, r *http.Request) { + json.NewEncoder(w).Encode(a.DefaultGame()) +} + +func handleCliDefaultGame() { + byts, _ := json.Marshal(a.DefaultGame()) + fmt.Println(string(byts)) +} + func handleServerParseGame(w http.ResponseWriter, r *http.Request) { var ig api.InputGame if err := json.NewDecoder(r.Body).Decode(&ig); err != nil { @@ -57,6 +74,50 @@ func handleCliParseGame(flagParseGame *string) { fmt.Println(string(byts)) } +func handleServerDoAction(w http.ResponseWriter, r *http.Request) { + type args struct { + Game api.InputGame `json:"game"` + Action api.InputAction `json:"action"` + } + var input args + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + fmt.Fprintln(w, formatError(err)) + return + } + defer r.Body.Close() + outputGame, outputAction, err := a.DoAction(input.Game, input.Action) + if err != nil { + fmt.Fprintln(w, formatError(err)) + return + } + type out struct { + Game api.OutputGame `json:"game"` + Action api.OutputAction `json:"action"` + } + json.NewEncoder(w).Encode(out{outputGame, outputAction}) +} + +func handleCliDoAction(flagDoAction *string) { + type args struct { + Game api.InputGame `json:"game"` + Action api.InputAction `json:"action"` + } + var input args + if err := json.Unmarshal([]byte(*flagDoAction), &input); err != nil { + mustCliFatal(err) + } + outputGame, outputAction, err := a.DoAction(input.Game, input.Action) + if err != nil { + mustCliFatal(err) + } + type out struct { + Game api.OutputGame `json:"game"` + Action api.OutputAction `json:"action"` + } + byts, _ := json.Marshal(out{outputGame, outputAction}) + fmt.Println(string(byts)) +} + func mustCliFatal(err error) { fmt.Println(formatError(err)) os.Exit(1)