DEV 1666: Fix /end requests and clean up logging (#109)
* ensure /end request is always called, and refactor win/draw logic * clean up logging and error handling during initialization * automatically generate friendly snake names * title-case snake names * print out list of alive snake names instead of count * log snake names, IDs, and URLs at startup * print out state for turn zero
This commit is contained in:
parent
006f394355
commit
09aea9c49d
10 changed files with 317 additions and 151 deletions
|
|
@ -2,10 +2,11 @@ package commands
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
log "github.com/spf13/jwalterweatherman"
|
||||
|
||||
"github.com/BattlesnakeOfficial/rules/maps"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type mapInfo struct {
|
||||
|
|
@ -35,7 +36,7 @@ func NewMapInfoCommand() *cobra.Command {
|
|||
if len(args) < 1 {
|
||||
err := cmd.Help()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.ERROR.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -59,7 +60,7 @@ func NewMapInfoCommand() *cobra.Command {
|
|||
func (m *mapInfo) display(id string) {
|
||||
gameMap, err := maps.GetMap(id)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load game map %#v: %v", id, err)
|
||||
log.ERROR.Fatalf("Failed to load game map %v: %v", id, err)
|
||||
}
|
||||
meta := gameMap.Meta()
|
||||
fmt.Println("Name:", meta.Name)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
log "github.com/spf13/jwalterweatherman"
|
||||
)
|
||||
|
||||
func NewMapCommand() *cobra.Command {
|
||||
|
|
@ -15,7 +14,7 @@ func NewMapCommand() *cobra.Command {
|
|||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := cmd.Help()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.ERROR.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
|||
153
cli/commands/names.go
Normal file
153
cli/commands/names.go
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
// This file uses material from the Wikipedia article <a href="https://en.wikipedia.org/wiki/List_of_snakes_by_common_name">"List of snakes by common name"</a>, which is released under the <a href="https://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-Share-Alike License 3.0</a>.
|
||||
package commands
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var snakeNames = []string{
|
||||
"Adder",
|
||||
"Aesculapian Snake",
|
||||
"Anaconda",
|
||||
"Arafura File Snake",
|
||||
"Asp",
|
||||
"African Beaked Snake",
|
||||
"Ball Python",
|
||||
"Bird Snake",
|
||||
"Black-headed Snake",
|
||||
"Mexican Black Kingsnake",
|
||||
"Black Rat Snake",
|
||||
"Black Snake",
|
||||
"Blind Snake",
|
||||
"Boa",
|
||||
"Boiga",
|
||||
"Boomslang",
|
||||
"Brown Snake",
|
||||
"Bull Snake",
|
||||
"Bushmaster",
|
||||
"Dwarf Beaked Snake",
|
||||
"Rufous Beaked Snake",
|
||||
"Canebrake",
|
||||
"Cantil",
|
||||
"Cascabel",
|
||||
"Cat-eyed Snake",
|
||||
"Cat Snake",
|
||||
"Chicken Snake",
|
||||
"Coachwhip Snake",
|
||||
"Cobra",
|
||||
"Collett's Snake",
|
||||
"Congo Snake",
|
||||
"Copperhead",
|
||||
"Coral Snake",
|
||||
"Corn Snake",
|
||||
"Cottonmouth",
|
||||
"Crowned Snake",
|
||||
"Cuban Wood Snake",
|
||||
"Egg-eater",
|
||||
"Eyelash Viper",
|
||||
"Fer-de-lance",
|
||||
"Fierce Snake",
|
||||
"Fishing Snake",
|
||||
"Flying Snake",
|
||||
"Fox Snake",
|
||||
"Forest Flame Snake",
|
||||
"Garter Snake",
|
||||
"Glossy Snake",
|
||||
"Gopher Snake",
|
||||
"Grass Snake",
|
||||
"Green Snake",
|
||||
"Ground Snake",
|
||||
"Habu",
|
||||
"Harlequin Snake",
|
||||
"Herald Snake",
|
||||
"Hognose Snake",
|
||||
"Hoop Snake",
|
||||
"Hundred Pacer",
|
||||
"Ikaheka Snake",
|
||||
"Indigo Snake",
|
||||
"Jamaican Tree Snake",
|
||||
"Jararacussu",
|
||||
"Keelback",
|
||||
"King Brown",
|
||||
"King Cobra",
|
||||
"King Snake",
|
||||
"Krait",
|
||||
"Lancehead",
|
||||
"Lora",
|
||||
"Lyre Snake",
|
||||
"Machete Savane",
|
||||
"Mamba",
|
||||
"Mamushi",
|
||||
"Mangrove Snake",
|
||||
"Milk Snake",
|
||||
"Moccasin Snake",
|
||||
"Montpellier Snake",
|
||||
"Mud Snake",
|
||||
"Mussurana",
|
||||
"Night Snake",
|
||||
"Nose-horned Viper",
|
||||
"Parrot Snake",
|
||||
"Patchnose Snake",
|
||||
"Pine Snake",
|
||||
"Pipe Snake",
|
||||
"Python",
|
||||
"Queen Snake",
|
||||
"Racer",
|
||||
"Raddysnake",
|
||||
"Rat Snake",
|
||||
"Rattlesnake",
|
||||
"Ribbon Snake",
|
||||
"Rinkhals",
|
||||
"River Jack",
|
||||
"Sea Snake",
|
||||
"Shield-tailed Snake",
|
||||
"Sidewinder",
|
||||
"Small-eyed Snake",
|
||||
"Stiletto Snake",
|
||||
"Striped Snake",
|
||||
"Sunbeam Snake",
|
||||
"Taipan",
|
||||
"Tentacled Snake",
|
||||
"Tic Polonga",
|
||||
"Tiger Snake",
|
||||
"Tigre Snake",
|
||||
"Tree Snake",
|
||||
"Trinket Snake",
|
||||
"Twig Snake",
|
||||
"Twin Headed King Snake",
|
||||
"Titanoboa",
|
||||
"Urutu",
|
||||
"Vine Snake",
|
||||
"Viper",
|
||||
"Wart Snake",
|
||||
"Water Moccasin",
|
||||
"Water Snake",
|
||||
"Whip Snake",
|
||||
"Wolf Snake",
|
||||
"Worm Snake",
|
||||
"Wutu",
|
||||
"Yarara",
|
||||
"Zebra Snake",
|
||||
}
|
||||
|
||||
func init() {
|
||||
randGen := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
randGen.Shuffle(len(snakeNames), func(i, j int) {
|
||||
snakeNames[i], snakeNames[j] = snakeNames[j], snakeNames[i]
|
||||
})
|
||||
}
|
||||
|
||||
// Generate a random unique snake name, or return a UUID if there are no more names available.
|
||||
func GenerateSnakeName() string {
|
||||
if len(snakeNames) == 0 {
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
name := snakeNames[0]
|
||||
snakeNames = snakeNames[1:]
|
||||
|
||||
return name
|
||||
}
|
||||
|
|
@ -3,9 +3,10 @@ package commands
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
log "github.com/spf13/jwalterweatherman"
|
||||
|
||||
"github.com/BattlesnakeOfficial/rules/client"
|
||||
)
|
||||
|
||||
|
|
@ -30,7 +31,7 @@ func (ge *GameExporter) FlushToFile(filepath string, format string) error {
|
|||
if format == "JSONL" {
|
||||
formattedOutput, formattingErr = ge.ConvertToJSON()
|
||||
} else {
|
||||
log.Fatalf("Invalid output format passed: %s", format)
|
||||
log.ERROR.Fatalf("Invalid output format passed: %s", format)
|
||||
}
|
||||
|
||||
if formattingErr != nil {
|
||||
|
|
@ -50,7 +51,7 @@ func (ge *GameExporter) FlushToFile(filepath string, format string) error {
|
|||
}
|
||||
}
|
||||
|
||||
log.Printf("Written %d lines of output to file: %s\n", len(formattedOutput), filepath)
|
||||
log.DEBUG.Printf("Written %d lines of output to file: %s\n", len(formattedOutput), filepath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,12 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -22,6 +21,7 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/pkg/browser"
|
||||
"github.com/spf13/cobra"
|
||||
log "github.com/spf13/jwalterweatherman"
|
||||
)
|
||||
|
||||
// Used to store state for each SnakeState while running a local game
|
||||
|
|
@ -55,7 +55,6 @@ type GameState struct {
|
|||
UseColor bool
|
||||
Seed int64
|
||||
TurnDelay int
|
||||
DebugRequests bool
|
||||
Output string
|
||||
ViewInBrowser bool
|
||||
BoardURL string
|
||||
|
|
@ -98,7 +97,6 @@ func NewPlayCommand() *cobra.Command {
|
|||
playCmd.Flags().Int64VarP(&gameState.Seed, "seed", "r", time.Now().UTC().UnixNano(), "Random Seed")
|
||||
playCmd.Flags().IntVarP(&gameState.TurnDelay, "delay", "d", 0, "Turn Delay in Milliseconds")
|
||||
playCmd.Flags().IntVarP(&gameState.TurnDuration, "duration", "D", 0, "Minimum Turn Duration in Milliseconds")
|
||||
playCmd.Flags().BoolVar(&gameState.DebugRequests, "debug-requests", false, "Log body of all requests sent")
|
||||
playCmd.Flags().StringVarP(&gameState.Output, "output", "o", "", "File path to output game state to. Existing files will be overwritten")
|
||||
playCmd.Flags().BoolVar(&gameState.ViewInBrowser, "browser", false, "View the game in the browser using the Battlesnake game board")
|
||||
playCmd.Flags().StringVar(&gameState.BoardURL, "board-url", "https://board.battlesnake.com", "Base URL for the game board when using --browser")
|
||||
|
|
@ -129,7 +127,7 @@ func (gameState *GameState) initialize() {
|
|||
// Load game map
|
||||
gameMap, err := maps.GetMap(gameState.MapName)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load game map %#v: %v", gameState.MapName, err)
|
||||
log.ERROR.Fatalf("Failed to load game map %#v: %v", gameState.MapName, err)
|
||||
}
|
||||
gameState.gameMap = gameMap
|
||||
|
||||
|
|
@ -173,10 +171,6 @@ func (gameState *GameState) Run() {
|
|||
isDraw: false,
|
||||
}
|
||||
|
||||
if gameState.ViewMap {
|
||||
gameState.printMap(boardState)
|
||||
}
|
||||
|
||||
boardGame := board.Game{
|
||||
ID: gameState.gameID,
|
||||
Status: "running",
|
||||
|
|
@ -194,23 +188,30 @@ func (gameState *GameState) Run() {
|
|||
if gameState.ViewInBrowser {
|
||||
serverURL, err := boardServer.Listen()
|
||||
if err != nil {
|
||||
log.Fatalf("Error starting HTTP server: %v", err)
|
||||
log.ERROR.Fatalf("Error starting HTTP server: %v", err)
|
||||
}
|
||||
log.Printf("Board server listening on %s", serverURL)
|
||||
defer boardServer.Shutdown()
|
||||
log.INFO.Printf("Board server listening on %s", serverURL)
|
||||
|
||||
boardURL := fmt.Sprintf(gameState.BoardURL+"?engine=%s&game=%s&autoplay=true", serverURL, gameState.gameID)
|
||||
|
||||
log.Printf("Opening board URL: %s", boardURL)
|
||||
log.INFO.Printf("Opening board URL: %s", boardURL)
|
||||
if err := browser.OpenURL(boardURL); err != nil {
|
||||
log.Printf("Failed to open browser: %v", err)
|
||||
log.ERROR.Printf("Failed to open browser: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if gameState.ViewInBrowser {
|
||||
// send turn zero to websocket server
|
||||
boardServer.SendEvent(gameState.buildFrameEvent(boardState))
|
||||
}
|
||||
|
||||
log.INFO.Printf("Ruleset: %v, Seed: %v", gameState.GameType, gameState.Seed)
|
||||
|
||||
if gameState.ViewMap {
|
||||
gameState.printMap(boardState)
|
||||
} else {
|
||||
gameState.printState(boardState)
|
||||
}
|
||||
|
||||
var endTime time.Time
|
||||
for v := false; !v; v, _ = gameState.ruleset.IsGameOver(boardState) {
|
||||
if gameState.TurnDuration > 0 {
|
||||
|
|
@ -238,7 +239,7 @@ func (gameState *GameState) Run() {
|
|||
if gameState.ViewMap {
|
||||
gameState.printMap(boardState)
|
||||
} else {
|
||||
log.Printf("[%v]: State: %v\n", boardState.Turn, boardState)
|
||||
gameState.printState(boardState)
|
||||
}
|
||||
|
||||
if gameState.TurnDelay > 0 {
|
||||
|
|
@ -254,36 +255,29 @@ func (gameState *GameState) Run() {
|
|||
}
|
||||
}
|
||||
|
||||
isDraw := true
|
||||
if gameState.GameType == "solo" {
|
||||
log.Printf("[DONE]: Game completed after %v turns.", boardState.Turn)
|
||||
if exportGame {
|
||||
// These checks for exportGame are present to avoid vacuuming up RAM when an export is not requred.
|
||||
for _, snakeState := range gameState.snakeStates {
|
||||
gameExporter.winner = snakeState
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var winner SnakeState
|
||||
for _, snake := range boardState.Snakes {
|
||||
snakeState := gameState.snakeStates[snake.ID]
|
||||
if snake.EliminatedCause == rules.NotEliminated {
|
||||
isDraw = false
|
||||
winner = snakeState
|
||||
}
|
||||
gameState.sendEndRequest(boardState, snakeState)
|
||||
gameExporter.isDraw = false
|
||||
|
||||
if len(gameState.snakeStates) > 1 {
|
||||
// A draw is possible if there is more than one snake in the game.
|
||||
gameExporter.isDraw = true
|
||||
}
|
||||
|
||||
for _, snake := range boardState.Snakes {
|
||||
snakeState := gameState.snakeStates[snake.ID]
|
||||
if snake.EliminatedCause == rules.NotEliminated {
|
||||
gameExporter.isDraw = false
|
||||
gameExporter.winner = snakeState
|
||||
}
|
||||
|
||||
if isDraw {
|
||||
log.Printf("[DONE]: Game completed after %v turns. It was a draw.", boardState.Turn)
|
||||
} else {
|
||||
log.Printf("[DONE]: Game completed after %v turns. %v is the winner.", boardState.Turn, winner.Name)
|
||||
}
|
||||
if exportGame {
|
||||
gameExporter.winner = winner
|
||||
gameExporter.isDraw = isDraw
|
||||
}
|
||||
gameState.sendEndRequest(boardState, snakeState)
|
||||
}
|
||||
|
||||
if gameExporter.isDraw {
|
||||
log.INFO.Printf("Game completed after %v turns. It was a draw.", boardState.Turn)
|
||||
} else if gameExporter.winner.Name != "" {
|
||||
log.INFO.Printf("Game completed after %v turns. %v was the winner.", boardState.Turn, gameExporter.winner.Name)
|
||||
} else {
|
||||
log.INFO.Printf("Game completed after %v turns.", boardState.Turn)
|
||||
}
|
||||
|
||||
if gameState.ViewInBrowser {
|
||||
|
|
@ -296,14 +290,9 @@ func (gameState *GameState) Run() {
|
|||
if exportGame {
|
||||
err := gameExporter.FlushToFile(gameState.Output, "JSONL")
|
||||
if err != nil {
|
||||
log.Printf("[WARN]: Unable to export game. Reason: %v\n", err.Error())
|
||||
os.Exit(1)
|
||||
log.ERROR.Fatalf("Unable to export game. Reason: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if gameState.ViewInBrowser {
|
||||
boardServer.Shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
func (gameState *GameState) initializeBoardFromArgs() *rules.BoardState {
|
||||
|
|
@ -313,11 +302,11 @@ func (gameState *GameState) initializeBoardFromArgs() *rules.BoardState {
|
|||
}
|
||||
boardState, err := maps.SetupBoard(gameState.gameMap.ID(), gameState.ruleset.Settings(), gameState.Width, gameState.Height, snakeIds)
|
||||
if err != nil {
|
||||
log.Fatalf("Error Initializing Board State: %v", err)
|
||||
log.ERROR.Fatalf("Error Initializing Board State: %v", err)
|
||||
}
|
||||
boardState, err = gameState.ruleset.ModifyInitialBoardState(boardState)
|
||||
if err != nil {
|
||||
log.Fatalf("Error Initializing Board State: %v", err)
|
||||
log.ERROR.Fatalf("Error Initializing Board State: %v", err)
|
||||
}
|
||||
|
||||
for _, snakeState := range gameState.snakeStates {
|
||||
|
|
@ -325,12 +314,10 @@ func (gameState *GameState) initializeBoardFromArgs() *rules.BoardState {
|
|||
requestBody := serialiseSnakeRequest(snakeRequest)
|
||||
u, _ := url.ParseRequestURI(snakeState.URL)
|
||||
u.Path = path.Join(u.Path, "start")
|
||||
if gameState.DebugRequests {
|
||||
log.Printf("POST %s: %v", u, string(requestBody))
|
||||
}
|
||||
log.DEBUG.Printf("POST %s: %v", u, string(requestBody))
|
||||
_, err = gameState.httpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
log.Printf("[WARN]: Request to %v failed", u.String())
|
||||
log.WARN.Printf("Request to %v failed", u.String())
|
||||
}
|
||||
}
|
||||
return boardState
|
||||
|
|
@ -376,12 +363,12 @@ func (gameState *GameState) createNextBoardState(boardState *rules.BoardState) *
|
|||
}
|
||||
boardState, err := gameState.ruleset.CreateNextBoardState(boardState, moves)
|
||||
if err != nil {
|
||||
log.Fatalf("Error producing next board state: %v", err)
|
||||
log.ERROR.Fatalf("Error producing next board state: %v", err)
|
||||
}
|
||||
|
||||
boardState, err = maps.UpdateBoard(gameState.gameMap.ID(), boardState, gameState.ruleset.Settings())
|
||||
if err != nil {
|
||||
log.Fatalf("Error updating board with game map: %v", err)
|
||||
log.ERROR.Fatalf("Error updating board with game map: %v", err)
|
||||
}
|
||||
|
||||
boardState.Turn += 1
|
||||
|
|
@ -394,54 +381,52 @@ func (gameState *GameState) getMoveForSnake(boardState *rules.BoardState, snakeS
|
|||
requestBody := serialiseSnakeRequest(snakeRequest)
|
||||
u, _ := url.ParseRequestURI(snakeState.URL)
|
||||
u.Path = path.Join(u.Path, "move")
|
||||
if gameState.DebugRequests {
|
||||
log.Printf("POST %s: %v", u, string(requestBody))
|
||||
}
|
||||
log.DEBUG.Printf("POST %s: %v", u, string(requestBody))
|
||||
res, err := gameState.httpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
||||
|
||||
// Use snake's last move as the default in case of an error
|
||||
snakeMove := rules.SnakeMove{ID: snakeState.ID, Move: snakeState.LastMove}
|
||||
|
||||
if err != nil {
|
||||
log.Printf(
|
||||
"[WARN]: Request to %v failed\n"+
|
||||
"\tError: %s\n", u.String(), err)
|
||||
log.WARN.Printf(
|
||||
"Request to %v failed\n"+
|
||||
"\tError: %s", u.String(), err)
|
||||
return snakeMove
|
||||
}
|
||||
if res.Body == nil {
|
||||
log.Printf(
|
||||
"[WARN]: Failed to parse response from %v\n"+
|
||||
"\tError: body is empty\n", u.String())
|
||||
log.WARN.Printf(
|
||||
"Failed to parse response from %v\n"+
|
||||
"\tError: body is empty", u.String())
|
||||
return snakeMove
|
||||
}
|
||||
defer res.Body.Close()
|
||||
body, readErr := ioutil.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
log.Printf(
|
||||
"[WARN]: Failed to read response body from %v\n"+
|
||||
"\tError: %v\n", u.String(), readErr)
|
||||
log.WARN.Printf(
|
||||
"Failed to read response body from %v\n"+
|
||||
"\tError: %v", u.String(), readErr)
|
||||
return snakeMove
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
log.Printf(
|
||||
"[WARN]: Got non-ok status code from %v\n"+
|
||||
log.WARN.Printf(
|
||||
"Got non-ok status code from %v\n"+
|
||||
"\tStatusCode: %d (expected %d)\n"+
|
||||
"\tBody: %q\n", u.String(), res.StatusCode, http.StatusOK, body)
|
||||
"\tBody: %q", u.String(), res.StatusCode, http.StatusOK, body)
|
||||
return snakeMove
|
||||
}
|
||||
playerResponse := client.MoveResponse{}
|
||||
jsonErr := json.Unmarshal(body, &playerResponse)
|
||||
if jsonErr != nil {
|
||||
log.Printf(
|
||||
"[WARN]: Failed to decode JSON from %v\n"+
|
||||
log.WARN.Printf(
|
||||
"Failed to decode JSON from %v\n"+
|
||||
"\tError: %v\n"+
|
||||
"\tBody: %q\n"+
|
||||
"\tSee https://docs.battlesnake.com/references/api#post-move", u.String(), jsonErr, body)
|
||||
return snakeMove
|
||||
}
|
||||
if playerResponse.Move != "up" && playerResponse.Move != "down" && playerResponse.Move != "left" && playerResponse.Move != "right" {
|
||||
log.Printf(
|
||||
"[WARN]: Failed to parse JSON data from %v\n"+
|
||||
log.WARN.Printf(
|
||||
"Failed to parse JSON data from %v\n"+
|
||||
"\tError: invalid move %q, valid moves are \"up\", \"down\", \"left\" or \"right\"\n"+
|
||||
"\tBody: %q\n"+
|
||||
"\tSee https://docs.battlesnake.com/references/api#post-move", u.String(), playerResponse.Move, body)
|
||||
|
|
@ -457,12 +442,10 @@ func (gameState *GameState) sendEndRequest(boardState *rules.BoardState, snakeSt
|
|||
requestBody := serialiseSnakeRequest(snakeRequest)
|
||||
u, _ := url.ParseRequestURI(snakeState.URL)
|
||||
u.Path = path.Join(u.Path, "end")
|
||||
if gameState.DebugRequests {
|
||||
log.Printf("POST %s: %v", u, string(requestBody))
|
||||
}
|
||||
log.DEBUG.Printf("POST %s: %v", u, string(requestBody))
|
||||
_, err := gameState.httpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
log.Printf("[WARN]: Request to %v failed", u.String())
|
||||
log.WARN.Printf("Request to %v failed", u.String())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -507,9 +490,6 @@ func (gameState *GameState) buildSnakesFromOptions() map[string]SnakeState {
|
|||
} 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
|
||||
|
|
@ -519,21 +499,18 @@ func (gameState *GameState) buildSnakesFromOptions() map[string]SnakeState {
|
|||
if i < numNames {
|
||||
snakeName = gameState.Names[i]
|
||||
} else {
|
||||
log.Printf("[WARN]: Name for URL %v is missing: a default name will be applied\n", gameState.URLs[i])
|
||||
snakeName = id
|
||||
log.DEBUG.Printf("Name for URL %v is missing: a name will be generated automatically", gameState.URLs[i])
|
||||
snakeName = GenerateSnakeName()
|
||||
}
|
||||
|
||||
if i < numURLs {
|
||||
u, err := url.ParseRequestURI(gameState.URLs[i])
|
||||
if err != nil {
|
||||
log.Printf("[WARN]: URL %v is not valid: a default will be applied\n", gameState.URLs[i])
|
||||
snakeURL = "https://example.com"
|
||||
} else {
|
||||
snakeURL = u.String()
|
||||
log.ERROR.Fatalf("URL %v is not valid: %v", gameState.URLs[i], err)
|
||||
}
|
||||
snakeURL = u.String()
|
||||
} else {
|
||||
log.Printf("[WARN]: URL for Name %v is missing: a default URL will be applied\n", gameState.Names[i])
|
||||
snakeURL = "https://example.com"
|
||||
log.ERROR.Fatalf("URL for name %v is missing", gameState.Names[i])
|
||||
}
|
||||
|
||||
snakeState := SnakeState{
|
||||
|
|
@ -542,44 +519,60 @@ func (gameState *GameState) buildSnakesFromOptions() map[string]SnakeState {
|
|||
var snakeErr error
|
||||
res, err := gameState.httpClient.Get(snakeURL)
|
||||
if err != nil {
|
||||
log.Printf("[WARN]: Request to %v failed: %v", snakeURL, err)
|
||||
snakeErr = err
|
||||
} else {
|
||||
snakeState.StatusCode = res.StatusCode
|
||||
|
||||
if res.Body != nil {
|
||||
defer res.Body.Close()
|
||||
body, readErr := ioutil.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
log.Fatal(readErr)
|
||||
}
|
||||
|
||||
pingResponse := client.SnakeMetadataResponse{}
|
||||
jsonErr := json.Unmarshal(body, &pingResponse)
|
||||
if jsonErr != nil {
|
||||
snakeErr = jsonErr
|
||||
log.Printf("Error reading response from %v: %v", snakeURL, jsonErr)
|
||||
} else {
|
||||
snakeState.Head = pingResponse.Head
|
||||
snakeState.Tail = pingResponse.Tail
|
||||
snakeState.Color = pingResponse.Color
|
||||
snakeState.Author = pingResponse.Author
|
||||
snakeState.Version = pingResponse.Version
|
||||
}
|
||||
}
|
||||
log.ERROR.Fatalf("Snake metadata request to %v failed: %v", snakeURL, err)
|
||||
}
|
||||
|
||||
snakeState.StatusCode = res.StatusCode
|
||||
|
||||
if res.Body == nil {
|
||||
log.ERROR.Fatalf("Empty response body from snake metadata URL: %v", snakeURL)
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
body, readErr := ioutil.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
log.ERROR.Fatalf("Error reading from snake metadata URL %v: %v", snakeURL, readErr)
|
||||
}
|
||||
|
||||
pingResponse := client.SnakeMetadataResponse{}
|
||||
jsonErr := json.Unmarshal(body, &pingResponse)
|
||||
if jsonErr != nil {
|
||||
log.ERROR.Fatalf("Failed to parse response from %v: %v", snakeURL, jsonErr)
|
||||
}
|
||||
|
||||
snakeState.Head = pingResponse.Head
|
||||
snakeState.Tail = pingResponse.Tail
|
||||
snakeState.Color = pingResponse.Color
|
||||
snakeState.Author = pingResponse.Author
|
||||
snakeState.Version = pingResponse.Version
|
||||
|
||||
if snakeErr != nil {
|
||||
snakeState.Error = snakeErr
|
||||
}
|
||||
|
||||
snakes[snakeState.ID] = snakeState
|
||||
|
||||
log.INFO.Printf("Snake ID: %v URL: %v, Name: \"%v\"", snakeState.ID, snakeURL, snakeState.Name)
|
||||
}
|
||||
return snakes
|
||||
}
|
||||
|
||||
func (gameState *GameState) printState(boardState *rules.BoardState) {
|
||||
var aliveSnakeNames []string
|
||||
for _, snake := range boardState.Snakes {
|
||||
if snake.EliminatedCause == rules.NotEliminated {
|
||||
aliveSnakeNames = append(aliveSnakeNames, gameState.snakeStates[snake.ID].Name)
|
||||
}
|
||||
}
|
||||
log.INFO.Printf(
|
||||
"Turn: %d, Snakes Alive: [%v], Food: %d, Hazards: %d",
|
||||
boardState.Turn, strings.Join(aliveSnakeNames, ", "), len(boardState.Food), len(boardState.Hazards),
|
||||
)
|
||||
}
|
||||
|
||||
func (gameState *GameState) printMap(boardState *rules.BoardState) {
|
||||
var o bytes.Buffer
|
||||
o.WriteString(fmt.Sprintf("Ruleset: %s, Seed: %d, Turn: %v\n", gameState.GameType, gameState.Seed, boardState.Turn))
|
||||
o.WriteString(fmt.Sprintf("Turn: %d\n", boardState.Turn))
|
||||
board := make([][]string, boardState.Width)
|
||||
for i := range board {
|
||||
board[i] = make([]string, boardState.Height)
|
||||
|
|
@ -618,21 +611,28 @@ func (gameState *GameState) printMap(boardState *rules.BoardState) {
|
|||
o.WriteString(fmt.Sprintf("Food ⚕: %v\n", boardState.Food))
|
||||
}
|
||||
for _, s := range boardState.Snakes {
|
||||
red, green, blue := parseSnakeColor(gameState.snakeStates[s.ID].Color)
|
||||
state := gameState.snakeStates[s.ID]
|
||||
|
||||
red, green, blue := parseSnakeColor(state.Color)
|
||||
for _, b := range s.Body {
|
||||
if b.X >= 0 && b.X < boardState.Width && b.Y >= 0 && b.Y < boardState.Height {
|
||||
if gameState.UseColor {
|
||||
board[b.X][b.Y] = fmt.Sprintf(TERM_FG_RGB+"■", red, green, blue)
|
||||
} else {
|
||||
board[b.X][b.Y] = string(gameState.snakeStates[s.ID].Character)
|
||||
board[b.X][b.Y] = string(state.Character)
|
||||
}
|
||||
}
|
||||
}
|
||||
if gameState.UseColor {
|
||||
o.WriteString(fmt.Sprintf("%v "+TERM_FG_RGB+TERM_BG_WHITE+"■■■"+TERM_RESET+": %v\n", gameState.snakeStates[s.ID].Name, red, green, blue, s))
|
||||
o.WriteString(fmt.Sprintf("%v "+TERM_FG_RGB+TERM_BG_WHITE+"■■■"+TERM_RESET+": ", state.Name, red, green, blue))
|
||||
} else {
|
||||
o.WriteString(fmt.Sprintf("%v %c: %v\n", gameState.snakeStates[s.ID].Name, gameState.snakeStates[s.ID].Character, s))
|
||||
o.WriteString(fmt.Sprintf("%v %c: ", state.Name, state.Character))
|
||||
}
|
||||
o.WriteString(fmt.Sprintf("Health: %d", s.Health))
|
||||
if s.EliminatedCause != rules.NotEliminated {
|
||||
o.WriteString(fmt.Sprintf(", Eliminated: %v, Turn: %d", s.EliminatedCause, s.EliminatedOnTurn))
|
||||
}
|
||||
o.WriteString("\n")
|
||||
}
|
||||
for y := boardState.Height - 1; y >= 0; y-- {
|
||||
if gameState.UseColor {
|
||||
|
|
@ -646,7 +646,7 @@ func (gameState *GameState) printMap(boardState *rules.BoardState) {
|
|||
}
|
||||
o.WriteString("\n")
|
||||
}
|
||||
log.Print(o.String())
|
||||
fmt.Println(o.String())
|
||||
}
|
||||
|
||||
func (gameState *GameState) buildFrameEvent(boardState *rules.BoardState) board.GameEvent {
|
||||
|
|
@ -705,7 +705,7 @@ func (gameState *GameState) buildFrameEvent(boardState *rules.BoardState) board.
|
|||
func serialiseSnakeRequest(snakeRequest client.SnakeRequest) []byte {
|
||||
requestJSON, err := json.Marshal(snakeRequest)
|
||||
if err != nil {
|
||||
log.Fatalf("Error marshalling JSON from State: %v", err)
|
||||
log.ERROR.Fatalf("Error marshalling JSON from State: %v", err)
|
||||
}
|
||||
return requestJSON
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ func buildDefaultGameState() *GameState {
|
|||
Seed: 1,
|
||||
TurnDelay: 0,
|
||||
TurnDuration: 0,
|
||||
DebugRequests: false,
|
||||
Output: "",
|
||||
FoodSpawnChance: 15,
|
||||
MinimumFood: 1,
|
||||
|
|
|
|||
|
|
@ -2,15 +2,18 @@ package commands
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
stdlog "log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
log "github.com/spf13/jwalterweatherman"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
var verbose bool
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "battlesnake",
|
||||
|
|
@ -41,7 +44,7 @@ func init() {
|
|||
// will be global for your application.
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.battlesnake.yaml)")
|
||||
|
||||
rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Enable debug logging")
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
|
|
@ -68,4 +71,13 @@ func initConfig() {
|
|||
if err := viper.ReadInConfig(); err == nil {
|
||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||
}
|
||||
|
||||
// Setup logging
|
||||
log.SetStdoutOutput(os.Stderr)
|
||||
log.SetFlags(stdlog.Ltime | stdlog.Lmicroseconds)
|
||||
if verbose {
|
||||
log.SetStdoutThreshold(log.LevelDebug)
|
||||
} else {
|
||||
log.SetStdoutThreshold(log.LevelInfo)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue