diff --git a/server/src/events/JoinGame.ts b/server/src/events/JoinGame.ts index b37c02b..972947c 100644 --- a/server/src/events/JoinGame.ts +++ b/server/src/events/JoinGame.ts @@ -122,14 +122,24 @@ export default (io: Server, socket: Socket, data: JoinGame) => { let rooms: string[] = [game.id]; game.log.info(`Player Reconnected to the game (name=${data.name})`); - // Get the hand of the player's team let hand: string[] = []; - if (sameName.team && sameName.role == `guesser`) { - hand = game.teams[sameName.team - 1].hand; + + // Ensure that the user has a role before connecting them to + // the websocket rooms + if (sameName.role && sameName.team) { rooms.push( `${game.id}:*:${sameName.role}`, `${game.id}:${sameName.team}:${sameName.role}` ); + + switch (sameName.role) { + case "guesser": + hand = game.teams[sameName.team - 1].hand; + break; + case "writer": + hand = game.teams[sameName.team - 1].spiritHand; + break; + }; }; socket.join(rooms); diff --git a/server/src/events/RandomizeTeams.ts b/server/src/events/RandomizeTeams.ts new file mode 100644 index 0000000..76beb27 --- /dev/null +++ b/server/src/events/RandomizeTeams.ts @@ -0,0 +1,63 @@ +import { games, log } from '../main'; +import { Server, Socket } from 'socket.io'; + +export default (io: Server, socket: Socket, data: RandomizeTeams) => { + try { + + // Assert game exists + if (!games[data.game_code]) { + log.debug(`Can't find game with code: ${data.game_code}`); + socket.emit(`RandomizedTeams`, { + status: 404, + message: `Game with code ${data.game_code} could not be found`, + source: `RandomizeTeams` + }); + return; + }; + let game = games[data.game_code]; + + let players = [...game.players]; + // game.log.info(players); + let new_team: 1|2 = 1; + while (players.length > 0) { + + let player_index = Math.floor(Math.random() * players.length); + let player = players[player_index]; + players.splice(player_index, 1); + + game.log.debug(`Randomized ${player.name} onto team ${new_team}`); + + // Move the socket rooms that the player's socket is in + player.socket?.leave(`${game.id}:*:${player.role}`); + player.socket?.leave(`${game.id}:${player.team}:${player.role}`); + player.socket?.join([ + `${game.id}:*:guesser`, + `${game.id}:${new_team}:guesser` + ]); + + // Update the player's object + player.role = `guesser`; + player.team = new_team; + + // Add the next player to the other team + new_team = new_team == 1 ? 2 : 1; + + // Alert all connected clients that they need to update the UI + io.to(game.id).emit(`PlayerUpdate`, { + status: 200, + action: `modify`, + name: player.name, + role: player.role, + team: player.team, + }); + }; + } + catch (err) { + log.prettyError(err); + socket.emit(`RandomizedTeams`, { + status: 500, + message: `${err.name}: ${err.message}`, + source: `RandomizeTeams`, + }); + }; +}; \ No newline at end of file diff --git a/server/src/events/SendCard.ts b/server/src/events/SendCard.ts index 380c5b6..491cce6 100644 --- a/server/src/events/SendCard.ts +++ b/server/src/events/SendCard.ts @@ -50,7 +50,7 @@ export default (io: Server, socket: Socket, data: SendCard) => { game.log.debug(`Guesser is sending a card to the writer`); // Update the team's hand - team.removeCard(data.text); + team.askSpirit(data.text); // send the question text to the writer player io.to(`${game.id}:${team.id}:writer`).emit(`UpdateHand`, { diff --git a/server/src/events/UpdatePlayer.ts b/server/src/events/UpdatePlayer.ts index 9cc3799..2b694a5 100644 --- a/server/src/events/UpdatePlayer.ts +++ b/server/src/events/UpdatePlayer.ts @@ -38,11 +38,15 @@ export default (io: Server, socket: Socket, data: UpdatePlayer) => { message: `${err.name}: ${err.message}`, source: `UpdatePlayer`, }); - } + }; }; -const modifyPlayer = (io: Server, socket: Socket, data: UpdatePlayer): void => { +export const modifyPlayer = ( + io: Server, + socket: Socket, + data: UpdatePlayer +): void => { let game = games[data.game_code]; let player = game.players.find(x => x.name === data.name); diff --git a/server/src/objects/Deck.ts b/server/src/objects/Deck.ts index a65e249..cecb6dc 100644 --- a/server/src/objects/Deck.ts +++ b/server/src/objects/Deck.ts @@ -14,14 +14,14 @@ export class Deck { get size(): number { return this._deck.length; } + /** + * Draws X cards from the deck + * + * @param quantity The number of cards to draw + * @throws Error If quantity is <= 0 + * @throws Error If quantity > size + */ public draw(quantity: number): T[] { - /** - * Draws X cards from the deck - * - * @param quantity -> The number of cards to draw - * @throws Error -> If quantity is <= 0 - * @throws Error -> If quantity > size - */ if (quantity <= 0) { throw new Error(`Cannot get ${quantity} cards.`); } else if (quantity > this.size) { @@ -47,12 +47,12 @@ export class Deck { }; + /** + * Adds the specific card to the discard pile + * + * @param card The card to add to the discard pile + */ public discard(card: T) { - /** - * Adds the specific card to the discard pile - * - * @param card -> The card to add to the discard pile - */ this._unknown = this._unknown.filter(x => x != card); this._discard.push(card); }; @@ -65,10 +65,10 @@ export class Deck { }; + /** + * Converts this Deck into a JSON-compatible object + */ public toJSON(): datastoreDeck { - /** - * Converts this Deck into a JSON-compatible object - */ return { deck: this._deck, unknown: this._unknown, @@ -76,10 +76,10 @@ export class Deck { }; }; + /** + * Converts the JSON representation of a deck into a Deck + */ public static fromJSON(data: datastoreDeck): Deck { - /** - * Converts the JSON representation of a deck into a Deck - */ let d = new Deck(data.deck); d._discard = data.discard; d._unknown = data.unknown; diff --git a/server/src/objects/Game.ts b/server/src/objects/Game.ts index f02db0e..0c4953a 100644 --- a/server/src/objects/Game.ts +++ b/server/src/objects/Game.ts @@ -46,10 +46,10 @@ export class Game { get questions() { return this._questions; }; + /** + * Return the objects that the spirits can choose from for the game. + */ get objects() { - /** - * Return the objects that the spirits can choose from for the game. - */ if (!this._objectCard) { this._objectCard = this._objects.draw(1)[0]; }; @@ -69,11 +69,11 @@ export class Game { } + /** + * Parses out the CSV files and creates the decks for the game to run + * on. + */ private parseDeckCSV() { - /** - * Parses out the CSV files and creates the decks for the game to run - * on. - */ // parse the questions from the CSV readFile(conf.game.cards.questions.fingerprint, `utf-8`, (err, filebuffer) => { @@ -102,11 +102,11 @@ export class Game { }); }; + /** + * Fetches and parses the CSV data from Google Sheets instead of local + * CSV files. + */ private parseDeckGoogleSheets() { - /** - * Fetches and parses the CSV data from Google Sheets instead of local - * CSV files. - */ let key = conf.game.cards.key as string; let questions_id = conf.game.cards.questions.fingerprint; let objects_id = conf.game.cards.objects.fingerprint; @@ -160,10 +160,10 @@ export class Game { }; + /** + * Resets the objects card, for restarting the game + */ public resetObject() { - /** - * Resets the objects card, for restarting the game - */ if (this._objectCard) { this._objects.discard(this._objectCard); this._objectCard = null; @@ -172,10 +172,10 @@ export class Game { }; + /** + * Returns a JSON representation of the game. + */ public toJSON(): datastoreGame { - /** - * Returns a JSON representation of the game. - */ return { players: this.players.map(p => p.toJSON()), teams: this.teams.map(t => t.toJSON()), @@ -190,10 +190,10 @@ export class Game { }; }; + /** + * Converts a JSON representation into a Game object + */ public static fromJSON(host: Player, data: datastoreGame): Game { - /** - * Converts a JSON representation into a Game object - */ let game = new this(host, { id: data.id }); // Re-create the deck objects @@ -217,12 +217,12 @@ export class Game { }; + /** + * Generates a game code with the given length + * + * @param length The length of the code we want to generate + */ public static generateID(length: number): string { - /** - * Generates a game code with the given length - * - * @param length -> The length of the code we want to generate - */ let code: string; // Generate a code until we don't have a collision diff --git a/server/src/objects/Player.ts b/server/src/objects/Player.ts index 2d279ba..0d9c31f 100644 --- a/server/src/objects/Player.ts +++ b/server/src/objects/Player.ts @@ -13,6 +13,9 @@ export class Player { this.isHost = isHost; }; + /** + * Converts the Player into a JSON-compatible representation of the player + */ public toJSON(): datastorePlayer { return { name: this.name, @@ -22,6 +25,11 @@ export class Player { }; }; + /** + * Converts JSON-compatible player data into a Player object. + * + * @param data The player data to convert + */ public static fromJSON(data: datastorePlayer): Player { let player = new this(data.name, null, data.host); player.role = data.role; diff --git a/server/src/objects/Team.ts b/server/src/objects/Team.ts index da5f1f3..530cc57 100644 --- a/server/src/objects/Team.ts +++ b/server/src/objects/Team.ts @@ -4,7 +4,7 @@ export class Team { readonly id: team; public guessers: Player[]; public writer: Player | null; - private _hand: string[]; + private _hand: team_hands; private _questions: string[]; private _answers: string[]; @@ -12,68 +12,81 @@ export class Team { this.id = id; this._answers = new Array(8).fill(``); this._questions = []; - this._hand = []; + this._hand = { + guesser: [], + writer: [] + }; this.guessers = []; }; - /* - * The getters for the various class properties - */ - get hand(): string[] { return this._hand; }; + /* The getters for the various class properties */ + get hand(): string[] { return this._hand.guesser; }; + get spiritHand(): string[] { return this._hand.writer }; get answers(): string[] { return this._answers; }; get questions(): string[] { return this._questions; }; + /** + * Adds the question(s) to the medium's hand + * + * @param questions The array of question text to add the medium's hand. + */ public addCardsToHand(questions: string[]): void { - /** - * Adds the question(s) to the medium's hand - * - * @param questions -> The array of question text to add the medium's - * hand. - */ - this._hand.push(...questions); + this._hand.guesser.push(...questions); }; + /** + * Resets all the per-game data related to this team + */ public reset(): void { - /** - * Resets all the per-game data related to this team - */ - this._hand = []; + this._hand.guesser = []; this._questions = []; this._answers = new Array(8).fill(``); } + /** + * Removes a card from the medium's hand + * + * @param question The card to remove + */ public removeCard(question: string) { - /** - * Removes the given question from the medium's hand - * - * @param question -> The card text to remove from the hand. - */ - this._hand = this._hand.filter(x => x != question); + this._hand.guesser = this._hand.guesser.filter(x => x != question); }; + /** + * Asks the spirit a question, removing it from the medium's hands + * + * @param question The question that is being asked + */ + public askSpirit(question: string) { + this._hand.writer.push(question); + this.removeCard(question); + }; + + + /** + * Adds the given question to the history of the questions. + * + * @param question The question the spirit is answering + */ public selectQuestion(question: string) { - /** - * Adds the given question to the history of the questions. - * - * @param question -> The question the spirit is answering - */ this._questions.push(question); + this._hand.writer = []; }; + /** + * Takes the value of an answer and modifies in the storage. + * + * @param answerIndex The value of the answer between 1 and 8 (inclusive) + * @param answer The new answer for that index + * @throws Error If the answerIndex is not in range + */ public modifyAnswer(answerIndex: answer, answer: string) { - /** - * Takes the value of an answer and modifies in the storage. - * - * @param answerIndex -> The value of the answer between 1 and 8 (inclusive) - * @param answer -> The new answer for that index - * @throws Error -> If the answerIndex is not in range - */ if (answerIndex > this._answers.length || answerIndex <= 0) { throw new Error(`Cannot set answer at index ${answerIndex}.`) }; @@ -81,26 +94,26 @@ export class Team { }; + /** + * Converts the given object into a JSON representation of the data + */ public toJSON(): datastoreTeam { - /** - * Converts the given object into a JSON representation of the data - */ return { questions: this._questions, answers: this._answers, - hand: this._hand, + hands: this._hand, id: this.id, }; }; + /** + * Converts a team JSON object back into a Team object. + */ public static fromJSON(data: datastoreTeam): Team { - /** - * Converts a team JSON object back into a Team object. - */ let t = new Team(data.id); t._questions = data.questions; t._answers = data.answers; - t._hand = data.hand; + t._hand = data.hands; return t; }; }; \ No newline at end of file diff --git a/server/src/types/data.d.ts b/server/src/types/data.d.ts index 07b35b8..1653d17 100644 --- a/server/src/types/data.d.ts +++ b/server/src/types/data.d.ts @@ -72,6 +72,14 @@ interface GameRejoined extends response { } +interface RandomizeTeam { + game_code: string; +} +interface TeamsRandomized extends response { + players: player[]; +} + + interface GetHand { game_code: string; team: team; diff --git a/server/src/types/datastore.d.ts b/server/src/types/datastore.d.ts index e819395..e3b32f0 100644 --- a/server/src/types/datastore.d.ts +++ b/server/src/types/datastore.d.ts @@ -10,7 +10,7 @@ type datastoreObjectCard = string[]; interface datastoreTeam { questions: datastoreQuestionCard[]; - hand: datastoreQuestionCard[]; + hands: team_hands; answers: string[]; id: team; } diff --git a/server/src/types/team_hands.d.ts b/server/src/types/team_hands.d.ts new file mode 100644 index 0000000..3175670 --- /dev/null +++ b/server/src/types/team_hands.d.ts @@ -0,0 +1,4 @@ +interface team_hands { + guesser: question_deck[]; + writer: question_deck[]; +} \ No newline at end of file diff --git a/server/src/websocket.ts b/server/src/websocket.ts index f3e6ac5..71b1aac 100644 --- a/server/src/websocket.ts +++ b/server/src/websocket.ts @@ -15,6 +15,7 @@ import UpdatePlayer from "./events/UpdatePlayer"; import SelectObject from "./events/SelectObject"; import UpdateAnswer from "./events/UpdateAnswer"; import GetPastQuestions from "./events/GetPastQuestions"; +import RandomizeTeams from "./events/RandomizeTeams"; export default async (conf: config) => { @@ -42,6 +43,7 @@ export default async (conf: config) => { socket.on(`JoinGame`, (data: JoinGame) => JoinGame(io, socket, data)); socket.on(`UpdatePlayer`, (data: UpdatePlayer) => UpdatePlayer(io, socket, data)); socket.on(`LeaveGame`, (data: LeaveGame) => LeaveGame(io, socket, data)); + socket.on(`RandomizeTeams`, (data: RandomizeTeams) => RandomizeTeams(io, socket, data)); // Game Mechanisms diff --git a/web/src/components/TeamReminder.vue b/web/src/components/TeamReminder.vue index e1e0317..965b641 100644 --- a/web/src/components/TeamReminder.vue +++ b/web/src/components/TeamReminder.vue @@ -1,17 +1,56 @@ @@ -31,7 +74,7 @@ export default { @import "../css/theme.css"; @import "../css/style.css"; -#TeamReminder { +#TeamReminder > .container { background-color: var(--background3); border-radius: 0 100% 0 0; height: var(--size); @@ -46,7 +89,7 @@ img.team_1 { width: calc(var(--size) / 1.5); position: absolute; bottom: 7px; - left: 7px;; + left: 7px; } img.team_2 { @@ -55,4 +98,19 @@ img.team_2 { left: calc(var(--size) / 8); position: absolute; } + + +h1, h2, p { + margin: 5px 0; +} + +.team-list { + justify-content: center; + flex-direction: row; + display: flex; +} + +.team { + width: 40%; +} \ No newline at end of file diff --git a/web/src/views/Lobby.vue b/web/src/views/Lobby.vue index 4755566..6eb656f 100644 --- a/web/src/views/Lobby.vue +++ b/web/src/views/Lobby.vue @@ -24,9 +24,10 @@
+
+
@@ -61,6 +68,9 @@ export default { gameCode() { return this.$store.state.game_code; }, + canRandomize() { + return this.$store.state.is_host; + }, }, methods: { copySuccess() { @@ -92,7 +102,12 @@ export default { startGame() { this.$socket.client.emit(`StartGame`, { game_code: this.gameCode - }) + }); + }, + randomizeTeams() { + this.$socket.client.emit(`RandomizeTeams`, { + game_code: this.gameCode + }); }, }, sockets: { @@ -126,6 +141,7 @@ export default { justify-content: center; align-items: stretch; display: flex; + flex-wrap: wrap; } button { @@ -139,4 +155,10 @@ button { } button:hover { background-color: var(--background2-darken); } button:focus { background-color: var(--background2-lighten); } + + +div.new-line { + width: 100% !important; + height: 0px; +} \ No newline at end of file