scrabble-solver
Version:
Scrabble Solver 2 - Free, open-source, cross-platform, multi-language analysis tool for Scrabble, Scrabble Duel, Super Scrabble, Letter League, Crossplay, Literaki, and Kelimelik. Quickly find the top-scoring words using the given board and tiles.
92 lines (76 loc) • 3.04 kB
text/typescript
import { type Trie } from '@kamilmielnik/trie';
import { getConfig } from '@scrabble-solver/configs';
import { BLANK } from '@scrabble-solver/constants';
import { solve } from '@scrabble-solver/solver';
import { Board, Tile } from '@scrabble-solver/types';
import { registerRoute } from 'workbox-routing';
import { type SolveRequestPayload } from '@/types';
import { average } from './average';
import { revalidateDictionary } from './dictionaries';
import { getTrie } from './getTrie';
const headers = {
'Content-Type': 'application/json; charset=utf-8',
};
const MIN_MEASUREMENTS = 5;
const localDurations: number[] = [];
const serverDurations: number[] = [];
export const routeSolveRequests = () => {
registerRoute(
({ url }) => url.origin === location.origin && url.pathname === '/api/solve',
async ({ request }) => {
const requestJson: SolveRequestPayload = await request.clone().json();
const { board, characters, game, locale } = requestJson;
const solveLocal = async (trie: Trie): Promise<Response> => {
const config = getConfig(game, locale);
const tiles = characters.map((character: string) => new Tile({ character, isBlank: character === BLANK }));
return new Promise((resolve) => {
const resultsJson = solve(trie, config, Board.fromJson(board), tiles);
const responseJson = JSON.stringify(resultsJson);
resolve(new Response(responseJson, { headers }));
});
};
const solveServer = () => fetch(request);
const trie = await getTrie(locale);
if (trie && typeof isSlowDevice() === 'undefined') {
const response = await Promise.race([
(async () => {
const start = Date.now();
const result = solveLocal(trie);
localDurations.push(Date.now() - start);
return result;
})(),
(async () => {
const start = Date.now();
const result = await solveServer();
serverDurations.push(Date.now() - start);
return result;
})(),
]);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
revalidateDictionary(locale);
return response;
}
const handleSolve = trie && !isSlowDevice() ? () => solveLocal(trie) : () => solveServer();
const response = await handleSolve();
// eslint-disable-next-line @typescript-eslint/no-floating-promises
revalidateDictionary(locale);
return response;
},
'POST',
);
};
const isSlowDevice = (): boolean | undefined => {
if (localDurations.length < MIN_MEASUREMENTS || serverDurations.length < MIN_MEASUREMENTS) {
return undefined;
}
const count = Math.min(localDurations.length, serverDurations.length);
const local = localDurations
.slice(0, count)
.sort((a, b) => a - b)
.slice(1, -1);
const server = localDurations
.slice(0, count)
.sort((a, b) => a - b)
.slice(1, -1);
return average(local) > average(server);
};