venerdì 30 settembre 2011

HowTo: Integrare SQLite nel proprio gioco Java

Come tutti i programmi, anche i giochi necessitano di dati. Abbiamo già parlato in precedenza di alcuni metodi per gestire (scrivere e rileggere) tali dati, utilizzando file xml piuttosto che file binari o ASCII. Ci sono naturalmente anche altri modi, e uno di questi è senza dubbio SQLite, un piccolo e leggero database relazionale su file.

Un database? Non sarà un po' troppo?

Dipende. Sicuramente se volessimo scomodare un vero server di database come MySQL o SQLServer occorrerebbe una giustificazione più che valida. Ma SQLite è, come lascia intendere il nome, piccolino. Piccolino ma, attenzione, adatto a gestire grandi mole di dati, soprattutto in lettura. Pensate alla comodità di avere le varie entità del vostro gioco serializzate in modo ordinato in tabelle di una base dati. La tabella Livelli, con i dati relativi ad ogni livello di gioco, la tabella Nemici, con tutte le statistiche delle varie tipologie di nemici, la tabella Oggetti, con tutti gli item presenti nel vostro modo, e perché no, la tabella Partite, con i salvataggi del giocatore. E così via. Tutti dati che dovreste comunque gestire in una qualche forma di file.

I vantaggi del relazionale.

Una volta modellata la base dati potrete poi sfruttare il principale vantaggio del suo utilizzo: le relazioni. La tabella NemiciPerLivello potrebbe contenere in modo semplice le coppie Livello-Nemico con la posizione all'interno del livello del tale nemico, ad esempio. In poche parole avete tra le mani, in forma gratuita, la potenza di chiavi primarie, chiavi esterne, vincoli di check e quant'altro.

I vantaggi del linguaggio di query.

Recuperare (ma anche salvare, aggiornare o eliminare) i dati diventa semplice. Volete popolare il livello con una lista di oggetti casuali ma che siano di valore compreso tra min e max? Complicato con xml, un delirio con ASCII o binario, ma semplice ed immediato con una query SQL come:

SELECT * FROM ITEMS WHERE ITEMVALUE >= @min AND ITEMVALUE <= @max

Già, ma come lo integro nel mio game?

Ed eccoci al punto. Per questo articolo prenderò come linguaggio di esempio Java. Essendo un linguaggio orientato per motivi storici ad applicazioni enterprise (da ufficio, per intenderci) ha già una buona integrazione con oggetti esterni come le basi dati. Per prima cosa occorre scaricare il pacchetto (.jar) contenente SQLite: al momento in cui scrivo ne trovate una copia qui.

(Per gli amici C++: SQLite è scritto in C, quindi la sua integrazione con C/C++ è assolutamente fattibile. Ne esiste anche la versione managed per .NET).

Questo pacchetto va poi inserito nel vostro progetto ed incluso tra le librerie in uso (in Eclipse si fa il tutto da Progetto->Proprietà->Java Build Paths->Librerie->Aggiungi JAR).

A questo punto siete pronti per scrivere codice! Vi servono solo 2 righe, che sono queste:


Class.forName("org.sqlite.JDBC");
Connection conn = DriverManager.getConnection("jdbc:sqlite:db/miodatabase.sqlite");


dove db/miodatabase.sqlite è il percorso + nome del vostro database, relativo alla root del progetto.

E come lo creo il database?

E qui viene il bello. Con file di testo semplici ci basta Blocco Note. Con file binari ci occorre un editor di qualche tipo, che spesso dobbiamo scrivere noi. Con file xml può andare un qualunque editor di testi, ma per documenti importanti può diventare necessario un editor apposito, ancora una volta custom e a nostre spese.

Per SQLite invece è pieno il mondo di programmi di amministrazione. Pensateli come fossero i vostri editor per i dati, perché è proprio quel che sono. C'è ad esempio un plugin per Firefox, SQLite Manager, comodissimo e ben realizzato, che vi fa creare ed editare il vostro database da browser. Check it out!

