WIP: Add multiplayer Snake game
N-player (2-8) real-time Snake game alongside Connect 4. Lobby has tabs to switch between games. Players join via invite link with 10-second countdown. Game loop runs at tick-based intervals with NATS pub/sub for state sync. Keyboard input not yet working (Datastar keydown binding issue still under investigation).
This commit is contained in:
148
snake/types.go
Normal file
148
snake/types.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package snake
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Direction int
|
||||
|
||||
const (
|
||||
DirUp Direction = iota
|
||||
DirDown
|
||||
DirLeft
|
||||
DirRight
|
||||
)
|
||||
|
||||
// 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 PlayerID string
|
||||
|
||||
type Player struct {
|
||||
ID PlayerID
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Grid presets
|
||||
type GridPreset struct {
|
||||
Name string
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
var GridPresets = []GridPreset{
|
||||
{Name: "Small", Width: 20, Height: 20},
|
||||
{Name: "Medium", Width: 30, Height: 20},
|
||||
{Name: "Large", Width: 40, Height: 20},
|
||||
}
|
||||
|
||||
// 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)
|
||||
return &cp
|
||||
}
|
||||
|
||||
// Snake colors (hex values for CSS)
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user