@scrabble-solver/scrabble-solver 2.12.2 → 2.12.4

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 (86) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +6 -6
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/eslint/.cache_8dgz12 +1 -1
  5. package/.next/cache/next-server.js.nft.json +1 -1
  6. package/.next/cache/webpack/client-production/0.pack +0 -0
  7. package/.next/cache/webpack/client-production/index.pack +0 -0
  8. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  9. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  10. package/.next/cache/webpack/server-production/0.pack +0 -0
  11. package/.next/cache/webpack/server-production/index.pack +0 -0
  12. package/.next/next-server.js.nft.json +1 -1
  13. package/.next/prerender-manifest.json +1 -1
  14. package/.next/routes-manifest.json +1 -1
  15. package/.next/server/chunks/131.js +478 -106
  16. package/.next/server/chunks/277.js +1132 -976
  17. package/.next/server/chunks/44.js +37 -9
  18. package/.next/server/chunks/865.js +597 -169
  19. package/.next/server/chunks/911.js +894 -773
  20. package/.next/server/middleware-build-manifest.js +1 -1
  21. package/.next/server/pages/404.html +1 -1
  22. package/.next/server/pages/404.js.nft.json +1 -1
  23. package/.next/server/pages/500.html +1 -1
  24. package/.next/server/pages/_app.js +3 -1
  25. package/.next/server/pages/_app.js.nft.json +1 -1
  26. package/.next/server/pages/_document.js.nft.json +1 -1
  27. package/.next/server/pages/api/dictionary/[locale]/[word].js +4 -2
  28. package/.next/server/pages/api/solve.js +23 -19
  29. package/.next/server/pages/api/verify.js +8 -5
  30. package/.next/server/pages/index.html +1 -1
  31. package/.next/server/pages/index.js +42 -55
  32. package/.next/server/pages/index.js.nft.json +1 -1
  33. package/.next/server/pages/index.json +1 -1
  34. package/.next/server/pages-manifest.json +2 -2
  35. package/.next/static/{9dmPfnTc_AQTHBPvL7xQe → 8oRk1nUMQYFWmhu4SExQI}/_buildManifest.js +1 -1
  36. package/.next/static/chunks/pages/_app-e7f3d1c9c09c8f91.js +32 -0
  37. package/.next/static/chunks/pages/index-82b2939158c7729f.js +1 -0
  38. package/.next/static/css/4e8b47fe382a8a8f.css +2 -0
  39. package/.next/static/css/cfae5256f1689f57.css +1 -0
  40. package/.next/trace +51 -52
  41. package/package.json +9 -9
  42. package/src/components/Board/components/Cell/Cell.module.scss +4 -12
  43. package/src/components/Board/components/Cell/Cell.tsx +4 -0
  44. package/src/components/Board/hooks/useBackgroundImage.tsx +23 -0
  45. package/src/components/PlainTiles/lib/createPlainTile.ts +4 -4
  46. package/src/components/Radio/Radio.module.scss +4 -0
  47. package/src/components/Radio/Radio.tsx +1 -0
  48. package/src/components/SeoMessage/SeoMessage.tsx +3 -3
  49. package/src/hooks/useAppLayout.ts +1 -2
  50. package/src/hooks/useLocalStorage.ts +5 -5
  51. package/src/i18n/constants.ts +31 -61
  52. package/src/lib/extractCharacters.test.ts +3 -3
  53. package/src/lib/extractCharactersByCase.test.ts +3 -3
  54. package/src/lib/getCellSize.ts +2 -2
  55. package/src/modals/MenuModal/MenuModal.module.scss +0 -1
  56. package/src/modals/MenuModal/MenuModal.tsx +3 -3
  57. package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +16 -7
  58. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +1 -4
  59. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +8 -8
  60. package/src/pages/_app.tsx +3 -1
  61. package/src/pages/api/dictionary/[locale]/[word].ts +6 -3
  62. package/src/pages/api/solve.ts +11 -7
  63. package/src/pages/api/verify.ts +11 -7
  64. package/src/pages/index.module.scss +0 -1
  65. package/src/parameters/index.ts +4 -6
  66. package/src/sdk/solve.ts +3 -3
  67. package/src/sdk/verify.ts +3 -3
  68. package/src/service-worker/routeSolveRequests.ts +3 -3
  69. package/src/state/localStorage.ts +6 -6
  70. package/src/state/sagas.ts +18 -7
  71. package/src/state/selectors.ts +3 -3
  72. package/src/state/slices/boardInitialState.ts +3 -3
  73. package/src/state/slices/boardSlice.ts +20 -6
  74. package/src/state/slices/settingsInitialState.ts +3 -4
  75. package/src/state/slices/settingsSlice.ts +5 -5
  76. package/src/styles/variables.scss +0 -9
  77. package/.next/static/chunks/pages/_app-02851b06b95b19cb.js +0 -32
  78. package/.next/static/chunks/pages/index-0ba5607d1aad8a09.js +0 -1
  79. package/.next/static/css/09dfdea53eba31a9.css +0 -2
  80. package/.next/static/css/60e8258da7362a1a.css +0 -1
  81. package/src/i18n/i18n.module.scss +0 -27
  82. package/src/modals/SettingsModal/components/ConfigSetting/options.ts +0 -19
  83. package/src/modals/SettingsModal/components/ConfigSetting/types.ts +0 -9
  84. package/src/modals/SettingsModal/components/InputModeSetting/types.ts +0 -7
  85. package/src/modals/SettingsModal/components/LocaleSetting/types.ts +0 -9
  86. /package/.next/static/{9dmPfnTc_AQTHBPvL7xQe → 8oRk1nUMQYFWmhu4SExQI}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.12.2",
3
+ "version": "2.12.4",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "engines": {
6
6
  "node": ">=16"
@@ -31,13 +31,13 @@
31
31
  "@floating-ui/react": "^0.22.3",
32
32
  "@kamilmielnik/trie": "^2.0.1",
33
33
  "@reduxjs/toolkit": "^1.9.3",
34
- "@scrabble-solver/configs": "^2.12.2",
35
- "@scrabble-solver/constants": "^2.12.2",
36
- "@scrabble-solver/dictionaries": "^2.12.2",
37
- "@scrabble-solver/logger": "^2.12.2",
38
- "@scrabble-solver/solver": "^2.12.2",
39
- "@scrabble-solver/types": "^2.12.2",
40
- "@scrabble-solver/word-definitions": "^2.12.2",
34
+ "@scrabble-solver/configs": "^2.12.4",
35
+ "@scrabble-solver/constants": "^2.12.4",
36
+ "@scrabble-solver/dictionaries": "^2.12.4",
37
+ "@scrabble-solver/logger": "^2.12.4",
38
+ "@scrabble-solver/solver": "^2.12.4",
39
+ "@scrabble-solver/types": "^2.12.4",
40
+ "@scrabble-solver/word-definitions": "^2.12.4",
41
41
  "classnames": "^2.3.2",
42
42
  "include-media": "^2.0.0",
43
43
  "include-media-query-builder": "^1.1.0",
@@ -74,5 +74,5 @@
74
74
  "sass": "^1.61.0",
75
75
  "workbox-webpack-plugin": "^6.5.4"
76
76
  },
77
- "gitHead": "22ec8b756bf1f50d94028c8e69ccc269c23e5759"
77
+ "gitHead": "b7af635998fc55dcfb05c009d856819550cb20d8"
78
78
  }
@@ -17,18 +17,14 @@
17
17
  }
18
18
 
19
19
  [dir='ltr'] & {
20
- &:nth-child(15n + 1),
21
- &:nth-child(15n + 2),
22
- &:nth-child(15n + 3) {
20
+ &.first3 {
23
21
  input {
24
22
  left: 0;
25
23
  clip-path: polygon(0 (100% / 3), (100% / 3) (100% / 3), (100% / 3) (200% / 3), 0 (200% / 3));
26
24
  }
27
25
  }
28
26
 
29
- &:nth-last-child(15n + 1),
30
- &:nth-last-child(15n + 2),
31
- &:nth-last-child(15n + 3) {
27
+ &.last3 {
32
28
  input {
33
29
  left: -200%;
34
30
  clip-path: polygon((200% / 3) (100% / 3), 100% (100% / 3), 100% (200% / 3), (200% / 3) (200% / 3));
@@ -37,9 +33,7 @@
37
33
  }
38
34
 
39
35
  [dir='rtl'] & {
40
- &:nth-child(15n + 1),
41
- &:nth-child(15n + 2),
42
- &:nth-child(15n + 3) {
36
+ &.first3 {
43
37
  input {
44
38
  left: -200%;
45
39
  right: 0;
@@ -47,9 +41,7 @@
47
41
  }
48
42
  }
49
43
 
50
- &:nth-last-child(15n + 1),
51
- &:nth-last-child(15n + 2),
52
- &:nth-last-child(15n + 3) {
44
+ &.last3 {
53
45
  input {
54
46
  left: 0;
55
47
  right: -200%;
@@ -13,6 +13,7 @@ import {
13
13
 
14
14
  import {
15
15
  selectCellIsValid,
16
+ selectConfig,
16
17
  selectInputMode,
17
18
  selectLocale,
18
19
  selectTilePoints,
@@ -52,6 +53,7 @@ const Cell: FunctionComponent<Props> = ({
52
53
  const { tile, x, y } = cell;
53
54
  const translate = useTranslate();
54
55
  const locale = useTypedSelector(selectLocale);
56
+ const config = useTypedSelector(selectConfig);
55
57
  const inputMode = useTypedSelector(selectInputMode);
56
58
  const points = useTypedSelector((state) => selectTilePoints(state, cell.tile));
57
59
  const isValid = useTypedSelector((state) => selectCellIsValid(state, cell));
@@ -98,6 +100,8 @@ const Cell: FunctionComponent<Props> = ({
98
100
  y: (y + 1).toLocaleString(locale),
99
101
  })}
100
102
  className={classNames(styles.tile, className, {
103
+ [styles.first3]: x < 3,
104
+ [styles.last3]: config.boardWidth - x - 1 < 3,
101
105
  [styles.sharpTopLeft]: cellTop?.hasTile() || cellLeft?.hasTile(),
102
106
  [styles.sharpTopRight]: cellTop?.hasTile() || cellRight?.hasTile(),
103
107
  [styles.sharpBottomLeft]: cellBottom?.hasTile() || cellLeft?.hasTile(),
@@ -21,6 +21,7 @@ const VERTICAL_LINE = 'v';
21
21
  const BONUS = 'b';
22
22
  const BONUS_WORD_2 = 'b2';
23
23
  const BONUS_WORD_3 = 'b3';
24
+ const BONUS_WORD_4 = 'b4';
24
25
  const CELL_FILTER = 'c';
25
26
 
26
27
  const useBackgroundImage = () => {
@@ -41,6 +42,7 @@ const useBackgroundImage = () => {
41
42
  const characterBonuses = config.bonuses.filter((bonus) => bonus.type === BONUS_CHARACTER);
42
43
  const word2Bonuses = config.bonuses.filter((bonus) => bonus.type === BONUS_WORD && bonus.multiplier === 2);
43
44
  const word3Bonuses = config.bonuses.filter((bonus) => bonus.type === BONUS_WORD && bonus.multiplier === 3);
45
+ const word4Bonuses = config.bonuses.filter((bonus) => bonus.type === BONUS_WORD && bonus.multiplier === 4);
44
46
 
45
47
  const getX = (point: Point): number => point.x * (cellSize + BORDER_WIDTH);
46
48
 
@@ -101,6 +103,23 @@ const useBackgroundImage = () => {
101
103
  </text>
102
104
  </symbol>
103
105
 
106
+ <symbol id={BONUS_WORD_4}>
107
+ <rect height={bonusSize} rx={borderRadius} width={bonusSize} x={bonusOffset} y={bonusOffset} />
108
+
109
+ <text
110
+ dominantBaseline="central"
111
+ fill="white"
112
+ fontFamily="system-ui, sans-serif"
113
+ fontSize={fontSize}
114
+ fontWeight="bold"
115
+ textAnchor="middle"
116
+ x={fontOffset}
117
+ y={fontOffset}
118
+ >
119
+ x4
120
+ </text>
121
+ </symbol>
122
+
104
123
  <symbol id={CELL_FILTER}>
105
124
  <rect
106
125
  fill={COLOR_FILTERED}
@@ -137,6 +156,10 @@ const useBackgroundImage = () => {
137
156
  <use fill={getBonusColor(bonus)} key={index} href={`#${BONUS_WORD_3}`} x={getX(bonus)} y={getY(bonus)} />
138
157
  ))}
139
158
 
159
+ {word4Bonuses.map((bonus, index) => (
160
+ <use fill={getBonusColor(bonus)} key={index} href={`#${BONUS_WORD_4}`} x={getX(bonus)} y={getY(bonus)} />
161
+ ))}
162
+
140
163
  <rect
141
164
  fill={COLOR_BONUS_START}
142
165
  height={bonusSize}
@@ -1,11 +1,11 @@
1
- import { literaki } from '@scrabble-solver/configs';
2
- import { Locale } from '@scrabble-solver/types';
1
+ import { getConfig } from '@scrabble-solver/configs';
2
+ import { Game, Locale } from '@scrabble-solver/types';
3
3
 
4
4
  import {
5
5
  PLAIN_TILES_COLOR_DEFAULT,
6
6
  PLAIN_TILES_POINTS_COLORS,
7
- PLAIN_TILES_TILE_MAX_SCATTER,
8
7
  PLAIN_TILES_TILE_MAX_ROTATE,
8
+ PLAIN_TILES_TILE_MAX_SCATTER,
9
9
  PLAIN_TILES_TILE_SIZE,
10
10
  } from 'parameters';
11
11
 
@@ -16,7 +16,7 @@ import getY from './getY';
16
16
  import randomize from './randomize';
17
17
 
18
18
  const createPlainTile = ({ cellIndex, character, color, rowIndex, showPoints }: CreatePlainTileOptions): PlainTile => {
19
- const configPoints = literaki[Locale.EN_US].getCharacterPoints(character.toLowerCase());
19
+ const configPoints = getConfig(Game.Literaki, Locale.EN_US).getCharacterPoints(character.toLowerCase());
20
20
  const points = showPoints ? configPoints : undefined;
21
21
  const defaultColor =
22
22
  typeof configPoints === 'number' ? PLAIN_TILES_POINTS_COLORS[configPoints] : PLAIN_TILES_COLOR_DEFAULT;
@@ -22,6 +22,10 @@ $radio-box-size: $radio-size + 2 * $radio-inner-border;
22
22
  }
23
23
  }
24
24
  }
25
+
26
+ &.disabled {
27
+ opacity: var(--opacity--disabled);
28
+ }
25
29
  }
26
30
 
27
31
  .input {
@@ -17,6 +17,7 @@ const Radio: FunctionComponent<Props> = ({ checked, children, className, disable
17
17
  <label
18
18
  className={classNames(styles.radio, className, {
19
19
  [styles.checked]: checked,
20
+ [styles.disabled]: disabled,
20
21
  })}
21
22
  >
22
23
  <input
@@ -10,9 +10,9 @@ const INVISIBLE_STYLE: CSSProperties = {
10
10
 
11
11
  const SeoMessage: FunctionComponent = () => (
12
12
  <p style={INVISIBLE_STYLE}>
13
- Scrabble Solver 2 is a free and open-source analysis tool for Scrabble and Literaki. Quickly find top scoring words
14
- using given letters and board state. Available in English, French, German, Polish & Spanish. Source code is
15
- available on GitHub - contributions are welcome!
13
+ Scrabble Solver 2 is a free and open-source analysis tool for Scrabble, Super Scrabble &amp; Literaki. Quickly find
14
+ top scoring words using given letters and board state. Available in English, French, German, Polish & Spanish.
15
+ Source code is available on GitHub - contributions are welcome!
16
16
  </p>
17
17
  );
18
18
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  import {
4
4
  BOARD_TILE_SIZE_MAX,
5
- BOARD_TILE_SIZE_MIN,
6
5
  BORDER_WIDTH,
7
6
  BUTTON_HEIGHT,
8
7
  COMPONENTS_SPACING,
@@ -47,7 +46,7 @@ const useAppLayout = () => {
47
46
  : Math.max(solverHeight - bottomContainerHeight, 0);
48
47
  const cellWidth = (maxBoardWidth - (config.boardWidth + 1) * BORDER_WIDTH) / config.boardWidth;
49
48
  const cellHeight = (maxBoardHeight - (config.boardHeight + 1) * BORDER_WIDTH) / config.boardHeight;
50
- const cellSize = Math.min(Math.max(Math.min(cellWidth, cellHeight), BOARD_TILE_SIZE_MIN), BOARD_TILE_SIZE_MAX);
49
+ const cellSize = Math.min(Math.min(cellWidth, cellHeight), BOARD_TILE_SIZE_MAX);
51
50
  const boardSize = (cellSize + BORDER_WIDTH) * config.boardWidth + BORDER_WIDTH;
52
51
  const maxControlsWidth = tileSize * config.maximumCharactersCount + 2 * BORDER_WIDTH;
53
52
  const showResultsInModal = isLessThanL;
@@ -4,7 +4,7 @@ import {
4
4
  localStorage,
5
5
  selectAutoGroupTiles,
6
6
  selectBoard,
7
- selectConfigId,
7
+ selectGame,
8
8
  selectInputMode,
9
9
  selectLocale,
10
10
  selectRack,
@@ -14,7 +14,7 @@ import {
14
14
  const useLocalStorage = () => {
15
15
  const autoGroupTiles = useTypedSelector(selectAutoGroupTiles);
16
16
  const board = useTypedSelector(selectBoard);
17
- const configId = useTypedSelector(selectConfigId);
17
+ const game = useTypedSelector(selectGame);
18
18
  const inputMode = useTypedSelector(selectInputMode);
19
19
  const locale = useTypedSelector(selectLocale);
20
20
  const rack = useTypedSelector(selectRack);
@@ -32,10 +32,10 @@ const useLocalStorage = () => {
32
32
  }, [board]);
33
33
 
34
34
  useEffect(() => {
35
- if (configId) {
36
- localStorage.setConfigId(configId);
35
+ if (game) {
36
+ localStorage.setGame(game);
37
37
  }
38
- }, [configId]);
38
+ }, [game]);
39
39
 
40
40
  useEffect(() => {
41
41
  if (inputMode) {
@@ -4,12 +4,14 @@ import { FunctionComponent, SVGAttributes } from 'react';
4
4
 
5
5
  import { FlagDe, FlagEs, FlagFa, FlagFr, FlagGb, FlagPl, FlagUs } from 'icons';
6
6
 
7
- import styles from './i18n.module.scss';
8
-
9
7
  interface LocaleFeatures {
10
8
  comma: string;
11
9
  consonants: boolean;
12
10
  direction: 'ltr' | 'rtl';
11
+ Icon: FunctionComponent<SVGAttributes<SVGElement>>;
12
+ label: string;
13
+ locale: Locale;
14
+ name: string;
13
15
  separator: string;
14
16
  vowels: boolean;
15
17
  }
@@ -19,6 +21,10 @@ export const LOCALE_FEATURES: Record<Locale, LocaleFeatures> = {
19
21
  comma: COMMA_LATIN,
20
22
  consonants: true,
21
23
  direction: 'ltr',
24
+ Icon: FlagDe,
25
+ label: 'Deutsch',
26
+ locale: Locale.DE_DE,
27
+ name: 'German',
22
28
  separator: `${COMMA_LATIN} `,
23
29
  vowels: true,
24
30
  },
@@ -26,6 +32,10 @@ export const LOCALE_FEATURES: Record<Locale, LocaleFeatures> = {
26
32
  comma: COMMA_LATIN,
27
33
  consonants: true,
28
34
  direction: 'ltr',
35
+ Icon: FlagGb,
36
+ label: 'English (GB)',
37
+ locale: Locale.EN_GB,
38
+ name: 'English (GB)',
29
39
  separator: `${COMMA_LATIN} `,
30
40
  vowels: true,
31
41
  },
@@ -33,6 +43,10 @@ export const LOCALE_FEATURES: Record<Locale, LocaleFeatures> = {
33
43
  comma: COMMA_LATIN,
34
44
  consonants: true,
35
45
  direction: 'ltr',
46
+ Icon: FlagUs,
47
+ label: 'English (US)',
48
+ locale: Locale.EN_US,
49
+ name: 'English (US)',
36
50
  separator: `${COMMA_LATIN} `,
37
51
  vowels: true,
38
52
  },
@@ -40,6 +54,10 @@ export const LOCALE_FEATURES: Record<Locale, LocaleFeatures> = {
40
54
  comma: COMMA_LATIN,
41
55
  consonants: true,
42
56
  direction: 'ltr',
57
+ Icon: FlagEs,
58
+ label: 'Español',
59
+ locale: Locale.ES_ES,
60
+ name: 'Spanish',
43
61
  separator: `${COMMA_LATIN} `,
44
62
  vowels: true,
45
63
  },
@@ -47,6 +65,10 @@ export const LOCALE_FEATURES: Record<Locale, LocaleFeatures> = {
47
65
  comma: COMMA_ARABIC,
48
66
  consonants: false,
49
67
  direction: 'rtl',
68
+ Icon: FlagFa,
69
+ label: 'فارسی',
70
+ locale: Locale.FA_IR,
71
+ name: 'Persian',
50
72
  separator: `${COMMA_ARABIC} `,
51
73
  vowels: false,
52
74
  },
@@ -54,6 +76,10 @@ export const LOCALE_FEATURES: Record<Locale, LocaleFeatures> = {
54
76
  comma: COMMA_LATIN,
55
77
  consonants: true,
56
78
  direction: 'ltr',
79
+ Icon: FlagFr,
80
+ label: 'Français',
81
+ locale: Locale.FR_FR,
82
+ name: 'French',
57
83
  separator: `${COMMA_LATIN} `,
58
84
  vowels: true,
59
85
  },
@@ -61,67 +87,11 @@ export const LOCALE_FEATURES: Record<Locale, LocaleFeatures> = {
61
87
  comma: COMMA_LATIN,
62
88
  consonants: true,
63
89
  direction: 'ltr',
64
- separator: `${COMMA_LATIN} `,
65
- vowels: true,
66
- },
67
- };
68
-
69
- interface Flag {
70
- className: string;
71
- Icon: FunctionComponent<SVGAttributes<SVGElement>>;
72
- label: string;
73
- name: string;
74
- value: Locale;
75
- }
76
-
77
- export const LOCALE_FLAGS: Record<Locale, Flag> = {
78
- [Locale.DE_DE]: {
79
- className: styles.de,
80
- Icon: FlagDe,
81
- label: 'Deutsch',
82
- name: 'German',
83
- value: Locale.DE_DE,
84
- },
85
- [Locale.EN_GB]: {
86
- className: styles.gb,
87
- Icon: FlagGb,
88
- label: 'English (GB)',
89
- name: 'English (GB)',
90
- value: Locale.EN_GB,
91
- },
92
- [Locale.EN_US]: {
93
- className: styles.us,
94
- Icon: FlagUs,
95
- label: 'English (US)',
96
- name: 'English (US)',
97
- value: Locale.EN_US,
98
- },
99
- [Locale.ES_ES]: {
100
- className: styles.es,
101
- Icon: FlagEs,
102
- label: 'Español',
103
- name: 'Spanish',
104
- value: Locale.ES_ES,
105
- },
106
- [Locale.FA_IR]: {
107
- className: styles.fa,
108
- Icon: FlagFa,
109
- label: 'فارسی',
110
- name: 'Persian',
111
- value: Locale.FA_IR,
112
- },
113
- [Locale.FR_FR]: {
114
- className: styles.fr,
115
- Icon: FlagFr,
116
- label: 'Français',
117
- name: 'French',
118
- value: Locale.FR_FR,
119
- },
120
- [Locale.PL_PL]: {
121
- className: styles.pl,
122
90
  Icon: FlagPl,
123
91
  label: 'Polski',
92
+ locale: Locale.PL_PL,
124
93
  name: 'Polish',
125
- value: Locale.PL_PL,
94
+ separator: `${COMMA_LATIN} `,
95
+ vowels: true,
126
96
  },
127
97
  };
@@ -1,6 +1,6 @@
1
- import { scrabble } from '@scrabble-solver/configs';
1
+ import { getConfig } from '@scrabble-solver/configs';
2
2
  import { BLANK } from '@scrabble-solver/constants';
3
- import { Locale } from '@scrabble-solver/types';
3
+ import { Game, Locale } from '@scrabble-solver/types';
4
4
 
5
5
  import extractCharacters from './extractCharacters';
6
6
 
@@ -16,7 +16,7 @@ const tests = [
16
16
 
17
17
  describe('extractCharacters', () => {
18
18
  const locale = Locale.ES_ES;
19
- const config = scrabble[locale];
19
+ const config = getConfig(Game.Scrabble, locale);
20
20
 
21
21
  for (const { input, expected } of tests) {
22
22
  it(`[${locale}] "${input}"`, () => {
@@ -1,6 +1,6 @@
1
- import { scrabble } from '@scrabble-solver/configs';
1
+ import { getConfig } from '@scrabble-solver/configs';
2
2
  import { BLANK } from '@scrabble-solver/constants';
3
- import { Locale } from '@scrabble-solver/types';
3
+ import { Game, Locale } from '@scrabble-solver/types';
4
4
 
5
5
  import extractCharactersByCase from './extractCharactersByCase';
6
6
 
@@ -21,7 +21,7 @@ const tests = [
21
21
 
22
22
  describe('extractCharactersByCase', () => {
23
23
  const locale = Locale.ES_ES;
24
- const config = scrabble[locale];
24
+ const config = getConfig(Game.Scrabble, locale);
25
25
 
26
26
  for (const { input, expected } of tests) {
27
27
  it(`[${locale}] "${input}"`, () => {
@@ -1,12 +1,12 @@
1
1
  import { Config } from '@scrabble-solver/types';
2
2
 
3
- import { BOARD_CELL_BORDER_WIDTH, BOARD_TILE_SIZE_MAX, BOARD_TILE_SIZE_MIN } from 'parameters';
3
+ import { BOARD_CELL_BORDER_WIDTH, BOARD_TILE_SIZE_MAX } from 'parameters';
4
4
 
5
5
  const getCellSize = (config: Config, width: number, height: number): number => {
6
6
  const maxWidth = (width - BOARD_CELL_BORDER_WIDTH) / config.boardWidth - BOARD_CELL_BORDER_WIDTH;
7
7
  const maxHeight = (height - BOARD_CELL_BORDER_WIDTH) / config.boardHeight - BOARD_CELL_BORDER_WIDTH;
8
8
  const cellSize = Math.min(maxWidth, maxHeight);
9
- return Math.floor(Math.min(Math.max(cellSize, BOARD_TILE_SIZE_MIN), BOARD_TILE_SIZE_MAX));
9
+ return Math.floor(Math.min(cellSize, BOARD_TILE_SIZE_MAX));
10
10
  };
11
11
 
12
12
  export default getCellSize;
@@ -42,7 +42,6 @@
42
42
 
43
43
  .flag {
44
44
  position: fixed;
45
- width: calc(var(--button--icon--size) * var(--aspect-ratio));
46
45
  height: var(--button--icon--size);
47
46
  border-radius: var(--border--radius);
48
47
  box-shadow: 0 0 0 1px var(--box-shadow--color);
@@ -1,7 +1,7 @@
1
1
  import { FunctionComponent, memo } from 'react';
2
2
 
3
3
  import { Button, Modal } from 'components';
4
- import { LOCALE_FLAGS } from 'i18n';
4
+ import { LOCALE_FEATURES } from 'i18n';
5
5
  import { BookHalf, CardChecklist, Cog, Github, Sack } from 'icons';
6
6
  import { GITHUB_PROJECT_URL } from 'parameters';
7
7
  import { selectLocale, useTranslate, useTypedSelector } from 'state';
@@ -29,7 +29,7 @@ const MenuModal: FunctionComponent<Props> = ({
29
29
  }) => {
30
30
  const translate = useTranslate();
31
31
  const locale = useTypedSelector(selectLocale);
32
- const Flag = LOCALE_FLAGS[locale];
32
+ const { Icon } = LOCALE_FEATURES[locale];
33
33
 
34
34
  return (
35
35
  <Modal className={className} isOpen={isOpen} title={translate('menu')} onClose={onClose}>
@@ -64,7 +64,7 @@ const MenuModal: FunctionComponent<Props> = ({
64
64
  <Button aria-label={translate('settings')} className={styles.button} Icon={Cog} onClick={onShowSettings}>
65
65
  <div className={styles.settings}>
66
66
  <div className={styles.settingsLabel}>{translate('settings')}</div>
67
- <Flag.Icon className={styles.flag} />
67
+ <Icon className={styles.flag} />
68
68
  </div>
69
69
  </Button>
70
70
  </Modal>
@@ -1,11 +1,12 @@
1
+ import { games, hasConfig } from '@scrabble-solver/configs';
2
+ import { isGame } from '@scrabble-solver/types';
1
3
  import { ChangeEvent, FunctionComponent } from 'react';
2
4
  import { useDispatch } from 'react-redux';
3
5
 
4
6
  import { Radio } from 'components';
5
- import { selectConfigId, settingsSlice, useTypedSelector } from 'state';
7
+ import { selectGame, selectLocale, settingsSlice, useTypedSelector } from 'state';
6
8
 
7
9
  import styles from './ConfigSetting.module.scss';
8
- import options from './options';
9
10
 
10
11
  interface Props {
11
12
  className?: string;
@@ -14,21 +15,29 @@ interface Props {
14
15
 
15
16
  const ConfigSetting: FunctionComponent<Props> = ({ className, disabled }) => {
16
17
  const dispatch = useDispatch();
17
- const configId = useTypedSelector(selectConfigId);
18
+ const game = useTypedSelector(selectGame);
19
+ const locale = useTypedSelector(selectLocale);
20
+ const options = Object.values(games).map((gameConfig) => ({
21
+ disabled: !hasConfig(gameConfig.game, locale),
22
+ label: gameConfig.name,
23
+ value: gameConfig.game,
24
+ }));
18
25
 
19
26
  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
20
- dispatch(settingsSlice.actions.changeConfigId(event.target.value));
27
+ if (isGame(event.target.value)) {
28
+ dispatch(settingsSlice.actions.changeGame(event.target.value));
29
+ }
21
30
  };
22
31
 
23
32
  return (
24
33
  <div className={className}>
25
34
  {options.map((option) => (
26
35
  <Radio
27
- checked={configId === option.value}
36
+ checked={game === option.value}
28
37
  className={styles.option}
29
- disabled={disabled}
38
+ disabled={disabled || option.disabled}
30
39
  key={option.value}
31
- name="configId"
40
+ name="game"
32
41
  value={option.value}
33
42
  onChange={handleChange}
34
43
  >
@@ -13,10 +13,7 @@
13
13
  }
14
14
 
15
15
  .flag {
16
- --height: 32px;
17
-
18
- width: calc(var(--height) * var(--aspect-ratio));
19
- height: var(--height);
16
+ height: 32px;
20
17
  border-radius: var(--border--radius);
21
18
  box-shadow: var(--box-shadow);
22
19
  transition: var(--transition);
@@ -4,7 +4,7 @@ import { ChangeEvent, FunctionComponent } from 'react';
4
4
  import { useDispatch } from 'react-redux';
5
5
 
6
6
  import { Radio } from 'components';
7
- import { LOCALE_FLAGS } from 'i18n';
7
+ import { LOCALE_FEATURES } from 'i18n';
8
8
  import { selectLocale, settingsSlice, useTypedSelector } from 'state';
9
9
 
10
10
  import styles from './LocaleSetting.module.scss';
@@ -14,7 +14,7 @@ interface Props {
14
14
  disabled: boolean;
15
15
  }
16
16
 
17
- const OPTIONS = Object.values(LOCALE_FLAGS).sort((a, b) => a.name.localeCompare(b.name));
17
+ const OPTIONS = Object.values(LOCALE_FEATURES).sort((a, b) => a.name.localeCompare(b.name));
18
18
 
19
19
  const LocaleSetting: FunctionComponent<Props> = ({ className, disabled }) => {
20
20
  const dispatch = useDispatch();
@@ -29,18 +29,18 @@ const LocaleSetting: FunctionComponent<Props> = ({ className, disabled }) => {
29
29
  <div className={className}>
30
30
  {OPTIONS.map(({ Icon, ...option }) => (
31
31
  <Radio
32
- checked={locale === option.value}
32
+ checked={locale === option.locale}
33
33
  className={classNames(styles.option, className, {
34
- [styles.checked]: locale === option.value,
34
+ [styles.checked]: locale === option.locale,
35
35
  })}
36
36
  disabled={disabled}
37
- key={option.value}
37
+ key={option.locale}
38
38
  name="locale"
39
- value={option.value}
39
+ value={option.locale}
40
40
  onChange={handleChange}
41
41
  >
42
- <span className={classNames(styles.label, option.className)}>
43
- <Icon className={classNames(styles.flag, option.className)} />
42
+ <span className={styles.label}>
43
+ <Icon className={styles.flag} />
44
44
 
45
45
  <span>{option.label}</span>
46
46
  </span>
@@ -10,12 +10,14 @@ import 'styles/global.scss';
10
10
 
11
11
  const DESCRIPTION =
12
12
  // eslint-disable-next-line max-len
13
- 'Scrabble Solver 2 - Free and open-source analysis tool for Scrabble and Literaki. Quickly find top scoring words using given letters and board state. Available in English, French, German, Polish & Spanish.';
13
+ 'Scrabble Solver 2 - Free and open-source analysis tool for Scrabble, Super Scrabble & Literaki. Quickly find top scoring words using given letters and board state. Available in English, French, German, Polish & Spanish.';
14
14
 
15
15
  const KEYWORDS = [
16
16
  'Scrabble Solver',
17
17
  'Scrabble',
18
18
  'Solver',
19
+ 'Super Scrabble',
20
+ 'Super Scrabble Solver',
19
21
  'Board',
20
22
  'Open-source',
21
23
  'Open',
@@ -1,8 +1,8 @@
1
- import { scrabble } from '@scrabble-solver/configs';
1
+ import { games } from '@scrabble-solver/configs';
2
2
  import { COMMA_ARABIC, COMMA_LATIN } from '@scrabble-solver/constants';
3
3
  import { dictionaries } from '@scrabble-solver/dictionaries';
4
4
  import logger from '@scrabble-solver/logger';
5
- import { isLocale, Locale } from '@scrabble-solver/types';
5
+ import { Locale, isLocale } from '@scrabble-solver/types';
6
6
  import { getWordDefinition } from '@scrabble-solver/word-definitions';
7
7
  import { NextApiRequest, NextApiResponse } from 'next';
8
8
 
@@ -13,7 +13,10 @@ interface RequestData {
13
13
  words: string[];
14
14
  }
15
15
 
16
- const MAXIMUM_COLLISIONS_COUNT = scrabble['en-US'].maximumCharactersCount;
16
+ const MAXIMUM_COLLISIONS_COUNT = Object.values(games).reduce(
17
+ (result, game) => Math.max(result, game.maximumCharactersCount),
18
+ 0,
19
+ );
17
20
  const MAXIMUM_WORDS_COUNT = MAXIMUM_COLLISIONS_COUNT + 1;
18
21
 
19
22
  const dictionary = async (request: NextApiRequest, response: NextApiResponse): Promise<void> => {