IPB     Una pizza e una birra...
Hai risolto dei problemi sul tuo Mac grazie a questo forum? Offrici una pizza..

Benvenuto su Tevac ( Log In | Registrati )
Tevac è una allegra combriccola di amici, alcuni appassionati di Mac, altri di Fotografia, o di entrambi.
Partecipa, dai vita a questa community, condividi le tue esperienze!
Non aver paura di iniziare una discussione o di rispondere ad un amico che cerca aiuto!

4 Pagine V   1 2 3 > »   Condividi questo topic su Digg · Condividi questo topic su del.icio.us · Condividi questo topic su Slashdot · Condividi questo topic su Technorati · Condividi questo topic su Furl · Condividi questo topic su Reddit · Condividi questo topic su Facebook · Condividi questo topic su Fark · Condividi questo topic su Google · Condividi questo topic su ma.gnolia · Condividi questo topic su Wink · Condividi questo topic su MyWeb · Condividi questo topic su Netscape
Reply to this topicStart new topic
> [Cocoa] - BlowUpYourFriend, un tutorial da zero
Marco Coïsson
messaggio 27 Jul 2004, 10:52
Messaggio #1


life, n: a whim of several billion cells to be you for a while
***********

Gruppo: Forum User +
Messaggi: 9.340
Iscritto il: 18-May 03
Da: Torino
Utente Nr.: 65



Con questo thread vorrei iniziare un progetto un po' avventuroso, ovvero un tutorial da zero sull'uso di Cocoa. Forse è meglio chiarire alcune cose.
L'errore che molti neofiti fanno (e che faccio sistematicamente anch'io che pure neofita non sono più tanto) è quello di farsi venire un'idea per un programma, pensarci un po' su, poi cercare di implementarlo nella sua interezza, in un colpo solo. Risultato: ore e ore passate a scrivere codice, senza avere la minima idea se quello che si sta facendo porterà a qualche risultato oppure no, finché ci si stufa (spesso prima ancora di dare la "prima compilata"), il progetto cade nel dimenticatoio e il programmatore in erba decide che tutta 'sta roba non fa per lui.

Sbagliato. I programmi si sviluppano a piccoli passettini, un pezzettino per volta. Questo ha innumerevoli vantaggi:
- si scrive poco codice per volta
- lo si esegue subito, e si fa subito il debug (correzione degli errori) di poche linee di codice
- si ha la percezione dei progressi che si fanno, e non ci si scoraggia

Con questo tutorial vorrei pertanto far vedere non già come si fa a costruire un programma da zero (c'è chi è ben più competente di me), ma come faccio io a creare un programma da zero, con tutti i miei limiti, difetti ed errori. È un tutorial "in diretta", in quanto il programma è in corso di sviluppo e posterò qui, di volta in volta, i progressi, commentandoli. Riporterò il codice sorgente, e renderò disponibile per il download il progetto fino al punto in cui è giunto in quel momento. Siete tutti invitati a fare domande e commenti in questo thread, a postare suggerimenti, a contribuire specialmente per la parte grafica e la parte sonora, che come avrete modo di vedere faranno decisamente pena. Naturalmente, chiunque contribuirà vedrà riconosciuto il suo nome nell'about box, nella documentazione e tutto dove necessario. Tevac, di suo, sarà a sua volta pubblicizzato. È una sorta di progetto "OpenSource", ma open sono agli amici di Tevac icon_wink.gif

Sì, ma di che cosa si tratta? Non vi sto a spiegare la lunga storia. Ne faccio solo un pezzettino. C'era una volta un gioco per Atari e per PC (DOS), non so come si chiamasse, ma all'Università ci giocavamo in aula studio su un vecchio 286. Visto che si giocava in due e lo scopo del gioco era far saltare in aria l'avversario, l'avevamo soprannominato "fotti l'amico". Anni fa l'ho riprogrammato per il System 8 e 9 del Mac, lo potete ancora scaricare dalla mia homepage nella sezione delle vecchie glorie. Gira ancora in modalità Classic anche sotto MacOS X. Vi invito a provarlo, così vi fate un'idea di com'è fatto il giochino e di come si presenterà (concettualmente) una volta completata questa nuova versione per MacOS X.
Anziché riprendere in mano il vecchio codice e rimetterlo a posto, ho deciso di ripartire da zero, scrivendo il programma in Objective-C e facendo uso delle librerie di Cocoa. Vediamo dove andremo a finire! icon_biggrin.gif Per rendere la cosa un po' più "geek", ho rinominato il programma "Blow Up Your Friend", che, con un voluto inglese sgrammaticato, vorrebbe dire "Fai saltare per aria un tuo amico" (giusto perché "Fuck your friend" non stava bene icon_redface.gif )

Sperando che la cosa vi interessi, vi invito a leggere oltre per i primi passi in questa affascinante avventura!


--------------------
Marco Coïsson
http://web.me.com/marco_coisson

L'atomo divisibile: podcast gratuito di divulgazione scientifica.

X come Macintosh
Go to the top of the page
 
+Quote Post
Marco Coïsson
messaggio 27 Jul 2004, 11:13
Messaggio #2


life, n: a whim of several billion cells to be you for a while
***********

Gruppo: Forum User +
Messaggi: 9.340
Iscritto il: 18-May 03
Da: Torino
Utente Nr.: 65



Allora, prima puntata:
BlowUpYourFriend 0.0.1 (partiamo da lontano icon_lol.gif ), scaricabile da qui: http://homepage.mac.com/marco_coisson/Tuto...BUYF/v0.0.1.zip

Ho creato un nuovo progetto con XCode, come Cocoa Application. Il progetto contiene, al momento, 4 classi e un file di header. Vediamoli:

la classe di controllo - BUYFcontroller
Ecco il file di interfaccia:
CODICE
/* BUYFcontroller */



#import <Cocoa/Cocoa.h>



#import "CostantiGenerali.h"

#import "BattleFieldView.h"



@interface BUYFcontroller : NSObject

{

   IBOutlet BattleFieldView    *battleField;

   IBOutlet NSWindow      *window;

}





@end

Ed ecco il file di implementazione:
CODICE
#import "BUYFcontroller.h"



@implementation BUYFcontroller



- (id)init

{

    [super init];

    return self;

}



- (void)awakeFromNib

{

    // dobbiamo impostare il frame della view

    //battleField=[[BattleFieldView alloc] initWithFrame:NSMakeRect(0,0,kCasellaSize*kPlanciaWidth,kCasellaSize*kPlanciaHeight)];

    if(kDebugMask & kControllerDebug)

 NSLog(@"Checking battle field bounds: (%d,%d), (%d,%d)",(int)([battleField bounds]).origin.x,(int)([battleField bounds]).origin.y,(int)([battleField bounds]).size.width,(int)([battleField bounds]).size.height);

    if(kDebugMask & kControllerDebug)

 NSLog(@"Setting battle field bounds: (%d,%d), (%d,%d)",0,0,(int)kCasellaSize*kPlanciaWidth,kCasellaSize*kPlanciaHeight);

    [battleField setBounds:NSMakeRect(0,0,kCasellaSize*kPlanciaWidth,kCasellaSize*kPlanciaHeight)];

    [battleField generateNewField];

}



@end

È una classe molto semplice: ha due outlet che puntano uno alla finestra di gioco, creata nel file MainMenu.nib, l'altro ad un oggetto di classe BattleFieldView, una sottoclasse di NSView, dove si svolgerà l'azione vera e propria del gioco. Questa classe, al momento, non fa molto, se non generare un nuovo campo di battaglia una volta che il programma è caricato in memoria (mi perdonerete: quando programmo uso un misto di italiano e inglese… chiedete lumi se ci sono difficoltà).
Di già che ci siamo, diamo un'occhiata al file di header che contiene alcune costanti di uso generale:
CostantiGenerali.h
CODICE
#define kPlanciaWidth      30 //caselle

#define kPlanciaHeight      20 //caselle

#define kCasellaSize      20 //pixel

#define kGiocatore1SegnalinoName      @"giocatore1.tif"

#define kGiocatore2SegnalinoName      @"giocatore2.tif"

#define kCasellaVuotaSegnalinoName      @"casella_vuota.tif"

#define kCoordinateInizialiGiocatore1  NSMakePoint(0,0)

#define kCoordinateInizialiGiocatore2  NSMakePoint(kPlanciaWidth-1,kPlanciaHeight-1)

#define kCasellaVuota      0



// debug

#define kBattleFieldDebug        0x01

#define kControllerDebug        0x02

#define kCasellaDebug      0x04

#define kDebugMask          0x00 + kBattleFieldDebug + kCasellaDebug + kControllerDebug

In questo file si definiscono le dimensioni della plancia di gioco, le risorse grafiche (i segnalini dei giocatori e l'immagine di sfondo di ogni casella), alcune costanti che permettono di selezionare vari livelli di "verbosità" da usare in caso di debug (si seleziona il livello di verbosità con l'ultima riga, dove si attivano tutti i posti del programma in cui si vuole che siano visualizzati i messaggi di debug).

il campo di battaglia - BattleFieldView
Ecco il file di interfaccia:
CODICE
/* BattleFieldView */



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"

#import "Casella.h"

#import "Giocatore.h"



@interface BattleFieldView : NSView

{

    NSMutableArray  *caselle;

    Giocatore      *giocatore1,*giocatore2;

}



- (void)generateNewField;



@end

Ed ecco il file di implementazione:
CODICE
#import "BattleFieldView.h"



@implementation BattleFieldView



- (id)initWithFrame:(NSRect)frameRect

{

    if ((self = [super initWithFrame:frameRect]) != nil)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Initing battle field view");

    }

    return self;

}



- (void)drawRect:(NSRect)rect

{

    NSEnumerator  *en;

    id        obj;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Drawing battle field");

    en=[caselle objectEnumerator];

    while(obj=[en nextObject])

    {

 if(NSIntersectsRect(rect,[obj frame]))

 {

     [[obj immagine] drawInRect:[obj frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

 }

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Drawing players");

    [[giocatore1 segnalino] drawInRect:[giocatore1 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

    [[giocatore2 segnalino] drawInRect:[giocatore2 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

}



- (void)generateNewField

{

    int      i,j;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new battle field");

    caselle=[NSMutableArray arrayWithCapacity:kPlanciaWidth*kPlanciaHeight];

    for(i=0;i<kPlanciaWidth;i++)

    {

 for(j=0;j<kPlanciaHeight;j++)

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"i: %d, j: %d",i,j);

     [caselle addObject:[[Casella alloc] initWithCoordinates:NSMakePoint(i,j)]];

 }

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new players");

    giocatore1=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore1 placeholder:[NSImage imageNamed:kGiocatore1SegnalinoName]];

    giocatore2=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore2 placeholder:[NSImage imageNamed:kGiocatore2SegnalinoName]];

}



@end

Anche qui la faccenda non è molto complicata: un'array tiene traccia di tutte le caselle di cui è costituito il campo di battaglia, mentre due oggetti sono i due giocatori. Il metodo generateNewField crea tutte le caselle della plancia e i due giocatori. Il metodo drawRect: viene chiamato automaticamente dal sistema operativo ogni volta che la view ha bisogno di essere ridisegnata; qui cicliamo su tutte le caselle della plancia e selezioniamo solo quelle che si trovano nell'area che necessita di ridisegno (rect). Quindi ridisegnamo i giocatori.

Le caselle - Casella
Ecco il file di interfaccia:
CODICE
//

//  Casella.h

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Mon Jul 26 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"





@interface Casella : NSObject

{

    int      riga;

    int      colonna;

    int      contenuto;

    NSImage  *immagine;

    NSRect  frame;

}



- (id)initWithCoordinates:(NSPoint)theCoord;

- (NSRect)frame;

- (NSImage *)immagine;



@end

Ed ecco il file di implementazione:
CODICE
//

//  Casella.m

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Mon Jul 26 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import "Casella.h"





@implementation Casella



- (id)initWithCoordinates:(NSPoint)theCoord

{

    [super init];

    if(kDebugMask & kCasellaDebug)

 NSLog(@"Creating new cell: (%d,%d)",(int)theCoord.x,(int)theCoord.y);

    riga=(int)theCoord.y;

    colonna=(int)theCoord.x;

    contenuto=kCasellaVuota;

    immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

    frame=NSMakeRect(colonna*kCasellaSize,riga*kCasellaSize,kCasellaSize,kCasellaSize);

    if(kDebugMask & kCasellaDebug)

 NSLog(@"New cell frame: (%d,%d), (%d,%d)",(int)frame.origin.x,(int)frame.origin.y,(int)frame.size.width,(int)frame.size.height);

    return self;

}



- (NSRect)frame

{

    return frame;

}



- (NSImage *)immagine

{

    return immagine;

}



@end

Anche qui la faccenda è semplice: ogni oggetto di classe Casella tiene memoria delle sue coordinate, del suo contenuto (per ora tutte le caselle sono vuote), della sua immagine. La variabile frame memorizza il rettangolo (nelle coordinate della plancia di gioco) in cui la casella dovrà essere disegnata. Ci serve per velocizzare le operazioni di ridisegno, e per controllare se la casella si trova nell'area che va ridisegnata oppure no.

il giocatore - Giocatore
Ecco il file di interfaccia:
CODICE
//

//  Giocatore.h

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Tue Jul 27 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"



@interface Giocatore : NSObject

{

    int      x,y; // le coordinate in "caselle"

    NSRect  frame; // dove si trova il segnalino (può essere in movimento)

    NSImage  *segnalino;

}



- (id)initWithCoordinates:(NSPoint)theCoord placeholder:(NSImage *)ph;

- (NSImage *)segnalino;

- (NSRect)frame;



@end

Ed ecco il file di implementazione:
CODICE
//

//  Giocatore.m

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Tue Jul 27 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import "Giocatore.h"





@implementation Giocatore



- (id)initWithCoordinates:(NSPoint)theCoord placeholder:(NSImage *)ph

{

    [super init];

    x=theCoord.x;

    y=theCoord.y;

    frame=NSMakeRect(x*kCasellaSize,y*kCasellaSize,kCasellaSize,kCasellaSize);

    segnalino=ph;

    return self;

}



- (NSImage *)segnalino

{

    return segnalino;

}



- (NSRect)frame

{

    return frame;

}



@end

Anche qui le cose non sono molto diverse che per la casella: il giocatore tiene traccia delle sue coordinate, del suo frame e dell'immagine che costituisce il suo segnalino.

Se provate a compilare ed eseguire il programma usando XCode, vedrete che parte (la verbosità del debug è alta, leggetevi un po' che cosa vi racconta il programma dei fatti suoi), compare la finestra di gioco, la plancia viene disegnata in un bel verdino e i due giocatori vengono piazzati a due angoli opposti. Per ora non potete fare altro, se non uscire dal programma. Dare il movimento ai giocatori sarà il prossimo passo.

Sono stato volutamente sintetico nelle spiegazioni; questo perché non vorrei scrivere la storia dell'Universo, ma vorrei stimolare la discussione. Fate domande, commentate, chiedete: per ogni passaggio che non è chiaro, per ogni classe Cocoa che non conoscete, per ogni soluzione adottata nel programma che non vi spiegate o non vi convince: chiedete! Questo thread è aperto per questo.


--------------------
Marco Coïsson
http://web.me.com/marco_coisson

L'atomo divisibile: podcast gratuito di divulgazione scientifica.

X come Macintosh
Go to the top of the page
 
+Quote Post
ricktriv
messaggio 27 Jul 2004, 13:09
Messaggio #3


Level 5/11
*****

Gruppo: Forum User +
Messaggi: 508
Iscritto il: 21-February 04
Utente Nr.: 1.555



L'idea mi sembra molto carina e molto utile per chi, come me, sta cercando di imparare a programmare!

Non parteciperò per adesso perchè vado in vacanza... Spero però di poter dare un aiuto quando torno!

Un saluto,

Marco.


--------------------
Power corrupts.
PowerPoint corrupts absolutely.
Go to the top of the page
 
+Quote Post
Marco Coïsson
messaggio 27 Jul 2004, 13:15
Messaggio #4


life, n: a whim of several billion cells to be you for a while
***********

Gruppo: Forum User +
Messaggi: 9.340
Iscritto il: 18-May 03
Da: Torino
Utente Nr.: 65



CITAZIONE(ricktriv)
L'idea mi sembra molto carina e molto utile per chi, come me, sta cercando di imparare a programmare!

Non parteciperò per adesso perchè vado in vacanza... Spero però di poter dare un aiuto quando torno!

Un saluto,

Marco.

Ti aspetteremo! icon_biggrin.gif Per allora, forse ci sarà già qualche puntata in più, ma le varie versioni di BUYF saranno sempre scaricabili singolarmente, così anche chi inizia "in ritardo" può seguire tutto passo per passo (e fare domande). Grazie e buone vacanze! icon_biggrin.gif


--------------------
Marco Coïsson
http://web.me.com/marco_coisson

L'atomo divisibile: podcast gratuito di divulgazione scientifica.

X come Macintosh
Go to the top of the page
 
+Quote Post
Marco Coïsson
messaggio 27 Jul 2004, 15:08
Messaggio #5


life, n: a whim of several billion cells to be you for a while
***********

Gruppo: Forum User +
Messaggi: 9.340
Iscritto il: 18-May 03
Da: Torino
Utente Nr.: 65



BlowUpYourFriend 0.0.2: scaricabile da qui:
http://homepage.mac.com/marco_coisson/Tuto...BUYF/v0.0.2.zip

Questo piccolo aumento di versione aggiunge apparentemente poco, ma in realtà stiamo gettando le basi per un sacco di cose future! In pratica, abbiamo aggiunto il movimento dei giocatori. Ora l'utente può muovere il segnalino di sinistra usando i tasti w (su), s (giù), a (sinistra), d (destra) e il segnalino di destra usando i tasti i (su), k (giù), j (sinistra), l (destra). Vediamo come si fa:

Le classi BUYFcontroller e Casella non sono state modificate, quindi non le commenteremo. Vediamo invece che cosa è successo alle altre due classi:

la plancia e il movimento - BattleFieldView
Ecco il file di interfaccia:
CODICE
/* BattleFieldView */



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"

#import "Casella.h"

#import "Giocatore.h"



@interface BattleFieldView : NSView

{

    NSMutableArray  *caselle;

    Giocatore      *giocatore1,*giocatore2;

}



- (void)generateNewField;

- (void)keyDown:(NSEvent *)theEvent;

- (BOOL)isValidMove:(NSPoint)pos;

@end

Come vedete abbiamo aggiunto due metodi; il primo è una sovrascrittura del metodo keyDown: implementato dalla classe NSView; lo sovrascriviamo perché ci serve per intercettare la pressione dei tasti della tastiera quando la finestra del nostro gioco è in primo piano. Il secondo è un metodo che definiamo noi, vedremo tra poco perché.
Ecco intanto il file di implementazione:
CODICE
#import "BattleFieldView.h"



@implementation BattleFieldView



- (id)initWithFrame:(NSRect)frameRect

{

    if ((self = [super initWithFrame:frameRect]) != nil)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Initing battle field view");

    }

    return self;

}



// so that we can get keyboard events

- (BOOL)acceptsFirstResponder

{

    return YES;

}



- (void)drawRect:(NSRect)rect

{

    NSEnumerator  *en;

    id        obj;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Drawing battle field");

    en=[caselle objectEnumerator];

    while(obj=[en nextObject])

    {

 if(NSIntersectsRect(rect,[obj frame]))

 {

     [[obj immagine] drawInRect:[obj frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

 }

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Drawing players");

    [[giocatore1 segnalino] drawInRect:[giocatore1 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

    [[giocatore2 segnalino] drawInRect:[giocatore2 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

}



- (void)generateNewField

{

    int      i,j;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new battle field");

    caselle=[NSMutableArray arrayWithCapacity:kPlanciaWidth*kPlanciaHeight];

    [caselle retain];

    for(i=0;i<kPlanciaWidth;i++)

    {

 for(j=0;j<kPlanciaHeight;j++)

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"i: %d, j: %d",i,j);

     [caselle addObject:[[Casella alloc] initWithCoordinates:NSMakePoint(i,j)]];

 }

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new players");

    giocatore1=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore1 placeholder:[NSImage imageNamed:kGiocatore1SegnalinoName]];

    giocatore2=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore2 placeholder:[NSImage imageNamed:kGiocatore2SegnalinoName]];

    //[giocatore1 retain];

    //[giocatore2 retain];

}



// intercettiamo gli eventi della tastiera

- (void)keyDown:(NSEvent *)theEvent

{

    NSString  *tasti;

    NSRect      rettangolo1,rettangolo2,rettangolo3,rettangolo4;

    NSPoint      nuovaPosizione;

    id    thePlayer;

    

    // dobbiamo innanzitutto ricordarci dov'erano i giocatori per poter ridisegnare le caselle sottostanti se si muovono

    rettangolo1=[giocatore1 frame];

    rettangolo2=[giocatore2 frame];

    

    tasti=[theEvent charactersIgnoringModifiers];

    if(kDebugMask & kBattleFieldDebug)

 NSLog(tasti);

    if([tasti caseInsensitiveCompare:kGiocatore1Su]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kSu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Giu]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kGiu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Sinistra]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kSinistra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Destra]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kDestra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Su]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kSu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Giu]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kGiu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Sinistra]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kSinistra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Destra]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kDestra nuovaCasella:&nuovaPosizione];

    else

    {

 // ignoriamo altri tasti

    }

    if([self isValidMove:nuovaPosizione])

 [thePlayer validateMove:nuovaPosizione];

    else

 [thePlayer didWrongMove];

    

    rettangolo3=[giocatore1 frame];

    rettangolo4=[giocatore2 frame];

    [self setNeedsDisplayInRect:rettangolo1];

    [self setNeedsDisplayInRect:rettangolo2];

    [self setNeedsDisplayInRect:rettangolo3];

    [self setNeedsDisplayInRect:rettangolo4];    

}



- (BOOL)isValidMove:(NSPoint)pos

{

    if((int)pos.x<0 || (int)pos.x>=kPlanciaWidth || (int)pos.y<0 || (int)pos.y>=kPlanciaHeight)

    {

 // il giocatore non può uscire dalla plancia

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Out of bounds: cannot move");

 return NO;

    }

    

    // qui controlliamo che i giocatori non occupino la stessa casella

    if(NSEqualPoints(pos,[giocatore1 coordinate]) || NSEqualPoints(pos,[giocatore2 coordinate]))

 return NO;



    // qui controlleremo gli ostacoli sulla plancia

    

    // se arriviamo qui abbiamo passato tutti i test

    return YES;

}



@end

Benché nell'interfaccia avessimo aggiunto due soli metodi, qui ce n'è un terzo: acceptsFirstResponder. Essendo un metodo definito dalla classe NSView che sovrascriviamo (come keyDown:) possiamo scegliere se metterlo o no nel file di interfaccia (come keyDown:, che invece abbiamo scelto di mettere, sa il cielo perché). Il metodo acceptsFirstResponder che ritorna un valore logico positivo è indispensabile affinché il meccanismo di intercettazione dei tasti premuti funzioni; se non lo si aggiunge (il valore restituito nell'implementazione di default di NSView è NO), BattleFieldView non intercetta le pressioni dei tasti e il nostro giochino non funziona.

Veniamo al metodo keyDown: l'evento passato come argomento contiene i tasti che sono stati premuti dall'utente (sotto forma di stringhe). Una semplice casistica permette di informare il giocatore interessato della decisione dell'utente di muoverne il segnalino in una delle quattro direzioni. Il meccanismo di movimento è in due passaggi: gli oggetti giocatore1 e giocatore2, infatti, non sanno nulla l'uno dell'altro né tanto meno di che cosa ci sia sulla plancia. Facciamo quindi in modo che sia compito loro dirci dove andrebbero a finire se si muovessero nella direzione scelta dall'utente, incaricando poi il metodo isValidMove: di BattleFieldView di verificare se la mossa sia valida oppure no (il giocatore che vuole muoversi non deve uscire dalla plancia, non deve occupare la casella occupata dall'altro giocatore e non deve incocciare in ostacoli presenti sulla plancia -ancora non ce ne siamo occupati). Se isValidMove: stabilisce che il giocatore può muoversi là dove indicato, allora al giocatore stesso viene comunicato, mediante il suo metodo validateMove:, di spostarsi nella nuova casella.
Il resto del metodo keyDown: serve per ridisegnare correttamente la plancia: le vecchie posizioni dei segnalini dei due giocatori e le nuove posizioni (sotto forma dei rettangoli nei quali vengono disegnati sulla plancia) sono memorizzate in quattro variabili e successivamente aggiunti alla porzione di plancia che ha bisogno di essere ridisegnata (il metodo setNeedsDisplayInRect:, implementato dalla classe NSView). Il sistema operativo provvederà automaticamente a chiamare la funzione drawRect: di BattleFieldView (che abbiamo già implementato nella versione 0.0.1 di BUYF) con un argomento opportuno per assicurare che tutto ciò che abbiamo dichiarato "da aggiornare" venga aggiornato.

il movimento del giocatore - Giocatore
Vediamo il file di interfaccia della classe Giocatore:
CODICE
//

//  Giocatore.h

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Tue Jul 27 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"



@interface Giocatore : NSObject

{

    int      x,y; // le coordinate in "caselle"

    NSRect  frame; // dove si trova il segnalino (può essere in movimento)

    NSImage  *segnalino;

}



- (id)initWithCoordinates:(NSPoint)theCoord placeholder:(NSImage *)ph;

- (NSPoint)coordinate;

- (NSImage *)segnalino;

- (NSRect)frame;

- (void)calcolaFrame;

- (id)vai:(int)direzione nuovaCasella:(NSPoint *)nuovaCasella;

- (void)validateMove:(NSPoint)nuovaPosizione;

- (void)didWrongMove;



@end

Abbiamo aggiunto il metodo coordinate, che ci serviva nella classe BattleFieldView per verificare che i segnalini non incocciassero l'uno nell'altro, il metodo vai:nuovaCasella:, il metodo validateMove: e il metodo didWrongMove. Ecco il file di implementazione:
CODICE
//

//  Giocatore.m

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Tue Jul 27 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import "Giocatore.h"





@implementation Giocatore



- (id)initWithCoordinates:(NSPoint)theCoord placeholder:(NSImage *)ph

{

    [super init];

    x=theCoord.x;

    y=theCoord.y;

    [self calcolaFrame];

    segnalino=ph;

    return self;

}



- (NSPoint)coordinate

{

    return NSMakePoint(x,y);

}



- (NSImage *)segnalino

{

    return segnalino;

}



- (NSRect)frame

{

    return frame;

}



- (id)vai:(int)direzione nuovaCasella:(NSPoint *)nuovaCasella

{

    int      newx,newy;

    

    if(kDebugMask & kGiocatoreDebug)

 NSLog(@"Moving to direction: %d from position: (%d,%d)",direzione,x,y);

    switch(direzione)

    {

 case kSu:

     newy=(int)y+1;

     newx=(int)x;

     break;

 case kGiu:

     newy=(int)y-1;

     newx=(int)x;

     break;

 case kSinistra:

     newx=(int)x-1;

     newy=(int)y;

     break;

 case kDestra:

     newx=(int)x+1;

     newy=(int)y;

     break;

    }

    if(kDebugMask & kGiocatoreDebug)

 NSLog(@"New position: (%d,%d)",newx,newy);

    *nuovaCasella=NSMakePoint(newx,newy);

    return self;

}



- (void)calcolaFrame

{

    frame=NSMakeRect(x*kCasellaSize,y*kCasellaSize,kCasellaSize,kCasellaSize);

    if(kDebugMask & kGiocatoreDebug)

 NSLog(@"Calculating new player frame: (%d,%d), (%d,%d)",(int)frame.origin.x,(int)frame.origin.y,(int)frame.size.width,(int)frame.size.height);

}



- (void)validateMove:(NSPoint)nuovaPosizione

{

    x=(int)nuovaPosizione.x;

    y=(int)nuovaPosizione.y;

    [self calcolaFrame];

}



- (void)didWrongMove

{

    // quando ce l'avremo, suoneremo un suono (breve) d'errore

}



@end

Il metodo coordinate è banale.
Il metodo vai:nuovaCasella: è invece interessante. Gli viene detto col primo argomento in quale direzione muovere il segnalino. Un blocco switch() calcola la coordinata della casella in cui andrebbe a finire il giocatore, e la memorizza in nuovaCasella, argomento che, essendo stato passato sotto forma di puntatore, è modificabile (e infatti tornerà modificato al punto di chiamata, nel metodo keyDown: della classe BattleFieldView). Il metodo ritorna poi un puntatore all'oggetto di cui fa parte, così che la classe BattleFieldView possa poi rivolgersi al giocatore in movimento senza dover più processare i tasti che sono stati premuti per stabilire di quale segnalino debba occuparsi.
Se la classe BattleFieldView ha deciso che lo spostamento è valido, il metodo validateMove: aggiorna la posizione del segnalino e il suo frame; se no, il metodo didWrongMove viene chiamato; in futuro, se qualche anima pia vorrà fornire un file sonoro della durata di qualche frazione di secondo che dia l'idea della "mossa sbagliata", questo sarà il punto del programma in cui tale suono verrà riprodotto.

Per concludere, ecco il file con le costanti generali:
CODICE
#define kPlanciaWidth      30 //caselle

#define kPlanciaHeight      20 //caselle

#define kCasellaSize      20 //pixel

#define kGiocatore1SegnalinoName      @"giocatore1.tif"

#define kGiocatore2SegnalinoName      @"giocatore2.tif"

#define kCasellaVuotaSegnalinoName      @"casella_vuota.tif"

#define kCoordinateInizialiGiocatore1  NSMakePoint(0,0)

#define kCoordinateInizialiGiocatore2  NSMakePoint(kPlanciaWidth-1,kPlanciaHeight-1)

#define kCasellaVuota      0



// tasti, azioni e direzioni

#define kGiocatore1Su      @"w"

#define kGiocatore1Giu      @"s"

#define kGiocatore1Sinistra        @"a"

#define kGiocatore1Destra        @"d"

#define kGiocatore2Su      @"i"

#define kGiocatore2Giu      @"k"

#define kGiocatore2Sinistra        @"j"

#define kGiocatore2Destra        @"l"



#define kSu            1

#define kGiu        2

#define kDestra        3

#define kSinistra          4



#define kInvalidMove      -1



// debug

#define kBattleFieldDebug        0x01

#define kControllerDebug        0x02

#define kCasellaDebug      0x04

#define kGiocatoreDebug      0x08

#define kDebugMask          0x00 + kBattleFieldDebug + kGiocatoreDebug


Ora che i nostri segnalini sono liberi di muoversi sulla plancia, con l'unico obbligo di non ostacolarsi l'uno con l'altro, è giunto il momento di mettere qualche ostacolo, sotto forma di pareti indistruttibili (di quelle distruttibili e degli eventuali bonus/malus che nascondono ce ne occuperemo successivamente). Vi aspetto pertanto per la versione 0.0.3! icon_biggrin.gif


--------------------
Marco Coïsson
http://web.me.com/marco_coisson

L'atomo divisibile: podcast gratuito di divulgazione scientifica.

X come Macintosh
Go to the top of the page
 
+Quote Post
chebfarid
messaggio 27 Jul 2004, 16:00
Messaggio #6


Level 9/11
*********

Gruppo: Team Moderatori
Messaggi: 4.665
Iscritto il: 11-July 03
Da: Milano
Utente Nr.: 395



Bella idea e anche il gioco è carino icon_biggrin.gif Sfruttando le mie doti schizofreniche mi sono fatto qualche partita con la versione classic contre me stesso icon_twisted.gif
Quindi subito qualcosa per la wishlist: sarebbe molto complicato implementare una funziona multiplayer via rete/tcp ip?
Io di objective c proprio non so nulla, ma forse è fattibile?

Buon lavoro
Farid


--------------------
Go to the top of the page
 
+Quote Post
Marco Coïsson
messaggio 27 Jul 2004, 16:12
Messaggio #7


life, n: a whim of several billion cells to be you for a while
***********

Gruppo: Forum User +
Messaggi: 9.340
Iscritto il: 18-May 03
Da: Torino
Utente Nr.: 65



CITAZIONE(chebfarid)
Bella idea e anche il gioco è carino  :D  Sfruttando le mie doti schizofreniche mi sono fatto qualche partita con la versione classic contre me stesso  :twisted:

Hai vinto? icon_lol.gif icon_lol.gif

CITAZIONE(chebfarid)
Quindi subito qualcosa per la wishlist: sarebbe molto complicato implementare una funziona multiplayer via rete/tcp ip?
Io di objective c proprio non so nulla, ma forse è fattibile?

Non ne ho la più pallida idea! È un argomento, quello del networking (dal punto di vista della programmazione), che non ho mai affrontato, in nessun linguaggio di programmazione. È però un aspetto molto interessante; direi che entra senza dubbio nelle "cose da fare" una volta che il gioco funziona ed è completo "in locale".


--------------------
Marco Coïsson
http://web.me.com/marco_coisson

L'atomo divisibile: podcast gratuito di divulgazione scientifica.

X come Macintosh
Go to the top of the page
 
+Quote Post
Marco Coïsson
messaggio 27 Jul 2004, 16:30
Messaggio #8


life, n: a whim of several billion cells to be you for a while
***********

Gruppo: Forum User +
Messaggi: 9.340
Iscritto il: 18-May 03
Da: Torino
Utente Nr.: 65



BlowUpYourFriend 0.0.3, scaricabile da qui:
http://homepage.mac.com/marco_coisson/Tuto...BUYF/v0.0.3.zip

Come promesso, questa volta aggiungiamo i "muri fissi". Creiamo un'altra bellissima risorsa grafica (un quadratino grigio scuro) che identificherà il muro fisso sulla plancia, la aggiungiamo al progetto, quindi iniziamo a lavorare sul codice. Le uniche classi coinvolte sono Casella e BattleFieldView. Iniziamo dalla prima:

I muri fissi come caselle - Casella
Ecco il file di interfaccia:
CODICE
//

//  Casella.h

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Mon Jul 26 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"





@interface Casella : NSObject

{

    int      riga;

    int      colonna;

    int      contenuto;

    NSImage  *immagine;

    NSRect  frame;

}



- (id)initWithCoordinates:(NSPoint)theCoord;

- (NSPoint)coordinate;

- (NSRect)frame;

- (NSImage *)immagine;

- (void)impostaContenuto:(int)cont;

- (int)contenuto;



@end

Abbiamo aggiunto il metodo coordinate (come già avevamo fatto per la classe Giocatore), e due metodi per impostare e leggere il contenuto della casella (vuota oppure "muro fisso", per ora). Ecco l'implementazione:
CODICE
//

//  Casella.m

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Mon Jul 26 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import "Casella.h"





@implementation Casella



- (id)initWithCoordinates:(NSPoint)theCoord

{

    [super init];

    if(kDebugMask & kCasellaDebug)

 NSLog(@"Creating new cell: (%d,%d)",(int)theCoord.x,(int)theCoord.y);

    riga=(int)theCoord.y;

    colonna=(int)theCoord.x;

    contenuto=kCasellaVuota;

    immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

    frame=NSMakeRect(colonna*kCasellaSize,riga*kCasellaSize,kCasellaSize,kCasellaSize);

    if(kDebugMask & kCasellaDebug)

 NSLog(@"New cell frame: (%d,%d), (%d,%d)",(int)frame.origin.x,(int)frame.origin.y,(int)frame.size.width,(int)frame.size.height);

    return self;

}



- (NSPoint)coordinate

{

    return NSMakePoint(colonna,riga);

}



- (NSRect)frame

{

    return frame;

}



- (NSImage *)immagine

{

    return immagine;

}



- (void)impostaContenuto:(int)cont

{

    contenuto=cont;

    switch(contenuto)

    {

 case kCasellaVuota:

     immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

     break;

 case kMuroFisso:

     immagine=[NSImage imageNamed:kMuroFissoSegnalinoName];

     break;

    }

}



- (int)contenuto

{

    return contenuto;

}



@end

Niente di sorprendente: il metodo coordinate restituisce le coordinate della casella sotto forma di NSPoint; il metodo impostaContenuto: riceve un argomento di tipo int che rappresenta il tipo di contenuto (si veda oltre il file CostantiGenerali.h): esso viene memorizzato nella variabile contenuto e l'immagine che rappresenterà la casella sulla plancia viene aggiornata. Il metodo contenuto restituisce il valore dell'omonima variabile.

La plancia - BattleFieldView
Ecco il file di interfaccia:
CODICE
/* BattleFieldView */



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"

#import "Casella.h"

#import "Giocatore.h"

#import "MCRandomExtractor.h"



@interface BattleFieldView : NSView

{

    NSMutableArray  *caselle;

    Giocatore      *giocatore1,*giocatore2;

}



- (void)generateNewField;

- (void)keyDown:(NSEvent *)theEvent;

- (BOOL)isValidMove:(NSPoint)pos;

- (id)casellaConCoordinate:(NSPoint)coord;

@end

Ed ecco il file di implementazione:
CODICE
#import "BattleFieldView.h"



@implementation BattleFieldView



- (id)initWithFrame:(NSRect)frameRect

{

    if ((self = [super initWithFrame:frameRect]) != nil)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Initing battle field view");

    }

    return self;

}



// so that we can get keyboard events

- (BOOL)acceptsFirstResponder

{

    return YES;

}



- (void)drawRect:(NSRect)rect

{

    NSEnumerator  *en;

    id        obj;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Drawing battle field");

    en=[caselle objectEnumerator];

    while(obj=[en nextObject])

    {

 if(NSIntersectsRect(rect,[obj frame]))

 {

     [[obj immagine] drawInRect:[obj frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

 }

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Drawing players");

    [[giocatore1 segnalino] drawInRect:[giocatore1 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

    [[giocatore2 segnalino] drawInRect:[giocatore2 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

}



- (void)generateNewField

{

    int      i,j;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new battle field");

    caselle=[NSMutableArray arrayWithCapacity:kPlanciaWidth*kPlanciaHeight];

    [caselle retain];

    for(i=0;i<kPlanciaWidth;i++)

    {

 for(j=0;j<kPlanciaHeight;j++)

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"i: %d, j: %d",i,j);

     [caselle addObject:[[Casella alloc] initWithCoordinates:NSMakePoint(i,j)]];

 }

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new players");

    giocatore1=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore1 placeholder:[NSImage imageNamed:kGiocatore1SegnalinoName]];

    giocatore2=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore2 placeholder:[NSImage imageNamed:kGiocatore2SegnalinoName]];

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating fixed walls");

    // ora posizioniamo i muri fissi

    {

 NSMutableArray  *c;

 NSEnumerator  *en;

 id        obj;

 MCRandomExtractor   *r;

 double    frazione;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing array");

 c=[NSMutableArray arrayWithArray:caselle];

 en=[c objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore1) || NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore2))

     {

   // è la casella iniziale di uno dei giocatori, non può contenere un muro fisso

   [c removeObject:obj];

     }

 }

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing for extraction");

 r=[[MCRandomExtractor alloc] initWithItemsArray:(NSArray *)c resetSeed:YES];

 frazione=0.0;

 do

 {

     [[r randomItemExtractionWithRemoval] impostaContenuto:kMuroFisso];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fraction: %g",frazione);

     frazione=frazione+(double)1/(kPlanciaWidth*kPlanciaHeight);

 }while(frazione<kFrazioneMuriFissi);

    }

}



// intercettiamo gli eventi della tastiera

- (void)keyDown:(NSEvent *)theEvent

{

    NSString  *tasti;

    NSRect      rettangolo1,rettangolo2,rettangolo3,rettangolo4;

    NSPoint      nuovaPosizione;

    id    thePlayer;

    

    // dobbiamo innanzitutto ricordarci dov'erano i giocatori per poter ridisegnare le caselle sottostanti se si muovono

    rettangolo1=[giocatore1 frame];

    rettangolo2=[giocatore2 frame];

    

    tasti=[theEvent charactersIgnoringModifiers];

    if(kDebugMask & kBattleFieldDebug)

 NSLog(tasti);

    if([tasti caseInsensitiveCompare:kGiocatore1Su]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kSu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Giu]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kGiu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Sinistra]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kSinistra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Destra]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kDestra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Su]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kSu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Giu]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kGiu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Sinistra]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kSinistra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Destra]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kDestra nuovaCasella:&nuovaPosizione];

    else

    {

 // ignoriamo altri tasti

    }

    if([self isValidMove:nuovaPosizione])

 [thePlayer validateMove:nuovaPosizione];

    else

 [thePlayer didWrongMove];

    

    rettangolo3=[giocatore1 frame];

    rettangolo4=[giocatore2 frame];

    [self setNeedsDisplayInRect:rettangolo1];

    [self setNeedsDisplayInRect:rettangolo2];

    [self setNeedsDisplayInRect:rettangolo3];

    [self setNeedsDisplayInRect:rettangolo4];    

}



- (BOOL)isValidMove:(NSPoint)pos

{

    if((int)pos.x<0 || (int)pos.x>=kPlanciaWidth || (int)pos.y<0 || (int)pos.y>=kPlanciaHeight)

    {

 // il giocatore non può uscire dalla plancia

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Out of bounds: cannot move");

 return NO;

    }

    

    // qui controlliamo che i giocatori non occupino la stessa casella

    if(NSEqualPoints(pos,[giocatore1 coordinate]) || NSEqualPoints(pos,[giocatore2 coordinate]))

 return NO;



    // qui controlleremo gli ostacoli sulla plancia

    if([[self casellaConCoordinate:pos] contenuto]==kMuroFisso)

 return NO;

    

    // se arriviamo qui abbiamo passato tutti i test

    return YES;

}



- (id)casellaConCoordinate:(NSPoint)coord

{

    NSEnumerator  *en;

    id        obj;

    

    en=[caselle objectEnumerator];

    while(obj=[en nextObject])

    {

 if(NSEqualPoints([obj coordinate],coord))

     return obj;

    }

}



@end

Il metodo generateNewField si è arricchito di un blocco finale. In esso usiamo la classe MCRandomExtractor, di cui potete trovare la documentazione (in inglese) sul mio sito eventualmente anche consultando la documentazione relativa al tutorial "Battaglia Navale" (sempre sulla stessa pagina) e che era stata anche segnalata sul forum di Tevac. Essa, nel modo con cui la usiamo qua (i file sorgenti della classe sono inclusi nel download), viene inizializzata con un'array (l'array contenente le caselle della plancia, dopo che abbiamo tolto quelle in cui si trovano inizialmente i due giocatori); quindi, una per volta fino ad aver estratto una certa frazione delle caselle totali, viene estratta una casella sempre diversa a cui si assegna come contenuto un "muro fisso".
Dobbiamo ora modificare il metodo isValidMove: affinché riconosca come illecita una mossa che porterebbe un giocatore in una casella contenente un muro fisso. È in effetti sufficiente controllare che la casella di destinazione non abbia, come contenuto, un "muro fisso". Il problema è che isValidMove: riceve come argomento una coordinata, mentre a noi serve la casella con quelle coordinate; cercarla è compito del metodo casellaConCoordinate:, che scorre l'array con tutte le caselle alla ricerca di quella con le coordinate giuste, e la restituisce.

Per concludere, ecco il file CostantiGenerali.h, debitamente aggiornato:
CODICE
#define kPlanciaWidth      30 //caselle

#define kPlanciaHeight      20 //caselle

#define kCasellaSize      20 //pixel

#define kGiocatore1SegnalinoName      @"giocatore1.tif"

#define kGiocatore2SegnalinoName      @"giocatore2.tif"

#define kCasellaVuotaSegnalinoName      @"casella_vuota.tif"

#define kMuroFissoSegnalinoName    @"muro_fisso.tif"

#define kCoordinateInizialiGiocatore1  NSMakePoint(0,0)

#define kCoordinateInizialiGiocatore2  NSMakePoint(kPlanciaWidth-1,kPlanciaHeight-1)



#define kCasellaVuota      0

#define kMuroFisso          1



#define kFrazioneMuriFissi        0.15



// tasti, azioni e direzioni

#define kGiocatore1Su      @"w"

#define kGiocatore1Giu      @"s"

#define kGiocatore1Sinistra        @"a"

#define kGiocatore1Destra        @"d"

#define kGiocatore2Su      @"i"

#define kGiocatore2Giu      @"k"

#define kGiocatore2Sinistra        @"j"

#define kGiocatore2Destra        @"l"



#define kSu            1

#define kGiu        2

#define kDestra        3

#define kSinistra          4



#define kInvalidMove      -1



// debug

#define kBattleFieldDebug        0x01

#define kControllerDebug        0x02

#define kCasellaDebug      0x04

#define kGiocatoreDebug      0x08

#define kDebugMask          0x00 + kBattleFieldDebug


Il prossimo passo sarà più difficile: inizieremo a gestire le bombe, dovremo occuparci del loro posizionamento sulla plancia da parte del giocatore, calcolare il tempo di miccia, farle esplodere e propagare l'esplosione alle caselle adiacenti, controllare che un giocatore non muoia per via di un'esplosione. Insomma, la versione 0.0.4 sarà interessante! icon_biggrin.gif


--------------------
Marco Coïsson
http://web.me.com/marco_coisson

L'atomo divisibile: podcast gratuito di divulgazione scientifica.

X come Macintosh
Go to the top of the page
 
+Quote Post
hotel_vv
messaggio 27 Jul 2004, 17:08
Messaggio #9


Level 6/11
******

Gruppo: Forum User +
Messaggi: 1.501
Iscritto il: 9-February 04
Da: Barcelona
Utente Nr.: 1.474



ehm.... blow up significa anche "farlo venire" icon_mrgreen.gif

ma che giochi fai? sporcaccccccione!


--------------------
ciao....
Go to the top of the page
 
+Quote Post
Marco Coïsson
messaggio 27 Jul 2004, 17:43
Messaggio #10


life, n: a whim of several billion cells to be you for a while
***********

Gruppo: Forum User +
Messaggi: 9.340
Iscritto il: 18-May 03
Da: Torino
Utente Nr.: 65



CITAZIONE(hotel_vv)
ehm.... blow up significa anche "farlo venire" icon_mrgreen.gif

ma che giochi fai? sporcaccccccione!

icon_redface.gif Mi sa che sarà meglio cambiare nome icon_redface.gif
Diciamo che questo è il "nome in codice", poi, per la versione 1.0, troviamo un nome migliore icon_wink.gif icon_lol.gif


--------------------
Marco Coïsson
http://web.me.com/marco_coisson

L'atomo divisibile: podcast gratuito di divulgazione scientifica.

X come Macintosh
Go to the top of the page
 
+Quote Post
chebfarid
messaggio 27 Jul 2004, 17:52
Messaggio #11


Level 9/11
*********

Gruppo: Team Moderatori
Messaggi: 4.665
Iscritto il: 11-July 03
Da: Milano
Utente Nr.: 395



CITAZIONE(hotel_vv)
ehm.... blow up significa anche "farlo venire" icon_mrgreen.gif

ma che giochi fai? sporcaccccccione!

Be' che ti aspetti se un gioco che fino a poco fa si chiamava "Fotti l'amico" ? icon_twisted.gif

Basta, ora a nanna
Farid


