Cómo le enseñé a la IA a jugar Tetris para NES. Parte 1: análisis del código del juego.

En este artículo, exploraré la mecánica aparentemente simple de Nintendo Tetris, y en la segunda parte explicaré cómo creé una IA que explota estas mecánicas.


Pruébalo tú mismo


Sobre el proyecto


Para aquellos que carecen de la perseverancia, la paciencia y el tiempo necesarios para dominar Nintendo Tetris, creé una IA que puede jugar por sí sola. Finalmente puedes llegar al nivel 30 e incluso más. Verá cómo obtener el número máximo de puntos y observará el cambio sin fin de contadores de filas, niveles y estadísticas. Aprenderá qué colores aparecen en niveles por encima de los cuales una persona no podría escalar. Mira hasta dónde puedes llegar.

Requisitos


Para ejecutar la IA, necesita un emulador universal NES / Famicom FCEUX . La inteligencia artificial fue desarrollada para FCEUX 2.2.2 , la versión más nueva del emulador en el momento de la escritura.

También necesitarás el archivo ROM de Nintendo Tetris (versión estadounidense). Intenta buscarlo en Google .

Descargar


Descomprima lua/NintendoTetrisAI.lua de este archivo zip de origen .

Lanzamiento


Lanzamiento de FCEUX. Desde el menú, seleccione Archivo | Abrir ROM ... En el cuadro de diálogo Abrir archivo, selecciona el archivo ROM de Nintendo Tetris y haz clic en Abrir. El juego comenzará.

Desde el menú, seleccione Archivo | Lua Nueva ventana de Lua Script ... En la ventana de Lua Script, ingrese la ruta a NintendoTetrisAI.lua o haga clic en el botón Examinar para encontrarla. Después de eso, haga clic en Ejecutar.

El script en Lua lo redireccionará a la primera pantalla del menú. Deje el tipo de juego A-Type, y puede elegir cualquier música. En computadoras lentas, la música puede reproducirse muy bruscamente, entonces debes apagarla. Presione Inicio (Entrar) para ir a la siguiente pantalla de menú. En el segundo menú, puede usar las teclas de flecha para cambiar el nivel de inicio. Haz clic en Iniciar para comenzar el juego. Y aquí la IA tomará el control.

Si después de seleccionar un nivel en la segunda pantalla del menú, mantenga presionado el botón A del gamepad (puede cambiar la distribución del teclado en el menú Config | Entrada ...) y presione Inicio, entonces el nivel inicial será 10 más que el valor seleccionado. El nivel de entrada máximo es el decimonoveno.

Configuracion


Para que el juego se ejecute más rápido, abra el script Lua en un editor de texto. Al comienzo del archivo, busque la siguiente línea.

PLAY_FAST = false

Reemplace false con true como se muestra a continuación.

PLAY_FAST = true

Guarda el archivo. Luego haga clic en el botón Reiniciar en la ventana Lua Script.

Mecánica de Nintendo Tetris


Descripción del Tetrimino


Cada figura tetrimino corresponde a un nombre de una letra que se asemeja a su forma.


Los diseñadores de Nintendo Tetris establecen arbitrariamente el orden de tetrimino que se muestra arriba. Las figuras se muestran en la orientación en la que aparecen en la pantalla, y el circuito crea una imagen casi simétrica (quizás por eso se elige este orden). El índice de secuencia le da a cada tetrimino una identificación numérica única. Los identificadores de secuencia y tipo son importantes a nivel de programación; Además, se manifiestan en el orden de las cifras que se muestran en el campo de estadísticas (ver más abajo).


Las 19 orientaciones utilizadas en el Nintendo Tetris tetrimino están codificadas en una tabla ubicada en $8A9C de la memoria de la consola NES. Cada figura se representa como una secuencia de 12 bytes que se puede dividir en triples (Y, tile, X) que describen cada cuadrado de la figura. Los valores hexadecimales anteriores de las coordenadas superiores a $7F denotan enteros negativos ( $FF= −1 y $FE = −2 ).

; Y0 T0 X0 Y1 T1 X1 Y2 T2 X2 Y3 T3 X3

8A9C: 00 7B FF 00 7B 00 00 7B 01 FF 7B 00 ; 00: T up
8AA8: FF 7B 00 00 7B 00 00 7B 01 01 7B 00 ; 01: T right
8AB4: 00 7B FF 00 7B 00 00 7B 01 01 7B 00 ; 02: T down (spawn)
8AC0: FF 7B 00 00 7B FF 00 7B 00 01 7B 00 ; 03: T left

8ACC: FF 7D 00 00 7D 00 01 7D FF 01 7D 00 ; 04: J left
8AD8: FF 7D FF 00 7D FF 00 7D 00 00 7D 01 ; 05: J up
8AE4: FF 7D 00 FF 7D 01 00 7D 00 01 7D 00 ; 06: J right
8AF0: 00 7D FF 00 7D 00 00 7D 01 01 7D 01 ; 07: J down (spawn)

8AFC: 00 7C FF 00 7C 00 01 7C 00 01 7C 01 ; 08: Z horizontal (spawn)
8B08: FF 7C 01 00 7C 00 00 7C 01 01 7C 00 ; 09: Z vertical

8B14: 00 7B FF 00 7B 00 01 7B FF 01 7B 00 ; 0A: O (spawn)

8B20: 00 7D 00 00 7D 01 01 7D FF 01 7D 00 ; 0B: S horizontal (spawn)
8B2C: FF 7D 00 00 7D 00 00 7D 01 01 7D 01 ; 0C: S vertical

8B38: FF 7C 00 00 7C 00 01 7C 00 01 7C 01 ; 0D: L right
8B44: 00 7C FF 00 7C 00 00 7C 01 01 7C FF ; 0E: L down (spawn)
8B50: FF 7C FF FF 7C 00 00 7C 00 01 7C 00 ; 0F: L left
8B5C: FF 7C 01 00 7C FF 00 7C 00 00 7C 01 ; 10: L up

8B68: FE 7B 00 FF 7B 00 00 7B 00 01 7B 00 ; 11: I vertical
8B74: 00 7B FE 00 7B FF 00 7B 00 00 7B 01 ; 12: I horizontal (spawn)

8B80: 00 FF 00 00 FF 00 00 FF 00 00 FF 00 ; 13: Unused


En la parte inferior de la tabla hay un registro no utilizado, que potencialmente brinda la oportunidad de agregar otra orientación. Sin embargo, en varias partes del código, $13 indica que el identificador de orientación del tetrimino activo no tiene asignado un valor.

Para facilitar la lectura, las coordenadas de los cuadrados en decimal se muestran a continuación.

-- { { X0, Y0 }, { X1, Y1 }, { X2, Y2 }, { X3, Y3 }, },

{ { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, -1 }, }, -- 00: T up
{ { 0, -1 }, { 0, 0 }, { 1, 0 }, { 0, 1 }, }, -- 01: T right
{ { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, 1 }, }, -- 02: T down (spawn)
{ { 0, -1 }, { -1, 0 }, { 0, 0 }, { 0, 1 }, }, -- 03: T left

{ { 0, -1 }, { 0, 0 }, { -1, 1 }, { 0, 1 }, }, -- 04: J left
{ { -1, -1 }, { -1, 0 }, { 0, 0 }, { 1, 0 }, }, -- 05: J up
{ { 0, -1 }, { 1, -1 }, { 0, 0 }, { 0, 1 }, }, -- 06: J right
{ { -1, 0 }, { 0, 0 }, { 1, 0 }, { 1, 1 }, }, -- 07: J down (spawn)

{ { -1, 0 }, { 0, 0 }, { 0, 1 }, { 1, 1 }, }, -- 08: Z horizontal (spawn)
{ { 1, -1 }, { 0, 0 }, { 1, 0 }, { 0, 1 }, }, -- 09: Z vertical

{ { -1, 0 }, { 0, 0 }, { -1, 1 }, { 0, 1 }, }, -- 0A: O (spawn)

{ { 0, 0 }, { 1, 0 }, { -1, 1 }, { 0, 1 }, }, -- 0B: S horizontal (spawn)
{ { 0, -1 }, { 0, 0 }, { 1, 0 }, { 1, 1 }, }, -- 0C: S vertical

{ { 0, -1 }, { 0, 0 }, { 0, 1 }, { 1, 1 }, }, -- 0D: L right
{ { -1, 0 }, { 0, 0 }, { 1, 0 }, { -1, 1 }, }, -- 0E: L down (spawn)
{ { -1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 }, }, -- 0F: L left
{ { 1, -1 }, { -1, 0 }, { 0, 0 }, { 1, 0 }, }, -- 10: L up

{ { 0, -2 }, { 0, -1 }, { 0, 0 }, { 0, 1 }, }, -- 11: I vertical
{ { -2, 0 }, { -1, 0 }, { 0, 0 }, { 1, 0 }, }, -- 12: I horizontal (spawn)


Todas las orientaciones se colocan en una matriz de 5 × 5.


En la figura anterior, el cuadrado blanco indica el centro de la matriz, el punto de referencia para la rotación de la figura.

La tabla de orientación se presenta gráficamente a continuación.


El identificador de orientación (índice de la tabla) se muestra en hexadecimal en la esquina superior derecha de cada matriz. Y la mnemotecnia inventada para este proyecto se muestra en la esquina superior izquierda. u , r , d , l , h y v son abreviaturas de "arriba, derecha, abajo, izquierda, horizontal y vertical". Por ejemplo, es más fácil denotar la orientación de Jd lugar de $07 .

Las matrices que contienen las orientaciones de las figuras durante la creación están marcadas con un marco blanco.

Tetrimino I, S y Z podrían recibir 4 orientaciones separadas, pero los creadores de Nintendo Tetris decidieron limitarse a dos. Además, Zv y Sv no son imágenes especulares ideales entre sí. Ambos se crean girando en sentido antihorario, lo que conduce a un desequilibrio.

La tabla de orientación también contiene valores de mosaico para cada cuadrado en cada figura orientada. Sin embargo, con un estudio cuidadoso, queda claro que los valores para un tipo de tetrimino son siempre los mismos.

TJZOSLYo
7B7D7C7B7D7C7B

Los valores de mosaico son los índices de la tabla (pseudo-color) del patrón que se muestra a continuación.


Los mosaicos de $7B , $7C y $7D se encuentran directamente debajo de "ATIS" de la palabra "ESTADÍSTICAS". Estos son los tres tipos de cuadrados a partir de los cuales se hace el tetrimino.

Para los curiosos, diré que las avestruces y los pingüinos se usan al final del modo de tipo B. Este tema se trata en detalle en la sección "Final".

A continuación se muestra el resultado de modificar la ROM después de reemplazar $7B por $29 . El corazón es el mosaico debajo del símbolo P en la tabla de patrones para todas las orientaciones T.


Las fichas de corazón permanecen en el campo de juego incluso después de que los Ts modificados se bloqueen en su lugar. Como se indica a continuación en la sección "Creación de Tetrimino", esto significa que el campo de juego almacena los valores reales de los índices de mosaico del Tetrimino jugado.

Los programadores de juegos permitieron usar 4 fichas separadas para cada figura, y no solo un tipo invariable de cuadrados. Esta es una característica útil que puede usarse para modificar la apariencia del juego. La tabla de patrones tiene mucho espacio vacío para nuevos mosaicos que pueden dar a cada tetrimino un aspecto único.

Las coordenadas de los cuadrados son muy fáciles de manipular. Por ejemplo, a continuación se muestra una versión modificada de los primeros cuatro triples en la tabla de orientación.

8A9C: FE 7B FE FE 7B 02 02 7B FE 02 7B 02 ; 00: T up

Este cambio es similar al siguiente:

{ { -2, -2 }, { 2, -2 }, { -2, 2 }, { 2, 2 }, }, -- 00: T up

El resultado es un tetrimino dividido.


Al mover un tetrimino dividido, sus cuadrados no pueden ir más allá de los límites del campo de juego y no pueden pasar a través de figuras previamente bloqueadas. Además, el juego prohíbe la rotación en esta orientación si conduce a que una casilla caiga fuera de los límites del campo de juego o al hecho de que la casilla se superpone a una casilla ya tendida.

El tetrimino dividido se bloquea en su lugar cuando hay soporte para cualquiera de sus cuadrados. Si la figura está bloqueada, los cuadrados que cuelgan en el aire continúan colgando.

El juego maneja tetriminos divididos como cualquier figura normal. Esto nos hace comprender que no hay una tabla adicional que almacene los metadatos de las figuras. Por ejemplo, podría haber una tabla que almacene el tamaño del cuadro delimitador de cada orientación para verificar las colisiones con el perímetro del campo de juego. Pero esa tabla no se usa. En cambio, el juego simplemente verifica los cuatro cuadrados justo antes de manipular la forma.

Además, las coordenadas de los cuadrados pueden ser cualquier valor; no se limitan al intervalo [−2, 2] . Por supuesto, los valores que exceden en gran medida este intervalo nos darán cifras inaplicables que no pueden caber en el campo de juego. Más importante aún, como se indicó en la sección "Estados de juego y modos de renderizado", cuando una figura está bloqueada en su lugar, el mecanismo para limpiar líneas rellenas escanea solo los desplazamientos de filas de −2 a 1 desde el cuadrado central de la figura; un cuadrado con una coordenada y fuera de este intervalo no se reconocerá.

Rotación Tetrimino


En una ilustración gráfica de la tabla de orientación, la rotación consiste en pasar de una matriz a una de las matrices a la izquierda o a la derecha con la transferencia de la serie si es necesario. Este concepto está codificado en una tabla a $88EE .

; CCW CW
88EE: 03 01 ; Tl Tr
88F0: 00 02 ; Tu Td
88F2: 01 03 ; Tr Tl
88F4: 02 00 ; Td Tu
88F6: 07 05 ; Jd Ju
88F8: 04 06 ; Jl Jr
88FA: 05 07 ; Ju Jd
88FC: 06 04 ; Jr Jl
88FE: 09 09 ; Zv Zv
8900: 08 08 ; Zh Zh
8902: 0A 0A ; OO
8904: 0C 0C ; Sv Sv
8906: 0B 0B ; Sh Sh
8908: 10 0E ; Lu Ld
890A: 0D 0F ; Lr Ll
890C: 0E 10 ; Ld Lu
890E: 0F 0D ; Ll Lr
8910: 12 12 ; Ih Ih
8912: 11 11 ; Iv Iv


Para hacerlo más claro, moveremos cada columna de esta tabla a la fila de la tabla a continuación.
TuTrTdTlJlJuJrJdZhZvOShSvLrLdLlLuIvIh
En sentido antihorarioTlTuTrTdJdJlJuJrZvZhOSvShLuLrLdLlIhIv
En sentido horarioTrTdTlTuJuJrJdJlZvZhOSvShLdLlLuLrIhIv

La mnemotecnia en los encabezados anteriores se puede interpretar como un índice de secuencia o clave de distribución. Por ejemplo, girando en sentido antihorario Tu nos da Tl , y girando en sentido horario Tu da Tr .

La tabla de rotación codifica secuencias enlazadas en cadena de ID de orientación; por lo tanto, podemos modificar las grabaciones para que la rotación transforme un tipo de tetrimino en otro. Esta técnica puede utilizarse potencialmente para aprovechar una fila no utilizada en la tabla de orientación.

Delante de la tabla de rotación hay un código para acceder a ella.

88AB: LDA $0042
88AD: STA $00AE ; originalOrientationID = orientationID;

88AF: CLC
88B0: LDA $0042
88B2: ASL
88B3: TAX ; index = 2 * orientationID;

88B4: LDA $00B5
88B6: AND #$80 ; if (not just pressed button A) {
88B8: CMP #$80 ; goto aNotPressed;
88BA: BNE $88CF ; }

88BC: INX
88BD: LDA $88EE,X
88C0: STA $0042 ; orientationID = rotationTable[index + 1];

88C2: JSR $948B ; if (new orientation not valid) {
88C5: BNE $88E9 ; goto restoreOrientationID;
; }

88C7: LDA #$05
88C9: STA $06F1 ; play rotation sound effect;
88CC: JMP $88ED ; return;

aNotPressed:

88CF: LDA $00B5
88D1: AND #$40 ; if (not just pressed button B) {
88D3: CMP #$40 ; return;
88D5: BNE $88ED ; }

88D7: LDA $88EE,X
88DA: STA $0042 ; orientationID = rotationTable[index];

88DC: JSR $948B ; if (new orientation not valid) {
88DF: BNE $88E9 ; goto restoreOrientationID;
; }

88E1: LDA #$05
88E3: STA $06F1 ; play rotation sound effect;
88E6: JMP $88ED ; return;

restoreOrientationID:

88E9: LDA $00AE
88EB: STA $0042 ; orientationID = originalOrientationID;

88ED: RTS ; return;


Para la rotación en sentido antihorario, el índice de la tabla de rotación se resta duplicando la ID de orientación. Al agregarle 1, obtenemos el índice de rotación en el sentido de las agujas del reloj.

Las coordenadas x , y ID de orientación del tetrimino actual se almacenan en las direcciones $0040 , $0041 y $0042 respectivamente.

El código usa una variable temporal para hacer una copia de seguridad del ID de orientación. Más tarde, después de cambiar la orientación, el código verifica que los cuatro cuadrados estén dentro de los límites del campo de juego y que ninguno de ellos se superponga con los cuadrados ya existentes (el código de verificación se encuentra en $948B , debajo del fragmento de código que se muestra arriba). Si la nueva orientación es incorrecta, se restaura la original, sin permitir que el jugador gire la figura.

Contando con una cruz, el controlador NES tiene ocho botones, cuyo estado está representado por el bit de dirección $00B6 .

76543210
UnBSeleccioneInicioArribaAbajoA la izquierdaA la derecha

Por ejemplo, $00B6 contendrá el valor $81 mientras el jugador mantiene A e Izquierda.

Por otro lado, $00B5 informa cuando se presionaron los botones; los bits $00B5 son verdaderos solo durante una iteración del bucle del juego (1 cuadro procesado). El código usa $00B5 para responder a presionar A y B. Cada uno de ellos necesita ser liberado antes de ser usado nuevamente.

$00B5 y $00B6 son espejos de $00F5 y $00F6 . El código en las siguientes secciones usa estas direcciones indistintamente.

Crea Tetrimino


El campo de juego Nintendo Tetris consiste en una matriz con 22 filas y 10 columnas para que las dos primeras filas estén ocultas para el jugador.


Como se muestra en el siguiente código, al crear una figura de Tetrimino, siempre se encuentra en las coordenadas (5, 0) campo de juego.

98BA: LDA #$00
98BC: STA $00A4
98BE: STA $0045
98C0: STA $0041 ; Tetrimino Y = 0
98C2: LDA #$01
98C4: STA $0048
98C6: LDA #$05
98C8: STA $0040 ; Tetrimino X = 5


A continuación se muestra una matriz de 5 × 5 superpuesta sobre este punto.


Ninguna de las matrices de creación tiene cuadrados por encima del punto de partida. Es decir, al crear un tetrimino, sus cuatro cuadrados se vuelven visibles de inmediato para el jugador. Sin embargo, si el jugador gira rápidamente la pieza antes de que tenga tiempo de soltarla, parte de la pieza se ocultará temporalmente en las dos primeras líneas del campo de juego.

Por lo general, pensamos que el juego termina cuando el montón llega a la cima. Pero, de hecho, esto no es del todo cierto. El juego termina cuando ya no es posible crear la siguiente pieza. Es decir, antes de la aparición de la figura, las cuatro celdas del campo de juego correspondientes a las posiciones de los cuadrados del tetrimino creado deberían estar libres. La figura puede estar bloqueada en su lugar de tal manera que parte de sus cuadrados aparezca en líneas numeradas negativamente y el juego no termine; sin embargo, en Nintendo Tetris, las líneas negativas son una abstracción relacionada solo con el tetrimino activo. Después de que la figura se bloquea (se convierte en mentira), solo se escriben en el campo cuadrados en líneas desde cero y más. Conceptualmente, resulta que las líneas numeradas negativamente se borran automáticamente después del bloqueo. Pero en realidad, el juego simplemente no almacena estos datos, cortando las partes superiores de las figuras.

El área visible del campo de juego 20 × 10 se almacena en $0400 línea por línea, cada byte contiene el valor del mosaico de fondo. Las celdas vacías se denotan con el mosaico $EF , un cuadrado negro sólido.

Al crear una forma, se utilizan tres tablas de búsqueda. Si hay una ID de orientación arbitraria, la tabla en $9956 nos da la ID de orientación al crear el tipo correspondiente de tetrimino.

9956: 02 02 02 02 ; Td
995A: 07 07 07 07 ; Jd
995E: 08 08 ; Zh
9960: 0A ; O
9961: 0B 0B ; Sh
9963: 0E 0E 0E 0E ; Ld
9967: 12 12 ; Ih


Es más fácil mostrar esto en la tabla.

TuTrTdTlJlJuJrJdZhZvOShSvLrLdLlLuIvIh
TdTdTdTdJdJdJdJdZhZhOShShLdLdLdLdIhIh

Por ejemplo, todas las orientaciones de J están unidas a Jd .

La tabla en $993B contiene el tipo Tetrimino para la ID de orientación dada.

993B: 00 00 00 00 ; T
993F: 01 01 01 01 ; J
9943: 02 02 ; Z
9945: 03 ; O
9946: 04 04 ; S
9948: 05 05 05 05 ; L
994C: 06 06 ; I


Para mayor claridad, mostraré todo en forma de tabla.

TuTrTdTlJlJuJrJdZhZvOShSvLrLdLlLuIvIh
TTTTJJJJZZOSSLLLLII

Veremos la tercera tabla de búsqueda en la siguiente sección.

Selección de tetrimino


Nintendo Tetris utiliza un registro de desplazamiento de retroalimentación lineal de 16 bits (LFSR) como su generador de números pseudoaleatorios (PRNG) en su configuración de Fibonacci. El valor de 16 bits se almacena como big-endian en las direcciones $0017 - $0018 . Se utiliza un número arbitrario de $8988 como semilla.

80BC: LDX #$89
80BE: STX $0017
80C0: DEX
80C1: STX $0018


Cada número pseudoaleatorio posterior se genera de la siguiente manera: el valor se percibe como un número de 17 bits, y el bit más significativo se obtiene realizando XOR para los bits 1 y 9. Luego, el valor se desplaza hacia la derecha, descartando el bit menos significativo.


Este proceso ocurre en $AB47 .

AB47: LDA $00,X
AB49: AND #$02
AB4B: STA $0000 ; extract bit 1

AB4D: LDA $01,X
AB4F: AND #$02 ; extract bit 9

AB51: EOR $0000
AB53: CLC
AB54: BEQ $AB57
AB56: SEC ; XOR bits 1 and 9 together

AB57: ROR $00,X
AB59: INX
AB5A: DEY ; right shift
AB5B: BNE $AB57 ; shifting in the XORed value

AB5D: RTS ; return


Curiosamente, los parámetros de la subrutina anterior se pueden configurar para que la función de llamada pueda especificar el ancho del registro de desplazamiento y la dirección en la que se puede encontrar en la memoria. Sin embargo, los mismos parámetros se usan en todas partes, por lo que podemos suponer que los desarrolladores tomaron prestado este código en alguna parte.

Para aquellos que quieran modificar aún más el algoritmo, lo escribí en Java.

 int generateNextPseudorandomNumber(int value) { int bit1 = (value >> 1) & 1; int bit9 = (value >> 9) & 1; int leftmostBit = bit1 ^ bit9; return (leftmostBit << 15) | (value >> 1); } 

Y todo este código puede exprimirse en una sola línea.

 int generateNextPseudorandomNumber(int value) { return ((((value >> 9) & 1) ^ ((value >> 1) & 1)) << 15) | (value >> 1); } 

Este PRNG genera continua y determinísticamente 32,767 valores únicos, comenzando cada ciclo desde la semilla original. Este es uno menos de la mitad de los números posibles que pueden caber en el registro, y cualquier valor en este conjunto puede usarse como semilla. Muchos de los valores fuera del conjunto crean una cadena que eventualmente conduce a un número del conjunto. Sin embargo, algunos números iniciales dan como resultado una secuencia infinita de ceros.

Para evaluar aproximadamente el rendimiento de este PRNG, generé una representación gráfica de los valores que crea en base a una oración con RANDOM.ORG .


Al crear la imagen, PRNG se utilizó como generador de números pseudoaleatorios, en lugar de enteros de 16 bits. Cada píxel se colorea según el valor del bit 0. La imagen tiene un tamaño de 128 × 256, es decir, cubre toda la secuencia.

Además de las rayas apenas perceptibles en los lados superior e izquierdo, parece aleatorio. No aparecen patrones obvios.

Después de comenzar, el PRNG cambia constantemente el registro, trabajando al menos una vez por cuadro. Esto no sucede no solo en la pantalla de inicio y en las pantallas de menú, sino también cuando el tetrimino se encuentra entre las operaciones de creación de formas. Es decir, la figura que aparece a continuación depende del número de fotogramas que el jugador toma para colocar la figura. De hecho, el juego se basa en la aleatoriedad de las acciones de la persona que interactúa con él.

Durante la creación de la figura, el código se ejecuta en la dirección $9907 , que selecciona el tipo de la nueva figura.

9907: INC $001A ; spawnCount++;

9909: LDA $0017 ; index = high byte of randomValue;

990B: CLC
990C: ADC $001A ; index += spawnCount;

990E: AND #$07 ; index &= 7;

9910: CMP #$07 ; if (index == 7) {
9912: BEQ $991C ; goto invalidIndex;
; }

9914: TAX
9915: LDA $994E,X ; newSpawnID = spawnTable[index];

9918: CMP $0019 ; if (newSpawnID != spawnID) {
991A: BNE $9938 ; goto useNewSpawnID;
; }

invalidIndex:

991C: LDX #$17
991E: LDY #$02
9920: JSR $AB47 ; randomValue = generateNextPseudorandomNumber(randomValue);

9923: LDA $0017 ; index = high byte of randomValue;

9925: AND #$07 ; index &= 7;

9927: CLC
9928: ADC $0019 ; index += spawnID;

992A: CMP #$07
992C: BCC $9934
992E: SEC
992F: SBC #$07
9931: JMP $992A ; index %= 7;

9934: TAX
9935: LDA $994E,X ; newSpawnID = spawnTable[index];

useNewSpawnID:

9938: STA $0019 ; spawnID = newSpawnID;

993A: RTS ; return;


En la dirección $001A almacena un contador del número de figuras creadas con el encendido. El incremento del contador se realiza mediante la primera línea de la subrutina, y dado que es un contador de un solo byte, después de cada 256 piezas vuelve a cero. Como el contador no se reinicia entre juegos, el historial de juegos anteriores afecta el proceso de selección de figuras. Esta es otra forma en que el juego usa al jugador como fuente de aleatoriedad.

La rutina convierte el byte más significativo del número pseudoaleatorio ( $0017 ) a un tipo tetrimino y lo usa como el índice de la tabla ubicada en $994E para convertir el tipo a la ID de orientación de creación de forma.

994E: 02 ; Td
994F: 07 ; Jd
9950: 08 ; Zh
9951: 0A ; O
9952: 0B ; Sh
9953: 0E ; Ld
9954: 12 ; Ih


En la primera etapa de conversión, el contador de figuras creadas se agrega al byte superior. Luego se aplica una máscara para guardar solo los 3 bits inferiores. Si el resultado no es 7, entonces este es el tipo correcto de tetrimino, y si no es el mismo que la figura seleccionada anteriormente, entonces el número se usa como índice en la tabla para crear figuras. De lo contrario, se genera el siguiente número pseudoaleatorio y la máscara se aplica para obtener los 3 bits inferiores del byte superior, y luego se agrega la ID de orientación de creación de forma anterior. Finalmente, se realiza una operación de módulo para obtener el tipo correcto de tetrimino, que se utiliza como índice en la tabla de creación de formas.

Como el procesador no admite la división con el resto, este operador se emula restando repetidamente 7 hasta que el resultado sea menor que 7. La división con el resto se aplica a la suma del byte superior con la máscara aplicada y al ID de creación de orientación anterior. El valor máximo de esta suma es 25. Es decir, para reducirlo al resto de 4, solo se requieren 3 iteraciones.

Al comienzo de cada juego, el ID de orientación de creación de forma ( $0019 ) se inicializa con un valor de Tu ( $00 ). Este valor podría usarse potencialmente a $9928 durante la creación de la primera forma.

Cuando se usa la ID de orientación anterior para crear una figura, en lugar del tipo anterior, Tetrimino agrega distorsión, porque los valores de la ID de orientación no se distribuyen uniformemente. Esto se muestra en la tabla:

$ 00$02$07$08$0A$0B$0E$12
0 020 0134 40 04 4
13124 45 515 5
24 4235 56 626 6
35 534 46 60 030 0
4 46 64 45 50 014 41
5 50 05 56 6125 52
6 616 60 0236 63
7 720 0134 40 04 4

Cada celda contiene un tipo de tetrimino, calculado al agregar el ID de orientación de la figura (columna) creada a un valor de 3 bits (fila), y luego aplicar el resto de la división por 7 a la suma. Cada fila contiene duplicados, porque $07 y $0E dividen de manera uniforme a las 7, mientras que $0B y $12 tienen un saldo común. Las líneas 0 y 7 son iguales porque están a una distancia de 7.

Hay 56 combinaciones de entrada posibles, y si los tipos de tetrimino resultantes se distribuyen uniformemente, entonces podemos esperar que en la tabla anterior, cada tipo aparezca exactamente 8 veces. Pero como se muestra a continuación, este no es el caso.

TipoFrecuencia
T9 9
J8
Z8
O8
S9 9
L7 7
Yo7 7

T y S aparecen con más frecuencia, y L e I, con menos frecuencia. Pero el código sesgado que usa el ID de orientación no se ejecuta cada vez que se llama a la subrutina.

Suponga que PRNG crea una secuencia de valores estadísticos independientes distribuidos uniformemente. Esto es realmente una suposición justa, dada la forma en que el juego intenta obtener la aleatoriedad correcta de las acciones del jugador. Agregar el número de figuras creadas a la dirección $990C no afectará la distribución, porque el número aumenta de manera uniforme entre llamadas. Usar la máscara de bits a $990E similar a aplicar la división por 8 con el resto, lo que tampoco afecta la distribución. Por lo tanto, la comprobación a $9910 va a invalidIndex en 1/8 de todos los casos. Y la probabilidad de golpear cuando se verifica en la dirección $9918 , donde se compara la cifra recién seleccionada con la cifra anterior, es 7/8, con una probabilidad de coincidencia de 1/7.Esto significa que hay una posibilidad adicional de 7/8 × 1/7 = 1/8estar adentro invalidIndex. En general, hay un 25% de probabilidad de usar un código sesgado y un 75% de probabilidad de usar un código que seleccione Tetrimino de manera uniforme.

En un conjunto de 224 tetriminos creados, la expectativa matemática es de 32 instancias para cada tipo. Pero en realidad el código crea la siguiente distribución:

TipoFrecuencia
T33
J32
Z32
O32
S33
L31
Yo31

Es decir, despejando 90 líneas y alcanzando el nivel 9, el jugador recibirá una T y S extra y una L e I menos de lo que se espera estadísticamente.

Tetrimino se eligen con las siguientes probabilidades:

TipoProbabilidad
T14,73%
J14,29%
Z14,29%
O14,29%
S14,73%
L13,84%
Yo13,84%

Parece que en la declaración de que el "palo largo" nunca aparece cuando es necesario, hay parte de la verdad (al menos para Nintendo Tetris).

Tetrimino Shift


Nintendo Tetris utiliza el cambio automático retardado (DAS). Al hacer clic en "Izquierda" o "Derecha", el tetrimino se mueve instantáneamente una celda horizontalmente. Mientras mantiene presionado uno de estos botones de dirección, el juego cambia automáticamente la figura cada 6 cuadros con un retraso inicial de 16 cuadros.

Este tipo de movimiento horizontal está controlado por el código en la dirección $89AE. Como en el código de rotación, aquí se usa una variable temporal para hacer una copia de seguridad de las coordenadas en caso de que la nueva posición sea incorrecta. Tenga en cuenta que la comprobación le impide mover la pieza mientras el jugador presiona Abajo.

89AE: LDA $0040
89B0: STA $00AE ; originalX = tetriminoX;

89B2: LDA $00B6 ; if (pressing down) {
89B4: AND #$04 ; return;
89B6: BNE $8A09 ; }

89B8: LDA $00B5 ; if (just pressed left/right) {
89BA: AND #$03 ; goto resetAutorepeatX;
89BC: BNE $89D3 ; }

89BE: LDA $00B6 ; if (not pressing left/right) {
89C0: AND #$03 ; return;
89C2: BEQ $8A09 ; }

89C4: INC $0046 ; autorepeatX++;
89C6: LDA $0046 ; if (autorepeatX < 16) {
89C8: CMP #$10 ; return;
89CA: BMI $8A09 ; }

89CC: LDA #$0A
89CE: STA $0046 ; autorepeatX = 10;
89D0: JMP $89D7 ; goto buttonHeldDown;

resetAutorepeatX:

89D3: LDA #$00
89D5: STA $0046 ; autorepeatX = 0;

buttonHeldDown:

89D7: LDA $00B6 ; if (not pressing right) {
89D9: AND #$01 ; goto notPressingRight;
89DB: BEQ $89EC ; }

89DD: INC $0040 ; tetriminoX++;
89DF: JSR $948B ; if (new position not valid) {
89E2: BNE $8A01 ; goto restoreX;
; }

89E4: LDA #$03
89E6: STA $06F1 ; play shift sound effect;
89E9: JMP $8A09 ; return;

notPressingRight:

89EC: LDA $00B6 ; if (not pressing left) {
89EE: AND #$02 ; return;
89F0: BEQ $8A09 ; }

89F2: DEC $0040 ; tetriminoX--;
89F4: JSR $948B ; if (new position not valid) {
89F7: BNE $8A01 ; goto restoreX;
; }

89F9: LDA #$03
89FB: STA $06F1 ; play shift sound effect;
89FE: JMP $8A09 ; return;

restoreX:

8A01: LDA $00AE
8A03: STA $0040 ; tetriminoX = originalX;

8A05: LDA #$10
8A07: STA $0046 ; autorepeatX = 16;

8A09: RTS ; return;


x



Lanzando Tetrimino


La velocidad del descenso automático de Tetrimino es una función del número de nivel. Las velocidades se codifican como el número de cuadros renderizados para el descenso en la tabla ubicada en $898E. Como NES opera a 60.0988 cuadros / s, puede calcular el período entre descensos y la velocidad.

NivelMarcos de descensoPeriodo (s / descenso)Velocidad (celdas / s)
0 048.7991,25
143.7151,40
238.6321,58
333.5491,82
4 428.4662,15
5 523.3832,61
6 618 años.3003,34
7 713.2164.62
88.1337.51
9 96 6.10010.02
10-125 5.08312.02
13-154 4.06715.05
16-183.05020/03
19–282.03330.05
29+1.01760,10

La tabla tiene un total de 30 entradas. Después del nivel 29, el valor de cuadros para el descenso es siempre 1.

Un número entero de cuadros para el descenso no es una forma muy detallada de describir la velocidad. Como se muestra en el gráfico a continuación, la velocidad aumenta exponencialmente con cada nivel. De hecho, el nivel 29 es dos veces más rápido que el nivel 28.


Con 1 cuadro / descenso, el jugador no tiene más de 1/3 de segundo para colocar la figura, después de lo cual comenzará a moverse. A esta velocidad de descenso, DAS evita que la figura llegue a los bordes del campo de juego hasta que se bloquee en su lugar, lo que para la mayoría de las personas significa un final rápido del juego. Sin embargo, algunos jugadores, especialmente Thor Akerlund , lograron derrotar a DAS con la rápida vibración de los botones cruzados ( D-pad). En el código de cambio que se muestra arriba, se puede ver que mientras el botón de dirección horizontal se suelta a través del cuadro, es posible cambiar el tetrimino en los niveles 29 y superiores con media frecuencia. Este es un máximo teórico, pero cualquier vibración del pulgar por encima de 3.75 golpes / s puede vencer el retraso original de 16 cuadros.

Si el descenso automático y controlado por el jugador (presionando "Abajo") coinciden y ocurren en un cuadro, el efecto no suma. Cualquiera o ambos de estos eventos hacen que la forma baje exactamente una celda en este cuadro.

La lógica de control del disparador se encuentra en $8914. La tabla del marco de descenso está debajo de la etiqueta . Como se mencionó anteriormente, en el nivel 29 y superior, la velocidad es constantemente igual a 1 obturador / marco. (dirección ) comienza el descenso cuando llega a ( ). El incremento se realiza en una dirección fuera de este fragmento de código. Durante el descenso automático o controlado, se restablece a 0. La variable ( ) se inicializa con el valor (en la dirección

8914: LDA $004E ; if (autorepeatY > 0) {
8916: BPL $8922 ; goto autorepeating;
; } else if (autorepeatY == 0) {
; goto playing;
; }

; game just started
; initial Tetrimino hanging at spawn point

8918: LDA $00B5 ; if (not just pressed down) {
891A: AND #$04 ; goto incrementAutorepeatY;
891C: BEQ $8989 ; }

; player just pressed down ending startup delay

891E: LDA #$00
8920: STA $004E ; autorepeatY = 0;
8922: BNE $8939

playing:

8924: LDA $00B6 ; if (left or right pressed) {
8926: AND #$03 ; goto lookupDropSpeed;
8928: BNE $8973 ; }

; left/right not pressed

892A: LDA $00B5
892C: AND #$0F ; if (not just pressed only down) {
892E: CMP #$04 ; goto lookupDropSpeed;
8930: BNE $8973 ; }

; player exclusively just presssed down

8932: LDA #$01
8934: STA $004E ; autorepeatY = 1;

8936: JMP $8973 ; goto lookupDropSpeed;

autorepeating:

8939: LDA $00B6
893B: AND #$0F ; if (down pressed and not left/right) {
893D: CMP #$04 ; goto downPressed;
893F: BEQ $894A ; }

; down released

8941: LDA #$00
8943: STA $004E ; autorepeatY = 0
8945: STA $004F ; holdDownPoints = 0
8947: JMP $8973 ; goto lookupDropSpeed;

downPressed:

894A: INC $004E ; autorepeatY++;
894C: LDA $004E
894E: CMP #$03 ; if (autorepeatY < 3) {
8950: BCC $8973 ; goto lookupDropSpeed;
; }

8952: LDA #$01
8954: STA $004E ; autorepeatY = 1;

8956: INC $004F ; holdDownPoints++;

drop:

8958: LDA #$00
895A: STA $0045 ; fallTimer = 0;

895C: LDA $0041
895E: STA $00AE ; originalY = tetriminoY;

8960: INC $0041 ; tetriminoY++;
8962: JSR $948B ; if (new position valid) {
8965: BEQ $8972 ; return;
; }

; the piece is locked

8967: LDA $00AE
8969: STA $0041 ; tetriminoY = originalY;

896B: LDA #$02
896D: STA $0048 ; playState = UPDATE_PLAYFIELD;
896F: JSR $9CAF ; updatePlayfield();

8972: RTS ; return;

lookupDropSpeed:

8973: LDA #$01 ; tempSpeed = 1;

8975: LDX $0044 ; if (level >= 29) {
8977: CPX #$1D ; goto noTableLookup;
8979: BCS $897E ; }

897B: LDA $898E,X ; tempSpeed = framesPerDropTable[level];

noTableLookup:

897E: STA $00AF ; dropSpeed = tempSpeed;

8980: LDA $0045 ; if (fallTimer >= dropSpeed) {
8982: CMP $00AF ; goto drop;
8984: BPL $8958 ; }

8986: JMP $8972 ; return;

incrementAutorepeatY:

8989: INC $004E ; autorepeatY++;
898B: JMP $8972 ; return;


lookupDropSpeed

fallTimer$0045dropSpeed$00AFfallTimer$8892

autorepeatY$004E$0A$8739), que se interpreta como −96. Una condición al principio causa un retraso inicial. El primer Tetrimino permanece suspendido en el aire en el punto de creación hasta autorepeatYque aumenta a 0, lo que lleva 1.6 segundos. Sin embargo, cuando presiona Abajo en esta fase, se le autorepeatYasigna instantáneamente 0. Es interesante que pueda mover y rotar la figura en esta fase del retraso inicial sin cancelarla.

El incremento autorepeatYse realiza mientras se mantiene presionado. Cuando llega a 3, se produce un descenso controlado por el hombre (descenso "suave") y se le autorepeatYasigna 1. Por lo tanto, el descenso suave inicial requiere 3 cuadros, pero luego se repite en cada cuadro.

Además, autorepeatYaumenta de 0 a 1 solo cuando el juego reconoce que el jugador acaba de hacer clic en Abajo (en$00B5), pero no reconoce mantener presionado. Esto es importante porque se autorepeatYrestablece a 0 cuando se crea un tetrimino (en la dirección $98E8), lo que crea una característica importante: si el jugador mismo baja la figura y se bloquea, y continúa presionando "Abajo" al crear la siguiente figura, que a menudo ocurre en niveles altos, entonces Esto no conducirá a un suave descenso de la nueva figura. Para que esto suceda, el jugador debe soltar "Abajo" y luego presionar el botón nuevamente.

El descenso potencialmente suave puede aumentar los puntos. holdDownPoints( $004F) aumenta con cada descenso, pero cuando se suelta, "Abajo" se restablece a 0. Por lo tanto, para ganar puntos, es necesario bajar el tetrimino en la cerradura con un descenso suave. El descenso suave a corto plazo, que puede ocurrir en el camino de la figura, no afecta los puntos. La cuenta se actualiza en$9BFE, pero se holdDownPointsrestablece a 0 poco después, en la dirección $9C2F.

La verificación, que evita que el jugador realice un descenso suave con un desplazamiento horizontal de la figura, complica el conjunto de puntos. Significa que el último movimiento antes de bloquear la pieza en su lugar debe ser "Abajo".

Cuando ocurre el descenso, tetriminoY( $0041) se copia a originalY( $00AE). Si la nueva posición creada por el incremento tetriminoYresulta ser incorrecta (es decir, la figura empuja a través del piso del campo de juego o se superpone en cuadrados ya existentes), entonces el tetrimino permanece en la posición anterior. En este caso, se restauratetriminoYy la figura se considera bloqueada. Esto significa que el retraso antes del bloqueo (el número máximo de cuadros que espera un tetrimino, que se mantiene en el aire antes del bloqueo) es igual al retraso en el descenso.

El descenso rígido (caída instantánea) no es compatible con Nintendo Tetris.

Deslizamiento y desplazamiento


El manual de Nintendo Tetris tiene un ejemplo ilustrativo de deslizamiento:


El deslizamiento consiste en desplazarse a lo largo de la superficie de otras figuras o en el piso del campo de juego. Por lo general, se usa para empujar una figura debajo de un cuadrado saliente. El deslizamiento se puede realizar hasta que el temporizador de caída alcance la velocidad de descenso, después de lo cual la figura se bloqueará en su lugar. Un ejemplo animado se muestra a continuación.


Por otro lado, el desplazamiento le permite empujar figuras en espacios que son inalcanzables de otra manera (ver más abajo).


Al igual que el deslizamiento, el desplazamiento no es posible sin un retraso de bloqueo. Pero más allá de eso, el desplazamiento explota la forma en que el juego manipula las formas. Antes de mover o rotar la figura, el juego verifica que después de cambiar la posición todos los cuadrados tetriminos estarán en celdas vacías dentro de los límites del campo de juego. Tal verificación, como se muestra a continuación, no impide la rotación a través de bloques llenos cercanos. Como se indicó en la sección Descripción de Tetrimino, cada fila de la tabla de orientación contiene 12 bytes; por lo tanto, el índice en esta tabla se calcula multiplicando el ID de orientación del tetrimino activo por 12. Como se muestra a continuación, todas las multiplicaciones en la rutina se realizan utilizando cambios y sumas.

948B: LDA $0041
948D: ASL
948E: STA $00A8
9490: ASL
9491: ASL
9492: CLC
9493: ADC $00A8
9495: ADC $0040
9497: STA $00A8

9499: LDA $0042
949B: ASL
949C: ASL
949D: STA $00A9
949F: ASL
94A0: CLC
94A1: ADC $00A9
94A3: TAX ; index = 12 * orientationID;
94A4: LDY #$00

94A6: LDA #$04
94A8: STA $00AA ; for(i = 0; i < 4; i++) {

94AA: LDA $8A9C,X ; squareY = orientationTable[index];
94AD: CLC
94AE: ADC $0041 ; cellY = squareY + tetriminoY;
94B0: ADC #$02 ; if (cellY < -2 || cellY >= 20) {
94B2: CMP #$16 ; return false;
94B4: BCS $94E9 ; }

94B6: LDA $8A9C,X
94B9: ASL
94BA: STA $00AB
94BC: ASL
94BD: ASL
94BE: CLC
94BF: ADC $00AB
94C1: CLC
94C2: ADC $00A8
94C4: STA $00AD

94C6: INX
94C7: INX ; index += 2;

94C8: LDA $8A9C,X ; squareX = orientationTable[index];
94CB: CLC
94CC: ADC $00AD
94CE: TAY ; cellX = squareX + tetriminoX;
94CF: LDA ($B8),Y ; if (playfield[10 * cellY + cellX] != EMPTY_TILE) {
94D1: CMP #$EF ; return false;
94D3: BCC $94E9 ; }

94D5: LDA $8A9C,X
94D8: CLC
94D9: ADC $0040 ; if (cellX < 0 || cellX >= 10) {
94DB: CMP #$0A ; return false;
94DD: BCS $94E9 ; }

94DF: INX ; index++;
94E0: DEC $00AA
94E2: BNE $94AA ; }

94E4: LDA #$00
94E6: STA $00A8
94E8: RTS ; return true;

94E9: LDA #$FF
94EB: STA $00A8
94ED: RTS




index = (orientationID << 3) + (orientationID << 2); // index = 8 * orientationID + 4 * orientationID;

(cellY << 3) + (cellY << 1) // 8 * cellY + 2 * cellY


Cada iteración del ciclo desplaza la posición del tetrimino por las coordenadas relativas de uno de los cuadrados de la tabla de orientación para obtener la ubicación de la celda correspondiente en el campo de juego. Luego verifica que las coordenadas de la celda estén dentro de los límites del campo de juego y que la celda en sí esté vacía.

Los comentarios describen más claramente cómo verificar el espacio entre líneas. Además de las celdas en las líneas visibles, el código considera las dos líneas ocultas sobre el campo de juego como las posiciones legales de los cuadrados sin usar una condición compuesta. Esto funciona porque en el código adicional los números negativos representados por las variables de un solo byte son equivalentes a valores mayores que 127. En este caso, el valor mínimo es −2, que se almacena como

94AA: LDA $8A9C,X ; squareY = orientationTable[index];
94AD: CLC
94AE: ADC $0041 ; cellY = squareY + tetriminoY;
94B0: ADC #$02 ; if (cellY + 2 >= 22) {
94B2: CMP #$16 ; return false;
94B4: BCS $94E9 ; }


