Giocare e vincere con Excel

Scriviamo un gioco interattivo: Snake

Snake è un simpatico serpente che si muove all’interno di un campo di celle dove trova ostacoli e cibo. Ogni volta che mangia un boccone di cibo, oltre a fare punti, il nostro cresce di dimensioni. Lo scopo del gioco è mangiare quanto più cibo possibile senza andare a sbattere contro gli ostacoli e senza mangiarsi la coda.Per costruire questo gioco in Excel utilizzeremo lo stesso principio visto nel labirinto: coloreremo le celle in tinte diverse a seconda del loro significato. Le celle nere saranno gli ostacoli, le celle blu il cibo e le celle amaranto il nostro serpente che si sposta. In Figura 2 è mostrato lo schema iniziale di gioco.

Figura 2

Figura 2 – La posizione iniziale di Snake.
Il serpente si muove da sinistra verso destra

Lanciando l’esecuzione della macro di gioco il serpente si sposta verso destra. È possibile controllare il serpente con i tasti freccia come nell’esempio precedente fino a mangiare il cibo (Figura 3).

Figura 3

Figura 3 – Snake, guidato dal giocatore, si avventa sul cibo

Il punto è: come facciamo a muovere il serpente nella griglia? Il principio utilizzato qui è molto semplice: se il serpente non mangia il cibo allora bisogna colorare di amaranto la cella nella direzione in cui il serpente si muove e cancellare l’ultima casella della coda. Possiamo semplicemente aggiungere una casella all’inizio del serpente e toglierne una alla fine: poiché tutte le caselle che rappresentano il serpente sono uguali tra loro, questa procedura crea l’illusione che tutte le celle si spostino di una posizione nella direzione indicata. Il punto importante da rilevare è che bisogna memorizzare le coordinate di tutte le caselle del serpente, dato che prima o poi tutte verranno cancellate. Se invece il serpente mangia qualcosa allora basterà mettere una casella amaranto davanti al serpente senza cancellare l’ultima in fondo. L’effetto netto sarà l’allungamento del serpente di una casella.Per memorizzare le coordinate di tutte le caselle del serpente useremo un array circolare realizzato su un secondo foglio di supporto. Analizziamo il codice. Per prima cosa dobbiamo definire un gran numero di variabili per strutturare in modo completo il gioco:

Dim XStart As Integer    ' angolo in alto a sinistra
                         ' del campo di gioco
Dim YStart As Integer    ' angolo in alto a sinistra
                         ' del campo di gioco
Dim XMax As Integer      ' angolo in basso a destra
                         ' del campo di gioco
Dim YMax As Integer      ' angolo in basso a destra
                         ' del campo di gioco
Dim L As Integer         ' lunghezza serpente
Dim Area As Long         ' dimensioni area di gioco
Dim S As Worksheet       ' foglio contenente il gioco
Dim C As Worksheet       ' foglio contenente le coordinate
                         ' di tutti i pezzi del serpente
Dim DIR As Range         ' direzione del serpente
Dim CURDIR As String     ' direzione corrente del serpente
Dim STATUS As Range      ' stato del gioco
Dim SnakeStart As Long   ' casella su C della casella che
                         ' contiene le coordinate della testa
Dim SnakeEnd As Long     ' casella su C della casella che
                         ' contiene le coordinate della coda
Dim SnakeHeadX As Integer ' nuova posizione della testa
Dim SnakeHeadY As Integer ' nuova posizione della testa
Dim TIMEDELAY As Double   ' ritardo tra due eventi consecutivi
                          ' (velocità del gioco)
Dim XCoord As Integer     ' coordinate del cibo
Dim YCoord As Integer     ' coordinate del cibo
Dim ColorSnake As Integer ' colore del serpente
Dim ColorFood As Integer  ' colore del cibo
Dim Step As Long          ' numero di passi effettuati dal serpente
Dim Punti As Long         ' punteggio del gioco raggiunto
Dim PuntiCELL As Range    ' cella contenente il punteggio
Dim Extrapunti As Integer ' Estrapunteggio
Dim ExtraPuntiCell As Range ' cella contenente l'extrapunteggio
Dim MaxExtraPunti As Integer ' massimo numero di extrapunti concessi