--------------------
Go to the top of the page
 
+Quote Post
Marco Coïsson
messaggio 28 Jul 2004, 13:13
Messaggio #12


life, n: a whim of several billion cells to be you for a while
***********

Gruppo: Forum User +
Messaggi: 9.340
Iscritto il: 18-May 03
Da: Torino
Utente Nr.: 65



BlowUpYourFriend 0.0.4, scaricabile da qui:
http://homepage.mac.com/marco_coisson/Tuto...BUYF/v0.0.4.zip

Questa volta c'è un sacco di lavoro da fare! Come preannunciato, si tratta di implementare le bombe: i giocatori devono poter posizionare bombe sul campo di gioco, queste dopo un po' esplodono, l'esplosione rimane per un po'; se un giocatore capita in una casella con un'esplosione in corso muore.
Di roba ce n'è parecchia, ed è stato necessario scrivere codice un po' ovunque, e anche creare una nuova classe. Quindi fate un respiro profondo e procediamo con calma.

Innanzitutto creiamo due nuove risorse grafiche da aggiungere al progetto, casella_bomba.tif e casella_esplosione.tif (spero sia chiaro che cosa vogliono dire). La bellezza di queste risorse grafiche è comparabile con quelle già esistenti e non riduce affatto il livello qualitativo generale dell'estetica. Quindi aggiungiamo un po' di roba a CostantiGenerali.h:
CODICE
#define kPlanciaWidth      30 //caselle

#define kPlanciaHeight      20 //caselle

#define kCasellaSize      20 //pixel

#define kGiocatore1SegnalinoName      @"giocatore1.tif"

#define kGiocatore2SegnalinoName      @"giocatore2.tif"

#define kCasellaVuotaSegnalinoName      @"casella_vuota.tif"

#define kMuroFissoSegnalinoName    @"muro_fisso.tif"

#define kCasellaBombaSegnalinoName      @"casella_bomba.tif"

#define kCasellaEsplosioneSegnalinoName  @"casella_esplosione.tif"

#define kCoordinateInizialiGiocatore1  NSMakePoint(0,0)

#define kCoordinateInizialiGiocatore2  NSMakePoint(kPlanciaWidth-1,kPlanciaHeight-1)



#define kCasellaVuota      0

#define kMuroFisso          1

#define kBomba        2



#define kFrazioneMuriFissi        0.15



#define kPotenzaFuoco      2

#define kBombaTempoMiccia        4 //secondi

#define kBombaTempoEsplosione    4 //secondi

#define kVerificaVitaGiocatoriTimer      0.5 //secondi



#define kFineDelGiocoNotification      @"EndOfGameNotification"

#define kVincitoreKey      @"vincitore"

#define kPerdenteKey      @"perdente"



// tasti, azioni e direzioni

#define kGiocatore1Su      @"w"

#define kGiocatore1Giu      @"s"

#define kGiocatore1Sinistra        @"a"

#define kGiocatore1Destra        @"d"

#define kGiocatore1Bomba        @"x"

#define kGiocatore2Su      @"i"

#define kGiocatore2Giu      @"k"

#define kGiocatore2Sinistra        @"j"

#define kGiocatore2Destra        @"l"

#define kGiocatore2Bomba        @","



#define kSu            1

#define kGiu        2

#define kDestra        3

#define kSinistra          4



#define kInvalidMove      -1



// debug

#define kBattleFieldDebug        0x01

#define kControllerDebug        0x02

#define kCasellaDebug      0x04

#define kGiocatoreDebug      0x08

#define kDebugMask          0x00 + kBattleFieldDebug + kControllerDebug

Oltre ai nomi delle nuove risorse grafiche, si tratta dei tasti usati dai giocatori per posizionare le bombe (x e , rispettivamente), alcune costanti riguardanti quanto tempo dura la miccia della bomba e quanto tempo dura un'esplosione, alcune costanti riguardanti la gestione del caso in cui un giocatore muoia per effetto di un'esplosione. Esaminando il codice successivo, vedrete come sono impiegate tutte queste cose.

le bombe del giocatore - Giocatore
Il file di interfaccia della classe Giocatore si arricchisce:
CODICE
//

//  Giocatore.h

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Tue Jul 27 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"



#define kBombeIniziali  1



@interface Giocatore : NSObject

{

    int      x,y; // le coordinate in "caselle"

    NSRect  frame; // dove si trova il segnalino (può essere in movimento)

    NSImage  *segnalino;

    int      numeroBombe;

}



- (id)initWithCoordinates:(NSPoint)theCoord placeholder:(NSImage *)ph;

- (NSPoint)coordinate;

- (NSImage *)segnalino;

- (NSRect)frame;

- (void)calcolaFrame;

- (id)vai:(int)direzione nuovaCasella:(NSPoint *)nuovaCasella;

- (void)validateMove:(NSPoint)nuovaPosizione;

- (void)didWrongMove;

- (int)potenzaDiFuoco;

- (BOOL)canDropBomb;

- (void)dropBomb;

- (void)getBomb;



@end

La variabile numeroBombe indica quante bombe per volta può usare il giocatore. Se le esaurisce, deve aspettare che l'effetto dell'esplosione di una bomba precedentemente piazzata si esaurisca, affinché il valore di questa variabile torni ad incrementarsi. Il metodo potenzaDiFuoco dice di quante caselle si propaga, nelle quattro direzioni, l'esplosione della bomba, rispetto alla casella in cui è stata piazzata (dipende da quante bombe può giocare simultaneamente il giocatore). Il metodo canDropBomb restituisce un valore logico vero se il giocatore può piazzare un'altra bomba (numeroBombe è maggiore di zero); dropBomb viene chiamata quando il giocatore posiziona una bomba, getBomb quando una bomba è esplosa e torna ad essere utilizzabile dal giocatore (verrà chiamata anche da un bonus, quando, in futuro, ci occuperemo dei bonus). Vediamo l'implementazione:
CODICE
//

//  Giocatore.m

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Tue Jul 27 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import "Giocatore.h"





@implementation Giocatore



- (id)initWithCoordinates:(NSPoint)theCoord placeholder:(NSImage *)ph

{

    [super init];

    x=theCoord.x;

    y=theCoord.y;

    [self calcolaFrame];

    segnalino=ph;

    numeroBombe=kBombeIniziali;

    return self;

}



- (NSPoint)coordinate

{

    return NSMakePoint(x,y);

}



- (NSImage *)segnalino

{

    return segnalino;

}



- (NSRect)frame

{

    return frame;

}



- (id)vai:(int)direzione nuovaCasella:(NSPoint *)nuovaCasella

{

    int      newx,newy;

    

    if(kDebugMask & kGiocatoreDebug)

 NSLog(@"Moving to direction: %d from position: (%d,%d)",direzione,x,y);

    switch(direzione)

    {

 case kSu:

     newy=(int)y+1;

     newx=(int)x;

     break;

 case kGiu:

     newy=(int)y-1;

     newx=(int)x;

     break;

 case kSinistra:

     newx=(int)x-1;

     newy=(int)y;

     break;

 case kDestra:

     newx=(int)x+1;

     newy=(int)y;

     break;

    }

    if(kDebugMask & kGiocatoreDebug)

 NSLog(@"New position: (%d,%d)",newx,newy);

    *nuovaCasella=NSMakePoint(newx,newy);

    return self;

}



- (void)calcolaFrame

{

    frame=NSMakeRect(x*kCasellaSize,y*kCasellaSize,kCasellaSize,kCasellaSize);

    if(kDebugMask & kGiocatoreDebug)

 NSLog(@"Calculating new player frame: (%d,%d), (%d,%d)",(int)frame.origin.x,(int)frame.origin.y,(int)frame.size.width,(int)frame.size.height);

}



- (void)validateMove:(NSPoint)nuovaPosizione

{

    x=(int)nuovaPosizione.x;

    y=(int)nuovaPosizione.y;

    [self calcolaFrame];

}



- (void)didWrongMove

{

    // quando ce l'avremo, suoneremo un suono (breve) d'errore

}



- (int)potenzaDiFuoco

{

    return numeroBombe+kPotenzaFuoco;

}



- (BOOL)canDropBomb

{

    if(numeroBombe>0)

 return YES;

    else

 return NO;

}



- (void)dropBomb

{

    numeroBombe--;

}



- (void)getBomb

{

    numeroBombe++;

}



@end

Niente di pazzesco: la potenza di fuoco cresce linearmente col numero di bombe che il giocatore può piazzare. Inizialmente ne può piazzare una per volta; in futuro, coi bonus, questo numero aumenterà.

La plancia e le bombe - BattleFieldView
Ecco il file di interfaccia:
CODICE
/* BattleFieldView */



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"

#import "Casella.h"

#import "Giocatore.h"

#import "MCRandomExtractor.h"

#import "Bomba.h"



@interface BattleFieldView : NSView

{

    NSMutableArray  *caselle;

    Giocatore      *giocatore1,*giocatore2;

    NSTimer    *timerVitaGiocatori;

    BOOL    running;

}



- (void)generateNewField;

- (void)keyDown:(NSEvent *)theEvent;

- (BOOL)isValidMove:(NSPoint)pos;

- (id)casellaConCoordinate:(NSPoint)coord;

- (void)explodeBomb:(NSTimer *)theTimer;

- (void)endOfExplosion:(NSTimer *)theTimer;

- (void)propagateExplosion:(id)info;

- (void)checkPlayerLife:(NSTimer *)theTimer;

@end

Abbiamo definito una nuova classe, Bomba, di cui parleremo tra un po'. Abbiamo poi aggiunto un flag, running, che assume il valore vero durante una partita e il valore falso quando un giocatore muore, e abbiamo definito una variabile destinata ad accogliere un timer che, ad intervalli regolari, controllerà che se i giocatori sono ancora vivi o se uno dei due sia morto. I metodi nuovi si occupano di far esplodere le bombe e farne propagare le esplosioni, e verificare se i giocatori siano vivi o meno. Vediamo l'implementazione:
CODICE
#import "BattleFieldView.h"



@implementation BattleFieldView



- (id)initWithFrame:(NSRect)frameRect

{

    if ((self = [super initWithFrame:frameRect]) != nil)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Initing battle field view");

    }

    return self;

}



// so that we can get keyboard events

- (BOOL)acceptsFirstResponder

{

    if(running)

 return YES;

    else

 return NO;

}



- (void)drawRect:(NSRect)rect

{

    NSEnumerator  *en;

    id        obj;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Drawing battle field");

    if(running)

    {

 en=[caselle objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSIntersectsRect(rect,[obj frame]))

     {

   [[obj immagine] drawInRect:[obj frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

     }

 }

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Drawing players");

 [[giocatore1 segnalino] drawInRect:[giocatore1 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

 [[giocatore2 segnalino] drawInRect:[giocatore2 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

    }

}



- (void)generateNewField

{

    int      i,j;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new battle field");

    caselle=[NSMutableArray arrayWithCapacity:kPlanciaWidth*kPlanciaHeight];

    [caselle retain];

    for(i=0;i<kPlanciaWidth;i++)

    {

 for(j=0;j<kPlanciaHeight;j++)

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"i: %d, j: %d",i,j);

     [caselle addObject:[[Casella alloc] initWithCoordinates:NSMakePoint(i,j)]];

 }

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new players");

    giocatore1=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore1 placeholder:[NSImage imageNamed:kGiocatore1SegnalinoName]];

    giocatore2=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore2 placeholder:[NSImage imageNamed:kGiocatore2SegnalinoName]];

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating fixed walls");

    // ora posizioniamo i muri fissi

    {

 NSMutableArray  *c;

 NSEnumerator  *en;

 id        obj;

 MCRandomExtractor   *r;

 double    frazione;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing array");

 c=[NSMutableArray arrayWithArray:caselle];

 en=[c objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore1) || NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore2))

     {

   // è la casella iniziale di uno dei giocatori, non può contenere un muro fisso

   [c removeObject:obj];

     }

 }

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing for extraction");

 r=[[MCRandomExtractor alloc] initWithItemsArray:(NSArray *)c resetSeed:YES];

 frazione=0.0;

 do

 {

     [[r randomItemExtractionWithRemoval] impostaContenuto:kMuroFisso];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fraction: %g",frazione);

     frazione=frazione+(double)1/(kPlanciaWidth*kPlanciaHeight);

 }while(frazione<kFrazioneMuriFissi);

    }

    

    // per finire attiviamo il timer che si occupa di verificare che i giocatori non siano morti

    timerVitaGiocatori=[NSTimer scheduledTimerWithTimeInterval:kVerificaVitaGiocatoriTimer

     target:self

     selector:@selector(checkPlayerLife:)

     userInfo:nil

     repeats:YES];

    running=YES;

}



// intercettiamo gli eventi della tastiera

- (void)keyDown:(NSEvent *)theEvent

{

    NSString  *tasti;

    NSRect      rettangolo1,rettangolo2,rettangolo3,rettangolo4;

    NSPoint      nuovaPosizione;

    id    thePlayer;

    

    // dobbiamo innanzitutto ricordarci dov'erano i giocatori per poter ridisegnare le caselle sottostanti se si muovono

    rettangolo1=[giocatore1 frame];

    rettangolo2=[giocatore2 frame];

    

    tasti=[theEvent charactersIgnoringModifiers];

    if(kDebugMask & kBattleFieldDebug)

 NSLog(tasti);

    if([tasti caseInsensitiveCompare:kGiocatore1Su]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kSu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Giu]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kGiu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Sinistra]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kSinistra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Destra]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kDestra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Su]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kSu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Giu]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kGiu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Sinistra]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kSinistra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Destra]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kDestra nuovaCasella:&nuovaPosizione];

    else

    {

 // ignoriamo altri tasti

 nuovaPosizione=NSMakePoint(kInvalidMove,kInvalidMove);

 thePlayer=nil;

    }

    if([self isValidMove:nuovaPosizione])

    {

 [thePlayer validateMove:nuovaPosizione];

 rettangolo3=[giocatore1 frame];

 rettangolo4=[giocatore2 frame];

 [self setNeedsDisplayInRect:rettangolo1];

 [self setNeedsDisplayInRect:rettangolo2];

 [self setNeedsDisplayInRect:rettangolo3];

 [self setNeedsDisplayInRect:rettangolo4];

    }

    else

    {

 if(thePlayer)

     [thePlayer didWrongMove];

    }

    

    // però il giocatore potrebbe aver premuto il tasto per posizionare la bomba

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Looking if player wants to drop a bomb");

    if(([tasti caseInsensitiveCompare:kGiocatore1Bomba]==NSOrderedSame) || ([tasti caseInsensitiveCompare:kGiocatore2Bomba]==NSOrderedSame))

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"One player wants to drop a bomb");

 if([tasti caseInsensitiveCompare:kGiocatore1Bomba]==NSOrderedSame)

     thePlayer=giocatore1;

 else

     thePlayer=giocatore2;

 if([thePlayer canDropBomb])

 {

     Bomba      *b;

     NSTimer      *t;

     

     [thePlayer dropBomb];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"OK, ready to drop bomb, setting up bomb object");

     b=[[Bomba alloc] initWithPosition:[thePlayer coordinate] power:[thePlayer potenzaDiFuoco] player:thePlayer explode:YES];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Setting up timer for callback on explosion");

     t=[NSTimer scheduledTimerWithTimeInterval:kBombaTempoMiccia

           target:self

           selector:@selector(explodeBomb:)

           userInfo:b

           repeats:NO];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Telling cell to place bomb");

     [[self casellaConCoordinate:[thePlayer coordinate]] impostaContenuto:kBomba];

 }

    }

}



- (BOOL)isValidMove:(NSPoint)pos

{

    if((int)pos.x<0 || (int)pos.x>=kPlanciaWidth || (int)pos.y<0 || (int)pos.y>=kPlanciaHeight)

    {

 // il giocatore non può uscire dalla plancia

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Out of bounds: cannot move");

 return NO;

    }

    

    // qui controlliamo che i giocatori non occupino la stessa casella

    if(NSEqualPoints(pos,[giocatore1 coordinate]) || NSEqualPoints(pos,[giocatore2 coordinate]))

 return NO;



    // qui controlleremo gli ostacoli sulla plancia

    if([[self casellaConCoordinate:pos] contenuto]==kMuroFisso)

 return NO;

 

    // se arriviamo qui abbiamo passato tutti i test

    return YES;

}



- (id)casellaConCoordinate:(NSPoint)coord

{

    NSEnumerator  *en;

    id        obj;

    

    en=[caselle objectEnumerator];

    while(obj=[en nextObject])

    {

 if(NSEqualPoints([obj coordinate],coord))

     return obj;

    }

    return nil;

}



- (void)explodeBomb:(NSTimer *)theTimer

{

    id      info;

    Bomba  *b;

    NSTimer  *t;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Explosion!!!");

    info=[theTimer userInfo];

    

    // ora mettiamo l'esplosione e la propaghiamo

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Exploding bomb cell");

    [[self casellaConCoordinate:[info posizione]] impostaContenuto:kCasellaVuota];

    [[self casellaConCoordinate:[info posizione]] startExplosion];

    [self setNeedsDisplayInRect:[[self casellaConCoordinate:[info posizione]] frame]];

    

    [self propagateExplosion:info];

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Now let's set up timer and object for end of explosion");

    b=[[Bomba alloc] initWithPosition:[info posizione] power:[info potenza] player:[info giocatore] explode:NO];

    t=[NSTimer scheduledTimerWithTimeInterval:kBombaTempoEsplosione

         target:self

         selector:@selector(endOfExplosion:)

         userInfo:b

         repeats:NO];

}



- (void)endOfExplosion:(NSTimer *)theTimer

{

    id  info;

    

    if(running)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"End of explosion");

 info=[theTimer userInfo];

 

 // riportiamo innanzitutto la casella in condizioni normali

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Stopping explosion in bomb cell");

 [[self casellaConCoordinate:[info posizione]] stopExplosion];

 [self setNeedsDisplayInRect:[[self casellaConCoordinate:[info posizione]] frame]];

 

 // ora propaghiamo

 [self propagateExplosion:info];

 

 // infine ridiamo la bomba al giocatore

 [[info giocatore] getBomb];

    }

}



- (void)propagateExplosion:(id)info

{

    int  i;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Propagating (explosion|removal: %d) %d cell(s) to the left",[info esplosione],i);

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) to the left",i);

 p=[info posizione];

 p.x=p.x-i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

      [c startExplosion];

   else

      [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) to the right",i);

 p=[info posizione];

 p.x=p.x+i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) upwards",i);

 p=[info posizione];

 p.y=p.y+i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating explosion");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) downwards",i);

 p=[info posizione];

 p.y=p.y-i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

}



- (void)checkPlayerLife:(NSTimer *)theTimer

{

    id      player,otherplayer;

    

    player=nil;

    if([[self casellaConCoordinate:[giocatore1 coordinate]] isExploding])

    {

 // è morto il giocatore 1

 player=giocatore2;

 otherplayer=giocatore1;

    }

    if([[self casellaConCoordinate:[giocatore2 coordinate]] isExploding])

    {

 // è morto il giocatore 2

 player=giocatore1;

 otherplayer=giocatore2;

    }

    if(player!=nil)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Player dead!");

 running=NO;

 [timerVitaGiocatori invalidate];

 [[NSNotificationCenter defaultCenter] postNotificationName:kFineDelGiocoNotification object:nil userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[player segnalino],kVincitoreKey,[otherplayer segnalino],kPerdenteKey,nil]];

    }

}



@end

Abbiamo modificato il metodo acceptsFirstResponder giusto per eleganza: è inutile intercettare gli eventi della tastiera (pressione dei tasti) quando il gioco è fermo perché un giocatore è morto. Il metodo generateNewField si conclude ora con l'impostazione del flag running a YES e con la creazione di un timer che, ripetutamente ad intervalli prefissati (mezzo secondo, in accordo con CostantiGenerali.h) chiama il metodo checkPlayerLife: per verificare se i giocatori siano vivi o se uno di loro sia morto.
Il metodo keyDown: ha subito un lieve restyling per evitare che la procedura di ridisegno della plancia venga chiamata anche quando l'utente ha premuto un tasto che non comporta il movimento del segnalino. Inoltre ora intercettiamo anche i tasti che servono per piazzare le bombe. Il meccanismo qui è interessante. Creiamo un nuovo oggetto di classe Bomba (si veda oltre per un commento a questa classe) che sostanzialmente tiene traccia: delle coordinate in cui è stata piazzata la bomba, della potenza di fuoco che ha (al momento in cui è stata piazzata, perché il giocatore potrebbe aumentare la propria potenza di fuoco dopo aver piazzato la bomba ma prima che questa esploda), del giocatore che l'ha piazzata; inoltre, un flag indica se la bomba è appena stata messa e ancora deve esplodere, o se invece è già esplosa e stiamo aspettando che l'effetto devastante dell'esplosione termini. Un timer viene innescato e l'oggetto di classe Bomba viene accluso al timer stesso; il metodo explodeBomb: verrà chiamato quando il tempo sarà trascorso (è il tempo di miccia della bomba).
Il metodo explodeBomb: usa le informazioni contenute nell'oggetto di classe Bomba che era stato accluso al timer per innescare un'esplosione nella casella in cui era stata piazzata la bomba (il metodo startExplosion della classe Casella, si veda oltre). Inoltre, il metodo propagateExplosion: propagherà l'esplosione alle caselle adiacenti. Infine un altro timer viene innescato con un tempo pari a quello per cui durano gli effetti dell'esplosione. Nell'oggetto allegato vengono passate le stesse informazioni di prima, ma viene specificato che ora non bisogna più far esplodere la bomba ma attendere la fine degli effetti dell'esplosione. Allo scadere del timer, il metodo chiamato sarà endOfExplosion:
Quest'ultimo si comporta in maniera simile a explodeBomb:, riportando in condizioni normali la casella in cui c'era la bomba (stopExplosion della classe Casella) e chiamando la propagateExplosion: per le caselle adiacenti. Infine ridà al giocatore facoltà di depositare una nuova bomba in sostituzione di quella i cui effetti sono appena terminati.
propagateExplosion: verifica che le caselle adiacenti a quella in cui è stata posizionata la bomba, fino ad una distanza data dalla potenza di fuoco, siano libere (non ci siano muri fissi che fanno da schermo), e propaga ad esse l'esplosione o il termine della stessa.
Infine il metodo checkPlayerLife:, chiamato periodicamente, verifica che nessun giocatore si trovi in una casella in cui è in corso un'esplosione. Se uno di essi è in questa condizione, solleva una notifica, un meccanismo particolare grazie al quale un altro punto del programma, magari anche in un altro oggetto, che si sia opportunamente registrato viene informato dal sistema operativo che è successo qualche cosa. Usiamo questo meccanismo per informare BUYFcontroller che il gioco è finito e bisogna mostrare all'utente chi ha vinto e chi ha perso, e se necessario ricominciare una nuova partita.

le bombe - Bomba
La classe Bomba ha questo file di interfaccia:
CODICE
//

//  Bomba.h

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Wed Jul 28 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"





@interface Bomba : NSObject

{

    NSPoint      posizione;

    int    potenza;

    id    gioc;

    BOOL      explode; //YES: esplosione, NO: rimozione esplosione

}



- (id)initWithPosition:(NSPoint)thePos power:(int)thePow player:(id)thePlayer explode:(BOOL)theEx;

- (NSPoint)posizione;

- (int)potenza;

- (id)giocatore;

- (BOOL)esplosione;



@end

e questo file di implementazione:
CODICE
//

//  Bomba.m

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Wed Jul 28 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import "Bomba.h"





@implementation Bomba



- (id)initWithPosition:(NSPoint)thePos power:(int)thePow player:(id)thePlayer explode:(BOOL)theEx;

