Added modal for role descriptions, learning page, fixed cron job

This commit is contained in:
Alec Maier
2019-09-03 02:26:47 -04:00
parent 6c833e4acf
commit 053de5a1c1
10 changed files with 305 additions and 24 deletions

View File

@@ -6,28 +6,45 @@ const app = express();
const server = http.Server(app); const server = http.Server(app);
const io = socketIO(server); const io = socketIO(server);
// cron job for periodically clearing finished games
const CronJob = require('cron').CronJob; const CronJob = require('cron').CronJob;
var activeGames = {}; var activeGames = {};
// cron job for periodically clearing finished games
const job = new CronJob('0 0 */2 * * *', function() {
console.log(activeGames);
for (const key in activeGames) {
if (activeGames.hasOwnProperty(key) && activeGames[key].state === "ended") {
delete activeGames[key];
}
}
console.log("Games pruned at: " + (new Date().toDateString()) + " " + (new Date()).toTimeString());
});
console.log("cron job created");
job.start();
app.set('port', 5000); app.set('port', 5000);
app.use('/static', express.static(__dirname + '/static')); // Routing app.use('/static', express.static(__dirname + '/static')); // Routing
app.use('/assets', express.static(__dirname + '/assets')); // Routing app.use('/assets', express.static(__dirname + '/assets')); // Routing
app.get('/', function(request, response) { app.get('/', function(request, response) {
response.sendFile(__dirname + '/index.html'); response.sendFile(__dirname + '/views/index.html');
});
app.get('/learn', function(request, response) {
response.sendFile(__dirname + '/views/learn.html');
}); });
app.get('/create', function(request, response) { app.get('/create', function(request, response) {
response.sendFile(__dirname + '/create_game.html'); response.sendFile(__dirname + '/views/create_game.html');
}); });
app.get('/join', function(request, response) { app.get('/join', function(request, response) {
response.sendFile(__dirname + '/join_game.html'); response.sendFile(__dirname + '/views/join_game.html');
}); });
app.get('/:code', function(request, response) { app.get('/:code', function(request, response) {
response.sendFile(__dirname + '/game.html'); response.sendFile(__dirname + '/views/game.html');
}); });
// Starts the server. // Starts the server.
@@ -82,6 +99,7 @@ io.on('connection', function(socket) {
} }
} }
}); });
// broadcast current game state to all sockets in the room with a particular access code
socket.on('requestState', function(data) { socket.on('requestState', function(data) {
if(Object.keys(socket.rooms).includes(data.code) === false) { if(Object.keys(socket.rooms).includes(data.code) === false) {
socket.join(data.code, function() { socket.join(data.code, function() {

View File

@@ -5,28 +5,47 @@ export const cards = [
description: "During the day, find the wolves and kill them.", description: "During the day, find the wolves and kill them.",
powerRole: false powerRole: false
}, },
{ {
role: "Werewolf",
team: "wolf",
description: "During the night, choose a villager to kill. Don't get killed.",
powerRole: false
},
{
role: "Minion",
team: "wolf",
description: "You are an evil villager - you know who the wolves are, and you want them to win.",
powerRole: true
},
{
role: "Wolf Cub",
team: "wolf",
description: "If a wolf dies, you then become a wolf. Until then, you do not wake up with the other wolves.",
powerRole: true
},
{
role: "Seer", role: "Seer",
team: "village", team: "village",
description: "During the night, choose one person. The moderator will tell you whether that player is evil.", description: "During the night, choose one person. The moderator will tell you whether that player is a wolf.",
powerRole: true powerRole: true
}, },
{ {
role: "Hunter", role: "Hunter",
team: "village", team: "village",
description: "If you are alive with a wolf at the end of the game, the village wins.", description: "If you are alive with a wolf at the end of the game, you best the wolf, and the village wins.",
powerRole: true powerRole: true
}, },
{ {
role: "Werewolf", role: "Sorcerer",
team: "wolf", team: "village",
description: "During the night, choose a villager to kill. Don't get killed.", description: "Once a game, change who the wolves are going to kill to someone else, including yourself. You will" +
powerRole: false " see who is going to die each night until you use this power.",
powerRole: true
}, },
{ {
role: "Minion", role: "Prince",
team: "wolf", team: "village",
description: "You are villager, but you know who the wolves are - and want them to win.", description: "If you die, take someone else with you.",
powerRole: true powerRole: true
} }
]; ];

View File

@@ -199,7 +199,6 @@ function renderClock() {
function endGameDueToTimeExpired() { function endGameDueToTimeExpired() {
clearInterval(clock); clearInterval(clock);
console.log("expired!");
socket.emit("timerExpired", currentGame.accessCode); socket.emit("timerExpired", currentGame.accessCode);
} }

View File

@@ -36,15 +36,25 @@ var atLeastOnePlayer = false;
// register event listeners on buttons // register event listeners on buttons
document.getElementById("reset-btn").addEventListener("click", resetCardQuantities); document.getElementById("reset-btn").addEventListener("click", resetCardQuantities);
document.getElementById("create-btn").addEventListener("click", createGame); document.getElementById("create-btn").addEventListener("click", createGame);
document.getElementById("role-btn").addEventListener("click", displayRoleModal);
document.getElementById("close").addEventListener("click", closeModal);
// render all of the available cards to the user // render all of the available cards to the user
window.onload = function() { window.onload = function() {
for (const card of cards) { for (const card of cards) {
const newCard = new Card(card.role, card.team, card.description, card.powerRole); const newCard = new Card(card.role, card.team, card.description, card.powerRole);
const cardContainer = document.createElement("div"); // put card info in the informational role description modal
const modalRole = document.createElement("div");
modalRole.setAttribute("class", "modal-role");
modalRole.innerHTML = card.team === "village" ?
"<h2 class='role-village'>" + card.role + "</h2><p>" + card.description + "</p>"
: "<h2 class='role-wolf'>" + card.role + "</h2><p>" + card.description + "</p>";
document.getElementById("roles").appendChild(modalRole);
fullDeck.push(newCard); fullDeck.push(newCard);
const cardContainer = document.createElement("div");
cardContainer.setAttribute("class", "card"); cardContainer.setAttribute("class", "card");
cardContainer.innerHTML = "<p class='card-role'>" + newCard.role + "</p><br><p class='card-quantity'>" + newCard.quantity + "</p>"; cardContainer.innerHTML = "<p class='card-role'>" + newCard.role + "</p><br><p class='card-quantity'>" + newCard.quantity + "</p>";
@@ -84,6 +94,14 @@ function resetCardQuantities() {
}); });
} }
function displayRoleModal() {
document.getElementById("role-modal").classList.remove("hidden");
}
function closeModal() {
document.getElementById("role-modal").classList.add("hidden");
}
function buildDeckFromQuantities() { function buildDeckFromQuantities() {
let playerDeck = []; let playerDeck = [];
for (const card of fullDeck) { for (const card of fullDeck) {

View File

@@ -24,6 +24,30 @@
font-size: 0.9em; font-size: 0.9em;
margin: 0 0.7em 0.7em 0; margin: 0 0.7em 0.7em 0;
} }
.modal {
width: 92%;
}
.modal-content {
width: 90%;
}
.modal-body {
padding: 1em;
}
.modal-header {
padding: 0 1em;
}
.modal-role {
margin-right: 2em;
}
#learn-container {
margin: 3em 1em;
}
} }
@media(min-width: 750.01px) { @media(min-width: 750.01px) {
@@ -52,6 +76,30 @@
.app-content { .app-content {
width: 80%; width: 80%;
} }
#learn-container {
margin: 3em;
}
.modal-body {
padding: 2em 4em;
}
.modal {
width: 92%;
}
.modal-content {
width: 90%;
}
.modal-header {
padding: 0 3em;
}
.modal-role {
margin-right: 3em;
}
} }
@font-face { @font-face {
@@ -221,8 +269,9 @@ button {
#card-select-header { #card-select-header {
display: flex; display: flex;
align-items: center; justify-content: center;
margin-bottom: 1em; margin-bottom: 1em;
flex-direction: column;
} }
#card-select-header h3 { #card-select-header h3 {
@@ -233,6 +282,12 @@ button {
#card-select-header button { #card-select-header button {
margin-right: 1em; margin-right: 1em;
padding: 0.5em;
}
#card-select-header span {
display: flex;
align-items: center;
} }
#reset-btn { #reset-btn {
@@ -571,3 +626,99 @@ label {
font-family: 'diavlo', sans-serif; font-family: 'diavlo', sans-serif;
color: #7d0b0b; color: #7d0b0b;
} }
#learn-container h2 {
font-family: 'diavlo', sans-serif;
color: #7d0b0b;
font-size: 40px;
}
#learn-container button {
margin-top: 1em;
}
#roles {
display: flex;
flex-wrap: wrap;
}
.modal-role {
width: 22em;
}
.modal {
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.4);
}
.modal-header {
border-radius: 5px;
display: flex;
justify-content: space-between;
background-color: white;
color: gray;
}
.modal-header h2 {
margin-top: 2em;
margin-bottom: 0;
color: black;
}
.modal-footer {
padding: 1em;
background-color: white;
color: white;
}
.modal-content {
border-radius: 5px;
position: relative;
background-color: #fefefe;
margin: 1em auto;
padding: 0;
border: 1px solid #888;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
animation-name: animatetop;
animation-duration: 0.4s
}
.role-wolf {
color: #7d0b0b;
font-family: 'diavlo', sans-serif;
}
.role-village {
color: #171469;
font-family: 'diavlo', sans-serif;
}
@keyframes animatetop {
from {top: -300px; opacity: 0}
to {top: 0; opacity: 1}
}
.close {
margin-top: 0.2em;
color: #aaa;
float: right;
font-size: 46px;
height: 1em;
display: flex;
align-items: center;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}

