@scrabble-solver/scrabble-solver 2.12.2 → 2.12.3
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.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +6 -6
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/eslint/.cache_8dgz12 +1 -1
- package/.next/cache/next-server.js.nft.json +1 -1
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/131.js +478 -106
- package/.next/server/chunks/277.js +1132 -976
- package/.next/server/chunks/44.js +36 -6
- package/.next/server/chunks/865.js +478 -106
- package/.next/server/chunks/911.js +894 -773
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/404.js.nft.json +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/pages/_app.js +3 -1
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale]/[word].js +4 -2
- package/.next/server/pages/api/solve.js +23 -19
- package/.next/server/pages/api/verify.js +8 -5
- package/.next/server/pages/index.html +1 -1
- package/.next/server/pages/index.js +42 -55
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/static/chunks/pages/_app-e7f3d1c9c09c8f91.js +32 -0
- package/.next/static/chunks/pages/index-82b2939158c7729f.js +1 -0
- package/.next/static/css/4e8b47fe382a8a8f.css +2 -0
- package/.next/static/css/cfae5256f1689f57.css +1 -0
- package/.next/static/{9dmPfnTc_AQTHBPvL7xQe → wNIKOJJzkMSJYb2nOL21o}/_buildManifest.js +1 -1
- package/.next/trace +51 -52
- package/package.json +9 -9
- package/src/components/Board/components/Cell/Cell.module.scss +4 -12
- package/src/components/Board/components/Cell/Cell.tsx +4 -0
- package/src/components/Board/hooks/useBackgroundImage.tsx +23 -0
- package/src/components/PlainTiles/lib/createPlainTile.ts +4 -4
- package/src/components/Radio/Radio.module.scss +4 -0
- package/src/components/Radio/Radio.tsx +1 -0
- package/src/components/SeoMessage/SeoMessage.tsx +3 -3
- package/src/hooks/useAppLayout.ts +1 -2
- package/src/hooks/useLocalStorage.ts +5 -5
- package/src/i18n/constants.ts +31 -61
- package/src/lib/extractCharacters.test.ts +3 -3
- package/src/lib/extractCharactersByCase.test.ts +3 -3
- package/src/lib/getCellSize.ts +2 -2
- package/src/modals/MenuModal/MenuModal.module.scss +0 -1
- package/src/modals/MenuModal/MenuModal.tsx +3 -3
- package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +16 -7
- package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +1 -4
- package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +8 -8
- package/src/pages/_app.tsx +3 -1
- package/src/pages/api/dictionary/[locale]/[word].ts +6 -3
- package/src/pages/api/solve.ts +11 -7
- package/src/pages/api/verify.ts +11 -7
- package/src/pages/index.module.scss +0 -1
- package/src/parameters/index.ts +4 -6
- package/src/sdk/solve.ts +3 -3
- package/src/sdk/verify.ts +3 -3
- package/src/service-worker/routeSolveRequests.ts +3 -3
- package/src/state/localStorage.ts +6 -6
- package/src/state/sagas.ts +18 -7
- package/src/state/selectors.ts +3 -3
- package/src/state/slices/boardInitialState.ts +3 -3
- package/src/state/slices/boardSlice.ts +20 -6
- package/src/state/slices/settingsInitialState.ts +3 -4
- package/src/state/slices/settingsSlice.ts +5 -5
- package/src/styles/variables.scss +0 -9
- package/.next/static/chunks/pages/_app-02851b06b95b19cb.js +0 -32
- package/.next/static/chunks/pages/index-0ba5607d1aad8a09.js +0 -1
- package/.next/static/css/09dfdea53eba31a9.css +0 -2
- package/.next/static/css/60e8258da7362a1a.css +0 -1
- package/src/i18n/i18n.module.scss +0 -27
- package/src/modals/SettingsModal/components/ConfigSetting/options.ts +0 -19
- package/src/modals/SettingsModal/components/ConfigSetting/types.ts +0 -9
- package/src/modals/SettingsModal/components/InputModeSetting/types.ts +0 -7
- package/src/modals/SettingsModal/components/LocaleSetting/types.ts +0 -9
- /package/.next/static/{9dmPfnTc_AQTHBPvL7xQe → wNIKOJJzkMSJYb2nOL21o}/_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.
|
|
3
|
+
"version": "2.12.3",
|
|
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.
|
|
35
|
-
"@scrabble-solver/constants": "^2.12.
|
|
36
|
-
"@scrabble-solver/dictionaries": "^2.12.
|
|
37
|
-
"@scrabble-solver/logger": "^2.12.
|
|
38
|
-
"@scrabble-solver/solver": "^2.12.
|
|
39
|
-
"@scrabble-solver/types": "^2.12.
|
|
40
|
-
"@scrabble-solver/word-definitions": "^2.12.
|
|
34
|
+
"@scrabble-solver/configs": "^2.12.3",
|
|
35
|
+
"@scrabble-solver/constants": "^2.12.3",
|
|
36
|
+
"@scrabble-solver/dictionaries": "^2.12.3",
|
|
37
|
+
"@scrabble-solver/logger": "^2.12.3",
|
|
38
|
+
"@scrabble-solver/solver": "^2.12.3",
|
|
39
|
+
"@scrabble-solver/types": "^2.12.3",
|
|
40
|
+
"@scrabble-solver/word-definitions": "^2.12.3",
|
|
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": "
|
|
77
|
+
"gitHead": "5400e276feee01557044e4e758b11e86b83fb085"
|
|
78
78
|
}
|
|
@@ -17,18 +17,14 @@
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
[dir='ltr'] & {
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
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;
|
|
@@ -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
|
|
14
|
-
using given letters and board state. Available in English, French, German, Polish & Spanish.
|
|
15
|
-
available on GitHub - contributions are welcome!
|
|
13
|
+
Scrabble Solver 2 is a free and open-source analysis tool for Scrabble, Super Scrabble & 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.
|
|
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
|
-
|
|
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
|
|
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 (
|
|
36
|
-
localStorage.
|
|
35
|
+
if (game) {
|
|
36
|
+
localStorage.setGame(game);
|
|
37
37
|
}
|
|
38
|
-
}, [
|
|
38
|
+
}, [game]);
|
|
39
39
|
|
|
40
40
|
useEffect(() => {
|
|
41
41
|
if (inputMode) {
|
package/src/i18n/constants.ts
CHANGED
|
@@ -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
|
-
|
|
94
|
+
separator: `${COMMA_LATIN} `,
|
|
95
|
+
vowels: true,
|
|
126
96
|
},
|
|
127
97
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
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 =
|
|
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 {
|
|
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 =
|
|
24
|
+
const config = getConfig(Game.Scrabble, locale);
|
|
25
25
|
|
|
26
26
|
for (const { input, expected } of tests) {
|
|
27
27
|
it(`[${locale}] "${input}"`, () => {
|
package/src/lib/getCellSize.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Config } from '@scrabble-solver/types';
|
|
2
2
|
|
|
3
|
-
import { BOARD_CELL_BORDER_WIDTH, BOARD_TILE_SIZE_MAX
|
|
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(
|
|
9
|
+
return Math.floor(Math.min(cellSize, BOARD_TILE_SIZE_MAX));
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export default getCellSize;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FunctionComponent, memo } from 'react';
|
|
2
2
|
|
|
3
3
|
import { Button, Modal } from 'components';
|
|
4
|
-
import {
|
|
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
|
|
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
|
-
<
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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={
|
|
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="
|
|
40
|
+
name="game"
|
|
32
41
|
value={option.value}
|
|
33
42
|
onChange={handleChange}
|
|
34
43
|
>
|
|
@@ -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 {
|
|
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(
|
|
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.
|
|
32
|
+
checked={locale === option.locale}
|
|
33
33
|
className={classNames(styles.option, className, {
|
|
34
|
-
[styles.checked]: locale === option.
|
|
34
|
+
[styles.checked]: locale === option.locale,
|
|
35
35
|
})}
|
|
36
36
|
disabled={disabled}
|
|
37
|
-
key={option.
|
|
37
|
+
key={option.locale}
|
|
38
38
|
name="locale"
|
|
39
|
-
value={option.
|
|
39
|
+
value={option.locale}
|
|
40
40
|
onChange={handleChange}
|
|
41
41
|
>
|
|
42
|
-
<span className={
|
|
43
|
-
<Icon className={
|
|
42
|
+
<span className={styles.label}>
|
|
43
|
+
<Icon className={styles.flag} />
|
|
44
44
|
|
|
45
45
|
<span>{option.label}</span>
|
|
46
46
|
</span>
|
package/src/pages/_app.tsx
CHANGED
|
@@ -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
|
|
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 {
|
|
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 {
|
|
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 =
|
|
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> => {
|