diff --git a/server.js b/server.js index 62e9154..486f089 100644 --- a/server.js +++ b/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() { diff --git a/static/cards.js b/static/cards.js index 3d59298..c529e55 100644 --- a/static/cards.js +++ b/static/cards.js @@ -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 } ]; diff --git a/static/game.js b/static/game.js index 8985cbb..ea6b094 100644 --- a/static/game.js +++ b/static/game.js @@ -199,7 +199,6 @@ function renderClock() { function endGameDueToTimeExpired() { clearInterval(clock); - console.log("expired!"); socket.emit("timerExpired", currentGame.accessCode); } diff --git a/static/setup.js b/static/setup.js index 8d53297..bf63ba9 100644 --- a/static/setup.js +++ b/static/setup.js @@ -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" ? + "

" + card.role + "

" + card.description + "

" + : "

" + card.role + "

" + card.description + "

"; + document.getElementById("roles").appendChild(modalRole); fullDeck.push(newCard); + const cardContainer = document.createElement("div"); + cardContainer.setAttribute("class", "card"); cardContainer.innerHTML = "

" + newCard.role + "


" + newCard.quantity + "

"; @@ -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) { diff --git a/static/styles.css b/static/styles.css index cb28171..e62e195 100644 --- a/static/styles.css +++ b/static/styles.css @@ -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; +} diff --git a/create_game.html b/views/create_game.html similarity index 66% rename from create_game.html rename to views/create_game.html index 3fa1890..ee57e4a 100644 --- a/create_game.html +++ b/views/create_game.html @@ -4,10 +4,21 @@ Werewolf - + +

Create A Game

@@ -23,7 +34,10 @@
- + + + +

0 Players

diff --git a/game.html b/views/game.html similarity index 86% rename from game.html rename to views/game.html index 18132dd..c3a6a9e 100644 --- a/game.html +++ b/views/game.html @@ -4,7 +4,7 @@ Werewolf - + diff --git a/index.html b/views/index.html similarity index 75% rename from index.html rename to views/index.html index 565b3be..bc2e1e1 100644 --- a/index.html +++ b/views/index.html @@ -3,7 +3,7 @@ Werewolf - + @@ -19,9 +19,12 @@ + + +
diff --git a/join_game.html b/views/join_game.html similarity index 100% rename from join_game.html rename to views/join_game.html diff --git a/views/learn.html b/views/learn.html new file mode 100644 index 0000000..cdd0967 --- /dev/null +++ b/views/learn.html @@ -0,0 +1,59 @@ + + + + + Title + + + + + +
+ +

Introduction

+

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. +

+

Setup

+

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. +

+

Gameplay

+

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.

+ + 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.

+ + 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.

+ + 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.

+ + 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. +

+ +
+ +