Obiettivo: realizzare un semplice gioco di carte tipo Memory, utilizzando JavaScript e il framework jQuery.
Per chi non conoscesse il Memory, il gioco consiste nel cercare di indovinare le coppie di carte uguali. All’inizio possono venir mostrate tutte le carte (se vogliamo dare un’aiuto iniziale) che poi vengono coperte; a quel punto si tirano su due carte per volta per vedere se sono uguali: se lo sono le lasciamo scoperte, altrimenti le ricopriamo e procediamo col sollevare un’altra coppia e così via.
La versione giocabile del gioco, realizzato come illustrato nell’articolo, si trova in fondo all’articolo |
Il gioco può essere elaborato in diverse varianti, a fini di questo semplice esercizio prendiamo in considerazione le seguenti regole:
- Al primo turno vengono mostrate per un attimo tutte le carte, dopodiché vengono nascoste
- Quando il giocatore solleva due carte uguali queste vengono lasciate scoperte e vengono assegnati dei punti
- Se le carte non sono uguali le ricopriamo nuovamente
- Per ogni coppia di carte scoperte assegniamo dei punti, determinati anche in base allo scorrere del tempo (prima si scoprono e più punti si guadagnano); diamo un punteggio maggiorato se le carte vengono trovate uguali in sequenza
- Una volta che tutte le carte sono state scoperte il gioco finisce
Prima di procedere oltre nello sviluppo del programma vediamo di cosa avremo bisogno (e cosa ho utilizzato):
- Libreria jQuery
- Libreria jQuery Flip
- Qualche effetto audio preso da SoundBible (in particolare ho utilizzato il file Blast-SoundBible.com-2068539061.wav)
- Delle immagini da usare per le carte (nel mio caso ho usato alcuni giocatori dell’Everton le cui immagini sono state prese dal sito ufficiale dalla pagina del team)
- La libreria Bootstrap (opzionale)
Detto questo il risultato che vogliamo ottenere sarà simile a questo qua:
Costruiamo adesso l’idea del programma, che andremo a sviluppare come una singola classe in JavaScript che conterrà tutte le varie funzioni.
Il funzionamento lo potremmo riassumere in questo modo:
Cominciamo quindi costruendo la nostra pagina in HTML che conterrà lo script, per farla utilizziamo il prototipo base di Bootstrap nel modo seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<!doctype html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> <title>Memory Cards</title> </head> <body> <div class="everton"></div> <!-- Optional JavaScript --> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> <script src="flip.js"></script> <script src="carte.js"></script> </body> </html> |
Alla riga 15 abbiamo il pezzo di codice che riguarda il contenitore del gioco, nel mio caso un div. Questa parte si ricollega a quella direttamente utilizzata nel javascript, nel file carte.js.
1 2 3 4 5 6 7 |
$(document).ready(function(){ var carte = new EvertonMemory({ contenitore: '.everton' }); }); |
Affinché la classe funzioni in questo modo dobbiamo far sì che il costruttore accetti per argomento un dizionario e lo utilizzi per parametrizzare le opzioni dell’oggetto. Cominciamo quindi costruendo anche la nostra classe nel modo seguente:
1 2 3 4 5 6 7 |
class EvertonMemory { constructor( impostazioni ) { } } |
Nel nostro costruttore dichiareremo le impostazioni come una variabile globale this.impostazioni = {}
. Faccio notare che, a differenza di altri linguaggi di programmazione, le variabili (eccettuati i casi dei getter e dei setter) non possono essere dichiarate a livello di classe, ma esclusivamente dentro i metodi. Il prefisso this
fa sì che la variabile sia globale, sebbene con alcune specifiche eccezioni che vedremo.
Implementiamo ulteriormente la classe nel modo seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class EvertonMemory { constructor( impostazioni ) { this.impostazioni = { contenitore: '.everton', } this.impostazioni = Object.assign({},this.impostazioni,impostazioni); } } |
Per far sì che le impostazioni dell’utente si sovrappongano alle impostazioni predefinite utilizziamo l’istruzione this.impostazioni = Object.assign({},this.impostazioni,impostazioni)
. In questo modo uniamo i due dizionari in un nuovo dizionario vuoto e trascriviamo tutto su this.impostazioni
. In questo caso è molto importante l’ordine, difatti this.impostazioni
viene assegnato prima di impostazioni
, dizionario che contiene la parametrizzazione a livello utente. Così le nuove impostazioni andranno a sovrascrivere quelle precedenti, creando il dizionario definitivo delle impostazioni.
Adesso andiamo a creare il metodo di caricamento nella maniera seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class EvertonMemory { // costruttore ecc... caricamento() { this.creazioneStile(); this.contenitore = $(this.impostazioni.contenitore); this.contenitore.css("width", this.impostazioni.larghezza); this.contenitore.css("height", this.impostazioni.altezza); this.contenitore.css("border", this.impostazioni.bordo); this.contenitore.css("background-image", 'url("'+this.impostazioni.sfondo+'")'); this.contenitore.css("position", "relative"); this.creazioneHome(); } // altri metodi a seguire... } |
Qui stiamo utilizzando due metodi che non abbiamo ancora dichiarato, ovvero this.creazioneStile()
e this.creazioneHome()
. Con this.contenitore = $(this.impostazioni.contenitore);
intercettiamo il contenitore predefinito in precedenza e applichiamo, successivamente gli stili di base. Avrei potuto utilizzare anche un file CSS separato per la parametrizzazione degli stili, ma allo scopo di esercizio ho preferito fare tutto tramite JavaScript all’interno della classe stessa.
Delle istruzioni precedenti è molto importante passare l’opzione this.contenitore.css("position", "relative");
che definirà la posizione del contenitore come relativa. In questo modo potremo utilizzare internamente i riferimenti assoluti per posizionare i vari elementi. Senza la dichiarazione di posizione relativa in modo esplicito, gli elementi interni si sarebbero posizionati rispetto al primo oggetto genitore del contenitore che abbia una posizione dichiarata esplicitamente.
Per il metodo this.creazioneStile()
procediamo nel modo seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class EvertonMemory { // costruttore ecc... creazioneStile() { var stile = $('<style>'+ this.impostazioni.contenitore+' .carta{width:22%;height:20.2%;margin:1.5%;float:left;cursor:pointer;}'+ '</style>'); $('html > head').append(stile); } // altri metodi a seguire... } |
Sostanzialmente creiamo una sezione <style></style>
all’interno del codice dentro la quale andremo ad aggiungere tutti gli stili. Per evitare sovrapposizioni esterne al contenitore anteponiamo ad ogni stile il descrittore in CSS del contenitore con this.impostazioni.contenitore
. Infine aggiungiamo il gruppo di stile nell’intestazione con l’istruzione $('html > head').append(stile);
Dentro questo metodo procederemo ad aggiungere via via i vari stili di cui abbiamo bisogno.
Per il metodo this.creazioneHome()
procediamo aggiungendo la seguente porzione di codice:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class EvertonMemory { // costruttore ecc... creazioneHome() { var questo = this; this.contenitore.html('<div class="start" id="start'+questo.impostazioni.idunivoco+'">Inizia</div>'); $('#start'+questo.impostazioni.idunivoco).click(function() { questo.contenitore.html(''); questo.creazioneGioco(); }); } // altri metodi a seguire... } |
In questo metodo andremo a creare this.creazioneGioco()
che verrà richiamato al click su START nella schermata di inizio. Quello che mi interessa in particolare durante questo passaggio è l’utilizzo di this
. All’interno dell’evento click this si riferisce all’elemento oggetto del click. Di conseguenza non è più utilizzabile per riferirsi agli oggetti della classe. Per poterlo utilizzare dobbiamo riassegnarlo ad una variabile locale nel metodo genitore, come per esempio var questo = this;
In questo modo dentro l’evento di click potremo utilizzare l’istruzione questo.creazioneGioco();
che richiama un metodo della classe medesima (che dobbiamo ancora creare).
Per la creazione del gioco procediamo nel modo seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class EvertonMemory { // costruttore ecc... creazioneGioco() { this.carte = null; this.carte = $.merge($.merge([], this.impostazioni.immagini), this.impostazioni.immagini); this.carte.sort(() => Math.random() - 0.5); this.cartaattuale = ""; this.consecutivi = 0; this.punteggio = 0; this.giocoiniziato = false; this.cartetrovate = 0; this.max_consecutivi = 0; this.creazioneMenu(); this.creazioneTimer(); this.contenitore.append('<div class="combo" id="combo'+this.impostazioni.idunivoco+'"></div>'); $('#combo'+this.impostazioni.idunivoco).hide(); for(var i = 0; i < 16; i++ ) this.creaCarta(i); this.mostraTutte(); } // altri metodi a seguire... } |
Siccome il gioco potrà essere creato diverse volte, per esempio quando si riavvia durante la partita, oppure dopo che si è vinto, ci dobbiamo assicurare che tutte le variabili di gioco vengano inizializzate, ed eventualmente reimpostate, qui.
I metodi che mi interessano in particolare sono:
this.carte = $.merge($.merge([], this.impostazioni.immagini), this.impostazioni.immagini);
che ci consente di creare le 16 carte di cui avremo bisogno. Ricordiamoci che le carte sono in coppie quindi le 8 carte iniziali dovranno essere duplicate. Per farlo voglio unire lo stesso dizionario, dentrothis.impostazioni.immagini
, a se medesimo. Affinché la funzione$.merge
non crei un puntatore sul vettore, continuando ad aggiungere le solite immagini ad ogni riavvio successivo e quindi scombinando il gioco, bisogna usare il trucco di chiamare il metodo con un vettore vuoto a cui viene accodato il vettore originale, con l’istruzione$.merge([], this.impostazioni.immagini)
. Adesso abbiamo creato un vettore completamente nuovo, che potremo poi accodare al solito vettore di prima ripetendo il passaggio una seconda volta. Se non si procede in questo modo al primo riavvio del gioco si rischieranno di avere 3 o 4 carte dello stesso tipo, perché in totale non saranno più 16 ma 32, poi 48 ecc.this.carte.sort(() => Math.random() - 0.5);
che ci permette di ordinare casualmente le carte. Non è un metodo particolarmente efficiente e non produce nemmeno un livello di casualità particolarmente affidabile. Per generare una password non sarebbe quindi molto opportuno, ma per gli scopi che ci servono ai fini del gioco va benissimo, specialmente per la sua brevità di scrittura. Questa scrittura è anche la medesima dithis.carte.sort(function(){ return Math.random() - 0.5; });
$('#combo'+this.impostazioni.idunivoco).hide();
che nasconde il contenitore delle combo (poi vedremo meglio)for(var i = 0; i < 16; i++ ) this.creaCarta(i);
che inserisce le carte nell’ordine casuale determinato dal primo punto
Procediamo adesso con la creazione del menu utilizzando this.creazioneMenu();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class EvertonMemory { // costruttore ecc... creazioneMenu() { var questo = this; this.contenitore.append('<div class="menu">'+ 'Punteggio: <span id="punteggio'+this.impostazioni.idunivoco+'">0</span> • Tempo trascorso: <span id="tempo'+this.impostazioni.idunivoco+'">0:00:00</span><br><span id="restart'+this.impostazioni.idunivoco+'" class="restart">Ricomincia</span>'+ '</div>'); $('#restart'+this.impostazioni.idunivoco).click(function(){ questo.contenitore.html(''); questo.creazioneGioco(); }); } // altri metodi a seguire... } |
Faccio notare che il metodo di “restart” è praticamente identico al metodo che abbiamo visto per lo “start” nella home. Stessa procedura. Nel menu abbiamo aggiunto una voce per il punteggio corrente, una per il tempo trascorso e il tasto che consenta di ricominciare.
La creazione del timer implicherà due diversi passaggi, nella maniera seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class EvertonMemory { // costruttore ecc... creazioneTimer() { this.inizio = +new Date(); this.contatoreTimer(); } contatoreTimer() { var questo = this; setTimeout( function(){ var trascorso = +new Date() - questo.inizio; var secondi = trascorso / 1000; var minuti = secondi / 60; var ore = minuti / 60; $('#tempo'+questo.impostazioni.idunivoco).text(Math.floor(ore)+":"+(Math.floor(minuti-Math.floor(ore)*60)+"").padStart(2, '0')+":"+(Math.floor(secondi-Math.floor(minuti)*60)+"").padStart(2, '0')); if(questo.cartetrovate<8) questo.contatoreTimer(); else questo.fineGioco(); }, 500); } // altri metodi a seguire... } |
Il metodo contatoreTimer()
si richiamerà su se stesso ogni 500 millisecondi su se stessa, aggiornando lo stato del gioco e dichiarando infine la conclusione dello stesso. In tale contesto andremo anche a calcolare il tempo trascorso e ad aggiornarlo nella visualizzazione.
Arriviamo adesso alla parte più importante, quella dove creiamo le carte e definiamo il funzionamento del gioco. Ricordiamoci che ogni carta dovrà essere cliccabile.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
class EvertonMemory { // costruttore ecc... creaCarta(id) { var fronte = '<div class="front"></div>'; var retro = '<div class="back"><div class="titolo">' + this.carte[id].nome + '</div></div>'; var questo = this; this.contenitore.append('<div id="carta'+id+'-'+this.impostazioni.idunivoco+'" class="carta" data-nome="'+this.carte[id].nome+'" data-giocabile="1">'+fronte+retro+'</div>'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .front').css('background-image','url("'+this.impostazioni.immagine_retro_carta+'")'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .front').css('background-size','cover'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .front').css('background-repeat','no-repeat'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .front').css('background-position','center center'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .back').css('background-image','url("'+this.carte[id].img+'")'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .back').css('background-size','cover'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .back').css('background-repeat','no-repeat'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .back').css('background-position','center center'); $('#carta'+id+'-'+this.impostazioni.idunivoco).flip({ axis: 'y', trigger: 'manual' }); $('#carta'+id+'-'+this.impostazioni.idunivoco).click(function(){ if( $(this).attr('data-giocabile') == 1 && questo.giocoiniziato ) { var carta = $(this).data("flip-model"); if( carta.isFlipped ) { $(this).flip(false); } else { $(this).flip(true); } var cartagiocata = $(this).attr('data-nome'); if( questo.cartaattuale == "" ) { questo.cartaattuale = cartagiocata; $(this).attr('data-giocabile',0); } else { if( questo.cartaattuale == cartagiocata ) { // giusta $('div[data-nome="'+questo.cartaattuale+'"]').attr('data-giocabile',0); questo.consecutivi++; questo.incrementaPunteggio(); questo.cartetrovate++; } else if( questo.cartaattuale != "" && questo.cartaattuale != cartagiocata ) { // sbagliata var cartaattuale = questo.cartaattuale; setTimeout( function(){ $('div[data-nome="'+cartaattuale+'"]').flip(false); $('div[data-nome="'+cartagiocata+'"]').flip(false); $('div[data-nome="'+cartaattuale+'"]').attr('data-giocabile',1); $('div[data-nome="'+cartagiocata+'"]').attr('data-giocabile',1); }, 800); questo.consecutivi = 0; } questo.cartaattuale = ""; } } }); } // altri metodi a seguire... } |
Per gestire la rotazione delle carte stiamo utilizzando jQuery Flip, che ci permette sostanzialmente di ruotare gli elementi sia manualmente che ad ogni click.
Per ogni carta, procedendo in ordine, definiamo quindi le singole proprietà di CSS e attribuiamo la caratteristica flip. Dopodiché al click su ogni carta procediamo con l’impostazione della struttura di gioco. Anzitutto verifichiamo che il gioco sia iniziato e che la carta sia cliccabile. Se la carta è giocabile e non è ancora stata aperta, apriamola con $(this).flip(false)
. Leggiamo il nome della carta con var cartagiocata = $(this).attr('data-nome')
. Se la carta attuale non è ancora stata impostata, allora impostiamola e blocchiamo la carta con $(this).attr('data-giocabile',0)
. In caso contrario vuol dire che abbiamo già aperto una carta e quindi dobbiamo verificare se il risultato sia corretto o meno. Se la carta attuale è uguale a quella giocata allora vuol dire che è tutto corretto; blocchiamo quindi la carta, aumentiamo il conteggio delle carte aperte consecutivamente con questo.consecutivi++
, incrementiamo il punteggio con questo.incrementaPunteggio()
e conteggiamo il numero totale di carte aperte (8 coppie trovate equivale alla fine del gioco). Se le carte non sono uguali, allora diamo 800 millisecondi all’utente per poterle vedere entrambe e poi richiudiamole e reimpostiamole giocabili.
Nel suo complesso il codice che avremo creato sarà il seguente (ho incluso alcuni altri commenti specifici nel codice, per spiegare i singoli passaggi):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
$(document).ready(function(){ var carte = new EvertonMemory({ contenitore: '.everton' }); }); class EvertonMemory { // costruttore della classe a cui passiamo un dizionario di impostazioni constructor( impostazioni ) { // dizionario di impostazioni predefinite che andremo ad usare successivamente this.impostazioni = { contenitore: '.everton', larghezza: 600, altezza: 800, bordo: '1px solid #000', bordo_carta: '2px solid #000', raggio_bordo_carta: '12px', immagine_retro_carta: 'img/everton.jpg', sfondo: 'img/sfondo.jpg', // immagini delle carte sul fronte immagini: [ {nome:"Pickford",img:"img/pickford.jpg"}, {nome:"Calwert-Lewin",img:"img/calwert-lewin.jpg"}, {nome:"Digne",img:"img/digne.jpg"}, {nome:"Gomes",img:"img/gomes.jpg"}, {nome:"Holgate",img:"img/holgate.jpg"}, {nome:"Keane",img:"img/keane.jpg"}, {nome:"Richarlison",img:"img/richarlison.jpg"}, {nome:"Sigurdsson",img:"img/sigurdsson.jpg"} ], // immagini per le combo combo: [ {url:"img/basic.png"}, {url:"img/triple.png"}, {url:"img/dixie.png"}, {url:"img/great_escape.png"}, {url:"img/awesome.png"}, {url:"img/monster.png"}, {url:"img/pickford.png"} ], // descrizione finale per la vittoria finale: [ {msg:'Maddai! Neanche due carte consecutive!'}, {msg:'Ne hai beccati al massimo due di seguito, niente di che!'}, {msg:'Massimo tre di fila? Puoi fare di meglio!'}, {msg:'Quattro di fila? C\'è di peggio!'}, {msg:'Cinque di seguito? Niente male!'}, {msg:'Sei uno dopo l\'altro? Ti sei allenato proprio!'}, {msg:'Già che ne hai beccati 7 potevi beccarne 8!'}, {msg:'Otto in fila! Sbalorditivo!'} ], // numero utilizzato per calcolare il punteggio base basepunteggio: 6000000000000, // codice univoco da accorpare a tutti gli elementi #ID della classe, in modo da evitare interferenze con altri elementi omonimi non creati dalla classe medesima idunivoco: 'p9dk3l1o2ll' } // sovrapponiamo, dove sono dichiarate, le impostazioni dell'utente a quelle predefinite this.impostazioni = Object.assign({},this.impostazioni,impostazioni); // verifichiamo sul mobile this.controlloMobile(); // carichiamo il gioco this.caricamento(); } controlloMobile() { var larghezza = $(window).width(); var rapporto = this.impostazioni.altezza / this.impostazioni.larghezza; if( this.impostazioni.larghezza > larghezza ) { this.impostazioni.larghezza = larghezza; this.impostazioni.altezza = rapporto * larghezza; } } // carichiamo il gioco la prima volta caricamento() { this.creazioneStile(); // impostiamo le caratteristiche base del contenitore // faccio notare che il contenitore è istanziato in un oggetto globale della classe this.contenitore = $(this.impostazioni.contenitore); this.contenitore.css("width", this.impostazioni.larghezza); this.contenitore.css("height", this.impostazioni.altezza); this.contenitore.css("border", this.impostazioni.bordo); this.contenitore.css("background-image", 'url("'+this.impostazioni.sfondo+'")'); this.contenitore.css("position", "relative"); // creiamo la pagina iniziale this.creazioneHome(); } // creiamo la home introduttiva al gioco creazioneHome() { var questo = this; // creiamo un pulsante per lo start del gioco this.contenitore.html('<div class="start" id="start'+questo.impostazioni.idunivoco+'">Inizia</div>'); // azioni quando si clicca sul pulsante start $('#start'+questo.impostazioni.idunivoco).click(function() { // puliamo il contenuto del contenitore, rimuovendo il pulsante start stesso questo.contenitore.html(''); // creiamo il gioco questo.creazioneGioco(); }); } // creazione del gioco creazioneGioco() { // puliamo e creiamo l'elenco delle 16 carte da utilizzare this.carte = null; this.carte = $.merge($.merge([], this.impostazioni.immagini), this.impostazioni.immagini); // ordiniamo le carte come ci piace di più this.carte.sort(() => Math.random() - 0.5); this.cartaattuale = ""; this.consecutivi = 0; this.punteggio = 0; this.giocoiniziato = false; this.cartetrovate = 0; // per il commento finale calcoliamo il numero massimo di carte consecutive aperte this.max_consecutivi = 0; // creiamo il menu this.creazioneMenu(); // avviamo il timer this.creazioneTimer(); // creiamo lo spazio per le combo this.contenitore.append('<div class="combo" id="combo'+this.impostazioni.idunivoco+'"></div>'); // nascondiamo lo spazio delle combo $('#combo'+this.impostazioni.idunivoco).hide(); // aggiungiamo le carte for(var i = 0; i < 16; i++ ) this.creaCarta(i); // per un attimo mostriamo tutte le carte this.mostraTutte(); } // mostriamo tutte le carte mostraTutte() { var questo = this; // entro 500 ms mostriamo tutte le carte setTimeout( function(){ $('.carta').flip(true); // entro 2000 ms, ovvero 2 secondi, nascondiamo le carte mostrate setTimeout( function(){ $('.carta').flip(false); // confermiamo l'inizio del gioco questo.giocoiniziato = true; }, 2000); }, 500); } // incrementiamo il punteggio incrementaPunteggio() { // calcoliamo il tempo trascorso dalla data iniziale a quella attuale // questo valore è in millisecondi var trascorso = +new Date() - this.inizio; // verifichiamo il numero di elementi consecutivi massimo this.max_consecutivi = this.consecutivi > this.max_consecutivi ? this.consecutivi : this.max_consecutivi; // incrementiamo il punteggio con una funzione matematica this.punteggio += Math.round(this.impostazioni.basepunteggio/Math.pow(trascorso+1,2)*this.consecutivi); // aggiorniamo il punteggio $('#punteggio'+this.impostazioni.idunivoco).text(this.punteggio.toLocaleString('it-IT')); // chiamiamo il popup delle combo this.comboPopUp(); } // popup delle combo comboPopUp() { // se c'è più di un elemento consecutivo, quindi almeno 2 consecutivi aperti, allora mostra una "combo" if( this.consecutivi > 1 ) { var questo = this; $('#combo'+this.impostazioni.idunivoco).css('background-image','url("'+this.impostazioni.combo[this.consecutivi-2].url+'")'); $('#combo'+this.impostazioni.idunivoco).css('background-size','contain'); $('#combo'+this.impostazioni.idunivoco).css('background-repeat','no-repeat'); $('#combo'+this.impostazioni.idunivoco).css('background-position','center center'); $('#combo'+this.impostazioni.idunivoco).css('z-index','9999'); $('#combo'+this.impostazioni.idunivoco).show(); $("#combo"+this.impostazioni.idunivoco).animate({height: "128px",width: "400px","margin-top":"-64px","margin-left":"-200px"}); // all'animazione aggiungiamo un suono var obj = document.createElement("audio"); // prendiamo il file wav obj.src = "Blast-SoundBible.com-2068539061.wav"; // riproduciamolo obj.play(); // dopo 800ms nascondiamo il popup della combo setTimeout( function(){ $('#combo'+questo.impostazioni.idunivoco).hide(); }, 800); } } // creiamo gli stili creazioneStile() { var stile = $('<style>'+ this.impostazioni.contenitore+' .carta{width:22%;height:20.2%;margin:1.5%;float:left;cursor:pointer;}'+ this.impostazioni.contenitore+' .carta .front{width:100%;height:100%;border:'+this.impostazioni.bordo_carta+';border-radius:'+this.impostazioni.raggio_bordo_carta+';}'+ this.impostazioni.contenitore+' .carta .back{width:100%;height:100%;border:'+this.impostazioni.bordo_carta+';border-radius:'+this.impostazioni.raggio_bordo_carta+';position:relative;}'+ this.impostazioni.contenitore+' .carta .back .titolo{width:100%;color:#fff;text-align:center;position:absolute;bottom:6px;font-size:10px;}'+ this.impostazioni.contenitore+' .menu{width:100%;height:10.2%;padding-top:8px;text-align:center;}'+ this.impostazioni.contenitore+' .start{width:200px;height:64px;line-height:64px;background-color:#00319a;color:#fff;position:absolute;top:'+this.impostazioni.altezza/2+'px;left:'+this.impostazioni.larghezza/2+'px;margin-left:-100px;margin-top:-32px;text-align:center;cursor:pointer;}'+ this.impostazioni.contenitore+' .start:hover{background-color:#0040c8;}'+ this.impostazioni.contenitore+' .combo{width:200px;height:64px;position:absolute;top:'+this.impostazioni.altezza/2+'px;left:'+this.impostazioni.larghezza/2+'px;margin-left:-100px;margin-top:-32px;}'+ this.impostazioni.contenitore+' .restart{display:inline-block;padding:8px;background-color:#00319a;color:#fff;margin-top:4px;cursor:pointer;}'+ this.impostazioni.contenitore+' .restart:hover{background-color:#0040c8;}'+ this.impostazioni.contenitore+' .messaggio-fine{width:400px;height:200px;position:absolute;top:'+this.impostazioni.altezza/4+'px;left:'+this.impostazioni.larghezza/2+'px;margin-left:-200px;margin-top:-100px;text-align:center;display:table;}'+ this.impostazioni.contenitore+' .messaggio-fine span{width:100%;}'+ '</style>') $('html > head').append(stile); } // creiamo il menu creazioneMenu() { var questo = this; this.contenitore.append('<div class="menu">'+ 'Punteggio: <span id="punteggio'+this.impostazioni.idunivoco+'">0</span> • Tempo trascorso: <span id="tempo'+this.impostazioni.idunivoco+'">0:00:00</span><br><span id="restart'+this.impostazioni.idunivoco+'" class="restart">Ricomincia</span>'+ '</div>'); $('#restart'+this.impostazioni.idunivoco).click(function(){ questo.contenitore.html(''); questo.creazioneGioco(); }); } // avviamo il timer creazioneTimer() { this.inizio = +new Date(); this.contatoreTimer(); } // creiamo il timer contatoreTimer() { var questo = this; setTimeout( function(){ var trascorso = +new Date() - questo.inizio; var secondi = trascorso / 1000; var minuti = secondi / 60; var ore = minuti / 60; $('#tempo'+questo.impostazioni.idunivoco).text(Math.floor(ore)+":"+(Math.floor(minuti-Math.floor(ore)*60)+"").padStart(2, '0')+":"+(Math.floor(secondi-Math.floor(minuti)*60)+"").padStart(2, '0')); if(questo.cartetrovate<8) questo.contatoreTimer(); else questo.fineGioco(); }, 500); } // fine del gioco fineGioco() { var questo = this; // mostriamo il punteggio e tutti i commenti this.contenitore.html('<div class="messaggio-fine"><span><h3>Finito!</h3></span><span>Hai totalizzato</span><span><h4>'+this.punteggio.toLocaleString('it-IT')+'</h4></span><span><h5>punti</h5></span><span>'+this.impostazioni.finale[this.max_consecutivi-1].msg+'</span></div><div class="start" id="rigioca'+questo.impostazioni.idunivoco+'">Rigioca</div>'); $('#rigioca'+questo.impostazioni.idunivoco).click(function() { questo.contenitore.html(''); questo.creazioneGioco(); }); } // creiamo le carte creaCarta(id) { var fronte = '<div class="front"></div>'; var retro = '<div class="back"><div class="titolo">' + this.carte[id].nome + '</div></div>'; var questo = this; this.contenitore.append('<div id="carta'+id+'-'+this.impostazioni.idunivoco+'" class="carta" data-nome="'+this.carte[id].nome+'" data-giocabile="1">'+fronte+retro+'</div>'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .front').css('background-image','url("'+this.impostazioni.immagine_retro_carta+'")'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .front').css('background-size','cover'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .front').css('background-repeat','no-repeat'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .front').css('background-position','center center'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .back').css('background-image','url("'+this.carte[id].img+'")'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .back').css('background-size','cover'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .back').css('background-repeat','no-repeat'); $('#carta'+id+'-'+this.impostazioni.idunivoco+' .back').css('background-position','center center'); $('#carta'+id+'-'+this.impostazioni.idunivoco).flip({ axis: 'y', trigger: 'manual' }); $('#carta'+id+'-'+this.impostazioni.idunivoco).click(function(){ if( $(this).attr('data-giocabile') == 1 && questo.giocoiniziato ) { var carta = $(this).data("flip-model"); if( carta.isFlipped ) { $(this).flip(false); } else { $(this).flip(true); } var cartagiocata = $(this).attr('data-nome'); if( questo.cartaattuale == "" ) { questo.cartaattuale = cartagiocata; $(this).attr('data-giocabile',0); } else { if( questo.cartaattuale == cartagiocata ) { // giusta $('div[data-nome="'+questo.cartaattuale+'"]').attr('data-giocabile',0); questo.consecutivi++; questo.incrementaPunteggio(); questo.cartetrovate++; } else if( questo.cartaattuale != "" && questo.cartaattuale != cartagiocata ) { // sbagliata var cartaattuale = questo.cartaattuale; setTimeout( function(){ $('div[data-nome="'+cartaattuale+'"]').flip(false); $('div[data-nome="'+cartagiocata+'"]').flip(false); $('div[data-nome="'+cartaattuale+'"]').attr('data-giocabile',1); $('div[data-nome="'+cartagiocata+'"]').attr('data-giocabile',1); }, 800); questo.consecutivi = 0; } questo.cartaattuale = ""; } } }); } } |
Per chiunque volesse cimentarsi nel gioco ne riporto di seguito anche la versione giocabile. Inutile dire che ci sarebbero ulteriori possibilità di perfezionamento e che nel gioco ci sono anche alcuni bug che accadono quando si clicca troppo rapidamente tra una carta e l’altra.
Facendo un po’ di prove per ora il meglio che sono riuscito a ottenere, senza usare sotterfugi come screenshot e simili, è stato il seguente risultato: