[javascript] Realizzare un gioco di carte tipo Memory utilizzando jQuery

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:

  1. Al primo turno vengono mostrate per un attimo tutte le carte, dopodiché vengono nascoste
  2. Quando il giocatore solleva due carte uguali queste vengono lasciate scoperte e vengono assegnati dei punti
  3. Se le carte non sono uguali le ricopriamo nuovamente
  4. 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
  5. 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):

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:

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.

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:

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:

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:

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:

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:

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:

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:

  1. 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, dentro this.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.
  2. 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 di this.carte.sort(function(){ return Math.random() - 0.5; });
  3. $('#combo'+this.impostazioni.idunivoco).hide(); che nasconde il contenitore delle combo (poi vedremo meglio)
  4. 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();

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:

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.

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):

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:

2 thoughts on “[javascript] Realizzare un gioco di carte tipo Memory utilizzando jQuery

    1. Ciao Dario,
      Per farlo ti è sufficiente intervenire su questo pezzo di codice, al momento della creazione delle carte:

      $('#carta'+id+'-'+this.impostazioni.idunivoco+' .front').css('background-image','url("'+this.impostazioni.immagine_retro_carta+'")');

      Come vedi io metto su tutte le parti frontali la stessa immagine, mentre puoi usare un array con le immagini come succede per la parte retro.
      Il pezzo da sostituire è questo:

      this.impostazioni.immagine_retro_carta

      Ci potresti mettere una cosa tipo:

      this.carte[id].img

Rispondi

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.