Both game and snake packages had identical PlayerID types and the snake package imported game.GenerateID. Now both use player.ID and player.GenerateID from the shared player package.
177 lines
3.7 KiB
Go
177 lines
3.7 KiB
Go
package snake
|
|
|
|
import (
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/ryanhamamura/c4/player"
|
|
)
|
|
|
|
type Direction int
|
|
|
|
const (
|
|
DirUp Direction = iota
|
|
DirDown
|
|
DirLeft
|
|
DirRight
|
|
)
|
|
|
|
type GameMode int
|
|
|
|
const (
|
|
ModeMultiplayer GameMode = iota // Default (0) - backward compatible
|
|
ModeSinglePlayer // Single player survival mode
|
|
)
|
|
|
|
// Opposite returns true if a and b are 180-degree reversals.
|
|
func (d Direction) Opposite(other Direction) bool {
|
|
switch d {
|
|
case DirUp:
|
|
return other == DirDown
|
|
case DirDown:
|
|
return other == DirUp
|
|
case DirLeft:
|
|
return other == DirRight
|
|
case DirRight:
|
|
return other == DirLeft
|
|
}
|
|
return false
|
|
}
|
|
|
|
type Point struct {
|
|
X int `json:"x"`
|
|
Y int `json:"y"`
|
|
}
|
|
|
|
type Snake struct {
|
|
Body []Point `json:"body"`
|
|
Dir Direction `json:"dir"`
|
|
Alive bool `json:"alive"`
|
|
Growing bool `json:"growing"`
|
|
Color int `json:"color"` // 1-8
|
|
}
|
|
|
|
type GameState struct {
|
|
Width int `json:"width"`
|
|
Height int `json:"height"`
|
|
Snakes []*Snake `json:"snakes"`
|
|
Food []Point `json:"food"`
|
|
}
|
|
|
|
func (gs *GameState) ToJSON() string {
|
|
data, _ := json.Marshal(gs)
|
|
return string(data)
|
|
}
|
|
|
|
func GameStateFromJSON(data string) (*GameState, error) {
|
|
var gs GameState
|
|
if err := json.Unmarshal([]byte(data), &gs); err != nil {
|
|
return nil, err
|
|
}
|
|
return &gs, nil
|
|
}
|
|
|
|
type Status int
|
|
|
|
const (
|
|
StatusWaitingForPlayers Status = iota
|
|
StatusCountdown
|
|
StatusInProgress
|
|
StatusFinished
|
|
)
|
|
|
|
type Player struct {
|
|
ID player.ID
|
|
UserID *string
|
|
Nickname string
|
|
Slot int // 0-7
|
|
}
|
|
|
|
type SnakeGame struct {
|
|
ID string
|
|
State *GameState
|
|
Players []*Player // up to 8
|
|
Status Status
|
|
Winner *Player // nil if draw
|
|
CountdownEnd time.Time // when countdown reaches 0
|
|
RematchGameID *string
|
|
Mode GameMode // ModeMultiplayer or ModeSinglePlayer
|
|
Score int // tracks food eaten in single player
|
|
Speed int // cells per second
|
|
}
|
|
|
|
// SpeedPreset defines a named speed option for the snake game.
|
|
type SpeedPreset struct {
|
|
Name string
|
|
Speed int
|
|
}
|
|
|
|
var SpeedPresets = []SpeedPreset{
|
|
{Name: "Slow", Speed: 5},
|
|
{Name: "Normal", Speed: 7},
|
|
{Name: "Fast", Speed: 10},
|
|
{Name: "Insane", Speed: 15},
|
|
}
|
|
|
|
const DefaultSpeed = 7
|
|
|
|
func (sg *SnakeGame) IsFinished() bool {
|
|
return sg.Status == StatusFinished
|
|
}
|
|
|
|
func (sg *SnakeGame) PlayerCount() int {
|
|
count := 0
|
|
for _, p := range sg.Players {
|
|
if p != nil {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
// GridPreset defines a named grid size option for the snake game.
|
|
type GridPreset struct {
|
|
Name string
|
|
Width int
|
|
Height int
|
|
}
|
|
|
|
var GridPresets = []GridPreset{
|
|
{Name: "Tiny", Width: 15, Height: 15},
|
|
{Name: "Small", Width: 20, Height: 20},
|
|
{Name: "Medium", Width: 30, Height: 20},
|
|
{Name: "Large", Width: 40, Height: 20},
|
|
{Name: "XL", Width: 50, Height: 30},
|
|
}
|
|
|
|
// snapshot returns a shallow copy of the game safe for reading outside the lock.
|
|
// Slices and pointers are shared but the top-level struct is copied.
|
|
func (sg *SnakeGame) snapshot() *SnakeGame {
|
|
cp := *sg
|
|
if sg.State != nil {
|
|
stateCp := *sg.State
|
|
// Copy slices so the caller's iteration is safe
|
|
stateCp.Snakes = make([]*Snake, len(sg.State.Snakes))
|
|
copy(stateCp.Snakes, sg.State.Snakes)
|
|
stateCp.Food = make([]Point, len(sg.State.Food))
|
|
copy(stateCp.Food, sg.State.Food)
|
|
cp.State = &stateCp
|
|
}
|
|
cp.Players = make([]*Player, len(sg.Players))
|
|
copy(cp.Players, sg.Players)
|
|
// Mode and Score are value types, already copied by *sg
|
|
return &cp
|
|
}
|
|
|
|
// SnakeColors are hex color values for CSS, indexed by player slot.
|
|
var SnakeColors = []string{
|
|
"#00b894", // 1: Green
|
|
"#e17055", // 2: Orange
|
|
"#0984e3", // 3: Blue
|
|
"#6c5ce7", // 4: Purple
|
|
"#fd79a8", // 5: Pink
|
|
"#00cec9", // 6: Cyan
|
|
"#d63031", // 7: Red
|
|
"#fdcb6e", // 8: Yellow
|
|
}
|