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:
@@ -47,7 +47,7 @@ func (ss *SnakeStore) makeNotify(gameID string) func() {
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SnakeStore) Create(width, height int) *SnakeGameInstance {
|
||||
func (ss *SnakeStore) Create(width, height int, mode GameMode) *SnakeGameInstance {
|
||||
id := generateID(4)
|
||||
sg := &SnakeGame{
|
||||
ID: id,
|
||||
@@ -57,6 +57,7 @@ func (ss *SnakeStore) Create(width, height int) *SnakeGameInstance {
|
||||
},
|
||||
Players: make([]*Player, 8),
|
||||
Status: StatusWaitingForPlayers,
|
||||
Mode: mode,
|
||||
}
|
||||
si := &SnakeGameInstance{
|
||||
game: sg,
|
||||
@@ -134,8 +135,8 @@ func (ss *SnakeStore) Delete(id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ActiveGames returns metadata of games that can be joined.
|
||||
// Copies game data to avoid holding nested locks.
|
||||
// ActiveGames returns metadata of multiplayer games that can be joined.
|
||||
// Single player games are excluded. Copies game data to avoid holding nested locks.
|
||||
func (ss *SnakeStore) ActiveGames() []*SnakeGame {
|
||||
ss.gamesMu.RLock()
|
||||
instances := make([]*SnakeGameInstance, 0, len(ss.games))
|
||||
@@ -148,7 +149,7 @@ func (ss *SnakeStore) ActiveGames() []*SnakeGame {
|
||||
for _, si := range instances {
|
||||
si.gameMu.RLock()
|
||||
g := si.game
|
||||
if g.Status == StatusWaitingForPlayers || g.Status == StatusCountdown {
|
||||
if g.Mode == ModeMultiplayer && (g.Status == StatusWaitingForPlayers || g.Status == StatusCountdown) {
|
||||
games = append(games, g)
|
||||
}
|
||||
si.gameMu.RUnlock()
|
||||
@@ -220,7 +221,10 @@ func (si *SnakeGameInstance) Join(player *Player) bool {
|
||||
|
||||
si.notify()
|
||||
|
||||
if si.game.PlayerCount() >= 2 {
|
||||
// Single player starts countdown immediately when 1 player joins
|
||||
if si.game.Mode == ModeSinglePlayer && si.game.PlayerCount() >= 1 {
|
||||
si.startOrResetCountdownLocked()
|
||||
} else if si.game.Mode == ModeMultiplayer && si.game.PlayerCount() >= 2 {
|
||||
si.startOrResetCountdownLocked()
|
||||
}
|
||||
|
||||
@@ -267,9 +271,10 @@ func (si *SnakeGameInstance) CreateRematch() *SnakeGameInstance {
|
||||
// (which acquires gamesMu) to avoid lock ordering deadlock.
|
||||
width := si.game.State.Width
|
||||
height := si.game.State.Height
|
||||
mode := si.game.Mode
|
||||
si.gameMu.Unlock()
|
||||
|
||||
newSI := si.store.Create(width, height)
|
||||
newSI := si.store.Create(width, height, mode)
|
||||
newID := newSI.ID()
|
||||
|
||||
si.gameMu.Lock()
|
||||
|
||||
Reference in New Issue
Block a user