# Tetris and 3D Transforms

22 Dec 2018
Progress: Complete

This is page 9 of the Games Console project.

Tetris was one of the first games I made for the console, and it was early enough in the process that I didn't think to make a javascript prototype of it. The logic is, after all, fairly linear. Instead, you can feast your eyes on this 3D demo:

As I mentioned in the video, the 3D stuff was written without a particular use in mind, and only towards the end of the project did I think to do something with it.

The 3D effect is extremely simple (compared to most 3D implementations, I mean). It's just two rotations, one in the X-Y plane and then one in the Y-Z plane. The Z coordinate is then added to the Y coordinate to render a 2D image. Since there's no perspective, it often appears to do a weird depth inversion as it crosses the boundary (at least to my eyes). I actually did implement perspective in the JS version, but took it out as it would be too hard to implement in assembly. Really we needed a tangent lookup table, but I wasn't sure how to scale that, since it goes between infinity and minus-infinity. Alternatively, we could have attempted a division routine after sine and cosine lookups.

A rotation matrix looks like this.

So to rotate an (x, y) coordinate in a plane, we multiply by the rotation matrix. If you're not familiar with matrix multiplication, it translates to procedural code like this:

```nx = cos(theta)*x - sin(theta)*y;
ny = sin(theta)*x + cos(theta)*y;
x = nx;
y = ny;
```

This works nicely for the theta rotation in the X, Y plane. Every coordinate of our model has to go through the transform. For the next rotation, we basically just swap x with y, y with z, and theta with phi, in the code above. As it happens, we can simplify the code a bit after that.

Most importantly, for the entirety of a frame, theta and phi do not change. So, sin(theta) and so on will always return the same numbers, and we can cache them at the start of the frame and save a huge amount of time that would have been spent doing lookups.

Simplifying, caching the trig values, and not bothering with perspective, translated into a one-instruction-per-line format, the javascript transform looks like this.

```function transform1(x,y,z,st,ct,cp,sp){
nx=ct*x
nx-=st*y
ny=st*x
ny+=ct*y
ny=ny*cp
ny+=sp*z
return [nx, ny];
}
```

From here, paying close attention to where the "point" in the fixed-point multiplication lies, it's a fairly easy, if tedious, task to translate this to assembly.

```/* Function transform3D (x,y,z, sintheta, costheta, sinphi, cosphi)
* Applies transform matrices: x/y rotation (theta) followed
* by y/z rotation (phi). Returns x,y in r18,r19
*/
transform3D:
; Function arguments
; z = r13   ; signed
; x = r14   ; signed
; y = r15   ; signed

; Returns
.def nx = r18   ; unsigned
.def ny = r19   ; unsigned

; fmuls only works on registers r16 - r23
push x0 ; r16
push y0 ; r17

movw r17:r16, r15:r14       ; move two registers at once

; Lots of fixed-point multiplication - output goes into r1:r0.
; We throw away r0 but use its MSB to round up answer

fmuls costheta,x0
mov nx, r1
sbrc r0,7               ; round it up
inc nx

fmuls sintheta,y0
sub nx, r1              ; subtract! x row is cos theta minus sin theta
sbrc r0,7
dec nx

fmuls sintheta,x0
mov ny, r1
sbrc r0,7
inc ny

fmuls costheta,y0
sbrc r0,7
inc ny

fmuls ny,cosphi         ; Now apply phi matrix
mov ny, r1
sbrc r0,7
inc ny

mov x0, r13             ; load z value, move into x because we're
; so short on registers
fmuls sinphi, x0
sbrc r0,7
inc ny

subi nx, 128            ; and finally,
subi ny, 128            ; shift origin to center of unsigned range

pop y0
pop x0
ret
```

With that in place, all we have to do is extend our polygon-drawing routines to pass everything they read from program memory through the transform routine. For the entire 3D library, that's pretty much it! Here's the full file, most of it you've seen before.

