Applying Artificial Intelligence to Nintendo Tetris

In this article, I explore the deceptively simple mechanics of Nintendo Tetris.

Afterwards, I describe how to build an AI that exploits them.

Abstract

Table of Contents

Try It Yourself

About

Preliminaries

Download

Run

Configuration

The Mechanics of Nintendo Tetris

Representing Tetriminos

Rotating Tetriminos

Spawning Tetriminos

Picking Tetriminos

Shifting Tetriminos

Dropping Tetriminos

Slides and Spins

Level 30 and Beyond

Lines and Statistics

Coloring Tetriminos

Game Mode

Legal Screen

Demo

The Kill Screen

Endings

2 Player Versus

Music and Sound Effects

Play States and Render Modes

The Algorithm

Overview

Searching for Lock

Evaluation Function

Other Factors

AI Training

Java Version

About

Packages

AI Classes and Interfaces

Invoking the AI

Displaying the Playfield

Other Projects

Gamepad Version

For those lacking the persistence, patience and time necessary to master Nintendo Tetris, I created an AI to play it for you. Finally, you can experience level 30 and beyond. You can witness the score max out while the line, level and statistics counters wraparound indefinitely. Find out what colors appear in levels higher than any human has ever reached. Discover how long it can go.

To run the AI, you'll need FCEUX, the all-in-one NES/Famicom emulator. The AI was developed for FCEUX 2.2.2, the most recent version at the time of this writing.

You'll also need the Nintendo Tetris ROM file (USA version). Google might be able to help you to track it down.

Extract lua/NintendoTetrisAI.lua from this source zip.

Start up FCEUX.

From the menu bar, select File | Open ROM... In the Open File dialog box, select the Nintendo Tetris ROM file and press Open. The game will launch.

From the menu bar, select File | Lua | New Lua Script Window... From the Lua Script window, enter the path to NintendoTetrisAI.lua or hit the Browse button to navigate to it. Finally, press Run.

The Lua script will direct you to the first menu screen. Leave the game type as A-Type, but feel free to change the music using the arrow keys. On slower computers, the music may play choppy; you might want to disable it completely. Press Start (Enter) to advance to the next menu screen. In the second menu, you can change the starting level using the arrow keys. Press Start to begin the game. The AI will take over from there.

From the second menu screen, after you select the level, if you hold down gamepad button A (use Config | Input... to modify the keyboard mapping) and press Start, the resulting starting level will be 10 plus the selected value. The highest starting level is 19.

To make it play faster, open the Lua script in a text editor. Toward the top of the file, you'll find the following line.

PLAY_FAST = false

Change false to true as listed below.

PLAY_FAST = true

Save the file. Then, hit the Restart button on the Lua Script window.

Each Tetrimino (tetri-mee-noh) has a single-character name that resembles its shape.

The designers of Nintendo Tetris arbitrarily ordered the Tetriminos into the sequence shown above. The pieces are depicted in the spawn orientations and the arrangement produces a nearly symmetric image, which might explain why they chose this ordering. The index into the sequence provides each Tetrimino with a unique numerical type ID. The sequence and the type IDs are important at a programmatic level, but it also manifests itself in the order of the pieces displayed in the statistics area (see below).

The 19 orientations of Tetriminos used by Nintendo Tetris are encoded in a table located at $8A9C within NES memory. Each piece is represented as a sequence of 12 bytes that can be broken down into triples, (Y, tile, X), describing each square within the piece. Hex coordinate values above $7F represent negative integers ($FF = −1 and $FE = −2).

8A9C: 00 7B FF 00 7B 00 00 7B 01 FF 7B 00 8AA8: FF 7B 00 00 7B 00 00 7B 01 01 7B 00 8AB4: 00 7B FF 00 7B 00 00 7B 01 01 7B 00 8AC0: FF 7B 00 00 7B FF 00 7B 00 01 7B 00 8ACC: FF 7D 00 00 7D 00 01 7D FF 01 7D 00 8AD8: FF 7D FF 00 7D FF 00 7D 00 00 7D 01 8AE4: FF 7D 00 FF 7D 01 00 7D 00 01 7D 00 8AF0: 00 7D FF 00 7D 00 00 7D 01 01 7D 01 8AFC: 00 7C FF 00 7C 00 01 7C 00 01 7C 01 8B08: FF 7C 01 00 7C 00 00 7C 01 01 7C 00 8B14: 00 7B FF 00 7B 00 01 7B FF 01 7B 00 8B20: 00 7D 00 00 7D 01 01 7D FF 01 7D 00 8B2C: FF 7D 00 00 7D 00 00 7D 01 01 7D 01 8B38: FF 7C 00 00 7C 00 01 7C 00 01 7C 01 8B44: 00 7C FF 00 7C 00 00 7C 01 01 7C FF 8B50: FF 7C FF FF 7C 00 00 7C 00 01 7C 00 8B5C: FF 7C 01 00 7C FF 00 7C 00 00 7C 01 8B68: FE 7B 00 FF 7B 00 00 7B 00 01 7B 00 8B74: 00 7B FE 00 7B FF 00 7B 00 00 7B 01 8B80: 00 FF 00 00 FF 00 00 FF 00 00 FF 00

There is one unused entry at the bottom of the table potentially providing the means of adding an extra orientation. However, in various parts of the code, $13 indicates that the active Tetrimino's orientation ID is not assigned a value.

The square coordinates are re-expressed in decimal below to make it easier to read.

{ { -1 , 0 }, { 0 , 0 }, { 1 , 0 }, { 0 , -1 }, }, { { 0 , -1 }, { 0 , 0 }, { 1 , 0 }, { 0 , 1 }, }, { { -1 , 0 }, { 0 , 0 }, { 1 , 0 }, { 0 , 1 }, }, { { 0 , -1 }, { -1 , 0 }, { 0 , 0 }, { 0 , 1 }, }, { { 0 , -1 }, { 0 , 0 }, { -1 , 1 }, { 0 , 1 }, }, { { -1 , -1 }, { -1 , 0 }, { 0 , 0 }, { 1 , 0 }, }, { { 0 , -1 }, { 1 , -1 }, { 0 , 0 }, { 0 , 1 }, }, { { -1 , 0 }, { 0 , 0 }, { 1 , 0 }, { 1 , 1 }, }, { { -1 , 0 }, { 0 , 0 }, { 0 , 1 }, { 1 , 1 }, }, { { 1 , -1 }, { 0 , 0 }, { 1 , 0 }, { 0 , 1 }, }, { { -1 , 0 }, { 0 , 0 }, { -1 , 1 }, { 0 , 1 }, }, { { 0 , 0 }, { 1 , 0 }, { -1 , 1 }, { 0 , 1 }, }, { { 0 , -1 }, { 0 , 0 }, { 1 , 0 }, { 1 , 1 }, }, { { 0 , -1 }, { 0 , 0 }, { 0 , 1 }, { 1 , 1 }, }, { { -1 , 0 }, { 0 , 0 }, { 1 , 0 }, { -1 , 1 }, }, { { -1 , -1 }, { 0 , -1 }, { 0 , 0 }, { 0 , 1 }, }, { { 1 , -1 }, { -1 , 0 }, { 0 , 0 }, { 1 , 0 }, }, { { 0 , -2 }, { 0 , -1 }, { 0 , 0 }, { 0 , 1 }, }, { { -2 , 0 }, { -1 , 0 }, { 0 , 0 }, { 1 , 0 }, },

All of the orientations fit within a 5×5 matrix.

Above, a white square marks the center of the matrix, the pivot point for piece rotation.

The orientation table is depicted graphically below.

The orientation ID (the table index) is displayed in hexadecimal in the upper-right of each matrix. And, a mnemonic invented for this discussion appears in the upper-lefts. The lower-case u, r, d, l, h and v are short for up, right, down, left, horizontal and vertical respectively. It's easier to refer to Jd rather than $07, for instance.

The matrices containing spawn orientations are bordered in white.

Tetriminos I, S and Z could have been designed to have 4 distinct orientations, but the creators of Nintendo Tetris decided to limit them to 2. Also, Zv and Sv are not perfect mirrors of each other. Both were produced via counterclockwise rotation resulting in an imbalance.

The orientation table also contains the tile values for each square within each oriented piece. However, a close inspection reveals that the values are all the same among Tetrimino type.

T J Z O S L I 7B 7D 7C 7B 7D 7C 7B

The tile values are indices into the (false-color) pattern table shown below.

Tiles $7B, $7C and $7D are located directly below, "ATIS", in the word, "STATISTICS". They are the 3 types of squares from which Tetriminos are built.

If you are wondering, the ostriches and penguins are from the B-Type endings, a topic further discussed below.

Below is the result of modifying the ROM to change $7B to $29, the tile directly below character P in the pattern table, for all T orientations.

The heart tiles remain in the playfield even after the modified T locks into place. As discussed further below, this means that the playfield stores the actual tile index values from the played Tetriminos.

The game programmers provided the means of using 4 separate tiles for each piece as opposed to just one consistent square type. It's a useful feature that could be used to re-skin the game. There are plenty of empty spaces within the pattern table for new tiles that could give each Tetrimino a unique look.

The square coordinates are just as easy to manipulate. For example, below is a modified version of the first 4 triples in the orientation table.

8A9C: FE 7B FE FE 7B 02 02 7B FE 02 7B 02

The change is equivalent to the following.

{ { -2 , -2 }, { 2 , -2 }, { -2 , 2 }, { 2 , 2 }, },

The result is a disjoint Tetrimino.

When the disjoint Tetrimino is moved around, its squares are restricted to the boundaries of the playfield and they cannot pass through previously locked pieces. Also, the game prohibits rotation to this orientation if it would result in a square outside of the playfield bounds or if it would cause a square to overlap a locked square.

The disjoint Tetrimino locks into place when any of its squares are supported. And, when it locks, its floating squares remain floating.

The game treats the disjoint Tetrimino as it would any other normal piece. It reveals that there is no additional table containing piece metadata. For example, a table could have been used that stores the bounding-box dimensions of each orientation for playfield perimeter collision testing. But, no such table is used. Instead, the game simply performs tests on all 4 squares just prior to manipulating a piece.

In addition, the square coordinates can be any values; they are not restricted to [−2, 2]. Of course, values far outside of that range will yield unusable pieces that may not even fit in the playfield. More importantly, as discussed later, when a piece locks in place, the mechanism that clears out completed lines only scans row offsets −2 to 1 about the piece origin; a square with a y-coordinate outside of that range will go undetected at that time.

In the graphical depiction of the orientation table, rotation involves moving from a matrix to one of the matrices directly to its left or right, wrapping around the row if necessary. That concept is encoded in a table at $88EE.

88EE: 03 01 88F0: 00 02 88F2: 01 03 88F4: 02 00 88F6: 07 05 88F8: 04 06 88FA: 05 07 88FC: 06 04 88FE: 09 09 8900: 08 08 8902: 0A 0A 8904: 0C 0C 8906: 0B 0B 8908: 10 0E 890A: 0D 0F 890C: 0E 10 890E: 0F 0D 8910: 12 12 8912: 11 11

To make this clearer, each column above was transposed to a row in the table below.

Tu Tr Td Tl Jl Ju Jr Jd Zh Zv O Sh Sv Lr Ld Ll Lu Iv Ih Tl Tu Tr Td Jd Jl Ju Jr Zv Zh O Sv Sh Lu Lr Ld Ll Ih Iv Tr Td Tl Tu Ju Jr Jd Jl Zv Zh O Sv Sh Ld Ll Lu Lr Ih Iv

The mnemonics in the headers across the top can be interpreted as an index into the sequence or a key into a map. For instance, a counterclockwise rotation of Tu produces Tl, where as a clockwise rotation of Tu produces Tr.

The rotation table encodes chained sequences of orientation IDs and consequentially, it is possible to modify entries such that rotation will have the effect of transforming one Tetrimino type into another. That technique could potentially be used to take advantage of the unused row in the orientation table.

Preceding the rotation table is the code that accesses it.

88AB: LDA $0042 88AD: STA $00AE 88AF: CLC 88B0: LDA $0042 88B2: ASL 88B3: TAX 88B4: LDA $00B5 88B6: AND #$80 88B8: CMP #$80 88BA: BNE $88CF 88BC: INX 88BD: LDA $ 88EE ,X 88C0: STA $0042 88C2: JSR $948B 88C5: BNE $88E9 88C7: LDA #$05 88C9: STA $06F1 88CC: JMP $88ED aNotPressed: 88CF: LDA $00B5 88D1: AND #$40 88D3: CMP #$40 88D5: BNE $88ED 88D7: LDA $ 88EE ,X 88DA: STA $0042 88DC: JSR $948B 88DF: BNE $88E9 88E1: LDA #$05 88E3: STA $06F1 88E6: JMP $88ED restoreOrientationID: 88E9: LDA $00AE 88EB: STA $0042 88ED: RTS

For counterclockwise rotation, the index of the rotation table is computed by doubling the orientation ID. Adding 1 to that produces the index for clockwise rotation.

The x-coordinate, the y-coordinate and the orientation ID of the Tetrimino in play are stored at $0040, $0041 and $0042 respectively.

The code uses a temporary variable to backup the orientation ID. Later, after modifying the orientation, the code verifies that all 4 squares are within the bounds of the playfield and that none them overlap a previously locked square (the validation code is at $948B, beyond the snippet above). If the new orientation is invalid, it restores back to the original, preventing the player from rotating.

Including the D-pad, the NES controller has 8 buttons and the state of each is represented by a bit of $00B6.

7 6 5 4 3 2 1 0 A B Select Start Up Down Left Right

For example, $00B6 will contain $81 for as long as the player holds down A and Left.

On the other hand, $00B5 reports when buttons are pressed; the bits of $00B5 remain set for only one iteration of the game loop (1 rendered frame). The code uses $00B5 to respond to A and B. Each of them need to be released before they can be used again.

$00B5 and $00B6 are mirrors of $00F5 and $00F6 respectively. Code in subsequent sections uses these addresses interchangeably.

The Nintendo Tetris playfield consists of a matrix with 22 rows and 10 columns such that the top 2 rows are hidden from the player.

As seen in the snippet below, when a Tetrimino is spawned, it is always positioned at coordinates (5, 0) within the playfield.

98BA: LDA #$00 98BC: STA $00A4 98BE: STA $0045 98C0: STA $0041 98C2: LDA #$01 98C4: STA $0048 98C6: LDA #$05 98C8: STA $0040

Below, a 5×5 matrix is overlaid about that point.

None of the spawn matrices have squares above the origin. Meaning, when a Tetrimino is spawned, all 4 of its squares are immediately visible to the player. However, if the player quickly rotates the piece before it has an opportunity to drop, part of the piece could be hidden temporarily in the first 2 rows of the playfield.

In fact, normally, we think of game over as when the pile reaches the top. But, this is not entirely correct. The game actually ends when it is no longer possible to spawn the next piece. That is, all four cells of the playfield corresponding to the spawned Tetrimino's square positions must be empty before the piece can be introduced. A piece can be locked in place such that some of its squares end up in negatively numbered rows without ending the game; however, in Nintendo Tetris, negatively numbered rows are an abstraction that only applies to the active Tetrimino. After a piece is locked, only the squares in rows 0 and above are recorded in the playfield. Conceptually, it is as if the negatively number rows are automatically cleared after lock occurs. But, in reality, the game simply doesn't store the data, potentially truncating away the upper part of pieces.

The 20×10 visible region of the playfield is stored at $0400 in row-major order, each byte containing a background tile value. Empty cells are denoted by tile $EF, a solid black square.

Three lookup tables are used during spawning. Given an arbitrary orientation ID, the table at $9956 provides the spawn orientation ID for the associated Tetrimino type.

9956: 02 02 02 02 995A: 07 07 07 07 995E: 08 08 9960: 0A 9961: 0B 0B 9963: 0E 0E 0E 0E 9967: 12 12

This is easier to visualize with the representation below.

Tu Tr Td Tl Jl Ju Jr Jd Zh Zv O Sh Sv Lr Ld Ll Lu Iv Ih Td Td Td Td Jd Jd Jd Jd Zh Zh O Sh Sh Ld Ld Ld Ld Ih Ih

For instance, all J orientations map to Jd.

The table at $993B provides Tetrimino type for a given orientation ID.

993B: 00 00 00 00 993F: 01 01 01 01 9943: 02 02 9945: 03 9946: 04 04 9948: 05 05 05 05 994C: 06 06

For clarity, it is expressed in tabular form below.

Tu Tr Td Tl Jl Ju Jr Jd Zh Zv O Sh Sv Lr Ld Ll Lu Iv Ih T T T T J J J J Z Z O S S L L L L I I

The third lookup table is discussed in the following section.

Nintendo Tetris uses a 16-bit Fibonacci linear feedback shift register (LFSR) as a pseudorandom number generator (PRNG). The 16-bit value is stored big-endian at $0017–$0018. It is seeded to $8988, an arbitrary number.

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

Each successive pseudorandom number is generated by treating the value as a 17-bit number and setting the highest bit to the result of XORing bits 1 and 9 together. Then, the value is right shifted, tossing away the lowest bit.

This process takes place at $AB47.

AB47: LDA $00,X AB49: AND #$02 AB4B: STA $0000 AB4D: LDA $01,X AB4F: AND #$02 AB51: EOR $0000 AB53: CLC AB54: BEQ $AB57 AB56: SEC AB57: ROR $00,X AB59: INX AB5A: DEY AB5B: BNE $AB57 AB5D: RTS

Interestingly, the subroutine above was parameterized such that the caller can specify the width of the shift register and where to find it in memory. However, the same parameters are used throughout, suggesting that the developers may have borrowed this code from somewhere else.

For anyone who wants to repurpose it further, the algorithm is expressed in Java below.

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

And, that code can be compacted to a single line.

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

This PRNG repeatedly and deterministically generates 32,767 distinct values, each cycle starting with the original seed. That is one less than half of the possible numbers that can fit in the register and any value within that set can serve as the seed. Many of the values outside of the set will produce a chain that eventually leads to a number within the set. However, some starting numbers ultimately produce an endless sequence of zeros.

To gain a rough impression of this PRNG’s performance, I generated the visualization below of the values it produces based on a suggestion from RANDOM.ORG.

The image was created by treating the PRNG as a generator of pseudorandom bits rather than 16-bit integer numbers. Each pixel was colored based on the value in bit 0. The image is 128×256, covering the full sequence.

Aside from the faint bands across the top and left sides, it has the appearance of randomness. No obvious patterns manifest themselves.

From power on, the PRNG continually scrambles the register, executing at least once per frame. Not only does this occur on the title screen and the menu screens, it happens while Tetriminos are falling between spawns. Meaning, the number of frames that it takes the player to position a piece actually affects which piece comes up next. Essentially, the game taps directly into the inherent randomness of the human interfaced with it.

During spawning, the code at $9907 executes to pick the type of the new piece.>

9907: INC $001A 9909: LDA $0017 990B: CLC 990C: ADC $001A 990E: AND #$07 9910: CMP #$07 9912: BEQ $991C 9914: TAX 9915: LDA $994E,X 9918: CMP $0019 991A: BNE $9938 invalidIndex: 991C: LDX #$17 991E: LDY #$02 9920: JSR $AB47 9923: LDA $0017 9925: AND #$07 9927: CLC 9928: ADC $0019 992A: CMP #$07 992C: BCC $9934 992E: SEC 992F: SBC #$07 9931: JMP $992A 9934: TAX 9935: LDA $994E,X useNewSpawnID: 9938: STA $0019 993A: RTS

The program maintains a count at $001A of the number of pieces spawned since power on. It is incremented by the first line of the subroutine and because it is a 1-byte counter, it loops back to 0 every 256 spawns. Since the counter is not reset between games, prior play history actually contributes to the piece selection process. This is another way that the game takes advantage of the player for a source of randomness.

The subroutine transforms the high byte of the pseudorandom number ($0017) into Tetrimino type and it uses that as an index into a table located at $994E to convert the type into a spawn orientation ID.

994E: 02 994F: 07 9950: 08 9951: 0A 9952: 0B 9953: 0E 9954: 12

The first step of the transformation involves adding the spawn count to the high byte. A mask is applied to retain only the lower 3 bits. If the result is not 7, then it is a valid Tetrimino type and if it is not the same as the previously selected piece, then it is used as an index into the spawn table. Otherwise, the next pseudorandom number is generated and a mask is applied to retain the lower 3 bits of the high byte before adding the prior spawn orientation ID. Finally, a modulo operation is performed to obtain a valid Tetrimino type, which is used as an index into the spawn table.

Since the processor does not directly support modulo, the operator is emulated by repeatedly subtracting 7 until the result is less than 7. Modulo is applied to the sum of the masked high byte and the prior spawn orientation ID. The maximum value of that sum is 25. Hence, at most, it requires 3 iterations to reduce that to the remainder 4.

At the start of each game, the spawn orientation ID ($0019) is initialized to Tu ($00), a value potentially used at $9928 during the first spawn.

Using the prior spawn orientation ID in the sum as opposed to the prior Tetrimino type introduces a bias because the spawn orientation ID values are not evenly distributed. This is illustrated in the table below.

$00 $02 $07 $08 $0A $0B $0E $12 0 2 0 1 3 4 0 4 1 3 1 2 4 5 1 5 2 4 2 3 5 6 2 6 3 5 3 4 6 0 3 0 4 6 4 5 0 1 4 1 5 0 5 6 1 2 5 2 6 1 6 0 2 3 6 3 7 2 0 1 3 4 0 4

Each cell contains a Tetrimino type produced by adding a spawn orientation ID (column) to a 3-bit value (row) and then applying modulo 7 to the sum. Each row contains duplicates because $07 and $0E are both evenly divisible by 7 and $0B and $12 have a common remainder. Rows 0 and 7 are the same because they are 7 apart.

There are 56 possible input combinations and if the resultant Tetrimino types were evenly distributed, the expectation is that in the table above each type should appear exactly 8 times. But, as shown below, that is not the case.

Type Frequency T 9 J 8 Z 8 O 8 S 9 L 7 I 7

T and S appear more frequently and L and I less frequently. But, the biased code that uses the prior spawn orientation ID is not executed every time that the subroutine is called.

Assume that the PRNG actually produces a sequence of uniformly distributed statistically independent values. That is actually a fair assumption considering how the game attempts to appropriate randomness from the human player. Adding the spawn count at $990C will not affect the distribution because the count increases uniformly between calls. The application of a bit mask at $990E is equivalent to applying modulo 8, which also does not affect the distribution. Consequentially, the check at $9910 jumps to invalidIndex 1/8 of the time. And, the chance of arriving at the check at $9918 that compares the newly selected piece against the prior piece is 7/8 where the probability of a match is 1/7. This means that there is additional chance of 7/8 × 1/7 = 1/8 of it ending up at invalidIndex. Overall, there is a 25% chance of using the biased code and a 75% chance of using code that picks Tetriminos uniformly.

In a set of 224 spawned Tetriminos, the mathematical expectation is 32 instances of each type. But, the code will actually produce the following distribution.

Type Frequency T 33 J 32 Z 32 O 32 S 33 L 31 I 31

Meaning, in clearing 90 rows and reaching level 9, the player will encounter one additional T and S and one fewer L and I than statistically expected.

Tetriminos are picked with the following probabilities.

Type Probability T 14.73% J 14.29% Z 14.29% O 14.29% S 14.73% L 13.84% I 13.84%

Apparently, at least in Nintendo Tetris, there is some truth behind the notation that the I Tetrimino never shows up when you need it.

Nintendo Tetris provides Delayed Auto Shift (DAS). Pressing Left or Right immediately shifts the active Tetrimino 1 cell horizontally. While, holding down one of those direction buttons causes the game to automatically shift the piece every 6 frames after an initial delay of 16 frames.

This style of horizontal movement is controlled by code at $89AE.

