This repository has been archived by the owner on May 11, 2023. It is now read-only.
/
create.go
140 lines (116 loc) · 3.38 KB
/
create.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package rules
import (
"errors"
"github.com/battlesnakeio/engine/controller/pb"
uuid "github.com/satori/go.uuid"
)
// GameMode represents the mode the game is running in
type GameMode string
const (
// GameModeSinglePlayer represents the game running in single player mode, this means the game will
// run until the only snake in the game dies
GameModeSinglePlayer GameMode = "single-player"
// GameModeMultiPlayer represents when there is more then 1 snake in the game, this means the game will
// run until there is zero or one snakes left alive in the game.
GameModeMultiPlayer GameMode = "multi-player"
)
// CreateInitialGame creates a new game based on the create request passed in
func CreateInitialGame(req *pb.CreateRequest) (*pb.Game, []*pb.GameFrame, error) {
snakes, err := getSnakes(req)
if err != nil {
return nil, nil, err
}
food, err := generateFood(req, snakes)
if err != nil {
return nil, nil, err
}
id := uuid.NewV4().String()
game := &pb.Game{
ID: id,
Width: req.Width,
Height: req.Height,
Status: string(GameStatusStopped),
SnakeTimeout: 1000, // TODO: make this configurable
Mode: string(GameModeMultiPlayer),
MaxTurnsToNextFoodSpawn: req.MaxTurnsToNextFoodSpawn,
}
if len(snakes) == 1 {
game.Mode = string(GameModeSinglePlayer)
}
frames := []*pb.GameFrame{
{
Turn: 0,
Food: food,
Snakes: snakes,
},
}
notifyGameStart(game, frames[0])
return game, frames, nil
}
func isTournamentBoardSize(req *pb.CreateRequest) bool {
return isSmallBoard(req) || isMediumBoard(req) || isLargeBoard(req)
}
func isSmallBoard(req *pb.CreateRequest) bool {
return req.Width == 7 && req.Height == 7
}
func isMediumBoard(req *pb.CreateRequest) bool {
return req.Width == 11 && req.Height == 11
}
func isLargeBoard(req *pb.CreateRequest) bool {
return req.Width == 19 && req.Height == 19
}
func getTournamentStartPoint(size, index int32, snakes []*pb.Snake) *pb.Point {
if size == 7 {
return smallStarts[index]
} else if size == 11 {
return mediumStarts[index]
} else if size == 19 {
return largeStarts[index]
}
return getUnoccupiedPoint(size, size, []*pb.Point{}, snakes)
}
func getSnakes(req *pb.CreateRequest) ([]*pb.Snake, error) {
var snakes []*pb.Snake
for index, opts := range req.Snakes {
var startPoint *pb.Point
if isTournamentBoardSize(req) {
startPoint = getTournamentStartPoint(req.Width, int32(index), snakes)
} else {
startPoint = getUnoccupiedPoint(req.Width, req.Height, []*pb.Point{}, snakes)
}
if startPoint == nil {
return nil, errors.New("no unoccupied spots left for new snake")
}
snake := &pb.Snake{
ID: opts.ID,
Name: opts.Name,
URL: opts.URL,
Health: 100,
Body: []*pb.Point{
startPoint,
startPoint.Clone(),
startPoint.Clone(),
},
}
if len(snake.ID) == 0 {
snake.ID = uuid.NewV4().String()
}
for _, s := range snakes {
if s.ID == snake.ID {
return nil, errors.New("duplicate snake id found, create aborted")
}
}
snakes = append(snakes, snake)
}
return snakes, nil
}
func generateFood(req *pb.CreateRequest, snakes []*pb.Snake) ([]*pb.Point, error) {
food := []*pb.Point{}
for i := int32(0); i < req.Food; i++ {
p := getUnoccupiedPoint(req.Width, req.Height, food, snakes)
if p != nil {
food = append(food, p)
}
}
return food, nil
}