Scorrendo le variabili si trova di tutto: le coordinate del campo di gioco sul foglio di Excel, la lunghezza del serpente, la direzione di movimento del serpente stesso, la posizione sul foglio di supporto della cella rappresentativa della testa e della coda, i colori del serpente e del cibo e altro ancora. Vale la regola che è sempre meglio abbondare con le variabili anziché cercare di complicare il codice per metterne di meno. È necessario infatti che il ciclo che genera l’illusione del movimento si ripeta il più velocemente possibile, dunque al suo interno si deve evitare di effettuare calcoli per non rallentare inutilmente il codice. Tutto ciò che si risparmia in termini di calcoli ripetuti si guadagna in fluidità e giocabilità.Definiamo a questo punto le funzioni e le costanti per la gestione della tastiera:

Declare Function GetAsyncKeyState Lib "User32.dll"
 (ByVal vKey As Long) As Long

Const VK_LEFT As Long = 37
Const VK_DOWN As Long = 40
Const VK_RIGHT As Long = 39
Const VK_UP As Long = 38
Const VK_Z As Long = 90
Const VK_SHIFT As Long = 16
Const VK_ESCAPE As Long = 27
Const VK_RETURN As Long = 13

Ora inizializziamo il gioco definendo le variabili, pulendo l’area di gioco, creando il nuovo serpente e collocando il cibo in una posizione casuale nell’area di gioco:

Sub init()
    Dim i As Integer
    XStart = 4    'definizione della posizione
    YStart = 7    'dell'are di lavoro
    XMax = 43
    YMax = 46
    
    Area = (XMax - XStart + 1) * (YMax - YStart + 1)
    Set S = Worksheets("S")    'continele le posizioni
                               'delle caselle del serpente
    Set C = Worksheets("SNAKE") 'contiene l'area di gioco
    Set STATUS = C.Cells(6, 3)  'cella contenente un valore
                                'indica se il gioco è in corso o meno
    Set DIR = C.Cells(5, 5)     'cella contenente la direzione
                                'del serpente
    Set PuntiCELL = C.Cells(3, XMax + 3) 'cella contenente il punteggio
    Set ExtraPuntiCell = C.Cells(4, XMax + 3) 'cella contenente
                                              'l'extrapunteggio
    
    STATUS.Value = ""              'il gioco è in corso
    
    'copiamo uno schema vuoto e pulito: azzeriamo l'area di gioco
    
    Sheets("SNAKE (2)").Select
    Range("C6:AR47").Select
    Selection.Copy
    Sheets("SNAKE").Select
    Range("C6").Select
    ActiveSheet.Paste
    
    'inizializzazione del serpente
    L = 9 'lunghezza serpente iniziale
    XCoord = Int((XMax + XStart) / 2) - L
    YCoord = Int((YMax + YStart) / 2)
    
    'definizione dei colori degli elementi del gioco
    ColorSnake = 9 'colore amaranto
    ColorFood = 11 'colore blu
        
    For i = 1 To L
        S.Cells(i, 1).Value = XCoord
        S.Cells(i, 2).Value = YCoord
        C.Cells(YCoord, XCoord).Interior.ColorIndex = ColorSnake
        XCoord = XCoord + 1
    Next
    SnakeHeadX = XCoord - 1
    SnakeHeadY = YCoord
    DIR.Value = "dx"
    CURDIR = "dx"
    C.Cells(5, 5).Value = DIR
    SnakeStart = 8
    SnakeEnd = 0
    TIMEDELAY = 0.1 'velocità del gioco
    'inserisci cibo in posizione casuale
    'cerca posizione che non contiene nulla
    'prima di posizionare il cibo
    Do
        XCoord = Int(((XMax - XStart + 1) * Rnd) + 1) + XStart
        YCoord = Int(((YMax - YStart + 1) * Rnd) + 1) + YStart
    Loop Until C.Cells(YCoord, XCoord).Interior.ColorIndex = xlNone
    C.Cells(YCoord, XCoord).Interior.ColorIndex = ColorFood
    Step = 0
    Punti = 0
    MaxExtraPunti = 30
    Extrapunti = MaxExtraPunti
    PuntiCELL.Value = Punti
    ExtraPuntiCell = Extrapunti
    DIR.Select
    DIR.Value = "dx"
    STATUS.Value = "On"
    Call snake  'rutine di gestione del gioco
    
End Sub

Il codice è molto semplice. Genera in modo coerente tutte le variabili. Definisce e pulisce l’area di gioco, posiziona il serpente da sinistra verso destra e indica che il serpente deve muoversi a destra nel prossimo passo, quindi cerca una casella vuota dove poter collocare il cibo e lo posiziona. A questo punto attiva il gioco e fa partire la routine di gestione del serpente:

Sub snake()
Dim Start, Delay
Dim Moved As Boolean

Do While STATUS.Value = "On"
    Start = Timer            'Determina l'istante di partenza
    Delay = Start + TIMEDELAY
    Moved = False
    Do
    If Timer > Delay Then
    If Not Moved Then
          Moved = True
          Extrapunti = Extrapunti - 1
          If Extrapunti > 0 Then
            ExtraPuntiCell.Value = Extrapunti
          Else
            ExtraPuntiCell.Value = ""
            Extrapunti = 0
          End If
          
           If GetAsyncKeyState(VK_LEFT) <> 0 Then
               DIR.Value = "sx"
              ElseIf GetAsyncKeyState(VK_RIGHT) <> 0 Then
               DIR.Value = "dx"
              ElseIf GetAsyncKeyState(VK_UP) <> 0 Then
               DIR.Value = "su"
              ElseIf GetAsyncKeyState(VK_DOWN) <> 0 Then
               DIR.Value = "giu"
            End If
          
          Select Case DIR.Value
            Case "dx"
                If CURDIR <> "sx" Then
                    SnakeHeadX = SnakeHeadX + 1
                    CURDIR = "dx"
                Else
                    SnakeHeadX = SnakeHeadX - 1
                    CURDIR = "sx"
                End If
            Case "su"
                If CURDIR <> "giu" Then
                    SnakeHeadY = SnakeHeadY - 1
                    CURDIR = "su"
                Else
                    SnakeHeadY = SnakeHeadY + 1
                    CURDIR = "giu"
                End If
            Case "giu"
                If CURDIR <> "su" Then
                    SnakeHeadY = SnakeHeadY + 1
                    CURDIR = "giu"
                Else
                    SnakeHeadY = SnakeHeadY - 1
                    CURDIR = "su"
                End If
            Case "sx"
                If CURDIR <> "dx" Then
                    SnakeHeadX = SnakeHeadX - 1
                    CURDIR = "sx"
                Else
                    SnakeHeadX = SnakeHeadX + 1
                    CURDIR = "dx"
                End If
            End Select
            SnakeStart = (SnakeStart + 1) Mod Area
            S.Cells(SnakeStart + 1, 1).Value = SnakeHeadX
            S.Cells(SnakeStart + 1, 2).Value = SnakeHeadY
            'verifica scontro
            If C.Cells(SnakeHeadY, SnakeHeadX).Interior.ColorIndex = 1
 Or C.Cells(SnakeHeadY,
SnakeHeadX).Interior.ColorIndex = ColorSnake Then
                STATUS.Value = ""
                GoTo FineGioco
            ElseIf C.Cells(SnakeHeadY,
SnakeHeadX).Interior.ColorIndex = ColorFood Then
                  C.Cells(SnakeHeadY,
SnakeHeadX).Interior.ColorIndex = ColorSnake
                      'inserisci cibo
                      Punti = Punti + 1 + Extrapunti
                      Extrapunti = Extrapunti + MaxExtraPunti
                      PuntiCELL.Value = Punti
                  Do
                    XCoord = Int(((XMax - XStart + 1) * Rnd)
+ 1) + XStart
                    YCoord = Int(((YMax - YStart + 1) * Rnd)
+ 1) + YStart
                  Loop Until C.Cells(YCoord, XCoord).Interior.ColorIndex = xlNone
                  C.Cells(YCoord, XCoord).Interior.ColorIndex = ColorFood

            Else
                  C.Cells(S.Cells(SnakeEnd + 1, 2).Value,
S.Cells(SnakeEnd + 1, 1).Value).Interior.ColorIndex =
xlNone
                  SnakeEnd = (SnakeEnd + 1) Mod Area
                  C.Cells(SnakeHeadY,
SnakeHeadX).Interior.ColorIndex = ColorSnake
            End If
        End If
           If GetAsyncKeyState(VK_ESCAPE) <> 0 Then GoTo
