swapping moderator

This commit is contained in:
Alec
2021-12-16 23:05:15 -05:00
parent 342ae5b80b
commit 133d3a6a03
10 changed files with 207 additions and 54 deletions

View File

@@ -45,6 +45,7 @@ export const globals = {
USER_TYPE_ICONS: {
player: ' \uD83C\uDFAE',
moderator: ' \uD83D\uDC51',
'player / temp mod': ' \uD83C\uDFAE\uD83D\uDC51'
'player / temp mod': ' \uD83C\uDFAE\uD83D\uDC51',
spectator: ' \uD83D\uDC7B'
}
};

View File

@@ -68,7 +68,7 @@ export class GameStateRenderer {
let modTransferButton = document.getElementById("mod-transfer-button");
modTransferButton.addEventListener(
"click", () => {
this.displayAvailableModerators()
this.displayAvailableModerators();
ModalManager.displayModal(
"transfer-mod-modal",
"transfer-mod-modal-background",
@@ -79,6 +79,15 @@ export class GameStateRenderer {
this.renderPlayersWithRoleAndAlignmentInfo();
}
renderTempModView() {
let div = document.createElement("div");
div.innerHTML = templates.END_GAME_PROMPT;
document.body.appendChild(div);
renderPlayerRole(this.gameState);
this.renderPlayersWithNoRoleInformationUnlessRevealed(true);
}
renderPlayerView(isKilled=false) {
if (isKilled) {
let clientUserType = document.getElementById("client-user-type");
@@ -87,6 +96,10 @@ export class GameStateRenderer {
}
}
renderPlayerRole(this.gameState);
this.renderPlayersWithNoRoleInformationUnlessRevealed(false);
}
renderSpectatorView() {
this.renderPlayersWithNoRoleInformationUnlessRevealed();
}
@@ -123,12 +136,26 @@ export class GameStateRenderer {
}
renderPlayersWithNoRoleInformationUnlessRevealed() {
renderPlayersWithNoRoleInformationUnlessRevealed(tempMod = false) {
if (tempMod) {
document.querySelectorAll('.game-player').forEach((el) => {
let pointer = el.dataset.pointer;
if (pointer && this.killPlayerHandlers[pointer]) {
el.removeEventListener('click', this.killPlayerHandlers[pointer]);
delete this.killPlayerHandlers[pointer];
}
if (pointer && this.revealRoleHandlers[pointer]) {
el.removeEventListener('click', this.revealRoleHandlers[pointer]);
delete this.revealRoleHandlers[pointer];
}
el.remove();
});
}
document.querySelectorAll('.game-player').forEach((el) => el.remove());
this.gameState.people.sort((a, b) => {
return a.name >= b.name ? 1 : -1;
});
renderGroupOfPlayers(this.gameState.people, this.killPlayerHandlers);
renderGroupOfPlayers(this.gameState, this.killPlayerHandlers, this.revealRoleHandlers, this.gameState.accessCode, null, tempMod, this.socket);
document.getElementById("players-alive-label").innerText =
'Players: ' + this.gameState.people.filter((person) => !person.out).length + ' / ' + this.gameState.people.length + ' Alive';
@@ -153,27 +180,32 @@ export class GameStateRenderer {
});
let modalContent = document.getElementById("transfer-mod-form-content");
if (modalContent) {
for (let player of this.gameState.people) {
if (player.out) {
let container = document.createElement("div");
container.classList.add('potential-moderator');
container.dataset.pointer = player.id;
container.innerText = player.name;
this.transferModHandlers[player.id] = () => {
if (confirm("Transfer moderator powers to " + player.name + "?")) {
socket.emit(globals.COMMANDS.TRANSFER_MODERATOR, this.gameState.accessCode, player.id);
}
}
container.addEventListener('click', this.transferModHandlers[player.id]);
modalContent.appendChild(container);
}
}
renderPotentialMods(this.gameState, this.gameState.people, this.transferModHandlers, modalContent, this.socket);
renderPotentialMods(this.gameState, this.gameState.spectators, this.transferModHandlers, modalContent, this.socket);
}
}
}
function renderPotentialMods(gameState, group, transferModHandlers, modalContent, socket) {
for (let member of group) {
if ((member.out || member.userType === globals.USER_TYPES.SPECTATOR) && !(member.id === gameState.client.id)) {
let container = document.createElement("div");
container.classList.add('potential-moderator');
container.dataset.pointer = member.id;
container.innerText = member.name;
transferModHandlers[member.id] = () => {
if (confirm("Transfer moderator powers to " + member.name + "?")) {
socket.emit(globals.COMMANDS.TRANSFER_MODERATOR, gameState.accessCode, member.id);
}
}
container.addEventListener('click', transferModHandlers[member.id]);
modalContent.appendChild(container);
}
}
}
function renderLobbyPerson(name, userType) {
let el = document.createElement("div");
let personNameEl = document.createElement("div");
@@ -204,12 +236,13 @@ function removeExistingTitle() {
}
}
function renderGroupOfPlayers(players, killPlayerHandlers, revealRoleHandlers, accessCode=null, alignment=null, moderator=false, socket=null) {
for (let player of players) {
// TODO: refactor to reduce the cyclomatic complexity of this function
function renderGroupOfPlayers(gameState, killPlayerHandlers, revealRoleHandlers, accessCode=null, alignment=null, moderator=false, socket=null) {
for (let player of gameState.people) {
let container = document.createElement("div");
container.classList.add('game-player');
container.dataset.pointer = player.id;
if (alignment) {
if (moderator) {
container.dataset.pointer = player.id;
container.innerHTML = templates.MODERATOR_PLAYER;
} else {
container.innerHTML = templates.GAME_PLAYER;
@@ -219,14 +252,24 @@ function renderGroupOfPlayers(players, killPlayerHandlers, revealRoleHandlers, a
if (moderator) {
roleElement.classList.add(alignment);
roleElement.innerText = player.gameRole;
document.getElementById("player-list-moderator-team-" + alignment).appendChild(container);
if (gameState.moderator.userType === globals.USER_TYPES.MODERATOR) {
roleElement.innerText = player.gameRole;
document.getElementById("player-list-moderator-team-" + alignment).appendChild(container);
} else {
if (player.revealed) {
roleElement.innerText = player.gameRole;
roleElement.classList.add(player.alignment);
} else {
roleElement.innerText = "Unknown";
}
document.getElementById("game-player-list").appendChild(container);
}
} else if (player.revealed) {
roleElement.classList.add(player.alignment);
roleElement.innerText = player.gameRole;
document.getElementById("game-player-list").appendChild(container);
} else {
roleElement.innerText = "Unknown"
roleElement.innerText = "Unknown";
document.getElementById("game-player-list").appendChild(container);
}

View File

@@ -45,11 +45,24 @@ export const templates = {
"<label id='players-alive-label'></label>" +
"<div id='game-player-list'></div>" +
"</div>",
SPECTATOR_GAME_VIEW:
"<div id='game-header'>" +
"<div>" +
"<label for='game-timer'>Time Remaining</label>" +
"<div id='game-timer'></div>" +
"</div>" +
"</div>" +
"<div>" +
"<label id='players-alive-label'></label>" +
"<div id='game-player-list'></div>" +
"</div>",
MODERATOR_GAME_VIEW:
"<div id='transfer-mod-modal-background' class='modal-background' style='display: none'></div>" +
"<div id='transfer-mod-modal' class='modal' style='display: none'>" +
"<form id='transfer-mod-form'>" +
"<div id='transfer-mod-form-content'></div>" +
"<div id='transfer-mod-form-content'>" +
"<h3>Transfer Mod Powers &#128081;</h3>" +
"</div>" +
"<div id='modal-button-container'>" +
"<button id='close-modal-button'>Cancel</button>" +
"</div>" +
@@ -78,6 +91,41 @@ export const templates = {
"</div>" +
"</div>" +
"</div>",
TEMP_MOD_GAME_VIEW:
"<div id='transfer-mod-modal-background' class='modal-background' style='display: none'></div>" +
"<div id='transfer-mod-modal' class='modal' style='display: none'>" +
"<form id='transfer-mod-form'>" +
"<div id='transfer-mod-form-content'>" +
"<h3>Transfer Mod Powers &#128081;</h3>" +
"</div>" +
"<div id='modal-button-container'>" +
"<button id='close-modal-button'>Cancel</button>" +
"</div>" +
"</form>" +
"</div>" +
"<div id='game-header'>" +
"<div class='timer-container-moderator'>" +
"<div>" +
"<label for='game-timer'>Time Remaining</label>" +
"<div id='game-timer'></div>" +
"</div>" +
"<div id='play-pause'>" + "</div>" +
"</div>" +
"</div>" +
"<div id='game-role' style='display:none'>" +
"<h4 id='role-name'></h4>" +
"<img alt='role' id='role-image'/>" +
"<p id='role-description'></p>" +
"</div>" +
"<div id='game-role-back'>" +
"<h4>Click to reveal your role</h4>" +
"<p>(click again to hide)</p>" +
"</div>" +
"<div>" +
"<label id='players-alive-label'></label>" +
"<div id='game-player-list'></div>" +
"</div>" +
"</div>",
MODERATOR_PLAYER:
"<div>" +
"<div class='game-player-name'></div>" +

View File

@@ -41,7 +41,6 @@ function prepareGamePage(environment, socket, timerWorker) {
gameTimerManager = new GameTimerManager(gameState, socket);
}
setClientSocketHandlers(gameStateRenderer, socket, timerWorker, gameTimerManager);
displayClientInfo(gameState.client.name, gameState.client.userType);
processGameState(gameState, userId, socket, gameStateRenderer);
}
});
@@ -51,6 +50,7 @@ function prepareGamePage(environment, socket, timerWorker) {
}
function processGameState (gameState, userId, socket, gameStateRenderer) {
displayClientInfo(gameState.client.name, gameState.client.userType);
switch (gameState.status) {
case globals.STATUS.LOBBY:
document.getElementById("game-state-container").innerHTML = templates.LOBBY;
@@ -67,7 +67,6 @@ function processGameState (gameState, userId, socket, gameStateRenderer) {
}
break;
case globals.STATUS.IN_PROGRESS:
gameStateRenderer.gameState = gameState;
gameStateRenderer.renderGameHeader();
switch (gameState.client.userType) {
case globals.USER_TYPES.PLAYER:
@@ -75,6 +74,7 @@ function processGameState (gameState, userId, socket, gameStateRenderer) {
gameStateRenderer.renderPlayerView();
break;
case globals.USER_TYPES.KILLED_PLAYER:
document.querySelector("#end-game-prompt")?.remove();
document.getElementById("game-state-container").innerHTML = templates.PLAYER_GAME_VIEW;
gameStateRenderer.renderPlayerView(true);
break;
@@ -85,6 +85,13 @@ function processGameState (gameState, userId, socket, gameStateRenderer) {
break;
case globals.USER_TYPES.TEMPORARY_MODERATOR:
document.querySelector("#start-game-prompt")?.remove();
document.getElementById("game-state-container").innerHTML = templates.TEMP_MOD_GAME_VIEW;
gameStateRenderer.renderTempModView();
break;
case globals.USER_TYPES.SPECTATOR:
document.querySelector("#end-game-prompt")?.remove();
document.getElementById("game-state-container").innerHTML = templates.SPECTATOR_GAME_VIEW;
gameStateRenderer.renderSpectatorView();
break;
default:
break;
@@ -127,6 +134,8 @@ function setClientSocketHandlers(gameStateRenderer, socket, timerWorker, gameTim
gameStateRenderer.gameState.accessCode,
gameStateRenderer.gameState.client.cookie,
function (gameState) {
gameStateRenderer.gameState = gameState;
gameTimerManager.gameState = gameState;
processGameState(gameState, gameState.client.cookie, socket, gameStateRenderer);
}
);
@@ -156,7 +165,11 @@ function setClientSocketHandlers(gameStateRenderer, socket, timerWorker, gameTim
} else {
toast(killedPerson.name + ' was killed!', 'warning', false, true, 6);
}
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed();
if (gameStateRenderer.gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true);
} else {
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(false);
}
}
}
});
@@ -178,7 +191,11 @@ function setClientSocketHandlers(gameStateRenderer, socket, timerWorker, gameTim
} else {
toast(revealedPerson.name + ' was revealed as a ' + revealedPerson.gameRole + '!', 'warning', false, true, 6);
}
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed();
if (gameStateRenderer.gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true);
} else {
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(false);
}
}
}
});

View File

@@ -96,7 +96,6 @@ button:active, input[type=submit]:active {
flex-direction: column;
justify-content: center;
width: 95%;
max-width: 68em;
margin: 0 auto;
}

View File

@@ -454,6 +454,14 @@ label[for='moderator'] {
font-size: 25px;
}
#transfer-mod-form {
width: 100%;
}
#transfer-mod-form #modal-button-container {
justify-content: center;
}
@media(max-width: 685px) {
#end-game-button {
font-size: 25px;

View File

@@ -10,7 +10,8 @@ const globals = {
RESUME_TIMER: 'resumeTimer',
GET_TIME_REMAINING: 'getTimeRemaining',
KILL_PLAYER: 'killPlayer',
REVEAL_PLAYER: 'revealPlayer'
REVEAL_PLAYER: 'revealPlayer',
TRANSFER_MODERATOR: 'transferModerator'
},
STATUS: {
LOBBY: "lobby",

View File

@@ -9,6 +9,7 @@ class Game {
this.timerParams = timerParams;
this.isFull = false;
this.timeRemaining = null;
this.spectators = [];
}
}

View File

@@ -125,6 +125,33 @@ class GameManager {
})
}
}
});
socket.on(globals.CLIENT_COMMANDS.TRANSFER_MODERATOR, (accessCode, personId) => {
let game = this.activeGameRunner.activeGames[accessCode];
if (game) {
let person = game.people.find((person) => person.id === personId)
if (!person) {
person = game.spectators.find((spectator) => spectator.id === personId)
}
if (person && (person.out || person.userType === globals.USER_TYPES.SPECTATOR)) {
this.logger.debug('game ' + accessCode + ': transferring mod powers to ' + person.name);
if (game.people.includes(game.moderator)) { // the current moderator was at one point a dealt-in player.
game.moderator.userType = globals.USER_TYPES.KILLED_PLAYER; // restore their state from before being made mod.
} else {
game.moderator.userType = globals.USER_TYPES.SPECTATOR;
if (!game.spectators.includes(game.moderator)) {
game.spectators.push(game.moderator);
}
if (game.spectators.includes(person)) {
game.spectators.splice(game.spectators.indexOf(person), 1);
}
}
person.userType = globals.USER_TYPES.MODERATOR;
game.moderator = person;
namespace.in(accessCode).emit(globals.EVENTS.SYNC_GAME_STATE);
}
}
})
}
@@ -265,7 +292,10 @@ function handleRequestForGameState(namespace, logger, gameRunner, accessCode, pe
const game = gameRunner.activeGames[accessCode];
if (game) {
let matchingPerson = game.people.find((person) => person.cookie === personCookie);
if (!matchingPerson && game.moderator.cookie === personCookie) {
if (!matchingPerson) {
matchingPerson = game.spectators.find((spectator) => spectator.cookie = personCookie);
}
if (game.moderator.cookie === personCookie) {
matchingPerson = game.moderator;
}
if (matchingPerson) {

View File

@@ -34,7 +34,6 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) {
people: game.people
.filter((person) => {
return person.assigned === true
&& (person.userType !== globals.USER_TYPES.MODERATOR && person.userType !== globals.USER_TYPES.TEMPORARY_MODERATOR)
})
.map((filteredPerson) => mapPerson(filteredPerson)),
timerParams: game.timerParams,
@@ -49,7 +48,8 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) {
deck: game.deck,
people: mapPeopleForModerator(game.people, client),
timerParams: game.timerParams,
isFull: game.isFull
isFull: game.isFull,
spectators: game.spectators
}
case globals.USER_TYPES.TEMPORARY_MODERATOR:
return {
@@ -58,19 +58,38 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) {
moderator: mapPerson(game.moderator),
client: client,
deck: game.deck,
people: mapPeopleForTempModerator(game.people, client),
people: game.people
.filter((person) => {
return person.assigned === true
})
.map((filteredPerson) => mapPerson(filteredPerson)),
timerParams: game.timerParams,
isFull: game.isFull
}
case globals.USER_TYPES.SPECTATOR:
return {
accessCode: game.accessCode,
status: game.status,
moderator: mapPerson(game.moderator),
client: client,
deck: game.deck,
people: game.people
.filter((person) => {
return person.assigned === true
})
.map((filteredPerson) => mapPerson(filteredPerson)),
timerParams: game.timerParams,
isFull: game.isFull,
}
default:
break;
}
}
function mapPeopleForModerator(people, client) {
function mapPeopleForModerator(people) {
return people
.filter((person) => {
return person.assigned === true && person.cookie !== client.cookie
return person.assigned === true
})
.map((person) => ({
name: person.name,
@@ -84,20 +103,6 @@ function mapPeopleForModerator(people, client) {
}));
}
function mapPeopleForTempModerator(people, client) {
return people
.filter((person) => {
return person.assigned === true && person.cookie !== client.cookie
})
.map((person) => ({
name: person.name,
id: person.id,
userType: person.userType,
out: person.out,
revealed: person.revealed
}));
}
function mapPerson(person) {
if (person.revealed) {
return {