{

    [super init];

    posizione=thePos;

    potenza=thePow;

    gioc=thePlayer;

    explode=theEx;

    return self;

}



- (NSPoint)posizione

{

    return posizione;

}



- (int)potenza

{

    return potenza;

}



- (id)giocatore

{

    return gioc;

}



- (BOOL)esplosione

{

    return explode;

}



@end
Come detto, tiene traccia di chi ha posizionato la bomba, dove e quanto sia potente. Il flag explode ricorda se la bomba debba ancora esplodere o se sia già esplosa e si stia attendendo il termine degli effetti dell'esplosione.

le bombe e le caselle - Casella
Anche la gestione delle bombe da parte delle caselle è delicata. Iniziamo a vedere il file di interfaccia della classe Casella:
CODICE
//

//  Casella.h

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Mon Jul 26 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"





@interface Casella : NSObject

{

    int      riga;

    int      colonna;

    int      contenuto;

    NSImage  *immagine;

    NSRect  frame;

    int      explosionLevel;

}



- (id)initWithCoordinates:(NSPoint)theCoord;

- (NSPoint)coordinate;

- (NSRect)frame;

- (NSImage *)immagine;

- (void)impostaContenuto:(int)cont;

- (int)contenuto;

- (void)startExplosion;

- (void)stopExplosion;

- (BOOL)isExploding;



@end

Una nuova variabile, explosionLevel, viene usata per tenere memoria di quante esplosioni simultanee siano in corso in una casella: infatti, per effetto della propagazione, essa può essere colpita da più di un'esplosione per volta, ma i tempi di inizio e fine dell'esplosione potrebbero essere diversi, e non bisogna riportare la casella in condizioni normali troppo presto. startExplosion, stopExplosion e isExploding servono proprio per controllare tutto ciò. Vediamo come:
CODICE
//

//  Casella.m

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Mon Jul 26 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import "Casella.h"





@implementation Casella



- (id)initWithCoordinates:(NSPoint)theCoord

{

    [super init];

    if(kDebugMask & kCasellaDebug)

 NSLog(@"Creating new cell: (%d,%d)",(int)theCoord.x,(int)theCoord.y);

    riga=(int)theCoord.y;

    colonna=(int)theCoord.x;

    contenuto=kCasellaVuota;

    immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

    frame=NSMakeRect(colonna*kCasellaSize,riga*kCasellaSize,kCasellaSize,kCasellaSize);

    if(kDebugMask & kCasellaDebug)

 NSLog(@"New cell frame: (%d,%d), (%d,%d)",(int)frame.origin.x,(int)frame.origin.y,(int)frame.size.width,(int)frame.size.height);

    explosionLevel=0;

    return self;

}



- (NSPoint)coordinate

{

    return NSMakePoint(colonna,riga);

}



- (NSRect)frame

{

    return frame;

}



- (NSImage *)immagine

{

    return immagine;

}



- (void)impostaContenuto:(int)cont

{

    contenuto=cont;

    switch(contenuto)

    {

 case kCasellaVuota:

     immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

     break;

 case kMuroFisso:

     immagine=[NSImage imageNamed:kMuroFissoSegnalinoName];

     break;

 case kBomba:

     immagine=[NSImage imageNamed:kCasellaBombaSegnalinoName];

     break;

    }

}



- (int)contenuto

{

    return contenuto;

}



- (void)startExplosion

{

    explosionLevel++;

    if(explosionLevel>0)

 immagine=[NSImage imageNamed:kCasellaEsplosioneSegnalinoName];

}



- (void)stopExplosion

{

    explosionLevel--;

    if(explosionLevel<=0)

    {

 explosionLevel=0;

 immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

    }

}



- (BOOL)isExploding

{

    if(explosionLevel>0)

 return YES;

    else

 return NO;

}



@end

explosionLevel viene incrementato tutte le volte che inizia un'esplosione, e l'immagine della casella viene impostata in quella che visualizza l'esplosione. explosionLevel viene diminuito tutte le volte che un'esplosione termina, ma l'immagine è riportata a quella della casella vuota solo quando explosionLevel raggiunge il valore zero. isExploding restituisce un valore logico vero se explosionLevel è maggiore di zero per la casella.
Da notare che mentre la presenza di una bomba nella casella è individuata dalla variabile contenuto, la presenza di un'esplosione è gestita diversamente; questo perché, quando ci occuperemo di bonus e malus, un'esplosione che faccia saltare in aria un muro "mobile" può rivelare un contenuto benigno o maligno… ma stiamo anticipando troppo.

chi ha vinto? - BUYFcontroller
Per finire vediamo come gestire il caso di vittoria di un giocatore:
CODICE
/* BUYFcontroller */



#import <Cocoa/Cocoa.h>



#import "CostantiGenerali.h"

#import "BattleFieldView.h"



@interface BUYFcontroller : NSObject

{

   IBOutlet BattleFieldView    *battleField;

   IBOutlet NSWindow      *window;

    IBOutlet NSWindow      *winlosewindow;

    IBOutlet NSImageView  *whoWins,*whoLoses;

}



- (void)endOfGame:(NSNotification *)n;

- (IBAction)newGame:(id)sender;



@end

Aggiungiamo al progetto una finestra con due oggetti di classe NSImageView, destinati a contenere i segnalini del giocatore che ha vinto e di quello che ha perso.
CODICE
#import "BUYFcontroller.h"



@implementation BUYFcontroller



- (id)init

{

    [super init];

    return self;

}



- (void)awakeFromNib

{

    // dobbiamo impostare il frame della view

    //battleField=[[BattleFieldView alloc] initWithFrame:NSMakeRect(0,0,kCasellaSize*kPlanciaWidth,kCasellaSize*kPlanciaHeight)];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(endOfGame:) name:kFineDelGiocoNotification object:nil];

    if(kDebugMask & kControllerDebug)

 NSLog(@"Checking battle field bounds: (%d,%d), (%d,%d)",(int)([battleField bounds]).origin.x,(int)([battleField bounds]).origin.y,(int)([battleField bounds]).size.width,(int)([battleField bounds]).size.height);

    if(kDebugMask & kControllerDebug)

 NSLog(@"Setting battle field bounds: (%d,%d), (%d,%d)",0,0,(int)kCasellaSize*kPlanciaWidth,kCasellaSize*kPlanciaHeight);

    [battleField setBounds:NSMakeRect(0,0,kCasellaSize*kPlanciaWidth,kCasellaSize*kPlanciaHeight)];

    [battleField generateNewField];

}



- (void)endOfGame:(NSNotification *)n

{

    if(kDebugMask & kControllerDebug)

 NSLog(@"The game has ended. Displaying sheet");

    [whoWins setImage:[[n userInfo] objectForKey:kVincitoreKey]];

    [whoLoses setImage:[[n userInfo] objectForKey:kPerdenteKey]];

    [window close];

    [winlosewindow makeKeyAndOrderFront:self];

}



- (IBAction)newGame:(id)sender

{

    [winlosewindow close];

    [battleField generateNewField];

    [window makeKeyAndOrderFront:self];

    [battleField setNeedsDisplay:YES];

}



@end

All'interno del metodo awakeFromNib diciamo al sistema operativo che BUYFcontroller risponde, per mezzo del metodo endOfGame:, alla notifica sollevata da BattleFieldView in caso di morte di uno dei giocatori. endOfGame: usa le informazioni contenute nella notifica per disegnare correttamente il segnalino di chi ha vinto e di chi ha perso nell'apposita finestra. La finestra di gioco viene nascosta e il programma resta in attesa di un'azione da parte dell'utente, che può uscire dal gioco, oppure premere il pulsante OK, nel qual caso il metodo newGame: viene chiamato: la finestra con vincitori e vinti viene nascosta, la finestra di gioco viene mostrata e un nuovo campo di battaglia viene generato, dando inizio ad una nuova sfida.

Con la prossima versione, la 0.0.5, implementeremo i "muri mobili" o "muri distruttibili", indispensabili per potervi nascondere sotto i bonus e i malus che implementeremo in seguito. A presto!


--------------------
Marco Coïsson
http://web.me.com/marco_coisson

L'atomo divisibile: podcast gratuito di divulgazione scientifica.

X come Macintosh
Go to the top of the page
 
+Quote Post
Marco Coïsson
messaggio 28 Jul 2004, 14:33
Messaggio #13


life, n: a whim of several billion cells to be you for a while
***********

Gruppo: Forum User +
Messaggi: 9.340
Iscritto il: 18-May 03
Da: Torino
Utente Nr.: 65



BlowUpYourFriend 0.0.5, scaricabile da qui:
http://homepage.mac.com/marco_coisson/Tuto...BUYF/v0.0.5.zip

Ecco che fanno la loro comparsa i muri che possono essere fatti esplodere, familiarmente detti "muri mobili" (anche se mobili non sono). Questa volta le modifiche non sono troppo impegnative.

CostantiGenerali.h
CODICE
#define kPlanciaWidth      30 //caselle

#define kPlanciaHeight      20 //caselle

#define kCasellaSize      20 //pixel

#define kGiocatore1SegnalinoName      @"giocatore1.tif"

#define kGiocatore2SegnalinoName      @"giocatore2.tif"

#define kCasellaVuotaSegnalinoName      @"casella_vuota.tif"

#define kMuroFissoSegnalinoName    @"muro_fisso.tif"

#define kMuroMobileSegnalinoName      @"muro_mobile.tif"

#define kCasellaBombaSegnalinoName      @"casella_bomba.tif"

#define kCasellaEsplosioneSegnalinoName  @"casella_esplosione.tif"

#define kCoordinateInizialiGiocatore1  NSMakePoint(0,0)

#define kCoordinateInizialiGiocatore2  NSMakePoint(kPlanciaWidth-1,kPlanciaHeight-1)

#define kCoordinataLibera1        NSMakePoint(1,0)        // per assicurare che il giocatore abbia spazio al primo turno

#define kCoordinataLibera2        NSMakePoint(0,1)        // idem

#define kCoordinataLibera3        NSMakePoint(kPlanciaWidth-2,kPlanciaHeight-1)   // idem

#define kCoordinataLibera4        NSMakePoint(kPlanciaWidth-1,kPlanciaHeight-2)   // idem



#define kCasellaVuota      0

#define kMuroFisso          1

#define kBomba        2

#define kMuroMobile          3



#define kFrazioneMuriFissi        0.15

#define kFrazioneMuriMobili        0.35



#define kPotenzaFuoco      2

#define kBombaTempoMiccia        4 //secondi

#define kBombaTempoEsplosione    4 //secondi

#define kVerificaVitaGiocatoriTimer      0.5 //secondi



#define kFineDelGiocoNotification      @"EndOfGameNotification"

#define kVincitoreKey      @"vincitore"

#define kPerdenteKey      @"perdente"



// tasti, azioni e direzioni

#define kGiocatore1Su      @"w"

#define kGiocatore1Giu      @"s"

#define kGiocatore1Sinistra        @"a"

#define kGiocatore1Destra        @"d"

#define kGiocatore1Bomba        @"x"

#define kGiocatore2Su      @"i"

#define kGiocatore2Giu      @"k"

#define kGiocatore2Sinistra        @"j"

#define kGiocatore2Destra        @"l"

#define kGiocatore2Bomba        @","



#define kSu            1

#define kGiu        2

#define kDestra        3

#define kSinistra          4



#define kInvalidMove      -1



// debug

#define kBattleFieldDebug        0x01

#define kControllerDebug        0x02

#define kCasellaDebug      0x04

#define kGiocatoreDebug      0x08

#define kDebugMask          0x00 + kBattleFieldDebug + kControllerDebug

A parte le solite modifiche di mantenimento (per tenere conto della nuova risorsa grafica e del nuovo possibile contenuto per la casella), hanno fatto la loro comparsa quattro costanti che memorizzano la posizione delle due caselle adiacenti ai due giocatori nelle posizioni iniziali. Tali caselle vanno tenute sgombre di muri (fissi o mobili) in fase di creazione di una nuova plancia di gioco, così da consentire al giocatore un movimento minimo che gli consenta di mettersi al riparo da un'esplosione.

i muri mobili dal punto di vista della casella - Casella
Se il file di interfaccia della classe non cambia, quello di implementazione subisce delle modifiche, ancorché poche:
CODICE
//

//  Casella.m

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Mon Jul 26 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import "Casella.h"





@implementation Casella



- (id)initWithCoordinates:(NSPoint)theCoord

{

    [super init];

    if(kDebugMask & kCasellaDebug)

 NSLog(@"Creating new cell: (%d,%d)",(int)theCoord.x,(int)theCoord.y);

    riga=(int)theCoord.y;

    colonna=(int)theCoord.x;

    contenuto=kCasellaVuota;

    immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

    frame=NSMakeRect(colonna*kCasellaSize,riga*kCasellaSize,kCasellaSize,kCasellaSize);

    if(kDebugMask & kCasellaDebug)

 NSLog(@"New cell frame: (%d,%d), (%d,%d)",(int)frame.origin.x,(int)frame.origin.y,(int)frame.size.width,(int)frame.size.height);

    explosionLevel=0;

    return self;

}



- (NSPoint)coordinate

{

    return NSMakePoint(colonna,riga);

}



- (NSRect)frame

{

    return frame;

}



- (NSImage *)immagine

{

    return immagine;

}



- (void)impostaContenuto:(int)cont

{

    contenuto=cont;

    switch(contenuto)

    {

 case kCasellaVuota:

     immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

     break;

 case kMuroFisso:

     immagine=[NSImage imageNamed:kMuroFissoSegnalinoName];

     break;

 case kBomba:

     immagine=[NSImage imageNamed:kCasellaBombaSegnalinoName];

     break;

 case kMuroMobile:

     immagine=[NSImage imageNamed:kMuroMobileSegnalinoName];

     break;

    }

}



- (int)contenuto

{

    return contenuto;

}



- (void)startExplosion

{

    explosionLevel++;

    if(explosionLevel>0)

 immagine=[NSImage imageNamed:kCasellaEsplosioneSegnalinoName];

}



- (void)stopExplosion

{

    explosionLevel--;

    if(explosionLevel<=0)

    {

 explosionLevel=0;

 immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

    }

    if(contenuto==kMuroMobile)

 contenuto=kCasellaVuota;

}



- (BOOL)isExploding

{

    if(explosionLevel>0)

 return YES;

    else

 return NO;

}



@end

Ora si può gestire il contenuto "muro mobile" nella impostaContenuto, ma soprattutto, nel metodo stopExplosion, si converte il contenuto da "muro mobile" a "casella vuota" se necessario.

i muri mobili dal punto di vista della plancia - BattleFieldView
Anche in questo caso il file di interfaccia non cambia. Cambia invece quello di implementazione:
CODICE
#import "BattleFieldView.h"



@implementation BattleFieldView



- (id)initWithFrame:(NSRect)frameRect

{

    if ((self = [super initWithFrame:frameRect]) != nil)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Initing battle field view");

    }

    return self;

}



// so that we can get keyboard events

- (BOOL)acceptsFirstResponder

{

    if(running)

 return YES;

    else

 return NO;

}



- (void)drawRect:(NSRect)rect

{

    NSEnumerator  *en;

    id        obj;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Drawing battle field");

    if(running)

    {

 en=[caselle objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSIntersectsRect(rect,[obj frame]))

     {

   [[obj immagine] drawInRect:[obj frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

     }

 }

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Drawing players");

 [[giocatore1 segnalino] drawInRect:[giocatore1 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

 [[giocatore2 segnalino] drawInRect:[giocatore2 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

    }

}



- (void)generateNewField

{

    int      i,j;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new battle field");

    if(caselle)

    {

 [caselle removeAllObjects];

 [caselle release];

    }

    caselle=[NSMutableArray arrayWithCapacity:kPlanciaWidth*kPlanciaHeight];

    [caselle retain];

    for(i=0;i<kPlanciaWidth;i++)

    {

 for(j=0;j<kPlanciaHeight;j++)

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"i: %d, j: %d",i,j);

     [caselle addObject:[[Casella alloc] initWithCoordinates:NSMakePoint(i,j)]];

 }

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new players");

    giocatore1=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore1 placeholder:[NSImage imageNamed:kGiocatore1SegnalinoName]];

    giocatore2=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore2 placeholder:[NSImage imageNamed:kGiocatore2SegnalinoName]];

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating fixed walls");

    // ora posizioniamo i muri fissi

    {

 NSMutableArray  *c;

 NSEnumerator  *en;

 id        obj;

 MCRandomExtractor   *r;

 double    frazione;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing array");

 c=[NSMutableArray arrayWithArray:caselle];

 en=[c objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore1) || NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore2) || NSEqualPoints([obj coordinate],kCoordinataLibera1) || NSEqualPoints([obj coordinate],kCoordinataLibera2) || NSEqualPoints([obj coordinate],kCoordinataLibera3) || NSEqualPoints([obj coordinate],kCoordinataLibera4))

     {

   // è la casella iniziale di uno dei giocatori, non può contenere un muro fisso

   [c removeObject:obj];

     }

 }

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing for extraction");

 r=[[MCRandomExtractor alloc] initWithItemsArray:(NSArray *)c resetSeed:YES];

 frazione=0.0;

 do

 {

     [[r randomItemExtractionWithRemoval] impostaContenuto:kMuroFisso];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fraction: %g",frazione);

     frazione=frazione+(double)1/(kPlanciaWidth*kPlanciaHeight);

 }while(frazione<kFrazioneMuriFissi);

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating movable walls");

    // è tempo di mettere i muri mobili

    {

 NSMutableArray  *c;

 NSEnumerator  *en;

 id        obj;

 MCRandomExtractor   *r;

 double    frazione;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing array");

 c=[NSMutableArray arrayWithArray:caselle];

 en=[c objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore1) || NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore2) || ([obj contenuto]==kMuroFisso) || NSEqualPoints([obj coordinate],kCoordinataLibera1) || NSEqualPoints([obj coordinate],kCoordinataLibera2) || NSEqualPoints([obj coordinate],kCoordinataLibera3) || NSEqualPoints([obj coordinate],kCoordinataLibera4))

     {

   // è la casella iniziale di uno dei giocatori oppure ha già un muro fisso, non può contenere un muro mobile

   [c removeObject:obj];

     }

 }

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing for extraction");

 r=[[MCRandomExtractor alloc] initWithItemsArray:(NSArray *)c resetSeed:NO];

 frazione=0.0;

 do

 {

     [[r randomItemExtractionWithRemoval] impostaContenuto:kMuroMobile];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fraction: %g",frazione);

     frazione=frazione+(double)1/(kPlanciaWidth*kPlanciaHeight);

 }while(frazione<kFrazioneMuriMobili);

    }

    

    // per finire attiviamo il timer che si occupa di verificare che i giocatori non siano morti

    timerVitaGiocatori=[NSTimer scheduledTimerWithTimeInterval:kVerificaVitaGiocatoriTimer

     target:self

     selector:@selector(checkPlayerLife:)

     userInfo:nil

     repeats:YES];

    running=YES;

}



// intercettiamo gli eventi della tastiera

- (void)keyDown:(NSEvent *)theEvent

{

    NSString  *tasti;

    NSRect      rettangolo1,rettangolo2,rettangolo3,rettangolo4;

    NSPoint      nuovaPosizione;

    id    thePlayer;

    

    // dobbiamo innanzitutto ricordarci dov'erano i giocatori per poter ridisegnare le caselle sottostanti se si muovono

    rettangolo1=[giocatore1 frame];

    rettangolo2=[giocatore2 frame];

    

    tasti=[theEvent charactersIgnoringModifiers];

    if(kDebugMask & kBattleFieldDebug)

 NSLog(tasti);

    if([tasti caseInsensitiveCompare:kGiocatore1Su]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kSu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Giu]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kGiu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Sinistra]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kSinistra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Destra]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kDestra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Su]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kSu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Giu]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kGiu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Sinistra]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kSinistra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Destra]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kDestra nuovaCasella:&nuovaPosizione];

    else

    {

 // ignoriamo altri tasti

 nuovaPosizione=NSMakePoint(kInvalidMove,kInvalidMove);

 thePlayer=nil;

    }

    if([self isValidMove:nuovaPosizione])

    {

 [thePlayer validateMove:nuovaPosizione];

 rettangolo3=[giocatore1 frame];

 rettangolo4=[giocatore2 frame];

 [self setNeedsDisplayInRect:rettangolo1];

 [self setNeedsDisplayInRect:rettangolo2];

 [self setNeedsDisplayInRect:rettangolo3];

 [self setNeedsDisplayInRect:rettangolo4];

    }

    else

    {

 if(thePlayer)

     [thePlayer didWrongMove];

    }

    

    // però il giocatore potrebbe aver premuto il tasto per posizionare la bomba

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Looking if player wants to drop a bomb");

    if(([tasti caseInsensitiveCompare:kGiocatore1Bomba]==NSOrderedSame) || ([tasti caseInsensitiveCompare:kGiocatore2Bomba]==NSOrderedSame))

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"One player wants to drop a bomb");

 if([tasti caseInsensitiveCompare:kGiocatore1Bomba]==NSOrderedSame)

     thePlayer=giocatore1;

 else

     thePlayer=giocatore2;

 if([thePlayer canDropBomb])

 {

     Bomba      *b;

     NSTimer      *t;

     

     [thePlayer dropBomb];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"OK, ready to drop bomb, setting up bomb object");

     b=[[Bomba alloc] initWithPosition:[thePlayer coordinate] power:[thePlayer potenzaDiFuoco] player:thePlayer explode:YES];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Setting up timer for callback on explosion");

     t=[NSTimer scheduledTimerWithTimeInterval:kBombaTempoMiccia

           target:self

           selector:@selector(explodeBomb:)

           userInfo:b

           repeats:NO];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Telling cell to place bomb");

     [[self casellaConCoordinate:[thePlayer coordinate]] impostaContenuto:kBomba];

 }

    }

}



- (BOOL)isValidMove:(NSPoint)pos

{

    if((int)pos.x<0 || (int)pos.x>=kPlanciaWidth || (int)pos.y<0 || (int)pos.y>=kPlanciaHeight)

    {

 // il giocatore non può uscire dalla plancia

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Out of bounds: cannot move");

 return NO;

    }

    

    // qui controlliamo che i giocatori non occupino la stessa casella

    if(NSEqualPoints(pos,[giocatore1 coordinate]) || NSEqualPoints(pos,[giocatore2 coordinate]))

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Crashing into other player: cannot move");

 return NO;

    }



    // qui controlleremo gli ostacoli sulla plancia

    if(([[self casellaConCoordinate:pos] contenuto]==kMuroFisso) || (([[self casellaConCoordinate:pos] contenuto]==kMuroMobile) && [[self casellaConCoordinate:pos] isExploding]==NO))

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Crashing into wall: cannot move");

 return NO;

    }

 

    // se arriviamo qui abbiamo passato tutti i test

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"OK: move!");

    return YES;

}



- (id)casellaConCoordinate:(NSPoint)coord

{

    NSEnumerator  *en;

    id        obj;

    

    en=[caselle objectEnumerator];

    while(obj=[en nextObject])

    {

 if(NSEqualPoints([obj coordinate],coord))

     return obj;

    }

    return nil;

}



- (void)explodeBomb:(NSTimer *)theTimer

{

    id      info;

    Bomba  *b;

    NSTimer  *t;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Explosion!!!");

    info=[theTimer userInfo];

    

    // ora mettiamo l'esplosione e la propaghiamo

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Exploding bomb cell");

    [[self casellaConCoordinate:[info posizione]] impostaContenuto:kCasellaVuota];

    [[self casellaConCoordinate:[info posizione]] startExplosion];

    [self setNeedsDisplayInRect:[[self casellaConCoordinate:[info posizione]] frame]];

    

    [self propagateExplosion:info];

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Now let's set up timer and object for end of explosion");

    b=[[Bomba alloc] initWithPosition:[info posizione] power:[info potenza] player:[info giocatore] explode:NO];

    t=[NSTimer scheduledTimerWithTimeInterval:kBombaTempoEsplosione

         target:self

         selector:@selector(endOfExplosion:)

         userInfo:b

         repeats:NO];

}



