@windward/games 0.0.3 → 0.0.4

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.
@@ -0,0 +1,542 @@
1
+ <template>
2
+ <v-container>
3
+ <div>
4
+ <h2>{{ block.metadata.config.title }}</h2>
5
+ <p>{{ block.metadata.config.instructions }}</p>
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">
19
+ <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
+ />
50
+ </template>
51
+ <!-- /ROW -->
52
+ </template>
53
+ </div>
54
+ </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>
98
+ </v-col>
99
+ </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>
108
+ </v-row>
109
+ </div>
110
+ </div>
111
+ </v-container>
112
+ </template>
113
+
114
+ <script>
115
+ import { Crossword } from './Crossword'
116
+ import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
117
+ 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
+ }
184
+
185
+ export default {
186
+ name: 'CrossWordPuzzle',
187
+ 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
+ },
202
+ beforeMount() {
203
+ if (_.isEmpty(this.block)) {
204
+ this.block = {}
205
+ }
206
+ if (_.isEmpty(this.block.metadata)) {
207
+ this.block.metadata = {}
208
+ }
209
+ if (_.isEmpty(this.block.metadata.config)) {
210
+ this.block.metadata.config = {}
211
+ }
212
+ if (_.isEmpty(this.block.metadata.config.title)) {
213
+ this.block.metadata.config.title = ''
214
+ }
215
+ if (_.isEmpty(this.block.metadata.config.instructions)) {
216
+ this.block.metadata.config.instructions = ''
217
+ }
218
+ this.onSetUpData()
219
+ },
220
+ data() {
221
+ return {
222
+ selectedWords: [],
223
+ acrossWords: [],
224
+ downWords: [],
225
+ matrix: null,
226
+ grid: null,
227
+ 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
+ }
260
+ },
261
+ computed: {
262
+ words: function () {
263
+ return this.selectedWords.map(
264
+ (wordWithReflexion) => wordWithReflexion.word
265
+ )
266
+ },
267
+
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
285
+ },
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
296
+ }
297
+ return downWord
298
+ },
299
+ },
300
+ watch: {
301
+ render(newVal) {
302
+ if (newVal === true) {
303
+ this.onSetUpData(true)
304
+ }
305
+ },
306
+ },
307
+ mounted() {
308
+ this.generateGrid()
309
+ this.$nextTick(function () {
310
+ window.addEventListener('resize', this.getWindowWidth)
311
+ this.getWindowWidth()
312
+ })
313
+ },
314
+ 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
320
+ } 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
+ })
333
+
334
+ deleteArray.forEach((index) => {
335
+ this.block.metadata.config.words.splice(index)
336
+ })
337
+
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
345
+ )
346
+ }
347
+ }
348
+ if (withoutMount) {
349
+ this.generateGrid()
350
+ }
351
+ },
352
+ index: function (index1, index2) {
353
+ let id = `item${index1 + 1}-${index2 + 1}`
354
+ return id
355
+ },
356
+
357
+ pattern: function (value) {
358
+ if (value) {
359
+ let pattern = `^[${value.toLowerCase()}${value.toUpperCase()}]{1}$`
360
+ return pattern
361
+ }
362
+ },
363
+
364
+ equalsLetters: function (letter1, letter2) {
365
+ if (letter1 === null) {
366
+ letter1 = ''
367
+ }
368
+ if (letter2 === null) {
369
+ letter2 = ''
370
+ }
371
+ return (
372
+ letter1.toUpperCase() === letter2.toUpperCase() ||
373
+ letter1.toLowerCase() === letter2.toLowerCase()
374
+ )
375
+ },
376
+ setRandomWords: function () {
377
+ let words = this.shuffle(this.wordsWithReflexions)
378
+ this.selectedWords = words
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: 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)
411
+ }
412
+
413
+ this.grid = grid
414
+
415
+ this.form = [...Array(MATRIX_LENGTH)].map(() =>
416
+ Array(MATRIX_LENGTH).fill('')
417
+ )
418
+
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([])
424
+ }
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
+ }
436
+ },
437
+
438
+ playAgain: function () {
439
+ this.generateGrid()
440
+ },
441
+
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
449
+ }
450
+ },
451
+ },
452
+ beforeDestroy() {
453
+ window.removeEventListener('resize', this.getWindowWidth)
454
+ },
455
+ }
456
+ </script>
457
+
458
+ <style>
459
+ .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;
469
+ }
470
+
471
+ .crossword-board-container {
472
+ position: relative;
473
+ /* background: #fff; */
474
+ }
475
+
476
+ .crossword-board {
477
+ position: relative;
478
+ z-index: 1;
479
+ background: transparent;
480
+ border: 1px solid #000;
481
+ width: 650px;
482
+ height: 650px;
483
+ display: grid;
484
+ grid-template: repeat(13, 7.69231%) / repeat(13, 7.69231%);
485
+ list-style-type: none;
486
+ padding: 0;
487
+ margin: 0 auto;
488
+ }
489
+
490
+ .crossword-board__item {
491
+ border: 1px solid #000;
492
+ position: relative;
493
+ z-index: 100;
494
+ text-align: center;
495
+ font-size: 20px;
496
+ font-weight: bold;
497
+ text-transform: uppercase;
498
+ }
499
+
500
+ .crossword-board__item:not(.crossword-board__item--valid) {
501
+ background-color: #fff;
502
+ }
503
+
504
+ .crossword-board__item:active:not(.crossword-board__item--valid),
505
+ .crossword-board__item:focus:not(.crossword-board__item--valid) {
506
+ background: #ffff74;
507
+ border: 1px solid #000;
508
+ outline: 1px solid #000;
509
+ }
510
+
511
+ .crossword-board__item--blank {
512
+ background-color: #75489794;
513
+ border: 1px solid #000;
514
+ outline: 1px solid #000;
515
+ }
516
+
517
+ .crossword-clues {
518
+ /* position: absolute;
519
+ margin-top: 650px; */
520
+ color: #000;
521
+ /* top: 0;
522
+ left: 650px;
523
+ width: 650px; */
524
+ }
525
+
526
+ .crossword-clues__list {
527
+ margin: 0 0 0 60px;
528
+ padding: 0;
529
+ display: inline-block;
530
+ vertical-align: top;
531
+ }
532
+
533
+ .crossword-clues__list-title {
534
+ font-weight: bold;
535
+ padding: 4px;
536
+ }
537
+
538
+ .crossword-clues__list-item {
539
+ margin: 0;
540
+ padding: 4px;
541
+ }
542
+ </style>
@@ -19,7 +19,7 @@
19
19
  v-for="(word, index) in block.metadata.config.words"
