Battaglia Navale. Un gioco che insegna il Vba. - dal 04/09/04 pagina vista: volte

Questa volta torniamo ad occuparci di Random , ovvero come generare casualmente degli  intervalli  di celle che conterranno dei valori, all'interno di aree predefinite.

La Casualità, o Randomizzazione, si può ottenere solo su valori numerici, e non su testo. (vedi anche sul sito http://ennius.interfree.it, sezione vba, articolo "Estrarre dati casuali"). Riassumiamo velocemente comunque i concetti: un numero casuale si ottiene in vba con una semplice istruzione:

  • Int(x * Rnd) + 1   -  dove "x" sarà un numero che rappresenta il numero da randomizzare, e che rapresenta il limite massimo raggiungibile dal calcolo - 1, infatti restituirà un valore compreso tra zero (0) e il valore di "x" escluso. Poichè in vb i numeri e gli indici, iniziano con zero (non con 1). Se per esempio "x" fosse il numero 20, verrebbero considerati 20 numeri da casualizzare. Se il valore randomizzato su questi 20 numeri fosse 5, otterremo in realtà in restituzione il numero 4; iniziando infatti a contare da zero, 4 è il quinto numero. Per questo nella formula aggiungiamo + 1:  per restituirci un numero che corrisponda ad un valore così come siamo abituati a considerarlo.

I numeri ci servono anche per identificare le righe e le colonne quando usiamo la notazione Cells(numero riga, numero colonna). Randomizzando quindi questi numeri di identificazione cella, possiamo mirare a celle o intervalli i cui riferimenti risultano casuali. Leggendo quindi quante righe e quante colonne compongono una determinata area del foglio di lavoro, potremo usare questi numeri come valori da randomizzare.

Provando alcune routines, ne è venuto fuori un esercizio interessante da segnalare e che può essere spiegato usando un vecchio gioco: la Battaglia Navale. Tutti conoscono questo gioco: due tabelle formate ognuna da 100 celle: 10 righe su 10 colonne: in queste tabelle due avversari posizionano delle "navi" di dimensioni variabili, poi un giocatore alla volta, puntano ad una cella dell'avversario cercando di colpire una "nave". Quando tutte le navi dell'avversario sono state colpite e quindi affondate, il gioco finisce con un vincitore. Nella realtà del gioco, i due avversari nascondono le rispettive tabelle, su un foglio di lavoro usiamo l'accorgimento, per non vedere dove posiziona le "navi" l'avversario, di usare su campo bianco, i caratteri (che rappresentano le navi) di colore bianco.

Lo svolgimento del gioco è semplice : giocheremo noi contro il computer:  selezioniamo una cella nell'area del nemico: se questa cella contiene un valore, la cella verrà colorata in rosso, altrimenti in grigio; ad ogni nostro "colpo" il computer reagirà colpendo una cella nella nostra tabella, ed anche qui coloreremo le celle. Vediamo un immagine:

Ho fissato, ma si possono variare, le dimensioni delle 5 navi che ogni giocatore avrà:

  • 1 unità da due caselle

  • 2 unità da 3 caselle

  • 1 unità da 4 caselle

  • 1 unità da 5 caselle

E' inoltre possibile possibile decidere il posizionamento delle unità : in verticale o in orizzontale; io ne ho previste 4 in verticale e una in orizzontale per il giocatore, mentre per l'avversario (il computer) ne ho previste 3 in verticale, una in orizzontale, ed una la cui posizione viene randomizzata: o verticale o orizzontale; non sapremo come questa verrà posizionata fino a chè, colpendola, non la individueremo.

In questo gioco, è il giocatore avvantaggiato: non ho previsto per il computer nessuna particolare precisione: le nostre celle verranno colpite in maniera casuale, mentre noi potremo decidere, una volta colpita una nave, se proseguire nelle celle vicine per afforndare l'unità.

Invito i lettori a sviluppare le istruzioni per migliorare la precisione del computer. I loro lavori saranno messi nella sezione " i vostri lavori".

Vediamo ora i passaggi più importanti usati nelle routines.

  • Sub GeneraNemico() - questa routine serve a pulire l'area avversaria, generando casualmente le coordinate relative alla posizione delle celle che conterranno le unità, e all'interno dell'intervallo così reperito, inseriamo il carattere asterisco (*). Potevamo inserire un numero, ma per noi sarebbe stato anche troppo facile determinare dal numero colpito, la lunghezza della "nave". Riporto sotto solo l'inizio delle prime istruzioni, poi per ogni nave le istruzioni sono simili, cambieranno solo i valori che determinano la lunghezza della nave e il loro posizionamento. Scaricando il file potrete leggere tutte le routines.

Sub generaNemico()
Dim CL As Object 
'dimensioniamo CL come oggetto: sarà una cella

'sotto: impostiamo una tantum con Nemico, l'area avversaria
Set Nemico = ActiveSheet.Range("O5:X15")
Nemico.ClearContents 
'puliamo l'area Nemico
Nemico.Cells.Interior.ColorIndex = xlNone
 'impostiamo il colore delle celle al naturale
'sotto: con "R" contiamo quante sono le righe nell'area Nemico, e con "C" quante colonne. 'Essendo numeri fissi, potevamo semplicemente scrivere R = 10 e C = 10, ma così vediamo come 'poter determinare questi valori in routine che contino righe e colonne in aree modificabili
R = Nemico.Rows.Count   
C = Nemico.Columns.Count
10:
 'indice di riga istruzioni
Randomize 
'con Randomize aiutiamo a generare dei numeri casuali, non ripetitivi

'sotto: con "rig" randomiziamo il valore di "R", aggiungendo 4 perchè la tabella inizia dalla riga 'numero 5, lo stesso facciamo con le colonne "C", aggiungendone 14 perchè la tabella inizia dalla 'colonna O, che è la quindicesima
rig = Int((R * Rnd) + 1) + 4
col = Int((C * Rnd) + 1) + 14

'sotto: ora impostiamo con "nave1" il primo intervallo di celle, che inizierà dalla cella(rig, col) e 'comprenderà la cella(rig + tre celle, col): in questo modo determiniamo un intervallo di celle 'formato da 4 celle, quindi realiziamo la prima delle 5 navi. Con lo stesso sistema, variando il 'numero che identifica l'ultima cella, determineremo la composizione delle altre "navi".
Set nave1 = Range(Cells(rig, col), Cells(rig + 3, col))

'sotto: per evitare che vengano generati intervalli che fuoriescono dall'area (tabella Nemico), si 'controlla che l'ultima riga dell'intervallo "nave1" non superi la riga 15, se ciò avvenisse, 'rimandiamo con GoTo 10, alla generazione di un altro intervallo.
If nave1.Offset(3, 0).Row > 15 Then GoTo 10

'sotto: altrettanto facciamo, per evitare di sovrascrivere le "navi", controlando con un ciclo For 'Each, che ogni cella di "nave1" non contenga già valori, altrimenti si ritorna a 10
For Each CL In nave1
If CL.Value <> "" Then GoTo 10
Next

'terminati questi due controlli, se "nave1" è gestibile, si riempono le celle con l'asterisco. Questo 'formerà la nostra "nave"
nave1.Cells.Value = "*"

Ovviamente a seguire ci saranno le istruzioni, simili a questa sopra, per generare una dopo l'altra, le 5 navi. Lo stesso sistema lo useremo per la generazione delle "navi" nella nostra area; solo che in questo caso, ho usato dei numeri al posto degli asterischi: ogni nave un numero in successione, partendo da 1.

Unica variante che segnalo in questo articolo, è la routine che serve a determinare se una "nave" verrà posizionata in verticale o in orizzontale: anche qui usiamo una randomizzazione impostata sul numero 0 e 1; se uscirà o, seguiamo un orientamento, se esce 1 ne seguiamo un'altro. Vediamo il particolare:

'........omissis

30:   'indice di riga istruzioni

'sotto: si crea una randomizzazione basata su 2 soli numeri, quindi 0 e 1 e questo valore lo 'assegniamo alla variabile "come"
Randomize
come = Int(2 * Rnd)

'sotto: se "come" è uguale a 0, seguiamo questa routine, altrimenti passiamo a Else. In questa 'condizione facciamo randomizzare un'intervallo verticale, per una "nave" a 5 celle, le spiegazioni 'sono ugali a quelle viste sopra:
If come = 0 Then
Randomize
rig = Int((R * Rnd) + 1) + 4
col = Int((C * Rnd) + 1) + 14
Set nave3 = Range(Cells(rig, col), Cells(rig + 4, col))
If nave3.Offset(4, 0).Row > 15 Then GoTo 30
For Each CL In nave3
If CL.Value <> "" Then GoTo 30
Next
nave3.Cells.Value = "*"


Else
35:   
'indice di riga istruzioni
Randomize
rig = Int((R * Rnd) + 1) + 4
col = Int((C * Rnd) + 1) + 14

'sotto: in queste istruzioni anzichè generare l'intervallo aggiungendo il numero di quante celle in più 'considerare rispetto alle righe, aggiungiamo il numero alle colonne; questo è sufficiente a generare 'un'intervallo orizzontale. bisogna poi modificare il successivo controllo per non uscire fuori dalla 'tabella, indicando quale è la colonna oltre la quale non si può andare, cioè la X (la 24esima)
Set nave3 = Range(Cells(rig, col), Cells(rig, col + 4))
If nave3.Offset(0, 4).Column > 24 Then GoTo 35
For Each CL In nave3
If CL.Value <> "" Then GoTo 35
Next
nave3.Cells.Value = "*"
End If

'...........omissis

Va da se che potrete creare la randomizzazione dell'orientamento per ogni "nave", io l'ho fatta solo su una, per fornire un possibile esempio.

Ora vediamo cosa avviene quando selezioniamo (spariamo un colpo) una cella del "Nemico". Usiamo l'evento Worksheet_SelectionChange, unito all'uso del metodo Intersect per limitare l'esecuzione delle istruzioni solo all'area indicata.

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Intersect(Target, Range("O5:X15")) Is Nothing Then
Exit Sub
End If
If ActiveCell <> "" Then
'se la cella selezionata contiene dati, coloriamo la cella di rosso
ActiveCell.Interior.ColorIndex = 3
MsgBox "HAI COLPITO UNA UNITA'" 
'e si avvisa con questo messaggio
Else
  'altrimenti
ActiveCell.Interior.ColorIndex = 15  
'la coloriamo di grigio
End If

Colpito 
'indi si chiama la macro "Colpito", che agirà sulla nostra area: è questa la risposta del 'computer

End Sub

 

Ora vediamo la macro "Colpito", che serve a restituire il colpo, e che colpirà una cella randomizzata, ma nella nostra area, le istruzioni a questo punto sono comprensibili e non le commento :

Sub Colpito()
Set Amico = ActiveSheet.Range("B5:K15")
Ri = Amico.Rows.Count
Co = Amico.Columns.Count
10:
Randomize
riga = Int((Ri * Rnd) + 1) + 4
colo = Int((Co * Rnd) + 1) + 1

If Cells(riga, colo).Interior.ColorIndex = 3 Or _
Cells(riga, colo).Interior.ColorIndex = 15 Then GoTo 10

If Cells(riga, colo) <> "" Then
Cells(riga, colo).Interior.ColorIndex = 3
MsgBox "SONO STATO COLPITO"

Else
Cells(riga, colo).Interior.ColorIndex = 15
End If
End Sub

Questa ultima routine è semplice, e non ci penalizza troppo, lasciandoci vincere con facilità. Ripeto l'invito a quanti vorranno cimentarsi nel realizzare variazioni che consentano al computer, una volta identificata una cella col fondo rosso, e con un determinato valore, di continuare a colpire nelle vicinanze fino all'affondamento della "nave" rappresentata da tutte le celle con lo stesso numero. Ho già suggerito anche troppo....

Esiste poi l'altra routine Sub GeneraAmico() che troverete nel file, e che si basa sulle istruzioni viste in GeneraNemico.

 

File scaricabile e consultabile  :    BattleShip.zip     15  Kb.  

 

Buon lavoro.

prelevato sul sito www.ennius.altervista.org