2009-02-19 12 views
14

Sto lavorando alla codifica di un clone di Tetris in XNA C# e sono incerto sul modo migliore per avvicinarsi al lato della struttura di dati del gioco ad alto livello.Come creare un clone di Tetris?

Sto perfettamente bene con il rilevamento delle collisioni, le rotazioni, le animazioni ecc. Quello che mi serve sapere il modo migliore per fare la memorizzazione dei "blocchi rilasciati" - cioè blocchi che non sono più sotto il controllo del giocatore.

Penso che ogni blocco Tetromino debba essere memorizzato nella propria classe che consiste in un array 4x4 in modo che il blocco possa essere facilmente ruotato. Il problema è allora come memorizzare la posizione finale del tetromino nella griglia di gioco tagliando il tetromino in singoli blocchi (per ogni cella) e quindi impostare le posizioni corrispondenti della griglia di gioco principale per tenere questi stessi blocchi, quindi scomparire il tetromino una volta ha raggiunto la sua posizione finale. Forse c'è qualche svantaggio per il mio metodo.

Devo creare una matrice 10x20 per la griglia di gioco principale che può quindi essere archiviata? o dovrei usare pile o code per archiviare in qualche modo i blocchi rilasciati. O forse c'è qualche migliore metodo/struttura dati per archiviare le cose?

Sono sicuro che il mio modo funzionerebbe, ma sto cercando di capire se qualcuno conosce un modo migliore o se la mia strada è abbastanza buona?

P.S. Non a casa, questo sarà un progetto per il mio portfolio. Grazie.

risposta

20

Una volta che un blocco è immobile, non c'è nulla che lo distingua da qualsiasi altro blocco che è ora immobile. A questo proposito, penso che abbia più senso immagazzinare l'intera griglia come una matrice, in cui ogni quadrato è riempito o meno (insieme al colore del blocco, se lo è).

Mi sento come se la matrice avesse molti vantaggi. Rende semplice il rilevamento delle collisioni (non è necessario confrontarsi con più oggetti, solo posizioni su una matrice). Memorizzarlo come una matrice renderà anche più facile determinare quando è stata creata una linea completa. Oltre a questo, non devi preoccuparti di splicing un Tetromino immobile quando una linea scompare.E quando lo fai, puoi semplicemente spostare l'intera matrice in un colpo solo.

+1

Sono rispettosamente in disaccordo - vedi la mia risposta. –

+0

Inoltre, non puoi fare animazioni dolci o gravità avanzata. La mia scheda è un mucchio di riferimenti ai pezzi. Quando una linea viene cancellata, ogni blocco cade separatamente, e se vengono divisi o i bit che causano l'impiccagione vengono rimossi, i pezzi cadranno come dovrebbero. –

+1

@toast: Va bene dire che la sua risposta non va bene. E posso assolutamente vedere il tuo punto lì. Forse dovresti fornire una risposta che spieghi come lo faresti. –

1

Tenete a mente che il precedente vincitore del Obfuscated C Codice Contest implementato un buon gioco di tetris (per i terminali VT100 su BSD UNIX) a meno di 512 byte di C offuscato:

