fix(games): reject duplicate queue entries

This commit is contained in:
RissRIce 2026-08-12 21:03:08 -06:00
parent 97ce177dd3
commit a64e4dc1b8
3 changed files with 16 additions and 2 deletions

View file

@ -70,6 +70,9 @@ func (a *app) handleGame(s ssh.Session) {
case errors.Is(err, games.ErrNoOpponent):
_ = conn.Send(errEnvelope("no opponent found — try again later"))
_ = s.Exit(1)
case errors.Is(err, games.ErrAlreadyQueued):
_ = conn.Send(errEnvelope("this account is already queued for that game"))
_ = s.Exit(1)
case err != nil && !errors.Is(err, games.ErrUnknownGame):
// Unknown-game is already handled above; anything else is a wait abort
// (e.g. the agent disconnected) and needs no message.

View file

@ -77,9 +77,13 @@ func (a *app) handleGameWS(w http.ResponseWriter, r *http.Request) {
defer func() { _ = a.st.EndSession(sessID) }()
_ = p.Send(map[string]any{"type": "queued", "game": gameID})
if err := a.mm.Play(context.Background(), gameID, p); errors.Is(err, games.ErrNoOpponent) {
err = a.mm.Play(context.Background(), gameID, p)
if errors.Is(err, games.ErrNoOpponent) {
_ = p.Send(errEnvelope("no opponent found — try again later"))
}
if errors.Is(err, games.ErrAlreadyQueued) {
_ = p.Send(errEnvelope("this account is already queued for that game"))
}
}
// wsPlayer adapts a gorilla WebSocket connection to games.PlayerIO. The match

View file

@ -13,6 +13,9 @@ var ErrNoOpponent = errors.New("no opponent found")
// ErrUnknownGame means the requested game id is not in the registry.
var ErrUnknownGame = errors.New("unknown game")
// ErrAlreadyQueued means the same player already has a connection waiting for this game.
var ErrAlreadyQueued = errors.New("player already queued")
// Store persists finished matches and tracks per-game ELO ratings. The SQLite
// store implements it; the matchmaker stays storage-agnostic.
type Store interface {
@ -77,7 +80,11 @@ func (mm *Matchmaker) Play(ctx context.Context, gameID string, io PlayerIO) erro
}
mm.mu.Lock()
if w, waiting := mm.queue[gameID]; waiting && w.io.Name() != io.Name() {
if w, waiting := mm.queue[gameID]; waiting {
if w.io.Name() == io.Name() {
mm.mu.Unlock()
return ErrAlreadyQueued
}
// An opponent is waiting — pair up and run the match.
delete(mm.queue, gameID)
mm.mu.Unlock()