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
|
|
@ -3,12 +3,12 @@ package board
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
|
log "github.com/spf13/jwalterweatherman"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A minimal server capable of handling the requests from a single browser client running the board viewer.
|
// A minimal server capable of handling the requests from a single browser client running the board viewer.
|
||||||
|
|
@ -51,7 +51,7 @@ func (server *BoardServer) handleGame(w http.ResponseWriter, r *http.Request) {
|
||||||
Game Game
|
Game Game
|
||||||
}{server.game})
|
}{server.game})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Unable to serialize game: %v", err)
|
log.ERROR.Printf("Unable to serialize game: %v", err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -61,37 +61,37 @@ func (server *BoardServer) handleGame(w http.ResponseWriter, r *http.Request) {
|
||||||
func (server *BoardServer) handleWebsocket(w http.ResponseWriter, r *http.Request) {
|
func (server *BoardServer) handleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||||
ws, err := upgrader.Upgrade(w, r, nil)
|
ws, err := upgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Unable to upgrade connection: %v", err)
|
log.ERROR.Printf("Unable to upgrade connection: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
err = ws.Close()
|
err = ws.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Unable to close websocket stream")
|
log.ERROR.Printf("Unable to close websocket stream")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for event := range server.events {
|
for event := range server.events {
|
||||||
jsonStr, err := json.Marshal(event)
|
jsonStr, err := json.Marshal(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Unable to serialize event for websocket: %v", err)
|
log.ERROR.Printf("Unable to serialize event for websocket: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ws.WriteMessage(websocket.TextMessage, jsonStr)
|
err = ws.WriteMessage(websocket.TextMessage, jsonStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Unable to write to websocket: %v", err)
|
log.ERROR.Printf("Unable to write to websocket: %v", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Finished writing all game events, signalling game server to stop")
|
log.DEBUG.Printf("Finished writing all game events, signalling game server to stop")
|
||||||
close(server.done)
|
close(server.done)
|
||||||
|
|
||||||
log.Printf("Sending websocket close message")
|
log.DEBUG.Printf("Sending websocket close message")
|
||||||
err = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
err = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Problem closing websocket: %v", err)
|
log.ERROR.Printf("Problem closing websocket: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,7 +103,7 @@ func (server *BoardServer) Listen() (string, error) {
|
||||||
go func() {
|
go func() {
|
||||||
err = server.httpServer.Serve(listener)
|
err = server.httpServer.Serve(listener)
|
||||||
if err != http.ErrServerClosed {
|
if err != http.ErrServerClosed {
|
||||||
log.Printf("Error in board HTTP server: %v", err)
|
log.ERROR.Printf("Error in board HTTP server: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -115,13 +115,13 @@ func (server *BoardServer) Listen() (string, error) {
|
||||||
func (server *BoardServer) Shutdown() {
|
func (server *BoardServer) Shutdown() {
|
||||||
close(server.events)
|
close(server.events)
|
||||||
|
|
||||||
log.Printf("Waiting for websocket clients to finish")
|
log.DEBUG.Printf("Waiting for websocket clients to finish")
|
||||||
<-server.done
|
<-server.done
|
||||||
log.Printf("Server is done, exiting")
|
log.DEBUG.Printf("Server is done, exiting")
|
||||||
|
|
||||||
err := server.httpServer.Shutdown(context.Background())
|
err := server.httpServer.Shutdown(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error shutting down HTTP server: %v", err)
|
log.ERROR.Printf("Error shutting down HTTP server: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
log "github.com/spf13/jwalterweatherman"
|
||||||
|
|
||||||
"github.com/BattlesnakeOfficial/rules/maps"
|
"github.com/BattlesnakeOfficial/rules/maps"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type mapInfo struct {
|
type mapInfo struct {
|
||||||
|
|
@ -35,7 +36,7 @@ func NewMapInfoCommand() *cobra.Command {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
err := cmd.Help()
|
err := cmd.Help()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.ERROR.Fatal(err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -59,7 +60,7 @@ func NewMapInfoCommand() *cobra.Command {
|
||||||
func (m *mapInfo) display(id string) {
|
func (m *mapInfo) display(id string) {
|
||||||
gameMap, err := maps.GetMap(id)
|
gameMap, err := maps.GetMap(id)
|
||||||
if err != nil {
|
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()
|
meta := gameMap.Meta()
|
||||||
fmt.Println("Name:", meta.Name)
|
fmt.Println("Name:", meta.Name)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
log "github.com/spf13/jwalterweatherman"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewMapCommand() *cobra.Command {
|
func NewMapCommand() *cobra.Command {
|
||||||
|
|
@ -15,7 +14,7 @@ func NewMapCommand() *cobra.Command {
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
err := cmd.Help()
|
err := cmd.Help()
|
||||||
if err != nil {
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
log "github.com/spf13/jwalterweatherman"
|
||||||
|
|
||||||
"github.com/BattlesnakeOfficial/rules/client"
|
"github.com/BattlesnakeOfficial/rules/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -30,7 +31,7 @@ func (ge *GameExporter) FlushToFile(filepath string, format string) error {
|
||||||
if format == "JSONL" {
|
if format == "JSONL" {
|
||||||
formattedOutput, formattingErr = ge.ConvertToJSON()
|
formattedOutput, formattingErr = ge.ConvertToJSON()
|
||||||
} else {
|
} else {
|
||||||
log.Fatalf("Invalid output format passed: %s", format)
|
log.ERROR.Fatalf("Invalid output format passed: %s", format)
|
||||||
}
|
}
|
||||||
|
|
||||||
if formattingErr != nil {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -22,6 +21,7 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/browser"
|
"github.com/pkg/browser"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
log "github.com/spf13/jwalterweatherman"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Used to store state for each SnakeState while running a local game
|
// Used to store state for each SnakeState while running a local game
|
||||||
|
|
@ -55,7 +55,6 @@ type GameState struct {
|
||||||
UseColor bool
|
UseColor bool
|
||||||
Seed int64
|
Seed int64
|
||||||
TurnDelay int
|
TurnDelay int
|
||||||
DebugRequests bool
|
|
||||||
Output string
|
Output string
|
||||||
ViewInBrowser bool
|
ViewInBrowser bool
|
||||||
BoardURL string
|
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().Int64VarP(&gameState.Seed, "seed", "r", time.Now().UTC().UnixNano(), "Random Seed")
|
||||||
playCmd.Flags().IntVarP(&gameState.TurnDelay, "delay", "d", 0, "Turn Delay in Milliseconds")
|
playCmd.Flags().IntVarP(&gameState.TurnDelay, "delay", "d", 0, "Turn Delay in Milliseconds")
|
||||||
playCmd.Flags().IntVarP(&gameState.TurnDuration, "duration", "D", 0, "Minimum Turn Duration in Milliseconds")
|
playCmd.Flags().IntVarP(&gameState.TurnDuration, "duration", "D", 0, "Minimum Turn Duration in Milliseconds")
|
||||||
playCmd.Flags().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().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().BoolVar(&gameState.ViewInBrowser, "browser", false, "View the game in the browser using the Battlesnake game board")
|
||||||
playCmd.Flags().StringVar(&gameState.BoardURL, "board-url", "https://board.battlesnake.com", "Base URL for the game board when using --browser")
|
playCmd.Flags().StringVar(&gameState.BoardURL, "board-url", "https://board.battlesnake.com", "Base URL for the game board when using --browser")
|
||||||
|
|
@ -129,7 +127,7 @@ func (gameState *GameState) initialize() {
|
||||||
// Load game map
|
// Load game map
|
||||||
gameMap, err := maps.GetMap(gameState.MapName)
|
gameMap, err := maps.GetMap(gameState.MapName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.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
|
gameState.gameMap = gameMap
|
||||||
|
|
||||||
|
|
@ -173,10 +171,6 @@ func (gameState *GameState) Run() {
|
||||||
isDraw: false,
|
isDraw: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
if gameState.ViewMap {
|
|
||||||
gameState.printMap(boardState)
|
|
||||||
}
|
|
||||||
|
|
||||||
boardGame := board.Game{
|
boardGame := board.Game{
|
||||||
ID: gameState.gameID,
|
ID: gameState.gameID,
|
||||||
Status: "running",
|
Status: "running",
|
||||||
|
|
@ -194,23 +188,30 @@ func (gameState *GameState) Run() {
|
||||||
if gameState.ViewInBrowser {
|
if gameState.ViewInBrowser {
|
||||||
serverURL, err := boardServer.Listen()
|
serverURL, err := boardServer.Listen()
|
||||||
if err != nil {
|
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)
|
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 {
|
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
|
// send turn zero to websocket server
|
||||||
boardServer.SendEvent(gameState.buildFrameEvent(boardState))
|
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
|
var endTime time.Time
|
||||||
for v := false; !v; v, _ = gameState.ruleset.IsGameOver(boardState) {
|
for v := false; !v; v, _ = gameState.ruleset.IsGameOver(boardState) {
|
||||||
if gameState.TurnDuration > 0 {
|
if gameState.TurnDuration > 0 {
|
||||||
|
|
@ -238,7 +239,7 @@ func (gameState *GameState) Run() {
|
||||||
if gameState.ViewMap {
|
if gameState.ViewMap {
|
||||||
gameState.printMap(boardState)
|
gameState.printMap(boardState)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[%v]: State: %v\n", boardState.Turn, boardState)
|
gameState.printState(boardState)
|
||||||
}
|
}
|
||||||
|
|
||||||
if gameState.TurnDelay > 0 {
|
if gameState.TurnDelay > 0 {
|
||||||
|
|
@ -254,36 +255,29 @@ func (gameState *GameState) Run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isDraw := true
|
gameExporter.isDraw = false
|
||||||
if gameState.GameType == "solo" {
|
|
||||||
log.Printf("[DONE]: Game completed after %v turns.", boardState.Turn)
|
if len(gameState.snakeStates) > 1 {
|
||||||
if exportGame {
|
// A draw is possible if there is more than one snake in the game.
|
||||||
// These checks for exportGame are present to avoid vacuuming up RAM when an export is not requred.
|
gameExporter.isDraw = true
|
||||||
for _, snakeState := range gameState.snakeStates {
|
|
||||||
gameExporter.winner = snakeState
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var winner SnakeState
|
|
||||||
for _, snake := range boardState.Snakes {
|
for _, snake := range boardState.Snakes {
|
||||||
snakeState := gameState.snakeStates[snake.ID]
|
snakeState := gameState.snakeStates[snake.ID]
|
||||||
if snake.EliminatedCause == rules.NotEliminated {
|
if snake.EliminatedCause == rules.NotEliminated {
|
||||||
isDraw = false
|
gameExporter.isDraw = false
|
||||||
winner = snakeState
|
gameExporter.winner = snakeState
|
||||||
}
|
}
|
||||||
|
|
||||||
gameState.sendEndRequest(boardState, snakeState)
|
gameState.sendEndRequest(boardState, snakeState)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isDraw {
|
if gameExporter.isDraw {
|
||||||
log.Printf("[DONE]: Game completed after %v turns. It was a draw.", boardState.Turn)
|
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 {
|
} else {
|
||||||
log.Printf("[DONE]: Game completed after %v turns. %v is the winner.", boardState.Turn, winner.Name)
|
log.INFO.Printf("Game completed after %v turns.", boardState.Turn)
|
||||||
}
|
|
||||||
if exportGame {
|
|
||||||
gameExporter.winner = winner
|
|
||||||
gameExporter.isDraw = isDraw
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if gameState.ViewInBrowser {
|
if gameState.ViewInBrowser {
|
||||||
|
|
@ -296,14 +290,9 @@ func (gameState *GameState) Run() {
|
||||||
if exportGame {
|
if exportGame {
|
||||||
err := gameExporter.FlushToFile(gameState.Output, "JSONL")
|
err := gameExporter.FlushToFile(gameState.Output, "JSONL")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARN]: Unable to export game. Reason: %v\n", err.Error())
|
log.ERROR.Fatalf("Unable to export game. Reason: %v", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if gameState.ViewInBrowser {
|
|
||||||
boardServer.Shutdown()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gameState *GameState) initializeBoardFromArgs() *rules.BoardState {
|
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)
|
boardState, err := maps.SetupBoard(gameState.gameMap.ID(), gameState.ruleset.Settings(), gameState.Width, gameState.Height, snakeIds)
|
||||||
if err != nil {
|
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)
|
boardState, err = gameState.ruleset.ModifyInitialBoardState(boardState)
|
||||||
if err != nil {
|
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 {
|
for _, snakeState := range gameState.snakeStates {
|
||||||
|
|
@ -325,12 +314,10 @@ func (gameState *GameState) initializeBoardFromArgs() *rules.BoardState {
|
||||||
requestBody := serialiseSnakeRequest(snakeRequest)
|
requestBody := serialiseSnakeRequest(snakeRequest)
|
||||||
u, _ := url.ParseRequestURI(snakeState.URL)
|
u, _ := url.ParseRequestURI(snakeState.URL)
|
||||||
u.Path = path.Join(u.Path, "start")
|
u.Path = path.Join(u.Path, "start")
|
||||||
if gameState.DebugRequests {
|
log.DEBUG.Printf("POST %s: %v", u, string(requestBody))
|
||||||
log.Printf("POST %s: %v", u, string(requestBody))
|
|
||||||
}
|
|
||||||
_, err = gameState.httpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
_, err = gameState.httpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARN]: Request to %v failed", u.String())
|
log.WARN.Printf("Request to %v failed", u.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return boardState
|
return boardState
|
||||||
|
|
@ -376,12 +363,12 @@ func (gameState *GameState) createNextBoardState(boardState *rules.BoardState) *
|
||||||
}
|
}
|
||||||
boardState, err := gameState.ruleset.CreateNextBoardState(boardState, moves)
|
boardState, err := gameState.ruleset.CreateNextBoardState(boardState, moves)
|
||||||
if err != nil {
|
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())
|
boardState, err = maps.UpdateBoard(gameState.gameMap.ID(), boardState, gameState.ruleset.Settings())
|
||||||
if err != nil {
|
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
|
boardState.Turn += 1
|
||||||
|
|
@ -394,54 +381,52 @@ func (gameState *GameState) getMoveForSnake(boardState *rules.BoardState, snakeS
|
||||||
requestBody := serialiseSnakeRequest(snakeRequest)
|
requestBody := serialiseSnakeRequest(snakeRequest)
|
||||||
u, _ := url.ParseRequestURI(snakeState.URL)
|
u, _ := url.ParseRequestURI(snakeState.URL)
|
||||||
u.Path = path.Join(u.Path, "move")
|
u.Path = path.Join(u.Path, "move")
|
||||||
if gameState.DebugRequests {
|
log.DEBUG.Printf("POST %s: %v", u, string(requestBody))
|
||||||
log.Printf("POST %s: %v", u, string(requestBody))
|
|
||||||
}
|
|
||||||
res, err := gameState.httpClient.Post(u.String(), "application/json", bytes.NewBuffer(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
|
// Use snake's last move as the default in case of an error
|
||||||
snakeMove := rules.SnakeMove{ID: snakeState.ID, Move: snakeState.LastMove}
|
snakeMove := rules.SnakeMove{ID: snakeState.ID, Move: snakeState.LastMove}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf(
|
log.WARN.Printf(
|
||||||
"[WARN]: Request to %v failed\n"+
|
"Request to %v failed\n"+
|
||||||
"\tError: %s\n", u.String(), err)
|
"\tError: %s", u.String(), err)
|
||||||
return snakeMove
|
return snakeMove
|
||||||
}
|
}
|
||||||
if res.Body == nil {
|
if res.Body == nil {
|
||||||
log.Printf(
|
log.WARN.Printf(
|
||||||
"[WARN]: Failed to parse response from %v\n"+
|
"Failed to parse response from %v\n"+
|
||||||
"\tError: body is empty\n", u.String())
|
"\tError: body is empty", u.String())
|
||||||
return snakeMove
|
return snakeMove
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
body, readErr := ioutil.ReadAll(res.Body)
|
body, readErr := ioutil.ReadAll(res.Body)
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
log.Printf(
|
log.WARN.Printf(
|
||||||
"[WARN]: Failed to read response body from %v\n"+
|
"Failed to read response body from %v\n"+
|
||||||
"\tError: %v\n", u.String(), readErr)
|
"\tError: %v", u.String(), readErr)
|
||||||
return snakeMove
|
return snakeMove
|
||||||
}
|
}
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
log.Printf(
|
log.WARN.Printf(
|
||||||
"[WARN]: Got non-ok status code from %v\n"+
|
"Got non-ok status code from %v\n"+
|
||||||
"\tStatusCode: %d (expected %d)\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
|
return snakeMove
|
||||||
}
|
}
|
||||||
playerResponse := client.MoveResponse{}
|
playerResponse := client.MoveResponse{}
|
||||||
jsonErr := json.Unmarshal(body, &playerResponse)
|
jsonErr := json.Unmarshal(body, &playerResponse)
|
||||||
if jsonErr != nil {
|
if jsonErr != nil {
|
||||||
log.Printf(
|
log.WARN.Printf(
|
||||||
"[WARN]: Failed to decode JSON from %v\n"+
|
"Failed to decode JSON from %v\n"+
|
||||||
"\tError: %v\n"+
|
"\tError: %v\n"+
|
||||||
"\tBody: %q\n"+
|
"\tBody: %q\n"+
|
||||||
"\tSee https://docs.battlesnake.com/references/api#post-move", u.String(), jsonErr, body)
|
"\tSee https://docs.battlesnake.com/references/api#post-move", u.String(), jsonErr, body)
|
||||||
return snakeMove
|
return snakeMove
|
||||||
}
|
}
|
||||||
if playerResponse.Move != "up" && playerResponse.Move != "down" && playerResponse.Move != "left" && playerResponse.Move != "right" {
|
if playerResponse.Move != "up" && playerResponse.Move != "down" && playerResponse.Move != "left" && playerResponse.Move != "right" {
|
||||||
log.Printf(
|
log.WARN.Printf(
|
||||||
"[WARN]: Failed to parse JSON data from %v\n"+
|
"Failed to parse JSON data from %v\n"+
|
||||||
"\tError: invalid move %q, valid moves are \"up\", \"down\", \"left\" or \"right\"\n"+
|
"\tError: invalid move %q, valid moves are \"up\", \"down\", \"left\" or \"right\"\n"+
|
||||||
"\tBody: %q\n"+
|
"\tBody: %q\n"+
|
||||||
"\tSee https://docs.battlesnake.com/references/api#post-move", u.String(), playerResponse.Move, body)
|
"\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)
|
requestBody := serialiseSnakeRequest(snakeRequest)
|
||||||
u, _ := url.ParseRequestURI(snakeState.URL)
|
u, _ := url.ParseRequestURI(snakeState.URL)
|
||||||
u.Path = path.Join(u.Path, "end")
|
u.Path = path.Join(u.Path, "end")
|
||||||
if gameState.DebugRequests {
|
log.DEBUG.Printf("POST %s: %v", u, string(requestBody))
|
||||||
log.Printf("POST %s: %v", u, string(requestBody))
|
|
||||||
}
|
|
||||||
_, err := gameState.httpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
_, err := gameState.httpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
||||||
if err != nil {
|
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 {
|
} else {
|
||||||
numSnakes = numURLs
|
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++ {
|
for i := int(0); i < numSnakes; i++ {
|
||||||
var snakeName string
|
var snakeName string
|
||||||
var snakeURL string
|
var snakeURL string
|
||||||
|
|
@ -519,21 +499,18 @@ func (gameState *GameState) buildSnakesFromOptions() map[string]SnakeState {
|
||||||
if i < numNames {
|
if i < numNames {
|
||||||
snakeName = gameState.Names[i]
|
snakeName = gameState.Names[i]
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[WARN]: Name for URL %v is missing: a default name will be applied\n", gameState.URLs[i])
|
log.DEBUG.Printf("Name for URL %v is missing: a name will be generated automatically", gameState.URLs[i])
|
||||||
snakeName = id
|
snakeName = GenerateSnakeName()
|
||||||
}
|
}
|
||||||
|
|
||||||
if i < numURLs {
|
if i < numURLs {
|
||||||
u, err := url.ParseRequestURI(gameState.URLs[i])
|
u, err := url.ParseRequestURI(gameState.URLs[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARN]: URL %v is not valid: a default will be applied\n", gameState.URLs[i])
|
log.ERROR.Fatalf("URL %v is not valid: %v", gameState.URLs[i], err)
|
||||||
snakeURL = "https://example.com"
|
|
||||||
} else {
|
|
||||||
snakeURL = u.String()
|
|
||||||
}
|
}
|
||||||
|
snakeURL = u.String()
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[WARN]: URL for Name %v is missing: a default URL will be applied\n", gameState.Names[i])
|
log.ERROR.Fatalf("URL for name %v is missing", gameState.Names[i])
|
||||||
snakeURL = "https://example.com"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
snakeState := SnakeState{
|
snakeState := SnakeState{
|
||||||
|
|
@ -542,44 +519,60 @@ func (gameState *GameState) buildSnakesFromOptions() map[string]SnakeState {
|
||||||
var snakeErr error
|
var snakeErr error
|
||||||
res, err := gameState.httpClient.Get(snakeURL)
|
res, err := gameState.httpClient.Get(snakeURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARN]: Request to %v failed: %v", snakeURL, err)
|
log.ERROR.Fatalf("Snake metadata request to %v failed: %v", snakeURL, err)
|
||||||
snakeErr = err
|
}
|
||||||
} else {
|
|
||||||
snakeState.StatusCode = res.StatusCode
|
snakeState.StatusCode = res.StatusCode
|
||||||
|
|
||||||
if res.Body != nil {
|
if res.Body == nil {
|
||||||
|
log.ERROR.Fatalf("Empty response body from snake metadata URL: %v", snakeURL)
|
||||||
|
}
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
body, readErr := ioutil.ReadAll(res.Body)
|
body, readErr := ioutil.ReadAll(res.Body)
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
log.Fatal(readErr)
|
log.ERROR.Fatalf("Error reading from snake metadata URL %v: %v", snakeURL, readErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pingResponse := client.SnakeMetadataResponse{}
|
pingResponse := client.SnakeMetadataResponse{}
|
||||||
jsonErr := json.Unmarshal(body, &pingResponse)
|
jsonErr := json.Unmarshal(body, &pingResponse)
|
||||||
if jsonErr != nil {
|
if jsonErr != nil {
|
||||||
snakeErr = jsonErr
|
log.ERROR.Fatalf("Failed to parse response from %v: %v", snakeURL, jsonErr)
|
||||||
log.Printf("Error reading response from %v: %v", snakeURL, jsonErr)
|
}
|
||||||
} else {
|
|
||||||
snakeState.Head = pingResponse.Head
|
snakeState.Head = pingResponse.Head
|
||||||
snakeState.Tail = pingResponse.Tail
|
snakeState.Tail = pingResponse.Tail
|
||||||
snakeState.Color = pingResponse.Color
|
snakeState.Color = pingResponse.Color
|
||||||
snakeState.Author = pingResponse.Author
|
snakeState.Author = pingResponse.Author
|
||||||
snakeState.Version = pingResponse.Version
|
snakeState.Version = pingResponse.Version
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if snakeErr != nil {
|
if snakeErr != nil {
|
||||||
snakeState.Error = snakeErr
|
snakeState.Error = snakeErr
|
||||||
}
|
}
|
||||||
|
|
||||||
snakes[snakeState.ID] = snakeState
|
snakes[snakeState.ID] = snakeState
|
||||||
|
|
||||||
|
log.INFO.Printf("Snake ID: %v URL: %v, Name: \"%v\"", snakeState.ID, snakeURL, snakeState.Name)
|
||||||
}
|
}
|
||||||
return snakes
|
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) {
|
func (gameState *GameState) printMap(boardState *rules.BoardState) {
|
||||||
var o bytes.Buffer
|
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)
|
board := make([][]string, boardState.Width)
|
||||||
for i := range board {
|
for i := range board {
|
||||||
board[i] = make([]string, boardState.Height)
|
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))
|
o.WriteString(fmt.Sprintf("Food ⚕: %v\n", boardState.Food))
|
||||||
}
|
}
|
||||||
for _, s := range boardState.Snakes {
|
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 {
|
for _, b := range s.Body {
|
||||||
if b.X >= 0 && b.X < boardState.Width && b.Y >= 0 && b.Y < boardState.Height {
|
if b.X >= 0 && b.X < boardState.Width && b.Y >= 0 && b.Y < boardState.Height {
|
||||||
if gameState.UseColor {
|
if gameState.UseColor {
|
||||||
board[b.X][b.Y] = fmt.Sprintf(TERM_FG_RGB+"■", red, green, blue)
|
board[b.X][b.Y] = fmt.Sprintf(TERM_FG_RGB+"■", red, green, blue)
|
||||||
} else {
|
} else {
|
||||||
board[b.X][b.Y] = string(gameState.snakeStates[s.ID].Character)
|
board[b.X][b.Y] = string(state.Character)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if gameState.UseColor {
|
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 {
|
} 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-- {
|
for y := boardState.Height - 1; y >= 0; y-- {
|
||||||
if gameState.UseColor {
|
if gameState.UseColor {
|
||||||
|
|
@ -646,7 +646,7 @@ func (gameState *GameState) printMap(boardState *rules.BoardState) {
|
||||||
}
|
}
|
||||||
o.WriteString("\n")
|
o.WriteString("\n")
|
||||||
}
|
}
|
||||||
log.Print(o.String())
|
fmt.Println(o.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gameState *GameState) buildFrameEvent(boardState *rules.BoardState) board.GameEvent {
|
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 {
|
func serialiseSnakeRequest(snakeRequest client.SnakeRequest) []byte {
|
||||||
requestJSON, err := json.Marshal(snakeRequest)
|
requestJSON, err := json.Marshal(snakeRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error marshalling JSON from State: %v", err)
|
log.ERROR.Fatalf("Error marshalling JSON from State: %v", err)
|
||||||
}
|
}
|
||||||
return requestJSON
|
return requestJSON
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ func buildDefaultGameState() *GameState {
|
||||||
Seed: 1,
|
Seed: 1,
|
||||||
TurnDelay: 0,
|
TurnDelay: 0,
|
||||||
TurnDuration: 0,
|
TurnDuration: 0,
|
||||||
DebugRequests: false,
|
|
||||||
Output: "",
|
Output: "",
|
||||||
FoodSpawnChance: 15,
|
FoodSpawnChance: 15,
|
||||||
MinimumFood: 1,
|
MinimumFood: 1,
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,18 @@ package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
stdlog "log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
|
log "github.com/spf13/jwalterweatherman"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cfgFile string
|
var cfgFile string
|
||||||
|
var verbose bool
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "battlesnake",
|
Use: "battlesnake",
|
||||||
|
|
@ -41,7 +44,7 @@ func init() {
|
||||||
// will be global for your application.
|
// will be global for your application.
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.battlesnake.yaml)")
|
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.
|
// initConfig reads in config file and ENV variables if set.
|
||||||
|
|
@ -68,4 +71,13 @@ func initConfig() {
|
||||||
if err := viper.ReadInConfig(); err == nil {
|
if err := viper.ReadInConfig(); err == nil {
|
||||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -9,6 +9,7 @@ require (
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
||||||
github.com/rs/cors v1.8.2
|
github.com/rs/cors v1.8.2
|
||||||
github.com/spf13/cobra v1.1.1
|
github.com/spf13/cobra v1.1.1
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0
|
||||||
github.com/spf13/viper v1.7.1
|
github.com/spf13/viper v1.7.1
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/stretchr/testify v1.4.0
|
||||||
)
|
)
|
||||||
|
|
@ -24,7 +25,6 @@ require (
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/spf13/afero v1.1.2 // indirect
|
github.com/spf13/afero v1.1.2 // indirect
|
||||||
github.com/spf13/cast v1.3.0 // indirect
|
github.com/spf13/cast v1.3.0 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.0.0 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/subosito/gotenv v1.2.0 // indirect
|
github.com/subosito/gotenv v1.2.0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
||||||
|
|
|
||||||
3
go.sum
3
go.sum
|
|
@ -173,8 +173,9 @@ github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
|
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
|
||||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue