Skip to content

Commit 5eaecf2

Browse files
committed
feat: Add ScrabbleDex API
1 parent 3e3ad40 commit 5eaecf2

2 files changed

Lines changed: 76 additions & 0 deletions

File tree

src/database/games.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import mongoose, { type HydratedDocument } from 'mongoose';
2+
import { pokedex } from 'ps-client/data';
23

34
import { IS_ENABLED } from '@/enabled';
5+
import { ScrabbleMods } from '@/ps/games/scrabble/constants';
6+
import { GamesList } from '@/ps/games/types';
7+
import { toId } from '@/tools';
48

9+
import type { Log as ScrabbleLog } from '@/ps/games/scrabble/logs';
10+
import type { WinCtx as ScrabbleWinCtx } from '@/ps/games/scrabble/types';
511
import type { Player } from '@/ps/games/types';
612

713
const schema = new mongoose.Schema({
@@ -80,3 +86,44 @@ export async function getGameById(gameType: string, gameId: string): Promise<Hyd
8086
if (!game) throw new Error(`Unable to find a game of ${gameType} with ID ${id}.`);
8187
return game;
8288
}
89+
90+
type ScrabbleDexEntry = {
91+
pokemon: string;
92+
pokemonName: string;
93+
num: number;
94+
by: string;
95+
at: Date;
96+
gameId: string;
97+
mod: string;
98+
won: boolean;
99+
};
100+
export async function getScrabbleDex(): Promise<ScrabbleDexEntry[] | null> {
101+
if (!IS_ENABLED.DB) return null;
102+
const scrabbleGames = await model.find({ game: GamesList.Scrabble, mod: [ScrabbleMods.CRAZYMONS, ScrabbleMods.POKEMON] }).lean();
103+
return scrabbleGames.flatMap(game => {
104+
const baseCtx = { gameId: game.id, mod: game.mod! };
105+
const winCtx = game.winCtx as ScrabbleWinCtx | undefined;
106+
const winners = winCtx?.type === 'win' ? winCtx.winnerIds : [];
107+
const logs = game.log.map<ScrabbleLog>(log => JSON.parse(log));
108+
return logs
109+
.filterMap<ScrabbleDexEntry[]>(log => {
110+
if (log.action !== 'play') return;
111+
const words = Object.keys(log.ctx.words).map(toId).unique();
112+
return words.filterMap<ScrabbleDexEntry>(word => {
113+
if (!(word in pokedex)) return;
114+
const mon = pokedex[word];
115+
if (mon.num <= 0) return;
116+
return {
117+
...baseCtx,
118+
pokemon: word,
119+
pokemonName: mon.name,
120+
num: mon.num,
121+
by: log.turn,
122+
at: log.time,
123+
won: winners.includes(log.turn),
124+
};
125+
});
126+
})
127+
.flat();
128+
});
129+
}

src/web/api/scrabbledex/[user].ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { getScrabbleDex } from '@/database/games';
2+
import { IS_ENABLED } from '@/enabled';
3+
import { toId } from '@/tools';
4+
5+
import type { RequestHandler } from 'express';
6+
7+
// TODO: Make this a prototype
8+
function groupBy<Entry, Key extends string | number>(entries: Entry[], keyBy: (entry: Entry) => Key): Partial<Record<Key, Entry[]>> {
9+
return entries.reduce<Partial<Record<Key, Entry[]>>>((grouped, entry) => {
10+
const key = keyBy(entry);
11+
if (grouped[key]) grouped[key].push(entry);
12+
else grouped[key] = [entry];
13+
14+
return grouped;
15+
}, {});
16+
}
17+
18+
export const handler: RequestHandler = async (req, res) => {
19+
if (!IS_ENABLED.DB) throw new Error('Database is disabled.');
20+
const { user } = req.params as { user: string };
21+
const userId = toId(user);
22+
const allEntries = await getScrabbleDex();
23+
const results = allEntries!.filter(entry => entry.by === userId);
24+
const grouped = groupBy(
25+
results.map(res => res.pokemon.toUpperCase()),
26+
mon => mon.length
27+
);
28+
res.json(grouped);
29+
};

0 commit comments

Comments
 (0)