@windward/games 0.0.4 → 0.0.6
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/components/content/DatableEditor.vue +0 -3
- package/components/content/blocks/crosswordPuzzle/Crossword.ts +231 -153
- package/components/content/blocks/crosswordPuzzle/CrosswordClues.vue +91 -0
- package/components/content/blocks/crosswordPuzzle/CrosswordElements.ts +8 -6
- package/components/content/blocks/crosswordPuzzle/CrosswordPuzzle.vue +502 -371
- package/components/content/blocks/matchingGame/MatchingGame.vue +12 -1
- package/components/content/blocks/multipleChoice/MultipleChoice.vue +187 -127
- package/components/content/blocks/multipleChoice/QuestionDialog.vue +37 -13
- package/components/content/blocks/sevenStrikes/SevenStikes.vue +368 -0
- package/components/content/blocks/sevenStrikes/keyboard.vue +71 -0
- package/components/content/blocks/wordJumble/Jumble.vue +2 -10
- package/components/settings/CrosswordPuzzleSettingsManager.vue +12 -15
- package/components/settings/MatchingGameManager.vue +1 -1
- package/components/settings/MultipleChoiceSettingsManager.vue +21 -7
- package/components/settings/SevenStrikesSettingsManager.vue +288 -0
- package/i18n/en-US/components/content/blocks/crossword.ts +18 -3
- package/i18n/en-US/components/content/blocks/index.ts +2 -0
- package/i18n/en-US/components/content/blocks/multiple_choice.ts +2 -4
- package/i18n/en-US/components/content/blocks/seven_strikes.ts +6 -0
- package/i18n/en-US/components/settings/crossword.ts +2 -1
- package/i18n/en-US/components/settings/index.ts +2 -0
- package/i18n/en-US/components/settings/multiple_choice.ts +1 -1
- package/i18n/en-US/components/settings/seven_strikes.ts +8 -0
- package/i18n/en-US/shared/content_blocks.ts +1 -0
- package/i18n/en-US/shared/settings.ts +1 -0
- package/package.json +2 -1
- package/plugin.js +21 -0
- package/test/blocks/sevenStrikes/sevenStrikes.spec.js +24 -0
- package/test/settings/SevenStrikesManager.spec.js +53 -0
|
@@ -170,8 +170,6 @@ export default {
|
|
|
170
170
|
const item = evt.draggedContext.element
|
|
171
171
|
const itemIdx = evt.draggedContext.futureIndex
|
|
172
172
|
|
|
173
|
-
console.log('onMoveCallback', evt)
|
|
174
|
-
|
|
175
173
|
if (item.locked) {
|
|
176
174
|
return false
|
|
177
175
|
}
|
|
@@ -179,7 +177,6 @@ export default {
|
|
|
179
177
|
return true
|
|
180
178
|
},
|
|
181
179
|
onDropCallback(evt, originalEvent) {
|
|
182
|
-
console.log('onDropCallback')
|
|
183
180
|
this.$emit('input', this.items)
|
|
184
181
|
},
|
|
185
182
|
editItem(item) {
|
|
@@ -9,163 +9,172 @@ class Crossword {
|
|
|
9
9
|
// This is an index of the positions of the char in the crossword (so we know where we can potentially place words)
|
|
10
10
|
// example {"a" : [{'row' : 10, 'col' : 5}, {'row' : 62, 'col' :17}], {'row' : 54, 'col' : 12}], "b" : [{'row' : 3, 'col' : 13}]}
|
|
11
11
|
// where the two item arrays are the row and column of where the letter occurs
|
|
12
|
-
public
|
|
12
|
+
public charIndex: any = {}
|
|
13
13
|
// these words are the words that can't be placed on the crossword
|
|
14
|
-
public
|
|
15
|
-
public
|
|
16
|
-
public
|
|
14
|
+
public badWords!: any
|
|
15
|
+
public wordsIn!: any
|
|
16
|
+
public cluesIn!: any
|
|
17
17
|
public grid: any = []
|
|
18
|
-
public
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
this.
|
|
18
|
+
public wordElements: any = []
|
|
19
|
+
|
|
20
|
+
constructor(wordsIn: any, cluesIn: any) {
|
|
21
|
+
this.wordsIn = wordsIn
|
|
22
|
+
this.cluesIn = cluesIn
|
|
22
23
|
// constructor
|
|
23
|
-
if (this.
|
|
24
|
+
if (this.wordsIn.length < 2)
|
|
24
25
|
throw 'A crossword must have at least 2 words'
|
|
25
|
-
if (this.
|
|
26
|
+
if (this.wordsIn.length !== cluesIn.length)
|
|
26
27
|
throw 'The number of words must equal the number of clues'
|
|
27
28
|
// build the grid;
|
|
28
29
|
this.grid = new Array(this.GRID_ROWS)
|
|
29
|
-
for (
|
|
30
|
+
for (let i = 0; i < this.GRID_ROWS; i++) {
|
|
30
31
|
this.grid[i] = new Array(this.GRID_COLS)
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
// build the element list (need to keep track of indexes in the originial input arrays)
|
|
34
|
-
for (let i = 0; i <
|
|
35
|
-
this.
|
|
35
|
+
for (let i = 0; i < wordsIn.length; i++) {
|
|
36
|
+
this.wordElements.push(new WordElement(wordsIn[i], i))
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
// I got this sorting idea from http://stackoverflow.com/questions/943113/algorithm-to-generate-a-crossword/1021800#1021800
|
|
39
40
|
// seems to work well
|
|
40
|
-
this.
|
|
41
|
+
this.wordElements.sort(function (a: any, b: any) {
|
|
41
42
|
return b.word.length - a.word.length
|
|
42
43
|
})
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
// returns the crossword grid that has the ratio closest to 1 or null if it can't build one
|
|
46
|
-
public getSquareGrid(
|
|
47
|
-
let
|
|
48
|
-
|
|
49
|
-
for (
|
|
50
|
-
|
|
51
|
-
if (
|
|
47
|
+
public getSquareGrid(maxTries: number) {
|
|
48
|
+
let bestGrid: any = null
|
|
49
|
+
let bestRatio = 0
|
|
50
|
+
for (let i = 0; i < maxTries; i++) {
|
|
51
|
+
let aGrid: any = this.getGrid(1)
|
|
52
|
+
if (aGrid === null) {
|
|
52
53
|
continue
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
-
(Math.min(
|
|
56
|
-
Math.max(
|
|
57
|
-
if (ratio >
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
let ratio =
|
|
56
|
+
(Math.min(aGrid.length, aGrid[0].length) * 1.0) /
|
|
57
|
+
Math.max(aGrid.length, aGrid[0].length)
|
|
58
|
+
if (ratio > bestRatio) {
|
|
59
|
+
bestGrid = aGrid
|
|
60
|
+
bestRatio = ratio
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
if (
|
|
63
|
+
if (bestRatio === 1) break
|
|
63
64
|
}
|
|
64
|
-
return
|
|
65
|
+
return bestGrid
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
// returns an abitrary grid, or null if it can't build one
|
|
68
|
-
public getGrid(
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
public getGrid(maxTries: number) {
|
|
70
|
+
const groups: any = []
|
|
71
|
+
let wordHasBeenAddedToGrid: boolean = false
|
|
72
|
+
|
|
73
|
+
for (let tries = 0; tries < maxTries; tries++) {
|
|
74
|
+
this.clear() // always start with a fresh grid and charIndex
|
|
71
75
|
// place the first word in the middle of the grid
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
let start_dir = this.randomDirection()
|
|
77
|
+
let r = Math.floor(this.grid.length / 2)
|
|
78
|
+
let c = Math.floor(this.grid[0].length / 2)
|
|
75
79
|
|
|
76
|
-
|
|
77
|
-
if (start_dir
|
|
78
|
-
c -= Math.floor(
|
|
80
|
+
let wordElement = this.wordElements[0]
|
|
81
|
+
if (start_dir === 'across') {
|
|
82
|
+
c -= Math.floor(wordElement.word.length / 2)
|
|
79
83
|
} else {
|
|
80
|
-
r -= Math.floor(
|
|
84
|
+
r -= Math.floor(wordElement.word.length / 2)
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
if (
|
|
84
|
-
this.canPlaceWordAt(
|
|
85
|
-
false
|
|
88
|
+
this.canPlaceWordAt(wordElement.word, r, c, start_dir) !== false
|
|
86
89
|
) {
|
|
87
90
|
this.placeWordAt(
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
wordElement.word,
|
|
92
|
+
wordElement.index,
|
|
90
93
|
r,
|
|
91
94
|
c,
|
|
92
95
|
start_dir
|
|
93
96
|
)
|
|
94
97
|
} else {
|
|
95
|
-
this.
|
|
98
|
+
this.badWords = [wordElement]
|
|
96
99
|
return null
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
// start with a group containing all the words (except the first)
|
|
100
103
|
// as we go, we try to place each word in the group onto the grid
|
|
101
104
|
// if the word can't go on the grid, we add that word to the next group
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
groups.
|
|
105
|
-
|
|
106
|
-
word_has_been_added_to_grid = false
|
|
105
|
+
|
|
106
|
+
groups.push(this.wordElements.slice(1))
|
|
107
|
+
for (let g = 0; g < groups.length; g++) {
|
|
108
|
+
wordHasBeenAddedToGrid = false
|
|
107
109
|
// try to add all the words in this group to the grid
|
|
108
|
-
for (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
for (let i = 0; i < groups[g].length; i++) {
|
|
111
|
+
wordElement = groups[g][i]
|
|
112
|
+
const bestPosition: any = this.findPositionForWord(
|
|
113
|
+
wordElement.word
|
|
112
114
|
)
|
|
113
|
-
if (!
|
|
115
|
+
if (!bestPosition) {
|
|
114
116
|
// make the new group (if needed)
|
|
115
|
-
if (groups.length - 1
|
|
117
|
+
if (groups.length - 1 === g) groups.push([])
|
|
116
118
|
// place the word in the next group
|
|
117
|
-
groups[g + 1].push(
|
|
119
|
+
groups[g + 1].push(wordElement)
|
|
118
120
|
} else {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
r = bestPosition['row']
|
|
122
|
+
c = bestPosition['col']
|
|
123
|
+
|
|
121
124
|
this.placeWordAt(
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
wordElement.word,
|
|
126
|
+
wordElement.index,
|
|
124
127
|
r,
|
|
125
128
|
c,
|
|
126
|
-
|
|
129
|
+
bestPosition['direction']
|
|
127
130
|
)
|
|
128
|
-
|
|
131
|
+
wordHasBeenAddedToGrid = true
|
|
129
132
|
}
|
|
130
133
|
}
|
|
131
134
|
// if we haven't made any progress, there is no point in going on to the next group
|
|
132
|
-
if (!
|
|
135
|
+
if (!wordHasBeenAddedToGrid) break
|
|
133
136
|
}
|
|
134
137
|
// no need to try again
|
|
135
|
-
if (
|
|
138
|
+
if (wordHasBeenAddedToGrid) return this.minimizeGrid()
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
this.
|
|
141
|
+
this.badWords = groups[groups.length - 1]
|
|
139
142
|
return null
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
// returns the list of WordElements that can't fit on the crossword
|
|
143
146
|
public getBadWords() {
|
|
144
|
-
return this.
|
|
147
|
+
return this.badWords
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
// get two arrays ("across" and "down") that contain objects describing the
|
|
148
151
|
// topological position of the word (e.g. 1 is the first word starting from
|
|
149
152
|
// the top left, going to the bottom right), the index of the word (in the
|
|
150
153
|
// original input list), the clue, and the word itself
|
|
151
|
-
public getLegend(grid) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
for (
|
|
155
|
-
for (
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
public getLegend(grid: any) {
|
|
155
|
+
let groups: any = { across: [], down: [] }
|
|
156
|
+
let position = 1
|
|
157
|
+
for (let row = 0; row < grid.length; row++) {
|
|
158
|
+
for (let col = 0; col < grid[row].length; col++) {
|
|
159
|
+
let cell = grid[row][col]
|
|
160
|
+
let increment_position = false
|
|
158
161
|
// check across and down
|
|
159
|
-
for (
|
|
162
|
+
for (let direction in groups) {
|
|
160
163
|
// does a word start here? (make sure the cell isn't null, first)
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
if (
|
|
165
|
+
cell &&
|
|
166
|
+
cell[direction] &&
|
|
167
|
+
cell[direction].isStartOfWord
|
|
168
|
+
) {
|
|
169
|
+
const index = cell[direction].index
|
|
170
|
+
groups[direction].push({
|
|
164
171
|
position: position,
|
|
165
172
|
index: index,
|
|
166
|
-
clue: this.
|
|
167
|
-
word: this.
|
|
173
|
+
clue: this.cluesIn[index],
|
|
174
|
+
word: this.wordsIn[index],
|
|
168
175
|
})
|
|
176
|
+
// Set the cell position now that we've generated a legend
|
|
177
|
+
cell[direction].position = position
|
|
169
178
|
increment_position = true
|
|
170
179
|
}
|
|
171
180
|
}
|
|
@@ -179,14 +188,15 @@ class Crossword {
|
|
|
179
188
|
// move the grid onto the smallest grid that will fit it
|
|
180
189
|
public minimizeGrid() {
|
|
181
190
|
// find bounds
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
191
|
+
let r_min = this.GRID_ROWS - 1
|
|
192
|
+
let r_max = 0
|
|
193
|
+
let c_min = this.GRID_COLS - 1
|
|
194
|
+
let c_max = 0
|
|
195
|
+
|
|
196
|
+
for (let r = 0; r < this.GRID_ROWS; r++) {
|
|
197
|
+
for (let c = 0; c < this.GRID_COLS; c++) {
|
|
198
|
+
let cell = this.grid[r][c]
|
|
199
|
+
if (cell !== null) {
|
|
190
200
|
if (r < r_min) r_min = r
|
|
191
201
|
if (r > r_max) r_max = r
|
|
192
202
|
if (c < c_min) c_min = c
|
|
@@ -195,9 +205,9 @@ class Crossword {
|
|
|
195
205
|
}
|
|
196
206
|
}
|
|
197
207
|
// initialize new grid
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
208
|
+
let rows = r_max - r_min + 1
|
|
209
|
+
let cols = c_max - c_min + 1
|
|
210
|
+
let new_grid = new Array(rows)
|
|
201
211
|
for (let r = 0; r < rows; r++) {
|
|
202
212
|
for (let c = 0; c < cols; c++) {
|
|
203
213
|
new_grid[r] = new Array(cols)
|
|
@@ -216,50 +226,55 @@ class Crossword {
|
|
|
216
226
|
|
|
217
227
|
// helper for placeWordAt();
|
|
218
228
|
public addCellToGrid(
|
|
219
|
-
word,
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
direction
|
|
229
|
+
word: any,
|
|
230
|
+
indexOfWordInInputList: any,
|
|
231
|
+
indexOfChar: any,
|
|
232
|
+
row: any,
|
|
233
|
+
col: any,
|
|
234
|
+
direction: any
|
|
225
235
|
) {
|
|
226
|
-
|
|
227
|
-
if (this.grid[
|
|
228
|
-
this.grid[
|
|
236
|
+
const char = word.charAt(indexOfChar)
|
|
237
|
+
if (this.grid[row][col] === null) {
|
|
238
|
+
this.grid[row][col] = new CrosswordCell(char)
|
|
229
239
|
|
|
230
|
-
// init the
|
|
231
|
-
if (!this.
|
|
240
|
+
// init the charIndex for that character if needed
|
|
241
|
+
if (!this.charIndex[char]) this.charIndex[char] = []
|
|
232
242
|
|
|
233
243
|
// add to index
|
|
234
|
-
this.
|
|
244
|
+
this.charIndex[char].push({ row, col })
|
|
235
245
|
}
|
|
236
246
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
index_of_word_in_input_list
|
|
247
|
+
this.grid[row][col][direction] = new CrosswordCellNode(
|
|
248
|
+
indexOfChar === 0,
|
|
249
|
+
indexOfWordInInputList
|
|
241
250
|
)
|
|
242
251
|
}
|
|
243
252
|
|
|
244
253
|
// place the word at the row and col indicated (the first char goes there)
|
|
245
254
|
// the next chars go to the right (across) or below (down), depending on the direction
|
|
246
|
-
public placeWordAt(
|
|
247
|
-
|
|
255
|
+
public placeWordAt(
|
|
256
|
+
word: any,
|
|
257
|
+
indexOfWordInInputList: any,
|
|
258
|
+
row: any,
|
|
259
|
+
col: any,
|
|
260
|
+
direction: any
|
|
261
|
+
) {
|
|
262
|
+
if (direction === 'across') {
|
|
248
263
|
for (let c = col, i = 0; c < col + word.length; c++, i++) {
|
|
249
264
|
this.addCellToGrid(
|
|
250
265
|
word,
|
|
251
|
-
|
|
266
|
+
indexOfWordInInputList,
|
|
252
267
|
i,
|
|
253
268
|
row,
|
|
254
269
|
c,
|
|
255
270
|
direction
|
|
256
271
|
)
|
|
257
272
|
}
|
|
258
|
-
} else if (direction
|
|
273
|
+
} else if (direction === 'down') {
|
|
259
274
|
for (let r = row, i = 0; r < row + word.length; r++, i++) {
|
|
260
275
|
this.addCellToGrid(
|
|
261
276
|
word,
|
|
262
|
-
|
|
277
|
+
indexOfWordInInputList,
|
|
263
278
|
i,
|
|
264
279
|
r,
|
|
265
280
|
col,
|
|
@@ -276,17 +291,17 @@ class Crossword {
|
|
|
276
291
|
// returns false, if you can't place the char
|
|
277
292
|
// 0 if you can place the char, but there is no intersection
|
|
278
293
|
// 1 if you can place the char, and there is an intersection
|
|
279
|
-
public canPlaceCharAt(char, row, col) {
|
|
294
|
+
public canPlaceCharAt(char: any, row: any, col: any) {
|
|
280
295
|
// no intersection
|
|
281
|
-
if (this.grid[row][col]
|
|
296
|
+
if (this.grid[row][col] === null) return 0
|
|
282
297
|
// intersection!
|
|
283
|
-
if (this.grid[row][col]['char']
|
|
298
|
+
if (this.grid[row][col]['char'] === char) return 1
|
|
284
299
|
|
|
285
300
|
return false
|
|
286
301
|
}
|
|
287
302
|
|
|
288
303
|
// determines if you can place a word at the row, column in the direction
|
|
289
|
-
public canPlaceWordAt(word, row, col, direction): any {
|
|
304
|
+
public canPlaceWordAt(word: any, row: any, col: any, direction: any): any {
|
|
290
305
|
// out of bounds
|
|
291
306
|
if (
|
|
292
307
|
row < 0 ||
|
|
@@ -296,17 +311,17 @@ class Crossword {
|
|
|
296
311
|
)
|
|
297
312
|
return false
|
|
298
313
|
|
|
299
|
-
|
|
314
|
+
let intersections
|
|
300
315
|
|
|
301
|
-
if (direction
|
|
316
|
+
if (direction === 'across') {
|
|
302
317
|
// out of bounds (word too long)
|
|
303
318
|
if (col + word.length > this.grid[row].length) return false
|
|
304
319
|
// can't have a word directly to the left
|
|
305
|
-
if (col - 1 >= 0 && this.grid[row][col - 1]
|
|
320
|
+
if (col - 1 >= 0 && this.grid[row][col - 1] !== null) return false
|
|
306
321
|
// can't have word directly to the right
|
|
307
322
|
if (
|
|
308
323
|
col + word.length < this.grid[row].length &&
|
|
309
|
-
this.grid[row][col + word.length]
|
|
324
|
+
this.grid[row][col + word.length] !== null
|
|
310
325
|
)
|
|
311
326
|
return false
|
|
312
327
|
|
|
@@ -318,10 +333,10 @@ class Crossword {
|
|
|
318
333
|
r >= 0 && c < col + word.length;
|
|
319
334
|
c++, i++
|
|
320
335
|
) {
|
|
321
|
-
let is_empty = this.grid[r][c]
|
|
336
|
+
let is_empty = this.grid[r][c] === null
|
|
322
337
|
let is_intersection =
|
|
323
|
-
this.grid[row][c]
|
|
324
|
-
this.grid[row][c]['char']
|
|
338
|
+
this.grid[row][c] !== null &&
|
|
339
|
+
this.grid[row][c]['char'] === word.charAt(i)
|
|
325
340
|
let can_place_here = is_empty || is_intersection
|
|
326
341
|
if (!can_place_here) return false
|
|
327
342
|
}
|
|
@@ -332,10 +347,10 @@ class Crossword {
|
|
|
332
347
|
r < this.grid.length && c < col + word.length;
|
|
333
348
|
c++, i++
|
|
334
349
|
) {
|
|
335
|
-
let is_empty = this.grid[r][c]
|
|
350
|
+
let is_empty = this.grid[r][c] === null
|
|
336
351
|
let is_intersection =
|
|
337
|
-
this.grid[row][c]
|
|
338
|
-
this.grid[row][c]['char']
|
|
352
|
+
this.grid[row][c] !== null &&
|
|
353
|
+
this.grid[row][c]['char'] === word.charAt(i)
|
|
339
354
|
let can_place_here = is_empty || is_intersection
|
|
340
355
|
if (!can_place_here) return false
|
|
341
356
|
}
|
|
@@ -348,15 +363,15 @@ class Crossword {
|
|
|
348
363
|
if (result === false) return false
|
|
349
364
|
intersections += result
|
|
350
365
|
}
|
|
351
|
-
} else if (direction
|
|
366
|
+
} else if (direction === 'down') {
|
|
352
367
|
// out of bounds
|
|
353
368
|
if (row + word.length > this.grid.length) return false
|
|
354
369
|
// can't have a word directly above
|
|
355
|
-
if (row - 1 >= 0 && this.grid[row - 1][col]
|
|
370
|
+
if (row - 1 >= 0 && this.grid[row - 1][col] !== null) return false
|
|
356
371
|
// can't have a word directly below
|
|
357
372
|
if (
|
|
358
373
|
row + word.length < this.grid.length &&
|
|
359
|
-
this.grid[row + word.length][col]
|
|
374
|
+
this.grid[row + word.length][col] !== null
|
|
360
375
|
)
|
|
361
376
|
return false
|
|
362
377
|
|
|
@@ -369,10 +384,10 @@ class Crossword {
|
|
|
369
384
|
c >= 0 && r < row + word.length;
|
|
370
385
|
r++, i++
|
|
371
386
|
) {
|
|
372
|
-
let is_empty = this.grid[r][c]
|
|
387
|
+
let is_empty = this.grid[r][c] === null
|
|
373
388
|
let is_intersection =
|
|
374
|
-
this.grid[r][col]
|
|
375
|
-
this.grid[r][col]['char']
|
|
389
|
+
this.grid[r][col] !== null &&
|
|
390
|
+
this.grid[r][col]['char'] === word.charAt(i)
|
|
376
391
|
let can_place_here = is_empty || is_intersection
|
|
377
392
|
if (!can_place_here) return false
|
|
378
393
|
}
|
|
@@ -383,10 +398,10 @@ class Crossword {
|
|
|
383
398
|
r < row + word.length && c < this.grid[r].length;
|
|
384
399
|
r++, i++
|
|
385
400
|
) {
|
|
386
|
-
let is_empty = this.grid[r][c]
|
|
401
|
+
let is_empty = this.grid[r][c] === null
|
|
387
402
|
let is_intersection =
|
|
388
|
-
this.grid[r][col]
|
|
389
|
-
this.grid[r][col]['char']
|
|
403
|
+
this.grid[r][col] !== null &&
|
|
404
|
+
this.grid[r][col]['char'] === word.charAt(i)
|
|
390
405
|
let can_place_here = is_empty || is_intersection
|
|
391
406
|
if (!can_place_here) return false
|
|
392
407
|
}
|
|
@@ -409,24 +424,24 @@ class Crossword {
|
|
|
409
424
|
return Math.floor(Math.random() * 2) ? 'across' : 'down'
|
|
410
425
|
}
|
|
411
426
|
|
|
412
|
-
public findPositionForWord(word) {
|
|
413
|
-
// check the
|
|
427
|
+
public findPositionForWord(word: any) {
|
|
428
|
+
// check the charIndex for every letter, and see if we can put it there in a direction
|
|
414
429
|
let bests: object[] = []
|
|
415
|
-
for (
|
|
416
|
-
|
|
430
|
+
for (let i = 0; i < word.length; i++) {
|
|
431
|
+
const possible_locations_on_grid = this.charIndex[word.charAt(i)]
|
|
417
432
|
if (!possible_locations_on_grid) continue
|
|
418
|
-
for (
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
433
|
+
for (let j = 0; j < possible_locations_on_grid.length; j++) {
|
|
434
|
+
const point = possible_locations_on_grid[j]
|
|
435
|
+
const r = point['row']
|
|
436
|
+
const c = point['col']
|
|
422
437
|
// the c - i, and r - i here compensate for the offset of character in the word
|
|
423
|
-
|
|
438
|
+
const intersections_across = this.canPlaceWordAt(
|
|
424
439
|
word,
|
|
425
440
|
r,
|
|
426
441
|
c - i,
|
|
427
442
|
'across'
|
|
428
443
|
)
|
|
429
|
-
|
|
444
|
+
const intersections_down = this.canPlaceWordAt(
|
|
430
445
|
word,
|
|
431
446
|
r - i,
|
|
432
447
|
c,
|
|
@@ -434,7 +449,7 @@ class Crossword {
|
|
|
434
449
|
)
|
|
435
450
|
|
|
436
451
|
if (intersections_across !== false) {
|
|
437
|
-
|
|
452
|
+
const objectAcross = {
|
|
438
453
|
intersections: intersections_across,
|
|
439
454
|
row: r,
|
|
440
455
|
col: c - i,
|
|
@@ -443,7 +458,7 @@ class Crossword {
|
|
|
443
458
|
bests.push(objectAcross)
|
|
444
459
|
}
|
|
445
460
|
if (intersections_down !== false) {
|
|
446
|
-
|
|
461
|
+
const objectDown = {
|
|
447
462
|
intersections: intersections_down,
|
|
448
463
|
row: r - i,
|
|
449
464
|
col: c,
|
|
@@ -454,22 +469,85 @@ class Crossword {
|
|
|
454
469
|
}
|
|
455
470
|
}
|
|
456
471
|
|
|
457
|
-
if (bests.length
|
|
472
|
+
if (bests.length === 0) return false
|
|
458
473
|
|
|
459
474
|
// find a good random position
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
return best
|
|
475
|
+
return bests[Math.floor(Math.random() * bests.length)]
|
|
463
476
|
}
|
|
464
477
|
|
|
465
478
|
public clear() {
|
|
466
|
-
for (
|
|
467
|
-
for (
|
|
479
|
+
for (let r = 0; r < this.grid.length; r++) {
|
|
480
|
+
for (let c = 0; c < this.grid[r].length; c++) {
|
|
468
481
|
this.grid[r][c] = null
|
|
469
482
|
}
|
|
470
483
|
}
|
|
471
|
-
this.
|
|
484
|
+
this.charIndex = {}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
class CrosswordUtils {
|
|
489
|
+
public static PATH_TO_PNGS_OF_NUMBERS: string = 'numbers/'
|
|
490
|
+
|
|
491
|
+
public static toHtml(grid: any, show_answers: any) {
|
|
492
|
+
if (grid === null) return
|
|
493
|
+
const html: Array<string> = []
|
|
494
|
+
html.push("<table class='crossword'>")
|
|
495
|
+
let label = 1
|
|
496
|
+
for (let r = 0; r < grid.length; r++) {
|
|
497
|
+
html.push('<tr>')
|
|
498
|
+
for (let c = 0; c < grid[r].length; c++) {
|
|
499
|
+
const cell = grid[r][c]
|
|
500
|
+
let isStartOfWord = false
|
|
501
|
+
let css_class, char
|
|
502
|
+
if (cell === null) {
|
|
503
|
+
char = ' '
|
|
504
|
+
css_class = 'no-border'
|
|
505
|
+
} else {
|
|
506
|
+
char = cell['char']
|
|
507
|
+
css_class = ''
|
|
508
|
+
isStartOfWord =
|
|
509
|
+
(cell.across && cell.across.isStartOfWord) ||
|
|
510
|
+
(cell.down && cell.down.isStartOfWord)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (isStartOfWord) {
|
|
514
|
+
const img_url =
|
|
515
|
+
this.PATH_TO_PNGS_OF_NUMBERS + label.toString() + '.png'
|
|
516
|
+
html.push(
|
|
517
|
+
"<td class='" +
|
|
518
|
+
css_class +
|
|
519
|
+
"' title='" +
|
|
520
|
+
r +
|
|
521
|
+
', ' +
|
|
522
|
+
c +
|
|
523
|
+
"' style=\"background-image:url('" +
|
|
524
|
+
img_url +
|
|
525
|
+
'\')">'
|
|
526
|
+
)
|
|
527
|
+
label++
|
|
528
|
+
} else {
|
|
529
|
+
html.push(
|
|
530
|
+
"<td class='" +
|
|
531
|
+
css_class +
|
|
532
|
+
"' title='" +
|
|
533
|
+
r +
|
|
534
|
+
', ' +
|
|
535
|
+
c +
|
|
536
|
+
"'>"
|
|
537
|
+
)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (show_answers) {
|
|
541
|
+
html.push(char)
|
|
542
|
+
} else {
|
|
543
|
+
html.push(' ')
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
html.push('</tr>')
|
|
547
|
+
}
|
|
548
|
+
html.push('</table>')
|
|
549
|
+
return html.join('\n')
|
|
472
550
|
}
|
|
473
551
|
}
|
|
474
552
|
|
|
475
|
-
export { Crossword }
|
|
553
|
+
export { Crossword, CrosswordUtils }
|