@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.
Files changed (29) hide show
  1. package/components/content/DatableEditor.vue +0 -3
  2. package/components/content/blocks/crosswordPuzzle/Crossword.ts +231 -153
  3. package/components/content/blocks/crosswordPuzzle/CrosswordClues.vue +91 -0
  4. package/components/content/blocks/crosswordPuzzle/CrosswordElements.ts +8 -6
  5. package/components/content/blocks/crosswordPuzzle/CrosswordPuzzle.vue +502 -371
  6. package/components/content/blocks/matchingGame/MatchingGame.vue +12 -1
  7. package/components/content/blocks/multipleChoice/MultipleChoice.vue +187 -127
  8. package/components/content/blocks/multipleChoice/QuestionDialog.vue +37 -13
  9. package/components/content/blocks/sevenStrikes/SevenStikes.vue +368 -0
  10. package/components/content/blocks/sevenStrikes/keyboard.vue +71 -0
  11. package/components/content/blocks/wordJumble/Jumble.vue +2 -10
  12. package/components/settings/CrosswordPuzzleSettingsManager.vue +12 -15
  13. package/components/settings/MatchingGameManager.vue +1 -1
  14. package/components/settings/MultipleChoiceSettingsManager.vue +21 -7
  15. package/components/settings/SevenStrikesSettingsManager.vue +288 -0
  16. package/i18n/en-US/components/content/blocks/crossword.ts +18 -3
  17. package/i18n/en-US/components/content/blocks/index.ts +2 -0
  18. package/i18n/en-US/components/content/blocks/multiple_choice.ts +2 -4
  19. package/i18n/en-US/components/content/blocks/seven_strikes.ts +6 -0
  20. package/i18n/en-US/components/settings/crossword.ts +2 -1
  21. package/i18n/en-US/components/settings/index.ts +2 -0
  22. package/i18n/en-US/components/settings/multiple_choice.ts +1 -1
  23. package/i18n/en-US/components/settings/seven_strikes.ts +8 -0
  24. package/i18n/en-US/shared/content_blocks.ts +1 -0
  25. package/i18n/en-US/shared/settings.ts +1 -0
  26. package/package.json +2 -1
  27. package/plugin.js +21 -0
  28. package/test/blocks/sevenStrikes/sevenStrikes.spec.js +24 -0
  29. 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
- <div class="d-flex justify-center" v-if="dummyData">
8
- {{
9
- $t(
10
- 'plugin.games.components.content.blocks.crossword.placeholder'
11
- )
12
- }}
13
- </div>
14
- <!-- I've used this template as guide:
15
- https://codepen.io/adrianroworth/pen/OpeyZq
16
- 54 3544 570127 -->
17
- <div class="crossword-container" ref="crossword-container">
18
- <div v-if="grid" class="crossword-board-container">
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 :class="[smallContainer ? 'col-12' : 'col-7']">
21
- <div class="crossword-board">
22
- <template v-for="(row, index1) in grid">
23
- <!-- ROW -->
24
- <template v-for="(item, index2) in row">
25
- <input
26
- v-if="item"
27
- :key="`row-${index1}-${index2}`"
28
- :id="index(index1, index2)"
29
- class="crossword-board__item"
30
- :class="{
31
- 'crossword-board__item--valid':
32
- equalsLetters(
33
- form[index1][index2],
34
- item.char
35
- ),
36
- }"
37
- type="text"
38
- minlength="1"
39
- maxlength="1"
40
- :pattern="pattern(item.char)"
41
- required="required"
42
- v-model="form[index1][index2]"
43
- />
44
- <span
45
- v-else
46
- :key="`row-${index1}-${index2}`"
47
- :id="index(index1, index2)"
48
- class="crossword-board__item--blank"
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
- <!-- /ROW -->
72
+ <!-- /Cell in row -->
52
73
  </template>
53
- </div>
74
+ </v-container>
54
75
  </v-col>
