C64 Tetris

Here is the original Tetris program I wrote back in the early 90s. Everything is as it was. Nothing is changed. That's quite generous on my part, owing to the very weird spacing conventions I used. Did someone in COMPUTE! tell me to do that to save valuable bytes? Very weird. I would have to pay a memory inducing quack thousands of dollars to pluck that little shard of memory from my head. Apart from the "out-there" spacing conventions, there are numerous other errors too embarrassing to bring to everyone's attention in bullet point form.

Of particular note is the fact that this isn't ordinary Tetris -- this is Tetris with pieces that are made with 5 bricks instead of the usual humdrum 4 brick pieces. I guess this shouldn't really be called Tetris. Pentris would have been better, but then people would have vandalized the arcade machine cases and made crude remarks with the game's name... so Tetris II is the official name for this one. For a straw-man attack to support this mediocre naming scheme, see the Intel naming scheme for the Pentium chips.

I was able to pluck it from the original disk and into ASCII land by using the XE1451 Extended Cable, the Star Commander Program, and a paper clip. Many thanks to the creators of these products... I don't believe the original disks would have withstood another winter outside in an unheated shed on my parent's farm. The screen shots were obtained from the VICE emulator.

2 poke53281,0:poke53280,0:printchr$(8)
Everything in black. How psychologically interesting. My mother said that even as toddlers, a personality is quite well formed. And look no further than right here -- we've got the POKEs and the Cascading Style Sheets to prove this idiom right.
3 printchr$(158):poke650,0 5 dims(17,4,4) 6 b=1053:c=55325:fora=1to7:pokeb,93:b=b+40:pokec,12:c=c+40:nexta:pokeb+40,74 8 pokeb+1,75:pokec+1,12:c=c-41:b=b-41:fora=1to7:pokeb,66:b=b-40:pokec,12:c=c-40
All these FOR loops created the border for the "next shape" box -- we're POKE'ing in the border lines with FOR loops. The screen display codes give this one away, if you translate them using the proper Commodore cypher -- 12 is space, 74 is a round character in the upper right corner, 75 is the same in the left corner, and 66 is "|".
12 fora=1to30:readz9:readx9$:nexta 15 fora=1to17:forb=1to4:forc=1to4:reads(a,b,c):nextc:nextb:nexta
All of the shapes are loaded into the "s" array. Each indice represents one block of a total shape. The a indice delineates each shape, the b indice determines the shape it becomes for each of the three rotations, and the c indice describes the each individual block of the pieces. There are only 4 because the middle block is always known to exist, so there is no need to store its indice.
20 print"{clear}":goto25 21 b=1052:c=55324:fora=1to7:pokeb,93:b=b+40:pokec,12:c=c+40:nexta:pokeb,74 22 pokec,12:c=c+1:b=b+1:fora=1to10:pokeb,67:b=b+1:pokec,12:c=c+1:nexta 23 pokeb,75:pokec,12:c=c-40:b=b-40:fora=1to7:pokeb,66:b=b-40:pokec,12:c=c-40 24 nexta:return 25 gosub21
GOTOs considered harmful -- what about a GOTO (line 20) into a GOSUB (line 25) to prime three back-to-back FOR loops. Why did my parents never buy me academic journals for Christmas?
30 fora=1to17 60 fora=1034to1760step40:pokea,66:nexta:gosub10000 70 poke1794,109:fora=1795to1810:pokea,67:nexta 80 poke1811,125:fora=1771to1049step-40:pokea,93:nexta 82 b=55306:fora=1to19:pokeb,12:b=b+40:nexta:fora=1to17:pokeb,12:b=b+1:nexta 83 fora=1to20:pokeb,12:b=b-40:nexta 90 c=1924:wg=8:gosub5000

The image shown displays the screen as it is being constructed. The circles (character number 81 in Commodore-speak) were actually put into the screen buffer, and then turned black, and therefore invisible. The pieces were made visible by changing the color of the individual circles black.

