import { BONUS_WORD } from '@scrabble-solver/constants'; import { type Board, type BoardWord, type Bonus, type Result } from '@scrabble-solver/types'; import { COLOR_BONUS_CHARACTER, COLOR_BONUS_CHARACTER_MULTIPLIER, COLOR_BONUS_WORD } from '@/parameters'; import { type Direction, type Point } from '@/types'; export const createGrid = (width: number, height: number, getInitialValue: (x: number, y: number) => T): T[][] => { return Array.from({ length: height }).map((_row, y) => { return Array.from({ length: width }).map((_cell, x) => { return getInitialValue(x, y); }); }); }; export const getBonusColor = (bonus: Bonus): string => { if (bonus.type === BONUS_WORD) { return COLOR_BONUS_WORD[bonus.multiplier]; } if (bonus.score) { return COLOR_BONUS_CHARACTER[bonus.score]; } return COLOR_BONUS_CHARACTER_MULTIPLIER[bonus.multiplier]; }; export const getPositionInGrid = (grid: T[][], constraint: (value: T) => boolean): Point | undefined => { for (let y = 0; y < grid.length; ++y) { for (let x = 0; x < grid[0].length; ++x) { if (constraint(grid[y][x])) { return { x, y }; } } } return undefined; }; export const getReachableCells = (board: Board, charactersCount: number): boolean[][] => { const boardHeight = board.rowsCount; const boardWidth = board.columnsCount; const reachable = createGrid(boardWidth, boardHeight, () => false); if (charactersCount === 0) { return reachable; } const isBoardEmpty = board.isEmpty(); const center = board.center; const isAnchor = (x: number, y: number, playDirection: Direction): boolean => { if (isBoardEmpty) { return x === center.x && y === center.y; } if (playDirection === 'horizontal') { const above = y > 0 && !board.rows[y - 1][x].isEmpty; const below = y < boardHeight - 1 && !board.rows[y + 1][x].isEmpty; return above || below; } const left = x > 0 && !board.rows[y][x - 1].isEmpty; const right = x < boardWidth - 1 && !board.rows[y][x + 1].isEmpty; return left || right; }; const isReachableInDirection = (x: number, y: number, direction: Direction): boolean => { const isHorizontal = direction === 'horizontal'; const length = isHorizontal ? boardWidth : boardHeight; const startOffset = isHorizontal ? x : y; const cellAt = (offset: number) => (isHorizontal ? board.rows[y][offset] : board.rows[offset][x]); if (isAnchor(x, y, direction)) { return true; } for (const step of [-1, 1] as const) { let newTiles = 1; for (let offset = startOffset + step; offset >= 0 && offset < length; offset += step) { const cell = cellAt(offset); if (cell.hasTile()) { if (newTiles <= charactersCount) { return true; } break; } newTiles += 1; if (newTiles > charactersCount) { break; } if (isAnchor(cell.x, cell.y, direction)) { return true; } } } return false; }; for (let y = 0; y < boardHeight; ++y) { for (let x = 0; x < boardWidth; ++x) { if (board.rows[y][x].hasTile()) { reachable[y][x] = true; continue; } if (isReachableInDirection(x, y, 'horizontal') || isReachableInDirection(x, y, 'vertical')) { reachable[y][x] = true; } } } return reachable; }; export const getReachableCellsFromResults = (board: Board, results: Result[]): boolean[][] => { const reachable = createGrid(board.columnsCount, board.rowsCount, (x, y) => board.rows[y][x].hasTile()); for (const result of results) { for (const cell of result.cells) { reachable[cell.y][cell.x] = true; } } return reachable; }; export const getWordCells = (board: Board, word: BoardWord): boolean[][] => { const wordCells = createGrid(board.columnsCount, board.rowsCount, () => false); const isHorizontal = word.direction === 'horizontal'; let { x, y } = word; while (x < board.columnsCount && y < board.rowsCount && board.rows[y][x].hasTile()) { wordCells[y][x] = true; if (isHorizontal) { x += 1; } else { y += 1; } } return wordCells; };