Un esempio di lettura.

Concludiamo con un piccolo esempio che mostra quanto diventi semplice ottenere i dati (per la loro creazione avrete probabilmente usato un tool come quelli citati qui sopra).

List<quest> ret = new ArrayList<quest>();
Connection conn = DriverManager.getConnection("jdbc:sqlite:db/miodatabase.sqlite");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from quests where npc = " + npcId + " and done = 0");
while(rs.next()){
ret.add(new Quest(rs.getInt("id"),
rs.getString("text"),
rs.getString("type"),
rs.getString("onsuccess"),
rs.getString("onwait"),
rs.getInt("reward_gold")));
}
rs.close();
conn.close();

Una volta creato il vostro oggetto Connection potrete interrogare la base dati attraverso un oggetto Statement. La query in questo esempio preleva tutte le Quest di un ipotetico gioco di ruolo fornite da un personaggio non giocante con un certo id. Si riempie quindi un oggetto di gioco Quest con i dati prelevati e lo si aggiunge alla lista da restituire. Quando il set di dati finisce (rs.next() ritorna false quando non trova più nulla da leggere) si chiude la connessione. Abbiamo ora una lista di oggetti Quest pronti per essere gestiti dal motore di gioco!

Con questo spero di avere aggiunto un'ulteriore freccia alla faretra dello sviluppatore in erba. Considerate che esistono anche i database e che non è vietato utilizzarli. Specie se se ne restano confinati in un unico, piccolo file binario.

Buon coding!

lunedì 26 settembre 2011

Il gioco indie che forse non conoscete: Whack Your Boss!

Se c'è un giorno della settimana in cui giocare a questo gioco assume gusto e senso del tutto particolari, beh, è proprio il lunedì. Cioè oggi. Quindi non mi dilungherò in verbose esposizioni.


Whack your boss di Tom Winkler ha il gameplay di un trova le differenze da settimana enigmistica, ma con una dose massiccia di splatter e humor sanguinolento. Alcune trovate sono veramente esilaranti, sebbene inadatte ad un pubblico troppo sensibile (e, naturalmente, al vostro capo).

Coraggio dunque: se credete di aver già pensato a tutti i possibili modi per far la festa al vostro superiore, scoprirete di esservi sbagliati alla grande. Scommetto che d'ora in poi guarderete la tazza sulla vostra scivania con occhi diversi...

lunedì 19 settembre 2011

Tutorial: scriviamo un gioco in C++... Tris! (parte 6)

Nell'ultima puntata, risalente ormai a quasi due mesi fa (vergogna!), eravamo quasi arrivati al gioco completo. Quasi...

Il nostro gioco infatti funziona, ma non è molto interessante poichè il computer non ragiona sulle proprie mosse, ma si limita a posizionare simboli a caso nelle celle vuote. Vincere contro di lui è fin troppo facile.

Quel che ci manca è di fornire il computer di una sorta di intelligenza, o almeno di quella che basta per capire come controbattere alle nostre mosse e magari metterci in difficoltà.

Nel nostro caso specifico non si tratta di una gran difficoltà, e con un po’ di ragionamento si può giungere ad una soluzione casereccia o addirittura scomodare qualche algoritmo famoso come il minimax.

Il punto focale di questo post non è però il come scrivere la logica, ma il come organizzarne la gestione. Quel che faremo sarà creare un meccanismo per cui un tipo di intelligenza artificiale tra molte disponibili venga prelevato ed utilizzato ad ogni partita. Ognuno implementerà la propria logica per gestire le mosse.

Il come scrivere queste logiche dipende dal programmatore, dal suo intuito, il suo gusto, la sua formazione. It's up to you, come dicono quelli là.

Utilizzeremo due design pattern: Strategy e Factory. Strategy ci servirà per implementare le diverse AI (che nel nostro caso saranno due), mentre una factory ci fornirà una implementazione concreta ad ogni partita, secondo una logica random.

Più facile a vedersi che a dirsi. Iniziamo quindi con la classe base astratta StrategyAI.

#ifndef STRATEGYAI_H
#define STRATEGYAI_H

enum GridSymbols
{
EGS_X, EGS_O, EGS_NONE
};

class StrategyAI
{
public:
StrategyAI();
virtual ~StrategyAI();

virtual void PlaceSymbol(GridSymbols grid[3][3]) = 0;

protected:
private:
};

#endif // STRATEGYAI_H

Come vedete è occorso un po’ di refactor: abbiamo spostato qui la definizione dell’enumerativo dei simboli presente nella classe PlayGameState. Per il resto si tratta di una classettina molto semplice; contiene infatti un solo metodo virtuale puro atto a posizionare la mossa del computer all’interno della griglia. Le classi concrete che implementeranno StrategyAI dovranno implementare proprio questo metodo.

Una prima classe di esempio è DummyStrategyAI, che altro non fa che simulare quel che già avevamo, ovvero un riempimento casuale della griglia. Ecco la sua implementazione (ne ometto la dichiarazione)

#include "../include/DummyStrategyAI.h"
#include <cstdlib>

//-----------------------------------------------------------------------------
DummyStrategyAI::DummyStrategyAI() : StrategyAI()
{
//ctor
}

//-----------------------------------------------------------------------------
DummyStrategyAI::~DummyStrategyAI()
{
//dtor
}

//-----------------------------------------------------------------------------
void
DummyStrategyAI::PlaceSymbol(GridSymbols grid[3][3])
{
int gridx, gridy;

bool ok = false;
while(!ok)
{
gridx = rand()%3;
gridy = rand()%3;

if(grid[gridy][gridx] == EGS_NONE)
{
ok = true;
grid[gridy][gridx] = EGS_O;
}
}
}

Assieme alla DummyStrategyAI ho implementato anche una DefenderStrategyAI, una semplice AI che tende a bloccare i tentativi di tris del giocatore umano, pur non giocando d’attacco. Non riporto il codice di questa classe perchè non introduce nulla di nuovo.

Molto più interessante è vedere invece come queste classi vengono usate dalla PlayGameState.

Il metodo Reset è ora diventato così:

//-----------------------------------------------------------------------------
void
PlayGameState::Reset()
{
m_SymbolsPlacedCount = 0;

m_PlayerWins = m_ComputerWins = false;

if(m_pWinText)
{
SDL_FreeSurface(m_pWinText);
}
m_pWinText = 0;

for(int r=0; r<3; ++r)
{
for(int c=0; c<3; ++c)
{
m_Grid[r][c] = EGS_NONE;
}
}

if(m_pAI)
{
delete m_pAI;
m_pAI = 0;
}

StrategyAIFactory factory;
m_pAI = factory.CreateStrategyAI();
}

Come potete vedere c’è ora un nuovo membro, m_AI, che altro non è che un puntatore a StrategyAI. Tramite la factory otteniamo una istanza concreta di StrategyAI ad ogni chiamata della Reset, ovvero ad ogni inizio gioco. Il suo utilizzo poi è molto semplice: nel metodo HandleMouseInput, dove prima eseguivamo la logica random, ora abbiamo una sola riga:

m_pAI->PlaceSymbol(m_Grid);

Piuttosto intuitivo, no?

Non ci resta che dare un’occhiata alla StrategyFactory. La sua definizione:

#ifndef STRATEGYAIFACTORY_H
#define STRATEGYAIFACTORY_H

#include "StrategyAI.h"

class StrategyAIFactory
{
public:
StrategyAIFactory();
virtual ~StrategyAIFactory();

StrategyAI* CreateStrategyAI();

protected:
private:
};

#endif // STRATEGYAIFACTORY_H

… e la relativa implementazione:

#include <cstdlib>
#include "../include/StrategyAIFactory.h"
#include "../include/DummyStrategyAI.h"
#include "../include/DefenderStrategyAI.h"

//-----------------------------------------------------------------------------
StrategyAIFactory::StrategyAIFactory()
{
//ctor
}