89AE: LDA $0040 89B0: STA $00AE 89B2: LDA $00B6 89B4: AND #$04 89B6: BNE $8A09 89B8: LDA $00B5 89BA: AND #$03 89BC: BNE $89D3 89BE: LDA $00B6 89C0: AND #$03 89C2: BEQ $8A09 89C4: INC $0046 89C6: LDA $0046 89C8: CMP #$10 89CA: BMI $8A09 89CC: LDA #$0A 89CE: STA $0046 89D0: JMP $89D7 resetAutorepeatX: 89D3: LDA #$00 89D5: STA $0046 buttonHeldDown: 89D7: LDA $00B6 89D9: AND #$01 89DB: BEQ $89EC 89DD: INC $0040 89DF: JSR $948B 89E2: BNE $8A01 89E4: LDA #$03 89E6: STA $06F1 89E9: JMP $8A09 notPressingRight: 89EC: LDA $00B6 89EE: AND #$02 89F0: BEQ $8A09 89F2: DEC $0040 89F4: JSR $948B 89F7: BNE $8A01 89F9: LDA #$03 89FB: STA $06F1 89FE: JMP $8A09 restoreX: 8A01: LDA $00AE 8A03: STA $0040 8A05: LDA #$10 8A07: STA $0046 8A09: RTS

Similar to the rotation code, a temporary variable is used to backup the x-coordinate in case the new position turns out to be invalid.

Also, note that a check prevents shifting the piece while the player is pressing Down.

The speed at which Tetriminos automatically drop is a function of the level. The speeds are encoded as rendered frames per drop in a table located at $898E. Since the NES runs at 60.0988 frames/sec, the period between drops and the speed can be computed.

Level Frames/drop Period (sec/drop) Speed (drops/sec) 0 48 .799 1.25 1 43 .715 1.40 2 38 .632 1.58 3 33 .549 1.82 4 28 .466 2.15 5 23 .383 2.61 6 18 .300 3.34 7 13 .216 4.62 8 8 .133 7.51 9 6 .100 10.02 10–12 5 .083 12.02 13–15 4 .067 15.05 16–18 3 .050 20.03 19–28 2 .033 30.05 29+ 1 .017 60.10

The table only contains 30 entries. Above level 29, the frames per drop is locked at 1.

An integer number of frames per drop is not a very granular way of representing speed. As shown in the chart below, speed increases exponentially with level. In fact, level 29 is twice as fast as level 28.

At 1 frame/drop, the player has at most 1/3 of a second to position the piece once it starts moving. At that drop speed, the DAS does not permit the piece to reach the edges of the playfield before lock, quickly ending the game for most humans. However, some players, most notably Thor Aackerlund, overcome the DAS by rapidly vibrating the D-pad. From the shift code above, as long as the horizontal direction button is released exactly every other frame, it is possible to shift the Tetrimino at half the rate that it drops on levels 29 and above. That is the theoretical maximum, but any vibration of the thumb above 3.75 presses/sec will overcome the initial 16 frame delay.

If a timed automatic drop and a player controlled soft drop (pressing Down) coincide within the same frame, the effect is not cumulative. Either or both of those events will cause the piece to advance downward exactly one cell in that frame.

The logic that controls the drop is located at $8914.

8914: LDA $004E 8916: BPL $8922 8918: LDA $00B5 891A: AND #$04 891C: BEQ $8989 891E: LDA #$00 8920: STA $004E 8922: BNE $8939 playing: 8924: LDA $00B6 8926: AND #$03 8928: BNE $8973 892A: LDA $00B5 892C: AND #$0F 892E: CMP #$04 8930: BNE $8973 8932: LDA #$01 8934: STA $004E 8936: JMP $8973 autorepeating: 8939: LDA $00B6 893B: AND #$0F 893D: CMP #$04 893F: BEQ $894A 8941: LDA #$00 8943: STA $004E 8945: STA $004F 8947: JMP $8973 downPressed: 894A: INC $004E 894C: LDA $004E 894E: CMP #$03 8950: BCC $8973 8952: LDA #$01 8954: STA $004E 8956: INC $004F drop: 8958: LDA #$00 895A: STA $0045 895C: LDA $0041 895E: STA $00AE 8960: INC $0041 8962: JSR $948B 8965: BEQ $8972 8967: LDA $00AE 8969: STA $0041 896B: LDA #$02 896D: STA $0048 896F: JSR $9CAF 8972: RTS lookupDropSpeed: 8973: LDA #$01 8975: LDX $0044 8977: CPX #$1D 8979: BCS $897E 897B: LDA $ 898E ,X noTableLookup: 897E: STA $00AF 8980: LDA $0045 8982: CMP $00AF 8984: BPL $8958 8986: JMP $8972 incrementAutorepeatY: 8989: INC $004E 898B: JMP $8972

The frames-per-drop table is referenced at label lookupDropSpeed. As mentioned above, the speed defaults to 1 drop/frame when the level is 29 or higher.

The fallTimer ($0045) triggers a drop when it reaches the dropSpeed ($00AF). The fallTimer is incremented at $8892, outside of the snippet above. It is reset to 0 upon an automatic or soft drop.

The variable autorepeatY ($004E) is initialized to $0A (at $8739), which is interpreted as −96. The condition at the very top causes an opening entry delay. The very first Tetrimino remains suspended at the spawn location until autorepeatY is incremented to 0, which takes 1.6 seconds. However, pressing down during this phase immediately sets autorepeatY to 0. Interestingly, it is possible to shift and rotate during the opening entry delay without cancelling it.

autorepeatY is incremented if Down is held. When it reaches 3, a soft drop happens and autorepeatY is set to 1. Consequentially, the initial soft drop requires 3 frames, but from there it repeats every other frame.

In addition, autorepeatY will advance from 0 to 1 only if the game detects that the player just pressed Down (via $00B5) as opposed to detecting that Down is held. This is significant because autorepeatY is reset to 0 when a Tetrimino is spawned (at $98E8), providing an important feature: if the player soft drops a piece into lock and he inadvertently continues to keep Down held through the subsequent spawn, which is a common occurrence at higher levels, it will not cause the new piece to soft drop. To convey that intension, the player must release Down before subsequently reusing it.

Soft drops can potentially increase the score. holdDownPoints ($004F) is incremented for each drop, but it is reset to 0 when Down is released. As a result, to gain points, the Tetrimino needs to be soft dropped into lock. Any brief soft drop that might occur prior along the way in positioning the piece will not contribute to the score. The score is updated at $9BFE and holdDownPoints is reset to 0 shortly afterward at $9C2F.

A check that prevents the player from soft dropping while shifting complicates gaining points. It means that the final move must be down.

When a drop occurs, tetriminoY ($0041) is backed up to originalY ($00AE). If the new position created by incrementing tetriminoY turns out to be invalid (it either pushed through the floor of the playfield or it overlapped other existing squares), then the Tetrimino was actually supported in the prior position. In that event, tetriminoY is restored and the piece is considered locked. This means that the lock delay—the maximum number of frames that a Tetrimino waits while supported before locking—is equal to the drop delay.

Nintendo Tetris does not support hard drops.

The Nintendo Tetris Instruction Booklet contains an illustrated lesson on how to slide:

A slide involves shifting across the surface of other pieces or across the floor of the playfield usually with the intension of positioning a piece under an overhang. It is possible to slide until the fall timer reaches the drop speed at which point the piece will lock. Below is an animated example.

On the other hand, spins rotate pieces into spaces unreachable by any other means (see below).

Like slides, spins would not be possible without lock delay. But, spins also exploit the way that the game manipulates pieces. Prior to moving or rotating a piece, the game verifies that all of the Tetrimino's squares when repositioned are situated on empty cells within the bounds of the playfield. That check, which is listed below, does not inhibit rotation through adjacent solid blocks.

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 94A4: LDY #$00 94A6: LDA #$04 94A8: STA $00AA 94AA: LDA $8A9C,X 94AD: CLC 94AE: ADC $0041 94B0: ADC #$02 94B2: CMP #$16 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 94C8: LDA $8A9C,X 94CB: CLC 94CC: ADC $00AD 94CE: TAY 94CF: LDA ($B8),Y 94D1: CMP #$EF 94D3: BCC $94E9 94D5: LDA $8A9C,X 94D8: CLC 94D9: ADC $0040 94DB: CMP #$0A 94DD: BCS $94E9 94DF: INX 94E0: DEC $00AA 94E2: BNE $94AA 94E4: LDA #$00 94E6: STA $00A8 94E8: RTS 94E9: LDA #$FF 94EB: STA $00A8 94ED: RTS

As discussed in an earlier section, each row of the orientation table contains 12 bytes; hence, the index into the table is computed by multiplying the active Tetrimino’s orientation ID by 12. As shown below, all of the multiplications in the subroutine are performed via shifts and additions.

index = (orientationID << 3) + (orientationID << 2); (cellY << 3) + (cellY << 1)

Each iteration of the loop offsets the Tetrimino’s position by the relative coordinates of one of the squares from the orientation table to obtain the corresponding playfield cell location. Then, it verifies that the cell coordinates are within the bounds of the playfield and that the cell itself is empty.

The comments below better reflect the way the row range check is performed.

94AA: LDA $8A9C,X 94AD: CLC 94AE: ADC $0041 94B0: ADC #$02 94B2: CMP #$16 94B4: BCS $94E9

In addition to cells of the visible rows, the code treats the cells of the 2 hidden rows above the playfield as legal square positions without using a compound condition. This works because in two's-complement, negative numbers represented by single-byte variables are equivalent to values above 127. In this case, the minimum value of cellY is −2, which is stored as $FE (254 in decimal).

The playfield index is sum of cellY multiplied by 10 and cellX. However, when cellY is −1 ($FF = 255) or −2 ($FE = 254), the product yields −10 ($F6 = 246) and −20 ($EC = 236) respectively. When in range, cellX can be as large as 9, producing a maximum index of 246 + 9 = 255, well past the end of the playfield. However, the game initializes $0400–$04FF to $EF (the empty tile) providing 56 extra bytes of padding.

Oddly, the cellX range check is performed after the playfield cell is examined. But, it will function correctly in either order. The range check also avoids a compound condition as denoted by the revised comments below.

94D5: LDA $8A9C,X 94D8: CLC 94D9: ADC $0040 94DB: CMP #$0A 94DD: BCS $94E9

Further examples of spins made possible by the way this code validates positions appear below.

As shown below, it is also possible to slide into a spin.

The AI takes advantage of the full range of motion available in Nintendo Tetris including slides and spins.

After reaching level 30, the level appears to reset to zero as shown below.

But, level 31 reveals that something different is going on:

The displayed level values are located in a table at $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

As illustrated below, the pattern table is organized such that tiles $00 to $0F are the glyphs for 0 to F. This means that displaying a digit of a number, decimal or hexadecimal, involves using that digit value directly as the index into the pattern table. In this case, the level values are stored as binary-coded decimal (BCD); each nibble of each byte in the sequence is a tile value.

Unfortunately, the game designers apparently assumed that no one would get past level 29 and consequentially, they decided to put only 30 entries into the table. The strange displayed values are the various bytes beyond. Only a single byte (at $0044) is used for level number, causing the game to slowly cycle through the 256 values shown below.

00 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 1 16 17 18 19 20 21 22 23 24 25 26 27 28 29 00 0A 2 14 1E 28 32 3C 46 50 5A 64 6E 78 82 8C 96 A0 AA 3 B4 BE C6 20 E6 20 06 21 26 21 46 21 66 21 86 21 4 A6 21 C6 21 E6 21 06 22 26 22 46 22 66 22 86 22 5 A6 22 C6 22 E6 22 06 23 26 23 85 A8 29 F0 4A 4A 6 4A 4A 8D 07 20 A5 A8 29 0F 8D 07 20 60 A6 49 E0 7 15 10 53 BD D6 96 A8 8A 0A AA E8 BD EA 96 8D 06 8 20 CA A5 BE C9 01 F0 1E A5 B9 C9 05 F0 0C BD EA 9 96 38 E9 02 8D 06 20 4C 67 97 BD EA 96 18 69 0C A 8D 06 20 4C 67 97 BD EA 96 18 69 06 8D 06 20 A2 B 0A B1 B8 8D 07 20 C8 CA D0 F7 E6 49 A5 49 C9 14 C 30 04 A9 20 85 49 60 A5 B1 29 03 D0 78 A9 00 85 D AA A6 AA B5 4A F0 5C 0A A8 B9 EA 96 85 A8 A5 BE E C9 01 D0 0A A5 A8 18 69 06 85 A8 4C BD 97 A5 B9 F C9 04 D0 0A A5 A8 38 E9 02 85 A8 4C BD 97 A5 A8

The first 20 successive values are actually another table that stores the offsets into the playfield for each of the 20 rows.

