Commit Graph
3 Commits
Author SHA1 Message Date
tiennm99 aea9ea8cc0 feat(wsapi): offer a rematch when an online game ends
A finished room now stays alive to ask both players whether they want another,
and restarts with a fresh opening word once both do. Only a room with two
connected humans offers one: a bot has nothing to negotiate, so a bot room
closes the moment its game ends rather than leaking a goroutine and an engine
per finished game.

turn_seq no longer restarts at one. A rematch reuses the same connections, so a
submission still in flight from the previous game could otherwise match a turn
in the new one and be applied to it.

The end-of-game decision sits after the whole select, so every way a game can
end reaches it. Opening the offer from the message arm alone meant the turn
clock — the most common natural ending — closed the room with nothing to accept.

A resume is refused into a finished game, including one waiting on a rematch
answer, so the room now retires the connection being replaced only once it has
agreed to the swap. Retiring it up front ended the game the client was trying
to rejoin, which a duplicated tab was enough to trigger.

attach releases the room it is leaving. Nothing else told that room the
connection had gone, so a session asking for several rooms stranded all but the
last, each parked in select holding a goroutine and a room code for the life of
the process.

Also: RequestRematch is rate limited, because it is the only client message
that fans out to both players and an unbounded one lets a burst fill the
opponent's outbox until their session is closed for falling behind. And a
resume announces itself to the opponent, who was otherwise left watching a
disconnect banner for someone already playing again.
2026-09-05 14:17:47 +07:00
tiennm99 e5b03d041d fix(wsapi): set cache headers on the served frontend
Assets under _app/immutable carry a content hash in the name, so a changed file
is a changed URL and the old one can be cached forever. The shell cannot: it
names those hashed assets, so a copy cached across a deploy points at files that
no longer exist and the app loads into a blank page with nothing in the log.
2026-09-05 12:55:09 +07:00
tiennm99 5935f5c879 feat(wsapi): add websocket server with rooms, turn timers, and bot play
One goroutine owns each room and its engine. The room goroutine starts before
anyone is seated and seating is itself a message, so reading run() is a complete
proof of the concurrency contract rather than a convention to uphold. The bot
searches a frozen copy of the board instead of the live engine.

Reads carry no deadline; liveness is ping-based, because a read timeout cannot
distinguish a healthy player idling in the lobby from a dead socket.

The hub no longer binds a joiner to a seat before the room decides whether to
seat them. Anyone holding a room code could previously resign or play on a
seated player's behalf, and the room code is the only credential online 1v1 has.

cmd/noitu-server serves the API and, when NOITU_WEB_DIR is set, the built
frontend, with unknown paths falling back to index.html for client routes. All
configuration is environment-only and every variable has a working default.
2026-09-05 12:07:57 +07:00