diff --git a/client/src/config/globals.js b/client/src/config/globals.js index b8a95f0..6d07eb2 100644 --- a/client/src/config/globals.js +++ b/client/src/config/globals.js @@ -4,7 +4,7 @@ export const globals = { MAX_CUSTOM_ROLE_NAME_LENGTH: 30, MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH: 500, TOAST_DURATION_DEFAULT: 6, - ACCESS_CODE_LENGTH: 6, + ACCESS_CODE_LENGTH: 4, PLAYER_ID_COOKIE_KEY: 'play-werewolf-anon-id', ACCESS_CODE_CHAR_POOL: 'abcdefghijklmnopqrstuvwxyz0123456789', COMMANDS: { diff --git a/server/config/globals.js b/server/config/globals.js index 1c16e11..57fab5c 100644 --- a/server/config/globals.js +++ b/server/config/globals.js @@ -1,6 +1,7 @@ const globals = { ACCESS_CODE_CHAR_POOL: 'BCDFGHJKLMNPQRSTVWXYZ0123456789', - ACCESS_CODE_LENGTH: 6, + ACCESS_CODE_LENGTH: 4, + ACCESS_CODE_GENERATION_ATTEMPTS: 50, CLOCK_TICK_INTERVAL_MILLIS: 10, STALE_GAME_HOURS: 12, CLIENT_COMMANDS: { @@ -33,7 +34,8 @@ const globals = { }, ERROR_MESSAGE: { GAME_IS_FULL: 'This game is full', - BAD_CREATE_REQUEST: 'Game has invalid options.' + BAD_CREATE_REQUEST: 'Game has invalid options.', + NO_UNIQUE_ACCESS_CODE: 'Could not generate a unique access code.' }, EVENTS: { PLAYER_JOINED: 'playerJoined', diff --git a/server/modules/GameManager.js b/server/modules/GameManager.js index 1ba9d0d..506fa92 100644 --- a/server/modules/GameManager.js +++ b/server/modules/GameManager.js @@ -171,7 +171,10 @@ class GameManager { } else { // to avoid excessive memory build-up, every time a game is created, check for and purge any stale games. pruneStaleGames(this.activeGameRunner.activeGames, this.activeGameRunner.timerThreads, this.logger); - const newAccessCode = this.generateAccessCode(); + const newAccessCode = this.generateAccessCode(globals.ACCESS_CODE_CHAR_POOL); + if (newAccessCode === null) { + return Promise.reject(globals.ERROR_MESSAGE.NO_UNIQUE_ACCESS_CODE); + } const moderator = initializeModerator(gameParams.moderatorName, gameParams.hasDedicatedModerator); moderator.assigned = true; if (gameParams.timerParams !== null) { @@ -205,15 +208,23 @@ class GameManager { } }; - generateAccessCode = () => { - const numLetters = globals.ACCESS_CODE_CHAR_POOL.length; - const codeDigits = []; - let iterations = globals.ACCESS_CODE_LENGTH; - while (iterations > 0) { - iterations--; - codeDigits.push(globals.ACCESS_CODE_CHAR_POOL[getRandomInt(numLetters)]); + generateAccessCode = (charPool) => { + const charCount = charPool.length; + let codeDigits, accessCode; + let attempts = 0; + while (!accessCode || (this.activeGameRunner.activeGames[accessCode] && attempts < globals.ACCESS_CODE_GENERATION_ATTEMPTS)) { + codeDigits = []; + let iterations = globals.ACCESS_CODE_LENGTH; + while (iterations > 0) { + iterations--; + codeDigits.push(charPool[getRandomInt(charCount)]); + } + accessCode = codeDigits.join(''); + attempts ++; } - return codeDigits.join(''); + return this.activeGameRunner.activeGames[accessCode] + ? null + : accessCode; }; transferModeratorPowers = (game, person, namespace, logger) => { diff --git a/spec/unit/server/modules/GameManager_Spec.js b/spec/unit/server/modules/GameManager_Spec.js index 9dd5be8..2f10f01 100644 --- a/spec/unit/server/modules/GameManager_Spec.js +++ b/spec/unit/server/modules/GameManager_Spec.js @@ -260,4 +260,24 @@ describe('GameManager', () => { expect(gameManager.namespace.in().emit).toHaveBeenCalledWith(globals.EVENTS.NEW_SPECTATOR, jasmine.anything()); }); }); + + describe('#generateAccessCode', () => { + it('should continue to generate access codes up to the max attempts when the generated code is already in use by another game', () => { + gameManager.activeGameRunner.activeGames = { + 'AAAA': {} + }; + + const accessCode = gameManager.generateAccessCode(['A']); + expect(accessCode).toEqual(null); // we might the max generation attempts of 50. + }); + + it('should generate and return a unique access code', () => { + gameManager.activeGameRunner.activeGames = { + 'AAAA': {} + }; + + const accessCode = gameManager.generateAccessCode(['B']); + expect(accessCode).toEqual('BBBB'); + }); + }) });