mirror of
https://github.com/AlecM33/Werewolf.git
synced 2025-12-26 07:47:50 +01:00
Added modal for role descriptions, learning page, fixed cron job
This commit is contained in:
28
server.js
28
server.js
@@ -6,28 +6,45 @@ const app = express();
|
||||
const server = http.Server(app);
|
||||
const io = socketIO(server);
|
||||
|
||||
// cron job for periodically clearing finished games
|
||||
|
||||
const CronJob = require('cron').CronJob;
|
||||
|
||||
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.use('/static', express.static(__dirname + '/static')); // Routing
|
||||
app.use('/assets', express.static(__dirname + '/assets')); // Routing
|
||||
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) {
|
||||
response.sendFile(__dirname + '/create_game.html');
|
||||
response.sendFile(__dirname + '/views/create_game.html');
|
||||
});
|
||||
|
||||
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) {
|
||||
response.sendFile(__dirname + '/game.html');
|
||||
response.sendFile(__dirname + '/views/game.html');
|
||||
});
|
||||
|
||||
// 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) {
|
||||
if(Object.keys(socket.rooms).includes(data.code) === false) {
|
||||
socket.join(data.code, function() {
|
||||
|
||||
@@ -5,28 +5,47 @@ export const cards = [
|
||||
description: "During the day, find the wolves and kill them.",
|
||||
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",
|
||||
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
|
||||
},
|
||||
{
|
||||
role: "Hunter",
|
||||
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
|
||||
},
|
||||
{
|
||||
role: "Werewolf",
|
||||
team: "wolf",
|
||||
description: "During the night, choose a villager to kill. Don't get killed.",
|
||||
powerRole: false
|
||||
role: "Sorcerer",
|
||||
team: "village",
|
||||
description: "Once a game, change who the wolves are going to kill to someone else, including yourself. You will" +
|
||||
" see who is going to die each night until you use this power.",
|
||||
powerRole: true
|
||||
},
|
||||
{
|
||||
role: "Minion",
|
||||
team: "wolf",
|
||||
description: "You are villager, but you know who the wolves are - and want them to win.",
|
||||
powerRole: true
|
||||
role: "Prince",
|
||||
team: "village",
|
||||
description: "If you die, take someone else with you.",
|
||||
powerRole: true
|
||||
}
|
||||
];
|
||||
|
||||
@@ -199,7 +199,6 @@ function renderClock() {
|
||||
|
||||
function endGameDueToTimeExpired() {
|
||||
clearInterval(clock);
|
||||
console.log("expired!");
|
||||
socket.emit("timerExpired", currentGame.accessCode);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,15 +36,25 @@ var atLeastOnePlayer = false;
|
||||
// register event listeners on buttons
|
||||
document.getElementById("reset-btn").addEventListener("click", resetCardQuantities);
|
||||
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
|
||||
window.onload = function() {
|
||||
for (const card of cards) {
|
||||
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);
|
||||
|
||||
const cardContainer = document.createElement("div");
|
||||
|
||||
cardContainer.setAttribute("class", "card");
|
||||
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() {
|
||||
let playerDeck = [];
|
||||
for (const card of fullDeck) {
|
||||
|
||||
@@ -24,6 +24,30 @@
|
||||
font-size: 0.9em;
|
||||
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) {
|
||||
@@ -52,6 +76,30 @@
|
||||
.app-content {
|
||||
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 {
|
||||
@@ -221,8 +269,9 @@ button {
|
||||
|
||||
#card-select-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 1em;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#card-select-header h3 {
|
||||
@@ -233,6 +282,12 @@ button {
|
||||
|
||||
#card-select-header button {
|
||||
margin-right: 1em;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
#card-select-header span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#reset-btn {
|
||||
@@ -571,3 +626,99 @@ label {
|
||||
font-family: 'diavlo', sans-serif;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,21 @@
|
||||
<title>Werewolf</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" />
|
||||
<link rel = "stylesheet" type = "text/css" href = "../static/styles.css" />
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
</head>
|
||||
<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">×</span>
|
||||
</div>
|
||||
<div id="modal-body" class="modal-body">
|
||||
<div id="roles"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-content">
|
||||
<div id="create-game-container">
|
||||
<h2 class="app-header-secondary">Create A Game</h2>
|
||||
@@ -23,7 +34,10 @@
|
||||
<input id="time" type="number"/>
|
||||
</label>
|
||||
<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>
|
||||
<h3 id="game-size">0 Players</h3>
|
||||
<p id="size-error"></p>
|
||||
@@ -4,7 +4,7 @@
|
||||
<title>Werewolf</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" />
|
||||
<link rel = "stylesheet" type = "text/css" href = "../static/styles.css" />
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<title>Werewolf</title>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
@@ -19,9 +19,12 @@
|
||||
<a href="/join">
|
||||
<button class="app-btn">Join</button>
|
||||
</a>
|
||||
<a href="/learn">
|
||||
<button class="app-btn">Learn the Game</button>
|
||||
</a>
|
||||
</div>
|
||||
<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>
|
||||
</footer>
|
||||
</body>
|
||||
59
views/learn.html
Normal file
59
views/learn.html
Normal 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>
|
||||
Reference in New Issue
Block a user