- (void)endOfExplosion:(NSTimer *)theTimer

{

    id  info;

    

    if(running)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"End of explosion");

 info=[theTimer userInfo];

 

 // riportiamo innanzitutto la casella in condizioni normali

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Stopping explosion in bomb cell");

 [[self casellaConCoordinate:[info posizione]] stopExplosion];

 [self setNeedsDisplayInRect:[[self casellaConCoordinate:[info posizione]] frame]];

 

 // ora propaghiamo

 [self propagateExplosion:info];

 

 // infine ridiamo la bomba al giocatore

 [[info giocatore] getBomb];

    }

}



- (void)propagateExplosion:(id)info

{

    int  i;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Propagating (explosion|removal: %d) %d cell(s) to the left",[info esplosione],i);

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) to the left",i);

 p=[info posizione];

 p.x=p.x-i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

      [c startExplosion];

   else

      [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) to the right",i);

 p=[info posizione];

 p.x=p.x+i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) upwards",i);

 p=[info posizione];

 p.y=p.y+i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating explosion");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) downwards",i);

 p=[info posizione];

 p.y=p.y-i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

}



- (void)checkPlayerLife:(NSTimer *)theTimer

{

    id      player,otherplayer;

    

    player=nil;

    if([[self casellaConCoordinate:[giocatore1 coordinate]] isExploding])

    {

 // è morto il giocatore 1

 player=giocatore2;

 otherplayer=giocatore1;

    }

    if([[self casellaConCoordinate:[giocatore2 coordinate]] isExploding])

    {

 // è morto il giocatore 2

 player=giocatore1;

 otherplayer=giocatore2;

    }

    if(player!=nil)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Player dead!");

 running=NO;

 [timerVitaGiocatori invalidate];

 [[NSNotificationCenter defaultCenter] postNotificationName:kFineDelGiocoNotification object:nil userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[player segnalino],kVincitoreKey,[otherplayer segnalino],kPerdenteKey,nil]];

    }

}



@end

Nella generateNewField, il codice per il posizionamento dei muri mobili è sostanzialmente equivalente a quello per il posizionamento dei muri fissi (avete notato che escludiamo dall'array che passiamo al generatore di oggetti casuali le caselle adiacenti ai giocatori nella posizione iniziale?); ora, però, escludiamo non solo le caselle iniziali dei giocatori e quelle a loro adiacenti, ma anche quelle che contengono muri fissi.
La propagateExplosion: deve ora avere a che fare con i muri mobili: investito da un'esplosione, un muro mobile salta per aira, ma scherma quello che c'è dietro (è una via di mezzo tra una casella vuota e un muro fisso).
Infine, la isValidMove: deve tenere conto che non si può andare a cozzare contro un muro mobile, ma se questo sta esplodendo non c'è più (ma la variabile contenuto della classe Casella assume il valore "casella vuota" solo al termine dell'esplosione): pertanto, una casella con un muro mobile in cui non vi siano esplosioni in corso è vietata, ma una casella con un muro mobile che sta esplodendo non lo è (e il giocatore, se ci va, morirà)!

Al prossimo passo implementiamo il primo bonus, quello che consente di aumentare la propria potenza di fuoco (numero di bombe giocabili simultaneamente da ogni giocatore e distanza alla quale si propaga l'esplosione di ogni bomba). Poiché questo passaggio renderà BUYF finalmente giocabile a tutti gli effetti, seppur ancora incompleto, promuoveremo il numero di versione di un decimale anziché di un centesimale, portandolo alla versione 0.1!!! A presto icon_cool.gif


--------------------
Marco Coïsson
http://web.me.com/marco_coisson

L'atomo divisibile: podcast gratuito di divulgazione scientifica.

X come Macintosh
Go to the top of the page
 
+Quote Post
Marco Coïsson
messaggio 28 Jul 2004, 16:36
Messaggio #14


life, n: a whim of several billion cells to be you for a while
***********

Gruppo: Forum User +
Messaggi: 9.340
Iscritto il: 18-May 03
Da: Torino
Utente Nr.: 65



BlowUpYourFriend 0.1, scaricabile da qui:
http://homepage.mac.com/marco_coisson/Tuto...l/BUYF/v0.1.zip

È giunto il momento dei bonus! Incominciamo col fuoco. Dobbiamo mettere a posto un po' di cosette. Dapprima creiamo la consueta, stupenda risorsa grafica, poi aggiorniamo il file CostantiGenerali.h:
CODICE
#define kPlanciaWidth      30 //caselle

#define kPlanciaHeight      20 //caselle

#define kCasellaSize      20 //pixel

#define kGiocatore1SegnalinoName      @"giocatore1.tif"

#define kGiocatore2SegnalinoName      @"giocatore2.tif"

#define kCasellaVuotaSegnalinoName      @"casella_vuota.tif"

#define kMuroFissoSegnalinoName    @"muro_fisso.tif"

#define kMuroMobileSegnalinoName      @"muro_mobile.tif"

#define kCasellaBombaSegnalinoName      @"casella_bomba.tif"

#define kCasellaEsplosioneSegnalinoName  @"casella_esplosione.tif"

#define kCasellaFuocoSegnalinoName      @"fuoco.tif"

#define kCoordinateInizialiGiocatore1  NSMakePoint(0,0)

#define kCoordinateInizialiGiocatore2  NSMakePoint(kPlanciaWidth-1,kPlanciaHeight-1)

#define kCoordinataLibera1        NSMakePoint(1,0)        // per assicurare che il giocatore abbia spazio al primo turno

#define kCoordinataLibera2        NSMakePoint(0,1)        // idem

#define kCoordinataLibera3        NSMakePoint(kPlanciaWidth-2,kPlanciaHeight-1)   // idem

#define kCoordinataLibera4        NSMakePoint(kPlanciaWidth-1,kPlanciaHeight-2)   // idem



// contenuti delle caselle

#define kCasellaVuota      0

#define kMuroFisso          1

#define kBomba        2

#define kMuroMobile          3



// bonus delle caselle

#define kNoBonus          0

#define kFuoco        1



#define kFrazioneMuriFissi        0.15

#define kFrazioneMuriMobili        0.35 // rispetto alle caselle vuote dopo aver messo i muri fissi

#define kFrazioneBonus      0.05 // rispetto ai muri mobili

#define kDiCuiFuoco          0.02 // rispetto ai muri mobili



#define kPotenzaFuoco      2

#define kMassimaPotenzaDiFuoco    7

#define kBombaTempoMiccia        4 //secondi

#define kBombaTempoEsplosione    4 //secondi

#define kVerificaVitaGiocatoriTimer      0.5 //secondi



#define kFineDelGiocoNotification      @"EndOfGameNotification"

#define kVincitoreKey      @"vincitore"

#define kPerdenteKey      @"perdente"



// tasti, azioni e direzioni

#define kGiocatore1Su      @"w"

#define kGiocatore1Giu      @"s"

#define kGiocatore1Sinistra        @"a"

#define kGiocatore1Destra        @"d"

#define kGiocatore1Bomba        @"x"

#define kGiocatore2Su      @"i"

#define kGiocatore2Giu      @"k"

#define kGiocatore2Sinistra        @"j"

#define kGiocatore2Destra        @"l"

#define kGiocatore2Bomba        @","



#define kSu            1

#define kGiu        2

#define kDestra        3

#define kSinistra          4



#define kInvalidMove      -1



// debug

#define kBattleFieldDebug        0x01

#define kControllerDebug        0x02

#define kCasellaDebug      0x04

#define kGiocatoreDebug      0x08

#define kDebugMask          0x00 + kBattleFieldDebug + kControllerDebug

Abbiamo aggiunto la categoria bonus (per non "nulla" e "fuoco"), e inserito la frazione di muri mobili sotto i quali si trovano i bonus, incluso il fuoco.

il fuoco come bonus della casella - Casella
La classe casella deve essere ampliata per supportare i bonus:
CODICE
//

//  Casella.h

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Mon Jul 26 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"





@interface Casella : NSObject

{

    int      riga;

    int      colonna;

    int      contenuto;

    NSImage  *immagine;

    NSRect  frame;

    int      explosionLevel;

    int      bonus;

}



- (id)initWithCoordinates:(NSPoint)theCoord;

- (NSPoint)coordinate;

- (NSRect)frame;

- (NSImage *)immagine;

- (void)impostaContenuto:(int)cont;

- (int)contenuto;

- (void)startExplosion;

- (void)stopExplosion;

- (BOOL)isExploding;

- (void)setBonus:(int)theBonus;

- (int)bonus;



@end

Una nuova variabile e due nuovi metodi si occuperanno dei bonus. Vediamo come:
CODICE
//

//  Casella.m

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Mon Jul 26 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import "Casella.h"





@implementation Casella



- (id)initWithCoordinates:(NSPoint)theCoord

{

    [super init];

    if(kDebugMask & kCasellaDebug)

 NSLog(@"Creating new cell: (%d,%d)",(int)theCoord.x,(int)theCoord.y);

    riga=(int)theCoord.y;

    colonna=(int)theCoord.x;

    contenuto=kCasellaVuota;

    immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

    frame=NSMakeRect(colonna*kCasellaSize,riga*kCasellaSize,kCasellaSize,kCasellaSize);

    if(kDebugMask & kCasellaDebug)

 NSLog(@"New cell frame: (%d,%d), (%d,%d)",(int)frame.origin.x,(int)frame.origin.y,(int)frame.size.width,(int)frame.size.height);

    explosionLevel=0;

    bonus=kNoBonus;

    return self;

}



- (NSPoint)coordinate

{

    return NSMakePoint(colonna,riga);

}



- (NSRect)frame

{

    return frame;

}



- (NSImage *)immagine

{

    return immagine;

}



- (void)impostaContenuto:(int)cont

{

    contenuto=cont;

    switch(contenuto)

    {

 case kCasellaVuota:

     if(bonus==kNoBonus)

   immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

     else if(bonus==kFuoco)

   immagine=[NSImage imageNamed:kCasellaFuocoSegnalinoName];

   break;

     break;

 case kMuroFisso:

     immagine=[NSImage imageNamed:kMuroFissoSegnalinoName];

     break;

 case kBomba:

     immagine=[NSImage imageNamed:kCasellaBombaSegnalinoName];

     break;

 case kMuroMobile:

     immagine=[NSImage imageNamed:kMuroMobileSegnalinoName];

     break;

    }

}



- (int)contenuto

{

    return contenuto;

}



- (void)startExplosion

{

    explosionLevel++;

    if(explosionLevel>0)

 immagine=[NSImage imageNamed:kCasellaEsplosioneSegnalinoName];

}



- (void)stopExplosion

{

    explosionLevel--;

    if(explosionLevel<=0)

    {

 explosionLevel=0;

 if(contenuto==kMuroMobile)

     contenuto=kCasellaVuota; // lo mettiamo qui e non in startExplosion perché la propagateExplosion: di BattleFieldView, se trova una casella vuota in cui c'è un'esplosione, propaga il cancellamento della stessa alla casella successiva, ma in caso di un muro mobile esploso la casella successiva non deve essere toccata. Quindi modifichiamo la variabile conenuto solo al termine dell'esplosione, e la isValidMove: di BattleFieldView consentirà il movimento verso un muro mobile quando c'è un'esplosione, e lo vieterà quando non c'è

 [self impostaContenuto:contenuto];

    }

}



- (BOOL)isExploding

{

    if(explosionLevel>0)

 return YES;

    else

 return NO;

}



- (void)setBonus:(int)theBonus

{

    bonus=theBonus;

    [self impostaContenuto:contenuto];

}



- (int)bonus

{

    return bonus;

}



@end

Innanzitutto aggiorniamo la impostaContenuto:, affinché selezioni l'immagine giusta per la casella vuota (senza nulla o con un bonus). Quindi modifichiamo un po' la stopExplosion: questa operazione è necessaria per "abbellire" il codice, ma anche per tenere conto del fatto che, al termine di un'esplosione, la casella, che magari conteneva un muro mobile, ora può rivelare il suo bonus. Ecco allora che anche di qui chiamiamo la impostaContenuto. Le ultime due funzioni dovrebbero essere chiare, e non le commenteremo oltre.

il fuoco dal punto di vista della plancia - BattleFieldView
Poiché né le caselle, né il giocatore hanno la visione globale della situazione, ma solo la plancia ce l'ha, tocca alla plancia sorbirsi il mal di pancia di gestire i bonus. Ecco allora che guadagna un metodo:
CODICE
/* BattleFieldView */



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"

#import "Casella.h"

#import "Giocatore.h"

#import "MCRandomExtractor.h"

#import "Bomba.h"



@interface BattleFieldView : NSView

{

    NSMutableArray  *caselle;

    Giocatore      *giocatore1,*giocatore2;

    NSTimer    *timerVitaGiocatori;

    BOOL    running;

}



- (void)generateNewField;

- (void)keyDown:(NSEvent *)theEvent;

- (BOOL)isValidMove:(NSPoint)pos;

- (id)casellaConCoordinate:(NSPoint)coord;

- (void)explodeBomb:(NSTimer *)theTimer;

- (void)endOfExplosion:(NSTimer *)theTimer;

- (void)propagateExplosion:(id)info;

- (void)checkPlayerLife:(NSTimer *)theTimer;

- (void)processBonusAtCell:(NSPoint)theCoord forPlayer:(id)thePlayer;

@end

Ecco qui l'implementazione della classe:
CODICE
#import "BattleFieldView.h"



@implementation BattleFieldView



- (id)initWithFrame:(NSRect)frameRect

{

    if ((self = [super initWithFrame:frameRect]) != nil)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Initing battle field view");

    }

    return self;

}



// so that we can get keyboard events

- (BOOL)acceptsFirstResponder

{

    if(running)

 return YES;

    else

 return NO;

}



- (void)drawRect:(NSRect)rect

{

    NSEnumerator  *en;

    id        obj;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Drawing battle field");

    if(running)

    {

 en=[caselle objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSIntersectsRect(rect,[obj frame]))

     {

   [[obj immagine] drawInRect:[obj frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

     }

 }

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Drawing players");

 [[giocatore1 segnalino] drawInRect:[giocatore1 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

 [[giocatore2 segnalino] drawInRect:[giocatore2 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

    }

}



- (void)generateNewField

{

    int      i,j;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new battle field");

    if(caselle)

    {

 [caselle removeAllObjects];

 [caselle release];

    }

    caselle=[NSMutableArray arrayWithCapacity:kPlanciaWidth*kPlanciaHeight];

    [caselle retain];

    for(i=0;i<kPlanciaWidth;i++)

    {

 for(j=0;j<kPlanciaHeight;j++)

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"i: %d, j: %d",i,j);

     [caselle addObject:[[Casella alloc] initWithCoordinates:NSMakePoint(i,j)]];

 }

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new players");

    giocatore1=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore1 placeholder:[NSImage imageNamed:kGiocatore1SegnalinoName]];

    giocatore2=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore2 placeholder:[NSImage imageNamed:kGiocatore2SegnalinoName]];

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating fixed walls");

    // ora posizioniamo i muri fissi

    {

 NSMutableArray  *c;

 NSEnumerator  *en;

 id        obj;

 MCRandomExtractor   *r;

 double    frazione;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing array");

 c=[NSMutableArray arrayWithArray:caselle];

 en=[c objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore1) || NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore2) || NSEqualPoints([obj coordinate],kCoordinataLibera1) || NSEqualPoints([obj coordinate],kCoordinataLibera2) || NSEqualPoints([obj coordinate],kCoordinataLibera3) || NSEqualPoints([obj coordinate],kCoordinataLibera4))

     {

   // è la casella iniziale di uno dei giocatori, non può contenere un muro fisso

   [c removeObject:obj];

     }

 }

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing for extraction");

 r=[[MCRandomExtractor alloc] initWithItemsArray:(NSArray *)c resetSeed:YES];

 frazione=0.0;

 do

 {

     [[r randomItemExtractionWithRemoval] impostaContenuto:kMuroFisso];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fraction: %g",frazione);

     frazione=frazione+(double)1/(kPlanciaWidth*kPlanciaHeight);

 }while(frazione<kFrazioneMuriFissi);

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating movable walls");

    // è tempo di mettere i muri mobili

    {

 NSMutableArray  *c;

 NSEnumerator  *en;

 id        obj;

 MCRandomExtractor   *r;

 double    frazione;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing array");

 c=[NSMutableArray arrayWithArray:caselle];

 en=[c objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore1) || NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore2) || ([obj contenuto]==kMuroFisso) || NSEqualPoints([obj coordinate],kCoordinataLibera1) || NSEqualPoints([obj coordinate],kCoordinataLibera2) || NSEqualPoints([obj coordinate],kCoordinataLibera3) || NSEqualPoints([obj coordinate],kCoordinataLibera4))

     {

   // è la casella iniziale di uno dei giocatori oppure ha già un muro fisso, non può contenere un muro mobile

   [c removeObject:obj];

     }

 }

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing for extraction");

 r=[[MCRandomExtractor alloc] initWithItemsArray:(NSArray *)c resetSeed:NO];

 frazione=0.0;

 do

 {

     [[r randomItemExtractionWithRemoval] impostaContenuto:kMuroMobile];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fraction: %g",frazione);

     frazione=frazione+(double)1/(kPlanciaWidth*kPlanciaHeight);

 }while(frazione<kFrazioneMuriMobili);

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating bonuses");

    // adesso si passa ai bonus

    {

 NSMutableArray  *c;

 NSEnumerator  *en;

 id        obj;

 MCRandomExtractor   *r;

 double    frazione;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing array");

 c=[NSMutableArray arrayWithArray:caselle];

 en=[c objectEnumerator];

 while(obj=[en nextObject])

 {

     if([obj contenuto]!=kMuroMobile)

     {

   // i bonus possono stare solo sotto i muri mobili

   [c removeObject:obj];

     }

 }

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing for extraction");

 r=[[MCRandomExtractor alloc] initWithItemsArray:(NSArray *)c resetSeed:NO];

 frazione=0.0;

 do

 {

     if(frazione<kDiCuiFuoco)

     {

   [[r randomItemExtractionWithRemoval] setBonus:kFuoco];

     }

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fraction: %g",frazione);

     frazione=frazione+(double)1/(kPlanciaWidth*kPlanciaHeight);

 }while(frazione<kFrazioneBonus);

    }

    

    // per finire attiviamo il timer che si occupa di verificare che i giocatori non siano morti

    timerVitaGiocatori=[NSTimer scheduledTimerWithTimeInterval:kVerificaVitaGiocatoriTimer

     target:self

     selector:@selector(checkPlayerLife:)

     userInfo:nil

     repeats:YES];

    running=YES;

}



// intercettiamo gli eventi della tastiera

- (void)keyDown:(NSEvent *)theEvent

{

    NSString  *tasti;

    NSRect      rettangolo1,rettangolo2,rettangolo3,rettangolo4;

    NSPoint      nuovaPosizione;

    id    thePlayer;

    

    // dobbiamo innanzitutto ricordarci dov'erano i giocatori per poter ridisegnare le caselle sottostanti se si muovono

    rettangolo1=[giocatore1 frame];

    rettangolo2=[giocatore2 frame];

    

    tasti=[theEvent charactersIgnoringModifiers];

    if(kDebugMask & kBattleFieldDebug)

 NSLog(tasti);

    if([tasti caseInsensitiveCompare:kGiocatore1Su]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kSu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Giu]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kGiu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Sinistra]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kSinistra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Destra]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kDestra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Su]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kSu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Giu]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kGiu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Sinistra]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kSinistra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Destra]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kDestra nuovaCasella:&nuovaPosizione];

    else

    {

 // ignoriamo altri tasti

 nuovaPosizione=NSMakePoint(kInvalidMove,kInvalidMove);

 thePlayer=nil;

    }

    if([self isValidMove:nuovaPosizione])

    {

 [thePlayer validateMove:nuovaPosizione];

 rettangolo3=[giocatore1 frame];

 rettangolo4=[giocatore2 frame];

 [self setNeedsDisplayInRect:rettangolo1];

 [self setNeedsDisplayInRect:rettangolo2];

 [self setNeedsDisplayInRect:rettangolo3];

 [self setNeedsDisplayInRect:rettangolo4];

 [self processBonusAtCell:nuovaPosizione forPlayer:thePlayer];

    }

    else

    {

 if(thePlayer)

     [thePlayer didWrongMove];

    }

    

    // però il giocatore potrebbe aver premuto il tasto per posizionare la bomba

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Looking if player wants to drop a bomb");

    if(([tasti caseInsensitiveCompare:kGiocatore1Bomba]==NSOrderedSame) || ([tasti caseInsensitiveCompare:kGiocatore2Bomba]==NSOrderedSame))

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"One player wants to drop a bomb");

 if([tasti caseInsensitiveCompare:kGiocatore1Bomba]==NSOrderedSame)

     thePlayer=giocatore1;

 else

     thePlayer=giocatore2;

 if([thePlayer canDropBomb])

 {

     Bomba      *b;

     NSTimer      *t;

     

     [thePlayer dropBomb];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"OK, ready to drop bomb, setting up bomb object");

     b=[[Bomba alloc] initWithPosition:[thePlayer coordinate] power:[thePlayer potenzaDiFuoco] player:thePlayer explode:YES];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Setting up timer for callback on explosion");

     t=[NSTimer scheduledTimerWithTimeInterval:kBombaTempoMiccia

           target:self

           selector:@selector(explodeBomb:)

           userInfo:b

           repeats:NO];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Telling cell to place bomb");

     [[self casellaConCoordinate:[thePlayer coordinate]] impostaContenuto:kBomba];

 }

    }

}



- (BOOL)isValidMove:(NSPoint)pos

{

    if((int)pos.x<0 || (int)pos.x>=kPlanciaWidth || (int)pos.y<0 || (int)pos.y>=kPlanciaHeight)

    {

 // il giocatore non può uscire dalla plancia

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Out of bounds: cannot move");

 return NO;

    }

    

    // qui controlliamo che i giocatori non occupino la stessa casella

    if(NSEqualPoints(pos,[giocatore1 coordinate]) || NSEqualPoints(pos,[giocatore2 coordinate]))

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Crashing into other player: cannot move");

 return NO;

    }



    // qui controlleremo gli ostacoli sulla plancia

    if(([[self casellaConCoordinate:pos] contenuto]==kMuroFisso) || (([[self casellaConCoordinate:pos] contenuto]==kMuroMobile) && [[self casellaConCoordinate:pos] isExploding]==NO))

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Crashing into wall: cannot move");

 return NO;

    }

 

    // se arriviamo qui abbiamo passato tutti i test

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"OK: move!");

    return YES;

}



- (id)casellaConCoordinate:(NSPoint)coord

{

    NSEnumerator  *en;

    id        obj;

    

    en=[caselle objectEnumerator];

    while(obj=[en nextObject])

    {

 if(NSEqualPoints([obj coordinate],coord))

     return obj;

    }

    return nil;

}



- (void)explodeBomb:(NSTimer *)theTimer