long h[4];t(){h[3]-=h[3]/3000;setitimer(0,h,0);}c,d,l,v[]={(int)t,0,2},w,s,I,K 
=0,i=276,j,k,q[276],Q[276],*n=q,*m,x=17,f[]={7,-13,-12,1,8,-11,-12,-1,9,-1,1, 
12,3,-13,-12,-1,12,-1,11,1,15,-1,13,1,18,-1,1,2,0,-12,-1,11,1,-12,1,13,10,-12, 
1,12,11,-12,-1,1,2,-12,-1,12,13,-12,12,13,14,-11,-1,1,4,-13,-12,12,16,-11,-12, 
12,17,-13,1,-1,5,-12,12,11,6,-12,12,24};u(){for(i=11;++i<264;)if((k=q[i])-Q[i] 
){Q[i]=k;if(i-++I||i%12<1)printf("\033[%d;%dH",(I=i)/12,i%12*2+28);printf(
"\033[%dm "+(K-k?0:5),k);K=k;}Q[263]=c=getchar();}G(b){for(i=4;i--;)if(q[i?b+ 
n[i]:b])return 0;return 1;}g(b){for(i=4;i--;q[i?x+n[i]:x]=b);}main(C,V,a)char* 
*V,*a;{h[3]=1000000/(l=C>1?atoi(V[1]):2);for(a=C>2?V[2]:"jkl pq";i;i--)*n++=i< 
25||i%12<2?7:0;srand(getpid());system("stty cbreak -echo stop u");sigvec(14,v, 
0);t();puts("\033[H\033[J");for(n=f+rand()%7*4;;g(7),u(),g(0)){if(c<0){if(G(x+ 
12))x+=12;else{g(7);++w;for(j=0;j<252;j=12*(j/12+1))for(;q[++j];)if(j%12==10){ 
for(;j%12;q[j--]=0);u();for(;--j;q[j+12]=q[j]);u();}n=f+rand()%7*4;G(x=17)||(c 
=a[5]);}}if(c==*a)G(--x)||++x;if(c==a[1])n=f+4**(m=n),G(x)||(n=m);if(c==a[2])G 
(++x)||--x;if(c==a[3])for(;G(x+12);++w)x+=12;if(c==a[4]||c==a[5]){s=sigblock(
8192);printf("\033[H\033[J\033[0m%d\n",w);if(c==a[5])break;for(j=264;j--;Q[j]= 
0);while(getchar()-a[4]);puts("\033[H\033[J\033[7m");sigsetmask(s);}}d=popen(
"stty -cbreak echo stop \023;cat - HI|sort -rn|head -20>/tmp/$$;mv /tmp/$$ HI\ 
;cat HI","w");fprintf(d,"%4d on level %1d by %s\n",w,l,getlogin());pclose(d);} 

http://www.ioccc.org/1989/tromp.hint

+0

sì so che potrei solo forza bruta mia strada attraverso il problema. Non è quello che mi interessa fare altrimenti sarei andato avanti con la mia teoria. Ho fatto la domanda per vedere se qualcuno avesse una soluzione elegante/migliore per il mio modo di procedere. –

+1

Vedo ora, è tutto chiaro! – KingNestor

+1

Scrivere un buon codice ben strutturato è importante. Hacks non voluto qui –

4

Questo odora di compiti a casa, ma il mio approccio a Tetris orientato agli oggetti sarebbe di avere ogni singolo quadrato come un oggetto, e sia i "blocchi" (tetrominos) che la griglia stessa sarebbero raccolte degli stessi oggetti quadrati.

Gli oggetti del blocco gestiscono la rotazione e la posizione dei quadrati in caduta e le maniglie della griglia che li visualizzano e che rimuovono le righe completate. Ad ogni blocco sarebbe associato un colore o una trama che sarebbe stato fornito dall'oggetto di blocco originale da cui proveniva, ma in caso contrario i quadrati alla base della griglia non avrebbero avuto altra indicazione che facessero parte dello stesso blocco originale.

Per elaborare, quando si crea un nuovo oggetto blocco, viene creato un insieme di 4 quadrati con lo stesso colore/trama sulla griglia. La griglia gestisce la loro visualizzazione. Quindi, quando il blocco tocca il fondo, dimentichi semplicemente il blocco, e i quadrati rimangono referenziati dalla griglia.

Le rotazioni e le cadute sono operazioni di cui solo un blocco deve occuparsi, e solo uno dei suoi quattro quadrati (anche se dovrà essere in grado di interrogare la griglia per assicurarsi che la rotazione possa adattarsi).

1

Non sono affatto un esperto di Tetris, ma come hai descritto una matrice 10x20 mi sembra una scelta naturale.

Renderà molto semplice quando verrà il momento di controllare se hai completato una linea o meno e di occupartene. Semplicemente iterando sulla 2d-array guardando i valori booleani di ogni posizione per vedere se sommano fino a 10 posizioni di blocco.

Tuttavia, è necessario eseguire una pulizia manuale da eseguire se è presente una linea completata. Dovendo spostare tutto verso il basso. Tutto però non è un grosso problema quando si tratta di farlo.

2

L'utilizzo degli array sarebbe il modo più semplice per gestire il tetris. Esiste una correlazione diretta tra ciò che vedi sullo schermo e le strutture utilizzate nella memoria. Usare stack/code sarebbe eccessivo e inutilmente complicato.

È possibile avere 2 copie di un blocco in caduta. Uno sarà per la visualizzazione (Alfa) e l'altro sarà il movimento (Beta).

ti serve uno struttura come

 

class FallingBlock 
{ 
    int pos_grid_x; 
    int pos_grid_y; 
    int blocks_alpha[4][4]; 
    int blocks_beta[4][4]; 

    function movedDown(); 
    function rotate(int direction(); 
    function checkCollision(); 
    function revertToAlpha(); 
    function copyToBeta() 
}; 
 

La matrice _beta sarebbe mossa o ruotato e controllato contro il bordo per collisioni. Se c'è una collisione, ripristinala a _alpha, altrimenti, copia _beta su _alpha.

E se c'è una collisione su moveDown(), la vita del blocco è finita e la griglia _alpha dovrebbe essere copiata sul tabellone e l'oggetto FallingBlock cancellato.

Il bordo sarebbe ovviamente deve essere un'altra struttura come:

 

class Board 
{ 
    int gameBoard[10][20]; 

    //some functions go here 
} 
 

ho usato int per rappresentare un blocco, ciascun valore (come 1,2,3) che rappresentano una trama o un colore differente (0 sarebbe significa un punto vuoto).

Una volta che il blocco fa parte del tabellone, è necessario solo un identificatore di trama/colore da visualizzare.

+0

perché ha avuto un negativo..solo curioso? – johnny

+0

+1 da parte mia, probabilmente non il modo in cui andrò, ma apprezzo l'input nondimeno –

2

In realtà l'ho fatto solo pochi giorni fa, tranne che in WPF anziché in XNA. Ecco cosa ho fatto:

Modifica: Sembra che definisco "Blocco" in modo diverso rispetto ad altre persone. Quello che definisco come un blocco è una delle 4 celle che compongono un tetromino e un vero e proprio tetromino come un pezzo.

Avere un blocco come struttura con coordinate X, Y e Colore. (In seguito ho aggiunto un bool IsSet per indicare se era in un pezzo fluttuante o sulla scheda effettiva, ma era solo perché volevo distinguerli visivamente)

Come metodi su Blocco, avevo sinistra, destra, giù e Ruota (centro del blocco) che ha restituito un nuovo blocco spostato. Questo mi ha permesso di ruotare o spostare qualsiasi pezzo senza conoscere la forma o l'orientamento del pezzo.

Avevo un oggetto Piece generico con un elenco di tutti i blocchi in esso contenuti e l'indice del blocco che era il centro, utilizzato come centro di rotazione.

Ho quindi realizzato una PieceFactory in grado di produrre tutti i diversi pezzi, e con un pezzo che non ha bisogno di sapere che tipo di pezzo era, ho potuto (e ho fatto) aggiungere facilmente una variazione di pezzi composta da più o meno di 4 Blocchi senza la necessità di creare nuove classi

La scheda era costituita da un dizionario che conteneva tutti i blocchi attualmente presenti sulla scheda, nonché le dimensioni della scheda configurabile. È possibile utilizzare Matrix altrettanto bene probabilmente, ma con un dizionario ho solo bisogno di scorrere i blocchi senza spazi bianchi.

3

Il fatto di non rendere effettivamente blocchi di blocchi autonomi è, a mio avviso, un grosso fallimento di molti cloni di Tetris. Ho fatto uno sforzo particolare per garantire che my clone abbia sempre avuto un aspetto corretto, indipendentemente dal fatto che il blocco sia ancora "in riproduzione" o abbandonato. Ciò significava andare leggermente al di là della semplice struttura dei dati della matrice e trovare qualcosa che supportasse il concetto di "connessione" tra le parti del blocco.

Avevo una classe chiamata BlockGrid che viene utilizzata come classe base sia per un Block sia per lo Board. BlockGrid ha un metodo astratto (puro virtuale in C++) chiamato AreBlockPartsSameBlock che le sottoclassi devono eseguire l'override per determinare se due parti di blocco diverse appartengono allo stesso blocco. Per l'implementazione in Block, restituisce semplicemente true se vi sono parti di blocco in entrambe le posizioni. Per l'implementazione in Board, restituisce true se entrambe le posizioni contengono lo stesso Block.

La classe BlockGrid utilizza queste informazioni per "riempire" i dettagli nei blocchi renderizzati, in modo che sembrino effettivamente blocchi.

+1

Rendere i pezzi "connessi" come questo è puramente una scelta visiva. L'originale Tetris NES non lo faceva, ogni blocco era separato, ma aveva il suo colore impostato dal tipo di pezzo da cui proveniva. Nel complesso, penso che aggiungerebbe molta complessità per qualcuno che sta solo cercando di scrivere un clone di base. –

+0

IMO ha un aspetto più brutto collegato che come quadrati distinti, ma se ti piace davvero quell'aspetto allora la tua strada è la strada da percorrere. – Davy8

+0

Sì Kent, sono d'accordo su ciò che hai detto sul rendere i blocchi attivi in ​​gioco visivamente diversi usando un contorno o bagliore esterno o qualcosa del genere. Puoi spiegare in cosa sei in disaccordo nella risposta di Daniel Lew? –

2

La mia soluzione (progettazione), con esempi in Python come un buon sostituto per lo pseudo codice.

Utilizzare una griglia 20 x 10, che i tetrominoes cadano.

I tetrominoes sono costituiti da blocchi, che hanno attributi di coordinate (x, y) e colori.

Così, per esempio, il tetrominoe T-forma assomiglia a questo ...

 . 4 5 6 7 8 . 
    . 
    19  # # # 
    20  # 
    . 

Così, la forma a T è una raccolta di blocchi con le coordinate (5,19), (6, 19), (7,19), (6,20).

Lo spostamento della forma è una questione di applicazione di una semplice trasformazione a tutte le corde del gruppo. per esempio. per spostare la forma verso il basso aggiungi (0,1), sinistra (-1,0) o destra (1,0) a tutte le corde della collezione che formano la forma.

Ciò consente anche di utilizzare alcuni semplici trigze per ruotare la forma di 90 gradi. La regola è che quando si ruota di 90 gradi rispetto a un'origine, allora (x, y) diventa uguale a (-y, x).

Ecco un esempio per spiegarlo. Prendendo la forma a T dall'alto, usa il (6,19) come blocco centrale per ruotare intorno. Per semplicità, rendono questa la prima coordinata nella raccolta, quindi ...

t_shape = [ [6,19], [5,19], [7,19], [6,20] ] 

Poi, ecco una semplice funzione per ruotare quell'insieme di coordinate di 90 gradi

def rotate(shape): 
    X=0  # for selecting the X and Y coords 
    Y=1 

    # get the middle block 
    middle = shape[0] 

    # work out the coordinates of the other blocks relative to the 
    # middle block 
    rel = [] 
    for coords in shape: 
     rel.append([ coords[X]-middle[X], coords[Y]-middle[Y] ]) 

    # now rotate 90-degrees; x,y = -y, x 
    new_shape = [] 
    for coords in rel: 
     new_shape.append([ middle[X]-coords[Y], middle[Y]+coords[X] ]) 

    return new_shape 

Ora, se si applica questa funzione alla nostra collezione di coordinate per la forma a T ...

new_t_shape = rotate(t_shape) 

    new_t_shape 
    [[6, 19], [6, 18], [6, 20], [5, 19]] 

Plot questo fuori nel sistema di coordinate e sembra che questo ...

 . 4 5 6 7 8 . 
    . 
    18  # 
    19  # # 
    20  # 
    . 

Questa è stata la parte più difficile per me, spero che questo aiuta qualcuno.

+0

Hai utilizzato la logica e l'hai modificata in C# –

+0

Sono contento che l'abbia aiutato. –

0

Usando la logica Simon Peverett, qui è quello che ho finito con in C#

public class Tetromino 
{ 
    // Block is composed of a Point called Position and the color 
    public Block[] Blocks { get; protected internal set; } 

    // Constructors, etc. 

    // Rotate the tetromino by 90 degrees, clock-wise 
    public void Rotate() 
    { 
     Point middle = Blocks[0].Position; 
     List<Point> rel = new List<Point>(); 
     foreach (Block b in Blocks) 
      rel.Add(new Point(b.Position.x - middle.x, b.Position.y - middle.y)); 

     List<Block> shape = new List<Block>(); 
     foreach (Point p in rel) 
      shape.Add(new Block(middle.x - p.y, middle.y + p.x)); 

     Blocks = shape.ToArray(); 
    } 

    public void Translate(Point p) 
    { 
     // Block Translation: Position+= p; 
     foreach (Block b in Blocks) 
      b.Translate(p); 
    } 
} 

Nota: Utilizzando XNA, Point struttura potrebbe essere scambiato per Vector2D

0

nel mio esempio (Java) - tutte le figure hanno elenchi di blocchi - che possono essere rimossi quando necessario. Anche nella mia classe Board ho una lista di figure e una figura variabile di campo - che è controllata dall'utente. Quando la figura è "atterrata", entra nell'elenco di altre figure e una nuova figura è resa controllabile dall'utente. Una spiegazione migliore qui: http://bordiani.wordpress.com/2014/10/20/tetris-in-java-part-i-overview/

Problemi correlati