@scrabble-solver/solver 2.8.7 → 2.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/build/fillPattern.d.ts +5 -0
  2. package/build/fillPattern.js +52 -0
  3. package/build/generateEndIndices.d.ts +3 -0
  4. package/build/generateEndIndices.js +9 -0
  5. package/build/generateHorizontalPatterns.d.ts +3 -0
  6. package/build/generateHorizontalPatterns.js +18 -0
  7. package/build/generatePattern.d.ts +8 -0
  8. package/build/generatePattern.js +22 -0
  9. package/build/generatePatterns.d.ts +3 -0
  10. package/build/generatePatterns.js +13 -0
  11. package/build/generateStartIndices.d.ts +3 -0
  12. package/build/generateStartIndices.js +9 -0
  13. package/build/generateVectors.d.ts +6 -0
  14. package/build/generateVectors.js +8 -0
  15. package/build/generateVerticalPatterns.d.ts +3 -0
  16. package/build/generateVerticalPatterns.js +18 -0
  17. package/build/getCellsScore.d.ts +3 -0
  18. package/build/getCellsScore.js +16 -0
  19. package/build/getPatternHash.d.ts +3 -0
  20. package/build/getPatternHash.js +13 -0
  21. package/build/getPatternScore.d.ts +3 -0
  22. package/build/getPatternScore.js +15 -0
  23. package/build/getUniquePatterns.js +5 -11
  24. package/build/index.d.ts +1 -1
  25. package/build/index.js +3 -3
  26. package/build/solve.d.ts +4 -0
  27. package/build/solve.js +23 -0
  28. package/package.json +6 -6
  29. package/src/fillPattern.test.ts +107 -0
  30. package/src/fillPattern.ts +70 -0
  31. package/src/generateEndIndices.test.ts +18 -0
  32. package/src/generateEndIndices.ts +10 -0
  33. package/src/generateHorizontalPatterns.test.ts +31 -0
  34. package/src/generateHorizontalPatterns.ts +17 -0
  35. package/src/generatePattern.ts +35 -0
  36. package/src/generatePatterns.ts +13 -0
  37. package/src/generateStartIndices.test.ts +14 -0
  38. package/src/generateStartIndices.ts +10 -0
  39. package/src/generateVectors.test.ts +12 -0
  40. package/src/generateVectors.ts +15 -0
  41. package/src/generateVerticalPatterns.test.ts +32 -0
  42. package/src/generateVerticalPatterns.ts +17 -0
  43. package/src/getCellsScore.ts +22 -0
  44. package/src/getPatternHash.test.ts +17 -0
  45. package/src/getPatternHash.ts +14 -0
  46. package/src/getPatternScore.test.ts +60 -0
  47. package/src/getPatternScore.ts +15 -0
  48. package/src/getUniquePatterns.ts +1 -10
  49. package/src/index.ts +1 -1
  50. package/src/{Solver.test.ts → solve.test.ts} +14 -13
  51. package/src/solve.ts +25 -0
  52. package/build/PatternsFiller.d.ts +0 -12
  53. package/build/PatternsFiller.js +0 -70
  54. package/build/PatternsGenerator.d.ts +0 -32
  55. package/build/PatternsGenerator.js +0 -66
  56. package/build/ScoresCalculator.d.ts +0 -17
  57. package/build/ScoresCalculator.js +0 -37
  58. package/build/Solver.d.ts +0 -10
  59. package/build/Solver.js +0 -30
  60. package/src/PatternsFiller.test.ts +0 -92
  61. package/src/PatternsFiller.ts +0 -91
  62. package/src/PatternsGenerator.test.ts +0 -89
  63. package/src/PatternsGenerator.ts +0 -103
  64. package/src/ScoresCalculator.test.ts +0 -70
  65. package/src/ScoresCalculator.ts +0 -52
  66. package/src/Solver.ts +0 -39
@@ -0,0 +1,5 @@
1
+ import { Trie } from '@kamilmielnik/trie';
2
+ import { Config, Pattern, Tile } from '@scrabble-solver/types';
3
+ declare const fillPattern: (trie: Trie, config: Config, pattern: Pattern, tiles: Tile[]) => Pattern[];
4
+ export declare const fillPatternRecursive: (results: Pattern[], trie: Trie, config: Config, pattern: Pattern, word: string, tiles: Tile[]) => void;
5
+ export default fillPattern;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fillPatternRecursive = void 0;
4
+ const constants_1 = require("@scrabble-solver/constants");
5
+ const types_1 = require("@scrabble-solver/types");
6
+ const fillPattern = (trie, config, pattern, tiles) => {
7
+ if (pattern.getEmptyCellsCount() > tiles.length) {
8
+ return [];
9
+ }
10
+ const results = [];
11
+ (0, exports.fillPatternRecursive)(results, trie, config, pattern, pattern.toString(), tiles);
12
+ return results;
13
+ };
14
+ const fillPatternRecursive = (
15
+ /** gets mutated when this function is called */
16
+ results, trie, config, pattern, word, tiles) => {
17
+ const indexOfFirstCellWithoutTile = pattern.getIndexOfFirstCellWithoutTile();
18
+ if (indexOfFirstCellWithoutTile === -1) {
19
+ if (trie.has(word) && pattern.getCollisions().every((collision) => trie.has(collision.toString()))) {
20
+ results.push(new types_1.FinalPattern(pattern.clone()));
21
+ }
22
+ return;
23
+ }
24
+ for (let index = 0; index < tiles.length; ++index) {
25
+ const tile = tiles[index];
26
+ const previousTile = pattern.cells[indexOfFirstCellWithoutTile].tile;
27
+ pattern.cells[indexOfFirstCellWithoutTile].tile = tile;
28
+ const indexOfNextCellWithoutTile = pattern.getIndexOfFirstCellWithoutTile();
29
+ const indexOfFirstEmptyLetter = word.indexOf(constants_1.EMPTY_CELL);
30
+ const prefix = word.substring(0, indexOfFirstEmptyLetter);
31
+ const suffix = word.substring(indexOfFirstEmptyLetter + 1);
32
+ const characters = tile.isBlank ? config.alphabet : [tile.character];
33
+ for (const character of characters) {
34
+ const newWordPrefix = prefix + character;
35
+ const newWord = newWordPrefix + suffix;
36
+ tile.character = character;
37
+ if (indexOfNextCellWithoutTile === -1) {
38
+ if (trie.has(newWord) && pattern.getCollisions().every((collision) => trie.has(collision.toString()))) {
39
+ results.push(new types_1.FinalPattern(pattern.clone()));
40
+ }
41
+ }
42
+ else if (trie.hasPrefix(newWordPrefix)) {
43
+ tiles.splice(index, 1);
44
+ (0, exports.fillPatternRecursive)(results, trie, config, pattern, newWord, tiles);
45
+ tiles.splice(index, 0, tile);
46
+ }
47
+ }
48
+ pattern.cells[indexOfFirstCellWithoutTile].tile = previousTile;
49
+ }
50
+ };
51
+ exports.fillPatternRecursive = fillPatternRecursive;
52
+ exports.default = fillPattern;
@@ -0,0 +1,3 @@
1
+ import { Cell } from '@scrabble-solver/types';
2
+ declare const generateEndIndices: (cells: Cell[], startIndex: number) => number[];
3
+ export default generateEndIndices;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const generateEndIndices = (cells, startIndex) => {
4
+ return Array(cells.length - startIndex - 1)
5
+ .fill(0)
6
+ .map((_, endIndex) => endIndex + startIndex + 1)
7
+ .filter((endIndex) => endIndex >= cells.length - 1 || !cells[endIndex + 1].hasTile());
8
+ };
9
+ exports.default = generateEndIndices;
@@ -0,0 +1,3 @@
1
+ import { Board, Config, Pattern } from '@scrabble-solver/types';
2
+ declare const generateHorizontalPatterns: (config: Config, board: Board) => Pattern[];
3
+ export default generateHorizontalPatterns;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const types_1 = require("@scrabble-solver/types");
7
+ const generatePattern_1 = __importDefault(require("./generatePattern"));
8
+ const generateVectors_1 = __importDefault(require("./generateVectors"));
9
+ const generateHorizontalPatterns = (config, board) => {
10
+ const getNthVector = (index) => board.getRow(index);
11
+ const vectorsCount = config.boardHeight;
12
+ const horizontalVectors = (0, generateVectors_1.default)({ getNthVector, vectorsCount });
13
+ const horizontalPatterns = horizontalVectors.flatMap((cells) => {
14
+ return (0, generatePattern_1.default)({ board, config, PatternModel: types_1.HorizontalPattern, cells });
15
+ });
16
+ return horizontalPatterns;
17
+ };
18
+ exports.default = generateHorizontalPatterns;
@@ -0,0 +1,8 @@
1
+ import { Board, Cell, Config, Pattern } from '@scrabble-solver/types';
2
+ declare const generatePattern: <P extends Pattern>({ board, cells, config, PatternModel, }: {
3
+ board: Board;
4
+ cells: Cell[];
5
+ config: Config;
6
+ PatternModel: new (board: Board, cells: Cell[]) => P;
7
+ }) => P[];
8
+ export default generatePattern;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const generateEndIndices_1 = __importDefault(require("./generateEndIndices"));
7
+ const generateStartIndices_1 = __importDefault(require("./generateStartIndices"));
8
+ const generatePattern = ({ board, cells, config, PatternModel, }) => {
9
+ const startIndices = (0, generateStartIndices_1.default)(cells);
10
+ return startIndices.flatMap((startIndex) => {
11
+ const endIndices = (0, generateEndIndices_1.default)(cells, startIndex);
12
+ const patterns = [];
13
+ for (const endIndex of endIndices) {
14
+ const pattern = new PatternModel(board, cells.slice(startIndex, endIndex + 1));
15
+ if (pattern.canBePlaced(config)) {
16
+ patterns.push(pattern);
17
+ }
18
+ }
19
+ return patterns;
20
+ });
21
+ };
22
+ exports.default = generatePattern;
@@ -0,0 +1,3 @@
1
+ import { Board, Config, Pattern } from '@scrabble-solver/types';
2
+ declare const generatePatterns: (config: Config, board: Board) => Pattern[];
3
+ export default generatePatterns;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const generateHorizontalPatterns_1 = __importDefault(require("./generateHorizontalPatterns"));
7
+ const generateVerticalPatterns_1 = __importDefault(require("./generateVerticalPatterns"));
8
+ const generatePatterns = (config, board) => {
9
+ const horizontalPatterns = (0, generateHorizontalPatterns_1.default)(config, board);
10
+ const verticalPatterns = (0, generateVerticalPatterns_1.default)(config, board);
11
+ return horizontalPatterns.concat(verticalPatterns);
12
+ };
13
+ exports.default = generatePatterns;
@@ -0,0 +1,3 @@
1
+ import { Cell } from '@scrabble-solver/types';
2
+ declare const generateStartIndices: (cells: Cell[]) => number[];
3
+ export default generateStartIndices;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const generateStartIndices = (cells) => {
4
+ return Array(cells.length - 1)
5
+ .fill(0)
6
+ .map((_, startIndex) => startIndex)
7
+ .filter((startIndex) => startIndex === 0 || !cells[startIndex - 1].hasTile());
8
+ };
9
+ exports.default = generateStartIndices;
@@ -0,0 +1,6 @@
1
+ import { Cell } from '@scrabble-solver/types';
2
+ declare const generateVectors: ({ getNthVector, vectorsCount, }: {
3
+ getNthVector: (index: number) => Cell[];
4
+ vectorsCount: number;
5
+ }) => Cell[][];
6
+ export default generateVectors;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const generateVectors = ({ getNthVector, vectorsCount, }) => {
4
+ return Array(vectorsCount)
5
+ .fill(0)
6
+ .map((_, index) => getNthVector(index));
7
+ };
8
+ exports.default = generateVectors;
@@ -0,0 +1,3 @@
1
+ import { Board, Config, Pattern } from '@scrabble-solver/types';
2
+ declare const generateVerticalPatterns: (config: Config, board: Board) => Pattern[];
3
+ export default generateVerticalPatterns;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const types_1 = require("@scrabble-solver/types");
7
+ const generatePattern_1 = __importDefault(require("./generatePattern"));
8
+ const generateVectors_1 = __importDefault(require("./generateVectors"));
9
+ const generateVerticalPatterns = (config, board) => {
10
+ const getNthVector = (index) => board.getColumn(index);
11
+ const vectorsCount = config.boardWidth;
12
+ const verticalVectors = (0, generateVectors_1.default)({ getNthVector, vectorsCount });
13
+ const verticalPatterns = verticalVectors.flatMap((cells) => {
14
+ return (0, generatePattern_1.default)({ board, config, PatternModel: types_1.VerticalPattern, cells });
15
+ });
16
+ return verticalPatterns;
17
+ };
18
+ exports.default = generateVerticalPatterns;
@@ -0,0 +1,3 @@
1
+ import { Cell, Config } from '@scrabble-solver/types';
2
+ declare const getCellsScore: (config: Config, cells: Cell[]) => number;
3
+ export default getCellsScore;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const constants_1 = require("@scrabble-solver/constants");
4
+ const getCellsScore = (config, cells) => {
5
+ const total = cells.reduce(({ multiplier, score }, cell) => {
6
+ const bonus = config.getCellBonus(cell);
7
+ const { characterMultiplier, wordMultiplier } = bonus && bonus.canApply(config, cell) ? bonus.value : constants_1.NO_BONUS;
8
+ const characterScore = cell.tile.isBlank ? config.blankScore : config.pointsMap[cell.tile.character];
9
+ return {
10
+ multiplier: multiplier * wordMultiplier,
11
+ score: score + characterScore * characterMultiplier,
12
+ };
13
+ }, { multiplier: 1, score: 0 });
14
+ return total.score * total.multiplier;
15
+ };
16
+ exports.default = getCellsScore;
@@ -0,0 +1,3 @@
1
+ import { Pattern } from '@scrabble-solver/types';
2
+ declare const getPatternHash: (pattern: Pattern) => string;
3
+ export default getPatternHash;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const getPatternHash = (pattern) => {
4
+ return pattern.cells
5
+ .map((cell) => {
6
+ const blank = cell.tile.isBlank ? '!' : '';
7
+ const tile = cell.tile.character + blank;
8
+ // eslint-disable-next-line prefer-template
9
+ return cell.x + ',' + cell.y + ',' + tile;
10
+ })
11
+ .join('-');
12
+ };
13
+ exports.default = getPatternHash;
@@ -0,0 +1,3 @@
1
+ import { Config, Pattern } from '@scrabble-solver/types';
2
+ declare const getPatternScore: (config: Config, pattern: Pattern) => number;
3
+ export default getPatternScore;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const getCellsScore_1 = __importDefault(require("./getCellsScore"));
7
+ const getPatternScore = (config, pattern) => {
8
+ const areAllTilesUsed = pattern.getEmptyCellsCount() === config.maximumCharactersCount;
9
+ const bonusScore = areAllTilesUsed ? config.allTilesBonusScore : 0;
10
+ const score = pattern
11
+ .getCollisions()
12
+ .reduce((sum, collision) => sum + (0, getCellsScore_1.default)(config, collision.cells), (0, getCellsScore_1.default)(config, pattern.cells));
13
+ return score + bonusScore;
14
+ };
15
+ exports.default = getPatternScore;
@@ -1,20 +1,14 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const getPatternHash = (pattern) => {
4
- return pattern.cells
5
- .map((cell) => {
6
- const blank = cell.tile.isBlank ? '!' : '';
7
- const tile = cell.tile.character + blank;
8
- // eslint-disable-next-line prefer-template
9
- return cell.x + ',' + cell.y + ',' + tile;
10
- })
11
- .join('-');
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
4
  };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const getPatternHash_1 = __importDefault(require("./getPatternHash"));
13
7
  const getUniquePatterns = (patterns) => {
14
8
  const hashes = new Set();
15
9
  const uniquePatterns = [];
16
10
  for (const pattern of patterns) {
17
- const hash = getPatternHash(pattern);
11
+ const hash = (0, getPatternHash_1.default)(pattern);
18
12
  if (!hashes.has(hash)) {
19
13
  hashes.add(hash);
20
14
  uniquePatterns.push(pattern);
package/build/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { default } from './Solver';
1
+ export { default as solve } from './solve';
package/build/index.js CHANGED
@@ -3,6 +3,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.default = void 0;
7
- var Solver_1 = require("./Solver");
8
- Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(Solver_1).default; } });
6
+ exports.solve = void 0;
7
+ var solve_1 = require("./solve");
8
+ Object.defineProperty(exports, "solve", { enumerable: true, get: function () { return __importDefault(solve_1).default; } });
@@ -0,0 +1,4 @@
1
+ import { Trie } from '@kamilmielnik/trie';
2
+ import { Board, Config, Result, Tile } from '@scrabble-solver/types';
3
+ declare const solve: (trie: Trie, config: Config, board: Board, tiles: Tile[]) => Result[];
4
+ export default solve;
package/build/solve.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const types_1 = require("@scrabble-solver/types");
7
+ const fillPattern_1 = __importDefault(require("./fillPattern"));
8
+ const generatePatterns_1 = __importDefault(require("./generatePatterns"));
9
+ const getPatternScore_1 = __importDefault(require("./getPatternScore"));
10
+ const getUniquePatterns_1 = __importDefault(require("./getUniquePatterns"));
11
+ const solve = (trie, config, board, tiles) => {
12
+ const patterns = (0, generatePatterns_1.default)(config, board);
13
+ const filledPatterns = patterns.flatMap((pattern) => (0, fillPattern_1.default)(trie, config, pattern, tiles));
14
+ const uniquePatterns = (0, getUniquePatterns_1.default)(filledPatterns);
15
+ const results = uniquePatterns.map((pattern, index) => new types_1.Result({
16
+ cells: pattern.cells,
17
+ collisions: pattern.getCollisions().map((collision) => collision.cells),
18
+ id: index,
19
+ points: (0, getPatternScore_1.default)(config, pattern),
20
+ }));
21
+ return results;
22
+ };
23
+ exports.default = solve;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/solver",
3
- "version": "2.8.7",
3
+ "version": "2.8.9",
4
4
  "description": "Scrabble Solver 2 - Solver",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -25,12 +25,12 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@kamilmielnik/trie": "^2.0.0",
