GESTIONE DEGLI STATI
Olà! Oggi risolverò un problema che ritengo sia molto sentito agli inizi: la gestione degli stati di un programma.
Supponiamo di avere nel nostro gioco tre stati:
- Menu
- Gioco
- Pausa
La prima idea che ci verrebbe sarebbe questa: teniamo una variabile chiamata “stato” e la gestiamo in questo modo: int stato=0;
<programma>
Codice:
switch (stato)
{
case 0: //gioco normale;
break;
case 1: //menu;
break;
case 2://pausa
break;
}
Questo può andare bene per un po’, ma con la crescita della complessità, con possibili intersezioni tra vari casi, e magari anche dei sottostati (ad esempio quando nel menu passiamo il mouse su un tasto, e vogliamo che venga animato) il tutto diverrà decisamente troppo complicato.
Come possiamo fare allora? Pensiamo ad un sistema organizzato in questo modo: abbiamo una pila di stati, e quello che faremo sarà eseguire quello in cima a questa pila.
Quando sarà necessario, metteremo uno stato in cima alla pila, in modo che venga eseguito, e quando questo stato non ci servirà più,
lo toglieremo, così ricomincerà ad essere eseguito quello che ci stava sotto.
Facciamo un esempio:
Supponiamo che il nostro programma debba funzionare così: appena lanciato, lancia il menù. Da questo menù potremo scegliere se giocare od uscire (c'erano dei giochi per console dove c'era solo "gioca" ).
Una volta nel gioco, premendo ESC sarà possibile tornare al menù, e premendo “I” aprirà la pagina del nostro inventario.
Dall'inventario, premendo ESC si tornerà al gioco.
Vediamo come organizzare questa cosa con il sistema spiegato sopra:
PILA:
MENU -> In esecuzione
Ora, nostro malgrado, qualcuno preme il tasto "Gioca". Accade questo:
PILA:
GIOCO -> In esecuzione
MENU
Notiamo che ora il menù è sotto il gioco, poiché questo è stato inserito in cima. Il gioco, essendo in cima, viene eseguito.
Ora qualche uomo dalla scarsa morale certamente vorrà tornare al menù, premendo ESC:
PILA:
MENU -> In esecuzione
Il gioco è stato eliminato, e siamo nel menù. Questa è una situazione teorica, per mostrare come funzionano gli stati; in realtà il menù dovrebbe essere diverso, per adattarsi al fatto che siamo in gioco.
Ora premendo di nuovo il tasto "Gioco" torniamo al gioco:
PILA:
GIOCO -> In esecuzione
MENU
Di certo ora qualche protervo marrano vorrà vedere l'inventario, premendo I:
PILA:
INVENTARIO -> In esecuzione
GIOCO
MENU
Ora dovremmo iniziare a capire la faccenda. Quando avremo finito con l'inventario, tornerà il gioco perchè è sotto.
Vediamo come fare tutto ciò con una comoda classe C++.
Codice:
class jManagerStati
{
struct stato
{
void (*Funzione)(void *Ptr,long Scopo);
stato *prossimo;
stato()
{
Funzione=NULL;
prossimo=NULL;
}
~stato()
{
delete prossimo;
}
};
protected:
stato *jstackstati;
public:
jManagerStati();
~jManagerStati();
void Push(void (*Funzione)(void *ptr,long Scopo),void *Dataptr);
BOOL Pop(void *Dataptr=NULL);
void PopAll(void *Dataptr=NULL);
BOOL Process(void *Dataptr=NULL);
};
La struttura di questa classe è la tipica di una pila, tranne per il metodo "Process".
Notiamo che la “Pop” non restituisce altro che un BOOL. Questo perché a noi non interessa la funzione, poiché è un dato a noi ben noto e sempre reperibile; l’unica cosa che ci interessa è che venga tolto.
La “j” davanti al nome della classe sta per “July”, il software di cui questa classe fa parte.
Vediamo l’implementazione:
Codice:
jManagerStati::jManagerStati()
{
jstackstati=NULL;
}
jManagerStati::~jManagerStati()
{
PopAll();
}
BOOL jManagerStati::Pop(void *Dataptr)
{
stato *tempptr;
if ((tempptr= jstackstati) !=NULL)
{
jstackstati->Funzione(Dataptr,SCOPOFINE);
jstackstati=tempptr->prossimo;
tempptr->prossimo=NULL;
delete tempptr;
}
if (jstackstati==NULL)
return FALSE;
return TRUE;
}
void jManagerStati::PopAll(void *Dataptr)
{
while (Pop(Dataptr)==TRUE);
}
BOOL jManagerStati::Process(void *Dataptr)
{
if (jstackstati==NULL)
return FALSE;
jstackstati->Funzione(Dataptr,SCOPOFRAME);
return TRUE;
}
void jManagerStati::Push(void (*Funzione)(void *ptr,long Scopo),void *Dataptr)
{
stato *tempptr;
if (Funzione!=NULL)
{
tempptr=new stato;
tempptr->Funzione=Funzione;
tempptr->prossimo=jstackstati;
jstackstati=tempptr;
tempptr->Funzione(Dataptr,SCOPOINIT);
}
}
Noterete che ci sono tre costanti che non ho scritto in questa trattazione, e queste sono:
SCOPOINIZIO
SCOPOFINE
SCOPOFRAME
E rappresentano lo scopo per il quale la funzione viene chiamata.
Notere infatti che le funzioni vengono chiamate passando SCOPOINIT nella Push, SCOPOFINE nella Pop e SCOPOFRAME nella Process.
Starà a voi, nell’implementazione delle vostre funzioni, il compito di controllare il parametro “Scopo” ed agire di conseguenza.
Mi preme dire che l’articolo è ispirato ad un articolo di Jim Adams, che mostrava questa idea per la gestione degli stati[1].
Alla prossima!
Alessandro Monopoli
Biografia:
[1] Programming Role Playing Games with DirectX, J. Adams.