mirror of https://github.com/Trivernis/whooshy.git
Added bingo
- added bingo front and backend script - added sessions - added configuration files (default-config.yaml is needed, config.yaml for custom configuration)pull/3/head
parent
229a6b6ee7
commit
5cde198890
@ -0,0 +1,5 @@
|
|||||||
|
sessions:
|
||||||
|
secret: averysecuresessionsecret
|
||||||
|
maxAge: 1000000
|
||||||
|
|
||||||
|
port: 3000
|
@ -0,0 +1,88 @@
|
|||||||
|
async function submitBingoWords() {
|
||||||
|
let textContent = document.querySelector('#bingo-textarea').value;
|
||||||
|
let words = textContent.replace(/[<>]/g, '').split('\n');
|
||||||
|
let size = document.querySelector('#bingo-grid-size').value;
|
||||||
|
let dimY = document.querySelector('#bingo-grid-y').value;
|
||||||
|
let response = await postLocData({
|
||||||
|
bingoWords: words,
|
||||||
|
size: size
|
||||||
|
});
|
||||||
|
|
||||||
|
let data = JSON.parse(response.data);
|
||||||
|
let gameid = data.id;
|
||||||
|
insertParam('game', gameid);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitUsername() {
|
||||||
|
let username = document.querySelector('#username-input').value;
|
||||||
|
let response = await postLocData({
|
||||||
|
username: username
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitWord(word) {
|
||||||
|
let response = await postLocData({
|
||||||
|
bingoWord: word
|
||||||
|
});
|
||||||
|
console.log(response);
|
||||||
|
|
||||||
|
let data = JSON.parse(response.data);
|
||||||
|
for (let row of data.fieldGrid) {
|
||||||
|
for (let field of row) {
|
||||||
|
document.querySelector(`.bingo-word-panel[b-word="${field.word}"]`)
|
||||||
|
.setAttribute('b-sub', field.submitted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.bingo) {
|
||||||
|
document.querySelector('#bingo-button').setAttribute('class', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitBingo() {
|
||||||
|
let response = await postLocData({
|
||||||
|
bingo: true
|
||||||
|
});
|
||||||
|
let data = JSON.parse(response.data);
|
||||||
|
if (data.bingos.length > 0) {
|
||||||
|
displayWinner(data.users[data.bingos[0]].username);
|
||||||
|
clearInterval(refrInterval)
|
||||||
|
}
|
||||||
|
console.log(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refresh() {
|
||||||
|
let response = await postLocData({});
|
||||||
|
if (response.status === 400)
|
||||||
|
clearInterval(refrInterval);
|
||||||
|
let data = JSON.parse(response.data);
|
||||||
|
if (data.bingos.length > 0) {
|
||||||
|
displayWinner(data.users[data.bingos[0]].username);
|
||||||
|
clearInterval(refrInterval)
|
||||||
|
}
|
||||||
|
console.log(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayWinner(name) {
|
||||||
|
let winnerDiv = document.createElement('div');
|
||||||
|
let greyoverDiv = document.createElement('div');
|
||||||
|
let winnerSpan = document.createElement('span');
|
||||||
|
winnerDiv.setAttribute('class', 'popup');
|
||||||
|
greyoverDiv.setAttribute('class', 'greyover');
|
||||||
|
winnerSpan.innerText = `${name} has won!`;
|
||||||
|
winnerDiv.appendChild(winnerSpan);
|
||||||
|
document.body.append(greyoverDiv);
|
||||||
|
document.body.appendChild(winnerDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = () => {
|
||||||
|
if (window && !document.querySelector('#bingoform')) {
|
||||||
|
refrInterval = setInterval(refresh, 1000);
|
||||||
|
}
|
||||||
|
let gridSizeElem = document.querySelector('#bingo-grid-size');
|
||||||
|
document.querySelector('#bingo-grid-y').innerText = gridSizeElem.value;
|
||||||
|
gridSizeElem.oninput = () => {
|
||||||
|
document.querySelector('#bingo-grid-y').innerText = gridSizeElem.value;
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,45 @@
|
|||||||
|
function postLocData(postBody) {
|
||||||
|
let request = new XMLHttpRequest();
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
|
||||||
|
request.onload = () => {
|
||||||
|
res({
|
||||||
|
status: request.status,
|
||||||
|
data: request.responseText
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onerror = () => {
|
||||||
|
rej(request.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
request.open('POST', '#', true);
|
||||||
|
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||||
|
request.send(JSON.stringify(postBody));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertParam(key, value) {
|
||||||
|
key = encodeURI(key);
|
||||||
|
value = encodeURI(value);
|
||||||
|
|
||||||
|
let kvp = document.location.search.substr(1).split('&');
|
||||||
|
|
||||||
|
let i = kvp.length;
|
||||||
|
let x;
|
||||||
|
while (i--) {
|
||||||
|
x = kvp[i].split('=');
|
||||||
|
|
||||||
|
if (x[0] === key) {
|
||||||
|
x[1] = value;
|
||||||
|
kvp[i] = x.join('=');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < 0) {
|
||||||
|
kvp[kvp.length] = [key, value].join('=');
|
||||||
|
}
|
||||||
|
|
||||||
|
document.location.search = kvp.join('&');
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
@import ../mixins
|
||||||
|
@import ../vars
|
||||||
|
|
||||||
|
button
|
||||||
|
margin: 1rem
|
||||||
|
|
||||||
|
textarea
|
||||||
|
@include default-element
|
||||||
|
display: block
|
||||||
|
margin: 1rem
|
||||||
|
border-radius: 0
|
||||||
|
height: 50%
|
||||||
|
width: 50%
|
||||||
|
font-size: 15pt
|
||||||
|
|
||||||
|
.number-input
|
||||||
|
width: 4rem
|
||||||
|
margin: 1rem
|
||||||
|
|
||||||
|
#words-container
|
||||||
|
display: table
|
||||||
|
|
||||||
|
.bingo-word-row
|
||||||
|
display: table-row
|
||||||
|
|
||||||
|
.bingo-word-panel
|
||||||
|
@include default-element
|
||||||
|
display: table-cell
|
||||||
|
padding: 3rem
|
||||||
|
transition-duration: 0.3s
|
||||||
|
max-width: 15rem
|
||||||
|
|
||||||
|
.bingo-word-panel:hover
|
||||||
|
background-color: darken($primary, 2%)
|
||||||
|
cursor: pointer
|
||||||
|
|
||||||
|
.bingo-word-panel:active
|
||||||
|
background-color: $primary
|
||||||
|
|
||||||
|
.bingo-word-panel[b-sub="true"]
|
||||||
|
background-color: forestgreen
|
||||||
|
|
||||||
|
.popup
|
||||||
|
@include default-element
|
||||||
|
height: 5%
|
||||||
|
width: 40%
|
||||||
|
top: 47.5%
|
||||||
|
left: 30%
|
||||||
|
text-align: center
|
||||||
|
transition-duration: 1s
|
||||||
|
span
|
||||||
|
margin: 2%
|
||||||
|
display: block
|
||||||
|
|
||||||
|
.greyover
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
position: fixed
|
||||||
|
z-index: 99
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
background-color: transparentize($primary, 0.5)
|
@ -0,0 +1,13 @@
|
|||||||
|
.tableRow
|
||||||
|
display: table-row
|
||||||
|
|
||||||
|
.hidden
|
||||||
|
display: None
|
||||||
|
|
||||||
|
.popup
|
||||||
|
height: 60%
|
||||||
|
width: 40%
|
||||||
|
z-index: 1000
|
||||||
|
position: fixed
|
||||||
|
top: 20%
|
||||||
|
left: 30%
|
@ -0,0 +1,7 @@
|
|||||||
|
@import vars
|
||||||
|
|
||||||
|
@mixin default-element
|
||||||
|
background: lighten($primary, 10%)
|
||||||
|
color: $primarySurface
|
||||||
|
border: 2px solid $primarySurface
|
||||||
|
border-radius: $borderRadius
|
@ -0,0 +1,22 @@
|
|||||||
|
@import ../mixins
|
||||||
|
@import ../vars
|
||||||
|
|
||||||
|
#download-list
|
||||||
|
margin: 1rem 0
|
||||||
|
|
||||||
|
.download-container
|
||||||
|
@include default-element
|
||||||
|
display: inline-block
|
||||||
|
margin: 1rem
|
||||||
|
padding: 1rem
|
||||||
|
.subredditName
|
||||||
|
font-weight: bold
|
||||||
|
a
|
||||||
|
text-decoration: none
|
||||||
|
color: $primarySurface
|
||||||
|
|
||||||
|
#submit-download
|
||||||
|
margin: 0 1rem
|
||||||
|
|
||||||
|
#subreddit-input
|
||||||
|
margin: 0 1rem
|
@ -0,0 +1,28 @@
|
|||||||
|
@import vars
|
||||||
|
@import classes
|
||||||
|
@import mixins
|
||||||
|
|
||||||
|
body
|
||||||
|
background-color: $primary
|
||||||
|
color: $primarySurface
|
||||||
|
font-size: 18pt
|
||||||
|
font-family: Arial, sans-serif
|
||||||
|
|
||||||
|
button
|
||||||
|
@include default-element
|
||||||
|
font-size: 20pt
|
||||||
|
padding: 10px
|
||||||
|
transition-duration: 0.2s
|
||||||
|
|
||||||
|
button:hover
|
||||||
|
background-color: darken($primary, 2%)
|
||||||
|
cursor: pointer
|
||||||
|
|
||||||
|
button:active
|
||||||
|
background-color: lighten($primary, 15%)
|
||||||
|
|
||||||
|
input
|
||||||
|
@include default-element
|
||||||
|
font-size: 20pt
|
||||||
|
background-color: lighten($primary, 10%)
|
||||||
|
padding: 9px
|
@ -0,0 +1,4 @@
|
|||||||
|
$primary: #223
|
||||||
|
$primarySurface: white
|
||||||
|
|
||||||
|
$borderRadius: 20px
|
@ -0,0 +1,259 @@
|
|||||||
|
const express = require('express'),
|
||||||
|
router = express.Router(),
|
||||||
|
cproc = require('child_process'),
|
||||||
|
fsx = require('fs-extra');
|
||||||
|
|
||||||
|
const rWordOnly = /^\w+$/;
|
||||||
|
|
||||||
|
let bingoSessions = {};
|
||||||
|
|
||||||
|
class BingoSession {
|
||||||
|
/**
|
||||||
|
* constructor
|
||||||
|
* @param words List<String>
|
||||||
|
* @param [size] Number
|
||||||
|
*/
|
||||||
|
constructor(words, size = 3) {
|
||||||
|
this.id = generateBingoId();
|
||||||
|
this.words = words;
|
||||||
|
this.gridSize = size;
|
||||||
|
this.users = {};
|
||||||
|
this.bingos = []; // array with the users that already had bingo
|
||||||
|
this.finished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a user to the session
|
||||||
|
* @param user
|
||||||
|
*/
|
||||||
|
addUser(user) {
|
||||||
|
let id = user.id;
|
||||||
|
this.users[id] = user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BingoUser {
|
||||||
|
constructor() {
|
||||||
|
this.id = generateBingoId();
|
||||||
|
this.game = null;
|
||||||
|
this.username = 'anonymous';
|
||||||
|
this.grids = {};
|
||||||
|
this.submittedWords = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BingoWordField {
|
||||||
|
constructor(word) {
|
||||||
|
this.word = word;
|
||||||
|
this.submitted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BingoGrid {
|
||||||
|
constructor(wordGrid) {
|
||||||
|
this.wordGrid = wordGrid;
|
||||||
|
this.fieldGrid = wordGrid.map(x => x.map(y => new BingoWordField(y)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuffles the elements in an array
|
||||||
|
* @param array {Array<*>}
|
||||||
|
* @returns {Array<*>}
|
||||||
|
*/
|
||||||
|
function shuffleArray(array) {
|
||||||
|
let counter = array.length;
|
||||||
|
while (counter > 0) {
|
||||||
|
let index = Math.floor(Math.random() * counter);
|
||||||
|
counter--;
|
||||||
|
let temp = array[counter];
|
||||||
|
array[counter] = array[index];
|
||||||
|
array[index] = temp;
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an id for a subreddit download.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function generateBingoId() {
|
||||||
|
return Date.now().toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a word grid with random word placements in the given dimensions
|
||||||
|
* @param dimensions {Array<Number>} - the dimensions of the grid
|
||||||
|
* @param words {Array<String>} - the words included in the grid
|
||||||
|
* @returns {BingoGrid}
|
||||||
|
*/
|
||||||
|
function generateWordGrid(dimensions, words) {
|
||||||
|
let shuffledWords = shuffleArray(words);
|
||||||
|
let grid = [];
|
||||||
|
for (let x = 0; x < dimensions[1]; x++) {
|
||||||
|
grid[x] = [];
|
||||||
|
for (let y = 0; y < dimensions[0]; y++) {
|
||||||
|
grid[x][y] = shuffledWords[(x * dimensions[0]) + y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (new BingoGrid(grid));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the submitted parameter of the words in the bingo grid that match to true.
|
||||||
|
* @param word {String}
|
||||||
|
* @param bingoGrid {BingoGrid}
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function submitWord(word, bingoGrid) {
|
||||||
|
let results = bingoGrid.fieldGrid.find(x => x.find(y => (y.word === word))).find(x => x.word === word);
|
||||||
|
|
||||||
|
if (results) {
|
||||||
|
(results instanceof Array)? results.forEach(x => {x.submitted = true}): results.submitted = true;
|
||||||
|
checkBingo(bingoGrid);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a bingo exists in the bingo grid.
|
||||||
|
* @param bingoGrid {BingoGrid}
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function checkBingo(bingoGrid) {
|
||||||
|
let fg = bingoGrid.fieldGrid.map(x => x.map(y => y.submitted));
|
||||||
|
|
||||||
|
let diagonalBingo = true;
|
||||||
|
// diagonal check
|
||||||
|
for (let i = 0; i < fg.length; i++)
|
||||||
|
diagonalBingo = fg[i][i] && diagonalBingo;
|
||||||
|
if (diagonalBingo) {
|
||||||
|
bingoGrid.bingo = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
diagonalBingo = true;
|
||||||
|
for (let i = 0; i < fg.length; i++)
|
||||||
|
diagonalBingo = fg[i][fg.length - i - 1] && diagonalBingo;
|
||||||
|
if (diagonalBingo) {
|
||||||
|
bingoGrid.bingo = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let bingoCheck = true;
|
||||||
|
// horizontal check
|
||||||
|
for (let row of fg) {
|
||||||
|
bingoCheck = true;
|
||||||
|
for (let field of row)
|
||||||
|
bingoCheck = field && bingoCheck;
|
||||||
|
if (bingoCheck)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (bingoCheck) {
|
||||||
|
bingoGrid.bingo = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bingoCheck = true;
|
||||||
|
// vertical check
|
||||||
|
for (let i = 0; i < fg.length; i++) {
|
||||||
|
bingoCheck = true;
|
||||||
|
for (let j = 0; j < fg.length; j++)
|
||||||
|
bingoCheck = fg[j][i] && bingoCheck;
|
||||||
|
if (bingoCheck)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (bingoCheck) {
|
||||||
|
bingoGrid.bingo = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Router stuff
|
||||||
|
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
if (!req.session.bingoUser) {
|
||||||
|
req.session.bingoUser = new BingoUser();
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/', (req, res) => {
|
||||||
|
let bingoUser = req.session.bingoUser;
|
||||||
|
if (req.query.game) {
|
||||||
|
let gameId = req.query.game;
|
||||||
|
|
||||||
|
if (bingoSessions[gameId] && !bingoSessions[gameId].finished) {
|
||||||
|
bingoUser.game = gameId;
|
||||||
|
let bingoSession = bingoSessions[gameId];
|
||||||
|
bingoSession.addUser(bingoUser);
|
||||||
|
|
||||||
|
if (!bingoUser.grids[gameId]) {
|
||||||
|
bingoUser.grids[gameId] = generateWordGrid([bingoSession.gridSize, bingoSession.gridSize], bingoSession.words);
|
||||||
|
}
|
||||||
|
res.render('bingo/bingo-game', {grid: bingoUser.grids[gameId].wordGrid, username: bingoUser.username});
|
||||||
|
} else {
|
||||||
|
res.render('bingo/bingo-submit');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.render('bingo/bingo-submit');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/', (req, res) => {
|
||||||
|
let data = req.body;
|
||||||
|
let gameId = req.query.game;
|
||||||
|
let bingoUser = req.session.bingoUser;
|
||||||
|
let bingoSession = bingoSessions[gameId];
|
||||||
|
|
||||||
|
if (data.bingoWords) {
|
||||||
|
let words = data.bingoWords;
|
||||||
|
let size = data.size;
|
||||||
|
let game = new BingoSession(words, size);
|
||||||
|
|
||||||
|
bingoSessions[game.id] = game;
|
||||||
|
|
||||||
|
setTimeout(() => { // delete the game after one day
|
||||||
|
delete bingoSessions[game.id];
|
||||||
|
}, 86400000);
|
||||||
|
|
||||||
|
res.send(game);
|
||||||
|
} else if (data.username) {
|
||||||
|
bingoUser.username = data.username;
|
||||||
|
bingoSessions[gameId].addUser(bingoUser);
|
||||||
|
|
||||||
|
res.send(bingoUser);
|
||||||
|
} else if (data.game) {
|
||||||
|
res.send(bingoSessions[data.game]);
|
||||||
|
} else if (data.bingoWord) {
|
||||||
|
if (!bingoUser.submittedWords[gameId])
|
||||||
|
bingoUser.submittedWords[gameId] = [];
|
||||||
|
bingoUser.submittedWords[gameId].push(data.bingoWord);
|
||||||
|
console.log(typeof bingoUser.grids[gameId]);
|
||||||
|
if (bingoUser.grids[gameId])
|
||||||
|
submitWord(data.bingoWord, bingoUser.grids[gameId]);
|
||||||
|
res.send(bingoUser.grids[gameId]);
|
||||||
|
} else if (data.bingo) {
|
||||||
|
if (checkBingo(bingoUser.grids[gameId])) {
|
||||||
|
if (!bingoSession.bingos.includes(bingoUser.id))
|
||||||
|
bingoSession.bingos.push(bingoUser.id);
|
||||||
|
bingoSession.finished = true;
|
||||||
|
setTimeout(() => { // delete the finished game after five minutes
|
||||||
|
delete bingoSessions[game.id];
|
||||||
|
}, 360000);
|
||||||
|
res.send(bingoSession);
|
||||||
|
} else {
|
||||||
|
res.status(400);
|
||||||
|
res.send({'error': "this is not a bingo!"})
|
||||||
|
}
|
||||||
|
} else if (bingoSession) {
|
||||||
|
res.send(bingoSession);
|
||||||
|
} else {
|
||||||
|
res.status(400);
|
||||||
|
res.send({
|
||||||
|
error: 'invalid request data'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
@ -0,0 +1,13 @@
|
|||||||
|
include bingo-layout
|
||||||
|
|
||||||
|
block content
|
||||||
|
div(id='username-form')
|
||||||
|
input(type='text', id='username-input', placeholder='username', value=username)
|
||||||
|
button(onclick='submitUsername()') Set Username
|
||||||
|
button(id='bingo-button' onclick='submitBingo()', class='hidden') Bingo!
|
||||||
|
div(id='words-container')
|
||||||
|
each val in grid
|
||||||
|
div(class='bingo-word-row')
|
||||||
|
each word in val
|
||||||
|
div(class='bingo-word-panel', onclick=`submitWord('${word}')`, b-word=word, b-sub='false')
|
||||||
|
span= word
|
@ -0,0 +1,8 @@
|
|||||||
|
html
|
||||||
|
head
|
||||||
|
include ../includes/head
|
||||||
|
script(type='text/javascript', src='/javascripts/bingo-web.js')
|
||||||
|
link(rel='stylesheet', href='/sass/bingo/style.sass')
|
||||||
|
|
||||||
|
body
|
||||||
|
block content
|
@ -0,0 +1,9 @@
|
|||||||
|
extends bingo-layout
|
||||||
|
|
||||||
|
block content
|
||||||
|
div(id='bingoform')
|
||||||
|
input(type='number', id='bingo-grid-size', class='number-input', value=3, min=1, max=8)
|
||||||
|
span x
|
||||||
|
span(id='bingo-grid-y', class='number-input') 3
|
||||||
|
button(onclick='submitBingoWords()') Submit
|
||||||
|
textarea(id='bingo-textarea', placeholder='Bingo Words')
|
@ -0,0 +1,2 @@
|
|||||||
|
link(rel='stylesheet', href='/sass/style.sass')
|
||||||
|
script(type='text/javascript', src='/javascripts/common.js')
|
Loading…
Reference in New Issue