lettersoup 1.0.2 → 1.1.0
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 +67 -10
- package/dist/Entities/Cordenates.d.ts +20 -0
- package/dist/Entities/Cordenates.js +90 -0
- package/dist/Entities/CrossRow.d.ts +14 -0
- package/dist/Entities/CrossRow.js +44 -0
- package/dist/Entities/DimensionBoard.d.ts +11 -0
- package/dist/Entities/DimensionBoard.js +20 -0
- package/dist/Entities/PlayBoard.d.ts +12 -0
- package/dist/Entities/PlayBoard.js +39 -0
- package/dist/Entities/Word.d.ts +15 -0
- package/dist/Entities/Word.js +40 -0
- package/dist/Types.d.ts +33 -0
- package/dist/Types.js +17 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/letterSoup.d.ts +16 -0
- package/dist/letterSoup.js +127 -0
- package/dist/utils/regex.d.ts +8 -0
- package/dist/utils/regex.js +50 -0
- package/dist/utils/string.d.ts +4 -0
- package/dist/utils/string.js +5 -0
- package/package.json +15 -2
- package/.github/workflows/npm-publish.yml +0 -33
- package/.nvmrc +0 -1
- package/__tests__/index.test.ts +0 -36
- package/__tests__/utils/regex.test.ts +0 -80
- package/jest.config.js +0 -7
- package/src/Entities/Cords.ts +0 -127
- package/src/Entities/Dimension.ts +0 -24
- package/src/Entities/Lines.ts +0 -59
- package/src/Entities/Matrix.ts +0 -43
- package/src/Entities/Word.ts +0 -115
- package/src/Types.ts +0 -40
- package/src/index.ts +0 -2
- package/src/letterSoup.ts +0 -130
- package/src/utils/regex.ts +0 -68
- package/src/utils/string.ts +0 -10
- package/tsconfig.json +0 -111
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# LetterSoup (Algorithm)
|
|
1
|
+
# LetterSoup (Algorithm)
|
|
2
2
|
|
|
3
3
|
Algorithm for make letterSoups (sopa de letras)
|
|
4
4
|
|
|
@@ -10,21 +10,51 @@ Algorithm for make letterSoups (sopa de letras)
|
|
|
10
10
|
|
|
11
11
|
## Import/Examples
|
|
12
12
|
|
|
13
|
-
```
|
|
14
|
-
import { WordSearch } from '
|
|
13
|
+
```javascript
|
|
14
|
+
import { WordSearch } from 'lettersoup';
|
|
15
15
|
|
|
16
|
-
const listWords = ['Doctor', 'Dog', 'Banana']
|
|
16
|
+
const listWords = ['Doctor', 'Dog', 'Banana', 'Apple', 'Orange'];
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
getPuzzle,
|
|
20
|
-
getWords
|
|
21
|
-
} = new WordSearch(wordsArray)
|
|
18
|
+
const wordSearch = new WordSearch(listWords);
|
|
22
19
|
|
|
23
|
-
const puzzle = getPuzzle()
|
|
24
|
-
const words = getWords()
|
|
20
|
+
const puzzle = wordSearch.getPuzzle();
|
|
21
|
+
const words = wordSearch.getWords();
|
|
22
|
+
|
|
23
|
+
console.log('Puzzle:', puzzle);
|
|
24
|
+
console.log('Words:', words);
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
## API Reference
|
|
29
|
+
|
|
30
|
+
### `new WordSearch(words: string[])`
|
|
31
|
+
|
|
32
|
+
Creates a new instance of WordSearch.
|
|
33
|
+
|
|
34
|
+
- `words` (string[]): A list of words to be included in the letter soup.
|
|
35
|
+
|
|
36
|
+
### `getPuzzle(): string[][]`
|
|
37
|
+
|
|
38
|
+
Returns the generated letter soup as a 2D array of strings.
|
|
39
|
+
|
|
40
|
+
### `getWords(): string[]`
|
|
41
|
+
|
|
42
|
+
Returns the list of words that were included in the letter soup.
|
|
43
|
+
|
|
44
|
+
### `getRemainingWords(): string[]`
|
|
45
|
+
|
|
46
|
+
Returns the list of words that could not be placed in the letter soup.
|
|
47
|
+
|
|
48
|
+
## Board Expansion
|
|
49
|
+
|
|
50
|
+
The board is sized from the longest word plus a margin gap of `1` cell on every side, so every word geometrically fits in the puzzle. However, the placement heuristic walks the board coordinates in random order and tries to place a single word per visited coordinate, so a coordinate may lose its chance before the space around it is opened by other words.
|
|
51
|
+
|
|
52
|
+
To fix that, the generator re-walks **all** coordinates over and over (re-shuffled on every pass) until a full pass places nothing — a fixed point. Only then, if words are still pending, the board is expanded by one extra row and one extra column and the process restarts.
|
|
53
|
+
|
|
54
|
+
- Expansions are capped at `MAX_EXPANSIONS = 3` to avoid growing forever when a word truly cannot fit.
|
|
55
|
+
- Words that still cannot be placed after the limit are returned by `getRemainingWords()`.
|
|
56
|
+
- The puzzle is not filled with random letters: blank cells remain as separators.
|
|
57
|
+
|
|
28
58
|
## Run Locally
|
|
29
59
|
|
|
30
60
|
Clone repository
|
|
@@ -66,8 +96,35 @@ To run tests, run the following command
|
|
|
66
96
|
npm run test
|
|
67
97
|
```
|
|
68
98
|
|
|
99
|
+
## Contributing
|
|
100
|
+
|
|
101
|
+
Contributions are always welcome!
|
|
102
|
+
|
|
103
|
+
Please adhere to this project's `code of conduct`.
|
|
104
|
+
|
|
105
|
+
### Pull Request Process
|
|
106
|
+
|
|
107
|
+
1. Ensure any install or build dependencies are removed before the end of the layer when doing a build.
|
|
108
|
+
2. Update the README.md with details of changes to the interface, this includes new environment variables, exposed ports, useful file locations and container parameters.
|
|
109
|
+
3. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent.
|
|
110
|
+
4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you.
|
|
111
|
+
|
|
112
|
+
### Bug Reports
|
|
113
|
+
|
|
114
|
+
Please include the following information when reporting a bug:
|
|
115
|
+
|
|
116
|
+
- A clear and descriptive title.
|
|
117
|
+
- A detailed description of the bug.
|
|
118
|
+
- Steps to reproduce the bug.
|
|
119
|
+
- The expected behavior.
|
|
120
|
+
- The actual behavior.
|
|
121
|
+
- Screenshots or videos if possible.
|
|
122
|
+
|
|
69
123
|
|
|
70
124
|
## Authors
|
|
71
125
|
|
|
72
126
|
- [@Fabian Escarate](https://www.github.com/FabianEscarate)
|
|
73
127
|
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
[ISC](https://choosealicense.com/licenses/isc/)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { orientationType } from "../Types";
|
|
2
|
+
import PlayBoard from "./PlayBoard";
|
|
3
|
+
export declare class Cordenate {
|
|
4
|
+
cordX: number;
|
|
5
|
+
cordY: number;
|
|
6
|
+
constructor(cordX: number, cordY: number);
|
|
7
|
+
}
|
|
8
|
+
export default class Cordenates {
|
|
9
|
+
private board;
|
|
10
|
+
private allCordsOfMatrix;
|
|
11
|
+
constructor(_board: PlayBoard);
|
|
12
|
+
private getAllCordsOfMatrix;
|
|
13
|
+
scrambleCordsOfMatrix: () => Cordenate[];
|
|
14
|
+
getEmptySlots: () => Cordenate[];
|
|
15
|
+
private getHorizontalCordsByCordenate;
|
|
16
|
+
private getVerticalCordenatesByCordenate;
|
|
17
|
+
private getDiagonalCordenatesByCordenate;
|
|
18
|
+
private getAntiDiagonalCordenatesByCordenate;
|
|
19
|
+
getCordsByOrientationAndCordenate: (orientation: orientationType, currentCordenate: Cordenate) => Cordenate[];
|
|
20
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Cordenate = void 0;
|
|
4
|
+
class Cordenate {
|
|
5
|
+
constructor(cordX, cordY) {
|
|
6
|
+
this.cordX = cordX;
|
|
7
|
+
this.cordY = cordY;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.Cordenate = Cordenate;
|
|
11
|
+
class Cordenates {
|
|
12
|
+
constructor(_board) {
|
|
13
|
+
this.getAllCordsOfMatrix = (dimensionMatrix) => {
|
|
14
|
+
const { width, height } = dimensionMatrix;
|
|
15
|
+
const result = [];
|
|
16
|
+
for (let x = 0; x < height; x++)
|
|
17
|
+
for (let y = 0; y < width; y++)
|
|
18
|
+
result.push(new Cordenate(x, y));
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
this.scrambleCordsOfMatrix = () => {
|
|
22
|
+
let scrambleCords = [];
|
|
23
|
+
const cordsOfMatrix = this.allCordsOfMatrix.slice();
|
|
24
|
+
while (cordsOfMatrix.length > 0) {
|
|
25
|
+
scrambleCords.push(cordsOfMatrix.splice(Math.floor(Math.random() * cordsOfMatrix.length), 1)[0]);
|
|
26
|
+
}
|
|
27
|
+
return scrambleCords;
|
|
28
|
+
};
|
|
29
|
+
this.getEmptySlots = () => {
|
|
30
|
+
return this.allCordsOfMatrix.slice().filter((cord) => this.board.getPlayBoard()[cord.cordX][cord.cordY] === ' ');
|
|
31
|
+
};
|
|
32
|
+
this.getHorizontalCordsByCordenate = (cordenate) => {
|
|
33
|
+
const result = [];
|
|
34
|
+
const { width } = this.board.getDimension();
|
|
35
|
+
const { cordX } = cordenate;
|
|
36
|
+
for (let i = 0; i < width; i++) {
|
|
37
|
+
result.push(new Cordenate(cordX, i));
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
this.getVerticalCordenatesByCordenate = (cordenate) => {
|
|
42
|
+
const result = [];
|
|
43
|
+
const { height } = this.board.getDimension();
|
|
44
|
+
const { cordY } = cordenate;
|
|
45
|
+
for (let i = 0; i < height; i++) {
|
|
46
|
+
result.push(new Cordenate(i, cordY));
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
this.getDiagonalCordenatesByCordenate = (cordenate) => {
|
|
51
|
+
const result = [];
|
|
52
|
+
const { width, height } = this.board.getDimension();
|
|
53
|
+
const { cordX, cordY } = cordenate;
|
|
54
|
+
for (let x = Math.max(0, cordX - cordY); x < Math.min(height, height + cordX - cordY); x++) {
|
|
55
|
+
const y = x - (cordX - cordY);
|
|
56
|
+
if (0 <= y && y < width) {
|
|
57
|
+
result.push(new Cordenate(x, y));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
};
|
|
62
|
+
this.getAntiDiagonalCordenatesByCordenate = (cordenate) => {
|
|
63
|
+
const result = [];
|
|
64
|
+
const { width, height } = this.board.getDimension();
|
|
65
|
+
const { cordX, cordY } = cordenate;
|
|
66
|
+
for (let x = Math.max(0, cordX + cordY - width + 1); x < Math.min(height, cordX + cordY + 1); x++) {
|
|
67
|
+
const y = cordX + cordY - x;
|
|
68
|
+
if (0 <= y && y < width) {
|
|
69
|
+
result.push(new Cordenate(x, y));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return result.reverse();
|
|
73
|
+
};
|
|
74
|
+
this.getCordsByOrientationAndCordenate = (orientation, currentCordenate) => {
|
|
75
|
+
switch (orientation) {
|
|
76
|
+
case "horizontally":
|
|
77
|
+
return this.getHorizontalCordsByCordenate(currentCordenate);
|
|
78
|
+
case "vertically":
|
|
79
|
+
return this.getVerticalCordenatesByCordenate(currentCordenate);
|
|
80
|
+
case "diagonally":
|
|
81
|
+
return this.getDiagonalCordenatesByCordenate(currentCordenate);
|
|
82
|
+
case "antidiagonally":
|
|
83
|
+
return this.getAntiDiagonalCordenatesByCordenate(currentCordenate);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
this.board = _board;
|
|
87
|
+
this.allCordsOfMatrix = this.getAllCordsOfMatrix(_board.getDimension());
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.default = Cordenates;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { orientationType, regexValueByLineType } from "../Types";
|
|
2
|
+
import '../utils/string';
|
|
3
|
+
import { Cordenate } from "./Cordenates";
|
|
4
|
+
import PlayBoard from "./PlayBoard";
|
|
5
|
+
export default class CrossRow {
|
|
6
|
+
private board;
|
|
7
|
+
private horizontallyRowCordenates;
|
|
8
|
+
private verticallyRowCordenates;
|
|
9
|
+
private diagonallyDownRowCordenates;
|
|
10
|
+
private diagonallyUpRowCordenates;
|
|
11
|
+
constructor(_board: PlayBoard, currentPosition: Cordenate);
|
|
12
|
+
getLinesWithRegex: () => regexValueByLineType;
|
|
13
|
+
getLineByOrientation: (orientation: orientationType) => string;
|
|
14
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const Types_1 = require("../Types");
|
|
4
|
+
const regex_1 = require("../utils/regex");
|
|
5
|
+
require("../utils/string");
|
|
6
|
+
class CrossRow {
|
|
7
|
+
constructor(_board, currentPosition) {
|
|
8
|
+
this.getLinesWithRegex = () => {
|
|
9
|
+
const lines = {
|
|
10
|
+
horizontallyLine: this.horizontallyRowCordenates.map(cord => this.board.getSlot(cord.cordX, cord.cordY)).join(''),
|
|
11
|
+
verticallyLine: this.verticallyRowCordenates.map(cord => this.board.getSlot(cord.cordX, cord.cordY)).join(''),
|
|
12
|
+
diagonallyDownLine: this.diagonallyDownRowCordenates.map(cord => this.board.getSlot(cord.cordX, cord.cordY)).join(''),
|
|
13
|
+
diagonallyUpLine: this.diagonallyUpRowCordenates.map(cord => this.board.getSlot(cord.cordX, cord.cordY)).join('')
|
|
14
|
+
};
|
|
15
|
+
const regexForLines = Object.keys(lines).reduce((result, key) => {
|
|
16
|
+
const newObj = {};
|
|
17
|
+
newObj[`regex${key.capitalize()}`] = (0, regex_1.generateRegexByLine)(lines[key]);
|
|
18
|
+
return Object.assign(Object.assign({}, result), newObj);
|
|
19
|
+
}, {});
|
|
20
|
+
return regexForLines;
|
|
21
|
+
};
|
|
22
|
+
this.getLineByOrientation = (orientation) => {
|
|
23
|
+
let selectedOrientation;
|
|
24
|
+
switch (orientation) {
|
|
25
|
+
case "horizontally":
|
|
26
|
+
selectedOrientation = this.horizontallyRowCordenates;
|
|
27
|
+
case "vertically":
|
|
28
|
+
selectedOrientation = this.verticallyRowCordenates;
|
|
29
|
+
case "diagonally":
|
|
30
|
+
selectedOrientation = this.diagonallyDownRowCordenates;
|
|
31
|
+
case "antidiagonally":
|
|
32
|
+
selectedOrientation = this.diagonallyUpRowCordenates;
|
|
33
|
+
}
|
|
34
|
+
return selectedOrientation.map(cord => this.board.getSlot(cord.cordX, cord.cordY)).join('');
|
|
35
|
+
};
|
|
36
|
+
const { cordenates: { getCordsByOrientationAndCordenate }, } = _board;
|
|
37
|
+
this.board = _board;
|
|
38
|
+
this.horizontallyRowCordenates = getCordsByOrientationAndCordenate(Types_1.orientationEnum.horizontally, currentPosition);
|
|
39
|
+
this.verticallyRowCordenates = getCordsByOrientationAndCordenate(Types_1.orientationEnum.vertically, currentPosition);
|
|
40
|
+
this.diagonallyDownRowCordenates = getCordsByOrientationAndCordenate(Types_1.orientationEnum.diagonally, currentPosition);
|
|
41
|
+
this.diagonallyUpRowCordenates = getCordsByOrientationAndCordenate(Types_1.orientationEnum.antidiagonally, currentPosition);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.default = CrossRow;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const MARGIN_PUZZLE = 1;
|
|
4
|
+
class DimensionBoard {
|
|
5
|
+
constructor(length) {
|
|
6
|
+
this.width = MARGIN_PUZZLE + length + MARGIN_PUZZLE;
|
|
7
|
+
this.height = MARGIN_PUZZLE + length + MARGIN_PUZZLE;
|
|
8
|
+
}
|
|
9
|
+
getDimension() {
|
|
10
|
+
return {
|
|
11
|
+
width: this.width,
|
|
12
|
+
height: this.height
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
incrementDimensions() {
|
|
16
|
+
this.width++;
|
|
17
|
+
this.height++;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.default = DimensionBoard;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Cordenates from "./Cordenates";
|
|
2
|
+
import DimensionBoard from "./DimensionBoard";
|
|
3
|
+
export default class PlayBoard extends DimensionBoard {
|
|
4
|
+
cordenates: Cordenates;
|
|
5
|
+
private board;
|
|
6
|
+
constructor(length: number);
|
|
7
|
+
private generate;
|
|
8
|
+
setSlot: (cordX: number, cordY: number, letter: string) => void;
|
|
9
|
+
getSlot: (cordX: number, cordY: number) => string;
|
|
10
|
+
getPlayBoard: () => string[][];
|
|
11
|
+
expandBoard: () => void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
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 Cordenates_1 = __importDefault(require("./Cordenates"));
|
|
7
|
+
const DimensionBoard_1 = __importDefault(require("./DimensionBoard"));
|
|
8
|
+
const BLANK_SPACE = ' ';
|
|
9
|
+
class PlayBoard extends DimensionBoard_1.default {
|
|
10
|
+
constructor(length) {
|
|
11
|
+
super(length);
|
|
12
|
+
this.generate = () => {
|
|
13
|
+
const { width, height } = this.getDimension();
|
|
14
|
+
const board = Array(...Array(width * height)).reduce((resultValue, currentValue, index, array) => {
|
|
15
|
+
resultValue = [...resultValue, array.splice(0, width).map(index => BLANK_SPACE)];
|
|
16
|
+
return resultValue;
|
|
17
|
+
}, []);
|
|
18
|
+
return board;
|
|
19
|
+
};
|
|
20
|
+
this.setSlot = (cordX, cordY, letter) => { this.board[cordX][cordY] = letter; };
|
|
21
|
+
this.getSlot = (cordX, cordY) => this.board[cordX][cordY];
|
|
22
|
+
this.getPlayBoard = () => this.board;
|
|
23
|
+
this.expandBoard = () => {
|
|
24
|
+
this.incrementDimensions();
|
|
25
|
+
const { width, height } = this.getDimension();
|
|
26
|
+
const newBoard = Array.from({ length: height }, () => Array.from({ length: width }, () => BLANK_SPACE));
|
|
27
|
+
for (let i = 0; i < this.board.length; i++) {
|
|
28
|
+
for (let j = 0; j < this.board[i].length; j++) {
|
|
29
|
+
newBoard[i][j] = this.board[i][j];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
this.board = newBoard;
|
|
33
|
+
this.cordenates = new Cordenates_1.default(this);
|
|
34
|
+
};
|
|
35
|
+
this.board = this.generate();
|
|
36
|
+
this.cordenates = new Cordenates_1.default(this);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.default = PlayBoard;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { wordListByLineType } from "../Types";
|
|
2
|
+
import { Cordenate } from "./Cordenates";
|
|
3
|
+
import CrossRow from "./CrossRow";
|
|
4
|
+
import PlayBoard from "./PlayBoard";
|
|
5
|
+
export default class Words {
|
|
6
|
+
private board;
|
|
7
|
+
private listOfWords;
|
|
8
|
+
constructor(_board: PlayBoard, listOfWords: string[]);
|
|
9
|
+
listWords: () => string[];
|
|
10
|
+
getRemainingWords: () => string[];
|
|
11
|
+
hasMoreWordsToPut: () => boolean;
|
|
12
|
+
removeWord: (word: string) => void;
|
|
13
|
+
putWordByRowCordenates: (word: string, rowCordenates: Cordenate[], padStart?: number) => void;
|
|
14
|
+
getPossibleWordsByLines: (lines: CrossRow) => wordListByLineType;
|
|
15
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const LETTERS = 'ABCDEFGHIJKLMNÑOPQRSTUVWXYZ';
|
|
4
|
+
class Words {
|
|
5
|
+
constructor(_board, listOfWords) {
|
|
6
|
+
this.listWords = () => {
|
|
7
|
+
return this.listOfWords;
|
|
8
|
+
};
|
|
9
|
+
this.getRemainingWords = () => {
|
|
10
|
+
return this.listOfWords.slice();
|
|
11
|
+
};
|
|
12
|
+
this.hasMoreWordsToPut = () => {
|
|
13
|
+
return this.listOfWords.length > 0;
|
|
14
|
+
};
|
|
15
|
+
this.removeWord = (word) => {
|
|
16
|
+
this.listOfWords.splice(this.listOfWords.indexOf(word), 1);
|
|
17
|
+
};
|
|
18
|
+
this.putWordByRowCordenates = (word, rowCordenates, padStart = 0) => {
|
|
19
|
+
let wordIndex = 0;
|
|
20
|
+
rowCordenates.forEach((cordenate, rowIndex) => {
|
|
21
|
+
if (rowIndex >= padStart && wordIndex < word.length) {
|
|
22
|
+
this.board.setSlot(cordenate.cordX, cordenate.cordY, word.charAt(wordIndex));
|
|
23
|
+
wordIndex++;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
this.getPossibleWordsByLines = (lines) => {
|
|
28
|
+
const linesWithSearhRegex = lines.getLinesWithRegex();
|
|
29
|
+
const wordsByLine = Object.keys(linesWithSearhRegex).reduce((result, key) => {
|
|
30
|
+
const newObj = {};
|
|
31
|
+
newObj[`match${key.capitalize()}`] = this.listOfWords.filter(word => word.match(linesWithSearhRegex[key]));
|
|
32
|
+
return Object.assign(Object.assign({}, result), newObj);
|
|
33
|
+
}, {});
|
|
34
|
+
return wordsByLine;
|
|
35
|
+
};
|
|
36
|
+
this.board = _board;
|
|
37
|
+
this.listOfWords = listOfWords.slice();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.default = Words;
|
package/dist/Types.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type puzzleType = string[][];
|
|
2
|
+
export type orientationType = "horizontally" | "vertically" | "diagonally" | "antidiagonally";
|
|
3
|
+
export type linesByCordsType = {
|
|
4
|
+
horizontallyLine: string;
|
|
5
|
+
verticallyLine: string;
|
|
6
|
+
diagonallyDownLine: string;
|
|
7
|
+
diagonallyUpLine: string;
|
|
8
|
+
};
|
|
9
|
+
export declare enum matchRegexLines {
|
|
10
|
+
matchRegexDiagonallyDownLine = "diagonally",
|
|
11
|
+
matchRegexDiagonallyUpLine = "antidiagonally",
|
|
12
|
+
matchRegexHorizontallyLine = "horizontally",
|
|
13
|
+
matchRegexVerticallyLine = "vertically"
|
|
14
|
+
}
|
|
15
|
+
export type regexValueByLine<Type> = {
|
|
16
|
+
[key in keyof Type as `regex${Capitalize<string & key>}`]: RegExp;
|
|
17
|
+
};
|
|
18
|
+
export type regexValueByLineType = regexValueByLine<linesByCordsType>;
|
|
19
|
+
export declare enum orientationEnum {
|
|
20
|
+
horizontally = "horizontally",
|
|
21
|
+
vertically = "vertically",
|
|
22
|
+
diagonally = "diagonally",
|
|
23
|
+
antidiagonally = "antidiagonally"
|
|
24
|
+
}
|
|
25
|
+
export type wordListByLine<Type> = {
|
|
26
|
+
[key in keyof Type as `match${Capitalize<string & key>}`]: string[];
|
|
27
|
+
};
|
|
28
|
+
export type wordListByLineType = wordListByLine<regexValueByLineType>;
|
|
29
|
+
export type interectionType = {
|
|
30
|
+
wordIndex: number;
|
|
31
|
+
lineIndex: number;
|
|
32
|
+
};
|
|
33
|
+
export type interectionsType = interectionType[];
|
package/dist/Types.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.orientationEnum = exports.matchRegexLines = void 0;
|
|
4
|
+
var matchRegexLines;
|
|
5
|
+
(function (matchRegexLines) {
|
|
6
|
+
matchRegexLines["matchRegexDiagonallyDownLine"] = "diagonally";
|
|
7
|
+
matchRegexLines["matchRegexDiagonallyUpLine"] = "antidiagonally";
|
|
8
|
+
matchRegexLines["matchRegexHorizontallyLine"] = "horizontally";
|
|
9
|
+
matchRegexLines["matchRegexVerticallyLine"] = "vertically";
|
|
10
|
+
})(matchRegexLines || (exports.matchRegexLines = matchRegexLines = {}));
|
|
11
|
+
var orientationEnum;
|
|
12
|
+
(function (orientationEnum) {
|
|
13
|
+
orientationEnum["horizontally"] = "horizontally";
|
|
14
|
+
orientationEnum["vertically"] = "vertically";
|
|
15
|
+
orientationEnum["diagonally"] = "diagonally";
|
|
16
|
+
orientationEnum["antidiagonally"] = "antidiagonally";
|
|
17
|
+
})(orientationEnum || (exports.orientationEnum = orientationEnum = {}));
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LetterSoup = void 0;
|
|
4
|
+
require("./utils/string");
|
|
5
|
+
var letterSoup_1 = require("./letterSoup");
|
|
6
|
+
Object.defineProperty(exports, "LetterSoup", { enumerable: true, get: function () { return letterSoup_1.LetterSoup; } });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class LetterSoup {
|
|
2
|
+
private listOfWord;
|
|
3
|
+
private playBoard;
|
|
4
|
+
private words;
|
|
5
|
+
constructor(listOfWords: string[]);
|
|
6
|
+
private generate;
|
|
7
|
+
private tryPutWords;
|
|
8
|
+
private canPutWordByCrossRow;
|
|
9
|
+
private pickRandomElement;
|
|
10
|
+
private selectAnyMatch;
|
|
11
|
+
private getIntersections;
|
|
12
|
+
private checkIntersections;
|
|
13
|
+
getPuzzle: () => string[][];
|
|
14
|
+
getWords: () => string[];
|
|
15
|
+
getRemainingWords: () => string[];
|
|
16
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
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
|
+
exports.LetterSoup = void 0;
|
|
7
|
+
const CrossRow_1 = __importDefault(require("./Entities/CrossRow"));
|
|
8
|
+
const PlayBoard_1 = __importDefault(require("./Entities/PlayBoard"));
|
|
9
|
+
const Word_1 = __importDefault(require("./Entities/Word"));
|
|
10
|
+
const Types_1 = require("./Types");
|
|
11
|
+
const regex_1 = require("./utils/regex");
|
|
12
|
+
const MAX_EXPANSIONS = 3;
|
|
13
|
+
class LetterSoup {
|
|
14
|
+
constructor(listOfWords) {
|
|
15
|
+
this.tryPutWords = (cord) => {
|
|
16
|
+
const crossRows = new CrossRow_1.default(this.playBoard, cord);
|
|
17
|
+
const canPutWord = this.canPutWordByCrossRow(crossRows);
|
|
18
|
+
if (!canPutWord)
|
|
19
|
+
return false;
|
|
20
|
+
const { orientation, wordSelected } = this.selectAnyMatch(canPutWord);
|
|
21
|
+
const lineCordenates = this.playBoard.cordenates.getCordsByOrientationAndCordenate(orientation, cord);
|
|
22
|
+
const orientedRow = lineCordenates.map(cord => this.playBoard.getSlot(cord.cordX, cord.cordY)).join('');
|
|
23
|
+
const intersections = this.getIntersections(wordSelected, orientedRow);
|
|
24
|
+
const intersection = this.checkIntersections(intersections, wordSelected, orientedRow);
|
|
25
|
+
if (intersection) {
|
|
26
|
+
this.words.putWordByRowCordenates(wordSelected, lineCordenates, (intersection.lineIndex - intersection.wordIndex));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
this.words.putWordByRowCordenates(wordSelected, lineCordenates);
|
|
30
|
+
}
|
|
31
|
+
this.words.removeWord(wordSelected);
|
|
32
|
+
return true;
|
|
33
|
+
};
|
|
34
|
+
this.canPutWordByCrossRow = (crossRows) => {
|
|
35
|
+
const wordsForEveryOrientation = this.words.getPossibleWordsByLines(crossRows);
|
|
36
|
+
const { matchRegexDiagonallyDownLine, matchRegexDiagonallyUpLine, matchRegexHorizontallyLine, matchRegexVerticallyLine } = wordsForEveryOrientation;
|
|
37
|
+
const canPutWord = [
|
|
38
|
+
matchRegexDiagonallyDownLine.length > 0,
|
|
39
|
+
matchRegexDiagonallyUpLine.length > 0,
|
|
40
|
+
matchRegexHorizontallyLine.length > 0,
|
|
41
|
+
matchRegexVerticallyLine.length > 0
|
|
42
|
+
].some(Boolean);
|
|
43
|
+
if (!canPutWord)
|
|
44
|
+
return false;
|
|
45
|
+
return wordsForEveryOrientation;
|
|
46
|
+
};
|
|
47
|
+
this.pickRandomElement = (listOfElements) => {
|
|
48
|
+
return listOfElements[Math.floor(Math.random() * listOfElements.length)];
|
|
49
|
+
};
|
|
50
|
+
this.selectAnyMatch = (lineMatches) => {
|
|
51
|
+
const matchKeyToScramble = Object.keys(lineMatches)
|
|
52
|
+
.filter(keyLineMatch => lineMatches[keyLineMatch].length > 0);
|
|
53
|
+
const randomSelection = this.pickRandomElement(matchKeyToScramble);
|
|
54
|
+
return {
|
|
55
|
+
orientation: Types_1.matchRegexLines[randomSelection],
|
|
56
|
+
wordSelected: this.pickRandomElement(lineMatches[randomSelection]),
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
this.getIntersections = (word, line) => {
|
|
60
|
+
const listOfIntersections = [];
|
|
61
|
+
Array.from(word).forEach((wordLetter, wordIndex) => {
|
|
62
|
+
Array.from(line).forEach((lineLetter, lineIndex) => {
|
|
63
|
+
if (wordLetter === lineLetter)
|
|
64
|
+
listOfIntersections.push({
|
|
65
|
+
lineIndex,
|
|
66
|
+
wordIndex
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
return listOfIntersections;
|
|
71
|
+
};
|
|
72
|
+
this.checkIntersections = (intersections, word, line) => {
|
|
73
|
+
if (intersections.length === 1)
|
|
74
|
+
return intersections[0];
|
|
75
|
+
if (intersections.length > 1) {
|
|
76
|
+
// console.warn('have more of one intersection')
|
|
77
|
+
const lengthLine = line.length;
|
|
78
|
+
const lengthWord = word.length;
|
|
79
|
+
return intersections.filter(intersection => {
|
|
80
|
+
const diffIntersection = (intersection.lineIndex - intersection.wordIndex);
|
|
81
|
+
if ((diffIntersection + lengthWord) > lengthLine || diffIntersection < 0) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const subStrLine = line.substring(diffIntersection, (diffIntersection + lengthWord));
|
|
85
|
+
const regex = (0, regex_1.generateRegexByLine)(subStrLine);
|
|
86
|
+
if (!regex.test(word)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
})[0];
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
this.getPuzzle = () => this.playBoard.getPlayBoard();
|
|
94
|
+
this.getWords = () => this.listOfWord;
|
|
95
|
+
this.getRemainingWords = () => this.words.getRemainingWords();
|
|
96
|
+
this.listOfWord = listOfWords.map(word => word.toUpperCase());
|
|
97
|
+
const maxWordLegth = Math.max(...listOfWords.map(word => word.length));
|
|
98
|
+
this.playBoard = new PlayBoard_1.default(maxWordLegth);
|
|
99
|
+
this.words = new Word_1.default(this.playBoard, this.listOfWord);
|
|
100
|
+
this.generate();
|
|
101
|
+
}
|
|
102
|
+
generate() {
|
|
103
|
+
let expansions = 0;
|
|
104
|
+
let isExpandable = true;
|
|
105
|
+
while (this.words.hasMoreWordsToPut() && isExpandable) {
|
|
106
|
+
let hasProgress = true;
|
|
107
|
+
while (hasProgress && this.words.hasMoreWordsToPut()) {
|
|
108
|
+
hasProgress = false;
|
|
109
|
+
const scrambleCordenates = this.playBoard.cordenates.scrambleCordsOfMatrix();
|
|
110
|
+
for (const currentCordenate of scrambleCordenates) {
|
|
111
|
+
if (this.tryPutWords(currentCordenate))
|
|
112
|
+
hasProgress = true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!this.words.hasMoreWordsToPut())
|
|
116
|
+
return;
|
|
117
|
+
if (expansions >= MAX_EXPANSIONS) {
|
|
118
|
+
isExpandable = false;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
this.playBoard.expandBoard();
|
|
122
|
+
expansions++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
exports.LetterSoup = LetterSoup;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
declare const hasAnotherLetters: (space: string) => boolean;
|
|
2
|
+
declare const isWhiteSpace: (groupToText: string) => boolean;
|
|
3
|
+
declare const splitsGroupOfSpacesAndLetter: (line: string) => RegExpMatchArray;
|
|
4
|
+
declare const chunkListCharactersByCriterial: (groups: RegExpMatchArray) => string[][];
|
|
5
|
+
declare const joinRegexStringWithPipe: (...stringRegex: string[]) => string;
|
|
6
|
+
declare const generateRegexByGroupOfSpacesAndLetters: (groups: RegExpMatchArray) => RegExp;
|
|
7
|
+
declare const generateRegexByLine: (line: string) => RegExp;
|
|
8
|
+
export { hasAnotherLetters, isWhiteSpace, splitsGroupOfSpacesAndLetter, joinRegexStringWithPipe, chunkListCharactersByCriterial, generateRegexByGroupOfSpacesAndLetters, generateRegexByLine };
|