From f8082d6eb9048561acbe4d3d19754a85f64adb95 Mon Sep 17 00:00:00 2001 From: Josh LaFayette Date: Tue, 26 Jul 2022 04:52:41 -0700 Subject: [PATCH] fix CLI to handle invalid responses Previously the CLI would exit immediately with an error if a snake returned a response with invalid JSON. Now the CLI continues running the game and a snake with an invalid response continues in the direction of it's last move. Also logs a warning with details about the invalid response. Where possible, the log includes the response body in case it contains helpful info. --- cli/commands/play.go | 70 ++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/cli/commands/play.go b/cli/commands/play.go index a2357ae..61883ac 100644 --- a/cli/commands/play.go +++ b/cli/commands/play.go @@ -398,26 +398,58 @@ func (gameState *GameState) getMoveForSnake(boardState *rules.BoardState, snakeS log.Printf("POST %s: %v", u, string(requestBody)) } res, err := gameState.httpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody)) - move := snakeState.LastMove + + // Use snake's last move as the default in case of an error + snakeMove := rules.SnakeMove{ID: snakeState.ID, Move: snakeState.LastMove} + if err != nil { - log.Printf("[WARN]: Request to %v failed\n", u.String()) - log.Printf("Body --> %v\n", string(requestBody)) - } else if res.Body != nil { - defer res.Body.Close() - body, readErr := ioutil.ReadAll(res.Body) - if readErr != nil { - log.Fatal(readErr) - } else { - playerResponse := client.MoveResponse{} - jsonErr := json.Unmarshal(body, &playerResponse) - if jsonErr != nil { - log.Fatal(jsonErr) - } else { - move = playerResponse.Move - } - } - } - return rules.SnakeMove{ID: snakeState.ID, Move: move} + log.Printf( + "[WARN]: Request to %v failed\n"+ + "\tError: %s\n", u.String(), err) + return snakeMove + } + if res.Body == nil { + log.Printf( + "[WARN]: Failed to parse response from %v\n"+ + "\tError: body is empty\n", u.String()) + return snakeMove + } + defer res.Body.Close() + body, readErr := ioutil.ReadAll(res.Body) + if readErr != nil { + log.Printf( + "[WARN]: Failed to read response body from %v\n"+ + "\tError: %v\n", u.String(), readErr) + return snakeMove + } + if res.StatusCode != http.StatusOK { + log.Printf( + "[WARN]: Got non-ok status code from %v\n"+ + "\tStatusCode: %d (expected %d)\n"+ + "\tBody: %q\n", u.String(), res.StatusCode, http.StatusOK, body) + return snakeMove + } + playerResponse := client.MoveResponse{} + jsonErr := json.Unmarshal(body, &playerResponse) + if jsonErr != nil { + log.Printf( + "[WARN]: Failed to decode JSON from %v\n"+ + "\tError: %v\n"+ + "\tBody: %q\n"+ + "\tSee https://docs.battlesnake.com/references/api#post-move", u.String(), jsonErr, body) + return snakeMove + } + if playerResponse.Move != "up" && playerResponse.Move != "down" && playerResponse.Move != "left" && playerResponse.Move != "right" { + log.Printf( + "[WARN]: Failed to parse JSON data from %v\n"+ + "\tError: invalid move %q, valid moves are \"up\", \"down\", \"left\" or \"right\"\n"+ + "\tBody: %q\n"+ + "\tSee https://docs.battlesnake.com/references/api#post-move", u.String(), playerResponse.Move, body) + return snakeMove + } + + snakeMove.Move = playerResponse.Move + return snakeMove } func (gameState *GameState) sendEndRequest(boardState *rules.BoardState, snakeState SnakeState) {