diff --git a/client/config/globals.js b/client/config/globals.js index 901ecf9..52f6f4b 100644 --- a/client/config/globals.js +++ b/client/config/globals.js @@ -5,17 +5,20 @@ export const globals = { ACCESS_CODE_CHAR_POOL: 'abcdefghijklmnopqrstuvwxyz0123456789', COMMANDS: { FETCH_GAME_STATE: 'fetchGameState', - GET_ENVIRONMENT: 'getEnvironment' + GET_ENVIRONMENT: 'getEnvironment', + START_GAME: 'startGame' }, - GAME_STATE: { - LOBBY: 'lobby' + STATUS: { + LOBBY: "lobby", + IN_PROGRESS: "in progress" }, ALIGNMENT: { GOOD: "good", EVIL: "evil" }, EVENTS: { - PLAYER_JOINED: "playerJoined" + PLAYER_JOINED: "playerJoined", + SYNC_GAME_STATE: "syncGameState" }, USER_TYPES: { MODERATOR: "moderator", diff --git a/client/model/Game.js b/client/model/Game.js index 763a34f..d901851 100644 --- a/client/model/Game.js +++ b/client/model/Game.js @@ -4,6 +4,7 @@ export class Game { this.hasTimer = hasTimer; this.moderatorName = moderatorName; this.timerParams = timerParams; - this.hasDedicatedModerator = hasDedicatedModerator + this.hasDedicatedModerator = hasDedicatedModerator; + this.accessCode = null; } } diff --git a/client/modules/GameStateRenderer.js b/client/modules/GameStateRenderer.js index 3c22239..b7cc8e4 100644 --- a/client/modules/GameStateRenderer.js +++ b/client/modules/GameStateRenderer.js @@ -61,6 +61,25 @@ export class GameStateRenderer { cardEl.classList.add('lobby-card') } } + + renderGameHeader() { + let title = document.createElement("h1"); + title.innerText = "Game"; + document.querySelector('#game-title h1')?.remove(); + document.getElementById("game-title").appendChild(title); + } + + renderPlayerRole() { + let name = document.querySelector('#role-name'); + name.innerText = this.gameState.client.gameRole; + if (this.gameState.client.alignment === globals.ALIGNMENT.GOOD) { + name.classList.add('good'); + } else { + name.classList.add('evil'); + } + name.setAttribute("title", this.gameState.client.gameRole); + document.querySelector('#role-description').innerText = this.gameState.client.gameRoleDescription; + } } function renderClient(client, container) { diff --git a/client/modules/Templates.js b/client/modules/Templates.js index ca547d6..51ad6fd 100644 --- a/client/modules/Templates.js +++ b/client/modules/Templates.js @@ -24,5 +24,22 @@ export const templates = { START_GAME_PROMPT: "
" + "" + + "
", + GAME: + "
" + + "
" + + "
" + + "" + + "
" + + "
" + + "
" + + "" + + "
" + + "
" + + "
" + + "
" + + "

" + + "role" + + "

" + "
" } diff --git a/client/scripts/game.js b/client/scripts/game.js index f4426ad..f9a5c52 100644 --- a/client/scripts/game.js +++ b/client/scripts/game.js @@ -2,7 +2,7 @@ import { UserUtility } from "../modules/UserUtility.js"; import { globals } from "../config/globals.js"; import {templates} from "../modules/Templates.js"; import {GameStateRenderer} from "../modules/GameStateRenderer.js"; -import {toast} from "../modules/Toast.js"; +import {cancelCurrentToast, toast} from "../modules/Toast.js"; export const game = () => { socket.emit(globals.COMMANDS.GET_ENVIRONMENT, function(environment) { @@ -15,6 +15,7 @@ export const game = () => { window.location.replace('/not-found'); } else { console.log(gameState); + gameState.accessCode = accessCode; userId = gameState.client.id; UserUtility.setAnonymousUserId(userId, environment); let gameStateRenderer = new GameStateRenderer(gameState); @@ -29,8 +30,9 @@ export const game = () => { }; function processGameState (gameState, userId, socket, gameStateRenderer) { + cancelCurrentToast(); switch (gameState.status) { - case globals.GAME_STATE.LOBBY: + case globals.STATUS.LOBBY: document.getElementById("game-state-container").innerHTML = templates.LOBBY; gameStateRenderer.renderLobbyHeader(); gameStateRenderer.renderLobbyPlayers(); @@ -41,9 +43,16 @@ function processGameState (gameState, userId, socket, gameStateRenderer) { || gameState.userType === globals.USER_TYPES.TEMPORARY_MODERATOR ) ) { - displayStartGamePromptForModerators(); + displayStartGamePromptForModerators(gameStateRenderer); } break; + case globals.STATUS.IN_PROGRESS: + document.querySelector("#start-game-prompt")?.remove(); + gameStateRenderer.gameState = gameState; + document.getElementById("game-state-container").innerHTML = templates.GAME; + gameStateRenderer.renderGameHeader(); + gameStateRenderer.renderPlayerRole(); + break; default: break; } @@ -61,14 +70,32 @@ function setClientSocketHandlers(gameStateRenderer, socket) { || gameStateRenderer.gameState.userType === globals.USER_TYPES.TEMPORARY_MODERATOR ) ) { - displayStartGamePromptForModerators(); + displayStartGamePromptForModerators(gameStateRenderer); } + }); + + socket.on(globals.EVENTS.SYNC_GAME_STATE, () => { + socket.emit( + globals.COMMANDS.FETCH_GAME_STATE, + gameStateRenderer.gameState.accessCode, + gameStateRenderer.gameState.client.id, + function (gameState) { + processGameState(gameState, gameState.client.id, socket, gameStateRenderer); + } + ); }) } -function displayStartGamePromptForModerators() { +function displayStartGamePromptForModerators(gameStateRenderer) { document.getElementById("lobby-players").setAttribute("style", 'margin-bottom: 130px'); let div = document.createElement("div"); div.innerHTML = templates.START_GAME_PROMPT; document.body.appendChild(div); + document.getElementById("start-game-button").addEventListener('click', (e) => { + e.preventDefault(); + if (confirm("Start the game and deal roles?")) { + socket.emit(globals.COMMANDS.START_GAME, gameStateRenderer.gameState.accessCode, gameStateRenderer.gameState.client.id); + } + + }); } diff --git a/client/styles/game.css b/client/styles/game.css index ecdddc9..62c8ef3 100644 --- a/client/styles/game.css +++ b/client/styles/game.css @@ -62,6 +62,61 @@ h1 { font-size: 25px; } +#game-role { + position: relative; + border-bottom: 2px solid gray; + background-color: #e7e7e7; + display: flex; + flex-direction: column; + cursor: pointer; + justify-content: space-between; + max-width: 17em; + border-radius: 3px; + height: 23em; + margin: 0 auto 2em auto; + width: 100%; + box-shadow: 0 1px 1px rgba(0,0,0,0.11), + 0 2px 2px rgba(0,0,0,0.11), + 0 4px 4px rgba(0,0,0,0.11), + 0 8px 8px rgba(0,0,0,0.11), + 0 16px 16px rgba(0,0,0,0.11), + 0 32px 32px rgba(0,0,0,0.11); + /*perspective: 1000px;*/ + /*transform-style: preserve-3d;*/ +} + +#role-name { + position: absolute; + top: 6%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 20px; + font-family: 'diavlo', sans-serif; + width: 95%; + text-align: center; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +#role-image { + position: absolute; + top: 34%; + left: 50%; + transform: translate(-50%, -50%); +} + +#role-description { + overflow: auto; + position: absolute; + bottom: 8%; + left: 50%; + transform: translate(-50%, 0); + font-size: 16px; + width: 78%; + max-height: 7em; +} + #game-link img { width: 20px; margin-left: 0.5em; diff --git a/server/config/globals.js b/server/config/globals.js index a943580..15fa0cc 100644 --- a/server/config/globals.js +++ b/server/config/globals.js @@ -3,7 +3,8 @@ const globals = { ACCESS_CODE_LENGTH: 6, CLIENT_COMMANDS: { FETCH_GAME_STATE: 'fetchGameState', - GET_ENVIRONMENT: 'getEnvironment' + GET_ENVIRONMENT: 'getEnvironment', + START_GAME: 'startGame' }, STATUS: { LOBBY: "lobby", @@ -19,7 +20,8 @@ const globals = { GAME_IS_FULL: "This game is full" }, EVENTS: { - PLAYER_JOINED: "playerJoined" + PLAYER_JOINED: "playerJoined", + SYNC_GAME_STATE: "syncGameState" }, ENVIRONMENT: { LOCAL: "local", diff --git a/server/model/Person.js b/server/model/Person.js index 1df969e..1886e3d 100644 --- a/server/model/Person.js +++ b/server/model/Person.js @@ -1,11 +1,13 @@ +// noinspection DuplicatedCode class Person { - constructor(id, name, userType, gameRole=null, gameRoleDescription=null, assigned=false) { + constructor(id, name, userType, gameRole=null, gameRoleDescription=null, alignment=null, assigned=false) { this.id = id; this.socketId = null; this.name = name; this.userType = userType; this.gameRole = gameRole; this.gameRoleDescription = gameRoleDescription; + this.alignment = alignment; this.assigned = assigned; this.out = false; } diff --git a/server/modules/GameManager.js b/server/modules/GameManager.js index 348fbab..42e2d1c 100644 --- a/server/modules/GameManager.js +++ b/server/modules/GameManager.js @@ -30,7 +30,15 @@ class GameManager { socket.on(globals.CLIENT_COMMANDS.GET_ENVIRONMENT, (ackFn) => { ackFn(this.environment); - }) + }); + + socket.on(globals.CLIENT_COMMANDS.START_GAME, (accessCode, personId) => { + let game = this.activeGameRunner.activeGames[accessCode]; + if (game) { + game.status = globals.STATUS.IN_PROGRESS; + namespace.in(accessCode).emit(globals.EVENTS.SYNC_GAME_STATE); + } + }); } @@ -44,15 +52,12 @@ class GameManager { let moderator = initializeModerator(gameParams.moderatorName, gameParams.hasDedicatedModerator); this.activeGameRunner.activeGames[newAccessCode] = new Game( globals.STATUS.LOBBY, - initializePeopleForGame(gameParams.deck), + initializePeopleForGame(gameParams.deck, moderator), gameParams.deck, gameParams.hasTimer, moderator, gameParams.timerParams ); - if (!gameParams.hasDedicatedModerator) { - this.activeGameRunner.activeGames[newAccessCode].people.push(moderator); - } return Promise.resolve(newAccessCode); } } @@ -96,7 +101,7 @@ function initializeModerator(name, hasDedicatedModerator) { return new Person(createRandomUserId(), name, userType) } -function initializePeopleForGame(uniqueCards) { +function initializePeopleForGame(uniqueCards, moderator) { let people = []; let cards = []; // this will contain copies of each card equal to the quantity. let numberOfRoles = 0; @@ -109,8 +114,18 @@ function initializePeopleForGame(uniqueCards) { cards = shuffleArray(cards); // The deck should probably be shuffled, ey?. - for(let j = 0; j < numberOfRoles; j ++) { - people.push(new Person(createRandomUserId(), UsernameGenerator.generate(), globals.USER_TYPES.PLAYER, cards[j].role, cards[j].description)) + let j = 0; + if (moderator.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { // temporary moderators should be dealt in. + moderator.gameRole = cards[j].role; + moderator.gameRoleDescription = cards[j].description; + moderator.alignment = cards[j].team; + people.push(moderator); + j ++; + } + + while (j < numberOfRoles) { + people.push(new Person(createRandomUserId(), UsernameGenerator.generate(), globals.USER_TYPES.PLAYER, cards[j].role, cards[j].description, cards[j].team)) + j ++; } return people; diff --git a/server/modules/GameStateCurator.js b/server/modules/GameStateCurator.js index 27e32a7..9ed1320 100644 --- a/server/modules/GameStateCurator.js +++ b/server/modules/GameStateCurator.js @@ -13,7 +13,8 @@ function getGameStateBasedOnPermissions(game, person) { name: person.name, id: person.id, gameRole: person.gameRole, - roleDescription: person.roleDescription + gameRoleDescription: person.gameRoleDescription, + alignment: person.alignment } switch (person.userType) { case globals.USER_TYPES.PLAYER: @@ -66,7 +67,8 @@ function mapPeopleForModerator(people, client) { .map((person) => ({ name: person.name, gameRole: person.gameRole, - gameRoleDescription: person.gameRoleDescription + gameRoleDescription: person.gameRoleDescription, + alignment: person.alignment })); }