FineGioco
        
    End If
    Loop Until Moved
Loop
FineGioco:
temp = MsgBox("Il tuo punteggio è:" & Punti, vbOKOnly,
"Snake!")
End Sub

Questa routine si può dividere in più parti. Vediamo un ciclo esterno che verifica lo stato del gioco:

Do While STATUS.Value = “On”
…
Loop

Al suo interno viene ripetuto indefinitamente e in modo ritmico con la funzione Timer:

Start = Timer 
Delay = Start + TIMEDELAY
Moved = False
Do 
   If Timer > Delay Then 
      … 
   End if
Loop Until Moved

Questo ciclo è leggermente differente rispetto a quello presentato nel paragrafo che descrive la funzione Timer. Il suo scopo è quello di eseguire il contenuto dell’if centrale una volta sola a ogni mossa, cioè una sola volta per ogni incremento del timer di TIMEDELAY secondi.
La variabile booleana Moved gestisce il flusso del codice permettendo che l’if centrale venga eseguito una volta ogni TIMEDELAY secondi. Questa parte del codice muove di un passo il serpente. Per prima cosa deve verificare se un tasto è stato premuto:

If GetAsyncKeyState(VK_LEFT) <> 0 Then 
      DIR.Value = “sx” 
   ElseIf GetAsyncKeyState(VK_RIGHT) <> 0 Then 
      DIR.Value = “dx” 
   ElseIf GetAsyncKeyState(VK_UP) <> 0 Then 
      DIR.Value = “su” 
   ElseIf GetAsyncKeyState(VK_DOWN) <> 0 Then 
      DIR.Value = “giu”
End If

Il codice è identico a quanto già visto in precedenza. In questo caso il risultato dell’analisi della tastiera viene memorizzato in una cella del foglio di lavoro, rappresentata dall’oggetto DIR (si veda la funzione Init precedente).
Ora che abbiamo recuperato la direzione voluta dal giocatore, calcoliamo la nuova posizione della testa del serpente:

Select Case DIR.Value 
   Case “dx” 
      If CURDIR <> “sx” Then 
         SnakeHeadX = SnakeHeadX + 1 
         CURDIR = “dx” 
      Else 
         SnakeHeadX = SnakeHeadX - 1 
         CURDIR = “sx” 
      End If
…
End Select

Il codice verifica se la direzione voluta dal giocatore è “dx” e, in questo caso, prima di modificare la direzione mandando il serpente verso destra verifica che il serpente non si stia muovendo verso sinistra. In questo caso, infatti, farebbe tornare il serpente sui suoi passi, il che non è concesso dal gioco: solo se la direzione attuale non è “sx” gira il serpente nella direzione “dx”, altrimenti lo lascia proseguire verso “sx”. Lo stresso identico controllo viene eseguito per le altre tre direzioni.
Memorizziamo ora la nuova posizione della testa nell’array circolare:

SnakeStart = (SnakeStart + 1) Mod Area
S.Cells(SnakeStart + 1, 1).Value = SnakeHeadX
S.Cells(SnakeStart + 1, 2).Value = SnakeHeadY

La funzione Mod consente di realizzare l’array corcolare senza sforzo. Con questo comando SnakeStart si incrementa sempre di uno; quando raggiunge la dimensione massima (Area) allora ricomincia da 0 restando sempre confinato tra i valori 0 e Area-1.
Le coordinate della testa del serpente verranno memorizzate nelle celle con riga da 1 a Area.Verifichiamo lo scontro della testa con un ostacolo (colore 1) o con se stesso:

If C.Cells(SnakeHeadY, SnakeHeadX).Interior.ColorIndex = 1 Or C.Cells(SnakeHeadY,
 SnakeHeadX).Interior.ColorIndex = ColorSnake Then 
      STATUS.Value = “” 
      GoTo FineGioco

In questo caso annulliamo lo stato del gioco e terminiamo la partita portando l’esecuzione alla posizione del codice indicata con FineGioco.
In caso contrario (il serpente non è morto) verifichiamo se c’era del cibo nella posizione ora occupata dalla testa:

ElseIf C.Cells(SnakeHeadY, SnakeHeadX).Interior.ColorIndex = 
ColorFood Then

In questo caso il codice colora il cibo con la tinta della testa (il serpente mangia il cibo), incrementa il punteggio e inserisce un nuovo boccone di cibo:

C.Cells(SnakeHeadY, SnakeHeadX).Interior.ColorIndex =
ColorSnake 
      ‘inserisci cibo 
      Punti = Punti + 1 + Extrapunti 
      Extrapunti = Extrapunti + MaxExtraPunti 
      PuntiCELL.Value = Punti 
   Do 
      XCoord = Int(((XMax - XStart + 1) * Rnd) + 1) +
      XStart 
      YCoord = Int(((YMax - YStart + 1) * Rnd) + 1) +
      YStart 
   Loop Until C.Cells(YCoord, XCoord).Interior.ColorIndex =
   xlNone 
C.Cells(YCoord, XCoord).Interior.ColorIndex = ColorFood

Infine, se il serpente non si è scontrato con gli ostacoli o con se stesso e non ha mangiato cibo, significa che si è mosso in una casella vuota. Dunque bisogna semplicemente spostarlo di una casella cancellando l’ultimo pezzo di coda:

Else 
      C.Cells(S.Cells(SnakeEnd + 1, 2).Value, 
      S.Cells(SnakeEnd + 1, 1).Value).Interior.ColorIndex =
      xlNone
      SnakeEnd = (SnakeEnd + 1) Mod Area 
   C.Cells(SnakeHeadY, SnakeHeadX).Interior.ColorIndex =  
   ColorSnake 
End If

In tutto questo giro le variabili SnakeEnd e SnakeStart tengono traccia delle coordinate della testa e della coda all’interno dell’array circolare.La routine di gestione del serpente è terminata. In essa vi è anche la gestione della variabile ExtraPunti, di cui non abbiamo parlato. Lasciamo al lettore la curiosità di verificare direttamente sul gioco qual è lo scopo di questa variabile.


xxx

Gianclaudio Floria - Autore top seller su Excel per Edizioni FAG, collabora con Microsoft ed è il fondatore del portale www.excelling.it
Andrea Terzaghi - Ingegnere con esperienza pluriennale nell'uso lavorativo e ludico di Excel; condivide trucchi e segreti sul suo sito www.terzaghi.it

Articolo tratto da:
Gianclaudio Floria, Andrea Terzaghi, Giocare e vincere con Excel, Assago (Milano), FAG, 2006.


In partnership con Fag
**
**
**
**

Risorse