96D6: 00 96D7: 0A 96D8: 14 96D9: 1E 96DA: 28 96DB: 32 96DC: 3C 96DD: 46 96DE: 50 96DF: 5A 96E0: 64 96E1: 6E 96E2: 78 96E3: 82 96E4: 8C 96E5: 96 96E6: A0 96E7: AA 96E8: B4 96E9: BE

Since the playfield starts at $0400 and each row contains 10 cells, the address of an arbitrary cell is:

$0400 + 10 * y + x

Considering that the processor does not directly support multiplication, that lookup table provides an extremely fast way of obtaining the product.

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

A related table occupies the next 40 bytes. It contains 20 addresses, stored little endian, into nametable 0, the region of VRAM holding background tile values. They are pointers to the rows of the playfield offset by $06.

The remaining bytes that produce the displayed level values are instructions.

The number of completed lines and the Tetrimino statistics occupy 2 bytes each at the following locations.

Addresses Count 0050 – 0051 Lines 03F0 – 03F1 T 03F2 – 03F3 J 03F4 – 03F5 Z 03F6 – 03F7 O 03F8 – 03F9 S 03FA – 03FB L 03FC – 03FD I

The values are essentially stored as little endian 16-bit packed BCD. For example, a line count of 123 is represented below. The bytes appear right-to-left to order the decimal digits.

However, the game designers assumed that none of the values would ever exceed 999. As such, the display logic properly treats the first byte as packed BCD, each nibble serving as a tile value. But, the entire second byte effectively acts the upper decimal digit. Whenever the lower digits rolls over from 99 to 00, the second byte is simply incremented. Ultimately, the second byte cycles through all 256 tiles. Below, you can see this mid-action.

When a row is cleared, the following code executes to increment the line count.

9BA8: INC $0050 9BAA: LDA $0050 9BAC: AND #$0F 9BAE: CMP #$0A 9BB0: BMI $9BC7 9BB2: LDA $0050 9BB4: CLC 9BB5: ADC #$06 9BB7: STA $0050 9BB9: AND #$F0 9BBB: CMP #$A0 9BBD: BCC $9BC7 9BBF: LDA $0050 9BC1: AND #$0F 9BC3: STA $0050 9BC5: INC $0051

Checks are performed on the middle and lowest digits to keep them between 0 and 9. But, the highest digit can be incremented indefinitely.

If the lowest digit is 0 after incrementing the line count, then the player just completed a set of 10 rows and the level number needs to be incremented as well. As you can see in the code that follows, an additional check is performed before incrementing the level.

9BC7: LDA $0050 9BC9: AND #$0F 9BCB: BNE $9BFB 9BCD: JMP $9BD0 9BD0: LDA $0051 9BD2: STA $00A9 9BD4: LDA $0050 9BD6: STA $00A8 9BD8: LSR $00A9 9BDA: ROR $00A8 9BDC: LSR $00A9 9BDE: ROR $00A8 9BE0: LSR $00A9 9BE2: ROR $00A8 9BE4: LSR $00A9 9BE6: ROR $00A8 9BE8: LDA $0044 9BEA: CMP $00A8 9BEC: BPL $9BFB 9BEE: INC $0044

The second check is related to the selected starting level. To advance to some level X, the player must clear 10X rows regardless of the starting level. For example, if the player starts at level 5, he will remain there until he clears 60 rows, at which point he will advance to level 6. From there, each additional 10 rows will increment the level number.

To perform this check, the completed lines value is copied from $0050–$0051 to $00A8–$00A9. Then, the copy is right-shifted 4 times, which for packed BCD is equivalent to dividing by 10. The lowest decimal digit is discarded and the upper and middle decimal digits shift over one position ending up as the nibbles of $00A8.

However, at $9BEA the level number is directly compared against the packed BCD value in $00A8. A table lookup to convert the BCD value to decimal is missing, an apparent bug. For instance, in the image above, the level number would be compared against $12 (18 in decimal) instead of 12. Consequentially, if the player decided to start at level 17, the level would actually advance at 120 lines because 18 exceeds 17.

The table below depicts the expected number of lines required to advance at each starting level against what actually happens due to the bug.

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 10 20 30 40 50 60 70 80 90 100 100 100 100 100 100 100 110 120 130 140

Expected matches actual for starting levels 0–9. In fact, starting level 9 only works coincidentally; 10–15 also advance at 100 lines because $10 is 16 in decimal. The largest gap between expected and actual is 60 lines.

I suspect that the bug is related to a late stage design change. Take a look at the menu screen that allows the player to select the starting level below.

There is no indication of how to start beyond level 9. But, the Nintendo Tetris Instruction Booklet reveals the secret:

That hidden feature feels like an afterthought. It might have been introduced very close to the target release date, limiting the amount of testing that could be performed.

The starting line check actually contains a second bug related to values going out of range. The code comments are revised below to better convey what is happening at the lower level.

9BE8: LDA $0044 9BEA: CMP $00A8 9BEC: BPL $9BFB 9BEE: INC $0044

The comparison is performed by subtraction and testing the sign of the result. But, a 1-byte signed number is limited to the range −128 to 127. If the difference is less than −128, then it wraps around and the result is a positive number. The concept is conveyed in the further revised comments below.

9BE8: LDA $0044 9BEA: CMP $00A8 9BEC: BPL $9BFB 9BEE: INC $0044

In evaluating when the difference ends up in that range, consider that the level number wraps around to 0 when it is incremented beyond 255 and $00A8 could potentially contain any value because its upper nibble originates from $0051, which is incremented indefinitely.

These effects collude together to produce periods where the level number erroneously remains constant. The periods occur at regular intervals of 2900 lines starting at 2190 lines and they last for 800 lines. For instance, from 2190 (L90) to 2990 (T90), the level remains as $DB (96), as shown below.

The next period happens from 5090 to 5890, the level fixed at $AD (06). And, during the periods, the color palette also remains the same.

The Tetrimino tiles are assigned 4 distinct colors in each level. The colors originate from a table at $984C. Its entries are reused every 10 levels.

984C: 0F 30 21 12 9850: 0F 30 29 1A 9854: 0F 30 24 14 9858: 0F 30 2A 12 985C: 0F 30 2B 15 9860: 0F 30 22 2B 9864: 0F 30 00 16 9868: 0F 30 05 13 986C: 0F 30 16 12 9870: 0F 30 27 16

From left-to-right, the columns of that table correspond to the black, white, blue and red regions in the image below respectively.

The values refer to the NES color palette.

The first 2 colors of every entry are black and white. However, the first color is actually ignored; regardless of its value, it is treated as a transparent color through which the solid black background is exposed.

The color table is accessed in a subroutine located at $9808.

9808: LDA $0064 980A: CMP #$0A 980C: BMI $9814 980E: SEC 980F: SBC #$0A 9811: JMP $980A 9814: ASL 9815: ASL 9816: TAX 9817: LDA #$00 9819: STA $00A8 981B: LDA #$3F 981D: STA $2006 9820: LDA #$08 9822: CLC 9823: ADC $00A8 9825: STA $2006 9828: LDA $984C,X 982B: STA $2007 982E: LDA $984D,X 9831: STA $2007 9834: LDA $984E,X 9837: STA $2007 983A: LDA $984F,X 983D: STA $2007 9840: LDA $00A8 9842: CLC 9843: ADC #$10 9845: STA $00A8 9847: CMP #$20 9849: BNE $981B 984B: RTS

The index into the color table is based on the level number modulo 10. The loop copies an entry to the palette tables in VRAM.

Modulo is emulated by repeatedly subtracting 10 until the result is less than 10. Below, the top of the subroutine is listed with revised comments to reflect this.

9808: LDA $0064 980A: CMP #$0A 980C: BMI $9814 980E: SEC 980F: SBC #$0A 9811: JMP $980A

However, as discussed in the prior section, comparison involves subtracting and branching based on the sign of the difference. And, a 1-byte signed number is limited to the range −128 to 127. The updated comments below factor in those ideas.

9808: LDA $0064 980A: CMP #$0A 980C: BMI $9814 980E: SEC 980F: SBC #$0A 9811: JMP $980A

The comments are simplified further below.

9808: LDA $0064 980A: CMP #$0A 980C: BMI $9814 980E: SEC 980F: SBC #$0A 9811: JMP $980A

This formulation exposes a bug in the code. The modulo operation is completely bypassed for levels 138 and up. Instead, the index is directly assigned to the level number, providing access to the bytes well beyond the end of the color table. This can even result in nearly invisible Tetriminos as shown below.

The colors of all 256 levels are depicted below. The tiles are arranged in 10 columns to reflect the cyclic application of the color table that breaks down at level 138. The row and column headers are in decimal.

After 255, the level number loops back to 0.

Also, as mentioned in the prior section, some levels will not advance until 800 lines are cleared. The colors remain constant throughout those long levels.

The game mode, maintained at $00C0, determines which of the various screens and menus is currently presented to the user.

Value Description 00 Legal Screen 01 Title Screen 02 Game Type Menu 03 Level and Height Menu 04 Play / High Score Screen / Ending / Pause 05 Demo

As demonstrated below, the game contains a cleverly written subroutine that acts as a switch statement via a little endian jump table located directly after the call.

8161: LDA $00C0 8163: JSR $AC82 8166: 00 82 8168: 4F 82 816A: D1 82 816C: D7 83 816E: 5D 81 8170: 5D 81

The listing above also reveals the addresses of all the game modes. Note that play and demo mode use the same code.

The subroutine never returns. Instead, the code takes advantage of the return address; it normally points to the instruction immediately following the jump-to-subroutine call (minus 1 byte), but in this case, it points to the jump table. The return address is popped off the stack and stored at $0000–$0001. With the jump table address captured, the code uses the value in the A register as the index and jumps accordingly.

AC82: ASL AC83: TAY AC84: INY AC85: PLA AC86: STA $0000 AC88: PLA AC89: STA $0001 AC8B: LDA ($00),Y AC8D: TAX AC8E: INY AC8F: LDA ($00),Y AC91: STA $0001 AC93: STX $0000 AC95: JMP ($0000)

The code is able to take advantage of this switching subroutine as long as the indices are all near 0 and there are few or no gaps between the possible cases.

The game starts up with a screen that displays the legal notice.

At the bottom, the screen aptly credits Alexey Pazhitnov (Alexéy Pájitnov) for conceiving, designing and programming the original Tetris. In 1984, while working as a computer engineer at the Dorodnitsyn Computing Centre, a leading research institute of the Russian Academy of Sciences in Moscow, he developed the prototype on an Electronika 60, a Soviet clone of the DEC LSI-11. It was designed for green monochrome text mode, using pairs of brackets [] to represent squares. With the help of 16-year-old high school student Vadim Gerasimov and computer engineer Dmitry Pavlovsky, a few days after its inception, the prototype was ported to the IBM PC with Turbo Pascal under MS DOS. They collectively improved the game over 2 years introducing features such as Tetrimino colors and statistics and more importantly timing and graphics code that would make the game run consistently on the multitude of PC models and clones.

Unfortunately, due nature of the Soviet Union at the time, their attempts to monetize the game failed and they ultimately decided to share the PC version with friends gratis. From there, Tetris spread virally across the country and outward, copied from floppy to floppy. However, since the game was developed by employees of a government institution, it was owned by the state and in 1987, the organization in charge of international trade of electronic technology, Electronorgtechnica (ELORG) became responsible for licensing the game. The V/O abbreviation in the legal screen might be short for, "Version Originale".

A British software company called Andromeda attempted to secure the rights to Tetris and before completing the deal, they sublicensed the game to other vendors, such as the United Kingdom computer game publisher Mirrorsoft. Mirrorsoft, in turn, sublicensed it further to Tengen, a subsidiary of Atari Games. And, Tengen provided Bullet-Proof Software with the rights to develop the game for computers and consoles in Japan, resulting in Tetris for Nintendo's Famicom, the legal screen of which is shown below.

Interestingly, in that version, high school student Vadim Gerasimov is credited as the original designer and programmer.

