0
0
Fork 0

Merge pull request #52 from Oliver-Akins/dev

v1.2.2
This commit is contained in:
Oliver 2021-01-11 16:53:50 -07:00 committed by GitHub
commit 7e12124330
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 149 additions and 40 deletions

1
.gitignore vendored
View file

@ -7,6 +7,7 @@ server/built/*
server/dist/* server/dist/*
server/resources/* server/resources/*
*.log *.log
*.sh
#=============================================================================# #=============================================================================#
# The files that were auto-generated into a .gitignore by Vue-cli # The files that were auto-generated into a .gitignore by Vue-cli

View file

@ -15,6 +15,7 @@
"@types/engine.io": "^3.1.4", "@types/engine.io": "^3.1.4",
"@types/node": "^14.14.14", "@types/node": "^14.14.14",
"@types/socket.io": "^2.1.12", "@types/socket.io": "^2.1.12",
"axios": "^0.21.1",
"fs": "^0.0.1-security", "fs": "^0.0.1-security",
"neat-csv": "^6.0.0", "neat-csv": "^6.0.0",
"socket.io": "^3.0.4", "socket.io": "^3.0.4",

19
server/pnpm-lock.yaml generated
View file

@ -2,6 +2,7 @@ dependencies:
'@types/engine.io': 3.1.4 '@types/engine.io': 3.1.4
'@types/node': 14.14.14 '@types/node': 14.14.14
'@types/socket.io': 2.1.12 '@types/socket.io': 2.1.12
axios: 0.21.1
fs: 0.0.1-security fs: 0.0.1-security
neat-csv: 6.0.0 neat-csv: 6.0.0
socket.io: 3.0.4 socket.io: 3.0.4
@ -54,6 +55,12 @@ packages:
node: '>= 0.6' node: '>= 0.6'
resolution: resolution:
integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
/axios/0.21.1:
dependencies:
follow-redirects: 1.13.1
dev: false
resolution:
integrity: sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
/base64-arraybuffer/0.1.4: /base64-arraybuffer/0.1.4:
dev: false dev: false
engines: engines:
@ -127,6 +134,17 @@ packages:
node: '>=10.0.0' node: '>=10.0.0'
resolution: resolution:
integrity: sha512-Ri+whTNr2PKklxQkfbGjwEo+kCBUM4Qxk4wtLqLrhH+b1up2NFL9g9pjYWiCV/oazwB0rArnvF/ZmZN2ab5Hpg== integrity: sha512-Ri+whTNr2PKklxQkfbGjwEo+kCBUM4Qxk4wtLqLrhH+b1up2NFL9g9pjYWiCV/oazwB0rArnvF/ZmZN2ab5Hpg==
/follow-redirects/1.13.1:
dev: false
engines:
node: '>=4.0'
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
resolution:
integrity: sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==
/fs/0.0.1-security: /fs/0.0.1-security:
dev: false dev: false
resolution: resolution:
@ -266,6 +284,7 @@ specifiers:
'@types/engine.io': ^3.1.4 '@types/engine.io': ^3.1.4
'@types/node': ^14.14.14 '@types/node': ^14.14.14
'@types/socket.io': ^2.1.12 '@types/socket.io': ^2.1.12
axios: ^0.21.1
fs: ^0.0.1-security fs: ^0.0.1-security
neat-csv: ^6.0.0 neat-csv: ^6.0.0
socket.io: ^3.0.4 socket.io: ^3.0.4

View file

@ -1,7 +1,7 @@
import { readFileSync } from 'fs';
import { Game } from '../objects/Game'; import { Game } from '../objects/Game';
import { Player } from '../objects/Player'; import { Player } from '../objects/Player';
import { Server, Socket } from 'socket.io'; import { Server, Socket } from 'socket.io';
import { readFileSync, unlinkSync } from 'fs';
import { games, hibernatedGames, log, conf } from '../main'; import { games, hibernatedGames, log, conf } from '../main';
export default (io: Server, socket: Socket, data: JoinGame) => { export default (io: Server, socket: Socket, data: JoinGame) => {
@ -10,28 +10,40 @@ export default (io: Server, socket: Socket, data: JoinGame) => {
// Game object and bring it back to being alive // Game object and bring it back to being alive
let hibernatedIndex = hibernatedGames.indexOf(data.game_code) let hibernatedIndex = hibernatedGames.indexOf(data.game_code)
if (hibernatedIndex >= 0) { if (hibernatedIndex >= 0) {
log.info(`Recreating game from datastore.`); log.info(`Attempting to recreate game from datastore.`);
// Reinstantiate the game using the data from the disk
let datastore = JSON.parse(readFileSync( let datastore = JSON.parse(readFileSync(
`${conf.datastores.directory}/${data.game_code}.${conf.datastores.filetype}`, `${conf.datastores.directory}/${data.game_code}.${conf.datastores.filetype}`,
`utf-8` `utf-8`
)) as datastoreGame; )) as datastoreGame;
let playerData = datastore.players.find(p => p.name === data.name);
// Assert that the name matches someone in the hibernated game
if (!playerData) {
log.info(`[${data.game_code}] User attempted unhibernate game with an invalid name`);
socket.emit(`GameJoined`, {
status: 403,
message: `Game with code "${data.game_code}" could not be found`,
source: `JoinGame`
});
return;
}
// Instantiate the host's player object
let host = new Player(data.name, socket, true); let host = new Player(data.name, socket, true);
host.role = playerData.role;
host.team = playerData.team;
// Re-instantiate the game object
let game = Game.fromJSON(host, datastore); let game = Game.fromJSON(host, datastore);
game.log = log.getChildLogger({ game.log = log.getChildLogger({
displayLoggerName: true, displayLoggerName: true,
name: game.id, name: game.id,
}); });
game.ingame = datastore.ingame; game.ingame = datastore.ingame;
// Get the specific information for team
let playerData = datastore.players.find(p => p.name === data.name);
if (playerData) {
host.role = playerData.role;
host.team = playerData.team;
};
let hand: string[] = []; let hand: string[] = [];
if (host.team) { if (host.team) {
let team = game.teams[host.team - 1]; let team = game.teams[host.team - 1];
@ -50,12 +62,20 @@ export default (io: Server, socket: Socket, data: JoinGame) => {
`${game.id}:*:${host.role}`, `${game.id}:*:${host.role}`,
`${game.id}:${host.team}:${host.role}` `${game.id}:${host.team}:${host.role}`
]); ]);
game.log.debug(`Host assigned to team`); game.log.debug(`Host assigned to team object`);
}; };
hibernatedGames.splice(hibernatedIndex, 1); hibernatedGames.splice(hibernatedIndex, 1);
games[game.id] = game; games[game.id] = game;
// Try removing the file from the directory
try {
unlinkSync(`${conf.datastores.directory}/${game.id}.${conf.datastores.filetype}`);
game.log.info(`Game datastore deleted`);
} catch (err) {
game.log.prettyError(err);
};
game.log.info(`Successfully unhibernated`); game.log.info(`Successfully unhibernated`);
socket.join(game.id); socket.join(game.id);
socket.emit(`GameRejoined`, { socket.emit(`GameRejoined`, {
@ -94,8 +114,8 @@ export default (io: Server, socket: Socket, data: JoinGame) => {
socket. This will also function as the main game joining for hibernated socket. This will also function as the main game joining for hibernated
games that were reloaded from disk. games that were reloaded from disk.
*/ */
let sameName = game.players.find(x => x.name == data.name); let sameName = game.players.find(x => x.name === data.name);
if (sameName != null) { if (sameName) {
if (!sameName.socket?.connected) { if (!sameName.socket?.connected) {
sameName.socket = socket; sameName.socket = socket;
@ -132,7 +152,7 @@ export default (io: Server, socket: Socket, data: JoinGame) => {
game.log.debug(`${socket.id} attempted to join with a name already in use ${data.name}`); game.log.debug(`${socket.id} attempted to join with a name already in use ${data.name}`);
socket.emit(`GameJoined`, { socket.emit(`GameJoined`, {
status: 403, status: 403,
message: `A player already has that name in the game.`, message: `Cannot connect to a game that's in progress.`,
source: `JoinGame` source: `JoinGame`
}); });
return; return;

View file

@ -13,9 +13,11 @@ export default (io: Server, socket: Socket, data: ResetGame) => {
return; return;
}; };
let game = games[data.game_code]; let game = games[data.game_code];
game.log.info(`Resetting game`)
game.questions.reset(); game.questions.reset();
game.resetObject(); game.resetObject();
game.ingame = false;
io.to(game.id).emit(`GameReset`, { status: 200 }); io.to(game.id).emit(`GameReset`, { status: 200 });
} catch (err) { } catch (err) {

View file

@ -85,11 +85,11 @@ const modifyPlayer = (io: Server, socket: Socket, data: UpdatePlayer): void => {
switch (data.to.role) { switch (data.to.role) {
case "guesser": case "guesser":
if (team.guessers.length >= 7) { if (team.guessers.length >= conf.game.guesser_limit) {
game.log.debug(`Game cannot have more than 7 guessers`); game.log.debug(`Game cannot have more than ${conf.game.guesser_limit} guessers`);
socket.emit(`PlayerUpdate`, { socket.emit(`PlayerUpdate`, {
status: 403, status: 403,
message: `A team can't have 8 or more ${conf.game.guesser_name}`, message: `That team already has the maximum number of ${conf.game.guesser_name}s`,
source: `UpdatePlayer.Modify` source: `UpdatePlayer.Modify`
}); });
return; return;
@ -128,11 +128,11 @@ const modifyPlayer = (io: Server, socket: Socket, data: UpdatePlayer): void => {
let team = game.teams[data.to.team - 1]; let team = game.teams[data.to.team - 1];
switch (data.to.role) { switch (data.to.role) {
case "guesser": case "guesser":
if (team.guessers.length >= 7) { if (team.guessers.length >= conf.game.guesser_limit) {
game.log.debug(`Game cannot have more than 7 guessers`); game.log.debug(`Game cannot have more than ${conf.game.guesser_limit} guessers`);
socket.emit(`PlayerUpdate`, { socket.emit(`PlayerUpdate`, {
status: 403, status: 403,
message: `A team can't have 8 or more ${conf.game.guesser_name}`, message: `That team already has the maximum number of ${conf.game.guesser_name}s`,
source: `UpdatePlayer.Modify` source: `UpdatePlayer.Modify`
}); });
return; return;
@ -181,11 +181,11 @@ const modifyPlayer = (io: Server, socket: Socket, data: UpdatePlayer): void => {
switch (data.to.role) { switch (data.to.role) {
case "guesser": case "guesser":
// Ensure we don't get 8 guessers // Ensure we don't get 8 guessers
if (newTeam.guessers.length >= 7) { if (newTeam.guessers.length >= conf.game.guesser_limit) {
game.log.debug(`Game cannot have 8 or more guessers on a team.`); game.log.debug(`That team already`);
socket.emit(`PlayerUpdate`, { socket.emit(`PlayerUpdate`, {
status: 403, status: 403,
message: `Cannot have 8 players as ${conf.game.guesser_name}s on a single team.`, message: `That team already has the maximum number of ${conf.game.guesser_name}s`,
source: `UpdatePlayer.Modify` source: `UpdatePlayer.Modify`
}); });
return; return;

View file

@ -1,10 +1,11 @@
import axios from "axios";
import { Team } from "./Team"; import { Team } from "./Team";
import { Deck } from "./Deck"; import { Deck } from "./Deck";
import { readFile } from "fs"; import { readFile } from "fs";
import neatCSV from "neat-csv"; import neatCSV from "neat-csv";
import { Logger } from "tslog"; import { Logger } from "tslog";
import { Player } from "./Player"; import { Player } from "./Player";
import { games, hibernatedGames, conf } from "../main"; import { games, hibernatedGames, conf, log } from "../main";
export class Game { export class Game {
readonly id: string; readonly id: string;
@ -32,10 +33,10 @@ export class Game {
// Get the decks based on what type of data they are. // Get the decks based on what type of data they are.
switch (conf.game.cards.type) { switch (conf.game.cards.type) {
case "csv": case "csv":
this.parseDeckCSV(conf); this.parseDeckCSV();
break; break;
case "sheets": case "sheets":
this.parseDeckGoogleSheets(conf); this.parseDeckGoogleSheets();
break; break;
}; };
// Instantiate everything for the teams // Instantiate everything for the teams
@ -68,11 +69,10 @@ export class Game {
} }
private parseDeckCSV(conf: config) { private parseDeckCSV() {
/** /**
* Parses out the CSV files and creates the decks for the game to run on * Parses out the CSV files and creates the decks for the game to run
* * on.
* @param path -> The filepath of the CSV file
*/ */
// parse the questions from the CSV // parse the questions from the CSV
@ -102,13 +102,61 @@ export class Game {
}); });
}; };
private parseDeckGoogleSheets(conf: config) { private parseDeckGoogleSheets() {
/** /**
* Fetches and parses the CSV data from Google Sheets instead of local * Fetches and parses the CSV data from Google Sheets instead of local
* CSV files. * CSV files.
*
* @param conf -> The config object
*/ */
let key = conf.game.cards.key as string;
let questions_id = conf.game.cards.questions.fingerprint;
let objects_id = conf.game.cards.objects.fingerprint;
// Get the questions deck
axios.get(`https://docs.google.com/spreadsheets/d/e/${key}/pub?gid=${questions_id}&single=true&output=csv`)
.then(response => {
// Ensure not errored
if (response.status !== 200) {
log.warn(`Error Downloading CSV: ${response.statusText}`);
return;
};
// Parse the loaded CSV
neatCSV(response.data)
.then((data) => {
let questions: question_deck[] = [];
for (var entry of data) {
questions.push(Object.values(entry)[conf.game.cards.questions.column]);
};
this._questions = new Deck(questions);
});
})
.catch(err => {
log.prettyError(err);
});
// Get the objects deck
axios.get(`https://docs.google.com/spreadsheets/d/e/${key}/pub?gid=${objects_id}&single=true&output=csv`)
.then(response => {
// Ensure not errored
if (response.status !== 200) {
log.warn(`Error Downloading CSV: ${response.statusText}`);
return;
};
// Parse the downloaded CSV
neatCSV(response.data)
.then((data) => {
let objects: object_deck[] = [];
for (var line of data) {
objects.push(Object.values(line));
};
this._objects = new Deck(objects);
});
})
.catch(err => {
log.prettyError(err);
});
}; };
@ -119,6 +167,7 @@ export class Game {
if (this._objectCard) { if (this._objectCard) {
this._objects.discard(this._objectCard); this._objects.discard(this._objectCard);
this._objectCard = null; this._objectCard = null;
this.object = ``;
}; };
}; };

View file

@ -17,6 +17,7 @@ interface config {
code_length: number; code_length: number;
writer_name: string; writer_name: string;
guesser_name: string; guesser_name: string;
guesser_limit: number;
cards: { cards: {
type: `csv` | `sheets`; type: `csv` | `sheets`;
key?: string; key?: string;

View file

@ -12,7 +12,7 @@ export class Validate {
}; };
// Assert data in the game object // Assert data in the game object
if (![`csv`].includes(conf.game.cards.type)) { if (![`csv`, `sheets`].includes(conf.game.cards.type)) {
log.error(`Unsupported cards type: ${conf.game.cards.type}`); log.error(`Unsupported cards type: ${conf.game.cards.type}`);
valid = false; valid = false;
}; };

View file

@ -11,6 +11,8 @@ code_length = 6
writer_name = "Spirit" writer_name = "Spirit"
guesser_name = "Medium" guesser_name = "Medium"
# The limit of players that are allowed to be guessers on each team.
guesser_limit = 7
[game.cards] [game.cards]

View file

@ -61,6 +61,7 @@ export default {
"Toml": "https://www.npmjs.com/package/toml", "Toml": "https://www.npmjs.com/package/toml",
"tslog": "https://www.npmjs.com/package/tslog", "tslog": "https://www.npmjs.com/package/tslog",
"Socket.io": "https://socket.io", "Socket.io": "https://socket.io",
"Axios": "https://www.npmjs.com/package/axios",
"neat-csv": "https://github.com/sindresorhus/neat-csv", "neat-csv": "https://github.com/sindresorhus/neat-csv",
} }
}}, }},

View file

@ -13,7 +13,8 @@
v-for="answerIndex in 8" v-for="answerIndex in 8"
:class="[ :class="[
`answer`, `answer`,
answers[`team_${3 - $store.state.team}`][answerIndex-1].toLowerCase() == $store.state.chosen_object+`.` ? `correct`: `` answers[`team_${3 - $store.state.team}`][answerIndex-1].toLowerCase() === getObject
? `correct`: ``
]" ]"
:key="`${otherTeamID}-answer-container-${answerIndex}`" :key="`${otherTeamID}-answer-container-${answerIndex}`"
> >
@ -57,7 +58,8 @@
v-for="answerIndex in 8" v-for="answerIndex in 8"
:class="[ :class="[
`answer`, `answer`,
answers[`team_${$store.state.team}`][answerIndex-1].toLowerCase() == $store.state.chosen_object+`.` ? `correct`: `` answers[`team_${$store.state.team}`][answerIndex-1].toLowerCase() === getObject
? `correct`: ``
]" ]"
:key="`${teamID}-answer-container-${answerIndex}`" :key="`${teamID}-answer-container-${answerIndex}`"
> >
@ -122,6 +124,12 @@ export default {
answers() { answers() {
return this.$store.state.answers; return this.$store.state.answers;
}, },
getObject() {
if (!this.$store.state.chosen_object) {
return ``;
};
return this.$store.state.chosen_object.toLowerCase() + `.`;
},
}, },
methods: { methods: {
isCorrect(team, answerIndex) { isCorrect(team, answerIndex) {

View file

@ -5,7 +5,7 @@
</div> </div>
<div class="flex-center" v-else-if="gameOver"> <div class="flex-center" v-else-if="gameOver">
<a <a
v-if="$store.state.survery_link" v-if="$store.state.survey_link"
:href="$store.state.survey_link" :href="$store.state.survey_link"
target="_blank" target="_blank"
rel="noopener" rel="noopener"
@ -74,8 +74,13 @@ export default {
gameOver() { gameOver() {
if (this.$store.state.chosen_object) { if (this.$store.state.chosen_object) {
let targetAnswer = this.$store.state.chosen_object.toLowerCase()+`.`; let targetAnswer = this.$store.state.chosen_object.toLowerCase()+`.`;
return this.$store.state.answers.team_1.includes(targetAnswer) for (var team in this.$store.state.answers) {
|| this.$store.state.answers.team_2.includes(targetAnswer); for (var answer of this.$store.state.answers[team]) {
if (answer.toLowerCase() === targetAnswer) {
return true;
};
};
};
}; };
return false; return false;
}, },

View file

@ -114,7 +114,7 @@ export default {
* }, * },
*/ */
console.log(data) console.log(data)
if (!(200 <= data.status && data.status < 300)) { if (data.status < 200 || 300 <= data.status) {
this.$emit(`error`, data); this.$emit(`error`, data);
return; return;
}; };