La creazione di un progetto da zero in Visual Basic NET? Lo trovi su Opentraining.it
Corso di Visual Basic:
Quindicesima lezione - Campo Minato: il controllo Timer, le funzioni Rnd e Int(), le istruzioni Randomize e GoTo e la proprietà Tag (a cura di Giorgio Abraini)

Cominciamo dall'etichetta lblTempo, che dovrà misurare il trascorrere del tempo di gioco: la cosa più ovvia è aggiornarla ogni secondo, quindi dobbiamo impostare correttamente le proprietà del Timer; questo controllo misura evidentemente il trascorrere del tempo generando un evento Timer ogni volta che passano tot millisecondi.
Il numero di millisecondi trascorsi i quali viene generato l'evento è indicato dalla proprietà Interval: dato che noi vogliamo contare i secondi, sarà bene impostare questa proprietà a 1000 ms, cioè 1 secondo; in parole povere, quando il timer viene attivato comincerà a contare i millisecondi: quando arriva a 1000 genera l'evento Timer, reimposta a 0 il contatore e ricomincia a contare fino ad arrivare nuovamente a 1000, generando un altro evento Timer; e così via.
Il controllo Timer non deve essere attivo subito, ma deve cominciare a contare solo quando l'utente darà il via alla partita: quindi nella finestra delle proprietà impostate Enabled a false.
Cliccando due volte sul Timer, comparirà l'editor del codice sulla routine dell'evento Timer: qui dobbiamo inserire le istruzioni per aggiornare l'etichetta; banalmente, sarà sufficiente dichiarare una variabile statica che funzioni da contatore e scriverne il valore nell'etichetta:

Static i As Integer
i = i + 1
lblTempo.Caption = CStr(i)

La variabile i va dichiarata come Static perché deve tener conto dei secondi passati anche "al di fuori" della routine "timer1_timer": se non si usa la parola chiave Static, la variabile verrà creata ogni volta che l'evento timer viene generato, e di conseguenza assumerà sempre il valore zero.
Il timer, come ho detto, dovrà essere attivato quando comincia una nuova partita, quindi nella routine mnuNew_click bisognerà scrivere:

Timer1.Enabled = True

e corrispondentemente esso dovrà essere disattivato quando la partita finisce: ce ne occuperemo tra poco.
Ora viene la parte più difficile, scrivere il codice associato ai pulsanti, che costituiscono il gioco vero e proprio: innanzitutto bisogna decidere dove piazzare le mine; visto che le caselle sono 16, potremmo piazzare 3 mine.
Naturalmente non possiamo decidere noi direttamente dove metterle, ma dovremo affidarci al caso, usando quindi i numeri casuali: per generare i numeri casuali esiste una funzione, Rnd, che restituisce un numero (più o meno) casuale compreso tra 0 (incluso) e 1 (escluso), ovvero tra 0 e 0,99999.
Noi però dobbiamo decidere sotto quale pulsante mettere la mina, quindi dobbiamo avere un numero che ci dica qual è l'indice del pulsante in questione; e dal momento che i pulsanti sono 16, il loro indice andrà da 0 a 15, e pertanto abbiamo bisogno di un numero casuale compreso tra 0 e 15: ottenerlo è banalissimo, basta infatti moltiplicare il risultato della funzione Rnd per 16, e prenderne la parte intera:

Int(16 * Rnd)

La funzione Int() non fa altro che troncare la parte decimale di un numero restituendo solo la parte intera: se ad esempio Rnd restituisce 0.1, 16 * 0.1 = 1.6 e quindi int(1.6) = 1; visto che Rnd restituisce sempre un numero minore di 1, il prodotto 16 * Rnd sarà sempre minore di 16, ovvero compreso tra 0 e 15: proprio ciò che ci serve.
Dato che abbiamo bisogno di 3 mine, ci conviene fare un vettore di tre elementi, anziché dichiarare tre variabili distinte; quindi, nella sezione delle dichiarazioni del form, scriviamo:

Dim PosMine(2) As Integer

La posizione delle mine dobbiamo deciderla all'inizio di ogni partita: quindi in mnuNew_click, scriviamo:

Dim i As Integer 'contatore per il ciclo
Randomize Timer
For i = 0 To 2
PosMine(i) = Int(Rnd * 16)
Next i

