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
|
|
@ -3,9 +3,7 @@ package commands
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"io"
|
||||||
|
|
||||||
log "github.com/spf13/jwalterweatherman"
|
|
||||||
|
|
||||||
"github.com/BattlesnakeOfficial/rules/client"
|
"github.com/BattlesnakeOfficial/rules/client"
|
||||||
)
|
)
|
||||||
|
|
@ -23,37 +21,20 @@ type result struct {
|
||||||
IsDraw bool `json:"isDraw"`
|
IsDraw bool `json:"isDraw"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ge *GameExporter) FlushToFile(filepath string, format string) error {
|
func (ge *GameExporter) FlushToFile(outputFile io.Writer) (int, error) {
|
||||||
var formattedOutput []string
|
formattedOutput, err := ge.ConvertToJSON()
|
||||||
var formattingErr error
|
|
||||||
|
|
||||||
// TODO: Support more formats
|
|
||||||
if format == "JSONL" {
|
|
||||||
formattedOutput, formattingErr = ge.ConvertToJSON()
|
|
||||||
} else {
|
|
||||||
log.ERROR.Fatalf("Invalid output format passed: %s", format)
|
|
||||||
}
|
|
||||||
|
|
||||||
if formattingErr != nil {
|
|
||||||
return formattingErr
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
for _, line := range formattedOutput {
|
for _, line := range formattedOutput {
|
||||||
_, err := f.WriteString(fmt.Sprintf("%s\n", line))
|
_, err := io.WriteString(outputFile, fmt.Sprintf("%s\n", line))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.DEBUG.Printf("Written %d lines of output to file: %s\n", len(formattedOutput), filepath)
|
return len(formattedOutput), nil
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ge *GameExporter) ConvertToJSON() ([]string, error) {
|
func (ge *GameExporter) ConvertToJSON() ([]string, error) {
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -56,7 +58,7 @@ type GameState struct {
|
||||||
UseColor bool
|
UseColor bool
|
||||||
Seed int64
|
Seed int64
|
||||||
TurnDelay int
|
TurnDelay int
|
||||||
Output string
|
OutputPath string
|
||||||
ViewInBrowser bool
|
ViewInBrowser bool
|
||||||
BoardURL string
|
BoardURL string
|
||||||
FoodSpawnChance int
|
FoodSpawnChance int
|
||||||
|
|
@ -71,6 +73,8 @@ type GameState struct {
|
||||||
httpClient TimedHttpClient
|
httpClient TimedHttpClient
|
||||||
ruleset rules.Ruleset
|
ruleset rules.Ruleset
|
||||||
gameMap maps.GameMap
|
gameMap maps.GameMap
|
||||||
|
outputFile io.WriteCloser
|
||||||
|
idGenerator func(int) string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPlayCommand() *cobra.Command {
|
func NewPlayCommand() *cobra.Command {
|
||||||
|
|
@ -81,6 +85,9 @@ func NewPlayCommand() *cobra.Command {
|
||||||
Short: "Play a game of Battlesnake locally.",
|
Short: "Play a game of Battlesnake locally.",
|
||||||
Long: "Play a game of Battlesnake locally.",
|
Long: "Play a game of Battlesnake locally.",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if err := gameState.Initialize(); err != nil {
|
||||||
|
log.ERROR.Fatalf("Error initializing game: %v", err)
|
||||||
|
}
|
||||||
gameState.Run()
|
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().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.TurnDelay, "delay", "d", 0, "Turn Delay in Milliseconds")
|
||||||
playCmd.Flags().IntVarP(&gameState.TurnDuration, "duration", "D", 0, "Minimum Turn Duration 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().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")
|
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.
|
// 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
|
// Generate game ID
|
||||||
gameState.gameID = uuid.New().String()
|
gameState.gameID = uuid.New().String()
|
||||||
|
|
||||||
|
|
@ -130,7 +137,7 @@ func (gameState *GameState) initialize() {
|
||||||
// Load game map
|
// Load game map
|
||||||
gameMap, err := maps.GetMap(gameState.MapName)
|
gameMap, err := maps.GetMap(gameState.MapName)
|
||||||
if err != nil {
|
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
|
gameState.gameMap = gameMap
|
||||||
|
|
||||||
|
|
@ -153,19 +160,26 @@ func (gameState *GameState) initialize() {
|
||||||
|
|
||||||
// Initialize snake states as empty until we can ping the snake URLs
|
// Initialize snake states as empty until we can ping the snake URLs
|
||||||
gameState.snakeStates = map[string]SnakeState{}
|
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.
|
// Setup and run a full game.
|
||||||
func (gameState *GameState) Run() {
|
func (gameState *GameState) Run() {
|
||||||
gameState.initialize()
|
|
||||||
|
|
||||||
// Setup local state for snakes
|
// Setup local state for snakes
|
||||||
gameState.snakeStates = gameState.buildSnakesFromOptions()
|
gameState.snakeStates = gameState.buildSnakesFromOptions()
|
||||||
|
|
||||||
rand.Seed(gameState.Seed)
|
rand.Seed(gameState.Seed)
|
||||||
|
|
||||||
boardState := gameState.initializeBoardFromArgs()
|
boardState := gameState.initializeBoardFromArgs()
|
||||||
exportGame := gameState.Output != ""
|
|
||||||
|
|
||||||
gameExporter := GameExporter{
|
gameExporter := GameExporter{
|
||||||
game: gameState.createClientGame(),
|
game: gameState.createClientGame(),
|
||||||
|
|
@ -173,6 +187,10 @@ func (gameState *GameState) Run() {
|
||||||
winner: SnakeState{},
|
winner: SnakeState{},
|
||||||
isDraw: false,
|
isDraw: false,
|
||||||
}
|
}
|
||||||
|
exportGame := gameState.outputFile != nil
|
||||||
|
if exportGame {
|
||||||
|
defer gameState.outputFile.Close()
|
||||||
|
}
|
||||||
|
|
||||||
boardGame := board.Game{
|
boardGame := board.Game{
|
||||||
ID: gameState.gameID,
|
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
|
gameExporter.isDraw = false
|
||||||
|
|
||||||
if len(gameState.snakeStates) > 1 {
|
if len(gameState.snakeStates) > 1 {
|
||||||
|
|
@ -291,10 +318,11 @@ func (gameState *GameState) Run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if exportGame {
|
if exportGame {
|
||||||
err := gameExporter.FlushToFile(gameState.Output, "JSONL")
|
lines, err := gameExporter.FlushToFile(gameState.outputFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ERROR.Fatalf("Unable to export game. Reason: %v", err)
|
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 snakeName string
|
||||||
var snakeURL 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 {
|
if i < numNames {
|
||||||
snakeName = gameState.Names[i]
|
snakeName = gameState.Names[i]
|
||||||
|
|
@ -677,6 +710,7 @@ func (gameState *GameState) buildFrameEvent(boardState *rules.BoardState) board.
|
||||||
snakeState := gameState.snakeStates[snake.ID]
|
snakeState := gameState.snakeStates[snake.ID]
|
||||||
|
|
||||||
latencyMS := snakeState.Latency.Milliseconds()
|
latencyMS := snakeState.Latency.Milliseconds()
|
||||||
|
// round up latency of 0 to 1, to avoid legacy error display in board
|
||||||
if latencyMS == 0 {
|
if latencyMS == 0 {
|
||||||
latencyMS = 1
|
latencyMS = 1
|
||||||
}
|
}
|
||||||
|
|
@ -734,12 +768,13 @@ func serialiseSnakeRequest(snakeRequest client.SnakeRequest) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertRulesSnake(snake rules.Snake, snakeState SnakeState) client.Snake {
|
func convertRulesSnake(snake rules.Snake, snakeState SnakeState) client.Snake {
|
||||||
|
latencyMS := snakeState.Latency.Milliseconds()
|
||||||
return client.Snake{
|
return client.Snake{
|
||||||
ID: snake.ID,
|
ID: snake.ID,
|
||||||
Name: snakeState.Name,
|
Name: snakeState.Name,
|
||||||
Health: snake.Health,
|
Health: snake.Health,
|
||||||
Body: client.CoordFromPointArray(snake.Body),
|
Body: client.CoordFromPointArray(snake.Body),
|
||||||
Latency: "0",
|
Latency: fmt.Sprint(latencyMS),
|
||||||
Head: client.CoordFromPoint(snake.Body[0]),
|
Head: client.CoordFromPoint(snake.Body[0]),
|
||||||
Length: int(len(snake.Body)),
|
Length: int(len(snake.Body)),
|
||||||
Shout: "",
|
Shout: "",
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -31,7 +32,7 @@ func buildDefaultGameState() *GameState {
|
||||||
Seed: 1,
|
Seed: 1,
|
||||||
TurnDelay: 0,
|
TurnDelay: 0,
|
||||||
TurnDuration: 0,
|
TurnDuration: 0,
|
||||||
Output: "",
|
OutputPath: "",
|
||||||
FoodSpawnChance: 15,
|
FoodSpawnChance: 15,
|
||||||
MinimumFood: 1,
|
MinimumFood: 1,
|
||||||
HazardDamagePerTurn: 14,
|
HazardDamagePerTurn: 14,
|
||||||
|
|
@ -67,7 +68,8 @@ func TestGetIndividualBoardStateForSnake(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
gameState := buildDefaultGameState()
|
gameState := buildDefaultGameState()
|
||||||
gameState.initialize()
|
err := gameState.Initialize()
|
||||||
|
require.NoError(t, err)
|
||||||
gameState.gameID = "GAME_ID"
|
gameState.gameID = "GAME_ID"
|
||||||
gameState.snakeStates = map[string]SnakeState{
|
gameState.snakeStates = map[string]SnakeState{
|
||||||
s1State.ID: s1State,
|
s1State.ID: s1State,
|
||||||
|
|
@ -118,7 +120,8 @@ func TestSettingsRequestSerialization(t *testing.T) {
|
||||||
gameState.ShrinkEveryNTurns = 17
|
gameState.ShrinkEveryNTurns = 17
|
||||||
gameState.GameType = gt
|
gameState.GameType = gt
|
||||||
|
|
||||||
gameState.initialize()
|
err := gameState.Initialize()
|
||||||
|
require.NoError(t, err)
|
||||||
gameState.gameID = "GAME_ID"
|
gameState.gameID = "GAME_ID"
|
||||||
gameState.snakeStates = map[string]SnakeState{s1State.ID: s1State, s2State.ID: s2State}
|
gameState.snakeStates = map[string]SnakeState{s1State.ID: s1State, s2State.ID: s2State}
|
||||||
|
|
||||||
|
|
@ -159,13 +162,14 @@ func TestConvertRulesSnakes(t *testing.T) {
|
||||||
Color: "#012345",
|
Color: "#012345",
|
||||||
LastMove: "up",
|
LastMove: "up",
|
||||||
Character: '+',
|
Character: '+',
|
||||||
|
Latency: time.Millisecond * 42,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: []client.Snake{
|
expected: []client.Snake{
|
||||||
{
|
{
|
||||||
ID: "one",
|
ID: "one",
|
||||||
Name: "ONE",
|
Name: "ONE",
|
||||||
Latency: "0",
|
Latency: "42",
|
||||||
Health: 100,
|
Health: 100,
|
||||||
Body: []client.Coord{{X: 3, Y: 3}, {X: 2, Y: 3}},
|
Body: []client.Coord{{X: 3, Y: 3}, {X: 2, Y: 3}},
|
||||||
Head: client.Coord{X: 3, Y: 3},
|
Head: client.Coord{X: 3, Y: 3},
|
||||||
|
|
@ -507,9 +511,10 @@ func TestGetMoveForSnake(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
gameState := buildDefaultGameState()
|
gameState := buildDefaultGameState()
|
||||||
gameState.initialize()
|
err := gameState.Initialize()
|
||||||
|
require.NoError(t, err)
|
||||||
gameState.snakeStates = map[string]SnakeState{test.snakeState.ID: test.snakeState}
|
gameState.snakeStates = map[string]SnakeState{test.snakeState.ID: test.snakeState}
|
||||||
gameState.httpClient = stubHTTPClient{test.responseErr, test.responseCode, test.responseBody, test.responseLatency}
|
gameState.httpClient = stubHTTPClient{test.responseErr, test.responseCode, func(_ string) string { return test.responseBody }, test.responseLatency}
|
||||||
|
|
||||||
nextSnakeState := gameState.getSnakeUpdate(test.boardState, test.snakeState)
|
nextSnakeState := gameState.getSnakeUpdate(test.boardState, test.snakeState)
|
||||||
if test.expectedSnakeState.Error != nil {
|
if test.expectedSnakeState.Error != nil {
|
||||||
|
|
@ -539,9 +544,10 @@ func TestCreateNextBoardState(t *testing.T) {
|
||||||
t.Run(fmt.Sprintf("sequential_%v", sequential), func(t *testing.T) {
|
t.Run(fmt.Sprintf("sequential_%v", sequential), func(t *testing.T) {
|
||||||
gameState := buildDefaultGameState()
|
gameState := buildDefaultGameState()
|
||||||
gameState.Sequential = sequential
|
gameState.Sequential = sequential
|
||||||
gameState.initialize()
|
err := gameState.Initialize()
|
||||||
|
require.NoError(t, err)
|
||||||
gameState.snakeStates = map[string]SnakeState{s1.ID: snakeState}
|
gameState.snakeStates = map[string]SnakeState{s1.ID: snakeState}
|
||||||
gameState.httpClient = stubHTTPClient{nil, 200, `{"move": "right"}`, 54 * time.Millisecond}
|
gameState.httpClient = stubHTTPClient{nil, 200, func(_ string) string { return `{"move": "right"}` }, 54 * time.Millisecond}
|
||||||
|
|
||||||
nextBoardState := gameState.createNextBoardState(boardState)
|
nextBoardState := gameState.createNextBoardState(boardState)
|
||||||
snakeState = gameState.snakeStates[s1.ID]
|
snakeState = gameState.snakeStates[s1.ID]
|
||||||
|
|
@ -556,18 +562,92 @@ func TestCreateNextBoardState(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOutputFile(t *testing.T) {
|
||||||
|
gameState := buildDefaultGameState()
|
||||||
|
gameState.Names = []string{"example snake"}
|
||||||
|
gameState.URLs = []string{"http://example.com"}
|
||||||
|
err := gameState.Initialize()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
gameState.gameID = "GAME_ID"
|
||||||
|
gameState.idGenerator = func(index int) string { return fmt.Sprintf("snk_%d", index) }
|
||||||
|
|
||||||
|
gameState.httpClient = stubHTTPClient{nil, http.StatusOK, func(url string) string {
|
||||||
|
switch url {
|
||||||
|
case "http://example.com":
|
||||||
|
return `
|
||||||
|
{
|
||||||
|
"apiversion": "1",
|
||||||
|
"author": "author",
|
||||||
|
"color": "#123456",
|
||||||
|
"head": "safe",
|
||||||
|
"tail": "curled",
|
||||||
|
"version": "0.0.1-beta"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
case "http://example.com/move":
|
||||||
|
return `{"move": "left"}`
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}, time.Millisecond * 42}
|
||||||
|
outputFile := new(closableBuffer)
|
||||||
|
gameState.outputFile = outputFile
|
||||||
|
|
||||||
|
gameState.ruleset = StubRuleset{maxTurns: 1, settings: rules.Settings{
|
||||||
|
FoodSpawnChance: 1,
|
||||||
|
MinimumFood: 2,
|
||||||
|
HazardDamagePerTurn: 3,
|
||||||
|
RoyaleSettings: rules.RoyaleSettings{
|
||||||
|
ShrinkEveryNTurns: 4,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
gameState.Run()
|
||||||
|
|
||||||
|
lines := strings.Split(outputFile.String(), "\n")
|
||||||
|
require.Len(t, lines, 5)
|
||||||
|
test.RequireJSONMatchesFixture(t, "testdata/jsonl_game.json", lines[0])
|
||||||
|
test.RequireJSONMatchesFixture(t, "testdata/jsonl_turn_0.json", lines[1])
|
||||||
|
test.RequireJSONMatchesFixture(t, "testdata/jsonl_turn_1.json", lines[2])
|
||||||
|
test.RequireJSONMatchesFixture(t, "testdata/jsonl_game_complete.json", lines[3])
|
||||||
|
require.Equal(t, "", lines[4])
|
||||||
|
}
|
||||||
|
|
||||||
|
type closableBuffer struct {
|
||||||
|
bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (closableBuffer) Close() error { return nil }
|
||||||
|
|
||||||
|
type StubRuleset struct {
|
||||||
|
maxTurns int
|
||||||
|
settings rules.Settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ruleset StubRuleset) Name() string { return "standard" }
|
||||||
|
func (ruleset StubRuleset) Settings() rules.Settings { return ruleset.settings }
|
||||||
|
func (ruleset StubRuleset) ModifyInitialBoardState(initialState *rules.BoardState) (*rules.BoardState, error) {
|
||||||
|
return initialState, nil
|
||||||
|
}
|
||||||
|
func (ruleset StubRuleset) CreateNextBoardState(prevState *rules.BoardState, moves []rules.SnakeMove) (*rules.BoardState, error) {
|
||||||
|
return prevState, nil
|
||||||
|
}
|
||||||
|
func (ruleset StubRuleset) IsGameOver(state *rules.BoardState) (bool, error) {
|
||||||
|
return state.Turn >= ruleset.maxTurns, nil
|
||||||
|
}
|
||||||
|
|
||||||
type stubHTTPClient struct {
|
type stubHTTPClient struct {
|
||||||
err error
|
err error
|
||||||
statusCode int
|
statusCode int
|
||||||
body string
|
body func(url string) string
|
||||||
latency time.Duration
|
latency time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client stubHTTPClient) request() (*http.Response, time.Duration, error) {
|
func (client stubHTTPClient) request(url string) (*http.Response, time.Duration, error) {
|
||||||
if client.err != nil {
|
if client.err != nil {
|
||||||
return nil, client.latency, client.err
|
return nil, client.latency, client.err
|
||||||
}
|
}
|
||||||
body := ioutil.NopCloser(bytes.NewBufferString(client.body))
|
body := ioutil.NopCloser(bytes.NewBufferString(client.body(url)))
|
||||||
|
|
||||||
response := &http.Response{
|
response := &http.Response{
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
|
|
@ -579,9 +659,9 @@ func (client stubHTTPClient) request() (*http.Response, time.Duration, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client stubHTTPClient) Get(url string) (*http.Response, time.Duration, error) {
|
func (client stubHTTPClient) Get(url string) (*http.Response, time.Duration, error) {
|
||||||
return client.request()
|
return client.request(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client stubHTTPClient) Post(url string, contentType string, body io.Reader) (*http.Response, time.Duration, error) {
|
func (client stubHTTPClient) Post(url string, contentType string, body io.Reader) (*http.Response, time.Duration, error) {
|
||||||
return client.request()
|
return client.request(url)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
cli/commands/testdata/jsonl_game.json
vendored
Normal file
26
cli/commands/testdata/jsonl_game.json
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"id": "GAME_ID",
|
||||||
|
"ruleset": {
|
||||||
|
"name": "standard",
|
||||||
|
"version": "cli",
|
||||||
|
"settings": {
|
||||||
|
"foodSpawnChance": 1,
|
||||||
|
"minimumFood": 2,
|
||||||
|
"hazardDamagePerTurn": 3,
|
||||||
|
"hazardMap": "",
|
||||||
|
"hazardMapAuthor": "",
|
||||||
|
"royale": {
|
||||||
|
"shrinkEveryNTurns": 4
|
||||||
|
},
|
||||||
|
"squad": {
|
||||||
|
"allowBodyCollisions": false,
|
||||||
|
"sharedElimination": false,
|
||||||
|
"sharedHealth": false,
|
||||||
|
"sharedLength": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map": "standard",
|
||||||
|
"timeout": 500,
|
||||||
|
"source": ""
|
||||||
|
}
|
||||||
5
cli/commands/testdata/jsonl_game_complete.json
vendored
Normal file
5
cli/commands/testdata/jsonl_game_complete.json
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"winnerId": "snk_0",
|
||||||
|
"winnerName": "example snake",
|
||||||
|
"isDraw": false
|
||||||
|
}
|
||||||
110
cli/commands/testdata/jsonl_turn_0.json
vendored
Normal file
110
cli/commands/testdata/jsonl_turn_0.json
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
{
|
||||||
|
"game": {
|
||||||
|
"id": "GAME_ID",
|
||||||
|
"ruleset": {
|
||||||
|
"name": "standard",
|
||||||
|
"version": "cli",
|
||||||
|
"settings": {
|
||||||
|
"foodSpawnChance": 1,
|
||||||
|
"minimumFood": 2,
|
||||||
|
"hazardDamagePerTurn": 3,
|
||||||
|
"hazardMap": "",
|
||||||
|
"hazardMapAuthor": "",
|
||||||
|
"royale": {
|
||||||
|
"shrinkEveryNTurns": 4
|
||||||
|
},
|
||||||
|
"squad": {
|
||||||
|
"allowBodyCollisions": false,
|
||||||
|
"sharedElimination": false,
|
||||||
|
"sharedHealth": false,
|
||||||
|
"sharedLength": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map": "standard",
|
||||||
|
"timeout": 500,
|
||||||
|
"source": ""
|
||||||
|
},
|
||||||
|
"turn": 0,
|
||||||
|
"board": {
|
||||||
|
"height": 11,
|
||||||
|
"width": 11,
|
||||||
|
"snakes": [
|
||||||
|
{
|
||||||
|
"id": "snk_0",
|
||||||
|
"name": "example snake",
|
||||||
|
"latency": "0",
|
||||||
|
"health": 100,
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"head": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
"length": 3,
|
||||||
|
"shout": "",
|
||||||
|
"squad": "",
|
||||||
|
"customizations": {
|
||||||
|
"color": "#123456",
|
||||||
|
"head": "safe",
|
||||||
|
"tail": "curled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"food": [
|
||||||
|
{
|
||||||
|
"x": 0,
|
||||||
|
"y": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 5,
|
||||||
|
"y": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hazards": []
|
||||||
|
},
|
||||||
|
"you": {
|
||||||
|
"id": "snk_0",
|
||||||
|
"name": "example snake",
|
||||||
|
"latency": "0",
|
||||||
|
"health": 100,
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"head": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
"length": 3,
|
||||||
|
"shout": "",
|
||||||
|
"squad": "",
|
||||||
|
"customizations": {
|
||||||
|
"color": "#123456",
|
||||||
|
"head": "safe",
|
||||||
|
"tail": "curled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
110
cli/commands/testdata/jsonl_turn_1.json
vendored
Normal file
110
cli/commands/testdata/jsonl_turn_1.json
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
{
|
||||||
|
"game": {
|
||||||
|
"id": "GAME_ID",
|
||||||
|
"ruleset": {
|
||||||
|
"name": "standard",
|
||||||
|
"version": "cli",
|
||||||
|
"settings": {
|
||||||
|
"foodSpawnChance": 1,
|
||||||
|
"minimumFood": 2,
|
||||||
|
"hazardDamagePerTurn": 3,
|
||||||
|
"hazardMap": "",
|
||||||
|
"hazardMapAuthor": "",
|
||||||
|
"royale": {
|
||||||
|
"shrinkEveryNTurns": 4
|
||||||
|
},
|
||||||
|
"squad": {
|
||||||
|
"allowBodyCollisions": false,
|
||||||
|
"sharedElimination": false,
|
||||||
|
"sharedHealth": false,
|
||||||
|
"sharedLength": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map": "standard",
|
||||||
|
"timeout": 500,
|
||||||
|
"source": ""
|
||||||
|
},
|
||||||
|
"turn": 1,
|
||||||
|
"board": {
|
||||||
|
"height": 11,
|
||||||
|
"width": 11,
|
||||||
|
"snakes": [
|
||||||
|
{
|
||||||
|
"id": "snk_0",
|
||||||
|
"name": "example snake",
|
||||||
|
"latency": "42",
|
||||||
|
"health": 100,
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"head": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
"length": 3,
|
||||||
|
"shout": "",
|
||||||
|
"squad": "",
|
||||||
|
"customizations": {
|
||||||
|
"color": "#123456",
|
||||||
|
"head": "safe",
|
||||||
|
"tail": "curled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"food": [
|
||||||
|
{
|
||||||
|
"x": 0,
|
||||||
|
"y": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 5,
|
||||||
|
"y": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hazards": []
|
||||||
|
},
|
||||||
|
"you": {
|
||||||
|
"id": "snk_0",
|
||||||
|
"name": "example snake",
|
||||||
|
"latency": "42",
|
||||||
|
"health": 100,
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"head": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
"length": 3,
|
||||||
|
"shout": "",
|
||||||
|
"squad": "",
|
||||||
|
"customizations": {
|
||||||
|
"color": "#123456",
|
||||||
|
"head": "safe",
|
||||||
|
"tail": "curled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue