scrabble-solver 2.16.7 → 2.18.0

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 (589) hide show
  1. package/.github/workflows/bunx.yml +13 -10
  2. package/.github/workflows/e2e.yml +7 -9
  3. package/.github/workflows/npx.yml +13 -10
  4. package/.nx/workspace-data/d/server-process.json +2 -2
  5. package/.nx/workspace-data/file-map.json +796 -816
  6. package/.nx/workspace-data/lockfile-dependencies.hash +1 -1
  7. package/.nx/workspace-data/lockfile-nodes.hash +1 -1
  8. package/.nx/workspace-data/nx_files.nxt +0 -0
  9. package/.nx/workspace-data/parsed-lock-file.dependencies.json +218 -1298
  10. package/.nx/workspace-data/parsed-lock-file.nodes.json +770 -2669
  11. package/.nx/workspace-data/project-graph.json +1083 -4104
  12. package/.nx/workspace-data/source-maps.json +128 -0
  13. package/.oxlintrc.json +13 -10
  14. package/CLAUDE.md +36 -28
  15. package/README.md +17 -1
  16. package/bun.lock +233 -528
  17. package/e2e/app.spec.ts +124 -0
  18. package/e2e/bugs/112-character-bonus.spec.ts +25 -0
  19. package/e2e/bugs/129-esc-sidebar.spec.ts +17 -0
  20. package/e2e/bugs/2-q-tile-in-english.spec.ts +19 -0
  21. package/e2e/bugs/3-x-tile-in-polish.spec.ts +19 -0
  22. package/e2e/bugs/30-click-inside-radio-icon.spec.ts +25 -0
  23. package/e2e/bugs/333-disabled-radio-cursor.spec.ts +18 -0
  24. package/e2e/bugs/342-results-sorting.spec.ts +30 -0
  25. package/e2e/bugs/352-dictionary-race-condition.spec.ts +27 -0
  26. package/e2e/bugs/363-dictionary-input-unique-words.spec.ts +27 -0
  27. package/e2e/bugs/380-transliteration-when-pasting.spec.ts +55 -0
  28. package/e2e/bugs/398-digraphs-in-created-words.spec.ts +23 -0
  29. package/e2e/bugs/401-board-state-on-game-change.spec.ts +24 -0
  30. package/e2e/features/198-highlight-unreachable-cells.spec.ts +85 -0
  31. package/e2e/features/91-hover-remaining-tile-highlights.spec.ts +76 -0
  32. package/e2e/features/hidden-coordinates-migration.spec.ts +27 -0
  33. package/e2e/features/solver-paths.spec.ts +45 -0
  34. package/e2e/lib/actions.ts +119 -0
  35. package/e2e/lib/selectors.ts +71 -0
  36. package/e2e/tsconfig.json +10 -0
  37. package/lerna.json +1 -1
  38. package/package.json +16 -18
  39. package/packages/configs/package.json +3 -3
  40. package/packages/constants/package.json +1 -1
  41. package/packages/dictionaries/README.md +1 -1
  42. package/packages/dictionaries/build/Dictionaries.d.ts +2 -2
  43. package/packages/dictionaries/build/Dictionaries.js +6 -6
  44. package/packages/dictionaries/build/lib/DiskCache.d.ts +11 -4
  45. package/packages/dictionaries/build/lib/DiskCache.js +27 -10
  46. package/packages/dictionaries/build/lib/LayeredCache.d.ts +4 -4
  47. package/packages/dictionaries/build/lib/LayeredCache.js +3 -3
  48. package/packages/dictionaries/build/lib/MemoryCache.d.ts +4 -4
  49. package/packages/dictionaries/build/lib/MemoryCache.js +2 -2
  50. package/packages/dictionaries/build/lib/createCacheTimestampComparator.d.ts +2 -2
  51. package/packages/dictionaries/build/lib/downloadDictionary.d.ts +2 -2
  52. package/packages/dictionaries/build/lib/downloadDictionary.js +4 -3
  53. package/packages/dictionaries/build/lib/getDictionaryFilepath.d.ts +3 -1
  54. package/packages/dictionaries/build/lib/getDictionaryFilepath.js +8 -3
  55. package/packages/dictionaries/package.json +7 -6
  56. package/packages/dictionaries/src/Dictionaries.ts +12 -12
  57. package/packages/dictionaries/src/lib/DiskCache.test.ts +85 -0
  58. package/packages/dictionaries/src/lib/DiskCache.ts +34 -14
  59. package/packages/dictionaries/src/lib/LayeredCache.ts +7 -7
  60. package/packages/dictionaries/src/lib/MemoryCache.ts +6 -6
  61. package/packages/dictionaries/src/lib/createCacheTimestampComparator.ts +2 -2
  62. package/packages/dictionaries/src/lib/downloadDictionary.ts +5 -4
  63. package/packages/dictionaries/src/lib/getDictionaryFilepath.ts +7 -2
  64. package/packages/gaddag/build/Gaddag.d.ts +39 -0
  65. package/packages/gaddag/build/Gaddag.js +128 -0
  66. package/packages/gaddag/build/buildGaddag.d.ts +10 -0
  67. package/packages/gaddag/build/buildGaddag.js +360 -0
  68. package/packages/gaddag/build/constants.d.ts +12 -0
  69. package/packages/gaddag/build/constants.js +15 -0
  70. package/packages/gaddag/build/index.d.ts +3 -0
  71. package/packages/gaddag/build/index.js +13 -0
  72. package/packages/logger/package.json +1 -1
  73. package/packages/scrabble-solver/.next/BUILD_ID +1 -1
  74. package/packages/scrabble-solver/.next/build-manifest.json +22 -20
  75. package/packages/scrabble-solver/.next/cache/.previewinfo +1 -1
  76. package/packages/scrabble-solver/.next/cache/.rscinfo +1 -1
  77. package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
  78. package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
  79. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
  80. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack.old +0 -0
  81. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
  82. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  83. package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
  84. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
  85. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack.old +0 -0
  86. package/packages/scrabble-solver/.next/diagnostics/framework.json +1 -1
  87. package/packages/scrabble-solver/.next/dynamic-css-manifest.json +8 -1
  88. package/packages/scrabble-solver/.next/next-minimal-server.js.nft.json +1 -1
  89. package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
  90. package/packages/scrabble-solver/.next/prerender-manifest.json +4 -4
  91. package/packages/scrabble-solver/.next/react-loadable-manifest.json +176 -1
  92. package/packages/scrabble-solver/.next/required-server-files.js +3 -2
  93. package/packages/scrabble-solver/.next/required-server-files.json +3 -2
  94. package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
  95. package/packages/scrabble-solver/.next/server/chunks/30.js +1 -1
  96. package/packages/scrabble-solver/.next/server/chunks/309.js +1 -0
  97. package/packages/scrabble-solver/.next/server/chunks/316.js +1 -0
  98. package/packages/scrabble-solver/.next/server/chunks/344.js +1 -0
  99. package/packages/scrabble-solver/.next/server/chunks/371.js +1 -0
  100. package/packages/scrabble-solver/.next/server/chunks/392.js +1 -1
  101. package/packages/scrabble-solver/.next/server/chunks/465.js +1 -0
  102. package/packages/scrabble-solver/.next/server/chunks/546.js +1 -0
  103. package/packages/scrabble-solver/.next/server/chunks/623.js +1 -0
  104. package/packages/scrabble-solver/.next/server/chunks/624.js +1 -0
  105. package/packages/scrabble-solver/.next/server/chunks/641.js +1 -0
  106. package/packages/scrabble-solver/.next/server/chunks/671.js +1 -1
  107. package/packages/scrabble-solver/.next/server/chunks/692.js +1 -0
  108. package/packages/scrabble-solver/.next/server/chunks/705.js +1 -0
  109. package/packages/scrabble-solver/.next/server/chunks/712.js +1 -1
  110. package/packages/scrabble-solver/.next/server/chunks/846.js +1 -0
  111. package/packages/scrabble-solver/.next/server/chunks/853.js +15 -0
  112. package/packages/scrabble-solver/.next/server/chunks/886.js +1 -0
  113. package/packages/scrabble-solver/.next/server/chunks/916.js +1 -0
  114. package/packages/scrabble-solver/.next/server/chunks/953.js +1 -0
  115. package/packages/scrabble-solver/.next/server/chunks/961.js +1 -0
  116. package/packages/scrabble-solver/.next/server/chunks/968.js +1 -0
  117. package/packages/scrabble-solver/.next/server/dynamic-css-manifest.js +1 -1
  118. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  119. package/packages/scrabble-solver/.next/server/middleware-react-loadable-manifest.js +1 -1
  120. package/packages/scrabble-solver/.next/server/pages/404.html +28 -1
  121. package/packages/scrabble-solver/.next/server/pages/500.html +28 -1
  122. package/packages/scrabble-solver/.next/server/pages/_app.js +1 -1
  123. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  124. package/packages/scrabble-solver/.next/server/pages/_document.js +28 -1
  125. package/packages/scrabble-solver/.next/server/pages/_document.js.nft.json +1 -1
  126. package/packages/scrabble-solver/.next/server/pages/_error.js +28 -1
  127. package/packages/scrabble-solver/.next/server/pages/_error.js.nft.json +1 -1
  128. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js +1 -1
  129. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  130. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js +1 -1
  131. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  132. package/packages/scrabble-solver/.next/server/pages/api/solve.js +1 -1
  133. package/packages/scrabble-solver/.next/server/pages/api/solve.js.nft.json +1 -1
  134. package/packages/scrabble-solver/.next/server/pages/api/verify.js +1 -1
  135. package/packages/scrabble-solver/.next/server/pages/api/verify.js.nft.json +1 -1
  136. package/packages/scrabble-solver/.next/server/pages/api/visit.js.nft.json +1 -1
  137. package/packages/scrabble-solver/.next/server/pages/index.html +28 -1
  138. package/packages/scrabble-solver/.next/server/pages/index.js +30 -1
  139. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  140. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  141. package/packages/scrabble-solver/.next/server/pages/not-found.html +28 -1
  142. package/packages/scrabble-solver/.next/server/pages/not-found.js.nft.json +1 -1
  143. package/packages/scrabble-solver/.next/server/pages-manifest.json +1 -1
  144. package/packages/scrabble-solver/.next/server/webpack-api-runtime.js +1 -1
  145. package/packages/scrabble-solver/.next/server/webpack-runtime.js +1 -1
  146. package/packages/scrabble-solver/.next/static/FL82obwzFHRzvV6z76I-U/_buildManifest.js +1 -0
  147. package/packages/scrabble-solver/.next/static/chunks/101-829ab8a8d310e7a0.js +3 -0
  148. package/packages/scrabble-solver/.next/static/chunks/106.e414e88694add27f.js +1 -0
  149. package/packages/scrabble-solver/.next/static/chunks/127.125d896a19061d71.js +1 -0
  150. package/packages/scrabble-solver/.next/static/chunks/18.8b91e137d239e15c.js +1 -0
  151. package/packages/scrabble-solver/.next/static/chunks/277.6785dfaf22fdb0b7.js +1 -0
  152. package/packages/scrabble-solver/.next/static/chunks/307.67f52f13e3d60d4e.js +1 -0
  153. package/packages/scrabble-solver/.next/static/chunks/341.79535ba85bf1986f.js +1 -0
  154. package/packages/scrabble-solver/.next/static/chunks/363.01eed3233f92b6a4.js +1 -0
  155. package/packages/scrabble-solver/.next/static/chunks/422.1559ae6756912ca5.js +1 -0
  156. package/packages/scrabble-solver/.next/static/chunks/541.dcd9e8b804f88da5.js +1 -0
  157. package/packages/scrabble-solver/.next/static/chunks/583.7a46807be14c377e.js +1 -0
  158. package/packages/scrabble-solver/.next/static/chunks/631.98d42abad0a60af6.js +1 -0
  159. package/packages/scrabble-solver/.next/static/chunks/663.d2c9ca36b26695b8.js +1 -0
  160. package/packages/scrabble-solver/.next/static/chunks/711.ba7b8f900dfd40d0.js +1 -0
  161. package/packages/scrabble-solver/.next/static/chunks/744.b06c9a3de4a1d877.js +1 -0
  162. package/packages/scrabble-solver/.next/static/chunks/820.97b82f751ca49579.js +1 -0
  163. package/packages/scrabble-solver/.next/static/chunks/865.a9b563a45848a83c.js +1 -0
  164. package/packages/scrabble-solver/.next/static/chunks/912.ff8037ba9d4db5c8.js +1 -0
  165. package/packages/scrabble-solver/.next/static/chunks/939.19b33a3d3c351d73.js +1 -0
  166. package/packages/scrabble-solver/.next/static/chunks/990.61296e5870b91f11.js +1 -0
  167. package/packages/scrabble-solver/.next/static/chunks/framework-626cbd48ac5aca57.js +1 -0
  168. package/packages/scrabble-solver/.next/static/chunks/main-b92e08f3fc96dd8d.js +5 -0
  169. package/packages/scrabble-solver/.next/static/chunks/pages/_app-ba5d628e81a15461.js +1 -0
  170. package/packages/scrabble-solver/.next/static/chunks/pages/{_error-afeda7c5231b6c3c.js → _error-f0290a98eb3ed0ec.js} +1 -1
  171. package/packages/scrabble-solver/.next/static/chunks/pages/index-5f9fa6ac3cd3d97b.js +1 -0
  172. package/packages/scrabble-solver/.next/static/chunks/pages/not-found-23d61fd3ed791cad.js +1 -0
  173. package/packages/scrabble-solver/.next/static/chunks/webpack-e34c3ae12275e978.js +1 -0
  174. package/packages/scrabble-solver/.next/static/css/2d03f0da2c678dfc.css +1 -0
  175. package/packages/scrabble-solver/.next/static/css/7b6724d2df70f48f.css +1 -0
  176. package/packages/scrabble-solver/.next/static/css/abc2425d3ba607bc.css +1 -0
  177. package/packages/scrabble-solver/.next/static/css/b736be3bd02b5d99.css +1 -0
  178. package/packages/scrabble-solver/.next/static/css/b74cd7de82dabcdd.css +1 -0
  179. package/packages/scrabble-solver/.next/static/css/c403ce739a9c0d83.css +1 -0
  180. package/packages/scrabble-solver/.next/static/css/ca311e6c030663d4.css +1 -0
  181. package/packages/scrabble-solver/.next/static/css/dee878eb64099d92.css +1 -0
  182. package/packages/scrabble-solver/.next/static/css/fbfdce70731d0d2b.css +1 -0
  183. package/packages/scrabble-solver/.next/trace +21 -21
  184. package/packages/scrabble-solver/.next/trace-build +1 -1
  185. package/packages/scrabble-solver/empty-module.js +2 -0
  186. package/packages/scrabble-solver/next.config.js +13 -8
  187. package/packages/scrabble-solver/package.json +34 -15
  188. package/packages/scrabble-solver/public/llms.txt +11 -0
  189. package/packages/scrabble-solver/public/robots.txt +4 -0
  190. package/packages/scrabble-solver/public/service-worker.js +2 -2
  191. package/packages/scrabble-solver/scripts/generate-scss-types.ts +112 -0
  192. package/packages/scrabble-solver/src/@types/scss.d.ts +6 -9
  193. package/packages/scrabble-solver/src/@types/svg.d.ts +1 -1
  194. package/packages/scrabble-solver/src/components/Alert/Alert.module.scss.d.ts +13 -0
  195. package/packages/scrabble-solver/src/components/Alert/Alert.tsx +4 -1
  196. package/packages/scrabble-solver/src/components/Badge/Badge.module.scss.d.ts +6 -0
  197. package/packages/scrabble-solver/src/components/Board/Board.module.scss +10 -0
  198. package/packages/scrabble-solver/src/components/Board/Board.module.scss.d.ts +17 -0
  199. package/packages/scrabble-solver/src/components/Board/Board.tsx +14 -26
  200. package/packages/scrabble-solver/src/components/Board/BoardPure.tsx +54 -49
  201. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.module.scss +18 -18
  202. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.module.scss.d.ts +7 -0
  203. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.tsx +6 -3
  204. package/packages/scrabble-solver/src/components/Board/components/Actions/lib.ts +3 -1
  205. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss.d.ts +13 -0
  206. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.tsx +32 -34
  207. package/packages/scrabble-solver/src/components/Board/components/InputPrompt/InputPrompt.module.scss.d.ts +10 -0
  208. package/packages/scrabble-solver/src/components/Board/components/InputPrompt/InputPrompt.tsx +1 -1
  209. package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.module.scss.d.ts +8 -0
  210. package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +2 -2
  211. package/packages/scrabble-solver/src/components/Board/getBoardBackground.ts +175 -0
  212. package/packages/scrabble-solver/src/components/Board/hooks/index.ts +0 -1
  213. package/packages/scrabble-solver/src/components/Board/hooks/useBoardStyle.ts +10 -20
  214. package/packages/scrabble-solver/src/components/Board/hooks/useFloatingActions.ts +3 -6
  215. package/packages/scrabble-solver/src/components/Board/hooks/useGrid.ts +23 -9
  216. package/packages/scrabble-solver/src/components/Board/selectors.ts +1 -1
  217. package/packages/scrabble-solver/src/components/Button/Button.module.scss.d.ts +12 -0
  218. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss +8 -22
  219. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss.d.ts +14 -0
  220. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.tsx +1 -10
  221. package/packages/scrabble-solver/src/components/DictionaryInput/DictionaryInput.module.scss.d.ts +7 -0
  222. package/packages/scrabble-solver/src/components/DictionaryInput/DictionaryInput.tsx +1 -1
  223. package/packages/scrabble-solver/src/components/EmptyState/EmptyState.module.scss.d.ts +7 -0
  224. package/packages/scrabble-solver/src/components/EmptyState/EmptyState.tsx +1 -1
  225. package/packages/scrabble-solver/src/components/IconButton/IconButton.module.scss.d.ts +8 -0
  226. package/packages/scrabble-solver/src/components/Key/Key.module.scss.d.ts +6 -0
  227. package/packages/scrabble-solver/src/components/Keys/Arrows/Arrows.module.scss.d.ts +11 -0
  228. package/packages/scrabble-solver/src/components/Keys/Arrows/Arrows.tsx +4 -1
  229. package/packages/scrabble-solver/src/components/Keys/index.tsx +1 -1
  230. package/packages/scrabble-solver/src/components/Loading/Loading.module.scss.d.ts +9 -0
  231. package/packages/scrabble-solver/src/components/Loading/Loading.tsx +1 -1
  232. package/packages/scrabble-solver/src/components/Logo/Logo.tsx +4 -9
  233. package/packages/scrabble-solver/src/components/Logo/LogoBlueprint.tsx +1 -0
  234. package/packages/scrabble-solver/src/components/Modal/Modal.module.scss.d.ts +15 -0
  235. package/packages/scrabble-solver/src/components/Modal/Modal.tsx +16 -3
  236. package/packages/scrabble-solver/src/components/Modal/components/Section/Section.module.scss.d.ts +7 -0
  237. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.module.scss +20 -0
  238. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.module.scss.d.ts +13 -0
  239. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.tsx +29 -45
  240. package/packages/scrabble-solver/src/components/NotFound/NotFound.module.scss.d.ts +9 -0
  241. package/packages/scrabble-solver/src/components/NotFound/NotFound.tsx +1 -1
  242. package/packages/scrabble-solver/src/components/PlainTiles/PlainTiles.module.scss.d.ts +11 -0
  243. package/packages/scrabble-solver/src/components/PlainTiles/PlainTiles.tsx +0 -1
  244. package/packages/scrabble-solver/src/components/PlainTiles/Tile.tsx +3 -4
  245. package/packages/scrabble-solver/src/components/PlainTiles/lib.ts +4 -19
  246. package/packages/scrabble-solver/src/components/PlainTiles/types.ts +0 -1
  247. package/packages/scrabble-solver/src/components/Progress/Progress.module.scss +7 -0
  248. package/packages/scrabble-solver/src/components/Progress/Progress.module.scss.d.ts +6 -0
  249. package/packages/scrabble-solver/src/components/Progress/Progress.tsx +2 -11
  250. package/packages/scrabble-solver/src/components/Rack/Rack.module.scss +3 -0
  251. package/packages/scrabble-solver/src/components/Rack/Rack.module.scss.d.ts +10 -0
  252. package/packages/scrabble-solver/src/components/Rack/Rack.tsx +8 -11
  253. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +2 -0
  254. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.module.scss.d.ts +7 -0
  255. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.tsx +1 -13
  256. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/lib.ts +1 -1
  257. package/packages/scrabble-solver/src/components/Rack/components/RackTile/RackTile.module.scss.d.ts +6 -0
  258. package/packages/scrabble-solver/src/components/Rack/components/RackTile/RackTile.tsx +5 -4
  259. package/packages/scrabble-solver/src/components/Rack/selectors.ts +1 -1
  260. package/packages/scrabble-solver/src/components/Radio/Radio.module.scss.d.ts +12 -0
  261. package/packages/scrabble-solver/src/components/Results/Cell.tsx +3 -12
  262. package/packages/scrabble-solver/src/components/Results/Header.tsx +9 -18
  263. package/packages/scrabble-solver/src/components/Results/HeaderButton.tsx +4 -5
  264. package/packages/scrabble-solver/src/components/Results/Result.tsx +4 -15
  265. package/packages/scrabble-solver/src/components/Results/Results.module.scss +4 -7
  266. package/packages/scrabble-solver/src/components/Results/Results.module.scss.d.ts +27 -0
  267. package/packages/scrabble-solver/src/components/Results/Results.tsx +14 -5
  268. package/packages/scrabble-solver/src/components/Results/SolveButton.tsx +1 -1
  269. package/packages/scrabble-solver/src/components/Results/types.ts +3 -3
  270. package/packages/scrabble-solver/src/components/ResultsInput/ResultsInput.module.scss.d.ts +7 -0
  271. package/packages/scrabble-solver/src/components/ResultsInput/ResultsInput.tsx +1 -1
  272. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +6 -5
  273. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss.d.ts +20 -0
  274. package/packages/scrabble-solver/src/components/Solver/Solver.tsx +23 -24
  275. package/packages/scrabble-solver/src/components/Solver/components/InsertButton/InsertButton.module.scss.d.ts +7 -0
  276. package/packages/scrabble-solver/src/components/Solver/components/InsertButton/InsertButton.tsx +1 -1
  277. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss.d.ts +18 -0
  278. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +3 -1
  279. package/packages/scrabble-solver/src/components/Spinner/Spinner.module.scss.d.ts +10 -0
  280. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss +3 -0
  281. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss.d.ts +19 -0
  282. package/packages/scrabble-solver/src/components/Tile/Tile.tsx +5 -14
  283. package/packages/scrabble-solver/src/components/Tile/TilePure.tsx +2 -12
  284. package/packages/scrabble-solver/src/components/Tooltip/Tooltip.module.scss.d.ts +7 -0
  285. package/packages/scrabble-solver/src/components/Tooltip/Tooltip.tsx +14 -3
  286. package/packages/scrabble-solver/src/components/Tooltip/useTooltip.ts +2 -1
  287. package/packages/scrabble-solver/src/hooks/useColumns.ts +4 -7
  288. package/packages/scrabble-solver/src/hooks/useDeferredRender.ts +20 -0
  289. package/packages/scrabble-solver/src/hooks/useDirection.ts +4 -3
  290. package/packages/scrabble-solver/src/hooks/useLanguage.ts +2 -2
  291. package/packages/scrabble-solver/src/hooks/useLocalStorage.ts +32 -8
  292. package/packages/scrabble-solver/src/hooks/useMedia.ts +26 -26
  293. package/packages/scrabble-solver/src/i18n/constants.ts +2 -15
  294. package/packages/scrabble-solver/src/i18n/i18n.ts +18 -17
  295. package/packages/scrabble-solver/src/i18n/languages/english.json +0 -1
  296. package/packages/scrabble-solver/src/i18n/languages/french.json +0 -1
  297. package/packages/scrabble-solver/src/i18n/languages/german.json +0 -1
  298. package/packages/scrabble-solver/src/i18n/languages/persian.json +0 -1
  299. package/packages/scrabble-solver/src/i18n/languages/polish.json +0 -1
  300. package/packages/scrabble-solver/src/i18n/languages/romanian.json +0 -1
  301. package/packages/scrabble-solver/src/i18n/languages/spanish.json +0 -1
  302. package/packages/scrabble-solver/src/i18n/languages/turkish.json +0 -1
  303. package/packages/scrabble-solver/src/i18n/localeIcons.ts +24 -0
  304. package/packages/scrabble-solver/src/lib/localeTransliterate.test.ts +5 -0
  305. package/packages/scrabble-solver/src/lib/localeTransliterate.ts +26 -6
  306. package/packages/scrabble-solver/src/lib/waitForIdleOrFirstIntent.ts +33 -0
  307. package/packages/scrabble-solver/src/modals/DictionaryModal/DictionaryModal.module.scss.d.ts +8 -0
  308. package/packages/scrabble-solver/src/modals/DictionaryModal/DictionaryModal.tsx +3 -1
  309. package/packages/scrabble-solver/src/modals/KeyMapModal/KeyMapModal.tsx +3 -1
  310. package/packages/scrabble-solver/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss.d.ts +11 -0
  311. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.module.scss +5 -0
  312. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.module.scss.d.ts +10 -0
  313. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.tsx +16 -5
  314. package/packages/scrabble-solver/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss +4 -1
  315. package/packages/scrabble-solver/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss.d.ts +9 -0
  316. package/packages/scrabble-solver/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +4 -6
  317. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -0
  318. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.module.scss.d.ts +12 -0
  319. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.tsx +5 -12
  320. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.module.scss.d.ts +8 -0
  321. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.tsx +8 -4
  322. package/packages/scrabble-solver/src/modals/SettingsModal/SettingsModal.tsx +2 -2
  323. package/packages/scrabble-solver/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.module.scss.d.ts +7 -0
  324. package/packages/scrabble-solver/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +1 -1
  325. package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss.d.ts +7 -0
  326. package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +1 -1
  327. package/packages/scrabble-solver/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.module.scss.d.ts +7 -0
  328. package/packages/scrabble-solver/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.tsx +1 -1
  329. package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.module.scss.d.ts +7 -0
  330. package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.tsx +1 -1
  331. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss.d.ts +8 -0
  332. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +24 -21
  333. package/packages/scrabble-solver/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.module.scss.d.ts +7 -0
  334. package/packages/scrabble-solver/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.tsx +1 -1
  335. package/packages/scrabble-solver/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.module.scss.d.ts +7 -0
  336. package/packages/scrabble-solver/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.tsx +1 -5
  337. package/packages/scrabble-solver/src/modals/WordsModal/WordsModal.module.scss.d.ts +11 -0
  338. package/packages/scrabble-solver/src/modals/WordsModal/WordsModal.tsx +4 -2
  339. package/packages/scrabble-solver/src/modals/preload.ts +24 -0
  340. package/packages/scrabble-solver/src/pages/_app.tsx +5 -8
  341. package/packages/scrabble-solver/src/pages/_document.tsx +91 -5
  342. package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/[word].ts +2 -2
  343. package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/index.ts +80 -3
  344. package/packages/scrabble-solver/src/pages/api/solve.ts +3 -3
  345. package/packages/scrabble-solver/src/pages/api/verify.ts +12 -3
  346. package/packages/scrabble-solver/src/pages/index.module.scss +1 -0
  347. package/packages/scrabble-solver/src/pages/index.module.scss.d.ts +11 -0
  348. package/packages/scrabble-solver/src/pages/index.tsx +98 -38
  349. package/packages/scrabble-solver/src/pages/not-found.tsx +1 -1
  350. package/packages/scrabble-solver/src/parameters/index.ts +24 -93
  351. package/packages/scrabble-solver/src/sdk/solve.ts +13 -7
  352. package/packages/scrabble-solver/src/sdk/verify.ts +12 -6
  353. package/packages/scrabble-solver/src/service-worker/appShell.test.ts +86 -0
  354. package/packages/scrabble-solver/src/service-worker/appShell.ts +39 -0
  355. package/packages/scrabble-solver/src/service-worker/index.ts +4 -5
  356. package/packages/scrabble-solver/src/serviceWorkerManager.ts +5 -19
  357. package/packages/scrabble-solver/src/solver-worker/client.test.ts +151 -0
  358. package/packages/scrabble-solver/src/solver-worker/client.ts +106 -0
  359. package/packages/scrabble-solver/src/solver-worker/dictionaries/constants.ts +9 -0
  360. package/packages/scrabble-solver/src/solver-worker/dictionaries/getDictionary.ts +21 -0
  361. package/packages/scrabble-solver/src/solver-worker/dictionaries/index.ts +2 -0
  362. package/packages/scrabble-solver/src/solver-worker/dictionaries/revalidateDictionary.test.ts +199 -0
  363. package/packages/scrabble-solver/src/solver-worker/dictionaries/revalidateDictionary.ts +86 -0
  364. package/packages/scrabble-solver/src/solver-worker/dictionaries/revalidatedAt.ts +43 -0
  365. package/packages/scrabble-solver/src/solver-worker/getGaddag.test.ts +109 -0
  366. package/packages/scrabble-solver/src/solver-worker/getGaddag.ts +62 -0
  367. package/packages/scrabble-solver/src/solver-worker/index.ts +1 -0
  368. package/packages/scrabble-solver/src/solver-worker/index.worker.ts +124 -0
  369. package/packages/scrabble-solver/src/solver-worker/messages.ts +20 -0
  370. package/packages/scrabble-solver/src/state/actions.ts +1 -1
  371. package/packages/scrabble-solver/src/state/app/index.ts +4 -0
  372. package/packages/scrabble-solver/src/state/app/initialState.ts +6 -0
  373. package/packages/scrabble-solver/src/state/app/selectors.ts +5 -0
  374. package/packages/scrabble-solver/src/state/app/slice.ts +20 -0
  375. package/packages/scrabble-solver/src/state/app/types.ts +4 -0
  376. package/packages/scrabble-solver/src/state/board/initialState.ts +1 -2
  377. package/packages/scrabble-solver/src/state/i18n/index.ts +4 -0
  378. package/packages/scrabble-solver/src/state/i18n/initialState.ts +5 -0
  379. package/packages/scrabble-solver/src/state/i18n/selectors.ts +3 -0
  380. package/packages/scrabble-solver/src/state/i18n/slice.ts +16 -0
  381. package/packages/scrabble-solver/src/state/i18n/types.ts +7 -0
  382. package/packages/scrabble-solver/src/state/index.ts +3 -0
  383. package/packages/scrabble-solver/src/state/lib.ts +1 -1
  384. package/packages/scrabble-solver/src/state/localStorage.ts +86 -29
  385. package/packages/scrabble-solver/src/state/rack/initialState.ts +1 -2
  386. package/packages/scrabble-solver/src/state/rack/slice.ts +4 -1
  387. package/packages/scrabble-solver/src/state/results/lib.ts +5 -5
  388. package/packages/scrabble-solver/src/state/results/selectors.ts +1 -1
  389. package/packages/scrabble-solver/src/state/sagas.ts +139 -13
  390. package/packages/scrabble-solver/src/state/selectors.ts +1 -1
  391. package/packages/scrabble-solver/src/state/settings/initialState.ts +7 -10
  392. package/packages/scrabble-solver/src/state/settings/selectors.ts +7 -2
  393. package/packages/scrabble-solver/src/state/settings/slice.ts +5 -2
  394. package/packages/scrabble-solver/src/state/store.ts +4 -0
  395. package/packages/scrabble-solver/src/state/types.ts +4 -0
  396. package/packages/scrabble-solver/src/state/useTypedStore.ts +6 -0
  397. package/packages/scrabble-solver/src/styles/_tokens.scss +1 -0
  398. package/packages/scrabble-solver/src/styles/global.scss +9 -0
  399. package/packages/scrabble-solver/src/styles/variables.module.scss +0 -21
  400. package/packages/scrabble-solver/src/styles/variables.module.scss.d.ts +27 -0
  401. package/packages/scrabble-solver/src/styles/variables.scss +46 -2
  402. package/packages/scrabble-solver/src/types/api.ts +1 -2
  403. package/packages/scrabble-solver/src/types/index.ts +1 -2
  404. package/packages/scrabble-solver/tsconfig.json +4 -0
  405. package/packages/scrabble-solver/tsconfig.tsbuildinfo +1 -1
  406. package/packages/solver/README.md +24 -0
  407. package/packages/solver/benchmarks/index.ts +15 -0
  408. package/packages/solver/benchmarks/lib.ts +31 -0
  409. package/packages/solver/benchmarks/renderChart.ts +165 -0
  410. package/packages/solver/benchmarks/results/chart.svg +65 -0
  411. package/packages/solver/benchmarks/runBenchmarks.ts +66 -0
  412. package/packages/solver/benchmarks/scenarios.ts +64 -0
  413. package/packages/solver/benchmarks/types.ts +16 -0
  414. package/packages/solver/benchmarks/updateReadme.ts +53 -0
  415. package/packages/solver/build/MoveGenerator.d.ts +116 -0
  416. package/packages/solver/build/MoveGenerator.js +686 -0
  417. package/packages/solver/build/index.d.ts +0 -13
  418. package/packages/solver/build/index.js +1 -27
  419. package/packages/solver/build/solve.d.ts +2 -2
  420. package/packages/solver/build/solve.js +3 -19
  421. package/packages/solver/package.json +7 -6
  422. package/packages/solver/src/MoveGenerator.test.ts +1009 -0
  423. package/packages/solver/src/MoveGenerator.ts +926 -0
  424. package/packages/solver/src/ReferenceSolver.ts +401 -0
  425. package/packages/solver/src/index.ts +0 -13
  426. package/packages/solver/src/solve.test.ts +21 -21
  427. package/packages/solver/src/solve.ts +4 -22
  428. package/packages/solver/tsconfig.json +1 -1
  429. package/packages/types/build/Cell.d.ts +1 -0
  430. package/packages/types/build/Cell.js +3 -0
  431. package/packages/types/build/Direction.d.ts +1 -0
  432. package/packages/types/build/Result.d.ts +3 -2
  433. package/packages/types/build/Result.js +11 -6
  434. package/packages/types/build/ResultJson.d.ts +5 -4
  435. package/packages/types/build/ShowCoordinates.d.ts +1 -2
  436. package/packages/types/build/TextDirection.d.ts +1 -0
  437. package/packages/types/build/index.d.ts +2 -6
  438. package/packages/types/build/index.js +1 -9
  439. package/packages/types/build/readResultCells.d.ts +5 -0
  440. package/packages/types/build/readResultCells.js +75 -0
  441. package/packages/types/package.json +4 -3
  442. package/packages/types/src/Cell.ts +4 -0
  443. package/packages/types/src/Direction.ts +1 -0
  444. package/packages/types/src/Result.ts +15 -6
  445. package/packages/types/src/ResultJson.ts +5 -5
  446. package/packages/types/src/ShowCoordinates.ts +1 -2
  447. package/packages/types/src/TextDirection.ts +1 -0
  448. package/packages/types/src/index.ts +2 -6
  449. package/packages/types/src/readResultCells.test.ts +140 -0
  450. package/packages/types/src/readResultCells.ts +90 -0
  451. package/packages/word-definitions/package.json +2 -2
  452. package/packages/word-lists/package.json +2 -2
  453. package/playwright-report/index.html +49 -0
  454. package/playwright.config.ts +30 -0
  455. package/screencast.gif +0 -0
  456. package/scripts/record-screencast.ts +347 -0
  457. package/test-results/.last-run.json +4 -0
  458. package/tsconfig.json +1 -1
  459. package/babel.config.js +0 -4
  460. package/cypress/e2e/app.spec.cy.ts +0 -123
  461. package/cypress/e2e/bugs/112-character-bonus.ts +0 -40
  462. package/cypress/e2e/bugs/129-esc-sidebar.ts +0 -23
  463. package/cypress/e2e/bugs/2-q-tile-in-english.ts +0 -33
  464. package/cypress/e2e/bugs/3-x-tile-in-polish.ts +0 -33
  465. package/cypress/e2e/bugs/30-click-inside-radio-icon.ts +0 -26
  466. package/cypress/e2e/bugs/333-disabled-radio-cursor.ts +0 -24
  467. package/cypress/e2e/bugs/342-results-sorting.ts +0 -48
  468. package/cypress/e2e/bugs/348-results-word-padding.ts +0 -74
  469. package/cypress/e2e/bugs/352-dictionary-race-condition.ts +0 -29
  470. package/cypress/e2e/bugs/363-dictionary-input-unique-words.ts +0 -41
  471. package/cypress/e2e/bugs/380-transliteration-when-pasting.ts +0 -75
  472. package/cypress/e2e/bugs/398-digraphs-in-created-words.ts +0 -38
  473. package/cypress/e2e/bugs/401-board-state-on-game-change.ts +0 -36
  474. package/cypress/e2e/bugs.spec.cy.ts +0 -13
  475. package/cypress/e2e/features/198-highlight-unreachable-cells.ts +0 -101
  476. package/cypress/e2e/features/91-hover-remaining-tile-highlights.ts +0 -90
  477. package/cypress/e2e/features.spec.cy.ts +0 -2
  478. package/cypress/support/commands.ts +0 -4
  479. package/cypress/support/e2e.ts +0 -1
  480. package/cypress/support/index.ts +0 -1
  481. package/cypress/support/lib/actions.ts +0 -85
  482. package/cypress/support/lib/selectors.ts +0 -70
  483. package/cypress/tsconfig.json +0 -10
  484. package/cypress.config.ts +0 -17
  485. package/packages/scrabble-solver/.next/server/chunks/318.js +0 -3
  486. package/packages/scrabble-solver/.next/server/chunks/791.js +0 -15
  487. package/packages/scrabble-solver/.next/static/chunks/framework-3cf15b8041c59dd3.js +0 -1
  488. package/packages/scrabble-solver/.next/static/chunks/main-852475ca502b4e49.js +0 -5
  489. package/packages/scrabble-solver/.next/static/chunks/pages/_app-a5bbe02710f13d05.js +0 -7
  490. package/packages/scrabble-solver/.next/static/chunks/pages/index-ec0f6aeeaf7be227.js +0 -1
  491. package/packages/scrabble-solver/.next/static/chunks/pages/not-found-9e076427bad69d90.js +0 -1
  492. package/packages/scrabble-solver/.next/static/chunks/webpack-1a7a166b34ed8d24.js +0 -1
  493. package/packages/scrabble-solver/.next/static/css/0af35adb4bab56f0.css +0 -2
  494. package/packages/scrabble-solver/.next/static/css/a78e04fe95452380.css +0 -1
  495. package/packages/scrabble-solver/.next/static/vYpMmuC_yEurZGcyGVnLf/_buildManifest.js +0 -1
  496. package/packages/scrabble-solver/src/app-layout/AppLayoutContext.tsx +0 -25
  497. package/packages/scrabble-solver/src/app-layout/index.ts +0 -1
  498. package/packages/scrabble-solver/src/app-layout/useAppLayoutValue.ts +0 -112
  499. package/packages/scrabble-solver/src/components/Board/hooks/useBackgroundImage.tsx +0 -279
  500. package/packages/scrabble-solver/src/components/index.ts +0 -26
  501. package/packages/scrabble-solver/src/hooks/index.ts +0 -12
  502. package/packages/scrabble-solver/src/hooks/useOnWindowResize.ts +0 -11
  503. package/packages/scrabble-solver/src/hooks/useViewportSize.ts +0 -17
  504. package/packages/scrabble-solver/src/i18n/index.ts +0 -2
  505. package/packages/scrabble-solver/src/icons/Flag.svg +0 -4
  506. package/packages/scrabble-solver/src/icons/Star.svg +0 -4
  507. package/packages/scrabble-solver/src/icons/index.ts +0 -49
  508. package/packages/scrabble-solver/src/lib/getTileSizes.ts +0 -13
  509. package/packages/scrabble-solver/src/lib/index.ts +0 -24
  510. package/packages/scrabble-solver/src/modals/index.ts +0 -7
  511. package/packages/scrabble-solver/src/sdk/getDictionary.ts +0 -9
  512. package/packages/scrabble-solver/src/service-worker/average.ts +0 -7
  513. package/packages/scrabble-solver/src/service-worker/dictionaries/constants.ts +0 -5
  514. package/packages/scrabble-solver/src/service-worker/dictionaries/getDictionary.ts +0 -20
  515. package/packages/scrabble-solver/src/service-worker/dictionaries/index.ts +0 -2
  516. package/packages/scrabble-solver/src/service-worker/dictionaries/revalidateDictionary.ts +0 -33
  517. package/packages/scrabble-solver/src/service-worker/getTrie.ts +0 -24
  518. package/packages/scrabble-solver/src/service-worker/routeSolveRequests.ts +0 -91
  519. package/packages/scrabble-solver/src/service-worker/routeVerifyRequests.ts +0 -37
  520. package/packages/solver/build/areDigraphsValid.d.ts +0 -2
  521. package/packages/solver/build/areDigraphsValid.js +0 -17
  522. package/packages/solver/build/fillPattern.d.ts +0 -6
  523. package/packages/solver/build/fillPattern.js +0 -52
  524. package/packages/solver/build/generateEndIndices.d.ts +0 -2
  525. package/packages/solver/build/generateEndIndices.js +0 -17
  526. package/packages/solver/build/generateHorizontalPatterns.d.ts +0 -2
  527. package/packages/solver/build/generateHorizontalPatterns.js +0 -16
  528. package/packages/solver/build/generatePattern.d.ts +0 -7
  529. package/packages/solver/build/generatePattern.js +0 -20
  530. package/packages/solver/build/generatePatterns.d.ts +0 -2
  531. package/packages/solver/build/generatePatterns.js +0 -11
  532. package/packages/solver/build/generateStartIndices.d.ts +0 -2
  533. package/packages/solver/build/generateStartIndices.js +0 -16
  534. package/packages/solver/build/generateVectors.d.ts +0 -7
  535. package/packages/solver/build/generateVectors.js +0 -7
  536. package/packages/solver/build/generateVerticalPatterns.d.ts +0 -2
  537. package/packages/solver/build/generateVerticalPatterns.js +0 -16
  538. package/packages/solver/build/getCellsScore.d.ts +0 -2
  539. package/packages/solver/build/getCellsScore.js +0 -18
  540. package/packages/solver/build/getPatternHash.d.ts +0 -2
  541. package/packages/solver/build/getPatternHash.js +0 -14
  542. package/packages/solver/build/getPatternScore.d.ts +0 -2
  543. package/packages/solver/build/getPatternScore.js +0 -22
  544. package/packages/solver/build/getUniquePatterns.d.ts +0 -2
  545. package/packages/solver/build/getUniquePatterns.js +0 -17
  546. package/packages/solver/src/areDigraphsValid.ts +0 -18
  547. package/packages/solver/src/fillPattern.test.ts +0 -107
  548. package/packages/solver/src/fillPattern.ts +0 -68
  549. package/packages/solver/src/generateEndIndices.test.ts +0 -23
  550. package/packages/solver/src/generateEndIndices.ts +0 -19
  551. package/packages/solver/src/generateHorizontalPatterns.test.ts +0 -31
  552. package/packages/solver/src/generateHorizontalPatterns.ts +0 -15
  553. package/packages/solver/src/generatePattern.ts +0 -33
  554. package/packages/solver/src/generatePatterns.test.ts +0 -13
  555. package/packages/solver/src/generatePatterns.ts +0 -11
  556. package/packages/solver/src/generateStartIndices.test.ts +0 -19
  557. package/packages/solver/src/generateStartIndices.ts +0 -17
  558. package/packages/solver/src/generateVectors.test.ts +0 -12
  559. package/packages/solver/src/generateVectors.ts +0 -10
  560. package/packages/solver/src/generateVerticalPatterns.test.ts +0 -32
  561. package/packages/solver/src/generateVerticalPatterns.ts +0 -15
  562. package/packages/solver/src/getCellsScore.ts +0 -21
  563. package/packages/solver/src/getPatternHash.test.ts +0 -17
  564. package/packages/solver/src/getPatternHash.ts +0 -12
  565. package/packages/solver/src/getPatternScore.test.ts +0 -204
  566. package/packages/solver/src/getPatternScore.ts +0 -23
  567. package/packages/solver/src/getUniquePatterns.ts +0 -19
  568. package/packages/types/build/CollisionJson.d.ts +0 -2
  569. package/packages/types/build/FinalPattern.d.ts +0 -6
  570. package/packages/types/build/FinalPattern.js +0 -14
  571. package/packages/types/build/HorizontalPattern.d.ts +0 -5
  572. package/packages/types/build/HorizontalPattern.js +0 -34
  573. package/packages/types/build/Pattern.d.ts +0 -20
  574. package/packages/types/build/Pattern.js +0 -50
  575. package/packages/types/build/PatternJson.d.ts +0 -6
  576. package/packages/types/build/VerticalPattern.d.ts +0 -5
  577. package/packages/types/build/VerticalPattern.js +0 -34
  578. package/packages/types/src/CollisionJson.ts +0 -3
  579. package/packages/types/src/FinalPattern.ts +0 -14
  580. package/packages/types/src/HorizontalPattern.ts +0 -37
  581. package/packages/types/src/Pattern.ts +0 -71
  582. package/packages/types/src/PatternJson.ts +0 -7
  583. package/packages/types/src/VerticalPattern.ts +0 -37
  584. /package/{cypress/support → e2e}/lib/index.ts +0 -0
  585. /package/packages/scrabble-solver/.next/static/{vYpMmuC_yEurZGcyGVnLf → FL82obwzFHRzvV6z76I-U}/_ssgManifest.js +0 -0
  586. /package/packages/scrabble-solver/src/{service-worker → solver-worker}/dictionaries/expirationManager.ts +0 -0
  587. /package/packages/scrabble-solver/src/{service-worker → solver-worker}/dictionaries/getDictionaryUrl.ts +0 -0
  588. /package/packages/types/build/{CollisionJson.js → Direction.js} +0 -0
  589. /package/packages/types/build/{PatternJson.js → TextDirection.js} +0 -0
@@ -3,3 +3,27 @@
3
3
  ![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)
4
4
 
5
5
  The brains of Scrabble Solver.
6
+
7
+ ## Benchmarks
8
+
9
+ `solve()` speed for a mid-game Scrabble position in English (US), English (GB), and Polish, grouped by the number of blank tiles on the rack.
10
+
11
+ Rerun the benchmarks and refresh the results below with:
12
+
13
+ ```sh
14
+ bun run benchmark
15
+ ```
16
+
17
+ <!-- benchmark-results:start -->
18
+
19
+ ![Median solve() duration grouped by number of blanks on the rack](benchmarks/results/chart.svg)
20
+
21
+ | Blanks | English (US) | English (GB) | Polish |
22
+ | --- | --- | --- | --- |
23
+ | 0 | 2 ms (1,559 results) | 2 ms (2,398 results) | 1 ms (1,826 results) |
24
+ | 1 | 11 ms (7,546 results) | 15 ms (11,680 results) | 13 ms (15,291 results) |
25
+ | 2 | 39 ms (25,659 results) | 52 ms (39,286 results) | 66 ms (70,693 results) |
26
+
27
+ Median of 5 runs (after 5 warmup runs)
28
+
29
+ <!-- benchmark-results:end -->
@@ -0,0 +1,15 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { renderChart } from './renderChart';
5
+ import { runBenchmarks } from './runBenchmarks';
6
+ import { updateReadme } from './updateReadme';
7
+
8
+ const RESULTS_DIRECTORY = path.join(import.meta.dir, 'results');
9
+
10
+ const measurements = await runBenchmarks();
11
+
12
+ fs.mkdirSync(RESULTS_DIRECTORY, { recursive: true });
13
+ fs.writeFileSync(path.join(RESULTS_DIRECTORY, 'chart.svg'), `${renderChart(measurements)}\n`);
14
+ updateReadme(measurements);
15
+ console.log('Updated README.md and benchmarks/results/chart.svg');
@@ -0,0 +1,31 @@
1
+ import { type Measurement } from './types';
2
+
3
+ export const getMeasurement = (measurements: Measurement[], label: string, blanksCount: number): Measurement => {
4
+ const measurement = measurements.find(
5
+ (candidate) => candidate.label === label && candidate.blanksCount === blanksCount,
6
+ );
7
+
8
+ if (!measurement) {
9
+ throw new Error(`Missing measurement for "${label}" with ${blanksCount} blanks`);
10
+ }
11
+
12
+ return measurement;
13
+ };
14
+
15
+ export const median = (values: number[]): number => {
16
+ const sorted = [...values].sort((a, b) => a - b);
17
+ const middle = Math.floor(sorted.length / 2);
18
+ return sorted.length % 2 === 0 ? (sorted[middle - 1] + sorted[middle]) / 2 : sorted[middle];
19
+ };
20
+
21
+ export const formatDuration = (milliseconds: number): string => {
22
+ return milliseconds < 1000 ? `${Math.round(milliseconds)} ms` : `${(milliseconds / 1000).toFixed(1)} s`;
23
+ };
24
+
25
+ export const formatBlanksCount = (blanksCount: number): string => {
26
+ return blanksCount === 1 ? '1 blank' : `${blanksCount} blanks`;
27
+ };
28
+
29
+ export const formatInteger = (value: number): string => value.toLocaleString('en-US');
30
+
31
+ export const unique = <T>(values: T[]): T[] => [...new Set(values)];
@@ -0,0 +1,165 @@
1
+ import { formatBlanksCount, formatDuration, getMeasurement, median, unique } from './lib';
2
+ import { type Measurement } from './types';
3
+
4
+ const WIDTH = 720;
5
+ const HEIGHT = 420;
6
+ const PLOT_TOP = 96;
7
+ const PLOT_BOTTOM = HEIGHT - 48;
8
+ const PLOT_LEFT = 56;
9
+ const PLOT_RIGHT = WIDTH - 24;
10
+ const BAR_WIDTH = 24;
11
+ const BAR_PITCH = 44;
12
+ const CORNER_RADIUS = 4;
13
+ const MAX_TICKS = 6;
14
+ const TICK_STEP_MULTIPLIERS = [1, 2, 2.5, 5, 10];
15
+ const SCALE_HEADROOM = 1.08;
16
+
17
+ const STYLE = `
18
+ text { font-family: system-ui, -apple-system, 'Segoe UI', sans-serif; }
19
+ .axis { stroke: #c3c2b7; }
20
+ .grid { stroke: #e1e0d9; }
21
+ .group-label, .legend-label { fill: #52514e; font-size: 12px; }
22
+ .series-0 { fill: #2a78d6; }
23
+ .series-1 { fill: #eb6834; }
24
+ .series-2 { fill: #1baf7a; }
25
+ .subtitle { fill: #898781; font-size: 12px; }
26
+ .surface { fill: #fcfcfb; }
27
+ .tick-label { fill: #898781; font-size: 11px; font-variant-numeric: tabular-nums; }
28
+ .title { fill: #0b0b0b; font-size: 15px; font-weight: 600; }
29
+ .value-label { fill: #52514e; font-size: 11px; }
30
+ @media (prefers-color-scheme: dark) {
31
+ .axis { stroke: #383835; }
32
+ .grid { stroke: #2c2c2a; }
33
+ .group-label, .legend-label, .value-label { fill: #c3c2b7; }
34
+ .series-0 { fill: #3987e5; }
35
+ .series-1 { fill: #d95926; }
36
+ .series-2 { fill: #199e70; }
37
+ .surface { fill: #1a1a19; }
38
+ .title { fill: #ffffff; }
39
+ }`;
40
+
41
+ export const renderChart = (measurements: Measurement[]): string => {
42
+ const blanksCounts = unique(measurements.map((measurement) => measurement.blanksCount)).sort((a, b) => a - b);
43
+ const labels = unique(measurements.map((measurement) => measurement.label));
44
+ const maxDuration = Math.max(...measurements.map((measurement) => median(measurement.durations)));
45
+ const tickStep = getTickStep(maxDuration * SCALE_HEADROOM);
46
+ const scaleMax = Math.ceil((maxDuration * SCALE_HEADROOM) / tickStep) * tickStep;
47
+
48
+ return [
49
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${WIDTH}" height="${HEIGHT}" viewBox="0 0 ${WIDTH} ${HEIGHT}" role="img" aria-label="Median solve() duration by number of blanks on the rack, for ${labels.join(', ')}">`,
50
+ ` <style>${STYLE}</style>`,
51
+ ` <rect class="surface" width="${WIDTH}" height="${HEIGHT}" rx="8" />`,
52
+ ` <text class="title" x="24" y="32">solve() median duration</text>`,
53
+ ` <text class="subtitle" x="24" y="50">Scrabble, mid-game board, 7 tiles on the rack, letters replaced by blanks</text>`,
54
+ renderLegend(labels),
55
+ renderScale(tickStep, scaleMax),
56
+ renderBars(measurements, blanksCounts, labels, scaleMax),
57
+ renderGroupLabels(blanksCounts),
58
+ '</svg>',
59
+ ].join('\n');
60
+ };
61
+
62
+ const renderLegend = (labels: string[]): string => {
63
+ let x = 24;
64
+
65
+ return labels
66
+ .map((label, index) => {
67
+ const item = [
68
+ ` <rect class="series-${index}" x="${x}" y="63" width="10" height="10" rx="2" />`,
69
+ ` <text class="legend-label" x="${x + 16}" y="73">${label}</text>`,
70
+ ].join('\n');
71
+ x += 16 + Math.ceil(label.length * 6.7) + 24;
72
+ return item;
73
+ })
74
+ .join('\n');
75
+ };
76
+
77
+ const renderScale = (tickStep: number, scaleMax: number): string => {
78
+ const lines: string[] = [];
79
+
80
+ for (let tick = 0; tick <= scaleMax; tick += tickStep) {
81
+ const y = getY(tick, scaleMax);
82
+ const lineClass = tick === 0 ? 'axis' : 'grid';
83
+ lines.push(` <line class="${lineClass}" x1="${PLOT_LEFT}" y1="${y}" x2="${PLOT_RIGHT}" y2="${y}" />`);
84
+ lines.push(
85
+ ` <text class="tick-label" text-anchor="end" x="${PLOT_LEFT - 8}" y="${y + 4}">${formatTick(tick)}</text>`,
86
+ );
87
+ }
88
+
89
+ return lines.join('\n');
90
+ };
91
+
92
+ const renderBars = (
93
+ measurements: Measurement[],
94
+ blanksCounts: number[],
95
+ labels: string[],
96
+ scaleMax: number,
97
+ ): string => {
98
+ return blanksCounts
99
+ .flatMap((blanksCount, groupIndex) =>
100
+ labels.map((label, seriesIndex) => {
101
+ const measurement = getMeasurement(measurements, label, blanksCount);
102
+ const duration = median(measurement.durations);
103
+ const x = getBarX(groupIndex, seriesIndex, blanksCounts.length, labels.length);
104
+ const y = getY(duration, scaleMax);
105
+ return [
106
+ ` <path class="series-${seriesIndex}" d="${getBarPath(x, y)}" />`,
107
+ ` <text class="value-label" text-anchor="middle" x="${round(x + BAR_WIDTH / 2)}" y="${round(y - 6)}">${formatDuration(duration)}</text>`,
108
+ ].join('\n');
109
+ }),
110
+ )
111
+ .join('\n');
112
+ };
113
+
114
+ const renderGroupLabels = (blanksCounts: number[]): string => {
115
+ const bandWidth = (PLOT_RIGHT - PLOT_LEFT) / blanksCounts.length;
116
+
117
+ return blanksCounts
118
+ .map((blanksCount, groupIndex) => {
119
+ const x = round(PLOT_LEFT + bandWidth * (groupIndex + 0.5));
120
+ return ` <text class="group-label" text-anchor="middle" x="${x}" y="${PLOT_BOTTOM + 22}">${formatBlanksCount(blanksCount)}</text>`;
121
+ })
122
+ .join('\n');
123
+ };
124
+
125
+ const getBarPath = (x: number, y: number): string => {
126
+ const radius = Math.min(CORNER_RADIUS, (PLOT_BOTTOM - y) / 2);
127
+ return [
128
+ `M ${round(x)} ${PLOT_BOTTOM}`,
129
+ `L ${round(x)} ${round(y + radius)}`,
130
+ `Q ${round(x)} ${round(y)} ${round(x + radius)} ${round(y)}`,
131
+ `L ${round(x + BAR_WIDTH - radius)} ${round(y)}`,
132
+ `Q ${round(x + BAR_WIDTH)} ${round(y)} ${round(x + BAR_WIDTH)} ${round(y + radius)}`,
133
+ `L ${round(x + BAR_WIDTH)} ${PLOT_BOTTOM}`,
134
+ 'Z',
135
+ ].join(' ');
136
+ };
137
+
138
+ const getBarX = (groupIndex: number, seriesIndex: number, groupsCount: number, seriesCount: number): number => {
139
+ const bandWidth = (PLOT_RIGHT - PLOT_LEFT) / groupsCount;
140
+ const contentWidth = (seriesCount - 1) * BAR_PITCH + BAR_WIDTH;
141
+ return round(PLOT_LEFT + bandWidth * groupIndex + (bandWidth - contentWidth) / 2 + seriesIndex * BAR_PITCH);
142
+ };
143
+
144
+ const getY = (duration: number, scaleMax: number): number => {
145
+ return round(PLOT_BOTTOM - (duration / scaleMax) * (PLOT_BOTTOM - PLOT_TOP));
146
+ };
147
+
148
+ const getTickStep = (maxValue: number): number => {
149
+ const magnitude = 10 ** Math.floor(Math.log10(maxValue / MAX_TICKS));
150
+ const step = TICK_STEP_MULTIPLIERS.map((multiplier) => multiplier * magnitude).find(
151
+ (candidate) => maxValue / candidate <= MAX_TICKS,
152
+ );
153
+
154
+ if (!step) {
155
+ throw new Error(`Cannot pick a tick step for ${maxValue}`);
156
+ }
157
+
158
+ return step;
159
+ };
160
+
161
+ const formatTick = (milliseconds: number): string => {
162
+ return milliseconds === 0 ? '0' : `${Number((milliseconds / 1000).toFixed(2))}s`;
163
+ };
164
+
165
+ const round = (value: number): number => Math.round(value * 100) / 100;
@@ -0,0 +1,65 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="720" height="420" viewBox="0 0 720 420" role="img" aria-label="Median solve() duration by number of blanks on the rack, for English (US), English (GB), Polish">
2
+ <style>
3
+ text { font-family: system-ui, -apple-system, 'Segoe UI', sans-serif; }
4
+ .axis { stroke: #c3c2b7; }
5
+ .grid { stroke: #e1e0d9; }
6
+ .group-label, .legend-label { fill: #52514e; font-size: 12px; }
7
+ .series-0 { fill: #2a78d6; }
8
+ .series-1 { fill: #eb6834; }
9
+ .series-2 { fill: #1baf7a; }
10
+ .subtitle { fill: #898781; font-size: 12px; }
11
+ .surface { fill: #fcfcfb; }
12
+ .tick-label { fill: #898781; font-size: 11px; font-variant-numeric: tabular-nums; }
13
+ .title { fill: #0b0b0b; font-size: 15px; font-weight: 600; }
14
+ .value-label { fill: #52514e; font-size: 11px; }
15
+ @media (prefers-color-scheme: dark) {
16
+ .axis { stroke: #383835; }
17
+ .grid { stroke: #2c2c2a; }
18
+ .group-label, .legend-label, .value-label { fill: #c3c2b7; }
19
+ .series-0 { fill: #3987e5; }
20
+ .series-1 { fill: #d95926; }
21
+ .series-2 { fill: #199e70; }
22
+ .surface { fill: #1a1a19; }
23
+ .title { fill: #ffffff; }
24
+ }</style>
25
+ <rect class="surface" width="720" height="420" rx="8" />
26
+ <text class="title" x="24" y="32">solve() median duration</text>
27
+ <text class="subtitle" x="24" y="50">Scrabble, mid-game board, 7 tiles on the rack, letters replaced by blanks</text>
28
+ <rect class="series-0" x="24" y="63" width="10" height="10" rx="2" />
29
+ <text class="legend-label" x="40" y="73">English (US)</text>
30
+ <rect class="series-1" x="145" y="63" width="10" height="10" rx="2" />
31
+ <text class="legend-label" x="161" y="73">English (GB)</text>
32
+ <rect class="series-2" x="266" y="63" width="10" height="10" rx="2" />
33
+ <text class="legend-label" x="282" y="73">Polish</text>
34
+ <line class="axis" x1="56" y1="372" x2="696" y2="372" />
35
+ <text class="tick-label" text-anchor="end" x="48" y="376">0</text>
36
+ <line class="grid" x1="56" y1="303" x2="696" y2="303" />
37
+ <text class="tick-label" text-anchor="end" x="48" y="307">0.02s</text>
38
+ <line class="grid" x1="56" y1="234" x2="696" y2="234" />
39
+ <text class="tick-label" text-anchor="end" x="48" y="238">0.04s</text>
40
+ <line class="grid" x1="56" y1="165" x2="696" y2="165" />
41
+ <text class="tick-label" text-anchor="end" x="48" y="169">0.06s</text>
42
+ <line class="grid" x1="56" y1="96" x2="696" y2="96" />
43
+ <text class="tick-label" text-anchor="end" x="48" y="100">0.08s</text>
44
+ <path class="series-0" d="M 106.67 372 L 106.67 368.78 Q 106.67 365.55 109.9 365.55 L 127.45 365.55 Q 130.67 365.55 130.67 368.78 L 130.67 372 Z" />
45
+ <text class="value-label" text-anchor="middle" x="118.67" y="359.55">2 ms</text>
46
+ <path class="series-1" d="M 150.67 372 L 150.67 368.22 Q 150.67 364.44 154.45 364.44 L 170.89 364.44 Q 174.67 364.44 174.67 368.22 L 174.67 372 Z" />
47
+ <text class="value-label" text-anchor="middle" x="162.67" y="358.44">2 ms</text>
48
+ <path class="series-2" d="M 194.67 372 L 194.67 369.59 Q 194.67 367.17 197.08 367.17 L 216.26 367.17 Q 218.67 367.17 218.67 369.59 L 218.67 372 Z" />
49
+ <text class="value-label" text-anchor="middle" x="206.67" y="361.17">1 ms</text>
50
+ <path class="series-0" d="M 320 372 L 320 337.05 Q 320 333.05 324 333.05 L 340 333.05 Q 344 333.05 344 337.05 L 344 372 Z" />
51
+ <text class="value-label" text-anchor="middle" x="332" y="327.05">11 ms</text>
52
+ <path class="series-1" d="M 364 372 L 364 325.84 Q 364 321.84 368 321.84 L 384 321.84 Q 388 321.84 388 325.84 L 388 372 Z" />
53
+ <text class="value-label" text-anchor="middle" x="376" y="315.84">15 ms</text>
54
+ <path class="series-2" d="M 408 372 L 408 330.12 Q 408 326.12 412 326.12 L 428 326.12 Q 432 326.12 432 330.12 L 432 372 Z" />
55
+ <text class="value-label" text-anchor="middle" x="420" y="320.12">13 ms</text>
56
+ <path class="series-0" d="M 533.33 372 L 533.33 239.86 Q 533.33 235.86 537.33 235.86 L 553.33 235.86 Q 557.33 235.86 557.33 239.86 L 557.33 372 Z" />
57
+ <text class="value-label" text-anchor="middle" x="545.33" y="229.86">39 ms</text>
58
+ <path class="series-1" d="M 577.33 372 L 577.33 195.63 Q 577.33 191.63 581.33 191.63 L 597.33 191.63 Q 601.33 191.63 601.33 195.63 L 601.33 372 Z" />
59
+ <text class="value-label" text-anchor="middle" x="589.33" y="185.63">52 ms</text>
60
+ <path class="series-2" d="M 621.33 372 L 621.33 147.51 Q 621.33 143.51 625.33 143.51 L 641.33 143.51 Q 645.33 143.51 645.33 147.51 L 645.33 372 Z" />
61
+ <text class="value-label" text-anchor="middle" x="633.33" y="137.51">66 ms</text>
62
+ <text class="group-label" text-anchor="middle" x="162.67" y="394">0 blanks</text>
63
+ <text class="group-label" text-anchor="middle" x="376" y="394">1 blank</text>
64
+ <text class="group-label" text-anchor="middle" x="589.33" y="394">2 blanks</text>
65
+ </svg>
@@ -0,0 +1,66 @@
1
+ import { type Gaddag } from '@kamilmielnik/gaddag';
2
+ import { getConfig } from '@scrabble-solver/configs';
3
+ import { BLANK } from '@scrabble-solver/constants';
4
+ import { dictionaries } from '@scrabble-solver/dictionaries';
5
+ import { Board, type Config, Game, Tile } from '@scrabble-solver/types';
6
+
7
+ import { solve } from '../src';
8
+
9
+ import { formatBlanksCount, formatDuration, median } from './lib';
10
+ import { LANGUAGE_SCENARIOS } from './scenarios';
11
+ import { type LanguageScenario, type Measurement } from './types';
12
+
13
+ const GAME = Game.Scrabble;
14
+ export const MEASURED_RUNS = 5;
15
+ export const WARMUP_RUNS = 5;
16
+
17
+ export const runBenchmarks = async (): Promise<Measurement[]> => {
18
+ const measurements: Measurement[] = [];
19
+
20
+ for (const scenario of LANGUAGE_SCENARIOS) {
21
+ const config = getConfig(GAME, scenario.locale);
22
+ const gaddag = await dictionaries.get(scenario.locale);
23
+
24
+ for (let blanksCount = 0; blanksCount <= config.blanksCount; blanksCount++) {
25
+ const measurement = measureScenario(gaddag, config, scenario, blanksCount);
26
+ console.log(
27
+ `${scenario.label} - ${formatBlanksCount(blanksCount)} - median ${formatDuration(median(measurement.durations))} - ${measurement.resultsCount} results`,
28
+ );
29
+ measurements.push(measurement);
30
+ }
31
+ }
32
+
33
+ return measurements;
34
+ };
35
+
36
+ const measureScenario = (
37
+ gaddag: Gaddag,
38
+ config: Config,
39
+ scenario: LanguageScenario,
40
+ blanksCount: number,
41
+ ): Measurement => {
42
+ const durations: number[] = [];
43
+ let resultsCount = 0;
44
+
45
+ for (let run = 0; run < WARMUP_RUNS + MEASURED_RUNS; run++) {
46
+ const board = Board.fromStringArray(scenario.boardRows);
47
+ const tiles = generateRack(scenario.baseRack, blanksCount);
48
+ const start = performance.now();
49
+ const results = solve(gaddag, config, board, tiles);
50
+ const duration = performance.now() - start;
51
+
52
+ if (run >= WARMUP_RUNS) {
53
+ durations.push(Number(duration.toFixed(2)));
54
+ }
55
+
56
+ resultsCount = results.length;
57
+ }
58
+
59
+ return { blanksCount, durations, label: scenario.label, locale: scenario.locale, resultsCount };
60
+ };
61
+
62
+ const generateRack = (baseRack: string[], blanksCount: number): Tile[] => {
63
+ return baseRack
64
+ .map((character, index) => (index < baseRack.length - blanksCount ? character : BLANK))
65
+ .map((character) => new Tile({ character, isBlank: character === BLANK }));
66
+ };
@@ -0,0 +1,64 @@
1
+ import { Locale } from '@scrabble-solver/types';
2
+
3
+ import { type LanguageScenario } from './types';
4
+
5
+ const ENGLISH_BOARD_ROWS = [
6
+ ' ',
7
+ ' ',
8
+ ' ',
9
+ ' ',
10
+ ' ',
11
+ ' cozy ',
12
+ ' a ',
13
+ ' flames a ',
14
+ ' taxi ',
15
+ ' ratio e ',
16
+ ' a n ',
17
+ ' verge ',
18
+ ' e ',
19
+ ' n ',
20
+ ' ',
21
+ ];
22
+
23
+ const ENGLISH_BASE_RACK = ['r', 'e', 't', 'i', 'n', 'a', 's'];
24
+
25
+ const POLISH_BOARD_ROWS = [
26
+ ' ',
27
+ ' ',
28
+ ' ',
29
+ ' ',
30
+ ' s ',
31
+ ' mysz ',
32
+ ' a ',
33
+ ' korale ',
34
+ ' tor ',
35
+ ' wino a ',
36
+ ' a s k ',
37
+ ' kier ',
38
+ ' ',
39
+ ' ',
40
+ ' ',
41
+ ];
42
+
43
+ const POLISH_BASE_RACK = ['a', 'e', 'i', 'n', 'o', 'r', 's'];
44
+
45
+ export const LANGUAGE_SCENARIOS: LanguageScenario[] = [
46
+ {
47
+ baseRack: ENGLISH_BASE_RACK,
48
+ boardRows: ENGLISH_BOARD_ROWS,
49
+ label: 'English (US)',
50
+ locale: Locale.EN_US,
51
+ },
52
+ {
53
+ baseRack: ENGLISH_BASE_RACK,
54
+ boardRows: ENGLISH_BOARD_ROWS,
55
+ label: 'English (GB)',
56
+ locale: Locale.EN_GB,
57
+ },
58
+ {
59
+ baseRack: POLISH_BASE_RACK,
60
+ boardRows: POLISH_BOARD_ROWS,
61
+ label: 'Polish',
62
+ locale: Locale.PL_PL,
63
+ },
64
+ ];
@@ -0,0 +1,16 @@
1
+ import { type Locale } from '@scrabble-solver/types';
2
+
3
+ export interface LanguageScenario {
4
+ baseRack: string[];
5
+ boardRows: string[];
6
+ label: string;
7
+ locale: Locale;
8
+ }
9
+
10
+ export interface Measurement {
11
+ blanksCount: number;
12
+ durations: number[];
13
+ label: string;
14
+ locale: Locale;
15
+ resultsCount: number;
16
+ }
@@ -0,0 +1,53 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { formatDuration, formatInteger, getMeasurement, median, unique } from './lib';
5
+ import { MEASURED_RUNS, WARMUP_RUNS } from './runBenchmarks';
6
+ import { type Measurement } from './types';
7
+
8
+ const README_PATH = path.join(import.meta.dir, '..', 'README.md');
9
+
10
+ const START_MARKER = '<!-- benchmark-results:start -->';
11
+ const END_MARKER = '<!-- benchmark-results:end -->';
12
+
13
+ export const updateReadme = (measurements: Measurement[]): void => {
14
+ const readme = fs.readFileSync(README_PATH, 'utf-8');
15
+ const startIndex = readme.indexOf(START_MARKER);
16
+ const endIndex = readme.indexOf(END_MARKER);
17
+
18
+ if (startIndex === -1 || endIndex === -1) {
19
+ throw new Error(`Cannot update ${README_PATH} - missing "${START_MARKER}" / "${END_MARKER}" markers`);
20
+ }
21
+
22
+ const prefix = readme.slice(0, startIndex + START_MARKER.length);
23
+ const suffix = readme.slice(endIndex);
24
+ fs.writeFileSync(README_PATH, `${prefix}\n\n${generateResults(measurements)}\n\n${suffix}`);
25
+ };
26
+
27
+ const generateResults = (measurements: Measurement[]): string => {
28
+ return [
29
+ '![Median solve() duration grouped by number of blanks on the rack](benchmarks/results/chart.svg)',
30
+ generateTable(measurements),
31
+ generateFootnote(),
32
+ ].join('\n\n');
33
+ };
34
+
35
+ const generateTable = (measurements: Measurement[]): string => {
36
+ const blanksCounts = unique(measurements.map((measurement) => measurement.blanksCount)).sort((a, b) => a - b);
37
+ const labels = unique(measurements.map((measurement) => measurement.label));
38
+ const header = `| Blanks | ${labels.join(' | ')} |`;
39
+ const separator = `| ${['---', ...labels.map(() => '---')].join(' | ')} |`;
40
+ const rows = blanksCounts.map((blanksCount) => {
41
+ const cells = labels.map((label) => {
42
+ const measurement = getMeasurement(measurements, label, blanksCount);
43
+ return `${formatDuration(median(measurement.durations))} (${formatInteger(measurement.resultsCount)} results)`;
44
+ });
45
+ return `| ${[blanksCount, ...cells].join(' | ')} |`;
46
+ });
47
+
48
+ return [header, separator, ...rows].join('\n');
49
+ };
50
+
51
+ const generateFootnote = (): string => {
52
+ return `Median of ${MEASURED_RUNS} runs (after ${WARMUP_RUNS} warmup runs)`;
53
+ };
@@ -0,0 +1,116 @@
1
+ import { type Gaddag } from '@kamilmielnik/gaddag';
2
+ import { type Board, type Config, type ResultJson, type Tile } from '@scrabble-solver/types';
3
+ /**
4
+ * Anchor-based move generation over a {@link Gaddag} (Gordon, 1994).
5
+ *
6
+ * For every anchor (an empty cell adjacent to a tile, or the board center when
7
+ * the board is empty) the generator extends leftwards from the anchor through
8
+ * the reversed-prefix part of the GADDAG, crosses the separator, and extends
9
+ * rightwards — validating perpendicular words with precomputed cross-check
10
+ * masks. Both directions run the same code path: the vertical pass remaps
11
+ * (line, position) to transposed coordinates. Each placement is scored and
12
+ * emitted as a compact {@link ResultJson} — placed tiles plus placement — from
13
+ * which {@link Result.fromJson} rebuilds the full cells and collisions against
14
+ * the board.
15
+ */
16
+ export declare class MoveGenerator {
17
+ private readonly gaddag;
18
+ private readonly config;
19
+ private readonly width;
20
+ private readonly height;
21
+ private readonly cellsCount;
22
+ private readonly alphaSize;
23
+ private readonly alphaChars;
24
+ private readonly alphaLetter1;
25
+ private readonly alphaLetter2;
26
+ private readonly alphaPoints;
27
+ private readonly leftAlphaStart;
28
+ private readonly leftAlphaList;
29
+ private readonly rightAlphaStart;
30
+ private readonly rightAlphaList;
31
+ private readonly alphaSecondLeft;
32
+ private readonly alphaSecondRight;
33
+ private readonly rackCounts;
34
+ private blanksLeft;
35
+ private rackTotal;
36
+ private readonly bonusType;
37
+ private readonly bonusMult;
38
+ private readonly bonusReq;
39
+ private readonly boardFilled;
40
+ private readonly boardChar;
41
+ private readonly boardIsBlank;
42
+ private readonly boardScore;
43
+ private readonly boardLetter1;
44
+ private readonly boardLetter2;
45
+ private readonly anchor;
46
+ private boardIsEmpty;
47
+ private isHorizontal;
48
+ private lineLength;
49
+ private linesCount;
50
+ private readonly passFilled;
51
+ private readonly passLetter1;
52
+ private readonly passLetter2;
53
+ private readonly passScore;
54
+ private readonly passGlobal;
55
+ private readonly passAnchor;
56
+ private readonly maskLo;
57
+ private readonly maskHi;
58
+ private readonly crossBase;
59
+ private readonly hasCross;
60
+ private line;
61
+ private lineBase;
62
+ private anchorPos;
63
+ private limit;
64
+ private anchorRightOpen;
65
+ private leftMost;
66
+ private readonly placedAt;
67
+ private readonly placedBlankAt;
68
+ private placedCount;
69
+ private readonly digraphs;
70
+ private readonly blankScore;
71
+ private readonly rackSize;
72
+ private readonly seenSingleTiles;
73
+ private readonly results;
74
+ private readonly sortKeys;
75
+ private readonly rankKeys;
76
+ private readonly alphaFirstRackIndex;
77
+ private blankRackIndex;
78
+ constructor(gaddag: Gaddag, config: Config, board: Board, tiles: Tile[]);
79
+ run(): ResultJson[];
80
+ private runPass;
81
+ private setupPass;
82
+ /**
83
+ * For every empty cell with perpendicular neighbors, computes the set of
84
+ * placeable tile characters (as a 64-bit mask), the sum of the existing
85
+ * perpendicular tiles' scores, and the perpendicular word span.
86
+ */
87
+ private computeCrossChecks;
88
+ /**
89
+ * A digraph must be played as its single tile, so a candidate that would
90
+ * spell a digraph with the adjacent perpendicular tile is not placeable.
91
+ * {@link record} enforces the same rule along the main word.
92
+ */
93
+ private spellsDigraphWithNeighbor;
94
+ private isOpen;
95
+ private generateLeft;
96
+ private generateRight;
97
+ /**
98
+ * Tries every rack tile (and blank interpretation) at an empty cell,
99
+ * recursing further. Iterates the state's arcs instead of the alphabet:
100
+ * deep GADDAG states have very few arcs, so this is much cheaper than
101
+ * probing every letter.
102
+ */
103
+ private placeAt;
104
+ private pushPlacement;
105
+ private popPlacement;
106
+ private afterPlacement;
107
+ /**
108
+ * Records a word that ends at the anchor (a pure reversed-word GADDAG path).
109
+ * Words spanning a single cell are skipped: a lone tile's word is the
110
+ * perpendicular one, generated by the other pass.
111
+ */
112
+ private recordLeftIfWord;
113
+ private record;
114
+ private hasInvalidDigraph;
115
+ private characterAt;
116
+ }