Rename module path github.com/ryanhamamura/c4 to github.com/ryanhamamura/games across go.mod, all source files, and golangci config.
177 lines
5.3 KiB
Go
177 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"
|
|
"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(), "user_id")
|
|
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(), "nickname", 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(), "nickname", 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
|
|
}
|
|
}
|