cellY$FE(254 en notación decimal).

El índice del campo de juego es la suma cellYmultiplicada por 10 y cellX. Sin embargo, cuando cellY−1 ( $FF= 255) o −2 ( $FE= 254), el producto produce −10 ( $F6= 246) y −20 ( $EC= 236). Al estar en el intervalo, no cellXpuede ser más de 9, lo que da un índice máximo de 246 + 9 = 255, y esto está mucho más allá del final del campo de juego. Sin embargo, el juego se inicializa $0400, $04FFcon un valor $EF(de un mosaico vacío), creando otros 56 bytes adicionales de espacio vacío.

Extraño ese chequeo de intervalocellXrealizado después de examinar la celda del campo de juego. Pero funciona correctamente en cualquier orden. Además, verificar el intervalo evita la condición compuesta, como se indica en el comentario a continuación. Los ejemplos de desplazamiento que se muestran a continuación son posibles debido a la forma en que este código verifica las posiciones.

94D5: LDA $8A9C,X
94D8: CLC
94D9: ADC $0040 ; if (cellX >= 10) {
94DB: CMP #$0A ; return false;
94DD: BCS $94E9 ; }






Como se muestra a continuación, incluso puede realizar el deslizamiento con desplazamiento.


AI aprovecha al máximo las capacidades de movimiento de Nintendo Tetris, incluidos el desplazamiento y el desplazamiento.

Nivel 30 y superior


Después de alcanzar el nivel 30, parece que el nivel se restablece a cero.


Pero el nivel 31 muestra que algo más está sucediendo:


Los valores de nivel mostrados se encuentran en la tabla en la dirección $96B8.

96B8: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

Como se muestra a continuación, la tabla de patrones se ordena de manera que las baldosas con $00el $0Fson símbolos de glifos 0en F. Esto significa que cuando se muestra un dígito decimal o hexadecimal, el valor del dígito en sí mismo se usa como índice de la tabla de patrones. En nuestro caso, los valores de nivel se almacenan como decimal codificado en binario (BCD); cada mordisco de cada byte en la secuencia es un valor de mosaico.


Desafortunadamente, parece que los diseñadores del juego asumieron que nadie pasaría el nivel 29 y, por lo tanto, decidieron insertar solo 30 entradas en la tabla. Los valores extraños mostrados son bytes diferentes después de la tabla. Solo $0044se usa un byte (en la dirección ) para indicar el número de nivel , razón por la cual el juego gira lentamente alrededor de los 256 valores que se muestran a continuación.

000123456789ABCDEF
000010203040506070809101112131415
11617181920212223242526272829000A
2141E28323C46505A646E78828C96A0AA
3B4BEC620E62006212621462166218621
4A621C621E62106222622462266228622
5A622C622E6220623262385A829F04A4A
64A4A8D0720A5A8290F8D072060A649E0
7151053BDD696A88A0AAAE8BDEA968D06
820CAA5BEC901F01EA5B9C905F00CBDEA
99638E9028D06204C6797BDEA9618690C
A8D06204C6797BDEA961869068D0620A2
B0AB1B88D0720C8CAD0F7E649A549C914
C3004A920854960A5B12903D078A90085
DAAA6AAB54AF05C0AA8B9EA9685A8A5BE
EC901D00AA5A818690685A84CBD97A5B9
FC904D00AA5A838E90285A84CBD97A5A8

Los primeros 20 valores ordinales son en realidad otra tabla que almacena las compensaciones en el campo de juego para cada una de las 20 filas. Dado que el campo de juego comienza con y cada línea contiene 10 celdas, la dirección de una celda arbitraria es: Dado que el procesador no admite la multiplicación directamente, esta tabla de búsqueda proporciona una forma extremadamente rápida de obtener el producto. La tabla correspondiente ocupa los siguientes 40 bytes. Contiene 20 direcciones en formato little endian para la tabla de nombres 0 (un área de memoria VRAM que contiene los valores de los mosaicos de fondo). Son punteros a las líneas de desplazamiento del campo de juego . Los bytes restantes de los que se componen los valores de nivel mostrados son instrucciones.

96D6: 00 ; 0
96D7: 0A ; 10
96D8: 14 ; 20
96D9: 1E ; 30
96DA: 28 ; 40
96DB: 32 ; 50
96DC: 3C ; 60
96DD: 46 ; 70
96DE: 50 ; 80
96DF: 5A ; 90
96E0: 64 ; 100
96E1: 6E ; 110
96E2: 78 ; 120
96E3: 82 ; 130
96E4: 8C ; 140
96E5: 96 ; 150
96E6: A0 ; 160
96E7: AA ; 170
96E8: B4 ; 180
96E9: BE ; 190


$0400

$0400 + 10 * y + x



$0400 + [$96D6 + y] + x

$06



Filas y estadísticas


El número de filas completadas y estadísticas de tetrimino ocupan 2 bytes cada una en las siguientes direcciones.

DireccionesCantidad
0050 - 0051Rangos
03F0 - 03F1T
03F2 - 03F3J
03F4 - 03F5Z
03F6 - 03F7O
03F8 - 03F9S
03FA - 03FBL
03FC - 03FDYo

De hecho, estos valores se almacenan como BCD pequeños endian empaquetados de 16 bits. Por ejemplo, el número de filas se muestra a continuación, que es 123. Los bytes se cuentan de derecha a izquierda para que los dígitos decimales estén en orden.


Sin embargo, los diseñadores de juegos asumieron que ninguno de los valores sería mayor que 999. Por lo tanto, la lógica de visualización procesa correctamente el primer byte como un BCD empaquetado, donde cada mordisco se usa como un valor de mosaico. Pero todo el segundo byte se usa realmente como el dígito decimal superior. Cuando los dígitos inferiores van de 99a 00, se produce el incremento normal del segundo byte. Como resultado, el segundo byte recorre los 256 mosaicos. Un ejemplo de esto se muestra a continuación.


Después de borrar la línea, se ejecuta el siguiente código para incrementar el número de filas. Se realizan verificaciones para los dígitos medio e inferior para que permanezcan entre 0 y 9. Pero el dígito superior se puede aumentar infinitamente. Si después del incremento del número de filas, el dígito inferior es 0, entonces esto significa que el jugador acaba de completar un conjunto de 10 líneas y necesita aumentar el número de nivel. Como puede ver en el siguiente código, se realiza una verificación adicional antes del incremento de nivel. La segunda verificación está relacionada con el nivel de entrada seleccionado. Para ir a un cierto nivel , independientemente del nivel inicial, el jugador debe borrar

9BA8: INC $0050 ; increment middle-lowest digit pair
9BAA: LDA $0050
9BAC: AND #$0F
9BAE: CMP #$0A ; if (lowest digit > 9) {
9BB0: BMI $9BC7
9BB2: LDA $0050
9BB4: CLC
9BB5: ADC #$06 ; set lowest digit to 0, increment middle digit
9BB7: STA $0050
9BB9: AND #$F0
9BBB: CMP #$A0 ; if (middle digit > 9) {
9BBD: BCC $9BC7
9BBF: LDA $0050
9BC1: AND #$0F
9BC3: STA $0050 ; set middle digit to 0
9BC5: INC $0051 ; increment highest digit
; }
; }






9BC7: LDA $0050
9BC9: AND #$0F
9BCB: BNE $9BFB ; if (lowest digit == 0) {
9BCD: JMP $9BD0

9BD0: LDA $0051
9BD2: STA $00A9
9BD4: LDA $0050
9BD6: STA $00A8 ; copy digits from $0050-$0051 to $00A8-$00A9

9BD8: LSR $00A9
9BDA: ROR $00A8
9BDC: LSR $00A9
9BDE: ROR $00A8
9BE0: LSR $00A9
9BE2: ROR $00A8 ; treat $00A8-$00A9 as a 16-bit packed BCD value
9BE4: LSR $00A9 ; and right-shift it 4 times
9BE6: ROR $00A8 ; this leaves the highest and middle digits in $00A8

9BE8: LDA $0044
9BEA: CMP $00A8 ; if (level < [$00A8]) {
9BEC: BPL $9BFB

9BEE: INC $0044 ; increment level
; }
; }


X10Xlíneas Por ejemplo, si un jugador comienza en el nivel 5, permanecerá en él hasta que haya despejado 60 líneas, después de lo cual irá al nivel 6. Después de eso, cada 10 líneas adicionales conducirán a un aumento en el número de nivel.

Para realizar esta verificación, el valor de las filas rellenas se copia de $0050- $0051a $00A8- $00A9. Luego, la copia se desplaza a la derecha 4 veces, lo que para un BCD empaquetado es similar a dividir por 10. El dígito decimal más pequeño se descarta, y los dígitos más altos y medios se desplazan por una posición, lo que da como resultado mordiscos $00A8.


Sin embargo, en la dirección, $9BEAel número de nivel se compara directamente con el valor empaquetado del BCD $00A8. No hay búsqueda en la tabla para convertir el valor BCD a decimal, y este es un claro error. Por ejemplo, en la imagen de arriba, el número de nivel debe compararse con $12(18 en decimal) y no con 12. Por lo tanto, si un jugador decide comenzar en el nivel 17, el nivel en realidad irá a 120 filas, porque 18 es más que 17.

La tabla muestra el número esperado de filas requeridas para la transición en cada nivel inicial. Se compara con lo que realmente sucede debido a un error.

0 01234 45 56 67 789 9101112131415161718 años19
102030405060 60708090100110120130140150160170180190200
102030405060 60708090100100100100100100100110120130140

La cantidad esperada es la misma que la verdadera para los niveles iniciales 0–9. De hecho, la coincidencia para el nivel de entrada 9 es aleatoria; 10-15 también va al siguiente nivel con 100 filas, porque $10- esto es 16 en forma decimal. La mayor diferencia entre lo esperado y lo real es de 60 filas.

Sospecho que el error se debe a cambios de diseño en las últimas etapas de desarrollo. Mire la pantalla del menú, permitiendo al jugador elegir un nivel de entrada.


No hay una explicación sobre cómo comenzar desde los niveles superiores a 9. Pero en el folleto de Nintendo Tetris, se revela este secreto:


Parece que esta característica oculta fue inventada en el último momento. Tal vez se agregó muy cerca de la fecha de lanzamiento, lo que no permitió probarlo por completo.

De hecho, la comprobación de la serie inicial contiene un segundo error relacionado con la salida de valores para el intervalo. A continuación hay comentarios en el código que explican mejor lo que sucede en un nivel bajo. La comparación se realiza restando y verificando el signo del resultado. Pero un número con signo de un solo byte se limita a −128 a 127. Si la diferencia es menor que −128, el número se transfiere y el resultado se convierte en un número positivo. Este principio se explica en los comentarios sobre el código. Al verificar que la diferencia está en este intervalo, se debe tener en cuenta que el número de nivel, cuando se incrementa a valores superiores a 255, realiza la transferencia a 0, y

9BE8: LDA $0044
9BEA: CMP $00A8 ; if (level - [$00A8] < 0) {
9BEC: BPL $9BFB

9BEE: INC $0044 ; increment level
; }




9BE8: LDA $0044 ; difference = level - [$00A8];
9BEA: CMP $00A8 ; if (difference < 0 && difference >= -128) {
9BEC: BPL $9BFB

9BEE: INC $0044 ; increment level
; }


$00A8potencialmente puede contener cualquier valor, porque se toma su mordisco superior $0051, cuyo incremento puede ocurrir infinitamente.

Estos efectos se superponen, creando períodos en los que el número de nivel permanece sin cambios por error. Los períodos ocurren a intervalos regulares de 2,900 filas, comenzando en 2,190 filas, y duran 800 filas. Por ejemplo, de 2190 ( L90) a 2990 ( T90), el nivel permanece igual a $DB( 96), como se muestra a continuación.


El siguiente período pasa de 5090 a 5890, el nivel es constantemente igual a $AD( 06). Además, durante estos períodos, la paleta de colores tampoco cambia.

Colorear Tetrimino


En cada nivel, a los azulejos tetrimino se les asignan 4 colores únicos. Los colores se toman de la tabla ubicada en $984C. Sus registros se reutilizan cada 10 niveles. De izquierda a derecha: las columnas de la tabla correspondientes a las áreas negra, blanca, azul y roja de la imagen a continuación.

984C: 0F 30 21 12 ; level 0
9850: 0F 30 29 1A ; level 1
9854: 0F 30 24 14 ; level 2
9858: 0F 30 2A 12 ; level 3
985C: 0F 30 2B 15 ; level 4
9860: 0F 30 22 2B ; level 5
9864: 0F 30 00 16 ; level 6
9868: 0F 30 05 13 ; level 7
986C: 0F 30 16 12 ; level 8
9870: 0F 30 27 16 ; level 9





Los valores corresponden a la paleta de colores NES.


Los primeros 2 colores de cada entrada son siempre en blanco y negro. Sin embargo, el primer color se ignora realmente; independientemente del valor, se considera un color transparente a través del cual se asoma un fondo negro sólido.

El acceso a la tabla de colores se realiza en la rutina en $9808. El índice de la tabla de colores se basa en el número de nivel dividido por un resto de 10. El ciclo copia la entrada a las tablas de paleta en VRAM. La división con el resto se emula mediante una resta constante de 10 hasta que el resultado sea menor que 10. El comienzo de la subrutina con comentarios se muestra a continuación.

9808: LDA $0064
980A: CMP #$0A
980C: BMI $9814
980E: SEC
980F: SBC #$0A
9811: JMP $980A ; index = levelNumber % 10;

9814: ASL
9815: ASL
9816: TAX ; index *= 4;

9817: LDA #$00
9819: STA $00A8 ; for(i = 0; i < 32; i += 16) {

981B: LDA #$3F
981D: STA $2006
9820: LDA #$08
9822: CLC
9823: ADC $00A8
9825: STA $2006 ; palette = $3F00 + i + 8;

9828: LDA $984C,X
982B: STA $2007 ; palette[0] = colorTable[index + 0];

982E: LDA $984D,X
9831: STA $2007 ; palette[1] = colorTable[index + 1];

9834: LDA $984E,X
9837: STA $2007 ; palette[2] = colorTable[index + 2];

983A: LDA $984F,X
983D: STA $2007 ; palette[3] = colorTable[index + 3];

9840: LDA $00A8
9842: CLC
9843: ADC #$10
9845: STA $00A8
9847: CMP #$20
9849: BNE $981B ; }

984B: RTS ; return;






9808: LDA $0064 ; index = levelNumber;
980A: CMP #$0A ; while(index >= 10) {
980C: BMI $9814
980E: SEC
980F: SBC #$0A ; index -= 10;
9811: JMP $980A ; }


Sin embargo, como se indicó en la sección anterior, la resta y la ramificación basadas en el signo de diferencia se usan en comparación. Un número con signo de un solo byte está limitado a −128 a 127. Los comentarios actualizados a continuación reflejan este principio. Los comentarios a continuación se simplifican aún más. Esta redacción revela un error en el código. La operación de división restante se omite por completo para niveles de 138 y superiores. En cambio, el índice se asigna directamente al número de nivel, que proporciona acceso a bytes mucho más allá del final de la tabla de colores. Como se muestra a continuación, esto incluso puede conducir a un tetrimino casi invisible.

9808: LDA $0064 ; index = levelNumber;
; difference = index - 10;
980A: CMP #$0A ; while(difference >= 0 && difference <= 127) {
980C: BMI $9814
980E: SEC ; index -= 10;
980F: SBC #$0A ; difference = index - 10;
9811: JMP $980A ; }




9808: LDA $0064 ; index = levelNumber;
980A: CMP #$0A ; while(index >= 10 && index <= 137) {
980C: BMI $9814
980E: SEC
980F: SBC #$0A ; index -= 10;
9811: JMP $980A ; }





A continuación se muestran los colores de los 256 niveles. Los mosaicos se organizan en 10 columnas para enfatizar el uso cíclico de la tabla de colores, violada en el nivel 138. Las filas y columnas en los encabezados se indican en decimal.


Después de 255, el número de nivel vuelve a 0.

Además, como se mencionó en la sección anterior, algunos niveles no cambian hasta que se eliminan 800 filas. Durante estos largos niveles, los colores permanecen sin cambios.

Modo de juego


El modo de juego almacenado en la dirección $00C0determina cuál de las diversas pantallas y menús se muestran actualmente al usuario.

ValorDescripción
00Pantalla de información legal
01Pantalla de bienvenida
02Menú de tipo de juego
03Menú de niveles y alturas.
04Juego / puntuación más alta / final / pausa
05Demo

Como se muestra arriba, el juego tiene una rutina ingeniosamente escrita que actúa como una declaración de cambio usando la pequeña tabla de navegación endian ubicada inmediatamente después de la llamada. La lista anterior muestra las direcciones de todos los modos de juego. Tenga en cuenta que los modos "Juego" y "Demostración" usan el mismo código. Esta rutina nunca regresa. En cambio, el código usa la dirección de retorno; generalmente apunta a la instrucción que sigue inmediatamente a la llamada al salto a la subrutina (menos 1 byte), pero en este caso apunta a la tabla de salto. La dirección de retorno se saca de la pila y se almacena en - . Después de guardar la dirección de la tabla de salto, el código usa el valor en el registro A como índice y realiza la transición correspondiente.

8161: LDA $00C0
8163: JSR $AC82 ; switch(gameMode) {
8166: 00 82 ; case 0: goto 8200; //
8168: 4F 82 ; case 1: goto 824F; //
816A: D1 82 ; case 2: goto 82D1; //
816C: D7 83 ; case 3: goto 83D7; //
816E: 5D 81 ; case 4: goto 815D; // / / /
8170: 5D 81 ; case 5: goto 815D; //
; }




$0000$0001

AC82: ASL
AC83: TAY
AC84: INY

AC85: PLA
AC86: STA $0000
AC88: PLA ; pop return address off of stack
AC89: STA $0001 ; and store it at $0000-$0001

AC8B: LDA ($00),Y
AC8D: TAX
AC8E: INY
AC8F: LDA ($00),Y
AC91: STA $0001
AC93: STX $0000
AC95: JMP ($0000) ; goto Ath 16-bit address
; in table at [$0000-$0001]


El código puede usar esta rutina de cambio siempre que los índices estén cerca de 0 y no haya espacios o pocos entre los posibles casos.

Pantalla de información legal


El juego comienza con una pantalla que muestra un aviso legal.


En la parte inferior de la pantalla, Aleksey Pazhitnov es mencionado como el inventor, diseñador y programador del primer Tetris. En 1984, trabajando como desarrollador de computadoras en el Dorodnitsyn Computing Center (un instituto de investigación líder de la Academia de Ciencias de Rusia en Moscú), desarrolló un prototipo del juego en Electronics-60 (clon soviético DEC LSI-11 ). Se desarrolló un prototipo para un modo de texto monocromo verde en el que los cuadrados se indican mediante pares de corchetes []. Con la ayuda del niño de 16 años Vadim Gerasimov y el ingeniero informático Dmitry Pavlovsky unos días después de la invención del juego, el prototipo fue trasladado a una PC IBM con MS DOS y Turbo Pascal. En el transcurso de dos años, perfeccionaron el juego juntos, agregando características como colores de tetrimino, estadísticas y, lo que es más importante, un código de tiempo y gráficos que permitió que el juego funcionara en una variedad de modelos de PC y clones.

Desafortunadamente, debido a las peculiaridades de la Unión Soviética en ese momento, sus intentos de monetizar el juego no tuvieron éxito y al final decidieron compartir la versión para PC con sus amigos de forma gratuita. A partir de ese momento, "Tetris" comenzó a extenderse viralmente en todo el país y más allá, copiado de un disco a otro. Pero dado que el juego fue desarrollado por empleados de una agencia gubernamental, el estado lo poseía, y en 1987 la organización responsable del comercio internacional de tecnologías electrónicas se hizo cargo de la licencia del juego ( Electronorgtekhnika (ELORG)) La abreviatura V / O en la pantalla de información legal puede ser abreviada para Version Originale.

La compañía británica de software Andromeda intentó obtener los derechos de Tetris y, antes de completar la transacción, otorgó la licencia del juego a otros proveedores, por ejemplo, el editor británico de juegos de computadora Mirrorsoft . Mirrorsoft, a su vez, lo sublicencia a Tengen , una subsidiaria de Atari Games. Tengen otorgó a Bullet-Proof Software los derechos para desarrollar un juego para computadoras y consolas en Japón, lo que resultó en Tetris para Nintendo Famicom . A continuación se muestra su pantalla de información legal.


Curiosamente, en esta versión, el escolar Vadim Gerasimov es llamado el diseñador y programador original.

Intentando asegurar la versión portátil de la próxima consola Game Boy, Nintendo usó el software Bullet-Proof para concluir un acuerdo exitoso directamente con ELORG. En el proceso de concluir el acuerdo, ELORG revisó su contrato con Andromeda, agregando que Andromeda solo obtuvo derechos para juegos para computadoras y máquinas recreativas. Debido a esto, Bullet-Proof Software tuvo que pagar regalías ELORG por todos los cartuchos vendidos a Famicom, porque los derechos que recibió de Tengen resultaron ser falsos. Pero a través de la reconciliación con ELORG, Bullet-Proof Software finalmente logró obtener los derechos mundiales de juegos de consola para Nintendo.

Bullet-Proof Software otorgó la licencia de los derechos de juegos portátiles de Nintendo y juntos desarrollaron Game Boy Tetris, que se refleja en la pantalla de información legal a continuación.


Con los derechos de juego de la consola global, Nintendo ha desarrollado la versión Tetris para NES que estamos explorando en este artículo. Luego, Bullet-Proof Software otorgó la licencia de los derechos de Nintendo, lo que le permitió continuar vendiendo cartuchos para Famicom en Japón.

Esto fue seguido por una compleja batalla legal. Tanto Nintendo como Tengen exigieron que la parte contraria dejara de producir y vender su versión del juego. Como resultado, Nintendo ganó y cientos de miles de cartuchos Tengen Tetris fueron destruidos. El veredicto judicial también prohibió a otras compañías como Mirrorsoft crear versiones de consola.

Pajitnov nunca recibió ninguna deducción de ELORG o del estado soviético. Sin embargo, en 1991 se mudó a los EE. UU. Y en 1996 con el apoyo del propietario del software Bullet-ProofHenka Rogers cofundó The Tetris Company , que le permitió beneficiarse de versiones para dispositivos móviles y consolas modernas.

Es interesante mirar la pantalla de información legal como una ventana que da una idea del origen modesto del juego y las batallas subsiguientes por los derechos de propiedad intelectual, porque para la mayoría de los jugadores esta pantalla es solo un obstáculo molesto, cuya desaparición parece tener que esperar para siempre. El retraso se establece en dos contadores, contando secuencialmente de 255 a 0. La primera fase no se puede omitir, y la segunda se omite presionando el botón Iniciar. Por lo tanto, la pantalla de información legal se muestra al menos 4,25 segundos y no más de 8,5 segundos. Sin embargo, creo que la mayoría de los jugadores se rinden y dejan de presionar Start durante el primer intervalo, y debido a esto están esperando su finalización completa.

El momento de las fases, así como el resto del juego, se rige por un controlador de interrupción desenmascarado llamado al comienzo de cada intervalo de supresión vertical, un corto período de tiempo entre la representación de cuadros de televisión. Es decir, cada 16.6393 milisegundos, el siguiente código interrumpe la ejecución normal del programa. El controlador comienza pasando los valores de los registros principales a la pila y recuperándolos después de la finalización para no interferir con la tarea interrumpida. La llamada actualiza VRAM, convirtiendo la descripción del modelo de memoria a lo que se muestra en la pantalla. Además, el controlador reduce el valor del contador de la pantalla de información legal si es mayor que cero. Desafío

8005: PHA
8006: TXA
8007: PHA
8008: TYA
8009: PHA ; save A, X, Y

800A: LDA #$00
800C: STA $00B3
800E: JSR $804B ; render();

8011: DEC $00C3 ; legalScreenCounter1--;

8013: LDA $00C3
8015: CMP #$FF ; if (legalScreenCounter1 < 0) {
8017: BNE $801B ; legalScreenCounter1 = 0;
8019: INC $00C3 ; }

801B: JSR $AB5E ; initializeOAM();

801E: LDA $00B1
8020: CLC
8021: ADC #$01
8023: STA $00B1
8025: LDA #$00
8027: ADC $00B2
8029: STA $00B2 ; frameCounter++;

802B: LDX #$17
802D: LDY #$02
802F: JSR $AB47 ; randomValue = generateNextPseudorandomNumber(randomValue);

8032: LDA #$00
8034: STA $00FD
8036: STA $2005 ; scrollX = 0;
8039: STA $00FC
803B: STA $2005 ; scrollY = 0;

803E: LDA #$01
8040: STA $0033 ; verticalBlankingInterval = true;

8042: JSR $9D51 ; pollControllerButtons();

8045: PLA
8046: TAY
8047: PLA
8048: TAX
8049: PLA ; restore A, X, Y

804A: RTI ; resume interrupted task


render()initializeOAM()realiza el paso requerido por el equipo de generación de cuadros. El controlador continúa trabajando incrementando el contador de trama, el pequeño valor endian de 16 bits almacenado en la dirección $00B1, $00B2que utiliza en diferentes lugares para controlar el tiempo. Después de eso, se genera el siguiente número pseudoaleatorio; como se mencionó anteriormente, esto sucede independientemente del modo al menos una vez por cuadro. El $8040indicador de intervalo de supresión vertical se establece en la dirección , lo que significa que el controlador acaba de ejecutarse. Finalmente, se sondean los botones del controlador; El comportamiento de esta rutina se describe a continuación en la sección Demo.

La bandera es verticalBlankingIntervalutilizada por la rutina discutida anteriormente. Continúa hasta que comienza la ejecución del controlador de interrupciones.

AA2F: JSR $E000 ; updateAudio();

AA32: LDA #$00
AA34: STA $0033 ; verticalBlankingInterval = false;

AA36: NOP

AA37: LDA $0033
AA39: BEQ $AA37 ; while(!verticalBlankingInterval) { }

AA3B: LDA #$FF
AA3D: LDX #$02
AA3F: LDY #$02
AA41: JSR $AC6A ; fill memory page 2 with all $FF's

AA44: RTS ; return;


Esta rutina de bloqueo es utilizada por dos etapas de sincronización de la pantalla de información legal, que se ejecutan una tras otra. El script Lua AI evita este retraso al establecer ambos contadores en 0.

8236: LDA #$FF
8238: JSR $A459

...

A459: STA $00C3 ; legalScreenCounter1 = 255;

A45B: JSR $AA2F ; do {
A45E: LDA $00C3 ; waitForVerticalBlankingInterval();
A460: BNE $A45B ; } while(legalScreenCounter1 > 0);

A462: RTS ; return;


823B: LDA #$FF
823D: STA $00A8 ; legalScreenCounter2 = 255;

; do {

823F: LDA $00F5 ; if (just pressed Start) {
8241: CMP #$10 ; break;
8243: BEQ $824C ; }

8245: JSR $AA2F ; waitForVerticalBlankingInterval();

8248: DEC $00A8 ; legalScreenCounter2--;
824A: BNE $823F ; } while(legalScreenCounter2 > 0);

824C: INC $00C0 ; gameMode = TITLE_SCREEN;




Demo


La demostración muestra unos 80 segundos de juego pregrabado. No solo muestra el archivo de video, sino que usa el mismo motor que en el juego. Durante la reproducción, se utilizan dos tablas. El primero, ubicado en la dirección $DF00, contiene la siguiente secuencia de creación de tetrimino:

TJTSZJTSZJSZLZJTTSITO JSZLZLIOLZLIOJTSITOJ

Al crear una figura, se selecciona al azar o se lee de la tabla, según el modo. El cambio ocurre en la dirección $98EB. El tipo Tetrimino se extrae de los bits 6, 5 y 4 de cada byte. De vez en cuando, esta operación nos da un valor : el tipo incorrecto. Sin embargo, la tabla de la creación de formas ( ) utilizado para una conversión de tipo en Tetrimino orientación ID se encuentra realmente entre las dos tablas vinculadas: Significado

98EB: LDA $00C0
98ED: CMP #$05
98EF: BNE $9903 ; if (gameMode == DEMO) {

98F1: LDX $00D3
98F3: INC $00D3
98F5: LDA $DF00,X ; value = demoTetriminoTypeTable[++demoIndex];

98F8: LSR
98F9: LSR
98FA: LSR
98FB: LSR
98FC: AND #$07
98FE: TAX ; tetriminoType = bits 6,5,4 of value;

98FF: LDA $994E,X
9902: RTS ; return spawnTable[tetriminoType];
; } else {
; pickRandomTetrimino();
; }


$07$994E

993B: 00 00 00 00 ; T
993F: 01 01 01 01 ; J
9943: 02 02 ; Z
9945: 03 ; O
9946: 04 04 ; S
9948: 05 05 05 05 ; L
994C: 06 06 ; I


994E: 02 ; Td
994F: 07 ; Jd
9950: 08 ; Zh
9951: 0A ; O
9952: 0B ; Sh
9953: 0E ; Ld
9954: 12 ; Ih


9956: 02 02 02 02 ; Td
995A: 07 07 07 07 ; Jd
995E: 08 08 ; Zh
9960: 0A ; O
9961: 0B 0B ; Sh
9963: 0E 0E 0E 0E ; Ld
9967: 12 12 ; Ih


$07lo obliga a leer más allá del final de la tabla, en el siguiente, que da Td( $02).

Debido a este efecto, este esquema puede darnos una secuencia ilimitada pero reproducible de ID pseudoaleatorios de la orientación de las figuras creadas. El código funcionará porque cualquier dirección arbitraria en una secuencia cambiante de bytes no nos permite determinar dónde termina la tabla. De hecho, la secuencia en la dirección $DF00puede ser parte de algo completamente ajeno a esto, especialmente teniendo en cuenta que la asignación de los 5 bits restantes que no son cero no está clara, y la secuencia generada demuestra repetibilidad.

Durante la inicialización del modo de demostración, el índice de la tabla ( $00D3) se restablece a la dirección $872B.

La segunda tabla de la demostración contiene un registro de los botones del gamepad codificados en pares de bytes. Los bits del primer byte corresponden a los botones.

76543210
UnBSeleccioneInicioArribaAbajoA la izquierdaA la derecha

El segundo byte almacena el número de cuadros durante los cuales se presiona una combinación de botones.

La tabla toma direcciones $DD00, $DEFFy consta de 256 pares. El acceso a él se realiza mediante la subrutina en la dirección $9D5B. Dado que la tabla de botones de demostración tiene una longitud de 512 bytes, se requiere un índice de dos bytes para acceder a ella. El índice se almacena como little endian en - . Se inicializa con el valor de la dirección de la tabla , y su incremento se realiza mediante el siguiente código. Los programadores dejaron el procesamiento de entrada del jugador en el código, lo que nos permite ver el proceso de desarrollo y reemplazar la demostración con otro registro. El modo de grabación de demostración se activa cuando se asigna un valor.

9D5B: LDA $00D0 ; if (recording mode) {
9D5D: CMP #$FF ; goto recording;
9D5F: BEQ $9DB0 ; }

9D61: JSR $AB9D ; pollController();
9D64: LDA $00F5 ; if (start button pressed) {
9D66: CMP #$10 ; goto startButtonPressed;
9D68: BEQ $9DA3 ; }