In seeking the handheld rights for the upcoming Game Boy, Nintendo ended up using Bullet-Proof Software to successfully broker a deal directly with ELORG. In the process, ELORG revised its contract with Andromeda, clarifying that Andromeda acquired computer and arcade rights only. This forced Bullet-Proof Software to pay royalties to ELORG for all the Famicom cartridges it sold since the rights they got from Tengen were bogus. But, by appeasing ELORG, it helped Bullet-Proof Software to ultimately obtain the worldwide console rights for Nintendo.

Bullet-Proof Software sublicensed the handheld rights to Nintendo and they co-developed Game Boy Tetris as shown in the legal screen below.

With the worldwide console rights, Nintendo developed the version of Tetris for the NES discussed in this article. And, Bullet-Proof Software subsequently sublicensed the rights from Nintendo to enable them to continue to sell Famicom cartridges in Japan.

A complex legal battle ensued. Nintendo and Tengen each demanded that the other stop production and sale of their version of the game. Nintendo ultimately prevailed and hundreds of thousands of Tengen Tetris cartridges still in their boxes were scrapped. The judgment also blocked several other companies like Mirrorsoft from manufacturing a console version.

Pazhitnov never received any royalties from ELORG or the Soviet government. However, in 1991, he moved to the United States and in 1996, with backing from Henk Rogers, owner of Bullet-Proof Software, he cofounded The Tetris Company, enabling him to profit from mobile and modern console versions.

As intriguing as it is to view the legal screen as a window into the humble origin of the game and the intellectual properties rights battle that followed, as far as most players are concerned, it is just an annoying screen that takes forever to disappear. The delay is established by 2 counters that tick down in succession from 255 to 0. The first phase is unskippable, but the second phase can be cut short by pressing Start. As such, the legal screen appears for a minimum of 4.25 seconds and up to 8.5 seconds. Though, I imagine that most players give up pressing Start during the first interval forcing them to wait through the whole thing.

The timing of the phases, as well as the rest of the game, is regulated by a non-maskable interrupt handler that is invoked at the start of each vertical blanking interval, the brief period between the rendering of television frames. Meaning, every 16.6393 milliseconds, normal program flow is interrupted by the following code.

8005: PHA 8006: TXA 8007: PHA 8008: TYA 8009: PHA 800A: LDA #$00 800C: STA $00B3 800E: JSR $804B 8011: DEC $00C3 8013: LDA $00C3 8015: CMP #$FF 8017: BNE $801B 8019: INC $00C3 801B: JSR $AB5E 801E: LDA $00B1 8020: CLC 8021: ADC #$01 8023: STA $00B1 8025: LDA #$00 8027: ADC $00B2 8029: STA $00B2 802B: LDX #$17 802D: LDY #$02 802F: JSR $AB47 8032: LDA #$00 8034: STA $00FD 8036: STA $2005 8039: STA $00FC 803B: STA $2005 803E: LDA #$01 8040: STA $0033 8042: JSR $9D51 8045: PLA 8046: TAY 8047: PLA 8048: TAX 8049: PLA 804A: RTI

The handler starts out by pushing the values of the primary registers onto the stack and popping them off at the end to avoid interfering with the interrupted task. The call to render() updates VRAM, converting memory model representations into something that appears on screen. Next, the handler decrements the first legal screen counter if it is greater than zero. The initializeOAM() call performs a step required by the frame generation hardware. The handler continues by incrementing the frame counter, a 16-bit little endian value stored at $00B1–$00B2, which is used in various places for controlled timing. After which, the next pseudorandom number is generated; as mentioned, this occurs at least once per frame regardless of the mode. At $8040, the vertical blanking interval flag is set to indicate that the handler just executed. Finally, the controller buttons are polled; the behavior of that subroutine is described further in the Demo section.

The verticalBlankingInterval flag is used by the subroutine listed below. It spins until an execution of the interrupt handler occurs.

AA2F: JSR $E000 AA32: LDA #$00 AA34: STA $0033 AA36: NOP AA37: LDA $0033 AA39: BEQ $AA37 AA3B: LDA #$FF AA3D: LDX #$02 AA3F: LDY #$02 AA41: JSR $AC6A AA44: RTS

That blocking subroutine is used by the 2 timing phases of the legal screen, shown below, which run consecutively.

8236: LDA #$FF 8238: JSR $A459 ... A459: STA $00C3 A45B: JSR $AA2F A45E: LDA $00C3 A460: BNE $A45B A462: RTS

823B: LDA #$FF 823D: STA $00A8 823F: LDA $00F5 8241: CMP #$10 8243: BEQ $824C 8245: JSR $AA2F 8248: DEC $00A8 824A: BNE $823F 824C: INC $00C0

The AI Lua Script bypasses the delay by forcing the counters to 0.

The demo presents approximately 80 seconds of pre-recorded gameplay. It takes advantage of the same engine that runs during play as opposed to simply displaying a movie file. Two tables are involved in the reproduction. The first, located at $DF00, stores the following Tetrimino spawn sequence:

T J T S Z J T S Z J S Z L Z J T T S I T O J S Z L Z L I O L Z L I O J T S I T O J

During spawning, the piece is either randomly selected or it is pulled from the table depending on the mode. The switch occurs at $98EB.

98EB: LDA $00C0 98ED: CMP #$05 98EF: BNE $9903 98F1: LDX $00D3 98F3: INC $00D3 98F5: LDA $DF00,X 98F8: LSR 98F9: LSR 98FA: LSR 98FB: LSR 98FC: AND #$07 98FE: TAX 98FF: LDA $994E,X 9902: RTS

Tetrimino type is extracted from bits 6, 5 and 4 of each byte. Occasionally, this yields a value of $07, an invalid type. However, the spawn table ($994E) used to convert Tetrimino type into orientation ID is actually sandwiched between 2 related tables:

993B: 00 00 00 00 993F: 01 01 01 01 9943: 02 02 9945: 03 9946: 04 04 9948: 05 05 05 05 994C: 06 06

994E: 02 994F: 07 9950: 08 9951: 0A 9952: 0B 9953: 0E 9954: 12

9956: 02 02 02 02 995A: 07 07 07 07 995E: 08 08 9960: 0A 9961: 0B 0B 9963: 0E 0E 0E 0E 9967: 12 12

The value $07 causes it to read past the end of the table into the next one, bearing Td ($02).

Due to this effect, this scheme can provide an unlimited yet reproducible sequence of pseudorandom spawn orientation IDs. The code will function given any arbitrary address to a varying byte sequence making it impossible to determine where the table ends. In fact, the sequence at $DF00 may actually be part of something entirely unrelated especially considering that the purpose of the remaining 5 nonzero bits is unclear and that the generated sequence appears to contain repetition.

The index into the table ($00D3) is reset to 0 during demo mode initialization at $872B.

The second demo table contains a recording of the gamepad buttons encoded in pairs of bytes. The bits of the first byte correspond to the asserted buttons.

7 6 5 4 3 2 1 0 A B Select Start Up Down Left Right

The second byte stores the number of frames that the button combination was applied.

The table spans $DD00–$DEFF, composed of 256 pairs. It is accessed by the subroutine at $9D5B.

9D5B: LDA $00D0 9D5D: CMP #$FF 9D5F: BEQ $9DB0 9D61: JSR $AB9D 9D64: LDA $00F5 9D66: CMP #$10 9D68: BEQ $9DA3 9D6A: LDA $00CF 9D6C: BEQ $9D73 9D6E: DEC $00CF 9D70: JMP $9D9A finishedMove: 9D73: LDX #$00 9D75: LDA ($D1,X) 9D77: STA $00A8 9D79: JSR $9DE8 9D7C: LDA $00CE 9D7E: EOR $00A8 9D80: AND $00A8 9D82: STA $00F5 9D84: LDA $00A8 9D86: STA $00CE 9D88: LDX #$00 9D8A: LDA ($D1,X) 9D8C: STA $00CF 9D8E: JSR $9DE8 9D91: LDA $00D2 9D93: CMP #$DF 9D95: BEQ $9DA2 9D97: JMP $9D9E moveInProgress: 9D9A: LDA #$00 9D9C: STA $00F5 holdButtons: 9D9E: LDA $00CE 9DA0: STA $00F7 9DA2: RTS startButtonPressed: 9DA3: LDA #$DD 9DA5: STA $00D2 9DA7: LDA #$00 9DA9: STA $00B2 9DAB: LDA #$01 9DAD: STA $00C0 9DAF: RTS

Since the demo buttons table is 512 bytes long, a 2-byte index is required to access it. The index is stored little endian at $00D1–$00D2. It is initialized to the address of the table at $872D and it is incremented via the following code.

9DE8: LDA $00D1 9DEA: CLC 9DEB: ADC #$01 9DED: STA $00D1 9DEF: LDA #$00 9DF1: ADC $00D2 9DF3: STA $00D2 9DF5: RTS

The programmers actually left in the code for capturing player input, providing a glimpse into the development process and making it possible to replace the demo with an alternate recording. Demo recording mode is enabled when $00D0 is set to $FF. It triggers the following code designed to write to the demo buttons table.

recording: 9DB0: JSR $AB9D 9DB3: LDA $00C0 9DB5: CMP #$05 9DB7: BNE $9DE7 9DB9: LDA $00D0 9DBB: CMP #$FF 9DBD: BNE $9DE7 9DBF: LDA $00F7 9DC1: CMP $00CE 9DC3: BEQ $9DE4 9DC5: LDX #$00 9DC7: LDA $00CE 9DC9: STA ($D1,X) 9DCB: JSR $9DE8 9DCE: LDA $00CF 9DD0: STA ($D1,X) 9DD2: JSR $9DE8 9DD5: LDA $00D2 9DD7: CMP #$DF 9DD9: BEQ $9DE7 9DDB: LDA $00F7 9DDD: STA $00CE 9DDF: LDA #$00 9DE1: STA $00CF 9DE3: RTS buttonsNotChanged: 9DE4: INC $00CF 9DE6: RTS 9DE7: RTS

But, the table is stored in PRG-ROM. Attempting to write to it has no effect on the stored data. Instead, each write triggers a bank switch resulting in the trippy effect shown below.

This suggests that the developers were able to execute the program partially or entirely in RAM.

To get around this inconvenience, I created lua/RecordDemo.lua, available in the source zip. After switching to demo recording mode, it redirects table writes to the Lua console. From there, the bytes can be copied and pasted into the ROM.

To record your own demo, first launch FCEUX and load the Nintendo Tetris ROM (File | Open ROM...). Then, open a Lua Script window (File | Lua | New Lua Script Window...) and either browse for the file or type in the path. When ready, press the Run button to begin demo recording mode, followed by a mouse click on the FCEUX window to restore the input focus. You will be able to control the pieces until the buttons table reaches capacity. At that point, the game will automatically return to the Title Screen. Press Stop in the Lua Script window to terminate the script. The recorded data will appear in the Output Console as shown below.

Select the full contents and copy it to the clipboard (Ctrl+C). Then, launch the Hex Editor (Debug | Hex Editor...). From the Hex Editor menu, select View | ROM File followed by File | Goto Address. In the Goto dialog box, enter 5D10 (the location of the demo buttons table in the ROM file) and press Ok. Next, paste the clipboard contents (Ctrl+V).

Finally, from the FCEUX menu, select NES | Reset. If you managed to follow all these steps, then the demo should be replaced by your recorded version.

If you wish to preserve your changes, from the Hex Editor menu, select File | Save Rom As... and enter in a name for the modified ROM file before hitting Save.

The sequence of spawned Tetriminos can be customized in a similar way.

As discussed above, most players cannot endure the drop speed of level 29, which swiftly brings the game to an end. For this reason, it has become associated with the notation of a kill screen. But, technically, a kill screen prevents the player from advancing due to a bug whereas the rapid drop is actually a feature. The designers were kind enough to let the game continue as long as the player could maintain superhuman speed.

The actual kill screen occurs around 1550 lines. It manifests itself in different ways. Sometimes the game resets. Other times, the screen just goes black. Usually, the game freezes just after clearing a line as shown below. And, these effects are often preceded by random visual artifacts.

The kill screen is the result of a bug in the code that adds points to the score when lines are cleared. The 6-digit score is stored as little endian 24-bit packed BCD and it is located across $0053–$0055. A table is used to convert between number of lines cleared and gained points; each entry is a little endian 16-bit packed BCD value.

9CA5: 00 00 9CA7: 40 00 9CA9: 00 01 9CAB: 00 03 9CAD: 00 12

