gcloud, crude killing players functionality

This commit is contained in:
Alec
2021-12-07 01:49:16 -05:00
parent 2debf7be35
commit bad4e3dfc4
12 changed files with 187 additions and 74 deletions

17
.gcloudignore Normal file
View File

@@ -0,0 +1,17 @@
# This file specifies files that are *not* uploaded to Google Cloud
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore
# Node.js dependencies:
node_modules/

1
app.yaml Normal file
View File

@@ -0,0 +1 @@
runtime: nodejs14

View File

@@ -11,7 +11,8 @@ export const globals = {
START_GAME: 'startGame', START_GAME: 'startGame',
PAUSE_TIMER: 'pauseTimer', PAUSE_TIMER: 'pauseTimer',
RESUME_TIMER: 'resumeTimer', RESUME_TIMER: 'resumeTimer',
GET_TIME_REMAINING: 'getTimeRemaining' GET_TIME_REMAINING: 'getTimeRemaining',
KILL_PLAYER: 'killPlayer'
}, },
STATUS: { STATUS: {
LOBBY: "lobby", LOBBY: "lobby",

View File

@@ -3,8 +3,10 @@ import { toast } from "./Toast.js";
import {templates} from "./Templates.js"; import {templates} from "./Templates.js";
export class GameStateRenderer { export class GameStateRenderer {
constructor(gameState) { constructor(gameState, socket) {
this.gameState = gameState; this.gameState = gameState;
this.socket = socket;
this.killPlayerHandlers = {};
this.cardFlipped = false; this.cardFlipped = false;
} }
@@ -61,38 +63,26 @@ export class GameStateRenderer {
// document.getElementById("game-title").appendChild(title); // 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;
document.getElementById("role-image").setAttribute(
'src',
'../images/roles/' + this.gameState.client.gameRole.replaceAll(' ', '') + '.png'
);
document.getElementById("game-role-back").addEventListener('click', () => {
document.getElementById("game-role").style.display = 'flex';
document.getElementById("game-role-back").style.display = 'none';
});
document.getElementById("game-role").addEventListener('click', () => {
document.getElementById("game-role-back").style.display = 'flex';
document.getElementById("game-role").style.display = 'none';
});
}
renderModeratorView() { renderModeratorView() {
let div = document.createElement("div"); let div = document.createElement("div");
div.innerHTML = templates.END_GAME_PROMPT; div.innerHTML = templates.END_GAME_PROMPT;
document.body.appendChild(div); document.body.appendChild(div);
renderModeratorPlayers(this.gameState.people); renderPlayersWithRoleAndAlignmentInfo(this.gameState.people, this.socket, this.gameState.accessCode, this.killPlayerHandlers);
} }
renderPlayerView() {
renderPlayerRole(this.gameState);
renderPlayersWithNoRoleInformation(this.gameState.people, this.killPlayerHandlers);
}
refreshPlayerList(isModerator) {
if (isModerator) {
renderPlayersWithRoleAndAlignmentInfo(this.gameState.people, this.socket, this.gameState.accessCode, this.killPlayerHandlers)
} else {
renderPlayersWithNoRoleInformation(this.gameState.people, this.killPlayerHandlers);
}
}
} }
function renderLobbyPerson(name, userType) { function renderLobbyPerson(name, userType) {
@@ -125,27 +115,95 @@ function removeExistingTitle() {
} }
} }
function renderModeratorPlayers(people) { function renderPlayersWithRoleAndAlignmentInfo(people, socket, accessCode, handlers) {
people.sort(); document.querySelectorAll('.game-player').forEach((el) => {
let pointer = el.dataset.pointer;
if (pointer && handlers[pointer]) {
el.removeEventListener('click', handlers[pointer])
}
el.remove();
});
people.sort((a, b) => {
return a.name >= b.name ? 1 : -1;
});
let teamGood = people.filter((person) => person.alignment === globals.ALIGNMENT.GOOD); let teamGood = people.filter((person) => person.alignment === globals.ALIGNMENT.GOOD);
let teamEvil = people.filter((person) => person.alignment === globals.ALIGNMENT.EVIL); let teamEvil = people.filter((person) => person.alignment === globals.ALIGNMENT.EVIL);
renderGroupOfPlayersFromTeam(teamEvil, globals.ALIGNMENT.EVIL); renderGroupOfPlayers(teamEvil, handlers, accessCode, globals.ALIGNMENT.EVIL, true, socket);
renderGroupOfPlayersFromTeam(teamGood, globals.ALIGNMENT.GOOD); renderGroupOfPlayers(teamGood, handlers, accessCode, globals.ALIGNMENT.GOOD, true, socket);
document.getElementById("players-alive-label").innerText = document.getElementById("players-alive-label").innerText =
'Players: ' + people.filter((person) => !person.out).length + ' / ' + people.length + ' Alive'; 'Players: ' + people.filter((person) => !person.out).length + ' / ' + people.length + ' Alive';
} }
function renderGroupOfPlayersFromTeam(players, alignment) { function renderPlayersWithNoRoleInformation(people, handlers) {
document.querySelectorAll('.game-player').forEach((el) => el.remove());
people.sort((a, b) => {
return a.name >= b.name ? 1 : -1;
});
renderGroupOfPlayers(people, handlers);
document.getElementById("players-alive-label").innerText =
'Players: ' + people.filter((person) => !person.out).length + ' / ' + people.length + ' Alive';
}
function renderGroupOfPlayers(players, handlers, accessCode=null, alignment=null, moderator=false, socket=null) {
for (let player of players) { for (let player of players) {
let container = document.createElement("div"); let container = document.createElement("div");
container.classList.add('moderator-player'); container.classList.add('game-player');
container.innerHTML = templates.MODERATOR_PLAYER; container.dataset.pointer = player.id;
container.querySelector('.moderator-player-name').innerText = player.name; if (alignment) {
let roleElement = container.querySelector('.moderator-player-role') container.innerHTML = templates.MODERATOR_PLAYER;
roleElement.innerText = player.gameRole; } else {
roleElement.classList.add(alignment); container.innerHTML = templates.GAME_PLAYER;
}
container.querySelector('.game-player-name').innerText = player.name;
let roleElement = container.querySelector('.game-player-role')
document.getElementById("player-list-moderator-team-" + alignment).appendChild(container); if (alignment) {
roleElement.classList.add(alignment);
roleElement.innerText = player.gameRole;
document.getElementById("player-list-moderator-team-" + alignment).appendChild(container);
} else {
roleElement.innerText = "Unknown"
document.getElementById("game-player-list").appendChild(container);
}
if (moderator) {
handlers[player.id] = () => {
socket.emit(globals.COMMANDS.KILL_PLAYER, accessCode, player.id);
}
if (player.out) {
container.classList.add('killed');
container.querySelector('.kill-player-button').remove();
} else {
container.querySelector('.kill-player-button').addEventListener('click', handlers[player.id]);
}
}
} }
} }
function renderPlayerRole(gameState) {
let name = document.querySelector('#role-name');
name.innerText = gameState.client.gameRole;
if (gameState.client.alignment === globals.ALIGNMENT.GOOD) {
name.classList.add('good');
} else {
name.classList.add('evil');
}
name.setAttribute("title", gameState.client.gameRole);
document.querySelector('#role-description').innerText = gameState.client.gameRoleDescription;
document.getElementById("role-image").setAttribute(
'src',
'../images/roles/' + gameState.client.gameRole.replaceAll(' ', '') + '.png'
);
document.getElementById("game-role-back").addEventListener('click', () => {
document.getElementById("game-role").style.display = 'flex';
document.getElementById("game-role-back").style.display = 'none';
});
document.getElementById("game-role").addEventListener('click', () => {
document.getElementById("game-role-back").style.display = 'flex';
document.getElementById("game-role").style.display = 'none';
});
}

View File

@@ -25,16 +25,12 @@ export const templates = {
"<div id='end-game-prompt'>" + "<div id='end-game-prompt'>" +
"<button id='end-game-button'>End Game</button>" + "<button id='end-game-button'>End Game</button>" +
"</div>", "</div>",
GAME: PLAYER_GAME_VIEW:
"<div id='game-header'>" + "<div id='game-header'>" +
"<div>" + "<div>" +
"<label for='game-timer'>Time Remaining</label>" + "<label for='game-timer'>Time Remaining</label>" +
"<div id='game-timer'></div>" + "<div id='game-timer'></div>" +
"</div>" + "</div>" +
"<div>" +
"<label for='alive-count'>Players Left</label>" +
"<div id='alive-count'></div>" +
"</div>" +
"</div>" + "</div>" +
"<div id='game-role' style='display:none'>" + "<div id='game-role' style='display:none'>" +
"<h4 id='role-name'></h4>" + "<h4 id='role-name'></h4>" +
@@ -44,6 +40,10 @@ export const templates = {
"<div id='game-role-back'>" + "<div id='game-role-back'>" +
"<h4>Click to reveal your role</h4>" + "<h4>Click to reveal your role</h4>" +
"<p>(click again to hide)</p>" + "<p>(click again to hide)</p>" +
"</div>" +
"<div>" +
"<label id='players-alive-label'></label>" +
"<div id='game-player-list'></div>" +
"</div>", "</div>",
MODERATOR_GAME_VIEW: MODERATOR_GAME_VIEW:
"<div id='game-header'>" + "<div id='game-header'>" +
@@ -58,7 +58,7 @@ export const templates = {
"</div>" + "</div>" +
"<div>" + "<div>" +
"<label id='players-alive-label'></label>" + "<label id='players-alive-label'></label>" +
"<div id='player-list-moderator'>" + "<div id='game-player-list'>" +
"<div class='evil-players'>" + "<div class='evil-players'>" +
"<label class='evil'>Team Evil</label>" + "<label class='evil'>Team Evil</label>" +
"<div id='player-list-moderator-team-evil'></div>" + "<div id='player-list-moderator-team-evil'></div>" +
@@ -71,10 +71,15 @@ export const templates = {
"</div>", "</div>",
MODERATOR_PLAYER: MODERATOR_PLAYER:
"<div>" + "<div>" +
"<div class='moderator-player-name'></div>" + "<div class='game-player-name'></div>" +
"<div class='moderator-player-role'></div>" + "<div class='game-player-role'></div>" +
"</div>" + "</div>" +
"<div>" + "<div>" +
"<button class='moderator-player-button kill-player-button'>Kill \u2694</button>" + "<button class='moderator-player-button kill-player-button'>Kill \u2694</button>" +
"</div>",
GAME_PLAYER:
"<div>" +
"<div class='game-player-name'></div>" +
"<div class='game-player-role'></div>" +
"</div>" "</div>"
} }

View File

@@ -33,9 +33,9 @@ function prepareGamePage(environment, socket, timerWorker) {
} else { } else {
toast('You are connected.', 'success', true, true, 3); toast('You are connected.', 'success', true, true, 3);
console.log(gameState); console.log(gameState);
userId = gameState.client.id; userId = gameState.client.cookie;
UserUtility.setAnonymousUserId(userId, environment); UserUtility.setAnonymousUserId(userId, environment);
let gameStateRenderer = new GameStateRenderer(gameState); let gameStateRenderer = new GameStateRenderer(gameState, socket);
let gameTimerManager; let gameTimerManager;
if (gameState.timerParams) { if (gameState.timerParams) {
gameTimerManager = new GameTimerManager(gameState, socket); gameTimerManager = new GameTimerManager(gameState, socket);
@@ -71,8 +71,8 @@ function processGameState (gameState, userId, socket, gameStateRenderer) {
gameStateRenderer.renderGameHeader(); gameStateRenderer.renderGameHeader();
switch (gameState.client.userType) { switch (gameState.client.userType) {
case globals.USER_TYPES.PLAYER: case globals.USER_TYPES.PLAYER:
document.getElementById("game-state-container").innerHTML = templates.GAME; document.getElementById("game-state-container").innerHTML = templates.PLAYER_GAME_VIEW;
gameStateRenderer.renderPlayerRole(); gameStateRenderer.renderPlayerView();
break; break;
case globals.USER_TYPES.MODERATOR: case globals.USER_TYPES.MODERATOR:
document.querySelector("#start-game-prompt")?.remove(); document.querySelector("#start-game-prompt")?.remove();
@@ -121,9 +121,9 @@ function setClientSocketHandlers(gameStateRenderer, socket, timerWorker, gameTim
socket.emit( socket.emit(
globals.COMMANDS.FETCH_GAME_STATE, globals.COMMANDS.FETCH_GAME_STATE,
gameStateRenderer.gameState.accessCode, gameStateRenderer.gameState.accessCode,
gameStateRenderer.gameState.client.id, gameStateRenderer.gameState.client.cookie,
function (gameState) { function (gameState) {
processGameState(gameState, gameState.client.id, socket, gameStateRenderer); processGameState(gameState, gameState.client.cookie, socket, gameStateRenderer);
} }
); );
}); });
@@ -142,7 +142,7 @@ function displayStartGamePromptForModerators(gameStateRenderer, socket) {
document.getElementById("start-game-button").addEventListener('click', (e) => { document.getElementById("start-game-button").addEventListener('click', (e) => {
e.preventDefault(); e.preventDefault();
if (confirm("Start the game and deal roles?")) { if (confirm("Start the game and deal roles?")) {
socket.emit(globals.COMMANDS.START_GAME, gameStateRenderer.gameState.accessCode, gameStateRenderer.gameState.client.id); socket.emit(globals.COMMANDS.START_GAME, gameStateRenderer.gameState.accessCode, gameStateRenderer.gameState.client.cookie);
} }
}); });

View File

@@ -314,7 +314,6 @@ label[for='moderator'] {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
justify-content: space-evenly;
flex-direction: column; flex-direction: column;
} }
@@ -326,7 +325,7 @@ label[for='moderator'] {
margin-bottom: 1em; margin-bottom: 1em;
} }
.moderator-player { .game-player {
border-left: 2px solid gray; border-left: 2px solid gray;
display: flex; display: flex;
color: #d7d7d7; color: #d7d7d7;
@@ -335,9 +334,10 @@ label[for='moderator'] {
padding: 0 5px; padding: 0 5px;
justify-content: space-between; justify-content: space-between;
margin: 0.5em 0; margin: 0.5em 0;
position: relative;
} }
.moderator-player-name { .game-player-name {
width: 10em; width: 10em;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
@@ -360,6 +360,21 @@ label[for='moderator'] {
min-width: 6em; min-width: 6em;
} }
.killed::after {
content: '\01F480';
position: absolute;
right: -44px;
font-size: 24px;
}
.killed {
filter: grayscale(1);
opacity: 0.6;
pointer-events: none;
}
}
.make-mod-button { .make-mod-button {
background-color: #3f5256; background-color: #3f5256;
font-size: 18px; font-size: 18px;
@@ -378,7 +393,7 @@ label[for='moderator'] {
background-color: #3f5256; background-color: #3f5256;
} }
#player-list-moderator > div { #game-player-list > div {
padding: 2px 10px; padding: 2px 10px;
border-radius: 3px; border-radius: 3px;
margin-bottom: 1em; margin-bottom: 1em;

View File

@@ -1,7 +1,7 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const debugMode = Array.from(process.argv.map((arg) => arg.trim().toLowerCase())).includes('debug'); const debugMode = Array.from(process.argv.map((arg) => arg.trim().toLowerCase())).includes('debug');
const logger = require('../modules/logger')(debugMode); const logger = require('../modules/Logger')(debugMode);
const GameManager = require('../modules/GameManager.js'); const GameManager = require('../modules/GameManager.js');
const gameManager = new GameManager().getInstance(); const gameManager = new GameManager().getInstance();

View File

@@ -8,7 +8,8 @@ const globals = {
START_GAME: 'startGame', START_GAME: 'startGame',
PAUSE_TIMER: 'pauseTimer', PAUSE_TIMER: 'pauseTimer',
RESUME_TIMER: 'resumeTimer', RESUME_TIMER: 'resumeTimer',
GET_TIME_REMAINING: 'getTimeRemaining' GET_TIME_REMAINING: 'getTimeRemaining',
KILL_PLAYER: 'killPlayer'
}, },
STATUS: { STATUS: {
LOBBY: "lobby", LOBBY: "lobby",

View File

@@ -1,7 +1,8 @@
// noinspection DuplicatedCode // noinspection DuplicatedCode
class Person { class Person {
constructor(id, name, userType, gameRole=null, gameRoleDescription=null, alignment=null, assigned=false) { constructor(id, cookie, name, userType, gameRole=null, gameRoleDescription=null, alignment=null, assigned=false) {
this.id = id; this.id = id;
this.cookie = cookie
this.socketId = null; this.socketId = null;
this.name = name; this.name = name;
this.userType = userType; this.userType = userType;

View File

@@ -91,6 +91,18 @@ class GameManager {
} }
} }
}); });
socket.on(globals.CLIENT_COMMANDS.KILL_PLAYER, (accessCode, personId) => {
let game = this.activeGameRunner.activeGames[accessCode];
if (game) {
let person = game.people.find((person) => person.id === personId)
if (person) {
this.logger.debug('game ' + accessCode + ': killing player ' + person.name);
person.out = true;
namespace.in(accessCode).emit(globals.CLIENT_COMMANDS.KILL_PLAYER, )
}
}
})
} }
@@ -154,7 +166,7 @@ function initializeModerator(name, hasDedicatedModerator) {
const userType = hasDedicatedModerator const userType = hasDedicatedModerator
? globals.USER_TYPES.MODERATOR ? globals.USER_TYPES.MODERATOR
: globals.USER_TYPES.TEMPORARY_MODERATOR; : globals.USER_TYPES.TEMPORARY_MODERATOR;
return new Person(createRandomUserId(), name, userType) return new Person(createRandomId(), createRandomId(), name, userType)
} }
function initializePeopleForGame(uniqueCards, moderator) { function initializePeopleForGame(uniqueCards, moderator) {
@@ -180,7 +192,7 @@ function initializePeopleForGame(uniqueCards, moderator) {
} }
while (j < numberOfRoles) { while (j < numberOfRoles) {
people.push(new Person(createRandomUserId(), UsernameGenerator.generate(), globals.USER_TYPES.PLAYER, cards[j].role, cards[j].description, cards[j].team)) people.push(new Person(createRandomId(), createRandomId(), UsernameGenerator.generate(), globals.USER_TYPES.PLAYER, cards[j].role, cards[j].description, cards[j].team))
j ++; j ++;
} }
@@ -197,7 +209,7 @@ function shuffleArray (array) {
return array; return array;
} }
function createRandomUserId () { function createRandomId () {
let id = ''; let id = '';
for (let i = 0; i < globals.USER_SIGNATURE_LENGTH; i++) { for (let i = 0; i < globals.USER_SIGNATURE_LENGTH; i++) {
id += globals.ACCESS_CODE_CHAR_POOL[Math.floor(Math.random() * globals.ACCESS_CODE_CHAR_POOL.length)]; id += globals.ACCESS_CODE_CHAR_POOL[Math.floor(Math.random() * globals.ACCESS_CODE_CHAR_POOL.length)];
@@ -226,11 +238,11 @@ class Singleton {
cookie. Though if a client wants to clear their cookie and reset their connection, there's not much we can do. cookie. Though if a client wants to clear their cookie and reset their connection, there's not much we can do.
The best thing in my opinion is to make it hard for clients to _accidentally_ break their experience. The best thing in my opinion is to make it hard for clients to _accidentally_ break their experience.
*/ */
function handleRequestForGameState(namespace, logger, gameRunner, accessCode, personId, ackFn, socket) { function handleRequestForGameState(namespace, logger, gameRunner, accessCode, personCookie, ackFn, socket) {
const game = gameRunner.activeGames[accessCode]; const game = gameRunner.activeGames[accessCode];
if (game) { if (game) {
let matchingPerson = game.people.find((person) => person.id === personId); let matchingPerson = game.people.find((person) => person.cookie === personCookie);
if (!matchingPerson && game.moderator.id === personId) { if (!matchingPerson && game.moderator.cookie === personCookie) {
matchingPerson = game.moderator; matchingPerson = game.moderator;
} }
if (matchingPerson) { if (matchingPerson) {

View File

@@ -8,10 +8,10 @@ const GameStateCurator = {
function 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. 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, userType: person.userType } ? { name: person.name, cookie: person.cookie, userType: person.userType }
: { : {
name: person.name, name: person.name,
id: person.id, cookie: person.cookie,
userType: person.userType, userType: person.userType,
gameRole: person.gameRole, gameRole: person.gameRole,
gameRoleDescription: person.gameRoleDescription, gameRoleDescription: person.gameRoleDescription,
@@ -27,7 +27,7 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) {
deck: game.deck, deck: game.deck,
people: game.people people: game.people
.filter((person) => { .filter((person) => {
return person.assigned === true && person.id !== client.id return person.assigned === true && person.cookie !== client.cookie
&& (person.userType !== globals.USER_TYPES.MODERATOR && person.userType !== globals.USER_TYPES.TEMPORARY_MODERATOR) && (person.userType !== globals.USER_TYPES.MODERATOR && person.userType !== globals.USER_TYPES.TEMPORARY_MODERATOR)
}) })
.map((filteredPerson) => ({ name: filteredPerson.name, userType: filteredPerson.userType })), .map((filteredPerson) => ({ name: filteredPerson.name, userType: filteredPerson.userType })),
@@ -64,10 +64,11 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) {
function mapPeopleForModerator(people, client) { function mapPeopleForModerator(people, client) {
return people return people
.filter((person) => { .filter((person) => {
return person.assigned === true && person.id !== client.id return person.assigned === true && person.cookie !== client.cookie
}) })
.map((person) => ({ .map((person) => ({
name: person.name, name: person.name,
id: person.id,
userType: person.userType, userType: person.userType,
gameRole: person.gameRole, gameRole: person.gameRole,
gameRoleDescription: person.gameRoleDescription, gameRoleDescription: person.gameRoleDescription,
@@ -79,10 +80,11 @@ function mapPeopleForModerator(people, client) {
function mapPeopleForTempModerator(people, client) { function mapPeopleForTempModerator(people, client) {
return people return people
.filter((person) => { .filter((person) => {
return person.assigned === true && person.id !== client.id return person.assigned === true && person.cookie !== client.cookie
}) })
.map((person) => ({ .map((person) => ({
name: person.name, name: person.name,
id: person.id,
userType: person.userType, userType: person.userType,
out: person.out out: person.out
})); }));