1
+ const selectors = {
2
+ boardContainer : document . querySelector ( '.board-container' ) ,
3
+ board : document . querySelector ( '.board' ) ,
4
+ moves : document . querySelector ( '.moves' ) ,
5
+ timer : document . querySelector ( '.timer' ) ,
6
+ start : document . querySelector ( 'button' ) ,
7
+ win : document . querySelector ( '.win' )
8
+ }
9
+
10
+ const state = {
11
+ gameStarted : false ,
12
+ flippedCards : 0 ,
13
+ totalFlips : 0 ,
14
+ totalTime : 0 ,
15
+ loop : null
16
+ }
17
+
18
+ const shuffle = array => {
19
+ const clonedArray = [ ...array ]
20
+
21
+ for ( let index = clonedArray . length - 1 ; index > 0 ; index -- ) {
22
+ const randomIndex = Math . floor ( Math . random ( ) * ( index + 1 ) )
23
+ const original = clonedArray [ index ]
24
+
25
+ clonedArray [ index ] = clonedArray [ randomIndex ]
26
+ clonedArray [ randomIndex ] = original
27
+ }
28
+
29
+ return clonedArray
30
+ }
31
+
32
+ const pickRandom = ( array , items ) => {
33
+ const clonedArray = [ ...array ]
34
+ const randomPicks = [ ]
35
+
36
+ for ( let index = 0 ; index < items ; index ++ ) {
37
+ const randomIndex = Math . floor ( Math . random ( ) * clonedArray . length )
38
+
39
+ randomPicks . push ( clonedArray [ randomIndex ] )
40
+ clonedArray . splice ( randomIndex , 1 )
41
+ }
42
+
43
+ return randomPicks
44
+ }
45
+
46
+ const generateGame = ( ) => {
47
+ const dimensions = selectors . board . getAttribute ( 'data-dimension' )
48
+
49
+ if ( dimensions % 2 !== 0 ) {
50
+ throw new Error ( "The dimension of the board must be an even number." )
51
+ }
52
+
53
+ const emojis = [ '🥔' , '🍒' , '🥑' , '🌽' , '🥕' , '🍇' , '🍉' , '🍌' , '🥭' , '🍍' ]
54
+ const picks = pickRandom ( emojis , ( dimensions * dimensions ) / 2 )
55
+ const items = shuffle ( [ ...picks , ...picks ] )
56
+ const cards = `
57
+ <div class="board" style="grid-template-columns: repeat(${ dimensions } , auto)">
58
+ ${ items . map ( item => `
59
+ <div class="card">
60
+ <div class="card-front"></div>
61
+ <div class="card-back">${ item } </div>
62
+ </div>
63
+ ` ) . join ( '' ) }
64
+ </div>
65
+ `
66
+
67
+ const parser = new DOMParser ( ) . parseFromString ( cards , 'text/html' )
68
+
69
+ selectors . board . replaceWith ( parser . querySelector ( '.board' ) )
70
+ }
71
+
72
+ const startGame = ( ) => {
73
+ state . gameStarted = true
74
+ selectors . start . classList . add ( 'disabled' )
75
+
76
+ state . loop = setInterval ( ( ) => {
77
+ state . totalTime ++
78
+
79
+ selectors . moves . innerText = `${ state . totalFlips } moves`
80
+ selectors . timer . innerText = `time: ${ state . totalTime } sec`
81
+ } , 1000 )
82
+ }
83
+
84
+ const flipBackCards = ( ) => {
85
+ document . querySelectorAll ( '.card:not(.matched)' ) . forEach ( card => {
86
+ card . classList . remove ( 'flipped' )
87
+ } )
88
+
89
+ state . flippedCards = 0
90
+ }
91
+
92
+ const flipCard = card => {
93
+ state . flippedCards ++
94
+ state . totalFlips ++
95
+
96
+ if ( ! state . gameStarted ) {
97
+ startGame ( )
98
+ }
99
+
100
+ if ( state . flippedCards <= 2 ) {
101
+ card . classList . add ( 'flipped' )
102
+ }
103
+
104
+ if ( state . flippedCards === 2 ) {
105
+ const flippedCards = document . querySelectorAll ( '.flipped:not(.matched)' )
106
+
107
+ if ( flippedCards [ 0 ] . innerText === flippedCards [ 1 ] . innerText ) {
108
+ flippedCards [ 0 ] . classList . add ( 'matched' )
109
+ flippedCards [ 1 ] . classList . add ( 'matched' )
110
+ }
111
+
112
+ setTimeout ( ( ) => {
113
+ flipBackCards ( )
114
+ } , 1000 )
115
+ }
116
+
117
+ // If there are no more cards that we can flip, we won the game
118
+ if ( ! document . querySelectorAll ( '.card:not(.flipped)' ) . length ) {
119
+ setTimeout ( ( ) => {
120
+ selectors . boardContainer . classList . add ( 'flipped' )
121
+ selectors . win . innerHTML = `
122
+ <span class="win-text">
123
+ You won!<br />
124
+ with <span class="highlight">${ state . totalFlips } </span> moves<br />
125
+ under <span class="highlight">${ state . totalTime } </span> seconds
126
+ </span>
127
+ `
128
+
129
+ clearInterval ( state . loop )
130
+ } , 1000 )
131
+ }
132
+ }
133
+
134
+ const attachEventListeners = ( ) => {
135
+ document . addEventListener ( 'click' , event => {
136
+ const eventTarget = event . target
137
+ const eventParent = eventTarget . parentElement
138
+
139
+ if ( eventTarget . className . includes ( 'card' ) && ! eventParent . className . includes ( 'flipped' ) ) {
140
+ flipCard ( eventParent )
141
+ } else if ( eventTarget . nodeName === 'BUTTON' && ! eventTarget . className . includes ( 'disabled' ) ) {
142
+ startGame ( )
143
+ }
144
+ } )
145
+ }
146
+
147
+ generateGame ( ) ;
148
+ attachEventListeners ( ) ;
0 commit comments