After the total lines count and potentially the level are incremented, a value in that list is multiplied by level number plus one and the result is added to the score. This is nicely conveyed by a table in the Nintendo Tetris Instruction Booklet:

As listed below, that multiplication is emulated by a loop that repeatedly adds the points to the score. It executes when a piece is locked even when no lines are cleared.

9C31: LDA $0044 9C33: STA $00A8 9C35: INC $00A8 9C37: LDA $0056 9C39: ASL 9C3A: TAX 9C3B: LDA $9CA5,X 9C3E: CLC 9C3F: ADC $0053 9C41: STA $0053 9C43: CMP #$A0 9C45: BCC $9C4E 9C47: CLC 9C48: ADC #$60 9C4A: STA $0053 9C4C: INC $0054 9C4E: INX 9C4F: LDA $9CA5,X 9C52: CLC 9C53: ADC $0054 9C55: STA $0054 9C57: AND #$0F 9C59: CMP #$0A 9C5B: BCC $9C64 9C5D: LDA $0054 9C5F: CLC 9C60: ADC #$06 9C62: STA $0054 9C64: LDA $0054 9C66: AND #$F0 9C68: CMP #$A0 9C6A: BCC $9C75 9C6C: LDA $0054 9C6E: CLC 9C6F: ADC #$60 9C71: STA $0054 9C73: INC $0055 9C75: LDA $0055 9C77: AND #$0F 9C79: CMP #$0A 9C7B: BCC $9C84 9C7D: LDA $0055 9C7F: CLC 9C80: ADC #$06 9C82: STA $0055 9C84: LDA $0055 9C86: AND #$F0 9C88: CMP #$A0 9C8A: BCC $9C94 9C8C: LDA #$99 9C8E: STA $0053 9C90: STA $0054 9C92: STA $0055 9C94: DEC $00A8 9C96: BNE $9C37

It is unfortunate that the Ricoh 2A03 lacks the binary-coded decimal mode of the 6502; it could have simplified much of the body of the loop. Instead, the addition is carried out stepwise using binary mode. Any digit that exceeds 9 after the addition is adjusted by effectively subtracting 10 and incrementing the digit directly to the left. For instance, $07 + $07 = $0E, which is converted to $14. But, that scheme is not entirely foolproof. For example, $09 + $09 = $12 and the checks are incapable of transforming that into $18. To compensate, none of the decimal digits in the entries of the points table exceed 6. Also, to save a check, the final digit of all the entries is 0.

This long and complicated loop takes time to execute. For high levels, the large number of iterations affects the timing of the game as each frame takes longer than 1/60th of a second to generate. That ultimately leads to the various manifestations of the kill screen.

The AI Lua Script caps the number of iterations of the loop at 30, the maximum value that the game designers expected it to reach, eliminating the kill screen.

The Nintendo Tetris Instruction Booklet describes the A-Type game as:

And, the game rewards players who gain significantly high scores with one of five ending animations. The choice of ending is based entirely on the 2 left-most digits of the 6-digit score. As shown below, the player must achieve at least 30,000 points to receive any of the endings.

9A4D: LDA $0075 9A4F: CMP #$03 9A51: BCC $9A5E 9A53: LDA #$80 9A55: JSR $A459 9A58: JSR $9E3A 9A5B: JMP $9A64

It should be noted that $0060–$007F is a mirror of $0040–$005F. The score is replicated across $0073–$0075.

If that first check passes, the ending animation is selected by the following switch.

A96E: LDA #$00 A970: STA $00C4 A972: LDA $0075 A974: CMP #$05 A976: BCC $A9A5 A978: LDA #$01 A97A: STA $00C4 A97C: LDA $0075 A97E: CMP #$07 A980: BCC $A9A5 A982: LDA #$02 A984: STA $00C4 A986: LDA $0075 A988: CMP #$10 A98A: BCC $A9A5 A98C: LDA #$03 A98E: STA $00C4 A990: LDA $0075 A992: CMP #$12 A994: BCC $A9A5 A996: LDA #$04 A998: STA $00C4

The endings consist of rockets of increasing size launched from a pad neighboring Saint Basil's Cathedral. The fourth ending depicts the Buran spacecraft, the Soviet version of the US Space Shuttle. In the best ending, the cathedral itself rockets up into the air as a UFO hovers over the launch pad. An image of each ending and its associated high score range appear below.

30000–49999

50000–69999

70000–99999

100000–119999

120000+



The B-Type game provides a different challenge, which the Nintendo Tetris Instruction Booklet describes as:

If the player successfully clears 25 lines, the game display an ending determined by the starting level. The endings for levels 0–8 consists of animals and objects flying or running across the frame, enigmatically passing behind Saint Basil's Cathedral. The UFO from the best A-Type ending makes an appearance in ending 3. Ending 4 features extinct flying pterosaurs, while ending 7 shows mythical flying dragons. Endings 2 and 6 display flightless birds: running penguins and ostriches respectively. Ending 5 is filled with GOOD blimps, not to be confused with Goodyear blimps. And, multiple Buran spacecraft zoom across the screen in ending 8, even though there was only ever one of them.

The starting height (plus 1) acts as a multiplier, rewarding the player with more animals/objects for the greater difficulty.

The best B-Type ending features a castle ebullient with characters from the Nintendo universe: Princess Peach clapping her hands, Kid Icarus playing the violin, Donkey Kong playing a bass drum, Mario and Luigi dancing, Bowser playing the accordion, Samus playing the cello and Link playing the flute as the domes of Saint Basil's Cathedral rocket up into the air. The starting height establishes the number of these elements revealed during the ending.

Images of all 10 endings follow.

The AI can rapidly plow through the 25 lines required by B-Type games at any starting level and height, providing a convenient way to watch any of the endings. It is also fascinating to witness how it deals with large stacks of random blocks.

Endings 0–8 display up to 6 objects moving across the frame. The y-coordinates of the objects are stored in a table located at $A7B7.

A7B7: 98 A8 C0 A8 90 B0 A7BD: B0 B8 A0 B8 A8 A0 A7C3: C8 C8 C8 C8 C8 C8 A7C9: 30 20 40 28 A0 80 A7CF: A8 88 68 A8 48 78 A7D5: 58 68 18 48 78 38 A7DB: C8 C8 C8 C8 C8 C8 A7E1: 90 58 70 A8 40 38 A7E7: 68 88 78 18 48 A8

The horizontal spacing between the objects is held in a table at $A77B.

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

A sequence of signed values at $A771 determines the speed and direction of the objects.

A771: 01 A772: 01 A773: FF A774: FC A775: 01 A776: FF A777: 02 A778: 02 A779: FE

The sprite indices are located at $A7F3.

A7F3: 2C A7F4: 2E A7F5: 54 A7F6: 32 A7F7: 34 A7F8: 36 A7F9: 4B A7FA: 38 A7FB: 3A

Each object actually consists of 2 sprites with adjacent indices. Add 1 for the second index. For example, the dragon consists of $38 and $39. The tiles for those sprites appear in the pattern tables below.

The center pattern table is the same one discussed above for displaying Tetriminos and the playfield. Interestingly, it contains the full alphabet whereas the others only contain a subset to save space. But, what is more interesting is the airplane and the helicopter sprites in the pattern table on the left; they do not appear in any of the endings or anywhere else in the game. It turns out that the airplane and the helicopter have sprite indices of $30 and $16 respectively and it is possible to modify the table above to see them in action. The results appear below.

Unfortunately, the helicopter skids do not appear, but the main and tail rotors are nicely animated.

Nintendo Tetris contains a hidden, unfinished 2 player versus mode that can be enabled by internally setting the number of players ($00BE) to 2. As shown below, 2 playfields appear side-by-side on top of the single player mode background.

No border appears between the playfields because the center region of the background is solid black. The 003 values displayed above the playfields indicate the number of lines cleared by each player. A lone, shared piece preview appears in the same location as it did in single player mode, which unfortunately, coincides with the right playfield. The squares and other tiles are not colored properly. And, when a player loses, the game resets.

Those issues aside, the mode is playable. Each player can independently control the pieces in the corresponding playfield. And, when a player achieves a Double, Triple or Tetris, garbage lines containing a single missing square push up from beneath the opponent's playfield.

The additional playfield is located at $0500. And, $0060–$007F, which is normally a mirror of $0040–$005F, is used for the second player.

A rushed development schedule may have extinguished this attractive feature. Or, it may have been left incomplete due to something more intentional. One of the reasons that Tetris was selected as the pack-in game for the Nintendo Game Boy is that it promoted the Game Link Cable, an accessory that hooked 2 Game Boys together for 2 player versus mode. That cable introduced a social element to the system, encouraging friends to go out and buy a Game Boy to link into the fun. Perhaps Nintendo feared that if the console version of the game provided 2 player versus mode that the promotional power that Tetris had on the Game Boy might actually be weakened.

Background music is triggered when $06F5 is set to one of the values listed in the table below.

Value Description 01 Unused title screen music 02 B-Type goal achieved 03 Music-1 04 Music-2 05 Music-3 06 Music-1 allegro 07 Music-2 allegro 08 Music-3 allegro 09 Congratulations screen 0A Endings 0B B-Type goal achieved

You can listen to the unused title screen music here. The title screen in the actual game is silent.

Music-1 is a version of the "Dance of the Sugar Plum Fairy," the music for the ballerina in the third movement in Tchaikovsky’s The Nutcracker pas de deux. The ending music is a variation of the "Toreador Song," the aria from the opera Carmen by Georges Bizet. Those pieces were arranged by Hirokazu Tanaka, the composer of all the other music in the game.

Music-2 was inspired by traditional Russian folk songs. And, Music-3 is mysterious, futuristic and mellow; at one point, it was the hold music heard on Nintendo of America’s customer service line.

To help push the player into a state of panic when the height of the pile grows close to the playfield ceiling, a rapid tempo version of the background music ($06–$08) commences.

Notably missing is "Korobeiniki", the well-known theme heard in Game Boy Tetris.

Sound effects are initiated by writing to $06F0 and $06F1 as described in the following table.

Address Value Description 06F0 02 Game over curtain 06F0 03 Ending rocket 06F1 01 Menu option select 06F1 02 Menu screen select 06F1 03 Shift Tetrimino 06F1 04 Tetris achieved 06F1 05 Rotate Tetrimino 06F1 06 Level up 06F1 07 Lock Tetrimino 06F1 08 Chirp-chirp 06F1 09 Line clearing 06F1 0A Line completed

During play, the current state of the game is represented as an integer at $0048. Most of the time, it is set to $01, indicating that the player is controlling the active Tetrimino. However, when the piece locks, the game steps through states $02 through $08, as listed in the table below.

State Description 00 Unassign orientation ID 01 Player controls active Tetrimino 02 Lock Tetrimino into playfield 03 Check for completed rows 04 Display line clearing animation 05 Update lines and statistics 06 B-Type goal check 07 Unused 08 Spawn next Tetrimino 09 Unused 0A Update game over curtain 0B Increment play state

The code branches on the play state at $81B2:

81B2: LDA $0048 81B4: JSR $AC82 81B7: 2F 9E 81B9: CF 81 81BB: A2 99 81BD: 6B 9A 81BF: 39 9E 81C1: 58 9B 81C3: F2 A3 81C5: 03 9B 81C7: 8E 98 81C9: 39 9E 81CB: 11 9A 81CD: 37 9E

For state $00, the switch jumps to the following the code that assigns $13 to orientationID, indicating that it is not set.

9E2F: LDA #$13 9E31: STA $0042 9E33: RTS

The handler is never invoked; however, play state $00 serves as a signal for other parts of the code.

State $01 enables the player to shift, rotate and drop the active Tetrimino:

81CF: JSR $89AE 81D2: JSR $88AB 81D5: JSR $8914 81D8: RTS

As discussed in prior sections, the shift, rotate and drop subroutines validate new Tetrimino positions before committing to a move. The only way that a piece can lock at an invalid position is if it spawned on top of an existing piece, ending the game. As listed below, the code for state $02 performs that check.

99A2: JSR $948B 99A5: BEQ $99B8 99A7: LDA #$02 99A9: STA $06F0 99AC: LDA #$0A 99AE: STA $0048 99B0: LDA #$F0 99B2: STA $0058 99B4: JSR $E003 99B7: RTS

