0
0
Fork 0

Merge pull request #78 from Oliver-Akins/dev

v1.2.6
This commit is contained in:
Oliver 2021-03-01 16:46:03 -07:00 committed by GitHub
commit de2f02b321
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 296 additions and 104 deletions

View file

@ -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);

View file

@ -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`,
});
};
};

View file

@ -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`, {

View file

@ -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);

View file

@ -14,14 +14,14 @@ export class Deck<T> {
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<T> {
};
/**
* 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<T> {
};
/**
* Converts this Deck into a JSON-compatible object
*/
public toJSON(): datastoreDeck<T> {
/**
* Converts this Deck into a JSON-compatible object
*/
return {
deck: this._deck,
unknown: this._unknown,
@ -76,10 +76,10 @@ export class Deck<T> {
};
};
/**
* Converts the JSON representation of a deck into a Deck
*/
public static fromJSON<A>(data: datastoreDeck<A>): Deck<A> {
/**
* Converts the JSON representation of a deck into a Deck
*/
let d = new Deck(data.deck);
d._discard = data.discard;
d._unknown = data.unknown;

View file

@ -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

View file

@ -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;

View file

@ -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<string>(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<string>(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;
};
};

View file

@ -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;

View file

@ -10,7 +10,7 @@ type datastoreObjectCard = string[];
interface datastoreTeam {
questions: datastoreQuestionCard[];
hand: datastoreQuestionCard[];
hands: team_hands;
answers: string[];
id: team;
}

4
server/src/types/team_hands.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
interface team_hands {
guesser: question_deck[];
writer: question_deck[];
}

View file

@ -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

View file

@ -1,17 +1,56 @@
<template>
<div id="TeamReminder">
<img
:class="`team_${teamNumber}`"
:src="`/assets/${teamIcon}`"
:alt="`${teamName} Team Icon`"
<div
class="container clickable"
@click="modal_visible = true"
>
<img
:class="`team_${teamNumber}`"
:src="`/assets/${teamIcon}`"
:alt="`${teamName} Team Icon`"
>
</div>
<ModalAnimation
:show="modal_visible"
@closed="modal_visible = false"
>
<h1 class="centre">Players</h1>
<div class="team-list">
<div
class="team centre"
v-for="teamID in 2"
:key="`team-${teamID}`"
>
<h2>{{$store.state[`team_${teamID}`].name}} Team:</h2>
<div class="players">
<p
class="player"
v-for="p in getTeamPlayers(teamID)"
:key="p.name"
>
{{p.name}}
<span v-if="p.role == 'writer'">
<b>({{$store.state.writer_name}})</b>
</span>
</p>
</div>
</div>
</div>
</ModalAnimation>
</div>
</template>
<script>
import ModalAnimation from "./Modal";
export default {
name: `TeamReminder`,
components: {},
components: {
ModalAnimation: ModalAnimation
},
data() {return {
modal_visible: false
}},
computed: {
teamNumber() {
return this.$store.state.team;
@ -23,7 +62,11 @@ export default {
return this.$store.state[`team_${this.teamNumber}`].icon;
},
},
methods: {},
methods: {
getTeamPlayers(teamID) {
return this.$store.state.players.filter(p => p.team == teamID);
}
},
}
</script>
@ -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%;
}
</style>

View file

@ -24,9 +24,10 @@
</div>
<div class="flex-row">
<button
@click.stop="exitGame()"
class="clickable"
@click.stop="randomizeTeams()"
>
{{ $store.state.is_host ? `Delete` : `Leave`}} Game
Randomize Teams
</button>
<button
class="clickable"
@ -34,6 +35,12 @@
>
Click to Start the Game
</button>
<div class="new-line"></div>
<button
@click.stop="exitGame()"
>
{{ $store.state.is_host ? `Delete` : `Leave`}} Game
</button>
</div>
</div>
</template>
@ -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;
}
</style>