2009-02-19 11 views
14

Estoy trabajando en la codificación de un clon de Tetris en XNA C# y no estoy seguro de la mejor manera de acercarse a la estructura de datos del juego en un alto nivel.Cómo hacer un clon de Tetris?

Estoy totalmente bien con la detección de colisiones, rotaciones, animaciones, etc. Lo que necesito saber es la mejor manera de almacenar "bloques caídos", es decir, bloques que ya no están bajo el control del jugador.

Creo que cada bloque Tetromino debe almacenarse en su propia clase que consiste en una matriz de 4x4 para que el bloque pueda girarse fácilmente. El problema es cómo almacenar la posición final del tetromino en la cuadrícula del juego cortando el tetromino en bloques individuales (para cada celda) y luego establecer las posiciones correspondientes de la grilla principal del juego para mantener estos mismos bloques, y luego desaparecer el tetromino una vez ha llegado a su posición final. Tal vez haya algún inconveniente en mi método.

¿Debo crear una matriz de 10x20 para la cuadrícula principal del juego que luego pueda almacenar? o debería usar pilas o colas para almacenar de alguna manera los bloques caídos. ¿O tal vez hay alguna mejor estructura de método/datos para almacenar cosas?

Estoy seguro de que mi camino podría funcionar, pero estoy tratando de ver si alguien conoce una mejor manera o si mi camino es lo suficientemente bueno?

P.S. No es tarea, este será un proyecto para mi cartera. Gracias.

Respuesta

20

Una vez que un bloque está inmóvil, no hay nada que lo distinga de cualquier otro bloque que ahora esté inmóvil. En ese sentido, creo que tiene más sentido almacenar toda la cuadrícula como una matriz, donde cada cuadrado está lleno o no (junto con el color del bloque, si es así).

Siento que la matriz tiene muchas ventajas. Hará que la detección de colisiones sea simple (sin tener que comparar con múltiples objetos, solo ubicaciones en una matriz). Almacenarlo como una matriz también hará que sea más fácil determinar cuándo se ha creado una línea completa. Además de eso, no tienes que preocuparte por empalmar un Tetromino inmóvil cuando una línea desaparece.Y cuando lo hace, puede cambiar toda la matriz de una sola vez.

+1

Discretamente, no estoy de acuerdo - ver mi respuesta. –

+0

Además, no puedes hacer animaciones dulces o gravedad avanzada. Mi tablero es un montón de referencias a las piezas. Cuando se borra una línea, cada bloque cae por separado, y si se dividen o se eliminan los bits que causan colgar, las piezas caerán como deberían. –

+1

@toast: Está muy bien decir que su respuesta no es buena. Y definitivamente puedo ver tu punto allí. Tal vez debería proporcionar una respuesta que explique cómo lo haría. –

1

Tenga en cuenta que un ganador anterior del concurso Código ofuscado C implementado un muy buen juego de tetris (para terminales VT100 en BSD UNIX) en menos de 512 bytes de C ofuscado:

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í, sé que podría simplemente abrirme paso a través del problema. Eso no es lo que me interesa hacer o de lo contrario habría seguido con mi teoría. Hice la pregunta para ver si alguien tenía una solución elegante/mejor a la forma en que planeo proceder. –

+1

¡Ahora veo, todo está claro! – KingNestor

+1

Escribir un buen código estructurado es importante. Hacks no queridos aquí –

4

Esto huele a tarea, pero mi enfoque de Tetris orientado a objetos sería tener cada cuadrado individual como un objeto, y tanto los "bloques" (tetrominos) como la propia cuadrícula serían colecciones de los mismos objetos cuadrados.

Los objetos de bloque administran la rotación y la posición de los cuadrados que caen, y los controladores de cuadrícula que los muestran y la destrucción de las filas completadas. Cada bloque tendría un color o textura asociado que sería proporcionado por el objeto de bloque original del que venía, pero de lo contrario los cuadrados en la base de la grilla no tendrían otra indicación de que alguna vez fueron parte del mismo bloque original.

Para elaborar, cuando creas un nuevo objeto de bloque, crea un conjunto de 4 cuadrados con el mismo color/textura en la cuadrícula. La grilla maneja su pantalla. Entonces, cuando el bloque toca el fondo, simplemente te olvidas del bloque y los cuadrados siguen siendo referenciados por la cuadrícula.

Las rotaciones y caídas son operaciones con las que solo se debe ocupar un bloque, y solo una de sus cuatro plazas (aunque deberá ser capaz de consultar la cuadrícula para asegurarse de que la rotación pueda ajustarse).

1

No soy de ninguna manera un experto en Tetris, pero como usted describió, una matriz de 10x20 me parece una opción natural.

Hará que sea muy fácil cuando llegue el momento de verificar si ha completado una línea o no, y tratar con ella. Simplemente iterando sobre la matriz 2d mirando los valores booleanos de cada posición para ver si suman hasta 10 posiciones de bloque.

Sin embargo, tendrá que realizar una limpieza manual si hay una línea completa. Tener que cambiar todo. Aunque no es un gran problema cuando se trata de eso.

2

Usar arreglos sería la forma más fácil de manejar tetris. Existe una correlación directa entre lo que se ve en la pantalla y las estructuras utilizadas en la memoria. Usar la pila/colas sería una exageración e innecesariamente complicado.

Puede tener 2 copias de un bloque que se cae. Uno será para mostrar (Alpha) y el otro será movimiento (Beta).

Usted necesitará una estructura como

 

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 matriz _beta sería mover o rotada y se comprueba con la junta de colisiones. Si hay una colisión, vuélvala a _alpha, si no, copie _beta en _alpha.

Y si hay una colisión en moveDown(), la vida del bloque ha terminado y la cuadrícula _alpha tendría que copiarse en el tablero de juego y el objeto FallingBlock eliminado.

La junta por supuesto tiene que ser otra estructura como:

 

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

    //some functions go here 
} 
 

Solía ​​int para representar un bloque, cada valor (como 1,2,3) que representa una textura o color diferente (0 haría significa un lugar vacío).

Una vez que el bloque es parte del tablero, solo necesitaría un identificador de textura/color para mostrar.

+0

¿por qué recibió un negativo ... solo curiosidad? – johnny

+0

+1 de mí, probablemente no sea la forma en que voy a ir, pero aprecio la entrada sin embargo –

2

De hecho acabo de hacer esto hace unos días, excepto en WPF en lugar de XNA. Esto es lo que hice:

Editar: Parece que defino "Bloquear" de manera diferente que otras personas. Lo que defino como un Bloque es una de las 4 celdas que componen un Tetromino, y un Tetromino real en sí mismo como una Pieza.

Tienen un bloque como estructura que tiene coordenadas X, Y y Color. (Más tarde agregué un bool IsSet para indicar si estaba en una pieza flotante o en la placa real, pero eso fue solo porque quería distinguirlos visualmente)

Como métodos en Block, tenía Left, Right, Down y Girar (centro del bloque) que devuelve un nuevo bloque desplazado. Esto me permitió rotar o mover cualquier pieza sin conocer la forma o la orientación de la pieza.

Tenía un objeto pieza genérico que tenía una lista de todos los bloques que contenía y el índice del bloque que era el centro, que se usa como centro de rotación.

Luego hice un PieceFactory que podría producir todas las piezas diferentes, y con una pieza que no necesita saber qué tipo de pieza era, pude (y lo hice) agregar fácilmente la variación de las piezas que constan de más o menos de 4 Bloques sin necesidad de crear ninguna clase nueva

La placa consistía en un diccionario que contenía todos los bloques que se encontraban actualmente en la placa, así como las dimensiones de la placa que se podía configurar. También es probable que pueda utilizar una Matriz, pero con un Diccionario solo necesitaba iterar a través de Bloques sin espacios en blanco.

3