{

    id      info;

    Bomba  *b;

    NSTimer  *t;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Explosion!!!");

    info=[theTimer userInfo];

    

    // ora mettiamo l'esplosione e la propaghiamo

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Exploding bomb cell");

    [[self casellaConCoordinate:[info posizione]] impostaContenuto:kCasellaVuota];

    [[self casellaConCoordinate:[info posizione]] startExplosion];

    [self setNeedsDisplayInRect:[[self casellaConCoordinate:[info posizione]] frame]];

    

    [self propagateExplosion:info];

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Now let's set up timer and object for end of explosion");

    b=[[Bomba alloc] initWithPosition:[info posizione] power:[info potenza] player:[info giocatore] explode:NO];

    t=[NSTimer scheduledTimerWithTimeInterval:kBombaTempoEsplosione

         target:self

         selector:@selector(endOfExplosion:)

         userInfo:b

         repeats:NO];

}



- (void)endOfExplosion:(NSTimer *)theTimer

{

    id  info;

    

    if(running)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"End of explosion");

 info=[theTimer userInfo];

 

 // riportiamo innanzitutto la casella in condizioni normali

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Stopping explosion in bomb cell");

 [[self casellaConCoordinate:[info posizione]] stopExplosion];

 [self setNeedsDisplayInRect:[[self casellaConCoordinate:[info posizione]] frame]];

 

 // ora propaghiamo

 [self propagateExplosion:info];

 

 // infine ridiamo la bomba al giocatore

 [[info giocatore] getBomb];

    }

}



- (void)propagateExplosion:(id)info

{

    int  i;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Propagating (explosion|removal: %d) %d cell(s) to the left",[info esplosione],[info potenza]);

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) to the left",i);

 p=[info posizione];

 p.x=p.x-i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota || [c contenuto]==kBomba)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

      [c startExplosion];

   else

      [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) to the right",i);

 p=[info posizione];

 p.x=p.x+i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota || [c contenuto]==kBomba)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) upwards",i);

 p=[info posizione];

 p.y=p.y+i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota || [c contenuto]==kBomba)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating explosion");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) downwards",i);

 p=[info posizione];

 p.y=p.y-i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota || [c contenuto]==kBomba)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

}



- (void)checkPlayerLife:(NSTimer *)theTimer

{

    id      player,otherplayer;

    

    player=nil;

    if([[self casellaConCoordinate:[giocatore1 coordinate]] isExploding])

    {

 // è morto il giocatore 1

 player=giocatore2;

 otherplayer=giocatore1;

    }

    if([[self casellaConCoordinate:[giocatore2 coordinate]] isExploding])

    {

 // è morto il giocatore 2

 player=giocatore1;

 otherplayer=giocatore2;

    }

    if(player!=nil)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Player dead!");

 running=NO;

 [timerVitaGiocatori invalidate];

 [[NSNotificationCenter defaultCenter] postNotificationName:kFineDelGiocoNotification object:nil userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[player segnalino],kVincitoreKey,[otherplayer segnalino],kPerdenteKey,nil]];

    }

}



- (void)processBonusAtCell:(NSPoint)theCoord forPlayer:(id)thePlayer

{

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Processing bonuses");

    

    switch([[self casellaConCoordinate:theCoord] bonus])

    {

 case kNoBonus:

     // che fare? nulla!

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"No bonus!");

     break;

 case kFuoco:

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fire: get another bomb!");

     [thePlayer getBomb];

     [[self casellaConCoordinate:theCoord] setBonus:kNoBonus];

     break;

    }

}



@end

La generateNewField, col meccanismo ormai noto, posiziona il numero necessario di bonus (una frazione dei quali saranno "fuochi") nelle sole caselle il cui contenuto sia un muro mobile.
Nel metodo keyDown:, se la isValidMove: afferma che sì, il giocatore è libero di muoversi, chiamiamo la processBonusAtCell:forPlayer:, che si preoccuperà di vedere se la casella (sicuramente vuota e senza esplosioni) in cui è capitato il giocatore contenga o meno un bonus, e in caso affermativo prenderà i dovuti provvedimenti.
Segue un piccolo bug fix alla propagateExplosion: le caselle che contengono una bomba (inesplosa) sono da paragonare a quelle che non contengono nulla, ovvero fanno "filtrare" l'esplosione che continua a propagarsi. Prima non avveniva. Non ce n'eravamo accorti perché potevamo usare una sola bomba per volta, ora possiamo usarne più d'una e il baco è saltato all'occhio.
Per finire tocca alla processBonusAtCell:forPlayer: se nella casella in cui è giunto il giocatore c'è un bonus (esiste solo il fuoco, al momento), si prendono iniziative adeguate. Nel caso del fuoco, viene detto al giocatore che può disporre di una bomba in più (metodo getBomb).

Un'ultima modifica, che commento senza riportare, riguarda il file Giocatore.m: per evitare che un giocatore che accumula troppi fuochi abbia una potenza di fuoco troppo devastante, il metodo potenzaDiFuoco è stato limitato a restituire un valore massimo, definito nelle costanti generali. Questo non scoraggi comunque i giocatori a raccogliere il massimo numero possibile di "fuochi": essi infatti non sono molti, e fare incetta vuol dire impedire all'avversario di raggiungere la massima potenza di fuoco possibile!

Questa è la prima versione di BUYF ad essere "giocabile": potete mettervi in due davanti allo schermo, usare i tasti predefiniti (potete cambiarli intervenendo sul file CostantiGenerali.h) e divertirvi: le funzionalità minime del gioco sono già presenti! Naturalmente, la grafica fa un po' pena (qualche volontario per disegnare qualche cosa di decente?) e ancora mancano gli effetti sonori (qualche volontario che ne crei qualcuno?), ma il design generale dell'applicazione sta prendendo forma.

Con la versione 0.2 aggiungeremo un altro bonus, o meglio un malus: il teschio o "cagamine"!


--------------------
Marco Coïsson
http://web.me.com/marco_coisson

L'atomo divisibile: podcast gratuito di divulgazione scientifica.

X come Macintosh
Go to the top of the page
 
+Quote Post
kaio
messaggio 28 Jul 2004, 16:53
Messaggio #15


Level 3/11
***

Gruppo: Members
Messaggi: 233
Iscritto il: 1-February 04
Da: San Pier d'Isonzo (Gorizia)
Utente Nr.: 1.410



Spero che prima o poi lo riunirai in un unico documento icon_razz.gif
Go to the top of the page
 
+Quote Post
Marco Coïsson
messaggio 28 Jul 2004, 17:10
Messaggio #16


life, n: a whim of several billion cells to be you for a while
***********

Gruppo: Forum User +
Messaggi: 9.340
Iscritto il: 18-May 03
Da: Torino
Utente Nr.: 65



CITAZIONE(kaio)
Spero che prima o poi lo riunirai in un unico documento icon_razz.gif

Sarà un lavoraccio! Ma, ad applicazione finita, in linea di massima sì. Adesso l'idea è che chi vuole inizia dalla "prima puntata", si scarica il progetto XCode, se lo compila, e un po' usando i messaggi di debug scritti dall'applicazione stessa, un po' usando le mie sintetiche note in questo thread, un po' postando domande si studia come funziona. Capita la prima puntata, si passa alla seconda con la stessa procedura, ecc…

Quando i vari pezzi verranno "riuniti" in un unico documento, vorrei comunque che restasse la suddivisione in "puntate", perché è proprio il loro susseguirsi ad avere valenza didattica: partire dal nulla e, un po' per volta, costruire un programma, senza mai aggiungere troppe linee di codice tutte insieme. Un commento all'applicazione completa, senza aver visto come nasce e perché, sarebbe a mio parere solo frustrante, in quanto darebbe l'idea che bisogna partire direttamente con l'idea del progetto grosso e completo. BUYF vuole dimostrare l'esatto contrario: si parte da una plancia vuota in cui i segnalini non si possono nemmeno muovere, e si aggiunge tutto, un pezzo per volta. Per ora (credo) sta venendo abbastanza bene!


--------------------
Marco Coïsson
http://web.me.com/marco_coisson

L'atomo divisibile: podcast gratuito di divulgazione scientifica.

X come Macintosh
Go to the top of the page
 
+Quote Post
Marco Coïsson
messaggio 28 Jul 2004, 23:38
Messaggio #17


life, n: a whim of several billion cells to be you for a while
***********

Gruppo: Forum User +
Messaggi: 9.340
Iscritto il: 18-May 03
Da: Torino
Utente Nr.: 65



BlowUpYourFriend 0.2, scaricabile da qui:
http://homepage.mac.com/marco_coisson/Tuto...l/BUYF/v0.2.zip

Con questa versione, implementiamo un altro bonus, il teschio o "cagamine", e correggiamo un paio di bachi che ci eravamo lasciati dietro e che erano passati inosservati. Iniziamo a ricordare che cos'è il cagamine: più che un bonus, è un malus: il giocatore che si imbatte in esso è costretto a depositare tutte le bombe che ha a disposizione, una per volta, una casella dopo l'altra. Ed è bene che si sbrighi, perché il tempo passa, e se finisce la miccia e il giocatore è nei paraggi… sono guai!

Cominciamo da CostantiGenerali.h:
CODICE
#define kPlanciaWidth      30 //caselle

#define kPlanciaHeight      20 //caselle

#define kCasellaSize      20 //pixel

#define kGiocatore1SegnalinoName      @"giocatore1.tif"

#define kGiocatore2SegnalinoName      @"giocatore2.tif"

#define kCasellaVuotaSegnalinoName      @"casella_vuota.tif"

#define kMuroFissoSegnalinoName    @"muro_fisso.tif"

#define kMuroMobileSegnalinoName      @"muro_mobile.tif"

#define kCasellaBombaSegnalinoName      @"casella_bomba.tif"

#define kCasellaEsplosioneSegnalinoName  @"casella_esplosione.tif"

#define kCasellaFuocoSegnalinoName      @"fuoco.tif"

#define kCasellaTeschioSegnalinoName  @"teschio.tif"

#define kCoordinateInizialiGiocatore1  NSMakePoint(0,0)

#define kCoordinateInizialiGiocatore2  NSMakePoint(kPlanciaWidth-1,kPlanciaHeight-1)

#define kCoordinataLibera1        NSMakePoint(1,0)        // per assicurare che il giocatore abbia spazio al primo turno

#define kCoordinataLibera2        NSMakePoint(0,1)        // idem

#define kCoordinataLibera3        NSMakePoint(kPlanciaWidth-2,kPlanciaHeight-1)   // idem

#define kCoordinataLibera4        NSMakePoint(kPlanciaWidth-1,kPlanciaHeight-2)   // idem



// contenuti delle caselle

#define kCasellaVuota      0

#define kMuroFisso          1

#define kBomba        2

#define kMuroMobile          3



// bonus delle caselle

#define kNoBonus          0

#define kFuoco        1

#define kTeschio          2



#define kFrazioneMuriFissi        0.15

#define kFrazioneMuriMobili        0.35 // rispetto alle caselle vuote dopo aver messo i muri fissi

#define kFrazioneBonus      0.05 // rispetto ai muri mobili

#define kDiCuiFuoco          0.02 // rispetto ai muri mobili

#define kDiCuiTeschio      0.05 // -kDiCuiFuoco = rispetto ai muri mobili



#define kPotenzaFuoco      2

#define kMassimaPotenzaDiFuoco    7

#define kBombaTempoMiccia        4 //secondi

#define kBombaTempoEsplosione    4 //secondi

#define kVerificaVitaGiocatoriTimer      0.5 //secondi



#define kFineDelGiocoNotification      @"EndOfGameNotification"

#define kVincitoreKey      @"vincitore"

#define kPerdenteKey      @"perdente"



// tasti, azioni e direzioni

#define kGiocatore1Su      @"w"

#define kGiocatore1Giu      @"s"

#define kGiocatore1Sinistra        @"a"

#define kGiocatore1Destra        @"d"

#define kGiocatore1Bomba        @"x"

#define kGiocatore2Su      @"i"

#define kGiocatore2Giu      @"k"

#define kGiocatore2Sinistra        @"j"

#define kGiocatore2Destra        @"l"

#define kGiocatore2Bomba        @","



#define kSu            1

#define kGiu        2

#define kDestra        3

#define kSinistra          4



#define kInvalidMove      -1



// debug

#define kBattleFieldDebug        0x01

#define kControllerDebug        0x02

#define kCasellaDebug      0x04

#define kGiocatoreDebug      0x08

#define kDebugMask          0x00 + kBattleFieldDebug + kControllerDebug

Nessuna novità di rilievo: le solite costanti per la nuova risorsa grafica (che dovrebbe essere un teschio, ma non riuscivo a farlo, e allora ho fatto due tibie incrociate che fanno veramente schifo), per il nuovo bonus e la sua frazione di presenza nel totale dei bonus.

il cagamine nelle caselle - Casella
Il file di interfaccia della classe Casella non ha bisogno di essere modificato. Piccole aggiunte invece al file di implementazione:
CODICE
//

//  Casella.m

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Mon Jul 26 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import "Casella.h"





@implementation Casella



- (id)initWithCoordinates:(NSPoint)theCoord

{

    [super init];

    if(kDebugMask & kCasellaDebug)

 NSLog(@"Creating new cell: (%d,%d)",(int)theCoord.x,(int)theCoord.y);

    riga=(int)theCoord.y;

    colonna=(int)theCoord.x;

    contenuto=kCasellaVuota;

    immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

    frame=NSMakeRect(colonna*kCasellaSize,riga*kCasellaSize,kCasellaSize,kCasellaSize);

    if(kDebugMask & kCasellaDebug)

 NSLog(@"New cell frame: (%d,%d), (%d,%d)",(int)frame.origin.x,(int)frame.origin.y,(int)frame.size.width,(int)frame.size.height);

    explosionLevel=0;

    bonus=kNoBonus;

    return self;

}



- (NSPoint)coordinate

{

    return NSMakePoint(colonna,riga);

}



- (NSRect)frame

{

    return frame;

}



- (NSImage *)immagine

{

    return immagine;

}



- (void)impostaContenuto:(int)cont

{

    contenuto=cont;

    switch(contenuto)

    {

 case kCasellaVuota:

     if(bonus==kNoBonus)

   immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

     else if(bonus==kFuoco)

   immagine=[NSImage imageNamed:kCasellaFuocoSegnalinoName];

     else if(bonus==kTeschio)

   immagine=[NSImage imageNamed:kCasellaTeschioSegnalinoName];

     break;

 case kMuroFisso:

     immagine=[NSImage imageNamed:kMuroFissoSegnalinoName];

     break;

 case kBomba:

     immagine=[NSImage imageNamed:kCasellaBombaSegnalinoName];

     break;

 case kMuroMobile:

     immagine=[NSImage imageNamed:kMuroMobileSegnalinoName];

     break;

    }

}



- (int)contenuto

{

    return contenuto;

}



- (void)startExplosion

{

    explosionLevel++;

    if(explosionLevel>0)

 immagine=[NSImage imageNamed:kCasellaEsplosioneSegnalinoName];

}



- (void)stopExplosion

{

    explosionLevel--;

    if(explosionLevel<=0)

    {

 explosionLevel=0;

 if(contenuto==kMuroMobile)

     contenuto=kCasellaVuota; // lo mettiamo qui e non in startExplosion perché la propagateExplosion: di BattleFieldView, se trova una casella vuota in cui c'è un'esplosione, propaga il cancellamento della stessa alla casella successiva, ma in caso di un muro mobile esploso la casella successiva non deve essere toccata. Quindi modifichiamo la variabile conenuto solo al termine dell'esplosione, e la isValidMove: di BattleFieldView consentirà il movimento verso un muro mobile quando c'è un'esplosione, e lo vieterà quando non c'è

 [self impostaContenuto:contenuto];

    }

}



- (BOOL)isExploding

{

    if(explosionLevel>0)

 return YES;

    else

 return NO;

}



- (void)setBonus:(int)theBonus

{

    bonus=theBonus;

    [self impostaContenuto:contenuto];

}



- (int)bonus

{

    return bonus;

}



@end

Niente di sorprendente, solo la impostaContenuto: che è consapevole del nuovo bonus esistente.

il cagamine nella plancia - BattleFieldView
Più consistenti le modifiche alla classe BattleFieldView. È cambiato anche il file di interfaccia, perché abbiamo aggiunto un metodo:
CODICE
/* BattleFieldView */



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"

#import "Casella.h"

#import "Giocatore.h"

#import "MCRandomExtractor.h"

#import "Bomba.h"



@interface BattleFieldView : NSView

{

    NSMutableArray  *caselle;

    Giocatore      *giocatore1,*giocatore2;

    NSTimer    *timerVitaGiocatori;

    BOOL    running;

}



- (void)generateNewField;

- (void)keyDown:(NSEvent *)theEvent;

- (void)playerDropsBomb:(id)thePlayer atCell:(NSPoint)thePos;

- (BOOL)isValidMove:(NSPoint)pos;

- (id)casellaConCoordinate:(NSPoint)coord;

- (void)explodeBomb:(NSTimer *)theTimer;

- (void)endOfExplosion:(NSTimer *)theTimer;

- (void)propagateExplosion:(id)info;

- (void)checkPlayerLife:(NSTimer *)theTimer;

- (void)processBonusAtCell:(NSPoint)theCoord forPlayer:(id)thePlayer;

@end

Si tratta del metodo playerDropsBomb:atCell:, che abbiamo aggiunto per maggiore eleganza del codice, e per riciclarne un po'. Vediamo come:
CODICE
#import "BattleFieldView.h"



@implementation BattleFieldView



- (id)initWithFrame:(NSRect)frameRect

{

    if ((self = [super initWithFrame:frameRect]) != nil)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Initing battle field view");

    }

    return self;

}



// so that we can get keyboard events

- (BOOL)acceptsFirstResponder

{

    if(running)

 return YES;

    else

 return NO;

}



- (void)drawRect:(NSRect)rect

{

    NSEnumerator  *en;

    id        obj;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Drawing battle field");

    if(running)

    {

 en=[caselle objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSIntersectsRect(rect,[obj frame]))

     {

   [[obj immagine] drawInRect:[obj frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

     }

 }

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Drawing players");

 [[giocatore1 segnalino] drawInRect:[giocatore1 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

 [[giocatore2 segnalino] drawInRect:[giocatore2 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

    }

}



- (void)generateNewField

{

    int      i,j;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new battle field");

    if(caselle)

    {

 [caselle removeAllObjects];

 [caselle release];

    }

    caselle=[NSMutableArray arrayWithCapacity:kPlanciaWidth*kPlanciaHeight];

    [caselle retain];

    for(i=0;i<kPlanciaWidth;i++)

    {

 for(j=0;j<kPlanciaHeight;j++)

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"i: %d, j: %d",i,j);

     [caselle addObject:[[Casella alloc] initWithCoordinates:NSMakePoint(i,j)]];

 }

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new players");

    giocatore1=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore1 placeholder:[NSImage imageNamed:kGiocatore1SegnalinoName]];

    giocatore2=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore2 placeholder:[NSImage imageNamed:kGiocatore2SegnalinoName]];

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating fixed walls");

    // ora posizioniamo i muri fissi

    {

 NSMutableArray  *c;

 NSEnumerator  *en;

 id        obj;

 MCRandomExtractor   *r;

 double    frazione;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing array");

 c=[NSMutableArray arrayWithArray:caselle];

 en=[c objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore1) || NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore2) || NSEqualPoints([obj coordinate],kCoordinataLibera1) || NSEqualPoints([obj coordinate],kCoordinataLibera2) || NSEqualPoints([obj coordinate],kCoordinataLibera3) || NSEqualPoints([obj coordinate],kCoordinataLibera4))

     {

   // è la casella iniziale di uno dei giocatori, non può contenere un muro fisso

   [c removeObject:obj];

     }

 }

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing for extraction");

 r=[[MCRandomExtractor alloc] initWithItemsArray:(NSArray *)c resetSeed:YES];

 frazione=0.0;

 do

 {

     [[r randomItemExtractionWithRemoval] impostaContenuto:kMuroFisso];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fraction: %g",frazione);

     frazione=frazione+(double)1/(kPlanciaWidth*kPlanciaHeight);

 }while(frazione<kFrazioneMuriFissi);

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating movable walls");

    // è tempo di mettere i muri mobili

    {

 NSMutableArray  *c;

 NSEnumerator  *en;

 id        obj;

 MCRandomExtractor   *r;

 double    frazione;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing array");

 c=[NSMutableArray arrayWithArray:caselle];

 en=[c objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore1) || NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore2) || ([obj contenuto]==kMuroFisso) || NSEqualPoints([obj coordinate],kCoordinataLibera1) || NSEqualPoints([obj coordinate],kCoordinataLibera2) || NSEqualPoints([obj coordinate],kCoordinataLibera3) || NSEqualPoints([obj coordinate],kCoordinataLibera4))

     {

   // è la casella iniziale di uno dei giocatori oppure ha già un muro fisso, non può contenere un muro mobile

   [c removeObject:obj];

     }

 }

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing for extraction");

 r=[[MCRandomExtractor alloc] initWithItemsArray:(NSArray *)c resetSeed:NO];

 frazione=0.0;

 do

 {

     [[r randomItemExtractionWithRemoval] impostaContenuto:kMuroMobile];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fraction: %g",frazione);

     frazione=frazione+(double)1/(kPlanciaWidth*kPlanciaHeight);

 }while(frazione<kFrazioneMuriMobili);

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating bonuses");

    // adesso si passa ai bonus

    {

 NSMutableArray  *c;

 NSEnumerator  *en;

 id        obj;

 MCRandomExtractor   *r;

 double    frazione;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing array");

 c=[NSMutableArray arrayWithArray:caselle];

 en=[c objectEnumerator];

 while(obj=[en nextObject])

 {

     if([obj contenuto]!=kMuroMobile)

     {

   // i bonus possono stare solo sotto i muri mobili

   [c removeObject:obj];

     }

 }

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing for extraction");

 r=[[MCRandomExtractor alloc] initWithItemsArray:(NSArray *)c resetSeed:NO];

 frazione=0.0;

 do

 {

     if(frazione<kDiCuiFuoco)

     {

   [[r randomItemExtractionWithRemoval] setBonus:kFuoco];

     }

     else if(frazione<kDiCuiTeschio)

     {

   [[r randomItemExtractionWithRemoval] setBonus:kTeschio];

     }

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fraction: %g",frazione);

     frazione=frazione+(double)1/(kPlanciaWidth*kPlanciaHeight);

 }while(frazione<kFrazioneBonus);

    }

    

    // per finire attiviamo il timer che si occupa di verificare che i giocatori non siano morti

    timerVitaGiocatori=[NSTimer scheduledTimerWithTimeInterval:kVerificaVitaGiocatoriTimer

     target:self

     selector:@selector(checkPlayerLife:)

     userInfo:nil

     repeats:YES];

    running=YES;

}



// intercettiamo gli eventi della tastiera

- (void)keyDown:(NSEvent *)theEvent

