blank-white-cards/apps/bwc_web/assets/static/bwc.old/server.js
2020-12-28 14:35:10 -08:00

469 lines
14 KiB
JavaScript
Executable File

const fs = require("fs");
const express = require('express');
const path = require('path');
const WebSocket = require('ws'); // new
function noop() {}
function heartbeat() {
this.isAlive = true;
}
const app = express();// express code
const url = require('url');
const User = require('./user');
const moment = require('moment');
const timeFormat = 'h:mm a [PDT]'
const threshold = 1;
const playerThreshold = 2;
var playerTurn = false;
var turnIndex = false;
var nextPlayer = false;
var cardsDir = "cards/";
var cardsFile = cardsDir + moment().format("YYYY-MM-DD") + ".cards";
var cards = []; /*
{
"title": "Test Card 1",
"image": "/img/profiles/6.jpg",
"content": "Angelica approves.",
"revealed": false,
"author": "admin"
},
{
"title": "19.jpg",
"image": "/img/profiles/19.jpg",
"content": "Taking work calls like",
"revealed": false,
"author": "admin"
},
{
"title": "YUGE",
"image": "/img/profiles/12.jpg",
"content": ""
"revealed": false,
"author": "admin"
}
]*/
var lastCard = false;
var graveyard = [];
var incompleteCard = false;
fs.readFile(cardsFile, 'utf-8', (err, data) => {
if (err) console.log(err);
console.log("Opened %s", cardsFile);
// cards = JSON.parse(data);
});
const messages = [
{'username': 'ADMIN',
'pId': 0,
'message': 'Welcome to Blank White Cards!',
'timestamp': moment().format(timeFormat)
}
];
var players = [];
var playerProfilesMap = {
'panda':{
'img': '/img/profiles/6.jpg',
'pId': 1000
},
'hodor':{
'img': '/img/profiles/12.jpg',
'pId': 2000
}
};
var pId = 0;
var username = "";
var profilepic = "";
var mode = "draw"; // can also be "play" or "postgame"
app.get('/game/admin', (req, res) => {
console.log(req);
res.sendFile(path.join(__dirname, 'admin.html'));
});
app.get('/game', (req, res) => {
const querystring = url.parse(req.url,true).query;
username = querystring.username;
res.sendFile(path.join(__dirname, 'game.html'));
});
const port = process.env.PORT || 8765;
app.listen(port, () => {
console.log(`listening http://localhost:${port}`);
});
const wss = new WebSocket.Server({port: 3030});
const updateClients = (wss, data) => {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
data.timestamp = moment(data.timestamp).format(timeFormat);
client.send(JSON.stringify(data));
}
});
};
wss.on('connection', function connection(ws) {
ws.isAlive = true;
ws.on('pong', heartbeat);
ws.ping(null, null, false);ws._socket.setKeepAlive(true,100);
const packet = {
"type": "init",
"players": players,
"mode": mode,
"playerTurn": playerTurn,
"messages": messages,
"cards": cards.length,
"incompleteCard": incompleteCard
}
var existingPlayer = false; var status = "entered";
pId = players.length + 1;
console.log("Trying to join: " + username + " (Player " + pId + ")");
/* players.forEach(function each(player) {
if(player.username == username){
packet.mode = "error";
packet.error_msg = "That username is taken. Try another.";
ws.send(JSON.stringify(packet));
console.log("Kicked out due to duplicate username: " + username);
ws.close();
existingPlayer = true;
}
});
*/
if(existingPlayer){
pId = pId - 1;
return "Fuckoff";
}
else{
if(playerProfilesMap[username]){
pId = playerProfilesMap[username].pId;
console.log("Found '" + username + "' in the archives (Player "+pId+").");
status = "rejoined";
profilepic = playerProfilesMap[username].img;
}
else{
profilepic = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjQsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMjUwcHgiIGhlaWdodD0iMjUwcHgiIHZpZXdCb3g9IjAgMCAyNTAgMjUwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyNTAgMjUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxyZWN0IGZpbGw9IiNGNUY1RjUiIHdpZHRoPSIyNTAiIGhlaWdodD0iMjUwIi8+DQo8Zz4NCgk8cGF0aCBmaWxsPSIjRDhEOEQ4IiBkPSJNMjI3LjgxOCwyMDcuMjQ1Yy0xLjA0NS01Ljg0Ny0yLjM2OS0xMS4yNTktMy45NjUtMTYuMjUyYy0xLjU5OC00Ljk5NC0zLjc0Mi05Ljg1OS02LjQ0MS0xNC42MDYNCgkJYy0yLjY5Ny00Ljc0LTUuNzg5LTguNzg1LTkuMjgzLTEyLjEzMWMtMy41MDEtMy4zNDMtNy43NjgtNi4wMTUtMTIuODA5LTguMDEyYy01LjA0NS0xLjk5Ni0xMC42MTEtMi45OTUtMTYuNy0yLjk5NQ0KCQljLTAuODk3LDAtMi45OTQsMS4wNzQtNi4yOTEsMy4yMTljLTMuMjk1LDIuMTUtNy4wMTcsNC41NDYtMTEuMTU4LDcuMTg4Yy00LjE0NiwyLjY0Ni05LjUzNyw1LjA0NC0xNi4xNzYsNy4xODkNCgkJYy02LjY0MiwyLjE0OC0xMy4zMDgsMy4yMjEtMTkuOTk1LDMuMjIxYy02LjY4OSwwLTEzLjM1NC0xLjA3MS0xOS45OTUtMy4yMjFjLTYuNjQzLTIuMTQ2LTEyLjAzNi00LjU0My0xNi4xNzYtNy4xODkNCgkJYy00LjE0OC0yLjY0My03Ljg2My01LjAzNy0xMS4xNTgtNy4xODhjLTMuMjk1LTIuMTQ1LTUuMzkxLTMuMjE5LTYuMjkxLTMuMjE5Yy02LjA5NSwwLTExLjY2MSwwLjk5OS0xNi43MDEsMi45OTUNCgkJYy01LjA0MSwxLjk5Ny05LjMxMyw0LjY2OS0xMi44MDMsOC4wMTJjLTMuNTAxLDMuMzQ2LTYuNTkyLDcuMzkxLTkuMjg3LDEyLjEzMWMtMi42OTYsNC43NDctNC44NDcsOS42MTItNi40NDEsMTQuNjA2DQoJCWMtMS41OTcsNC45OTMtMi45MjIsMTAuNDA1LTMuOTcxLDE2LjI1MmMtMS4wNDYsNS44MzktMS43NDgsMTEuMjgtMi4wOTYsMTYuMzIzYy0wLjM0OSw1LjA0My0wLjUyNCwxMC4yMTMtMC41MjQsMTUuNQ0KCQljMCwzLjkyNSwwLjQzMiw3LjU1LDEuMjExLDEwLjkzMmgyMDguNDY0YzAuNzgxLTMuMzgyLDEuMjEzLTcuMDA3LDEuMjEzLTEwLjkzMmMwLTUuMjg3LTAuMTc2LTEwLjQ1Ny0wLjUyNi0xNS41DQoJCUMyMjkuNTY2LDIxOC41MjUsMjI4Ljg2OSwyMTMuMDg0LDIyNy44MTgsMjA3LjI0NXoiLz4NCgk8cGF0aCBmaWxsPSIjRDhEOEQ4IiBkPSJNMTI1LDE2Mi44MzRjMTUuODc1LDAsMjkuNDMtNS42MTcsNDAuNjY2LTE2Ljg1YzExLjIzMi0xMS4yMzUsMTYuODUtMjQuNzg5LDE2Ljg1LTQwLjY2Nw0KCQljMC0xNS44NzctNS42MTctMjkuNDI5LTE2Ljg1LTQwLjY2M0MxNTQuNDMsNTMuNDIyLDE0MC44NzUsNDcuODA0LDEyNSw0Ny44MDRzLTI5LjQzNCw1LjYxOS00MC42NjQsMTYuODUyDQoJCUM3My4xLDc1Ljg5LDY3LjQ4NCw4OS40NDEsNjcuNDg0LDEwNS4zMThjMCwxNS44NzgsNS42MTUsMjkuNDMxLDE2Ljg1Miw0MC42NjdDOTUuNTY2LDE1Ny4yMTcsMTA5LjEyNSwxNjIuODM0LDEyNSwxNjIuODM0eiIvPg0KPC9nPg0KPC9zdmc+DQo=";
}
newPlayer = {
"pId": pId,
"username": username,
"profilepic": profilepic
}
players.push(newPlayer);
newData = {
'pId': newPlayer.pId,
'img': newPlayer.profilepic
}
playerProfilesMap[newPlayer.username] = newData;
console.log("'" + username + "' has "+status+" the game.");
if(newPlayer.username == playerTurn.username){console.log(">> It's "+newPlayer.username+"'s turn.");}
packet.id = pId;
ws.pId = pId;
ws.send(JSON.stringify(packet));
const newPlayerInfo = {
'type': 'newPlayer',
'player': newPlayer
}
updateClients(wss, newPlayerInfo);
}
ws.on('message', function incoming(data) {
data = JSON.parse(data);
response = {
"type": "error",
"content": "Unknown server error."
}
switch(data.type){
case "startPlayMode":
if((mode != "play") && (cards.length + 1 > threshold) && (players.length + 1 > playerThreshold)){
mode = "play";
console.log("'" + data.player + "' started the game. (%s cards)", cards.length);
console.log(">> GAME MODE IS NOW: " + mode);
type = "changeMode";
nextPlayerMove(type);
}
else{
if(cards.length < threshold){
console.log("'" + data.player + "' tried to start the game. (Error: Not enough cards)");
response = {
"type": "error",
"content": "The game won't start until we have at least "+threshold+" cards."
}
ws.send(JSON.stringify(response));
}
if(players.length < playerThreshold){
console.log("'" + data.player + "' tried to start the game. (Error: Not enough players)");
response = {
"type": "error",
"content": "The game won't start until we have at least "+playerThreshold+" players."
}
ws.send(JSON.stringify(response));
}
}
break;
case "message":
console.log("Received message from '%s': '%s'", data.username, data.message);
messages.push(data);
updateClients(wss,data);
break;
case "vote":
console.log("Caught a vote for Player "+data.pId);
updateClients(wss,data);
break;
case "changeProfilePic":
console.log("'" + data.player.username + "' has changed their profile picture.");
players.map(function(p){
if(p.pId == data.player.pId){
p.profilepic = data.href;
}
});
newData = {
'pId': data.player.pId,
'img': data.href
}
playerProfilesMap[data.player.username] = newData;
updateClients(wss,data);
break;
case "changeUsername":
response.type = "notification";
if(username == data.username){
response.type = "error";
response.content = "Already your name.";
}
else{
var someoneElse = false;
players.forEach(function(p){
if(p.username == data.username){
someoneElse = true;
}
});
if(someoneElse){ // if there's a current player with this name
response.type = "error";
response.content = "Already someone's name.";
}
else{ // Not already your name, and not someone else's name
if(playerProfilesMap[data.username]){
// We've seen this username before
if(playerProfilesMap[data.username].pId == pId){
// Same pId, good to go
}
else{
// Someone else's profile, don't touch it
response.content = "This looks like someone else's name.";
response.type = "error";
}
}
else {
// Never seen this before
username = data.username;
players.forEach(function(p){
if(p.pId == pId){
// This is us within the players array; update it
p.username = username;
}
});
playerProfilesMap[username] = {
'pId': pId,
'img': profilepic
}
}
}
response.content = "Changed your name to '"+username+"'.";
console.log("Changed player %s to '%s'.", pId, username);
};
updateClients(wss,data);
ws.send(JSON.stringify(response));
break;
case "pullCard":
if(incompleteCard){
// There's a card in memory that hasn't been played yet
pulledCard = incompleteCard;
}else{
mycard = true;
pulledCardIndex = Math.floor(Math.random() * cards.length);
/* while(mycard){
if(cards[pulledCardIndex].author != username){
mycard = false
}
} */
pulledCard = cards.splice(pulledCardIndex,1)[0];
graveyard.push(pulledCard); // Keep track of it for later viewing/saving/reloading
incompleteCard = pulledCard; // Make sure this card remains active until the player does something with it
console.log("Pulled card ("+cards.length+" remaining).");
console.log(graveyard);
if(cards.length == 0){
console.log("[[ LAST CARD! ]]");
lastCard = true;
}
}
response = {
'type': 'pulledCard',
'card': pulledCard,
'last': lastCard
}
ws.send(JSON.stringify(response));
break;
case "revealCard":
console.log(">> '"+playerTurn.username+"' has revealed their card.");
data.card.revealed = true;
incompleteCard.revealed = true;
updateClients(wss,data);
break;
case "endTurn":
console.log(">> '"+playerTurn.username+"' has ended their turn.");
incompleteCard = false;
if(lastCard){
// Game over
gameOver();
}else{
nextPlayerMove("nextTurn");
}
break;
case "card":
var dupe = false;
const card = data.card;
const author = data.card.author;
console.log("'%s' submitted a card.", author);
cards.forEach(function each(c){
if(c.content == card.content){
console.log("Duplicate card");
dupe = true;
}
});
if(dupe){
response = {
'type':'error',
'content': 'This looks like a duplicate of a card we already have.'
}
}
else{
cards.push(card);
response = {
'type':'updateCardsList',
'author': author
}
}
updateClients(wss, response);
break;
default:
console.log("Unknown client request:\n" + data);
}
});
ws.on('close', function close() {
console.log('Client disconnected');
players = players.filter(function(p){
return p.pId != ws.pId;
});
pId = pId - 1;
});
});
const gameOver = () => {
console.log("Game is over");
notice = {
'type': "gameOver",
"content": "Thanks for playing - the game's over!"
}
updateClients(wss, notice);
updateClients(wss, {
'type': 'message',
'pId': 0,
'username': "ADMIN",
'message': "Thanks for playing - the game's over!"
});
var saveFile = cardsDir + moment().format("YYYY-MM-DD-hh-mm") + ".cards";
fs.writeFile(saveFile, JSON.stringify(graveyard), (err) => {
if (err) console.log(err);
});
}
const nextPlayerMove = (type) => {
if(type == "changeMode"){
// First move of the game
console.log("First move; choosing random player");
turnIndex = Math.floor(Math.random() * players.length); // For now, just random
}
else{
// Mid-game
console.log(players[turnIndex].username + " just finished. Incrementing..");
turnIndex = turnIndex + 1;
if(!players[turnIndex]){turnIndex = 0}
}
console.log(players[turnIndex].username + " is up now.");
turnIndex = 1;
playerTurn = players[turnIndex];
response = {
"type": type,
"mode": "play",
"playerTurn": playerTurn,
"message": null
}
updateClients(wss,response);
/*
var order = "Next";
var firstLine = playerTurn.username + ' has ended their turn.';
var name = nextPlayer.username;
if(type == "changeMode"){
name = playerTurn.username;
order = "First";
firstLine = 'The game has started.';
}
message = {
'username': 'ADMIN',
'pId': 0,
'message': firstLine + '<br/>'+order+' up: <b>'+name+'</b>',
'timestamp': moment().format(timeFormat)
}
*/
}
const interval = setInterval(function ping() {
wss.clients.forEach(function each(ws) {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping(noop);
});
}, 30000);
wss.on('close', function close() {
clearInterval(interval);
});