Dal C al C++/Gioco life di Conway

Indice del libro

Di seguito il listato in C++ per un'implementazione testuale del gioco della vita.


#include <iostream>
#include <cstdlib>
#include <vector>
#include <set> 
#include <ctime>

//Ho usato quasi solo funzioni "void" perché sono mooolto più belle e perché le funzioni "normali" sono sopravvalutate.

using namespace std;
/*Ho provato a dare dei nomi a tutte le funzioni e variabili che aiutani la spiegazione di ciò che succede all'interno di tale funzione
o a cosa serve tale variabile, molti sono in inglese perché sono più brevi in inglese e perché l'inglese è una lingua più bella.*/
 
// Osservabile e osservato contemporaneamente :D, gestisce le regole "biologiche"
class Casella{
public: //accessibili dall’esterno e in ogni punto del programma.
    enum stato{vuoto=0,pieno}; //Questa è una enumerazioni, essa è esattamente come i tipi fondamentali e composti, un altro "tipo"
    //essa nello specifico tratta solo valori che possono essere assunti da una variabile enumerazione e questi
    //sono ristretti ad un insieme di valori interi costanti, ad ognuno dei quali viene associato un nome.
    typedef std::set<Casella *> Container; //Typedef ha come scopo quello di semplificare la vita, esso infatti,
    //a differenza di una dichiarazione standard troppo fastidiosa, riesce a rendere il codice moooolto più riutilizzabile 
    //tra un'implementazione e un'altra.
    typedef Container::iterator Iterator; 
 
private: //utilizzabili soltanto all’interno della classe stessa.
    stato stato_;
    int vicini_futuri;
    int vicini;
    Container osservatori;
 
public:
    Casella( const Casella::stato & s = Casella::vuoto ) //const non ha bisogno di essere spiegato.
    : stato_(s), vicini_futuri(0), vicini(0){}  
 
    //non copia gli osservatori
    Casella(const Casella& c): vicini(c.vicini),
            vicini_futuri(c.vicini_futuri), stato_(c.stato_){}
    virtual ~Casella(){}  //Virtual è un pò più difficile da spiegare...è anche questa come int, double, typedef, enum un "tipo" di dichiarazione
    //virtual però è diferso dagli altri tipi di dichiarazioni poiché consente di condizionare l’esecuzione del codice 
    //secondo il tipo dell’istanza oggetto cui si fa riferimento. Scusi se non è molto chiaro ma non ho idea di come spiegarlo...
     
    // Registra gli osservatori a cui notificare i cambiamenti
    inline void Registra(Casella& c) { //Riguardo ad "inline" non sono ancora sicuro cosa faccia, ma credo aumenti le prestazioni...
        //ma ripeto che non sono sicurissimo su questo fatto, scusi se non sono ferrato ma me l'ha consigliato di usare un amico.
        if (&c != this){ //this identifica un puntatore speciale che contiene l’indirizzo dell’istanza della classe 
        //che ha invocato il metodo.
            osservatori.insert(&c);
        }
    } 
  
 
    inline void Imposta(const Casella::stato& s){
    	stato_=s;
    }
 
    inline void Muori(){
    	if(stato_==pieno){
        Imposta(Casella::vuoto);
        Notifica(-1);//Utile per la notifica, vedi dopo.
    	}
    }
 
 
    inline void Nasci() {
    	if (stato_==vuoto){
        Imposta(Casella::pieno);
        Notifica(1);//Utile per la notifica, vedi dopo.
        }
    } 
     
    // Aggiorna 
    inline void Ciclo() {
       vicini=vicini_futuri;
    }
 
    // Regole del gioco
    inline void Verifica() {
        if (stato_==pieno){
        	if (vicini<2 || vicini>3) // Modificare questa riga per cambiare le regole di morte
            	Muori();
        }else {
            if (vicini==3) // Modificare questa riga per cambiare le regole di nascita
               Nasci();
 	   }
    } 
  
    inline bool Leggi() const{
        return stato_==pieno;
    }
 
private:
    // Notifica ai vicini nascita o morte
    inline void Notifica(int msg) const{
        for (Iterator it = osservatori.begin(); it != osservatori.end(); ++it){ //A fare sta parte ci ho messo 2 giorni.
            (*it)->RiceviNotifica(msg);//Per questa infatti mi sono dovuto studiare una libreria intera.
        }
    }
     
     // Ricevi la notifica dai vicini
    inline void RiceviNotifica(int msg) {
        vicini_futuri += msg; //Stessa cosa per sta dannata parte
    }
 }; 
 
// Contenitore di caselle, che genera automaticamente le corrispondenze tra vicini. 
// Gestisce le regole "topologiche" --> termine preso da wikipedia
class Griglia{
public:
    typedef std::vector<Casella> Container;
    typedef Container::iterator Iterator;
 
private: //utilizzabili soltanto all’interno della classe stessa.
    int righe_;
    int colonne_;
    int time;
    Container griglia;
 
public: //accessibili dall’esterno e in ogni punto del programma.
    virtual ~Griglia(){}
     
    Griglia(const int& righe, const int& colonne)
    :righe_(righe), colonne_(colonne)    {
       if (righe_ < 5) righe_=5;
       if (colonne_ < 5) righe_=5;
       griglia.reserve(righe_*colonne_);
       griglia.resize(righe_*colonne_);
    }
    
    inline Casella& operator[](const int n) {
       return griglia[n];
    }
    
    inline void Cicla() {    
       for (int i = 0; i < righe_*colonne_;++i)
           griglia[i].Ciclo();
    }
 
    void Verifica(){
        for (int i = 0; i < righe_*colonne_;++i)
           griglia[i].Verifica();
     }
 
    inline void Imposta (const int& pos) {
        if ((pos >=0)&&(pos < griglia.size()))     	  
           griglia[pos].Nasci();
     }
 
    inline void Resetta (const int& pos) {
        if ((pos >=0)&&(pos < griglia.size()))	  
           griglia[pos].Muori();
     }
 
    // Modificare questa per cambiare le regole topologiche di vicinanza
    inline void Genera(){ //Scusi se questa parte è un poco incasinata ma non avevo trovato modi per scrivere più "ristretto" 
    //e comunque in un modo "chiaro e capibile".
        bool primo, ultimo;
        for (int indice=0; indice< righe_*colonne_;++indice){
        	primo = ( (indice%colonne_) == 0);
            ultimo = ( (indice%colonne_) == colonne_-1);
 
         	if (indice > colonne_){      
           		if (!primo)
              		griglia[indice].Registra(griglia[indice-colonne_ - 1]);
           		griglia[indice].Registra(griglia[indice-colonne_]);
            if (!ultimo)
              	griglia[indice].Registra(griglia[indice-colonne_ + 1]);
        }
         
        if (!primo)
           griglia[indice].Registra(griglia[indice-1]);
        if (!ultimo)
           griglia[indice].Registra(griglia[indice+1]);
    
        if (indice < ((colonne_)*(righe_-1)) ){
        	if (!ultimo)
             	griglia[indice].Registra(griglia[indice+colonne_+1]);
           	griglia[indice].Registra(griglia[indice+colonne_]);      
           	if (!primo)
             	griglia[indice].Registra(griglia[indice+colonne_-1]);
        	}	 
        } 
    }
 
    inline const int Righe() const { return righe_; }
    inline const int Colonne() const { return colonne_; }
}; 
 
//Stampa a schermo la griglia. Gestione !statica! delle dimensioni. Per variare, utilizzare griglia.Righe() e Colonne()
void Stampa( Griglia& g) {
    cout << "\n\n"; //ne abbiamo messi 2 per bellezza
    cout << "+--------------------+\n|";
    for (int indice=0;indice < 400;++indice){ //Caratteri.
     	bool ultimo = ( (indice%20) == 19);
     	g[indice].Leggi()?cout << "O":cout << " ";
     	if (ultimo){
       		if (indice !=399)
         	{cout << "|\n|";}
       		else
         	{cout << "|\n";}
     	}
    }
    cout << "+--------------------+\n"; 
 }
 
int main () {
    Griglia griglia(20,20); 
    griglia.Genera();
 
    // Inizializza la griglia casualmente, con fattore di 
    // riempimento un ottavo.
    // Gestione !statica! delle dimensioni. Per variare, utilizzare griglia.Righe() e Colonne()
    srand(time(0));
    for (int i=0; i < 100;++i){
     	griglia.Imposta((rand()>>4) %400);
    }
 
   	while (1){
     	Stampa(griglia);   
     	griglia.Cicla();   
     	griglia.Verifica();
     	// Aspetta la pressione di enter
     	cin.get(); //Comando che è stra utile poiché registra la pressione del tasto, e quindi è stra utile.
   }
   	// Mai raggiunto
   	return 0;
}