scrabble-solver 2.8.5 → 2.8.7
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/README.md +31 -24
- package/lerna.json +1 -1
- package/package.json +2 -2
- package/packages/configs/build/literaki/literaki.js +2 -2
- package/packages/configs/build/scrabble/scrabble.js +2 -2
- package/packages/configs/package.json +3 -3
- package/packages/configs/src/literaki/literaki.ts +2 -2
- package/packages/configs/src/scrabble/scrabble.ts +2 -2
- package/packages/constants/package.json +1 -1
- package/packages/dictionaries/package-lock.json +2 -2
- package/packages/dictionaries/package.json +4 -4
- package/packages/logger/package-lock.json +2 -2
- package/packages/logger/package.json +1 -1
- package/packages/scrabble-solver/.next/BUILD_ID +1 -1
- package/packages/scrabble-solver/.next/build-manifest.json +9 -9
- package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
- package/packages/scrabble-solver/.next/cache/eslint/.cache_8dgz12 +1 -1
- package/packages/scrabble-solver/.next/cache/next-server.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/prerender-manifest.json +1 -1
- package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
- package/packages/scrabble-solver/.next/server/chunks/413.js +183 -94
- package/packages/scrabble-solver/.next/server/chunks/{206.js → 429.js} +2 -4137
- package/packages/scrabble-solver/.next/server/chunks/515.js +111 -78
- package/packages/scrabble-solver/.next/server/chunks/{907.js → 911.js} +134 -367
- package/packages/scrabble-solver/.next/server/chunks/939.js +218 -0
- package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/404.html +2 -2
- package/packages/scrabble-solver/.next/server/pages/404.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/500.html +2 -2
- package/packages/scrabble-solver/.next/server/pages/_app.js +3 -2
- package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/_document.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/_error.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js +33 -17
- package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/solve.js +404 -58
- package/packages/scrabble-solver/.next/server/pages/api/solve.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/visit.js +3 -2
- package/packages/scrabble-solver/.next/server/pages/api/visit.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.html +3 -7
- package/packages/scrabble-solver/.next/server/pages/index.js +12 -14
- package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
- package/packages/scrabble-solver/.next/static/chunks/56-cf37c430261bbea5.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-0b12a65bea70a0df.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/pages/index-fcb69802550afb81.js +1 -0
- package/packages/scrabble-solver/.next/static/css/1f39b55d50f5b30b.css +1 -0
- package/packages/scrabble-solver/.next/static/css/751e8a14776d05d8.css +1 -0
- package/packages/scrabble-solver/.next/static/z_0_lqfmiI_ISokr6NNRq/_buildManifest.js +1 -0
- package/packages/scrabble-solver/.next/static/{TzKQ3IntkvaYmHBkWpfoi → z_0_lqfmiI_ISokr6NNRq}/_ssgManifest.js +0 -0
- package/packages/scrabble-solver/.next/trace +40 -42
- package/packages/scrabble-solver/package-lock.json +2 -2
- package/packages/scrabble-solver/package.json +8 -8
- package/packages/scrabble-solver/src/api/index.ts +3 -9
- package/packages/scrabble-solver/src/api/isBoardValid.ts +43 -0
- package/packages/scrabble-solver/src/api/isCellValid.ts +26 -0
- package/packages/scrabble-solver/src/api/isRowValid.ts +19 -0
- package/packages/scrabble-solver/src/components/Board/Board.tsx +3 -1
- package/packages/scrabble-solver/src/components/Board/BoardPure.tsx +4 -1
- package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss +28 -11
- package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.tsx +12 -1
- package/packages/scrabble-solver/src/components/Board/components/Cell/CellPure.tsx +3 -1
- package/packages/scrabble-solver/src/components/Board/components/Cell/lib.ts +10 -2
- package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss +20 -0
- package/packages/scrabble-solver/src/components/Dictionary/Dictionary.tsx +40 -29
- package/packages/scrabble-solver/src/components/Results/Cell.tsx +3 -2
- package/packages/scrabble-solver/src/components/Results/Result.tsx +16 -6
- package/packages/scrabble-solver/src/components/ResultsInput/ResultsInput.tsx +11 -3
- package/packages/scrabble-solver/src/hooks/useIsTablet.ts +2 -2
- package/packages/scrabble-solver/src/lib/getRemainingTiles.ts +1 -1
- package/packages/scrabble-solver/src/lib/index.ts +2 -1
- package/packages/scrabble-solver/src/lib/isRegExp.ts +11 -0
- package/packages/scrabble-solver/src/lib/isStringArray.ts +5 -0
- package/packages/scrabble-solver/src/lib/sortResults.ts +5 -5
- package/packages/scrabble-solver/src/pages/_app.tsx +6 -3
- package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/[word].ts +35 -11
- package/packages/scrabble-solver/src/pages/api/solve.ts +39 -19
- package/packages/scrabble-solver/src/pages/api/visit.ts +1 -0
- package/packages/scrabble-solver/src/pages/index.module.scss +5 -11
- package/packages/scrabble-solver/src/pages/index.tsx +5 -5
- package/packages/scrabble-solver/src/sdk/{findWordDefinition.ts → findWordDefinitions.ts} +3 -3
- package/packages/scrabble-solver/src/sdk/index.ts +1 -1
- package/packages/scrabble-solver/src/state/sagas.ts +11 -11
- package/packages/scrabble-solver/src/state/selectors.ts +6 -2
- package/packages/scrabble-solver/src/state/slices/dictionaryInitialState.ts +3 -3
- package/packages/scrabble-solver/src/state/slices/dictionarySlice.ts +4 -10
- package/packages/solver/build/PatternsFiller.js +1 -1
- package/packages/solver/build/PatternsGenerator.d.ts +4 -4
- package/packages/solver/build/PatternsGenerator.js +6 -6
- package/packages/solver/build/ScoresCalculator.js +1 -1
- package/packages/solver/build/Solver.js +3 -3
- package/packages/solver/build/getUniquePatterns.d.ts +3 -0
- package/packages/solver/build/getUniquePatterns.js +25 -0
- package/packages/solver/package-lock.json +3 -29
- package/packages/solver/package.json +5 -7
- package/packages/solver/src/PatternsFiller.ts +1 -1
- package/packages/solver/src/PatternsGenerator.test.ts +2 -2
- package/packages/solver/src/PatternsGenerator.ts +8 -8
- package/packages/solver/src/ScoresCalculator.ts +1 -1
- package/packages/solver/src/Solver.ts +3 -3
- package/packages/solver/src/getUniquePatterns.ts +30 -0
- package/packages/types/build/Board.d.ts +2 -2
- package/packages/types/build/Board.js +8 -8
- package/packages/types/build/BoardJson.d.ts +2 -1
- package/packages/types/build/BoardJson.js +19 -0
- package/packages/types/build/Cell.d.ts +2 -3
- package/packages/types/build/Cell.js +0 -15
- package/packages/types/build/CellJson.d.ts +1 -0
- package/packages/types/build/CellJson.js +14 -0
- package/packages/types/build/Collision.d.ts +3 -0
- package/packages/types/build/Collision.js +2 -0
- package/packages/types/build/CollisionJson.d.ts +3 -0
- package/packages/types/build/CollisionJson.js +2 -0
- package/packages/types/build/Config.d.ts +2 -2
- package/packages/types/build/Config.js +5 -5
- package/packages/types/build/ConfigJson.d.ts +2 -2
- package/packages/types/build/Locale.d.ts +1 -0
- package/packages/types/build/Locale.js +4 -0
- package/packages/types/build/Pattern.d.ts +1 -1
- package/packages/types/build/Pattern.js +4 -4
- package/packages/types/build/PatternJson.d.ts +1 -1
- package/packages/types/build/Result.d.ts +10 -8
- package/packages/types/build/Result.js +15 -10
- package/packages/types/build/ResultJson.d.ts +3 -2
- package/packages/types/build/TileJson.d.ts +1 -0
- package/packages/types/build/TileJson.js +9 -0
- package/packages/types/build/index.d.ts +7 -4
- package/packages/types/build/index.js +10 -1
- package/packages/types/build/isObject.d.ts +2 -0
- package/packages/types/build/isObject.js +6 -0
- package/packages/types/package.json +2 -2
- package/packages/types/src/Board.ts +11 -11
- package/packages/types/src/BoardJson.ts +22 -2
- package/packages/types/src/Cell.ts +2 -19
- package/packages/types/src/CellJson.ts +12 -1
- package/packages/types/src/Collision.ts +5 -0
- package/packages/types/src/CollisionJson.ts +5 -0
- package/packages/types/src/Config.ts +6 -6
- package/packages/types/src/ConfigJson.ts +2 -2
- package/packages/types/src/Locale.ts +4 -0
- package/packages/types/src/Pattern.ts +4 -5
- package/packages/types/src/PatternJson.ts +1 -1
- package/packages/types/src/Result.ts +33 -23
- package/packages/types/src/ResultJson.ts +3 -2
- package/packages/types/src/TileJson.ts +6 -0
- package/packages/types/src/index.ts +7 -4
- package/packages/types/src/isObject.ts +5 -0
- package/packages/word-definitions/package-lock.json +2 -2
- package/packages/word-definitions/package.json +2 -2
- package/packages/word-lists/build/getDeDeWordList.js +1 -1
- package/packages/word-lists/build/lib/extractWords.js +4 -1
- package/packages/word-lists/package-lock.json +2 -2
- package/packages/word-lists/package.json +2 -2
- package/packages/word-lists/src/getDeDeWordList.ts +1 -1
- package/packages/word-lists/src/lib/extractWords.ts +4 -1
- package/packages/scrabble-solver/.next/static/TzKQ3IntkvaYmHBkWpfoi/_buildManifest.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/56-2d34867599a0ac66.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-6ffa2ab900772b67.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/index-13ea7770a65c69ee.js +0 -1
- package/packages/scrabble-solver/.next/static/css/3159cfe62ff742a3.css +0 -1
- package/packages/scrabble-solver/.next/static/css/551d09cac435debb.css +0 -1
- package/packages/scrabble-solver/src/api/validateBoard.ts +0 -45
- package/packages/scrabble-solver/src/api/validateCell.ts +0 -40
- package/packages/scrabble-solver/src/api/validateCharacter.ts +0 -14
- package/packages/scrabble-solver/src/api/validateCharacters.ts +0 -24
- package/packages/scrabble-solver/src/api/validateConfigId.ts +0 -9
- package/packages/scrabble-solver/src/api/validateLocale.ts +0 -15
- package/packages/scrabble-solver/src/api/validateRow.ts +0 -17
- package/packages/scrabble-solver/src/api/validateTile.ts +0 -21
- package/packages/scrabble-solver/src/api/validateWord.ts +0 -11
- package/packages/scrabble-solver/src/lib/isLocale.ts +0 -7
|
@@ -1,45 +1,69 @@
|
|
|
1
|
+
import { scrabble } from '@scrabble-solver/configs';
|
|
1
2
|
import logger from '@scrabble-solver/logger';
|
|
2
|
-
import { Locale } from '@scrabble-solver/types';
|
|
3
|
+
import { isLocale, Locale } from '@scrabble-solver/types';
|
|
3
4
|
import { getWordDefinition } from '@scrabble-solver/word-definitions';
|
|
4
5
|
import { NextApiRequest, NextApiResponse } from 'next';
|
|
5
6
|
|
|
6
|
-
import { getServerLoggingData
|
|
7
|
+
import { getServerLoggingData } from 'api';
|
|
7
8
|
|
|
8
9
|
interface RequestData {
|
|
9
10
|
locale: Locale;
|
|
10
|
-
|
|
11
|
+
words: string[];
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
const MAXIMUM_WORDS_COUNT = scrabble['en-US'].maximumCharactersCount;
|
|
15
|
+
|
|
13
16
|
const dictionary = async (request: NextApiRequest, response: NextApiResponse): Promise<void> => {
|
|
14
17
|
const meta = getServerLoggingData(request);
|
|
15
18
|
|
|
16
19
|
try {
|
|
17
|
-
const { locale,
|
|
20
|
+
const { locale, words } = parseRequest(request);
|
|
21
|
+
|
|
18
22
|
logger.info('dictionary - request', {
|
|
19
23
|
meta,
|
|
20
24
|
payload: {
|
|
21
25
|
locale,
|
|
22
|
-
|
|
26
|
+
words,
|
|
23
27
|
},
|
|
24
28
|
});
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
|
|
30
|
+
const results = await Promise.all(words.map((word) => getWordDefinition(locale, word)));
|
|
31
|
+
response.status(200).send(results.map((result) => result.toJson()));
|
|
27
32
|
} catch (error) {
|
|
28
33
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
29
34
|
logger.error('dictionary - error', { error, meta });
|
|
30
35
|
response.status(500).send({ error: 'Server error', message });
|
|
36
|
+
throw error;
|
|
31
37
|
}
|
|
32
38
|
};
|
|
33
39
|
|
|
34
40
|
const parseRequest = (request: NextApiRequest): RequestData => {
|
|
35
41
|
const { locale, word } = request.query;
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
if (!isLocale(locale)) {
|
|
44
|
+
throw new Error('Invalid "locale" parameter');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (typeof word !== 'string' || word.length === 0) {
|
|
48
|
+
throw new Error('Invalid "word" parameter');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const words = Array.from(
|
|
52
|
+
new Set(
|
|
53
|
+
word
|
|
54
|
+
.split(',')
|
|
55
|
+
.map((part) => part.trim())
|
|
56
|
+
.filter(Boolean),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (words.length > MAXIMUM_WORDS_COUNT) {
|
|
61
|
+
throw new Error('Invalid "word" parameter');
|
|
62
|
+
}
|
|
39
63
|
|
|
40
64
|
return {
|
|
41
|
-
locale
|
|
42
|
-
|
|
65
|
+
locale,
|
|
66
|
+
words,
|
|
43
67
|
};
|
|
44
68
|
};
|
|
45
69
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { getLocaleConfig } from '@scrabble-solver/configs';
|
|
1
|
+
import { getLocaleConfig, isConfigId } from '@scrabble-solver/configs';
|
|
2
2
|
import { BLANK } from '@scrabble-solver/constants';
|
|
3
3
|
import { dictionaries } from '@scrabble-solver/dictionaries';
|
|
4
4
|
import logger from '@scrabble-solver/logger';
|
|
5
5
|
import Solver from '@scrabble-solver/solver';
|
|
6
|
-
import { Board, Config, Locale, Tile } from '@scrabble-solver/types';
|
|
6
|
+
import { Board, Config, isBoardJson, isLocale, Locale, Tile } from '@scrabble-solver/types';
|
|
7
7
|
import { NextApiRequest, NextApiResponse } from 'next';
|
|
8
8
|
|
|
9
|
-
import { getServerLoggingData,
|
|
9
|
+
import { getServerLoggingData, isBoardValid } from 'api';
|
|
10
|
+
import { isStringArray } from 'lib';
|
|
10
11
|
|
|
11
12
|
interface RequestData {
|
|
12
13
|
board: Board;
|
|
@@ -20,6 +21,7 @@ const solve = async (request: NextApiRequest, response: NextApiResponse): Promis
|
|
|
20
21
|
|
|
21
22
|
try {
|
|
22
23
|
const { board, characters, config, locale } = parseRequest(request);
|
|
24
|
+
|
|
23
25
|
logger.info('solve - request', {
|
|
24
26
|
meta,
|
|
25
27
|
payload: {
|
|
@@ -31,7 +33,7 @@ const solve = async (request: NextApiRequest, response: NextApiResponse): Promis
|
|
|
31
33
|
locale,
|
|
32
34
|
},
|
|
33
35
|
});
|
|
34
|
-
|
|
36
|
+
|
|
35
37
|
const trie = await dictionaries.get(locale);
|
|
36
38
|
const tiles = characters.map((character) => new Tile({ character, isBlank: character === BLANK }));
|
|
37
39
|
const solver = new Solver(config, trie);
|
|
@@ -41,33 +43,51 @@ const solve = async (request: NextApiRequest, response: NextApiResponse): Promis
|
|
|
41
43
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
42
44
|
logger.error('solve - error', { error, meta });
|
|
43
45
|
response.status(500).send({ error: 'Server error', message });
|
|
46
|
+
throw error;
|
|
44
47
|
}
|
|
45
48
|
};
|
|
46
49
|
|
|
47
50
|
const parseRequest = (request: NextApiRequest): RequestData => {
|
|
48
|
-
const { board, characters, configId, locale } = request.body;
|
|
51
|
+
const { board: boardJson, characters, configId, locale } = request.body;
|
|
52
|
+
|
|
53
|
+
if (!isLocale(locale)) {
|
|
54
|
+
throw new Error('Invalid "locale" parameter');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!isConfigId(configId)) {
|
|
58
|
+
throw new Error('Invalid "configId" parameter');
|
|
59
|
+
}
|
|
49
60
|
|
|
50
|
-
validateConfigId(configId);
|
|
51
|
-
validateLocale(locale);
|
|
52
61
|
const config = getLocaleConfig(configId, locale);
|
|
53
|
-
validateBoard(board, config);
|
|
54
|
-
validateCharacters(characters, config);
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
+
if (!isBoardJson(boardJson) || !isBoardValid(boardJson, config)) {
|
|
64
|
+
throw new Error('Invalid "board" parameter');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!isStringArray(characters) || characters.length === 0) {
|
|
68
|
+
throw new Error('Invalid "characters" parameter');
|
|
69
|
+
}
|
|
63
70
|
|
|
64
|
-
const
|
|
71
|
+
for (const character of characters) {
|
|
72
|
+
if (!config.hasCharacter(character) && character !== BLANK) {
|
|
73
|
+
throw new Error('Invalid "characters" parameter');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const board = Board.fromJson(boardJson);
|
|
65
78
|
const blankTilesCount = characters.filter((character) => character === BLANK).length;
|
|
66
79
|
const blanksCount = board.getBlanksCount() + blankTilesCount;
|
|
67
80
|
|
|
68
|
-
if (blanksCount > config.
|
|
69
|
-
throw new Error(
|
|
81
|
+
if (blanksCount > config.blanksCount) {
|
|
82
|
+
throw new Error('Too many blank tiles passed');
|
|
70
83
|
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
board,
|
|
87
|
+
characters,
|
|
88
|
+
config,
|
|
89
|
+
locale,
|
|
90
|
+
};
|
|
71
91
|
};
|
|
72
92
|
|
|
73
93
|
export default solve;
|
|
@@ -13,6 +13,7 @@ const visit = async (request: NextApiRequest, response: NextApiResponse): Promis
|
|
|
13
13
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
14
14
|
logger.error('visit - error', { error, meta });
|
|
15
15
|
response.status(500).send({ error: 'Server error', message });
|
|
16
|
+
throw error;
|
|
16
17
|
}
|
|
17
18
|
};
|
|
18
19
|
|
|
@@ -25,11 +25,15 @@
|
|
|
25
25
|
padding: var(--spacing--l);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
.
|
|
28
|
+
.navLogo {
|
|
29
29
|
display: flex;
|
|
30
30
|
flex: 1;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
.logoContainer {
|
|
34
|
+
display: flex;
|
|
35
|
+
}
|
|
36
|
+
|
|
33
37
|
.logo {
|
|
34
38
|
height: 60px;
|
|
35
39
|
user-select: none;
|
|
@@ -127,13 +131,3 @@
|
|
|
127
131
|
.submitInput {
|
|
128
132
|
display: none;
|
|
129
133
|
}
|
|
130
|
-
|
|
131
|
-
.version {
|
|
132
|
-
position: fixed;
|
|
133
|
-
bottom: var(--spacing--m);
|
|
134
|
-
left: 0;
|
|
135
|
-
right: 0;
|
|
136
|
-
font-size: var(--font--size--xs);
|
|
137
|
-
color: var(--color--background);
|
|
138
|
-
text-align: center;
|
|
139
|
-
}
|
|
@@ -74,8 +74,10 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
74
74
|
<>
|
|
75
75
|
<div className={classNames(styles.index, { [styles.initialized]: isInitialized })}>
|
|
76
76
|
<div className={styles.nav}>
|
|
77
|
-
<div className={styles.
|
|
78
|
-
<
|
|
77
|
+
<div className={styles.navLogo}>
|
|
78
|
+
<a className={styles.logoContainer} href="/" title={version}>
|
|
79
|
+
<Logo className={styles.logo} />
|
|
80
|
+
</a>
|
|
79
81
|
</div>
|
|
80
82
|
|
|
81
83
|
<NavButtons
|
|
@@ -114,8 +116,6 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
114
116
|
<Rack className={styles.rack} />
|
|
115
117
|
<input className={styles.submitInput} tabIndex={-1} type="submit" />
|
|
116
118
|
</form>
|
|
117
|
-
|
|
118
|
-
<span className={styles.version}>v{version}</span>
|
|
119
119
|
</div>
|
|
120
120
|
|
|
121
121
|
<Settings isOpen={showSettings} onClose={() => setShowSettings(false)} />
|
|
@@ -138,7 +138,7 @@ const readVersion = async (): Promise<string> => {
|
|
|
138
138
|
const packageJsonFilepath = path.resolve(process.cwd(), 'package.json');
|
|
139
139
|
const data = await fs.promises.readFile(packageJsonFilepath, 'utf-8');
|
|
140
140
|
const packageJson = JSON.parse(data);
|
|
141
|
-
return packageJson.version
|
|
141
|
+
return `v${packageJson.version}`;
|
|
142
142
|
};
|
|
143
143
|
|
|
144
144
|
export default Index;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Locale, WordDefinition } from '@scrabble-solver/types';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const findWordDefinitions = async (locale: Locale, word: string): Promise<WordDefinition[]> => {
|
|
4
4
|
const url = `/api/dictionary/${locale}/${word}`;
|
|
5
5
|
const json = await fetch(url).then((response) => response.json());
|
|
6
|
-
return WordDefinition.fromJson
|
|
6
|
+
return json.map(WordDefinition.fromJson);
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
export default
|
|
9
|
+
export default findWordDefinitions;
|
|
@@ -3,7 +3,7 @@ import { Result } from '@scrabble-solver/types';
|
|
|
3
3
|
import { call, delay, put, select, takeEvery, takeLatest } from 'redux-saga/effects';
|
|
4
4
|
|
|
5
5
|
import { memoize } from 'lib';
|
|
6
|
-
import {
|
|
6
|
+
import { findWordDefinitions, solve, visit } from 'sdk';
|
|
7
7
|
|
|
8
8
|
import { initialize, reset } from './actions';
|
|
9
9
|
import {
|
|
@@ -18,7 +18,7 @@ import { boardSlice, dictionarySlice, rackSlice, resultsSlice, settingsSlice, so
|
|
|
18
18
|
|
|
19
19
|
const SUBMIT_DELAY = 150;
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const memoizedFindWordDefinitions = memoize(findWordDefinitions);
|
|
22
22
|
|
|
23
23
|
// Can't conveniently type generators for sagas yet,
|
|
24
24
|
// see: https://github.com/microsoft/TypeScript/issues/43632
|
|
@@ -53,13 +53,13 @@ function* onDictionarySubmit(): AnyGenerator {
|
|
|
53
53
|
const { input: word } = yield select(selectDictionary);
|
|
54
54
|
const locale = yield select(selectLocale);
|
|
55
55
|
|
|
56
|
-
if (!
|
|
56
|
+
if (!memoizedFindWordDefinitions.hasCache(locale, word)) {
|
|
57
57
|
yield delay(SUBMIT_DELAY);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
try {
|
|
61
|
-
const
|
|
62
|
-
yield put(dictionarySlice.actions.submitSuccess(
|
|
61
|
+
const wordDefinitions = yield call(memoizedFindWordDefinitions, locale, word);
|
|
62
|
+
yield put(dictionarySlice.actions.submitSuccess(wordDefinitions));
|
|
63
63
|
} catch (error) {
|
|
64
64
|
yield put(dictionarySlice.actions.submitFailure());
|
|
65
65
|
}
|
|
@@ -85,7 +85,7 @@ function* onLocaleChange(): AnyGenerator {
|
|
|
85
85
|
|
|
86
86
|
function* onResultCandidateChange({ payload: result }: PayloadAction<Result | null>): AnyGenerator {
|
|
87
87
|
if (result) {
|
|
88
|
-
yield put(dictionarySlice.actions.changeInput(result.
|
|
88
|
+
yield put(dictionarySlice.actions.changeInput(result.words.join(', ')));
|
|
89
89
|
yield put(dictionarySlice.actions.submit());
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -121,12 +121,12 @@ function* ensureProperTilesCount(): AnyGenerator {
|
|
|
121
121
|
const { config } = yield select(selectConfig);
|
|
122
122
|
const characters = yield select(selectCharacters);
|
|
123
123
|
|
|
124
|
-
if (config.
|
|
125
|
-
const differenceCount = Math.abs(config.
|
|
124
|
+
if (config.maximumCharactersCount > characters.length) {
|
|
125
|
+
const differenceCount = Math.abs(config.maximumCharactersCount - characters.length);
|
|
126
126
|
yield put(rackSlice.actions.init([...characters, ...Array(differenceCount).fill(null)]));
|
|
127
|
-
} else if (config.
|
|
128
|
-
const nonNulls = characters.filter(Boolean).slice(0, config.
|
|
129
|
-
const differenceCount = Math.abs(config.
|
|
127
|
+
} else if (config.maximumCharactersCount < characters.length) {
|
|
128
|
+
const nonNulls = characters.filter(Boolean).slice(0, config.maximumCharactersCount);
|
|
129
|
+
const differenceCount = Math.abs(config.maximumCharactersCount - nonNulls.length);
|
|
130
130
|
const autoGroupTiles = yield select(selectAutoGroupTiles);
|
|
131
131
|
yield put(rackSlice.actions.init([...nonNulls, ...Array(differenceCount).fill(null)]));
|
|
132
132
|
yield put(rackSlice.actions.groupTiles(autoGroupTiles));
|
|
@@ -59,8 +59,12 @@ export const selectSortedFilteredResults = createSelector(
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
return results.filter((result) => {
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
try {
|
|
63
|
+
const regExp = new RegExp(query, 'gi');
|
|
64
|
+
return regExp.test(result.word);
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
64
68
|
});
|
|
65
69
|
},
|
|
66
70
|
);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { WordDefinition } from '@scrabble-solver/types';
|
|
2
|
+
|
|
1
3
|
const dictionaryInitialState = {
|
|
2
|
-
definitions: [] as string[],
|
|
3
4
|
input: '',
|
|
4
|
-
isAllowed: null as boolean | null,
|
|
5
5
|
isLoading: false,
|
|
6
|
-
|
|
6
|
+
results: [] as WordDefinition[],
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
export default dictionaryInitialState;
|
|
@@ -16,30 +16,24 @@ const dictionarySlice = createSlice({
|
|
|
16
16
|
submit: (state) => {
|
|
17
17
|
return {
|
|
18
18
|
...state,
|
|
19
|
-
definitions: dictionaryInitialState.definitions,
|
|
20
|
-
isAllowed: dictionaryInitialState.isAllowed,
|
|
21
19
|
isLoading: true,
|
|
22
|
-
|
|
20
|
+
results: dictionaryInitialState.results,
|
|
23
21
|
};
|
|
24
22
|
},
|
|
25
23
|
|
|
26
24
|
submitFailure: (state) => {
|
|
27
25
|
return {
|
|
28
26
|
...state,
|
|
29
|
-
definitions: dictionaryInitialState.definitions,
|
|
30
|
-
isAllowed: false,
|
|
31
27
|
isLoading: false,
|
|
32
|
-
|
|
28
|
+
results: dictionaryInitialState.results,
|
|
33
29
|
};
|
|
34
30
|
},
|
|
35
31
|
|
|
36
|
-
submitSuccess: (state, action: PayloadAction<WordDefinition>) => {
|
|
32
|
+
submitSuccess: (state, action: PayloadAction<WordDefinition[]>) => {
|
|
37
33
|
return {
|
|
38
34
|
...state,
|
|
39
|
-
definitions: action.payload.definitions,
|
|
40
|
-
isAllowed: action.payload.isAllowed,
|
|
41
35
|
isLoading: false,
|
|
42
|
-
|
|
36
|
+
results: action.payload,
|
|
43
37
|
};
|
|
44
38
|
},
|
|
45
39
|
},
|
|
@@ -9,7 +9,7 @@ class PatternsFiller {
|
|
|
9
9
|
}
|
|
10
10
|
fill(pattern, tiles) {
|
|
11
11
|
const patterns = [];
|
|
12
|
-
if (pattern.
|
|
12
|
+
if (pattern.getEmptyCellsCount() > tiles.length) {
|
|
13
13
|
return [];
|
|
14
14
|
}
|
|
15
15
|
const onPatternFound = (newPattern) => patterns.push(newPattern);
|
|
@@ -5,18 +5,18 @@ declare class PatternsGenerator {
|
|
|
5
5
|
generate(board: Board): Pattern[];
|
|
6
6
|
generateHorizontal(board: Board): HorizontalPattern[];
|
|
7
7
|
generateVertical(board: Board): VerticalPattern[];
|
|
8
|
-
generatePatterns<P extends Pattern>({ board, getNthVector,
|
|
8
|
+
generatePatterns<P extends Pattern>({ board, getNthVector, vectorsCount, PatternModel, }: {
|
|
9
9
|
board: Board;
|
|
10
10
|
getNthVector: (index: number) => Cell[];
|
|
11
|
-
numberOfVectors: number;
|
|
12
11
|
PatternModel: new (parameters: {
|
|
13
12
|
board: Board;
|
|
14
13
|
cells: Cell[];
|
|
15
14
|
}) => P;
|
|
15
|
+
vectorsCount: number;
|
|
16
16
|
}): P[];
|
|
17
|
-
generateVectors({ getNthVector,
|
|
17
|
+
generateVectors({ getNthVector, vectorsCount, }: {
|
|
18
18
|
getNthVector: (index: number) => Cell[];
|
|
19
|
-
|
|
19
|
+
vectorsCount: number;
|
|
20
20
|
}): Cell[][];
|
|
21
21
|
generateCellsPatterns<P extends Pattern>({ board, cells, PatternModel, }: {
|
|
22
22
|
board: Board;
|
|
@@ -12,25 +12,25 @@ class PatternsGenerator {
|
|
|
12
12
|
return this.generatePatterns({
|
|
13
13
|
board,
|
|
14
14
|
getNthVector: (index) => board.getRow(index),
|
|
15
|
-
numberOfVectors: this.config.boardHeight,
|
|
16
15
|
PatternModel: types_1.HorizontalPattern,
|
|
16
|
+
vectorsCount: this.config.boardHeight,
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
generateVertical(board) {
|
|
20
20
|
return this.generatePatterns({
|
|
21
21
|
board,
|
|
22
22
|
getNthVector: (index) => board.getColumn(index),
|
|
23
|
-
numberOfVectors: this.config.boardWidth,
|
|
24
23
|
PatternModel: types_1.VerticalPattern,
|
|
24
|
+
vectorsCount: this.config.boardWidth,
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
-
generatePatterns({ board, getNthVector,
|
|
28
|
-
return this.generateVectors({ getNthVector,
|
|
27
|
+
generatePatterns({ board, getNthVector, vectorsCount, PatternModel, }) {
|
|
28
|
+
return this.generateVectors({ getNthVector, vectorsCount }).reduce((patterns, cells) => {
|
|
29
29
|
return patterns.concat(this.generateCellsPatterns({ board, PatternModel, cells }));
|
|
30
30
|
}, []);
|
|
31
31
|
}
|
|
32
|
-
generateVectors({ getNthVector,
|
|
33
|
-
return Array(
|
|
32
|
+
generateVectors({ getNthVector, vectorsCount, }) {
|
|
33
|
+
return Array(vectorsCount)
|
|
34
34
|
.fill(0)
|
|
35
35
|
.map((_, index) => getNthVector(index));
|
|
36
36
|
}
|
|
@@ -18,7 +18,7 @@ class ScoresCalculator {
|
|
|
18
18
|
return this.calculatePatternScoreWithCollisions(pattern) + this.calculateBonusScore(pattern);
|
|
19
19
|
}
|
|
20
20
|
calculateBonusScore(pattern) {
|
|
21
|
-
const areAllTilesUsed = pattern.
|
|
21
|
+
const areAllTilesUsed = pattern.getEmptyCellsCount() === this.config.maximumCharactersCount;
|
|
22
22
|
return areAllTilesUsed ? this.config.allTilesBonusScore : 0;
|
|
23
23
|
}
|
|
24
24
|
calculatePatternScoreWithCollisions(pattern) {
|
|
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const types_1 = require("@scrabble-solver/types");
|
|
7
|
-
const
|
|
7
|
+
const getUniquePatterns_1 = __importDefault(require("./getUniquePatterns"));
|
|
8
8
|
const PatternsFiller_1 = __importDefault(require("./PatternsFiller"));
|
|
9
9
|
const PatternsGenerator_1 = __importDefault(require("./PatternsGenerator"));
|
|
10
10
|
const ScoresCalculator_1 = __importDefault(require("./ScoresCalculator"));
|
|
@@ -17,11 +17,11 @@ class Solver {
|
|
|
17
17
|
solve(board, tiles) {
|
|
18
18
|
const patterns = this.patternsGenerator.generate(board);
|
|
19
19
|
const filledPatterns = patterns.flatMap((pattern) => this.patternsFiller.fill(pattern, tiles));
|
|
20
|
-
const uniquePatterns = (0,
|
|
20
|
+
const uniquePatterns = (0, getUniquePatterns_1.default)(filledPatterns);
|
|
21
21
|
const results = uniquePatterns.map((pattern, index) => new types_1.Result({
|
|
22
22
|
cells: pattern.cells,
|
|
23
|
+
collisions: pattern.getCollisions().map((collision) => collision.cells),
|
|
23
24
|
id: index,
|
|
24
|
-
numberOfCollisions: pattern.getCollisions().length,
|
|
25
25
|
points: this.scoresCalculator.calculate(pattern),
|
|
26
26
|
}));
|
|
27
27
|
return results;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const getPatternHash = (pattern) => {
|
|
4
|
+
return pattern.cells
|
|
5
|
+
.map((cell) => {
|
|
6
|
+
const blank = cell.tile.isBlank ? '!' : '';
|
|
7
|
+
const tile = cell.tile.character + blank;
|
|
8
|
+
// eslint-disable-next-line prefer-template
|
|
9
|
+
return cell.x + ',' + cell.y + ',' + tile;
|
|
10
|
+
})
|
|
11
|
+
.join('-');
|
|
12
|
+
};
|
|
13
|
+
const getUniquePatterns = (patterns) => {
|
|
14
|
+
const hashes = new Set();
|
|
15
|
+
const uniquePatterns = [];
|
|
16
|
+
for (const pattern of patterns) {
|
|
17
|
+
const hash = getPatternHash(pattern);
|
|
18
|
+
if (!hashes.has(hash)) {
|
|
19
|
+
hashes.add(hash);
|
|
20
|
+
uniquePatterns.push(pattern);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return uniquePatterns;
|
|
24
|
+
};
|
|
25
|
+
exports.default = getUniquePatterns;
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrabble-solver/solver",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.7",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@scrabble-solver/solver",
|
|
9
|
-
"version": "2.8.
|
|
9
|
+
"version": "2.8.7",
|
|
10
10
|
"license": "CC-BY-NC-ND-4.0",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@kamilmielnik/trie": "^2.0.0"
|
|
13
|
-
"lodash": "^4.17.21"
|
|
14
|
-
},
|
|
15
|
-
"devDependencies": {
|
|
16
|
-
"@types/lodash": "^4.14.186"
|
|
12
|
+
"@kamilmielnik/trie": "^2.0.0"
|
|
17
13
|
}
|
|
18
14
|
},
|
|
19
15
|
"node_modules/@kamilmielnik/trie": {
|
|
@@ -23,17 +19,6 @@
|
|
|
23
19
|
"engines": {
|
|
24
20
|
"node": ">=16"
|
|
25
21
|
}
|
|
26
|
-
},
|
|
27
|
-
"node_modules/@types/lodash": {
|
|
28
|
-
"version": "4.14.186",
|
|
29
|
-
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz",
|
|
30
|
-
"integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==",
|
|
31
|
-
"dev": true
|
|
32
|
-
},
|
|
33
|
-
"node_modules/lodash": {
|
|
34
|
-
"version": "4.17.21",
|
|
35
|
-
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
|
36
|
-
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
|
37
22
|
}
|
|
38
23
|
},
|
|
39
24
|
"dependencies": {
|
|
@@ -41,17 +26,6 @@
|
|
|
41
26
|
"version": "2.0.0",
|
|
42
27
|
"resolved": "https://registry.npmjs.org/@kamilmielnik/trie/-/trie-2.0.0.tgz",
|
|
43
28
|
"integrity": "sha512-YWTYtrV97bQ5hRifG1FImP47PI400a56O8+oQClQOtDjY3tioxab4sOgFHckiDdswciN1RwngCZ9Kv3phJfSuA=="
|
|
44
|
-
},
|
|
45
|
-
"@types/lodash": {
|
|
46
|
-
"version": "4.14.186",
|
|
47
|
-
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz",
|
|
48
|
-
"integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==",
|
|
49
|
-
"dev": true
|
|
50
|
-
},
|
|
51
|
-
"lodash": {
|
|
52
|
-
"version": "4.17.21",
|
|
53
|
-
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
|
54
|
-
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
|
55
29
|
}
|
|
56
30
|
}
|
|
57
31
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrabble-solver/solver",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.7",
|
|
4
4
|
"description": "Scrabble Solver 2 - Solver",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -25,14 +25,12 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@kamilmielnik/trie": "^2.0.0",
|
|
28
|
-
"@scrabble-solver/types": "^2.8.
|
|
29
|
-
"lodash": "^4.17.21"
|
|
28
|
+
"@scrabble-solver/types": "^2.8.7"
|
|
30
29
|
},
|
|
31
30
|
"devDependencies": {
|
|
32
|
-
"@scrabble-solver/configs": "^2.8.
|
|
33
|
-
"@scrabble-solver/constants": "^2.8.
|
|
34
|
-
"@scrabble-solver/dictionaries": "^2.8.
|
|
35
|
-
"@types/lodash": "^4.14.186"
|
|
31
|
+
"@scrabble-solver/configs": "^2.8.7",
|
|
32
|
+
"@scrabble-solver/constants": "^2.8.7",
|
|
33
|
+
"@scrabble-solver/dictionaries": "^2.8.7"
|
|
36
34
|
},
|
|
37
35
|
"gitHead": "1e2d1ad49a288d4a44196d8e3336740da132c637"
|
|
38
36
|
}
|
|
@@ -8,7 +8,7 @@ const board = Board.fromStringArray([' t ', 'do ', ' ']);
|
|
|
8
8
|
const patternsGenerator = new PatternsGenerator({
|
|
9
9
|
boardHeight: 3,
|
|
10
10
|
boardWidth: 3,
|
|
11
|
-
|
|
11
|
+
maximumCharactersCount: 7,
|
|
12
12
|
} as Config);
|
|
13
13
|
|
|
14
14
|
describe('PatternsGenerator', () => {
|
|
@@ -45,7 +45,7 @@ describe('PatternsGenerator', () => {
|
|
|
45
45
|
it('generates vectors', () => {
|
|
46
46
|
const vectors = patternsGenerator.generateVectors({
|
|
47
47
|
getNthVector: () => [],
|
|
48
|
-
|
|
48
|
+
vectorsCount: 3,
|
|
49
49
|
});
|
|
50
50
|
expect(vectors.length).toBe(3);
|
|
51
51
|
expect(vectors).toEqual([[], [], []]);
|