55
- <v-col
56
- :class="[
57
- smallContainer
58
- ? ['col-12', 'px-5', 'my-5']
59
- : 'col-4',
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 v-if="showPlayAgain" class="d-flex justify-center">
101
- <v-btn size="sm" class="m-3 primary" @click="playAgain">
102
- {{
103
- $t(
104
- 'plugin.games.components.content.blocks.crossword.play_again'
105
- )
106
- }}
107
- </v-btn>
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
- var CrosswordUtils = {
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 = '&nbsp;'
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('&nbsp;')
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
- props: {
189
- lang: {
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
- selectedWords: [],
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
- words: function () {
263
- return this.selectedWords.map(
264
- (wordWithReflexion) => wordWithReflexion.word
148
+ wordMap() {
149
+ const map = _.cloneDeep(
150
+ _.get(this.block, 'metadata.config.words', [])
265
151
  )
266
- },
267
152
 
268
- clues: function () {
269
- return this.selectedWords.map(
270
- (wordWithReflexion) => wordWithReflexion.description
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
- down: function () {
288
- let downWord
289
- switch (this.lang) {
290
- case 'ES':
291
- downWord = 'Verticales'
292
- break
293
- default:
294
- downWord = 'Down'
295
- break
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
- return downWord
177
+ classValue += ' ' + crosswordSize
178
+ return classValue
298
179
  },
299
180
  },
300
181
  watch: {
301
182
  render(newVal) {
302
183
  if (newVal === true) {
303
- this.onSetUpData(true)
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.generateGrid()
309
- this.$nextTick(function () {
310
- window.addEventListener('resize', this.getWindowWidth)
311
- this.getWindowWidth()
312
- })
197
+ this.onSetUpData()
313
198
  },
314
199
  methods: {
315
- onSetUpData(withoutMount = null) {
316
- if (_.isEmpty(this.block.metadata.config.words)) {
317
- this.block.metadata.config.words = []
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
- let deleteArray = []
322
- this.block.metadata.config.words.forEach(function callback(
323
- value,
324
- index
325
- ) {
326
- if (
327
- (value.description === '' && value.word === '') ||
328
- (value.description === null && value.word === null)
329
- ) {
330
- deleteArray.push(index)
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
- deleteArray.forEach((index) => {
335
- this.block.metadata.config.words.splice(index)
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
- if (this.block.metadata.config.words.length < 2) {
339
- this.wordsWithReflexions = this.dummyWordData
340
- this.dummyData = true
341
- } else {
342
- this.dummyData = false
343
- this.wordsWithReflexions = _.cloneDeep(
344
- this.block.metadata.config.words
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
- if (withoutMount) {
349
- this.generateGrid()
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: function (index1, index2) {
353
- let id = `item${index1 + 1}-${index2 + 1}`
355
+ index(rowIndex, cellIndex) {
356
+ let id = `${this.id}-cell-${rowIndex + 1}-${cellIndex + 1}`
354
357
  return id
355
358
  },
356
359
 
357
- pattern: function (value) {
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: function (letter1, letter2) {
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
- setRandomWords: function () {
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: function () {
400
- const MATRIX_LENGTH = 13
401
-
402
- let grid
403
-
404
- while (!grid || grid.length > MATRIX_LENGTH) {
405
- // // Create crossword object with the words and clues
406
- this.setRandomWords()
407
- var cw = new Crossword(this.words, this.clues)
408
- // create the crossword grid (try to make it have a 1:1 width to height ratio in 10 tries)
409
- var tries = 10
410
- grid = cw.getSquareGrid(tries)
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
- this.grid = grid
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
- if (this.grid.length < MATRIX_LENGTH) {
421
- // add rows
422
- while (this.grid.length < MATRIX_LENGTH) {
423
- this.grid.push([])
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
- playAgain: function () {
439
- this.generateGrid()
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
- getWindowWidth: function () {
443
- if (
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: #9aff67;
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
- /* background: #fff; */
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
- margin: 0 auto;
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 #000;
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: #ffff74;
507
- border: 1px solid #000;
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: #75489794;
513
- border: 1px solid #000;
514
- outline: 1px solid #000;
580
+ background-color: #111 !important;
581
+ border: 1px solid #333;
515
582
  }
516
583
 
517
584
  .crossword-clues {
518
- /* position: absolute;
519
- margin-top: 650px; */
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
- /* top: 0;
522
- left: 650px;
523
- width: 650px; */
595
+ user-select: none;
596
+ pointer-events: none;
524
597
  }
525
598
 
526
- .crossword-clues__list {
527
- margin: 0 0 0 60px;
528
- padding: 0;
529
- display: inline-block;
530
- vertical-align: top;
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-clues__list-title {
534
- font-weight: bold;
535
- padding: 4px;
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-clues__list-item {
539
- margin: 0;
540
- padding: 4px;
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>