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
101 lines
2.6 KiB
Go
101 lines
2.6 KiB
Go
package ui
|
||
|
||
import (
|
||
"fmt"
|
||
|
||
"github.com/ryanhamamura/c4/snake"
|
||
"github.com/ryanhamamura/via/h"
|
||
)
|
||
|
||
func SnakeLobbyTab(nicknameBind h.H, soloClicks, multiClicks []h.H, activeGames []*snake.SnakeGame) h.H {
|
||
// Solo play buttons
|
||
var soloButtons []h.H
|
||
for i, preset := range snake.GridPresets {
|
||
var click h.H
|
||
if i < len(soloClicks) {
|
||
click = soloClicks[i]
|
||
}
|
||
soloButtons = append(soloButtons,
|
||
h.Button(
|
||
h.Class("btn btn-secondary"),
|
||
h.Type("button"),
|
||
h.Text(fmt.Sprintf("%s (%d×%d)", preset.Name, preset.Width, preset.Height)),
|
||
click,
|
||
),
|
||
)
|
||
}
|
||
|
||
// Multiplayer buttons
|
||
var multiButtons []h.H
|
||
for i, preset := range snake.GridPresets {
|
||
var click h.H
|
||
if i < len(multiClicks) {
|
||
click = multiClicks[i]
|
||
}
|
||
multiButtons = append(multiButtons,
|
||
h.Button(
|
||
h.Class("btn btn-primary"),
|
||
h.Type("button"),
|
||
h.Text(fmt.Sprintf("%s (%d×%d)", preset.Name, preset.Width, preset.Height)),
|
||
click,
|
||
),
|
||
)
|
||
}
|
||
|
||
nicknameField := h.Div(h.Class("mb-4"),
|
||
h.FieldSet(h.Class("fieldset"),
|
||
h.Label(h.Class("label"), h.Text("Your Nickname"), h.Attr("for", "snake-nickname")),
|
||
h.Input(
|
||
h.Class("input input-bordered w-full"),
|
||
h.ID("snake-nickname"),
|
||
h.Type("text"),
|
||
h.Placeholder("Enter your nickname"),
|
||
nicknameBind,
|
||
h.Attr("required"),
|
||
),
|
||
),
|
||
)
|
||
|
||
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...)...),
|
||
)
|
||
|
||
multiSection := h.Div(h.Class("mb-6"),
|
||
h.H3(h.Class("text-lg font-bold mb-2"), h.Text("Create Multiplayer Game")),
|
||
h.Div(append([]h.H{h.Class("flex gap-2 justify-center")}, multiButtons...)...),
|
||
)
|
||
|
||
var gameListEl h.H
|
||
if len(activeGames) > 0 {
|
||
var items []h.H
|
||
for _, g := range activeGames {
|
||
playerCount := g.PlayerCount()
|
||
sizeLabel := fmt.Sprintf("%d×%d", g.State.Width, g.State.Height)
|
||
statusLabel := "Waiting"
|
||
if g.Status == snake.StatusCountdown {
|
||
statusLabel = "Starting soon"
|
||
}
|
||
items = append(items, h.A(
|
||
h.Href("/snake/"+g.ID),
|
||
h.Class("flex justify-between items-center p-3 bg-base-200 rounded-lg hover:bg-base-300 no-underline text-base-content"),
|
||
h.Span(h.Text(fmt.Sprintf("%s — %d/8 players", sizeLabel, playerCount))),
|
||
h.Span(h.Class("text-sm opacity-60"), h.Text(statusLabel)),
|
||
))
|
||
}
|
||
listAttrs := []h.H{h.Class("flex flex-col gap-2")}
|
||
listAttrs = append(listAttrs, items...)
|
||
gameListEl = h.Div(h.Class("mt-6"),
|
||
h.H3(h.Class("text-lg font-bold mb-2 text-center"), h.Text("Join a Game")),
|
||
h.Div(listAttrs...),
|
||
)
|
||
}
|
||
|
||
return h.Div(
|
||
nicknameField,
|
||
soloSection,
|
||
multiSection,
|
||
gameListEl,
|
||
)
|
||
}
|