DEV 1916: Fix output issues in CLI (#114)
* adding testing of output file and write line for /end * fix regression in latency rounding for board
This commit is contained in:
parent
5f60ccbba8
commit
639362ef46
7 changed files with 396 additions and 49 deletions
|
|
@ -4,10 +4,12 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -56,7 +58,7 @@ type GameState struct {
|
|||
UseColor bool
|
||||
Seed int64
|
||||
TurnDelay int
|
||||
Output string
|
||||
OutputPath string
|
||||
ViewInBrowser bool
|
||||
BoardURL string
|
||||
FoodSpawnChance int
|
||||
|
|
@ -71,6 +73,8 @@ type GameState struct {
|
|||
httpClient TimedHttpClient
|
||||
ruleset rules.Ruleset
|
||||
gameMap maps.GameMap
|
||||
outputFile io.WriteCloser
|
||||
idGenerator func(int) string
|
||||
}
|
||||
|
||||
func NewPlayCommand() *cobra.Command {
|
||||
|
|
@ -81,6 +85,9 @@ func NewPlayCommand() *cobra.Command {
|
|||
Short: "Play a game of Battlesnake locally.",
|
||||
Long: "Play a game of Battlesnake locally.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := gameState.Initialize(); err != nil {
|
||||
log.ERROR.Fatalf("Error initializing game: %v", err)
|
||||
}
|
||||
gameState.Run()
|
||||
},
|
||||
}
|
||||
|
|
@ -98,7 +105,7 @@ func NewPlayCommand() *cobra.Command {
|
|||
playCmd.Flags().Int64VarP(&gameState.Seed, "seed", "r", time.Now().UTC().UnixNano(), "Random Seed")
|
||||
playCmd.Flags().IntVarP(&gameState.TurnDelay, "delay", "d", 0, "Turn Delay in Milliseconds")
|
||||
playCmd.Flags().IntVarP(&gameState.TurnDuration, "duration", "D", 0, "Minimum Turn Duration in Milliseconds")
|
||||
playCmd.Flags().StringVarP(&gameState.Output, "output", "o", "", "File path to output game state to. Existing files will be overwritten")
|
||||
playCmd.Flags().StringVarP(&gameState.OutputPath, "output", "o", "", "File path to output game state to. Existing files will be overwritten")
|
||||
playCmd.Flags().BoolVar(&gameState.ViewInBrowser, "browser", false, "View the game in the browser using the Battlesnake game board")
|
||||
playCmd.Flags().StringVar(&gameState.BoardURL, "board-url", "https://board.battlesnake.com", "Base URL for the game board when using --browser")
|
||||
|
||||
|
|
@ -113,7 +120,7 @@ func NewPlayCommand() *cobra.Command {
|
|||
}
|
||||
|
||||
// Setup a GameState once all the fields have been parsed from the command-line.
|
||||
func (gameState *GameState) initialize() {
|
||||
func (gameState *GameState) Initialize() error {
|
||||
// Generate game ID
|
||||
gameState.gameID = uuid.New().String()
|
||||
|
||||
|
|
@ -130,7 +137,7 @@ func (gameState *GameState) initialize() {
|
|||
// Load game map
|
||||
gameMap, err := maps.GetMap(gameState.MapName)
|
||||
if err != nil {
|
||||
log.ERROR.Fatalf("Failed to load game map %#v: %v", gameState.MapName, err)
|
||||
return fmt.Errorf("Failed to load game map %#v: %v", gameState.MapName, err)
|
||||
}
|
||||
gameState.gameMap = gameMap
|
||||
|
||||
|
|
@ -153,19 +160,26 @@ func (gameState *GameState) initialize() {
|
|||
|
||||
// Initialize snake states as empty until we can ping the snake URLs
|
||||
gameState.snakeStates = map[string]SnakeState{}
|
||||
|
||||
if gameState.OutputPath != "" {
|
||||
f, err := os.OpenFile(gameState.OutputPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to open output file: %w", err)
|
||||
}
|
||||
gameState.outputFile = f
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Setup and run a full game.
|
||||
func (gameState *GameState) Run() {
|
||||
gameState.initialize()
|
||||
|
||||
// Setup local state for snakes
|
||||
gameState.snakeStates = gameState.buildSnakesFromOptions()
|
||||
|
||||
rand.Seed(gameState.Seed)
|
||||
|
||||
boardState := gameState.initializeBoardFromArgs()
|
||||
exportGame := gameState.Output != ""
|
||||
|
||||
gameExporter := GameExporter{
|
||||
game: gameState.createClientGame(),
|
||||
|
|
@ -173,6 +187,10 @@ func (gameState *GameState) Run() {
|
|||
winner: SnakeState{},
|
||||
isDraw: false,
|
||||
}
|
||||
exportGame := gameState.outputFile != nil
|
||||
if exportGame {
|
||||
defer gameState.outputFile.Close()
|
||||
}
|
||||
|
||||
boardGame := board.Game{
|
||||
ID: gameState.gameID,
|
||||
|
|
@ -258,6 +276,15 @@ func (gameState *GameState) Run() {
|
|||
}
|
||||
}
|
||||
|
||||
// Export final turn
|
||||
if exportGame {
|
||||
for _, snakeState := range gameState.snakeStates {
|
||||
snakeRequest := gameState.getRequestBodyForSnake(boardState, snakeState)
|
||||
gameExporter.AddSnakeRequest(snakeRequest)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
gameExporter.isDraw = false
|
||||
|
||||
if len(gameState.snakeStates) > 1 {
|
||||
|
|
@ -291,10 +318,11 @@ func (gameState *GameState) Run() {
|
|||
}
|
||||
|
||||
if exportGame {
|
||||
err := gameExporter.FlushToFile(gameState.Output, "JSONL")
|
||||
lines, err := gameExporter.FlushToFile(gameState.outputFile)
|
||||
if err != nil {
|
||||
log.ERROR.Fatalf("Unable to export game. Reason: %v", err)
|
||||
}
|
||||
log.INFO.Printf("Wrote %d lines to output file: %s", lines, gameState.OutputPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -515,7 +543,12 @@ func (gameState *GameState) buildSnakesFromOptions() map[string]SnakeState {
|
|||
var snakeName string
|
||||
var snakeURL string
|
||||
|
||||
id := uuid.New().String()
|
||||
var id string
|
||||
if gameState.idGenerator != nil {
|
||||
id = gameState.idGenerator(i)
|
||||
} else {
|
||||
id = uuid.New().String()
|
||||
}
|
||||
|
||||
if i < numNames {
|
||||
snakeName = gameState.Names[i]
|
||||
|
|
@ -677,6 +710,7 @@ func (gameState *GameState) buildFrameEvent(boardState *rules.BoardState) board.
|
|||
snakeState := gameState.snakeStates[snake.ID]
|
||||
|
||||
latencyMS := snakeState.Latency.Milliseconds()
|
||||
// round up latency of 0 to 1, to avoid legacy error display in board
|
||||
if latencyMS == 0 {
|
||||
latencyMS = 1
|
||||
}
|
||||
|
|
@ -734,12 +768,13 @@ func serialiseSnakeRequest(snakeRequest client.SnakeRequest) []byte {
|
|||
}
|
||||
|
||||
func convertRulesSnake(snake rules.Snake, snakeState SnakeState) client.Snake {
|
||||
latencyMS := snakeState.Latency.Milliseconds()
|
||||
return client.Snake{
|
||||
ID: snake.ID,
|
||||
Name: snakeState.Name,
|
||||
Health: snake.Health,
|
||||
Body: client.CoordFromPointArray(snake.Body),
|
||||
Latency: "0",
|
||||
Latency: fmt.Sprint(latencyMS),
|
||||
Head: client.CoordFromPoint(snake.Body[0]),
|
||||
Length: int(len(snake.Body)),
|
||||
Shout: "",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue