DEV 1247: Add a new map generator interface (#71)
* reorganize code * first draft of map generator interfaces * add explicit random interface to board helpers * implement standard map * rename Generator to GameMap * allow initializing snakes separately from placing them * add random number generator to Settings * updates to GameMap interface * add helpers for creating and updating BoardState with maps
This commit is contained in:
parent
1c3f434841
commit
dab9178a55
16 changed files with 916 additions and 160 deletions
78
board.go
78
board.go
|
|
@ -1,9 +1,5 @@
|
|||
package rules
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
type BoardState struct {
|
||||
Turn int32
|
||||
Height int32
|
||||
|
|
@ -13,6 +9,20 @@ type BoardState struct {
|
|||
Hazards []Point
|
||||
}
|
||||
|
||||
type Point struct {
|
||||
X int32
|
||||
Y int32
|
||||
}
|
||||
|
||||
type Snake struct {
|
||||
ID string
|
||||
Body []Point
|
||||
Health int32
|
||||
EliminatedCause string
|
||||
EliminatedOnTurn int32
|
||||
EliminatedBy string
|
||||
}
|
||||
|
||||
// NewBoardState returns an empty but fully initialized BoardState
|
||||
func NewBoardState(width, height int32) *BoardState {
|
||||
return &BoardState{
|
||||
|
|
@ -49,15 +59,15 @@ func (prevState *BoardState) Clone() *BoardState {
|
|||
// "default" board state with snakes and food.
|
||||
// In a real game, the engine may generate the board without calling this
|
||||
// function, or customize the results based on game-specific settings.
|
||||
func CreateDefaultBoardState(width int32, height int32, snakeIDs []string) (*BoardState, error) {
|
||||
func CreateDefaultBoardState(rand Rand, width int32, height int32, snakeIDs []string) (*BoardState, error) {
|
||||
initialBoardState := NewBoardState(width, height)
|
||||
|
||||
err := PlaceSnakesAutomatically(initialBoardState, snakeIDs)
|
||||
err := PlaceSnakesAutomatically(rand, initialBoardState, snakeIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = PlaceFoodAutomatically(initialBoardState)
|
||||
err = PlaceFoodAutomatically(rand, initialBoardState)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -66,14 +76,14 @@ func CreateDefaultBoardState(width int32, height int32, snakeIDs []string) (*Boa
|
|||
}
|
||||
|
||||
// PlaceSnakesAutomatically initializes the array of snakes based on the provided snake IDs and the size of the board.
|
||||
func PlaceSnakesAutomatically(b *BoardState, snakeIDs []string) error {
|
||||
func PlaceSnakesAutomatically(rand Rand, b *BoardState, snakeIDs []string) error {
|
||||
if isKnownBoardSize(b) {
|
||||
return PlaceSnakesFixed(b, snakeIDs)
|
||||
return PlaceSnakesFixed(rand, b, snakeIDs)
|
||||
}
|
||||
return PlaceSnakesRandomly(b, snakeIDs)
|
||||
return PlaceSnakesRandomly(rand, b, snakeIDs)
|
||||
}
|
||||
|
||||
func PlaceSnakesFixed(b *BoardState, snakeIDs []string) error {
|
||||
func PlaceSnakesFixed(rand Rand, b *BoardState, snakeIDs []string) error {
|
||||
b.Snakes = make([]Snake, len(snakeIDs))
|
||||
|
||||
for i := 0; i < len(snakeIDs); i++ {
|
||||
|
|
@ -116,7 +126,7 @@ func PlaceSnakesFixed(b *BoardState, snakeIDs []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func PlaceSnakesRandomly(b *BoardState, snakeIDs []string) error {
|
||||
func PlaceSnakesRandomly(rand Rand, b *BoardState, snakeIDs []string) error {
|
||||
b.Snakes = make([]Snake, len(snakeIDs))
|
||||
|
||||
for i := 0; i < len(snakeIDs); i++ {
|
||||
|
|
@ -127,7 +137,7 @@ func PlaceSnakesRandomly(b *BoardState, snakeIDs []string) error {
|
|||
}
|
||||
|
||||
for i := 0; i < len(b.Snakes); i++ {
|
||||
unoccupiedPoints := getEvenUnoccupiedPoints(b)
|
||||
unoccupiedPoints := GetEvenUnoccupiedPoints(b)
|
||||
if len(unoccupiedPoints) <= 0 {
|
||||
return ErrorNoRoomForSnake
|
||||
}
|
||||
|
|
@ -139,8 +149,30 @@ func PlaceSnakesRandomly(b *BoardState, snakeIDs []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Adds all snakes without body coordinates to the board.
|
||||
// This allows GameMaps to access the list of snakes and perform initial placement.
|
||||
func InitializeSnakes(b *BoardState, snakeIDs []string) {
|
||||
b.Snakes = make([]Snake, len(snakeIDs))
|
||||
|
||||
for i := 0; i < len(snakeIDs); i++ {
|
||||
b.Snakes[i] = Snake{
|
||||
ID: snakeIDs[i],
|
||||
Health: SnakeMaxHealth,
|
||||
Body: []Point{},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PlaceSnake adds a snake to the board with the given ID and body coordinates.
|
||||
func PlaceSnake(b *BoardState, snakeID string, body []Point) error {
|
||||
// Update an existing snake that already has a body
|
||||
for index, snake := range b.Snakes {
|
||||
if snake.ID == snakeID {
|
||||
b.Snakes[index].Body = body
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Add a new snake
|
||||
b.Snakes = append(b.Snakes, Snake{
|
||||
ID: snakeID,
|
||||
Health: SnakeMaxHealth,
|
||||
|
|
@ -150,14 +182,14 @@ func PlaceSnake(b *BoardState, snakeID string, body []Point) error {
|
|||
}
|
||||
|
||||
// PlaceFoodAutomatically initializes the array of food based on the size of the board and the number of snakes.
|
||||
func PlaceFoodAutomatically(b *BoardState) error {
|
||||
func PlaceFoodAutomatically(rand Rand, b *BoardState) error {
|
||||
if isKnownBoardSize(b) {
|
||||
return PlaceFoodFixed(b)
|
||||
return PlaceFoodFixed(rand, b)
|
||||
}
|
||||
return PlaceFoodRandomly(b, int32(len(b.Snakes)))
|
||||
return PlaceFoodRandomly(rand, b, int32(len(b.Snakes)))
|
||||
}
|
||||
|
||||
func PlaceFoodFixed(b *BoardState) error {
|
||||
func PlaceFoodFixed(rand Rand, b *BoardState) error {
|
||||
centerCoord := Point{(b.Width - 1) / 2, (b.Height - 1) / 2}
|
||||
|
||||
// Place 1 food within exactly 2 moves of each snake, but never towards the center or in a corner
|
||||
|
|
@ -220,7 +252,7 @@ func PlaceFoodFixed(b *BoardState) error {
|
|||
|
||||
// Finally, always place 1 food in center of board for dramatic purposes
|
||||
isCenterOccupied := true
|
||||
unoccupiedPoints := getUnoccupiedPoints(b, true)
|
||||
unoccupiedPoints := GetUnoccupiedPoints(b, true)
|
||||
for _, point := range unoccupiedPoints {
|
||||
if point == centerCoord {
|
||||
isCenterOccupied = false
|
||||
|
|
@ -236,9 +268,9 @@ func PlaceFoodFixed(b *BoardState) error {
|
|||
}
|
||||
|
||||
// PlaceFoodRandomly adds up to n new food to the board in random unoccupied squares
|
||||
func PlaceFoodRandomly(b *BoardState, n int32) error {
|
||||
func PlaceFoodRandomly(rand Rand, b *BoardState, n int32) error {
|
||||
for i := int32(0); i < n; i++ {
|
||||
unoccupiedPoints := getUnoccupiedPoints(b, false)
|
||||
unoccupiedPoints := GetUnoccupiedPoints(b, false)
|
||||
if len(unoccupiedPoints) > 0 {
|
||||
newFood := unoccupiedPoints[rand.Intn(len(unoccupiedPoints))]
|
||||
b.Food = append(b.Food, newFood)
|
||||
|
|
@ -254,9 +286,9 @@ func absInt32(n int32) int32 {
|
|||
return n
|
||||
}
|
||||
|
||||
func getEvenUnoccupiedPoints(b *BoardState) []Point {
|
||||
func GetEvenUnoccupiedPoints(b *BoardState) []Point {
|
||||
// Start by getting unoccupied points
|
||||
unoccupiedPoints := getUnoccupiedPoints(b, true)
|
||||
unoccupiedPoints := GetUnoccupiedPoints(b, true)
|
||||
|
||||
// Create a new array to hold points that are even
|
||||
evenUnoccupiedPoints := []Point{}
|
||||
|
|
@ -269,7 +301,7 @@ func getEvenUnoccupiedPoints(b *BoardState) []Point {
|
|||
return evenUnoccupiedPoints
|
||||
}
|
||||
|
||||
func getUnoccupiedPoints(b *BoardState, includePossibleMoves bool) []Point {
|
||||
func GetUnoccupiedPoints(b *BoardState, includePossibleMoves bool) []Point {
|
||||
pointIsOccupied := map[int32]map[int32]bool{}
|
||||
for _, p := range b.Food {
|
||||
if _, xExists := pointIsOccupied[p.X]; !xExists {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue