commit
7e12124330
14 changed files with 149 additions and 40 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -7,6 +7,7 @@ server/built/*
|
|||
server/dist/*
|
||||
server/resources/*
|
||||
*.log
|
||||
*.sh
|
||||
|
||||
#=============================================================================#
|
||||
# The files that were auto-generated into a .gitignore by Vue-cli
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"@types/engine.io": "^3.1.4",
|
||||
"@types/node": "^14.14.14",
|
||||
"@types/socket.io": "^2.1.12",
|
||||
"axios": "^0.21.1",
|
||||
"fs": "^0.0.1-security",
|
||||
"neat-csv": "^6.0.0",
|
||||
"socket.io": "^3.0.4",
|
||||
|
|
|
|||
19
server/pnpm-lock.yaml
generated
19
server/pnpm-lock.yaml
generated
|
|
@ -2,6 +2,7 @@ dependencies:
|
|||
'@types/engine.io': 3.1.4
|
||||
'@types/node': 14.14.14
|
||||
'@types/socket.io': 2.1.12
|
||||
axios: 0.21.1
|
||||
fs: 0.0.1-security
|
||||
neat-csv: 6.0.0
|
||||
socket.io: 3.0.4
|
||||
|
|
@ -54,6 +55,12 @@ packages:
|
|||
node: '>= 0.6'
|
||||
resolution:
|
||||
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:
|
||||
dev: false
|
||||
engines:
|
||||
|
|
@ -127,6 +134,17 @@ packages:
|
|||
node: '>=10.0.0'
|
||||
resolution:
|
||||
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:
|
||||
dev: false
|
||||
resolution:
|
||||
|
|
@ -266,6 +284,7 @@ specifiers:
|
|||
'@types/engine.io': ^3.1.4
|
||||
'@types/node': ^14.14.14
|
||||
'@types/socket.io': ^2.1.12
|
||||
axios: ^0.21.1
|
||||
fs: ^0.0.1-security
|
||||
neat-csv: ^6.0.0
|
||||
socket.io: ^3.0.4
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { readFileSync } from 'fs';
|
||||
import { Game } from '../objects/Game';
|
||||
import { Player } from '../objects/Player';
|
||||
import { Server, Socket } from 'socket.io';
|
||||
import { readFileSync, unlinkSync } from 'fs';
|
||||
import { games, hibernatedGames, log, conf } from '../main';
|
||||
|
||||
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
|
||||
let hibernatedIndex = hibernatedGames.indexOf(data.game_code)
|
||||
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(
|
||||
`${conf.datastores.directory}/${data.game_code}.${conf.datastores.filetype}`,
|
||||
`utf-8`
|
||||
)) 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);
|
||||
host.role = playerData.role;
|
||||
host.team = playerData.team;
|
||||
|
||||
// Re-instantiate the game object
|
||||
let game = Game.fromJSON(host, datastore);
|
||||
game.log = log.getChildLogger({
|
||||
displayLoggerName: true,
|
||||
name: game.id,
|
||||
});
|
||||
|
||||
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[] = [];
|
||||
if (host.team) {
|
||||
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.team}:${host.role}`
|
||||
]);
|
||||
game.log.debug(`Host assigned to team`);
|
||||
game.log.debug(`Host assigned to team object`);
|
||||
};
|
||||
|
||||
hibernatedGames.splice(hibernatedIndex, 1);
|
||||
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`);
|
||||
socket.join(game.id);
|
||||
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
|
||||
games that were reloaded from disk.
|
||||
*/
|
||||
let sameName = game.players.find(x => x.name == data.name);
|
||||
if (sameName != null) {
|
||||
let sameName = game.players.find(x => x.name === data.name);
|
||||
if (sameName) {
|
||||
|
||||
if (!sameName.socket?.connected) {
|
||||
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}`);
|
||||
socket.emit(`GameJoined`, {
|
||||
status: 403,
|
||||
message: `A player already has that name in the game.`,
|
||||
message: `Cannot connect to a game that's in progress.`,
|
||||
source: `JoinGame`
|
||||
});
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ export default (io: Server, socket: Socket, data: ResetGame) => {
|
|||
return;
|
||||
};
|
||||
let game = games[data.game_code];
|
||||
game.log.info(`Resetting game`)
|
||||
|
||||
game.questions.reset();
|
||||
game.resetObject();
|
||||
game.ingame = false;
|
||||
|
||||
io.to(game.id).emit(`GameReset`, { status: 200 });
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -85,11 +85,11 @@ const modifyPlayer = (io: Server, socket: Socket, data: UpdatePlayer): void => {
|
|||
|
||||
switch (data.to.role) {
|
||||
case "guesser":
|
||||
if (team.guessers.length >= 7) {
|
||||
game.log.debug(`Game cannot have more than 7 guessers`);
|
||||
if (team.guessers.length >= conf.game.guesser_limit) {
|
||||
game.log.debug(`Game cannot have more than ${conf.game.guesser_limit} guessers`);
|
||||
socket.emit(`PlayerUpdate`, {
|
||||
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`
|
||||
});
|
||||
return;
|
||||
|
|
@ -128,11 +128,11 @@ const modifyPlayer = (io: Server, socket: Socket, data: UpdatePlayer): void => {
|
|||
let team = game.teams[data.to.team - 1];
|
||||
switch (data.to.role) {
|
||||
case "guesser":
|
||||
if (team.guessers.length >= 7) {
|
||||
game.log.debug(`Game cannot have more than 7 guessers`);
|
||||
if (team.guessers.length >= conf.game.guesser_limit) {
|
||||
game.log.debug(`Game cannot have more than ${conf.game.guesser_limit} guessers`);
|
||||
socket.emit(`PlayerUpdate`, {
|
||||
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`
|
||||
});
|
||||
return;
|
||||
|
|
@ -181,11 +181,11 @@ const modifyPlayer = (io: Server, socket: Socket, data: UpdatePlayer): void => {
|
|||
switch (data.to.role) {
|
||||
case "guesser":
|
||||
// Ensure we don't get 8 guessers
|
||||
if (newTeam.guessers.length >= 7) {
|
||||
game.log.debug(`Game cannot have 8 or more guessers on a team.`);
|
||||
if (newTeam.guessers.length >= conf.game.guesser_limit) {
|
||||
game.log.debug(`That team already`);
|
||||
socket.emit(`PlayerUpdate`, {
|
||||
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`
|
||||
});
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import axios from "axios";
|
||||
import { Team } from "./Team";
|
||||
import { Deck } from "./Deck";
|
||||
import { readFile } from "fs";
|
||||
import neatCSV from "neat-csv";
|
||||
import { Logger } from "tslog";
|
||||
import { Player } from "./Player";
|
||||
import { games, hibernatedGames, conf } from "../main";
|
||||
import { games, hibernatedGames, conf, log } from "../main";
|
||||
|
||||
export class Game {
|
||||
readonly id: string;
|
||||
|
|
@ -32,10 +33,10 @@ export class Game {
|
|||
// Get the decks based on what type of data they are.
|
||||
switch (conf.game.cards.type) {
|
||||
case "csv":
|
||||
this.parseDeckCSV(conf);
|
||||
this.parseDeckCSV();
|
||||
break;
|
||||
case "sheets":
|
||||
this.parseDeckGoogleSheets(conf);
|
||||
this.parseDeckGoogleSheets();
|
||||
break;
|
||||
};
|
||||
// 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
|
||||
*
|
||||
* @param path -> The filepath of the CSV file
|
||||
* Parses out the CSV files and creates the decks for the game to run
|
||||
* on.
|
||||
*/
|
||||
|
||||
// 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
|
||||
* 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) {
|
||||
this._objects.discard(this._objectCard);
|
||||
this._objectCard = null;
|
||||
this.object = ``;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
1
server/src/types/config.d.ts
vendored
1
server/src/types/config.d.ts
vendored
|
|
@ -17,6 +17,7 @@ interface config {
|
|||
code_length: number;
|
||||
writer_name: string;
|
||||
guesser_name: string;
|
||||
guesser_limit: number;
|
||||
cards: {
|
||||
type: `csv` | `sheets`;
|
||||
key?: string;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export class Validate {
|
|||
};
|
||||
|
||||
// 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}`);
|
||||
valid = false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ code_length = 6
|
|||
writer_name = "Spirit"
|
||||
guesser_name = "Medium"
|
||||
|
||||
# The limit of players that are allowed to be guessers on each team.
|
||||
guesser_limit = 7
|
||||
|
||||
[game.cards]
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ export default {
|
|||
"Toml": "https://www.npmjs.com/package/toml",
|
||||
"tslog": "https://www.npmjs.com/package/tslog",
|
||||
"Socket.io": "https://socket.io",
|
||||
"Axios": "https://www.npmjs.com/package/axios",
|
||||
"neat-csv": "https://github.com/sindresorhus/neat-csv",
|
||||
}
|
||||
}},
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@
|
|||
v-for="answerIndex in 8"
|
||||
:class="[
|
||||
`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}`"
|
||||
>
|
||||
|
|
@ -57,7 +58,8 @@
|
|||
v-for="answerIndex in 8"
|
||||
:class="[
|
||||
`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}`"
|
||||
>
|
||||
|
|
@ -122,6 +124,12 @@ export default {
|
|||
answers() {
|
||||
return this.$store.state.answers;
|
||||
},
|
||||
getObject() {
|
||||
if (!this.$store.state.chosen_object) {
|
||||
return ``;
|
||||
};
|
||||
return this.$store.state.chosen_object.toLowerCase() + `.`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isCorrect(team, answerIndex) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
</div>
|
||||
<div class="flex-center" v-else-if="gameOver">
|
||||
<a
|
||||
v-if="$store.state.survery_link"
|
||||
v-if="$store.state.survey_link"
|
||||
:href="$store.state.survey_link"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
|
|
@ -74,8 +74,13 @@ export default {
|
|||
gameOver() {
|
||||
if (this.$store.state.chosen_object) {
|
||||
let targetAnswer = this.$store.state.chosen_object.toLowerCase()+`.`;
|
||||
return this.$store.state.answers.team_1.includes(targetAnswer)
|
||||
|| this.$store.state.answers.team_2.includes(targetAnswer);
|
||||
for (var team in this.$store.state.answers) {
|
||||
for (var answer of this.$store.state.answers[team]) {
|
||||
if (answer.toLowerCase() === targetAnswer) {
|
||||
return true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return false;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ export default {
|
|||
* },
|
||||
*/
|
||||
console.log(data)
|
||||
if (!(200 <= data.status && data.status < 300)) {
|
||||
if (data.status < 200 || 300 <= data.status) {
|
||||
this.$emit(`error`, data);
|
||||
return;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue