@scrabble-solver/solver 2.8.8 → 2.8.9
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/build/fillPattern.d.ts +5 -0
- package/build/fillPattern.js +52 -0
- package/build/generateEndIndices.d.ts +3 -0
- package/build/generateEndIndices.js +9 -0
- package/build/generateHorizontalPatterns.d.ts +3 -0
- package/build/generateHorizontalPatterns.js +18 -0
- package/build/generatePattern.d.ts +8 -0
- package/build/generatePattern.js +22 -0
- package/build/generatePatterns.d.ts +3 -0
- package/build/generatePatterns.js +13 -0
- package/build/generateStartIndices.d.ts +3 -0
- package/build/generateStartIndices.js +9 -0
- package/build/generateVectors.d.ts +6 -0
- package/build/generateVectors.js +8 -0
- package/build/generateVerticalPatterns.d.ts +3 -0
- package/build/generateVerticalPatterns.js +18 -0
- package/build/getCellsScore.d.ts +3 -0
- package/build/getCellsScore.js +16 -0
- package/build/getPatternHash.d.ts +3 -0
- package/build/getPatternHash.js +13 -0
- package/build/getPatternScore.d.ts +3 -0
- package/build/getPatternScore.js +15 -0
- package/build/getUniquePatterns.js +5 -11
- package/build/index.d.ts +1 -1
- package/build/index.js +3 -3
- package/build/solve.d.ts +4 -0
- package/build/solve.js +23 -0
- package/package.json +6 -6
- package/src/fillPattern.test.ts +107 -0
- package/src/fillPattern.ts +70 -0
- package/src/generateEndIndices.test.ts +18 -0
- package/src/generateEndIndices.ts +10 -0
- package/src/generateHorizontalPatterns.test.ts +31 -0
- package/src/generateHorizontalPatterns.ts +17 -0
- package/src/generatePattern.ts +35 -0
- package/src/generatePatterns.ts +13 -0
- package/src/generateStartIndices.test.ts +14 -0
- package/src/generateStartIndices.ts +10 -0
- package/src/generateVectors.test.ts +12 -0
- package/src/generateVectors.ts +15 -0
- package/src/generateVerticalPatterns.test.ts +32 -0
- package/src/generateVerticalPatterns.ts +17 -0
- package/src/getCellsScore.ts +22 -0
- package/src/getPatternHash.test.ts +17 -0
- package/src/getPatternHash.ts +14 -0
- package/src/getPatternScore.test.ts +60 -0
- package/src/getPatternScore.ts +15 -0
- package/src/getUniquePatterns.ts +1 -10
- package/src/index.ts +1 -1
- package/src/{Solver.test.ts → solve.test.ts} +14 -13
- package/src/solve.ts +25 -0
- package/build/PatternsFiller.d.ts +0 -12
- package/build/PatternsFiller.js +0 -70
- package/build/PatternsGenerator.d.ts +0 -32
- package/build/PatternsGenerator.js +0 -66
- package/build/ScoresCalculator.d.ts +0 -17
- package/build/ScoresCalculator.js +0 -37
- package/build/Solver.d.ts +0 -10
- package/build/Solver.js +0 -30
- package/src/PatternsFiller.test.ts +0 -92
- package/src/PatternsFiller.ts +0 -91
- package/src/PatternsGenerator.test.ts +0 -89
- package/src/PatternsGenerator.ts +0 -103
- package/src/ScoresCalculator.test.ts +0 -70
- package/src/ScoresCalculator.ts +0 -52
- package/src/Solver.ts +0 -39
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Trie } from '@kamilmielnik/trie';
|
|
2
|
+
import { Config, Pattern, Tile } from '@scrabble-solver/types';
|
|
3
|
+
declare const fillPattern: (trie: Trie, config: Config, pattern: Pattern, tiles: Tile[]) => Pattern[];
|
|
4
|
+
export declare const fillPatternRecursive: (results: Pattern[], trie: Trie, config: Config, pattern: Pattern, word: string, tiles: Tile[]) => void;
|
|
5
|
+
export default fillPattern;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fillPatternRecursive = void 0;
|
|
4
|
+
const constants_1 = require("@scrabble-solver/constants");
|
|
5
|
+
const types_1 = require("@scrabble-solver/types");
|
|
6
|
+
const fillPattern = (trie, config, pattern, tiles) => {
|
|
7
|
+
if (pattern.getEmptyCellsCount() > tiles.length) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
const results = [];
|
|
11
|
+
(0, exports.fillPatternRecursive)(results, trie, config, pattern, pattern.toString(), tiles);
|
|
12
|
+
return results;
|
|
13
|
+
};
|
|
14
|
+
const fillPatternRecursive = (
|
|
15
|
+
/** gets mutated when this function is called */
|
|
16
|
+
results, trie, config, pattern, word, tiles) => {
|
|
17
|
+
const indexOfFirstCellWithoutTile = pattern.getIndexOfFirstCellWithoutTile();
|
|
18
|
+
if (indexOfFirstCellWithoutTile === -1) {
|
|
19
|
+
if (trie.has(word) && pattern.getCollisions().every((collision) => trie.has(collision.toString()))) {
|
|
20
|
+
results.push(new types_1.FinalPattern(pattern.clone()));
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
for (let index = 0; index < tiles.length; ++index) {
|
|
25
|
+
const tile = tiles[index];
|
|
26
|
+
const previousTile = pattern.cells[indexOfFirstCellWithoutTile].tile;
|
|
27
|
+
pattern.cells[indexOfFirstCellWithoutTile].tile = tile;
|
|
28
|
+
const indexOfNextCellWithoutTile = pattern.getIndexOfFirstCellWithoutTile();
|
|
29
|
+
const indexOfFirstEmptyLetter = word.indexOf(constants_1.EMPTY_CELL);
|
|
30
|
+
const prefix = word.substring(0, indexOfFirstEmptyLetter);
|
|
31
|
+
const suffix = word.substring(indexOfFirstEmptyLetter + 1);
|
|
32
|
+
const characters = tile.isBlank ? config.alphabet : [tile.character];
|
|
33
|
+
for (const character of characters) {
|
|
34
|
+
const newWordPrefix = prefix + character;
|
|
35
|
+
const newWord = newWordPrefix + suffix;
|
|
36
|
+
tile.character = character;
|
|
37
|
+
if (indexOfNextCellWithoutTile === -1) {
|
|
38
|
+
if (trie.has(newWord) && pattern.getCollisions().every((collision) => trie.has(collision.toString()))) {
|
|
39
|
+
results.push(new types_1.FinalPattern(pattern.clone()));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (trie.hasPrefix(newWordPrefix)) {
|
|
43
|
+
tiles.splice(index, 1);
|
|
44
|
+
(0, exports.fillPatternRecursive)(results, trie, config, pattern, newWord, tiles);
|
|
45
|
+
tiles.splice(index, 0, tile);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
pattern.cells[indexOfFirstCellWithoutTile].tile = previousTile;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
exports.fillPatternRecursive = fillPatternRecursive;
|
|
52
|
+
exports.default = fillPattern;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const generateEndIndices = (cells, startIndex) => {
|
|
4
|
+
return Array(cells.length - startIndex - 1)
|
|
5
|
+
.fill(0)
|
|
6
|
+
.map((_, endIndex) => endIndex + startIndex + 1)
|
|
7
|
+
.filter((endIndex) => endIndex >= cells.length - 1 || !cells[endIndex + 1].hasTile());
|
|
8
|
+
};
|
|
9
|
+
exports.default = generateEndIndices;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const types_1 = require("@scrabble-solver/types");
|
|
7
|
+
const generatePattern_1 = __importDefault(require("./generatePattern"));
|
|
8
|
+
const generateVectors_1 = __importDefault(require("./generateVectors"));
|
|
9
|
+
const generateHorizontalPatterns = (config, board) => {
|
|
10
|
+
const getNthVector = (index) => board.getRow(index);
|
|
11
|
+
const vectorsCount = config.boardHeight;
|
|
12
|
+
const horizontalVectors = (0, generateVectors_1.default)({ getNthVector, vectorsCount });
|
|
13
|
+
const horizontalPatterns = horizontalVectors.flatMap((cells) => {
|
|
14
|
+
return (0, generatePattern_1.default)({ board, config, PatternModel: types_1.HorizontalPattern, cells });
|
|
15
|
+
});
|
|
16
|
+
return horizontalPatterns;
|
|
17
|
+
};
|
|
18
|
+
exports.default = generateHorizontalPatterns;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Board, Cell, Config, Pattern } from '@scrabble-solver/types';
|
|
2
|
+
declare const generatePattern: <P extends Pattern>({ board, cells, config, PatternModel, }: {
|
|
3
|
+
board: Board;
|
|
4
|
+
cells: Cell[];
|
|
5
|
+
config: Config;
|
|
6
|
+
PatternModel: new (board: Board, cells: Cell[]) => P;
|
|
7
|
+
}) => P[];
|
|
8
|
+
export default generatePattern;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const generateEndIndices_1 = __importDefault(require("./generateEndIndices"));
|
|
7
|
+
const generateStartIndices_1 = __importDefault(require("./generateStartIndices"));
|
|
8
|
+
const generatePattern = ({ board, cells, config, PatternModel, }) => {
|
|
9
|
+
const startIndices = (0, generateStartIndices_1.default)(cells);
|
|
10
|
+
return startIndices.flatMap((startIndex) => {
|
|
11
|
+
const endIndices = (0, generateEndIndices_1.default)(cells, startIndex);
|
|
12
|
+
const patterns = [];
|
|
13
|
+
for (const endIndex of endIndices) {
|
|
14
|
+
const pattern = new PatternModel(board, cells.slice(startIndex, endIndex + 1));
|
|
15
|
+
if (pattern.canBePlaced(config)) {
|
|
16
|
+
patterns.push(pattern);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return patterns;
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
exports.default = generatePattern;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const generateHorizontalPatterns_1 = __importDefault(require("./generateHorizontalPatterns"));
|
|
7
|
+
const generateVerticalPatterns_1 = __importDefault(require("./generateVerticalPatterns"));
|
|
8
|
+
const generatePatterns = (config, board) => {
|
|
9
|
+
const horizontalPatterns = (0, generateHorizontalPatterns_1.default)(config, board);
|
|
10
|
+
const verticalPatterns = (0, generateVerticalPatterns_1.default)(config, board);
|
|
11
|
+
return horizontalPatterns.concat(verticalPatterns);
|
|
12
|
+
};
|
|
13
|
+
exports.default = generatePatterns;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const generateStartIndices = (cells) => {
|
|
4
|
+
return Array(cells.length - 1)
|
|
5
|
+
.fill(0)
|
|
6
|
+
.map((_, startIndex) => startIndex)
|
|
7
|
+
.filter((startIndex) => startIndex === 0 || !cells[startIndex - 1].hasTile());
|
|
8
|
+
};
|
|
9
|
+
exports.default = generateStartIndices;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const generateVectors = ({ getNthVector, vectorsCount, }) => {
|
|
4
|
+
return Array(vectorsCount)
|
|
5
|
+
.fill(0)
|
|
6
|
+
.map((_, index) => getNthVector(index));
|
|
7
|
+
};
|
|
8
|
+
exports.default = generateVectors;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const types_1 = require("@scrabble-solver/types");
|
|
7
|
+
const generatePattern_1 = __importDefault(require("./generatePattern"));
|
|
8
|
+
const generateVectors_1 = __importDefault(require("./generateVectors"));
|
|
9
|
+
const generateVerticalPatterns = (config, board) => {
|
|
10
|
+
const getNthVector = (index) => board.getColumn(index);
|
|
11
|
+
const vectorsCount = config.boardWidth;
|
|
12
|
+
const verticalVectors = (0, generateVectors_1.default)({ getNthVector, vectorsCount });
|
|
13
|
+
const verticalPatterns = verticalVectors.flatMap((cells) => {
|
|
14
|
+
return (0, generatePattern_1.default)({ board, config, PatternModel: types_1.VerticalPattern, cells });
|
|
15
|
+
});
|
|
16
|
+
return verticalPatterns;
|
|
17
|
+
};
|
|
18
|
+
exports.default = generateVerticalPatterns;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const constants_1 = require("@scrabble-solver/constants");
|
|
4
|
+
const getCellsScore = (config, cells) => {
|
|
5
|
+
const total = cells.reduce(({ multiplier, score }, cell) => {
|
|
6
|
+
const bonus = config.getCellBonus(cell);
|
|
7
|
+
const { characterMultiplier, wordMultiplier } = bonus && bonus.canApply(config, cell) ? bonus.value : constants_1.NO_BONUS;
|
|
8
|
+
const characterScore = cell.tile.isBlank ? config.blankScore : config.pointsMap[cell.tile.character];
|
|
9
|
+
return {
|
|
10
|
+
multiplier: multiplier * wordMultiplier,
|
|
11
|
+
score: score + characterScore * characterMultiplier,
|
|
12
|
+
};
|
|
13
|
+
}, { multiplier: 1, score: 0 });
|
|
14
|
+
return total.score * total.multiplier;
|
|
15
|
+
};
|
|
16
|
+
exports.default = getCellsScore;
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
exports.default = getPatternHash;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const getCellsScore_1 = __importDefault(require("./getCellsScore"));
|
|
7
|
+
const getPatternScore = (config, pattern) => {
|
|
8
|
+
const areAllTilesUsed = pattern.getEmptyCellsCount() === config.maximumCharactersCount;
|
|
9
|
+
const bonusScore = areAllTilesUsed ? config.allTilesBonusScore : 0;
|
|
10
|
+
const score = pattern
|
|
11
|
+
.getCollisions()
|
|
12
|
+
.reduce((sum, collision) => sum + (0, getCellsScore_1.default)(config, collision.cells), (0, getCellsScore_1.default)(config, pattern.cells));
|
|
13
|
+
return score + bonusScore;
|
|
14
|
+
};
|
|
15
|
+
exports.default = getPatternScore;
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
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('-');
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
12
4
|
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const getPatternHash_1 = __importDefault(require("./getPatternHash"));
|
|
13
7
|
const getUniquePatterns = (patterns) => {
|
|
14
8
|
const hashes = new Set();
|
|
15
9
|
const uniquePatterns = [];
|
|
16
10
|
for (const pattern of patterns) {
|
|
17
|
-
const hash =
|
|
11
|
+
const hash = (0, getPatternHash_1.default)(pattern);
|
|
18
12
|
if (!hashes.has(hash)) {
|
|
19
13
|
hashes.add(hash);
|
|
20
14
|
uniquePatterns.push(pattern);
|
package/build/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default } from './
|
|
1
|
+
export { default as solve } from './solve';
|
package/build/index.js
CHANGED
|
@@ -3,6 +3,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
-
var
|
|
8
|
-
Object.defineProperty(exports, "
|
|
6
|
+
exports.solve = void 0;
|
|
7
|
+
var solve_1 = require("./solve");
|
|
8
|
+
Object.defineProperty(exports, "solve", { enumerable: true, get: function () { return __importDefault(solve_1).default; } });
|
package/build/solve.d.ts
ADDED
package/build/solve.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const types_1 = require("@scrabble-solver/types");
|
|
7
|
+
const fillPattern_1 = __importDefault(require("./fillPattern"));
|
|
8
|
+
const generatePatterns_1 = __importDefault(require("./generatePatterns"));
|
|
9
|
+
const getPatternScore_1 = __importDefault(require("./getPatternScore"));
|
|
10
|
+
const getUniquePatterns_1 = __importDefault(require("./getUniquePatterns"));
|
|
11
|
+
const solve = (trie, config, board, tiles) => {
|
|
12
|
+
const patterns = (0, generatePatterns_1.default)(config, board);
|
|
13
|
+
const filledPatterns = patterns.flatMap((pattern) => (0, fillPattern_1.default)(trie, config, pattern, tiles));
|
|
14
|
+
const uniquePatterns = (0, getUniquePatterns_1.default)(filledPatterns);
|
|
15
|
+
const results = uniquePatterns.map((pattern, index) => new types_1.Result({
|
|
16
|
+
cells: pattern.cells,
|
|
17
|
+
collisions: pattern.getCollisions().map((collision) => collision.cells),
|
|
18
|
+
id: index,
|
|
19
|
+
points: (0, getPatternScore_1.default)(config, pattern),
|
|
20
|
+
}));
|
|
21
|
+
return results;
|
|
22
|
+
};
|
|
23
|
+
exports.default = solve;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrabble-solver/solver",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.9",
|
|
4
4
|
"description": "Scrabble Solver 2 - Solver",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@kamilmielnik/trie": "^2.0.0",
|
|
28
|
-
"@scrabble-solver/types": "^2.8.
|
|
28
|
+
"@scrabble-solver/types": "^2.8.9"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@scrabble-solver/configs": "^2.8.
|
|
32
|
-
"@scrabble-solver/constants": "^2.8.
|
|
33
|
-
"@scrabble-solver/dictionaries": "^2.8.
|
|
31
|
+
"@scrabble-solver/configs": "^2.8.9",
|
|
32
|
+
"@scrabble-solver/constants": "^2.8.9",
|
|
33
|
+
"@scrabble-solver/dictionaries": "^2.8.9"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "8557cc2c5214e6689a5c59373b228b28f5dd8ed4"
|
|
36
36
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Trie } from '@kamilmielnik/trie';
|
|
2
|
+
import { literaki } from '@scrabble-solver/configs';
|
|
3
|
+
import { dictionaries } from '@scrabble-solver/dictionaries';
|
|
4
|
+
import { Board, Cell, FinalPattern, Locale, Pattern, Tile, VerticalPattern } from '@scrabble-solver/types';
|
|
5
|
+
|
|
6
|
+
import fillPattern, { fillPatternRecursive } from './fillPattern';
|
|
7
|
+
|
|
8
|
+
const board = Board.fromStringArray([' t ', 'do ', ' ']);
|
|
9
|
+
const locale = Locale.PL_PL;
|
|
10
|
+
const config = literaki[locale];
|
|
11
|
+
|
|
12
|
+
describe('fillPattern', () => {
|
|
13
|
+
let trie: Trie | undefined;
|
|
14
|
+
|
|
15
|
+
beforeAll(() => {
|
|
16
|
+
return dictionaries.get(locale).then((loadedTrie) => {
|
|
17
|
+
trie = loadedTrie;
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('fills patterns', () => {
|
|
22
|
+
const pattern = new VerticalPattern(board, [
|
|
23
|
+
new Cell({ x: 0, y: 0 }),
|
|
24
|
+
new Cell({ x: 0, y: 1, tile: new Tile({ character: 'd', isBlank: false }) }),
|
|
25
|
+
new Cell({ x: 0, y: 2 }),
|
|
26
|
+
]);
|
|
27
|
+
const tiles = [
|
|
28
|
+
new Tile({ character: 'o', isBlank: false }),
|
|
29
|
+
new Tile({ character: 'a', isBlank: false }),
|
|
30
|
+
new Tile({ character: 'd', isBlank: false }),
|
|
31
|
+
];
|
|
32
|
+
const filledPatterns = fillPattern(trie!, config, pattern, tiles);
|
|
33
|
+
|
|
34
|
+
expect(filledPatterns.length).toBe(1);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('fills patterns with blanks', () => {
|
|
38
|
+
const pattern = new VerticalPattern(board, [board.rows[0][1], board.rows[1][1], board.rows[2][1]]);
|
|
39
|
+
const tiles = [new Tile({ character: ' ', isBlank: true }), new Tile({ character: 'ó', isBlank: false })];
|
|
40
|
+
const filledPatterns = fillPattern(trie!, config, pattern, tiles);
|
|
41
|
+
|
|
42
|
+
expect(filledPatterns.map(String)).toEqual([
|
|
43
|
+
'toć',
|
|
44
|
+
'tog',
|
|
45
|
+
'toi',
|
|
46
|
+
'tok',
|
|
47
|
+
'tom',
|
|
48
|
+
'ton',
|
|
49
|
+
'toń',
|
|
50
|
+
'top',
|
|
51
|
+
'tor',
|
|
52
|
+
'tos',
|
|
53
|
+
'toy',
|
|
54
|
+
'toż',
|
|
55
|
+
]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('does not modify filled patterns', () => {
|
|
59
|
+
const pattern = new FinalPattern(
|
|
60
|
+
new VerticalPattern(board, [
|
|
61
|
+
new Cell({ x: 0, y: 0, isEmpty: false, tile: new Tile({ character: 'o', isBlank: false }) }),
|
|
62
|
+
new Cell({ x: 0, y: 1, isEmpty: false, tile: new Tile({ character: 'k', isBlank: false }) }),
|
|
63
|
+
new Cell({ x: 0, y: 2, isEmpty: false, tile: new Tile({ character: 'o', isBlank: false }) }),
|
|
64
|
+
]),
|
|
65
|
+
);
|
|
66
|
+
const tiles = [new Tile({ character: 'ń', isBlank: false })];
|
|
67
|
+
const filledPatterns = fillPattern(trie!, config, pattern, tiles);
|
|
68
|
+
|
|
69
|
+
expect(filledPatterns.length).toBe(1);
|
|
70
|
+
expect(filledPatterns[0]).toEqual(pattern);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('does not accept non-placeable filled patterns', () => {
|
|
74
|
+
const pattern = new VerticalPattern(board, [
|
|
75
|
+
new Cell({ x: 0, y: 0, isEmpty: false, tile: new Tile({ character: 'd', isBlank: false }) }),
|
|
76
|
+
new Cell({ x: 0, y: 1, isEmpty: false, tile: new Tile({ character: 'd', isBlank: false }) }),
|
|
77
|
+
new Cell({ x: 0, y: 2, isEmpty: false, tile: new Tile({ character: 'd', isBlank: false }) }),
|
|
78
|
+
]);
|
|
79
|
+
const tiles = [new Tile({ character: 'ń', isBlank: false })];
|
|
80
|
+
const filledPatterns = fillPattern(trie!, config, pattern, tiles);
|
|
81
|
+
|
|
82
|
+
expect(filledPatterns.length).toBe(0);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('fillPatternRecursive', () => {
|
|
87
|
+
let trie: Trie | undefined;
|
|
88
|
+
|
|
89
|
+
beforeAll(() => {
|
|
90
|
+
return dictionaries.get(locale).then((loadedTrie) => {
|
|
91
|
+
trie = loadedTrie;
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('checks collisions', () => {
|
|
96
|
+
const pattern = new VerticalPattern(board, [
|
|
97
|
+
new Cell({ x: 2, y: 0, isEmpty: true, tile: new Tile({ character: 'k', isBlank: false }) }),
|
|
98
|
+
new Cell({ x: 2, y: 1, isEmpty: true, tile: new Tile({ character: 'o', isBlank: false }) }),
|
|
99
|
+
new Cell({ x: 2, y: 2, isEmpty: true, tile: new Tile({ character: 't', isBlank: false }) }),
|
|
100
|
+
]);
|
|
101
|
+
const results: Pattern[] = [];
|
|
102
|
+
const tiles = [new Tile({ character: 'ń', isBlank: false })];
|
|
103
|
+
fillPatternRecursive(results, trie!, config, pattern, pattern.toString(), tiles);
|
|
104
|
+
|
|
105
|
+
expect(results.length).toBe(0);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* eslint-disable max-params, max-statements */
|
|
2
|
+
import { Trie } from '@kamilmielnik/trie';
|
|
3
|
+
import { EMPTY_CELL } from '@scrabble-solver/constants';
|
|
4
|
+
import { Config, FinalPattern, Pattern, Tile } from '@scrabble-solver/types';
|
|
5
|
+
|
|
6
|
+
const fillPattern = (trie: Trie, config: Config, pattern: Pattern, tiles: Tile[]): Pattern[] => {
|
|
7
|
+
if (pattern.getEmptyCellsCount() > tiles.length) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const results: Pattern[] = [];
|
|
12
|
+
|
|
13
|
+
fillPatternRecursive(results, trie, config, pattern, pattern.toString(), tiles);
|
|
14
|
+
|
|
15
|
+
return results;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const fillPatternRecursive = (
|
|
19
|
+
/** gets mutated when this function is called */
|
|
20
|
+
results: Pattern[],
|
|
21
|
+
trie: Trie,
|
|
22
|
+
config: Config,
|
|
23
|
+
pattern: Pattern,
|
|
24
|
+
word: string,
|
|
25
|
+
tiles: Tile[],
|
|
26
|
+
): void => {
|
|
27
|
+
const indexOfFirstCellWithoutTile = pattern.getIndexOfFirstCellWithoutTile();
|
|
28
|
+
|
|
29
|
+
if (indexOfFirstCellWithoutTile === -1) {
|
|
30
|
+
if (trie.has(word) && pattern.getCollisions().every((collision) => trie.has(collision.toString()))) {
|
|
31
|
+
results.push(new FinalPattern(pattern.clone()));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (let index = 0; index < tiles.length; ++index) {
|
|
38
|
+
const tile = tiles[index];
|
|
39
|
+
const previousTile = pattern.cells[indexOfFirstCellWithoutTile].tile;
|
|
40
|
+
|
|
41
|
+
pattern.cells[indexOfFirstCellWithoutTile].tile = tile;
|
|
42
|
+
|
|
43
|
+
const indexOfNextCellWithoutTile = pattern.getIndexOfFirstCellWithoutTile();
|
|
44
|
+
const indexOfFirstEmptyLetter = word.indexOf(EMPTY_CELL);
|
|
45
|
+
const prefix = word.substring(0, indexOfFirstEmptyLetter);
|
|
46
|
+
const suffix = word.substring(indexOfFirstEmptyLetter + 1);
|
|
47
|
+
const characters = tile.isBlank ? config.alphabet : [tile.character];
|
|
48
|
+
|
|
49
|
+
for (const character of characters) {
|
|
50
|
+
const newWordPrefix = prefix + character;
|
|
51
|
+
const newWord = newWordPrefix + suffix;
|
|
52
|
+
|
|
53
|
+
tile.character = character;
|
|
54
|
+
|
|
55
|
+
if (indexOfNextCellWithoutTile === -1) {
|
|
56
|
+
if (trie.has(newWord) && pattern.getCollisions().every((collision) => trie.has(collision.toString()))) {
|
|
57
|
+
results.push(new FinalPattern(pattern.clone()));
|
|
58
|
+
}
|
|
59
|
+
} else if (trie.hasPrefix(newWordPrefix)) {
|
|
60
|
+
tiles.splice(index, 1);
|
|
61
|
+
fillPatternRecursive(results, trie, config, pattern, newWord, tiles);
|
|
62
|
+
tiles.splice(index, 0, tile);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pattern.cells[indexOfFirstCellWithoutTile].tile = previousTile;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export default fillPattern;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Cell } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import generateEndIndices from './generateEndIndices';
|
|
4
|
+
|
|
5
|
+
describe('generateEndIndices', () => {
|
|
6
|
+
const emptyCell: Cell = { hasTile: () => false } as Cell;
|
|
7
|
+
const filledCell: Cell = { hasTile: () => true } as Cell;
|
|
8
|
+
|
|
9
|
+
it('finds indices where a new word can end - XXOOOX', () => {
|
|
10
|
+
const cells = [filledCell, filledCell, emptyCell, emptyCell, emptyCell, filledCell];
|
|
11
|
+
expect(generateEndIndices(cells, 3)).toEqual([5]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('finds indices where a new word can end - XXOOOXO', () => {
|
|
15
|
+
const cells = [filledCell, filledCell, emptyCell, emptyCell, emptyCell, filledCell, emptyCell];
|
|
16
|
+
expect(generateEndIndices(cells, 3)).toEqual([5, 6]);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Cell } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
const generateEndIndices = (cells: Cell[], startIndex: number): number[] => {
|
|
4
|
+
return Array(cells.length - startIndex - 1)
|
|
5
|
+
.fill(0)
|
|
6
|
+
.map((_, endIndex) => endIndex + startIndex + 1)
|
|
7
|
+
.filter((endIndex) => endIndex >= cells.length - 1 || !cells[endIndex + 1].hasTile());
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default generateEndIndices;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { EMPTY_CELL } from '@scrabble-solver/constants';
|
|
2
|
+
import { Board, Config } from '@scrabble-solver/types';
|
|
3
|
+
|
|
4
|
+
import generateHorizontalPatterns from './generateHorizontalPatterns';
|
|
5
|
+
|
|
6
|
+
const board = Board.fromStringArray([' t ', 'do ', ' ']);
|
|
7
|
+
|
|
8
|
+
const config = {
|
|
9
|
+
boardHeight: 3,
|
|
10
|
+
boardWidth: 3,
|
|
11
|
+
maximumCharactersCount: 7,
|
|
12
|
+
} as Config;
|
|
13
|
+
|
|
14
|
+
describe('generateHorizontalPatterns', () => {
|
|
15
|
+
it('generates some horizontal patterns', () => {
|
|
16
|
+
expect(generateHorizontalPatterns(config, board).length).toBeGreaterThan(0);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('generates proper horizontal patterns', () => {
|
|
20
|
+
const horizontal = generateHorizontalPatterns(config, board);
|
|
21
|
+
expect(horizontal.map(({ cells }) => cells.map(String))).toEqual([
|
|
22
|
+
[EMPTY_CELL, 't'],
|
|
23
|
+
[EMPTY_CELL, 't', EMPTY_CELL],
|
|
24
|
+
['t', EMPTY_CELL],
|
|
25
|
+
['d', 'o', EMPTY_CELL],
|
|
26
|
+
[EMPTY_CELL, EMPTY_CELL],
|
|
27
|
+
[EMPTY_CELL, EMPTY_CELL, EMPTY_CELL],
|
|
28
|
+
[EMPTY_CELL, EMPTY_CELL],
|
|
29
|
+
]);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Board, Config, HorizontalPattern, Pattern } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import generatePattern from './generatePattern';
|
|
4
|
+
import generateVectors from './generateVectors';
|
|
5
|
+
|
|
6
|
+
const generateHorizontalPatterns = (config: Config, board: Board): Pattern[] => {
|
|
7
|
+
const getNthVector = (index: number) => board.getRow(index);
|
|
8
|
+
const vectorsCount = config.boardHeight;
|
|
9
|
+
const horizontalVectors = generateVectors({ getNthVector, vectorsCount });
|
|
10
|
+
const horizontalPatterns = horizontalVectors.flatMap((cells) => {
|
|
11
|
+
return generatePattern({ board, config, PatternModel: HorizontalPattern, cells });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
return horizontalPatterns;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default generateHorizontalPatterns;
|