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:
parent
6140f232c2
commit
4a9dbbcaef
10 changed files with 665 additions and 197 deletions
|
|
@ -15,100 +15,26 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/BattlesnakeOfficial/rules"
|
"github.com/BattlesnakeOfficial/rules"
|
||||||
|
"github.com/BattlesnakeOfficial/rules/client"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Battlesnake struct {
|
// Used to store state for each SnakeState while running a local game
|
||||||
|
type SnakeState struct {
|
||||||
URL string
|
URL string
|
||||||
Name string
|
Name string
|
||||||
ID string
|
ID string
|
||||||
API string
|
|
||||||
LastMove string
|
LastMove string
|
||||||
Squad string
|
Squad string
|
||||||
Character rune
|
Character rune
|
||||||
}
|
Color string
|
||||||
|
Head string
|
||||||
type Coord struct {
|
Tail string
|
||||||
X int32 `json:"x"`
|
|
||||||
Y int32 `json:"y"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SnakeResponse struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Health int32 `json:"health"`
|
|
||||||
Body []Coord `json:"body"`
|
|
||||||
Latency string `json:"latency"`
|
|
||||||
Head Coord `json:"head"`
|
|
||||||
Length int32 `json:"length"`
|
|
||||||
Shout string `json:"shout"`
|
|
||||||
Squad string `json:"squad"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BoardResponse struct {
|
|
||||||
Height int32 `json:"height"`
|
|
||||||
Width int32 `json:"width"`
|
|
||||||
Food []Coord `json:"food"`
|
|
||||||
Hazards []Coord `json:"hazards"`
|
|
||||||
Snakes []SnakeResponse `json:"snakes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GameResponseRulesetSettings struct {
|
|
||||||
HazardDamagePerTurn int32 `json:"hazardDamagePerTurn"`
|
|
||||||
FoodSpawnChance int32 `json:"foodSpawnChance"`
|
|
||||||
MinimumFood int32 `json:"minimumFood"`
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GameResponseRuleset struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
Settings GameResponseRulesetSettings `json:"settings"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GameResponse struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Timeout int32 `json:"timeout"`
|
|
||||||
Ruleset GameResponseRuleset `json:"ruleset"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResponsePayload struct {
|
|
||||||
Game GameResponse `json:"game"`
|
|
||||||
Turn int32 `json:"turn"`
|
|
||||||
Board BoardResponse `json:"board"`
|
|
||||||
You SnakeResponse `json:"you"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PlayerResponse struct {
|
|
||||||
Move string `json:"move"`
|
|
||||||
Shout string `json:"shout"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PingResponse struct {
|
|
||||||
APIVersion string `json:"apiversion"`
|
|
||||||
Author string `json:"author"`
|
|
||||||
Color string `json:"color"`
|
|
||||||
Head string `json:"head"`
|
|
||||||
Tail string `json:"tail"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var GameId string
|
var GameId string
|
||||||
var Turn int32
|
var Turn int32
|
||||||
var Battlesnakes map[string]Battlesnake
|
|
||||||
var HttpClient http.Client
|
var HttpClient http.Client
|
||||||
var Width int32
|
var Width int32
|
||||||
var Height int32
|
var Height int32
|
||||||
|
|
@ -162,24 +88,20 @@ func init() {
|
||||||
var run = func(cmd *cobra.Command, args []string) {
|
var run = func(cmd *cobra.Command, args []string) {
|
||||||
rand.Seed(Seed)
|
rand.Seed(Seed)
|
||||||
|
|
||||||
Battlesnakes = make(map[string]Battlesnake)
|
|
||||||
GameId = uuid.New().String()
|
GameId = uuid.New().String()
|
||||||
Turn = 0
|
Turn = 0
|
||||||
|
|
||||||
snakes := buildSnakesFromOptions()
|
snakeStates := buildSnakesFromOptions()
|
||||||
|
|
||||||
ruleset := getRuleset(Seed, snakes)
|
ruleset := getRuleset(Seed, snakeStates)
|
||||||
state := initializeBoardFromArgs(ruleset, snakes)
|
state := initializeBoardFromArgs(ruleset, snakeStates)
|
||||||
for _, snake := range snakes {
|
|
||||||
Battlesnakes[snake.ID] = snake
|
|
||||||
}
|
|
||||||
|
|
||||||
for v := false; !v; v, _ = ruleset.IsGameOver(state) {
|
for v := false; !v; v, _ = ruleset.IsGameOver(state) {
|
||||||
Turn++
|
Turn++
|
||||||
state = createNextBoardState(ruleset, state, snakes, Turn)
|
state = createNextBoardState(ruleset, state, snakeStates, Turn)
|
||||||
|
|
||||||
if ViewMap {
|
if ViewMap {
|
||||||
printMap(state, Turn)
|
printMap(state, snakeStates, Turn)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[%v]: State: %v\n", Turn, state)
|
log.Printf("[%v]: State: %v\n", Turn, state)
|
||||||
}
|
}
|
||||||
|
|
@ -197,9 +119,9 @@ var run = func(cmd *cobra.Command, args []string) {
|
||||||
for _, snake := range state.Snakes {
|
for _, snake := range state.Snakes {
|
||||||
if snake.EliminatedCause == rules.NotEliminated {
|
if snake.EliminatedCause == rules.NotEliminated {
|
||||||
isDraw = false
|
isDraw = false
|
||||||
winner = Battlesnakes[snake.ID].Name
|
winner = snakeStates[snake.ID].Name
|
||||||
}
|
}
|
||||||
sendEndRequest(ruleset, state, Battlesnakes[snake.ID])
|
sendEndRequest(ruleset, state, snakeStates[snake.ID], snakeStates)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isDraw {
|
if isDraw {
|
||||||
|
|
@ -210,7 +132,7 @@ var run = func(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRuleset(seed int64, snakes []Battlesnake) rules.Ruleset {
|
func getRuleset(seed int64, snakeStates map[string]SnakeState) rules.Ruleset {
|
||||||
var ruleset rules.Ruleset
|
var ruleset rules.Ruleset
|
||||||
var royale rules.RoyaleRuleset
|
var royale rules.RoyaleRuleset
|
||||||
|
|
||||||
|
|
@ -231,8 +153,8 @@ func getRuleset(seed int64, snakes []Battlesnake) rules.Ruleset {
|
||||||
ruleset = &royale
|
ruleset = &royale
|
||||||
case "squad":
|
case "squad":
|
||||||
squadMap := map[string]string{}
|
squadMap := map[string]string{}
|
||||||
for _, snake := range snakes {
|
for _, snakeState := range snakeStates {
|
||||||
squadMap[snake.ID] = snake.Squad
|
squadMap[snakeState.ID] = snakeState.Squad
|
||||||
}
|
}
|
||||||
ruleset = &rules.SquadRuleset{
|
ruleset = &rules.SquadRuleset{
|
||||||
StandardRuleset: standard,
|
StandardRuleset: standard,
|
||||||
|
|
@ -260,7 +182,7 @@ func getRuleset(seed int64, snakes []Battlesnake) rules.Ruleset {
|
||||||
return ruleset
|
return ruleset
|
||||||
}
|
}
|
||||||
|
|
||||||
func initializeBoardFromArgs(ruleset rules.Ruleset, snakes []Battlesnake) *rules.BoardState {
|
func initializeBoardFromArgs(ruleset rules.Ruleset, snakeStates map[string]SnakeState) *rules.BoardState {
|
||||||
if Timeout == 0 {
|
if Timeout == 0 {
|
||||||
Timeout = 500
|
Timeout = 500
|
||||||
}
|
}
|
||||||
|
|
@ -269,8 +191,8 @@ func initializeBoardFromArgs(ruleset rules.Ruleset, snakes []Battlesnake) *rules
|
||||||
}
|
}
|
||||||
|
|
||||||
snakeIds := []string{}
|
snakeIds := []string{}
|
||||||
for _, snake := range snakes {
|
for _, snakeState := range snakeStates {
|
||||||
snakeIds = append(snakeIds, snake.ID)
|
snakeIds = append(snakeIds, snakeState.ID)
|
||||||
}
|
}
|
||||||
state, err := rules.CreateDefaultBoardState(Width, Height, snakeIds)
|
state, err := rules.CreateDefaultBoardState(Width, Height, snakeIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -281,9 +203,9 @@ func initializeBoardFromArgs(ruleset rules.Ruleset, snakes []Battlesnake) *rules
|
||||||
log.Panic("[PANIC]: Error Initializing Board State")
|
log.Panic("[PANIC]: Error Initializing Board State")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, snake := range snakes {
|
for _, snakeState := range snakeStates {
|
||||||
requestBody := getIndividualBoardStateForSnake(state, snake, ruleset)
|
requestBody := getIndividualBoardStateForSnake(state, snakeState, snakeStates, ruleset)
|
||||||
u, _ := url.ParseRequestURI(snake.URL)
|
u, _ := url.ParseRequestURI(snakeState.URL)
|
||||||
u.Path = path.Join(u.Path, "start")
|
u.Path = path.Join(u.Path, "start")
|
||||||
if DebugRequests {
|
if DebugRequests {
|
||||||
log.Printf("POST %s: %v", u, string(requestBody))
|
log.Printf("POST %s: %v", u, string(requestBody))
|
||||||
|
|
@ -296,25 +218,28 @@ func initializeBoardFromArgs(ruleset rules.Ruleset, snakes []Battlesnake) *rules
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNextBoardState(ruleset rules.Ruleset, state *rules.BoardState, snakes []Battlesnake, turn int32) *rules.BoardState {
|
func createNextBoardState(ruleset rules.Ruleset, state *rules.BoardState, snakeStates map[string]SnakeState, turn int32) *rules.BoardState {
|
||||||
var moves []rules.SnakeMove
|
var moves []rules.SnakeMove
|
||||||
if Sequential {
|
if Sequential {
|
||||||
for _, snake := range snakes {
|
for _, snakeState := range snakeStates {
|
||||||
for _, stateSnake := range state.Snakes {
|
for _, snake := range state.Snakes {
|
||||||
if snake.ID == stateSnake.ID && stateSnake.EliminatedCause == rules.NotEliminated {
|
if snakeState.ID == snake.ID && snake.EliminatedCause == rules.NotEliminated {
|
||||||
moves = append(moves, getMoveForSnake(ruleset, state, snake))
|
moves = append(moves, getMoveForSnake(ruleset, state, snakeState, snakeStates))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
c := make(chan rules.SnakeMove, len(snakes))
|
c := make(chan rules.SnakeMove, len(snakeStates))
|
||||||
|
|
||||||
for _, snake := range snakes {
|
for _, snakeState := range snakeStates {
|
||||||
for _, stateSnake := range state.Snakes {
|
for _, snake := range state.Snakes {
|
||||||
if snake.ID == stateSnake.ID && stateSnake.EliminatedCause == rules.NotEliminated {
|
if snakeState.ID == snake.ID && snake.EliminatedCause == rules.NotEliminated {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go getConcurrentMoveForSnake(&wg, ruleset, state, snake, c)
|
go func(snakeState SnakeState) {
|
||||||
|
defer wg.Done()
|
||||||
|
c <- getMoveForSnake(ruleset, state, snakeState, snakeStates)
|
||||||
|
}(snakeState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -327,14 +252,13 @@ func createNextBoardState(ruleset rules.Ruleset, state *rules.BoardState, snakes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, move := range moves {
|
for _, move := range moves {
|
||||||
snake := Battlesnakes[move.ID]
|
snakeState := snakeStates[move.ID]
|
||||||
snake.LastMove = move.Move
|
snakeState.LastMove = move.Move
|
||||||
Battlesnakes[move.ID] = snake
|
snakeStates[move.ID] = snakeState
|
||||||
}
|
}
|
||||||
state, err := ruleset.CreateNextBoardState(state, moves)
|
state, err := ruleset.CreateNextBoardState(state, moves)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic("[PANIC]: Error Producing Next Board State")
|
log.Panicf("[PANIC]: Error Producing Next Board State: %v", err)
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Turn = turn
|
state.Turn = turn
|
||||||
|
|
@ -342,20 +266,15 @@ func createNextBoardState(ruleset rules.Ruleset, state *rules.BoardState, snakes
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConcurrentMoveForSnake(wg *sync.WaitGroup, ruleset rules.Ruleset, state *rules.BoardState, snake Battlesnake, c chan rules.SnakeMove) {
|
func getMoveForSnake(ruleset rules.Ruleset, state *rules.BoardState, snakeState SnakeState, snakeStates map[string]SnakeState) rules.SnakeMove {
|
||||||
defer wg.Done()
|
requestBody := getIndividualBoardStateForSnake(state, snakeState, snakeStates, ruleset)
|
||||||
c <- getMoveForSnake(ruleset, state, snake)
|
u, _ := url.ParseRequestURI(snakeState.URL)
|
||||||
}
|
|
||||||
|
|
||||||
func getMoveForSnake(ruleset rules.Ruleset, state *rules.BoardState, snake Battlesnake) rules.SnakeMove {
|
|
||||||
requestBody := getIndividualBoardStateForSnake(state, snake, ruleset)
|
|
||||||
u, _ := url.ParseRequestURI(snake.URL)
|
|
||||||
u.Path = path.Join(u.Path, "move")
|
u.Path = path.Join(u.Path, "move")
|
||||||
if DebugRequests {
|
if DebugRequests {
|
||||||
log.Printf("POST %s: %v", u, string(requestBody))
|
log.Printf("POST %s: %v", u, string(requestBody))
|
||||||
}
|
}
|
||||||
res, err := HttpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
res, err := HttpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
||||||
move := snake.LastMove
|
move := snakeState.LastMove
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARN]: Request to %v failed\n", u.String())
|
log.Printf("[WARN]: Request to %v failed\n", u.String())
|
||||||
log.Printf("Body --> %v\n", string(requestBody))
|
log.Printf("Body --> %v\n", string(requestBody))
|
||||||
|
|
@ -365,7 +284,7 @@ func getMoveForSnake(ruleset rules.Ruleset, state *rules.BoardState, snake Battl
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
log.Fatal(readErr)
|
log.Fatal(readErr)
|
||||||
} else {
|
} else {
|
||||||
playerResponse := PlayerResponse{}
|
playerResponse := client.MoveResponse{}
|
||||||
jsonErr := json.Unmarshal(body, &playerResponse)
|
jsonErr := json.Unmarshal(body, &playerResponse)
|
||||||
if jsonErr != nil {
|
if jsonErr != nil {
|
||||||
log.Fatal(jsonErr)
|
log.Fatal(jsonErr)
|
||||||
|
|
@ -374,12 +293,12 @@ func getMoveForSnake(ruleset rules.Ruleset, state *rules.BoardState, snake Battl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rules.SnakeMove{ID: snake.ID, Move: move}
|
return rules.SnakeMove{ID: snakeState.ID, Move: move}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendEndRequest(ruleset rules.Ruleset, state *rules.BoardState, snake Battlesnake) {
|
func sendEndRequest(ruleset rules.Ruleset, state *rules.BoardState, snakeState SnakeState, snakeStates map[string]SnakeState) {
|
||||||
requestBody := getIndividualBoardStateForSnake(state, snake, ruleset)
|
requestBody := getIndividualBoardStateForSnake(state, snakeState, snakeStates, ruleset)
|
||||||
u, _ := url.ParseRequestURI(snake.URL)
|
u, _ := url.ParseRequestURI(snakeState.URL)
|
||||||
u.Path = path.Join(u.Path, "end")
|
u.Path = path.Join(u.Path, "end")
|
||||||
if DebugRequests {
|
if DebugRequests {
|
||||||
log.Printf("POST %s: %v", u, string(requestBody))
|
log.Printf("POST %s: %v", u, string(requestBody))
|
||||||
|
|
@ -390,26 +309,26 @@ func sendEndRequest(ruleset rules.Ruleset, state *rules.BoardState, snake Battle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIndividualBoardStateForSnake(state *rules.BoardState, snake Battlesnake, ruleset rules.Ruleset) []byte {
|
func getIndividualBoardStateForSnake(state *rules.BoardState, snakeState SnakeState, snakeStates map[string]SnakeState, ruleset rules.Ruleset) []byte {
|
||||||
var youSnake rules.Snake
|
var youSnake rules.Snake
|
||||||
for _, snk := range state.Snakes {
|
for _, snk := range state.Snakes {
|
||||||
if snake.ID == snk.ID {
|
if snakeState.ID == snk.ID {
|
||||||
youSnake = snk
|
youSnake = snk
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response := ResponsePayload{
|
request := client.SnakeRequest{
|
||||||
Game: GameResponse{Id: GameId, Timeout: Timeout, Ruleset: GameResponseRuleset{
|
Game: client.Game{ID: GameId, Timeout: Timeout, Ruleset: client.Ruleset{
|
||||||
Name: ruleset.Name(),
|
Name: ruleset.Name(),
|
||||||
Version: "cli", // TODO: Use GitHub Release Version
|
Version: "cli", // TODO: Use GitHub Release Version
|
||||||
Settings: GameResponseRulesetSettings{
|
Settings: client.RulesetSettings{
|
||||||
HazardDamagePerTurn: HazardDamagePerTurn,
|
HazardDamagePerTurn: HazardDamagePerTurn,
|
||||||
FoodSpawnChance: FoodSpawnChance,
|
FoodSpawnChance: FoodSpawnChance,
|
||||||
MinimumFood: MinimumFood,
|
MinimumFood: MinimumFood,
|
||||||
RoyaleSettings: RoyaleSettings{
|
RoyaleSettings: client.RoyaleSettings{
|
||||||
ShrinkEveryNTurns: ShrinkEveryNTurns,
|
ShrinkEveryNTurns: ShrinkEveryNTurns,
|
||||||
},
|
},
|
||||||
SquadSettings: SquadSettings{
|
SquadSettings: client.SquadSettings{
|
||||||
AllowBodyCollisions: true,
|
AllowBodyCollisions: true,
|
||||||
SharedElimination: true,
|
SharedElimination: true,
|
||||||
SharedHealth: true,
|
SharedHealth: true,
|
||||||
|
|
@ -418,63 +337,56 @@ func getIndividualBoardStateForSnake(state *rules.BoardState, snake Battlesnake,
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
Turn: Turn,
|
Turn: Turn,
|
||||||
Board: BoardResponse{
|
Board: client.Board{
|
||||||
Height: state.Height,
|
Height: state.Height,
|
||||||
Width: state.Width,
|
Width: state.Width,
|
||||||
Food: coordFromPointArray(state.Food),
|
Food: client.CoordFromPointArray(state.Food),
|
||||||
Hazards: coordFromPointArray(state.Hazards),
|
Hazards: client.CoordFromPointArray(state.Hazards),
|
||||||
Snakes: buildSnakesResponse(state.Snakes),
|
Snakes: convertRulesSnakes(state.Snakes, snakeStates),
|
||||||
},
|
},
|
||||||
You: snakeResponseFromSnake(youSnake),
|
You: convertRulesSnake(youSnake, snakeStates[youSnake.ID]),
|
||||||
}
|
}
|
||||||
responseJson, err := json.Marshal(response)
|
requestJSON, err := json.Marshal(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic("[PANIC]: Error Marshalling JSON from State")
|
log.Panic("[PANIC]: Error Marshalling JSON from State")
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return responseJson
|
return requestJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
func snakeResponseFromSnake(snake rules.Snake) SnakeResponse {
|
func convertRulesSnake(snake rules.Snake, snakeState SnakeState) client.Snake {
|
||||||
return SnakeResponse{
|
return client.Snake{
|
||||||
Id: snake.ID,
|
ID: snake.ID,
|
||||||
Name: Battlesnakes[snake.ID].Name,
|
Name: snakeState.Name,
|
||||||
Health: snake.Health,
|
Health: snake.Health,
|
||||||
Body: coordFromPointArray(snake.Body),
|
Body: client.CoordFromPointArray(snake.Body),
|
||||||
Latency: "0",
|
Latency: "0",
|
||||||
Head: coordFromPoint(snake.Body[0]),
|
Head: client.CoordFromPoint(snake.Body[0]),
|
||||||
Length: int32(len(snake.Body)),
|
Length: int32(len(snake.Body)),
|
||||||
Shout: "",
|
Shout: "",
|
||||||
Squad: Battlesnakes[snake.ID].Squad,
|
Squad: snakeState.Squad,
|
||||||
|
Customizations: client.Customizations{
|
||||||
|
Head: snakeState.Head,
|
||||||
|
Tail: snakeState.Tail,
|
||||||
|
Color: snakeState.Color,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSnakesResponse(snakes []rules.Snake) []SnakeResponse {
|
func convertRulesSnakes(snakes []rules.Snake, snakeStates map[string]SnakeState) []client.Snake {
|
||||||
var a []SnakeResponse
|
var a []client.Snake
|
||||||
for _, snake := range snakes {
|
for _, snake := range snakes {
|
||||||
if snake.EliminatedCause == rules.NotEliminated {
|
if snake.EliminatedCause == rules.NotEliminated {
|
||||||
a = append(a, snakeResponseFromSnake(snake))
|
a = append(a, convertRulesSnake(snake, snakeStates[snake.ID]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func coordFromPoint(pt rules.Point) Coord {
|
func buildSnakesFromOptions() map[string]SnakeState {
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildSnakesFromOptions() []Battlesnake {
|
|
||||||
bodyChars := []rune{'■', '⌀', '●', '⍟', '◘', '☺', '□', '☻'}
|
bodyChars := []rune{'■', '⌀', '●', '⍟', '◘', '☺', '□', '☻'}
|
||||||
var numSnakes int
|
var numSnakes int
|
||||||
var snakes []Battlesnake
|
snakes := map[string]SnakeState{}
|
||||||
numNames := len(Names)
|
numNames := len(Names)
|
||||||
numURLs := len(URLs)
|
numURLs := len(URLs)
|
||||||
numSquads := len(Squads)
|
numSquads := len(Squads)
|
||||||
|
|
@ -521,10 +433,12 @@ func buildSnakesFromOptions() []Battlesnake {
|
||||||
snakeSquad = strconv.Itoa(i / 2)
|
snakeSquad = strconv.Itoa(i / 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
snakeState := SnakeState{
|
||||||
|
Name: snakeName, URL: snakeURL, ID: id, LastMove: "up", Character: bodyChars[i%8],
|
||||||
|
}
|
||||||
res, err := HttpClient.Get(snakeURL)
|
res, err := HttpClient.Get(snakeURL)
|
||||||
api := "0"
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARN]: Request to %v failed", snakeURL)
|
log.Printf("[WARN]: Request to %v failed: %v", snakeURL, err)
|
||||||
} else if res.Body != nil {
|
} else if res.Body != nil {
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
body, readErr := ioutil.ReadAll(res.Body)
|
body, readErr := ioutil.ReadAll(res.Body)
|
||||||
|
|
@ -532,24 +446,25 @@ func buildSnakesFromOptions() []Battlesnake {
|
||||||
log.Fatal(readErr)
|
log.Fatal(readErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pingResponse := PingResponse{}
|
pingResponse := client.SnakeMetadataResponse{}
|
||||||
jsonErr := json.Unmarshal(body, &pingResponse)
|
jsonErr := json.Unmarshal(body, &pingResponse)
|
||||||
if jsonErr != nil {
|
if jsonErr != nil {
|
||||||
log.Fatal(jsonErr)
|
log.Printf("Error reading response from %v: %v", snakeURL, jsonErr)
|
||||||
} else {
|
} else {
|
||||||
api = pingResponse.APIVersion
|
snakeState.Head = pingResponse.Head
|
||||||
|
snakeState.Tail = pingResponse.Tail
|
||||||
|
snakeState.Color = pingResponse.Color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
snake := Battlesnake{Name: snakeName, URL: snakeURL, ID: id, API: api, LastMove: "up", Character: bodyChars[i%8]}
|
|
||||||
if GameType == "squad" {
|
if GameType == "squad" {
|
||||||
snake.Squad = snakeSquad
|
snakeState.Squad = snakeSquad
|
||||||
}
|
}
|
||||||
snakes = append(snakes, snake)
|
snakes[snakeState.ID] = snakeState
|
||||||
}
|
}
|
||||||
return snakes
|
return snakes
|
||||||
}
|
}
|
||||||
|
|
||||||
func printMap(state *rules.BoardState, gameTurn int32) {
|
func printMap(state *rules.BoardState, snakeStates map[string]SnakeState, gameTurn int32) {
|
||||||
var o bytes.Buffer
|
var o bytes.Buffer
|
||||||
o.WriteString(fmt.Sprintf("Ruleset: %s, Seed: %d, Turn: %v\n", GameType, Seed, gameTurn))
|
o.WriteString(fmt.Sprintf("Ruleset: %s, Seed: %d, Turn: %v\n", GameType, Seed, gameTurn))
|
||||||
board := make([][]rune, state.Width)
|
board := make([][]rune, state.Width)
|
||||||
|
|
@ -572,10 +487,10 @@ func printMap(state *rules.BoardState, gameTurn int32) {
|
||||||
for _, s := range state.Snakes {
|
for _, s := range state.Snakes {
|
||||||
for _, b := range s.Body {
|
for _, b := range s.Body {
|
||||||
if b.X >= 0 && b.X < state.Width && b.Y >= 0 && b.Y < state.Height {
|
if b.X >= 0 && b.X < state.Width && b.Y >= 0 && b.Y < state.Height {
|
||||||
board[b.X][b.Y] = Battlesnakes[s.ID].Character
|
board[b.X][b.Y] = snakeStates[s.ID].Character
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
o.WriteString(fmt.Sprintf("%v %c: %v\n", Battlesnakes[s.ID].Name, Battlesnakes[s.ID].Character, s))
|
o.WriteString(fmt.Sprintf("%v %c: %v\n", snakeStates[s.ID].Name, snakeStates[s.ID].Character, s))
|
||||||
}
|
}
|
||||||
for y := state.Height - 1; y >= 0; y-- {
|
for y := state.Height - 1; y >= 0; y-- {
|
||||||
for x := int32(0); x < state.Width; x++ {
|
for x := int32(0); x < state.Width; x++ {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/BattlesnakeOfficial/rules"
|
"github.com/BattlesnakeOfficial/rules"
|
||||||
|
"github.com/BattlesnakeOfficial/rules/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetIndividualBoardStateForSnake(t *testing.T) {
|
func TestGetIndividualBoardStateForSnake(t *testing.T) {
|
||||||
|
|
@ -14,8 +15,27 @@ func TestGetIndividualBoardStateForSnake(t *testing.T) {
|
||||||
Width: 11,
|
Width: 11,
|
||||||
Snakes: []rules.Snake{s1, s2},
|
Snakes: []rules.Snake{s1, s2},
|
||||||
}
|
}
|
||||||
snake := Battlesnake{Name: "one", URL: "", ID: "one"}
|
s1State := SnakeState{
|
||||||
requestBody := getIndividualBoardStateForSnake(state, snake, &rules.StandardRuleset{})
|
ID: "one",
|
||||||
|
Name: "ONE",
|
||||||
|
URL: "http://example1.com",
|
||||||
|
Head: "safe",
|
||||||
|
Tail: "curled",
|
||||||
|
Color: "#123456",
|
||||||
|
}
|
||||||
|
s2State := SnakeState{
|
||||||
|
ID: "two",
|
||||||
|
Name: "TWO",
|
||||||
|
URL: "http://example2.com",
|
||||||
|
Head: "silly",
|
||||||
|
Tail: "bolt",
|
||||||
|
Color: "#654321",
|
||||||
|
}
|
||||||
|
snakeStates := map[string]SnakeState{
|
||||||
|
s1State.ID: s1State,
|
||||||
|
s2State.ID: s2State,
|
||||||
|
}
|
||||||
|
requestBody := getIndividualBoardStateForSnake(state, s1State, snakeStates, &rules.StandardRuleset{})
|
||||||
|
|
||||||
rules.RequireJSONMatchesFixture(t, "testdata/snake_request_body.json", string(requestBody))
|
test.RequireJSONMatchesFixture(t, "testdata/snake_request_body.json", string(requestBody))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
cli/commands/testdata/snake_request_body.json
vendored
46
cli/commands/testdata/snake_request_body.json
vendored
|
|
@ -1,14 +1,13 @@
|
||||||
{
|
{
|
||||||
"game": {
|
"game": {
|
||||||
"id": "",
|
"id": "",
|
||||||
"timeout": 500,
|
|
||||||
"ruleset": {
|
"ruleset": {
|
||||||
"name": "standard",
|
"name": "standard",
|
||||||
"version": "cli",
|
"version": "cli",
|
||||||
"settings": {
|
"settings": {
|
||||||
"hazardDamagePerTurn": 14,
|
|
||||||
"foodSpawnChance": 15,
|
"foodSpawnChance": 15,
|
||||||
"minimumFood": 1,
|
"minimumFood": 1,
|
||||||
|
"hazardDamagePerTurn": 14,
|
||||||
"royale": {
|
"royale": {
|
||||||
"shrinkEveryNTurns": 25
|
"shrinkEveryNTurns": 25
|
||||||
},
|
},
|
||||||
|
|
@ -19,18 +18,19 @@
|
||||||
"sharedLength": true
|
"sharedLength": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"timeout": 500,
|
||||||
|
"source": ""
|
||||||
},
|
},
|
||||||
"turn": 0,
|
"turn": 0,
|
||||||
"board": {
|
"board": {
|
||||||
"height": 11,
|
"height": 11,
|
||||||
"width": 11,
|
"width": 11,
|
||||||
"food": [],
|
|
||||||
"hazards": [],
|
|
||||||
"snakes": [
|
"snakes": [
|
||||||
{
|
{
|
||||||
"id": "one",
|
"id": "one",
|
||||||
"name": "",
|
"name": "ONE",
|
||||||
|
"latency": "0",
|
||||||
"health": 0,
|
"health": 0,
|
||||||
"body": [
|
"body": [
|
||||||
{
|
{
|
||||||
|
|
@ -38,18 +38,23 @@
|
||||||
"y": 3
|
"y": 3
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"latency": "0",
|
|
||||||
"head": {
|
"head": {
|
||||||
"x": 3,
|
"x": 3,
|
||||||
"y": 3
|
"y": 3
|
||||||
},
|
},
|
||||||
"length": 1,
|
"length": 1,
|
||||||
"shout": "",
|
"shout": "",
|
||||||
"squad": ""
|
"squad": "",
|
||||||
|
"customizations": {
|
||||||
|
"color": "#123456",
|
||||||
|
"head": "safe",
|
||||||
|
"tail": "curled"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "two",
|
"id": "two",
|
||||||
"name": "",
|
"name": "TWO",
|
||||||
|
"latency": "0",
|
||||||
"health": 0,
|
"health": 0,
|
||||||
"body": [
|
"body": [
|
||||||
{
|
{
|
||||||
|
|
@ -57,20 +62,27 @@
|
||||||
"y": 3
|
"y": 3
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"latency": "0",
|
|
||||||
"head": {
|
"head": {
|
||||||
"x": 4,
|
"x": 4,
|
||||||
"y": 3
|
"y": 3
|
||||||
},
|
},
|
||||||
"length": 1,
|
"length": 1,
|
||||||
"shout": "",
|
"shout": "",
|
||||||
"squad": ""
|
"squad": "",
|
||||||
|
"customizations": {
|
||||||
|
"color": "#654321",
|
||||||
|
"head": "silly",
|
||||||
|
"tail": "bolt"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"food": [],
|
||||||
|
"hazards": []
|
||||||
},
|
},
|
||||||
"you": {
|
"you": {
|
||||||
"id": "one",
|
"id": "one",
|
||||||
"name": "",
|
"name": "ONE",
|
||||||
|
"latency": "0",
|
||||||
"health": 0,
|
"health": 0,
|
||||||
"body": [
|
"body": [
|
||||||
{
|
{
|
||||||
|
|
@ -78,13 +90,17 @@
|
||||||
"y": 3
|
"y": 3
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"latency": "0",
|
|
||||||
"head": {
|
"head": {
|
||||||
"x": 3,
|
"x": 3,
|
||||||
"y": 3
|
"y": 3
|
||||||
},
|
},
|
||||||
"length": 1,
|
"length": 1,
|
||||||
"shout": "",
|
"shout": "",
|
||||||
"squad": ""
|
"squad": "",
|
||||||
|
"customizations": {
|
||||||
|
"color": "#123456",
|
||||||
|
"head": "safe",
|
||||||
|
"tail": "curled"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
90
client/fixtures_test.go
Normal file
90
client/fixtures_test.go
Normal 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
107
client/models.go
Normal 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
26
client/models_test.go
Normal 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
144
client/testdata/snake_request.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
144
client/testdata/snake_request_empty_ruleset_settings.json
vendored
Normal file
144
client/testdata/snake_request_empty_ruleset_settings.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
// included to allow using -update-fixtures for every package without errors
|
||||||
|
_ "github.com/BattlesnakeOfficial/rules/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRulesetError(t *testing.T) {
|
func TestRulesetError(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
package rules
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
@ -26,6 +27,8 @@ func RequireJSONMatchesFixture(t *testing.T, filename string, actual string) {
|
||||||
require.NoError(t, err, "Failed to indent JSON")
|
require.NoError(t, err, "Failed to indent JSON")
|
||||||
err = ioutil.WriteFile(filename, indented.Bytes(), 0644)
|
err = ioutil.WriteFile(filename, indented.Bytes(), 0644)
|
||||||
require.NoError(t, err, "Failed to update fixture", filename)
|
require.NoError(t, err, "Failed to update fixture", filename)
|
||||||
|
|
||||||
|
log.Printf("Updating fixture file %#v", filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedData, err := ioutil.ReadFile(filename)
|
expectedData, err := ioutil.ReadFile(filename)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue