scrabble-solver 2.8.8 → 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 (190) hide show
  1. package/.eslintrc.prettier-only.js +1 -1
  2. package/.prettierignore +3 -0
  3. package/.prettierrc +3 -3
  4. package/babel.config.js +1 -1
  5. package/lerna.json +1 -1
  6. package/package.json +6 -6
  7. package/packages/configs/package.json +3 -3
  8. package/packages/constants/package.json +1 -1
  9. package/packages/dictionaries/package-lock.json +2 -2
  10. package/packages/dictionaries/package.json +4 -4
  11. package/packages/logger/package-lock.json +2 -2
  12. package/packages/logger/package.json +1 -1
  13. package/packages/scrabble-solver/.next/BUILD_ID +1 -1
  14. package/packages/scrabble-solver/.next/build-manifest.json +10 -10
  15. package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
  16. package/packages/scrabble-solver/.next/cache/eslint/.cache_8dgz12 +1 -1
  17. package/packages/scrabble-solver/.next/cache/next-server.js.nft.json +1 -1
  18. package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
  19. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
  20. package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
  21. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
  22. package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
  23. package/packages/scrabble-solver/.next/prerender-manifest.json +1 -1
  24. package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
  25. package/packages/scrabble-solver/.next/server/chunks/413.js +179 -44
  26. package/packages/scrabble-solver/.next/server/chunks/44.js +802 -0
  27. package/packages/scrabble-solver/.next/server/chunks/515.js +359 -95
  28. package/packages/scrabble-solver/.next/server/chunks/911.js +53 -23
  29. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  30. package/packages/scrabble-solver/.next/server/pages/404.html +2 -2
  31. package/packages/scrabble-solver/.next/server/pages/404.js.nft.json +1 -1
  32. package/packages/scrabble-solver/.next/server/pages/500.html +2 -2
  33. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  34. package/packages/scrabble-solver/.next/server/pages/_error.js.nft.json +1 -1
  35. package/packages/scrabble-solver/.next/server/pages/api/solve.js +193 -927
  36. package/packages/scrabble-solver/.next/server/pages/api/solve.js.nft.json +1 -1
  37. package/packages/scrabble-solver/.next/server/pages/api/verify.js +217 -0
  38. package/packages/scrabble-solver/.next/server/pages/api/verify.js.nft.json +1 -0
  39. package/packages/scrabble-solver/.next/server/pages/index.html +3 -3
  40. package/packages/scrabble-solver/.next/server/pages/index.js +7 -1
  41. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  42. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  43. package/packages/scrabble-solver/.next/server/pages-manifest.json +1 -0
  44. package/packages/scrabble-solver/.next/static/chunks/317-a33dd38e9b9a17ed.js +1 -0
  45. package/packages/scrabble-solver/.next/static/chunks/pages/{404-8eb3ba4f0ba17e08.js → 404-90c624da3c83fd17.js} +1 -1
  46. package/packages/scrabble-solver/.next/static/chunks/pages/_app-f8f360878e1c2aff.js +1 -0
  47. package/packages/scrabble-solver/.next/static/chunks/pages/index-ecea697d3e5d8a6f.js +1 -0
  48. package/packages/scrabble-solver/.next/static/css/64dc2ce1811912f1.css +1 -0
  49. package/packages/scrabble-solver/.next/static/css/{cdbc9e0afcff5473.css → ad2a08918868cad8.css} +1 -1
  50. package/packages/scrabble-solver/.next/static/yCxjzzYpw5JjJE53PO_s6/_buildManifest.js +1 -0
  51. package/packages/scrabble-solver/.next/static/{z3J3qmq1nazbDv_ENIkCo → yCxjzzYpw5JjJE53PO_s6}/_ssgManifest.js +0 -0
  52. package/packages/scrabble-solver/.next/trace +42 -41
  53. package/packages/scrabble-solver/package-lock.json +2 -2
  54. package/packages/scrabble-solver/package.json +8 -8
  55. package/packages/scrabble-solver/src/components/Badge/Badge.module.scss +13 -0
  56. package/packages/scrabble-solver/src/components/Badge/Badge.tsx +15 -0
  57. package/packages/scrabble-solver/src/components/Badge/index.ts +1 -0
  58. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.tsx +33 -14
  59. package/packages/scrabble-solver/src/components/RemainingTiles/RemainingTiles.module.scss +8 -7
  60. package/packages/scrabble-solver/src/components/RemainingTiles/RemainingTiles.tsx +13 -4
  61. package/packages/scrabble-solver/src/components/Sidebar/Sidebar.tsx +2 -2
  62. package/packages/scrabble-solver/src/components/Sidebar/components/Section/Section.module.scss +0 -1
  63. package/packages/scrabble-solver/src/components/Sidebar/components/Section/Section.tsx +1 -1
  64. package/packages/scrabble-solver/src/components/SquareButton/SquareButton.module.scss +5 -0
  65. package/packages/scrabble-solver/src/components/Words/Words.module.scss +35 -0
  66. package/packages/scrabble-solver/src/components/Words/Words.tsx +57 -0
  67. package/packages/scrabble-solver/src/components/Words/index.ts +1 -0
  68. package/packages/scrabble-solver/src/components/index.ts +2 -0
  69. package/packages/scrabble-solver/src/i18n/de.json +4 -1
  70. package/packages/scrabble-solver/src/i18n/en.json +4 -1
  71. package/packages/scrabble-solver/src/i18n/es.json +4 -1
  72. package/packages/scrabble-solver/src/i18n/fr.json +4 -1
  73. package/packages/scrabble-solver/src/i18n/pl.json +4 -1
  74. package/packages/scrabble-solver/src/icons/BookHalf.svg +4 -0
  75. package/packages/scrabble-solver/src/icons/Check.svg +4 -0
  76. package/packages/scrabble-solver/src/icons/Cross.svg +2 -2
  77. package/packages/scrabble-solver/src/icons/CrossFill.svg +4 -0
  78. package/packages/scrabble-solver/src/icons/index.ts +3 -0
  79. package/packages/scrabble-solver/src/pages/api/solve.ts +2 -3
  80. package/packages/scrabble-solver/src/pages/api/verify.ts +71 -0
  81. package/packages/scrabble-solver/src/pages/index.tsx +5 -0
  82. package/packages/scrabble-solver/src/sdk/index.ts +1 -0
  83. package/packages/scrabble-solver/src/sdk/verify.ts +24 -0
  84. package/packages/scrabble-solver/src/state/rootReducer.ts +2 -0
  85. package/packages/scrabble-solver/src/state/sagas.ts +33 -6
  86. package/packages/scrabble-solver/src/state/selectors.ts +8 -0
  87. package/packages/scrabble-solver/src/state/slices/index.ts +2 -0
  88. package/packages/scrabble-solver/src/state/slices/verifyInitialState.ts +12 -0
  89. package/packages/scrabble-solver/src/state/slices/verifySlice.ts +31 -0
  90. package/packages/scrabble-solver/src/styles/variables.scss +2 -1
  91. package/packages/scrabble-solver/src/types/index.ts +4 -1
  92. package/packages/solver/build/fillPattern.d.ts +5 -0
  93. package/packages/solver/build/fillPattern.js +52 -0
  94. package/packages/solver/build/generateEndIndices.d.ts +3 -0
  95. package/packages/solver/build/generateEndIndices.js +9 -0
  96. package/packages/solver/build/generateHorizontalPatterns.d.ts +3 -0
  97. package/packages/solver/build/generateHorizontalPatterns.js +18 -0
  98. package/packages/solver/build/generatePattern.d.ts +8 -0
  99. package/packages/solver/build/generatePattern.js +22 -0
  100. package/packages/solver/build/generatePatterns.d.ts +3 -0
  101. package/packages/solver/build/generatePatterns.js +13 -0
  102. package/packages/solver/build/generateStartIndices.d.ts +3 -0
  103. package/packages/solver/build/generateStartIndices.js +9 -0
  104. package/packages/solver/build/generateVectors.d.ts +6 -0
  105. package/packages/solver/build/generateVectors.js +8 -0
  106. package/packages/solver/build/generateVerticalPatterns.d.ts +3 -0
  107. package/packages/solver/build/generateVerticalPatterns.js +18 -0
  108. package/packages/solver/build/getCellsScore.d.ts +3 -0
  109. package/packages/solver/build/getCellsScore.js +16 -0
  110. package/packages/solver/build/getPatternHash.d.ts +3 -0
  111. package/packages/solver/build/getPatternHash.js +13 -0
  112. package/packages/solver/build/getPatternScore.d.ts +3 -0
  113. package/packages/solver/build/getPatternScore.js +15 -0
  114. package/packages/solver/build/getUniquePatterns.js +5 -11
  115. package/packages/solver/build/index.d.ts +1 -1
  116. package/packages/solver/build/index.js +3 -3
  117. package/packages/solver/build/solve.d.ts +4 -0
  118. package/packages/solver/build/solve.js +23 -0
  119. package/packages/solver/package-lock.json +2 -2
  120. package/packages/solver/package.json +5 -5
  121. package/packages/solver/src/fillPattern.test.ts +107 -0
  122. package/packages/solver/src/fillPattern.ts +70 -0
  123. package/packages/solver/src/generateEndIndices.test.ts +18 -0
  124. package/packages/solver/src/generateEndIndices.ts +10 -0
  125. package/packages/solver/src/generateHorizontalPatterns.test.ts +31 -0
  126. package/packages/solver/src/generateHorizontalPatterns.ts +17 -0
  127. package/packages/solver/src/generatePattern.ts +35 -0
  128. package/packages/solver/src/generatePatterns.ts +13 -0
  129. package/packages/solver/src/generateStartIndices.test.ts +14 -0
  130. package/packages/solver/src/generateStartIndices.ts +10 -0
  131. package/packages/solver/src/generateVectors.test.ts +12 -0
  132. package/packages/solver/src/generateVectors.ts +15 -0
  133. package/packages/solver/src/generateVerticalPatterns.test.ts +32 -0
  134. package/packages/solver/src/generateVerticalPatterns.ts +17 -0
  135. package/packages/solver/src/getCellsScore.ts +22 -0
  136. package/packages/solver/src/getPatternHash.test.ts +17 -0
  137. package/packages/solver/src/getPatternHash.ts +14 -0
  138. package/packages/solver/src/getPatternScore.test.ts +60 -0
  139. package/packages/solver/src/getPatternScore.ts +15 -0
  140. package/packages/solver/src/getUniquePatterns.ts +1 -10
  141. package/packages/solver/src/index.ts +1 -1
  142. package/packages/solver/src/{Solver.test.ts → solve.test.ts} +14 -13
  143. package/packages/solver/src/solve.ts +25 -0
  144. package/packages/types/build/Board.d.ts +1 -0
  145. package/packages/types/build/Board.js +17 -1
  146. package/packages/types/build/Config.js +1 -1
  147. package/packages/types/build/FinalPattern.d.ts +7 -0
  148. package/packages/types/build/FinalPattern.js +16 -0
  149. package/packages/types/build/HorizontalPattern.js +2 -5
  150. package/packages/types/build/Pattern.d.ts +1 -4
  151. package/packages/types/build/Pattern.js +2 -5
  152. package/packages/types/build/Result.js +3 -5
  153. package/packages/types/build/Tile.d.ts +2 -2
  154. package/packages/types/build/VerticalPattern.js +2 -5
  155. package/packages/types/build/index.d.ts +1 -0
  156. package/packages/types/build/index.js +3 -1
  157. package/packages/types/package.json +2 -2
  158. package/packages/types/src/Board.ts +23 -1
  159. package/packages/types/src/Config.ts +1 -1
  160. package/packages/types/src/FinalPattern.ts +16 -0
  161. package/packages/types/src/HorizontalPattern.ts +5 -5
  162. package/packages/types/src/Pattern.ts +5 -5
  163. package/packages/types/src/Result.ts +3 -5
  164. package/packages/types/src/Tile.ts +2 -2
  165. package/packages/types/src/VerticalPattern.ts +5 -5
  166. package/packages/types/src/index.ts +1 -0
  167. package/packages/word-definitions/package-lock.json +2 -2
  168. package/packages/word-definitions/package.json +2 -2
  169. package/packages/word-lists/package-lock.json +2 -2
  170. package/packages/word-lists/package.json +2 -2
  171. package/packages/scrabble-solver/.next/static/chunks/615-d258f6c528c18622.js +0 -1
  172. package/packages/scrabble-solver/.next/static/chunks/pages/_app-4a663fd3d5ca4524.js +0 -1
  173. package/packages/scrabble-solver/.next/static/chunks/pages/index-1a9826d740cc8830.js +0 -1
  174. package/packages/scrabble-solver/.next/static/css/180c6c26317ac90f.css +0 -1
  175. package/packages/scrabble-solver/.next/static/z3J3qmq1nazbDv_ENIkCo/_buildManifest.js +0 -1
  176. package/packages/solver/build/PatternsFiller.d.ts +0 -12
  177. package/packages/solver/build/PatternsFiller.js +0 -70
  178. package/packages/solver/build/PatternsGenerator.d.ts +0 -32
  179. package/packages/solver/build/PatternsGenerator.js +0 -66
  180. package/packages/solver/build/ScoresCalculator.d.ts +0 -17
  181. package/packages/solver/build/ScoresCalculator.js +0 -37
  182. package/packages/solver/build/Solver.d.ts +0 -10
  183. package/packages/solver/build/Solver.js +0 -30
  184. package/packages/solver/src/PatternsFiller.test.ts +0 -92
  185. package/packages/solver/src/PatternsFiller.ts +0 -91
  186. package/packages/solver/src/PatternsGenerator.test.ts +0 -89
  187. package/packages/solver/src/PatternsGenerator.ts +0 -103
  188. package/packages/solver/src/ScoresCalculator.test.ts +0 -70
  189. package/packages/solver/src/ScoresCalculator.ts +0 -52
  190. package/packages/solver/src/Solver.ts +0 -39
@@ -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;
@@ -0,0 +1,35 @@
1
+ import { Board, Cell, Config, Pattern } from '@scrabble-solver/types';
2
+
3
+ import generateEndIndices from './generateEndIndices';
4
+ import generateStartIndices from './generateStartIndices';
5
+
6
+ const generatePattern = <P extends Pattern>({
7
+ board,
8
+ cells,
9
+ config,
10
+ PatternModel,
11
+ }: {
12
+ board: Board;
13
+ cells: Cell[];
14
+ config: Config;
15
+ PatternModel: new (board: Board, cells: Cell[]) => P;
16
+ }): P[] => {
17
+ const startIndices = generateStartIndices(cells);
18
+
19
+ return startIndices.flatMap((startIndex) => {
20
+ const endIndices = generateEndIndices(cells, startIndex);
21
+ const patterns: P[] = [];
22
+
23
+ for (const endIndex of endIndices) {
24
+ const pattern = new PatternModel(board, cells.slice(startIndex, endIndex + 1));
25
+
26
+ if (pattern.canBePlaced(config)) {
27
+ patterns.push(pattern);
28
+ }
29
+ }
30
+
31
+ return patterns;
32
+ });
33
+ };
34
+
35
+ export default generatePattern;
@@ -0,0 +1,13 @@
1
+ import { Board, Config, Pattern } from '@scrabble-solver/types';
2
+
3
+ import generateHorizontalPatterns from './generateHorizontalPatterns';
4
+ import generateVerticalPatterns from './generateVerticalPatterns';
5
+
6
+ const generatePatterns = (config: Config, board: Board): Pattern[] => {
7
+ const horizontalPatterns = generateHorizontalPatterns(config, board);
8
+ const verticalPatterns = generateVerticalPatterns(config, board);
9
+
10
+ return horizontalPatterns.concat(verticalPatterns);
11
+ };
12
+
13
+ export default generatePatterns;
@@ -0,0 +1,14 @@
1
+ import { Cell } from '@scrabble-solver/types';
2
+
3
+ import generateStartIndices from './generateStartIndices';
4
+
5
+ describe('generateStartIndices', () => {
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 start', () => {
10
+ const cells = [filledCell, filledCell, emptyCell, emptyCell, emptyCell, filledCell];
11
+ const startIndices = generateStartIndices(cells);
12
+ expect(startIndices).toEqual([0, 3, 4]);
13
+ });
14
+ });
@@ -0,0 +1,10 @@
1
+ import { Cell } from '@scrabble-solver/types';
2
+
3
+ const generateStartIndices = (cells: Cell[]): number[] => {
4
+ return Array(cells.length - 1)
5
+ .fill(0)
6
+ .map((_, startIndex) => startIndex)
7
+ .filter((startIndex) => startIndex === 0 || !cells[startIndex - 1].hasTile());
8
+ };
9
+
10
+ export default generateStartIndices;
@@ -0,0 +1,12 @@
1
+ import generateVectors from './generateVectors';
2
+
3
+ describe('generateVectors', () => {
4
+ it('generates given number of vectors', () => {
5
+ const vectors = generateVectors({
6
+ getNthVector: () => [],
7
+ vectorsCount: 3,
8
+ });
9
+ expect(vectors.length).toBe(3);
10
+ expect(vectors).toEqual([[], [], []]);
11
+ });
12
+ });
@@ -0,0 +1,15 @@
1
+ import { Cell } from '@scrabble-solver/types';
2
+
3
+ const generateVectors = ({
4
+ getNthVector,
5
+ vectorsCount,
6
+ }: {
7
+ getNthVector: (index: number) => Cell[];
8
+ vectorsCount: number;
9
+ }): Cell[][] => {
10
+ return Array(vectorsCount)
11
+ .fill(0)
12
+ .map((_, index) => getNthVector(index));
13
+ };
14
+
15
+ export default generateVectors;
@@ -0,0 +1,32 @@
1
+ import { EMPTY_CELL } from '@scrabble-solver/constants';
2
+ import { Board, Config } from '@scrabble-solver/types';
3
+
4
+ import generateVerticalPatterns from './generateVerticalPatterns';
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('generateVerticalPatterns', () => {
15
+ it('generates some vertical patterns', () => {
16
+ expect(generateVerticalPatterns(config, board).length).toBeGreaterThan(0);
17
+ });
18
+
19
+ it('generates proper vertical patterns', () => {
20
+ const vertical = generateVerticalPatterns(config, board);
21
+
22
+ expect(vertical.map(({ cells }) => cells.map(String))).toEqual([
23
+ [EMPTY_CELL, 'd'],
24
+ [EMPTY_CELL, 'd', EMPTY_CELL],
25
+ ['d', EMPTY_CELL],
26
+ ['t', 'o', EMPTY_CELL],
27
+ [EMPTY_CELL, EMPTY_CELL],
28
+ [EMPTY_CELL, EMPTY_CELL, EMPTY_CELL],
29
+ [EMPTY_CELL, EMPTY_CELL],
30
+ ]);
31
+ });
32
+ });
@@ -0,0 +1,17 @@
1
+ import { Board, Config, Pattern, VerticalPattern } from '@scrabble-solver/types';
2
+
3
+ import generatePattern from './generatePattern';
4
+ import generateVectors from './generateVectors';
5
+
6
+ const generateVerticalPatterns = (config: Config, board: Board): Pattern[] => {
7
+ const getNthVector = (index: number) => board.getColumn(index);
8
+ const vectorsCount = config.boardWidth;
9
+ const verticalVectors = generateVectors({ getNthVector, vectorsCount });
10
+ const verticalPatterns = verticalVectors.flatMap((cells) => {
11
+ return generatePattern({ board, config, PatternModel: VerticalPattern, cells });
12
+ });
13
+
14
+ return verticalPatterns;
15
+ };
16
+
17
+ export default generateVerticalPatterns;
@@ -0,0 +1,22 @@
1
+ import { NO_BONUS } from '@scrabble-solver/constants';
2
+ import { Cell, Config } from '@scrabble-solver/types';
3
+
4
+ const getCellsScore = (config: Config, cells: Cell[]): number => {
5
+ const total = cells.reduce(
6
+ ({ multiplier, score }, cell: Cell): { multiplier: number; score: number } => {
7
+ const bonus = config.getCellBonus(cell);
8
+ const { characterMultiplier, wordMultiplier } = bonus && bonus.canApply(config, cell) ? bonus.value : NO_BONUS;
9
+ const characterScore = cell.tile.isBlank ? config.blankScore : config.pointsMap[cell.tile.character];
10
+
11
+ return {
12
+ multiplier: multiplier * wordMultiplier,
13
+ score: score + characterScore * characterMultiplier,
14
+ };
15
+ },
16
+ { multiplier: 1, score: 0 },
17
+ );
18
+
19
+ return total.score * total.multiplier;
20
+ };
21
+
22
+ export default getCellsScore;
@@ -0,0 +1,17 @@
1
+ import { Board, Cell, Pattern, Tile } from '@scrabble-solver/types';
2
+
3
+ import getPatternHash from './getPatternHash';
4
+
5
+ describe('getPatternHash', () => {
6
+ const board = Board.fromStringArray([' t ', 'do ', ' ']);
7
+
8
+ it('hashes blanks', () => {
9
+ const pattern = new Pattern(board, [
10
+ new Cell({ x: 0, y: 0, tile: new Tile({ character: 'a', isBlank: false }) }),
11
+ new Cell({ x: 0, y: 1, tile: new Tile({ character: 'b', isBlank: true }) }),
12
+ new Cell({ x: 0, y: 2, tile: new Tile({ character: 'c', isBlank: false }) }),
13
+ ]);
14
+
15
+ expect(getPatternHash(pattern)).toBe('0,0,a-0,1,b!-0,2,c');
16
+ });
17
+ });
@@ -0,0 +1,14 @@
1
+ import { Pattern } from '@scrabble-solver/types';
2
+
3
+ const getPatternHash = (pattern: Pattern): string => {
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
+
14
+ export default getPatternHash;
@@ -0,0 +1,60 @@
1
+ import { literaki } from '@scrabble-solver/configs';
2
+ import { Board, Cell, HorizontalPattern, Locale, Pattern, Tile, VerticalPattern } from '@scrabble-solver/types';
3
+
4
+ import getPatternScore from './getPatternScore';
5
+
6
+ const config = literaki[Locale.PL_PL];
7
+ const board = Board.fromStringArray([
8
+ ' kasom ',
9
+ ' i ',
10
+ ' napiją ',
11
+ ' w i ',
12
+ ' krabim ',
13
+ ' z ',
14
+ ' ę eh ',
15
+ 'f s srać ',
16
+ 'i z t s ',
17
+ 'knebel e ',
18
+ 'a ew warcz',
19
+ 'ł żyło wody ',
20
+ 'o pecyj chu',
21
+ ' y ',
22
+ ' t ',
23
+ ]);
24
+
25
+ describe('getPatternScore', () => {
26
+ it('gives proper score without collisions', () => {
27
+ const pattern = new Pattern(board, [
28
+ new Cell({ x: 0, y: 0, tile: new Tile({ character: 'ź' }), isEmpty: true }),
29
+ new Cell({ x: 1, y: 0, tile: new Tile({ character: 'a' }), isEmpty: true }),
30
+ new Cell({ x: 2, y: 0, tile: new Tile({ character: 'a' }), isEmpty: true }),
31
+ new Cell({ x: 3, y: 0, tile: new Tile({ character: 'a' }), isEmpty: true }),
32
+ new Cell({ x: 4, y: 0, tile: new Tile({ character: 'a' }), isEmpty: true }),
33
+ new Cell({ x: 5, y: 0, tile: new Tile({ character: 'a' }), isEmpty: true }),
34
+ new Cell({ x: 6, y: 0, tile: new Tile({ character: 'a' }), isEmpty: true }),
35
+ new Cell({ x: 7, y: 0, tile: new Tile({ character: 'ź' }), isEmpty: false }),
36
+ ]);
37
+
38
+ expect(getPatternScore(config, pattern)).toBe(128);
39
+ });
40
+
41
+ it('gives proper score with collisions', () => {
42
+ const pattern = new VerticalPattern(board, [
43
+ new Cell({ x: 2, y: 11, tile: new Tile({ character: 'l' }), isEmpty: true }),
44
+ new Cell({ x: 2, y: 12, tile: new Tile({ character: 'i' }), isEmpty: true }),
45
+ new Cell({ x: 2, y: 13, tile: new Tile({ character: 'n' }), isEmpty: true }),
46
+ new Cell({ x: 2, y: 14, tile: new Tile({ character: 'o' }), isEmpty: true }),
47
+ ]);
48
+
49
+ expect(getPatternScore(config, pattern)).toBe(44);
50
+ });
51
+
52
+ it('gives proper score for blank', () => {
53
+ const pattern = new HorizontalPattern(board, [
54
+ new Cell({ x: 13, y: 14, tile: new Tile({ character: 'o', isBlank: true }), isEmpty: true }),
55
+ new Cell({ x: 12, y: 14, tile: new Tile({ character: 't' }), isEmpty: false }),
56
+ ]);
57
+
58
+ expect(getPatternScore(config, pattern)).toBe(2);
59
+ });
60
+ });
@@ -0,0 +1,15 @@
1
+ import { Config, Pattern } from '@scrabble-solver/types';
2
+
3
+ import getCellsScore from './getCellsScore';
4
+
5
+ const getPatternScore = (config: Config, pattern: Pattern) => {
6
+ const areAllTilesUsed = pattern.getEmptyCellsCount() === config.maximumCharactersCount;
7
+ const bonusScore = areAllTilesUsed ? config.allTilesBonusScore : 0;
8
+ const score = pattern
9
+ .getCollisions()
10
+ .reduce((sum, collision) => sum + getCellsScore(config, collision.cells), getCellsScore(config, pattern.cells));
11
+
12
+ return score + bonusScore;
13
+ };
14
+
15
+ export default getPatternScore;
@@ -1,15 +1,6 @@
1
1
  import { Pattern } from '@scrabble-solver/types';
2
2
 
3
- const getPatternHash = (pattern: Pattern): string => {
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
- };
3
+ import getPatternHash from './getPatternHash';
13
4
 
14
5
  const getUniquePatterns = (patterns: Pattern[]): Pattern[] => {
15
6
  const hashes = new Set<string>();
@@ -1 +1 @@
1
- export { default } from './Solver';
1
+ export { default as solve } from './solve';
@@ -1,8 +1,9 @@
1
+ import { Trie } from '@kamilmielnik/trie';
1
2
  import { literaki, scrabble } from '@scrabble-solver/configs';
2
3
  import { dictionaries } from '@scrabble-solver/dictionaries';
3
4
  import { Board, Locale, Result, Tile } from '@scrabble-solver/types';
4
5
 
5
- import Solver from './Solver';
6
+ import solve from './solve';
6
7
 
7
8
  const generateTiles = (characters: string[]): Tile[] => {
8
9
  return characters.map((character) => new Tile({ character, isBlank: false }));
@@ -15,14 +16,14 @@ const getBestResult = ([firstResult, ...results]: Result[]): Result => {
15
16
  );
16
17
  };
17
18
 
18
- describe('Solver - PL', () => {
19
+ describe('solve - PL', () => {
19
20
  const locale = Locale.PL_PL;
20
21
  const config = literaki[locale];
21
- let solver: Solver | undefined;
22
+ let trie: Trie | undefined;
22
23
 
23
24
  beforeAll(() => {
24
- return dictionaries.get(locale).then((trie) => {
25
- solver = new Solver(config, trie);
25
+ return dictionaries.get(locale).then((loadedTrie) => {
26
+ trie = loadedTrie;
26
27
  });
27
28
  });
28
29
 
@@ -45,7 +46,7 @@ describe('Solver - PL', () => {
45
46
  ' ',
46
47
  ]);
47
48
  const tiles = generateTiles(['l', 'i', 'n', 'o']);
48
- const results = solver!.solve(board, tiles);
49
+ const results = solve(trie!, config, board, tiles);
49
50
  expect(results.length).toBe(61);
50
51
  });
51
52
 
@@ -68,7 +69,7 @@ describe('Solver - PL', () => {
68
69
  ' ',
69
70
  ]);
70
71
  const tiles = generateTiles(['a', 'a', 'ą', 'r', 't', 'w', 'z']);
71
- const results = solver!.solve(board, tiles);
72
+ const results = solve(trie!, config, board, tiles);
72
73
  const bestResult = getBestResult(results);
73
74
  expect(bestResult.word).toBe('zmartwychwstałą');
74
75
  expect(bestResult.points).toBe(682);
@@ -93,21 +94,21 @@ describe('Solver - PL', () => {
93
94
  'ar ń m ',
94
95
  ]);
95
96
  const tiles = generateTiles(['ą', 'c', 'h', 't', 'w', 'w', 'z']);
96
- const results = solver!.solve(board, tiles);
97
+ const results = solve(trie!, config, board, tiles);
97
98
  const bestResult = getBestResult(results);
98
99
  expect(bestResult.word).toBe('zmartwychwstałą');
99
100
  expect(bestResult.points).toBe(1157);
100
101
  });
101
102
  });
102
103
 
103
- describe('Solver - ES', () => {
104
+ describe('solve - ES', () => {
104
105
  const locale = Locale.ES_ES;
105
106
  const config = scrabble[locale];
106
- let solver: Solver | undefined;
107
+ let trie: Trie | undefined;
107
108
 
108
109
  beforeAll(() => {
109
- return dictionaries.get(locale).then((trie) => {
110
- solver = new Solver(config, trie);
110
+ return dictionaries.get(locale).then((loadedTrie) => {
111
+ trie = loadedTrie;
111
112
  });
112
113
  });
113
114
 
@@ -130,7 +131,7 @@ describe('Solver - ES', () => {
130
131
  ' ',
131
132
  ]);
132
133
  const tiles = generateTiles(['ll', 'a', 'n', 'a']);
133
- const results = solver!.solve(board, tiles);
134
+ const results = solve(trie!, config, board, tiles);
134
135
  const bestResult = getBestResult(results);
135
136
  expect(results.length).toBe(24);
136
137
  expect(bestResult.points).toBe(22);