{

    NSString  *tasti;

    NSRect      rettangolo1,rettangolo2,rettangolo3,rettangolo4;

    NSPoint      nuovaPosizione;

    id    thePlayer;

    

    // dobbiamo innanzitutto ricordarci dov'erano i giocatori per poter ridisegnare le caselle sottostanti se si muovono

    rettangolo1=[giocatore1 frame];

    rettangolo2=[giocatore2 frame];

    

    tasti=[theEvent charactersIgnoringModifiers];

    if(kDebugMask & kBattleFieldDebug)

 NSLog(tasti);

    if([tasti caseInsensitiveCompare:kGiocatore1Su]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kSu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Giu]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kGiu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Sinistra]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kSinistra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Destra]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kDestra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Su]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kSu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Giu]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kGiu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Sinistra]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kSinistra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Destra]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kDestra nuovaCasella:&nuovaPosizione];

    else

    {

 // ignoriamo altri tasti

 nuovaPosizione=NSMakePoint(kInvalidMove,kInvalidMove);

 thePlayer=nil;

    }

    if([self isValidMove:nuovaPosizione])

    {

 [thePlayer validateMove:nuovaPosizione];

 if([thePlayer cagamine])

     [self playerDropsBomb:thePlayer atCell:nuovaPosizione];

 rettangolo3=[giocatore1 frame];

 rettangolo4=[giocatore2 frame];

 [self setNeedsDisplayInRect:rettangolo1];

 [self setNeedsDisplayInRect:rettangolo2];

 [self setNeedsDisplayInRect:rettangolo3];

 [self setNeedsDisplayInRect:rettangolo4];

 [self processBonusAtCell:nuovaPosizione forPlayer:thePlayer];

    }

    else

    {

 if(thePlayer)

     [thePlayer didWrongMove];

    }

    

    // però il giocatore potrebbe aver premuto il tasto per posizionare la bomba

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Looking if player wants to drop a bomb");

    if(([tasti caseInsensitiveCompare:kGiocatore1Bomba]==NSOrderedSame) || ([tasti caseInsensitiveCompare:kGiocatore2Bomba]==NSOrderedSame))

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"One player wants to drop a bomb");

 if([tasti caseInsensitiveCompare:kGiocatore1Bomba]==NSOrderedSame)

     thePlayer=giocatore1;

 else

     thePlayer=giocatore2;

 [self playerDropsBomb:thePlayer atCell:[thePlayer coordinate]];

    }

}



- (void)playerDropsBomb:(id)thePlayer atCell:(NSPoint)thePos

{

    if([thePlayer canDropBomb])

    {

 Bomba      *b;

 NSTimer      *t;

 

 [thePlayer dropBomb];

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"OK, ready to drop bomb, setting up bomb object");

 b=[[Bomba alloc] initWithPosition:[thePlayer coordinate] power:[thePlayer potenzaDiFuoco] player:thePlayer explode:YES];

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Setting up timer for callback on explosion");

 t=[NSTimer scheduledTimerWithTimeInterval:kBombaTempoMiccia

            target:self

          selector:@selector(explodeBomb:)

          userInfo:b

           repeats:NO];

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Telling cell to place bomb");

 [[self casellaConCoordinate:[thePlayer coordinate]] impostaContenuto:kBomba];

    }

}



- (BOOL)isValidMove:(NSPoint)pos

{

    if((int)pos.x<0 || (int)pos.x>=kPlanciaWidth || (int)pos.y<0 || (int)pos.y>=kPlanciaHeight)

    {

 // il giocatore non può uscire dalla plancia

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Out of bounds: cannot move");

 return NO;

    }

    

    // qui controlliamo che i giocatori non occupino la stessa casella

    if(NSEqualPoints(pos,[giocatore1 coordinate]) || NSEqualPoints(pos,[giocatore2 coordinate]))

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Crashing into other player: cannot move");

 return NO;

    }



    // qui controlleremo gli ostacoli sulla plancia

    if(([[self casellaConCoordinate:pos] contenuto]==kMuroFisso) || (([[self casellaConCoordinate:pos] contenuto]==kMuroMobile) && [[self casellaConCoordinate:pos] isExploding]==NO) || ([[self casellaConCoordinate:pos] contenuto]==kBomba))

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Crashing into wall or bomb: cannot move");

 return NO;

    }

 

    // se arriviamo qui abbiamo passato tutti i test

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"OK: move!");

    return YES;

}



- (id)casellaConCoordinate:(NSPoint)coord

{

    NSEnumerator  *en;

    id        obj;

    

    en=[caselle objectEnumerator];

    while(obj=[en nextObject])

    {

 if(NSEqualPoints([obj coordinate],coord))

     return obj;

    }

    return nil;

}



- (void)explodeBomb:(NSTimer *)theTimer

{

    id      info;

    Bomba  *b;

    NSTimer  *t;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Explosion!!!");

    info=[theTimer userInfo];

    

    // ora mettiamo l'esplosione e la propaghiamo

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Exploding bomb cell");

    [[self casellaConCoordinate:[info posizione]] impostaContenuto:kCasellaVuota];

    [[self casellaConCoordinate:[info posizione]] startExplosion];

    [self setNeedsDisplayInRect:[[self casellaConCoordinate:[info posizione]] frame]];

    

    [self propagateExplosion:info];

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Now let's set up timer and object for end of explosion");

    b=[[Bomba alloc] initWithPosition:[info posizione] power:[info potenza] player:[info giocatore] explode:NO];

    t=[NSTimer scheduledTimerWithTimeInterval:kBombaTempoEsplosione

         target:self

         selector:@selector(endOfExplosion:)

         userInfo:b

         repeats:NO];

}



- (void)endOfExplosion:(NSTimer *)theTimer

{

    id  info;

    

    if(running)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"End of explosion");

 info=[theTimer userInfo];

 

 // riportiamo innanzitutto la casella in condizioni normali

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Stopping explosion in bomb cell");

 [[self casellaConCoordinate:[info posizione]] stopExplosion];

 [self setNeedsDisplayInRect:[[self casellaConCoordinate:[info posizione]] frame]];

 

 // ora propaghiamo

 [self propagateExplosion:info];

 

 // infine ridiamo la bomba al giocatore

 [[info giocatore] getBomb];

    }

}



- (void)propagateExplosion:(id)info

{

    int  i;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Propagating (explosion|removal: %d) %d cell(s) to the left",[info esplosione],[info potenza]);

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) to the left",i);

 p=[info posizione];

 p.x=p.x-i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota || [c contenuto]==kBomba)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

      [c startExplosion];

   else

      [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) to the right",i);

 p=[info posizione];

 p.x=p.x+i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota || [c contenuto]==kBomba)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) upwards",i);

 p=[info posizione];

 p.y=p.y+i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota || [c contenuto]==kBomba)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating explosion");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) downwards",i);

 p=[info posizione];

 p.y=p.y-i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota || [c contenuto]==kBomba)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

}



- (void)checkPlayerLife:(NSTimer *)theTimer

{

    id      player,otherplayer;

    

    player=nil;

    if([[self casellaConCoordinate:[giocatore1 coordinate]] isExploding])

    {

 // è morto il giocatore 1

 player=giocatore2;

 otherplayer=giocatore1;

    }

    if([[self casellaConCoordinate:[giocatore2 coordinate]] isExploding])

    {

 // è morto il giocatore 2

 player=giocatore1;

 otherplayer=giocatore2;

    }

    if(player!=nil)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Player dead!");

 running=NO;

 [timerVitaGiocatori invalidate];

 [[NSNotificationCenter defaultCenter] postNotificationName:kFineDelGiocoNotification object:nil userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[player segnalino],kVincitoreKey,[otherplayer segnalino],kPerdenteKey,nil]];

    }

}



- (void)processBonusAtCell:(NSPoint)theCoord forPlayer:(id)thePlayer

{

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Processing bonuses");

    

    switch([[self casellaConCoordinate:theCoord] bonus])

    {

 case kNoBonus:

     // che fare? nulla!

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"No bonus!");

     break;

 case kFuoco:

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fire: get another bomb!");

     [thePlayer getBomb];

     [[self casellaConCoordinate:theCoord] setBonus:kNoBonus];

     break;

 case kTeschio:

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Skull: drop all bombs!");

     [self playerDropsBomb:thePlayer atCell:theCoord];

     [thePlayer setCagamine];

     [[self casellaConCoordinate:theCoord] setBonus:kNoBonus];

     break;

    }

}



@end

Un semplice else if nella sezione dedicata ai bonus in generateNewField si occupa di inserire i cagamine sotto alcuni muri mobili. In keyDown:, invece, togliamo il codice relativo al posizionamento delle bombe, spostato al metodo playerDropsBomb:atCell:, così da poterlo invocare sia quando il giocatore preme esplicitamente il tasto che consente di posizionare una bomba, sia quando il giocatore è capitato su una casella con un teschio. L'oggetto del giocatore, mediante il metodo cagamine, fa sapere alla keyDown: se una bomba debba essere depositata automaticamente oppure no subito dopo che il giocatore si è posizionato in un'altra casella.
Nel metodo isValidMove: correggiamo un errore, o meglio, miglioriamo il gioco: ora che abbiamo il cagamine che può costringerci a scappare di volata da una sfilza di bombe depositate contro la nostra volontà, è molto più divertente far sì che una bomba posizionata sulla plancia sia invalicabile al giocatore (come un muro fisso o un muro mobile), ma non alle esplosioni; ovvero, se prendete un cagamine, occhio a non cacciarvi in un vicolo cieco, perché le bombe che avrete depositato non vi permetteranno di tornare indietro!
Infine, la processBonusAtCell:forPlayer: gestisce il caso in cui il giocatore capiti su un cagamine: la casella in cui si trova il giocatore viene occupata da una bomba, e al giocatore viene comunicato, mediante il metodo setCagamine, di aver preso questo malus.

Il giocatore e il cagamine - Giocatore
La classe Giocatore, oltre alle aggiunte necessarie per supportare il nuovo bonus (malus), vede anche la correzione di un baco. Vediamo come iniziando dal file di interfaccia:
CODICE
//

//  Giocatore.h

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Tue Jul 27 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import <Cocoa/Cocoa.h>

#import "CostantiGenerali.h"



#define kBombeIniziali  1



@interface Giocatore : NSObject

{

    int      x,y; // le coordinate in "caselle"

    NSRect  frame; // dove si trova il segnalino (può essere in movimento)

    NSImage  *segnalino;

    int      numeroBombe;

    int      potenzaDiFuoco;

    BOOL  cagamine;

}



- (id)initWithCoordinates:(NSPoint)theCoord placeholder:(NSImage *)ph;

- (NSPoint)coordinate;

- (NSImage *)segnalino;

- (NSRect)frame;

- (void)calcolaFrame;

- (id)vai:(int)direzione nuovaCasella:(NSPoint *)nuovaCasella;

- (void)validateMove:(NSPoint)nuovaPosizione;

- (void)didWrongMove;

- (int)potenzaDiFuoco;

- (BOOL)canDropBomb;

- (void)dropBomb;

- (void)getBomb;

- (void)setCagamine;

- (BOOL)cagamine;



@end

Abbiamo aggiunto una variabile, potenzaDiFuoco, resasi necessaria perché calcolare la potenza di fuoco semplicemente basandosi sul valore della variabile numeroBombe non è corretto: infatti, il giocatore che può depositare parecchie bombe simultaneamente, vede la prima bomba che deposita avere potenza piena, la seconda (quando la prima ancora non è esplosa) avere una potenza inferiore (perché è diminuita numeroBombe), la terza (depositata prima che le prime due siano esplose) avere una potenza ancora inferiore, e così via. Aggiungiamo quindi la variabile potenzaDiFuoco per ovviare a questo problema. Il flag cagamine, invece, avrà un valore logico vero se il giocatore ha preso il malus, falso se non l'ha preso o se il suo effetto si è concluso. Due metodi permettono di gestire il contenuto di questo flag.
CODICE
//

//  Giocatore.m

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Tue Jul 27 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import "Giocatore.h"





@implementation Giocatore



- (id)initWithCoordinates:(NSPoint)theCoord placeholder:(NSImage *)ph

{

    [super init];

    x=theCoord.x;

    y=theCoord.y;

    [self calcolaFrame];

    segnalino=ph;

    numeroBombe=kBombeIniziali;

    potenzaDiFuoco=numeroBombe+kPotenzaFuoco;

    cagamine=NO;

    return self;

}



- (NSPoint)coordinate

{

    return NSMakePoint(x,y);

}



- (NSImage *)segnalino

{

    return segnalino;

}



- (NSRect)frame

{

    return frame;

}



- (id)vai:(int)direzione nuovaCasella:(NSPoint *)nuovaCasella

{

    int      newx,newy;

    

    if(kDebugMask & kGiocatoreDebug)

 NSLog(@"Moving to direction: %d from position: (%d,%d)",direzione,x,y);

    switch(direzione)

    {

 case kSu:

     newy=(int)y+1;

     newx=(int)x;

     break;

 case kGiu:

     newy=(int)y-1;

     newx=(int)x;

     break;

 case kSinistra:

     newx=(int)x-1;

     newy=(int)y;

     break;

 case kDestra:

     newx=(int)x+1;

     newy=(int)y;

     break;

    }

    if(kDebugMask & kGiocatoreDebug)

 NSLog(@"New position: (%d,%d)",newx,newy);

    *nuovaCasella=NSMakePoint(newx,newy);

    return self;

}



- (void)calcolaFrame

{

    frame=NSMakeRect(x*kCasellaSize,y*kCasellaSize,kCasellaSize,kCasellaSize);

    if(kDebugMask & kGiocatoreDebug)

 NSLog(@"Calculating new player frame: (%d,%d), (%d,%d)",(int)frame.origin.x,(int)frame.origin.y,(int)frame.size.width,(int)frame.size.height);

}



- (void)validateMove:(NSPoint)nuovaPosizione

{

    x=(int)nuovaPosizione.x;

    y=(int)nuovaPosizione.y;

    [self calcolaFrame];

}



- (void)didWrongMove

{

    // quando ce l'avremo, suoneremo un suono (breve) d'errore

}



- (int)potenzaDiFuoco

{

    return potenzaDiFuoco;

}



- (BOOL)canDropBomb

{

    if(numeroBombe>0)

 return YES;

    else

 return NO;

}



- (void)dropBomb

{

    numeroBombe--;

    if(cagamine==YES && numeroBombe==0)

 cagamine=NO;

}



- (void)getBomb

{

    numeroBombe++;

    if((numeroBombe+kPotenzaFuoco)>potenzaDiFuoco)

    {

 potenzaDiFuoco++;

 if(potenzaDiFuoco>kMassimaPotenzaDiFuoco)

     potenzaDiFuoco=kMassimaPotenzaDiFuoco;

    }

}



- (void)setCagamine

{

    cagamine=YES;

}



- (BOOL)cagamine

{

    return cagamine;

}



@end

potenzaDiFuoco viene inizializzata nel metodo initWithCoordinates:placeholder:, e viene restituita direttamente dal metodo potenzaDiFuoco. La variabile viene incrementata in getBomb; per assicurarsi che non cresca indefinitamente ogni volta che una bomba esplosa torna a disposizione del giocatore, potenzaDiFuoco viene incrementata solo quando numeroBombe diventa maggiore di essa: quindi, se prendete un fuoco e volete beneficiare dell'incremento della potenza della bomba (l'esplosione si propaga per più caselle), dovete lasciare che tutte le bombe che avete piazzato esplodano, senza che voi ne piazziate delle altre; così, numeroBombe raggiunge il suo valore massimo (compatibile con i fuochi che avete preso), e potenzaDiFuoco viene a sua volta incrementata.
In dropBomb, invece, se numeroBombe giunge a zero quando il flag cagamine è attivo, allora il flag viene riportato al valore falso: l'effetto del malus si è esaurito, il giocatore ha depositato tutte le bombe che aveva.
Gli ultimi due metodi impostano e leggono la variabile cagamine.

Con la prossima versione, la 0.3, implementeremo l'ultimo dei bonus, il teletrasporto. Tenetevi forte! icon_biggrin.gif


--------------------
Marco Coïsson
http://web.me.com/marco_coisson

L'atomo divisibile: podcast gratuito di divulgazione scientifica.

X come Macintosh
Go to the top of the page
 
+Quote Post
Cubiq
messaggio 29 Jul 2004, 07:39
Messaggio #18


Level 6/11
******

Gruppo: Forum User +
Messaggi: 1.204
Iscritto il: 12-April 04
Da: Firenze
Utente Nr.: 1.747



CITAZIONE(kaio)
Spero che prima o poi lo riunirai in un unico documento icon_razz.gif


Quoto. Trovo estremamente dispersivo seguire il tuo tutorial sul forum.
Visto che ritengo la tua iniziativa molto interessante, credo che la cosa migliore potrebbe essere realizzare il tutorial in html (anche diviso in "puntate") e poi semplicemente segnalare il link sul forum dove magari possiamo commentare e fare domande. Solo un consiglio ovviamente.
Go to the top of the page
 
+Quote Post
Marco Coïsson
messaggio 29 Jul 2004, 07:49
Messaggio #19


life, n: a whim of several billion cells to be you for a while
***********

Gruppo: Forum User +
Messaggi: 9.340
Iscritto il: 18-May 03
Da: Torino
Utente Nr.: 65



CITAZIONE(Cubiq)
CITAZIONE(kaio)
Spero che prima o poi lo riunirai in un unico documento icon_razz.gif


