In
realtà erano anni che mi frullava per la testa l'idea di creare una
mia versione del gioco della vita, ma per un motivo o per un altro,
non l'avevo mai sviluppato.
Però
ultimamente sono stato preso dalla documentazione ufficiale del
progetto GNOME, e sono venuto a conoscere una libreria di nome
clutter
la quale viene descritta nel progetto come “una libreria per creare
animazioni e interfacce grafiche eleganti”; mi sono così
incuriosito e ho dato un'occhiata alla documentazione ufficiale di
questa libreria: non mi ci è voluto molto a capire che segue lo
stesso stile di GTK+, usa le sue stesse librerie (GObject, GLib, GDK
ecc.), è integrata nell'ambiente GNOME e
usa le librerie OpenGL.
Così,
mani sulla tastiera, ho cominciato a sviluppare con clutter il
solitario che ho sviluppato circa due anni fa per windows e creare
una versione giocabile, ma il progetto è lungo e continua ancora
adesso.
Quindi
ho deciso di usare clutter per sviluppare il gioco della vita, che è
più veloce e meno impegnativo: 4 giorni e ho implementato la
versione 0.8, in cui oltre a
usare clutter, mi sono avvalso della libreria clutter-gtk la quale fa
da “ponte” per le librerie clutter e gtk.
Più
avanti ne approfitterò per avviare un mini-tutorial su clutter
descrivendo il codice sorgente del gioco della vita.
Il
gioco della vita
Il
gioco della vita è un automa cellulare che come il nome stesso dice,
simula alcuni comportamenti assimilabili alla vita, alla crescita
della popolazione e così via. Personalmente mi piace immaginarlo
come popolazioni di umani su una terra che si stanziano (forme
stabili) o vagano (forme dinamiche) su un territorio, dove possono
entrare in guerra fra loro (collisioni) o convivere pacificamente,
oppure ancora dove possono prosperare (automi a crescita infinita), o
morire (configurazioni che scompaiono dopo tot-generazioni).
Dopo
aver confuso le idee del lettore che non conosce il gioco della vita,
passo a spiegare come funziona lo
stesso.
Esso
si basa su uno spazio costituito da celle, dove ogni cella può
essere accesa (viva) o spenta (morta). La
configurazione in cui si trova lo spazio funziona da input per la
generazione seguente, la quale funzionerà da input per la
generazione seguente ancora e così via: esso è Turing-compatibile,
infatti è possibile configurare le celle in una maniera particolare
costruendo porte logiche e implementare istruzioni.
Le
regole che determinano il passaggio da una generazione a un'altra
sono essenzialmente due:
Se
una cella è morta, essa nasce se ha esattamente 3 celle vive
accanto
Se
una cella è viva, essa muore se ha un numero di celle accanto
diverso da 2 o da 3, per isolamento o sovraffollamento
Queste
semplici regole permettono di creare un gran numero di
configurazioni, elencate
qui appresso.
Forme stabili:
Blocco
Barca
Oscillatori:
Lampeggiatore
Rospo
Nav icelle:
Aliante
Astronave
Altro:
Cannone di alianti
Die hard
…
Il
software
Il
software che ho sviluppato è minimale, usa clutter ma non usa
appieno le sue potenzialità (probabilmente avrei potuto sviluppare
il gioco anche col solo uso di gtk).
Si
presenta con la barra degli strumenti in alto, lo stage al centro, e
la barra di stato in basso per il conteggio delle generazioni:
La scena del gioco con un oscillatore e un aliante
La
barra degli
strumenti:
La barra degli strumenti
contiene
i comandi per:
Creare una nuova scena
Aprire un file
Salvare un file
Mettere in pausa/riprendere
Le impostazioni
Modificare il ritardo (in ms) tra una
generazione e un'altra
Informazioni sul programma
Il
supporto per i file è in realtà un modo per salvare delle
configurazioni particolari (per esempio salvare una navicella), ma
devo ancora perfezionarlo perché non è molto utile (potrebbe essere
utile se riuscissi a importare nella scena il contenuto del file in
una determinata posizione della scena stessa).
Installazione
Innanzitutto, per ora è disponibile solo per linux. Purtroppo per varie difficoltà nel compilare i sorgenti di clutter e clutter-gtk su windows, non posso andare avanti (fortunatamente il team gtk+ fornisce già i binari compilati per windows). È
possibile installare il software in due modi:
1)Compilando
i sorgenti
2)Installando
il pacchetto .deb per i sistemi debian/ubuntu
Per
compilare i sorgenti è
necessario avere installati sul sistemai
seguenti pacchetti:
pkg-config
libgtk-3-dev
libclutter-1.0-dev
libclutter-gtk-1.0-dev
quindi
basta
scaricare ed estrarre in una cartella il contenuto del file
lifegame-0.8-src.tar.gz, aprire il terminale, posizionarsi sulla
cartella con
i file estratti:
cd
[percorso-cartella]
e
lanciare il comando:
sudo
./install
il
quale lancia il file di script “install” che ho preparato il
quale compila i sorgenti e installa il software nel sistema. Per
i sistemi che hanno GNOME o simili, come ubuntu, è possibile
trovarlo nelle applicazioni sotto la categoria “giochi”,
altrimenti è possibile lanciarlo da terminale col
comando “lifegame”.
Per installare direttamente i binari è possibile scaricare il comodo pacchetto .dev (per i sistemi con pacchettizzazione debian, come Debian stesso e Ubuntu) che risolve automaticamente le dipendenze. Per installare il pacchetto .deb su ubuntu basta aprirlo con un doppio click oppure digitare da terminale:
Ci sono alcuni comandi (=programmi) che ho scoperto su linux che non sono altro che comandi creati a scopo goliardico, senza alcuna utilità se non quella di strapparci un piccolo sorriso. Possiamo vederli qui appresso, ordinati iniziando da quelli che considerò più divertenti.
1. sl
Il comando ls su linux ("list) serve per elencare i file contenuti in una cartella, senza opzioni elenca i file contenuti nella cartella corrente. Essendo un comando molto usato, può capitare che nella fretta qualcuno scriva "sl" invece che "ls", causando un simpatico effetto: un treno che passa dalla finestra del terminale.
Normalmente nelle distro non è installato, ma su Debian/Ubuntu per installarlo basta scrivere sul terminale:
sudo apt-get install sl
2. xcowsay
Questo comando non fa altro che lanciare una applicazione GTK+ che visualizza una mucca che "dice" quello che noi le passiamo come argomento.
Per installarla lanciamo:
sudo apt-get install xcowsay
3. figlet & toilet
Due programmi analoghi che stampano usando l'ASCII art quello che noi passiamo come argomento.
Per installarli:
sudo apt-get install figlet
sudo apt-get install toilet
4. oneko
Per i gattofili c'è una simpatica applicazione che in effetti potete lanciare e lasciarla in background. Mostra un gattino che segue sempre il vostro mouse, (in realtà il puntatore del mouse):
Per installarlo:
sudo apt-get install oneko
Mentre per lanciarlo e lasciarlo in background:
oneko &
Poi premete INVIO e scrivete "exit" per uscire dal terminale senza chiudere l'applicazione.
5. moo
Rimanendo sempre in tema mucca, visto che fa molto simpatia, ecco quest'altra chicca: in realtà è un easter-egg di apt-get, sistema di pacchettizzazione di debian. Basta scrivere:
apt-get moo
6. fortune
Questa è una vecchia applicazione per Unix, quindi presente anche in Linux oggi. Mostra un messaggio pseudo-casuale preso da un database di citazioni. Pesa circa 2 megabyte, per installarlo:
Aggiornamento: clicca qui Ispirandomi al gioco che era presente nel vecchio Nokia 3210, ho creato questo gioco con poche linee di codice in Javascript e qualcosa basilare di CSS e HTML. Il file HTML su cui mi baso (ci sono riferimenti alla tabella, id in particolare, e un tasto per "nuovo gioco"):
<CENTER>
<p style="font-size:x-large;font-style:italic;">Memory: clicca sulle caselle e memorizzale per trovare le coppie, fino a scoprire tutte le caselle.</p>
<a href="#">
<table>
<tr><th id="11" onclick="clickTo(1,1)">*</th><th id="12" onclick="clickTo(1,2)">*</th><th id="13" onclick="clickTo(1,3)">*</th><th id="14" onclick="clickTo(1,4)">*</th></tr>
<tr><th id="21" onclick="clickTo(2,1)">*</th><th id="22" onclick="clickTo(2,2)">*</th><th id="23" onclick="clickTo(2,3)">*</th><th id="24" onclick="clickTo(2,4)">*</th></tr>
<tr><th id="31" onclick="clickTo(3,1)">*</th><th id="32" onclick="clickTo(3,2)">*</th><th id="33" onclick="clickTo(3,3)">*</th><th id="34" onclick="clickTo(3,4)">*</th></tr>
<tr><th id="41" onclick="clickTo(4,1)">*</th><th id="42" onclick="clickTo(4,2)">*</th><th id="43" onclick="clickTo(4,3)">*</th><th id="44" onclick="clickTo(4,4)">*</th></tr>
</table>
<input type="button" value="Nuovo gioco" onclick="nuovoGioco()" style="width:100px;height:50px;">
</a>
</CENTER>
Mentre il cuore del programma vero e proprio è lo script in javascript, tutto commentato:
<script type="text/javascript">
//Array per contenere i caratteri da scoprire
var array = new Array();
//Array per controllare il flusso del gioco (se sono state aperte due caselle e sono uguale allora scopri, altrimenti nascondi ecc.
var caselle_aperte = new Array();
/*INIZIO*/
nuovoGioco();
/*
*Questa funzione gestisce quando viene cliccata una casella, e decide cosa fare
*a seconda del contenuto di caselle_aperte
*/
function clickTo(riga,colonna){
//Caso 1: non ci sono caselle aperte, quindi deve scoprirne una sola
if (caselle_aperte.length==0){
mostraSimbolo(riga,colonna);
caselle_aperte[0]=riga.toString()+colonna.toString();
return;
}
//Caso 2: c'è già una casella aperta, scopri la nuova e controlla se sono uguali
if (caselle_aperte.length==1){
//se è la stessa ignora ed esci
if (caselle_aperte[0]==riga.toString()+colonna.toString())
return;
//scopri la casella
mostraSimbolo(riga,colonna);
//memorizza la nuova casella in caselle_aperte
caselle_aperte[1]=riga.toString()+colonna.toString();
//controlla se i simboli sono uguali
if (getSimboloAt(caselle_aperte[1])==(getSimboloAt(caselle_aperte[0]))){
document.getElementById(riga.toString()+colonna.toString()).setAttribute('style','background-color:#81F781;');
document.getElementById(caselle_aperte[0]).setAttribute('style','background-color:#81F781;');
//cancella il contenuto di caselle_aperte
caselle_aperte.pop();
caselle_aperte.pop();
//Controlla se hai vinto (cioè se tutte le caselle sono state scoperte
var conta;
conta=0;
for (i=1;i<=4;i++)
for (c=1;c<=4;c++){
if (document.getElementById(i.toString()+c.toString()).innerHTML!="*")
conta++;
}
}
if (conta==16)
alert("Hai vinto!");
return;
}
//Caso 3: ci sono già due caselle scoperte e sono entrambe diverse.
if (caselle_aperte.length==2){
//Nascondi i simboli
nascondiSimbolo(caselle_aperte[0]);
nascondiSimbolo(caselle_aperte[1]);
//svuota caselle_aperte
caselle_aperte.pop();
caselle_aperte.pop();
return;
}
}
/*
* Mostra semplicemente il simbolo in riga,colonna
*/
function mostraSimbolo(riga,colonna){
document.getElementById(riga.toString()+colonna.toString()).innerHTML=array[((riga-1)*4)+colonna-1];
}
/*
*Nasconde semplicemente il simbolo in riga, colonna
*/
function nascondiSimbolo(riga,colonna){
document.getElementById(riga.toString()+colonna.toString()).innerHTML="*";
}
/*
*Come sopra, ma overload per un solo parametro di tipo stringa(es:"32, riga 3, colonna 2)
*/
function nascondiSimbolo(_stringa){
stringa = new String(_stringa);
riga = stringa.charAt(0);
colonna = stringa.charAt(1);
document.getElementById(riga.toString()+colonna.toString()).innerHTML="*";
}
/*
*Ritorna il simbolo che c'è memorizzato in riga, colonna
*/
function getSimboloAt(riga,colonna){
riga--;
colonna--;
return array[(riga*4)+colonna];
}
/*
*Come sopra, ma overload per un solo parametro di tipo stringa(es:"32, riga 3, colonna 2)
function getSimboloAt(_stringa){
stringa = new String(_stringa);
riga = stringa.charAt(0);
colonna = stringa.charAt(1);
riga--;
colonna--;
return array[(riga*4)+colonna];
}
/*
*Inizia un nuovo gioco
*/
function nuovoGioco(){
//creo di nuovo un array
array = new Array();
//idem
caselle_aperte=new Array();
//array che contiene i simbolo, per ora solo lettere
var array_simboli= new Array("a","b","c","d","e","f","g","h");
var simbolo;
//Cancello il contenuto delle caselle e metto "*" al posto
for (i=1;i<=4;i++)
for(c=1;c<=4;c++) {
riga = Math.floor(i/4)+1;
colonna = i-((riga-1)*4);
document.getElementById(i.toString()+c.toString()).innerHTML="*";
document.getElementById(i.toString()+c.toString()).setAttribute("style","background-color:white;");
}
//Preparo l'array a random
var pos1,pos2;
for (i=0;i<8;i++){
while(array[pos1]!=null)
pos1=Math.floor((Math.random()*16));
array[pos1]=array_simboli[i];
while(array[pos2]!=null)
pos2=Math.floor((Math.random()*16));
array[pos2]=array_simboli[i];
}
//Correggo bug, secondo il quale "a" viene memorizzata una sola volta mentre l'altra è null
for (i=0;i<16;i++)
if (array[i]==null){
array[i]="a";
break;
}
}
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.
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.
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+).
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
Ho creato un risolutore automatico del famoso solitario di Windows, Apple, Gnome, KDE e chi più ne ha più ne metta. Scritto in C++ per Win32.
La mia intenzione era di crere un risolutore in grado di vincere ogni partita, ma mi sbagliavo di grosso; infatti sappiamo che dipende tutto dalle carte, e possono presentarsi situazioni (frequenti) in cui è impossibile vincere, come in questo caso, per esempio:
Le carte per "sbloccare" il gioco sono 2: il nove di picche e il quattro di fiori, che quarda caso si trovano sotto il dieci di cuori e il nove di quadri.
Comunque, il computer vince 1 volta su 5, e non sono poche!
Metto il file exe scaricabile per Windows, conm poche modifiche ne farò anche una versione per KDE. Le mosse le fa il computer ad ogni click del mouse (mi è stato utile per seguire le mosse del computer). Risoluzione consigliata: almeno 800x600 pixel.
Scusate le carte ritagliate male e prese da wikipedia XD. Nel prossimo post darò i dettagli tecnici di questo programma. Download
Alberto
NOME: Minicomposer LINGUAGGIO: Borland Pascal PIATTAFORMA: DOS LIBRERIE: Giobe.TPU PERIODO DI SVILUPPO: Finito il 29/05/2003
Ai tempi questo programma-gioco fu pubblicato (con tanto di scritta rossa in prima pagina) qui: http://pascal.stormdrop.com/ ,un sito dedicato agli sviluppatori di programmini in Pascal, ma ormai da tempo è "freezato", ovvero l'ultimo aggiornamento è l'aggiunta di questo mio programma.
Si tratta di uno spartano programmino che visualizza una tastiera (priva di diesis, ovvero non suonabili) con la quale possiamo suonare note col mouse e registrarle in un file, e poi quest'ultimo può essere riproducibile con un altro programmino incluso nel pacchetto: PlayMusic.
E' stato il mio primo programma "utile" in assoluto, da quando mi avvicinai alla programmazione col Pascal. Inoltre è l'antenato di quel programma che sviluppai nel 2009 per la piattaforma .NET (Prodigious)
Ringrazio il creatore della libreria Giobe.TPU Ing. Giorgio Ober , webmaster del sito www.giobe2000.it che ha creato una libreria direi essenziale per chi programma(va) in Pascal.
Nota: questo programma per suonare le note utilizza il beeper interno del sistema, ovvero non le casse (eh...era arduo con Turbo Pascal ed ero alle prime armi). Per esempio nel mio portatile con l'emulatore DOS di windows non si sente nulla. Ho provato invece con DosBox e funziona perfettamente.
Bye
Alberto