```
### 3Dfunctions.asm ###

/* Function drawPolygonPM(Z pointer)
* Draw from a list of coords in program memory
* First byte is number of vertices, followed by pairs of x,y bytes
*/
drawPolygonPM:
push r20
.def vertexCount=r20

lpm vertexCount, Z+
lpm r16,Z+              ; load starting point
lpm r17,Z+              ; all polygons must have at least 2 vertices
dec vertexCount

polyPMnextVert:
lpm r18,Z+              ; load next vertex
lpm r19,Z+
rcall drawLine          ; drawLine updates r16, r17 for us

dec vertexCount
brne polyPMnextVert     ; repeat until end of polygon

pop r20
ret

/* Function drawPolygonListPM(Z pointer)
* Draws lots of polygons from program memory.
* First byte is number of polygons
*/
drawPolygonListPM:
push r20
lpm r20, Z+         ; load number of polygons

polyListPMnext:
rcall drawPolygonPM ; just call drawPolygonPM that many times
dec r20
brne polyListPMnext

pop r20
ret

/* Function drawModelListPM(Z pointer, theta, phi)
* basically a 3D version of drawPolygonListPM
*/
drawModelListPM:
; arguments
.def theta= r24
.def phi  = r25

.def sintheta=r20   ; These values are going to be the same for all
.def costheta=r21   ; transforms, so we may as well cache them
.def sinphi = r22
.def cosphi = r23
push r20
push r21
push r22
push r23
push ZH
push ZL

; Load trig values. Table is backwards so we can use subtract immediate
ldi ZH, HIGH(sineTable*2+255)
ldi ZL,  LOW(sineTable*2+255)
sub ZL, theta
sbci ZH, \$00
lpm sintheta, Z

; Cos is just an offset, but we have to rewind
; to the start of the table or it could overflow
ldi ZH, HIGH(sineTable*2+255)
ldi ZL,  LOW(sineTable*2+255)
subi theta, 192                 ; subtract pi/2 from theta
sub ZL, theta
sbci ZH, \$00
lpm costheta, Z
subi theta, 64                  ; return theta to its orig value

; Now do the same for phi
ldi ZH, HIGH(sineTable*2+255)
ldi ZL,  LOW(sineTable*2+255)
sub ZL, phi
sbci ZH, \$00
lpm sinphi, Z

; cos phi
ldi ZH, HIGH(sineTable*2+255)
ldi ZL,  LOW(sineTable*2+255)
subi phi, 192                   ; subtract pi/2 from phi
sub ZL, phi
sbci ZH, \$00
lpm cosphi, Z
subi phi, 64                    ; return phi to its orig value

pop ZL
pop ZH  ; Now we can start to read coordinates from program mem

lpm r11, Z+                     ; Number of polygons

dModelListPMnext:
rcall draw3DModelPM
dec r11
brne dModelListPMnext           ; loop until we've drawn them all

pop r20
pop r21
pop r22
pop r23
ret

/* Function draw3DModelPM(Z pointer, sintheta, costheta, sinphi, cosphi)
* rotates and then draws a list of vertices from prog mem
*/
draw3DModelPM:
lpm r12, Z+                     ; Number of vertices

lpm r14, Z+             ; x
lpm r15, Z+             ; y
lpm r13, Z+             ; z
rcall transform3D       ; this returns in r18 and r19, we want r16, r17
movw r17:r16,r19:r18    ; they're not used as a word but movw still works
dec r12

d3DModelPMnext:             ; There will always be at least 2 vertices
lpm r14, Z+             ; x
lpm r15, Z+             ; y
lpm r13, Z+             ; z
rcall transform3D
rcall drawLine          ; draws a line from r16,r17 to r18,r19

dec r12
brne d3DModelPMnext             ; loop for each vertex

ret

/* Function transform3D (x,y,z, sintheta, costheta, sinphi, cosphi)
* Applies transform matrices: x/y rotation (theta) followed
* by y/z rotation (phi). Returns x,y in r18,r19
*/
transform3D:
; Function arguments
; z = r13   ; signed
; x = r14   ; signed
; y = r15   ; signed

; Returns
.def nx = r18   ; unsigned
.def ny = r19   ; unsigned

; fmuls only works on registers r16 - r23
push x0 ; r16
push y0 ; r17

movw r17:r16, r15:r14       ; move two registers at once

; Lots of fixed-point multiplication - output goes into r1:r0.
; We throw away r0 but use its MSB to round up answer

fmuls costheta,x0
mov nx, r1
sbrc r0,7               ; round it up
inc nx

fmuls sintheta,y0
sub nx, r1              ; subtract! x row is cos theta minus sin theta
sbrc r0,7
dec nx

fmuls sintheta,x0
mov ny, r1
sbrc r0,7
inc ny

fmuls costheta,y0
sbrc r0,7
inc ny

fmuls ny,cosphi         ; Now apply phi matrix
mov ny, r1
sbrc r0,7
inc ny

mov x0, r13             ; load z value, move into x because we're
; so short on registers
fmuls sinphi, x0
sbrc r0,7
inc ny

subi nx, 128            ; and finally,
subi ny, 128            ; shift origin to center of unsigned range

pop y0
pop x0
ret

cancelDrawLine:
ret

/*  Function drawLine (x0,y0,x1,y1)
*  My implementation of Bresenham line algorithm
*  based on pseudocode/description from wikipedia
*/
drawLine:

cp r16,r18
cpc r17,r19
breq cancelDrawLine

;Function arguments
.def x0=r16
.def y0=r17
.def dx=r18
.def dy=r19

;Local variables
push r20
push r21
push r22
push r23
push r24
push r25
.def slowx=r20
.def slowy=r21
.def fastx=r22
.def fasty=r23
.def err = r24
.def t   = r25

; First we split the magnitude and direction
; Need to be careful/explicit because mag could be >127

cp dx,x0
breq lineXeq
brcs lineXneg       ; Carry flag set if it overflowed

ldi slowx,1         ; slowx is the direction
sub dx,x0           ; dx is now the magnitude
rjmp lineY

lineXneg:
ldi slowx,-1
mov t, x0           ; t as temp var
sub t, dx
mov dx, t
rjmp lineY

lineXeq:
clr slowx
clr dx

lineY:                  ; Now do the same for y
cp dy,y0
breq lineYeq
brcs lineYneg

ldi slowy,1
sub dy,y0
rjmp lineCheckDistance

lineYneg:
ldi slowy,-1
mov t, y0           ; t as temp var
sub t, dy
mov dy, t
rjmp lineCheckDistance

lineYeq:
clr slowy
clr dy

lineCheckDistance:      ; Now to find which direction is "faster"
cp dx, dy
brcs lineDxLessthanDy

mov fastx, slowx    ; x is the fast direction
clr fasty

eor dx, dy          ; Tripple XOR, the classy way to
eor dy, dx          ; swap two registers
eor dx, dy

rjmp lineInit

lineDxLessthanDy:
mov fasty, slowy    ; y is the fast direction, no need to swap
clr fastx

lineInit:
mov err, dy         ; set err to half the furthest distance
lsr err

out PORTA, x0       ; We are now ready to draw the line
out PORTC, y0
rcall delayPixel

mov t, dy

lineNextPixel:
sub err, dx         ; Update error term
brcc lineStepParallel

add err, dy         ; Make error term positive again

rjmp lineLoopBack

lineStepParallel:

lineLoopBack:
out PORTA, x0       ; Plot point
out PORTC, y0
rcall delayPixel

dec t
brne lineNextPixel

pop r25
pop r24
pop r23
pop r22
pop r21
pop r20

ret                 ; End of drawLine

delayPixel:
push r16
ldi r16,8
delayPixelLoop:
dec r16
brne delayPixelLoop
pop r16
ret

/* Function sinS(coordinate, dir)
* Scales 8bit signed coordinate by sine of the direction
*/
sinS:
.def cor=r16
.def dir=r17

push ZH
push ZL
; As there is no add-immediate-with-carry, it is faster to
; go to the end of the table and walk backwards
ldi ZH, HIGH(sineTable*2+255)
ldi ZL,  LOW(sineTable*2+255)
sub ZL, dir
sbci ZH, \$00            ; sub-with-carry 0 from high byte
lpm dir, Z
fmuls cor,dir
mov cor,r1              ; result is in r1:r0
sbrc r0,7               ; round number up if necessary
inc cor

pop ZL
pop ZH
ret

/* Function cosS(coordinate, dir)
* Scales 8bit signed coordinate by cosine of the direction
*/
cosS:
.def cor=r16
.def dir=r17

push ZH
push ZL
ldi ZH, HIGH(sineTable*2+255)
ldi ZL,  LOW(sineTable*2+255)   ; reuse table, but shift by pi/2
subi dir, 192                   ; which in our notation is 64 (-192)
sub ZL, dir
sbci ZH, \$00
lpm dir, Z
fmuls cor,dir
mov cor,r1              ; result is in r1:r0
sbrc r0,7               ; round number up if necessary
inc cor

pop ZL
pop ZH
ret

; signed values, in reverse order
sineTable:

```

I have this to say about 3D. It's all about confidence. I was able to casually implement this because I've implemented 3D routines half a dozen times before. The first time I implemented a 3D routine was when I was 14, so I think that's before I'd even covered pythagoras in school, let alone trig functions (I went to a really bad school). It was all new to me but I managed it for just one reason. My online friend Atrius had said that he'd implemented a 3D rotating cube on a TI-83 calculator when he was younger than me! I'm not kidding when I say that I didn't know what trigonometry was, the whole thing was a huge stack of gamemaker's built-in lengthdir_ functions (just a wrapper for sine and cosine). Perhaps that's part of it, not knowing that what I was doing was actually this scary "trigonometry" thing.

If you balk when you see matrix transforms, you shouldn't! It's just equations with an in and an out, and you can treat them like a black box, stack them up, build a huge ugly script and it'll all be dandy. Unlike with "real" maths, you have immediate confirmation of whether it's working or not because you can see when it runs whether you've got a beautiful 3D render or a psychedelic mass of wires. So give it a go. Believe in yourself!

## Tetris

I'll now hand the narrative over to my younger self, as the part that follows was written in 2014, shortly after finishing the project. Apparently I anticipated the writeup having more screenshots.

* * *

'Paint' was the first thing I made. Actually the very first thing I made was just a cursor you could control with the NES pad.

The control pad is just a shift register. The pinout and some info is given here. I managed to get it working with the SPI port of the ATmega128 by using the data-out line to control the latch signal. We didn't want to send any data out of the SPI port anyway, so I send zeros, which means that for the duration of the readout the latch signal is low. It unfortunately means that the latch is high between readouts, so the buttons are latched immediately at the end of the readout. I suppose there's a delay to the controller because of that, but it's less than the length of one frame/game loop (16ms). Doing it this way means that just calling an SPI read gives us the button states with no mucking about.

With a moveable cursor on the screen (D-pad just increments/decrements the coordinates sent to the DACs) I made a simple system to push coordinates into the SRAM while A is held and draw all the points each frame. Easy. Paint.

After that I proceeded to try out all kinds of ideas, not all of which made it into games, but most of them did in some form: first, drawing straight lines via bressenham, then polygon lists from program memory, then text, the 3D stuff, bitmap sprites, and so on. I started and didn't finish a few 'simple' games like breakout because my progress was making me more ambitious. When I got the sound working the first midi file I played was the tetris theme. And so, the first game I decided to make into a finished form was Tetris.

I am very meticulous when it comes to these things, and there's nothing I hate more than a fan game that doesn't feel right. Luckily, for Tetris there were great resources, the main one I turned to was the tetrisconcept wiki. I stuck to the physics of TGM (Tetris The Grand Master). Believe it or not there a many different versions of the Tetris physics, but the most important thing is to be consistent.

I spent quite some time getting the delayed-auto-shift correct. Press left once, it moves one step left. Hold it, and after a certain time, it autoshifts at a faster speed than the initial delay. In my experience most flash games get this wrong.

Choosing which block appears next follows quite strict rules. I took the 'bag' approach, described here. To actually generate the randomness I used the pseudorandom number generator in the bootloader.

Lots of difficulties surround rotation. The main problem is dealing with wall kicks, and I again tried to be consistent and went by TGM rotation. Useful resource, that site is.

Scoring may seem simple, but fast-fall (holding down on the d-pad) awards one point, and scoring a tetris on level 9 gives 12000 points. Quite a range, so unfortunately a 16-bit number won't cut it. It's quite possible to score over 65,535... I had no choice but to make it a 24-bit number. At the end of a game, if you've scored high enough, this will go on the highscore table (stored to the cartridge) which I added right at the end of the project.