Quoto. Trovo estremamente dispersivo seguire il tuo tutorial sul forum.
Visto che ritengo la tua iniziativa molto interessante, credo che la cosa migliore potrebbe essere realizzare il tutorial in html (anche diviso in "puntate" e poi semplicemente segnalare il link sul forum dove magari possiamo commentare e fare domande. Solo un consiglio ovviamente.

Va bene icon_sad.gif Ora lo continuo così, poi, arrivati alla fine, riunisco e riorganizzo.


--------------------
Marco Coïsson
http://web.me.com/marco_coisson

L'atomo divisibile: podcast gratuito di divulgazione scientifica.

X come Macintosh
Go to the top of the page
 
+Quote Post
Marco Coïsson
messaggio 29 Jul 2004, 14:21
Messaggio #20


life, n: a whim of several billion cells to be you for a while
***********

Gruppo: Forum User +
Messaggi: 9.340
Iscritto il: 18-May 03
Da: Torino
Utente Nr.: 65



BlowUpYourFriend 0.3, scaricabile da qui:
http://homepage.mac.com/marco_coisson/Tuto...l/BUYF/v0.3.zip

Questo aggiornamento, abbastanza semplice in verità, aggiunge il teletrasporto alle caratteristiche del gioco. Sostanzialmente si tratta di due caselle dotate di un bonus (quindi inizialmente il teletrasporto è nascosto sotto dei muri mobili) che vengono "cortocircuitate": se un giocatore si posiziona nella casella dove c'è una delle porte del teletrasporto, viene teletrasportato (appunto) sull'altra casella.
Con dei caveat: se l'altra casella è ancora coperta da un muro, il giocatore muore schiacciato da esso; se nell'altra casella c'è un'esplosione, il giocatore muore dilaniato da essa; se nell'altra casella c'è una bomba o un altro giocatore, il teletrasporto è bloccato e non accade nulla (ma quando la bomba scoppia o l'altro giocatore si muove, occhio che la "porta" si libera!).

Dicevamo, non è nulla di molto complicato. Le CostantiGenerali.h subiscono le solite modifiche (aggiunta di qualche costante per tenere conto del nuovo bonus e della relativa risorsa grafica):
CODICE
#define kPlanciaWidth      30 //caselle

#define kPlanciaHeight      20 //caselle

#define kCasellaSize      20 //pixel

#define kGiocatore1SegnalinoName      @"giocatore1.tif"

#define kGiocatore2SegnalinoName      @"giocatore2.tif"

#define kCasellaVuotaSegnalinoName      @"casella_vuota.tif"

#define kMuroFissoSegnalinoName    @"muro_fisso.tif"

#define kMuroMobileSegnalinoName      @"muro_mobile.tif"

#define kCasellaBombaSegnalinoName      @"casella_bomba.tif"

#define kCasellaEsplosioneSegnalinoName  @"casella_esplosione.tif"

#define kCasellaFuocoSegnalinoName      @"fuoco.tif"

#define kCasellaTeschioSegnalinoName  @"teschio.tif"

#define kCasellaTeletrasportoSegnalinoName  @"teletrasporto.tif"

#define kCoordinateInizialiGiocatore1  NSMakePoint(0,0)

#define kCoordinateInizialiGiocatore2  NSMakePoint(kPlanciaWidth-1,kPlanciaHeight-1)

#define kCoordinataLibera1        NSMakePoint(1,0)        // per assicurare che il giocatore abbia spazio al primo turno

#define kCoordinataLibera2        NSMakePoint(0,1)        // idem

#define kCoordinataLibera3        NSMakePoint(kPlanciaWidth-2,kPlanciaHeight-1)   // idem

#define kCoordinataLibera4        NSMakePoint(kPlanciaWidth-1,kPlanciaHeight-2)   // idem



// contenuti delle caselle

#define kCasellaVuota      0

#define kMuroFisso          1

#define kBomba        2

#define kMuroMobile          3



// bonus delle caselle

#define kNoBonus          0

#define kFuoco        1

#define kTeschio          2

#define kTeletrasporto      3



#define kFrazioneMuriFissi        0.15

#define kFrazioneMuriMobili        0.35 // rispetto alle caselle vuote dopo aver messo i muri fissi

#define kFrazioneBonus      0.05 // rispetto ai muri mobili

#define kDiCuiFuoco          0.02 // rispetto ai muri mobili

#define kDiCuiTeschio      0.05 // -kDiCuiFuoco = rispetto ai muri mobili

#define kNumeroTeletrasporti    2 // e non più di 2



#define kPotenzaFuoco      2

#define kMassimaPotenzaDiFuoco    7

#define kBombaTempoMiccia        4 //secondi

#define kBombaTempoEsplosione    4 //secondi

#define kVerificaVitaGiocatoriTimer      0.5 //secondi



#define kFineDelGiocoNotification      @"EndOfGameNotification"

#define kVincitoreKey      @"vincitore"

#define kPerdenteKey      @"perdente"



// tasti, azioni e direzioni

#define kGiocatore1Su      @"w"

#define kGiocatore1Giu      @"s"

#define kGiocatore1Sinistra        @"a"

#define kGiocatore1Destra        @"d"

#define kGiocatore1Bomba        @"x"

#define kGiocatore2Su      @"i"

#define kGiocatore2Giu      @"k"

#define kGiocatore2Sinistra        @"j"

#define kGiocatore2Destra        @"l"

#define kGiocatore2Bomba        @","



#define kSu            1

#define kGiu        2

#define kDestra        3

#define kSinistra          4



#define kInvalidMove      -1



// debug

#define kBattleFieldDebug        0x01

#define kControllerDebug        0x02

#define kCasellaDebug      0x04

#define kGiocatoreDebug      0x08

#define kDebugMask          0x00 + kBattleFieldDebug + kControllerDebug


il teletrasporto nelle caselle - Casella
Anche qui le modifiche sono già praticamente note: dobbiamo solo aggiungere il supporto al nuovo bonus nella impostaContenuto: (in pratica dobbiamo solo caricarne l'immagine appropriata):
CODICE
//

//  Casella.m

//  BlowUpYourFriend

//

//  Created by Marco Coïsson on Mon Jul 26 2004.

//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.

//



#import "Casella.h"





@implementation Casella



- (id)initWithCoordinates:(NSPoint)theCoord

{

    [super init];

    if(kDebugMask & kCasellaDebug)

 NSLog(@"Creating new cell: (%d,%d)",(int)theCoord.x,(int)theCoord.y);

    riga=(int)theCoord.y;

    colonna=(int)theCoord.x;

    contenuto=kCasellaVuota;

    immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

    frame=NSMakeRect(colonna*kCasellaSize,riga*kCasellaSize,kCasellaSize,kCasellaSize);

    if(kDebugMask & kCasellaDebug)

 NSLog(@"New cell frame: (%d,%d), (%d,%d)",(int)frame.origin.x,(int)frame.origin.y,(int)frame.size.width,(int)frame.size.height);

    explosionLevel=0;

    bonus=kNoBonus;

    return self;

}



- (NSPoint)coordinate

{

    return NSMakePoint(colonna,riga);

}



- (NSRect)frame

{

    return frame;

}



- (NSImage *)immagine

{

    return immagine;

}



- (void)impostaContenuto:(int)cont

{

    contenuto=cont;

    switch(contenuto)

    {

 case kCasellaVuota:

     if(bonus==kNoBonus)

   immagine=[NSImage imageNamed:kCasellaVuotaSegnalinoName];

     else if(bonus==kFuoco)

   immagine=[NSImage imageNamed:kCasellaFuocoSegnalinoName];

     else if(bonus==kTeschio)

   immagine=[NSImage imageNamed:kCasellaTeschioSegnalinoName];

     else if(bonus==kTeletrasporto)

   immagine=[NSImage imageNamed:kCasellaTeletrasportoSegnalinoName];

     break;

 case kMuroFisso:

     immagine=[NSImage imageNamed:kMuroFissoSegnalinoName];

     break;

 case kBomba:

     immagine=[NSImage imageNamed:kCasellaBombaSegnalinoName];

     break;

 case kMuroMobile:

     immagine=[NSImage imageNamed:kMuroMobileSegnalinoName];

     break;

    }

}



- (int)contenuto

{

    return contenuto;

}



- (void)startExplosion

{

    explosionLevel++;

    if(explosionLevel>0)

 immagine=[NSImage imageNamed:kCasellaEsplosioneSegnalinoName];

}



- (void)stopExplosion

{

    explosionLevel--;

    if(explosionLevel<=0)

    {

 explosionLevel=0;

 if(contenuto==kMuroMobile)

     contenuto=kCasellaVuota; // lo mettiamo qui e non in startExplosion perché la propagateExplosion: di BattleFieldView, se trova una casella vuota in cui c'è un'esplosione, propaga il cancellamento della stessa alla casella successiva, ma in caso di un muro mobile esploso la casella successiva non deve essere toccata. Quindi modifichiamo la variabile conenuto solo al termine dell'esplosione, e la isValidMove: di BattleFieldView consentirà il movimento verso un muro mobile quando c'è un'esplosione, e lo vieterà quando non c'è

 [self impostaContenuto:contenuto];

    }

}



- (BOOL)isExploding

{

    if(explosionLevel>0)

 return YES;

    else

 return NO;

}



- (void)setBonus:(int)theBonus

{

    bonus=theBonus;

    [self impostaContenuto:contenuto];

}



- (int)bonus

{

    return bonus;

}



@end


il teletrasporto sulla plancia - BattleFieldView
Di poco più complesso è l'intervento su BattleFieldView, ovvero sulla gestione della plancia.
CODICE
#import "BattleFieldView.h"



@implementation BattleFieldView



- (id)initWithFrame:(NSRect)frameRect

{

    if ((self = [super initWithFrame:frameRect]) != nil)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Initing battle field view");

    }

    return self;

}



// so that we can get keyboard events

- (BOOL)acceptsFirstResponder

{

    if(running)

 return YES;

    else

 return NO;

}



- (void)drawRect:(NSRect)rect

{

    NSEnumerator  *en;

    id        obj;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Drawing battle field");

    if(running)

    {

 en=[caselle objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSIntersectsRect(rect,[obj frame]))

     {

   [[obj immagine] drawInRect:[obj frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

     }

 }

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Drawing players");

 [[giocatore1 segnalino] drawInRect:[giocatore1 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

 [[giocatore2 segnalino] drawInRect:[giocatore2 frame] fromRect:NSMakeRect(0,0,kCasellaSize,kCasellaSize) operation:NSCompositeCopy fraction:1.0];

    }

}



- (void)generateNewField

{

    int      i,j;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new battle field");

    if(caselle)

    {

 [caselle removeAllObjects];

 [caselle release];

    }

    caselle=[NSMutableArray arrayWithCapacity:kPlanciaWidth*kPlanciaHeight];

    [caselle retain];

    for(i=0;i<kPlanciaWidth;i++)

    {

 for(j=0;j<kPlanciaHeight;j++)

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"i: %d, j: %d",i,j);

     [caselle addObject:[[Casella alloc] initWithCoordinates:NSMakePoint(i,j)]];

 }

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating new players");

    giocatore1=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore1 placeholder:[NSImage imageNamed:kGiocatore1SegnalinoName]];

    giocatore2=[[Giocatore alloc] initWithCoordinates:kCoordinateInizialiGiocatore2 placeholder:[NSImage imageNamed:kGiocatore2SegnalinoName]];

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating fixed walls");

    // ora posizioniamo i muri fissi

    {

 NSMutableArray  *c;

 NSEnumerator  *en;

 id        obj;

 MCRandomExtractor   *r;

 double    frazione;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing array");

 c=[NSMutableArray arrayWithArray:caselle];

 en=[c objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore1) || NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore2) || NSEqualPoints([obj coordinate],kCoordinataLibera1) || NSEqualPoints([obj coordinate],kCoordinataLibera2) || NSEqualPoints([obj coordinate],kCoordinataLibera3) || NSEqualPoints([obj coordinate],kCoordinataLibera4))

     {

   // è la casella iniziale di uno dei giocatori, non può contenere un muro fisso

   [c removeObject:obj];

     }

 }

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing for extraction");

 r=[[MCRandomExtractor alloc] initWithItemsArray:(NSArray *)c resetSeed:YES];

 frazione=0.0;

 do

 {

     [[r randomItemExtractionWithRemoval] impostaContenuto:kMuroFisso];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fraction: %g",frazione);

     frazione=frazione+(double)1/(kPlanciaWidth*kPlanciaHeight);

 }while(frazione<kFrazioneMuriFissi);

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating movable walls");

    // è tempo di mettere i muri mobili

    {

 NSMutableArray  *c;

 NSEnumerator  *en;

 id        obj;

 MCRandomExtractor   *r;

 double    frazione;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing array");

 c=[NSMutableArray arrayWithArray:caselle];

 en=[c objectEnumerator];

 while(obj=[en nextObject])

 {

     if(NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore1) || NSEqualPoints([obj coordinate],kCoordinateInizialiGiocatore2) || ([obj contenuto]==kMuroFisso) || NSEqualPoints([obj coordinate],kCoordinataLibera1) || NSEqualPoints([obj coordinate],kCoordinataLibera2) || NSEqualPoints([obj coordinate],kCoordinataLibera3) || NSEqualPoints([obj coordinate],kCoordinataLibera4))

     {

   // è la casella iniziale di uno dei giocatori oppure ha già un muro fisso, non può contenere un muro mobile

   [c removeObject:obj];

     }

 }

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing for extraction");

 r=[[MCRandomExtractor alloc] initWithItemsArray:(NSArray *)c resetSeed:NO];

 frazione=0.0;

 do

 {

     [[r randomItemExtractionWithRemoval] impostaContenuto:kMuroMobile];

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fraction: %g",frazione);

     frazione=frazione+(double)1/(kPlanciaWidth*kPlanciaHeight);

 }while(frazione<kFrazioneMuriMobili);

    }

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Generating bonuses");

    // adesso si passa ai bonus

    {

 NSMutableArray  *c;

 NSEnumerator  *en;

 id        obj;

 MCRandomExtractor   *r;

 double    frazione;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing array");

 c=[NSMutableArray arrayWithArray:caselle];

 en=[c objectEnumerator];

 while(obj=[en nextObject])

 {

     if([obj contenuto]!=kMuroMobile)

     {

   // i bonus possono stare solo sotto i muri mobili

   [c removeObject:obj];

     }

 }

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Preparing for extraction");

 r=[[MCRandomExtractor alloc] initWithItemsArray:(NSArray *)c resetSeed:NO];

 frazione=0.0;

 do

 {

     if(frazione<kDiCuiFuoco)

     {

   [[r randomItemExtractionWithRemoval] setBonus:kFuoco];

     }

     else if(frazione<kDiCuiTeschio)

     {

   [[r randomItemExtractionWithRemoval] setBonus:kTeschio];

     }

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fraction: %g",frazione);

     frazione=frazione+(double)1/(kPlanciaWidth*kPlanciaHeight);

 }while(frazione<kFrazioneBonus);

 // ora mettiamo i teletrasporti

 for(i=1;i<=kNumeroTeletrasporti;i++)

     [[r randomItemExtractionWithRemoval] setBonus:kTeletrasporto];

    }

    

    // per finire attiviamo il timer che si occupa di verificare che i giocatori non siano morti

    timerVitaGiocatori=[NSTimer scheduledTimerWithTimeInterval:kVerificaVitaGiocatoriTimer

     target:self

     selector:@selector(checkPlayerLife:)

     userInfo:nil

     repeats:YES];

    running=YES;

}



// intercettiamo gli eventi della tastiera

- (void)keyDown:(NSEvent *)theEvent

{

    NSString  *tasti;

    NSRect      rettangolo1,rettangolo2,rettangolo3,rettangolo4;

    NSPoint      nuovaPosizione;

    id    thePlayer;

    

    // dobbiamo innanzitutto ricordarci dov'erano i giocatori per poter ridisegnare le caselle sottostanti se si muovono

    rettangolo1=[giocatore1 frame];

    rettangolo2=[giocatore2 frame];

    

    tasti=[theEvent charactersIgnoringModifiers];

    if(kDebugMask & kBattleFieldDebug)

 NSLog(tasti);

    if([tasti caseInsensitiveCompare:kGiocatore1Su]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kSu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Giu]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kGiu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Sinistra]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kSinistra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore1Destra]==NSOrderedSame)

 thePlayer=[giocatore1 vai:kDestra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Su]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kSu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Giu]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kGiu nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Sinistra]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kSinistra nuovaCasella:&nuovaPosizione];

    else if([tasti caseInsensitiveCompare:kGiocatore2Destra]==NSOrderedSame)

 thePlayer=[giocatore2 vai:kDestra nuovaCasella:&nuovaPosizione];

    else

    {

 // ignoriamo altri tasti

 nuovaPosizione=NSMakePoint(kInvalidMove,kInvalidMove);

 thePlayer=nil;

    }

    if([self isValidMove:nuovaPosizione])

    {

 [thePlayer validateMove:nuovaPosizione];

 if([thePlayer cagamine])

     [self playerDropsBomb:thePlayer atCell:nuovaPosizione];

 rettangolo3=[giocatore1 frame];

 rettangolo4=[giocatore2 frame];

 [self setNeedsDisplayInRect:rettangolo1];

 [self setNeedsDisplayInRect:rettangolo2];

 [self setNeedsDisplayInRect:rettangolo3];

 [self setNeedsDisplayInRect:rettangolo4];

 [self processBonusAtCell:nuovaPosizione forPlayer:thePlayer];

    }

    else

    {

 if(thePlayer)

     [thePlayer didWrongMove];

    }

    

    // però il giocatore potrebbe aver premuto il tasto per posizionare la bomba

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Looking if player wants to drop a bomb");

    if(([tasti caseInsensitiveCompare:kGiocatore1Bomba]==NSOrderedSame) || ([tasti caseInsensitiveCompare:kGiocatore2Bomba]==NSOrderedSame))

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"One player wants to drop a bomb");

 if([tasti caseInsensitiveCompare:kGiocatore1Bomba]==NSOrderedSame)

     thePlayer=giocatore1;

 else

     thePlayer=giocatore2;

 [self playerDropsBomb:thePlayer atCell:[thePlayer coordinate]];

    }

}



- (void)playerDropsBomb:(id)thePlayer atCell:(NSPoint)thePos

{

    if([thePlayer canDropBomb])

    {

 Bomba      *b;

 NSTimer      *t;

 

 [thePlayer dropBomb];

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"OK, ready to drop bomb, setting up bomb object");

 b=[[Bomba alloc] initWithPosition:[thePlayer coordinate] power:[thePlayer potenzaDiFuoco] player:thePlayer explode:YES];

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Setting up timer for callback on explosion");

 t=[NSTimer scheduledTimerWithTimeInterval:kBombaTempoMiccia

            target:self

          selector:@selector(explodeBomb:)

          userInfo:b

           repeats:NO];

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Telling cell to place bomb");

 [[self casellaConCoordinate:[thePlayer coordinate]] impostaContenuto:kBomba];

    }

}



- (BOOL)isValidMove:(NSPoint)pos

{

    if((int)pos.x<0 || (int)pos.x>=kPlanciaWidth || (int)pos.y<0 || (int)pos.y>=kPlanciaHeight)

    {

 // il giocatore non può uscire dalla plancia

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Out of bounds: cannot move");

 return NO;

    }

    

    // qui controlliamo che i giocatori non occupino la stessa casella

    if(NSEqualPoints(pos,[giocatore1 coordinate]) || NSEqualPoints(pos,[giocatore2 coordinate]))

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Crashing into other player: cannot move");

 return NO;

    }



    // qui controlleremo gli ostacoli sulla plancia

    if(([[self casellaConCoordinate:pos] contenuto]==kMuroFisso) || (([[self casellaConCoordinate:pos] contenuto]==kMuroMobile) && [[self casellaConCoordinate:pos] isExploding]==NO) || ([[self casellaConCoordinate:pos] contenuto]==kBomba))

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Crashing into wall or bomb: cannot move");

 return NO;

    }

 

    // se arriviamo qui abbiamo passato tutti i test

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"OK: move!");

    return YES;

}



- (id)casellaConCoordinate:(NSPoint)coord

{

    NSEnumerator  *en;

    id        obj;

    

    en=[caselle objectEnumerator];

    while(obj=[en nextObject])

    {

 if(NSEqualPoints([obj coordinate],coord))

     return obj;

    }

    return nil;

}



- (void)explodeBomb:(NSTimer *)theTimer

{

    id      info;

    Bomba  *b;

    NSTimer  *t;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Explosion!!!");

    info=[theTimer userInfo];

    

    // ora mettiamo l'esplosione e la propaghiamo

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Exploding bomb cell");

    [[self casellaConCoordinate:[info posizione]] impostaContenuto:kCasellaVuota];

    [[self casellaConCoordinate:[info posizione]] startExplosion];

    [self setNeedsDisplayInRect:[[self casellaConCoordinate:[info posizione]] frame]];

    

    [self propagateExplosion:info];

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Now let's set up timer and object for end of explosion");

    b=[[Bomba alloc] initWithPosition:[info posizione] power:[info potenza] player:[info giocatore] explode:NO];

    t=[NSTimer scheduledTimerWithTimeInterval:kBombaTempoEsplosione

         target:self

         selector:@selector(endOfExplosion:)

         userInfo:b

         repeats:NO];

}



- (void)endOfExplosion:(NSTimer *)theTimer

{

    id  info;

    

    if(running)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"End of explosion");

 info=[theTimer userInfo];

 

 // riportiamo innanzitutto la casella in condizioni normali

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Stopping explosion in bomb cell");

 [[self casellaConCoordinate:[info posizione]] stopExplosion];

 [self setNeedsDisplayInRect:[[self casellaConCoordinate:[info posizione]] frame]];

 

 // ora propaghiamo

 [self propagateExplosion:info];

 

 // infine ridiamo la bomba al giocatore

 [[info giocatore] getBomb];

    }

}



- (void)propagateExplosion:(id)info

{

    int  i;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Propagating (explosion|removal: %d) %d cell(s) to the left",[info esplosione],[info potenza]);

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) to the left",i);

 p=[info posizione];

 p.x=p.x-i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota || [c contenuto]==kBomba)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

      [c startExplosion];

   else

      [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) to the right",i);

 p=[info posizione];

 p.x=p.x+i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota || [c contenuto]==kBomba)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) upwards",i);

 p=[info posizione];

 p.y=p.y+i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota || [c contenuto]==kBomba)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating explosion");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

    

    for(i=1;i<=[info potenza];i++)

    {

 NSPoint  p;

 id      c;

 

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Propagating %d cell(s) downwards",i);

 p=[info posizione];

 p.y=p.y-i;

 if(c=[self casellaConCoordinate:p])

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"In bounds, checking if cell is empty");

     if([c contenuto]==kCasellaVuota || [c contenuto]==kBomba)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Empty! Propagating");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

     }

     else if([c contenuto]==kMuroMobile)

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Movable wall! Exploding and blocking");

   if([info esplosione])

       [c startExplosion];

   else

       [c stopExplosion];

   [self setNeedsDisplayInRect:[c frame]];

   i=[info potenza]+1;

     }

     else

     {

   if(kDebugMask & kBattleFieldDebug)

       NSLog(@"Not empty. Blocking");

   i=[info potenza]+1;

     }

 }

 else

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Out of bounds. Blocking");

     i=[info potenza]+1;

 }

    }

}



- (void)checkPlayerLife:(NSTimer *)theTimer

{

    id      player,otherplayer;

    

    player=nil;

    if([[self casellaConCoordinate:[giocatore1 coordinate]] isExploding])

    {

 // è morto il giocatore 1

 player=giocatore2;

 otherplayer=giocatore1;

    }

    if([[self casellaConCoordinate:[giocatore2 coordinate]] isExploding])

    {

 // è morto il giocatore 2

 player=giocatore1;

 otherplayer=giocatore2;

    }

    if([[self casellaConCoordinate:[giocatore1 coordinate]] bonus]==kTeletrasporto)

    {

 id      otherGate;

 

 otherGate=[self otherGate:[giocatore1 coordinate]];

 if([otherGate contenuto]==kMuroFisso || [otherGate contenuto]==kMuroMobile || [otherGate isExploding])

 {

     player=giocatore2;

     otherplayer=giocatore1;

 }

    }

    if([[self casellaConCoordinate:[giocatore2 coordinate]] bonus]==kTeletrasporto)

    {

 id      otherGate;

 

 otherGate=[self otherGate:[giocatore2 coordinate]];

 if([otherGate contenuto]==kMuroFisso || [otherGate contenuto]==kMuroMobile || [otherGate isExploding])

 {

     player=giocatore1;

     otherplayer=giocatore2;

 }

    }

    if(player!=nil)

    {

 if(kDebugMask & kBattleFieldDebug)

     NSLog(@"Player dead!");

 running=NO;

 [timerVitaGiocatori invalidate];

 [[NSNotificationCenter defaultCenter] postNotificationName:kFineDelGiocoNotification object:nil userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[player segnalino],kVincitoreKey,[otherplayer segnalino],kPerdenteKey,nil]];

    }

}



- (void)processBonusAtCell:(NSPoint)theCoord forPlayer:(id)thePlayer

{

    id  otherGate;

    

    if(kDebugMask & kBattleFieldDebug)

 NSLog(@"Processing bonuses");

    

    switch([[self casellaConCoordinate:theCoord] bonus])

    {

 case kNoBonus:

     // che fare? nulla!

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"No bonus!");

     break;

 case kFuoco:

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Fire: get another bomb!");

     [thePlayer getBomb];

     [[self casellaConCoordinate:theCoord] setBonus:kNoBonus];

     break;

 case kTeschio:

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Skull: drop all bombs!");

     [self playerDropsBomb:thePlayer atCell:theCoord];

     [thePlayer setCagamine];

     [[self casellaConCoordinate:theCoord] setBonus:kNoBonus];

     break;

 case kTeletrasporto:

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Teleporting…");

     otherGate=[self otherGate:theCoord];

     if([otherGate contenuto]==kCasellaVuota && !NSEqualPoints([otherGate coordinate],[giocatore1 coordinate]) && !NSEqualPoints([otherGate coordinate],[giocatore2 coordinate]))

     {

   [thePlayer validateMove:[otherGate coordinate]];

   [self setNeedsDisplayInRect:[thePlayer frame]];

     }

     break;

    }

}



- (id)otherGate:(NSPoint)theCoord

{

    NSEnumerator  *en;

    id        obj;

    

    en=[caselle objectEnumerator];

    while(obj=[en nextObject])

    {

 if([obj bonus]==kTeletrasporto && !NSEqualPoints(theCoord,[obj coordinate]))

 {

     if(kDebugMask & kBattleFieldDebug)

   NSLog(@"Found the other gate");

     return obj;

 }

    }

    return nil;

}



@end

Innanzitutto prendiamo il metodo generateNewField e, in coda all'inserimento di fuochi e teschi, aggiungiamo due (di numero) bonus di tipo kTeletrasporto. Poiché continuiamo ad estrarre caselle da quelle con i muri mobili eliminando quelle già utilizzate per i fuochi prima e per i teschi poi, siamo certi che i teletrasporti andranno a piazzarsi in caselle libere da altri bonus (ma naturalmente occupate, all'inizio, da un muro mobile).
Il nocciolo della questione sta, naturalmente, nella processBonusAtCell:forPlayer: Qui, nel caso in cui il giocatore capiti su una casella dotata del bonus teletrasporto, chiediamo al metodo otherGate: di localizzare, sulla plancia, l'altra porta del teletrasporto; una volta trovata, se questa è libera da ingombri (niente muri, niente bombe, non ci sono giocatori sopra) allora il giocatore viene teletrasportato (cambiano le sue coordinate sulla plancia).
Il metodo otherGate: non fa niente di particolare: passa in rassegna tutte le caselle della plancia finché ne trova una con un teletrasporto e di coordinate diverse da quelle che sono state passate per argomento (e che sono quelle della porta a cui si trova il giocatore da teletrasportare). Una volta trovatala, la restituisce al mittente.
Infine modifichiamo il metodo checkPlayerLife:, perché ora un giocatore che capiti su un teletrasporto la cui porta di "uscita" sia occupata da un muro o da un'esplosione deve morire; e così, in effetti, è.

Con questa versione abbiamo terminato di aggiungere funzioni al gioco (a parte il gioco via rete TCP/IP che, se riusciremo, aggiungeremo in una release successiva alla 1.0). Da ora in avanti, per qualche altra versione ancora, ci occuperemo di qualche dettaglio e qualche rifinitura, indispensabile non già per divertirsi, ma per rendere il programma più "professionale" e più adeguato agli standard di qualità a cui siamo abituati. Con la versione 0.4, infatti, inizieremo a gestire delle preferenze dell'utente, dando inizialmente la possibilità di configurare i tasti da usare per controllare entrambi i segnalini.

Poi, naturalmente, una parte significativa del "tocco professionale" verrà dall'uso di risorse grafiche e sonore più adeguate di quelle fatte da me; volontari? icon_wink.gif


--------------------
Marco Coïsson
http://web.me.com/marco_coisson

L'atomo divisibile: podcast gratuito di divulgazione scientifica.

X come Macintosh
Go to the top of the page
 
+Quote Post

4 Pagine V   1 2 3 > » 
Reply to this topicStart new topic

 

Collapse

> Argomenti simili o correlati

    Titolo discussione Risposte Autore discussione Visite Ultima azione
No New Posts   3 robby 302 4 September 2009 - 19:58
Ultimo messaggio di: MacMomo
No New Posts   6 casteddaia 762 12 August 2009 - 23:00
Ultimo messaggio di: Martini
No new   15 chebfarid 713 11 May 2009 - 16:15
Ultimo messaggio di: ecienzo
No New Posts   2 robby 222 16 April 2009 - 07:27
Ultimo messaggio di: MacMomo
No New Posts   4 Niccoloco 215 22 February 2009 - 20:01
Ultimo messaggio di: Lullaby71
No new   16 alebopp 10.904 23 October 2008 - 17:12
Ultimo messaggio di: cesri
No New Posts   7 Dr.Muttley 448 14 August 2008 - 20:08
Ultimo messaggio di: Dr.Muttley
No New Posts   4 NSLuca 314 4 August 2008 - 14:25
Ultimo messaggio di: NSLuca
No new   11 marg 1.267 2 July 2008 - 07:11
Ultimo messaggio di: marg
No New Posts   6 ilredelsilenzio 427 27 June 2008 - 09:19
Ultimo messaggio di: ilredelsilenzio
No New Posts   4 disable 306 23 June 2008 - 17:37
Ultimo messaggio di: volkov
No New Posts   4 germinara 295 22 June 2008 - 13:29
Ultimo messaggio di: sirguich_

Modalità di visualizzazione: Normale · Passa a: Lineare · Passa a: Outline


RSS Versione Lo-Fi Oggi è il: 9 February 2010 - 11:35
IP.Board Skin Developed By Creative Networks