//-----------------------------------------------------------------------------
StrategyAIFactory::~StrategyAIFactory()
{
//dtor
}

//-----------------------------------------------------------------------------
StrategyAI*
StrategyAIFactory::CreateStrategyAI()
{
int prob = rand() % 100;

if(prob < 50) return new DummyStrategyAI();
else return new DefenderStrategyAI();
}

Banalmente andiamo a scegliere con una probabilità del 50% tra le nostre due StrategyAI implementate. Ovviamente possiamo scrivere tutte le StrategyAI che vogliamo, arricchendo così il gioco di sfumature diverse e dando l’idea che il computer non si comporti seguendo sempre uno schema predefinito. Volendo, si potrebbe assegnare una faccina ad ogni Strategy e mostrarla nella schermata di gioco, umanizzando ulteriormente il computer e creando una certa aspettativa: il player inizierà a conoscere e distinguerei giocatori “forti” da quelli “scarsi”!

Con questo abbiamo finalmente concluso. TrisLick vuole essere un piccolo esempio di come realizzare qualcosa di piccolo ma concreto, lasciando aperte eventuali possibilità di sviluppo grazie ad una architettura flessibile.

Coraggio dunque, aprite il vostro editor, scaldate il compilatore e iniziate a scrivere il vostro prossimo gioco!

Buon coding!

venerdì 9 settembre 2011

A proposito di scrittura creativa...

Scrittura creativa?!?

No, non avete sbagliato blog. Sì ma, direte voi, cosa c'entra la scrittura creativa con la programmazione e i videogiochi? Vi rispondo con un'altra domanda. Cosa c'entrano il metal, i giochi di ruolo, i manga e/o la letteratura fantasy con gli argomenti tipicamente presenti in questi post? Nulla. Però chissà perchè finiscono per l'essere, tutti o in parte, presenti nella vita del programmatore medio. Chiamatele sinergie, se volete.

Ad ogni modo fatevene una ragione perchè il post di oggi riguarda proprio la scrittura creativa, l'attività cioè di scrivere per raccontare una storia in modo colorato e interessante. Se proprio volete trovarci un appiglio con la programmazione di videogiochi, pensate ad un game designer che voglia imbastire un plot per il proprio gioco, oppure smettete semplicemente di consumarvi le unghie sugli specchi e continuate a leggere sereni.

La scrittura creativa è alla base del mestiere dello scrittore. Io non sono uno scrittore e non mi permetterei mai di trattare con leggerezza un argomento tanto nobile. Ma, per sinergia, ho provato varie volte a scrivere qualcosa, come scommetto molti di voi. Lo stato d'animo costante con cui mi sono ritrovato dopo tali esperimenti è stato quasi sempre lo stesso: insoddisfazione, con pure una punta di frustrazione. Sì, perchè i miei scritti mi apparivano (appaiono) mosci, statici, poco interessanti.

Mi sono anche documentato, sapete, su come si scrive. Ho diversi libri a casa sull'argomento, che ho sempre trovato di estremo fascino (così come ho molti metodi per chitarra, ma questa è un'altra storia e un altro vagone di frustrazioni). Ogni tanto ne prendo uno e lo rileggo. Concordo sempre su tutto quel che dice l'autore, ma poi, all'atto pratico, cado negli stessi errori.

Ho capito che scrivere è una cosa dannatamente seria e complessa. Ma ho anche capito che uno dei miei errori fondamentali sta nel mancato rispetto di una semplice regola:

Mostra, non dire.

Facile, no? Anzichè essere didascalici ed elencare una serie di cose che accadono, ci si dovrebbe sforzare di mostrarle senza esplicitarle. Facciamo un esempio. Dovete parlare di un tizio di nome Mario che sta per uscire di casa quando si accorge che fuori piove. Scena semplice. Ecco come appare dicendo queste cose:

Mario si preparò per uscire di casa. Era già sull'uscio quando si accorse che stava piovendo.
Lineare. Grammaticalmente corretto (spero). Abbiamo detto quel che succede. Ora proviamo a mostrarlo:

