mirror of
https://github.com/AlecM33/Werewolf.git
synced 2025-12-26 07:47:50 +01:00
basic game creation
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
.idea
|
||||
node_modules/*
|
||||
.vscode/launch.json
|
||||
package-lock.json
|
||||
|
||||
BIN
client/images/logo.gif
Normal file
BIN
client/images/logo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 202 KiB |
7
client/model/Game.js
Normal file
7
client/model/Game.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export class Game {
|
||||
constructor(deck, hasTimer, timerParams=null) {
|
||||
this.deck = deck;
|
||||
this.hasTimer = hasTimer;
|
||||
this.timerParams = timerParams;
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,8 @@ export class DeckStateManager {
|
||||
}
|
||||
|
||||
addToDeck(role) {
|
||||
let option = this.customRoleOptions.find((option) => option.role === role)
|
||||
let existingCard = this.deck.find((card) => card.role === role)
|
||||
let option = this.customRoleOptions.find((option) => option.role === role);
|
||||
let existingCard = this.deck.find((card) => card.role === role);
|
||||
if (option && !existingCard) {
|
||||
option.quantity = 0;
|
||||
this.deck.push(option);
|
||||
@@ -32,11 +32,21 @@ export class DeckStateManager {
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentDeck() { return this.deck }
|
||||
getCurrentDeck() { return this.deck; }
|
||||
|
||||
getCard(role) { return this.deck.find((card) => card.role === role) }
|
||||
getCard(role) {
|
||||
return this.deck.find(
|
||||
(card) => card.role.toLowerCase().trim() === role.toLowerCase().trim()
|
||||
);
|
||||
}
|
||||
|
||||
getCurrentCustomRoleOptions() { return this.customRoleOptions }
|
||||
getCurrentCustomRoleOptions() { return this.customRoleOptions; }
|
||||
|
||||
getCustomRoleOption(role) {
|
||||
return this.customRoleOptions.find(
|
||||
(option) => option.role.toLowerCase().trim() === role.toLowerCase().trim()
|
||||
)
|
||||
};
|
||||
|
||||
getDeckSize() {
|
||||
let total = 0;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const ModalManager = {
|
||||
displayModal: displayModal
|
||||
displayModal: displayModal,
|
||||
dispelModal: dispelModal
|
||||
}
|
||||
|
||||
function displayModal(modalId, backgroundId, closeButtonId) {
|
||||
|
||||
40
client/modules/XHRUtility.js
Normal file
40
client/modules/XHRUtility.js
Normal file
@@ -0,0 +1,40 @@
|
||||
export const XHRUtility =
|
||||
{
|
||||
standardHeaders: [['Content-Type', 'application/json'], ['Accept', 'application/json'], ['X-Requested-With', 'XMLHttpRequest']],
|
||||
|
||||
// Easily make XHR calls with a promise wrapper. Defaults to GET and MIME type application/JSON
|
||||
xhr (url, method = 'GET', headers, body = null) {
|
||||
if (headers === undefined || headers === null) {
|
||||
headers = this.standardHeaders;
|
||||
}
|
||||
if (typeof url !== 'string' || url.trim().length < 1) {
|
||||
return Promise.reject('Cannot request with empty URL: ' + url);
|
||||
}
|
||||
|
||||
const req = new XMLHttpRequest();
|
||||
req.open(method, url.trim());
|
||||
|
||||
for (const hdr of headers) {
|
||||
if (hdr.length !== 2) continue;
|
||||
req.setRequestHeader(hdr[0], hdr[1]);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
req.onload = function () {
|
||||
const response = {
|
||||
status: this.status,
|
||||
statusText: this.statusText,
|
||||
content: this.responseText
|
||||
};
|
||||
if (this.status >= 200 && this.status < 400) {
|
||||
resolve(response);
|
||||
} else {
|
||||
reject(response);
|
||||
}
|
||||
};
|
||||
body ? req.send(body) : req.send();
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
2
client/modules/third_party/jQuery/jquery-3.6.0.min.js
vendored
Normal file
2
client/modules/third_party/jQuery/jquery-3.6.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
client/modules/third_party/semantic-ui/dropdown.min.js
vendored
Normal file
1
client/modules/third_party/semantic-ui/dropdown.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
3956
client/modules/third_party/semantic-ui/index.js
vendored
Normal file
3956
client/modules/third_party/semantic-ui/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
client/modules/third_party/semantic-ui/search.min.js
vendored
Normal file
1
client/modules/third_party/semantic-ui/search.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
client/modules/third_party/semantic-ui/transition.min.js
vendored
Normal file
1
client/modules/third_party/semantic-ui/transition.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -3,6 +3,8 @@ import { ModalManager } from "../modules/ModalManager.js";
|
||||
import { defaultCards } from "../config/defaultCards.js";
|
||||
import { customCards } from "../config/customCards.js";
|
||||
import { DeckStateManager } from "../modules/DeckStateManager.js";
|
||||
import {XHRUtility} from "../modules/XHRUtility.js";
|
||||
import {Game} from "../model/Game.js";
|
||||
|
||||
export const create = () => {
|
||||
let deckManager = new DeckStateManager();
|
||||
@@ -10,6 +12,35 @@ export const create = () => {
|
||||
loadCustomCards(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,
|
||||
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", () => {
|
||||
@@ -47,23 +78,25 @@ function loadCustomCards(deckManager) {
|
||||
});
|
||||
let selectEl = document.createElement("select");
|
||||
selectEl.setAttribute("id", "deck-select");
|
||||
selectEl.setAttribute("class", "ui search dropdown");
|
||||
addOptionsToList(customCards, selectEl);
|
||||
form.appendChild(selectEl);
|
||||
let submitBtn = document.createElement("input");
|
||||
submitBtn.setAttribute("type", "submit");
|
||||
submitBtn.setAttribute("value", "Add Role to Deck");
|
||||
submitBtn.setAttribute("value", "Add Role");
|
||||
submitBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
if (selectEl.selectedIndex > 0) {
|
||||
if (selectEl.value && selectEl.value.length > 0) {
|
||||
deckManager.addToDeck(selectEl.value);
|
||||
let cardEl = constructCompactDeckBuilderElement(deckManager.getCard(selectEl.value), deckManager);
|
||||
updateCustomRoleOptionsList(deckManager, selectEl);
|
||||
document.getElementById("deck").appendChild(cardEl);
|
||||
document.querySelector("#add-card-to-deck-form .text").innerText = "";
|
||||
}
|
||||
})
|
||||
form.appendChild(submitBtn);
|
||||
|
||||
|
||||
$('.ui.dropdown')
|
||||
.dropdown();
|
||||
deckManager.customRoleOptions = customCards;
|
||||
}
|
||||
|
||||
@@ -73,12 +106,7 @@ function updateCustomRoleOptionsList(deckManager, selectEl) {
|
||||
}
|
||||
|
||||
function addOptionsToList(options, selectEl) {
|
||||
let noneSelected = document.createElement("option");
|
||||
noneSelected.innerText = "None selected"
|
||||
noneSelected.disabled = true;
|
||||
noneSelected.selected = true;
|
||||
selectEl.appendChild(noneSelected);
|
||||
for (let i = 0; i < options.length; i ++) { // each dropdown should include every
|
||||
for (let i = 0; i < options.length; i ++) {
|
||||
let optionEl = document.createElement("option");
|
||||
optionEl.setAttribute("value", customCards[i].role);
|
||||
optionEl.innerText = customCards[i].role;
|
||||
@@ -91,30 +119,61 @@ function constructCompactDeckBuilderElement(card, deckManager) {
|
||||
|
||||
cardContainer.setAttribute("class", "compact-card");
|
||||
|
||||
cardContainer.setAttribute("id", "card-" + card.role);
|
||||
cardContainer.setAttribute("id", "card-" + card.role);
|
||||
cardContainer.setAttribute("id", "card-" + card.role.replaceAll(' ', '-'));
|
||||
|
||||
cardContainer.innerHTML =
|
||||
"<div class='compact-card-left'>" +
|
||||
"<p>-</p>" +
|
||||
"</div>" +
|
||||
"<div class='compact-card-header'>" +
|
||||
"<p class='card-role'>" + card.role + "</p>" +
|
||||
"<p class='card-role'></p>" +
|
||||
"<div class='card-quantity'>0</div>" +
|
||||
"</div>" +
|
||||
"<div class='compact-card-right'>" +
|
||||
"<p>+</p>" +
|
||||
"</div>";
|
||||
|
||||
cardContainer.querySelector('.card-role').innerText = card.role;
|
||||
|
||||
cardContainer.querySelector('.compact-card-right').addEventListener('click', () => {
|
||||
deckManager.addCopyOfCard(card.role);
|
||||
cardContainer.querySelector('.card-quantity').innerText = deckManager.getCard(card.role).quantity;
|
||||
document.querySelector('label[for="deck"]').innerText = 'Game Deck: ' + deckManager.getDeckSize() + ' Players';
|
||||
if (deckManager.getCard(card.role).quantity > 0) {
|
||||
document.getElementById('card-' + card.role.replaceAll(' ', '-')).classList.add('selected-card')
|
||||
}
|
||||
});
|
||||
cardContainer.querySelector('.compact-card-left').addEventListener('click', () => {
|
||||
deckManager.removeCopyOfCard(card.role);
|
||||
cardContainer.querySelector('.card-quantity').innerText = deckManager.getCard(card.role).quantity;
|
||||
document.querySelector('label[for="deck"]').innerText = 'Game Deck: ' + deckManager.getDeckSize() + ' Players';
|
||||
if (deckManager.getCard(card.role).quantity === 0) {
|
||||
document.getElementById('card-' + card.role.replaceAll(' ', '-')).classList.remove('selected-card')
|
||||
}
|
||||
});
|
||||
return cardContainer;
|
||||
}
|
||||
|
||||
function hasTimer() {
|
||||
return document.getElementById("game-hours").value.length > 0 || document.getElementById("game-minutes").value.length > 0
|
||||
}
|
||||
|
||||
function createGameForHosting(deck, hasTimer, timerParams) {
|
||||
XHRUtility.xhr(
|
||||
'/api/games/create',
|
||||
'POST',
|
||||
null,
|
||||
JSON.stringify(
|
||||
new Game(deck, hasTimer, timerParams)
|
||||
)
|
||||
)
|
||||
.then((res) => {
|
||||
if (res
|
||||
&& typeof res === 'object'
|
||||
&& Object.prototype.hasOwnProperty.call(res, 'content')
|
||||
&& typeof res.content === 'string'
|
||||
) {
|
||||
window.location = ('/games/' + res.content);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export const home = () => {
|
||||
|
||||
};
|
||||
|
||||
@@ -58,6 +58,11 @@ label {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
textarea, input {
|
||||
font-family: 'signika-negative', sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
button, input[type="submit"] {
|
||||
font-family: 'signika-negative', sans-serif !important;
|
||||
padding: 10px;
|
||||
@@ -76,3 +81,65 @@ button:hover, input[type="submit"]:hover {
|
||||
input {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.info-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
font-family: 'signika-negative', sans-serif;
|
||||
font-weight: 100;
|
||||
box-shadow: 0 2px 4px 0 rgb(0 0 0 / 25%);
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: fit-content;
|
||||
max-width: 30em;
|
||||
min-width: 15em;
|
||||
font-size: 20px;
|
||||
margin: 0 auto;
|
||||
animation: fade-in-slide-down-then-exit 6s ease;
|
||||
animation-fill-mode: forwards;
|
||||
animation-direction: normal;
|
||||
}
|
||||
|
||||
#navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
margin-bottom: -2em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#navbar a {
|
||||
color: #f7f7f7;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
#navbar a:hover {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
|
||||
@keyframes fade-in-slide-down-then-exit {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
5% {
|
||||
opacity: 1;
|
||||
transform: translateY(0px);
|
||||
}
|
||||
95% {
|
||||
opacity: 1;
|
||||
transform: translateY(0px);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.compact-card {
|
||||
border: 2px solid transparent;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
@@ -28,6 +29,11 @@
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 8em;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.selected-card {
|
||||
border: 2px solid #0075F2;
|
||||
}
|
||||
|
||||
.compact-card-right p {
|
||||
@@ -51,6 +57,7 @@
|
||||
.compact-card .card-quantity {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.compact-card-header {
|
||||
@@ -78,6 +85,8 @@
|
||||
#deck {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow: auto;
|
||||
max-height: 20em;
|
||||
}
|
||||
|
||||
form {
|
||||
@@ -116,13 +125,20 @@ select {
|
||||
|
||||
label[for="game-time"], label[for="add-card-to-deck-form"], label[for="deck"] {
|
||||
color: #0075F2;
|
||||
font-size: 30px;
|
||||
font-size: 20px;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#create-game{
|
||||
color: #45a445;
|
||||
font-size: 30px;
|
||||
margin-top: 2em;
|
||||
padding: 10px 50px;
|
||||
margin: 1em auto 3em auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
body {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 20px;
|
||||
font-size: 25px;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 1em 0;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
background-color: #1f1f1f;
|
||||
}
|
||||
|
||||
h3 {
|
||||
max-width: 30em;
|
||||
font-size: 16px;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 650px;
|
||||
width: 63vw;
|
||||
min-width: 430px;
|
||||
}
|
||||
|
||||
form > div {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
background-color: transparent;
|
||||
border: 1px solid white;
|
||||
border-radius: 3px;
|
||||
color: #f7f7f7;
|
||||
}
|
||||
|
||||
#join-container > label {
|
||||
font-size: 35px;
|
||||
font-family: 'diavlo', sans-serif;
|
||||
color: #ab2626;
|
||||
filter: drop-shadow(2px 2px 4px black);
|
||||
}
|
||||
|
||||
label[for="room-code"], label[for="player-name"] {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #f7f7f7;
|
||||
background-color: #23282b;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-width: 17em;
|
||||
@@ -40,3 +40,14 @@
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#modal-button-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#modal-button-container input {
|
||||
color: #21ba45;
|
||||
}
|
||||
|
||||
9
client/styles/third_party/dropdown.min.css
vendored
Normal file
9
client/styles/third_party/dropdown.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
9
client/styles/third_party/search.min.css
vendored
Normal file
9
client/styles/third_party/search.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
9
client/styles/third_party/transition.min.css
vendored
Normal file
9
client/styles/third_party/transition.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -16,21 +16,37 @@
|
||||
<link rel="stylesheet" href="../styles/GLOBAL.css">
|
||||
<link rel="stylesheet" href="../styles/create.css">
|
||||
<link rel="stylesheet" href="../styles/modal.css">
|
||||
<link rel="stylesheet" href="../styles/third_party/dropdown.min.css">
|
||||
<link rel="stylesheet" href="../styles/third_party/transition.min.css">
|
||||
<link rel="stylesheet" href="../styles/third_party/search.min.css">
|
||||
<script src="../modules/third_party/jQuery/jquery-3.6.0.min.js"></script>
|
||||
<script src="../modules/third_party/semantic-ui/transition.min.js"></script>
|
||||
<script src="../modules/third_party/semantic-ui/dropdown.min.js"></script>
|
||||
<script src="../modules/third_party/semantic-ui/search.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar">
|
||||
<a href="/">
|
||||
<img alt="logo" src="../images/Werewolf_Small.png"/>
|
||||
</a>
|
||||
<a href="/">Home</a>
|
||||
</div>
|
||||
<div id="add-role-modal-background" class="modal-background" style="display: none"></div>
|
||||
<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..."/>
|
||||
<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..."></textarea>
|
||||
<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>
|
||||
<button id="close-modal-button">Close</button>
|
||||
</div>
|
||||
<h1>Create A Game</h1>
|
||||
<h3>
|
||||
@@ -51,10 +67,10 @@
|
||||
<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" placeholder="e.g. 1"/>
|
||||
min="0" max="5" />
|
||||
<label for="game-hours">Minutes</label>
|
||||
<input type="number" id="game-minutes" name="game-minutes"
|
||||
min="0" max="60" placeholder="e.g. 30"/>
|
||||
min="1" max="60" />
|
||||
</div>
|
||||
</div>
|
||||
<input id="create-game" type="submit" value="Create"/>
|
||||
|
||||
@@ -20,9 +20,24 @@
|
||||
<link rel="stylesheet" href="../styles/home.css">
|
||||
</head>
|
||||
<body>
|
||||
<img src="../images/logo.gif"/>
|
||||
<h3>This is a tool to run games of Werewolf when not in-person, or when you don't possess a deck of cards. Create a game and moderate, or join one and have a role dealt to your device.</h3>
|
||||
<a href="/create">
|
||||
<button>Create A Game</button>
|
||||
</a>
|
||||
<div id="join-container">
|
||||
<label for="join-form">Join A Game</label>
|
||||
<form id="join-form">
|
||||
<div>
|
||||
<label for="room-code">Room Code</label>
|
||||
<input id="room-code" type="text" placeholder="six-character code..." required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="player-name">Your Name</label>
|
||||
<input id="player-name" type="text" required/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script type="module">
|
||||
import { home } from "../scripts/home.js";
|
||||
home();
|
||||
|
||||
@@ -12,11 +12,10 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cron": "^1.7.1",
|
||||
"express": "^4.17.1",
|
||||
"express-force-https": "^1.0.0",
|
||||
"jasmine": "^3.5.0",
|
||||
"socket.io": "^2.2.0"
|
||||
"socket.io": "^2.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"open": "^7.0.3"
|
||||
|
||||
20
server/api/GamesAPI.js
Normal file
20
server/api/GamesAPI.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const debugMode = Array.from(process.argv.map((arg) => arg.trim().toLowerCase())).includes('debug');
|
||||
const logger = require('../modules/logger')(debugMode);
|
||||
const GameManager = require('../modules/GameManager.js');
|
||||
|
||||
const gameManager = new GameManager().getInstance();
|
||||
|
||||
router.post('/create', function (req, res) {
|
||||
const gameCreationPromise = gameManager.createGame(req.body, false);
|
||||
gameCreationPromise.then((result) => {
|
||||
if (result instanceof Error) {
|
||||
res.status(500).send();
|
||||
} else {
|
||||
res.send(result); // game was created successfully, and access code was returned
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
6
server/config/globals.js
Normal file
6
server/config/globals.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const globals = {
|
||||
ACCESS_CODE_CHAR_POOL: 'abcdefghijklmnopqrstuvwxyz0123456789',
|
||||
ACCESS_CODE_LENGTH: 6,
|
||||
};
|
||||
|
||||
module.exports = globals;
|
||||
@@ -7,7 +7,7 @@ const socketIO = require('socket.io');
|
||||
const app = express();
|
||||
let main;
|
||||
const bodyParser = require('body-parser');
|
||||
// const GameManager = require('./modules/managers/GameManager.js');
|
||||
const GameManager = require('./modules/GameManager.js');
|
||||
// const QueueManager = require('./modules/managers/QueueManager');
|
||||
|
||||
app.use(bodyParser.json()); // to support JSON-encoded bodies
|
||||
@@ -55,17 +55,15 @@ const io = socketIO(main);
|
||||
app.set('port', port);
|
||||
|
||||
/* Instantiate the singleton game manager */
|
||||
//const gameManager = new GameManager(logger).getInstance();
|
||||
const gameManager = new GameManager(logger).getInstance();
|
||||
|
||||
/* Instantiate the singleton queue manager */
|
||||
//const queueManager = new QueueManager(matchmaking, logger).getInstance();
|
||||
|
||||
|
||||
/* api endpoints */
|
||||
// const games = require('./api/GamesAPI');
|
||||
// const words = require('./api/WordsAPI');
|
||||
// app.use('/api/games', games);
|
||||
// app.use('/api/words', words);
|
||||
const games = require('./api/GamesAPI');
|
||||
app.use('/api/games', games);
|
||||
|
||||
/* serve all the app's pages */
|
||||
app.use('/manifest.json', (req, res) => {
|
||||
@@ -88,7 +86,6 @@ app.use(function (req, res) {
|
||||
res.sendFile(path.join(__dirname, '../client/views/404.html'));
|
||||
});
|
||||
|
||||
// Starts the main.
|
||||
main.listen(port, function () {
|
||||
logger.log(`Starting server on port ${port} http://localhost:${port}` );
|
||||
});
|
||||
|
||||
52
server/modules/GameManager.js
Normal file
52
server/modules/GameManager.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const globals = require('../config/globals');
|
||||
|
||||
class GameManager {
|
||||
constructor (logger) {
|
||||
this.logger = logger;
|
||||
//this.activeGameRunner = new ActiveGameRunner(this.postGame).getInstance();
|
||||
//this.gameSocketUtility = GameSocketUtility;
|
||||
}
|
||||
|
||||
createGame = (gameParams) => {
|
||||
const expectedKeys = ['deck', 'hasTimer', 'timerParams'];
|
||||
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));
|
||||
return Promise.reject('Tried to create game with invalid options: ' + gameParams);
|
||||
} else {
|
||||
const newAccessCode = this.generateAccessCode();
|
||||
return Promise.resolve(newAccessCode);
|
||||
}
|
||||
}
|
||||
|
||||
generateAccessCode = () => {
|
||||
const numLetters = globals.ACCESS_CODE_CHAR_POOL.length;
|
||||
const codeDigits = [];
|
||||
let iterations = globals.ACCESS_CODE_LENGTH;
|
||||
while (iterations > 0) {
|
||||
iterations--;
|
||||
codeDigits.push(globals.ACCESS_CODE_CHAR_POOL[getRandomInt(numLetters)]);
|
||||
}
|
||||
return codeDigits.join('');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function getRandomInt (max) {
|
||||
return Math.floor(Math.random() * Math.floor(max));
|
||||
}
|
||||
|
||||
class Singleton {
|
||||
constructor (logger) {
|
||||
if (!Singleton.instance) {
|
||||
logger.log('CREATING SINGLETON GAME MANAGER');
|
||||
Singleton.instance = new GameManager(logger);
|
||||
}
|
||||
}
|
||||
|
||||
getInstance () {
|
||||
return Singleton.instance;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Singleton;
|
||||
@@ -3,11 +3,11 @@ const staticRouter = express.Router();
|
||||
const path = require('path');
|
||||
const checkIfFileExists = require("./util");
|
||||
|
||||
staticRouter.use('/styles/*', (req, res) => {
|
||||
staticRouter.use('/styles/**', (req, res) => {
|
||||
let filePath = path.join(__dirname, ('../../client/' + req.baseUrl));
|
||||
let extension = path.extname(filePath);
|
||||
checkIfFileExists(filePath).then((fileExists) => {
|
||||
if (fileExists && (extension === '.css')) {
|
||||
if (fileExists && (extension === '.css' || extension === '.min.css')) {
|
||||
res.sendFile(filePath);
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
@@ -27,11 +27,11 @@ staticRouter.use('/client/webfonts/*', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
staticRouter.use('/client/images/*', (req, res) => {
|
||||
let filePath = path.join(__dirname, ('../' + req.baseUrl));
|
||||
staticRouter.use('/images/*', (req, res) => {
|
||||
let filePath = path.join(__dirname, ('../../client/' + req.baseUrl));
|
||||
let extension = path.extname(filePath);
|
||||
checkIfFileExists(filePath).then((fileExists) => {
|
||||
if (fileExists && (extension === '.svg' || extension === '.png' || extension === '.jpg')) {
|
||||
if (fileExists && (extension === '.svg' || extension === '.png' || extension === '.jpg' || extension === '.gif')) {
|
||||
res.sendFile(filePath);
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
@@ -88,7 +88,19 @@ staticRouter.use('/config/*', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
staticRouter.use('/modules/*', (req, res) => {
|
||||
staticRouter.use('/modules/**', (req, res) => {
|
||||
let filePath = path.join(__dirname, ('../../client/' + req.baseUrl));
|
||||
let extension = path.extname(filePath);
|
||||
checkIfFileExists(filePath).then((fileExists) => {
|
||||
if (fileExists && (extension === '.js' || extension === '.min.js')) {
|
||||
res.sendFile(filePath);
|
||||
} else {
|
||||
res.sendFile('../views/404.html');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
staticRouter.use('/model/**', (req, res) => {
|
||||
let filePath = path.join(__dirname, ('../../client/' + req.baseUrl));
|
||||
let extension = path.extname(filePath);
|
||||
checkIfFileExists(filePath).then((fileExists) => {
|
||||
|
||||
Reference in New Issue
Block a user