scrabble-solver 2.8.6 → 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 +2 -1
- package/lerna.json +1 -1
- package/package.json +1 -1
- 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 +92 -70
- 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.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 +399 -56
- 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/{VjSpyGDWyVaO0muz54q_j → 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/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/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/package-lock.json +2 -2
- package/packages/word-lists/package.json +2 -2
- package/packages/scrabble-solver/.next/static/VjSpyGDWyVaO0muz54q_j/_buildManifest.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/56-e2797384ae4b0fc0.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-5136d33b9b007fd7.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/729bb37fe8f9bee6.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
|
@@ -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([[], [], []]);
|
|
@@ -15,8 +15,8 @@ class PatternsGenerator {
|
|
|
15
15
|
return this.generatePatterns({
|
|
16
16
|
board,
|
|
17
17
|
getNthVector: (index) => board.getRow(index),
|
|
18
|
-
numberOfVectors: this.config.boardHeight,
|
|
19
18
|
PatternModel: HorizontalPattern,
|
|
19
|
+
vectorsCount: this.config.boardHeight,
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -24,35 +24,35 @@ class PatternsGenerator {
|
|
|
24
24
|
return this.generatePatterns({
|
|
25
25
|
board,
|
|
26
26
|
getNthVector: (index) => board.getColumn(index),
|
|
27
|
-
numberOfVectors: this.config.boardWidth,
|
|
28
27
|
PatternModel: VerticalPattern,
|
|
28
|
+
vectorsCount: this.config.boardWidth,
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
public generatePatterns<P extends Pattern>({
|
|
33
33
|
board,
|
|
34
34
|
getNthVector,
|
|
35
|
-
|
|
35
|
+
vectorsCount,
|
|
36
36
|
PatternModel,
|
|
37
37
|
}: {
|
|
38
38
|
board: Board;
|
|
39
39
|
getNthVector: (index: number) => Cell[];
|
|
40
|
-
numberOfVectors: number;
|
|
41
40
|
PatternModel: new (parameters: { board: Board; cells: Cell[] }) => P;
|
|
41
|
+
vectorsCount: number;
|
|
42
42
|
}): P[] {
|
|
43
|
-
return this.generateVectors({ getNthVector,
|
|
43
|
+
return this.generateVectors({ getNthVector, vectorsCount }).reduce<P[]>((patterns, cells) => {
|
|
44
44
|
return patterns.concat(this.generateCellsPatterns<P>({ board, PatternModel, cells }));
|
|
45
45
|
}, []);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
public generateVectors({
|
|
49
49
|
getNthVector,
|
|
50
|
-
|
|
50
|
+
vectorsCount,
|
|
51
51
|
}: {
|
|
52
52
|
getNthVector: (index: number) => Cell[];
|
|
53
|
-
|
|
53
|
+
vectorsCount: number;
|
|
54
54
|
}): Cell[][] {
|
|
55
|
-
return Array(
|
|
55
|
+
return Array(vectorsCount)
|
|
56
56
|
.fill(0)
|
|
57
57
|
.map((_, index) => getNthVector(index));
|
|
58
58
|
}
|
|
@@ -13,7 +13,7 @@ class ScoresCalculator {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
public calculateBonusScore(pattern: Pattern): number {
|
|
16
|
-
const areAllTilesUsed = pattern.
|
|
16
|
+
const areAllTilesUsed = pattern.getEmptyCellsCount() === this.config.maximumCharactersCount;
|
|
17
17
|
return areAllTilesUsed ? this.config.allTilesBonusScore : 0;
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Trie } from '@kamilmielnik/trie';
|
|
2
2
|
import { Board, Config, Result, Tile } from '@scrabble-solver/types';
|
|
3
|
-
import uniqBy from 'lodash/uniqBy';
|
|
4
3
|
|
|
4
|
+
import getUniquePatterns from './getUniquePatterns';
|
|
5
5
|
import PatternsFiller from './PatternsFiller';
|
|
6
6
|
import PatternsGenerator from './PatternsGenerator';
|
|
7
7
|
import ScoresCalculator from './ScoresCalculator';
|
|
@@ -22,13 +22,13 @@ class Solver {
|
|
|
22
22
|
public solve(board: Board, tiles: Tile[]): Result[] {
|
|
23
23
|
const patterns = this.patternsGenerator.generate(board);
|
|
24
24
|
const filledPatterns = patterns.flatMap((pattern) => this.patternsFiller.fill(pattern, tiles));
|
|
25
|
-
const uniquePatterns =
|
|
25
|
+
const uniquePatterns = getUniquePatterns(filledPatterns);
|
|
26
26
|
const results = uniquePatterns.map(
|
|
27
27
|
(pattern, index) =>
|
|
28
28
|
new Result({
|
|
29
29
|
cells: pattern.cells,
|
|
30
|
+
collisions: pattern.getCollisions().map((collision) => collision.cells),
|
|
30
31
|
id: index,
|
|
31
|
-
numberOfCollisions: pattern.getCollisions().length,
|
|
32
32
|
points: this.scoresCalculator.calculate(pattern),
|
|
33
33
|
}),
|
|
34
34
|
);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Pattern } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
const getPatternHash = (pattern: Pattern): string => {
|
|
4
|
+
return pattern.cells
|
|
5
|
+
.map((cell) => {
|
|
6
|
+
const blank = cell.tile.isBlank ? '!' : '';
|
|
7
|
+
const tile = cell.tile.character + blank;
|
|
8
|
+
// eslint-disable-next-line prefer-template
|
|
9
|
+
return cell.x + ',' + cell.y + ',' + tile;
|
|
10
|
+
})
|
|
11
|
+
.join('-');
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const getUniquePatterns = (patterns: Pattern[]): Pattern[] => {
|
|
15
|
+
const hashes = new Set<string>();
|
|
16
|
+
const uniquePatterns: Pattern[] = [];
|
|
17
|
+
|
|
18
|
+
for (const pattern of patterns) {
|
|
19
|
+
const hash = getPatternHash(pattern);
|
|
20
|
+
|
|
21
|
+
if (!hashes.has(hash)) {
|
|
22
|
+
hashes.add(hash);
|
|
23
|
+
uniquePatterns.push(pattern);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return uniquePatterns;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default getUniquePatterns;
|
|
@@ -4,9 +4,9 @@ declare class Board {
|
|
|
4
4
|
static create(width: number, height: number): Board;
|
|
5
5
|
static fromJson(json: BoardJson): Board;
|
|
6
6
|
static fromStringArray(stringArray: string[]): Board;
|
|
7
|
+
readonly columnsCount: number;
|
|
7
8
|
readonly rows: Cell[][];
|
|
8
|
-
readonly
|
|
9
|
-
readonly numberOfRows: number;
|
|
9
|
+
readonly rowsCount: number;
|
|
10
10
|
constructor({ rows }: {
|
|
11
11
|
rows: Cell[][];
|
|
12
12
|
});
|
|
@@ -9,8 +9,8 @@ const Tile_1 = __importDefault(require("./Tile"));
|
|
|
9
9
|
class Board {
|
|
10
10
|
constructor({ rows }) {
|
|
11
11
|
this.rows = rows;
|
|
12
|
-
this.
|
|
13
|
-
this.
|
|
12
|
+
this.columnsCount = rows[0].length;
|
|
13
|
+
this.rowsCount = rows.length;
|
|
14
14
|
}
|
|
15
15
|
static create(width, height) {
|
|
16
16
|
return Board.fromStringArray(Array(height).fill(Array(width).fill(' ').join('')));
|
|
@@ -31,8 +31,8 @@ class Board {
|
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
get center() {
|
|
34
|
-
const x = Math.floor(this.
|
|
35
|
-
const y = Math.floor(this.
|
|
34
|
+
const x = Math.floor(this.columnsCount / 2);
|
|
35
|
+
const y = Math.floor(this.rowsCount / 2);
|
|
36
36
|
return this.rows[y][x];
|
|
37
37
|
}
|
|
38
38
|
clone() {
|
|
@@ -43,20 +43,20 @@ class Board {
|
|
|
43
43
|
return this.collidesUp(cell) || this.collidesDown(cell) || this.collidesLeft(cell) || this.collidesRight(cell);
|
|
44
44
|
}
|
|
45
45
|
collidesDown({ x, y }) {
|
|
46
|
-
return y < this.
|
|
46
|
+
return y < this.rowsCount - 1 && !this.rows[y + 1][x].isEmpty;
|
|
47
47
|
}
|
|
48
48
|
collidesLeft({ x, y }) {
|
|
49
49
|
return x > 0 && !this.rows[y][x - 1].isEmpty;
|
|
50
50
|
}
|
|
51
51
|
collidesRight({ x, y }) {
|
|
52
|
-
return x < this.
|
|
52
|
+
return x < this.columnsCount - 1 && !this.rows[y][x + 1].isEmpty;
|
|
53
53
|
}
|
|
54
54
|
collidesUp({ x, y }) {
|
|
55
55
|
return y > 0 && !this.rows[y - 1][x].isEmpty;
|
|
56
56
|
}
|
|
57
57
|
equals(other) {
|
|
58
|
-
return (this.
|
|
59
|
-
this.
|
|
58
|
+
return (this.columnsCount === other.columnsCount &&
|
|
59
|
+
this.rowsCount === other.rowsCount &&
|
|
60
60
|
this.rows.every((row, rowIndex) => {
|
|
61
61
|
return row.every((cell, cellIndex) => {
|
|
62
62
|
return cell.equals(other.rows[rowIndex][cellIndex]);
|
|
@@ -1,2 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isBoardJson = void 0;
|
|
4
|
+
const CellJson_1 = require("./CellJson");
|
|
5
|
+
const isBoardJson = (value) => {
|
|
6
|
+
if (!Array.isArray(value)) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
for (const row of value) {
|
|
10
|
+
if (!Array.isArray(value)) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
for (const cell of row) {
|
|
14
|
+
if (!(0, CellJson_1.isCellJson)(cell)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
};
|
|
21
|
+
exports.isBoardJson = isBoardJson;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import CellJson from './CellJson';
|
|
2
2
|
import Tile from './Tile';
|
|
3
3
|
declare class Cell {
|
|
4
|
-
static fromJson(json: CellJson
|
|
5
|
-
static readonly Null: Cell;
|
|
4
|
+
static fromJson(json: CellJson): Cell;
|
|
6
5
|
readonly isEmpty: boolean;
|
|
7
6
|
tile: Tile;
|
|
8
7
|
readonly x: number;
|
|
@@ -17,7 +16,7 @@ declare class Cell {
|
|
|
17
16
|
equals(other: Cell): boolean;
|
|
18
17
|
hasTile(): boolean;
|
|
19
18
|
isCandidate(): boolean;
|
|
20
|
-
toJson(): CellJson
|
|
19
|
+
toJson(): CellJson;
|
|
21
20
|
toString(): string;
|
|
22
21
|
}
|
|
23
22
|
export default Cell;
|