View File

@@ -4,10 +4,21 @@
<title>Werewolf</title> <title>Werewolf</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel = "stylesheet" type = "text/css" href = "static/styles.css" /> <link rel = "stylesheet" type = "text/css" href = "../static/styles.css" />
<script src="/socket.io/socket.io.js"></script> <script src="/socket.io/socket.io.js"></script>
</head> </head>
<body> <body>
<div class="modal hidden" id="role-modal">
<div class="modal-content">
<div class="modal-header">
<h2>Role descriptions</h2>
<span id="close" class="close">&times;</span>
</div>
<div id="modal-body" class="modal-body">
<div id="roles"></div>
</div>
</div>
</div>
<div class="app-content"> <div class="app-content">
<div id="create-game-container"> <div id="create-game-container">
<h2 class="app-header-secondary">Create A Game</h2> <h2 class="app-header-secondary">Create A Game</h2>
@@ -23,7 +34,10 @@
<input id="time" type="number"/> <input id="time" type="number"/>
</label> </label>
<div id="card-select-header"> <div id="card-select-header">
<button id="reset-btn" class="app-btn">Reset Deck</button> <span>
<button id="reset-btn" class="app-btn">Reset Deck</button>
<button id="role-btn" class="app-btn">View Role Info</button>
</span>
<span> <span>
<h3 id="game-size">0 Players</h3> <h3 id="game-size">0 Players</h3>
<p id="size-error"></p> <p id="size-error"></p>

