Configurable random seed.
Fix food spawning parameters. Added Solo game result condition. Added support for 'maze' mode. Added seed, ruleset to view output.
This commit is contained in:
parent
8d764e1477
commit
f2b52b85d6
1 changed files with 82 additions and 61 deletions
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
|
@ -16,7 +17,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InternalSnake struct {
|
type Battlesnake struct {
|
||||||
URL string
|
URL string
|
||||||
Name string
|
Name string
|
||||||
ID string
|
ID string
|
||||||
|
|
@ -26,28 +27,28 @@ type InternalSnake struct {
|
||||||
Character rune
|
Character rune
|
||||||
}
|
}
|
||||||
|
|
||||||
type XY struct {
|
type Coord struct {
|
||||||
X int32 `json:"x"`
|
X int32 `json:"x"`
|
||||||
Y int32 `json:"y"`
|
Y int32 `json:"y"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SnakeResponse struct {
|
type SnakeResponse struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Health int32 `json:"health"`
|
Health int32 `json:"health"`
|
||||||
Body []XY `json:"body"`
|
Body []Coord `json:"body"`
|
||||||
Latency int32 `json:"latency"`
|
Latency int32 `json:"latency"`
|
||||||
Head XY `json:"head"`
|
Head Coord `json:"head"`
|
||||||
Length int32 `json:"length"`
|
Length int32 `json:"length"`
|
||||||
Shout string `json:"shout"`
|
Shout string `json:"shout"`
|
||||||
Squad string `json:"squad"`
|
Squad string `json:"squad"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BoardResponse struct {
|
type BoardResponse struct {
|
||||||
Height int32 `json:"height"`
|
Height int32 `json:"height"`
|
||||||
Width int32 `json:"width"`
|
Width int32 `json:"width"`
|
||||||
Food []XY `json:"food"`
|
Food []Coord `json:"food"`
|
||||||
Hazards []XY `json:"hazards"`
|
Hazards []Coord `json:"hazards"`
|
||||||
Snakes []SnakeResponse `json:"snakes"`
|
Snakes []SnakeResponse `json:"snakes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +80,7 @@ type PingResponse struct {
|
||||||
|
|
||||||
var GameId string
|
var GameId string
|
||||||
var Turn int32
|
var Turn int32
|
||||||
var InternalSnakes map[string]InternalSnake
|
var Battlesnakes map[string]Battlesnake
|
||||||
var HttpClient http.Client
|
var HttpClient http.Client
|
||||||
var Width int32
|
var Width int32
|
||||||
var Height int32
|
var Height int32
|
||||||
|
|
@ -90,6 +91,7 @@ var Timeout int32
|
||||||
var Sequential bool
|
var Sequential bool
|
||||||
var GameType string
|
var GameType string
|
||||||
var ViewMap bool
|
var ViewMap bool
|
||||||
|
var Seed int64
|
||||||
|
|
||||||
var playCmd = &cobra.Command{
|
var playCmd = &cobra.Command{
|
||||||
Use: "play",
|
Use: "play",
|
||||||
|
|
@ -110,29 +112,30 @@ func init() {
|
||||||
playCmd.Flags().BoolVarP(&Sequential, "sequential", "s", false, "Use Sequential Processing")
|
playCmd.Flags().BoolVarP(&Sequential, "sequential", "s", false, "Use Sequential Processing")
|
||||||
playCmd.Flags().StringVarP(&GameType, "gametype", "g", "standard", "Type of Game Rules")
|
playCmd.Flags().StringVarP(&GameType, "gametype", "g", "standard", "Type of Game Rules")
|
||||||
playCmd.Flags().BoolVarP(&ViewMap, "viewmap", "v", false, "View the Map Each Turn")
|
playCmd.Flags().BoolVarP(&ViewMap, "viewmap", "v", false, "View the Map Each Turn")
|
||||||
|
playCmd.Flags().Int64VarP(&Seed, "seed", "r", time.Now().UTC().UnixNano(), "Random Seed")
|
||||||
}
|
}
|
||||||
|
|
||||||
var run = func(cmd *cobra.Command, args []string) {
|
var run = func(cmd *cobra.Command, args []string) {
|
||||||
InternalSnakes = make(map[string]InternalSnake)
|
rand.Seed(Seed)
|
||||||
|
|
||||||
|
Battlesnakes = make(map[string]Battlesnake)
|
||||||
GameId = uuid.New().String()
|
GameId = uuid.New().String()
|
||||||
Turn = 0
|
Turn = 0
|
||||||
|
|
||||||
snakes := buildSnakesFromOptions()
|
snakes := buildSnakesFromOptions()
|
||||||
|
|
||||||
seed := time.Now().UTC().UnixNano()
|
|
||||||
|
|
||||||
var ruleset rules.Ruleset
|
var ruleset rules.Ruleset
|
||||||
var royale rules.RoyaleRuleset
|
var royale rules.RoyaleRuleset
|
||||||
var outOfBounds []rules.Point
|
var outOfBounds []rules.Point
|
||||||
ruleset, _ = getRuleset(seed, Turn, snakes)
|
ruleset, _ = getRuleset(Seed, Turn, snakes)
|
||||||
state := initializeBoardFromArgs(ruleset, snakes)
|
state := initializeBoardFromArgs(ruleset, snakes)
|
||||||
for _, snake := range snakes {
|
for _, snake := range snakes {
|
||||||
InternalSnakes[snake.ID] = snake
|
Battlesnakes[snake.ID] = snake
|
||||||
}
|
}
|
||||||
|
|
||||||
for v := false; !v; v, _ = ruleset.IsGameOver(state) {
|
for v := false; !v; v, _ = ruleset.IsGameOver(state) {
|
||||||
Turn++
|
Turn++
|
||||||
ruleset, royale = getRuleset(seed, Turn, snakes)
|
ruleset, royale = getRuleset(Seed, Turn, snakes)
|
||||||
state, outOfBounds = createNextBoardState(ruleset, royale, state, outOfBounds, snakes)
|
state, outOfBounds = createNextBoardState(ruleset, royale, state, outOfBounds, snakes)
|
||||||
if ViewMap {
|
if ViewMap {
|
||||||
printMap(state, outOfBounds, Turn)
|
printMap(state, outOfBounds, Turn)
|
||||||
|
|
@ -141,29 +144,40 @@ var run = func(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var winner string
|
if GameType == "solo" {
|
||||||
isDraw := true
|
log.Printf("[DONE]: Game completed after %v turns.", Turn)
|
||||||
for _, snake := range state.Snakes {
|
|
||||||
if snake.EliminatedCause == rules.NotEliminated {
|
|
||||||
isDraw = false
|
|
||||||
winner = InternalSnakes[snake.ID].Name
|
|
||||||
sendEndRequest(state, InternalSnakes[snake.ID])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isDraw {
|
|
||||||
log.Printf("[DONE]: Game completed after %v turns. It was a draw.", Turn)
|
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[DONE]: Game completed after %v turns. %v is the winner.", Turn, winner)
|
var winner string
|
||||||
|
isDraw := true
|
||||||
|
for _, snake := range state.Snakes {
|
||||||
|
if snake.EliminatedCause == rules.NotEliminated {
|
||||||
|
isDraw = false
|
||||||
|
winner = Battlesnakes[snake.ID].Name
|
||||||
|
sendEndRequest(state, Battlesnakes[snake.ID])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDraw {
|
||||||
|
log.Printf("[DONE]: Game completed after %v turns. It was a draw.", Turn)
|
||||||
|
} else {
|
||||||
|
log.Printf("[DONE]: Game completed after %v turns. %v is the winner.", Turn, winner)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRuleset(seed int64, gameTurn int32, snakes []InternalSnake) (rules.Ruleset, rules.RoyaleRuleset) {
|
func getRuleset(seed int64, gameTurn int32, snakes []Battlesnake) (rules.Ruleset, rules.RoyaleRuleset) {
|
||||||
var ruleset rules.Ruleset
|
var ruleset rules.Ruleset
|
||||||
var royale rules.RoyaleRuleset
|
var royale rules.RoyaleRuleset
|
||||||
|
|
||||||
|
standard := rules.StandardRuleset{
|
||||||
|
FoodSpawnChance: 15,
|
||||||
|
MinimumFood: 1,
|
||||||
|
}
|
||||||
|
|
||||||
switch GameType {
|
switch GameType {
|
||||||
case "royale":
|
case "royale":
|
||||||
royale = rules.RoyaleRuleset{
|
royale = rules.RoyaleRuleset{
|
||||||
|
StandardRuleset: standard,
|
||||||
Seed: seed,
|
Seed: seed,
|
||||||
Turn: gameTurn,
|
Turn: gameTurn,
|
||||||
ShrinkEveryNTurns: 10,
|
ShrinkEveryNTurns: 10,
|
||||||
|
|
@ -176,6 +190,7 @@ func getRuleset(seed int64, gameTurn int32, snakes []InternalSnake) (rules.Rules
|
||||||
squadMap[snake.ID] = snake.Squad
|
squadMap[snake.ID] = snake.Squad
|
||||||
}
|
}
|
||||||
ruleset = &rules.SquadRuleset{
|
ruleset = &rules.SquadRuleset{
|
||||||
|
StandardRuleset: standard,
|
||||||
SquadMap: squadMap,
|
SquadMap: squadMap,
|
||||||
AllowBodyCollisions: true,
|
AllowBodyCollisions: true,
|
||||||
SharedElimination: true,
|
SharedElimination: true,
|
||||||
|
|
@ -183,14 +198,20 @@ func getRuleset(seed int64, gameTurn int32, snakes []InternalSnake) (rules.Rules
|
||||||
SharedLength: true,
|
SharedLength: true,
|
||||||
}
|
}
|
||||||
case "solo":
|
case "solo":
|
||||||
ruleset = &rules.SoloRuleset{}
|
ruleset = &rules.SoloRuleset{
|
||||||
|
StandardRuleset: standard,
|
||||||
|
}
|
||||||
|
case "maze":
|
||||||
|
ruleset = &rules.MazeRuleset{
|
||||||
|
StandardRuleset: standard,
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
ruleset = &rules.StandardRuleset{}
|
ruleset = &standard
|
||||||
}
|
}
|
||||||
return ruleset, royale
|
return ruleset, royale
|
||||||
}
|
}
|
||||||
|
|
||||||
func initializeBoardFromArgs(ruleset rules.Ruleset, snakes []InternalSnake) *rules.BoardState {
|
func initializeBoardFromArgs(ruleset rules.Ruleset, snakes []Battlesnake) *rules.BoardState {
|
||||||
if Timeout == 0 {
|
if Timeout == 0 {
|
||||||
Timeout = 500
|
Timeout = 500
|
||||||
}
|
}
|
||||||
|
|
@ -219,7 +240,7 @@ func initializeBoardFromArgs(ruleset rules.Ruleset, snakes []InternalSnake) *rul
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNextBoardState(ruleset rules.Ruleset, royale rules.RoyaleRuleset, state *rules.BoardState, outOfBounds []rules.Point, snakes []InternalSnake) (*rules.BoardState, []rules.Point) {
|
func createNextBoardState(ruleset rules.Ruleset, royale rules.RoyaleRuleset, state *rules.BoardState, outOfBounds []rules.Point, snakes []Battlesnake) (*rules.BoardState, []rules.Point) {
|
||||||
var moves []rules.SnakeMove
|
var moves []rules.SnakeMove
|
||||||
if Sequential {
|
if Sequential {
|
||||||
for _, snake := range snakes {
|
for _, snake := range snakes {
|
||||||
|
|
@ -235,9 +256,9 @@ func createNextBoardState(ruleset rules.Ruleset, royale rules.RoyaleRuleset, sta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, move := range moves {
|
for _, move := range moves {
|
||||||
snake := InternalSnakes[move.ID]
|
snake := Battlesnakes[move.ID]
|
||||||
snake.LastMove = move.Move
|
snake.LastMove = move.Move
|
||||||
InternalSnakes[move.ID] = snake
|
Battlesnakes[move.ID] = snake
|
||||||
}
|
}
|
||||||
if GameType == "royale" {
|
if GameType == "royale" {
|
||||||
_, err := royale.CreateNextBoardState(state, moves)
|
_, err := royale.CreateNextBoardState(state, moves)
|
||||||
|
|
@ -254,11 +275,11 @@ func createNextBoardState(ruleset rules.Ruleset, royale rules.RoyaleRuleset, sta
|
||||||
return state, royale.OutOfBounds
|
return state, royale.OutOfBounds
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConcurrentMoveForSnake(state *rules.BoardState, snake InternalSnake, outOfBounds []rules.Point, c chan rules.SnakeMove) {
|
func getConcurrentMoveForSnake(state *rules.BoardState, snake Battlesnake, outOfBounds []rules.Point, c chan rules.SnakeMove) {
|
||||||
c <- getMoveForSnake(state, snake, outOfBounds)
|
c <- getMoveForSnake(state, snake, outOfBounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMoveForSnake(state *rules.BoardState, snake InternalSnake, outOfBounds []rules.Point) rules.SnakeMove {
|
func getMoveForSnake(state *rules.BoardState, snake Battlesnake, outOfBounds []rules.Point) rules.SnakeMove {
|
||||||
requestBody := getIndividualBoardStateForSnake(state, snake, outOfBounds)
|
requestBody := getIndividualBoardStateForSnake(state, snake, outOfBounds)
|
||||||
u, _ := url.ParseRequestURI(snake.URL)
|
u, _ := url.ParseRequestURI(snake.URL)
|
||||||
u.Path = path.Join(u.Path, "move")
|
u.Path = path.Join(u.Path, "move")
|
||||||
|
|
@ -290,7 +311,7 @@ func getMoveForSnake(state *rules.BoardState, snake InternalSnake, outOfBounds [
|
||||||
return rules.SnakeMove{ID: snake.ID, Move: move}
|
return rules.SnakeMove{ID: snake.ID, Move: move}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendEndRequest(state *rules.BoardState, snake InternalSnake) {
|
func sendEndRequest(state *rules.BoardState, snake Battlesnake) {
|
||||||
requestBody := getIndividualBoardStateForSnake(state, snake, nil)
|
requestBody := getIndividualBoardStateForSnake(state, snake, nil)
|
||||||
u, _ := url.ParseRequestURI(snake.URL)
|
u, _ := url.ParseRequestURI(snake.URL)
|
||||||
u.Path = path.Join(u.Path, "end")
|
u.Path = path.Join(u.Path, "end")
|
||||||
|
|
@ -300,7 +321,7 @@ func sendEndRequest(state *rules.BoardState, snake InternalSnake) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIndividualBoardStateForSnake(state *rules.BoardState, snake InternalSnake, outOfBounds []rules.Point) []byte {
|
func getIndividualBoardStateForSnake(state *rules.BoardState, snake Battlesnake, outOfBounds []rules.Point) []byte {
|
||||||
var youSnake rules.Snake
|
var youSnake rules.Snake
|
||||||
for _, snk := range state.Snakes {
|
for _, snk := range state.Snakes {
|
||||||
if snake.ID == snk.ID {
|
if snake.ID == snk.ID {
|
||||||
|
|
@ -314,8 +335,8 @@ func getIndividualBoardStateForSnake(state *rules.BoardState, snake InternalSnak
|
||||||
Board: BoardResponse{
|
Board: BoardResponse{
|
||||||
Height: state.Height,
|
Height: state.Height,
|
||||||
Width: state.Width,
|
Width: state.Width,
|
||||||
Food: xyFromPointArray(state.Food),
|
Food: coordFromPointArray(state.Food),
|
||||||
Hazards: xyFromPointArray(outOfBounds),
|
Hazards: coordFromPointArray(outOfBounds),
|
||||||
Snakes: buildSnakesResponse(state.Snakes),
|
Snakes: buildSnakesResponse(state.Snakes),
|
||||||
},
|
},
|
||||||
You: snakeResponseFromSnake(youSnake),
|
You: snakeResponseFromSnake(youSnake),
|
||||||
|
|
@ -331,14 +352,14 @@ func getIndividualBoardStateForSnake(state *rules.BoardState, snake InternalSnak
|
||||||
func snakeResponseFromSnake(snake rules.Snake) SnakeResponse {
|
func snakeResponseFromSnake(snake rules.Snake) SnakeResponse {
|
||||||
return SnakeResponse{
|
return SnakeResponse{
|
||||||
Id: snake.ID,
|
Id: snake.ID,
|
||||||
Name: InternalSnakes[snake.ID].Name,
|
Name: Battlesnakes[snake.ID].Name,
|
||||||
Health: snake.Health,
|
Health: snake.Health,
|
||||||
Body: xyFromPointArray(snake.Body),
|
Body: coordFromPointArray(snake.Body),
|
||||||
Latency: 0,
|
Latency: 0,
|
||||||
Head: xyFromPoint(snake.Body[0]),
|
Head: coordFromPoint(snake.Body[0]),
|
||||||
Length: int32(len(snake.Body)),
|
Length: int32(len(snake.Body)),
|
||||||
Shout: "",
|
Shout: "",
|
||||||
Squad: InternalSnakes[snake.ID].Squad,
|
Squad: Battlesnakes[snake.ID].Squad,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -350,22 +371,22 @@ func buildSnakesResponse(snakes []rules.Snake) []SnakeResponse {
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func xyFromPoint(pt rules.Point) XY {
|
func coordFromPoint(pt rules.Point) Coord {
|
||||||
return XY{X: pt.X, Y: pt.Y}
|
return Coord{X: pt.X, Y: pt.Y}
|
||||||
}
|
}
|
||||||
|
|
||||||
func xyFromPointArray(ptArray []rules.Point) []XY {
|
func coordFromPointArray(ptArray []rules.Point) []Coord {
|
||||||
a := make([]XY, 0)
|
a := make([]Coord, 0)
|
||||||
for _, pt := range ptArray {
|
for _, pt := range ptArray {
|
||||||
a = append(a, xyFromPoint(pt))
|
a = append(a, coordFromPoint(pt))
|
||||||
}
|
}
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSnakesFromOptions() []InternalSnake {
|
func buildSnakesFromOptions() []Battlesnake {
|
||||||
bodyChars := []rune{'■', '⌀', '●', '⍟', '◘', '☺', '□', '☻'}
|
bodyChars := []rune{'■', '⌀', '●', '⍟', '◘', '☺', '□', '☻'}
|
||||||
var numSnakes int
|
var numSnakes int
|
||||||
var snakes []InternalSnake
|
var snakes []Battlesnake
|
||||||
numNames := len(Names)
|
numNames := len(Names)
|
||||||
numURLs := len(URLs)
|
numURLs := len(URLs)
|
||||||
numSquads := len(Squads)
|
numSquads := len(Squads)
|
||||||
|
|
@ -431,7 +452,7 @@ func buildSnakesFromOptions() []InternalSnake {
|
||||||
api = pingResponse.APIVersion
|
api = pingResponse.APIVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
snake := InternalSnake{Name: snakeName, URL: snakeURL, ID: id, API: api, LastMove: "up", Character: bodyChars[i%8]}
|
snake := Battlesnake{Name: snakeName, URL: snakeURL, ID: id, API: api, LastMove: "up", Character: bodyChars[i%8]}
|
||||||
if GameType == "squad" {
|
if GameType == "squad" {
|
||||||
snake.Squad = snakeSquad
|
snake.Squad = snakeSquad
|
||||||
}
|
}
|
||||||
|
|
@ -442,7 +463,7 @@ func buildSnakesFromOptions() []InternalSnake {
|
||||||
|
|
||||||
func printMap(state *rules.BoardState, outOfBounds []rules.Point, gameTurn int32) {
|
func printMap(state *rules.BoardState, outOfBounds []rules.Point, gameTurn int32) {
|
||||||
var o bytes.Buffer
|
var o bytes.Buffer
|
||||||
o.WriteString(fmt.Sprintf("[%v]\n", gameTurn))
|
o.WriteString(fmt.Sprintf("Ruleset: %s, Seed: %d, Turn: %v\n", GameType, Seed, gameTurn))
|
||||||
board := make([][]rune, state.Width)
|
board := make([][]rune, state.Width)
|
||||||
for i := range board {
|
for i := range board {
|
||||||
board[i] = make([]rune, state.Height)
|
board[i] = make([]rune, state.Height)
|
||||||
|
|
@ -462,9 +483,9 @@ func printMap(state *rules.BoardState, outOfBounds []rules.Point, gameTurn int32
|
||||||
o.WriteString(fmt.Sprintf("Food ⚕: %v\n", state.Food))
|
o.WriteString(fmt.Sprintf("Food ⚕: %v\n", state.Food))
|
||||||
for _, s := range state.Snakes {
|
for _, s := range state.Snakes {
|
||||||
for _, b := range s.Body {
|
for _, b := range s.Body {
|
||||||
board[b.X][b.Y] = InternalSnakes[s.ID].Character
|
board[b.X][b.Y] = Battlesnakes[s.ID].Character
|
||||||
}
|
}
|
||||||
o.WriteString(fmt.Sprintf("%v %c: %v\n", InternalSnakes[s.ID].Name, InternalSnakes[s.ID].Character, s))
|
o.WriteString(fmt.Sprintf("%v %c: %v\n", Battlesnakes[s.ID].Name, Battlesnakes[s.ID].Character, s))
|
||||||
}
|
}
|
||||||
for y := state.Height - 1; y >= 0; y-- {
|
for y := state.Height - 1; y >= 0; y-- {
|
||||||
for x := int32(0); x < state.Width; x++ {
|
for x := int32(0); x < state.Width; x++ {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue