giovedì 1 marzo 2012

Risolutore "Solitario Klondike", codice sorgente e spiegazione.

Per completare il precedente post, ecco questa parte dedicata al codice.

Il “Risolutore solitario Klondike” è stato scritto in C++, con alcune parti scritte in C (è questa la potenza e versatilità del C++…mantenere la compatibilità col caro vecchio C), soprattutto quelle parti di codice inerenti all’interfaccia grafica.
La collezione di classi principale è in C++ standard quindi OS indipendente, le parti di codice della GUI sono molto limitate. Quindi il programma è stato pensato per funzionare su Windows (Win32) e Linux(GTK+/Gnome).
Analizziamo prima il cuore del programma, ovvero la collezione di classi OS indipendente che creano la parte “logica” dell’applicazione,
Comincio a presentare la gerarchia, e poi analizzo le classi una per una.
 e invece questa è la classificazione fatta da me dei gruppi di carte che sono sul tavolo:
p1,p2,p3 e p4 sono di tipo Pila.
master è di tipo MazzoMaster.
terra è di tipo GruppoDiCarte.
 s2,s3,s4,s5,s6 ed s7(ovvero le carte sotto le scale, coperte) sono di tipo GruppoDiCarte.
g1,g2,g3,g4,g5,g6,g7 (ovvero le scale) sono di tipo GruppoOrdinato.
La classe fondamentale di cui si servono tutte le altre è Carta, di cui riporto il file "Carta.h" a cui appartiene:



#pragma once
#include 
#include 
using namespace std;
enum Seme {
 CUORI,QUADRI,PICCHE,FIORI,
};
enum Colore {
 ROSSO,NERO,
};
class Carta {
friend ostream& operator<<(ostream&,Carta&);
public:
 Carta(unsigned int,Seme,string);
 Carta(Carta&);
 unsigned int getValore() const;
 Seme getSeme() const;
 Colore getColore() const;
 void stampa() const;
 string getResource() const;
private:
 unsigned int valore;
 Seme seme;
 string risorsa;
 
 string getStringaSeme() const;
};
La classe presenta due costruttori, uno che crea la carta con il suo valore e il suo seme, e un altro che è un costruttore di copie (che comunque potrei togliere perché non ne faccio uso). Da notare l'importante metodo string getResource() const; che restituisce il percorso del file che contiene l'immagine associata alla carta, scelta appositamente in base al valore e il seme. L'overloading del'operatore << non fa altro che passare una sua stringa rappresentativa a uno stream (per esempio "2 di cuori"). Adesso presento la classe GruppoDiCarte che è una classe quasi-astratta, che rappresenta un gruppo generico di carte messe alla rinfusa.
#pragma once
#include "Carta.h"
#include 
using namespace std;
class GruppoOrdinato;
#define MAX_CARTE 52

class GruppoDiCarte{
friend ostream& operator<<(ostream&,GruppoDiCarte&);
public:
 GruppoDiCarte();
 GruppoDiCarte(unsigned int);
 unsigned int getNCarte(){return nCarte;}
 void setCarta(unsigned int);
 void mescola();
 virtual void ordina();
 virtual Carta* getPrimo(){return NULL;}
 virtual bool attaccaGruppo(GruppoOrdinato*){return NULL;}
 void stampa();
       Carta* pop();
 void push(Carta*);
 Carta* vediCarta();
 virtual bool attacca(Carta*);
 void reverse();
protected:
 unsigned int nCarte;
 Carta* carte[MAX_CARTE];
};
Da notare già quanti metodi virtuali ho inserito. Sono dei metodi "vuoti", potevo anche farli virtuali puri, ma poi non mi avrebbe permesso di instanziare oggetti dalla classe. Ad ogni modo questi metodi verranno chiamati solamente dalle classi figlie. Presenta due costruttori, uno che crea un gruppo di carte con 0 elementi (vuoto) e un altro che crea un gruppo di carte con un numero determinato di elementi, che saranno ovviamente inizializzati al valore delle carte tramite il metodo void setCarta(unsigned int); Poi ci sono i metodi void mescola(); e virtual void ordina(); inseriti per completezza, visto che non ne faccio uso nel programma. Per adesso tralasciamo i metodi virtual bool attaccaGruppo(GruppoOrdinato*); virtual bool attacca(Carta*); che saranno specializzati nelle classi figlie. Particolare importanza hanno i metodi Carta* pop(); void push(Carta*); e Carta* vediCarta(); visto che saranno utilizzati frequentemente in tutto il programma. push(Carta*) aggiunge una carta al mazzetto, mentre pop() la preleva, invece vediCarta() mostra la carta che si trova in cima alla pila (infatti ogni gruppo di carte, eccetto alcuni, viene trattato come se fosse una struttura di tipo LIFO), senza modificare il mazzetto, ovvero senza prelevarla. Una regola categorica per il corretto uso di queste classi è che ad ogni istruzione pop() eseguita su qualsiasi gruppo di carte, deve seguire una istruzione push() su un altro gruppo di carte, per conservare il numero totale di carte. Infatti se tolgo una carta a un gruppo senza inserirla in un altro gruppo, succede che perdo quella carta, scalando di uno il numero totale di carte. Un classico uso che ne faccio è questo: supponiamo un gruppo di carte di nome "g1" e una ltro di nome "g2", e che io voglia passare una carta da g2 a g1. g1.push(g2.pop()); oppure, verificando se effettivamente, stando alle regole del gioco, posso eseguire la suddetta operazione:
Carta* c = g2.vediCarta();  //guardo la carta
if (c==NULL)                //se il mazzo è vuoto ritorno
 return false;
bool result = g1.attacca(c);     //verifico se posso fare l'operazione
if (result) {               // in caso affermativo...
 g2.pop();            //prelevo la carta da g2, visto che l'ho già attaccata a g1
 return true;
}
Adesso analizziamo le classi che ereditano da GruppoDiCarte, ovvero GruppoOrdinato,MazzoMaster,Pila,Mazzo. Analizziamo prima mazzo, che rappresenta un mazzo intero di 52 carte.
#pragma once
#include "GruppoDiCarte.h"
#include 
using namespace std;

class Mazzo : public GruppoDiCarte {
public:
 Mazzo();
 void ordina();
private:
 string toString(int);
};
La classe (che ricordiamo eredita da GruppoDiCarte) non fa altro che creare un gruppo di carte di 52 elementi e mescolarli. Riporto il costruttore di questa classe che crea il mazzo e simula il mescolamento delle carte:
#include "Mazzo.h"
#include 
#include 

Mazzo::Mazzo() {
 nCarte = 52;
 for (int i=0;i<13;i++){
  carte[i] = new Carta(i+1,CUORI,"carte\\"+toString(i+1)+"c.bmp");
  carte[i+13] = new Carta(i+1,QUADRI,"carte\\"+toString(i+1)+"q.bmp");
  carte[i+26] = new Carta(i+1,PICCHE,"carte\\"+toString(i+1)+"p.bmp");
  carte[i+39] = new Carta(i+1,FIORI,"carte\\"+toString(i+1)+"f.bmp");
 }
 /*          mescolo le carte    */
 srand(time(NULL));
 int c;
 int d;
 Carta* app;  //carta di "appoggio"
 for (int i=0;i<208;i++){
  c = rand() % 52;
  d = rand() % 52;
  app = carte[c];
  carte [c] = carte[d];
  carte [d] = app;
 }
}
Passiamo a MazzoMaster, ovvero il mazzo posto in alto a sinistra:
#pragma once
#include "GruppoDiCarte.h"

class MazzoMaster : public GruppoDiCarte {
public:
 Carta* daiCarta();
 void setTerra(GruppoDiCarte*);
private:
 GruppoDiCarte* terra;
};
che aggiunge i metodi Carta* daiCarta(); e void setTerra(GruppoDiCarte*); dove il primo serve per scoprire una carta e il secondo serve per passare il gruppo di carte già scoperte alla classe, in modo che se MazzoMaster si esaurisce, "capovolga" le carte che ci sono a terra. Passiamo ancora a Pila (il nome non ha il noto significato in informatica, ma non sapevo che nome scegliere). Le pile sul tavolo sono quattro, si trovano in alto a destra e raggruppano le carte di uno stesso seme in ordine crescente.
#pragma once
#include "GruppoDiCarte.h"
#include 
#include 

class Pila : public GruppoDiCarte {
friend ostream& operator<<(ostream&, Pila&);
public:
 Pila();
 bool attacca(Carta* c);
 Seme getSeme();
 Carta* getPrimo(){return NULL;}
 bool attaccaGruppo(GruppoOrdinato*){return false;}
private:
 Seme seme;
};
bool attacca(Carta* c); è un metodo specializzato che è virtual della classe madre, e restituisce true se la carta può effettivamente essere attaccata al gruppo (stando alle regole del gioco), false altrimenti. Seme getSeme(); ritorna il seme del gruppo (visto che sono tutte carte di uno stesso seme),Carta* getPrimo() è una funzione ridefinita in modo che ritorni sempre NULL, e allo stesso modo ridefinisco bool attaccaGruppo() visto che non ha senso attaccare un gruppo di carte a questo, dato che andranno attaccate una per una. Passiamo all'ultima classe che rappresenta un gruppo di carte, ovvero GruppoOrdinato, quei gruppi di carte scoperti (7 in tutto) e ordinati in ordine decrescenti a colori alterni:
#pragma once
#include "GruppoDiCarte.h"
#include 
using namespace std;

class GruppoOrdinato : public GruppoDiCarte {
friend ostream& operator<<(ostream&, GruppoOrdinato&);
public:
 GruppoOrdinato();
 bool attacca(Carta*);
 bool attaccaGruppo(GruppoOrdinato*);
 Carta* getPrimo();
 void setAttaccaPrimaVolta(); //uso riservato solamente a Tavolo
private:
 bool attaccaPrimaVolta;
};
bool attacca(Carta*); e bool attaccaGruppo(GruppoOrdinato*); sappiamo già a cosa servono, bool attaccaPrimaVolta; è una variabile usata per "capire" se la carta che verrà attaccata è attaccata dal primo utilizzo o meno. void setAttaccaPrimaVolta(); resetta questa variabile al valore originario, pertanto non andrebbe utilizzata e il suo utilizzo è riservato alla classe Tavolo. Utilizzare questo metodo sarebbe deleterio e il programma non funzionerebbe come dovrebbe. Rimane l'ultima classe da analizzare (oltre a Risolutore), ovvero Tavolo.
#pragma once
#include "Mazzo.h"
#include "GruppoOrdinato.h"
#include "Pila.h"
#include "MazzoMaster.h"
#include 
using namespace std;
class Tavolo {
friend ostream& operator<<(ostream&,Tavolo&);
public:
 Tavolo(Mazzo*);
 void info();
 void checkGruppiOrdinati();
 unsigned int contaCarte();
 MazzoMaster& getMaster(){return master;}
 GruppoDiCarte& getTerra(){return terra;}
 GruppoOrdinato& getg1(){return g1;}
 GruppoOrdinato& getg2(){return g2;}
 GruppoOrdinato& getg3(){return g3;}
 GruppoOrdinato& getg4(){return g4;}
 GruppoOrdinato& getg5(){return g5;}
 GruppoOrdinato& getg6(){return g6;}
 GruppoOrdinato& getg7(){return g7;}
 GruppoDiCarte& gets2(){return s2;}
 GruppoDiCarte& gets3(){return s3;}
 GruppoDiCarte& gets4(){return s4;}
 GruppoDiCarte& gets5(){return s5;}
 GruppoDiCarte& gets6(){return s6;}
 GruppoDiCarte& gets7(){return s7;}
 Pila& getp1(){return p1;}
 Pila& getp2(){return p2;}
 Pila& getp3(){return p3;}
 Pila& getp4(){return p4;}

private:
 MazzoMaster master;
 GruppoDiCarte terra;

 GruppoOrdinato g1;
 GruppoOrdinato g2;
 GruppoOrdinato g3;
 GruppoOrdinato g4;
 GruppoOrdinato g5;
 GruppoOrdinato g6;
 GruppoOrdinato g7;
 

 GruppoDiCarte s2;
 GruppoDiCarte s3;
 GruppoDiCarte s4;
 GruppoDiCarte s5;
 GruppoDiCarte s6;
 GruppoDiCarte s7;

 Pila p1;
 Pila p2;
 Pila p3;
 Pila p4;
};
Praticamente il tavolo contiene i puntatore a tutti i gruppi di carte che sono presenti sul tavolo di un giocatore che gioca al solitario. Potete confrontare i membri di questa classe con l’immagine postata sopra. Da notare i metodi info(), che stampa su console informazioni sulle carte che contiene ogni gruppo; checkGruppiOrdinati() che controlla se c’è qualche GruppoOrdinato che non contiene alcuna carta, e quindi deve essere scoperta una carta del GruppoDiCarte che sta proprio sotto lo stesso; contacarte() che conta il numero totale di carte, mi è stato utile in debug per evitare la “scomparsa” (o l’”apparizione”) di alcune carte. Ultima classe fondamentale: il Risolutore, ovvero colui che secondo una logica ben precisa, muove le carte per vincere il giuoco.
#pragma once
#include "costanti.h"
#include "Tavolo.h"
#include "Interfaccia.h"
class InterfacciaWin;


class Risolutore {
public:
 Risolutore(Tavolo* t,Interfaccia*);
 bool mossa();
 bool getVincita(){return vincita;}
private:
 Tavolo* tavolo;
 int chiamateMaster; //tiene conto del numero di volte che io prendo una carta dal master
 int chiamateCiclo; //tiene conto del numero di volte che io chiamo mossa()
 GruppoDiCarte* gruppi[11];

 bool gruppi_pile();
 bool terra_gruppi();
 bool gruppi_gruppi();
 bool vincita;
 InterfacciaWin* interfaccia;
};
Il risolutore per giocare ha bisogno dell’oggetto Tavolo, e di un’interfaccia grafica, per comunicare gli spostamenti di carte. Il cuore di questa classe è contenuta nel metodo mossa(), che chiama ripetutamente i tre metodi privati della stessa classe. Ok, abbiamo presentato la parte logica della classe, passiamo alla parte fisica, o meglio, alla parte visiva, quella che ci fa vedere che cosa sta facendo il computer: INTERFACCIA GRAFICA L’interfaccia grafica per ora è stata implementata per Win32, ma sto lavorando per la versione Linux (GTK+).
#pragma once
#include "costanti.h"
#include "Tavolo.h"
#include "Risolutore.h"
class Risolutore;

#ifdef WINDOWS_APP
#include 
class InterfacciaWin {
public:
 InterfacciaWin(unsigned int,Tavolo*);
 void aggiungiRisolutore();
 bool mossa();
 char** clickOnMaster();
 void transizione(int,int);
 void aggiungiFinestra(HWND);
private:
 Tavolo* tavolo;
 Risolutore* risolutore;
 bool setRisolutore;
 unsigned int weight;
 
    HWND hwnd;
};
#define Interfaccia InterfacciaWin
#endif

#ifdef LINUX_APP
class InterfacciaLinux {
public:
 InterfacciaLinux(unsigned int,Tavolo*);
 void aggiungiRisolutore();
 bool mossa();
 char** clickOnMaster();
 void transizione(int,int);
private:
 Tavolo* tavolo;
 Risolutore* risolutore;
 bool setRisolutore;
 unsigned int weight;
};
#define Interfaccia InterfacciaLinux
#endif
Come vedete, siccome è OS dipendente, ho provveduto a creare le direttive per il precompilatore in modo tale che se WINDOWS_APP è definito, compila per windows, altrimento se LINUX_APP è definito, compila per LINUX. Nota che PER ORA, la funzione della classe Interfaccia è abbastanza futile, visto che potrei anche farne a meno. Ma l’ho messa nel caso mi servisse più avanti, quando continuerò a sviluppare questo programma permettendo di fare giocare anche un umano (potete infatti notare il metodo aggiungiRisolutore() , che come dice il nome, attiva la modalità automatica, altrimenti se non c’è il risolutore, vuol dire che sarà un umano a giocare. Stay connected!

 Per finire…beh, ecco il codice sorgente per voi: sono dei fottutissimi file .h e .cpp, quindi compilabili con qualunque compilatore all’altezza. Nessun progetto, nessun vincolo di sistema, only raw material. P.S. ci sono solo 2 file che non ho analizzato: “WinMain.c” e “Windows.h”, che sono troppo specifici del sistema operativo, ma comunuque lo trovate nel codice sorgente ;)
Fine del post chilometrico.
Alberto

Nessun commento:

Posta un commento