Line 5000 seems to contain the "a-mazin' subrutine", which 'unfolds' a message on the screen. See the subroutine for more details. The message displayed is identified by the wg variable, which in turn references a particular element of the data array of messages located later on. The c variable holds the screen location of where the message starts.
100 sh=int(rnd(0)*17+1):sp=1:s3=int(rnd(0)*17+1) 101 ifw<1030orw>1212thenl3=5 102 ifw<1229orw>1452thenl3=10 103 ifw<1469orw>1692thenl3=15 104 ifw<1709orw>1802thenl3=19 120 remshape-down! 130 w=55316:ti$="000000" 140 pokew,1:fora=1to4:pokew+s(sh,sp,a),1:nexta:geta$:iffd=1then4000
In line 140, we do a couple of things. One, we draw the shape. Secondly, if fd is 1, then the space bar has been pressed (see line 149), and we can go into the proverbial free-fall mode.
145 poke1138,81:fora=1to4:poke1138+s(s3,1,a),81:nexta 146 poke1053,14:poke1054,5:poke1055,24:poke1056,20:poke1058,19:poke1059,8 147 poke1060,1:poke1061,16:poke1062,5
This is particularly stupid. In Commodore-speak, 14 = N, 5 = e, 24 = x, etc. This puts down the words "next shape" during EVERY TURN.
149 ifa$=" "thenfd=1 150 ifa$="z"then1000 160 ifa$="x"then1000 170 ifa$=","then1000 180 ifa$="."then1000 190 ifa$=chr$(13)then3000 200 ifti$>"000000"then4000 210 goto140:print210 1000 remchehx 1001 ifti$>"000000"then4000
A string comparison... you know, this could be the root cause of the performance issues.
1002 pokew,0:fora=1to4:pokew+s(sh,sp,a),0:nexta 1003 w2=w:s7=sp:tr=0 1004 ifa$="z"thensp=sp+1 1005 ifsp=5thensp=1
Ignore this. Very sloppy work here. This is already taken care of in line 1015.
1010 ifa$="x"thensp=sp-1 1011 ifa$="."thenw=w+1 1012 ifa$=","thenw=w-1 1015 ifsp=0thensp=4
Looks like the sp variable is the rotation variable, and the w variable is the width variable.
1020 fora=1to4 1021 c=peek(s(sh,sp,a)+w) 1031 ifc=224thentr=tr+1 1032 ifc=160thentr=tr+1 1033 ifc=128thentr=tr+1 1034 ifc=176thentr=tr+1 1035 ifc=32thentr=tr+1 1037 ifc=144thentr=tr+1 1039 ifc=16thentr=tr+1 1040 ifc=0thentr=tr+1 1041 ifc=48thentr=tr+1 1042 ifc=112thentr=tr+1 1043 ifc=240thentr=tr+1 1044 ifc=96thentr=tr+1
We're checking to see if we hit any of the shapes currently placed on the board. We're PEEKing to see what's in that location. And then the tr variable... well, I'll get back to you. When it is 4, that means there are no spaces being taken up by the HYPOTHETICAL next position of the shape, and we can move the shape down one more line.
1045 nexta 1047 iftr=4then140
...and if we do see that we've got something in the way, we leave the shape right there.
1171 w=w2:sp=s7:goto140 3000 rempause
This is free fall mode. Straight down!
4000 w2=w:s7=sp:tr=0: 4020 pokew,0:fora=1to4:pokew+s(sh,sp,a),0:nexta
Erase the shape in the previous space.
4025 w=w+40
This moves the shape down a notch. This is the HYPOTHETICAL position that the shape will take on the next turn. We now check to make sure that it can fit there (i.e. no other shape's in the way).
4030 fora=1to4 4040 s9=w+s(sh,sp,a) 4050 c=peek(s9) 4060 ifc=0thentr=tr+1 4061 ifc=144thentr=tr+1 4062 ifc=16thentr=tr+1 4065 ifc=48thentr=tr+1 4067 ifc=96thentr=tr+1 4068 ifc=176thentr=tr+1 4070 ifc=112thentr=tr+1 4075 ifc=128thentr=tr+1 4080 ifc=240thentr=tr+1 4082 ifc=32thentr=tr+1 4083 ifc=160thentr=tr+1 4084 ifc=224thentr=tr+1
Tsk tsk. Copied code. Do you think I used the old print-the-lines-out-then-rewrite-the-new-line-numbers-over-top trick? I miss doing that -- vi-like power in a Microsoft product.
4085 nexta 4090 iftr=4then4500 4100 w=w2:sp=s7:gosub21 4110 pokew,1:fora=1to4:pokew+s(sh,sp,a),1:nexta 4115 poke1138,96:fora=1to4:poke1138+s(s3,1,a),96:nexta 4120 sh=s3:s3=int(rnd(0)*17+1) 4130 w=55316:gosub6000
If we've hit bottom, then we can check to see if any lines have been made.
4140 fd=0:ti$="000000":sp=1:goto140 4500 remo.k.
The shape is OK, nothing's in the way... let's drop the shape another level.
4510 pokew,1:fora=1to4:pokew+s(sh,sp,a),1:nexta 4520 ti$="000000" 4540 goto140 5000 rem*a-mazin' subrutinÁ

The amazing subroutine. This one takes in a string, an integer describing the length of the string, and the c variable which describes the middle point. and creates an ASCII animation with that string. Starting from the middle, a box containing the text expands to reveal the string (i.e. [l] -> [ell] -> [Hello]).
5005 ifwg>29then5035 5010 restore 5020 fora=1towg:forb=1to2:reada1$:nextb:nexta 5030 readb1:reada1$ 5035 c1=b1 5037 c2=c
Wouldn't want any side effects, would we? Can't be wrecking that valuable c variable. I am sure that EJD would have smiled at this childhood adherence to acceptable software practices from his place in the sky (well, actually, back then, I guess it would have been Texas).
5040 fora=0tob1 5050 d1=asc(mid$(a1$,b1,1)) 5051 ifd1<28ord1>62then5070 5069 pokec,asc(mid$(a1$,b1,1)):goto5075 5070 pokec,asc(mid$(a1$,b1,1))-64
POKE the text in there, starting from the MIDdle.
5075 pokec+40,64:pokec-40,64 5076 pokec+1,66:pokec+41,75:pokec-39,73 5080 b1=b1+1:c=c+1:nexta 5081 b1=c1:pokec,66:pokec+40,75:pokec-40,73
The lines are placed above and below the text (one column is 40 characters wide in Commodore-speak).
5085 c=c2 5090 fora=1tob1 5095 d1=asc(mid$(a1$,b1,1)) 5096 ifd1<28ord1>62then5100 5097 pokec,asc(mid$(a1$,b1,1)):goto5105 5100 pokec,asc(mid$(a1$,b1,1))-64
This code is never entered... Looks like I was testing to see if I could put everything into lowercase.
5105 pokec+40,64:pokec-40,64: 5107 pokec-1,66:pokec-41,85:pokec+39,74 5110 b1=b1-1:c=c-1:nexta 5115 pokec,66:pokec-40,85:pokec+40,74 5120 return 6000 rem check for 'da lines
Well, now that we're all the way here, I should tell you a little secret. This program never worked. I was so close... but it just wouldn't properly check for any lines. Perhaps the... OH MY GOD! I found the problem. The xx variable is incremented by one for every line we hit. If it hits 16, then we've got a line. BUT, if there was no line it would have been less than 16. And in the incriminating line 6060, we subtract by 16 every time. How heartbreaking. I was THIS close to child freakin' genius of the decade. UPDATE: Nope, that's not it. Not getting any smarter in my old age, I guess. Fairly embarrassing to leave those previous comments there. That said, in the spirit of this code review, I'll not discriminate against stupidity of a more recent vintage. The xx variable simply keeps track of the current screen location that we are checking for the presence of present blocks. Perhaps the wrong value from the peek is being returned in line 6025?
6010 xx=55307 6020 fornn=1to19 6023 fornj=1to16 6025 c=peek(xx) 6030 ff=c/16 6031 fg=int(ff) 6032 iffg<>ffthentr=tr+1 6040 xx=xx+1:nextnj 6050 iftr=16then9000 6060 xx=xx-16:xx=xx+40:nextnn 6070 return 9000 rem gnarly! a line!
Here, we move all the lines down. Laboriously going through all the lines that are above the subtracted lines, we look at what's above, and move it down. Is there any other way?
9010 xx=xx-nj:xx=xx-1 9020 fornn=1to16:xx=xx+1 9030 pokexx,15:forx=1to50:nextx:pokexx,12:forx=1to50:nextx:pokexx,11 9040 forx=1to50:nextx:pokexx,0 9050 dx=xx 9060 dx=dx-40 9065 c=peek(dx):ff=c/16:fg=int(ff) 9070 ifff<>fgthenpokedx-40,1:pokedx,0 9080 dx=dx-40:ifdx<55296thennextnn 9090 x=int(random(0)*13+1)
If ya got a line, I've got a message for ya...
9091 ifx=1thenwg=0 9092 ifx=2thenwg=1 9093 ifx=3thenwg=3 9094 ifx=4thenwg=12 9095 ifx=5thenwg=14 9096 ifx=6thenwg=15 9097 ifx=7thenwg=17 9098 ifx=8thenwg=19 9099 ifx=9thenwg=23 9100 ifx=10thenwg=24 9101 ifx=11thenwg=25 9102 ifx=12thenwg=26 9103 ifx=13thenwg=27 9110 c=1924:gosub5000 9120 return 10000 xx=1035:x1=55307:forb=1to19 10001 fora=xxtoxx+15:pokea,81:nexta:xx=xx+40:fora=x1tox1+15:pokea,0:nexta:x1=x1+40 10002 nextb:return 50000 data10,your're a tetris dude,12,keep those lines coming!
Here's the fun part. Is there any better word than gnarly? And dude. And superdude! Of course, I knew when to quit -- I cooled things down with that "regular tetris person" line.
50010 data8,gnarly! a tetris,14,excellent placing of tetrads 50020 data5,game over!,5,pause mode,19,select song by pressing number(1 to 6) 50030 data12,start song again (y-n)?,8,start your game! 50040 data18,maybe candyland would be more your game!
Ooooh. Ya, Candyland... just like my little SISTER plays. Loser.
50050 data17,press (space) or m to choose music 50060 data16,1000 points till your high score,5,high score 50070 data11,regular tetris person,4,oh! oh!,11,superdude! great going
These are interesting... the previously mentioned "regular tetris person" quotation... the orgasmic "oh! oh!"... and the saccharine yet jarring "superdude! great going". All in one line, folks.
50080 data2,wow!,6,high scores!,10,major gnarly player!,5,try again. 50090 data5,excellent!,14,ha! ha! that score is a joke 50100 data14,disgrace to the tetris race!,3,gross!
"Disgrace to the Tetris Race!" I rather like this one. Nice cadence.
50110 data14,tetrisdude. or tetrisdudette,9,been playing alot?,3,cool!!
Even back then, I knew that SEXISM WAS WRONG.
50120 data16,condider yourselve a mentalblock,7,you are a hero,4,you suck 55560 data40,80,1,41,40,41,42,1,40,80,41,81,1,2,41,42 55565 data40,80,39,41,40,80,39,38,1,2,41,81,40,41,42,80 55570 data40,1,41,81,1,2,40,41,40,80,41,81,1,40,41,39 55575 data1,2,42,43,40,39,79,119,1,41,42,43,40,80,79,119 55580 data40,80,120,160,1,2,3,4,40,80,120,160,1,2,3,4 55585 data1,41,81,121,40,1,2,3,40,80,120,121,40,39,38,37 55590 data1,40,80,120,40,41,42,43,40,80,120,119,1,2,3,43 55595 data40,41,81,121,40,39,1,2,40,80,81,121,1,40,39,38 55600 data40,41,42,2,1,41,81,80,1,2,40,42,40,80,81,1 55605 data40,80,120,79,1,2,3,42,40,80,120,41,39,40,41,42 55610 data40,80,120,39,1,2,3,41,40,80,120,81,38,39,40,41 55615 data40,80,39,41,40,80,41,39,40,80,41,39,40,80,41,39 55620 data40,80,81,82,40,80,79,78,1,2,42,82,1,2,40,80 55625 data1,40,39,79,40,41,81,82,40,39,79,78,1,41,42,82 55630 data40,39,41,81,1,40,80,39,40,41,42,81,40,80,41,79 55635 data40,80,81,39,40,39,38,79,1,41,81,42,40,41,39,79 55640 data40,39,38,78,1,41,81,82,40,41,42,82,1,41,81,82 55650 data0,31,254,0,31,254,0,95,254,0,199,112,3,135,112,6,63,254,12,0,0,13,187 55660 data46,29,18,168,53,147,46,69,18,162,133,146,174,4,0,0,5,255,254,4,7,112,4 55670 data7,112,8,31,254,0,31,254,0,31,254,0,0,0,0,0,0
Here are the shapes. I'm going to whip up a Perl script to read this in and display them.