; This is Tetris. You know, the game with the tetrominoes. ; ; Up to spin, left/right to move. No other controls. Most of the CPU time seems ; to be spent detecting collisions. When you clear a row, the score is ; incremented by the square of the amount of rows you've done at once. As such, ; you are incentivised to get as many rows at once as possible. ; ; Also, how do you pronounce tetromino? I always used to think the stress was on ; the penultimate syllable, but apparently it's on the antepenultimate, like ; "domino". If you don't know, "antepenultimate" is what comes before ; "penultimate", which itself comes before "ultimate". These words go even ; further; there's preantepenultimate, and propreantepenultimate. I wrote this ; whole comment without looking anything up. ; x8 - xB store cols to OR with ; x6, x7 store tetromino in all 4 rotations ; x5 is x coord start: ; set visible score to 0 st !0,!@score ld @scbm,x1 ld 11,x2 ld 1,x3 s1: st x0,x1 add x1,x3,x1 sub x2,x3,x2 jf s1 st !0x3800,x1 add x1,x3,x1 st !0x4400,x1 add x1,x3,x1 st !0x3800,x1 ; init cols. of display that are not changed during gameplay ; god I wish there was an easier way to do this ; (in all seriousness, this is how I'd do it in C) ld 0xe,xD xor x0,x0,xF xor x0,x0,x1 out xF,xD out x1,xD add x1,x3,x1 out xF,xD out x1,xD add x1,x3,x1 out xF,xD out x1,xD add x1,x3,x1 out xF,xD out x1,xD add x1,!9,x1 out xF,xD out x1,xD add x1,x3,x1 out x0,xD ; (we use x0 for these last ones) out x1,xD add x1,x3,x1 out x0,xD out x1,xD add x1,!16,x1 out x0,xD out x1,xD add x1,x3,x1 out x0,xD out x1,xD jmp dant ; MAIN LOOP -------------------------------------------------------------------- lskm: ld @ty,x1 ; loop (skip movement down) ld x1,x4 jmp lsk loop: ld @ty,x1 ld x1,x4 add x4,!1,x4 st x4,x1 lsk: and x6,!0xf,xE shf xE,x4,x1 and x7,!0xf,xE shf xE,x4,x2 shf x6,!-28,xE shf xE,x4,x3 shf x7,!-28,xE shf xE,x4,x4 ; check for collision; if so, place tetromino and x1,x8,x0 jf place and x2,x9,x0 jf place and x3,xA,x0 jf place and x4,xB,x0 jf place ; no collision; OR them and render the ORed result or x1,x8,x1 or x2,x9,x2 or x3,xA,x3 or x4,xB,x4 ; now draw x1-x4 ld 0xe,xE ld 1,xD out x1,xE out x5,xE add xD,x5,x5 out x2,xE out x5,xE add xD,x5,x5 out x3,xE out x5,xE add xD,x5,x5 out x4,xE out x5,xE sub x5,!3,x5 ; now check for inputs to see if we should rotate ld 0xd,x2 ; keyboard pin in x2,x2 ld @rand,x1 ; use user input for RNG ld x1,x3 xor x3,x2,x3 rot x3,xD,x3 ; xD is still equal to 1 st x3,x1 ; now dispatch to handler function for keypress xor x2,!-2,x0 ; key-code for UP jnf spin xor x2,!-1,x0 ; key-code for LEFT jnf left xor x2,!-3,x0 ; key-code for RIGHT jnf right jmp loop place: ; go back by one, using shf ld -1,xE ; now OR the tetromino into the registers that store board data shf x1,xE,x1 shf x2,xE,x2 shf x3,xE,x3 shf x4,xE,x4 or x1,x8,x8 ; note the dest of these 4 ORs; compare to above or x2,x9,x9 or x3,xA,xA or x4,xB,xB ; write the new 4 columns (x8 - xB) to RAM ld 1,x1 ld @lb,x2 add x2,x5,x2 st x8,x2 add x1,x2,x2 st x9,x2 add x1,x2,x2 st xA,x2 add x1,x2,x2 st xB,x2 ; set ty to 0 ld @ty,x1 st x0,x1 ; check for full row ld @board,x1 ld 1,x3 ld 7,x4 ld 0x7fffffff,x2 ; leftmost bit is the floor plc1: ld x1,xE and x2,xE,x2 jnf newtet ; no full row add x1,x3,x1 sub x4,x3,x4 jf plc1 ; We have between 1 and 4 (inclusive) full rows. ; We need to move bits from the right to the left by 1 to "remove" these ; rows. Where C is any given column, and R is our ANDed value (i.e. x2), ; the algorithm is: ; ; WORD R ; WORD C ; ; C := C & ~R ; WHILE R != 0 DO ; WORD T ; WORD RIGHT ; ; RIGHT := (R-1 ^ R) ; T := (C & RIGHT) << 1 ; C := (C & ~RIGHT) | T ; R := R & ~RIGHT ; DONE ; (In our case, it's a do-while loop rather than a while loop, since we ; know R != 0 at first) ; We no longer care about preserving the registers x8-xB. xor x0,x0,xB ; use this to accumulate the popcount xor x2,x0,xC ld @board,x1 ld 1,x3 ld 7,x4 plc2: ld x1,xD xor xC,x0,xF xor xC,x0,x2 and xD,xF,xD ; C := C & ~R plcdw: add x3,xB,xB ; (popcount++) sub x2,x3,xE xor x2,xE,xE ; RIGHT := (R-1 ^ R) and xD,xE,x8 shf x8,x3,x8 ; T := (C & RIGHT) << 1 xor xE,x0,xF ; try describing the contents of xF out loud, without ; confusing anyone and xD,xF,xD or xD,x8,xD ; C := (C & ~RIGHT) | T and x2,xF,x2 ; R := R & ~RIGHT st xD,x1 xor x2,x0,x0 ; loop handling for plcdw jf plcdw add x1,x3,x1 ; loop handling for plc2 sub x4,x3,x4 jf plc2 ; we want to update the visible score ; first, the popcount was generated 8 times in a loop, so: shf xB,!-3,xB mul xB,xB,xB ; we use the square, to reward getting more rows at once. ld !@score,x4 add xB,x4,x4 st x4,xE ; now update the digits in scbm mod x4,!10000,x4 ; loop around if we hit 9999 ld 12,x3 updig: ; update digit mod x4,!10,x1 ld nt2,xC xor x3,x0,x2 jmp digit nt2: sub x3,!4,x3 div x4,!10,x4 jf updig ld 1,x3 ; draw all cols to screen dant: ld @board,x1 ; "draw and new tetromino" ld 1,x3 ld 7,x4 ld 4,x8 ld 0xe,xE plc3: ld x1,x2 out x2,xE out x8,xE add x1,x3,x1 add x8,x3,x8 sub x4,x3,x4 jf plc3 newtet: ; create new tetromino ; we assume that the game board has been properly rendered to the screen ; with no un-placed tetromino ld 1,x3 ld 6,x5 ; x coord ld @lb,xB add x5,xB,xB ; assembler can't do this at assemble-time yet ; populate x8-xB with cols ld xB,x8 add xB,x3,xB ld xB,x9 add xB,x3,xB ld xB,xA add xB,x3,xB ld xB,xB ; first we read the next tetromino from "next", then we set "next" to a ; randomly selected tetromino ld @next,xC ld xC,x6 add xC,x3,xD ld xD,x7 ; select random tetromino ld @rand,x1 ld x1,x2 rot x2,!3,x2 st x2,x1 mod x2,!7,x2 ; there are only 7 tetrominoes ; (ANDing by 7 and setting to n<7 if equal to 7 would be faster, where n ; is some kind of default value, but I put in all this work to add a MOD ; instruction to the 323, so I'm going to use a MOD instruction.) add x2,x2,x2 ; shift left by 1 add x2,!@tets,x2 ld x2,x1 st x1,xC add x2,x3,x2 ld x2,x4 st x4,xD ; store tetromino preview to prvbm ;st x8,!@tmp ; next tetromino is now in x1 and x4 ld @prvbm1,xC ; x coord of left col. of preview and x1,!0xf,x2 shf x2,!3,x2 or x2,!0x102,x2 st x2,xC add xC,x3,xC and x4,!0xf,x2 shf x2,!3,x2 or x2,!0x102,x2 st x2,xC add xC,x3,xC shf x1,!-28,x2 shf x2,!3,x2 or x2,!0x102,x2 st x2,xC add xC,x3,xC shf x4,!-28,x2 shf x2,!3,x2 or x2,!0x102,x2 st x2,xC ;ld !@tmp,x8 ; display score with preview ; assume x3 is still 1 ld @prvbm,x1 ld @scbm,x2 ld 14,x4 ld 0xe,xE nt1: ld x1,xC ld x2,xD or xC,xD,xC out xC,xE ld 29,xC sub xC,x4,xC out xC,xE add x1,x3,x1 add x2,x3,x2 sub x4,x3,x4 jf nt1 ; check for collision; game over if so ld die,xC ld loop,xD xor x0,x0,x4 jmp ic1 spin: ld 8,x1 rot x6,x1,x6 rot x7,x1,x7 ld unspin,xC ld loop,xD jmp iscol unspin: ld -8,x1 rot x6,x1,x6 rot x7,x1,x7 jmp loop left: add x5,!3,xD ; first, draw rightmost column (xB) without tetromino ld 0xe,xE out xB,xE out xD,xE ; move x8 - xB over, fill x8 from RAM sub x5,!1,x5 xor xA,x0,xB xor x9,x0,xA xor x8,x0,x9 ld @lb,x8 add x8,x5,x8 ld x8,x8 ; check for collision ld right,xC ld lskm,xD jmp iscol right: ld 0xe,xE ; first, draw leftmost column (xB) without tetromino out x8,xE out x5,xE ; move x8 - xB over, fill xB from RAM add x5,!1,x5 xor x9,x0,x8 xor xA,x0,x9 xor xB,x0,xA ld @lb,xB add xB,x5,xB add xB,!3,xB ld xB,xB ld left,xC ld lskm,xD jmp iscol ; is collision? ; tests to see if tetromino is overlapping the game board's contents or border. ; returns to xC if so, else xD. iscol: ld @ty,x1 ld x1,x4 ic1: and x6,!0xf,xE shf xE,x4,xE and xE,x8,x0 jf xC and x7,!0xf,xE shf xE,x4,xE and xE,x9,x0 jf xC shf x6,!-28,xE shf xE,x4,xE and xE,xA,x0 jf xC shf x7,!-28,xE shf xE,x4,xE and xE,xB,x0 jf xC jmp xD upsc: ; update score ; FOR i = 0, 3 digit: ; draw digit x1 to scbm at x coord x2 add x2,!@scbm,x2 add x1,!@digs,x1 ld x1,x1 ; now x1 stores the data for the digit shf x1,!-10,xD shf xD,!10,xD st xD,x2 add x2,!1,x2 shf x1,!-5,xD and xD,!0x1f,xD shf xD,!10,xD st xD,x2 add x2,!1,x2 and x1,!0x1f,xD shf xD,!10,xD st xD,x2 jmp xC die: ; YOU ARE DEAD ; NOT BIG SURPRISE ld 0xe,xE out x0,xE out x0,xE hlt ; VARIABLES -------------------------------------------------------------------- align 2 ty: dw 0 ; y coord of tetromino lb: rep -1 4 ; left border board: rep 0x80000000 8 ; 8 columns to play in dw -1 rep 0 3 ; the board is surrounded by full columns on each side, to simplify ; collision code (you can't move a tetromino too far to either side) ; The tetrominoes are stored in a kind of complicated format. ; Rotation is too complicated to re-compute every time you want to spin ; a tetromino. As such, the tetrominoes are stored as 4x4 regions, with ; all 4 of their rotations stored separately. This adds up to 64 bits, ; or 2 words. ; ; So there are 4 nybbles, each of which is one column of the 4x4, which ; define a particular rotation of a particular tetromino. To minimise ; the amount of instructions needed to retrieve them, they are the left- ; most and right-most nybbles in the 2 words. To understand why, ; consider what is needed to retrieve a nybble anywhere else: 1 SHF, and ; 1 AND. For the right-most nybble, you only need AND, and for the left- ; most one, you just need SHF. As such, we save 2 or 4 instructions. ; ; Where A, B, C, and D denote the rotation that a nybble corresponds to, ; the structure of a pair of words is as such: ; ABBCCDDA ; ABBCCDDA ; 90-degree rotation is thus achieved by a literal ROT by 8 bits. ; I think it's elegant that bitwise rotation is used for geometric ; rotation. tets: dw 0xe0c0e060 ; T dw 0x04404444 dw 0x40f440f4 ; I dw 0x40044004 dw 0xc0c06060 ; S dw 0x0840c426 dw 0xc06060c0 ; Z dw 0x624c0480 dw 0x60606060 ; O dw 0x06060606 dw 0xe040e040 ; L dw 0x0c408462 dw 0xe040e040 ; J dw 0x264804c0 rand: dw 0x5defaced ; makeshift RNG next: ; 2 words to store the next tetromino; used for previews and init dw 0x40f440f4 ; I dw 0x40044004 score: dw 0 ; score bitmap scbm: rep 0x5a000000 15 ; preview bitmap prvbm: rep 0x00000000 3 rep 0x000001fe 1 rep 0x00000102 1 prvbm1: rep 0x00000102 5 rep 0x000001fe 1 rep 0x00000000 4 ; 5*3 bitmap data for decimal digits used to display score digs: dw 0x3a2e ; 0 dw 0x4bf0 ; 1 dw 0x66b2 ; 2 dw 0x46aa ; 3 dw 0x1c9f ; 4 dw 0x5ea9 ; 5 dw 0x3aad ; 6 dw 0x07a3 ; 7 dw 0x2aaa ; 8 dw 0x5aae ; 9 tmp: dw 0 ; for when you run out of registers