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.
256 lines (183 loc) • 8.84 kB
text/typescript
/* eslint-disable max-lines */
import { createSelector } from '@reduxjs/toolkit';
import { getConfig } from '@scrabble-solver/configs';
import { BLANK } from '@scrabble-solver/constants';
import { Cell, Config, isError, Tile } from '@scrabble-solver/types';
import { i18n, LOCALE_FEATURES } from 'i18n';
import {
createRegExp,
findCell,
getRemainingTiles,
getRemainingTilesGroups,
groupResults,
resultMatchesCellFilter,
sortGroupedResults,
unorderedArraysEqual,
} from 'lib';
import { Point, ResultColumnId, Translations } from 'types';
import { RootState } from './types';
const selectCell = (_: unknown, cell: Cell): Cell => cell;
const selectPoint = (_: unknown, point: Point): Point => point;
const selectResultIndex = (_: unknown, index: number): number => index;
const selectCharacter = (_: unknown, character: string | null): string | null => character;
const selectTile = (_: unknown, tile: Tile | null): Tile | null => tile;
const selectBoardRoot = (state: RootState): RootState['board'] => state.board;
const selectDictionaryRoot = (state: RootState): RootState['dictionary'] => state.dictionary;
const selectCellFilterRoot = (state: RootState): RootState['cellFilter'] => state.cellFilter;
const selectRackRoot = (state: RootState): RootState['rack'] => state.rack;
const selectResultsRoot = (state: RootState): RootState['results'] => state.results;
const selectSettingsRoot = (state: RootState): RootState['settings'] => state.settings;
const selectSolveRoot = (state: RootState): RootState['solve'] => state.solve;
const selectVerifyRoot = (state: RootState): RootState['verify'] => state.verify;
export const selectDictionary = selectDictionaryRoot;
export const selectDictionaryError = createSelector([selectDictionaryRoot], (dictionary) => {
return isError(dictionary.error) ? dictionary.error : undefined;
});
export const selectLocale = createSelector([selectSettingsRoot], (settings) => settings.locale);
export const selectAutoGroupTiles = createSelector([selectSettingsRoot], (settings) => settings.autoGroupTiles);
export const selectLocaleAutoGroupTiles = createSelector([selectLocale, selectSettingsRoot], (locale, settings) => {
if (LOCALE_FEATURES[locale].direction === 'ltr' || settings.autoGroupTiles === null) {
return settings.autoGroupTiles;
}
return settings.autoGroupTiles === 'left' ? 'right' : 'left';
});
export const selectBoard = selectBoardRoot;
export const selectInputMode = createSelector([selectSettingsRoot], (settings) => settings.inputMode);
export const selectShowCoordinates = createSelector([selectSettingsRoot], (settings) => settings.showCoordinates);
export const selectGame = createSelector([selectSettingsRoot], (settings) => settings.game);
export const selectConfig = createSelector([selectGame, selectLocale], getConfig);
export const selectFilteredCells = selectCellFilterRoot;
export const selectCellFilter = createSelector([selectFilteredCells, selectPoint], (cellFilter, { x, y }) => {
return cellFilter.find((cell) => cell.x === x && cell.y === y);
});
export const selectCellIsValid = createSelector([selectConfig, selectCell], (config, cell) => {
if (!cell.hasTile()) {
return true;
}
return config.tiles.some((tile) => tile.character === cell.tile.character);
});
export const selectResultsRaw = createSelector([selectResultsRoot], (results) => results.results);
export const selectResultsQuery = createSelector([selectResultsRoot], (results) => results.query);
export const selectResultsSort = createSelector([selectResultsRoot], (results) => results.sort);
export const selectGroupedResults = createSelector(
[selectResultsRaw, selectResultsQuery, selectFilteredCells],
groupResults,
);
export const selectGroupedSortedResults = createSelector(
[selectGroupedResults, selectResultsSort, selectLocale, selectShowCoordinates],
sortGroupedResults,
);
export const selectResults = createSelector([selectGroupedSortedResults], (groupedResults) => {
return groupedResults ? [...groupedResults.matching, ...groupedResults.other] : groupedResults;
});
export const selectIsResultMatching = createSelector(
[selectResults, selectResultsQuery, selectFilteredCells, selectResultIndex],
(results, query, cellFilter, index) => {
if (!results) {
return false;
}
const result = results[index];
const regExp = createRegExp(query);
if (!regExp.test(result.word)) {
return false;
}
return resultMatchesCellFilter(result, cellFilter);
},
);
export const selectResultCandidate = createSelector([selectResultsRoot], (results) => results.candidate);
export const selectResultCandidateCells = createSelector(
[selectResultCandidate],
(resultCandidate): Cell[] => resultCandidate?.cells || [],
);
export const selectResultCandidateTiles = createSelector(
[selectResultCandidate],
(resultCandidate): Tile[] => resultCandidate?.tiles || [],
);
export const selectRowsWithCandidate = createSelector([selectBoardRoot, selectResultCandidateCells], (board, cells) => {
return board.rows.map((row: Cell[], y: number) => row.map((cell: Cell, x: number) => findCell(cells, x, y) || cell));
});
export const selectCellBonus = createSelector([selectConfig, selectCell], (config: Config, cell: Cell) => {
return config.getCellBonus(cell);
});
export const selectCharacterPoints = createSelector(
[selectConfig, selectCharacter],
(config: Config, character: string | null) => {
return config.getCharacterPoints(character);
},
);
export const selectCharacterIsValid = createSelector(
[selectConfig, selectCharacter],
(config: Config, character: string | null) => {
if (character === null || character === BLANK) {
return true;
}
return config.tiles.some((tile) => tile.character === character);
},
);
export const selectTilePoints = createSelector([selectConfig, selectTile], (config: Config, tile: Tile | null) => {
return config.getTilePoints(tile);
});
export const selectTranslations = createSelector([selectLocale], (locale) => i18n[locale]);
export const selectTranslation = createSelector(
[selectTranslations, selectLocale, (_: unknown, id: keyof Translations) => id],
(translations, locale, id): string => {
const translation = translations[id];
if (typeof translation === 'undefined') {
throw new Error(`Untranslated key "${id}" in locale "${locale}"`);
}
return translation;
},
);
export const selectRack = selectRackRoot;
export const selectCharacters = createSelector(selectRackRoot, (rack) => rack.filter((tile) => tile !== null));
export const selectLastSolvedParameters = createSelector([selectSolveRoot], (solve) => solve.lastSolvedParameters);
export const selectIsLoading = createSelector([selectSolveRoot], (solve) => solve.isLoading);
export const selectSolveError = createSelector([selectSolveRoot], (solve) => {
return isError(solve.error) ? solve.error : undefined;
});
export const selectHaveCharactersChanged = createSelector(
[selectLastSolvedParameters, selectCharacters, selectLocale],
(lastSolvedParameters, characters, locale) => {
return !unorderedArraysEqual(lastSolvedParameters.characters, characters, locale);
},
);
export const selectHasBoardChanged = createSelector(
[selectLastSolvedParameters, selectBoardRoot],
(lastSolvedParameters, board) => !lastSolvedParameters.board.equals(board),
);
export const selectAreResultsOutdated = createSelector(
[selectHasBoardChanged, selectHaveCharactersChanged],
(hasBoardChanged, haveCharactersChanged) => hasBoardChanged || haveCharactersChanged,
);
export const selectRemainingTiles = createSelector(
[selectConfig, selectBoardRoot, selectCharacters, selectLocale],
getRemainingTiles,
);
export const selectHasOverusedTiles = createSelector([selectRemainingTiles], (remainingTiles) => {
return remainingTiles.some(({ count = 0, usedCount }) => usedCount > count);
});
export const selectRemainingTilesGroups = createSelector([selectRemainingTiles], getRemainingTilesGroups);
export const selectVerify = selectVerifyRoot;
export const selectHasInvalidWords = createSelector([selectVerify], ({ invalidWords }) => {
return invalidWords.length > 0;
});
export const selectColumns = createSelector([selectLocale, selectShowCoordinates], (locale, showCoordinates) => {
const { consonants, vowels } = LOCALE_FEATURES[locale];
const columns: ResultColumnId[] = [
ResultColumnId.Word,
ResultColumnId.TilesCount,
ResultColumnId.BlanksCount,
ResultColumnId.WordsCount,
ResultColumnId.Points,
];
if (showCoordinates !== 'hidden') {
columns.push(ResultColumnId.Coordinates);
}
if (vowels) {
columns.push(ResultColumnId.VowelsCount);
}
if (consonants) {
columns.push(ResultColumnId.ConsonantsCount);
}
return columns;
});