Bug
createGame correctly rejects a player who's already in a game:
// src/gameManager/index.ts
public createGame(playerId: string, color: Color): Game | undefined {
const game = this.playerMap.get(playerId);
if (game) return undefined; // Player already in a game
...
}
joinGame has no equivalent guard. A player who is already in a game can call joinGame with any open gameId, and the method will succeed.
What goes wrong
When this happens:
playerMap for the joining player is overwritten to point to the new game.
- Their original game still exists in
gameMap, with that player listed as a participant — but playerMap no longer references it.
- The other player in the original game is now stuck waiting for an opponent who has silently moved on.
- The orphaned game can only be reclaimed by
CleaningService once it crosses the stale threshold (100 seconds by default).
Steps to reproduce
- Player A creates a game.
- Player B joins Player A's game — both are now in
playerMap.
- Player B emits
joinGame again with a different open gameId.
Player B is now in two gameMap entries; Player A's game is orphaned.
Fix
Add the same guard used in createGame:
public joinGame(playerId: string, gameId: string): Game | undefined {
if (this.playerMap.get(playerId)) return undefined; // Player already in a game
...
}
One line, consistent with existing patterns.
Bug
createGamecorrectly rejects a player who's already in a game:joinGamehas no equivalent guard. A player who is already in a game can calljoinGamewith any opengameId, and the method will succeed.What goes wrong
When this happens:
playerMapfor the joining player is overwritten to point to the new game.gameMap, with that player listed as a participant — butplayerMapno longer references it.CleaningServiceonce it crosses the stale threshold (100 seconds by default).Steps to reproduce
playerMap.joinGameagain with a different opengameId.Player B is now in two
gameMapentries; Player A's game is orphaned.Fix
Add the same guard used in
createGame:One line, consistent with existing patterns.