mirror of
https://github.com/AlecM33/Werewolf.git
synced 2025-12-26 15:57:50 +01:00
some game joining logic
This commit is contained in:
9
client/config/globals.js
Normal file
9
client/config/globals.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export const globals = {
|
||||||
|
USER_SIGNATURE_LENGTH: 25,
|
||||||
|
ACCESS_CODE_LENGTH: 6,
|
||||||
|
PLAYER_ID_COOKIE_KEY: 'play-werewolf-anon-id',
|
||||||
|
ACCESS_CODE_CHAR_POOL: 'abcdefghijklmnopqrstuvwxyz0123456789',
|
||||||
|
COMMANDS: {
|
||||||
|
FETCH_GAME_STATE: 'fetchGameState'
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
export class Game {
|
export class Game {
|
||||||
constructor(deck, hasTimer, timerParams=null) {
|
constructor(deck, hasTimer, moderatorName, timerParams=null) {
|
||||||
this.deck = deck;
|
this.deck = deck;
|
||||||
this.hasTimer = hasTimer;
|
this.hasTimer = hasTimer;
|
||||||
|
this.moderatorName = moderatorName;
|
||||||
this.timerParams = timerParams;
|
this.timerParams = timerParams;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
client/modules/UserUtility.js
Normal file
41
client/modules/UserUtility.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { globals } from '../config/globals.js';
|
||||||
|
|
||||||
|
export const UserUtility = {
|
||||||
|
|
||||||
|
createNewAnonymousUserId (force = true) {
|
||||||
|
let newId;
|
||||||
|
const currentId = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
|
||||||
|
if (currentId !== null && !force) {
|
||||||
|
newId = currentId;
|
||||||
|
} else {
|
||||||
|
newId = createRandomUserId();
|
||||||
|
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, newId);
|
||||||
|
}
|
||||||
|
return newId;
|
||||||
|
},
|
||||||
|
|
||||||
|
setAnonymousUserId (id) {
|
||||||
|
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, id);
|
||||||
|
},
|
||||||
|
|
||||||
|
validateAnonUserSignature () {
|
||||||
|
const userSig = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
|
||||||
|
return (
|
||||||
|
userSig
|
||||||
|
&& typeof userSig === 'string'
|
||||||
|
&& /^[a-zA-Z0-9]+$/.test(userSig)
|
||||||
|
&& userSig.length === globals.USER_SIGNATURE_LENGTH
|
||||||
|
)
|
||||||
|
? userSig
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function createRandomUserId () {
|
||||||
|
let id = '';
|
||||||
|
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)];
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
@@ -3,54 +3,14 @@ import { ModalManager } from "../modules/ModalManager.js";
|
|||||||
import { defaultCards } from "../config/defaultCards.js";
|
import { defaultCards } from "../config/defaultCards.js";
|
||||||
import { customCards } from "../config/customCards.js";
|
import { customCards } from "../config/customCards.js";
|
||||||
import { DeckStateManager } from "../modules/DeckStateManager.js";
|
import { DeckStateManager } from "../modules/DeckStateManager.js";
|
||||||
import {XHRUtility} from "../modules/XHRUtility.js";
|
import { XHRUtility } from "../modules/XHRUtility.js";
|
||||||
import {Game} from "../model/Game.js";
|
import { Game } from "../model/Game.js";
|
||||||
|
|
||||||
export const create = () => {
|
export const create = () => {
|
||||||
let deckManager = new DeckStateManager();
|
let deckManager = new DeckStateManager();
|
||||||
loadDefaultCards(deckManager);
|
loadDefaultCards(deckManager);
|
||||||
loadCustomCards(deckManager);
|
loadCustomCards(deckManager);
|
||||||
document.getElementById("game-form").onsubmit = (e) => {
|
initializeRemainingEventListeners(deckManager);
|
||||||
e.preventDefault();
|
|
||||||
let timerBool = hasTimer();
|
|
||||||
let timerParams = timerBool
|
|
||||||
? {
|
|
||||||
hours: document.getElementById("game-hours").value,
|
|
||||||
minutes: document.getElementById("game-minutes").value
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
if (deckManager.getDeckSize() >= 5) {
|
|
||||||
createGameForHosting(
|
|
||||||
deckManager.getCurrentDeck().filter((card) => card.quantity > 0),
|
|
||||||
timerBool,
|
|
||||||
timerParams
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
toast("You must include enough cards for 5 players.", "error", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.getElementById("add-role-form").onsubmit = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
let name = document.getElementById("role-name").value.trim();
|
|
||||||
let description = document.getElementById("role-description").value.trim();
|
|
||||||
if (!deckManager.getCustomRoleOption(name)) { // confirm there is no existing custom role with the same name
|
|
||||||
deckManager.addToCustomRoleOptions({role: name, description: description});
|
|
||||||
updateCustomRoleOptionsList(deckManager, document.getElementById("deck-select"))
|
|
||||||
ModalManager.dispelModal("add-role-modal", "add-role-modal-background");
|
|
||||||
toast("Role Added", "success", true);
|
|
||||||
} else {
|
|
||||||
toast("There is already a custom role with this name.", "error", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.getElementById("custom-role-btn").addEventListener(
|
|
||||||
"click", () => {
|
|
||||||
ModalManager.displayModal(
|
|
||||||
"add-role-modal",
|
|
||||||
"add-role-modal-background",
|
|
||||||
"close-modal-button"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display a widget for each default card that allows copies of it to be added/removed. Set initial deck state.
|
// Display a widget for each default card that allows copies of it to be added/removed. Set initial deck state.
|
||||||
@@ -83,7 +43,7 @@ function loadCustomCards(deckManager) {
|
|||||||
form.appendChild(selectEl);
|
form.appendChild(selectEl);
|
||||||
let submitBtn = document.createElement("input");
|
let submitBtn = document.createElement("input");
|
||||||
submitBtn.setAttribute("type", "submit");
|
submitBtn.setAttribute("type", "submit");
|
||||||
submitBtn.setAttribute("value", "Add Role");
|
submitBtn.setAttribute("value", "Include Role");
|
||||||
submitBtn.addEventListener('click', (e) => {
|
submitBtn.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (selectEl.value && selectEl.value.length > 0) {
|
if (selectEl.value && selectEl.value.length > 0) {
|
||||||
@@ -100,6 +60,51 @@ function loadCustomCards(deckManager) {
|
|||||||
deckManager.customRoleOptions = customCards;
|
deckManager.customRoleOptions = customCards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initializeRemainingEventListeners(deckManager) {
|
||||||
|
document.getElementById("game-form").onsubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
let timerBool = hasTimer();
|
||||||
|
let timerParams = timerBool
|
||||||
|
? {
|
||||||
|
hours: document.getElementById("game-hours").value,
|
||||||
|
minutes: document.getElementById("game-minutes").value
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
if (deckManager.getDeckSize() >= 5) {
|
||||||
|
createGameForHosting(
|
||||||
|
deckManager.getCurrentDeck().filter((card) => card.quantity > 0),
|
||||||
|
timerBool,
|
||||||
|
document.getElementById("mod-name").value,
|
||||||
|
timerParams
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toast("You must include enough cards for 5 players.", "error", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById("add-role-form").onsubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
let name = document.getElementById("role-name").value.trim();
|
||||||
|
let description = document.getElementById("role-description").value.trim();
|
||||||
|
if (!deckManager.getCustomRoleOption(name)) { // confirm there is no existing custom role with the same name
|
||||||
|
deckManager.addToCustomRoleOptions({role: name, description: description});
|
||||||
|
updateCustomRoleOptionsList(deckManager, document.getElementById("deck-select"))
|
||||||
|
ModalManager.dispelModal("add-role-modal", "add-role-modal-background");
|
||||||
|
toast("Role Added", "success", true);
|
||||||
|
} else {
|
||||||
|
toast("There is already a custom role with this name.", "error", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById("custom-role-btn").addEventListener(
|
||||||
|
"click", () => {
|
||||||
|
ModalManager.displayModal(
|
||||||
|
"add-role-modal",
|
||||||
|
"add-role-modal-background",
|
||||||
|
"close-modal-button"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function updateCustomRoleOptionsList(deckManager, selectEl) {
|
function updateCustomRoleOptionsList(deckManager, selectEl) {
|
||||||
document.querySelectorAll('#deck-select option').forEach(e => e.remove());
|
document.querySelectorAll('#deck-select option').forEach(e => e.remove());
|
||||||
addOptionsToList(deckManager.customRoleOptions, selectEl);
|
addOptionsToList(deckManager.customRoleOptions, selectEl);
|
||||||
@@ -158,13 +163,13 @@ function hasTimer() {
|
|||||||
return document.getElementById("game-hours").value.length > 0 || document.getElementById("game-minutes").value.length > 0
|
return document.getElementById("game-hours").value.length > 0 || document.getElementById("game-minutes").value.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function createGameForHosting(deck, hasTimer, timerParams) {
|
function createGameForHosting(deck, hasTimer, modName, timerParams) {
|
||||||
XHRUtility.xhr(
|
XHRUtility.xhr(
|
||||||
'/api/games/create',
|
'/api/games/create',
|
||||||
'POST',
|
'POST',
|
||||||
null,
|
null,
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
new Game(deck, hasTimer, timerParams)
|
new Game(deck, hasTimer, modName, timerParams)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@@ -173,7 +178,7 @@ function createGameForHosting(deck, hasTimer, timerParams) {
|
|||||||
&& Object.prototype.hasOwnProperty.call(res, 'content')
|
&& Object.prototype.hasOwnProperty.call(res, 'content')
|
||||||
&& typeof res.content === 'string'
|
&& typeof res.content === 'string'
|
||||||
) {
|
) {
|
||||||
window.location = ('/games/' + res.content);
|
window.location = ('/game/' + res.content);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { UserUtility } from "../modules/UserUtility.js";
|
||||||
|
import { globals } from "../config/globals.js";
|
||||||
|
|
||||||
|
export const game = () => {
|
||||||
|
let userId = UserUtility.validateAnonUserSignature();
|
||||||
|
const splitUrl = window.location.href.split('/game/');
|
||||||
|
const accessCode = splitUrl[1];
|
||||||
|
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) {
|
||||||
|
socket.emit(globals.COMMANDS.FETCH_GAME_STATE, accessCode, userId, function (gameState) {
|
||||||
|
if (gameState === null) {
|
||||||
|
window.location.replace('/not-found');
|
||||||
|
} else {
|
||||||
|
console.log(gameState);
|
||||||
|
userId = gameState.id;
|
||||||
|
UserUtility.setAnonymousUserId(userId);
|
||||||
|
// processGameState(gameState, userId, socket);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.location.replace('/not-found');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,2 +1,37 @@
|
|||||||
|
import { XHRUtility } from "../modules/XHRUtility.js";
|
||||||
|
import { toast } from "../modules/Toast.js";
|
||||||
|
|
||||||
export const home = () => {
|
export const home = () => {
|
||||||
|
document.getElementById("join-form").onsubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
let userCode = document.getElementById("room-code").value;
|
||||||
|
if (roomCodeIsValid(userCode)) {
|
||||||
|
attemptToJoinGame(userCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function roomCodeIsValid(code) {
|
||||||
|
return typeof code === "string" && /^[a-z0-9]{6}$/.test(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
function attemptToJoinGame(code) {
|
||||||
|
XHRUtility.xhr(
|
||||||
|
'/api/games/availability/' + code,
|
||||||
|
'GET',
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
window.location = '/game/' + res.content;
|
||||||
|
} else if (res.status === 404) {
|
||||||
|
toast("Game not found", "error", true);
|
||||||
|
} else if (res.status === 400) {
|
||||||
|
toast(res.content, "error", true);
|
||||||
|
} else {
|
||||||
|
toast("An unknown error occurred. Please try again later.", "error", true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ body {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
width: 95%;
|
width: 95%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 75em;
|
max-width: 68em;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@@ -58,6 +58,13 @@ label {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input, textarea {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid white;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
textarea, input {
|
textarea, input {
|
||||||
font-family: 'signika-negative', sans-serif;
|
font-family: 'signika-negative', sans-serif;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@@ -66,12 +73,24 @@ textarea, input {
|
|||||||
button, input[type="submit"] {
|
button, input[type="submit"] {
|
||||||
font-family: 'signika-negative', sans-serif !important;
|
font-family: 'signika-negative', sans-serif !important;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: #1f1f1f;
|
background-color: black;
|
||||||
border: none;
|
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
color: white;
|
color: whitesmoke;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active, input[type=submit]:active {
|
||||||
|
border: 2px solid #21ba45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover, input[type="submit"]:hover {
|
button:hover, input[type="submit"]:hover {
|
||||||
@@ -109,7 +128,6 @@ input {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
margin-bottom: -2em;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,10 @@
|
|||||||
border: 2px solid #0075F2;
|
border: 2px solid #0075F2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
.compact-card-right p {
|
.compact-card-right p {
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
margin: 0 10px 0 0;
|
margin: 0 10px 0 0;
|
||||||
@@ -80,6 +84,9 @@
|
|||||||
|
|
||||||
#deck-container, #custom-roles-container {
|
#deck-container, #custom-roles-container {
|
||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
|
background-color: #1f1f1f;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#deck {
|
#deck {
|
||||||
@@ -91,7 +98,6 @@
|
|||||||
|
|
||||||
form {
|
form {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 1em 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@@ -100,19 +106,14 @@ select {
|
|||||||
font-family: 'signika-negative', sans-serif;
|
font-family: 'signika-negative', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
#deck-container, #custom-roles-container {
|
|
||||||
border: 1px solid #3d4448;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#game-form > div {
|
#game-form > div {
|
||||||
|
background-color: #1f1f1f;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border: 1px solid #3d4448;
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#game-form > div > label {
|
#game-form > div > label {
|
||||||
@@ -131,6 +132,14 @@ label[for="game-time"], label[for="add-card-to-deck-form"], label[for="deck"] {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
min-width: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#add-card-to-deck-form {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
#create-game{
|
#create-game{
|
||||||
color: #45a445;
|
color: #45a445;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
|
|||||||
0
client/styles/game.css
Normal file
0
client/styles/game.css
Normal file
@@ -15,6 +15,14 @@ form {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-color: #1f1f1f;
|
background-color: #1f1f1f;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#join-button {
|
||||||
|
min-width: 6em;
|
||||||
|
max-height: 3em;
|
||||||
|
color: #21ba45;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
@@ -33,13 +41,6 @@ form > div {
|
|||||||
margin: 1em;
|
margin: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"] {
|
|
||||||
background-color: transparent;
|
|
||||||
border: 1px solid white;
|
|
||||||
border-radius: 3px;
|
|
||||||
color: #f7f7f7;
|
|
||||||
}
|
|
||||||
|
|
||||||
#join-container > label {
|
#join-container > label {
|
||||||
font-size: 35px;
|
font-size: 35px;
|
||||||
font-family: 'diavlo', sans-serif;
|
font-family: 'diavlo', sans-serif;
|
||||||
|
|||||||
2
client/styles/third_party/dropdown.min.css
vendored
2
client/styles/third_party/dropdown.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -26,55 +26,58 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="navbar">
|
<div id="navbar">
|
||||||
<a href="/">
|
|
||||||
<img alt="logo" src="../images/Werewolf_Small.png"/>
|
|
||||||
</a>
|
|
||||||
<a href="/">Home</a>
|
<a href="/">Home</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="add-role-modal-background" class="modal-background" style="display: none"></div>
|
<div class="container">
|
||||||
<div id="add-role-modal" class="modal" style="display: none">
|
<div id="add-role-modal-background" class="modal-background" style="display: none"></div>
|
||||||
<form id="add-role-form">
|
<div id="add-role-modal" class="modal" style="display: none">
|
||||||
|
<form id="add-role-form">
|
||||||
|
<div>
|
||||||
|
<label for="role-name">Role Name</label>
|
||||||
|
<input id="role-name" type="text" placeholder="Name your role..." required/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="role-description">Description</label>
|
||||||
|
<textarea style="resize:none" id="role-description" rows="10" cols="30" placeholder="Describe your role..." required></textarea>
|
||||||
|
</div>
|
||||||
|
<div id="modal-button-container">
|
||||||
|
<button id="close-modal-button">Close</button>
|
||||||
|
<input type="submit" id="create-role-button" value="Create Role"/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<h1>Create A Game</h1>
|
||||||
|
<h3>
|
||||||
|
Creating a game gives you the moderator role with certain special permissions. You will not be dealt a card.
|
||||||
|
</h3>
|
||||||
|
<div id="custom-roles-container">
|
||||||
|
<label for="add-card-to-deck-form">Custom Roles</label>
|
||||||
|
<form id="add-card-to-deck-form"></form>
|
||||||
|
<button id="custom-role-btn">Create Custom Role</button>
|
||||||
|
</div>
|
||||||
|
<div id="deck-container">
|
||||||
|
<label for="deck">Game Deck: 0 Players</label>
|
||||||
|
<div id="deck"></div>
|
||||||
|
</div>
|
||||||
|
<form id="game-form">
|
||||||
<div>
|
<div>
|
||||||
<label for="role-name">Role Name</label>
|
<label for="game-time">Timer (Optional)</label>
|
||||||
<input id="role-name" type="text" placeholder="Name your role..." required/>
|
<div id="game-time">
|
||||||
|
<label for="game-hours">Hours (max 5)</label>
|
||||||
|
<input type="number" id="game-hours" name="game-hours"
|
||||||
|
min="0" max="5" />
|
||||||
|
<label for="game-hours">Minutes</label>
|
||||||
|
<input type="number" id="game-minutes" name="game-minutes"
|
||||||
|
min="1" max="60" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="role-description">Description</label>
|
<label for="mod-name">Your Name</label>
|
||||||
<textarea style="resize:none" id="role-description" rows="10" cols="30" placeholder="Describe your role..." required></textarea>
|
<input id="mod-name" type="text" maxlength="30" required/>
|
||||||
</div>
|
|
||||||
<div id="modal-button-container">
|
|
||||||
<button id="close-modal-button">Close</button>
|
|
||||||
<input type="submit" id="create-role-button" value="Create Role"/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<input id="create-game" type="submit" value="Create"/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<h1>Create A Game</h1>
|
|
||||||
<h3>
|
|
||||||
Creating a game gives you the moderator role with certain special permissions. You will not be dealt a card.
|
|
||||||
</h3>
|
|
||||||
<div id="custom-roles-container">
|
|
||||||
<label for="add-card-to-deck-form">Custom Roles</label>
|
|
||||||
<form id="add-card-to-deck-form"></form>
|
|
||||||
<button id="custom-role-btn">Create Custom Role</button>
|
|
||||||
</div>
|
|
||||||
<div id="deck-container">
|
|
||||||
<label for="deck">Game Deck: 0 Players</label>
|
|
||||||
<div id="deck"></div>
|
|
||||||
</div>
|
|
||||||
<form id="game-form">
|
|
||||||
<div>
|
|
||||||
<label for="game-time">Timer (Optional)</label>
|
|
||||||
<div id="game-time">
|
|
||||||
<label for="game-hours">Hours (max 5)</label>
|
|
||||||
<input type="number" id="game-hours" name="game-hours"
|
|
||||||
min="0" max="5" />
|
|
||||||
<label for="game-hours">Minutes</label>
|
|
||||||
<input type="number" id="game-minutes" name="game-minutes"
|
|
||||||
min="1" max="60" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input id="create-game" type="submit" value="Create"/>
|
|
||||||
</form>
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { create } from "../scripts/create.js";
|
import { create } from "../scripts/create.js";
|
||||||
create();
|
create();
|
||||||
|
|||||||
31
client/views/game.html
Normal file
31
client/views/game.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Active Game</title>
|
||||||
|
<meta name="description" content="Join or spectate this game of werewolf.">
|
||||||
|
<meta property="og:title" content="Werewolf Utility - Active Game">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:url" content="https://play-werewolf.herokuapp.com/create">
|
||||||
|
<meta property="og:description" content="Join or spectate this game of werewolf.">
|
||||||
|
<meta property="og:image" content="image.png">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||||
|
<link rel="stylesheet" href="../styles/GLOBAL.css">
|
||||||
|
<link rel="stylesheet" href="../styles/game.css">
|
||||||
|
<link rel="stylesheet" href="../styles/modal.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="module">
|
||||||
|
import { game } from "../scripts/game.js";
|
||||||
|
game();
|
||||||
|
</script>
|
||||||
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
|
<script>
|
||||||
|
const socket = io('/in-game');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -32,10 +32,7 @@
|
|||||||
<label for="room-code">Room Code</label>
|
<label for="room-code">Room Code</label>
|
||||||
<input id="room-code" type="text" placeholder="six-character code..." required/>
|
<input id="room-code" type="text" placeholder="six-character code..." required/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<input id="join-button" type="submit" value="Join"/>
|
||||||
<label for="player-name">Your Name</label>
|
|
||||||
<input id="player-name" type="text" required/>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
|
|||||||
@@ -17,4 +17,20 @@ router.post('/create', function (req, res) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get('/availability/:code', function (req, res) {
|
||||||
|
const joinGamePromise = gameManager.joinGame(req.params.code);
|
||||||
|
joinGamePromise.then((result) => {
|
||||||
|
if (result === 404) {
|
||||||
|
res.status(404).send();
|
||||||
|
} else if (result instanceof Error) {
|
||||||
|
res.status(400).send(result.message);
|
||||||
|
} else if (typeof result === "string") {
|
||||||
|
logger.debug(result);
|
||||||
|
res.status(200).send(result);
|
||||||
|
} else {
|
||||||
|
res.status(500).send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,6 +1,22 @@
|
|||||||
const globals = {
|
const globals = {
|
||||||
ACCESS_CODE_CHAR_POOL: 'abcdefghijklmnopqrstuvwxyz0123456789',
|
ACCESS_CODE_CHAR_POOL: 'abcdefghijklmnopqrstuvwxyz0123456789',
|
||||||
ACCESS_CODE_LENGTH: 6,
|
ACCESS_CODE_LENGTH: 6,
|
||||||
|
CLIENT_COMMANDS: {
|
||||||
|
FETCH_GAME_STATE: 'fetchGameState',
|
||||||
|
TOGGLE_READY: 'toggleReady',
|
||||||
|
PROCESS_GUESS: 'processGuess'
|
||||||
|
},
|
||||||
|
STATUS: {
|
||||||
|
LOBBY: "lobby"
|
||||||
|
},
|
||||||
|
USER_SIGNATURE_LENGTH: 25,
|
||||||
|
USER_TYPES: {
|
||||||
|
MODERATOR: "moderator",
|
||||||
|
PLAYER: "player"
|
||||||
|
},
|
||||||
|
ERROR_MESSAGE: {
|
||||||
|
GAME_IS_FULL: "This game is full"
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = globals;
|
module.exports = globals;
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ const io = socketIO(main);
|
|||||||
|
|
||||||
app.set('port', port);
|
app.set('port', port);
|
||||||
|
|
||||||
|
const inGame = io.of('/in-game');
|
||||||
|
|
||||||
|
|
||||||
/* Instantiate the singleton game manager */
|
/* Instantiate the singleton game manager */
|
||||||
const gameManager = new GameManager(logger).getInstance();
|
const gameManager = new GameManager(logger).getInstance();
|
||||||
|
|
||||||
@@ -86,6 +89,10 @@ app.use(function (req, res) {
|
|||||||
res.sendFile(path.join(__dirname, '../client/views/404.html'));
|
res.sendFile(path.join(__dirname, '../client/views/404.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
inGame.on('connection', function (socket) {
|
||||||
|
gameManager.addGameSocketHandlers(inGame, socket);
|
||||||
|
});
|
||||||
|
|
||||||
main.listen(port, function () {
|
main.listen(port, function () {
|
||||||
logger.log(`Starting server on port ${port} http://localhost:${port}` );
|
logger.log(`Starting server on port ${port} http://localhost:${port}` );
|
||||||
});
|
});
|
||||||
|
|||||||
11
server/model/Game.js
Normal file
11
server/model/Game.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
class Game {
|
||||||
|
constructor(status, people, deck, hasTimer, timerParams=null) {
|
||||||
|
this.status = status;
|
||||||
|
this.people = people;
|
||||||
|
this.deck = deck;
|
||||||
|
this.hasTimer = hasTimer;
|
||||||
|
this.timerParams = timerParams;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Game;
|
||||||
13
server/model/Person.js
Normal file
13
server/model/Person.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
class Person {
|
||||||
|
constructor(id, name, userType, gameRole=null, gameRoleDescription=null, assigned=false) {
|
||||||
|
this.id = id;
|
||||||
|
this.socketId = null;
|
||||||
|
this.name = name;
|
||||||
|
this.userType = userType;
|
||||||
|
this.gameRole = gameRole;
|
||||||
|
this.gameRoleDescription = gameRoleDescription;
|
||||||
|
this.assigned = assigned;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Person;
|
||||||
96
server/modules/ActiveGameRunner.js
Normal file
96
server/modules/ActiveGameRunner.js
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
const { fork } = require('child_process');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const logger = require('./logger')(false);
|
||||||
|
|
||||||
|
class ActiveGameRunner {
|
||||||
|
constructor () {
|
||||||
|
this.activeGames = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// runGame = (game, namespace, gameStateFn) => {
|
||||||
|
// logger.debug('running game ' + game.accessCode);
|
||||||
|
// const gameProcess = fork(path.join(__dirname, '/GameProcess.js'));
|
||||||
|
// gameProcess.on('message', (msg) => {
|
||||||
|
// switch (msg.command) {
|
||||||
|
// case serverGlobals.COMMAND.END_COUNTDOWN:
|
||||||
|
// logger.debug('GAME PARENT PROCESS ' + game.accessCode + ': COMMAND: END COUNTDOWN');
|
||||||
|
// namespace.in(game.accessCode).emit(serverGlobals.COMMAND.END_COUNTDOWN);
|
||||||
|
// gameProcess.send({
|
||||||
|
// command: serverGlobals.COMMAND.START_GAME,
|
||||||
|
// cycleNumber: game.words.length - 1,
|
||||||
|
// cycleLength: game.timePerWord * 1000,
|
||||||
|
// accessCode: game.accessCode
|
||||||
|
// });
|
||||||
|
// break;
|
||||||
|
// case serverGlobals.COMMAND.START_GAME:
|
||||||
|
// game.status = serverGlobals.GAME_STATE.STARTED;
|
||||||
|
// game.lastCycleTime = new Date().toJSON();
|
||||||
|
// logger.debug('GAME PARENT PROCESS ' + game.accessCode + ': COMMAND: START GAME');
|
||||||
|
// namespace.in(game.accessCode).emit(serverGlobals.COMMAND.START_GAME, {
|
||||||
|
// firstWord: game.words[0].baseword,
|
||||||
|
// gameLength: game.words.length,
|
||||||
|
// timePerWord: game.timePerWord * 1000
|
||||||
|
// });
|
||||||
|
// break;
|
||||||
|
// case serverGlobals.COMMAND.CYCLE_WORD:
|
||||||
|
// game.currentWordIndex += 1;
|
||||||
|
// game.lastCycleTime = new Date().toJSON();
|
||||||
|
// logger.debug('GAME PARENT PROCESS ' + game.accessCode + ': COMMAND: CYCLE WORD');
|
||||||
|
// if (game.currentWordIndex < game.words.length) {
|
||||||
|
// namespace.in(game.accessCode).emit(serverGlobals.COMMAND.CYCLE_WORD, {
|
||||||
|
// word: game.words[game.currentWordIndex].baseword,
|
||||||
|
// index: game.currentWordIndex + 1,
|
||||||
|
// totalTime: game.timePerWord * 1000,
|
||||||
|
// gameLength: game.words.length
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// gameProcess.send({
|
||||||
|
// command: serverGlobals.COMMAND.CYCLE_WORD,
|
||||||
|
// cycleIndex: game.currentWordIndex,
|
||||||
|
// cycleLength: game.timePerWord * 1000,
|
||||||
|
// accessCode: game.accessCode,
|
||||||
|
// gameLength: game.words.length
|
||||||
|
// });
|
||||||
|
// break;
|
||||||
|
// case serverGlobals.COMMAND.END_GAME:
|
||||||
|
// game.status = serverGlobals.GAME_STATE.ENDED;
|
||||||
|
// if (!game.posted) {
|
||||||
|
// logger.debug('GAME PARENT PROCESS: GAME ' + game.accessCode + ' HAS ENDED...BEGINNING POST TO DATABASE');
|
||||||
|
// this.postGameFn(game).then(() => {
|
||||||
|
// game.posted = true;
|
||||||
|
// logger.debug('GAME ' + game.accessCode + ' SUCCESSFULLY POSTED');
|
||||||
|
// namespace.in(game.accessCode).emit(serverGlobals.COMMAND.END_GAME, game.accessCode);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// gameProcess.on('exit', () => {
|
||||||
|
// if (this.activeGames[game.accessCode]) {
|
||||||
|
// delete this.activeGames[game.accessCode];
|
||||||
|
// logger.debug('GAME ' + game.accessCode + ' REMOVED FROM ACTIVE GAMES.');
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// gameProcess.send({ command: serverGlobals.COMMAND.START_COUNTDOWN, accessCode: game.accessCode });
|
||||||
|
// game.status = serverGlobals.GAME_STATE.STARTING;
|
||||||
|
// game.startCountdownTime = new Date().toJSON();
|
||||||
|
// namespace.in(game.accessCode).emit(serverGlobals.COMMAND.START_COUNTDOWN);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Singleton {
|
||||||
|
constructor () {
|
||||||
|
if (!Singleton.instance) {
|
||||||
|
logger.log('CREATING SINGLETON ACTIVE GAME RUNNER');
|
||||||
|
Singleton.instance = new ActiveGameRunner();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getInstance () {
|
||||||
|
return Singleton.instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Singleton;
|
||||||
@@ -1,23 +1,56 @@
|
|||||||
const globals = require('../config/globals');
|
const globals = require('../config/globals');
|
||||||
|
const ActiveGameRunner = require('./ActiveGameRunner');
|
||||||
|
const Game = require('../model/Game');
|
||||||
|
const Person = require('../model/Person');
|
||||||
|
|
||||||
class GameManager {
|
class GameManager {
|
||||||
constructor (logger) {
|
constructor (logger) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
//this.activeGameRunner = new ActiveGameRunner(this.postGame).getInstance();
|
this.activeGameRunner = new ActiveGameRunner().getInstance();
|
||||||
|
this.namespace = null;
|
||||||
//this.gameSocketUtility = GameSocketUtility;
|
//this.gameSocketUtility = GameSocketUtility;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addGameSocketHandlers = (namespace, socket) => {
|
||||||
|
this.namespace = namespace;
|
||||||
|
socket.on(globals.CLIENT_COMMANDS.FETCH_GAME_STATE, (accessCode, personId, ackFn) => {
|
||||||
|
handleRequestForGameState(this.namespace, this.logger, this.activeGameRunner, accessCode, personId, ackFn, socket);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
createGame = (gameParams) => {
|
createGame = (gameParams) => {
|
||||||
const expectedKeys = ['deck', 'hasTimer', 'timerParams'];
|
const expectedKeys = ['deck', 'hasTimer', 'timerParams', 'moderatorName'];
|
||||||
if (typeof gameParams !== 'object' || expectedKeys.some((key) => !Object.keys(gameParams).includes(key))) {
|
if (typeof gameParams !== 'object' || expectedKeys.some((key) => !Object.keys(gameParams).includes(key))) {
|
||||||
this.logger.error('Tried to create game with invalid options: ' + JSON.stringify(gameParams));
|
this.logger.error('Tried to create game with invalid options: ' + JSON.stringify(gameParams));
|
||||||
return Promise.reject('Tried to create game with invalid options: ' + gameParams);
|
return Promise.reject('Tried to create game with invalid options: ' + gameParams);
|
||||||
} else {
|
} else {
|
||||||
const newAccessCode = this.generateAccessCode();
|
const newAccessCode = this.generateAccessCode();
|
||||||
|
this.activeGameRunner.activeGames[newAccessCode] = new Game(
|
||||||
|
globals.STATUS.LOBBY,
|
||||||
|
initializePeopleForGame(gameParams.moderatorName, gameParams.deck),
|
||||||
|
gameParams.deck,
|
||||||
|
gameParams.hasTimer,
|
||||||
|
gameParams.timerParams
|
||||||
|
);
|
||||||
return Promise.resolve(newAccessCode);
|
return Promise.resolve(newAccessCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
joinGame = (code) => {
|
||||||
|
let game = this.activeGameRunner.activeGames[code];
|
||||||
|
if (game) {
|
||||||
|
let unassignedPerson = game.people.find((person) => person.assigned === false);
|
||||||
|
if (!unassignedPerson) {
|
||||||
|
return Promise.resolve(new Error(globals.ERROR_MESSAGE.GAME_IS_FULL));
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(code);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
generateAccessCode = () => {
|
generateAccessCode = () => {
|
||||||
const numLetters = globals.ACCESS_CODE_CHAR_POOL.length;
|
const numLetters = globals.ACCESS_CODE_CHAR_POOL.length;
|
||||||
const codeDigits = [];
|
const codeDigits = [];
|
||||||
@@ -36,6 +69,49 @@ function getRandomInt (max) {
|
|||||||
return Math.floor(Math.random() * Math.floor(max));
|
return Math.floor(Math.random() * Math.floor(max));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initializeModerator(name) {
|
||||||
|
return new Person(createRandomUserId(), name, globals.USER_TYPES.MODERATOR)
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializePeopleForGame(modName, uniqueCards) {
|
||||||
|
let people = [];
|
||||||
|
let cards = []; // this will contain copies of each card equal to the quantity.
|
||||||
|
people.push(initializeModerator(modName));
|
||||||
|
let numberOfRoles = 0;
|
||||||
|
for (let card of uniqueCards) {
|
||||||
|
for (let i = 0; i < card.quantity; i ++) {
|
||||||
|
cards.push(card);
|
||||||
|
numberOfRoles ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cards = shuffleArray(cards); // The deck should probably be shuffled, ey?.
|
||||||
|
|
||||||
|
for(let j = 0; j < numberOfRoles; j ++) {
|
||||||
|
people.push(new Person(createRandomUserId(), null, globals.USER_TYPES.PLAYER, cards[j].role, cards[j].description))
|
||||||
|
}
|
||||||
|
|
||||||
|
return people;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shuffleArray (array) {
|
||||||
|
for (let i = 0; i < array.length; i++) {
|
||||||
|
const randIndex = Math.floor(Math.random() * i);
|
||||||
|
const temp = array[i];
|
||||||
|
array[i] = array[randIndex];
|
||||||
|
array[randIndex] = temp;
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRandomUserId () {
|
||||||
|
let id = '';
|
||||||
|
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)];
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
class Singleton {
|
class Singleton {
|
||||||
constructor (logger) {
|
constructor (logger) {
|
||||||
if (!Singleton.instance) {
|
if (!Singleton.instance) {
|
||||||
@@ -49,4 +125,72 @@ class Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Since clients are anonymous, we have to rely to some extent on a cookie to identify them. Socket ids
|
||||||
|
are unique to a client, but they are re-generated if a client disconnects and then reconnects.
|
||||||
|
Thus, to have the most resilient identification i.e. to let them refresh, navigate away and come back,
|
||||||
|
get disconnected and reconnect, etc. we should have a combination of the socket id and the cookie. This
|
||||||
|
will also allow us to reject certain theoretical ways of breaking things, such as copying someone else's
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
function handleRequestForGameState(namespace, logger, gameRunner, accessCode, personId, ackFn, socket) {
|
||||||
|
const game = gameRunner.activeGames[accessCode];
|
||||||
|
if (game) {
|
||||||
|
let matchingPerson = game.people.find((person) => person.id === personId);
|
||||||
|
if (matchingPerson) {
|
||||||
|
if (matchingPerson.socketId === socket.id) {
|
||||||
|
logger.debug("matching person found with an established connection to the room: " + matchingPerson.name);
|
||||||
|
ackFn(getGameStateFromPerspectiveOfPerson(game, matchingPerson));
|
||||||
|
} else {
|
||||||
|
if (!roomContainsSocketOfMatchingPerson(namespace, matchingPerson, logger, accessCode)) {
|
||||||
|
logger.debug("matching person found with a new connection to the room: " + matchingPerson.name);
|
||||||
|
socket.join(accessCode);
|
||||||
|
matchingPerson.socketId = socket.id;
|
||||||
|
ackFn(getGameStateFromPerspectiveOfPerson(game, matchingPerson));
|
||||||
|
} else {
|
||||||
|
rejectClientRequestForGameState(ackFn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let personWithMatchingSocketId = findPersonWithMatchingSocketId(game.people, socket.id);
|
||||||
|
if (personWithMatchingSocketId) {
|
||||||
|
logger.debug("matching person found whose cookie got cleared after establishing a connection to the room: " + personWithMatchingSocketId.name);
|
||||||
|
ackFn(getGameStateFromPerspectiveOfPerson(game, personWithMatchingSocketId));
|
||||||
|
} else {
|
||||||
|
let unassignedPerson = game.people.find((person) => person.assigned === false);
|
||||||
|
if (unassignedPerson) {
|
||||||
|
logger.debug("completely new person with a first connection to the room: " + unassignedPerson.name);
|
||||||
|
socket.join(accessCode);
|
||||||
|
unassignedPerson.assigned = true;
|
||||||
|
unassignedPerson.socketId = socket.id;
|
||||||
|
ackFn(getGameStateFromPerspectiveOfPerson(game, unassignedPerson));
|
||||||
|
} else {
|
||||||
|
rejectClientRequestForGameState(ackFn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rejectClientRequestForGameState(ackFn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGameStateFromPerspectiveOfPerson(game, person) {
|
||||||
|
return person;
|
||||||
|
}
|
||||||
|
|
||||||
|
// in socket.io 2.x , the rooms property is an object. in 3.x and 4.x, it is a javascript Set.
|
||||||
|
function roomContainsSocketOfMatchingPerson(namespace, matchingPerson, logger, accessCode) {
|
||||||
|
return namespace.adapter
|
||||||
|
&& namespace.adapter.rooms[accessCode]
|
||||||
|
&& namespace.adapter.rooms[accessCode].sockets[matchingPerson.socketId];
|
||||||
|
}
|
||||||
|
|
||||||
|
function rejectClientRequestForGameState(acknowledgementFunction) {
|
||||||
|
return acknowledgementFunction(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPersonWithMatchingSocketId(people, socketId) {
|
||||||
|
return people.find((person) => person.socketId === socketId);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = Singleton;
|
module.exports = Singleton;
|
||||||
|
|||||||
@@ -10,4 +10,9 @@ router.get('/create', function (request, response) {
|
|||||||
response.sendFile(path.join(__dirname, '../../client/views/create.html'));
|
response.sendFile(path.join(__dirname, '../../client/views/create.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get('/game/:code', function (request, response) {
|
||||||
|
response.sendFile(path.join(__dirname, '../../client/views/game.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
Reference in New Issue
Block a user