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"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
|
@ -16,7 +17,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
type InternalSnake struct {
|
||||
type Battlesnake struct {
|
||||
URL string
|
||||
Name string
|
||||
ID string
|
||||
|
|
@ -26,7 +27,7 @@ type InternalSnake struct {
|
|||
Character rune
|
||||
}
|
||||
|
||||
type XY struct {
|
||||
type Coord struct {
|
||||
X int32 `json:"x"`
|
||||
Y int32 `json:"y"`
|
||||
}
|
||||
|
|
@ -35,9 +36,9 @@ type SnakeResponse struct {
|
|||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Health int32 `json:"health"`
|
||||
Body []XY `json:"body"`
|
||||
Body []Coord `json:"body"`
|
||||
Latency int32 `json:"latency"`
|
||||
Head XY `json:"head"`
|
||||
Head Coord `json:"head"`
|
||||
Length int32 `json:"length"`
|
||||
Shout string `json:"shout"`
|
||||
Squad string `json:"squad"`
|
||||
|
|
@ -46,8 +47,8 @@ type SnakeResponse struct {
|
|||
type BoardResponse struct {
|
||||
Height int32 `json:"height"`
|
||||
Width int32 `json:"width"`
|
||||
Food []XY `json:"food"`
|
||||
Hazards []XY `json:"hazards"`
|
||||
Food []Coord `json:"food"`
|
||||
Hazards []Coord `json:"hazards"`
|
||||
Snakes []SnakeResponse `json:"snakes"`
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +80,7 @@ type PingResponse struct {
|
|||
|
||||
var GameId string
|
||||
var Turn int32
|
||||
var InternalSnakes map[string]InternalSnake
|
||||
var Battlesnakes map[string]Battlesnake
|
||||
var HttpClient http.Client
|
||||
var Width int32
|
||||
var Height int32
|
||||
|
|
@ -90,6 +91,7 @@ var Timeout int32
|
|||
var Sequential bool
|
||||
var GameType string
|
||||
var ViewMap bool
|
||||
var Seed int64
|
||||
|
||||
var playCmd = &cobra.Command{
|
||||
Use: "play",
|
||||
|
|
@ -110,29 +112,30 @@ func init() {
|
|||
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")
|
||||
playCmd.Flags().Int64VarP(&Seed, "seed", "r", time.Now().UTC().UnixNano(), "Random Seed")
|
||||
}
|
||||
|
||||
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()
|
||||
Turn = 0
|
||||
|
||||
snakes := buildSnakesFromOptions()
|
||||
|
||||
seed := time.Now().UTC().UnixNano()
|
||||
|
||||
var ruleset rules.Ruleset
|
||||
var royale rules.RoyaleRuleset
|
||||
var outOfBounds []rules.Point
|
||||
ruleset, _ = getRuleset(seed, Turn, snakes)
|
||||
ruleset, _ = getRuleset(Seed, Turn, snakes)
|
||||
state := initializeBoardFromArgs(ruleset, snakes)
|
||||
for _, snake := range snakes {
|
||||
InternalSnakes[snake.ID] = snake
|
||||
Battlesnakes[snake.ID] = snake
|
||||
}
|
||||
|
||||
for v := false; !v; v, _ = ruleset.IsGameOver(state) {
|
||||
Turn++
|
||||
ruleset, royale = getRuleset(seed, Turn, snakes)
|
||||
ruleset, royale = getRuleset(Seed, Turn, snakes)
|
||||
state, outOfBounds = createNextBoardState(ruleset, royale, state, outOfBounds, snakes)
|
||||
if ViewMap {
|
||||
printMap(state, outOfBounds, Turn)
|
||||
|
|
@ -141,13 +144,16 @@ var run = func(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
}
|
||||
|
||||
if GameType == "solo" {
|
||||
log.Printf("[DONE]: Game completed after %v turns.", Turn)
|
||||
} else {
|
||||
var winner string
|
||||
isDraw := true
|
||||
for _, snake := range state.Snakes {
|
||||
if snake.EliminatedCause == rules.NotEliminated {
|
||||
isDraw = false
|
||||
winner = InternalSnakes[snake.ID].Name
|
||||
sendEndRequest(state, InternalSnakes[snake.ID])
|
||||
winner = Battlesnakes[snake.ID].Name
|
||||
sendEndRequest(state, Battlesnakes[snake.ID])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -156,14 +162,22 @@ var run = func(cmd *cobra.Command, args []string) {
|
|||
} 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 royale rules.RoyaleRuleset
|
||||
|
||||
standard := rules.StandardRuleset{
|
||||
FoodSpawnChance: 15,
|
||||
MinimumFood: 1,
|
||||
}
|
||||
|
||||
switch GameType {
|
||||
case "royale":
|
||||
royale = rules.RoyaleRuleset{
|
||||
StandardRuleset: standard,
|
||||
Seed: seed,
|
||||
Turn: gameTurn,
|
||||
ShrinkEveryNTurns: 10,
|
||||
|
|
@ -176,6 +190,7 @@ func getRuleset(seed int64, gameTurn int32, snakes []InternalSnake) (rules.Rules
|
|||
squadMap[snake.ID] = snake.Squad
|
||||
}
|
||||
ruleset = &rules.SquadRuleset{
|
||||
StandardRuleset: standard,
|
||||
SquadMap: squadMap,
|
||||
AllowBodyCollisions: true,
|
||||
SharedElimination: true,
|
||||
|
|
@ -183,14 +198,20 @@ func getRuleset(seed int64, gameTurn int32, snakes []InternalSnake) (rules.Rules
|
|||
SharedLength: true,
|
||||
}
|
||||
case "solo":
|
||||
ruleset = &rules.SoloRuleset{}
|
||||
ruleset = &rules.SoloRuleset{
|
||||
StandardRuleset: standard,
|
||||
}
|
||||
case "maze":
|
||||
ruleset = &rules.MazeRuleset{
|
||||
StandardRuleset: standard,
|
||||
}
|
||||
default:
|
||||
ruleset = &rules.StandardRuleset{}
|
||||
ruleset = &standard
|
||||
}
|
||||
return ruleset, royale
|
||||
}
|
||||
|
||||
func initializeBoardFromArgs(ruleset rules.Ruleset, snakes []InternalSnake) *rules.BoardState {
|
||||
func initializeBoardFromArgs(ruleset rules.Ruleset, snakes []Battlesnake) *rules.BoardState {
|
||||
if Timeout == 0 {
|
||||
Timeout = 500
|
||||
}
|
||||
|
|
@ -219,7 +240,7 @@ func initializeBoardFromArgs(ruleset rules.Ruleset, snakes []InternalSnake) *rul
|
|||
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
|
||||
if Sequential {
|
||||
for _, snake := range snakes {
|
||||
|
|
@ -235,9 +256,9 @@ func createNextBoardState(ruleset rules.Ruleset, royale rules.RoyaleRuleset, sta
|
|||
}
|
||||
}
|
||||
for _, move := range moves {
|
||||
snake := InternalSnakes[move.ID]
|
||||
snake := Battlesnakes[move.ID]
|
||||
snake.LastMove = move.Move
|
||||
InternalSnakes[move.ID] = snake
|
||||
Battlesnakes[move.ID] = snake
|
||||
}
|
||||
if GameType == "royale" {
|
||||
_, err := royale.CreateNextBoardState(state, moves)
|
||||
|
|
@ -254,11 +275,11 @@ func createNextBoardState(ruleset rules.Ruleset, royale rules.RoyaleRuleset, sta
|
|||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
u, _ := url.ParseRequestURI(snake.URL)
|
||||
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}
|
||||
}
|
||||
|
||||
func sendEndRequest(state *rules.BoardState, snake InternalSnake) {
|
||||
func sendEndRequest(state *rules.BoardState, snake Battlesnake) {
|
||||
requestBody := getIndividualBoardStateForSnake(state, snake, nil)
|
||||
u, _ := url.ParseRequestURI(snake.URL)
|
||||
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
|
||||
for _, snk := range state.Snakes {
|
||||
if snake.ID == snk.ID {
|
||||
|
|
@ -314,8 +335,8 @@ func getIndividualBoardStateForSnake(state *rules.BoardState, snake InternalSnak
|
|||
Board: BoardResponse{
|
||||
Height: state.Height,
|
||||
Width: state.Width,
|
||||
Food: xyFromPointArray(state.Food),
|
||||
Hazards: xyFromPointArray(outOfBounds),
|
||||
Food: coordFromPointArray(state.Food),
|
||||
Hazards: coordFromPointArray(outOfBounds),
|
||||
Snakes: buildSnakesResponse(state.Snakes),
|
||||
},
|
||||
You: snakeResponseFromSnake(youSnake),
|
||||
|
|
@ -331,14 +352,14 @@ func getIndividualBoardStateForSnake(state *rules.BoardState, snake InternalSnak
|
|||
func snakeResponseFromSnake(snake rules.Snake) SnakeResponse {
|
||||
return SnakeResponse{
|
||||
Id: snake.ID,
|
||||
Name: InternalSnakes[snake.ID].Name,
|
||||
Name: Battlesnakes[snake.ID].Name,
|
||||
Health: snake.Health,
|
||||
Body: xyFromPointArray(snake.Body),
|
||||
Body: coordFromPointArray(snake.Body),
|
||||
Latency: 0,
|
||||
Head: xyFromPoint(snake.Body[0]),
|
||||
Head: coordFromPoint(snake.Body[0]),
|
||||
Length: int32(len(snake.Body)),
|
||||
Shout: "",
|
||||
Squad: InternalSnakes[snake.ID].Squad,
|
||||
Squad: Battlesnakes[snake.ID].Squad,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -350,22 +371,22 @@ func buildSnakesResponse(snakes []rules.Snake) []SnakeResponse {
|
|||
return a
|
||||
}
|
||||
|
||||
func xyFromPoint(pt rules.Point) XY {
|
||||
return XY{X: pt.X, Y: pt.Y}
|
||||
func coordFromPoint(pt rules.Point) Coord {
|
||||
return Coord{X: pt.X, Y: pt.Y}
|
||||
}
|
||||
|
||||
func xyFromPointArray(ptArray []rules.Point) []XY {
|
||||
a := make([]XY, 0)
|
||||
func coordFromPointArray(ptArray []rules.Point) []Coord {
|
||||
a := make([]Coord, 0)
|
||||
for _, pt := range ptArray {
|
||||
a = append(a, xyFromPoint(pt))
|
||||
a = append(a, coordFromPoint(pt))
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func buildSnakesFromOptions() []InternalSnake {
|
||||
func buildSnakesFromOptions() []Battlesnake {
|
||||
bodyChars := []rune{'■', '⌀', '●', '⍟', '◘', '☺', '□', '☻'}
|
||||
var numSnakes int
|
||||
var snakes []InternalSnake
|
||||
var snakes []Battlesnake
|
||||
numNames := len(Names)
|
||||
numURLs := len(URLs)
|
||||
numSquads := len(Squads)
|
||||
|
|
@ -431,7 +452,7 @@ func buildSnakesFromOptions() []InternalSnake {
|
|||
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" {
|
||||
snake.Squad = snakeSquad
|
||||
}
|
||||
|
|
@ -442,7 +463,7 @@ func buildSnakesFromOptions() []InternalSnake {
|
|||
|
||||
func printMap(state *rules.BoardState, outOfBounds []rules.Point, gameTurn int32) {
|
||||
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)
|
||||
for i := range board {
|
||||
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))
|
||||
for _, s := range state.Snakes {
|
||||
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 x := int32(0); x < state.Width; x++ {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue