2020-12-10 15:02:29 -08:00
package commands
2020-12-10 17:35:52 -05:00
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
2020-12-10 15:56:22 -08:00
"math/rand"
2020-12-10 17:35:52 -05:00
"net/http"
"net/url"
2021-12-02 18:59:20 +01:00
"os"
2020-12-10 17:35:52 -05:00
"path"
"strconv"
2021-07-02 20:36:37 -07:00
"sync"
2020-12-10 17:35:52 -05:00
"time"
2021-07-03 06:42:16 +03:00
"github.com/BattlesnakeOfficial/rules"
2021-11-25 14:07:56 -08:00
"github.com/BattlesnakeOfficial/rules/client"
2021-07-03 06:42:16 +03:00
"github.com/google/uuid"
"github.com/spf13/cobra"
2020-12-10 17:35:52 -05:00
)
2021-11-25 14:07:56 -08:00
// Used to store state for each SnakeState while running a local game
type SnakeState struct {
2020-12-10 17:35:52 -05:00
URL string
Name string
ID string
LastMove string
Squad string
Character rune
2021-11-25 14:07:56 -08:00
Color string
Head string
Tail string
2020-12-10 17:35:52 -05:00
}
var GameId string
var Turn int32
var HttpClient http . Client
var Width int32
var Height int32
var Names [ ] string
var URLs [ ] string
var Squads [ ] string
var Timeout int32
var Sequential bool
var GameType string
var ViewMap bool
2020-12-10 15:56:22 -08:00
var Seed int64
2021-07-04 13:48:56 -07:00
var TurnDelay int32
2021-09-07 14:58:10 -07:00
var DebugRequests bool
2021-12-02 18:59:20 +01:00
var Output string
2020-12-10 17:35:52 -05:00
2021-09-02 15:32:46 -07:00
var FoodSpawnChance int32
var MinimumFood int32
var HazardDamagePerTurn int32
var ShrinkEveryNTurns int32
2020-12-10 17:35:52 -05:00
var playCmd = & cobra . Command {
Use : "play" ,
2020-12-10 15:02:29 -08:00
Short : "Play a game of Battlesnake locally." ,
Long : "Play a game of Battlesnake locally." ,
Run : run ,
2020-12-10 17:35:52 -05:00
}
func init ( ) {
rootCmd . AddCommand ( playCmd )
playCmd . Flags ( ) . Int32VarP ( & Width , "width" , "W" , 11 , "Width of Board" )
playCmd . Flags ( ) . Int32VarP ( & Height , "height" , "H" , 11 , "Height of Board" )
playCmd . Flags ( ) . StringArrayVarP ( & Names , "name" , "n" , nil , "Name of Snake" )
playCmd . Flags ( ) . StringArrayVarP ( & URLs , "url" , "u" , nil , "URL of Snake" )
playCmd . Flags ( ) . StringArrayVarP ( & Names , "squad" , "S" , nil , "Squad of Snake" )
playCmd . Flags ( ) . Int32VarP ( & Timeout , "timeout" , "t" , 500 , "Request Timeout" )
playCmd . Flags ( ) . BoolVarP ( & Sequential , "sequential" , "s" , false , "Use Sequential Processing" )
playCmd . Flags ( ) . StringVarP ( & GameType , "gametype" , "g" , "standard" , "Type of Game Rules" )
playCmd . Flags ( ) . BoolVarP ( & ViewMap , "viewmap" , "v" , false , "View the Map Each Turn" )
2020-12-10 15:56:22 -08:00
playCmd . Flags ( ) . Int64VarP ( & Seed , "seed" , "r" , time . Now ( ) . UTC ( ) . UnixNano ( ) , "Random Seed" )
2021-07-04 13:48:56 -07:00
playCmd . Flags ( ) . Int32VarP ( & TurnDelay , "delay" , "d" , 0 , "Turn Delay in Milliseconds" )
2021-09-07 14:58:10 -07:00
playCmd . Flags ( ) . BoolVar ( & DebugRequests , "debug-requests" , false , "Log body of all requests sent" )
2021-12-02 18:59:20 +01:00
playCmd . Flags ( ) . StringVarP ( & Output , "output" , "o" , "" , "File path to output game state to. Existing files will be overwritten" )
2021-09-02 15:32:46 -07:00
playCmd . Flags ( ) . Int32Var ( & FoodSpawnChance , "foodSpawnChance" , 15 , "Percentage chance of spawning a new food every round" )
playCmd . Flags ( ) . Int32Var ( & MinimumFood , "minimumFood" , 1 , "Minimum food to keep on the board every turn" )
playCmd . Flags ( ) . Int32Var ( & HazardDamagePerTurn , "hazardDamagePerTurn" , 14 , "Health damage a snake will take when ending its turn in a hazard" )
playCmd . Flags ( ) . Int32Var ( & ShrinkEveryNTurns , "shrinkEveryNTurns" , 25 , "In Royale mode, the number of turns between generating new hazards" )
playCmd . Flags ( ) . SortFlags = false
2020-12-10 17:35:52 -05:00
}
var run = func ( cmd * cobra . Command , args [ ] string ) {
2020-12-10 15:56:22 -08:00
rand . Seed ( Seed )
2020-12-10 17:35:52 -05:00
GameId = uuid . New ( ) . String ( )
Turn = 0
2021-11-25 14:07:56 -08:00
snakeStates := buildSnakesFromOptions ( )
2020-12-10 17:35:52 -05:00
2021-11-25 14:07:56 -08:00
ruleset := getRuleset ( Seed , snakeStates )
state := initializeBoardFromArgs ( ruleset , snakeStates )
2021-12-02 18:59:20 +01:00
exportGame := Output != ""
gameExporter := GameExporter {
game : createClientGame ( ruleset ) ,
snakeRequests : make ( [ ] client . SnakeRequest , 0 ) ,
winner : SnakeState { } ,
isDraw : false ,
}
2020-12-10 17:35:52 -05:00
for v := false ; ! v ; v , _ = ruleset . IsGameOver ( state ) {
Turn ++
2021-11-25 14:07:56 -08:00
state = createNextBoardState ( ruleset , state , snakeStates , Turn )
2021-07-04 13:41:44 -07:00
2020-12-10 17:35:52 -05:00
if ViewMap {
2021-11-25 14:07:56 -08:00
printMap ( state , snakeStates , Turn )
2020-12-10 17:35:52 -05:00
} else {
2021-08-17 16:47:06 -07:00
log . Printf ( "[%v]: State: %v\n" , Turn , state )
2020-12-10 17:35:52 -05:00
}
2021-07-04 13:48:56 -07:00
if TurnDelay > 0 {
time . Sleep ( time . Duration ( TurnDelay ) * time . Millisecond )
}
2021-12-02 18:59:20 +01:00
if exportGame {
// The output file was designed in a way so that (nearly) every entry is equivalent to a valid API request.
// This is meant to help unlock further development of tools such as replaying a saved game by simply copying each line and sending it as a POST request.
// There was a design choice to be made here: the difference between SnakeRequest and BoardState is the `you` key.
// We could choose to either store the SnakeRequest of each snake OR to omit the `you` key OR fill the `you` key with one of the snakes
// In all cases the API request is technically non-compliant with how the actual API request should be.
// The third option (filling the `you` key with an arbitrary snake) is the closest to the actual API request that would need the least manipulation to
// be adjusted to look like an API call for a specific snake in the game.
snakeState := snakeStates [ state . Snakes [ 0 ] . ID ]
snakeRequest := getIndividualBoardStateForSnake ( state , snakeState , snakeStates , ruleset )
gameExporter . AddSnakeRequest ( snakeRequest )
}
2020-12-10 17:35:52 -05:00
}
2021-12-02 18:59:20 +01:00
isDraw := true
2020-12-10 15:56:22 -08:00
if GameType == "solo" {
log . Printf ( "[DONE]: Game completed after %v turns." , Turn )
2021-12-02 18:59:20 +01:00
if exportGame {
// These checks for exportGame are present to avoid vacuuming up RAM when an export is not requred.
gameExporter . winner = snakeStates [ state . Snakes [ 0 ] . ID ]
}
2020-12-10 15:56:22 -08:00
} else {
2021-12-02 18:59:20 +01:00
var winner SnakeState
2020-12-10 15:56:22 -08:00
for _ , snake := range state . Snakes {
2021-12-02 18:59:20 +01:00
snakeState := snakeStates [ snake . ID ]
2020-12-10 15:56:22 -08:00
if snake . EliminatedCause == rules . NotEliminated {
isDraw = false
2021-12-02 18:59:20 +01:00
winner = snakeState
2020-12-10 15:56:22 -08:00
}
2021-12-02 18:59:20 +01:00
sendEndRequest ( ruleset , state , snakeState , snakeStates )
2020-12-10 17:35:52 -05:00
}
2020-12-10 15:56:22 -08:00
if isDraw {
log . Printf ( "[DONE]: Game completed after %v turns. It was a draw." , Turn )
} else {
2021-12-02 18:59:20 +01:00
log . Printf ( "[DONE]: Game completed after %v turns. %v is the winner." , Turn , winner . Name )
}
if exportGame {
gameExporter . winner = winner
}
}
if exportGame {
err := gameExporter . FlushToFile ( Output , "JSONL" )
if err != nil {
log . Printf ( "[WARN]: Unable to export game. Reason: %v\n" , err . Error ( ) )
os . Exit ( 1 )
2020-12-10 15:56:22 -08:00
}
2020-12-10 17:35:52 -05:00
}
}
2021-11-25 14:07:56 -08:00
func getRuleset ( seed int64 , snakeStates map [ string ] SnakeState ) rules . Ruleset {
2020-12-10 17:35:52 -05:00
var ruleset rules . Ruleset
var royale rules . RoyaleRuleset
2020-12-10 15:56:22 -08:00
standard := rules . StandardRuleset {
2021-09-02 15:32:46 -07:00
FoodSpawnChance : FoodSpawnChance ,
MinimumFood : MinimumFood ,
HazardDamagePerTurn : 0 ,
2020-12-10 15:56:22 -08:00
}
2020-12-10 17:35:52 -05:00
switch GameType {
case "royale" :
2021-09-02 15:32:46 -07:00
standard . HazardDamagePerTurn = HazardDamagePerTurn
2020-12-10 17:35:52 -05:00
royale = rules . RoyaleRuleset {
2020-12-10 15:56:22 -08:00
StandardRuleset : standard ,
2020-12-10 17:35:52 -05:00
Seed : seed ,
2021-09-02 15:32:46 -07:00
ShrinkEveryNTurns : ShrinkEveryNTurns ,
2020-12-10 17:35:52 -05:00
}
ruleset = & royale
case "squad" :
squadMap := map [ string ] string { }
2021-11-25 14:07:56 -08:00
for _ , snakeState := range snakeStates {
squadMap [ snakeState . ID ] = snakeState . Squad
2020-12-10 17:35:52 -05:00
}
ruleset = & rules . SquadRuleset {
2020-12-10 15:56:22 -08:00
StandardRuleset : standard ,
2020-12-10 17:35:52 -05:00
SquadMap : squadMap ,
AllowBodyCollisions : true ,
SharedElimination : true ,
SharedHealth : true ,
SharedLength : true ,
}
case "solo" :
2020-12-10 15:56:22 -08:00
ruleset = & rules . SoloRuleset {
StandardRuleset : standard ,
}
2021-08-24 12:11:36 -07:00
case "wrapped" :
ruleset = & rules . WrappedRuleset {
StandardRuleset : standard ,
}
2021-01-19 15:02:11 -08:00
case "constrictor" :
ruleset = & rules . ConstrictorRuleset {
2020-12-10 15:56:22 -08:00
StandardRuleset : standard ,
}
2020-12-10 17:35:52 -05:00
default :
2020-12-10 15:56:22 -08:00
ruleset = & standard
2020-12-10 17:35:52 -05:00
}
2021-07-02 20:00:19 -07:00
return ruleset
2020-12-10 17:35:52 -05:00
}
2021-11-25 14:07:56 -08:00
func initializeBoardFromArgs ( ruleset rules . Ruleset , snakeStates map [ string ] SnakeState ) * rules . BoardState {
2020-12-10 17:35:52 -05:00
if Timeout == 0 {
Timeout = 500
}
HttpClient = http . Client {
Timeout : time . Duration ( Timeout ) * time . Millisecond ,
}
snakeIds := [ ] string { }
2021-11-25 14:07:56 -08:00
for _ , snakeState := range snakeStates {
snakeIds = append ( snakeIds , snakeState . ID )
2020-12-10 17:35:52 -05:00
}
2021-08-23 17:13:58 -07:00
state , err := rules . CreateDefaultBoardState ( Width , Height , snakeIds )
2020-12-10 17:35:52 -05:00
if err != nil {
log . Panic ( "[PANIC]: Error Initializing Board State" )
}
2021-08-23 17:13:58 -07:00
state , err = ruleset . ModifyInitialBoardState ( state )
if err != nil {
log . Panic ( "[PANIC]: Error Initializing Board State" )
}
2021-11-25 14:07:56 -08:00
for _ , snakeState := range snakeStates {
2021-12-02 18:59:20 +01:00
snakeRequest := getIndividualBoardStateForSnake ( state , snakeState , snakeStates , ruleset )
requestBody := serialiseSnakeRequest ( snakeRequest )
2021-11-25 14:07:56 -08:00
u , _ := url . ParseRequestURI ( snakeState . URL )
2020-12-10 17:35:52 -05:00
u . Path = path . Join ( u . Path , "start" )
2021-09-07 14:58:10 -07:00
if DebugRequests {
log . Printf ( "POST %s: %v" , u , string ( requestBody ) )
}
2020-12-10 17:35:52 -05:00
_ , err = HttpClient . Post ( u . String ( ) , "application/json" , bytes . NewBuffer ( requestBody ) )
if err != nil {
log . Printf ( "[WARN]: Request to %v failed" , u . String ( ) )
}
}
return state
}
2021-11-25 14:07:56 -08:00
func createNextBoardState ( ruleset rules . Ruleset , state * rules . BoardState , snakeStates map [ string ] SnakeState , turn int32 ) * rules . BoardState {
2020-12-10 17:35:52 -05:00
var moves [ ] rules . SnakeMove
if Sequential {
2021-11-25 14:07:56 -08:00
for _ , snakeState := range snakeStates {
for _ , snake := range state . Snakes {
if snakeState . ID == snake . ID && snake . EliminatedCause == rules . NotEliminated {
moves = append ( moves , getMoveForSnake ( ruleset , state , snakeState , snakeStates ) )
2021-07-02 20:36:37 -07:00
}
}
2020-12-10 17:35:52 -05:00
}
} else {
2021-07-02 20:36:37 -07:00
var wg sync . WaitGroup
2021-11-25 14:07:56 -08:00
c := make ( chan rules . SnakeMove , len ( snakeStates ) )
2021-07-02 20:36:37 -07:00
2021-11-25 14:07:56 -08:00
for _ , snakeState := range snakeStates {
for _ , snake := range state . Snakes {
if snakeState . ID == snake . ID && snake . EliminatedCause == rules . NotEliminated {
2021-07-02 20:36:37 -07:00
wg . Add ( 1 )
2021-11-25 14:07:56 -08:00
go func ( snakeState SnakeState ) {
defer wg . Done ( )
c <- getMoveForSnake ( ruleset , state , snakeState , snakeStates )
} ( snakeState )
2021-07-02 20:36:37 -07:00
}
}
2020-12-10 17:35:52 -05:00
}
2021-07-02 20:36:37 -07:00
wg . Wait ( )
close ( c )
for move := range c {
moves = append ( moves , move )
2020-12-10 17:35:52 -05:00
}
}
for _ , move := range moves {
2021-11-25 14:07:56 -08:00
snakeState := snakeStates [ move . ID ]
snakeState . LastMove = move . Move
snakeStates [ move . ID ] = snakeState
2020-12-10 17:35:52 -05:00
}
state , err := ruleset . CreateNextBoardState ( state , moves )
if err != nil {
2021-11-25 14:07:56 -08:00
log . Panicf ( "[PANIC]: Error Producing Next Board State: %v" , err )
2020-12-10 17:35:52 -05:00
}
2021-08-27 13:28:12 -07:00
state . Turn = turn
2021-07-02 20:00:19 -07:00
return state
2020-12-10 17:35:52 -05:00
}
2021-11-25 14:07:56 -08:00
func getMoveForSnake ( ruleset rules . Ruleset , state * rules . BoardState , snakeState SnakeState , snakeStates map [ string ] SnakeState ) rules . SnakeMove {
2021-12-02 18:59:20 +01:00
snakeRequest := getIndividualBoardStateForSnake ( state , snakeState , snakeStates , ruleset )
requestBody := serialiseSnakeRequest ( snakeRequest )
2021-11-25 14:07:56 -08:00
u , _ := url . ParseRequestURI ( snakeState . URL )
2020-12-10 17:35:52 -05:00
u . Path = path . Join ( u . Path , "move" )
2021-09-07 14:58:10 -07:00
if DebugRequests {
log . Printf ( "POST %s: %v" , u , string ( requestBody ) )
}
2020-12-10 17:35:52 -05:00
res , err := HttpClient . Post ( u . String ( ) , "application/json" , bytes . NewBuffer ( requestBody ) )
2021-11-25 14:07:56 -08:00
move := snakeState . LastMove
2020-12-10 17:35:52 -05:00
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 {
2021-11-25 14:07:56 -08:00
playerResponse := client . MoveResponse { }
2020-12-10 17:35:52 -05:00
jsonErr := json . Unmarshal ( body , & playerResponse )
if jsonErr != nil {
log . Fatal ( jsonErr )
} else {
move = playerResponse . Move
}
}
}
2021-11-25 14:07:56 -08:00
return rules . SnakeMove { ID : snakeState . ID , Move : move }
2020-12-10 17:35:52 -05:00
}
2021-11-25 14:07:56 -08:00
func sendEndRequest ( ruleset rules . Ruleset , state * rules . BoardState , snakeState SnakeState , snakeStates map [ string ] SnakeState ) {
2021-12-02 18:59:20 +01:00
snakeRequest := getIndividualBoardStateForSnake ( state , snakeState , snakeStates , ruleset )
requestBody := serialiseSnakeRequest ( snakeRequest )
2021-11-25 14:07:56 -08:00
u , _ := url . ParseRequestURI ( snakeState . URL )
2020-12-10 17:35:52 -05:00
u . Path = path . Join ( u . Path , "end" )
2021-09-07 14:58:10 -07:00
if DebugRequests {
log . Printf ( "POST %s: %v" , u , string ( requestBody ) )
}
2020-12-10 17:35:52 -05:00
_ , err := HttpClient . Post ( u . String ( ) , "application/json" , bytes . NewBuffer ( requestBody ) )
if err != nil {
log . Printf ( "[WARN]: Request to %v failed" , u . String ( ) )
}
}
2021-12-02 18:59:20 +01:00
func getIndividualBoardStateForSnake ( state * rules . BoardState , snakeState SnakeState , snakeStates map [ string ] SnakeState , ruleset rules . Ruleset ) client . SnakeRequest {
2020-12-10 17:35:52 -05:00
var youSnake rules . Snake
for _ , snk := range state . Snakes {
2021-11-25 14:07:56 -08:00
if snakeState . ID == snk . ID {
2020-12-10 17:35:52 -05:00
youSnake = snk
break
}
}
2021-11-25 14:07:56 -08:00
request := client . SnakeRequest {
2021-12-02 18:59:20 +01:00
Game : createClientGame ( ruleset ) ,
Turn : Turn ,
Board : convertStateToBoard ( state , snakeStates ) ,
You : convertRulesSnake ( youSnake , snakeStates [ youSnake . ID ] ) ,
2020-12-10 17:35:52 -05:00
}
2021-12-02 18:59:20 +01:00
return request
}
func serialiseSnakeRequest ( snakeRequest client . SnakeRequest ) [ ] byte {
requestJSON , err := json . Marshal ( snakeRequest )
2020-12-10 17:35:52 -05:00
if err != nil {
log . Panic ( "[PANIC]: Error Marshalling JSON from State" )
panic ( err )
}
2021-11-25 14:07:56 -08:00
return requestJSON
2020-12-10 17:35:52 -05:00
}
2021-12-02 18:59:20 +01:00
func createClientGame ( ruleset rules . Ruleset ) client . Game {
return client . Game { ID : GameId , Timeout : Timeout , Ruleset : client . Ruleset {
Name : ruleset . Name ( ) ,
Version : "cli" , // TODO: Use GitHub Release Version
Settings : client . RulesetSettings {
HazardDamagePerTurn : HazardDamagePerTurn ,
FoodSpawnChance : FoodSpawnChance ,
MinimumFood : MinimumFood ,
RoyaleSettings : client . RoyaleSettings {
ShrinkEveryNTurns : ShrinkEveryNTurns ,
} ,
SquadSettings : client . SquadSettings {
AllowBodyCollisions : true ,
SharedElimination : true ,
SharedHealth : true ,
SharedLength : true ,
} ,
} ,
} }
}
2021-11-25 14:07:56 -08:00
func convertRulesSnake ( snake rules . Snake , snakeState SnakeState ) client . Snake {
return client . Snake {
ID : snake . ID ,
Name : snakeState . Name ,
2020-12-10 17:35:52 -05:00
Health : snake . Health ,
2021-11-25 14:07:56 -08:00
Body : client . CoordFromPointArray ( snake . Body ) ,
2021-06-21 17:25:31 -04:00
Latency : "0" ,
2021-11-25 14:07:56 -08:00
Head : client . CoordFromPoint ( snake . Body [ 0 ] ) ,
2020-12-10 17:35:52 -05:00
Length : int32 ( len ( snake . Body ) ) ,
Shout : "" ,
2021-11-25 14:07:56 -08:00
Squad : snakeState . Squad ,
Customizations : client . Customizations {
Head : snakeState . Head ,
Tail : snakeState . Tail ,
Color : snakeState . Color ,
} ,
2020-12-10 17:35:52 -05:00
}
}
2021-11-25 14:07:56 -08:00
func convertRulesSnakes ( snakes [ ] rules . Snake , snakeStates map [ string ] SnakeState ) [ ] client . Snake {
var a [ ] client . Snake
2020-12-10 17:35:52 -05:00
for _ , snake := range snakes {
2021-07-17 16:18:43 -04:00
if snake . EliminatedCause == rules . NotEliminated {
2021-11-25 14:07:56 -08:00
a = append ( a , convertRulesSnake ( snake , snakeStates [ snake . ID ] ) )
2021-07-17 16:18:43 -04:00
}
2020-12-10 17:35:52 -05:00
}
return a
}
2021-12-02 18:59:20 +01:00
func convertStateToBoard ( state * rules . BoardState , snakeStates map [ string ] SnakeState ) client . Board {
return client . Board {
Height : state . Height ,
Width : state . Width ,
Food : client . CoordFromPointArray ( state . Food ) ,
Hazards : client . CoordFromPointArray ( state . Hazards ) ,
Snakes : convertRulesSnakes ( state . Snakes , snakeStates ) ,
}
}
2021-11-25 14:07:56 -08:00
func buildSnakesFromOptions ( ) map [ string ] SnakeState {
2020-12-10 17:35:52 -05:00
bodyChars := [ ] rune { '■' , '⌀' , '●' , '⍟' , '◘' , '☺' , '□' , '☻' }
var numSnakes int
2021-11-25 14:07:56 -08:00
snakes := map [ string ] SnakeState { }
2020-12-10 17:35:52 -05:00
numNames := len ( Names )
numURLs := len ( URLs )
numSquads := len ( Squads )
if numNames > numURLs {
numSnakes = numNames
} else {
numSnakes = numURLs
}
if numNames != numURLs {
log . Println ( "[WARN]: Number of Names and URLs do not match: defaults will be applied to missing values" )
}
for i := int ( 0 ) ; i < numSnakes ; i ++ {
var snakeName string
var snakeURL string
var snakeSquad string
id := uuid . New ( ) . String ( )
if i < numNames {
snakeName = Names [ i ]
} else {
log . Printf ( "[WARN]: Name for URL %v is missing: a default name will be applied\n" , URLs [ i ] )
snakeName = id
}
if i < numURLs {
u , err := url . ParseRequestURI ( URLs [ i ] )
if err != nil {
log . Printf ( "[WARN]: URL %v is not valid: a default will be applied\n" , URLs [ i ] )
snakeURL = "https://example.com"
} else {
snakeURL = u . String ( )
}
} else {
log . Printf ( "[WARN]: URL for Name %v is missing: a default URL will be applied\n" , Names [ i ] )
snakeURL = "https://example.com"
}
if GameType == "squad" {
if i < numSquads {
snakeSquad = Squads [ i ]
} else {
log . Printf ( "[WARN]: Squad for URL %v is missing: a default squad will be applied\n" , URLs [ i ] )
snakeSquad = strconv . Itoa ( i / 2 )
}
}
2021-11-25 14:07:56 -08:00
snakeState := SnakeState {
Name : snakeName , URL : snakeURL , ID : id , LastMove : "up" , Character : bodyChars [ i % 8 ] ,
}
2020-12-10 17:35:52 -05:00
res , err := HttpClient . Get ( snakeURL )
if err != nil {
2021-11-25 14:07:56 -08:00
log . Printf ( "[WARN]: Request to %v failed: %v" , snakeURL , err )
2020-12-10 17:35:52 -05:00
} else if res . Body != nil {
defer res . Body . Close ( )
body , readErr := ioutil . ReadAll ( res . Body )
if readErr != nil {
log . Fatal ( readErr )
}
2021-11-25 14:07:56 -08:00
pingResponse := client . SnakeMetadataResponse { }
2020-12-10 17:35:52 -05:00
jsonErr := json . Unmarshal ( body , & pingResponse )
if jsonErr != nil {
2021-11-25 14:07:56 -08:00
log . Printf ( "Error reading response from %v: %v" , snakeURL , jsonErr )
2020-12-10 17:35:52 -05:00
} else {
2021-11-25 14:07:56 -08:00
snakeState . Head = pingResponse . Head
snakeState . Tail = pingResponse . Tail
snakeState . Color = pingResponse . Color
2020-12-10 17:35:52 -05:00
}
}
if GameType == "squad" {
2021-11-25 14:07:56 -08:00
snakeState . Squad = snakeSquad
2020-12-10 17:35:52 -05:00
}
2021-11-25 14:07:56 -08:00
snakes [ snakeState . ID ] = snakeState
2020-12-10 17:35:52 -05:00
}
return snakes
}
2021-11-25 14:07:56 -08:00
func printMap ( state * rules . BoardState , snakeStates map [ string ] SnakeState , gameTurn int32 ) {
2020-12-10 17:35:52 -05:00
var o bytes . Buffer
2020-12-10 15:56:22 -08:00
o . WriteString ( fmt . Sprintf ( "Ruleset: %s, Seed: %d, Turn: %v\n" , GameType , Seed , gameTurn ) )
2020-12-10 17:35:52 -05:00
board := make ( [ ] [ ] rune , state . Width )
for i := range board {
board [ i ] = make ( [ ] rune , state . Height )
}
for y := int32 ( 0 ) ; y < state . Height ; y ++ {
for x := int32 ( 0 ) ; x < state . Width ; x ++ {
board [ x ] [ y ] = '◦'
}
}
2021-08-17 16:47:06 -07:00
for _ , oob := range state . Hazards {
2020-12-10 17:35:52 -05:00
board [ oob . X ] [ oob . Y ] = '░'
}
2021-08-17 16:47:06 -07:00
o . WriteString ( fmt . Sprintf ( "Hazards ░: %v\n" , state . Hazards ) )
2020-12-10 17:35:52 -05:00
for _ , f := range state . Food {
board [ f . X ] [ f . Y ] = '⚕'
}
o . WriteString ( fmt . Sprintf ( "Food ⚕: %v\n" , state . Food ) )
for _ , s := range state . Snakes {
for _ , b := range s . Body {
2021-07-03 06:42:16 +03:00
if b . X >= 0 && b . X < state . Width && b . Y >= 0 && b . Y < state . Height {
2021-11-25 14:07:56 -08:00
board [ b . X ] [ b . Y ] = snakeStates [ s . ID ] . Character
2021-07-03 06:42:16 +03:00
}
2020-12-10 17:35:52 -05:00
}
2021-11-25 14:07:56 -08:00
o . WriteString ( fmt . Sprintf ( "%v %c: %v\n" , snakeStates [ s . ID ] . Name , snakeStates [ s . ID ] . Character , s ) )
2020-12-10 17:35:52 -05:00
}
for y := state . Height - 1 ; y >= 0 ; y -- {
for x := int32 ( 0 ) ; x < state . Width ; x ++ {
o . WriteRune ( board [ x ] [ y ] )
}
o . WriteString ( "\n" )
}
log . Print ( o . String ( ) )
}