;****************** TETRIS.ASM -- TinyTetris v1.0
; This is a Tetris game written in ASM.
Ideal
Jumps
LEFT = 4Bh ;Key values
RIGHT = 4Dh
DOWN = 50h
SPACE = 39h
ENTERK = 1Ch
ESCAPE = 01h
F10 = 44h
Model Tiny
P186
CodeSeg
Org 100h
Start: jmp Main
;**************************** Static data for program
CustomFont db 14 dup(-1),192,128,-1,-2,12 dup(-4),0,0
PieceStart dw offset Piece1,offset Piece2
dw offset Piece3,offset Piece4
dw offset Piece5,offset Piece6
dw offset Piece7
db 1 ;AND mask
Piece1 db -1, 0, 1, 0, 2, 0 ; ▄▄▄▄
db 0,-1, 0, 1, 0, 2
db 3 ;AND mask
Piece2 db 1,-1, 1, 0, -1, 0 ; █▄▄
db 1, 1, 0, 1, 0,-1
db -1, 1, -1, 0, 1, 0
db -1,-1, 0,-1, 0, 1
db 3 ;AND mask
Piece3 db -1,-1, -1, 0, 1, 0 ; ▄▄█
db 1,-1, 0,-1, 0, 1
db 1, 1, 1, 0, -1, 0
db -1, 1, 0, 1, 0,-1
db 3 ;AND mask
Piece4 db 1, 0, 0,-1, -1, 0 ; ▄█▄
db 0, 1, 1, 0, 0,-1
db -1, 0, 0, 1, 1, 0
db 0,-1, -1, 0, 0, 1
db 1 ;AND mask
Piece5 db -1, 0, 0,-1, 1,-1 ; ▄█▀
db 0, 1, -1, 0, -1,-1
db 1 ;AND mask
Piece6 db 1, 0, 0,-1, -1,-1 ; ▀█▄
db 0,-1, -1, 0, -1, 1
db 0 ;AND mask
Piece7 db 0,-1, -1,-1, -1, 0 ; ██
TitleStr db '* TinyTetris v1.0 *',0
PlayAgain db 'Play again (Y/N) ?',0
OverStr db ' G A M E O V E R ',0
ScoreStr db 'Score:',0
LevelStr db 'Level:',0
LinesStr db 'Lines:',0
LeftStr db 'Lines Left:',0
;**************************** Main program
Proc Main
mov ax,3 ;Set text mode
int 10h
mov ax,1110h ;Set font chars CE, CF to
mov bx,1000h ;the 'block' image
mov cx,2
mov dx,0CEh
mov bp,offset CustomFont
int 10h
mov ah,2 ;Turn off the cursor by
xor bh,bh ;placing it off the screen
mov dx,1E00h
int 10h
mov di,offset Spaces10 ;Set up space buffer
mov cx,10
mov al,20h
rep stosb
xor al,al
stosb
mov cx,18 ;Set up top/bottom lines
mov al,0DFh
rep stosb
xor al,al
stosb
mov cx,18
mov al,0DCh
rep stosb
xor ax,ax
stosb
push ax ;ES = 0
pop es
mov ax,[es:046Ch] ;Seed random number with
mov [RandNum],ax ;the BIOS time counter
mov ax,[es:046Eh]
mov [RandNum+2],ax
push 0B800h ;ES = 0B800h (text video memory)
pop es
GameLoop: call Tetris ;Play game
call ClearScreen ;Clear screen
mov cx,31 ;Print 'Play again?' string
mov dx,12 ;in the middle of the screen
mov si,offset PlayAgain
mov al,9Fh ;Color = blinking white on blue
call PutStr
call FlushBuffer ;Flush key buffer
PKeyLoop: xor ax,ax ;Get a key
int 16h
cmp ah,15h ;If it's a 'Y', then play again
je GameLoop
cmp ah,31h ;If it's an 'N', then quit
jne PKeyLoop
GameDone: mov ax,3 ;Set text mode, restore font
int 10h
ret ;Exit
EndP Main
;**************************** Tetris -- This is the actual game
Proc Tetris
pusha ;Save all registers
;****************** TETRIS Screen Setup
call ClearScreen ;Clear the screen
mov di,18 ;Start at (9, 0)
mov cx,25 ;25 lines
mov ax,7FB1h ;Color and character
WellLoop: stosw ;Left side of well
mov [word es:di+40],ax ;Right side
add di,158 ;Next line
loop WellLoop ;Loop back
mov cx,38 ;Print title string
xor dx,dx
mov si,offset TitleStr
mov al,07h
call PutStr
dec cx ;Print initial lines left
dec cx
mov dx,17
mov si,offset LeftStr
call PutStr
dec dx ;Print initial lines
dec dx
mov si,offset LinesStr
call PutStr
dec dx ;Print initial level
dec dx
mov si,offset LevelStr
call PutStr
dec dx ;Print initial score
dec dx
mov si,offset ScoreStr
call PutStr
;****************** TETRIS Initialization
xor ax,ax ;Initialize variables
mov [Score],ax
mov [Level],1
mov [Lines],ax
mov [LinesLeft],5
mov [DelayTime],750
mov [Rotate],ax
mov [X],4
mov [Y],24
call Rand7
mov [Piece],ax
;****************** TETRIS Main Loop
MainLoop: call ShowStatus ;Show status
mov ax,[DelayTime] ;Delay specified time
call Delay
inc bp ;Ctr = (Ctr + 1) mod 4
and bp,3
;****************** TETRIS Key Loop
KeyLoop: mov ah,1 ;Check for keys
int 16h
jz NoKeys
call LoadVals ;Erase current piece
xor di,di
call PutPiece
xor ax,ax ;Get the key
int 16h
cmp ah,LEFT ;Left arrow?
je KeyLeft
cmp ah,RIGHT ;Right arrow?
je KeyRight
cmp ah,DOWN ;Down arrow?
je KeyDown
cmp ah,ENTERK ;Enter?
je KeyDown
cmp ah,SPACE ;Space?
je KeySpace
cmp ah,ESCAPE ;Escape?
je KeyEsc
cmp ah,F10 ;F10?
je KeyF10
jmp KeyDone ;Not a recognized key
KeyLeft: call LoadVals ;If it fits at (X - 1),
dec cx
call Fits
jnc KeyDone
mov [X],cx ;move it to (X - 1).
jmp KeyDone
KeyRight: call LoadVals ;If it fits at (X + 1),
inc cx
call Fits
jnc KeyDone
mov [X],cx ;move it to (X + 1).
jmp KeyDone
KeyDown: call LoadVals ;Load values
mov si,dx ;Save old Y
DownLoop: dec dx ;While it fits at (Y-1),
call Fits ;decrement Y.
jc DownLoop
inc dx ;Move to where it last fit
mov [Y],dx ;Save it in Y
call PutPiece ;Display the piece
mov ax,dx ;Lock using (Y + old Y)
add ax,si
call PieceDown ;Piece is down
jmp KeyDone ;Done
KeySpace: call LoadVals ;Load values
inc ax ;Next rotation
and ax,3
call Fits ;If it fits,
jnc KeyDone
mov [Rotate],ax ;update rotation value
jmp KeyDone
KeyEsc: call GameOver ;Done with game
KeyF10: call LVPutPiece ;Show piece
xor ax,ax ;Wait for a key
int 16h
KeyDone: call LVPutPiece ;Show piece
jmp KeyLoop
;****************** TETRIS Piece Fall
NoKeys: test bp,bp ;Only if counter is zero
jne MainLoop
call LoadVals ;Erase current piece
xor di,di
call PutPiece
dec dx ;Check for fit at (Y - 1)
call Fits
jnc NoFit ;Jump if it doesn't fit
mov [Y],dx ;Save new Y
call LVPutPiece ;Show piece
jmp MainLoop
NoFit: call LVPutPiece ;Show piece
mov ax,dx ;Lock using Y
call PieceDown ;Piece is down
call LVPutPiece ;Show piece
jmp MainLoop ;Loop back
;****************** TETRIS Game Over
GameOver: pop ax ;Pop junk-word
mov cx,11 ;Print GO-top string
mov dx,11
mov si,offset GOTop
mov ax,04h
call PutStr
inc dx ;Print game-over message
mov si,offset OverStr ;in blinking blue
mov ax,0C9h
call PutStr
inc dx ;Print GO-bottom string
mov si,offset GOBottom
mov ax,04h
call PutStr
mov ax,5000 ;Delay 1/2 second
call Delay
call FlushBuffer ;Flush key buffer
xor ax,ax ;Wait for a key
int 16h
popa ;Restore registers
ret ;Return
PieceDown: call LockPiece
cmp dx,24 ;Too high, game over
jge GameOver
mov [Rotate],0 ;New piece, type (random, 0)
call Rand7
mov [Piece],ax
mov [X],4 ;Position (4, 24)
mov [Y],24
call FlushBuffer ;Flush key buffer
ret
LoadVals: mov cx,[X] ;Load piece values
mov dx,[Y]
mov bx,[Piece]
mov ax,[Rotate]
mov di,bx
inc di
ret
LVPutPiece: call LoadVals ;Load piece values
jmp PutPiece
EndP Tetris
;**************************** LockPiece -- Locks a piece in place
Proc LockPiece
pusha ;Save all registers
mov ax,[Level] ;AX = Level
imul ax,10 ; 10 * Level
add [Score],ax ;add to score
call DelLines ;Delete lines
add [Lines],ax ;Adjust line counter
sub [LinesLeft],ax
imul ax,50 ;Line score value
add [Score],ax ;add to score
cmp [LinesLeft],0 ;Done with level?
jg NotNew
mov [LinesLeft],0 ;LinesLeft = 0
call ShowStatus ;Show status
call ClearWell ;Clear well
imul ax,[Level],100 ;Score = Score + 100 * Level
add [Score],ax
inc [Level] ;Next level
imul ax,[DelayTime],7 ;Reduce delay by 12%
shr ax,3
mov [DelayTime],ax
imul ax,[Level],2 ;LinesLeft = 6 + 2 * Level
add ax,6
mov [LinesLeft],ax
NotNew: call ShowStatus ;Show status
popa ;Restore registers
ret ;Return
EndP LockPiece
;**************************** ShowStatus -- Display score, level, etc.
Proc ShowStatus
pusha ;Save all registers
mov cx,43 ;Clear Score field
mov dx,11
mov si,offset Spaces7
mov al,07h
call PutStr
inc dx ;Clear Level field
inc dx
call PutStr
inc dx ;Clear Lines field
inc dx
call PutStr
inc dx ;Clear Lines Left field
inc dx
add cx,5
add si,5
call PutStr
mov si,offset Buffer ;Offset of buffer
mov ax,[Score] ;Get decimal string for Score
mov di,si
call Cvt16
mov cx,43 ;Print it at (43, 11)
mov dx,11
mov al,0Ah
call PutStr
mov ax,[Level] ;Get decimal string for Level
mov di,si
call Cvt16
inc dx ;Print it at (43, 13)
inc dx
mov al,0Ah
call PutStr
mov ax,[Lines] ;Get decimal string for Lines
mov di,si
call Cvt16
inc dx ;Print it at (43, 15)
inc dx
mov al,0Ah
call PutStr
mov ax,[LinesLeft] ;Get decimal string for Lines Left
mov di,si
call Cvt16
mov cx,48 ;Print it at (48, 17)
inc dx
inc dx
mov al,0Ah
call PutStr
popa ;Restore registers
ret ;Return
EndP ShowStatus
;**************************** PutBlock -- Put block on screen
Proc PutBlock
;Supply CX = x, DX = y, AL = color
pusha ;Save all registers
imul di,dx,160 ;DI = DX * 160
add di,cx ;DI = DX * 160 + CX * 2
add di,cx
test al,al ;If zero, erase block
jz IsZero
mov ah,al ;AH = color
shl ah,4
add ah,8 ;Foreground = color + 8
add ah,al
mov al,0CEh ;Store first half
stosw
inc ax ;Store second half
stosw
popa ;Restore registers
ret ;Return
IsZero: mov ax,0720h ;Zero, store spaces
stosw
stosw
popa ;Restore registers
ret ;Return
EndP PutBlock
;**************************** IsBlock -- Check for block
Proc IsBlock
;Supply CX = x, DX = y
;Returns Carry = 1 if block
pusha ;Save all registers
add cx,cx
add cx,10 ;Adjust to screen position
neg dx
add dx,24
imul di,dx,160 ;DI = DX * 160
add di,cx ;DI = DX * 160 + CX * 2
add di,cx
mov al,[es:di] ;Load byte
cmp al,0CEh ;If it's < 0CEh,
jb NoBlock ;it isn't a block
stc ;Set carry flag
popa ;Restore registers
ret ;Return
NoBlock: clc ;Clear carry flag
popa ;Restore registers
ret ;Return
EndP IsBlock
;**************************** PutPiece -- Put piece in well
Proc PutPiece
;Supply CX = x, DX = y, BX = piece, AX = rotation, DI = color
pusha ;Save all registers
mov bp,di ;Color in BP
add bx,bx
mov si,[PieceStart+bx] ;SI = piece start
and al,[si-1] ;AND mask
imul ax,6 ;AX * 6
add si,ax ;SI = piece offset
mov di,4 ;4 blocks
xor ax,ax ;Start with (0, 0)
BlockLoop: push cx dx ;Save position
mov bl,ah ;Get offsets in AX, BX
cbw
xchg ax,bx
cbw
xchg ax,bx
add cx,ax ;Add in offsets
add dx,bx
cmp cx,10 ;Out of well, don't show
jae BlockNope
cmp dx,25
jae BlockNope
add cx,cx
add cx,10 ;Adjust to screen position
neg dx
add dx,24
mov ax,bp ;Color in AL
call PutBlock ;Show block
BlockNope: pop dx cx ;Restore position
lodsw ;Load word
dec di ;Loop back using DI
jnz BlockLoop
popa ;Restore registers
ret ;Return
EndP PutPiece
;**************************** Fits -- Check whether piece fits
Proc Fits
;Supply CX = x, DX = y, BX = piece, AX = rotation
;Returns: Carry = 1 if it fits, 0 if it doesn't.
pusha ;Save all registers
add bx,bx
mov si,[PieceStart+bx] ;SI = piece start
and al,[si-1] ;AND mask
imul ax,6 ;AX * 6
add si,ax ;SI = piece offset
mov di,4 ;4 blocks
xor ax,ax ;Start with (0, 0)
FitsLoop: push cx dx ;Save position
mov bl,ah ;Get offsets in AX, BX
cbw
xchg ax,bx
cbw
xchg ax,bx
add cx,ax ;Add in offsets
add dx,bx
cmp cx,10 ;Out of well, doesn't fit
jae DoesntFit
test dx,dx
jl DoesntFit
call IsBlock ;Check for block
jc DoesntFit
pop dx cx ;Restore position
lodsw ;Load word
dec di ;Loop back using DI
jnz FitsLoop
stc ;Set carry flag
popa ;Restore registers
ret ;Return
DoesntFit: clc ;Clear carry flag
pop dx cx ;Pop extra off stack
popa ;Restore registers
ret ;Return
EndP Fits
;***************************** DelLine -- Delete line
Proc DelLine
;Supply AX = y
pusha ;Save all registers
push ds ;Save DS
push es ;DS = ES
pop ds
neg ax ;Adjust for screen position
add ax,24
xchg dx,ax
imul di,dx,160 ;DI = DX * 160
add di,20 ;DI = DX * 160 + 20
mov si,di ;SI = previous line
sub si,160
ScDnLoop: push si si ;Save offsets
mov cx,20 ;Move line
rep movsw
pop si di ;DI = old SI,
sub si,160 ;SI = old SI - 160
dec dx ;Loop back using DX
jnz ScDnLoop
pop ds
popa ;Restore registers
ret ;Return
EndP DelLine
;***************************** DelLines -- Delete all completed lines
Proc DelLines
push cx ;Save CX
xor cx,cx ;Zero CX
mov ax,24 ;AX = 24
DelLoop: pusha ;Save all registers
xchg dx,ax ;Y in DX
mov cx,9 ;CX = 9
LChkLoop: call IsBlock ;Check for block
jnc NotLine ;Not a line if no block
dec cx ;Loop back using CX
jns LChkLoop
stc ;Set carry flag
NotLine: popa ;Restore registers
jnc $+6 ;Jump if not line
call DelLine ;Delete line
inc cx ;Increment counter
dec ax ;Next line
jns DelLoop ;Loop back
xchg ax,cx ;Lines in AX
pop cx ;Restore CX
ret ;Return
EndP DelLines
;**************************** ClearWell -- Clear the well
Proc ClearWell
pusha ;Save all registers
mov di,20 ;Start at offset 20
mov dx,25 ;25 rows on screen
mov ax,0720h ;Fill with spaces
ClearWLoop: mov cx,20 ;Width of well
rep stosw ;Clear line
add di,120 ;Go to next line
dec dx ;Loop back using DX
jnz ClearWLoop
popa ;Restore registers
ret ;Return
EndP ClearWell
;**************************** ClearScreen -- Clear the screen
Proc ClearScreen
pusha ;Save all registers
xor di,di ;Zero DI
mov ax,0720h ;Fill with spaces
mov cx,4000 ;4000 words
rep stosw ;Clear screen
popa ;Restore registers
ret ;Return
EndP ClearScreen
;**************************** PutStr -- Print ASCIIZ string
Proc PutStr
;Supply (CX, DX) = position, AL = color, DS:SI = string
pusha ;Save all registers
imul di,dx,160 ;DI = DX * 160
add di,cx ;DI = DX * 160 + CX * 2
add di,cx
mov ah,al ;AH = color
PutLoop: lodsb ;Load byte
test al,al ;Quit if zero
jz PutDone
stosw ;Store word
jmp PutLoop
PutDone: popa ;Restore registers
ret ;Return
EndP PutStr
;**************************** FlushBuffer -- flush keyboard buffer
Proc FlushBuffer
push ds ;Save DS
push 0 ;DS = 0
pop ds
push [word 041Ah] ;Key tail = key head
pop [word 041Ch]
pop ds ;Restore DS
ret ;Return
EndP FlushBuffer
;**************************** Cvt16 -- Convert 16-bit binary to decimal
Proc Cvt16
;Supply AX = binary value, DS:DI = 5 bytes string space
pusha ;Save registers
pop di
test ax,ax ;If zero, then just
je U16Zero ;output a zero.
xor cx,cx ;Zero CX
mov si,10 ;SI = 10
U16DivLoop: xor dx,dx ;Zero DX
div si ;Divide by 10
mov bl,dl ;Remainder in BL
add bl,30h ;Convert to digit
push bx ;Save digit
inc cx ;Increment CX
test ax,ax ;Zero now?
jnz U16DivLoop ;Loop back if not
U16PopLoop: pop ax ;Pop digit
mov [di],al ;Store digit
inc di
loop U16PopLoop ;Loop back
U16Ret: mov [byte di],0 ;Store ending null
push di ;Restore registers
popa
ret ;Return
U16Zero: mov [byte di],30h ;It was zero, so store a '0'.
inc di
jmp U16Ret
EndP Cvt16
;**************************** Rand7 -- Generate a random number from 1 to 7
Proc Rand7
mov ax,7 ;call Rand with value 7
call Rand
ret
EndP Rand7
;**************************** Rand -- Generate a random number
Proc Rand
push bx cx dx ax ;Save registers, push AX
mov ax,4E35h
mul [RandNum] ;Here the Random Number is
mov cx,dx ;multiplied by the value
xchg bx,ax ;015A4E35h. This is one of
imul ax,[RandNum],015Ah ;the optimal values for
add cx,ax ;this type of random number.
imul ax,[RandNum+2],4E35h
add cx,ax
add bx,1 ;Increment the number
adc cx,0
mov [RandNum],bx ;Save random number
mov [RandNum+2],cx
xchg ax,cx ;Now the bits are re-arranged,
shl ax,1 ;and the number is divided by
and bx,1 ;the value originally in AX.
add ax,bx
pop bx
xor dx,dx
div bx
xchg ax,dx ;Place result in AX
pop dx cx bx ;Restore registers
ret ;Return
EndP Rand
;**************************** Delay -- Delay.
Proc Delay
;Supply AX = milliseconds * 10.
pusha ;Save all registers
mov dx,100
mul dx
mov cx,dx
xchg dx,ax ;CX:DX = time in microseconds
mov ah,86h ;INT 15/86 = BIOS delay.
int 15h
popa ;Restore registers
ret ;Return
EndP Delay
;****************** Variables
Spaces10 db 3 dup(?)
Spaces7 db 8 dup(?)
GOBottom db 19 dup(?)
GOTop db 19 dup(?)
RandNum dw ?,?
Score dw ?
Lines dw ?
Level dw ?
DelayTime dw ?
LinesLeft dw ?
Piece dw ?
Rotate dw ?
X dw ?
Y dw ?
Buffer:
End Start