Files
games/snake/types.go
Ryan Hamamura c6885a069b
All checks were successful
CI / Deploy / test (pull_request) Successful in 14s
CI / Deploy / lint (pull_request) Successful in 25s
CI / Deploy / deploy (pull_request) Has been skipped
refactor: rename Go module from c4 to games
Rename module path github.com/ryanhamamura/c4 to
github.com/ryanhamamura/games across go.mod, all source files,
and golangci config.
2026-03-02 20:41:20 -10:00

177 lines
3.7 KiB
Go

package snake
import (
"encoding/json"
"time"
"github.com/ryanhamamura/games/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
}