9D6A: LDA $00CF ; if (repeats == 0) {
9D6C: BEQ $9D73 ; goto finishedMove;
; } else {
9D6E: DEC $00CF ; repeats--;
9D70: JMP $9D9A ; goto moveInProgress;
; }

finishedMove:

9D73: LDX #$00
9D75: LDA ($D1,X)
9D77: STA $00A8 ; buttons = demoButtonsTable[index];

9D79: JSR $9DE8 ; index++;

9D7C: LDA $00CE
9D7E: EOR $00A8
9D80: AND $00A8
9D82: STA $00F5 ; setNewlyPressedButtons(difference between heldButtons and buttons);

9D84: LDA $00A8
9D86: STA $00CE ; heldButtons = buttons;

9D88: LDX #$00
9D8A: LDA ($D1,X)
9D8C: STA $00CF ; repeats = demoButtonsTable[index];

9D8E: JSR $9DE8 ; index++;

9D91: LDA $00D2 ; if (reached end of demo table) {
9D93: CMP #$DF ; return;
9D95: BEQ $9DA2 ; }

9D97: JMP $9D9E ; goto holdButtons;

moveInProgress:

9D9A: LDA #$00
9D9C: STA $00F5 ; clearNewlyPressedButtons();

holdButtons:

9D9E: LDA $00CE
9DA0: STA $00F7 ; setHeldButtons(heldButtons);

9DA2: RTS ; return;

startButtonPressed:

9DA3: LDA #$DD
9DA5: STA $00D2 ; reset index;

9DA7: LDA #$00
9DA9: STA $00B2 ; counter = 0;

9DAB: LDA #$01
9DAD: STA $00C0 ; gameMode = TITLE_SCREEN;

9DAF: RTS ; return;


$00D1$00D2$872D

9DE8: LDA $00D1
9DEA: CLC ; increment [$00D1]
9DEB: ADC #$01 ; possibly causing wrap around to 0
9DED: STA $00D1 ; which produces a carry

9DEF: LDA #$00
9DF1: ADC $00D2
9DF3: STA $00D2 ; add carry to [$00D2]

9DF5: RTS ; return


$00D0$FF. En este caso, se inicia el siguiente código, destinado a escribir en la tabla de botones para la demostración. Sin embargo, la tabla se almacena en el PRG-ROM. Intentar escribir en él no afectará los datos guardados. En cambio, cada operación de escritura activa un cambio de banco, lo que resulta en el efecto de falla que se muestra a continuación.

recording:

9DB0: JSR $AB9D ; pollController();

9DB3: LDA $00C0 ; if (gameMode != DEMO) {
9DB5: CMP #$05 ; return;
9DB7: BNE $9DE7 ; }

9DB9: LDA $00D0 ; if (not recording mode) {
9DBB: CMP #$FF ; return;
9DBD: BNE $9DE7 ; }

9DBF: LDA $00F7 ; if (getHeldButtons() == heldButtons) {
9DC1: CMP $00CE ; goto buttonsNotChanged;
9DC3: BEQ $9DE4 ; }

9DC5: LDX #$00
9DC7: LDA $00CE
9DC9: STA ($D1,X) ; demoButtonsTable[index] = heldButtons;

9DCB: JSR $9DE8 ; index++;

9DCE: LDA $00CF
9DD0: STA ($D1,X) ; demoButtonsTable[index] = repeats;

9DD2: JSR $9DE8 ; index++;

9DD5: LDA $00D2 ; if (reached end of demo table) {
9DD7: CMP #$DF ; return;
9DD9: BEQ $9DE7 ; }

9DDB: LDA $00F7
9DDD: STA $00CE ; heldButtons = getHeldButtons();

9DDF: LDA #$00
9DE1: STA $00CF ; repeats = 0;

9DE3: RTS ; return;

buttonsNotChanged:

9DE4: INC $00CF ; repeats++;

9DE6: RTS
9DE7: RTS ; return;





Esto sugiere que los desarrolladores podrían ejecutar el programa parcial o totalmente en RAM.

Para sortear este obstáculo, creé lua/RecordDemo.luauno ubicado en un zip con código fuente . Después de cambiar al modo de grabación de demostración, redirige las operaciones de escritura a la tabla en la consola Lua. A partir de él, los bytes se pueden copiar y pegar en la ROM.

Para grabar su propia demostración, ejecute FCEUX y descargue el archivo ROM de Nintendo Tetris (Archivo | Abrir ROM ...). Luego abra la ventana Lua Script (Archivo | Lua | Nueva ventana Lua Script ...), busque el archivo o ingrese la ruta. Presione el botón Ejecutar para iniciar el modo de grabación de demostración y luego haga clic en la ventana FCEUX para cambiar el enfoque. Puede controlar las formas hasta que la tabla de botones esté llena. Después de eso, el juego volverá automáticamente al protector de pantalla. Haga clic en Detener en la ventana Lua Script para detener el script. Los datos grabados aparecerán en la Consola de salida, como se muestra en la figura a continuación.


Seleccione todos los contenidos y cópielos en el portapapeles (Ctrl + C). Luego ejecute el Editor Hex (Depuración | Editor Hex ...). En el menú Editor hexadecimal, seleccione Ver | Archivo ROM y luego Archivo | Ir a la dirección. En el cuadro de diálogo Ir a, ingrese 5D10 (dirección de la tabla de botones de demostración en el archivo ROM) y haga clic en Aceptar. Luego pegue el contenido del portapapeles (Ctrl + V).


Finalmente, en el menú FCEUX, seleccione NES | Restablecer Si logró repetir todos estos pasos, la demostración debe ser reemplazada por su propia versión.

Si desea guardar los cambios, seleccione Archivo | Guardar Rom como ... e ingrese el nombre del archivo ROM modificado, y luego haga clic en Guardar.

De manera similar, puede ajustar la secuencia de tetriminos creados.

Pantalla de la muerte


Como se mencionó anteriormente, la mayoría de los jugadores no pueden hacer frente a la velocidad del descenso de figuras en el nivel 29, lo que rápidamente lleva a la finalización del juego. Por lo tanto, los jugadores se asociaron con el nombre de "pantalla de la muerte". Pero desde un punto de vista técnico, la pantalla de muerte no permite que el jugador vaya más allá debido a un error en el que un descenso rápido en realidad no es un error, sino una característica. Los diseñadores fueron tan amables que permitieron que el juego continuara mientras el jugador podía soportar la velocidad sobrehumana.

Aparece una verdadera pantalla de muerte en aproximadamente 1550 filas retraídas. Se manifiesta de diferentes maneras. A veces el juego se reinicia. En otros casos, la pantalla se vuelve negra. Por lo general, un juego se congela ("congela") inmediatamente después de eliminar una fila, como se muestra a continuación. Dichos efectos suelen ir precedidos de artefactos gráficos aleatorios.


La pantalla de muerte es el resultado de un error en el código que agrega puntos al eliminar filas. La cuenta de seis caracteres se almacena como un pequeño BCD endian empaquetado de 24 bits y se encuentra en $0053- $0055. Para realizar conversiones entre el número de filas despejadas y los puntos obtenidos, se utiliza una tabla; cada entrada es un pequeño valor endian BCD empaquetado de 16 bits. Después de incrementar el número total de filas, y posiblemente el nivel, el valor en esta lista se multiplica por el número de nivel más uno, y el resultado se agrega a los puntos. Esto se demuestra claramente en la tabla del manual de Nintendo Tetris:

9CA5: 00 00 ; 0: 0
9CA7: 40 00 ; 1: 40
9CA9: 00 01 ; 2: 100
9CAB: 00 03 ; 3: 300
9CAD: 00 12 ; 4: 1200





Como se muestra a continuación, la multiplicación se simula mediante un ciclo que agrega puntos a la puntuación. Se ejecuta después de bloquear la forma, incluso si no se borran las filas. Desafortunadamente, el Ricoh 2A03 no tiene un modo decimal binario 6502; él podría simplificar enormemente el cuerpo del ciclo. En cambio, la adición se realiza en pasos usando el modo binario. Cualquier dígito que exceda 9 después de la suma se obtiene esencialmente restando 10 e incrementando los dígitos de la izquierda. Por ejemplo, eso se convierte a . Pero tal esquema no está completamente protegido. Tomar : un cheque no puede convertir el resultado a

9C31: LDA $0044
9C33: STA $00A8
9C35: INC $00A8 ; for(i = 0; i <= level; i++) {

9C37: LDA $0056
9C39: ASL
9C3A: TAX
9C3B: LDA $9CA5,X ; points[0] = pointsTable[2 * completedLines];

9C3E: CLC
9C3F: ADC $0053
9C41: STA $0053 ; score[0] += points[0];

9C43: CMP #$A0
9C45: BCC $9C4E ; if (upper digit of score[0] > 9) {

9C47: CLC
9C48: ADC #$60
9C4A: STA $0053 ; upper digit of score[0] -= 10;
9C4C: INC $0054 ; score[1]++;
; }

9C4E: INX
9C4F: LDA $9CA5,X ; points[1] = pointsTable[2 * completedLines + 1];

9C52: CLC
9C53: ADC $0054
9C55: STA $0054 ; score[1] += points[1];

9C57: AND #$0F
9C59: CMP #$0A
9C5B: BCC $9C64 ; if (lower digit of score[1] > 9) {

9C5D: LDA $0054
9C5F: CLC ; lower digit of score[1] -= 10;
9C60: ADC #$06 ; increment upper digit of score[1];
9C62: STA $0054 ; }

9C64: LDA $0054
9C66: AND #$F0
9C68: CMP #$A0
9C6A: BCC $9C75 ; if (upper digit of score[1] > 9) {

9C6C: LDA $0054
9C6E: CLC
9C6F: ADC #$60
9C71: STA $0054 ; upper digit of score[1] -= 10;
9C73: INC $0055 ; score[2]++;
; }

9C75: LDA $0055
9C77: AND #$0F
9C79: CMP #$0A
9C7B: BCC $9C84 ; if (lower digit of score[2] > 9) {

9C7D: LDA $0055
9C7F: CLC ; lower digit of score[2] -= 10;
9C80: ADC #$06 ; increment upper digit of score[2];
9C82: STA $0055 ; }

9C84: LDA $0055
9C86: AND #$F0
9C88: CMP #$A0
9C8A: BCC $9C94 ; if (upper digit of score[2] > 9) {

9C8C: LDA #$99
9C8E: STA $0053
9C90: STA $0054
9C92: STA $0055 ; max out score to 999999;
; }

9C94: DEC $00A8
9C96: BNE $9C37 ; }


$07 + $07 = $0E$14$09 + $09 = $12$18. Para compensar esto, ninguno de los dígitos decimales en las entradas en el cuadro de mando supera 6. Además, para poder usarlo, el último dígito de todas las entradas es siempre 0.

Se necesita tiempo para completar este ciclo largo y complicado. A niveles altos, una gran cantidad de iteraciones afecta el momento del juego, ya que lleva más de 1/60 de segundo generar cada cuadro. Todo esto como resultado conduce a diversas manifestaciones de la "pantalla de la muerte".

El script Lua AI limita el número de iteraciones en un bucle a 30, el valor máximo que los diseñadores podrían lograr según lo diseñado por los diseñadores, lo que elimina la pantalla de la muerte.

Finales


En el folleto de Nintendo Tetris, el juego tipo A se describe de la siguiente manera:


El juego recompensa a los jugadores que obtuvieron un número suficientemente grande de puntos en una de las cinco animaciones de los finales. La elección del final se basa completamente en los dos dígitos más a la izquierda de la puntuación de seis dígitos. Como se muestra a continuación, para obtener uno de los finales, el jugador debe anotar al menos 30,000 puntos. Vale la pena señalar que - es un espejo de direcciones - . La cuenta está duplicada en las direcciones - . Después de pasar la primera prueba, la siguiente animación de cambio selecciona la animación final.

9A4D: LDA $0075
9A4F: CMP #$03
9A51: BCC $9A5E ; if (score[2] >= $03) {

9A53: LDA #$80
9A55: JSR $A459
9A58: JSR $9E3A
9A5B: JMP $9A64 ; select ending;
; }


$0060$007F$0040$005F$0073$0075



A96E: LDA #$00
A970: STA $00C4
A972: LDA $0075 ; if (score[2] < $05) {
A974: CMP #$05 ; ending = 0;
A976: BCC $A9A5 ; }

A978: LDA #$01
A97A: STA $00C4
A97C: LDA $0075 ; else if (score[2] < $07) {
A97E: CMP #$07 ; ending = 1;
A980: BCC $A9A5 ; }

A982: LDA #$02
A984: STA $00C4
A986: LDA $0075 ; else if (score[2] < $10) {
A988: CMP #$10 ; ending = 2;
A98A: BCC $A9A5 ; }

A98C: LDA #$03
A98E: STA $00C4
A990: LDA $0075 ; else if (score[2] < $12) {
A992: CMP #$12 ; ending = 3;
A994: BCC $A9A5 ; }

A996: LDA #$04 ; else {
A998: STA $00C4 ; ending = 4;
; }


Al final, se lanzan cohetes de tamaño creciente desde la plataforma de lanzamiento junto a la Catedral de San Basilio. En el cuarto final, se muestra la nave espacial Buran, la versión soviética del transbordador espacial estadounidense. En el mejor final, la catedral se eleva en el aire, y un ovni cuelga sobre la plataforma de lanzamiento. A continuación se muestra una imagen de cada final y el puntaje asociado.
30000–49999
50000–69999
70000–99999
100000–119999
120000+

En el modo de juego B-Type, se implementa otra prueba, que se describe en el folleto de Nintendo Tetris de la siguiente manera:


Si el jugador borra con éxito 25 filas, el juego muestra el final, según el nivel inicial. Los finales para los niveles 0–8 consisten en animales y objetos que vuelan o corren en el cuadro, pasando misteriosamente detrás de la Catedral de San Basilio. El OVNI del mejor final del modo tipo A aparece en el final 3. En el final 4, aparecen pterosaurios voladores extintos, y en el final 7 se muestran los míticos dragones voladores. En las terminaciones 2 y 6, se muestran aves sin alas: pingüinos corriendo y avestruces. Al final de 5, el cielo se llena de BUENAS aeronaves (que no debe confundirse con las aeronaves Goodyear). Y al final de 8, muchos "Buranas" barren la pantalla, aunque en realidad solo había uno.

La altura inicial (más 1) se utiliza como multiplicador, recompensando al jugador con una gran cantidad de animales / objetos por su mayor complejidad.

En el mejor final del tipo B, se muestra un castillo lleno de personajes del universo de Nintendo: la princesa Peach aplaude, Kid Icarus toca el violín, Donkey Kong toca el gran tambor, Mario y Luigi bailan, Bowser toca el acordeón, Samus toca el violonchelo, Link - en una flauta, mientras las cúpulas de la Catedral de San Basilio se elevan en el aire. La cantidad de estos elementos que se muestran en el final depende de la altura inicial. A continuación se muestran imágenes de las 10 terminaciones.


AI puede borrar rápidamente las 25 filas requeridas en el modo Tipo B en cualquier nivel y altura iniciales, lo que le permite ver cualquiera de las terminaciones. También vale la pena evaluar qué tan genial maneja grandes montones de bloques al azar.

En las terminaciones 0–8, se pueden mover hasta 6 objetos en el cuadro. Las coordenadas y de los objetos se almacenan en una tabla ubicada en at $A7B7. Las distancias horizontales entre los objetos se almacenan en una tabla en la dirección . Una secuencia de valores con un signo en la dirección determina la velocidad y la dirección de los objetos. Los índices de sprites se almacenan en . De hecho, cada objeto consta de dos sprites con índices adyacentes. Para obtener el segundo índice, debe agregar 1. Por ejemplo, un dragón consta de

A7B7: 98 A8 C0 A8 90 B0 ; 0
A7BD: B0 B8 A0 B8 A8 A0 ; 1
A7C3: C8 C8 C8 C8 C8 C8 ; 2
A7C9: 30 20 40 28 A0 80 ; 3
A7CF: A8 88 68 A8 48 78 ; 4
A7D5: 58 68 18 48 78 38 ; 5
A7DB: C8 C8 C8 C8 C8 C8 ; 6
A7E1: 90 58 70 A8 40 38 ; 7
A7E7: 68 88 78 18 48 A8 ; 8


$A77B

A77B: 3A 24 0A 4A 3A FF ; 0
A781: 22 44 12 32 4A FF ; 1
A787: AE 6E 8E 6E 1E 02 ; 2
A78D: 42 42 42 42 42 02 ; 3
A793: 22 0A 1A 04 0A FF ; 4
A799: EE DE FC FC F6 02 ; 5
A79F: 80 80 80 80 80 FF ; 6
A7A5: E8 E8 E8 E8 48 FF ; 7
A7AB: 80 AE 9E 90 80 02 ; 8


$A771

A771: 01 ; 0: 1
A772: 01 ; 1: 1
A773: FF ; 2: -1
A774: FC ; 3: -4
A775: 01 ; 4: 1
A776: FF ; 5: -1
A777: 02 ; 6: 2
A778: 02 ; 7: 2
A779: FE ; 8: -1


$A7F3

A7F3: 2C ; 0: dragonfly
A7F4: 2E ; 1: dove
A7F5: 54 ; 2: penguin
A7F6: 32 ; 3: UFO
A7F7: 34 ; 4: pterosaur
A7F8: 36 ; 5: blimp
A7F9: 4B ; 6: ostrich
A7FA: 38 ; 7: dragon
A7FB: 3A ; 8: Buran


$38y $39. Los mosaicos para estos sprites están contenidos en las tablas de patrones a continuación.


Examinamos la tabla central del patrón de arriba, se usa para mostrar el tetrimino y el campo de juego. Curiosamente, contiene el alfabeto completo, mientras que otros contienen solo una parte para ahorrar espacio. Pero aún más interesantes son los sprites de aviones y helicópteros en la tabla de patrones a la izquierda; no aparecen en los finales ni en otras partes del juego. Resultó que el avión y el helicóptero tienen índices de sprites $30y $16y se puede cambiar la tabla mostrada anteriormente, para verlos en acción.



Desafortunadamente, las monturas de helicópteros no se muestran, pero los rotores principal y de cola están bellamente animados.

2 jugadores contra


Nintendo Tetris contiene un modo incompleto para dos jugadores que puedes habilitar cambiando la cantidad de jugadores ( $00BE) a 2. Como se muestra a continuación, aparecen dos campos de juego en el fondo del modo para un jugador.


No hay borde entre los campos porque la región central del fondo es negro sólido. Los valores que se 003muestran arriba de los campos de juego indican el número de filas despejadas por cada jugador. La única figura común para dos jugadores aparece en el mismo lugar que en el modo para un jugador. Desafortunadamente, se encuentra en el campo de juego correcto. Los cuadrados y otros mosaicos están coloreados incorrectamente. Y cuando el jugador pierde el juego se reinicia.

Pero si ignora estos problemas, entonces el modo es bastante jugable. Cada jugador puede controlar independientemente las piezas en el campo de juego correspondiente. Y cuando un jugador escribe Doble, Triple o Tetris (es decir, borra dos, tres o cuatro filas), aparecen filas de basura con un cuadrado faltante en la parte inferior del campo de juego del oponente.

Un campo adicional se encuentra en $0500. A $0060- $007Fespejo que normalmente forman $0040- $005Fse utiliza para el segundo jugador.

Probablemente, este modo interesante fue abandonado debido a un programa de desarrollo ocupado. O tal vez lo dejaron sin terminar intencionalmente. Una de las razones por las que se eligió a Tetris como el juego incluido con Nintendo Game Boy fue porque alentó la compra de Game Link Cable- un accesorio que conecta a dos Game Boys para lanzar 2 jugadores contra el modo. Este cable agregó un elemento de "socialidad" al sistema: alentó a los amigos a comprar un Game Boy para unirse a la diversión. Quizás Nintendo temía que si la versión de consola del juego tuviera 2 jugadores versus modo, entonces el poder "publicitario" de Tetris, que estimuló la compra de Game Boy, podría debilitarse.

Musica y efectos de sonido


La música de fondo se activa cuando $06F5se asigna uno de los valores enumerados en la tabla.

ValorDescripción
01Música de pantalla de bienvenida no utilizada
02Objetivo de modo B-Type alcanzado
03Música-1
04Música-2
05Música-3
06Música-1 allegro
07Música-2 allegro
08Música-3 allegro
09Pantalla de felicitaciones
0AFinales
0BObjetivo de modo B-Type alcanzado
Puede escuchar música no utilizada desde el protector de pantalla aquí . En el juego en sí, no suena nada durante la pantalla del protector de pantalla.

Music-1 es una versión de " Dance of the Dragee Fairy ", música para la bailarina del tercer acto del pas de deux waltz "El cascanueces" de Tchaikovsky. La música final es una variación de los " Versos del torero ", un aria de la ópera Carmen Georges Bizet. Estas composiciones son arregladas por el compositor del resto de la música de Hirokazu Tanaka .

Music-2 se inspiró en las canciones tradicionales rusas del folklore. Music-3 es misterioso, futurista y tierno; Durante un tiempo, fue el tono de llamada de atención al cliente de Nintendo of America.

Para ayudar al jugador a caer en un estado de pánico cuando la altura del montón se acerca al techo del campo de juego, una versión de música de fondo comienza a reproducirse a un ritmo rápido ( $06- $08).

Curiosamente, entre las composiciones musicales no hay " Chapman ", un tema famoso que suena en Game Boy Tetris.

Los efectos de sonido se activan al grabar $06F0y $06F1, de acuerdo con la siguiente tabla.

La direccionValorDescripción
06F002El telón del final del juego.
06F003Cohete al final
06F101Selección de opciones de menú
06F102Selección de pantalla de menú
06F103Tetrimino Shift
06F104Tetris recibido
06F105Rotación Tetrimino
06F106Nuevo nivel
06F107Cerradura Tetrimino
06F108Piar
06F109Limpieza de la fila
06F10AFila llena

Estados de juego y modos de renderizado


Durante el juego, el estado actual del juego está representado por un número entero en la dirección $0048. La mayoría de las veces tiene un significado $01que indica que el jugador controla el tetrimino activo. Sin embargo, cuando la pieza está bloqueada en su lugar, el juego pasa gradualmente de un estado $02a otro $08, como se muestra en la tabla.

CondiciónDescripción
00ID de orientación no asignada
01El jugador controla el tetrimino activo
02Tetrimino lock en el campo de juego
03Comprobación de filas llenas
04Mostrar animación de limpieza de fila
05Actualización de filas y estadísticas
06Comprobación del objetivo del modo B-Type
07No utilizado
08Crear siguiente Tetrimino
09No utilizado
0AActualización de la cortina del juego
0BIncremento de estado de juego

La ramificación del código, según el estado del juego, se produce en la siguiente dirección $81B2: en el estado de cambio, salta al código que asigna un valor que indica que la orientación no está especificada. El manejador nunca se llama; sin embargo, el estado del juego sirve como señal para otras partes del código. El estado permite al jugador cambiar, rotar y bajar el tetrimino activo: como se indicó en las secciones anteriores, las rutinas de cambio, rotación y descenso de la figura antes de ejecutar el código verifican las nuevas posiciones del tetrimino. La única forma de bloquear una forma en la posición incorrecta es crearla encima de una forma existente. En este caso, el juego termina. Como se muestra a continuación, el código de estado realiza esta verificación.

81B2: LDA $0048
81B4: JSR $AC82 ; switch(playState) {
81B7: 2F 9E ; case 00: goto 9E2F; // Unassign orientationID
81B9: CF 81 ; case 01: goto 81CF; // Player controls active Tetrimino
81BB: A2 99 ; case 02: goto 99A2; // Lock Tetrimino into playfield
81BD: 6B 9A ; case 03: goto 9A6B; // Check for completed rows
81BF: 39 9E ; case 04: goto 9E39; // Display line clearing animation
81C1: 58 9B ; case 05: goto 9B58; // Update lines and statistics
81C3: F2 A3 ; case 06: goto A3F2; // B-Type goal check; Unused frame for A-Type
81C5: 03 9B ; case 07: goto 9B03; // Unused frame; Execute unfinished 2 player mode logic
81C7: 8E 98 ; case 08: goto 988E; // Spawn next Tetrimino
81C9: 39 9E ; case 09: goto 9E39; // Unused
81CB: 11 9A ; case 0A: goto 9A11; // Update game over curtain
81CD: 37 9E ; case 0B: goto 9E37; // Increment play state
; }


$00orientationID$13

9E2F: LDA #$13
9E31: STA $0042 ; orientationID = UNASSIGNED;

9E33: RTS ; return;


$00

$01

81CF: JSR $89AE ; shift Tetrimino;
81D2: JSR $88AB ; rotate Tetrimino;
81D5: JSR $8914 ; drop Tetrimino;

81D8: RTS ; return;


$02. Si la posición bloqueada es correcta, marca las 4 celdas asociadas del campo de juego como ocupadas. De lo contrario, ella hace la transición a un estado : la ominosa cortina del final del juego.

99A2: JSR $948B ; if (new position valid) {
99A5: BEQ $99B8 ; goto updatePlayfield;
; }

99A7: LDA #$02
99A9: STA $06F0 ; play curtain sound effect;

99AC: LDA #$0A
99AE: STA $0048 ; playState = UPDATE_GAME_OVER_CURTAIN;

99B0: LDA #$F0
99B2: STA $0058 ; curtainRow = -16;

99B4: JSR $E003 ; updateAudio();

99B7: RTS ; return;


$0A


La cortina se dibuja desde la parte superior del campo de juego hacia abajo, descendiendo una línea cada 4 cuadros. curtainRow( $0058) se inicializa con un valor de −16, creando un retraso adicional de 0.27 segundos entre el bloqueo final y el inicio de la animación. En la dirección $9A21en el estado del $0Acódigo que se muestra a continuación, se accede a la tabla de multiplicación, que se muestra erróneamente como números de nivel. Esto se hace para escalar curtainRowen 10. Además, como se muestra arriba, el código en la dirección $9A51comienza la animación final si la puntuación del jugador no es inferior a 30,000 puntos; de lo contrario, espera hacer clic en Inicio. El código se completa asignando un valor al estado del juego , pero no se llama al controlador correspondiente porque el juego se ha completado.

9A11: LDA $0058 ; if (curtainRow == 20) {
9A13: CMP #$14 ; goto endGame;
9A15: BEQ $9A47 ; }

9A17: LDA $00B1 ; if (frameCounter not divisible by 4) {
9A19: AND #$03 ; return;
9A1B: BNE $9A46 ; }

9A1D: LDX $0058 ; if (curtainRow < 0) {
9A1F: BMI $9A3E ; goto incrementCurtainRow;
; }

9A21: LDA $96D6,X
9A24: TAY ; rowIndex = 10 * curtainRow;

9A25: LDA #$00
9A27: STA $00AA ; i = 0;

9A29: LDA #$13
9A2B: STA $0042 ; orientationID = NONE;

drawCurtainRow:

9A2D: LDA #$4F
9A2F: STA ($B8),Y ; playfield[rowIndex + i] = CURTAIN_TILE;
9A31: INY
9A32: INC $00AA ; i++;
9A34: LDA $00AA
9A36: CMP #$0A ; if (i != 10) {
9A38: BNE $9A2D ; goto drawCurtainRow;
; }

9A3A: LDA $0058
9A3C: STA $0049 ; vramRow = curtainRow;

incrementCurtainRow:

9A3E: INC $0058 ; curtainRow++;

9A40: LDA $0058 ; if (curtainRow != 20) {
9A42: CMP #$14 ; return;
9A44: BNE $9A46 ; }

9A46: RTS ; return;

endGame:

9A47: LDA $00BE
9A49: CMP #$02
9A4B: BEQ $9A64 ; if (numberOfPlayers == 1) {

9A4D: LDA $0075
9A4F: CMP #$03
9A51: BCC $9A5E ; if (score[2] >= $03) {

9A53: LDA #$80
9A55: JSR $A459
9A58: JSR $9E3A
9A5B: JMP $9A64 ; select ending;
; }

9A5E: LDA $00F5 ; if (not just pressed Start) {
9A60: CMP #$10 ; return;
9A62: BNE $9A6A ; }
; }

9A64: LDA #$00
9A66: STA $0048 ; playState = INITIALIZE_ORIENTATION_ID;
9A68: STA $00F5 ; clear newly pressed buttons;

9A6A: RTS ; return;


$00

Las líneas del campo de juego se copian de forma incremental en VRAM para mostrarlas. El índice de la fila actual a copiar está contenido en vramRow( $0049). Se $9A3C vramRowasigna un valor en la dirección curtainRow, lo que finalmente hace que esta línea sea visible cuando se procesa.

Las manipulaciones con VRAM se producen durante un intervalo de supresión vertical, que es reconocido por el controlador de interrupciones descrito en la sección "Pantalla de información legal". Llama a la subrutina que se muestra a continuación (marcada en los comentarios del controlador de interrupciones como render()). El modo de renderizado es similar al modo de juego. Se almacena en la dirección y puede tener uno de los siguientes valores:

804B: LDA $00BD
804D: JSR $AC82 ; switch(renderMode) {
8050: B1 82 ; case 0: goto 82B1; // Legal and title screens
8052: DA 85 ; case 1: goto 85DA; // Menu screens
8054: 44 A3 ; case 2: goto A344; // Congratulations screen
8056: EE 94 ; case 3: goto 94EE; // Play and demo
8058: 95 9F ; case 4: goto 9F95; // Ending animation
; }


$00BD

ValorDescripción
00Pantalla con legal información y protector de pantalla
01Pantallas de menú
02Pantalla de felicitaciones
03Juego y demo
04Finalizando la animación

Parte del modo de representación se $03muestra a continuación. Como puede ver a continuación, pasa en VRAM una línea del campo de juego con un índice . Si es mayor que 20, la rutina no hace nada. La tabla ( ) contiene las direcciones VRAM en formato little endian, correspondientes a las líneas mostradas del campo de juego, desplazadas por 6 en modo normal y por −2 y 12 para el campo de juego en modo inacabado 2 Player Versus. Los bytes de esta tabla son parte de la lista de valores que se muestran erróneamente como números de nivel después del nivel 29. Los bytes adyacentes inferior y superior de cada dirección se obtienen por separado y se combinan esencialmente en una dirección de 16 bits que se utiliza en el ciclo de copia. Se ejecuta un incremento al final de la subrutina.

952A: JSR $9725 ; copyPlayfieldRowToVRAM();
952D: JSR $9725 ; copyPlayfieldRowToVRAM();
9530: JSR $9725 ; copyPlayfieldRowToVRAM();
9533: JSR $9725 ; copyPlayfieldRowToVRAM();


copyPlayfieldRowToVRAM()vramRowvramRow

9725: LDX $0049 ; if (vramRow > 20) {
9727: CPX #$15 ; return;
9729: BPL $977E ; }

972B: LDA $96D6,X
972E: TAY ; playfieldAddress = 10 * vramRow;

972F: TXA
9730: ASL
9731: TAX
9732: INX ; high = vramPlayfieldRows[vramRow * 2 + 1];
9733: LDA $96EA,X
9736: STA $2006
9739: DEX