View File

@@ -4,7 +4,7 @@
<title>Werewolf</title> <title>Werewolf</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel = "stylesheet" type = "text/css" href = "static/styles.css" /> <link rel = "stylesheet" type = "text/css" href = "../static/styles.css" />
<script src="/socket.io/socket.io.js"></script> <script src="/socket.io/socket.io.js"></script>
</head> </head>
<body> <body>

View File

@@ -3,7 +3,7 @@
<head> <head>
<title>Werewolf</title> <title>Werewolf</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel = "stylesheet" type = "text/css" href = "static/styles.css" /> <link rel = "stylesheet" type = "text/css" href = "../static/styles.css" />
<script src="/socket.io/socket.io.js"></script> <script src="/socket.io/socket.io.js"></script>
</head> </head>
<body> <body>
@@ -19,9 +19,12 @@
<a href="/join"> <a href="/join">
<button class="app-btn">Join</button> <button class="app-btn">Join</button>
</a> </a>
<a href="/learn">
<button class="app-btn">Learn the Game</button>
</a>
</div> </div>
<footer id="footer"> <footer id="footer">
<img src="assets/images/vanilla_js.png"> <img src="../assets/images/vanilla_js.png">
<a href="https://github.com/AlecM33/Werewolf">Github</a> <a href="https://github.com/AlecM33/Werewolf">Github</a>
</footer> </footer>
</body> </body>

