feat: add configurable speed and expanded grid presets for snake
- Add per-game speed setting with presets (Slow/Normal/Fast/Insane) - Add speed selector UI in snake lobby - Expand grid presets with Tiny (15x15) and XL (50x30) - Auto-calculate cell size based on grid dimensions - Preserve speed setting in rematch games
This commit is contained in:
@@ -47,7 +47,10 @@ func (ss *SnakeStore) makeNotify(gameID string) func() {
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SnakeStore) Create(width, height int, mode GameMode) *SnakeGameInstance {
|
||||
func (ss *SnakeStore) Create(width, height int, mode GameMode, speed int) *SnakeGameInstance {
|
||||
if speed <= 0 {
|
||||
speed = DefaultSpeed
|
||||
}
|
||||
id := generateID(4)
|
||||
sg := &SnakeGame{
|
||||
ID: id,
|
||||
@@ -58,6 +61,7 @@ func (ss *SnakeStore) Create(width, height int, mode GameMode) *SnakeGameInstanc
|
||||
Players: make([]*Player, 8),
|
||||
Status: StatusWaitingForPlayers,
|
||||
Mode: mode,
|
||||
Speed: speed,
|
||||
}
|
||||
si := &SnakeGameInstance{
|
||||
game: sg,
|
||||
@@ -158,14 +162,14 @@ func (ss *SnakeStore) ActiveGames() []*SnakeGame {
|
||||
}
|
||||
|
||||
type SnakeGameInstance struct {
|
||||
game *SnakeGame
|
||||
gameMu sync.RWMutex
|
||||
pendingDir [8]*Direction
|
||||
notify func()
|
||||
persister Persister
|
||||
store *SnakeStore
|
||||
stopCh chan struct{}
|
||||
loopOnce sync.Once
|
||||
game *SnakeGame
|
||||
gameMu sync.RWMutex
|
||||
pendingDirQueue [8][]Direction // queued directions per slot (max 3)
|
||||
notify func()
|
||||
persister Persister
|
||||
store *SnakeStore
|
||||
stopCh chan struct{}
|
||||
loopOnce sync.Once
|
||||
}
|
||||
|
||||
func (si *SnakeGameInstance) ID() string {
|
||||
@@ -231,8 +235,8 @@ func (si *SnakeGameInstance) Join(player *Player) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SetDirection buffers a direction change for the given slot.
|
||||
// The write happens under the game lock to avoid a data race with the game loop.
|
||||
// SetDirection queues a direction change for the given slot.
|
||||
// Validates against the last queued direction (or current snake dir) to prevent 180° turns.
|
||||
func (si *SnakeGameInstance) SetDirection(slot int, dir Direction) {
|
||||
if slot < 0 || slot >= 8 {
|
||||
return
|
||||
@@ -240,13 +244,28 @@ func (si *SnakeGameInstance) SetDirection(slot int, dir Direction) {
|
||||
si.gameMu.Lock()
|
||||
defer si.gameMu.Unlock()
|
||||
|
||||
if si.game.State != nil && slot < len(si.game.State.Snakes) {
|
||||
s := si.game.State.Snakes[slot]
|
||||
if s != nil && s.Alive && !ValidateDirection(s.Dir, dir) {
|
||||
return
|
||||
}
|
||||
if si.game.State == nil || slot >= len(si.game.State.Snakes) {
|
||||
return
|
||||
}
|
||||
si.pendingDir[slot] = &dir
|
||||
s := si.game.State.Snakes[slot]
|
||||
if s == nil || !s.Alive {
|
||||
return
|
||||
}
|
||||
|
||||
// Validate against last queued direction, or current snake direction if queue empty
|
||||
refDir := s.Dir
|
||||
if len(si.pendingDirQueue[slot]) > 0 {
|
||||
refDir = si.pendingDirQueue[slot][len(si.pendingDirQueue[slot])-1]
|
||||
}
|
||||
if !ValidateDirection(refDir, dir) {
|
||||
return
|
||||
}
|
||||
|
||||
// Cap queue at 3 to prevent unbounded growth
|
||||
if len(si.pendingDirQueue[slot]) >= 3 {
|
||||
return
|
||||
}
|
||||
si.pendingDirQueue[slot] = append(si.pendingDirQueue[slot], dir)
|
||||
}
|
||||
|
||||
func (si *SnakeGameInstance) Stop() {
|
||||
@@ -272,9 +291,10 @@ func (si *SnakeGameInstance) CreateRematch() *SnakeGameInstance {
|
||||
width := si.game.State.Width
|
||||
height := si.game.State.Height
|
||||
mode := si.game.Mode
|
||||
speed := si.game.Speed
|
||||
si.gameMu.Unlock()
|
||||
|
||||
newSI := si.store.Create(width, height, mode)
|
||||
newSI := si.store.Create(width, height, mode, speed)
|
||||
newID := newSI.ID()
|
||||
|
||||
si.gameMu.Lock()
|
||||
|
||||
Reference in New Issue
Block a user