Skip to content

Commit

Permalink
Make the insomniac wait
Browse files Browse the repository at this point in the history
  • Loading branch information
190n committed Apr 4, 2020
1 parent 0f32e0c commit be8e566
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 37 deletions.
2 changes: 2 additions & 0 deletions db.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ What does the database currently look like?
- JSON representation of whatever getResults() returns
- `expirationTimes`
- hash mapping game ID to UNIX timestamp (in seconds) when that game will be destroyed
- `forcedWaits`
- hash of players that are being forced to wait (e.g. insomniac, so they can't learn about cards that were dealt. key is `{gameId}:{playerId}` and value is a UNIX timestamp in seconds. players added to this are also added to `games:{gameId}:waiting`.
111 changes: 76 additions & 35 deletions src/backend/connection.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import WebSocket from 'ws';
import { Commands } from 'redis';

import { assignCards, getInitialRevelation, performAction, canTakeAction, Swap, isTurnImmediatelyComplete, getResults } from './game';
import {
assignCards,
getInitialRevelation,
performAction,
canTakeAction,
Swap,
isTurnImmediatelyComplete,
getResults,
mayWait,
} from './game';

interface EndMessage {
type: 'end';
Expand Down Expand Up @@ -173,9 +182,10 @@ export default function createHandler(redisCall: <T>(command: keyof Commands<boo
});
}

async function runChecksInBackground(endDiscussion: (gameId: string) => Promise<void>, destroyGame: (gameId: string) => Promise<void>) {
async function runChecksInBackground(): Promise<void> {
const endTimes = await redisCall<{ [gameId: string]: string }>('hgetall', 'discussionEndTimes'),
expirationTimes = await redisCall<{ [gameId: string]: string }>('hgetall', 'expirationTimes');
expirationTimes = await redisCall<{ [gameId: string]: string }>('hgetall', 'expirationTimes'),
forcedWaits = await redisCall<{ [gameAndPlayer: string]: string }>('hgetall', 'forcedWaits');

if (endTimes) {
for (const [gameId, endTime] of Object.entries(endTimes)) {
Expand All @@ -197,7 +207,18 @@ export default function createHandler(redisCall: <T>(command: keyof Commands<boo
}
}

setTimeout(() => runChecksInBackground(endDiscussion, destroyGame), 1000);
if (forcedWaits) {
for (const [gameAndPlayer, time] of Object.entries(forcedWaits)) {
if (Date.now() / 1000 >= parseInt(time)) {
const [gameId, playerId] = gameAndPlayer.split(':');
console.log(`ending wait for player ${playerId} in game ${gameId}`);
await redisCall('hdel', 'forcedWaits', gameAndPlayer);
await endForcedWait(gameId, playerId);
}
}
}

setTimeout(runChecksInBackground, 1000);
}

async function endDiscussion(gameId: string) {
Expand Down Expand Up @@ -243,6 +264,38 @@ export default function createHandler(redisCall: <T>(command: keyof Commands<boo
'votes',
'results',
].map(k => `games:${gameId}:${k}`));

for (const key of await redisCall<string[]>('hkeys', 'forcedWaits')) {
if (key.startsWith(gameId)) {
await redisCall('hdel', 'forcedWaits', key);
}
}
}

async function endForcedWait(gameId: string, playerId: string): Promise<void> {
if (canTakeAction(playerId, await getAssignedCards(gameId), await redisCall<string[]>('smembers', `games:${gameId}:completedTurns`))) {
await redisCall('srem', `games:${gameId}:waiting`, playerId);
await askPlayerForAction(gameId, playerId);
}
}

async function askPlayerForAction(gameId: string, playerId: string): Promise<void> {
const sock = (openSockets.get(gameId) as Map<string, WebSocket>).get(playerId);
sock?.send(JSON.stringify({
type: 'stage',
stage: 'action',
}));

const revelation = getInitialRevelation(playerId, await getAssignedCards(gameId), await getSwaps(gameId));
if (revelation !== undefined) {
console.log(`storing revelation "${revelation}" for player ${playerId} in game ${gameId}`);

await redisCall<number>('rpush', `games:${gameId}:events`, `r:${playerId}:${revelation}`);
sock?.send(JSON.stringify({
type: 'revelation',
revelation,
}));
}
}

async function handleIncomingMessage(gameId: string, playerId: string, ws: WebSocket, data: string) {
Expand Down Expand Up @@ -333,31 +386,28 @@ export default function createHandler(redisCall: <T>(command: keyof Commands<boo
const sockets = openSockets.get(gameId) as Map<string, WebSocket>;

for (const p of await redisCall<string[]>('smembers', `games:${gameId}:playersInGame`)) {
const sock = sockets.get(p);
const sock = sockets.get(p),
card = await redisCall<string>('hget', `games:${gameId}:assignedCards`, p);

if (isTurnImmediatelyComplete(await redisCall<string>('hget', `games:${gameId}:assignedCards`, p))) {
if (isTurnImmediatelyComplete(card)) {
await redisCall('sadd', `games:${gameId}:completedTurns`, p);
sock?.send(JSON.stringify({
type: 'stage',
stage: 'wait',
}));
} else if (mayWait(card)) {
// they must wait at least fifteen seconds so they can't learn about
// which cards players have
sock?.send(JSON.stringify({
type: 'stage',
stage: 'wait',
}));

await redisCall('hset', 'forcedWaits', `${gameId}:${p}`, Math.floor(Date.now() / 1000 + 15).toString());
await redisCall('sadd', `games:${gameId}:waiting`, p);
} else {
if (canTakeAction(p, await getAssignedCards(gameId), [])) {
sock?.send(JSON.stringify({
type: 'stage',
stage: 'action',
}));

const revelation = getInitialRevelation(p, await getAssignedCards(gameId), await getSwaps(gameId));

if (revelation !== undefined) {
await redisCall<number>('rpush', `games:${gameId}:events`, `r:${p}:${revelation}`);

sock?.send(JSON.stringify({
type: 'revelation',
revelation,
}));
}
await askPlayerForAction(gameId, p);
} else {
await redisCall<number>('sadd', `games:${gameId}:waiting`, p);

Expand Down Expand Up @@ -446,21 +496,11 @@ export default function createHandler(redisCall: <T>(command: keyof Commands<boo
// see if that allowed new players to take actions
for (const p of await redisCall<string[]>('smembers', `games:${gameId}:waiting`)) {
if (canTakeAction(p, await getAssignedCards(gameId), await redisCall<string[]>('smembers', `games:${gameId}:completedTurns`))) {
console.log(`allowing ${p} to take action because ${playerId} finished their action`);
await redisCall<number>('srem', `games:${gameId}:waiting`, p);

const sock = (openSockets.get(gameId) as Map<string, WebSocket>).get(p);
sock?.send(JSON.stringify({
type: 'stage',
stage: 'action',
}));

const revelation = getInitialRevelation(p, await getAssignedCards(gameId), await getSwaps(gameId));
if (revelation !== undefined) {
await redisCall<number>('rpush', `games:${gameId}:events`, `r:${p}:${revelation}`);
sock?.send(JSON.stringify({
type: 'revelation',
revelation,
}));
if (!(await redisCall<number>('hexists', 'forcedWaits', `${gameId}:${p}`))) {
await askPlayerForAction(gameId, p);
}
}
}
Expand Down Expand Up @@ -596,6 +636,7 @@ export default function createHandler(redisCall: <T>(command: keyof Commands<boo
if (
await redisCall<number>('sismember', `games:${gameId}:waiting`, playerId)
|| await redisCall<number>('sismember', `games:${gameId}:completedTurns`, playerId)
|| await redisCall<number>('hexists', 'forcedWaits', `${gameId}:${playerId}`)
) {
ws.send(JSON.stringify({
type: 'stage',
Expand Down Expand Up @@ -674,7 +715,7 @@ export default function createHandler(redisCall: <T>(command: keyof Commands<boo
}
}

runChecksInBackground(endDiscussion, destroyGame);
runChecksInBackground();

// set expiration timers on all games in case no one connects
(async () => {
Expand Down
4 changes: 4 additions & 0 deletions src/backend/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,7 @@ export function getResults(

return { winners, winningTeam, executed, finalCards, finalCenter };
}

export function mayWait(card: string): boolean {
return card == 'insomniac';
}
6 changes: 4 additions & 2 deletions src/frontend/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,14 @@ $cards: (
margin-bottom: 0;

&.last-minute {
color: #600000;
color: #600020;
}

&.last-seconds {
color: #c00000;
color: #c02000;
}

transition: color 0.5s;
}

.hint {
Expand Down

0 comments on commit be8e566

Please sign in to comment.