@scrabble-solver/solver 2.8.8 → 2.8.10
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 +16 -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 +15 -0
- package/build/generateVectors.d.ts +7 -0
- package/build/generateVectors.js +6 -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 +22 -0
- package/package.json +7 -7
- package/src/fillPattern.test.ts +107 -0
- package/src/fillPattern.ts +70 -0
- package/src/generateEndIndices.test.ts +23 -0
- package/src/generateEndIndices.ts +21 -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 +19 -0
- package/src/generateStartIndices.ts +19 -0
- package/src/generateVectors.test.ts +12 -0
- package/src/generateVectors.ts +12 -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} +17 -16
- package/src/solve.ts +23 -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,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;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Board, Cell, Config, Pattern } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import generateEndIndices from './generateEndIndices';
|
|
4
|
+
import generateStartIndices from './generateStartIndices';
|
|
5
|
+
|
|
6
|
+
const generatePattern = <P extends Pattern>({
|
|
7
|
+
board,
|
|
8
|
+
cells,
|
|
9
|
+
config,
|
|
10
|
+
PatternModel,
|
|
11
|
+
}: {
|
|
12
|
+
board: Board;
|
|
13
|
+
cells: Cell[];
|
|
14
|
+
config: Config;
|
|
15
|
+
PatternModel: new (board: Board, cells: Cell[]) => P;
|
|
16
|
+
}): P[] => {
|
|
17
|
+
const startIndices = generateStartIndices(cells);
|
|
18
|
+
|
|
19
|
+
return startIndices.flatMap((startIndex) => {
|
|
20
|
+
const endIndices = generateEndIndices(cells, startIndex);
|
|
21
|
+
const patterns: P[] = [];
|
|
22
|
+
|
|
23
|
+
for (const endIndex of endIndices) {
|
|
24
|
+
const pattern = new PatternModel(board, cells.slice(startIndex, endIndex + 1));
|
|
25
|
+
|
|
26
|
+
if (pattern.canBePlaced(config)) {
|
|
27
|
+
patterns.push(pattern);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return patterns;
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default generatePattern;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Board, Config, Pattern } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import generateHorizontalPatterns from './generateHorizontalPatterns';
|
|
4
|
+
import generateVerticalPatterns from './generateVerticalPatterns';
|
|
5
|
+
|
|
6
|
+
const generatePatterns = (config: Config, board: Board): Pattern[] => {
|
|
7
|
+
const horizontalPatterns = generateHorizontalPatterns(config, board);
|
|
8
|
+
const verticalPatterns = generateVerticalPatterns(config, board);
|
|
9
|
+
|
|
10
|
+
return horizontalPatterns.concat(verticalPatterns);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default generatePatterns;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Cell } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import generateStartIndices from './generateStartIndices';
|
|
4
|
+
|
|
5
|
+
describe('generateStartIndices', () => {
|
|
6
|
+
const emptyCell: Cell = { hasTile: () => false } as Cell;
|
|
7
|
+
const filledCell: Cell = { hasTile: () => true } as Cell;
|
|
8
|
+
|
|
9
|
+
it('returns empty array when no cells given', () => {
|
|
10
|
+
const cells: Cell[] = [];
|
|
11
|
+
expect(generateStartIndices(cells).length).toBe(0);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('finds indices where a new word can start', () => {
|
|
15
|
+
const cells = [filledCell, filledCell, emptyCell, emptyCell, emptyCell, filledCell];
|
|
16
|
+
const startIndices = generateStartIndices(cells);
|
|
17
|
+
expect(startIndices).toEqual([0, 3, 4]);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Cell } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
const generateStartIndices = (cells: Cell[]): number[] => {
|
|
4
|
+
if (cells.length === 0) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const startIndices: number[] = [0];
|
|
9
|
+
|
|
10
|
+
for (let startIndex = 1; startIndex < cells.length - 1; ++startIndex) {
|
|
11
|
+
if (!cells[startIndex - 1].hasTile()) {
|
|
12
|
+
startIndices.push(startIndex);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return startIndices;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default generateStartIndices;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import generateVectors from './generateVectors';
|
|
2
|
+
|
|
3
|
+
describe('generateVectors', () => {
|
|
4
|
+
it('generates given number of vectors', () => {
|
|
5
|
+
const vectors = generateVectors({
|
|
6
|
+
getNthVector: () => [],
|
|
7
|
+
vectorsCount: 3,
|
|
8
|
+
});
|
|
9
|
+
expect(vectors.length).toBe(3);
|
|
10
|
+
expect(vectors).toEqual([[], [], []]);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Cell } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
interface Parameters {
|
|
4
|
+
getNthVector: (index: number) => Cell[];
|
|
5
|
+
vectorsCount: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const generateVectors = ({ getNthVector, vectorsCount }: Parameters): Cell[][] => {
|
|
9
|
+
return Array.from({ length: vectorsCount }, (_, index) => getNthVector(index));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default generateVectors;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { EMPTY_CELL } from '@scrabble-solver/constants';
|
|
2
|
+
import { Board, Config } from '@scrabble-solver/types';
|
|
3
|
+
|
|
4
|
+
import generateVerticalPatterns from './generateVerticalPatterns';
|
|
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('generateVerticalPatterns', () => {
|
|
15
|
+
it('generates some vertical patterns', () => {
|
|
16
|
+
expect(generateVerticalPatterns(config, board).length).toBeGreaterThan(0);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('generates proper vertical patterns', () => {
|
|
20
|
+
const vertical = generateVerticalPatterns(config, board);
|
|
21
|
+
|
|
22
|
+
expect(vertical.map(({ cells }) => cells.map(String))).toEqual([
|
|
23
|
+
[EMPTY_CELL, 'd'],
|
|
24
|
+
[EMPTY_CELL, 'd', EMPTY_CELL],
|
|
25
|
+
['d', EMPTY_CELL],
|
|
26
|
+
['t', 'o', EMPTY_CELL],
|
|
27
|
+
[EMPTY_CELL, EMPTY_CELL],
|
|
28
|
+
[EMPTY_CELL, EMPTY_CELL, EMPTY_CELL],
|
|
29
|
+
[EMPTY_CELL, EMPTY_CELL],
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Board, Config, Pattern, VerticalPattern } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import generatePattern from './generatePattern';
|
|
4
|
+
import generateVectors from './generateVectors';
|
|
5
|
+
|
|
6
|
+
const generateVerticalPatterns = (config: Config, board: Board): Pattern[] => {
|
|
7
|
+
const getNthVector = (index: number) => board.getColumn(index);
|
|
8
|
+
const vectorsCount = config.boardWidth;
|
|
9
|
+
const verticalVectors = generateVectors({ getNthVector, vectorsCount });
|
|
10
|
+
const verticalPatterns = verticalVectors.flatMap((cells) => {
|
|
11
|
+
return generatePattern({ board, config, PatternModel: VerticalPattern, cells });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
return verticalPatterns;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default generateVerticalPatterns;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NO_BONUS } from '@scrabble-solver/constants';
|
|
2
|
+
import { Cell, Config } from '@scrabble-solver/types';
|
|
3
|
+
|
|
4
|
+
const getCellsScore = (config: Config, cells: Cell[]): number => {
|
|
5
|
+
const total = cells.reduce(
|
|
6
|
+
({ multiplier, score }, cell: Cell): { multiplier: number; score: number } => {
|
|
7
|
+
const bonus = config.getCellBonus(cell);
|
|
8
|
+
const { characterMultiplier, wordMultiplier } = bonus && bonus.canApply(config, cell) ? bonus.value : NO_BONUS;
|
|
9
|
+
const characterScore = cell.tile.isBlank ? config.blankScore : config.pointsMap[cell.tile.character];
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
multiplier: multiplier * wordMultiplier,
|
|
13
|
+
score: score + characterScore * characterMultiplier,
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
{ multiplier: 1, score: 0 },
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
return total.score * total.multiplier;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default getCellsScore;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Board, Cell, Pattern, Tile } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import getPatternHash from './getPatternHash';
|
|
4
|
+
|
|
5
|
+
describe('getPatternHash', () => {
|
|
6
|
+
const board = Board.fromStringArray([' t ', 'do ', ' ']);
|
|
7
|
+
|
|
8
|
+
it('hashes blanks', () => {
|
|
9
|
+
const pattern = new Pattern(board, [
|
|
10
|
+
new Cell({ x: 0, y: 0, tile: new Tile({ character: 'a', isBlank: false }) }),
|
|
11
|
+
new Cell({ x: 0, y: 1, tile: new Tile({ character: 'b', isBlank: true }) }),
|
|
12
|
+
new Cell({ x: 0, y: 2, tile: new Tile({ character: 'c', isBlank: false }) }),
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
expect(getPatternHash(pattern)).toBe('0,0,a-0,1,b!-0,2,c');
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
export default getPatternHash;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { literaki } from '@scrabble-solver/configs';
|
|
2
|
+
import { Board, Cell, HorizontalPattern, Locale, Pattern, Tile, VerticalPattern } from '@scrabble-solver/types';
|
|
3
|
+
|
|
4
|
+
import getPatternScore from './getPatternScore';
|
|
5
|
+
|
|
6
|
+
const config = literaki[Locale.PL_PL];
|
|
7
|
+
const board = Board.fromStringArray([
|
|
8
|
+
' kasom ',
|
|
9
|
+
' i ',
|
|
10
|
+
' napiją ',
|
|
11
|
+
' w i ',
|
|
12
|
+
' krabim ',
|
|
13
|
+
' z ',
|
|
14
|
+
' ę eh ',
|
|
15
|
+
'f s srać ',
|
|
16
|
+
'i z t s ',
|
|
17
|
+
'knebel e ',
|
|
18
|
+
'a ew warcz',
|
|
19
|
+
'ł żyło wody ',
|
|
20
|
+
'o pecyj chu',
|
|
21
|
+
' y ',
|
|
22
|
+
' t ',
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
describe('getPatternScore', () => {
|
|
26
|
+
it('gives proper score without collisions', () => {
|
|
27
|
+
const pattern = new Pattern(board, [
|
|
28
|
+
new Cell({ x: 0, y: 0, tile: new Tile({ character: 'ź' }), isEmpty: true }),
|
|
29
|
+
new Cell({ x: 1, y: 0, tile: new Tile({ character: 'a' }), isEmpty: true }),
|
|
30
|
+
new Cell({ x: 2, y: 0, tile: new Tile({ character: 'a' }), isEmpty: true }),
|
|
31
|
+
new Cell({ x: 3, y: 0, tile: new Tile({ character: 'a' }), isEmpty: true }),
|
|
32
|
+
new Cell({ x: 4, y: 0, tile: new Tile({ character: 'a' }), isEmpty: true }),
|
|
33
|
+
new Cell({ x: 5, y: 0, tile: new Tile({ character: 'a' }), isEmpty: true }),
|
|
34
|
+
new Cell({ x: 6, y: 0, tile: new Tile({ character: 'a' }), isEmpty: true }),
|
|
35
|
+
new Cell({ x: 7, y: 0, tile: new Tile({ character: 'ź' }), isEmpty: false }),
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
expect(getPatternScore(config, pattern)).toBe(128);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('gives proper score with collisions', () => {
|
|
42
|
+
const pattern = new VerticalPattern(board, [
|
|
43
|
+
new Cell({ x: 2, y: 11, tile: new Tile({ character: 'l' }), isEmpty: true }),
|
|
44
|
+
new Cell({ x: 2, y: 12, tile: new Tile({ character: 'i' }), isEmpty: true }),
|
|
45
|
+
new Cell({ x: 2, y: 13, tile: new Tile({ character: 'n' }), isEmpty: true }),
|
|
46
|
+
new Cell({ x: 2, y: 14, tile: new Tile({ character: 'o' }), isEmpty: true }),
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
expect(getPatternScore(config, pattern)).toBe(44);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('gives proper score for blank', () => {
|
|
53
|
+
const pattern = new HorizontalPattern(board, [
|
|
54
|
+
new Cell({ x: 13, y: 14, tile: new Tile({ character: 'o', isBlank: true }), isEmpty: true }),
|
|
55
|
+
new Cell({ x: 12, y: 14, tile: new Tile({ character: 't' }), isEmpty: false }),
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
expect(getPatternScore(config, pattern)).toBe(2);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Config, Pattern } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import getCellsScore from './getCellsScore';
|
|
4
|
+
|
|
5
|
+
const getPatternScore = (config: Config, pattern: Pattern) => {
|
|
6
|
+
const areAllTilesUsed = pattern.getEmptyCellsCount() === config.maximumCharactersCount;
|
|
7
|
+
const bonusScore = areAllTilesUsed ? config.allTilesBonusScore : 0;
|
|
8
|
+
const score = pattern
|
|
9
|
+
.getCollisions()
|
|
10
|
+
.reduce((sum, collision) => sum + getCellsScore(config, collision.cells), getCellsScore(config, pattern.cells));
|
|
11
|
+
|
|
12
|
+
return score + bonusScore;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default getPatternScore;
|
package/src/getUniquePatterns.ts
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { Pattern } from '@scrabble-solver/types';
|
|
2
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('-');
|
|
12
|
-
};
|
|
3
|
+
import getPatternHash from './getPatternHash';
|
|
13
4
|
|
|
14
5
|
const getUniquePatterns = (patterns: Pattern[]): Pattern[] => {
|
|
15
6
|
const hashes = new Set<string>();
|
package/src/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default } from './
|
|
1
|
+
export { default as solve } from './solve';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { Trie } from '@kamilmielnik/trie';
|
|
1
2
|
import { literaki, scrabble } from '@scrabble-solver/configs';
|
|
2
3
|
import { dictionaries } from '@scrabble-solver/dictionaries';
|
|
3
4
|
import { Board, Locale, Result, Tile } from '@scrabble-solver/types';
|
|
4
5
|
|
|
5
|
-
import
|
|
6
|
+
import solve from './solve';
|
|
6
7
|
|
|
7
8
|
const generateTiles = (characters: string[]): Tile[] => {
|
|
8
9
|
return characters.map((character) => new Tile({ character, isBlank: false }));
|
|
@@ -15,14 +16,14 @@ const getBestResult = ([firstResult, ...results]: Result[]): Result => {
|
|
|
15
16
|
);
|
|
16
17
|
};
|
|
17
18
|
|
|
18
|
-
describe('
|
|
19
|
+
describe('solve - PL', () => {
|
|
19
20
|
const locale = Locale.PL_PL;
|
|
20
21
|
const config = literaki[locale];
|
|
21
|
-
let
|
|
22
|
+
let trie: Trie | undefined;
|
|
22
23
|
|
|
23
24
|
beforeAll(() => {
|
|
24
|
-
return dictionaries.get(locale).then((
|
|
25
|
-
|
|
25
|
+
return dictionaries.get(locale).then((loadedTrie) => {
|
|
26
|
+
trie = loadedTrie;
|
|
26
27
|
});
|
|
27
28
|
});
|
|
28
29
|
|
|
@@ -45,7 +46,7 @@ describe('Solver - PL', () => {
|
|
|
45
46
|
' ',
|
|
46
47
|
]);
|
|
47
48
|
const tiles = generateTiles(['l', 'i', 'n', 'o']);
|
|
48
|
-
const results =
|
|
49
|
+
const results = solve(trie!, config, board, tiles);
|
|
49
50
|
expect(results.length).toBe(61);
|
|
50
51
|
});
|
|
51
52
|
|
|
@@ -68,8 +69,8 @@ describe('Solver - PL', () => {
|
|
|
68
69
|
' ',
|
|
69
70
|
]);
|
|
70
71
|
const tiles = generateTiles(['a', 'a', 'ą', 'r', 't', 'w', 'z']);
|
|
71
|
-
const results =
|
|
72
|
-
const bestResult = getBestResult(results);
|
|
72
|
+
const results = solve(trie!, config, board, tiles);
|
|
73
|
+
const bestResult = getBestResult(results.map((result) => Result.fromJson(result)));
|
|
73
74
|
expect(bestResult.word).toBe('zmartwychwstałą');
|
|
74
75
|
expect(bestResult.points).toBe(682);
|
|
75
76
|
});
|
|
@@ -93,21 +94,21 @@ describe('Solver - PL', () => {
|
|
|
93
94
|
'ar ń m ',
|
|
94
95
|
]);
|
|
95
96
|
const tiles = generateTiles(['ą', 'c', 'h', 't', 'w', 'w', 'z']);
|
|
96
|
-
const results =
|
|
97
|
-
const bestResult = getBestResult(results);
|
|
97
|
+
const results = solve(trie!, config, board, tiles);
|
|
98
|
+
const bestResult = getBestResult(results.map((result) => Result.fromJson(result)));
|
|
98
99
|
expect(bestResult.word).toBe('zmartwychwstałą');
|
|
99
100
|
expect(bestResult.points).toBe(1157);
|
|
100
101
|
});
|
|
101
102
|
});
|
|
102
103
|
|
|
103
|
-
describe('
|
|
104
|
+
describe('solve - ES', () => {
|
|
104
105
|
const locale = Locale.ES_ES;
|
|
105
106
|
const config = scrabble[locale];
|
|
106
|
-
let
|
|
107
|
+
let trie: Trie | undefined;
|
|
107
108
|
|
|
108
109
|
beforeAll(() => {
|
|
109
|
-
return dictionaries.get(locale).then((
|
|
110
|
-
|
|
110
|
+
return dictionaries.get(locale).then((loadedTrie) => {
|
|
111
|
+
trie = loadedTrie;
|
|
111
112
|
});
|
|
112
113
|
});
|
|
113
114
|
|
|
@@ -130,8 +131,8 @@ describe('Solver - ES', () => {
|
|
|
130
131
|
' ',
|
|
131
132
|
]);
|
|
132
133
|
const tiles = generateTiles(['ll', 'a', 'n', 'a']);
|
|
133
|
-
const results =
|
|
134
|
-
const bestResult = getBestResult(results);
|
|
134
|
+
const results = solve(trie!, config, board, tiles);
|
|
135
|
+
const bestResult = getBestResult(results.map((result) => Result.fromJson(result)));
|
|
135
136
|
expect(results.length).toBe(24);
|
|
136
137
|
expect(bestResult.points).toBe(22);
|
|
137
138
|
});
|
package/src/solve.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Trie } from '@kamilmielnik/trie';
|
|
2
|
+
import { Board, Config, ResultJson, Tile } from '@scrabble-solver/types';
|
|
3
|
+
|
|
4
|
+
import fillPattern from './fillPattern';
|
|
5
|
+
import generatePatterns from './generatePatterns';
|
|
6
|
+
import getPatternScore from './getPatternScore';
|
|
7
|
+
import getUniquePatterns from './getUniquePatterns';
|
|
8
|
+
|
|
9
|
+
const solve = (trie: Trie, config: Config, board: Board, tiles: Tile[]): ResultJson[] => {
|
|
10
|
+
const patterns = generatePatterns(config, board);
|
|
11
|
+
const filledPatterns = patterns.flatMap((pattern) => fillPattern(trie, config, pattern, tiles));
|
|
12
|
+
const uniquePatterns = getUniquePatterns(filledPatterns);
|
|
13
|
+
const results = uniquePatterns.map((pattern, index) => ({
|
|
14
|
+
cells: pattern.cells.map((cell) => cell.toJson()),
|
|
15
|
+
collisions: pattern.getCollisions().map((collision) => collision.cells.map((cell) => cell.toJson())),
|
|
16
|
+
id: index,
|
|
17
|
+
points: getPatternScore(config, pattern),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
return results;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default solve;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Trie } from '@kamilmielnik/trie';
|
|
2
|
-
import { Config, Pattern, Tile } from '@scrabble-solver/types';
|
|
3
|
-
declare class PatternsFiller {
|
|
4
|
-
private readonly trie;
|
|
5
|
-
private readonly config;
|
|
6
|
-
constructor(config: Config, trie: Trie);
|
|
7
|
-
fill(pattern: Pattern, tiles: Tile[]): Pattern[];
|
|
8
|
-
fillPattern(pattern: Pattern, word: string, tiles: Tile[], onPatternFound: (newPattern: Pattern) => void): void;
|
|
9
|
-
canAddPattern(pattern: Pattern, word: string): boolean;
|
|
10
|
-
generateBlankTilesPermutations(tiles: Tile[]): Tile[][];
|
|
11
|
-
}
|
|
12
|
-
export default PatternsFiller;
|
package/build/PatternsFiller.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const constants_1 = require("@scrabble-solver/constants");
|
|
4
|
-
const types_1 = require("@scrabble-solver/types");
|
|
5
|
-
class PatternsFiller {
|
|
6
|
-
constructor(config, trie) {
|
|
7
|
-
this.config = config;
|
|
8
|
-
this.trie = trie;
|
|
9
|
-
}
|
|
10
|
-
fill(pattern, tiles) {
|
|
11
|
-
const patterns = [];
|
|
12
|
-
if (pattern.getEmptyCellsCount() > tiles.length) {
|
|
13
|
-
return [];
|
|
14
|
-
}
|
|
15
|
-
const onPatternFound = (newPattern) => patterns.push(newPattern);
|
|
16
|
-
const tilesPermutations = this.generateBlankTilesPermutations(tiles);
|
|
17
|
-
for (let index = 0; index < tilesPermutations.length; ++index) {
|
|
18
|
-
const tilesPermutation = tilesPermutations[index];
|
|
19
|
-
this.fillPattern(pattern, pattern.toString(), tilesPermutation, onPatternFound);
|
|
20
|
-
}
|
|
21
|
-
return patterns;
|
|
22
|
-
}
|
|
23
|
-
fillPattern(pattern, word, tiles, onPatternFound) {
|
|
24
|
-
const indexOfFirstCellWithoutTile = pattern.getIndexOfFirstCellWithoutTile();
|
|
25
|
-
if (indexOfFirstCellWithoutTile === -1) {
|
|
26
|
-
if (this.canAddPattern(pattern, word)) {
|
|
27
|
-
onPatternFound(pattern.clone());
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
for (let index = 0; index < tiles.length; ++index) {
|
|
32
|
-
const tile = tiles[index];
|
|
33
|
-
const previousTile = pattern.cells[indexOfFirstCellWithoutTile].tile;
|
|
34
|
-
pattern.cells[indexOfFirstCellWithoutTile].tile = tile;
|
|
35
|
-
const indexOfNextCellWithoutTile = pattern.getIndexOfFirstCellWithoutTile();
|
|
36
|
-
const indexOfFirstEmptyLetter = word.indexOf(constants_1.EMPTY_CELL);
|
|
37
|
-
const newWordPrefix = word.substring(0, indexOfFirstEmptyLetter) + tile.character;
|
|
38
|
-
const newWord = newWordPrefix + word.substring(indexOfFirstEmptyLetter + 1);
|
|
39
|
-
if (indexOfNextCellWithoutTile === -1) {
|
|
40
|
-
if (this.canAddPattern(pattern, newWord)) {
|
|
41
|
-
onPatternFound(pattern.clone());
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
else if (this.trie.hasPrefix(newWordPrefix)) {
|
|
45
|
-
tiles.splice(index, 1);
|
|
46
|
-
this.fillPattern(pattern, newWord, tiles, onPatternFound);
|
|
47
|
-
tiles.splice(index, 0, tile);
|
|
48
|
-
}
|
|
49
|
-
pattern.cells[indexOfFirstCellWithoutTile].tile = previousTile;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
canAddPattern(pattern, word) {
|
|
54
|
-
return this.trie.has(word) && pattern.getCollisions().every((collision) => this.trie.has(collision.toString()));
|
|
55
|
-
}
|
|
56
|
-
generateBlankTilesPermutations(tiles) {
|
|
57
|
-
const { alphabet } = this.config;
|
|
58
|
-
const firstBlankIndex = tiles.findIndex(({ character, isBlank }) => isBlank && !alphabet.includes(character));
|
|
59
|
-
if (firstBlankIndex === -1) {
|
|
60
|
-
return [tiles];
|
|
61
|
-
}
|
|
62
|
-
const remainingTiles = tiles.slice(0, firstBlankIndex).concat(tiles.slice(firstBlankIndex + 1));
|
|
63
|
-
return this.config.alphabet.reduce((permutations, character) => {
|
|
64
|
-
const newTile = new types_1.Tile({ character, isBlank: true });
|
|
65
|
-
const newTiles = [...remainingTiles, newTile];
|
|
66
|
-
return permutations.concat(this.generateBlankTilesPermutations(newTiles));
|
|
67
|
-
}, []);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
exports.default = PatternsFiller;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Board, Cell, Config, HorizontalPattern, Pattern, VerticalPattern } from '@scrabble-solver/types';
|
|
2
|
-
declare class PatternsGenerator {
|
|
3
|
-
private readonly config;
|
|
4
|
-
constructor(config: Config);
|
|
5
|
-
generate(board: Board): Pattern[];
|
|
6
|
-
generateHorizontal(board: Board): HorizontalPattern[];
|
|
7
|
-
generateVertical(board: Board): VerticalPattern[];
|
|
8
|
-
generatePatterns<P extends Pattern>({ board, getNthVector, vectorsCount, PatternModel, }: {
|
|
9
|
-
board: Board;
|
|
10
|
-
getNthVector: (index: number) => Cell[];
|
|
11
|
-
PatternModel: new (parameters: {
|
|
12
|
-
board: Board;
|
|
13
|
-
cells: Cell[];
|
|
14
|
-
}) => P;
|
|
15
|
-
vectorsCount: number;
|
|
16
|
-
}): P[];
|
|
17
|
-
generateVectors({ getNthVector, vectorsCount, }: {
|
|
18
|
-
getNthVector: (index: number) => Cell[];
|
|
19
|
-
vectorsCount: number;
|
|
20
|
-
}): Cell[][];
|
|
21
|
-
generateCellsPatterns<P extends Pattern>({ board, cells, PatternModel, }: {
|
|
22
|
-
board: Board;
|
|
23
|
-
cells: Cell[];
|
|
24
|
-
PatternModel: new (parameters: {
|
|
25
|
-
board: Board;
|
|
26
|
-
cells: Cell[];
|
|
27
|
-
}) => P;
|
|
28
|
-
}): P[];
|
|
29
|
-
generateStartIndices(cells: Cell[]): number[];
|
|
30
|
-
generateEndIndices(cells: Cell[], startIndex: number): number[];
|
|
31
|
-
}
|
|
32
|
-
export default PatternsGenerator;
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const types_1 = require("@scrabble-solver/types");
|
|
4
|
-
class PatternsGenerator {
|
|
5
|
-
constructor(config) {
|
|
6
|
-
this.config = config;
|
|
7
|
-
}
|
|
8
|
-
generate(board) {
|
|
9
|
-
return [...this.generateHorizontal(board), ...this.generateVertical(board)];
|
|
10
|
-
}
|
|
11
|
-
generateHorizontal(board) {
|
|
12
|
-
return this.generatePatterns({
|
|
13
|
-
board,
|
|
14
|
-
getNthVector: (index) => board.getRow(index),
|
|
15
|
-
PatternModel: types_1.HorizontalPattern,
|
|
16
|
-
vectorsCount: this.config.boardHeight,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
generateVertical(board) {
|
|
20
|
-
return this.generatePatterns({
|
|
21
|
-
board,
|
|
22
|
-
getNthVector: (index) => board.getColumn(index),
|
|
23
|
-
PatternModel: types_1.VerticalPattern,
|
|
24
|
-
vectorsCount: this.config.boardWidth,
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
generatePatterns({ board, getNthVector, vectorsCount, PatternModel, }) {
|
|
28
|
-
return this.generateVectors({ getNthVector, vectorsCount }).reduce((patterns, cells) => {
|
|
29
|
-
return patterns.concat(this.generateCellsPatterns({ board, PatternModel, cells }));
|
|
30
|
-
}, []);
|
|
31
|
-
}
|
|
32
|
-
generateVectors({ getNthVector, vectorsCount, }) {
|
|
33
|
-
return Array(vectorsCount)
|
|
34
|
-
.fill(0)
|
|
35
|
-
.map((_, index) => getNthVector(index));
|
|
36
|
-
}
|
|
37
|
-
generateCellsPatterns({ board, cells, PatternModel, }) {
|
|
38
|
-
return this.generateStartIndices(cells).flatMap((startIndex) => {
|
|
39
|
-
const endIndices = this.generateEndIndices(cells, startIndex);
|
|
40
|
-
const patterns = [];
|
|
41
|
-
for (const endIndex of endIndices) {
|
|
42
|
-
const pattern = new PatternModel({
|
|
43
|
-
board,
|
|
44
|
-
cells: cells.slice(startIndex, endIndex + 1),
|
|
45
|
-
});
|
|
46
|
-
if (pattern.canBePlaced(this.config)) {
|
|
47
|
-
patterns.push(pattern);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return patterns;
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
generateStartIndices(cells) {
|
|
54
|
-
return Array(cells.length - 1)
|
|
55
|
-
.fill(0)
|
|
56
|
-
.map((_, startIndex) => startIndex)
|
|
57
|
-
.filter((startIndex) => startIndex === 0 || !cells[startIndex - 1].hasTile());
|
|
58
|
-
}
|
|
59
|
-
generateEndIndices(cells, startIndex) {
|
|
60
|
-
return Array(cells.length - startIndex - 1)
|
|
61
|
-
.fill(0)
|
|
62
|
-
.map((_, endIndex) => endIndex + startIndex + 1)
|
|
63
|
-
.filter((endIndex) => endIndex >= cells.length - 1 || !cells[endIndex + 1].hasTile());
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
exports.default = PatternsGenerator;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { Cell, Config, Pattern } from '@scrabble-solver/types';
|
|
2
|
-
declare class ScoresCalculator {
|
|
3
|
-
private readonly config;
|
|
4
|
-
constructor(config: Config);
|
|
5
|
-
calculate(pattern: Pattern): number;
|
|
6
|
-
calculateBonusScore(pattern: Pattern): number;
|
|
7
|
-
calculatePatternScoreWithCollisions(pattern: Pattern): number;
|
|
8
|
-
calculatePatternScore(pattern: Pattern): number;
|
|
9
|
-
reduceCellScore: ({ multiplier, score }: {
|
|
10
|
-
multiplier: number;
|
|
11
|
-
score: number;
|
|
12
|
-
}, cell: Cell) => {
|
|
13
|
-
multiplier: number;
|
|
14
|
-
score: number;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
export default ScoresCalculator;
|