Skip to content
This repository has been archived by the owner on Jul 31, 2024. It is now read-only.

Commit

Permalink
feat: multiplayer games
Browse files Browse the repository at this point in the history
  • Loading branch information
BrandonHowe committed Jul 18, 2020
1 parent c0b5079 commit 7c65ae7
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 10 deletions.
45 changes: 45 additions & 0 deletions packages/api/src/modules/games/actions/createMultiGame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Achievement from "../../achievements/types/Achievement";
import Game from "../types/Game";
import { highestGameId } from "./highestGameId";
import knex from "../../../../db/knex";
import findAchievementsByRequirement from "../../achievements/actions/findAchievementsByRequirement";
import userHasAchievement from "../../achievements/actions/userHasAchievement";
import { GamePlayer } from "../../websockets/gamesData";

export const createGames = async (players: GamePlayer[]): Promise<Game> => {
const newHighestGame = (await highestGameId()) + 1;
const newGames = await Promise.all(
players.map(async l => {
const newGame = {
gameid: newHighestGame,
id: l.id,
wpm: l.wpm,
rawwpm: l.rawwpm,
acc: l.acc,
date: Date.now()
};
const possibleAchievements: Achievement[] = await findAchievementsByRequirement(
{
wpm: l.wpm,
rawwpm: l.rawwpm,
acc: l.acc
}
);
const achievements = possibleAchievements.filter(
async j => !(await userHasAchievement(l.id, j.id))
);
achievements.map(async j => {
await knex("users")
.update({
achievements: knex.raw(
"array_append(achievements, ?)",
[j.id]
)
})
.where({ id: l.id });
});
return newGame;
})
);
return (await knex<Game>("games").insert(newGames, "*"))[0];
};
1 change: 1 addition & 0 deletions packages/api/src/modules/games/actions/removeOldGame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const removeOldGame = async (userid: number): Promise<void> => {
for (const game of removalGames) {
await knex<Game>("games")
.delete()
.where({ userid })
.where({ gameid: game.gameid });
}
}
Expand Down
10 changes: 10 additions & 0 deletions packages/api/src/modules/websockets/actions/gameFinished.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Game } from "../gamesData";
import { createGames } from "../../games/actions/createMultiGame";
import { removeOldGame } from "../../games/actions/removeOldGame";

export default async (game: Game) => {
if (game.players.every(l => l.progress >= 100)) {
await createGames(game.players);
await Promise.all(game.players.map(l => removeOldGame(l.id)));
}
};
2 changes: 1 addition & 1 deletion packages/api/src/modules/websockets/actions/leaveQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import WebSocket from "ws";
export default (id: number, ws: WebSocket): HandlerResponse => {
queue.splice(queue.findIndex(l => l.id === id));
return {
category: "joinResponse",
category: "leaveResponse",
data: [{ client: ws, data: { success: true } }]
};
};
40 changes: 40 additions & 0 deletions packages/api/src/modules/websockets/actions/processQueue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { queue, games } from "../gamesData";
import getRandomText from "../../texts/actions/getRandomText";

const newMaxId = () => {
return Object.keys(games).length === 0
? 1
: Math.max(...Object.keys(games).map(Number)) + 1;
};

export default async () => {
if (queue.length >= 4) {
const players = queue.slice(0, 4);
for (let i = 0; i < 4; i++) {
queue.shift();
}
const userGameKeys = Array(4)
.fill(null)
.map(() => Math.floor(Math.random() * 899999) + 100000);
const newGame = {
players: players.map((l, idx) => ({
...l,
progress: 0,
resigned: false,
gameKey: userGameKeys[idx],
wpm: 0,
rawwpm: 0,
acc: 0
})),
textid: (await getRandomText(false)).id
};
games[newMaxId()] = newGame;
return {
category: "joinGame",
data: players.map((l, idx) => ({
client: l.ws,
data: newGame.players[idx].gameKey
}))
};
}
};
13 changes: 10 additions & 3 deletions packages/api/src/modules/websockets/actions/switchQueueLocation.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import WebSocket from "ws";
import { queue } from "../gamesData";
import HandlerResponse from "../types/HandlerResponse";

export default (data: number, ws: WebSocket) => {
export default (data: number, ws: WebSocket): HandlerResponse => {
const queueling = queue.find(l => l.changeWSKey === data);
if (!queueling) {
return false;
return {
category: "switchQueueResponse",
data: [{ client: ws, data: { success: false } }]
};
}
queueling.ws = ws;
return true;
return {
category: "switchQueueResponse",
data: [{ client: ws, data: { success: true } }]
};
};
44 changes: 44 additions & 0 deletions packages/api/src/modules/websockets/actions/updateProgress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import WebSocket from "ws";
import { games } from "../gamesData";
import gameFinished from "./gameFinished";
import HandlerResponse from "../types/HandlerResponse";

export default async (
data: { key: number; progress: number },
ws: WebSocket
): Promise<HandlerResponse> => {
const gameWithKey = Object.values(games).find(l =>
l.players.some(j => j.changeWSKey === data.key)
);
if (!gameWithKey) {
return {
category: "updateResponse",
data: [
{
client: ws,
data: {
success: false
}
}
]
};
}
gameWithKey.players.find(l => l.changeWSKey === data.key)!.progress =
data.progress;
await gameFinished(gameWithKey);
return {
category: "updateResponse",
data: [
{
client: ws,
data: {
success: true,
data: gameWithKey.players.map(l => ({
progress: l.progress,
resigned: l.resigned
}))
}
}
]
};
};
16 changes: 15 additions & 1 deletion packages/api/src/modules/websockets/gamesData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ interface Queueling {
changeWSKey: number;
}

export const games = {};
export type GamePlayer = Queueling & {
progress: number;
resigned: boolean;
gameKey: number;
wpm: number;
rawwpm: number;
acc: number;
};

export interface Game {
players: GamePlayer[];
textid: number;
}

export const games: Record<number, Game> = {};

export const queue: Queueling[] = [];
7 changes: 6 additions & 1 deletion packages/api/src/modules/websockets/types/Category.ts
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
export type Category = "ping" | "joinResponse";
export type Category =
| "ping"
| "joinResponse"
| "leaveResponse"
| "switchQueueResponse"
| "updateResponse";
16 changes: 12 additions & 4 deletions packages/api/src/modules/websockets/websocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@ import categoryParser from "./middleware/categoryParser";
import joinQueue from "./actions/joinQueue";
import leaveQueue from "./actions/leaveQueue";
import HandlerResponse from "./types/HandlerResponse";
import switchQueueLocation from "./actions/switchQueueLocation";
import updateProgress from "./actions/updateProgress";

const wsRoutes: Record<
string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(data: any, ws: WebSocket) => HandlerResponse
(data: any, ws: WebSocket) => HandlerResponse | Promise<HandlerResponse>
> = {
joinQueue,
leaveQueue
leaveQueue,
switchQueueLocation,
updateProgress
};

export default (wss: WebSocket.Server, ws: WebSocket, message: string) => {
export default async (
wss: WebSocket.Server,
ws: WebSocket,
message: string
) => {
const { category, data } = categoryParser(message.toString());
if (Object.keys(wsRoutes).includes(category)) {
const response = wsRoutes[category](data, ws);
const response = await wsRoutes[category](data, ws);
const { category: respCategory } = response;
response.data.map(l => {
l.client.send(
Expand Down

0 comments on commit 7c65ae7

Please sign in to comment.