20
20
  :key="index"
21
21
  >
22
- <v-row class="d-flex justify-center outline ma-2">
22
+ <v-row class="d-flex justify-center ma-2">
23
23
  <p class="pa-3 mb-0 clueAndJumble">
24
24
  <span class="spanBold"
25
25
  >{{
@@ -36,15 +36,22 @@
36
36
  {{ shuffle(word.value) }}
37
37
  </p>
38
38
  </v-row>
39
- <v-container :key="'feedback'" :class="feedbackStatus">
40
- <br />
41
- <v-row
42
- class="d-flex justify-space-around"
43
- align="center"
44
- justify="center"
45
- >{{ feedback }}
39
+ <v-container
40
+ v-if="showFeedback"
41
+ :key="'feedback'"
42
+ :class="feedbackStatus"
43
+ >
44
+ <v-row class="d-flex" align="center">
45
+ <v-col cols="4"></v-col>
46
+ <v-col cols="4" class="d-flex justify-center">
47
+ <p class="">{{ feedback }}</p>
48
+ </v-col>
49
+ <v-col cols="4" class="d-flex justify-end">
50
+ <v-icon @click="onHideFeedback">
51
+ mdi-alpha-x-circle-outline</v-icon
52
+ >
53
+ </v-col>
46
54
  </v-row>
47
- <br />
48
55
  </v-container>
49
56
  <v-row class="justify-center mt-4">
50
57
  <Jumble
@@ -124,6 +131,7 @@ export default {
124
131
  'plugin.games.components.content.blocks.word_jumble.feedback'
125
132
  ),
126
133
  showAnswer: false,
134
+ showFeedback: false,
127
135
  resetValue: false,
128
136
  studentResponse: '',
129
137
  feedbackStatus: '',
@@ -166,8 +174,12 @@ export default {
166
174
  // reveal prop changed to true to show answer
167
175
  this.showAnswer = true
168
176
  },
177
+ onHideFeedback() {
178
+ this.showFeedback = false
179
+ },
169
180
  onCheckAnswer(word) {
170
181
  this.studentResponse = this.studentResponse.toLowerCase()
182
+ this.showFeedback = true
171
183
  const answer = word.value.toLowerCase()
172
184
  if (this.studentResponse === answer) {
173
185
  // updates class
@@ -202,7 +214,7 @@ export default {
202
214
  },
203
215
  onSlideChanged() {
204
216
  // this function is called when the slide is changed
205
- // reset the game each time this occurs via props due to fact
217
+ // reset the game each time this occurs via props due to fact
206
218
  // that components do not remount on slides each time the slide is revisited
207
219
  // updates class
208
220
  this.feedbackStatus = ''