diff --git a/client/config/globals.js b/client/config/globals.js index ea4eb80..f667864 100644 --- a/client/config/globals.js +++ b/client/config/globals.js @@ -35,8 +35,8 @@ export const globals = { PRODUCTION: "production" }, USER_TYPE_ICONS: { - PLAYER: ' \uD83C\uDFAE', - MODERATOR: ' \uD83D\uDC51', - TEMP_MOD: ' \uD83C\uDFAE\uD83D\uDC51' + player: ' \uD83C\uDFAE', + moderator: ' \uD83D\uDC51', + 'player / temp mod': ' \uD83C\uDFAE\uD83D\uDC51' } }; diff --git a/client/images/pause-button.svg b/client/images/pause-button.svg new file mode 100644 index 0000000..76b6c6c --- /dev/null +++ b/client/images/pause-button.svg @@ -0,0 +1,8 @@ + + + Layer 1 + + + + + diff --git a/client/images/play-button.svg b/client/images/play-button.svg new file mode 100644 index 0000000..51cc5e1 --- /dev/null +++ b/client/images/play-button.svg @@ -0,0 +1,7 @@ + + + Layer 1 + + + + diff --git a/client/modules/GameStateRenderer.js b/client/modules/GameStateRenderer.js index 5a2725b..1f30dfd 100644 --- a/client/modules/GameStateRenderer.js +++ b/client/modules/GameStateRenderer.js @@ -10,33 +10,25 @@ export class GameStateRenderer { renderLobbyPlayers() { document.querySelectorAll('.lobby-player').forEach((el) => el.remove()) let lobbyPlayersContainer = document.getElementById("lobby-players"); - if (this.gameState.userType !== globals.USER_TYPES.MODERATOR) { - let modEl = document.createElement("div"); - modEl.innerText = this.gameState.moderator.name; - modEl.classList.add('lobby-player'); - lobbyPlayersContainer.appendChild(modEl); + if (this.gameState.client.userType === globals.USER_TYPES.PLAYER) { + lobbyPlayersContainer.appendChild(renderLobbyPerson(this.gameState.moderator.name, this.gameState.moderator.userType)) } for (let person of this.gameState.people) { - let personEl = document.createElement("div"); - personEl.innerText = person.name; - personEl.classList.add('lobby-player'); - lobbyPlayersContainer.appendChild(personEl); + lobbyPlayersContainer.appendChild(renderLobbyPerson(person.name,person.userType)) } - let playerCount; - if (this.gameState.userType === globals.USER_TYPES.MODERATOR) { - playerCount = this.gameState.people.length; - } else { - playerCount = 1 + this.gameState.people.length; + let playerCount = this.gameState.people.filter((person) => person.userType === globals.USER_TYPES.PLAYER).length; + if (this.gameState.moderator.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { + playerCount += 1; + } + if (this.gameState.client.userType === globals.USER_TYPES.PLAYER) { + playerCount += 1; } document.querySelector("label[for='lobby-players']").innerText = - "Other People ( " + playerCount + " )"; + "Other People (" + playerCount + "/" + getGameSize(this.gameState.deck) + " Players)"; } renderLobbyHeader() { - let existingTitle = document.querySelector('#game-link h1'); - if (existingTitle) { - existingTitle.remove(); - } + removeExistingTitle(); let title = document.createElement("h1"); title.innerText = "Lobby"; document.getElementById("game-title").appendChild(title); @@ -62,9 +54,9 @@ export class GameStateRenderer { } renderGameHeader() { + removeExistingTitle(); // let title = document.createElement("h1"); // title.innerText = "Game"; - // document.querySelector('#game-title h1')?.remove(); // document.getElementById("game-title").appendChild(title); } @@ -99,13 +91,18 @@ export class GameStateRenderer { } } -function renderLobbyPlayer(name, userType) { +function renderLobbyPerson(name, userType) { let el = document.createElement("div"); - el.innerHTML = "" - clientEl.innerText = client.name + ' (you)'; - clientEl.classList.add('lobby-player'); - clientEl.classList.add('lobby-player-client'); - container.prepend(clientEl); + let personNameEl = document.createElement("div"); + let personTypeEl = document.createElement("div"); + personNameEl.innerText = name; + personTypeEl.innerText = userType + globals.USER_TYPE_ICONS[userType]; + el.classList.add('lobby-player'); + + el.appendChild(personNameEl); + el.appendChild(personTypeEl); + + return el; } function getGameSize(cards) { @@ -116,3 +113,10 @@ function getGameSize(cards) { return quantity; } + +function removeExistingTitle() { + let existingTitle = document.querySelector('#game-title h1'); + if (existingTitle) { + existingTitle.remove(); + } +} diff --git a/client/modules/GameTimerManager.js b/client/modules/GameTimerManager.js index 9fac66f..3c1dcc9 100644 --- a/client/modules/GameTimerManager.js +++ b/client/modules/GameTimerManager.js @@ -1,24 +1,34 @@ import {globals} from "../config/globals.js"; export class GameTimerManager { - constructor() { - - } - - startGameTimer (hours, minutes, tickRate, soundManager, timerWorker) { - if (window.Worker) { - timerWorker.onmessage = function (e) { - if (e.data.hasOwnProperty('timeRemainingInMilliseconds') && e.data.timeRemainingInMilliseconds > 0) { - document.getElementById('game-timer').innerText = e.data.displayTime; - } - }; - const totalTime = convertFromHoursToMilliseconds(hours) + convertFromMinutesToMilliseconds(minutes); - timerWorker.postMessage({ totalTime: totalTime, tickInterval: tickRate }); + constructor(gameState, socket) { + this.gameState = gameState; + this.playListener = () => { + socket.emit(globals.COMMANDS.RESUME_TIMER, this.gameState.accessCode); + } + this.pauseListener = () => { + socket.emit(globals.COMMANDS.PAUSE_TIMER, this.gameState.accessCode); } } + // startGameTimer (hours, minutes, tickRate, soundManager, timerWorker) { + // if (window.Worker) { + // timerWorker.onmessage = function (e) { + // if (e.data.hasOwnProperty('timeRemainingInMilliseconds') && e.data.timeRemainingInMilliseconds > 0) { + // document.getElementById('game-timer').innerText = e.data.displayTime; + // } + // }; + // const totalTime = convertFromHoursToMilliseconds(hours) + convertFromMinutesToMilliseconds(minutes); + // timerWorker.postMessage({ totalTime: totalTime, tickInterval: tickRate }); + // } + // } + resumeGameTimer(totalTime, tickRate, soundManager, timerWorker) { if (window.Worker) { + if (this.gameState.client.userType !== globals.USER_TYPES.PLAYER) { + this.swapToPauseButton(); + } + let timer = document.getElementById('game-timer'); timer.classList.remove('paused'); timer.innerText = totalTime < 60000 @@ -35,6 +45,10 @@ export class GameTimerManager { pauseGameTimer(timerWorker, timeRemaining) { if (window.Worker) { + if (this.gameState.client.userType !== globals.USER_TYPES.PLAYER) { + this.swapToPlayButton(); + } + timerWorker.postMessage('stop'); let timer = document.getElementById('game-timer'); timer.innerText = timeRemaining < 60000 @@ -45,6 +59,10 @@ export class GameTimerManager { } displayPausedTime(time) { + if (this.gameState.client.userType !== globals.USER_TYPES.PLAYER) { + this.swapToPlayButton(); + } + let timer = document.getElementById('game-timer'); timer.innerText = time < 60000 ? returnHumanReadableTime(time, true) @@ -53,17 +71,17 @@ export class GameTimerManager { } attachTimerSocketListeners(socket, timerWorker, gameStateRenderer) { - if (!socket.hasListeners(globals.EVENTS.START_TIMER)) { - socket.on(globals.EVENTS.START_TIMER, () => { - this.startGameTimer( - gameStateRenderer.gameState.timerParams.hours, - gameStateRenderer.gameState.timerParams.minutes, - globals.CLOCK_TICK_INTERVAL_MILLIS, - null, - timerWorker - ) - }); - } + // if (!socket.hasListeners(globals.EVENTS.START_TIMER)) { + // socket.on(globals.EVENTS.START_TIMER, () => { + // this.startGameTimer( + // gameStateRenderer.gameState.timerParams.hours, + // gameStateRenderer.gameState.timerParams.minutes, + // globals.CLOCK_TICK_INTERVAL_MILLIS, + // null, + // timerWorker + // ) + // }); + // } if(!socket.hasListeners(globals.COMMANDS.PAUSE_TIMER)) { socket.on(globals.COMMANDS.PAUSE_TIMER, (timeRemaining) => { @@ -88,6 +106,32 @@ export class GameTimerManager { }); } } + + swapToPlayButton() { + let currentBtn = document.querySelector('#play-pause img'); + if (currentBtn) { + currentBtn.removeEventListener('click', this.pauseListener); + currentBtn.remove(); + } + + let playBtn = document.createElement('img'); + playBtn.setAttribute('src', '../images/play-button.svg'); + playBtn.addEventListener('click', this.playListener); + document.getElementById('play-pause').appendChild(playBtn); + } + + swapToPauseButton() { + let currentBtn = document.querySelector('#play-pause img'); + if (currentBtn) { + currentBtn.removeEventListener('click', this.playListener); + currentBtn.remove(); + } + + let pauseBtn = document.createElement('img'); + pauseBtn.setAttribute('src', '../images/pause-button.svg'); + pauseBtn.addEventListener('click', this.pauseListener); + document.getElementById('play-pause').appendChild(pauseBtn); + } } diff --git a/client/modules/Templates.js b/client/modules/Templates.js index dcdf771..00c6e73 100644 --- a/client/modules/Templates.js +++ b/client/modules/Templates.js @@ -22,7 +22,6 @@ export const templates = { "" + "", GAME: - "
" + "
" + "
" + "" + @@ -43,16 +42,15 @@ export const templates = { "

(click again to hide)

" + "
", MODERATOR_GAME_VIEW: - "
" + - "

Moderator

" + "
" + "
" + - "" + - "
" + - "
" + - "
" + - "" + - "" + + "
" + + "" + + "
" + + "
" + + "
" + + + "
" + "
" + "
" + "" + diff --git a/client/scripts/game.js b/client/scripts/game.js index 1d0035a..397460c 100644 --- a/client/scripts/game.js +++ b/client/scripts/game.js @@ -35,10 +35,10 @@ function prepareGamePage(environment, socket, timerWorker) { let gameStateRenderer = new GameStateRenderer(gameState); let gameTimerManager; if (gameState.timerParams) { - gameTimerManager = new GameTimerManager(); + gameTimerManager = new GameTimerManager(gameState, socket); } setClientSocketHandlers(gameStateRenderer, socket, timerWorker, gameTimerManager); - displayClientInfo(gameState.client.name, gameState.userType); + displayClientInfo(gameState.client.name, gameState.client.userType); processGameState(gameState, userId, socket, gameStateRenderer); } }); @@ -56,8 +56,8 @@ function processGameState (gameState, userId, socket, gameStateRenderer) { if ( gameState.isFull && ( - gameState.userType === globals.USER_TYPES.MODERATOR - || gameState.userType === globals.USER_TYPES.TEMPORARY_MODERATOR + gameState.client.userType === globals.USER_TYPES.MODERATOR + || gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR ) ) { displayStartGamePromptForModerators(gameStateRenderer, socket); @@ -67,21 +67,14 @@ function processGameState (gameState, userId, socket, gameStateRenderer) { document.querySelector("#start-game-prompt")?.remove(); gameStateRenderer.gameState = gameState; gameStateRenderer.renderGameHeader(); - if (gameState.userType === globals.USER_TYPES.PLAYER || gameState.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { + if (gameState.client.userType === globals.USER_TYPES.PLAYER || gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { document.getElementById("game-state-container").innerHTML = templates.GAME; gameStateRenderer.renderPlayerRole(); - } else if (gameState.userType === globals.USER_TYPES.MODERATOR) { + } else if (gameState.client.userType === globals.USER_TYPES.MODERATOR) { document.getElementById("game-state-container").innerHTML = templates.MODERATOR_GAME_VIEW; gameStateRenderer.renderModeratorView(); - console.log(gameState); - console.log(gameState.accessCode); - document.getElementById("pause-button").addEventListener('click', () => { - socket.emit(globals.COMMANDS.PAUSE_TIMER, gameState.accessCode); - }); - document.getElementById("play-button").addEventListener('click', () => { - socket.emit(globals.COMMANDS.RESUME_TIMER, gameState.accessCode); - }) } + socket.emit(globals.COMMANDS.GET_TIME_REMAINING, gameState.accessCode); break; default: break; @@ -91,14 +84,7 @@ function processGameState (gameState, userId, socket, gameStateRenderer) { function displayClientInfo(name, userType) { document.getElementById("client-name").innerText = name; document.getElementById("client-user-type").innerText = userType; - - if (userType === globals.USER_TYPES.MODERATOR) { - document.getElementById("client-user-type").innerText += globals.USER_TYPE_ICONS.MODERATOR; - } else if (userType === globals.USER_TYPES.PLAYER) { - document.getElementById("client-user-type").innerText += globals.USER_TYPE_ICONS.PLAYER; - } else if (userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { - document.getElementById("client-user-type").innerText += globals.USER_TYPE_ICONS.TEMP_MOD; - } + document.getElementById("client-user-type").innerText += globals.USER_TYPE_ICONS[userType]; } function setClientSocketHandlers(gameStateRenderer, socket, timerWorker, gameTimerManager) { @@ -110,8 +96,8 @@ function setClientSocketHandlers(gameStateRenderer, socket, timerWorker, gameTim if ( gameIsFull && ( - gameStateRenderer.gameState.userType === globals.USER_TYPES.MODERATOR - || gameStateRenderer.gameState.userType === globals.USER_TYPES.TEMPORARY_MODERATOR + gameStateRenderer.gameState.client.userType === globals.USER_TYPES.MODERATOR + || gameStateRenderer.gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR ) ) { displayStartGamePromptForModerators(gameStateRenderer, socket); diff --git a/client/styles/GLOBAL.css b/client/styles/GLOBAL.css index b8fdd3c..f43b683 100644 --- a/client/styles/GLOBAL.css +++ b/client/styles/GLOBAL.css @@ -57,7 +57,7 @@ h3 { label { color: #d7d7d7; font-family: 'signika-negative', sans-serif; - font-size: 18px; + font-size: 20px; font-weight: normal; } diff --git a/client/styles/game.css b/client/styles/game.css index f284138..7c91678 100644 --- a/client/styles/game.css +++ b/client/styles/game.css @@ -1,4 +1,8 @@ .lobby-player, #moderator { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; background-color: black; color: whitesmoke; padding: 10px; @@ -14,6 +18,10 @@ border: 2px solid #21ba45; } +.lobby-player div:nth-child(2) { + color: #21ba45; +} + #moderator.moderator-client { border: 2px solid lightgray; } @@ -182,7 +190,8 @@ h1 { } #client-container { - max-width: 30em; + max-width: 35em; + margin-top: 2em; } #client { @@ -198,7 +207,7 @@ h1 { #client-name { color: whitesmoke; font-family: 'diavlo', sans-serif; - font-size: 37px; + font-size: 30px; } #client-user-type { @@ -219,13 +228,6 @@ label[for='moderator'] { font-size: 30px; } -label[for='lobby-players'] { - font-family: 'diavlo', sans-serif; - color: #21ba45; - filter: drop-shadow(2px 2px 4px black); - font-size: 30px; -} - #start-game-prompt { display: flex; align-items: center; @@ -268,10 +270,46 @@ label[for='lobby-players'] { border: 2px solid #1c8a36; } +#play-pause { + display: flex; + align-items: center; +} + +#play-pause img { + cursor: pointer; + user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + width: 60px; +} + +#play-pause img:hover { + filter: brightness(0.5); +} + +#play-pause img:active { + transform: scale(0.95); +} + .paused { animation: pulse 0.75s linear infinite alternate; } +#game-header { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-evenly; +} + +.timer-container-moderator { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; +} + @keyframes pulse { from { color: rgba(255, 255, 255, 0.1); diff --git a/server/config/globals.js b/server/config/globals.js index e1b72ae..bd62171 100644 --- a/server/config/globals.js +++ b/server/config/globals.js @@ -7,7 +7,8 @@ const globals = { GET_ENVIRONMENT: 'getEnvironment', START_GAME: 'startGame', PAUSE_TIMER: 'pauseTimer', - RESUME_TIMER: 'resumeTimer' + RESUME_TIMER: 'resumeTimer', + GET_TIME_REMAINING: 'getTimeRemaining' }, STATUS: { LOBBY: "lobby", diff --git a/server/modules/ActiveGameRunner.js b/server/modules/ActiveGameRunner.js index 742e12b..58ab466 100644 --- a/server/modules/ActiveGameRunner.js +++ b/server/modules/ActiveGameRunner.js @@ -60,7 +60,7 @@ class ActiveGameRunner { minutes: game.timerParams.minutes }); game.startTime = new Date().toJSON(); - namespace.in(game.accessCode).emit(globals.GAME_PROCESS_COMMANDS.START_TIMER); + //namespace.in(game.accessCode).emit(globals.GAME_PROCESS_COMMANDS.START_TIMER); } } diff --git a/server/modules/GameManager.js b/server/modules/GameManager.js index c90be74..9d30c5c 100644 --- a/server/modules/GameManager.js +++ b/server/modules/GameManager.js @@ -39,6 +39,7 @@ class GameManager { game.status = globals.STATUS.IN_PROGRESS; namespace.in(accessCode).emit(globals.EVENTS.SYNC_GAME_STATE); if (game.hasTimer) { + game.timerParams.paused = true; this.activeGameRunner.runGame(game, namespace); } } @@ -58,7 +59,7 @@ class GameManager { }); } } - }) + }); socket.on(globals.CLIENT_COMMANDS.RESUME_TIMER, (accessCode) => { this.logger.trace(accessCode); @@ -74,7 +75,22 @@ class GameManager { }); } } - }) + }); + + socket.on(globals.CLIENT_COMMANDS.GET_TIME_REMAINING, (accessCode) => { + let game = this.activeGameRunner.activeGames[accessCode]; + if (game) { + let thread = this.activeGameRunner.timerThreads[accessCode]; + if (thread) { + thread.send({ + command: globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING, + accessCode: accessCode, + socketId: socket.id, + logLevel: this.logger.logLevel + }); + } + } + }); } @@ -250,7 +266,7 @@ function handleRequestForGameState(namespace, logger, gameRunner, accessCode, pe game.isFull = isFull; socket.to(accessCode).emit( globals.EVENTS.PLAYER_JOINED, - {name: unassignedPerson.name}, + {name: unassignedPerson.name, userType: unassignedPerson.userType}, isFull ); } else { diff --git a/server/modules/GameStateCurator.js b/server/modules/GameStateCurator.js index 47b402d..ad2543a 100644 --- a/server/modules/GameStateCurator.js +++ b/server/modules/GameStateCurator.js @@ -2,19 +2,17 @@ const globals = require("../config/globals") const GameStateCurator = { getGameStateFromPerspectiveOfPerson: (game, person, gameRunner, socket, logger) => { - if (game.timerParams && game.status === globals.STATUS.IN_PROGRESS) { - getTimeRemaining(game.accessCode, gameRunner, socket, logger) - } return getGameStateBasedOnPermissions(game, person, gameRunner); } } function getGameStateBasedOnPermissions(game, person, gameRunner) { let client = game.status === globals.STATUS.LOBBY // people won't be able to know their role until past the lobby stage. - ? { name: person.name, id: person.id } + ? { name: person.name, id: person.id, userType: person.userType } : { name: person.name, id: person.id, + userType: person.userType, gameRole: person.gameRole, gameRoleDescription: person.gameRoleDescription, alignment: person.alignment @@ -25,12 +23,12 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) { accessCode: game.accessCode, status: game.status, moderator: mapPerson(game.moderator), - userType: globals.USER_TYPES.PLAYER, client: client, deck: game.deck, people: game.people .filter((person) => { - return person.assigned === true && person.id !== client.id && person.userType !== globals.USER_TYPES.MODERATOR + return person.assigned === true && person.id !== client.id + && (person.userType !== globals.USER_TYPES.MODERATOR && person.userType !== globals.USER_TYPES.TEMPORARY_MODERATOR) }) .map((filteredPerson) => ({ name: filteredPerson.name, userType: filteredPerson.userType })), timerParams: game.timerParams, @@ -41,7 +39,6 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) { accessCode: game.accessCode, status: game.status, moderator: mapPerson(game.moderator), - userType: globals.USER_TYPES.MODERATOR, client: client, deck: game.deck, people: mapPeopleForModerator(game.people, client), @@ -53,7 +50,6 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) { accessCode: game.accessCode, status: game.status, moderator: mapPerson(game.moderator), - userType: globals.USER_TYPES.TEMPORARY_MODERATOR, client: client, deck: game.deck, people: mapPeopleForTempModerator(game.people, client), @@ -72,6 +68,7 @@ function mapPeopleForModerator(people, client) { }) .map((person) => ({ name: person.name, + userType: person.userType, gameRole: person.gameRole, gameRoleDescription: person.gameRoleDescription, alignment: person.alignment @@ -85,23 +82,12 @@ function mapPeopleForTempModerator(people, client) { }) .map((person) => ({ name: person.name, + userType: person.userType })); } function mapPerson(person) { - return { name: person.name }; -} - -function getTimeRemaining(accessCode, gameRunner, socket, logger) { - let thread = gameRunner.timerThreads[accessCode]; - if (thread) { - thread.send({ - command: globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING, - accessCode: accessCode, - socketId: socket.id, - logLevel: logger.logLevel - }); - } + return { name: person.name, userType: person.userType }; } module.exports = GameStateCurator; diff --git a/server/modules/ServerTimer.js b/server/modules/ServerTimer.js index e17f801..32a9049 100644 --- a/server/modules/ServerTimer.js +++ b/server/modules/ServerTimer.js @@ -36,7 +36,7 @@ class ServerTimer { this.totalTime = null; } - runTimer () { + runTimer (pausedInitially=true) { let total = convertFromHoursToMilliseconds(this.hours) + convertFromMinutesToMilliseconds(this.minutes); this.totalTime = total; this.currentTimeInMillis = total; @@ -47,18 +47,22 @@ class ServerTimer { this.timesUpResolver = resolve; }); const instance = this; - this.ticking = setTimeout(function () { - stepFn( - instance, - expected - ); - }, this.tickInterval); + if (!pausedInitially) { + this.ticking = setTimeout(function () { + stepFn( + instance, + expected + ); + }, this.tickInterval); + } return this.timesUpPromise; } stopTimer() { - clearTimeout(this.ticking); + if (this.ticking) { + clearTimeout(this.ticking); + } let now = Date.now(); this.logger.debug( 'ELAPSED (PAUSE): ' + (now - this.start) + 'ms (~'