Define KeyPlayerID, KeyUserID, and KeyNickname in the sessions package and use them across all handlers to avoid duplicated magic strings.
178 lines
5.3 KiB
Go
178 lines
5.3 KiB
Go
package lobby
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/ryanhamamura/games/connect4"
|
|
"github.com/ryanhamamura/games/db/repository"
|
|
lobbycomponents "github.com/ryanhamamura/games/features/lobby/components"
|
|
"github.com/ryanhamamura/games/features/lobby/pages"
|
|
appsessions "github.com/ryanhamamura/games/sessions"
|
|
"github.com/ryanhamamura/games/snake"
|
|
|
|
"github.com/alexedwards/scs/v2"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/starfederation/datastar-go/datastar"
|
|
)
|
|
|
|
// HandleLobbyPage renders the main lobby page with active games for logged-in users.
|
|
func HandleLobbyPage(queries *repository.Queries, sessions *scs.SessionManager, snakeStore *snake.SnakeStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
userID := sessions.GetString(r.Context(), appsessions.KeyUserID)
|
|
username := sessions.GetString(r.Context(), "username")
|
|
isLoggedIn := userID != ""
|
|
|
|
var userGames []lobbycomponents.GameListItem
|
|
if isLoggedIn {
|
|
ctx := context.Background()
|
|
games, err := queries.GetUserActiveGames(ctx, &userID)
|
|
if err == nil {
|
|
for _, g := range games {
|
|
isMyTurn := g.Status == 1 && g.CurrentTurn == g.MyColor
|
|
opponentName := ""
|
|
if g.OpponentNickname != nil {
|
|
opponentName = *g.OpponentNickname
|
|
}
|
|
var lastPlayed time.Time
|
|
if g.UpdatedAt != nil {
|
|
lastPlayed = *g.UpdatedAt
|
|
}
|
|
userGames = append(userGames, lobbycomponents.GameListItem{
|
|
ID: g.ID,
|
|
Status: int(g.Status),
|
|
OpponentName: opponentName,
|
|
IsMyTurn: isMyTurn,
|
|
LastPlayed: lastPlayed,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
var activeSnakeGames []pages.SnakeGameListItem
|
|
for _, g := range snakeStore.ActiveGames() {
|
|
statusLabel := "Waiting"
|
|
if g.Status == snake.StatusCountdown {
|
|
statusLabel = "Starting soon"
|
|
}
|
|
activeSnakeGames = append(activeSnakeGames, pages.SnakeGameListItem{
|
|
ID: g.ID,
|
|
Width: g.State.Width,
|
|
Height: g.State.Height,
|
|
PlayerCount: g.PlayerCount(),
|
|
StatusLabel: statusLabel,
|
|
})
|
|
}
|
|
|
|
data := pages.LobbyData{
|
|
IsLoggedIn: isLoggedIn,
|
|
Username: username,
|
|
UserGames: userGames,
|
|
ActiveSnakeGames: activeSnakeGames,
|
|
}
|
|
|
|
if err := pages.LobbyPage(data).Render(r.Context(), w); err != nil {
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
}
|
|
|
|
// HandleCreateGame reads the nickname signal, creates a connect4 game, and redirects via SSE.
|
|
func HandleCreateGame(store *connect4.Store, sessions *scs.SessionManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
type Signals struct {
|
|
Nickname string `json:"nickname"`
|
|
}
|
|
signals := &Signals{}
|
|
if err := datastar.ReadSignals(r, signals); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if signals.Nickname == "" {
|
|
return
|
|
}
|
|
|
|
sessions.Put(r.Context(), appsessions.KeyNickname, signals.Nickname)
|
|
|
|
gi := store.Create()
|
|
sse := datastar.NewSSE(w, r)
|
|
sse.ExecuteScript(fmt.Sprintf("window.location.href='/games/%s'", gi.ID())) //nolint:errcheck
|
|
}
|
|
}
|
|
|
|
// HandleDeleteGame deletes a connect4 game and redirects to the lobby.
|
|
func HandleDeleteGame(store *connect4.Store, sessions *scs.SessionManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
gameID := chi.URLParam(r, "id")
|
|
if gameID == "" {
|
|
http.Error(w, "missing game id", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
store.Delete(gameID) //nolint:errcheck
|
|
|
|
sse := datastar.NewSSE(w, r)
|
|
sse.ExecuteScript("window.location.href='/'") //nolint:errcheck
|
|
}
|
|
}
|
|
|
|
// HandleCreateSnakeGame reads nickname, grid preset, speed, and mode from the request,
|
|
// creates a snake game, and redirects via SSE.
|
|
func HandleCreateSnakeGame(snakeStore *snake.SnakeStore, sessions *scs.SessionManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
type Signals struct {
|
|
Nickname string `json:"nickname"`
|
|
SelectedSpeed int `json:"selectedSpeed"`
|
|
}
|
|
signals := &Signals{}
|
|
if err := datastar.ReadSignals(r, signals); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if signals.Nickname == "" {
|
|
return
|
|
}
|
|
|
|
sessions.Put(r.Context(), appsessions.KeyNickname, signals.Nickname)
|
|
|
|
mode := snake.ModeMultiplayer
|
|
if r.URL.Query().Get("mode") == "solo" {
|
|
mode = snake.ModeSinglePlayer
|
|
}
|
|
|
|
presetIdx, _ := strconv.Atoi(r.URL.Query().Get("preset"))
|
|
if presetIdx < 0 || presetIdx >= len(snake.GridPresets) {
|
|
presetIdx = 0
|
|
}
|
|
preset := snake.GridPresets[presetIdx]
|
|
|
|
speed := snake.DefaultSpeed
|
|
if signals.SelectedSpeed >= 0 && signals.SelectedSpeed < len(snake.SpeedPresets) {
|
|
speed = snake.SpeedPresets[signals.SelectedSpeed].Speed
|
|
}
|
|
|
|
si := snakeStore.Create(preset.Width, preset.Height, mode, speed)
|
|
|
|
sse := datastar.NewSSE(w, r)
|
|
sse.ExecuteScript(fmt.Sprintf("window.location.href='/snake/%s'", si.ID())) //nolint:errcheck
|
|
}
|
|
}
|
|
|
|
// HandleLogout clears the session and redirects to the lobby.
|
|
func HandleLogout(sessions *scs.SessionManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if err := sessions.Destroy(r.Context()); err != nil {
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
sse := datastar.NewSSE(w, r)
|
|
sse.ExecuteScript("window.location.href='/'") //nolint:errcheck
|
|
}
|
|
}
|