;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; miKroTetRIS v 2.1 Started 4/6/1992 by MAK-TRAXON's Prophet ;; ;; latest update: 15/2/1993 ;; ;; a TETRIS in less than 1/2 K ! ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; progseg segment public assume cs:progseg,ds:progseg;es:progseg;ss:progseg ; MASM-compatible header for EXE2BINable prog org 0 offs0 label byte o0 equ offset offs0 ; label at offset 0, later used for calculations by equ byte ptr wo equ word ptr jmps equ jmp short ; some handy shortcuts ;;;;;;;;;;;;;;;;;;;;;;;;;;;; VARS in the PSP ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; org 80h ; 2nd half of the PSP (can be used freely) arrlines = $ - offset offs0 ; address of lines array @arrlines db 24 dup (?) ; lines arrray: number of filled squares in ; each line ; must be at least 1 byte longer than the ; actual nb of lines (for optimisation in ; the "lines" procedure) randseed dw ? ; for the pseudo-random number succession ; these 2 vars must be just after arrlines (for ; optimisation at init) .erre 0feh gt ($ - o0) ; assembly error if end vars > end PSP ;;;;;;;;;;;;;;;;;;;;;;;;;; ASSEMBLER CONSTANTS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; wwidth = 10 ; width of Tetris well height = 20 ; height of Tetris well firstline = 3 ; first screen line used for Tetris well firstcol = 30 ; first screen col used for Tetris well ; must be even empty = 178 ; char used to fill empty positions falling = 255 ; char used to draw the falling piece steady = 32 ; char used to draw the steady pieces ; must be lower than the other 2 (for optimisation in ; the "hits" procedure) ; the three are used in reverse video mode ; (attribute 70h) left = 71 ; scan code of key used to move left ( num. pad 7 ) right = 73 ; scan code of key used to move right ( num. pad 9 ) krotation = 72 ; scan code of key used for rotation ( num. pad 8 ) kdrop = 57 ; scan code of key used to drop ( space ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; MAIN PROG ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; org 100h ; for .COM file entpt: cld ; assumed in the rest of the program mov di,arrlines xor ax,ax mov cx,height+4 ; must be the exact size of the buffer rep stosb ; clear lines array ; (set mode 3, remove cursor, set ES = 0b800h, randomize) ; change to mode 7 and ES = 0b000h for MONO mov al,3 ; ah = 0 push ax ; used at exit to CLS again int 10h mov ch,0b8h ; cl = 0 mov es,cx int 1ah ; get time (ah=0) mov [di],dx ; randomize (put ticks in randseed) mov bp,dx ; save ticks in BP inc ah ; -> ah = 1 mov ch,20h int 10h ; remove cursor ; display the Tetris screen: ; 2 = chr(178) is the background, spaces are used for the steady pieces, ; chr(255) is used for falling pieces ; all chars have the inverse video (70h) attribute ; the well is surrounded by spaces => no need for special checks for the ; border of the well. mov di,160*firstline+2*firstcol ; position on the screen mov bl,height ; nb of lines do1line: mov ax,7000h+empty ; attrib and char mov cx,2*wwidth ; nb of chars rep stosw add di,160-4*wwidth ; next line dec bl jnz do1line ; loop gameloop: ; 8 * (random between 0 and 6) -> AX randloop: mov ax,1001 mul wo randseed inc ax mov randseed,ax ; calc next random and ax,38h cmp al,30h ja randloop ; if not between 0 and 30h -> try again mov rotation,ah ; ah = 0 add ax,offset pieces mov thepiece,ax ; calc & save adr of current piece mov bx,7+100h*height ; bl = x position of the falling piece ; bh = y position of the falling piece ; start position : x = 7, y = height call hits ; test if current piece hits something jc lost ; if it does, you've lost whilefalling: call drawfalling; draw the piece with the 'falling' char add bp,3 ; ticks+3 -> bp looptime: call keyboard xor ah,ah int 1ah ; read ticks cmp dx,bp jc looptime jl looptime ; loop keyboard for 3 clock ticks ; This time-handling strategy works as long as the value returned in DX ; by int 1ah increases by 1 every 18th of a second. Only the lower word of ; the counter is used (CX is ignored), but the double comparison (jc and jl) ; works even when the counter is incremented past 0ffffh or past 7fffh. ; Time handling will only fail at midnight when the ticks value drops from 0b0h ; to 0. The game will be frozen for 0b3h/18d w 10 seconds. ; If the game is paused using Ctrl-NumLock (or Pause on AT keyboards), ; the pieces will "free-fall" afterwards ! call putempty ; remove piece dec bh ; one down call hits ; if does not hit anything jnc whilefalling; then loop inc bh ; up again mov al,steady call put ; draw it steady ; if some lines are full, remove them lines: mov cx,height mov si,arrlines looplines: lodsb cmp al,wwidth jnc line ; scan lines array for full lines loop looplines jmps gameloop ; return to game when there are no more ; lines to remove line: push si ; needed for later push cx ; needed for later ; remove line; cx=line number from 1 to 20 (20=bottom) mov si,160*firstline - 2 ; position on the screen mov di,160+160*firstline - 2 dec cx mov al,160 mul cl mov cx,ax ; length to move push ds push es pop ds ; set segments add di,ax ; end position (for std move) add si,ax std rep movsb ; scroll lines cld ; cld assumed by the prog pop ds ; reset segment pop cx pop si ; recover saved regs fix_arr: lodsb ; fix lines array mov [si-2],al loop fix_arr jmps lines ; restart the lines procedure ; cls and exit lost: pop ax ; = video mode int 10h ret ; exit ; this works on .COM files because DOS pushes a 0 ; on the stack before executing the program, and at ; address 0 (in the PSP), there is a INT 20h ; If this does not work on some configuration or DOS ; version, change this RET to an INT 20h ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; STRUCTURES ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; descrpiece struc ; encoding: for each piece, the 4 positions are cubes1_2 db ? ; stored in 2 words each, split into 8 2-bit fields cubes3_4 db ? ; for the X and Y coord of each square in a 4x4 descrpiece ends ; matrix piece struc shape1 descrpiece ? shape2 descrpiece ? shape3 descrpiece ? shape4 descrpiece ? piece ends ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; CONSTANTS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pieces label piece piece1 label piece ; ++++++++ descrpiece <026h 0aeh> descrpiece <089h 0abh> descrpiece <026h 0aeh> descrpiece <089h 0abh> piece2 label piece ; ++++ ; ++++ descrpiece <06ah 059h> descrpiece <06ah 059h> descrpiece <06ah 059h> descrpiece <06ah 059h> piece3 label piece ; ++++++ ; ++ descrpiece <096h 0aeh> descrpiece <09ah 0beh> descrpiece <059h 0dah> descrpiece <069h 0abh> piece4 label piece ; ++++++ ; ++ descrpiece <06ah 0deh> descrpiece <09ah 0bfh> descrpiece <059h 0d6h> descrpiece <059h 0abh> piece5 label piece ; ++++++ ; ++ descrpiece <056h 0aeh> descrpiece <09ah 0bdh> descrpiece <059h 0deh> descrpiece <079h 0abh> piece6 label piece ; ++++ ; ++++ descrpiece <059h 0aeh> descrpiece <0abh 0deh> descrpiece <059h 0aeh> descrpiece <0abh 0deh> piece7 label piece ; ++++ ; ++++ descrpiece <069h 0adh> descrpiece <09ah 0efh> descrpiece <069h 0adh> descrpiece <09ah 0efh> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; PROCEDURES ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; readpos proc ; input : CX = number of the square in the current piece ( 1 to 4 ) ; output: DI = offset in screen memory, DX = line number ; CX is saved push cx ; save CX dec cx shr cx,1 pushf add cl,rotation mov si,cx ; calc address of byte to get ; 2 bytes / position db 08ah, 84h ; opcode of mov al,[si+constant] thepiece dw 0 ; self-modifying prog: ; thepiece = adr of current piece data popf mov cl,4 jnc norott shr al,cl ; get the correct bits in the low nybble norott: and ax,0fh ; clear high nybble mov dx,ax and dl,03h shr cl,1 ; cl = 4 -> cl = 2 shr ax,cl add al,bl ; al = col , ah = 0 add dl,bh ; dl = line , dh = 0 ; firstline*160 + firstcol*2 + 160*(19-dx) + 4*ax + 3*160 - 16 -> DI ; firstline*160 + firstcol*2 - 160*dx + 160*19 + 4*ax + 3*160 - 16 -> DI ; 4 * [ 40*19+firstcol/2+40*firstline+3*40-4 + ax - 40*dx ] -> DI ; ( 19 is height-1 ) ; without changing DX; assumes CL=2 mov di,ax mov al,40 mul dl add di,40*(height - 1) + firstcol/2 + 40*firstline + 3*40 - 4 sub di,ax shl di,cl ; do the calculation pop cx ; restore cx ret readpos endp putempty proc mov al,empty putempty endp ; no RET: enters "put" with al=empty put proc ; input: AL = char ; draws the current piece in the current position, with the char in AL ; (AL=empty to clear the piece). ; does not check if it "hits" anything mov cx,4 ; 4 squares loopput: push ax call readpos ; calc position in screen mem in DI, ; line number in DX pop ax mov si,dx stosb ; put char inc di stosb ; 1 square = 2 chars cmp al,steady+1 adc by arrlines[-3][si],dh ; update lines array nosteady: loop loopput ; ret ; this is SAVAGE optimization: executing ; the next procedure here does no harm, ; so why not remove the RET ? put endp hits proc ; input: none ; output: CF = 1 if the current piece in the current position hits other ; pieces on the screen or does not fit in the well, else CF = 0 ; DH = 0 (used in some optimizations) mov cx,4 loophits: call readpos ; calc position in screen mem in DI cmp by es:[di],steady+1 ; is steady? jc a_ret ; yes -> exit with CF=1 loop loophits ; test the 4 squares a_ret: ret ; no need for CLC: loop does not ; change CF hits endp keyboard proc ; read the keyboard, process keys mov ah,1 int 16h ; keypressed? jz a_ret ; if not keypressed -> ret call putempty ; remove piece from screen mov ah,0 int 16h ; read key mov al,ah ; scan code in AL cmp al,left jnz notleft ; if key is left dec bl ; move left call hits ; does it hit ? adc bl,dh ; if it does, move right again ; (dh = 0) drawfalling label near ; call here to draw a falling piece put_exit: mov al,falling jmps put ; draw piece in new position ; "put" will do its ret notleft: cmp al,right jnz notright ; if key is right inc bl ; move right call hits ; does it hit? sbb bl,dh ; if it does: move left again (dh=0) right_ok: jmps put_exit ; draw piece & exit notright: cmp al,krotation; if key is rotation jnz notrot db 0b0h ; opcode for mov al,immediate rotation db 0 ; self-modifying code: mov al,rotation ; rotation = current rotation of the piece ; always even, from 0 to 6 push ax ; save rotation inc ax inc ax and al,6 ; calc new rot: add 2 and modulo 6 ; even number => modulo 6 = and 6 again: mov rotation,al ; set new rot call hits ; does it hit ? pop ax ; recover AX jnc put_exit ; if it does push ax ; AX back in the stack jmps again ; try again (with old AX so it will not fail) notrot: cmp al,kdrop ; key is drop? jnz put_exit ; if not, redraw piece & exit loopdrop: dec bh ; down 1 level call hits ; does it hit? jnc loopdrop ; if not, loop inc bh ; up 1 level jmps put_exit ; draw piece & exit ; the DROP key only brings the piece as low as it can go, so the playes still ; has time to move it sidewise. keyboard endp ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; END ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; progseg ends end entpt