No hacer que los bloques realmente parezcan bloques autónomos es, en mi opinión, una gran falla de muchos clones de Tetris. Me esforcé especialmente para asegurar que my clone siempre se viera bien, ya sea que el bloque esté todavía "en juego" o se haya caído. Esto significó ir un poco más allá de la estructura de datos de matriz simple y llegar a algo que apoyaba el concepto de "conexión" entre las partes del bloque.

Tuve una clase llamada BlockGrid que se usa como clase base tanto para Block como para Board. BlockGrid tiene un método abstracto (puro virtual en C++) llamado AreBlockPartsSameBlock que las subclases deben anular para determinar si dos partes diferentes del bloque pertenecen al mismo bloque. Para la implementación en Block, simplemente devuelve true si hay partes de bloque en ambas ubicaciones. Para la implementación en Board, devuelve true si ambas ubicaciones contienen el mismo Block.

La clase BlockGrid usa esta información para "completar" los detalles en los bloques representados, para que realmente se vean como bloques.

+1

Hacer que las piezas parezcan "conectadas" de esta manera es puramente una elección visual. El NES Tetris original no hizo esto, cada bloque estaba separado, pero tenía su color establecido por el tipo de pieza del que era originalmente. En general, creo que agregaría mucha complejidad a alguien que intenta escribir un clon básico. –

+0

IMO se ve más feo conectado que como cuadrados distintos, pero si realmente te gusta esa mirada, entonces tu camino es el camino a seguir. – Davy8

+0

Sí Kent, estoy de acuerdo con lo que dijiste sobre hacer que los bloques activos en juego sean visualmente diferentes mediante el uso de un contorno o brillo externo o algo así. ¿Puedes explicar lo que estás desacuerdo en la respuesta de Daniel Lew? –

2

Mi solución (diseño), con ejemplos en Python como un buen sustituto para el pseudo código.

Use una rejilla de 20 x 10, que los tetrominios caen.

Tetrominoes se componen de bloques, que tienen atributos de coordenada (x, y) y color.

Así, por ejemplo, el tetrominoe forma de T se parece a esto ...

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

Por lo tanto, la forma de T es un conjunto de bloques con las coordenadas (5,19), (6, 19), (7,19), (6,20).

Mover la forma es una cuestión de aplicar una transformación simple a todas las coordenadas del grupo. p.ej. para mover la forma hacia abajo, agregue (0,1), izquierda (-1,0) o derecha (1,0) a todos los cordones en la colección que hacen la forma.

Esto también le permite usar algunos trigonometría simple para girar la forma en 90 grados. La regla es que al girar 90 grados con respecto a un origen, entonces (x, y) se vuelve igual a (-y, x).

Aquí hay un ejemplo para explicarlo. Tomando la forma de T desde arriba, usa (6,19) como el bloque central para girar alrededor. Por simplicidad, hacen de esta la primera coordenada de la colección, así que ...

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

Entonces, aquí es una simple función para girar esa colección de coordenadas 90 grados

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 

Ahora, si aplica esta función a nuestra colección de coordenadas para la forma de T ...

new_t_shape = rotate(t_shape) 

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

Terreno esto en el sistema de coordenadas y parece que este ...

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

Esa fue la parte más difícil para mí, espero que esto ayude a alguien.

+0

Usó su lógica y la cambió en C# –

+0

Me alegro de que haya ayudado. –

0

Utilizando la lógica Simon Peverett, esto es lo que terminamos con en 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: El uso de XNA, Point estructura podría ser canjeado por Vector2D

0

en mi ejemplo (Java) - todas las figuras tienen listas de bloques, que pueden eliminarse siempre que sea necesario. También en mi clase de la Junta tengo una lista de figuras y una figura de variable de campo, que es controlada por el usuario. Cuando la figura "aterriza", entra en la lista de otras figuras y el usuario puede controlar una nueva figura. Una mejor explicación aquí: http://bordiani.wordpress.com/2014/10/20/tetris-in-java-part-i-overview/