DEV 559: Refactor CLI and add customizations (#57)

* move snake API structs into a new client package

* add customizations to snake objects

* refactor and add support for passing snake customizations in games
This commit is contained in:
Rob O'Dwyer 2021-11-25 14:07:56 -08:00 committed by GitHub
parent 6140f232c2
commit 4a9dbbcaef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 665 additions and 197 deletions

90
client/fixtures_test.go Normal file
View file

@ -0,0 +1,90 @@
package client
func exampleSnakeRequest() SnakeRequest {
return SnakeRequest{
Game: Game{
ID: "game-id",
Ruleset: Ruleset{
Name: "test-ruleset-name",
Version: "cli",
Settings: exampleRulesetSettings,
},
Timeout: 33,
Source: "league",
},
Turn: 11,
Board: Board{
Height: 22,
Width: 11,
Snakes: []Snake{
{
ID: "snake-0",
Name: "snake-0-name",
Latency: "snake-0-latency",
Health: 100,
Body: []Coord{{X: 1, Y: 2}, {X: 1, Y: 3}, {X: 1, Y: 4}},
Head: Coord{X: 1, Y: 2},
Length: 3,
Shout: "snake-0-shout",
Squad: "",
Customizations: Customizations{
Head: "safe",
Tail: "curled",
Color: "#123456",
},
},
{
ID: "snake-1",
Name: "snake-1-name",
Latency: "snake-1-latency",
Health: 200,
Body: []Coord{{X: 2, Y: 2}, {X: 2, Y: 3}, {X: 2, Y: 4}},
Head: Coord{X: 2, Y: 2},
Length: 3,
Shout: "snake-1-shout",
Squad: "snake-1-squad",
Customizations: Customizations{
Head: "silly",
Tail: "bolt",
Color: "#654321",
},
},
},
Food: []Coord{{X: 2, Y: 2}},
Hazards: []Coord{{X: 8, Y: 8}, {X: 9, Y: 9}},
},
You: Snake{
ID: "snake-1",
Name: "snake-1-name",
Latency: "snake-1-latency",
Health: 200,
Body: []Coord{{X: 2, Y: 2}, {X: 2, Y: 3}, {X: 2, Y: 4}},
Head: Coord{X: 2, Y: 2},
Length: 3,
Shout: "snake-1-shout",
Squad: "snake-1-squad",
Customizations: Customizations{
Head: "silly",
Tail: "bolt",
Color: "#654321",
},
},
}
}
var exampleRulesetSettings = RulesetSettings{
FoodSpawnChance: 10,
MinimumFood: 20,
HazardDamagePerTurn: 30,
RoyaleSettings: RoyaleSettings{
ShrinkEveryNTurns: 40,
},
SquadSettings: SquadSettings{
AllowBodyCollisions: true,
SharedElimination: true,
SharedHealth: true,
SharedLength: true,
},
}

107
client/models.go Normal file
View file

@ -0,0 +1,107 @@
package client
import "github.com/BattlesnakeOfficial/rules"
// The top-level message sent in /start, /move, and /end requests
type SnakeRequest struct {
Game Game `json:"game"`
Turn int32 `json:"turn"`
Board Board `json:"board"`
You Snake `json:"you"`
}
// Game represents the current game state
type Game struct {
ID string `json:"id"`
Ruleset Ruleset `json:"ruleset"`
Timeout int32 `json:"timeout"`
Source string `json:"source"`
}
// Board provides information about the game board
type Board struct {
Height int32 `json:"height"`
Width int32 `json:"width"`
Snakes []Snake `json:"snakes"`
Food []Coord `json:"food"`
Hazards []Coord `json:"hazards"`
}
// Snake represents information about a snake in the game
type Snake struct {
ID string `json:"id"`
Name string `json:"name"`
Latency string `json:"latency"`
Health int32 `json:"health"`
Body []Coord `json:"body"`
Head Coord `json:"head"`
Length int32 `json:"length"`
Shout string `json:"shout"`
Squad string `json:"squad"`
Customizations Customizations `json:"customizations"`
}
type Customizations struct {
Color string `json:"color"`
Head string `json:"head"`
Tail string `json:"tail"`
}
type Ruleset struct {
Name string `json:"name"`
Version string `json:"version"`
Settings RulesetSettings `json:"settings"`
}
type RulesetSettings struct {
FoodSpawnChance int32 `json:"foodSpawnChance"`
MinimumFood int32 `json:"minimumFood"`
HazardDamagePerTurn int32 `json:"hazardDamagePerTurn"`
RoyaleSettings RoyaleSettings `json:"royale"`
SquadSettings SquadSettings `json:"squad"`
}
type RoyaleSettings struct {
ShrinkEveryNTurns int32 `json:"shrinkEveryNTurns"`
}
type SquadSettings struct {
AllowBodyCollisions bool `json:"allowBodyCollisions"`
SharedElimination bool `json:"sharedElimination"`
SharedHealth bool `json:"sharedHealth"`
SharedLength bool `json:"sharedLength"`
}
// Coord represents a point on the board
type Coord struct {
X int32 `json:"x"`
Y int32 `json:"y"`
}
// The expected format of the response body from a /move request
type MoveResponse struct {
Move string `json:"move"`
Shout string `json:"shout"`
}
// The expected format of the response body from a GET request to a Battlesnake's index URL
type SnakeMetadataResponse struct {
APIVersion string `json:"apiversion,omitempty"`
Author string `json:"author,omitempty"`
Color string `json:"color,omitempty"`
Head string `json:"head,omitempty"`
Tail string `json:"tail,omitempty"`
Version string `json:"version,omitempty"`
}
func CoordFromPoint(pt rules.Point) Coord {
return Coord{X: pt.X, Y: pt.Y}
}
func CoordFromPointArray(ptArray []rules.Point) []Coord {
a := make([]Coord, 0)
for _, pt := range ptArray {
a = append(a, CoordFromPoint(pt))
}
return a
}

26
client/models_test.go Normal file
View file

@ -0,0 +1,26 @@
package client
import (
"encoding/json"
"testing"
"github.com/BattlesnakeOfficial/rules/test"
"github.com/stretchr/testify/require"
)
func TestBuildSnakeRequestJSON(t *testing.T) {
snakeRequest := exampleSnakeRequest()
data, err := json.MarshalIndent(snakeRequest, "", " ")
require.NoError(t, err)
test.RequireJSONMatchesFixture(t, "testdata/snake_request.json", string(data))
}
func TestBuildSnakeRequestJSONEmptyRulesetSettings(t *testing.T) {
snakeRequest := exampleSnakeRequest()
snakeRequest.Game.Ruleset.Settings = RulesetSettings{}
data, err := json.MarshalIndent(snakeRequest, "", " ")
require.NoError(t, err)
test.RequireJSONMatchesFixture(t, "testdata/snake_request_empty_ruleset_settings.json", string(data))
}

144
client/testdata/snake_request.json vendored Normal file
View file

@ -0,0 +1,144 @@
{
"game": {
"id": "game-id",
"ruleset": {
"name": "test-ruleset-name",
"version": "cli",
"settings": {
"foodSpawnChance": 10,
"minimumFood": 20,
"hazardDamagePerTurn": 30,
"royale": {
"shrinkEveryNTurns": 40
},
"squad": {
"allowBodyCollisions": true,
"sharedElimination": true,
"sharedHealth": true,
"sharedLength": true
}
}
},
"timeout": 33,
"source": "league"
},
"turn": 11,
"board": {
"height": 22,
"width": 11,
"snakes": [
{
"id": "snake-0",
"name": "snake-0-name",
"latency": "snake-0-latency",
"health": 100,
"body": [
{
"x": 1,
"y": 2
},
{
"x": 1,
"y": 3
},
{
"x": 1,
"y": 4
}
],
"head": {
"x": 1,
"y": 2
},
"length": 3,
"shout": "snake-0-shout",
"squad": "",
"customizations": {
"color": "#123456",
"head": "safe",
"tail": "curled"
}
},
{
"id": "snake-1",
"name": "snake-1-name",
"latency": "snake-1-latency",
"health": 200,
"body": [
{
"x": 2,
"y": 2
},
{
"x": 2,
"y": 3
},
{
"x": 2,
"y": 4
}
],
"head": {
"x": 2,
"y": 2
},
"length": 3,
"shout": "snake-1-shout",
"squad": "snake-1-squad",
"customizations": {
"color": "#654321",
"head": "silly",
"tail": "bolt"
}
}
],
"food": [
{
"x": 2,
"y": 2
}
],
"hazards": [
{
"x": 8,
"y": 8
},
{
"x": 9,
"y": 9
}
]
},
"you": {
"id": "snake-1",
"name": "snake-1-name",
"latency": "snake-1-latency",
"health": 200,
"body": [
{
"x": 2,
"y": 2
},
{
"x": 2,
"y": 3
},
{
"x": 2,
"y": 4
}
],
"head": {
"x": 2,
"y": 2
},
"length": 3,
"shout": "snake-1-shout",
"squad": "snake-1-squad",
"customizations": {
"color": "#654321",
"head": "silly",
"tail": "bolt"
}
}
}

View file

@ -0,0 +1,144 @@
{
"game": {
"id": "game-id",
"ruleset": {
"name": "test-ruleset-name",
"version": "cli",
"settings": {
"foodSpawnChance": 0,
"minimumFood": 0,
"hazardDamagePerTurn": 0,
"royale": {
"shrinkEveryNTurns": 0
},
"squad": {
"allowBodyCollisions": false,
"sharedElimination": false,
"sharedHealth": false,
"sharedLength": false
}
}
},
"timeout": 33,
"source": "league"
},
"turn": 11,
"board": {
"height": 22,
"width": 11,
"snakes": [
{
"id": "snake-0",
"name": "snake-0-name",
"latency": "snake-0-latency",
"health": 100,
"body": [
{
"x": 1,
"y": 2
},
{
"x": 1,
"y": 3
},
{
"x": 1,
"y": 4
}
],
"head": {
"x": 1,
"y": 2
},
"length": 3,
"shout": "snake-0-shout",
"squad": "",
"customizations": {
"color": "#123456",
"head": "safe",
"tail": "curled"
}
},
{
"id": "snake-1",
"name": "snake-1-name",
"latency": "snake-1-latency",
"health": 200,
"body": [
{
"x": 2,
"y": 2
},
{
"x": 2,
"y": 3
},
{
"x": 2,
"y": 4
}
],
"head": {
"x": 2,
"y": 2
},
"length": 3,
"shout": "snake-1-shout",
"squad": "snake-1-squad",
"customizations": {
"color": "#654321",
"head": "silly",
"tail": "bolt"
}
}
],
"food": [
{
"x": 2,
"y": 2
}
],
"hazards": [
{
"x": 8,
"y": 8
},
{
"x": 9,
"y": 9
}
]
},
"you": {
"id": "snake-1",
"name": "snake-1-name",
"latency": "snake-1-latency",
"health": 200,
"body": [
{
"x": 2,
"y": 2
},
{
"x": 2,
"y": 3
},
{
"x": 2,
"y": 4
}
],
"head": {
"x": 2,
"y": 2
},
"length": 3,
"shout": "snake-1-shout",
"squad": "snake-1-squad",
"customizations": {
"color": "#654321",
"head": "silly",
"tail": "bolt"
}
}
}