// One live game. Every player in it holds this same object.
type Room = { game: string; code: string; id: number; moves: number };
const games = ['chess', 'checkers'];
// The game server: one room per game and code, for as long as it runs.
class GameServer {
private rooms = new Map<string, Room>();
private created = 0;
// Makes one new room. Room numbers count this server's creations.
private create(game: string, code: string): Room {
this.created++;
return { game, code, id: this.created, moves: 0 };
}
// The whole key: the game, and the code in capitals, so "abcd" finds ABCD.
private keyFor(game: string, code: string): string {
if (!games.includes(game)) throw new Error(`unknown game: ${game}`);
if (!/^[A-Za-z]{4}$/.test(code)) throw new Error(`a code is four letters: ${code}`);
return `${game}/${code.toUpperCase()}`;
}
// A player joins a game by its code: the open room, or a new one.
join(game: string, code: string): Room {
const key = this.keyFor(game, code);
let room = this.rooms.get(key);
if (!room) {
room = this.create(game, code.toUpperCase());
this.rooms.set(key, room);
}
return room;
}
// How many rooms the server keeps open.
openRooms(): number {
return this.rooms.size;
}
}
The server is the owner, and a join is the keyed lookup: on a miss it makes the room and keeps it; on a hit it returns the room it kept, with whatever moves it has. The key is the whole identity of a game, the game and the code in capitals, so “abcd” and “ABCD” share and chess and checkers with the same letters do not. Building the key first, and rejecting a bad request before anything is made, keeps the room numbers honest. The checker ran two servers with the same codes, so a map shared by every server would have merged them, and it counted creations, so a room made for a rejected join or made and thrown away would have shown. In TypeScript the key has to be a string: an object key is compared by identity, so every lookup would miss. In Go the key is a comparable struct, and the server’s lock covers lookup and creation together, since players join from several goroutines. In Rust the map keeps one Rc and every player gets a clone of it.