59
views/learn.html Normal file
View File

@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel = "stylesheet" type = "text/css" href = "../static/styles.css" />
</head>
<body>
<div id="learn-container">
<a href="/"><button class="app-btn">Back</button></a>
<h2>Introduction</h2>
<p>This is a social strategy game involving deception, deduction, cooperation, and any number of clever tactics.
There are two teams - the village and the werewolves. The village has the objective of finding and killing all
the wolves, and the wolves are trying to eat all the villagers. The game is divided into two phases: day and
night. At night is when the werewolves operate, deciding together which villager to kill off. The daytime is when
the village is active, deciding which among them seem evil and killing them to end the day. During the day, everyone
is disguised as a villager - even those that are actually wolves.
</p>
<h2>Setup</h2>
<p>At least 5 players are needed for a sensible game. Players can decide on which cards should go in the deck to create
a balanced experience. For example, a 7 player game might involve 2 werewolves, 3 villagers, a hunter, and a seer.
For larger games, this can be a bit trickier, but the goal is to create a game that isn't too easy for the wolves
or the villagers. Once the deck is chosen, the deck is dealt, and players see only their card.
</p>
<h2>Gameplay</h2>
<p>Play begins with the Night One. Everyone "goes to sleep," closing their eyes and creating some sort of white
noise (commonly a patting of the hand on the thigh). At this point, the moderator will ask the Werewolves to wake
up and see each other. If you do not have a designated moderator, choose someone arbitrarily for the first night,
and then the first player to die can moderate the rest of the game.<br><br>
First, The Werewolves will wake up and see the other wolves, giving them the knowledge that will guide the game.
Then, the werewolves go back to sleep. If you are playing with a Minion, next the Werewolves will raise their
hands (but not awake), and the Minion will awake to spot the wolves. You can also play with a "double-blind"
minion, who does not know who the wolves are, but is still playing on the same team as the wolves. This is all
that needs to happen on the first night.<br><br>
At this point, everyone wakes up, and Day One begins. This is an open debate between everyone in the circle about
who they should kill in suspicion of being a wolf. This can take any amount of time, but watch out, because the
wolves win if time expires! You should have some system for exhibiting votes to kill another player. If a player
receives a majority vote, they should press the "I'm dead" button on their screen, and everyone else will have
that player's role revealed to them. At this point, everyone immediately goes to sleep, and the next night begins.<br><br>
On every night after the first, wolves will, in silence, agree on someone in the circle (other than themselves)
to eat during the night. After this concludes, and everyone wakes up, the player that was killed will be revealed
by the moderator, and they will reveal their role. Then, of course, Day Two begins.<br><br>
The game continues in this alternating fashion until an endgame is reached. If a day ends with the same number of
wolves as villagers, the wolves win (as they can kill a villager at night, and then have the majority to kill the
remaining villager during the day). If the village manages to kill every wolf, then they win. In the scenario
where there is one villager and one wolf remaining, if the remaining villager is a Hunter, then the village wins.
There are several "power roles" such as the Hunter, which can help the village or the wolves. If you are a power
role, you can read the description on your card to find out what your special ability is.
</p>
<a href="/"><button class="app-btn">Back</button></a>
</div>
</body>
</html>