;****************** 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
