Dal C al C++/Gioco life di Conway
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;
}