feat: add single player snake mode

Add solo mode where players survive as long as possible while tracking
score (food eaten). Single player games start with a shorter 3-second
countdown vs 10 seconds for multiplayer, maintain exactly 1 food item
for classic snake feel, and end when the player dies rather than when
one player remains.

- Add GameMode type (ModeMultiplayer/ModeSinglePlayer) and Score field
- Filter single player games from "Join a Game" lobby list
- Show "Ready?" and "Score: X" UI for single player mode
- Hide invite link for single player games
- Preserve game mode on rematch
This commit is contained in:
Ryan Hamamura
2026-02-04 07:33:02 -10:00
parent 7faf94fa6d
commit f454e0d220
14 changed files with 205 additions and 78 deletions

24
main.go
View File

@@ -152,17 +152,27 @@ func main() {
snakeNickname = c.Signal(username)
}
// Snake create game actions — one per preset
var snakePresetClicks []h.H
// Snake create game actions — one per preset for solo and multiplayer
var snakeSoloClicks []h.H
var snakeMultiClicks []h.H
for _, preset := range snake.GridPresets {
w, ht := preset.Width, preset.Height
snakePresetClicks = append(snakePresetClicks, c.Action(func() {
snakeSoloClicks = append(snakeSoloClicks, c.Action(func() {
name := snakeNickname.String()
if name == "" {
return
}
c.Session().Set("nickname", name)
si := snakeStore.Create(w, ht)
si := snakeStore.Create(w, ht, snake.ModeSinglePlayer)
c.Redirectf("/snake/%s", si.ID())
}).OnClick())
snakeMultiClicks = append(snakeMultiClicks, c.Action(func() {
name := snakeNickname.String()
if name == "" {
return
}
c.Session().Set("nickname", name)
si := snakeStore.Create(w, ht, snake.ModeMultiplayer)
c.Redirectf("/snake/%s", si.ID())
}).OnClick())
}
@@ -181,7 +191,8 @@ func main() {
TabClickConnect4: tabClickConnect4.OnClick(),
TabClickSnake: tabClickSnake.OnClick(),
SnakeNicknameBind: snakeNickname.Bind(),
SnakePresetClicks: snakePresetClicks,
SnakeSoloClicks: snakeSoloClicks,
SnakeMultiClicks: snakeMultiClicks,
ActiveSnakeGames: snakeStore.ActiveGames(),
})
})
@@ -599,7 +610,8 @@ func main() {
content = append(content, ui.SnakeBoard(sg))
}
if sg.Status == snake.StatusWaitingForPlayers || sg.Status == snake.StatusCountdown {
// Only show invite link for multiplayer games
if sg.Mode == snake.ModeMultiplayer && (sg.Status == snake.StatusWaitingForPlayers || sg.Status == snake.StatusCountdown) {
content = append(content, ui.SnakeInviteLink(sg.ID))
}