@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
|
@@ -4,107 +4,98 @@
|
|
|
4
4
|
<h2>{{ block.metadata.config.title }}</h2>
|
|
5
5
|
<p>{{ block.metadata.config.instructions }}</p>
|
|
6
6
|
</div>
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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">
|
|
19
36
|
<v-row>
|
|
20
|
-
<v-col
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
:key="
|
|
28
|
-
:
|
|
29
|
-
class="
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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>
|
|
50
71
|
</template>
|
|
51
|
-
<!-- /
|
|
72
|
+
<!-- /Cell in row -->
|
|
52
73
|
</template>
|
|
53
|
-
</
|
|
74
|
+
</v-container>
|
|
54
75
|
</v-col>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<div class="crossword-clues">
|
|
63
|
-
<dl
|
|
64
|
-
v-if="across.length > 0"
|
|
65
|
-
class="crossword-clues__list crossword-clues__list--across"
|
|
66
|
-
>
|
|
67
|
-
<dt class="crossword-clues__list-title">
|
|
68
|
-
{{ this.across }}
|
|
69
|
-
</dt>
|
|
70
|
-
<dd
|
|
71
|
-
class="crossword-clues__list-item"
|
|
72
|
-
v-for="(word, index) in acrossWords"
|
|
73
|
-
:key="`across-${word.word}-${index}`"
|
|
74
|
-
>
|
|
75
|
-
<span class="hightlighted"
|
|
76
|
-
>{{ index + 1 }}. {{ word.clue }}</span
|
|
77
|
-
>
|
|
78
|
-
</dd>
|
|
79
|
-
</dl>
|
|
80
|
-
<dl
|
|
81
|
-
v-if="down.length > 0"
|
|
82
|
-
class="crossword-clues__list crossword-clues__list--down"
|
|
83
|
-
>
|
|
84
|
-
<dt class="crossword-clues__list-title">
|
|
85
|
-
{{ this.down }}
|
|
86
|
-
</dt>
|
|
87
|
-
<dd
|
|
88
|
-
class="crossword-clues__list-item"
|
|
89
|
-
v-for="(word, index) in downWords"
|
|
90
|
-
:key="`down-${word.word}-${index}`"
|
|
91
|
-
>
|
|
92
|
-
<span class="hightlighted"
|
|
93
|
-
>{{ index + 1 }}. {{ word.clue }}</span
|
|
94
|
-
>
|
|
95
|
-
</dd>
|
|
96
|
-
</dl>
|
|
97
|
-
</div>
|
|
76
|
+
<!-- Clues -->
|
|
77
|
+
<v-col cols="12" class="crossword-clues">
|
|
78
|
+
<CrosswordClues
|
|
79
|
+
:down="downWords"
|
|
80
|
+
:across="acrossWords"
|
|
81
|
+
@click="onClickClue"
|
|
82
|
+
></CrosswordClues>
|
|
98
83
|
</v-col>
|
|
99
84
|
</v-row>
|
|
100
|
-
<v-row
|
|
101
|
-
<v-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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>
|
|
108
99
|
</v-row>
|
|
109
100
|
</div>
|
|
110
101
|
</div>
|
|
@@ -115,91 +106,16 @@
|
|
|
115
106
|
import { Crossword } from './Crossword'
|
|
116
107
|
import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
|
|
117
108
|
import _ from 'lodash'
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
PATH_TO_PNGS_OF_NUMBERS: 'numbers/',
|
|
121
|
-
|
|
122
|
-
toHtml: function (grid, show_answers) {
|
|
123
|
-
if (grid == null) return
|
|
124
|
-
var html = []
|
|
125
|
-
html.push("<table class='crossword'>")
|
|
126
|
-
var label = 1
|
|
127
|
-
for (var r = 0; r < grid.length; r++) {
|
|
128
|
-
html.push('<tr>')
|
|
129
|
-
for (var c = 0; c < grid[r].length; c++) {
|
|
130
|
-
var cell = grid[r][c]
|
|
131
|
-
var is_start_of_word = false
|
|
132
|
-
var css_class, char
|
|
133
|
-
if (cell == null) {
|
|
134
|
-
char = ' '
|
|
135
|
-
css_class = 'no-border'
|
|
136
|
-
} else {
|
|
137
|
-
char = cell['char']
|
|
138
|
-
css_class = ''
|
|
139
|
-
is_start_of_word =
|
|
140
|
-
(cell['across'] &&
|
|
141
|
-
cell['across']['is_start_of_word']) ||
|
|
142
|
-
(cell['down'] && cell['down']['is_start_of_word'])
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (is_start_of_word) {
|
|
146
|
-
var img_url =
|
|
147
|
-
CrosswordUtils.PATH_TO_PNGS_OF_NUMBERS + label + '.png'
|
|
148
|
-
html.push(
|
|
149
|
-
"<td class='" +
|
|
150
|
-
css_class +
|
|
151
|
-
"' title='" +
|
|
152
|
-
r +
|
|
153
|
-
', ' +
|
|
154
|
-
c +
|
|
155
|
-
"' style=\"background-image:url('" +
|
|
156
|
-
img_url +
|
|
157
|
-
'\')">'
|
|
158
|
-
)
|
|
159
|
-
label++
|
|
160
|
-
} else {
|
|
161
|
-
html.push(
|
|
162
|
-
"<td class='" +
|
|
163
|
-
css_class +
|
|
164
|
-
"' title='" +
|
|
165
|
-
r +
|
|
166
|
-
', ' +
|
|
167
|
-
c +
|
|
168
|
-
"'>"
|
|
169
|
-
)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (show_answers) {
|
|
173
|
-
html.push(char)
|
|
174
|
-
} else {
|
|
175
|
-
html.push(' ')
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
html.push('</tr>')
|
|
179
|
-
}
|
|
180
|
-
html.push('</table>')
|
|
181
|
-
return html.join('\n')
|
|
182
|
-
},
|
|
183
|
-
}
|
|
109
|
+
import Crypto from '~/helpers/Crypto'
|
|
110
|
+
import CrosswordClues from './CrosswordClues.vue'
|
|
184
111
|
|
|
185
112
|
export default {
|
|
186
113
|
name: 'CrossWordPuzzle',
|
|
187
114
|
extends: BaseContentBlock,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
type: String,
|
|
191
|
-
default: 'EN',
|
|
192
|
-
validator: function (value) {
|
|
193
|
-
// The value must match one of these strings
|
|
194
|
-
return ['EN', 'ES'].indexOf(value) !== -1
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
showPlayAgain: {
|
|
198
|
-
type: Boolean,
|
|
199
|
-
default: true,
|
|
200
|
-
},
|
|
201
|
-
},
|
|
115
|
+
components: { CrosswordClues },
|
|
116
|
+
props: {},
|
|
202
117
|
beforeMount() {
|
|
118
|
+
this.id = Crypto.id()
|
|
203
119
|
if (_.isEmpty(this.block)) {
|
|
204
120
|
this.block = {}
|
|
205
121
|
}
|
|
@@ -215,157 +131,244 @@ export default {
|
|
|
215
131
|
if (_.isEmpty(this.block.metadata.config.instructions)) {
|
|
216
132
|
this.block.metadata.config.instructions = ''
|
|
217
133
|
}
|
|
218
|
-
this.onSetUpData()
|
|
219
134
|
},
|
|
220
135
|
data() {
|
|
221
136
|
return {
|
|
222
|
-
|
|
137
|
+
id: '',
|
|
138
|
+
highlightIndex: -1,
|
|
223
139
|
acrossWords: [],
|
|
224
140
|
downWords: [],
|
|
141
|
+
error: '',
|
|
225
142
|
matrix: null,
|
|
226
143
|
grid: null,
|
|
227
144
|
form: null,
|
|
228
|
-
smallContainer: false,
|
|
229
|
-
dummyData: false,
|
|
230
|
-
wordsWithReflexions: [],
|
|
231
|
-
dummyWordData: [
|
|
232
|
-
{
|
|
233
|
-
word: 'Coffee',
|
|
234
|
-
description:
|
|
235
|
-
'Many people drink it in the morning with milk or cream.',
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
word: 'Tea',
|
|
239
|
-
description: "British people drink it at 5 o' clock.",
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
word: 'Peach',
|
|
243
|
-
description: 'Juicy, round fruit with a stone-like seed.',
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
word: 'Grape',
|
|
247
|
-
description: 'You make wine from this fruit.',
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
word: 'Primero',
|
|
251
|
-
description:
|
|
252
|
-
'Que ocupa el lugar número uno en una serie ordenada. Dicho de una persona o de una cosa: Que precede a las demás de su especie en orden, tiempo, lugar, situación, clase o jerarquía.',
|
|
253
|
-
},
|
|
254
|
-
{
|
|
255
|
-
word: 'Lemon',
|
|
256
|
-
description: 'You make lemonade from this fruit.',
|
|
257
|
-
},
|
|
258
|
-
],
|
|
259
145
|
}
|
|
260
146
|
},
|
|
261
147
|
computed: {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
(
|
|
148
|
+
wordMap() {
|
|
149
|
+
const map = _.cloneDeep(
|
|
150
|
+
_.get(this.block, 'metadata.config.words', [])
|
|
265
151
|
)
|
|
266
|
-
},
|
|
267
152
|
|
|
268
|
-
|
|
269
|
-
return
|
|
270
|
-
(
|
|
271
|
-
)
|
|
272
|
-
},
|
|
273
|
-
|
|
274
|
-
across: function () {
|
|
275
|
-
let acrossWord
|
|
276
|
-
switch (this.lang) {
|
|
277
|
-
case 'ES':
|
|
278
|
-
acrossWord = 'Horizontales'
|
|
279
|
-
break
|
|
280
|
-
default:
|
|
281
|
-
acrossWord = 'Across'
|
|
282
|
-
break
|
|
283
|
-
}
|
|
284
|
-
return acrossWord
|
|
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
|
+
})
|
|
285
157
|
},
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
let
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
+
})
|
|
296
176
|
}
|
|
297
|
-
|
|
177
|
+
classValue += ' ' + crosswordSize
|
|
178
|
+
return classValue
|
|
298
179
|
},
|
|
299
180
|
},
|
|
300
181
|
watch: {
|
|
301
182
|
render(newVal) {
|
|
302
183
|
if (newVal === true) {
|
|
303
|
-
|
|
184
|
+
// on render reset up grid and look for word changes
|
|
185
|
+
this.onSetUpData()
|
|
304
186
|
}
|
|
305
187
|
},
|
|
188
|
+
block: {
|
|
189
|
+
deep: true,
|
|
190
|
+
handler() {
|
|
191
|
+
// on render reset up grid and look for word changes
|
|
192
|
+
this.onSetUpData()
|
|
193
|
+
},
|
|
194
|
+
},
|
|
306
195
|
},
|
|
307
196
|
mounted() {
|
|
308
|
-
this.
|
|
309
|
-
this.$nextTick(function () {
|
|
310
|
-
window.addEventListener('resize', this.getWindowWidth)
|
|
311
|
-
this.getWindowWidth()
|
|
312
|
-
})
|
|
197
|
+
this.onSetUpData()
|
|
313
198
|
},
|
|
314
199
|
methods: {
|
|
315
|
-
|
|
316
|
-
if (
|
|
317
|
-
this.
|
|
318
|
-
this.wordsWithReflexions = this.dummyWordData
|
|
319
|
-
this.dummyData = true
|
|
200
|
+
onClickClue(word) {
|
|
201
|
+
if (this.highlightIndex !== word.index) {
|
|
202
|
+
this.highlightIndex = word.index
|
|
320
203
|
} else {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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'
|
|
333
223
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
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
|
|
337
249
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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'
|
|
345
276
|
)
|
|
346
277
|
}
|
|
347
278
|
}
|
|
348
|
-
|
|
349
|
-
|
|
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
|
+
}
|
|
350
352
|
}
|
|
353
|
+
return wordsThatDontShareLetters
|
|
351
354
|
},
|
|
352
|
-
index
|
|
353
|
-
let id =
|
|
355
|
+
index(rowIndex, cellIndex) {
|
|
356
|
+
let id = `${this.id}-cell-${rowIndex + 1}-${cellIndex + 1}`
|
|
354
357
|
return id
|
|
355
358
|
},
|
|
356
359
|
|
|
357
|
-
pattern
|
|
360
|
+
pattern(value) {
|
|
358
361
|
if (value) {
|
|
359
362
|
let pattern = `^[${value.toLowerCase()}${value.toUpperCase()}]{1}$`
|
|
360
363
|
return pattern
|
|
361
364
|
}
|
|
362
365
|
},
|
|
363
366
|
|
|
364
|
-
equalsLetters
|
|
365
|
-
if (letter1 === null) {
|
|
367
|
+
equalsLetters(letter1, letter2) {
|
|
368
|
+
if (letter1 === null || typeof letter1 === 'undefined') {
|
|
366
369
|
letter1 = ''
|
|
367
370
|
}
|
|
368
|
-
if (letter2 === null) {
|
|
371
|
+
if (letter2 === null || typeof letter2 === 'undefined') {
|
|
369
372
|
letter2 = ''
|
|
370
373
|
}
|
|
371
374
|
return (
|
|
@@ -373,10 +376,7 @@ export default {
|
|
|
373
376
|
letter1.toLowerCase() === letter2.toLowerCase()
|
|
374
377
|
)
|
|
375
378
|
},
|
|
376
|
-
|
|
377
|
-
let words = this.shuffle(this.wordsWithReflexions)
|
|
378
|
-
this.selectedWords = words
|
|
379
|
-
},
|
|
379
|
+
|
|
380
380
|
shuffle(array) {
|
|
381
381
|
let currentIndex = array.length,
|
|
382
382
|
randomIndex
|
|
@@ -396,147 +396,278 @@ export default {
|
|
|
396
396
|
|
|
397
397
|
return array
|
|
398
398
|
},
|
|
399
|
-
generateGrid
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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++
|
|
411
434
|
}
|
|
412
435
|
|
|
413
|
-
|
|
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
|
+
}
|
|
414
450
|
|
|
415
451
|
this.form = [...Array(MATRIX_LENGTH)].map(() =>
|
|
416
452
|
Array(MATRIX_LENGTH).fill('')
|
|
417
453
|
)
|
|
418
454
|
|
|
419
|
-
// fill grid in
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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)
|
|
424
464
|
}
|
|
425
|
-
// add columns
|
|
426
|
-
this.grid.forEach((row) => {
|
|
427
|
-
while (row.length < MATRIX_LENGTH) {
|
|
428
|
-
row.push(null)
|
|
429
|
-
}
|
|
430
|
-
})
|
|
431
|
-
|
|
432
|
-
let legend = cw.getLegend(this.grid)
|
|
433
|
-
this.acrossWords = legend.across
|
|
434
|
-
this.downWords = legend.down
|
|
435
465
|
}
|
|
436
|
-
},
|
|
437
466
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
|
441
472
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
this.$refs['crossword-container'].parentElement.parentElement &&
|
|
445
|
-
this.$refs['crossword-container'].parentElement.parentElement
|
|
446
|
-
.clientWidth < 1200
|
|
447
|
-
) {
|
|
448
|
-
this.smallContainer = true
|
|
473
|
+
if (!_.isEmpty(this.grid)) {
|
|
474
|
+
this.grid.splice(0)
|
|
449
475
|
}
|
|
476
|
+
|
|
477
|
+
this.grid = grid
|
|
450
478
|
},
|
|
451
479
|
},
|
|
452
|
-
beforeDestroy() {
|
|
453
|
-
window.removeEventListener('resize', this.getWindowWidth)
|
|
454
|
-
},
|
|
455
480
|
}
|
|
456
481
|
</script>
|
|
457
482
|
|
|
458
|
-
<style>
|
|
483
|
+
<style lang="scss" scoped>
|
|
459
484
|
.crossword-board__item--valid {
|
|
460
|
-
background:
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
.hightlighted {
|
|
464
|
-
background: rgba(255, 255, 255, 0.95);
|
|
465
|
-
padding: 3px 5px;
|
|
466
|
-
margin: -3px -5px;
|
|
467
|
-
line-height: 1.7;
|
|
468
|
-
display: inline;
|
|
485
|
+
background: var(--v-success-base);
|
|
486
|
+
color: #fff;
|
|
469
487
|
}
|
|
470
488
|
|
|
471
489
|
.crossword-board-container {
|
|
472
490
|
position: relative;
|
|
473
|
-
|
|
491
|
+
padding-top: 5px;
|
|
492
|
+
overflow: auto;
|
|
474
493
|
}
|
|
475
494
|
|
|
476
495
|
.crossword-board {
|
|
477
496
|
position: relative;
|
|
478
|
-
z-index: 1;
|
|
479
497
|
background: transparent;
|
|
480
|
-
border: 1px solid #000;
|
|
481
|
-
width: 650px;
|
|
482
|
-
height: 650px;
|
|
483
498
|
display: grid;
|
|
484
|
-
grid-template: repeat(13, 7.69231%) / repeat(13, 7.69231%);
|
|
485
499
|
list-style-type: none;
|
|
486
500
|
padding: 0;
|
|
487
|
-
|
|
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;
|
|
488
525
|
}
|
|
489
|
-
|
|
490
526
|
.crossword-board__item {
|
|
491
|
-
border: 1px solid #
|
|
527
|
+
border: 1px solid #333;
|
|
492
528
|
position: relative;
|
|
493
|
-
z-index: 100;
|
|
494
529
|
text-align: center;
|
|
495
530
|
font-size: 20px;
|
|
496
531
|
font-weight: bold;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.crossword-board__item > input {
|
|
535
|
+
width: 100%;
|
|
536
|
+
height: 100%;
|
|
537
|
+
text-align: center;
|
|
497
538
|
text-transform: uppercase;
|
|
498
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
|
+
}
|
|
499
548
|
|
|
500
549
|
.crossword-board__item:not(.crossword-board__item--valid) {
|
|
501
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
|
+
}
|
|
502
571
|
}
|
|
503
572
|
|
|
504
573
|
.crossword-board__item:active:not(.crossword-board__item--valid),
|
|
505
574
|
.crossword-board__item:focus:not(.crossword-board__item--valid) {
|
|
506
|
-
background:
|
|
507
|
-
border: 1px solid #
|
|
508
|
-
outline: 1px solid #000;
|
|
575
|
+
background: var(--v-error-base);
|
|
576
|
+
border: 1px solid #333;
|
|
509
577
|
}
|
|
510
578
|
|
|
511
579
|
.crossword-board__item--blank {
|
|
512
|
-
background-color: #
|
|
513
|
-
border: 1px solid #
|
|
514
|
-
outline: 1px solid #000;
|
|
580
|
+
background-color: #111 !important;
|
|
581
|
+
border: 1px solid #333;
|
|
515
582
|
}
|
|
516
583
|
|
|
517
584
|
.crossword-clues {
|
|
518
|
-
|
|
519
|
-
|
|
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;
|
|
520
594
|
color: #000;
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
width: 650px; */
|
|
595
|
+
user-select: none;
|
|
596
|
+
pointer-events: none;
|
|
524
597
|
}
|
|
525
598
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
+
}
|
|
531
607
|
}
|
|
532
|
-
|
|
533
|
-
.crossword-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
+
}
|
|
536
616
|
}
|
|
537
|
-
|
|
538
|
-
.crossword-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
+
}
|
|
541
672
|
}
|
|
542
673
|
</style>
|