Mario indossò la giacca blu, il regalo di Anna di un qualche Natale passato, quando ancora si facevano regali tra loro, e si diresse verso la porta d'ingresso. Stava per uscire quando l'odore della pioggia lo colse. Gettò uno sguardo alla finestra in soggiorno, in parte visibile anche da lì, torcendo un po' il collo. Goccioline scivolavano sul vetro, e ora sentiva anche gli schizzi sollevati dalle macchine. Anna avrebbe sbottato, ritta sull'uscio, dicendo che erano solo due gocce e di muoversi, che erano già in ritardo. Ma tanto Anna non c'era più. Mario prese l'ombrello e uscì.
Sono sicuro che un vero scrittore farebbe di meglio, ma spero di aver reso l'idea. In questa versione non ho mai detto che stava per uscire di casa, lo si intuisce dal fatto che indossa la giacca e va verso la porta. Non ho detto che stava piovendo, ho mostrato le gocce sul vetro e l'ombrello. L'odore della pioggia è un pò raccontato, lo ammetto, ma fate i conti con i miei limiti, per piacere.

Cosa si nota? Fisicamente è più lungo, il che potrebbe essere tanto un bene quanto un male. Quel che è bene invece è che mostrando le cose si è costretti a visualizzarsele nella mente, ma visualizzarsele per davvero, facendo uno sforzo. Si cercano dettagli. E saltano fuori cose a cui non avevamo pensato. Anna, per esempio, e il suo passato con Mario. Il lettore si pone delle domande. Chi è Anna? Stavano insieme? Erano sposati? Hanno divorziato? E' morta? Giuro che non avevo pensato ad Anna prima di provare a scrivere questo pezzetto, ma quando è stato il momento è saltata fuori con grande spontaneità. Anche la figura di Mario acquista spessore: anzichè il nome più comune in Italia diventa ora un personaggio di cui intuiamo qualcosa del carattere: forse pigro, abitudinario, amaro e un po' pedante. Bisognerebbe leggere il resto per capirlo.

Ci siete arrivati? Ve lo ripeto: bisognerebbe leggere il resto per capirlo. Dico, non è meraviglioso? 

La prima versione, quella detta, invoglia a proseguire? Non mi pare. Questa ha forse qualche chance in più, ergo mostrare le cose le rende più vive, più interessanti; e il racconto acquista corpo e immersione, oltre a trascinarci verso soluzioni non programmate.

Ora prendete un buon libro, di un autore che stimate, e provate a leggere qualche passaggio. Dice o mostra? E' un esercizio molto divertente e che, personalmente, mi fa apprezzare ancora di più la lettura. Giusto per farvi un esempio più professionale del mio, ecco come Robert Jordan introduce il personaggio di Rand al'Thor, il suo protagonista, nell'incipit della saga La Ruota del Tempo. Io avrei scritto che Rand avanzava a fatica lungo la strada battuta dal freddo vento che scendeva dai monti, e ne sarei anche stato orgoglioso. Ma ecco come se la cava Jordan, ecco il vento:

[...] Le raffiche incollarono il mantello alla schiena di Rand al'Thor e gli sbatterono contro le gambe la lana color terra, poi la fecero svolazzare dietro di lui. Rand rimpianse di non avere una giubba più pesante o una camicia in più. Quando cercava di stringersi addosso il mantello, finiva quasi sempre per farlo impigliare nella faretra che portava appesa a un fianco; e non serviva a molto neppure provare a tenerlo fermo con una mano sola, dal momento che nell'altra reggeva l'arco, con la freccia già incoccata, pronto all'uso. Una raffica particolarmente violenta gli strappò di mano il mantello. Rand lanciò un'occhiata a Tam, suo padre, che camminava dall'altro lato dell'irsuta giumenta saura, quasi ad assicurarsi che fosse sempre lì. Si sentì un po' sciocco, ma era una di quelle giornate in cui si ha bisogno di conforto. [...]

Brrr! Che freddo!