L'istruzione Randomize serve ad inizializzare il generatore di numeri casuali: come saprete, i numeri "casuali" generati da un qualunque computer non sono veramente casuali, poiché sono generati in modo deterministico, seguendo regole ben precise; solo che queste regole sono tali da garantire, entro certi limiti, una sorta di casualità nei numeri generati, che per questo vengono definiti più correttamente "pseudo-casuali".
Ora, la funzione rnd calcola i numeri pseudo-casuali a partire da un numero base, che viene modificato dall'istruzione Randomize tramite il suo argomento: nel nostro caso questo argomento è il valore restituito dalla funzione Timer, che restituisce il numero di secondi trascorsi dalla mezzanotte del giorno corrente (la funzione Timer non c'entra nulla con il controllo Timer, né tantomeno con l'evento Timer); dato che questo valore è altamente aleatorio, anche il numero base utilizzato dalla funzione rnd cambia in modo abbastanza casuale, e quindi tale funzione calcola numeri un po' più casuali di quanto accadrebbe se si utilizzasse rnd senza Randomize.
Ora che abbiamo le posizioni delle mine, potremmo controllare, ogni volta che il giocatore preme un pulsante, se l'indice di quel pulsante appartiene al vettore PosMine: questo è un metodo alquanto laborioso e inefficiente, e sarebbe molto meglio se ogni pulsante sapesse già se sotto di esso si nasconde una mina oppure no. Per fare questo è sufficiente sfruttare una proprietà comune a quasi tutti i controlli, la proprietà Tag, che consente di memorizzare dati aggiuntivi utili per la funzionalità del controllo: nel nostro caso, ad esempio, potremmo associare ai pulsanti che nascondono una mina la stringa "mina", assegnandola alla proprietà Tag; quando il giocatore preme il pulsante, basterà controllare se la proprietà contiene la stringa "mina" per sapere se il gioco è terminato o se può continuare.
Ancora meglio, visto che le possibilità sono solo due (la mina c'è o non c'è), potremmo assegnare alla proprietà Tag direttamente un valore booleano che indichi l'eventuale presenza della mina; in altre parole, potremmo scrivere, all'interno del ciclo descritto sopra:

Mina(PosMine(i)).Tag = True

C'è però da considerare un'altra cosa: sarebbe comodo che ogni pulsante sapesse non solo se nasconde o meno una delle mine, ma anche quante altre mine ci sono sotto i pulsanti che lo circondano; in tal caso, nella proprietà Tag dovremmo scrivere il numero di mine circostanti ogni pulsante e, se una mina si trova proprio sotto il pulsante considerato, dovremmo assegnare un valore particolare. Ad es., se intorno al pulsante x ci sono due mine, potremmo scrivere x.Tag = 2; se intorno ad esso ce n'è una sola, scriveremmo x.Tag = 1; ma se la mina si trova proprio sotto x, non possiamo scrivere di nuovo x.Tag = 1, altrimenti si farebbe confusione: possiamo però scrivere x.Tag = -1, perché il numero di mine circostanti non può essere negativo, e quindi se la proprietà Tag contiene il valore -1, il pulsante saprà che la mina è sotto di lui, e non sotto qualche altro pulsante che lo circonda.
Un pericolo di cui una persona inesperta si accorgerebbe difficilmente è che diversi elementi del vettore PosMine potrebbero assumere valori uguali: questo dipende banalmente dal fatto che noi prendiamo in considerazione non l'intero numero casuale (moltiplicato per 16), ma solo la sua parte intera; se ad esempio la funzione rnd restituisse consecutivamente i valori 0.19 e 0.24, risulterebbe che Int(16 * 0.19) = Int(16 * 0.24) = 3, con la conseguenza che le nostre mine sarebbero in realtà due e non tre, perché due dei tre valori calcolati coincidono.
Per evitare questo rischio, è sufficiente controllare che il numero casuale appena estratto sia diverso da quelli già memorizzati: il modo più naturale di eseguire questo controllo sarebbe utilizzare un altro ciclo (ma di tipo diverso rispetto al For...Next), e siccome non ne ho ancora parlato, preferisco usare un metodo meno ortodosso ma di più semplice comprensione; si tratta di riscrivere la routine mnuNew_click in questo modo:

Dim t As Integer 'variabile temporanea per eseguire i controlli
Randomize Timer
PosMine(0)= Int(Rnd * 16)
Mina(PosMine(0)).Tag = -1
'Estrai:
t = Int(Rnd * 16)
If t = PosMine(0) Then
GoTo Estrai
Else
PosMine(1) = t
Mina(PosMine(1)).Tag = -1
End If
'EstraiDiNuovo:
t = Int(Rnd * 16)
If t = PosMine(0) Or t = PosMine(1) Then
Goto EstraiDiNuovo
Else
PosMine(2) = t
Mina(PosMine(2)).Tag = -1
End if

Quello che succede è abbastanza intuitivo: si estrae un numero casuale e lo si memorizza in PosMine(0); se ne estrae un altro e si controlla che sia diverso da quello precedente: se è diverso, lo si memorizza nell'elemento successivo del vettore (PosMine(1)), altrimenti se ne estrae un altro; la riestrazione è ottenuta attraverso l'istruzione Goto, che ordina al computer di interrompere la normale esecuzione del programma saltando a un altro punto del programma stesso, e precisamente al punto indicato da un'apposita etichetta.
Nel nostro caso le etichette sono due: "Estrai" e "EstraiDiNuovo"; la sintassi vuole che il nome delle etichette non abbia alcuno spazio al suo interno e sia seguito dai due punti ":". La riestrazione continua finchè non si trova un numero diverso da quello memorizzato in PosMine(0).
Poi si passa alla terza e ultima estrazione, che segue lo stesso schema logico, controllando questa volta che il numero casuale sia diverso da entrambi i valori precedentemente memorizzati, ovvero PosMine(0) e PosMine(1), grazie all'operatore logico Or.



Archivio:
Lezioni Commenta questa lezione Invia la tua guida Avviso per le nuove lezioni
Proponi un argomento

Visual Basic Italia© copyright 2000 - tutti i diritti riservati
E-mail:
vbitalia@libero.it