@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
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-container>
|
|
3
|
+
<div>
|
|
4
|
+
<h2>{{ block.metadata.config.title }}</h2>
|
|
5
|
+
<p>{{ block.metadata.config.instructions }}</p>
|
|
6
|
+
</div>
|
|
7
|
+
<v-alert v-if="wordMap.length === 0" type="info" class="text-center">
|
|
8
|
+
<p>
|
|
9
|
+
{{
|
|
10
|
+
$t(
|
|
11
|
+
'plugin.games.components.content.blocks.crossword.initial_setup'
|
|
12
|
+
)
|
|
13
|
+
}}
|
|
14
|
+
</p>
|
|
15
|
+
</v-alert>
|
|
16
|
+
<v-alert
|
|
17
|
+
v-if="wordMap.length > 0 && error"
|
|
18
|
+
type="error"
|
|
19
|
+
class="text-center"
|
|
20
|
+
>
|
|
21
|
+
<p>
|
|
22
|
+
{{
|
|
23
|
+
$t(
|
|
24
|
+
'plugin.games.components.content.blocks.crossword.validation_error'
|
|
25
|
+
)
|
|
26
|
+
}}
|
|
27
|
+
</p>
|
|
28
|
+
<p>{{ error }}</p>
|
|
29
|
+
</v-alert>
|
|
30
|
+
<div
|
|
31
|
+
v-if="!error"
|
|
32
|
+
class="crossword-container"
|
|
33
|
+
ref="crossword-container"
|
|
34
|
+
>
|
|
35
|
+
<div v-if="grid">
|
|
36
|
+
<v-row>
|
|
37
|
+
<v-col cols="12" class="crossword-board-container">
|
|
38
|
+
<v-container :class="containerClass">
|
|
39
|
+
<!-- Rows in grid -->
|
|
40
|
+
<template v-for="(row, rowIndex) in grid">
|
|
41
|
+
<!-- Cell in row -->
|
|
42
|
+
<template v-for="(item, cellIndex) in row">
|
|
43
|
+
<div
|
|
44
|
+
:key="`${id}-cell-parent-${rowIndex}-${cellIndex}`"
|
|
45
|
+
:index="`${id}-cell-parent-${rowIndex}-${cellIndex}`"
|
|
46
|
+
:class="
|
|
47
|
+
cellClass(
|
|
48
|
+
item,
|
|
49
|
+
form[rowIndex][cellIndex],
|
|
50
|
+
isHighlighted(item)
|
|
51
|
+
)
|
|
52
|
+
"
|
|
53
|
+
>
|
|
54
|
+
<div
|
|
55
|
+
v-if="getPositionNumber(item)"
|
|
56
|
+
class="crossword-board__item--number"
|
|
57
|
+
>
|
|
58
|
+
{{ getPositionNumber(item) }}
|
|
59
|
+
</div>
|
|
60
|
+
<input
|
|
61
|
+
v-if="item"
|
|
62
|
+
v-model="form[rowIndex][cellIndex]"
|
|
63
|
+
:id="index(rowIndex, cellIndex)"
|
|
64
|
+
type="text"
|
|
65
|
+
minlength="1"
|
|
66
|
+
maxlength="1"
|
|
67
|
+
:pattern="pattern(item.char)"
|
|
68
|
+
required="required"
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
</template>
|
|
72
|
+
<!-- /Cell in row -->
|
|
73
|
+
</template>
|
|
74
|
+
</v-container>
|
|
75
|
+
</v-col>
|
|
76
|
+
<!-- Clues -->
|
|
77
|
+
<v-col cols="12" class="crossword-clues">
|
|
78
|
+
<CrosswordClues
|
|
79
|
+
:down="downWords"
|
|
80
|
+
:across="acrossWords"
|
|
81
|
+
@click="onClickClue"
|
|
82
|
+
></CrosswordClues>
|
|
83
|
+
</v-col>
|
|
84
|
+
</v-row>
|
|
85
|
+
<v-row class="d-flex justify-center">
|
|
86
|
+
<v-col cols="12" class="d-flex justify-center">
|
|
87
|
+
<v-btn
|
|
88
|
+
color="primary"
|
|
89
|
+
class="ma-3"
|
|
90
|
+
@click="onSetUpData"
|
|
91
|
+
>
|
|
92
|
+
{{
|
|
93
|
+
$t(
|
|
94
|
+
'plugin.games.components.content.blocks.crossword.play_again'
|
|
95
|
+
)
|
|
96
|
+
}}
|
|
97
|
+
</v-btn>
|
|
98
|
+
</v-col>
|
|
99
|
+
</v-row>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</v-container>
|
|
103
|
+
</template>
|
|
104
|
+
|
|
105
|
+
<script>
|
|
106
|
+
import { Crossword } from './Crossword'
|
|
107
|
+
import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
|
|
108
|
+
import _ from 'lodash'
|
|
109
|
+
import Crypto from '~/helpers/Crypto'
|
|
110
|
+
import CrosswordClues from './CrosswordClues.vue'
|
|
111
|
+
|
|
112
|
+
export default {
|
|
113
|
+
name: 'CrossWordPuzzle',
|
|
114
|
+
extends: BaseContentBlock,
|
|
115
|
+
components: { CrosswordClues },
|
|
116
|
+
props: {},
|
|
117
|
+
beforeMount() {
|
|
118
|
+
this.id = Crypto.id()
|
|
119
|
+
if (_.isEmpty(this.block)) {
|
|
120
|
+
this.block = {}
|
|
121
|
+
}
|
|
122
|
+
if (_.isEmpty(this.block.metadata)) {
|
|
123
|
+
this.block.metadata = {}
|
|
124
|
+
}
|
|
125
|
+
if (_.isEmpty(this.block.metadata.config)) {
|
|
126
|
+
this.block.metadata.config = {}
|
|
127
|
+
}
|
|
128
|
+
if (_.isEmpty(this.block.metadata.config.title)) {
|
|
129
|
+
this.block.metadata.config.title = ''
|
|
130
|
+
}
|
|
131
|
+
if (_.isEmpty(this.block.metadata.config.instructions)) {
|
|
132
|
+
this.block.metadata.config.instructions = ''
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
data() {
|
|
136
|
+
return {
|
|
137
|
+
id: '',
|
|
138
|
+
highlightIndex: -1,
|
|
139
|
+
acrossWords: [],
|
|
140
|
+
downWords: [],
|
|
141
|
+
error: '',
|
|
142
|
+
matrix: null,
|
|
143
|
+
grid: null,
|
|
144
|
+
form: null,
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
computed: {
|
|
148
|
+
wordMap() {
|
|
149
|
+
const map = _.cloneDeep(
|
|
150
|
+
_.get(this.block, 'metadata.config.words', [])
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
// Toss out crossword items that are missing the word or clue
|
|
154
|
+
return map.filter((v) => {
|
|
155
|
+
return !_.isEmpty(v.word) && !_.isEmpty(v.clue)
|
|
156
|
+
})
|
|
157
|
+
},
|
|
158
|
+
containerClass() {
|
|
159
|
+
let classValue = ''
|
|
160
|
+
let crosswordSize = 'crossword--sm'
|
|
161
|
+
|
|
162
|
+
// Apply the column size
|
|
163
|
+
classValue +=
|
|
164
|
+
'crossword-board crossword-col-' +
|
|
165
|
+
_.get(this.block, 'metadata.columns', 12) / 3
|
|
166
|
+
|
|
167
|
+
// If we're still showing sample data, figure out the size on that
|
|
168
|
+
// We do this instead of hardcoding (since it changes based on locale)
|
|
169
|
+
if (!_.isEmpty(this.wordMap)) {
|
|
170
|
+
// if render is true then see what grid size should be based on longest word
|
|
171
|
+
this.wordMap.forEach((element) => {
|
|
172
|
+
if (element.word.length > 13) {
|
|
173
|
+
crosswordSize = 'crossword--lg'
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
classValue += ' ' + crosswordSize
|
|
178
|
+
return classValue
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
watch: {
|
|
182
|
+
render(newVal) {
|
|
183
|
+
if (newVal === true) {
|
|
184
|
+
// on render reset up grid and look for word changes
|
|
185
|
+
this.onSetUpData()
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
block: {
|
|
189
|
+
deep: true,
|
|
190
|
+
handler() {
|
|
191
|
+
// on render reset up grid and look for word changes
|
|
192
|
+
this.onSetUpData()
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
mounted() {
|
|
197
|
+
this.onSetUpData()
|
|
198
|
+
},
|
|
199
|
+
methods: {
|
|
200
|
+
onClickClue(word) {
|
|
201
|
+
if (this.highlightIndex !== word.index) {
|
|
202
|
+
this.highlightIndex = word.index
|
|
203
|
+
} else {
|
|
204
|
+
this.highlightIndex = -1
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
isHighlighted(cell) {
|
|
208
|
+
const acrossIndex = _.get(cell, 'across.index', false)
|
|
209
|
+
const downIndex = _.get(cell, 'down.index', false)
|
|
210
|
+
|
|
211
|
+
if (acrossIndex !== false) {
|
|
212
|
+
return this.highlightIndex === acrossIndex
|
|
213
|
+
} else if (downIndex !== false) {
|
|
214
|
+
return this.highlightIndex === downIndex
|
|
215
|
+
} else {
|
|
216
|
+
return false
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
cellClass(cell, formValue, isHighlighted) {
|
|
220
|
+
let cellClass = 'crossword-board__item crossword-board__item--blank'
|
|
221
|
+
if (cell) {
|
|
222
|
+
cellClass = 'crossword-board__item'
|
|
223
|
+
|
|
224
|
+
if (this.equalsLetters(formValue, cell.char)) {
|
|
225
|
+
cellClass += ' crossword-board__item--valid'
|
|
226
|
+
}
|
|
227
|
+
if (isHighlighted) {
|
|
228
|
+
cellClass += ' crossword-board__item--highlighted'
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return cellClass
|
|
233
|
+
},
|
|
234
|
+
getPositionNumber(cell) {
|
|
235
|
+
const isAcross = _.get(cell, 'across.isStartOfWord', false)
|
|
236
|
+
const isDown = _.get(cell, 'down.isStartOfWord', false)
|
|
237
|
+
|
|
238
|
+
if (isAcross) {
|
|
239
|
+
return cell.across.position
|
|
240
|
+
} else if (isDown) {
|
|
241
|
+
return cell.down.position
|
|
242
|
+
} else {
|
|
243
|
+
return 0
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
onSetUpData(generate = true) {
|
|
247
|
+
this.error = ''
|
|
248
|
+
this.highlightIndex = -1
|
|
249
|
+
|
|
250
|
+
if (_.isEmpty(this.wordMap)) {
|
|
251
|
+
this.error = this.$t(
|
|
252
|
+
'plugin.games.components.content.blocks.crossword.error.min_words'
|
|
253
|
+
)
|
|
254
|
+
} else {
|
|
255
|
+
// checks if there are any words that don't share letters with any others
|
|
256
|
+
const letterCheck = this.onCheckWordsShareLetters(this.wordMap)
|
|
257
|
+
|
|
258
|
+
if (this.wordMap.length < 2) {
|
|
259
|
+
this.error = this.$t(
|
|
260
|
+
'plugin.games.components.content.blocks.crossword.error.min_words'
|
|
261
|
+
)
|
|
262
|
+
} else if (letterCheck.length > 0) {
|
|
263
|
+
// if a word doesn't share letters load with dummy data and alert the user
|
|
264
|
+
let words = letterCheck.toString()
|
|
265
|
+
this.error = this.$t(
|
|
266
|
+
'plugin.games.components.content.blocks.crossword.error.words_no_shared_letters',
|
|
267
|
+
[words]
|
|
268
|
+
)
|
|
269
|
+
} else if (!this.validateWordMaxLength(this.wordMap)) {
|
|
270
|
+
this.error = this.$t(
|
|
271
|
+
'plugin.games.components.content.blocks.crossword.error.word_length_max_limit'
|
|
272
|
+
)
|
|
273
|
+
} else if (!this.validateWordMinLength(this.wordMap)) {
|
|
274
|
+
this.error = this.$t(
|
|
275
|
+
'plugin.games.components.content.blocks.crossword.error.word_length_min_limit'
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// this is true if render is toggled back to true
|
|
281
|
+
if (generate && this.error === '') {
|
|
282
|
+
this.generateGrid(this.wordMap)
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
validateWordMaxLength(wordMap) {
|
|
286
|
+
let isValid = true
|
|
287
|
+
// inputs are not allowed over 20
|
|
288
|
+
// this was added to check if imports have more than 20 characters
|
|
289
|
+
wordMap.forEach((element) => {
|
|
290
|
+
if (_.isEmpty(element) || _.isEmpty(element.word)) {
|
|
291
|
+
isValid = false
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
if (element.word.length > 20) {
|
|
295
|
+
isValid = false
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
return isValid
|
|
299
|
+
},
|
|
300
|
+
validateWordMinLength(wordMap) {
|
|
301
|
+
let isValid = true
|
|
302
|
+
// inputs are not allowed over 20
|
|
303
|
+
// this was added to check if imports have more than 20 characters
|
|
304
|
+
wordMap.forEach((element) => {
|
|
305
|
+
if (_.isEmpty(element) || _.isEmpty(element.word)) {
|
|
306
|
+
isValid = false
|
|
307
|
+
return
|
|
308
|
+
}
|
|
309
|
+
if (element.word.length < 2) {
|
|
310
|
+
isValid = false
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
return isValid
|
|
314
|
+
},
|
|
315
|
+
onCheckWordsShareLetters(wordMap) {
|
|
316
|
+
let arrayOfWordsLetters = []
|
|
317
|
+
let wordsThatDontShareLetters = []
|
|
318
|
+
|
|
319
|
+
// splits words into an array of letters that is in an array
|
|
320
|
+
wordMap.forEach((element) => {
|
|
321
|
+
if (_.isEmpty(element) || _.isEmpty(element.word)) {
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
element.word = element.word.toUpperCase()
|
|
325
|
+
const letterArray = element.word.split('')
|
|
326
|
+
arrayOfWordsLetters.push(letterArray)
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
for (let i = 0; i < arrayOfWordsLetters.length; i++) {
|
|
330
|
+
let shareLetters = false
|
|
331
|
+
let theyShareLetters = false
|
|
332
|
+
// loop over individual letters to check if any words share the letters
|
|
333
|
+
arrayOfWordsLetters[i].forEach((letter) => {
|
|
334
|
+
wordMap.forEach((element) => {
|
|
335
|
+
const elementIndex = wordMap.indexOf(element)
|
|
336
|
+
// prevents from checking the letters against the word they came from
|
|
337
|
+
if (elementIndex !== i) {
|
|
338
|
+
// checks if the word from the block being looped over contains the letter
|
|
339
|
+
theyShareLetters = element.word.includes(letter)
|
|
340
|
+
// if it does contain the letter shareLetters is true and will not push
|
|
341
|
+
//the word into the array for words that don't share any letters
|
|
342
|
+
if (theyShareLetters) {
|
|
343
|
+
shareLetters = true
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
if (!shareLetters) {
|
|
349
|
+
// this holds all words that don't share any letters with other words
|
|
350
|
+
wordsThatDontShareLetters.push(wordMap[i].word)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return wordsThatDontShareLetters
|
|
354
|
+
},
|
|
355
|
+
index(rowIndex, cellIndex) {
|
|
356
|
+
let id = `${this.id}-cell-${rowIndex + 1}-${cellIndex + 1}`
|
|
357
|
+
return id
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
pattern(value) {
|
|
361
|
+
if (value) {
|
|
362
|
+
let pattern = `^[${value.toLowerCase()}${value.toUpperCase()}]{1}$`
|
|
363
|
+
return pattern
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
equalsLetters(letter1, letter2) {
|
|
368
|
+
if (letter1 === null || typeof letter1 === 'undefined') {
|
|
369
|
+
letter1 = ''
|
|
370
|
+
}
|
|
371
|
+
if (letter2 === null || typeof letter2 === 'undefined') {
|
|
372
|
+
letter2 = ''
|
|
373
|
+
}
|
|
374
|
+
return (
|
|
375
|
+
letter1.toUpperCase() === letter2.toUpperCase() ||
|
|
376
|
+
letter1.toLowerCase() === letter2.toLowerCase()
|
|
377
|
+
)
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
shuffle(array) {
|
|
381
|
+
let currentIndex = array.length,
|
|
382
|
+
randomIndex
|
|
383
|
+
|
|
384
|
+
// While there remain elements to shuffle.
|
|
385
|
+
while (currentIndex !== 0) {
|
|
386
|
+
// Pick a remaining element.
|
|
387
|
+
randomIndex = Math.floor(Math.random() * currentIndex)
|
|
388
|
+
currentIndex--
|
|
389
|
+
|
|
390
|
+
// And swap it with the current element.
|
|
391
|
+
;[array[currentIndex], array[randomIndex]] = [
|
|
392
|
+
array[randomIndex],
|
|
393
|
+
array[currentIndex],
|
|
394
|
+
]
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return array
|
|
398
|
+
},
|
|
399
|
+
generateGrid(wordMap) {
|
|
400
|
+
let MATRIX_LENGTH = 13
|
|
401
|
+
wordMap.forEach((e) => {
|
|
402
|
+
if (e.word.length > 13) {
|
|
403
|
+
MATRIX_LENGTH = 20
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
let grid = null
|
|
408
|
+
let cw = null
|
|
409
|
+
let attempts = 0
|
|
410
|
+
let correctSize = false
|
|
411
|
+
|
|
412
|
+
//while (!grid || grid.length > MATRIX_LENGTH) {
|
|
413
|
+
// // Create crossword object with the words and clues
|
|
414
|
+
|
|
415
|
+
// create the crossword grid (try to make it have a 1:1 width to height ratio in 10 tries)
|
|
416
|
+
// Sometimes the crossword will generate a 21x21 matrix even though we defined a 20x20.
|
|
417
|
+
while (attempts < 10 && !correctSize) {
|
|
418
|
+
// Re-randomize the words
|
|
419
|
+
const randomized = this.shuffle(wordMap)
|
|
420
|
+
const words = randomized.map((w) => w.word)
|
|
421
|
+
const clues = randomized.map((w) => w.clue)
|
|
422
|
+
|
|
423
|
+
cw = new Crossword(words, clues)
|
|
424
|
+
|
|
425
|
+
// We set 10 tries so we don't try calculating the grid forever
|
|
426
|
+
grid = cw.getSquareGrid(10)
|
|
427
|
+
|
|
428
|
+
// Verify the size of the grid
|
|
429
|
+
correctSize =
|
|
430
|
+
grid.length <= MATRIX_LENGTH &&
|
|
431
|
+
grid[0].length <= MATRIX_LENGTH
|
|
432
|
+
|
|
433
|
+
attempts++
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (grid.length > MATRIX_LENGTH) {
|
|
437
|
+
this.error = this.$t(
|
|
438
|
+
'plugin.games.components.content.blocks.crossword.error.could_not_generate'
|
|
439
|
+
)
|
|
440
|
+
return false
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (_.isEmpty(grid)) {
|
|
444
|
+
this.error = this.$t(
|
|
445
|
+
'plugin.games.components.content.blocks.crossword.error.unknown'
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
return false
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
this.form = [...Array(MATRIX_LENGTH)].map(() =>
|
|
452
|
+
Array(MATRIX_LENGTH).fill('')
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
// fill empty grid spots in
|
|
456
|
+
// add rows first
|
|
457
|
+
while (grid.length < MATRIX_LENGTH) {
|
|
458
|
+
grid.push([])
|
|
459
|
+
}
|
|
460
|
+
// add columns
|
|
461
|
+
for (let row = 0; row < grid.length; row++) {
|
|
462
|
+
while (grid[row].length < MATRIX_LENGTH) {
|
|
463
|
+
grid[row].push(null)
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Generate the legend now that everything's filled
|
|
468
|
+
const legend = cw.getLegend(grid)
|
|
469
|
+
|
|
470
|
+
this.acrossWords = legend.across
|
|
471
|
+
this.downWords = legend.down
|
|
472
|
+
|
|
473
|
+
if (!_.isEmpty(this.grid)) {
|
|
474
|
+
this.grid.splice(0)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
this.grid = grid
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
}
|
|
481
|
+
</script>
|
|
482
|
+
|
|
483
|
+
<style lang="scss" scoped>
|
|
484
|
+
.crossword-board__item--valid {
|
|
485
|
+
background: var(--v-success-base);
|
|
486
|
+
color: #fff;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.crossword-board-container {
|
|
490
|
+
position: relative;
|
|
491
|
+
padding-top: 5px;
|
|
492
|
+
overflow: auto;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.crossword-board {
|
|
496
|
+
position: relative;
|
|
497
|
+
background: transparent;
|
|
498
|
+
display: grid;
|
|
499
|
+
list-style-type: none;
|
|
500
|
+
padding: 0;
|
|
501
|
+
box-sizing: content-box !important;
|
|
502
|
+
border: 1px solid #000;
|
|
503
|
+
}
|
|
504
|
+
.crossword-board.crossword--sm {
|
|
505
|
+
grid-template: repeat(13, 7.69231%) / repeat(13, 7.69231%);
|
|
506
|
+
}
|
|
507
|
+
.crossword-board.crossword--lg {
|
|
508
|
+
grid-template: repeat(20, 5%) / repeat(20, 5%);
|
|
509
|
+
}
|
|
510
|
+
.crossword-col-4 {
|
|
511
|
+
width: 800px;
|
|
512
|
+
height: 800px;
|
|
513
|
+
}
|
|
514
|
+
.crossword-col-3 {
|
|
515
|
+
width: 550px;
|
|
516
|
+
height: 550px;
|
|
517
|
+
}
|
|
518
|
+
.crossword-col-2 {
|
|
519
|
+
width: 450px;
|
|
520
|
+
height: 450px;
|
|
521
|
+
}
|
|
522
|
+
.crossword-col-1 {
|
|
523
|
+
width: 220px;
|
|
524
|
+
height: 220px;
|
|
525
|
+
}
|
|
526
|
+
.crossword-board__item {
|
|
527
|
+
border: 1px solid #333;
|
|
528
|
+
position: relative;
|
|
529
|
+
text-align: center;
|
|
530
|
+
font-size: 20px;
|
|
531
|
+
font-weight: bold;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.crossword-board__item > input {
|
|
535
|
+
width: 100%;
|
|
536
|
+
height: 100%;
|
|
537
|
+
text-align: center;
|
|
538
|
+
text-transform: uppercase;
|
|
539
|
+
}
|
|
540
|
+
.crossword-board.crossword--sm .crossword-board__item--number {
|
|
541
|
+
font-size: 75%;
|
|
542
|
+
line-height: 100%;
|
|
543
|
+
}
|
|
544
|
+
.crossword-board.crossword--lg .crossword-board__item--number {
|
|
545
|
+
font-size: 50%;
|
|
546
|
+
line-height: 100%;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.crossword-board__item:not(.crossword-board__item--valid) {
|
|
550
|
+
background-color: #fff;
|
|
551
|
+
|
|
552
|
+
&.crossword-board__item--highlighted {
|
|
553
|
+
animation: animated-highlight 2s linear infinite;
|
|
554
|
+
@keyframes animated-highlight {
|
|
555
|
+
0% {
|
|
556
|
+
filter: brightness(120%);
|
|
557
|
+
background-color: var(--v-secondary-lighten1);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
50% {
|
|
561
|
+
filter: brightness(100%);
|
|
562
|
+
background-color: var(--v-secondary-lighten1);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
100% {
|
|
566
|
+
filter: brightness(120%);
|
|
567
|
+
background-color: var(--v-secondary-lighten1);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.crossword-board__item:active:not(.crossword-board__item--valid),
|
|
574
|
+
.crossword-board__item:focus:not(.crossword-board__item--valid) {
|
|
575
|
+
background: var(--v-error-base);
|
|
576
|
+
border: 1px solid #333;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.crossword-board__item--blank {
|
|
580
|
+
background-color: #111 !important;
|
|
581
|
+
border: 1px solid #333;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.crossword-clues {
|
|
585
|
+
background: var(--v-content-background-base);
|
|
586
|
+
position: sticky;
|
|
587
|
+
bottom: 100px;
|
|
588
|
+
border-radius: 5px;
|
|
589
|
+
}
|
|
590
|
+
.crossword-board__item--number {
|
|
591
|
+
position: relative;
|
|
592
|
+
height: 0;
|
|
593
|
+
width: 0;
|
|
594
|
+
color: #000;
|
|
595
|
+
user-select: none;
|
|
596
|
+
pointer-events: none;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
@media (max-width: 1570px) {
|
|
600
|
+
.crossword-col-4 {
|
|
601
|
+
width: 500px;
|
|
602
|
+
height: 500px;
|
|
603
|
+
}
|
|
604
|
+
.crossword-board-container {
|
|
605
|
+
box-sizing: content-box !important;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
@media (max-width: 1480px) {
|
|
609
|
+
.crossword-col-4 {
|
|
610
|
+
width: 400px;
|
|
611
|
+
height: 400px;
|
|
612
|
+
}
|
|
613
|
+
.crossword-board-container {
|
|
614
|
+
box-sizing: content-box !important;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
@media (max-width: 1360px) {
|
|
618
|
+
.crossword-col-4 {
|
|
619
|
+
width: 350px;
|
|
620
|
+
height: 350px;
|
|
621
|
+
}
|
|
622
|
+
.crossword-board-container {
|
|
623
|
+
box-sizing: content-box !important;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
@media (max-width: 1300px) {
|
|
627
|
+
.crossword-col-4 {
|
|
628
|
+
width: 300px;
|
|
629
|
+
height: 300px;
|
|
630
|
+
}
|
|
631
|
+
.crossword-board-container {
|
|
632
|
+
box-sizing: content-box !important;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
@media (max-width: 1263px) {
|
|
636
|
+
.crossword-col-4 {
|
|
637
|
+
width: 550px;
|
|
638
|
+
height: 550px;
|
|
639
|
+
}
|
|
640
|
+
.crossword-board-container {
|
|
641
|
+
box-sizing: content-box !important;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
@media (max-width: 823px) {
|
|
645
|
+
.crossword-col-4 {
|
|
646
|
+
width: 400px;
|
|
647
|
+
height: 400px;
|
|
648
|
+
}
|
|
649
|
+
.crossword-board-container {
|
|
650
|
+
box-sizing: content-box !important;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
@media (max-width: 631px) {
|
|
654
|
+
.crossword-col-4 {
|
|
655
|
+
width: 300px;
|
|
656
|
+
height: 300px;
|
|
657
|
+
}
|
|
658
|
+
.crossword-board-container {
|
|
659
|
+
box-sizing: content-box !important;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
@media (max-width: 565px) {
|
|
663
|
+
.crossword-col-4 {
|
|
664
|
+
width: 300px;
|
|
665
|
+
height: 300px;
|
|
666
|
+
}
|
|
667
|
+
.crossword-board-container {
|
|
668
|
+
box-sizing: content-box !important;
|
|
669
|
+
padding-top: 12px;
|
|
670
|
+
padding-left: 12px;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
</style>
|