[2018 edit: You should have done the scoring in BCD, idiot.]

Now, before making any games I had prepared the 3D code, right after doing the bressenham algorithm. But near the end of the project I still hadn't used it for anything, and so, I decided to make a splash screen for Tetris with it.

Watching it is weird. Without any sense of perspective, it's impossible to tell which way it's spinning, and in my mind it keeps changing back and forth. Maybe there's something to do with being left- or right-brained.

Once you're bored of watching it, you get to select a level.

And then play begins.

here it comes....

Woo 1200 points!

But that was only on level 0 of course. I mean, I had to operate the camera at the same time. Normally I'd start on level 9.

That's your lot. But wait, before you rush ahead to page 10, check out the full Tetris source code right here:

```

### Tetris.asm ###

;//////////////////////////////
; Title screen
;//////////////////////////////
ldi r24,0
ldi r20,0 ; level select - check unused
ldi XH,0  ; counter
ldi XL,0
call allNotesOff
showTetrisLogo:

sbrc r3,ctrlStart
rjmp chooseLevel

;increase theta, oscillate phi
inc r24
ldi r16,32
mov r17,XH
rcall sinS
mov r25,r16
subi r25,-64

ldi ZH, HIGH(TetrisLogo*2)
ldi ZL,  LOW(TetrisLogo*2)
rcall drawPolygonListPM

ldi ZH, HIGH(Tetris3DBlocks*2)
ldi ZL,  LOW(Tetris3DBlocks*2)
rcall drawModelListPM

ldi r16, (127+66)
ldi r17, (254)
ldi ZH, HIGH(msgPressStart*2)
ldi ZL,  LOW(msgPressStart*2)
rcall drawTextPM

call BootRandomNumber       ; Continuously seed the RNG

rjmp showTetrisLogo

chooseLevel:

ldi r16,240
mov r5,r16              ; Disable DAS
sbrc r3,ctrlLeft
dec r20
sbrc r3,ctrlRight
inc r20
sbrc r3,ctrlUp
subi r20,5
sbrc r3,ctrlDown
subi r20,-5

sbrc r3,ctrlStart
rjmp startLevel

chLevelCheckRange:  ; Wrap to range 0 - 9
cpi r20,10
brpl chLevelSub10
cpi r20,0

ldi r17, 139
ldi r16,24
mul r16,r20
mov r16,r0
cpi r20, 5
brpl chLevelLowerLine
rjmp chLevelDraw

subi r20,-10
rjmp chLevelCheckRange
chLevelSub10:
subi r20,10
rjmp chLevelCheckRange

chLevelLowerLine:
subi r16,120
subi r17,-30
rjmp chLevelDraw

chLevelDraw:
neg r16
subi r16, 97

; draw rect at r16,r17
ldi r18,24
ldi r19,20
rcall drawRectangle

ldi ZH, HIGH(TetrisLogo*2)
ldi ZL,  LOW(TetrisLogo*2)
rcall drawPolygonListPM

ldi r16,230
ldi r17,130
ldi ZH, HIGH(msgChooseALevel*2)
ldi ZL,  LOW(msgChooseALevel*2)
rcall drawTextPM
ldi r16,(255-80)
ldi r17,156
rcall drawTextPM
ldi r16,(255-80)
ldi r17,186
rcall drawTextPM

rjmp chooseLevel

startLevel:

;//////////////////////////////
; Main Tetris Game code
;//////////////////////////////

;init frame counter
ldi r16,0
out TCNT0, r16

ldi r16, (1<<CS02)|(1<<CS01)|(1<<CS00)
out TCCR0, r16

ldi r16, 128
out OCR0, r16

ldi r16, HIGH(Music*2)
sts MUSIC_START_H, r16
ldi r16,  LOW(Music*2)
sts MUSIC_START_L, r16
rcall resetMusic

.equ BLOCKSIZE = 11
.equ GRIDX = 100
.equ GRIDY = 20
.equ DAS_TIME = 248
.equ ANIM_TIME = 20

.equ HISTORY_LENGTH= 4
.equ HISTORY_TRIES = 6

.equ FALLTIME = D_START+1 ;\$011A
.equ NUMLINES = D_START+2 ;\$011B
.equ CURLEVEL = D_START+3 ;\$011C
.equ INCLEVEL = D_START+4 ;\$011D
.equ HISTORY  = D_START+5
.equ GRID     = D_START+10

.def nextPiece     = r9
.def currentPiece  = r10
.def currentPieceX = r11
.def currentPieceY = r12
.def AnimTimer     = r23
.def fallTimer     = r25

sts CURLEVEL, r20       ; r20 set on the level select screen
rcall setLevelFallSpeed

rcall emptyGrid

; Initially fill the history with S and Z pieces
ldi r16,4
sts (HISTORY), r16
sts (HISTORY+2), r16
ldi r16,5
sts (HISTORY+1), r16
sts (HISTORY+3), r16

rcall loadNewPiece      ; Set currentPiece and nextPiece

ldi r16,0
sts SCORE_H,r16
sts SCORE_M,r16
sts SCORE_L,r16
sts NUMLINES, r16
ldi r16,9
sts INCLEVEL, r16

ldi AnimTimer,0
lds fallTimer,FALLTIME

Main:
cpi AnimTimer,0
brne drawAnimation      ; If we're animating, skip the control sequence

sbrc r3,ctrlLeft
rcall moveLeft
sbrc r3,ctrlRight
rcall moveRight
sbrc r3,ctrlDown
rcall fastDrop

sbrc r3,ctrlB
rcall rotateClockwise
sbrc r3,ctrlA
rcall rotateWithershins

sbrc r3,ctrlStart
rcall PauseGame

dec fallTimer
brne drawScreen

lds fallTimer,FALLTIME  ; Reset Timer

inc currentPieceY
rcall checkCollision    ; Have we hit the bottom?
breq drawScreen         ; No - move along...

dec currentPieceY       ; Else move back again and
rcall placeOntoGrid     ; add it onto the grid

ldi r16, DAS_TIME       ; Important - reset the DAS timer
mov r5, r16

rcall checkForFullRows
breq drawScreen         ; If no rows are full, go to draw

ldi AnimTimer,ANIM_TIME
ldi r16, HIGH(soundLineClear*2)
sts EFFECT_H, r16
ldi r16,  LOW(soundLineClear*2)
sts EFFECT_L, r16

drawAnimation:
ldi r21,0               ; r21 will count the number of lines cleared
dec AnimTimer
breq animationEnd

mov r16,AnimTimer
lsr r16
sbrc r16,0              ; To achieve a flicker effect, only
rjmp drawScreen         ; draw if timer/2 is even

ldi YH, HIGH(GRID+6)
ldi YL,  LOW(GRID+6)
ldi r19, GRIDY-BLOCKSIZE
ldi r18,\$FF
ldi r21,20
drawAnimLoop:
ld r16,Y+
ld r17,Y+
subi r19, (-BLOCKSIZE)

cp r16,r18              ; If both bytes of
cpc r17,r18             ; the row are full...
brne drawAnimLoop2
rcall drawBar           ; draw a bar

drawAnimLoop2:
dec r21
brne drawAnimLoop

drawScreen: ; Continue

rcall drawPiece
rcall drawGrid

push currentPiece           ; drawPiece uses these registers
push currentPieceX          ; so to draw the "next piece"
push currentPieceY          ; we need to push and pop them all
mov currentPiece,nextPiece
ldi r16,6
mov currentPieceY,r16
ldi r16,-3
mov currentPieceX,r16
rcall drawPiece
pop currentPieceY
pop currentPieceX
pop currentPiece

rcall drawScore
ldi r16,0           ; Just move the beam off screen
out PORTA, r16      ; for the rest of the frame
out PORTC, r16

call processAudio

waitforframe:
in r16, TIFR
sbrs r16, OCF0
rjmp waitforframe
ldi r16, (1<<OCF0)
out TIFR, r16
ldi r16, 0
out TCNT0, r16
rjmp Main

animationEnd:
; Now remove the completed lines and increase score
ldi YH, HIGH(GRID+6)
ldi YL,  LOW(GRID+6)
ldi r18,\$FF

animationEndLoop:
ld r16,Y+
ld r17,Y+
cp r16,r18              ; If both bytes of
cpc r17,r18             ; the row are full...
breq shiftRows

cpi YH, HIGH(GRID+46)
brne animationEndLoop
cpi YL,  LOW(GRID+46)   ; Loop until we reach the end
brne animationEndLoop   ; of the playable grid

lds r16, NUMLINES       ; Add to number of lines cleared
sts NUMLINES, r16

; Calculate the score (formula: http://tetrisconcept.net/wiki/Scoring)
ldi r20, 4
cpi r21, 1          ; 1 line = 40 * (level +1)
breq calcScore
ldi r20, 10
cpi r21, 2          ; 2 lines = 100 * (level +1)
breq calcScore
ldi r20, 30
cpi r21,3           ; 3 lines = 300 * (level +1)
breq calcScore
ldi r20, 120        ; 4 lines = 1200 * (level +1)

calcScore:
lds r16, CURLEVEL
inc r16
mul r16,r20
movw r17:r16,r1:r0  ; Multiply by level number
ldi r20,10
mul r16,r20         ; Now multiply that by 10
movw X, r0          ; use X as temp var
mul r17,r20         ; multiply high byte
add XH, r0          ; Now X is equal to score

ldi r20,0
lds r16, SCORE_L
sts SCORE_L, r16
lds r16, SCORE_M
sts SCORE_M, r16
lds r16, SCORE_H
sts SCORE_H, r16

; Every ten lines, increase level
lds r16, INCLEVEL
sub r16, r21
brpl doneIncreaseLevel
subi r16,-10
lds r17, CURLEVEL
inc r17
sts CURLEVEL, r17
doneIncreaseLevel:
sts INCLEVEL, r16

rjmp drawScreen

shiftRows:
inc r21                 ; Count the number of lines cleared
sbiw Y, 2
shiftRowsLoop:
;sbiw Y, 2
ld r16,-Y
ld r17,-Y
std Y+3,r16
std Y+2,r17
cpi YH, HIGH(GRID+2)
brne shiftRowsLoop
cpi YL,  LOW(GRID+2)    ; Loop until we reach the start
brne shiftRowsLoop
rjmp animationEnd

; Function moveLeft
moveLeft:
inc currentPieceX
rcall checkCollision
brne moveLeftCollision
ret
moveLeftCollision:
dec currentPieceX
ret

; Function moveRight
moveRight:
dec currentPieceX
rcall checkCollision
brne moveRightCollision
ret
moveRightCollision:
inc currentPieceX
ret

; Function fastDrop
fastDrop:
ldi fallTimer,1

lds r16, SCORE_L
subi r16,-1
sts SCORE_L, r16
lds r16, SCORE_M
sbci r16,-1
sts SCORE_M, r16
lds r16, SCORE_H
sbci r16,-1
sts SCORE_H, r16

ret

; Function checkForFullRows
; Just a true/false of if any are present
checkForFullRows:
ldi YH, HIGH(GRID+6)
ldi YL,  LOW(GRID+6)

ldi r18,\$FF
checkFullRowLoop:
ld r16,Y+
ld r17,Y+
cp r16,r18              ; If both bytes of
cpc r17,r18             ; the row are full...
breq checkFullRowFull

cpi YH, HIGH(GRID+46)
brne checkFullRowLoop
cpi YL,  LOW(GRID+46)   ; Loop until we reach the end
brne checkFullRowLoop   ; of the playable grid

sez                     ; Set Z flag
ret

checkFullRowFull:
clz                     ; Clear Z flag
ret

placeOntoGrid:
ldi r16, HIGH(soundEffect1*2)
sts EFFECT_H, r16
ldi r16,  LOW(soundEffect1*2)
sts EFFECT_L, r16

rcall pointBlockMap
;move Y pointer to start of grid data
ldi YH, HIGH(GRID)
ldi YL,  LOW(GRID)
ldi r16,0

ldi r20, 4
placeOntoGridLoop:
;load byte of piece from Z+
lpm r16,Z+
ldi r17,0

mov r21, currentPieceX
cpi r21,0
breq placeOntoGridNoshift

placeGridColShiftLoop: ;shift X times
lsl r16
rol r17

dec r21
brne placeGridColShiftLoop

placeOntoGridNoshift:

ld r18, Y
or r18,r16
st Y+,r18

ld r18, Y
or r18,r17
st Y+,r18

dec r20
brne placeOntoGridLoop

ret

; Function rotatePiece - r18 is rotation direction/amount
rotatePiece:
ldi r16, HIGH(soundRotate*2)
sts EFFECT_H, r16
ldi r16,  LOW(soundRotate*2)
sts EFFECT_L, r16
; rotation = last 2 bits of piece type
push currentPiece

ldi r17, \$03
mov r16, currentPiece   ; To get the rotation state,
and r16, r17            ; AND the current piece with \$03
add r16,r18             ; Now do the rotation
and r16, r17            ; null the overflow so it wraps to 0
com r17
and currentPiece, r17   ; Now clear the rotation state of the piece
add currentPiece, r16   ; and set the new one

; Check for collisions
rcall checkCollision
breq rotNoCollision

; Try a wall kick - move left
inc currentPieceX
rcall checkCollision
breq rotNoCollision

; no luck? try moving right
dec currentPieceX
dec currentPieceX
rcall checkCollision
breq rotNoCollision

; still colliding? move back and undo rotation
inc currentPieceX
pop currentPiece
ret

rotNoCollision:
pop r16     ; just to clear currentPiece from stack
ret

; Function rotateClockwise
rotateClockwise:
push r18
ldi r18,-1
rcall rotatePiece
pop r18
ret
; Function rotateWithershins
rotateWithershins:
push r18
ldi r18,1
rcall rotatePiece
pop r18
ret

; function checkCollision
checkCollision:
; move Z pointer to progmem for current piece map
rcall pointBlockMap

;move Y pointer to start of grid data
ldi YH, HIGH(GRID)
ldi YL,  LOW(GRID)
ldi r16,0

ldi r20, 4
checkCollisionLoop:
;load byte of piece from Z+
lpm r16,Z+
ldi r17,0

mov r21, currentPieceX
cpi r21,0
breq checkNoshift

checkColShiftLoop: ;shift X times
lsl r16
rol r17

dec r21
brne checkColShiftLoop

checkNoshift:
;and it with grid data from Y+
;if nonzero goto collision
ld r18, Y+
and r18,r16
brne collision
ld r18, Y+
and r18,r17
brne collision

dec r20
brne checkCollisionLoop

sez     ; (set zero flag for return value)
ret

collision:
clz     ; Clear Z flag
ret

; The TGM method of chosing a new piece is based around keeping a history
; of the last four pieces dealt. We try a certain number of times to choose
; a piece which isn't in the history. This makes receiving the same piece
; several times in a row rare but not impossible.
mov currentPiece, nextPiece
ldi r16, 6
mov currentPieceX, r16
ldi r16, 2
mov currentPieceY, r16
rcall checkCollision
brne GameOver

ldi r21, HISTORY_TRIES

call BootRandomNumber   ; in the correct range
lds r16,RND_H
lsr r16                 ; The higher bits are the "most random"
lsr r16                 ; Divide down...
lsr r16
lsr r16
lsr r16
cpi r16, 7              ; 7 possible pieces
brcc loadNewPieceRnd    ; If outside desired range, try again

ldi YH, HIGH(HISTORY)
ldi YL,  LOW(HISTORY)
ldi r20,HISTORY_LENGTH
loadNewPieceCheck:          ; Compare byte of history (r17)
ld r17, Y+              ; with our chosen piece (r16)
cp r16,r17
breq lnpFoundInHistory

dec r20

; Not in history - we're good to go.

lnpFoundInHistory:          ; If it's in the history, we only try
dec r21                 ; so many times before using the piece anyway.

ldi YH, HIGH(HISTORY)   ; Now push our chosen piece onto the history
ldi YL,  LOW(HISTORY)
ldi r20,HISTORY_LENGTH
lnpUpdateHistory:
ldd r17, Y+1            ; Move each byte one place along
st Y+, r17

dec r20
brne lnpUpdateHistory
; Now place the new byte at the end
sts (HISTORY+HISTORY_LENGTH-1), r16

lsl r16                 ; Multiply by 4 since 'nextPiece' contains
lsl r16                 ; rotation info also. ( We do NOT want to randomize
mov nextPiece, r16      ; initial rotation)

ret

GameOver:
; Show that the piece collides
rcall placeOntoGrid

ldi YH, HIGH(GRID+46)
ldi YL,  LOW(GRID+46)

call allNotesOff
ldi r23,40

GameOverLoop1:
dec r23
mov r16,r23
call setNote1

ldi r16,\$FF
st -Y,r16
st -Y,r16
sbiw Y,2
ldi r16,\$00
st Y+,r16
st Y+,r16

rcall drawGrid

cpi YL,  LOW(GRID)
brne GameOverLoop1
cpi YH, HIGH(GRID)
brne GameOverLoop1

; go to boot Highscore table
;jmp 0
call allNotesOff
jmp BootRunHighscores
;////////////////

PauseGame:
call allnotesoff
ldi r16, 190 ; X
ldi r17, 100 ; Y

ldi r18, '*'
rcall drawCharDouble
ldi r18, '*'
rcall drawCharDouble
ldi r18, ' '
rcall drawCharDouble
ldi r18, 'P'
rcall drawCharDouble
ldi r18, 'A'
rcall drawCharDouble
ldi r18, 'U'
rcall drawCharDouble
ldi r18, 'S'
rcall drawCharDouble
ldi r18, 'E'
rcall drawCharDouble
ldi r18, ' '
rcall drawCharDouble
ldi r18, '*'
rcall drawCharDouble
ldi r18, '*'
rcall drawCharDouble

sbrs r3, ctrlStart
rjmp PauseGame

ret

; Function emptyGrid - resets grid data
emptyGrid:
ldi ZH, HIGH(GRID)
ldi ZL,  LOW(GRID)
ldi r17,23
emptyGridLoop:
ldi r16,0b00000111
st Z+,r16
ldi r16,0b11100000
st Z+,r16
dec r17
brne emptyGridLoop

ldi r16,0b11111111  ; Block up the floor
st Z+,r16
st Z+,r16
st Z+,r16
st Z+,r16
st Z+,r16
st Z+,r16
ret

drawGrid:
ldi r17, (GRIDY-2)
ldi r16, (GRIDX-2)
ldi r18, (BLOCKSIZE*10+4)
ldi r19, (BLOCKSIZE*20+4)
rcall drawRectangle

ldi r17,GRIDY           ; Y coord to top

ldi ZH, HIGH(GRID+6)    ; Start of visible grid
ldi ZL,  LOW(GRID+6)
ldi r20,20              ; 20 rows
drawGridLoop:
; Draw block row

.def RowByte=r24

drawRowRight:
ldi r16,GRIDX           ; X coord to start of row
ld RowByte, Z+          ; Load right hand side of row
ldi r21, 5              ; 5 blocks to draw in this byte
drawRowRightLoop:
sbrc RowByte, 3         ; Go through the five bits that matter
rcall drawBlock         ; draw if set
subi r16, (-BLOCKSIZE)  ; advance cursor X
lsr RowByte             ; shift byte to draw next block

dec r21
brne drawRowRightLoop

drawRowLeft:                ; Repeat for left row
ld RowByte, Z+
ldi r21, 5
drawRowLeftLoop:
sbrc RowByte, 0         ; Check bit 0 this time
rcall drawBlock
subi r16, (-BLOCKSIZE)
lsr RowByte

dec r21
brne drawRowLeftLoop

drawRowEnd:
subi r17,(-BLOCKSIZE)
dec r20
brne drawGridLoop

ret

;function drawPiece
drawPiece:
;move Z pointer to prog mem for current piece map
rcall pointBlockMap

;move x and y cursor to coords of block
ldi r18, BLOCKSIZE

mul currentPieceX, r18
mov r19,r0
mul currentPieceY, r18
mov r17,r0

subi r19, -GRIDX +BLOCKSIZE*3       ; Offset of the grid position and
subi r17, -GRIDY +BLOCKSIZE*3       ; the origin of the blockmap

ldi r20,4
drawPieceLoopY:
lpm r18,Z+          ; Load next byte (1 row)

mov r16, r19
ldi r21, 4
drawPieceLoopX:
sbrc r18, 0
rcall drawBlock
subi r16, (-BLOCKSIZE)
lsr r18

dec r21
brne drawPieceLoopX

subi r17, (-BLOCKSIZE)
dec r20
brne drawPieceLoopY

ret

; Function pointBlockMap
; Just moves the Z pointer to the area of prog mem for current piece
pointBlockMap:
ldi ZH, HIGH(blockMaps*2)
ldi ZL,  LOW(blockMaps*2)
mov r16, currentPiece
lsl r16                 ; Multiply by 4
lsl r16
ldi r16,0
ret

drawScore:
ldi r16, 85 ; X
ldi r17, 40 ; Y

; For such a small amount of text, this is faster
ldi r18, 'N'
rcall drawCharDouble
ldi r18, 'E'
rcall drawCharDouble
ldi r18, 'X'
rcall drawCharDouble
ldi r18, 'T'
rcall drawCharDouble

ldi r16, 85
ldi r17, 120

ldi r18, 'S'
rcall drawCharDouble
ldi r18, 'C'
rcall drawCharDouble
ldi r18, 'O'
rcall drawCharDouble
ldi r18, 'R'
rcall drawCharDouble
ldi r18, 'E'
rcall drawCharDouble

ldi r16, 85
subi r17, -16
lds r20, SCORE_L
lds r21, SCORE_M
lds r22, SCORE_H
rcall drawDec24bit

ldi r16, 85
subi r17, -32
ldi r18, 'L'
rcall drawCharDouble
ldi r18, 'E'
rcall drawCharDouble
ldi r18, 'V'
rcall drawCharDouble
ldi r18, 'E'
rcall drawCharDouble
ldi r18, 'L'
rcall drawCharDouble
ldi r16, 85
subi r17, -16
lds r20, CURLEVEL
rcall drawDecByte

ldi r16, 85
subi r17, -32
ldi r18, 'L'
rcall drawCharDouble
ldi r18, 'I'
rcall drawCharDouble
ldi r18, 'N'
rcall drawCharDouble
ldi r18, 'E'
rcall drawCharDouble
ldi r18, 'S'
rcall drawCharDouble
ldi r16, 85
subi r17, -16
lds r20, NUMLINES
rcall drawDecByte
ret

; function drawBlock(x=r16,y=r17)
drawBlock:
push r18
ldi r18,(BLOCKSIZE-2)
drawBlockL1:
rcall drawBlockPixel
rcall drawBlockPixel    ; Slewrate compensation
rcall drawBlockPixel
inc r17
dec r18
brne drawBlockL1
ldi r18,(BLOCKSIZE-2)
drawBlockL2:
rcall drawBlockPixel
inc r16
dec r18
brne drawBlockL2
ldi r18,(BLOCKSIZE-2)
drawBlockL3:
rcall drawBlockPixel
dec r17
dec r18
brne drawBlockL3
ldi r18,(BLOCKSIZE-2)
drawBlockL4:
rcall drawBlockPixel
dec r16
dec r18
brne drawBlockL4
subi r17,-2
subi r16,-1
ldi r18,(BLOCKSIZE-5)
drawBlockL5:
rcall drawBlockPixel
inc r17
dec r18
brne drawBlockL5
ldi r18,(BLOCKSIZE-4)
drawBlockL6:
rcall drawBlockPixel
inc r16
dec r18
brne drawBlockL6
subi r16, (BLOCKSIZE-3)
subi r17, (BLOCKSIZE-3)

pop r18
ret

drawBlockPixel:
out PORTA, r16
out PORTC, r17
nop
;   nop
;   nop
;   nop
;   nop
;   nop
;   nop
;   nop
;   nop
;   nop

ret

//  Function drawRectangle (x,y,width,height)
drawRectangle:
;Function arguments
.def x0=r16
.def y0=r17
.def width=r18
.def height=r19

push height
out PORTA,x0

drawRectDown:
inc y0
out PORTC,y0
rcall dRectDel
dec height
brne drawRectDown
pop height
push width

drawRectRight:
inc x0
out PORTA,x0
rcall dRectDel
dec width
brne drawRectRight
pop width

drawRectUp:
dec y0
out PORTC,y0
rcall dRectDel
dec height
brne drawRectUp

drawRectLeft:
dec x0
out PORTA,x0
rcall dRectDel
dec width
brne drawRectLeft

ret

dRectDel:
nop
nop
nop
nop
nop
ret

; Function drawBar (y = r19)
drawBar:
push r18
push r19
ldi r20, (BLOCKSIZE>>1)
ldi r16, GRIDX
mov r17, r19
drawBarLoop:
ldi r18, (BLOCKSIZE*10)
ldi r19, 2
rcall drawRectangle
inc r17
inc r17
dec r20
brne drawBarLoop

pop r19
pop r18
ret

/* Function drawDecByte(x,y,byte)
* draws a one byte number in decimal without leading zero
*/
drawDecByte:
push r18
ldi r18, '0'-1
cpi r20, 100
brcc drawByteCheckHund
cpi r20, 10
brcc drawByteCheckTens
rjmp drawByteOnes
drawByteCheckHund:
inc r18
subi r20, 100
brcc drawByteCheckHund
subi r20, -100
rcall drawCharDouble
ldi r18, '0'-1
drawByteCheckTens:
inc r18
subi r20, 10
brcc drawByteCheckTens
subi r20, -10
rcall drawCharDouble
drawByteOnes:
ldi r18, '0'
rcall drawCharDouble

pop r18
ret

/* Function drawDec24bit(x,y,low,med,high)
* draws a three byte number in decimal up to 9,999,999
*/
drawDec24bit:
; arguments
; x    = r16    ; x and y pass through to drawCharDouble unchanged
; y    = r17
.def byteL = r20
.def byteM = r21
.def byteH = r22
; local
push r18
push r23
ldi r18, '0'-1

cpi r20, BYTE1(1000000)     ; Remove leading zeros...
ldi r23, BYTE2(1000000)
cpc r21, r23
ldi r23, BYTE3(1000000)
cpc r22, r23
brcc d24bitChkMil

cpi r20, BYTE1(100000)
ldi r23, BYTE2(100000)
cpc r21, r23
ldi r23, BYTE3(100000)
cpc r22, r23
brcc d24bitChkHundThou

cpi r20,  LOW(10000)
ldi r23, HIGH(10000)
cpc r21, r23
brcc d24bitChkTenThou

cpi r20,  LOW(1000)
ldi r23, HIGH(1000)
cpc r21, r23
brcc d24bitChkThou

cpi r20,  LOW(100)
ldi r23, HIGH(100)
cpc r21, r23
brcc d24bitChkHund

cpi r20,  10
brcc d24bitChkTens

rjmp d24bitRemainder

d24bitChkMil:
inc r18
subi byteL,BYTE1(1000000)
sbci byteM,BYTE2(1000000)
sbci byteH,BYTE3(1000000)
brcc d24bitChkMil

subi byteL,BYTE1(-1000000)
sbci byteM,BYTE2(-1000000)
sbci byteH,BYTE3(-1000000)
rcall drawCharDouble

ldi r18, '0'-1
d24bitChkHundThou:
inc r18
subi byteL,BYTE1(100000)
sbci byteM,BYTE2(100000)
sbci byteH,BYTE3(100000)
brcc d24bitChkHundThou

subi byteL,BYTE1(-100000)
sbci byteM,BYTE2(-100000)
sbci byteH,BYTE3(-100000)
rcall drawCharDouble

ldi r18, '0'-1      ; From here on we know byteH = 0
d24bitChkTenThou:
inc r18
subi byteL, LOW(10000)
sbci byteM,HIGH(10000)
brcc d24bitChkTenThou

subi byteL, LOW(-10000)
sbci byteM,HIGH(-10000)

rcall drawCharDouble

ldi r18, '0'-1
d24bitChkThou:
inc r18
subi byteL, LOW(1000)
sbci byteM,HIGH(1000)
brcc d24bitChkThou

subi byteL, LOW(-1000)
sbci byteM,HIGH(-1000)

rcall drawCharDouble

ldi r18, '0'-1
d24bitChkHund:
inc r18
subi byteL, LOW(100)
sbci byteM,HIGH(100)
brcc d24bitChkHund

subi byteL, LOW(-100)
sbci byteM,HIGH(-100)

rcall drawCharDouble

ldi r18, '0'-1
d24bitChkTens:
inc r18
subi byteL, LOW(10)
sbci byteM,HIGH(10)
brcc d24bitChkTens

subi byteL, LOW(-10)
sbci byteM,HIGH(-10)

rcall drawCharDouble
d24bitRemainder:
ldi r18, '0'

rcall drawCharDouble

pop r23
pop r18
ret

/* Function drawCharDouble(x0,y0,char)
* Same as drawChar but twice the size
*/
drawCharDouble:
.def x0=r16
.def y0=r17
.def char=r18
push r19
push ZH
push ZL
; Each character is a 5 byte bitmap

ldi ZL,  LOW(bitmapFontTable*2)
ldi ZH, HIGH(bitmapFontTable*2)
subi char,32                    ; Font starts at ascii 32

ldi r19,5
mul char,r19                    ; Multiply char by 5
movw char,r0                    ; Result is in R1:R0

ldi r19,5

drawCharDblLoopByte:
lpm char, Z+
push y0

drawCharDblLoopBit:

sbrs char,0
rjmp drawCharDblNextBit

out PORTC,y0
out PORTA,x0
rcall delayChar

drawCharDblNextBit:
dec y0
dec y0
lsr char
brne drawCharDblLoopBit
dec x0
dec x0
pop y0

dec r19
brne drawCharDblLoopByte

dec x0                  ; Move cursor ready to draw next character
dec x0
pop ZL
pop ZH
pop r19
ret

delayChar:
push r16
ldi r16,\$07
count2:
dec r16
brne count2
pop r16
ret

/*  Function drawTextPM(x0,y0)
*  draws a message from Z pointer program memory
*/
drawTextPM:
out PORTC,y0
out PORTA,x0
push r18

drawTextPMLoop:
lpm r18,Z+              ; Load next character
cpi r18,0               ; (null terminated string)
breq drawTextPMend
rcall drawCharDouble    ; x and y are passed straight through to drawChar
rjmp drawTextPMLoop

drawTextPMend:
pop r18
ret

; r2 is buttons held down. r3 is buttons only just pressed.
; r4 and r5 as state variables
sbis SPSR,SPIF          ; Wait for previous reception to complete
com r2                  ; Invert (logic low is button down)

; Work out what buttons have just been pressed.
; r4 is the button states from the previous reading
mov r3,r2               ; Copy into r3
eor r3,r4               ; XOR gives us the buttons which have changed
and r3,r2               ; AND it with the the buttons currently held down

mov r4, r2              ; Save button states into r4 for next time

push r16
push r17

; Delayed-Auto-shift mechanism
; This is made slightly more complicated because we do not want to
; apply repeated presses to the rotation buttons - so screen them out first
ldi r17,\$0F
and r17,r2
ldi r16,DAS_TIME
mov r5,r16              ; reset DAS timer

inc r5                  ; increase DAS timer

or r3, r17              ; Load the held direction into the 'pressed' register
dec r5                  ; but don't affect the button presses
dec r5                  ; Rewind a bit so that repetition is every 2 frames

ldi r16,0b00000000      ; Initiate the next reading
out SPDR,r16
pop r17
pop r16
ret

; Function setLevelFallSpeed
; Numbers found at http://tetrisconcept.net/wiki/Tetris_%28NES,_Nintendo%29
setLevelFallSpeed:
lds r16,CURLEVEL            ; Above level 29, fall speed is 1.
cpi r16,29
brcc setLevelGt29

ldi ZH, HIGH(levelFallSpeed*2)
ldi ZL,  LOW(levelFallSpeed*2)
ldi r16,0
lpm r16,Z
sts FALLTIME,r16

ret

setLevelGt29:
ldi r16,1               ; Set speed to 1
sts FALLTIME,r16
ret

.include "3Dfunctions.asm"

; Level - fallspeed relation
levelFallSpeed:
.db 48,43,38,33,28,23,18,13,8,6,5,5,5,4,4,4,3,3,3,2,2,2,2,2,2,2,2,2,2,1

; Data mapping the shape of each possible piece, padded with zeros
; to ease collision checking. 4 bytes per rotation, 4 rotations per piece.
blockMaps:
; I
.db \
0b00000000,\
0b00001111,\
0b00000000,\
0b00000000,\
0b00000010,\
0b00000010,\
0b00000010,\
0b00000010,\
0b00000000,\
0b00001111,\
0b00000000,\
0b00000000,\
0b00000010,\
0b00000010,\
0b00000010,\
0b00000010

; T
.db \
0b00000000,\
0b00001110,\
0b00000100,\
0b00000000,\
0b00000100,\
0b00001100,\
0b00000100,\
0b00000000,\
0b00000000,\
0b00000100,\
0b00001110,\
0b00000000,\
0b00000100,\
0b00000110,\
0b00000100,\
0b00000000

;L
.db \
0b00000000,\
0b00001110,\
0b00001000,\
0b00000000,\
0b00001100,\
0b00000100,\
0b00000100,\
0b00000000,\
0b00000000,\
0b00000010,\
0b00001110,\
0b00000000,\
0b00000100,\
0b00000100,\
0b00000110,\
0b00000000

;J
.db \
0b00000000,\
0b00001110,\
0b00000010,\
0b00000000,\
0b00000100,\
0b00000100,\
0b00001100,\
0b00000000,\
0b00000000,\
0b00001000,\
0b00001110,\
0b00000000,\
0b00000110,\
0b00000100,\
0b00000100,\
0b00000000

;S
.db \
0b00000000,\
0b00000110,\
0b00001100,\
0b00000000,\
0b00001000,\
0b00001100,\
0b00000100,\
0b00000000,\
0b00000000,\
0b00000110,\
0b00001100,\
0b00000000,\
0b00001000,\
0b00001100,\
0b00000100,\
0b00000000

;Z
.db \
0b00000000,\
0b00001100,\
0b00000110,\
0b00000000,\
0b00000010,\
0b00000110,\
0b00000100,\
0b00000000,\
0b00000000,\
0b00001100,\
0b00000110,\
0b00000000,\
0b00000010,\
0b00000110,\
0b00000100,\
0b00000000

;O
.db \
0b00000000,\
0b00000110,\
0b00000110,\
0b00000000,\
0b00000000,\
0b00000110,\
0b00000110,\
0b00000000,\
0b00000000,\
0b00000110,\
0b00000110,\
0b00000000,\
0b00000000,\
0b00000110,\
0b00000110,\
0b00000000

; Polygon list for the logo.
TetrisLogo:
.db \
7, \
9, \
253,7, 208,7, 208,25, 221,25, 221,89, 240,89, 240,25, 253,25, 253,7, \
13, \
205,7, 205,89, 153,89, 163,70, 188,70, 188,45, 179,45, 171,31, 188,31, 188,24, 174,24, 165,7, 205,7, \
9, \
164,7, 164,25, 151,25, 151,89, 133,89, 133,25, 120,25, 120,7, 164,7, \
12, \
118,7, 118,89, 101,89, 101,19, 93,19, 101,33, 73,89, 51,89, 80,32, 88,32, 73,7, 118,7, \
5, \
67,7, 67,25, 49,25, 49,7, 67,7, \
5, \
67,27, 67,54, 49,89, 49,27, 67,27, \
13, \
47,7, 47,25, 25,70, 42,70, 47,66, 47,89, 5,89, 5,66, 29,20, 15,20, 13,25, 6,7, 47,7

Tetris3DBlocks:
.db \$3B,\
\$05,\
\$0A, \$9C, \$00,\
\$0A, \$C4, \$00,\
\$0A, \$C4, \$28,\
\$0A, \$9C, \$28,\
\$0A, \$9C, \$00,\
\$02,\
\$0A, \$B0, \$00,\
\$0A, \$B0, \$28,\
\$02,\
\$0A, \$9C, \$14,\
\$0A, \$C4, \$14,\
\$05,\
\$F6, \$9C, \$00,\
\$F6, \$C4, \$00,\
\$F6, \$C4, \$28,\
\$F6, \$9C, \$28,\
\$F6, \$9C, \$00,\
\$02,\
\$F6, \$B0, \$00,\
\$F6, \$B0, \$28,\
\$02,\
\$F6, \$9C, \$14,\
\$F6, \$C4, \$14,\
\$02,\
\$0A, \$B0, \$00,\
\$F6, \$B0, \$00,\
\$02,\
\$0A, \$B0, \$14,\
\$F6, \$B0, \$14,\
\$02,\
\$0A, \$B0, \$28,\
\$F6, \$B0, \$28,\
\$02,\
\$0A, \$9C, \$00,\
\$F6, \$9C, \$00,\
\$02,\
\$0A, \$9C, \$14,\
\$F6, \$9C, \$14,\
\$02,\
\$0A, \$9C, \$28,\
\$F6, \$9C, \$28,\
\$02,\
\$0A, \$C4, \$00,\
\$F6, \$C4, \$00,\
\$02,\
\$0A, \$C4, \$14,\
\$F6, \$C4, \$14,\
\$02,\
\$0A, \$C4, \$28,\
\$F6, \$C4, \$28,\
\$05,\
\$0A, \$6E, \$00,\
\$0A, \$32, \$00,\
\$0A, \$32, \$14,\
\$0A, \$6E, \$14,\
\$0A, \$6E, \$00,\
\$04,\
\$0A, \$46, \$00,\
\$0A, \$46, \$28,\
\$0A, \$5A, \$28,\
\$0A, \$5A, \$00,\
\$05,\
\$F6, \$6E, \$00,\
\$F6, \$32, \$00,\
\$F6, \$32, \$14,\
\$F6, \$6E, \$14,\
\$F6, \$6E, \$00,\
\$04,\
\$F6, \$46, \$00,\
\$F6, \$46, \$28,\
\$F6, \$5A, \$28,\
\$F6, \$5A, \$00,\
\$02,\
\$F6, \$46, \$00,\
\$0A, \$46, \$00,\
\$02,\
\$F6, \$5A, \$00,\
\$0A, \$5A, \$00,\
\$02,\
\$F6, \$6E, \$00,\
\$0A, \$6E, \$00,\
\$02,\
\$F6, \$32, \$00,\
\$0A, \$32, \$00,\
\$02,\
\$F6, \$46, \$14,\
\$0A, \$46, \$14,\
\$02,\
\$F6, \$5A, \$14,\
\$0A, \$5A, \$14,\
\$02,\
\$F6, \$6E, \$14,\
\$0A, \$6E, \$14,\
\$02,\
\$F6, \$32, \$14,\
\$0A, \$32, \$14,\
\$02,\
\$F6, \$46, \$28,\
\$0A, \$46, \$28,\
\$02,\
\$F6, \$5A, \$28,\
\$0A, \$5A, \$28,\
\$07,\
\$6E, \$0A, \$00,\
\$32, \$0A, \$00,\
\$32, \$0A, \$14,\
\$5A, \$0A, \$14,\
\$5A, \$0A, \$28,\
\$6E, \$0A, \$28,\
\$6E, \$0A, \$00,\
\$03,\
\$6E, \$0A, \$14,\
\$5A, \$0A, \$14,\
\$5A, \$0A, \$00,\
\$02,\
\$46, \$0A, \$14,\
\$46, \$0A, \$00,\
\$07,\
\$6E, \$F6, \$00,\
\$32, \$F6, \$00,\
\$32, \$F6, \$14,\
\$5A, \$F6, \$14,\
\$5A, \$F6, \$28,\
\$6E, \$F6, \$28,\
\$6E, \$F6, \$00,\
\$03,\
\$6E, \$F6, \$14,\
\$5A, \$F6, \$14,\
\$5A, \$F6, \$00,\
\$02,\
\$46, \$F6, \$14,\
\$46, \$F6, \$00,\
\$02,\
\$46, \$F6, \$00,\
\$46, \$0A, \$00,\
\$02,\
\$46, \$F6, \$14,\
\$46, \$0A, \$14,\
\$02,\
\$5A, \$F6, \$00,\
\$5A, \$0A, \$00,\
\$02,\
\$32, \$F6, \$00,\
\$32, \$0A, \$00,\
\$02,\
\$6E, \$F6, \$00,\
\$6E, \$0A, \$00,\
\$02,\
\$5A, \$F6, \$14,\
\$5A, \$0A, \$14,\
\$02,\
\$6E, \$F6, \$14,\
\$6E, \$0A, \$14,\
\$02,\
\$32, \$F6, \$14,\
\$32, \$0A, \$14,\
\$02,\
\$5A, \$F6, \$28,\
\$5A, \$0A, \$28,\
\$02,\
\$6E, \$F6, \$28,\
\$6E, \$0A, \$28,\
\$09,\
\$92, \$0A, \$00,\
\$BA, \$0A, \$00,\
\$BA, \$0A, \$14,\
\$CE, \$0A, \$14,\
\$CE, \$0A, \$28,\
\$A6, \$0A, \$28,\
\$A6, \$0A, \$14,\
\$92, \$0A, \$14,\
\$92, \$0A, \$00,\
\$04,\
\$A6, \$0A, \$00,\
\$A6, \$0A, \$14,\
\$BA, \$0A, \$14,\
\$BA, \$0A, \$28,\
\$09,\
\$92, \$F6, \$00,\
\$BA, \$F6, \$00,\
\$BA, \$F6, \$14,\
\$CE, \$F6, \$14,\
\$CE, \$F6, \$28,\
\$A6, \$F6, \$28,\
\$A6, \$F6, \$14,\
\$92, \$F6, \$14,\
\$92, \$F6, \$00,\
\$04,\
\$A6, \$F6, \$00,\
\$A6, \$F6, \$14,\
\$BA, \$F6, \$14,\
\$BA, \$F6, \$28,\
\$02,\
\$BA, \$F6, \$28,\
\$BA, \$0A, \$28,\
\$02,\
\$A6, \$F6, \$28,\
\$A6, \$0A, \$28,\
\$02,\
\$CE, \$F6, \$28,\
\$CE, \$0A, \$28,\
\$02,\
\$BA, \$F6, \$14,\
\$BA, \$0A, \$14,\
\$02,\
\$A6, \$F6, \$14,\
\$A6, \$0A, \$14,\
\$02,\
\$CE, \$F6, \$14,\
\$CE, \$0A, \$14,\
\$02,\
\$92, \$F6, \$14,\
\$92, \$0A, \$14,\
\$02,\
\$92, \$F6, \$00,\
\$92, \$0A, \$00,\
\$02,\
\$BA, \$F6, \$00,\
\$BA, \$0A, \$00,\
\$02,\
\$A6, \$F6, \$00,\
\$A6, \$0A, \$00

msgPressStart: .db "PRESS START",0
msgChooseALevel: .db "* SELECT A LEVEL *",0,"0 1 2 3 4",0,"5 6 7 8 9",0

bitmapFontTable:
.db \$00,\$00,\$00,\$00,\$00,\$00,\$00,\$FA,\$00,\$00,\$00,\$E0,\$00,\$E0,\$00,\$28,\$FE,\$28,\$FE,\$28,\$24,\$54,\$FE,\$54,\$48,\$C4,\$C8,\$10,\$26,\$46,\$6C,\$92,\$6A,\$04,\$0A,\$00,\$00,\$E0,\$00,\$00,\$38,\$44,\$82,\$00,\$00,\$00,\$00,\$82,\$44,\$38,\$44,\$28,\$FE,\$28,\$44,\$10,\$10,\$7C,\$10,\$10,\$00,\$02,\$0C,\$00,\$00,\$10,\$10,\$10,\$10,\$10,\$00,\$00,\$02,\$00,\$00,\$04,\$08,\$10,\$20,\$40,\$7C,\$8A,\$92,\$A2,\$7C,\$00,\$42,\$FE,\$02,\$00,\$46,\$8A,\$92,\$92,\$62,\$84,\$82,\$92,\$B2,\$CC,\$18,\$28,\$48,\$FE,\$08,\$E4,\$A2,\$A2,\$A2,\$9C,\$3C,\$52,\$92,\$92,\$8C,\$80,\$8E,\$90,\$A0,\$C0,\$6C,\$92,\$92,\$92,\$6C,\$62,\$92,\$92,\$94,\$78,\$00,\$00,\$28,\$00,\$00,\$00,\$02,\$2C,\$00,\$00,\$10,\$28,\$44,\$82,\$00,\$28,\$28,\$28,\$28,\$28,\$00,\$82,\$44,\$28,\$10,\$40,\$80,\$9A,\$A0,\$40,\$7C,\$82,\$BA,\$9A,\$72,\$3E,\$48,\$88,\$48,\$3E,\$FE,\$92,\$92,\$92,\$6C,\$7C,\$82,\$82,\$82,\$44,\$FE,\$82,\$82,\$82,\$7C,\$FE,\$92,\$92,\$92,\$82,\$FE,\$90,\$90,\$90,\$80,\$7C,\$82,\$82,\$8A,\$8E,\$FE,\$10,\$10,\$10,\$FE,\$00,\$82,\$FE,\$82,\$00,\$04,\$02,\$02,\$02,\$FC,\$FE,\$10,\$28,\$44,\$82,\$FE,\$02,\$02,\$02,\$02,\$FE,\$40,\$30,\$40,\$FE,\$FE,\$20,\$10,\$08,\$FE,\$7C,\$82,\$82,\$82,\$7C,\$FE,\$90,\$90,\$90,\$60,\$7C,\$82,\$8A,\$84,\$7A,\$FE,\$90,\$98,\$94,\$62,\$64,\$92,\$92,\$92,\$4C,\$80,\$80,\$FE,\$80,\$80,\$FC,\$02,\$02,\$02,\$FC,\$F8,\$04,\$02,\$04,\$F8,\$FE,\$04,\$18,\$04,\$FE,\$C6,\$28,\$10,\$28,\$C6,\$C0,\$20,\$1E,\$20,\$C0,\$86,\$8A,\$92,\$A2,\$C2,\$00,\$FE,\$82,\$82,\$00,\$40,\$20,\$10,\$08,\$04,\$00,\$82,\$82,\$FE,\$00,\$20,\$40,\$80,\$40,\$20,\$01,\$01,\$01,\$01,\$01,\$00,\$80,\$40,\$20,\$00,\$04,\$2A,\$2A,\$2A,\$1E,\$FE,\$22,\$22,\$22,\$1C,\$1C,\$22,\$22,\$22,\$22,\$1C,\$22,\$22,\$22,\$FE,\$1C,\$2A,\$2A,\$2A,\$1A,\$10,\$7E,\$90,\$90,\$40,\$18,\$25,\$25,\$25,\$1E,\$FE,\$20,\$20,\$20,\$1E,\$00,\$22,\$BE,\$02,\$00,\$02,\$01,\$21,\$BE,\$00,\$FE,\$08,\$08,\$14,\$22,\$00,\$82,\$FE,\$02,\$00,\$3E,\$20,\$1C,\$20,\$3E,\$3E,\$20,\$20,\$20,\$1E,\$1C,\$22,\$22,\$22,\$1C,\$3F,\$24,\$24,\$24,\$18,\$18,\$24,\$24,\$24,\$3F,\$3E,\$10,\$20,\$20,\$20,\$12,\$2A,\$2A,\$2A,\$24,\$20,\$FC,\$22,\$22,\$04,\$3C,\$02,\$02,\$04,\$3E,\$38,\$04,\$02,\$04,\$38,\$3E,\$02,\$0C,\$02,\$3E,\$22,\$14,\$08,\$14,\$22,\$38,\$05,\$05,\$05,\$3E,\$22,\$26,\$2A,\$32,\$22,\$10,\$6C,\$82,\$82,\$00,\$00,\$00,\$FF,\$00,\$00,\$00,\$82,\$82,\$6C,\$10,\$40,\$80,\$C0,\$40,\$80,\$54,\$28,\$54,\$28,\$54,\$00,\$00,\$00,\$00,\$00

soundEffect1:
.db \$FF,\$FF,\$FF,\$FF,\$FF,\$FF,\$FF,\$FF,\$FF,\$FF,\$FF,\$FF,0
soundRotate:
.db 35,35,35,35,35,43,43,43,43,43,43,43,43,43,0
soundLineClear:
.db 60,60,60,60,60,60,52,52,52,52,52,52,60,60,60,60,60,60,52,52,52,52,52,52,0

.include "audio.asm"
Music:
.include "Music/TetrisMusic1.asm"