@windward/games 0.0.3 → 0.0.5
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 +553 -0
- package/components/content/blocks/crosswordPuzzle/CrosswordClues.vue +91 -0
- package/components/content/blocks/crosswordPuzzle/CrosswordElements.ts +38 -0
- package/components/content/blocks/crosswordPuzzle/CrosswordPuzzle.vue +673 -0
- 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/content/blocks/wordJumble/WordJumble.vue +22 -10
- package/components/settings/CrosswordPuzzleSettingsManager.vue +271 -0
- package/components/settings/MultipleChoiceSettingsManager.vue +21 -7
- package/components/settings/SevenStrikesSettingsManager.vue +288 -0
- package/components/settings/WordJumbleSettingsManager.vue +4 -1
- package/i18n/en-US/components/content/blocks/crossword.ts +22 -0
- package/i18n/en-US/components/content/blocks/index.ts +4 -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 +7 -0
- package/i18n/en-US/components/settings/index.ts +4 -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/components/settings/word_jumble.ts +1 -1
- package/i18n/en-US/shared/content_blocks.ts +2 -0
- package/i18n/en-US/shared/settings.ts +2 -0
- package/package.json +2 -1
- package/plugin.js +43 -1
- package/test/blocks/crossword/CrosswordPuzzle.spec.js +49 -0
- package/test/blocks/sevenStrikes/sevenStrikes.spec.js +24 -0
- package/test/settings/BucketGameManager.spec.js +1 -1
- package/test/settings/CrosswordPuzzleManager.spec.js +103 -0
- package/test/settings/SevenStrikesManager.spec.js +53 -0
- package/test/settings/WordJumbleManager.spec.js +2 -2
|
@@ -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) {
|
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CrosswordCell,
|
|
3
|
+
CrosswordCellNode,
|
|
4
|
+
WordElement,
|
|
5
|
+
} from './CrosswordElements'
|
|
6
|
+
class Crossword {
|
|
7
|
+
public GRID_COLS: any = 50
|
|
8
|
+
public GRID_ROWS: any = 50
|
|
9
|
+
// This is an index of the positions of the char in the crossword (so we know where we can potentially place words)
|
|
10
|
+
// example {"a" : [{'row' : 10, 'col' : 5}, {'row' : 62, 'col' :17}], {'row' : 54, 'col' : 12}], "b" : [{'row' : 3, 'col' : 13}]}
|
|
11
|
+
// where the two item arrays are the row and column of where the letter occurs
|
|
12
|
+
public charIndex: any = {}
|
|
13
|
+
// these words are the words that can't be placed on the crossword
|
|
14
|
+
public badWords!: any
|
|
15
|
+
public wordsIn!: any
|
|
16
|
+
public cluesIn!: any
|
|
17
|
+
public grid: any = []
|
|
18
|
+
public wordElements: any = []
|
|
19
|
+
|
|
20
|
+
constructor(wordsIn: any, cluesIn: any) {
|
|
21
|
+
this.wordsIn = wordsIn
|
|
22
|
+
this.cluesIn = cluesIn
|
|
23
|
+
// constructor
|
|
24
|
+
if (this.wordsIn.length < 2)
|
|
25
|
+
throw 'A crossword must have at least 2 words'
|
|
26
|
+
if (this.wordsIn.length !== cluesIn.length)
|
|
27
|
+
throw 'The number of words must equal the number of clues'
|
|
28
|
+
// build the grid;
|
|
29
|
+
this.grid = new Array(this.GRID_ROWS)
|
|
30
|
+
for (let i = 0; i < this.GRID_ROWS; i++) {
|
|
31
|
+
this.grid[i] = new Array(this.GRID_COLS)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// build the element list (need to keep track of indexes in the originial input arrays)
|
|
35
|
+
for (let i = 0; i < wordsIn.length; i++) {
|
|
36
|
+
this.wordElements.push(new WordElement(wordsIn[i], i))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// I got this sorting idea from http://stackoverflow.com/questions/943113/algorithm-to-generate-a-crossword/1021800#1021800
|
|
40
|
+
// seems to work well
|
|
41
|
+
this.wordElements.sort(function (a: any, b: any) {
|
|
42
|
+
return b.word.length - a.word.length
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// returns the crossword grid that has the ratio closest to 1 or null if it can't build one
|
|
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) {
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
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
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (bestRatio === 1) break
|
|
64
|
+
}
|
|
65
|
+
return bestGrid
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// returns an abitrary grid, or null if it can't build one
|
|
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
|
|
75
|
+
// place the first word in the middle of the grid
|
|
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)
|
|
79
|
+
|
|
80
|
+
let wordElement = this.wordElements[0]
|
|
81
|
+
if (start_dir === 'across') {
|
|
82
|
+
c -= Math.floor(wordElement.word.length / 2)
|
|
83
|
+
} else {
|
|
84
|
+
r -= Math.floor(wordElement.word.length / 2)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (
|
|
88
|
+
this.canPlaceWordAt(wordElement.word, r, c, start_dir) !== false
|
|
89
|
+
) {
|
|
90
|
+
this.placeWordAt(
|
|
91
|
+
wordElement.word,
|
|
92
|
+
wordElement.index,
|
|
93
|
+
r,
|
|
94
|
+
c,
|
|
95
|
+
start_dir
|
|
96
|
+
)
|
|
97
|
+
} else {
|
|
98
|
+
this.badWords = [wordElement]
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// start with a group containing all the words (except the first)
|
|
103
|
+
// as we go, we try to place each word in the group onto the grid
|
|
104
|
+
// if the word can't go on the grid, we add that word to the next group
|
|
105
|
+
|
|
106
|
+
groups.push(this.wordElements.slice(1))
|
|
107
|
+
for (let g = 0; g < groups.length; g++) {
|
|
108
|
+
wordHasBeenAddedToGrid = false
|
|
109
|
+
// try to add all the words in this group to the grid
|
|
110
|
+
for (let i = 0; i < groups[g].length; i++) {
|
|
111
|
+
wordElement = groups[g][i]
|
|
112
|
+
const bestPosition: any = this.findPositionForWord(
|
|
113
|
+
wordElement.word
|
|
114
|
+
)
|
|
115
|
+
if (!bestPosition) {
|
|
116
|
+
// make the new group (if needed)
|
|
117
|
+
if (groups.length - 1 === g) groups.push([])
|
|
118
|
+
// place the word in the next group
|
|
119
|
+
groups[g + 1].push(wordElement)
|
|
120
|
+
} else {
|
|
121
|
+
r = bestPosition['row']
|
|
122
|
+
c = bestPosition['col']
|
|
123
|
+
|
|
124
|
+
this.placeWordAt(
|
|
125
|
+
wordElement.word,
|
|
126
|
+
wordElement.index,
|
|
127
|
+
r,
|
|
128
|
+
c,
|
|
129
|
+
bestPosition['direction']
|
|
130
|
+
)
|
|
131
|
+
wordHasBeenAddedToGrid = true
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// if we haven't made any progress, there is no point in going on to the next group
|
|
135
|
+
if (!wordHasBeenAddedToGrid) break
|
|
136
|
+
}
|
|
137
|
+
// no need to try again
|
|
138
|
+
if (wordHasBeenAddedToGrid) return this.minimizeGrid()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.badWords = groups[groups.length - 1]
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// returns the list of WordElements that can't fit on the crossword
|
|
146
|
+
public getBadWords() {
|
|
147
|
+
return this.badWords
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// get two arrays ("across" and "down") that contain objects describing the
|
|
151
|
+
// topological position of the word (e.g. 1 is the first word starting from
|
|
152
|
+
// the top left, going to the bottom right), the index of the word (in the
|
|
153
|
+
// original input list), the clue, and the word itself
|
|
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
|
|
161
|
+
// check across and down
|
|
162
|
+
for (let direction in groups) {
|
|
163
|
+
// does a word start here? (make sure the cell isn't null, first)
|
|
164
|
+
if (
|
|
165
|
+
cell &&
|
|
166
|
+
cell[direction] &&
|
|
167
|
+
cell[direction].isStartOfWord
|
|
168
|
+
) {
|
|
169
|
+
const index = cell[direction].index
|
|
170
|
+
groups[direction].push({
|
|
171
|
+
position: position,
|
|
172
|
+
index: index,
|
|
173
|
+
clue: this.cluesIn[index],
|
|
174
|
+
word: this.wordsIn[index],
|
|
175
|
+
})
|
|
176
|
+
// Set the cell position now that we've generated a legend
|
|
177
|
+
cell[direction].position = position
|
|
178
|
+
increment_position = true
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (increment_position) position++
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return groups
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// move the grid onto the smallest grid that will fit it
|
|
189
|
+
public minimizeGrid() {
|
|
190
|
+
// find bounds
|
|
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) {
|
|
200
|
+
if (r < r_min) r_min = r
|
|
201
|
+
if (r > r_max) r_max = r
|
|
202
|
+
if (c < c_min) c_min = c
|
|
203
|
+
if (c > c_max) c_max = c
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// initialize new grid
|
|
208
|
+
let rows = r_max - r_min + 1
|
|
209
|
+
let cols = c_max - c_min + 1
|
|
210
|
+
let new_grid = new Array(rows)
|
|
211
|
+
for (let r = 0; r < rows; r++) {
|
|
212
|
+
for (let c = 0; c < cols; c++) {
|
|
213
|
+
new_grid[r] = new Array(cols)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// copy the grid onto the smaller grid
|
|
218
|
+
for (let r = r_min, r2 = 0; r2 < rows; r++, r2++) {
|
|
219
|
+
for (let c = c_min, c2 = 0; c2 < cols; c++, c2++) {
|
|
220
|
+
new_grid[r2][c2] = this.grid[r][c]
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return new_grid
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// helper for placeWordAt();
|
|
228
|
+
public addCellToGrid(
|
|
229
|
+
word: any,
|
|
230
|
+
indexOfWordInInputList: any,
|
|
231
|
+
indexOfChar: any,
|
|
232
|
+
row: any,
|
|
233
|
+
col: any,
|
|
234
|
+
direction: any
|
|
235
|
+
) {
|
|
236
|
+
const char = word.charAt(indexOfChar)
|
|
237
|
+
if (this.grid[row][col] === null) {
|
|
238
|
+
this.grid[row][col] = new CrosswordCell(char)
|
|
239
|
+
|
|
240
|
+
// init the charIndex for that character if needed
|
|
241
|
+
if (!this.charIndex[char]) this.charIndex[char] = []
|
|
242
|
+
|
|
243
|
+
// add to index
|
|
244
|
+
this.charIndex[char].push({ row, col })
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
this.grid[row][col][direction] = new CrosswordCellNode(
|
|
248
|
+
indexOfChar === 0,
|
|
249
|
+
indexOfWordInInputList
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// place the word at the row and col indicated (the first char goes there)
|
|
254
|
+
// the next chars go to the right (across) or below (down), depending on the direction
|
|
255
|
+
public placeWordAt(
|
|
256
|
+
word: any,
|
|
257
|
+
indexOfWordInInputList: any,
|
|
258
|
+
row: any,
|
|
259
|
+
col: any,
|
|
260
|
+
direction: any
|
|
261
|
+
) {
|
|
262
|
+
if (direction === 'across') {
|
|
263
|
+
for (let c = col, i = 0; c < col + word.length; c++, i++) {
|
|
264
|
+
this.addCellToGrid(
|
|
265
|
+
word,
|
|
266
|
+
indexOfWordInInputList,
|
|
267
|
+
i,
|
|
268
|
+
row,
|
|
269
|
+
c,
|
|
270
|
+
direction
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
} else if (direction === 'down') {
|
|
274
|
+
for (let r = row, i = 0; r < row + word.length; r++, i++) {
|
|
275
|
+
this.addCellToGrid(
|
|
276
|
+
word,
|
|
277
|
+
indexOfWordInInputList,
|
|
278
|
+
i,
|
|
279
|
+
r,
|
|
280
|
+
col,
|
|
281
|
+
direction
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
throw 'Invalid Direction'
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// you can only place a char where the space is blank, or when the same
|
|
290
|
+
// character exists there already
|
|
291
|
+
// returns false, if you can't place the char
|
|
292
|
+
// 0 if you can place the char, but there is no intersection
|
|
293
|
+
// 1 if you can place the char, and there is an intersection
|
|
294
|
+
public canPlaceCharAt(char: any, row: any, col: any) {
|
|
295
|
+
// no intersection
|
|
296
|
+
if (this.grid[row][col] === null) return 0
|
|
297
|
+
// intersection!
|
|
298
|
+
if (this.grid[row][col]['char'] === char) return 1
|
|
299
|
+
|
|
300
|
+
return false
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// determines if you can place a word at the row, column in the direction
|
|
304
|
+
public canPlaceWordAt(word: any, row: any, col: any, direction: any): any {
|
|
305
|
+
// out of bounds
|
|
306
|
+
if (
|
|
307
|
+
row < 0 ||
|
|
308
|
+
row >= this.grid.length ||
|
|
309
|
+
col < 0 ||
|
|
310
|
+
col >= this.grid[row].length
|
|
311
|
+
)
|
|
312
|
+
return false
|
|
313
|
+
|
|
314
|
+
let intersections
|
|
315
|
+
|
|
316
|
+
if (direction === 'across') {
|
|
317
|
+
// out of bounds (word too long)
|
|
318
|
+
if (col + word.length > this.grid[row].length) return false
|
|
319
|
+
// can't have a word directly to the left
|
|
320
|
+
if (col - 1 >= 0 && this.grid[row][col - 1] !== null) return false
|
|
321
|
+
// can't have word directly to the right
|
|
322
|
+
if (
|
|
323
|
+
col + word.length < this.grid[row].length &&
|
|
324
|
+
this.grid[row][col + word.length] !== null
|
|
325
|
+
)
|
|
326
|
+
return false
|
|
327
|
+
|
|
328
|
+
// check the row above to make sure there isn't another word
|
|
329
|
+
// running parallel. It is ok if there is a character above, only if
|
|
330
|
+
// the character below it intersects with the current word
|
|
331
|
+
for (
|
|
332
|
+
let r = row - 1, c = col, i = 0;
|
|
333
|
+
r >= 0 && c < col + word.length;
|
|
334
|
+
c++, i++
|
|
335
|
+
) {
|
|
336
|
+
let is_empty = this.grid[r][c] === null
|
|
337
|
+
let is_intersection =
|
|
338
|
+
this.grid[row][c] !== null &&
|
|
339
|
+
this.grid[row][c]['char'] === word.charAt(i)
|
|
340
|
+
let can_place_here = is_empty || is_intersection
|
|
341
|
+
if (!can_place_here) return false
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// same deal as above, we just search in the row below the word
|
|
345
|
+
for (
|
|
346
|
+
let r = row + 1, c = col, i = 0;
|
|
347
|
+
r < this.grid.length && c < col + word.length;
|
|
348
|
+
c++, i++
|
|
349
|
+
) {
|
|
350
|
+
let is_empty = this.grid[r][c] === null
|
|
351
|
+
let is_intersection =
|
|
352
|
+
this.grid[row][c] !== null &&
|
|
353
|
+
this.grid[row][c]['char'] === word.charAt(i)
|
|
354
|
+
let can_place_here = is_empty || is_intersection
|
|
355
|
+
if (!can_place_here) return false
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// check to make sure we aren't overlapping a char (that doesn't match)
|
|
359
|
+
// and get the count of intersections
|
|
360
|
+
intersections = 0
|
|
361
|
+
for (let c = col, i = 0; c < col + word.length; c++, i++) {
|
|
362
|
+
let result = this.canPlaceCharAt(word.charAt(i), row, c)
|
|
363
|
+
if (result === false) return false
|
|
364
|
+
intersections += result
|
|
365
|
+
}
|
|
366
|
+
} else if (direction === 'down') {
|
|
367
|
+
// out of bounds
|
|
368
|
+
if (row + word.length > this.grid.length) return false
|
|
369
|
+
// can't have a word directly above
|
|
370
|
+
if (row - 1 >= 0 && this.grid[row - 1][col] !== null) return false
|
|
371
|
+
// can't have a word directly below
|
|
372
|
+
if (
|
|
373
|
+
row + word.length < this.grid.length &&
|
|
374
|
+
this.grid[row + word.length][col] !== null
|
|
375
|
+
)
|
|
376
|
+
return false
|
|
377
|
+
|
|
378
|
+
// check the column to the left to make sure there isn't another
|
|
379
|
+
// word running parallel. It is ok if there is a character to the
|
|
380
|
+
// left, only if the character to the right intersects with the
|
|
381
|
+
// current word
|
|
382
|
+
for (
|
|
383
|
+
let c = col - 1, r = row, i = 0;
|
|
384
|
+
c >= 0 && r < row + word.length;
|
|
385
|
+
r++, i++
|
|
386
|
+
) {
|
|
387
|
+
let is_empty = this.grid[r][c] === null
|
|
388
|
+
let is_intersection =
|
|
389
|
+
this.grid[r][col] !== null &&
|
|
390
|
+
this.grid[r][col]['char'] === word.charAt(i)
|
|
391
|
+
let can_place_here = is_empty || is_intersection
|
|
392
|
+
if (!can_place_here) return false
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// same deal, but look at the column to the right
|
|
396
|
+
for (
|
|
397
|
+
let c = col + 1, r = row, i = 0;
|
|
398
|
+
r < row + word.length && c < this.grid[r].length;
|
|
399
|
+
r++, i++
|
|
400
|
+
) {
|
|
401
|
+
let is_empty = this.grid[r][c] === null
|
|
402
|
+
let is_intersection =
|
|
403
|
+
this.grid[r][col] !== null &&
|
|
404
|
+
this.grid[r][col]['char'] === word.charAt(i)
|
|
405
|
+
let can_place_here = is_empty || is_intersection
|
|
406
|
+
if (!can_place_here) return false
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// check to make sure we aren't overlapping a char (that doesn't match)
|
|
410
|
+
// and get the count of intersections
|
|
411
|
+
intersections = 0
|
|
412
|
+
for (let r = row, i = 0; r < row + word.length; r++, i++) {
|
|
413
|
+
let result = this.canPlaceCharAt(word.charAt(i, 1), r, col)
|
|
414
|
+
if (result === false) return false
|
|
415
|
+
intersections += result
|
|
416
|
+
}
|
|
417
|
+
} else {
|
|
418
|
+
throw 'Invalid Direction'
|
|
419
|
+
}
|
|
420
|
+
return intersections
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
public randomDirection() {
|
|
424
|
+
return Math.floor(Math.random() * 2) ? 'across' : 'down'
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
public findPositionForWord(word: any) {
|
|
428
|
+
// check the charIndex for every letter, and see if we can put it there in a direction
|
|
429
|
+
let bests: object[] = []
|
|
430
|
+
for (let i = 0; i < word.length; i++) {
|
|
431
|
+
const possible_locations_on_grid = this.charIndex[word.charAt(i)]
|
|
432
|
+
if (!possible_locations_on_grid) continue
|
|
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']
|
|
437
|
+
// the c - i, and r - i here compensate for the offset of character in the word
|
|
438
|
+
const intersections_across = this.canPlaceWordAt(
|
|
439
|
+
word,
|
|
440
|
+
r,
|
|
441
|
+
c - i,
|
|
442
|
+
'across'
|
|
443
|
+
)
|
|
444
|
+
const intersections_down = this.canPlaceWordAt(
|
|
445
|
+
word,
|
|
446
|
+
r - i,
|
|
447
|
+
c,
|
|
448
|
+
'down'
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
if (intersections_across !== false) {
|
|
452
|
+
const objectAcross = {
|
|
453
|
+
intersections: intersections_across,
|
|
454
|
+
row: r,
|
|
455
|
+
col: c - i,
|
|
456
|
+
direction: 'across',
|
|
457
|
+
}
|
|
458
|
+
bests.push(objectAcross)
|
|
459
|
+
}
|
|
460
|
+
if (intersections_down !== false) {
|
|
461
|
+
const objectDown = {
|
|
462
|
+
intersections: intersections_down,
|
|
463
|
+
row: r - i,
|
|
464
|
+
col: c,
|
|
465
|
+
direction: 'down',
|
|
466
|
+
}
|
|
467
|
+
bests.push(objectDown)
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (bests.length === 0) return false
|
|
473
|
+
|
|
474
|
+
// find a good random position
|
|
475
|
+
return bests[Math.floor(Math.random() * bests.length)]
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
public clear() {
|
|
479
|
+
for (let r = 0; r < this.grid.length; r++) {
|
|
480
|
+
for (let c = 0; c < this.grid[r].length; c++) {
|
|
481
|
+
this.grid[r][c] = null
|
|
482
|
+
}
|
|
483
|
+
}
|
|
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')
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export { Crossword, CrosswordUtils }
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row>
|
|
3
|
+
<v-col
|
|
4
|
+
cols="6"
|
|
5
|
+
v-if="down.length > 0"
|
|
6
|
+
:class="`crossword-clues__list crossword-clues__list--down`"
|
|
7
|
+
>
|
|
8
|
+
<h3 class="crossword-clues__list-title">
|
|
9
|
+
{{
|
|
10
|
+
$t(`plugin.games.components.content.blocks.crossword.down`)
|
|
11
|
+
}}
|
|
12
|
+
</h3>
|
|
13
|
+
<v-list dense>
|
|
14
|
+
<v-list-item-group v-model="selectedItem">
|
|
15
|
+
<v-list-item
|
|
16
|
+
class="crossword-clues__list-item"
|
|
17
|
+
v-for="word in down"
|
|
18
|
+
:key="word.index"
|
|
19
|
+
:value="word.index"
|
|
20
|
+
@click="onClick(word)"
|
|
21
|
+
>
|
|
22
|
+
{{ word.position }}.
|
|
23
|
+
{{ word.clue }}
|
|
24
|
+
</v-list-item>
|
|
25
|
+
</v-list-item-group>
|
|
26
|
+
</v-list>
|
|
27
|
+
</v-col>
|
|
28
|
+
<v-col
|
|
29
|
+
cols="6"
|
|
30
|
+
v-if="across.length > 0"
|
|
31
|
+
:class="`crossword-clues__list crossword-clues__list--across`"
|
|
32
|
+
>
|
|
33
|
+
<h3 class="crossword-clues__list-title">
|
|
34
|
+
{{
|
|
35
|
+
$t(
|
|
36
|
+
`plugin.games.components.content.blocks.crossword.across`
|
|
37
|
+
)
|
|
38
|
+
}}
|
|
39
|
+
</h3>
|
|
40
|
+
<v-list dense>
|
|
41
|
+
<v-list-item-group v-model="selectedItem">
|
|
42
|
+
<v-list-item
|
|
43
|
+
class="crossword-clues__list-item"
|
|
44
|
+
v-for="word in across"
|
|
45
|
+
:key="word.index"
|
|
46
|
+
:value="word.index"
|
|
47
|
+
@click="onClick(word)"
|
|
48
|
+
>
|
|
49
|
+
{{ word.position }}.
|
|
50
|
+
{{ word.clue }}
|
|
51
|
+
</v-list-item>
|
|
52
|
+
</v-list-item-group>
|
|
53
|
+
</v-list>
|
|
54
|
+
</v-col>
|
|
55
|
+
</v-row>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<script>
|
|
59
|
+
export default {
|
|
60
|
+
name: 'CrosswordClues',
|
|
61
|
+
components: {},
|
|
62
|
+
props: {
|
|
63
|
+
down: {
|
|
64
|
+
type: Array,
|
|
65
|
+
required: false,
|
|
66
|
+
default: () => [],
|
|
67
|
+
},
|
|
68
|
+
across: {
|
|
69
|
+
type: Array,
|
|
70
|
+
required: false,
|
|
71
|
+
default: () => [],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
data() {
|
|
75
|
+
return {
|
|
76
|
+
selectedItem: null,
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
methods: {
|
|
80
|
+
onClick(word) {
|
|
81
|
+
this.$emit('click', word)
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<style scoped>
|
|
88
|
+
.crossword-clues__list-item {
|
|
89
|
+
padding: 4px;
|
|
90
|
+
}
|
|
91
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class CrosswordCell {
|
|
2
|
+
public char!: string
|
|
3
|
+
public across!: any
|
|
4
|
+
public down!: any
|
|
5
|
+
constructor(letter: string) {
|
|
6
|
+
this.char = letter // the actual letter for the cell on the crossword
|
|
7
|
+
// If a word hits this cell going in the "across" direction, this will be a CrosswordCellNode
|
|
8
|
+
this.across = null
|
|
9
|
+
// If a word hits this cell going in the "down" direction, this will be a CrosswordCellNode
|
|
10
|
+
this.down = null
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// You can tell if the Node is the start of a word (which is needed if you want to number the cells)
|
|
15
|
+
// and what word and clue it corresponds to (using the index)
|
|
16
|
+
class CrosswordCellNode {
|
|
17
|
+
public isStartOfWord!: any
|
|
18
|
+
public index!: any
|
|
19
|
+
public position = -1
|
|
20
|
+
|
|
21
|
+
constructor(isStartOfWord: any, index: any) {
|
|
22
|
+
this.isStartOfWord = isStartOfWord
|
|
23
|
+
this.index = index // use to map this node to its word or clue
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// public functionName
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class WordElement {
|
|
30
|
+
public word!: any
|
|
31
|
+
public index!: any
|
|
32
|
+
constructor(word: any, index: any) {
|
|
33
|
+
this.word = word // the actual word
|
|
34
|
+
this.index = index // use to map this node to its word or clue
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { CrosswordCell, CrosswordCellNode, WordElement }
|