scrabble-solver
Version:
Scrabble Solver 2 - Free, open-source, cross-platform, multi-language analysis tool for Scrabble, Scrabble Duel, Super Scrabble, Letter League, Literaki, and Kelimelik. Quickly find the top-scoring words using the given board and tiles.
114 lines (93 loc) • 2.99 kB
text/typescript
import { getConfig, hasConfig } from '@scrabble-solver/configs';
import { BLANK } from '@scrabble-solver/constants';
import { dictionaries } from '@scrabble-solver/dictionaries';
import { logger } from '@scrabble-solver/logger';
import { solve as solveScrabble } from '@scrabble-solver/solver';
import {
Board,
type Config,
type Game,
type Locale,
Tile,
isBoardJson,
isGame,
isLocale,
} from '@scrabble-solver/types';
import { type NextApiRequest, type NextApiResponse } from 'next';
import { getServerLoggingData, isBoardValid, isCharacterValid } from 'api';
import { isStringArray } from 'lib';
interface RequestData {
board: Board;
characters: string[];
config: Config;
game: Game;
locale: Locale;
}
const solve = async (request: NextApiRequest, response: NextApiResponse): Promise<void> => {
const meta = getServerLoggingData(request);
try {
const { board, characters, config, game, locale } = parseRequest(request);
logger.info('solve - request', {
meta,
payload: {
board: board.toString(),
configId: config.game,
boardBlanksCount: board.getBlanksCount(),
boardTilesCount: board.getTilesCount(),
characters: characters.join(''),
game,
locale,
},
});
const trie = await dictionaries.get(locale);
const tiles = characters.map((character) => new Tile({ character, isBlank: character === BLANK }));
const results = solveScrabble(trie, config, board, tiles);
response.status(200).send(results);
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
logger.error('solve - error', { error, meta });
response.status(500).send({ error: 'Server error', message });
}
};
const parseRequest = (request: NextApiRequest): RequestData => {
const { board: boardJson, characters, game, locale } = request.body;
if (!isLocale(locale)) {
throw new Error('Invalid "locale" parameter');
}
if (!isGame(game)) {
throw new Error('Invalid "game" parameter');
}
if (!isStringArray(characters) || characters.length === 0) {
throw new Error('Invalid "characters" parameter');
}
if (!hasConfig(game, locale)) {
throw new Error(`No game "${game}" in "${locale}"`);
}
const config = getConfig(game, locale);
for (const character of characters) {
if (!isCharacterValid(character)) {
throw new Error('Invalid "characters" parameter');
}
}
const blanksCount = characters.filter((character) => character === BLANK).length;
if (blanksCount > config.blanksCount) {
throw new Error('Too many blank tiles passed');
}
if (!isBoardJson(boardJson) || !isBoardValid(boardJson, config)) {
throw new Error('Invalid "board" parameter');
}
const board = Board.fromJson(boardJson);
return {
board,
characters,
config,
game,
locale,
};
};
export const config = {
api: {
responseLimit: '25mb',
},
};
export default solve;