973A: LDA $00BE
973C: CMP #$01
973E: BEQ $975E ; if (numberOfPlayers == 2) {

9740: LDA $00B9
9742: CMP #$05
9744: BEQ $9752 ; if (leftPlayfield) {

9746: LDA $96EA,X
9749: SEC
974A: SBC #$02
974C: STA $2006 ; low = vramPlayfieldRows[vramRow * 2] - 2;

974F: JMP $9767 ; } else {

9752: LDA $96EA,X
9755: CLC
9756: ADC #$0C
9758: STA $2006 ; low = vramPlayfieldRows[vramRow * 2] + 12;

975B: JMP $9767 ; } else {

975E: LDA $96EA,X
9761: CLC
9762: ADC #$06 ; low = vramPlayfieldRows[vramRow * 2] + 6;
9764: STA $2006 ; }

; vramAddress = (high << 8) | low;

9767: LDX #$0A
9769: LDA ($B8),Y
976B: STA $2007
976E: INY ; for(i = 0; i < 10; i++) {
976F: DEX ; vram[vramAddress + i] = playfield[playfieldAddress + i];
9770: BNE $9769 ; }

9772: INC $0049 ; vramRow++;
9774: LDA $0049 ; if (vramRow < 20) {
9776: CMP #$14 ; return;
9778: BMI $977E ; }

977A: LDA #$20
977C: STA $0049 ; vramRow = 32;

977E: RTS ; return;


vramPlayfieldRows$96EA

vramRow. Si el valor llega a 20, se le asigna un valor de 32, lo que significa que la copia está completamente completa. Como se muestra arriba, solo se copian 4 líneas por fotograma.

El controlador de estado $03es responsable de reconocer las líneas completadas y eliminarlas del campo de juego. Durante 4 llamadas separadas, escanea los desplazamientos de línea [−2, 1]cerca del centro del tetrimino (ambas coordenadas de todos los cuadrados del tetrimino están en este intervalo). Los índices de las filas completadas se almacenan en $004A- $004D; el índice registrado 0 se usa para indicar que no se encontraron filas completas en esta pasada. El controlador se muestra a continuación. La comprobación al principio no permite que el controlador se ejecute al transferir líneas del campo de juego a VRAM (controlador de estado

9A6B: LDA $0049
9A6D: CMP #$20 ; if (vramRow < 32) {
9A6F: BPL $9A74 ; return;
9A71: JMP $9B02 ; }

9A74: LDA $0041 ; rowY = tetriminoY - 2;
9A76: SEC
9A77: SBC #$02 ; if (rowY < 0) {
9A79: BPL $9A7D ; rowY = 0;
9A7B: LDA #$00 ; }

9A7D: CLC
9A7E: ADC $0057
9A80: STA $00A9 ; rowY += lineIndex;

9A82: ASL
9A83: STA $00A8
9A85: ASL
9A86: ASL
9A87: CLC
9A88: ADC $00A8
9A8A: STA $00A8 ; rowIndex = 10 * rowY;

9A8C: TAY
9A8D: LDX #$0A
9A8F: LDA ($B8),Y
9A91: CMP #$EF ; for(i = 0; i < 10; i++) {
9A93: BEQ $9ACC ; if (playfield[rowIndex + i] == EMPTY_TILE) {
9A95: INY ; goto rowNotComplete;
9A96: DEX ; }
9A97: BNE $9A8F ; }

9A99: LDA #$0A
9A9B: STA $06F1 ; play row completed sound effect;

9A9E: INC $0056 ; completedLines++;

9AA0: LDX $0057
9AA2: LDA $00A9
9AA4: STA $4A,X ; lines[lineIndex] = rowY;

9AA6: LDY $00A8
9AA8: DEY
9AA9: LDA ($B8),Y
9AAB: LDX #$0A
9AAD: STX $00B8
9AAF: STA ($B8),Y
9AB1: LDA #$00
9AB3: STA $00B8
9AB5: DEY ; for(i = rowIndex - 1; i >= 0; i--) {
9AB6: CPY #$FF ; playfield[i + 10] = playfield[i];
9AB8: BNE $9AA9 ; }

9ABA: LDA #$EF
9ABC: LDY #$00
9ABE: STA ($B8),Y
9AC0: INY ; for(i = 0; i < 10; i++) {
9AC1: CPY #$0A ; playfield[i] = EMPTY_TILE;
9AC3: BNE $9ABE ; }

9AC5: LDA #$13
9AC7: STA $0042 ; orientationID = UNASSIGNED;

9AC9: JMP $9AD2 ; goto incrementLineIndex;

rowNotComplete:

9ACC: LDX $0057
9ACE: LDA #$00
9AD0: STA $4A,X ; lines[lineIndex] = 0;

incrementLineIndex:

9AD2: INC $0057 ; lineIndex++;

9AD4: LDA $0057 ; if (lineIndex < 4) {
9AD6: CMP #$04 ; return;
9AD8: BMI $9B02 ; }

9ADA: LDY $0056
9ADC: LDA $9B53,Y
9ADF: CLC
9AE0: ADC $00BC
9AE2: STA $00BC ; totalGarbage += garbageLines[completedLines];

9AE4: LDA #$00
9AE6: STA $0049 ; vramRow = 0;
9AE8: STA $0052 ; clearColumnIndex = 0;

9AEA: LDA $0056
9AEC: CMP #$04
9AEE: BNE $9AF5 ; if (completedLines == 4) {
9AF0: LDA #$04 ; play Tetris sound effect;
9AF2: STA $06F1 ; }

9AF5: INC $0048 ; if (completedLines > 0) {
9AF7: LDA $0056 ; playState = DISPLAY_LINE_CLEARING_ANIMATION;
9AF9: BNE $9B02 ; return;
; }

9AFB: INC $0048 ; playState = UPDATE_LINES_AND_STATISTICS;

9AFD: LDA #$07
9AFF: STA $06F1 ; play piece locked sound effect;

9B02: RTS ; return;


vramRow$03llamado en cada cuadro). Si se detectan filas llenas, se vramRowrestablece a 0, lo que fuerza una transferencia completa.

lineIndex( $00A9) se inicializa con un valor de 0 y su incremento se realiza en cada pasada.

A diferencia del estado del juego $0Ay la rutina de copia del campo del juego, que usa la tabla de multiplicación de direcciones $96D6, un bloque que comienza con $9A82multiplica rowYpor 10 usando turnos y adiciones:

rowIndex = (rowY << 1) + (rowY << 3); // rowIndex = 2 * rowY + 8 * rowY;

Esto se hace solo porque está rowYlimitado por el intervalo [0, 20], y la tabla de multiplicación solo cubre [0, 19]. El escaneo de filas puede extenderse más allá del final del campo de juego. Sin embargo, como se dijo anteriormente, el juego se inicializa $0400, $04FFcon un valor$EF(mosaico vacío), creando más de 5 líneas ocultas vacías adicionales debajo del piso del campo de juego.

Un bloque que comienza $9ADAes parte del modo incompleto de 2 Player Versus. Como se mencionó anteriormente, despejar las filas agrega escombros al campo de juego del oponente. La tabla en la dirección determina el número de filas de basura $9B53: el ciclo en la dirección desplaza el material sobre la fila llena una línea hacia abajo. Aprovecha el hecho de que cada línea en una secuencia continua está separada de la otra por 10 bytes. El siguiente ciclo borra la línea superior. La animación de eliminación de filas se realiza durante el estado del juego , pero como se muestra a continuación, no ocurre en el controlador del estado del juego, que está completamente vacío.

9B53: 00 ; no cleared lines
9B54: 00 ; Single
9B55: 01 ; Double
9B56: 02 ; Triple
9B57: 04 ; Tetris


$9AA6

$04

9E39: RTS ; return;

En cambio, durante el estado del juego $04, se realiza la siguiente ramificación del modo de representación $03. y los valores reflejados son necesarios para el modo inacabado 2 Player Versus. La subrutina se muestra a continuación . Se llama en cada cuadro, pero la condición al principio permite que se ejecute solo en cada cuarto cuadro. En cada pasada, recorre la lista de índices de filas completadas y borra 2 columnas en estas filas, moviéndose desde la columna central hacia afuera. Una dirección VRAM de 16 bits se construye de la misma manera que se muestra en la rutina de campo de copia. Sin embargo, en este caso, realiza un desplazamiento por el índice de columna obtenido de la tabla a continuación.

94EE: LDA $0068
94F0: CMP #$04
94F2: BNE $9522 ; if (playState == DISPLAY_LINE_CLEARING_ANIMATION) {

94F4: LDA #$04
94F6: STA $00B9 ; leftPlayfield = true;

94F8: LDA $0072
94FA: STA $0052
94FC: LDA $006A
94FE: STA $004A
9500: LDA $006B
9502: STA $004B
9504: LDA $006C
9506: STA $004C
9508: LDA $006D
950A: STA $004D
950C: LDA $0068
950E: STA $0048 ; mirror values;

9510: JSR $977F ; updateLineClearingAnimation();

; ...
; }


leftPlayfield

updateLineClearingAnimation()

977F: LDA $00B1 ; if (frameCounter not divisible by 4) {
9781: AND #$03 ; return;
9783: BNE $97FD ; }

9785: LDA #$00 ; for(i = 0; i < 4; i++) {
9787: STA $00AA ; rowY = lines[i];
9789: LDX $00AA ; if (rowY == 0) {
978B: LDA $4A,X ; continue;
978D: BEQ $97EB ; }

978F: ASL
9790: TAY
9791: LDA $96EA,Y
9794: STA $00A8 ; low = vramPlayfieldRows[2 * rowY];

9796: LDA $00BE ; if (numberOfPlayers == 2) {
9798: CMP #$01 ; goto twoPlayers;
979A: BNE $97A6 ; }

979C: LDA $00A8
979E: CLC
979F: ADC #$06
97A1: STA $00A8 ; low += 6;

97A3: JMP $97BD ; goto updateVRAM;

twoPlayers:

97A6: LDA $00B9
97A8: CMP #$04
97AA: BNE $97B6 ; if (leftPlayfield) {

97AC: LDA $00A8
97AE: SEC
97AF: SBC #$02
97B1: STA $00A8 ; low -= 2;

97B3: JMP $97BD ; } else {

97B6: LDA $00A8
97B8: CLC
97B9: ADC #$0C ; low += 12;
97BB: STA $00A8 ; }

updateVRAM:

97BD: INY
97BE: LDA $96EA,Y
97C1: STA $00A9
97C3: STA $2006
97C6: LDX $0052 ; high = vramPlayfieldRows[2 * rowY + 1];
97C8: LDA $97FE,X
97CB: CLC ; rowAddress = (high << 8) | low;
97CC: ADC $00A8
97CE: STA $2006 ; vramAddress = rowAddress + leftColumns[clearColumnIndex];
97D1: LDA #$FF
97D3: STA $2007 ; vram[vramAddress] = 255;

97D6: LDA $00A9
97D8: STA $2006
97DB: LDX $0052 ; high = vramPlayfieldRows[2 * rowY + 1];
97DD: LDA $9803,X
97E0: CLC ; rowAddress = (high << 8) | low;
97E1: ADC $00A8
97E3: STA $2006 ; vramAddress = rowAddress + rightColumns[clearColumnIndex];
97E6: LDA #$FF
97E8: STA $2007 ; vram[vramAddress] = 255;

97EB: INC $00AA
97ED: LDA $00AA
97EF: CMP #$04
97F1: BNE $9789 ; }

97F3: INC $0052 ; clearColumnIndex++;
97F5: LDA $0052 ; if (clearColumnIndex < 5) {
97F7: CMP #$05 ; return;
97F9: BMI $97FD ; }

97FB: INC $0048 ; playState = UPDATE_LINES_AND_STATISTICS;

97FD: RTS ; return;




97FE: 04 03 02 01 00 ; left columns
9803: 05 06 07 08 09 ; right columns


Para limpiar la animación, se requieren 5 pases. Luego, el código pasa al siguiente estado del juego.

El controlador de estado del juego $05contiene el código descrito en la sección "Filas y estadísticas". El controlador termina con este código: la variable no se restablece hasta el final del estado del juego , después de lo cual se usa para actualizar el número total de filas y la puntuación. Esta secuencia permite ejecutar un error interesante. En el modo de demostración, debe esperar hasta que el juego recoja la fila completa y luego presione rápidamente Iniciar hasta que la animación para borrar la serie haya terminado. El juego volverá al protector de pantalla, pero si elige el momento adecuado, se guardará el valor . Ahora puedes iniciar el juego en modo A-Type. Cuando está bloqueado en lugar de la primera figura, el controlador de estado del juego

9C9E: LDA #$00
9CA0: STA $0056 ; completedLines = 0;

9CA2: INC $0048 ; playState = B_TYPE_GOAL_CHECK;

9CA4: RTS ; return;


completedLines$05completedLines$03comienza a escanear filas completadas. No los encontrará, pero los dejará completedLinessin cambios. Finalmente, cuando se cumple el estado del juego, el $05número total de filas y la puntuación aumentarán, como si las hubiera puntuado.

La forma más fácil de hacer esto es obtener la mayor cantidad, esperando que la demostración recolecte Tetris (habrá 2 de ellos en la demostración). Tan pronto como vea el parpadeo de la pantalla, haga clic en Inicio.


Después de comenzar un nuevo juego, la pantalla continuará parpadeando. Todo esto gracias al siguiente código llamado por el controlador de interrupciones. De hecho, si deja que la primera figura descienda automáticamente al piso del campo de juego, la puntuación aumentará en un valor aún mayor, porque ( ) también guardará su valor de la demostración. Esto es cierto incluso en los casos en que la demostración no llenó una sola fila. No se restablece hasta que se presiona el botón "Abajo". Además, si presiona Iniciar durante la animación de borrar la serie de combinaciones de Tetris en el modo de demostración, y luego espera a que la demostración comience nuevamente, no solo se contarán los puntos para Tetris en la demostración, sino que todo el tiempo se mezclará. Como resultado, la demo perderá el juego. Después de cortar el final del juego, puede volver al protector de pantalla haciendo clic en Inicio.

9673: LDA #$3F
9675: STA $2006
9678: LDA #$0E
967A: STA $2006 ; prepare to modify background tile color;

967D: LDX #$00 ; color = DARK_GRAY;

967F: LDA $0056
9681: CMP #$04
9683: BNE $9698 ; if (completedLines == 4) {

9685: LDA $00B1
9687: AND #$03
9689: BNE $9698 ; if (frameCounter divisible by 4) {

968B: LDX #$30 ; color = WHITE;

968D: LDA $00B1
968F: AND #$07
9691: BNE $9698 ; if (frameCounter divisible by 8) {

9693: LDA #$09
9695: STA $06F1 ; play clear sound effect;

; }
; }
; }

9698: STX $2007 ; update background tile color;


holdDownPoints$004FholdDownPoints



El estado del juego $06realiza una comprobación de objetivos para los juegos de tipo B. En el modo de tipo A, es esencialmente un marco no utilizado.

El estado del juego $07contiene exclusivamente la lógica incompleta de 2 jugadores contra. En el modo de un jugador, se comporta como un marco no utilizado.

El estado del juego se $08trata en las secciones "Creación de Tetrimino" y "Elección de Tetrimino".

El estado del juego $09no se usa. $0Baumenta el estado del juego, pero también se ve sin usar.

Y finalmente, el ciclo principal del juego:

; while(true) {

8138: JSR $8161 ; branchOnGameMode();

813B: CMP $00A7 ; if (vertical blanking interval wait requested) {
813D: BNE $8142 ; waitForVerticalBlankingInterval();
813F: JSR $AA2F ; }

8142: LDA $00C0
8144: CMP #$05
8146: BNE $815A ; if (gameMode == DEMO) {

8148: LDA $00D2
814A: CMP #$DF
814C: BNE $815A ; if (reached end of demo table) {

814E: LDA #$DD
8150: STA $00D2 ; reset demo table index;

8152: LDA #$00
8154: STA $00B2 ; clear upper byte of frame counter;

8156: LDA #$01
8158: STA $00C0 ; gameMode = TITLE_SCREEN;
; }
; }
815A: JMP $8138 ; }

Source: https://habr.com/ru/post/es420725/


All Articles