UNPKG

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
/* 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; });