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;

}