If the locked position is valid, it marks the 4 associated cells of the playfield as solid. Otherwise, it changes to state $0A, the dreaded game over curtain.

The curtain is drawn from the top of the playfield to the bottom, advancing one row every 4 frames. The curtainRow ($0058) is initialized to −16, providing an extra delay of 0.27 seconds between the final lock and the beginning of the animation. At $9A21 in the state $0A code below, the multiplication table, which erroneously gets displayed as level numbers, is accessed to scale curtainRow by 10. Also, as revealed earlier, the code at $9A51 triggers an ending animation if the player's score is at least 30,000 points; otherwise, it waits for Start.

9A11: LDA $0058 9A13: CMP #$14 9A15: BEQ $9A47 9A17: LDA $00B1 9A19: AND #$03 9A1B: BNE $9A46 9A1D: LDX $0058 9A1F: BMI $9A3E 9A21: LDA $96D6,X 9A24: TAY 9A25: LDA #$00 9A27: STA $00AA 9A29: LDA #$13 9A2B: STA $0042 drawCurtainRow: 9A2D: LDA #$4F 9A2F: STA ($B8),Y 9A31: INY 9A32: INC $00AA 9A34: LDA $00AA 9A36: CMP #$0A 9A38: BNE $9A2D 9A3A: LDA $0058 9A3C: STA $0049 incrementCurtainRow: 9A3E: INC $0058 9A40: LDA $0058 9A42: CMP #$14 9A44: BNE $9A46 9A46: RTS endGame: 9A47: LDA $00BE 9A49: CMP #$02 9A4B: BEQ $9A64 9A4D: LDA $0075 9A4F: CMP #$03 9A51: BCC $9A5E 9A53: LDA #$80 9A55: JSR $A459 9A58: JSR $9E3A 9A5B: JMP $9A64 9A5E: LDA $00F5 9A60: CMP #$10 9A62: BNE $9A6A 9A64: LDA #$00 9A66: STA $0048 9A68: STA $00F5 9A6A: RTS

The code ends by setting the play state to $00, but the corresponding handler does not get called since it is game over.

The playfield rows are incrementally copied to VRAM to display them. The index of the current row being copied is held in vramRow ($0049). At $9A3C, vramRow is set to curtainRow, which will ultimately render that row of the curtain visible.

VRAM manipulation occurs during the vertical blanking interval, which is detected by the interrupt handler that was discussed in the Legal Screen section. It invokes the subroutine listed below (denoted as render() in the interrupt handler comments).

804B: LDA $00BD 804D: JSR $AC82 8050: B1 82 8052: DA 85 8054: 44 A3 8056: EE 94 8058: 95 9F

Render mode is similar to game mode. It is stored at $00BD and it can have one of the following values:

Value Description 00 Legal and title screens 01 Menu screens 02 Congratulations screen 03 Play and demo 04 Ending animation

Part of render mode $03 is listed below.

952A: JSR $9725 952D: JSR $9725 9530: JSR $9725 9533: JSR $9725

As shown below, copyPlayfieldRowToVRAM() transfers the playfield row indexed by vramRow to VRAM. If vramRow is greater than 20, the subroutine does nothing.

9725: LDX $0049 9727: CPX #$15 9729: BPL $977E 972B: LDA $96D6,X 972E: TAY 972F: TXA 9730: ASL 9731: TAX 9732: INX 9733: LDA $96EA,X 9736: STA $2006 9739: DEX 973A: LDA $00BE 973C: CMP #$01 973E: BEQ $975E 9740: LDA $00B9 9742: CMP #$05 9744: BEQ $9752 9746: LDA $96EA,X 9749: SEC 974A: SBC #$02 974C: STA $2006 974F: JMP $9767 9752: LDA $96EA,X 9755: CLC 9756: ADC #$0C 9758: STA $2006 975B: JMP $9767 975E: LDA $96EA,X 9761: CLC 9762: ADC #$06 9764: STA $2006 9767: LDX #$0A 9769: LDA ($B8),Y 976B: STA $2007 976E: INY 976F: DEX 9770: BNE $9769 9772: INC $0049 9774: LDA $0049 9776: CMP #$14 9778: BMI $977E 977A: LDA #$20 977C: STA $0049 977E: RTS

The vramPlayfieldRows table ($96EA) contains the little endian VRAM addresses corresponding to the displayed playfield rows offset by 6 in normal mode and by −2 and 12 for the playfields in the unfinished 2 Player Versus mode. The bytes of that table are part of the list of values erroneously displayed as level numbers beyond level 29. The neighboring low and high bytes of each address are retrieved individually and they are effectively combined into a 16-bit address, which is used in the copy loop.

At the end of the subroutine, vramRow is incremented. If it reaches 20, it is set to 32, indicating that the full copy is completed. As shown above, only 4 rows are copied per frame.

The state $03 handler is responsible for identifying completed rows and removing them from the playfield. Across 4 separate calls, it scans row offsets [−2, 1] about the Tetrimino center (both coordinates of all Tetrimino squares existing within that range). The completed row indices are stored at $004A–$004D; a recorded index of 0 is used to indicate that no completed row was found on that pass. The handler appears below.

9A6B: LDA $0049 9A6D: CMP #$20 9A6F: BPL $9A74 9A71: JMP $9B02 9A74: LDA $0041 9A76: SEC 9A77: SBC #$02 9A79: BPL $9A7D 9A7B: LDA #$00 9A7D: CLC 9A7E: ADC $0057 9A80: STA $00A9 9A82: ASL 9A83: STA $00A8 9A85: ASL 9A86: ASL 9A87: CLC 9A88: ADC $00A8 9A8A: STA $00A8 9A8C: TAY 9A8D: LDX #$0A 9A8F: LDA ($B8),Y 9A91: CMP #$EF 9A93: BEQ $9ACC 9A95: INY 9A96: DEX 9A97: BNE $9A8F 9A99: LDA #$0A 9A9B: STA $06F1 9A9E: INC $0056 9AA0: LDX $0057 9AA2: LDA $00A9 9AA4: STA $4A,X 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 9AB6: CPY #$FF 9AB8: BNE $9AA9 9ABA: LDA #$EF 9ABC: LDY #$00 9ABE: STA ($B8),Y 9AC0: INY 9AC1: CPY #$0A 9AC3: BNE $9ABE 9AC5: LDA #$13 9AC7: STA $0042 9AC9: JMP $9AD2 rowNotComplete: 9ACC: LDX $0057 9ACE: LDA #$00 9AD0: STA $4A,X incrementLineIndex: 9AD2: INC $0057 9AD4: LDA $0057 9AD6: CMP #$04 9AD8: BMI $9B02 9ADA: LDY $0056 9ADC: LDA $9B53,Y 9ADF: CLC 9AE0: ADC $00BC 9AE2: STA $00BC 9AE4: LDA #$00 9AE6: STA $0049 9AE8: STA $0052 9AEA: LDA $0056 9AEC: CMP #$04 9AEE: BNE $9AF5 9AF0: LDA #$04 9AF2: STA $06F1 9AF5: INC $0048 9AF7: LDA $0056 9AF9: BNE $9B02 9AFB: INC $0048 9AFD: LDA #$07 9AFF: STA $06F1 9B02: RTS

The vramRow check at the top prevents the handler from executing while playfield rows are being transferred to VRAM (the state $03 handler is invoked every frame). If completed lines are discovered, vramRow is reset to 0 to cause a full transfer.

The lineIndex ($00A9) is initialized to 0 and it is incremented on each pass.

Unlike play state $0A and the playfield copy subroutine that took advantage of the multiplication table at $96D6, the block starting at $9A82 multiplies rowY by 10 using shifts and addition:

rowIndex = (rowY << 1) + (rowY << 3);

This was done because while rowY is constrained to [0, 20], the multiplication table only covers [0, 19]. The line scan can actually run off the end of the playfield. However, as discussed earlier, the game initializes $0400–$04FF to $EF (the empty tile), providing more than 5 additional empty hidden rows below the playfield floor.

The block starting at $9ADA is part of the unfinished 2 Player Versus mode. As mentioned, clearing lines sends garbage over to the opponent's playfield. The number of garbage lines is determined by a table at $9B53:

9B53: 00 9B54: 00 9B55: 01 9B56: 02 9B57: 04

The loop at $9AA6 shifts the material above the completed line down a row. It takes advantage the fact that each row is 10 bytes apart in a contiguous sequence. The loop following it clears the top row.

The line clearing animation occurs during play state $04, but as shown below, it does not happen within the play state handler, which is entirely empty.

9E39: RTS

Instead, the following branch of render mode $03 is followed during play state $04.

94EE: LDA $0068 94F0: CMP #$04 94F2: BNE $9522 94F4: LDA #$04 94F6: STA $00B9 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 9510: JSR $977F

The leftPlayfield and mirrored values are for the unfinished 2 Player Versus mode.

The updateLineClearingAnimation() subroutine is listed below. It is invoked every frame, but the condition at the top only enables it to run every fourth frame. On each pass, it loops over the list of completed row indices and it clears 2 columns from those rows, advancing from the center columns outward.

977F: LDA $00B1 9781: AND #$03 9783: BNE $97FD 9785: LDA #$00 9787: STA $00AA 9789: LDX $00AA 978B: LDA $4A,X 978D: BEQ $97EB 978F: ASL 9790: TAY 9791: LDA $96EA,Y 9794: STA $00A8 9796: LDA $00BE 9798: CMP #$01 979A: BNE $97A6 979C: LDA $00A8 979E: CLC 979F: ADC #$06 97A1: STA $00A8 97A3: JMP $97BD twoPlayers: 97A6: LDA $00B9 97A8: CMP #$04 97AA: BNE $97B6 97AC: LDA $00A8 97AE: SEC 97AF: SBC #$02 97B1: STA $00A8 97B3: JMP $97BD 97B6: LDA $00A8 97B8: CLC 97B9: ADC #$0C 97BB: STA $00A8 updateVRAM: 97BD: INY 97BE: LDA $96EA,Y 97C1: STA $00A9 97C3: STA $2006 97C6: LDX $0052 97C8: LDA $97FE,X 97CB: CLC 97CC: ADC $00A8 97CE: STA $2006 97D1: LDA #$FF 97D3: STA $2007 97D6: LDA $00A9 97D8: STA $2006 97DB: LDX $0052 97DD: LDA $9803,X 97E0: CLC 97E1: ADC $00A8 97E3: STA $2006 97E6: LDA #$FF 97E8: STA $2007 97EB: INC $00AA 97ED: LDA $00AA 97EF: CMP #$04 97F1: BNE $9789 97F3: INC $0052 97F5: LDA $0052 97F7: CMP #$05 97F9: BMI $97FD 97FB: INC $0048 97FD: RTS

The 16-bit VRAM address is built up in the same way show in the copy playfield subroutine. However, in this case, it is offset by a column index obtained from the table below.

97FE: 04 03 02 01 00 9803: 05 06 07 08 09

The clearing animation requires 5 passes. Then, the code advances to the next play state.

The play state $05 handler contains the code discussed in the Lines and Statistics section. The handler ends with this code:

9C9E: LDA #$00 9CA0: STA $0056 9CA2: INC $0048 9CA4: RTS

completedLines is not reset until the end of play state $05, after it is used to update the total lines count and the score. This sequence permits an interesting bug. In demo mode, wait for the game to get a complete line and then quickly press Start before the line clearing animation finishes. The game will return to the title screen, but, if you timed it right, completedLines will retain its value. Next, begin an A-Type game. When the first piece locks, the play state $03 handler will scan for completed rows. It won't record any, but it will leave completedLines unchanged. Finally, when play state $05 executes, the total lines count and the score will go up as if those lines were your own.

The easiest way to do this and the way that will result in the most points is to wait for the demo to get a Tetris (2 of them will occur). As soon as you see the screen flash, press Start.

After launching a new game, the screen actually will continue to flash thanks to the following code that is called by the interrupt handler.

9673: LDA #$3F 9675: STA $2006 9678: LDA #$0E 967A: STA $2006 967D: LDX #$00 967F: LDA $0056 9681: CMP #$04 9683: BNE $9698 9685: LDA $00B1 9687: AND #$03 9689: BNE $9698 968B: LDX #$30 968D: LDA $00B1 968F: AND #$07 9691: BNE $9698 9693: LDA #$09 9695: STA $06F1 9698: STX $2007

In fact, if you let the first piece automatically drop all the way to the floor of the playfield, the score will increase even more because holdDownPoints ($004F) will retain its demo value as well. This is true even if the demo did not complete any lines. holdDownPoints is not reset until Down is pressed.

Furthermore, if you press Start during the line clearing animation of a Tetris in demo mode and then you wait for the demo to start again, not only will the demo get credited for the Tetris, its timing will get screwed up. As a result, the demo will actually lose the game. From the game over curtain, you can navigate back to the title screen by pressing Start.

Play state $06 performs the goal check for B-Type games. For A-Type, it is effectively an unused frame.

Play state $07 exclusively contains logic for the unfinished 2 Player Versus mode. For single player mode, it acts as an unused frame.

Play state $08 was discussed in the Spawning Tetriminos and the Picking Tetriminos sections.

Play state $09 is unused. And, $0B advances the play state, but it also appears to be unused.

Finally, here is the main game loop:

8138: JSR $8161 813B: CMP $00A7 813D: BNE $8142 813F: JSR $AA2F 8142: LDA $00C0 8144: CMP #$05 8146: BNE $815A 8148: LDA $00D2 814A: CMP #$DF 814C: BNE $815A 814E: LDA #$DD 8150: STA $00D2 8152: LDA #$00 8154: STA $00B2 8156: LDA #$01 8158: STA $00C0 815A: JMP $8138

The algorithm repeatedly performs the following steps:

Sleep until a new Tetrimino is spawned. Observe the type of the newly spawned Tetrimino, the type of next Tetrimino (the preview piece) and the contents of the playfield. Consider all possible ways to add the 2 Tetriminos to the playfield and rate each possibility. Move the newly spawned Tetrimino to coincide with the placement in the best possibility found.

Each of these steps is discussed in detail below.

Consider a simplified version of Tetris where the pieces do not drop automatically. Rather, the only means of advancing downward is manually soft dropping. With timing stripped out of the game, the state of an active Tetrimino can be fully described by its position and orientation. It has a known initial spawn state and the operations that transform one state into another are:

Move one step down

Move one step left

Move one step right

Rotate one step counterclockwise

Rotate one step clockwise

Those operations are only applicable when the squares of the resultant Tetrimino correspond to empty cells within the boundaries of the playfield. When it is impossible to move one step down, the state is considered locked. However, since lock delay is effectively infinite in this simplified Tetris, a locked state can be further transformed by the other operations, permitting slides and spins.

The set of locked states along with the minimal sequence of operations that produce them can be found using breadth-first search (BFS). As described below, it uses a queue to store intermediate states.

Enqueue the spawn state. Dequeue a state. Obtain successor states by applying the transformation operations. If down is not among them, then the dequeued state is a locked state. Enqueue the successor states that were not previously visited. If the queue is not empty, repeat from step 2.

The program represents each state as an object with the following fields:

{ x, y, rotation, visited, predecessor }

In preparation, the program creates a 3-dimensional array of state objects (20 rows × 10 columns × 4 rotations), initializing x, y and rotation accordingly.

The visited field is marked when a state is enqueued. This is permissible in BFS because each successor increases the total path length by 1. Meaning, it is not possible to produce a successor that would need to be inserted anywhere except the end of the queue to keep it ordered by increasing path length.

The predecessor field points to the state object from which it descends. It is set when a state is enqueued. The spawn state has no predecessor.

The Tetrimino type and the solid blocks present in the playfield ultimately determine the set of lock states discovered during the search. The sequence of moves that generated them can be uncovered (in reverse) by repeatedly following the predecessor links back to the spawn state. When the PLAY_FAST constant at the top of the program is set to true, it skips the predecessors entirely, directly placing the Tetrimino into lock.

The 3-dimensional array of state objects, the queue and the BFS were packaged up into a reusable class. It provides a search method that accepts the playfield (a 2-dimensional array), the Tetrimino type, and a listener. Every time a locked state is found, the playfield is updated by adding the Tetrimino at the corresponding location. Then, the modified playfield along with details of the change is passed to the listener for processing. After the listener returns, the playfield is restored.

The listener is used to chain multiple searchers together, providing the means of finding all possible ways of adding 2 (or more) Tetriminos to the playfield. The first searcher in the chain executes the BFS only once. However, the second searcher executes the BFS every time the first search discovers a locked state. And so on, if there were more searchers in the chain.

The listener of the final searcher rates the modified playfield. When it encounters a playfield better than the ones examined prior, it records the locked state object in use at that time by the first searcher in the chain. Since the first searcher executes the BFS only once, the predecessor fields of its state objects remain valid after the entire search process is complete. Meaning, the final listener effectively captures the pathway that the first Tetrimino must take to ultimately lead to the best playfield configuration.

The modified playfield is rated by an evaluation function, a weighted sum of various influential factors. The evaluation function used in this application is based on the function developed by Islam El-Ashi. It uses the following features:

Total Lines Cleared

This is the total number of lines cleared as a consequence of introducing the 2 Tetriminos.

This is the total number of lines cleared as a consequence of introducing the 2 Tetriminos. Total Lock Height

Lock height is the height above the playfield floor where a piece locked. It is the vertical distance that a locked piece would drop if all the other solid squares in the playfield were removed and the orientation of the piece was maintained. Total lock height is the sum of the lock heights of the 2 Tetriminos.

Lock height is the height above the playfield floor where a piece locked. It is the vertical distance that a locked piece would drop if all the other solid squares in the playfield were removed and the orientation of the piece was maintained. Total lock height is the sum of the lock heights of the 2 Tetriminos. Total Well Cells

A well cell is an empty cell located above all the solid cells within its column such that its left and right neighbors are both solid cells; the playfield walls are treated as solid cells in this determination. The idea is that a well is a structure open at the top, sealed at the bottom and surrounded by walls on both sides. The possibility of intermittent gaps in the well walls means that well cells do not necessarily appear in a contiguous stack within a column.

A well cell is an empty cell located above all the solid cells within its column such that its left and right neighbors are both solid cells; the playfield walls are treated as solid cells in this determination. The idea is that a well is a structure open at the top, sealed at the bottom and surrounded by walls on both sides. The possibility of intermittent gaps in the well walls means that well cells do not necessarily appear in a contiguous stack within a column. Total Column Holes

A column hole is an empty cell directly beneath a solid cell. The playfield floor is not compared to the cell directly above it. Empty columns contain no holes.

A column hole is an empty cell directly beneath a solid cell. The playfield floor is not compared to the cell directly above it. Empty columns contain no holes. Total Column Transitions

A column transition is an empty cell adjacent to a solid cell (or vice versa) within the same column. The changeover from the highest solid block in the column to the empty space above it is not considered a transition. Similarly, the playfield floor is not compared to the cell directly above it. As a result, a completely empty column has no transitions.

A column transition is an empty cell adjacent to a solid cell (or vice versa) within the same column. The changeover from the highest solid block in the column to the empty space above it is not considered a transition. Similarly, the playfield floor is not compared to the cell directly above it. As a result, a completely empty column has no transitions. Total Row Transitions

A row transition is an empty cell adjacent to a solid cell (or vice versa) within the same row. Empty cells adjoining playfield walls are considered transitions. The total is computed across all rows in the playfield. However, rows that are completely empty do not contribute to the sum.

El-Ashi suggested that useful weights could be found using particle swarm optimization (PSO), an algorithm that iteratively improves a population of candidate solutions by imitating the kinds of swarm behavior observed in nature. In this case, each candidate solution is a vector of weights and the fitness of a candidate is determined by playing Tetris; it is the total number of Tetriminos it survived through before reaching game over.

These ideas were applied to the Java version, which is discussed below; it executes outside of FCEUX and it can be configured to play against a non-graphical, in-memory game that runs at a considerably faster rate. After setting up the PSO, I was surprised to find that it did not progress beyond the initial iteration. As it happened, several of the randomly generally candidate solutions were actually playing well. Over a period of days, the size of that set dwindled until there was only 1 remaining. Here are the values of that solution:

Factor Weight Total Lines Cleared 1.000000000000000 Total Lock Height 12.885008263218383 Total Well Cells 15.842707182438396 Total Column Holes 26.894496507795950 Total Column Transitions 27.616914062397015 Total Row Transitions 30.185110719279040

The playfields are evaluated by multiplying the factors with their associated weights and adding up the results. A lower evaluation implies a better rating. Since the factors and weights are all positive values, all the factors have an adverse influence; each of them should be minimized. It also means that the best possible rating is 0.

Since these weights were selected by random chance, there must be a very wide range of suitable values. This particular set of numbers and the relative significance it suggests about each factor may be inconsequential. But, scrutinizing them is interesting nonetheless.

The least adverse factor is Total Lines Cleared. The fact that this is even an adverse factor is counterintuitive. But, the primary goal of the AI is survival. It does not aim for a high score. Rather, it plays conservatively, usually clearing lines individually. To score a Double, a Triple or a Tetris, it would have to let the pile grow, which goes against its long term goal.

The next on the list is Total Lock Height. This can be minimized by getting the Tetriminos as close to the floor of the playfield as possible. It’s a basic strategy that contributes to long term survival and short term packing by attracting pieces to the bottom.

The weight assigned to Total Well Cells is bit a surprising because expert players intentionally build deep wells with the goal of scoring repeated Tetrises. But, as mentioned, that’s risky play that goes against the main objective of survival. In addition, the number of well cells provides an indication of pile roughness. A certain level of roughness is beneficial in accommodating certain pieces or piece combinations. But, high roughness is detrimental to tight packing.

Total Column Holes is equal to approximately half of the Total Column Transitions. Those factors can be combined and further folded into the final related factor resulting in the broader and most adverse factor: Total Transitions.

Densely packed regions exhibit few transitions in all directions. Hence, the main strategy driving the AI can be summarized as: pack the pieces as closely together as possible.

Here is a list of some of the other factors that I experimented with while developing the AI:

Pile Height

Solid blocks can be suspended over empty cells producing overhangs and holes; however, it is not possible to fix solid blocks above completely empty rows. Consequentially, pile height is the number of rows that contain at least one solid block.

Solid blocks can be suspended over empty cells producing overhangs and holes; however, it is not possible to fix solid blocks above completely empty rows. Consequentially, pile height is the number of rows that contain at least one solid block. Total Occupied Columns

This is the number of columns containing at least 1 solid cell.

This is the number of columns containing at least 1 solid cell. Total Solid Cells

This is the number of solid cells in the playfield.

This is the number of solid cells in the playfield. Total Connected Regions

This applies the flood fill algorithm to count the number of contiguously connected regions. It identifies holes that span both dimensions in addition to finding solid islands.

This applies the flood fill algorithm to count the number of contiguously connected regions. It identifies holes that span both dimensions in addition to finding solid islands. Column Height Variance

This is the statistical measure of how far the heights are spread out from each other. It’s a measure of surface roughness.

This is the statistical measure of how far the heights are spread out from each other. It’s a measure of surface roughness. Total Accommodations

This computes how accommodating the pile is to the next unknown piece. It counts the total number of ways that the 7 different piece types can be added to the playfield without introducing any holes. An accurate count would require repeatedly applying BFS. But, the search tree can be highly pruned to produce an approximation.

This computes how accommodating the pile is to the next unknown piece. It counts the total number of ways that the 7 different piece types can be added to the playfield without introducing any holes. An accurate count would require repeatedly applying BFS. But, the search tree can be highly pruned to produce an approximation. Average Next Evaluation

This deepens the search by analyzing all possibilities for the next unknown piece. It uses the other factors to independently place each piece type and then it returns the average of the 7 evaluations. Each placement requires a BFS execution.

This deepens the search by analyzing all possibilities for the next unknown piece. It uses the other factors to independently place each piece type and then it returns the average of the 7 evaluations. Each placement requires a BFS execution. Average Simulated Play

This simulates a series of short runs of Tetris play, picking pieces with its own pseudorandom number generator and using the AI to deal with them. At the end of each run, the modified playfield is evaluated using the other factors. The average of the runs is returned.

All of the factors can be adjusted by introducing tunable parameters. For example, instead of simply counting lines cleared, a different weight could be assigned to Singles, Doubles, Triples and Tetrises, mimicki