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:
Ryan Hamamura
2026-02-04 10:02:40 -10:00
parent f454e0d220
commit e239e948ae
13 changed files with 199 additions and 69 deletions

View File

@@ -5,13 +5,11 @@ import (
)
const (
targetFPS = 60
tickInterval = time.Second / targetFPS
snakeSpeed = 7 // cells per second
moveInterval = time.Second / snakeSpeed
countdownSecondsMultiplayer = 10
countdownSecondsSinglePlayer = 3
inactivityLimit = 60 * time.Second
targetFPS = 60
tickInterval = time.Second / targetFPS
countdownSecondsMultiplayer = 10
countdownSecondsSinglePlayer = 3
inactivityLimit = 60 * time.Second
)
func (si *SnakeGameInstance) startOrResetCountdownLocked() {
@@ -114,18 +112,13 @@ func (si *SnakeGameInstance) gamePhase() {
return
}
// Apply pending directions every tick for responsive input
inputReceived := false
// Track input activity for inactivity timeout
for i := 0; i < 8; i++ {
if si.pendingDir[i] != nil && i < len(si.game.State.Snakes) && si.game.State.Snakes[i] != nil {
si.game.State.Snakes[i].Dir = *si.pendingDir[i]
si.pendingDir[i] = nil
inputReceived = true
if len(si.pendingDirQueue[i]) > 0 {
lastInput = time.Now()
break
}
}
if inputReceived {
lastInput = time.Now()
}
// Inactivity timeout
if time.Since(lastInput) > inactivityLimit {
@@ -138,7 +131,14 @@ func (si *SnakeGameInstance) gamePhase() {
return
}
// Only advance game state at snakeSpeed
// Compute move interval from per-game speed
speed := si.game.Speed
if speed <= 0 {
speed = DefaultSpeed
}
moveInterval := time.Second / time.Duration(speed)
// Only advance game state at game speed
moveAccum += tickInterval
if moveAccum < moveInterval {
si.gameMu.Unlock()
@@ -146,6 +146,14 @@ func (si *SnakeGameInstance) gamePhase() {
}
moveAccum -= moveInterval
// Pop one direction from queue per movement frame
for i := 0; i < 8; i++ {
if len(si.pendingDirQueue[i]) > 0 && i < len(si.game.State.Snakes) && si.game.State.Snakes[i] != nil {
si.game.State.Snakes[i].Dir = si.pendingDirQueue[i][0]
si.pendingDirQueue[i] = si.pendingDirQueue[i][1:]
}
}
state := si.game.State
// Advance snakes