28
- "@scrabble-solver/types": "^2.8.7"
28
+ "@scrabble-solver/types": "^2.8.9"
29
29
  },
30
30
  "devDependencies": {
31
- "@scrabble-solver/configs": "^2.8.7",
32
- "@scrabble-solver/constants": "^2.8.7",
33
- "@scrabble-solver/dictionaries": "^2.8.7"
31
+ "@scrabble-solver/configs": "^2.8.9",
32
+ "@scrabble-solver/constants": "^2.8.9",
33
+ "@scrabble-solver/dictionaries": "^2.8.9"
34
34
  },
35
- "gitHead": "879bf9b68f27fa3d530d0582fd8fbbcd5c8b150b"
35
+ "gitHead": "8557cc2c5214e6689a5c59373b228b28f5dd8ed4"
36
36
  }
@@ -0,0 +1,107 @@
1
+ import { Trie } from '@kamilmielnik/trie';
2
+ import { literaki } from '@scrabble-solver/configs';
3
+ import { dictionaries } from '@scrabble-solver/dictionaries';
4
+ import { Board, Cell, FinalPattern, Locale, Pattern, Tile, VerticalPattern } from '@scrabble-solver/types';
5
+
6
+ import fillPattern, { fillPatternRecursive } from './fillPattern';
7
+
8
+ const board = Board.fromStringArray([' t ', 'do ', ' ']);
9
+ const locale = Locale.PL_PL;
10
+ const config = literaki[locale];
11
+
12
+ describe('fillPattern', () => {
13
+ let trie: Trie | undefined;
14
+
15
+ beforeAll(() => {
16
+ return dictionaries.get(locale).then((loadedTrie) => {
17
+ trie = loadedTrie;
18
+ });
19
+ });
20
+
21
+ it('fills patterns', () => {
22
+ const pattern = new VerticalPattern(board, [
23
+ new Cell({ x: 0, y: 0 }),
24
+ new Cell({ x: 0, y: 1, tile: new Tile({ character: 'd', isBlank: false }) }),
25
+ new Cell({ x: 0, y: 2 }),
26
+ ]);
27
+ const tiles = [
28
+ new Tile({ character: 'o', isBlank: false }),
29
+ new Tile({ character: 'a', isBlank: false }),
30
+ new Tile({ character: 'd', isBlank: false }),
31
+ ];
32
+ const filledPatterns = fillPattern(trie!, config, pattern, tiles);
33
+
34
+ expect(filledPatterns.length).toBe(1);
35
+ });
36
+
37
+ it('fills patterns with blanks', () => {
38
+ const pattern = new VerticalPattern(board, [board.rows[0][1], board.rows[1][1], board.rows[2][1]]);
39
+ const tiles = [new Tile({ character: ' ', isBlank: true }), new Tile({ character: 'ó', isBlank: false })];
40
+ const filledPatterns = fillPattern(trie!, config, pattern, tiles);
41
+
42
+ expect(filledPatterns.map(String)).toEqual([
43
+ 'toć',
44
+ 'tog',
45
+ 'toi',
46
+ 'tok',
47
+ 'tom',
48
+ 'ton',
49
+ 'toń',
50
+ 'top',
51
+ 'tor',
52
+ 'tos',
53
+ 'toy',
54
+ 'toż',
55
+ ]);
56
+ });
57
+
58
+ it('does not modify filled patterns', () => {
59
+ const pattern = new FinalPattern(
60
+ new VerticalPattern(board, [
61
+ new Cell({ x: 0, y: 0, isEmpty: false, tile: new Tile({ character: 'o', isBlank: false }) }),
62
+ new Cell({ x: 0, y: 1, isEmpty: false, tile: new Tile({ character: 'k', isBlank: false }) }),
63
+ new Cell({ x: 0, y: 2, isEmpty: false, tile: new Tile({ character: 'o', isBlank: false }) }),
64
+ ]),
65
+ );
66
+ const tiles = [new Tile({ character: 'ń', isBlank: false })];
67
+ const filledPatterns = fillPattern(trie!, config, pattern, tiles);
68
+
69
+ expect(filledPatterns.length).toBe(1);
70
+ expect(filledPatterns[0]).toEqual(pattern);
71
+ });
72
+
73
+ it('does not accept non-placeable filled patterns', () => {
74
+ const pattern = new VerticalPattern(board, [
75
+ new Cell({ x: 0, y: 0, isEmpty: false, tile: new Tile({ character: 'd', isBlank: false }) }),
76
+ new Cell({ x: 0, y: 1, isEmpty: false, tile: new Tile({ character: 'd', isBlank: false }) }),
77
+ new Cell({ x: 0, y: 2, isEmpty: false, tile: new Tile({ character: 'd', isBlank: false }) }),
78
+ ]);
79
+ const tiles = [new Tile({ character: 'ń', isBlank: false })];
80
+ const filledPatterns = fillPattern(trie!, config, pattern, tiles);
81
+
82
+ expect(filledPatterns.length).toBe(0);
83
+ });
84
+ });
85
+
86
+ describe('fillPatternRecursive', () => {
87
+ let trie: Trie | undefined;
88
+
89
+ beforeAll(() => {
90
+ return dictionaries.get(locale).then((loadedTrie) => {
91
+ trie = loadedTrie;
92
+ });
93
+ });
94
+
95
+ it('checks collisions', () => {
96
+ const pattern = new VerticalPattern(board, [
97
+ new Cell({ x: 2, y: 0, isEmpty: true, tile: new Tile({ character: 'k', isBlank: false }) }),
98
+ new Cell({ x: 2, y: 1, isEmpty: true, tile: new Tile({ character: 'o', isBlank: false }) }),
99
+ new Cell({ x: 2, y: 2, isEmpty: true, tile: new Tile({ character: 't', isBlank: false }) }),
100
+ ]);
101
+ const results: Pattern[] = [];
102
+ const tiles = [new Tile({ character: 'ń', isBlank: false })];
103
+ fillPatternRecursive(results, trie!, config, pattern, pattern.toString(), tiles);
104
+
105
+ expect(results.length).toBe(0);
106
+ });
107
+ });
@@ -0,0 +1,70 @@
1
+ /* eslint-disable max-params, max-statements */
2
+ import { Trie } from '@kamilmielnik/trie';
3
+ import { EMPTY_CELL } from '@scrabble-solver/constants';
4
+ import { Config, FinalPattern, Pattern, Tile } from '@scrabble-solver/types';
5
+
6
+ const fillPattern = (trie: Trie, config: Config, pattern: Pattern, tiles: Tile[]): Pattern[] => {
7
+ if (pattern.getEmptyCellsCount() > tiles.length) {
8
+ return [];
9
+ }
10
+
11
+ const results: Pattern[] = [];
12
+
13
+ fillPatternRecursive(results, trie, config, pattern, pattern.toString(), tiles);
14
+
15
+ return results;
16
+ };
17
+
18
+ export const fillPatternRecursive = (
19
+ /** gets mutated when this function is called */
20
+ results: Pattern[],
21
+ trie: Trie,
22
+ config: Config,
23
+ pattern: Pattern,
24
+ word: string,
25
+ tiles: Tile[],
26
+ ): void => {
27
+ const indexOfFirstCellWithoutTile = pattern.getIndexOfFirstCellWithoutTile();
28
+
29
+ if (indexOfFirstCellWithoutTile === -1) {
30
+ if (trie.has(word) && pattern.getCollisions().every((collision) => trie.has(collision.toString()))) {
31
+ results.push(new FinalPattern(pattern.clone()));
32
+ }
33
+
34
+ return;
35
+ }
36
+
37
+ for (let index = 0; index < tiles.length; ++index) {
38
+ const tile = tiles[index];
39
+ const previousTile = pattern.cells[indexOfFirstCellWithoutTile].tile;
40
+
41
+ pattern.cells[indexOfFirstCellWithoutTile].tile = tile;
42
+
43
+ const indexOfNextCellWithoutTile = pattern.getIndexOfFirstCellWithoutTile();
44
+ const indexOfFirstEmptyLetter = word.indexOf(EMPTY_CELL);
45
+ const prefix = word.substring(0, indexOfFirstEmptyLetter);
46
+ const suffix = word.substring(indexOfFirstEmptyLetter + 1);
47
+ const characters = tile.isBlank ? config.alphabet : [tile.character];
48
+
49
+ for (const character of characters) {
50
+ const newWordPrefix = prefix + character;
51
+ const newWord = newWordPrefix + suffix;
52
+
53
+ tile.character = character;
54
+
55
+ if (indexOfNextCellWithoutTile === -1) {
56
+ if (trie.has(newWord) && pattern.getCollisions().every((collision) => trie.has(collision.toString()))) {
57
+ results.push(new FinalPattern(pattern.clone()));
58
+ }
59
+ } else if (trie.hasPrefix(newWordPrefix)) {
60
+ tiles.splice(index, 1);
61
+ fillPatternRecursive(results, trie, config, pattern, newWord, tiles);
62
+ tiles.splice(index, 0, tile);
63
+ }
64
+ }
65
+
66
+ pattern.cells[indexOfFirstCellWithoutTile].tile = previousTile;
67
+ }
68
+ };
69
+
70
+ export default fillPattern;
@@ -0,0 +1,18 @@
1
+ import { Cell } from '@scrabble-solver/types';
2
+
3
+ import generateEndIndices from './generateEndIndices';
4
+
5
+ describe('generateEndIndices', () => {
6
+ const emptyCell: Cell = { hasTile: () => false } as Cell;
7
+ const filledCell: Cell = { hasTile: () => true } as Cell;
8
+
9
+ it('finds indices where a new word can end - XXOOOX', () => {
10
+ const cells = [filledCell, filledCell, emptyCell, emptyCell, emptyCell, filledCell];
11
+ expect(generateEndIndices(cells, 3)).toEqual([5]);
12
+ });
13
+
14
+ it('finds indices where a new word can end - XXOOOXO', () => {
15
+ const cells = [filledCell, filledCell, emptyCell, emptyCell, emptyCell, filledCell, emptyCell];
16
+ expect(generateEndIndices(cells, 3)).toEqual([5, 6]);
17
+ });
18
+ });
@@ -0,0 +1,10 @@
1
+ import { Cell } from '@scrabble-solver/types';
2
+
3
+ const generateEndIndices = (cells: Cell[], startIndex: number): number[] => {
4
+ return Array(cells.length - startIndex - 1)
5
+ .fill(0)
6
+ .map((_, endIndex) => endIndex + startIndex + 1)
7
+ .filter((endIndex) => endIndex >= cells.length - 1 || !cells[endIndex + 1].hasTile());
8
+ };
9
+
10
+ export default generateEndIndices;
@@ -0,0 +1,31 @@
1
+ import { EMPTY_CELL } from '@scrabble-solver/constants';
2
+ import { Board, Config } from '@scrabble-solver/types';
3
+
4
+ import generateHorizontalPatterns from './generateHorizontalPatterns';
5
+
6
+ const board = Board.fromStringArray([' t ', 'do ', ' ']);
7
+
8
+ const config = {
9
+ boardHeight: 3,
10
+ boardWidth: 3,
11
+ maximumCharactersCount: 7,
12
+ } as Config;
13
+
14
+ describe('generateHorizontalPatterns', () => {
15
+ it('generates some horizontal patterns', () => {
16
+ expect(generateHorizontalPatterns(config, board).length).toBeGreaterThan(0);
17
+ });
18
+
19
+ it('generates proper horizontal patterns', () => {
20
+ const horizontal = generateHorizontalPatterns(config, board);
21
+ expect(horizontal.map(({ cells }) => cells.map(String))).toEqual([
22
+ [EMPTY_CELL, 't'],
23
+ [EMPTY_CELL, 't', EMPTY_CELL],
24
+ ['t', EMPTY_CELL],
25
+ ['d', 'o', EMPTY_CELL],
26
+ [EMPTY_CELL, EMPTY_CELL],
27
+ [EMPTY_CELL, EMPTY_CELL, EMPTY_CELL],
28
+ [EMPTY_CELL, EMPTY_CELL],
29
+ ]);
30
+ });
31
+ });
@@ -0,0 +1,17 @@
1
+ import { Board, Config, HorizontalPattern, Pattern } from '@scrabble-solver/types';
2
+
3
+ import generatePattern from './generatePattern';
4
+ import generateVectors from './generateVectors';
5
+
6
+ const generateHorizontalPatterns = (config: Config, board: Board): Pattern[] => {
7
+ const getNthVector = (index: number) => board.getRow(index);
8
+ const vectorsCount = config.boardHeight;
9
+ const horizontalVectors = generateVectors({ getNthVector, vectorsCount });
10
+ const horizontalPatterns = horizontalVectors.flatMap((cells) => {
11
+ return generatePattern({ board, config, PatternModel: HorizontalPattern, cells });
12
+ });
13
+
14
+ return horizontalPatterns;
15
+ };
16
+
17
+ export default generateHorizontalPatterns;