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

@@ -21,6 +21,8 @@ type LobbyProps struct {
SnakeSoloClicks []h.H
SnakeMultiClicks []h.H
ActiveSnakeGames []*snake.SnakeGame
SelectedSpeedIndex int
SpeedSelectClicks []h.H
}
func LobbyView(p LobbyProps) h.H {
@@ -41,7 +43,7 @@ func LobbyView(p LobbyProps) h.H {
var tabContent h.H
if p.ActiveTab == "snake" {
tabContent = SnakeLobbyTab(p.SnakeNicknameBind, p.SnakeSoloClicks, p.SnakeMultiClicks, p.ActiveSnakeGames)
tabContent = SnakeLobbyTab(p.SnakeNicknameBind, p.SnakeSoloClicks, p.SnakeMultiClicks, p.ActiveSnakeGames, p.SelectedSpeedIndex, p.SpeedSelectClicks)
} else {
tabContent = connect4LobbyContent(p)
}

View File

@@ -45,10 +45,7 @@ func SnakeBoard(sg *snake.SnakeGame) h.H {
}
// Cell size scales with grid dimensions
cellSize := 20
if state.Width <= 20 {
cellSize = 24
}
cellSize := cellSizeForGrid(state.Width, state.Height)
var rows []h.H
for y := 0; y < state.Height; y++ {
@@ -94,3 +91,22 @@ func SnakeBoard(sg *snake.SnakeGame) h.H {
attrs = append(attrs, rows...)
return h.Div(attrs...)
}
func cellSizeForGrid(width, height int) int {
maxDim := width
if height > maxDim {
maxDim = height
}
switch {
case maxDim <= 15:
return 28
case maxDim <= 20:
return 24
case maxDim <= 30:
return 20
case maxDim <= 40:
return 16
default:
return 14
}
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/ryanhamamura/via/h"
)
func SnakeLobbyTab(nicknameBind h.H, soloClicks, multiClicks []h.H, activeGames []*snake.SnakeGame) h.H {
func SnakeLobbyTab(nicknameBind h.H, soloClicks, multiClicks []h.H, activeGames []*snake.SnakeGame, selectedSpeedIndex int, speedSelectClicks []h.H) h.H {
// Solo play buttons
var soloButtons []h.H
for i, preset := range snake.GridPresets {
@@ -56,6 +56,29 @@ func SnakeLobbyTab(nicknameBind h.H, soloClicks, multiClicks []h.H, activeGames
),
)
// Speed selector
var speedButtons []h.H
for i, preset := range snake.SpeedPresets {
btnClass := "btn btn-sm"
if i == selectedSpeedIndex {
btnClass += " btn-active"
}
var click h.H
if i < len(speedSelectClicks) {
click = speedSelectClicks[i]
}
speedButtons = append(speedButtons, h.Button(
h.Class(btnClass),
h.Type("button"),
h.Text(preset.Name),
click,
))
}
speedSelector := h.Div(h.Class("mb-4"),
h.Label(h.Class("label"), h.Text("Speed")),
h.Div(append([]h.H{h.Class("btn-group")}, speedButtons...)...),
)
soloSection := h.Div(h.Class("mb-6"),
h.H3(h.Class("text-lg font-bold mb-2"), h.Text("Play Solo")),
h.Div(append([]h.H{h.Class("flex gap-2 justify-center")}, soloButtons...)...),
@@ -93,6 +116,7 @@ func SnakeLobbyTab(nicknameBind h.H, soloClicks, multiClicks []h.H, activeGames
return h.Div(
nicknameField,
speedSelector,
soloSection,
multiSection,
gameListEl,