;****************** TETRIS.ASM -- TinyTetris v1.3
;                   This is a Tetris game written in ASM.

Ideal
Jumps

LEFT        = 4Bh                   ;Key values
RIGHT       = 4Dh
DOWN        = 50h
SPACE       = 39h
ENTERK      = 1Ch
ESCAPE      = 01h
BKSPACE     = 0Eh
F10         = 44h

Model Tiny
P186
CodeSeg
Org 100h

Start:      jmp Main

;**************************** Static data for program

CustomFont  db 255,255,6 dup(213,234),192,128
            db 255,254,6 dup(84,168),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.3 ---',0
PlayAgain   db 'Do you want to play again (Y/N) ?',0
YMHS        db 'You made a High Score!',0
NameStr     db 'Enter your name:',0
HiStr       db 'TinyTetris High Scores',0
HST1        db 'Name',0
HST2        db 'Score Lines Level',0
FileName    db 'TETRIS.SCO',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,1114h            ;Set 8x16 font
            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 dx,03CCh            ;Set 8 dot chars
            in al,dx
            and al,0F3h
            mov dx,03C2h
            out dx,al
            mov dx,03C4h
            mov ax,0101h
            out dx,ax

            mov ah,2                ;Turn off the cursor by
            xor bh,bh               ;placing it off the screen
            mov dx,1E00h
            int 10h

            cld
            mov di,offset HiScores  ;Zero out high scores
            mov cx,580
            xor ax,ax
            rep stosw

            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

            mov ax,3D00h            ;Open scorefile
            mov dx,offset FileName
            int 21h
            jc GameLoop             ;No scorefile, ignore

            xchg bx,ax              ;Read hiscores
            mov ah,3Fh
            mov cx,1160
            mov dx,offset HiScores
            int 21h

            mov ah,3Eh              ;Close scorefile
            int 21h

GameLoop:   call Tetris             ;Play game
            call AddHiScore         ;Add in score, show scores
            call ClearScreen        ;Clear screen
            mov cx,22               ;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 ah,3Ch              ;Create/truncate scorefile
            xor cx,cx
            mov dx,offset FileName
            int 21h

            xchg bx,ax              ;Write hiscores
            mov ah,40h
            mov cx,1160
            mov dx,offset HiScores
            int 21h

            mov ah,3Eh              ;Close scorefile
            int 21h

            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 di,1668             ;Start at (34, 10)
            mov cx,9                ;9 lines
            mov al,0DBh             ;AL = character
SBoxLoop:   stosb                   ;Left side of status box
            mov [es:di+33],al       ;Right side
            add di,159              ;Next line
            loop SBoxLoop           ;Loop back

            mov di,708              ;Start at (34, 4)
            mov cx,5                ;5 lines
NBoxLoop:   stosb                   ;Left side of next piece box
            mov [es:di+25],al       ;Right side
            add di,159              ;Next line
            loop NBoxLoop           ;Loop back

            mov cx,38               ;Print title string
            xor dx,dx
            mov si,offset TitleStr
            mov al,07h
            call PutStr

            mov cx,35               ;Print S-box top
            mov dx,10
            mov si,offset BoxTop
            call PutStr

            mov dx,18               ;Print S-box bottom
            mov si,offset BoxBottom
            call PutStr

            mov cx,35               ;Print N-box top
            mov dx,4
            mov si,offset BoxTop+4
            call PutStr

            dec cx                  ;Print N-box bottom
            mov dx,9
            dec si
            dec si
            call PutStr

            inc cx                  ;Print initial lines left
            inc 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 [Score+2],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
            call Rand7
            mov [NxPiece],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,3333             ;Delay 1/3 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 (NxPiece, 0)
            mov ax,[NxPiece]
            mov [Piece],ax
            call Rand7              ;New next-piece
            mov [NxPiece],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

            push ax                 ;Save y-value
            mov ax,150              ;15 msecs
            mov bx,660              ;Sound at 660
            call Sound
            call NoSound            ;End of sound
            pop ax                  ;Get back y-value

            add ax,25               ; y + 25
            mov dx,[Level]
            mul dx                  ; Level * (y + 25)
            imul ax,10              ; 10 * Level * (y + 25)
            cwd
            mov bx,25               ; (10 * Level * (y + 25)) / 25
            div bx
            add [Score],ax          ;add to score
            adc [Score+2],0

            call DelLines           ;Delete lines
            add [Lines],ax          ;Adjust line counter
            sub [LinesLeft],ax

            mul ax                  ; (l ^ 3)
            mul ax
            add ax,4                ; (l ^ 3) + 4
            mov bx,5
            cwd
            div bx                  ; ((l ^ 3) + 4) / 5
            imul ax,100             ;Line score value
            add [Score],ax          ;add to score
            adc [Score+2],0

            cmp [LinesLeft],0       ;Done with level?
            jg NotNew

            pusha                   ;Save all registers
            mov ax,200              ;20 msec
            mov bx,440              ;Start at 440
SndLoop1:   call Sound              ;Sound 440 to 880
            add bx,20
            cmp bx,880
            jne SndLoop1
SndLoop2:   call Sound              ;Sound 880 to 660
            sub bx,20
            cmp bx,660
            jne SndLoop2
SndLoop3:   call Sound              ;Sound 660 to 1100
            add bx,20
            cmp bx,1100
            jne SndLoop3
            call Sound              ;Last sound
            call NoSound            ;End of sound
            popa                    ;Restore registers

            mov [LinesLeft],0       ;LinesLeft = 0
            call ShowStatus         ;Show status

            call ClearWell          ;Clear well
            imul ax,[Level],100     ;Score = Score + 100 * Level
            add [Score],ax
            adc [Score+2],0
            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 dx,[Score+2]
            mov di,si
            call Cvt32
            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

            mov si,offset Spaces10  ;Clear Next-Piece area
            mov cx,36
            mov dx,5
            mov al,07h
            call PutStr
            inc dx
            call PutStr
            inc dx
            call PutStr
            inc dx
            call PutStr

            mov bx,[NxPiece]
            mov bp,bx
            inc bp
            xor ax,ax
            mov cx,40
            mov dx,6

            add bx,bx
            mov si,[PieceStart+bx]  ;SI = piece offset

            mov di,4                ;4 blocks
            xor ax,ax               ;Start - (0, 0)
SSBlkLoop:  push cx dx              ;Save position
            mov bl,ah               ;Get offsets in AX, BX
            cbw
            xchg ax,bx
            cbw
            xchg ax,bx
            add ax,ax               ;Add in offsets
            add cx,ax
            sub dx,bx
            mov ax,bp               ;Color in AL
            call PutBlock           ;Show block
            pop dx cx               ;Restore position
            lodsw                   ;Load word
            dec di                  ;Loop back using DI
            jnz SSBlkLoop

            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 ax                 ;Save AX
            mov ax,250              ;25 msecs
            mov bx,440              ;Sound at 440
            call Sound
            call Delay
            mov bx,660              ;Sound at 660
            call Sound
            call Delay
            mov bx,880              ;Sound at 880
            call Sound
            call Delay
            call NoSound            ;End of sound
            pop ax                  ;Restore AX

            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

;**************************** AddHiScore -- Add high score to list

Proc        AddHiScore

            pusha                   ;Save all registers
            push es                 ;Save ES
            push ds                 ;ES = DS
            pop es

            mov ax,[Score]          ;DX:AX = Score
            mov dx,[Score+2]
            mov bx,offset HiScores+1102 ;Last Score field
            cmp dx,[bx+2]           ;Make sure it's in the hiscores
            jb AddDone
            ja HSStart
            cmp ax,[bx]
            jbe AddDone

HSStart:    mov cx,19
HSCmpLoop:  cmp dx,[bx+2]           ;If Score <= HiScores[i].score,
            ja HSCmpLB              ;then it is done
            jb HSCmpDone
            cmp ax,[bx]
            jbe HSCmpDone
HSCmpLB:    sub bx,58               ;Previous hiscore
            dec cx                  ;Loop back using CX
            jns HSCmpLoop

HSCmpDone:  inc cx                  ;Get the actual position
            mov dx,cx               ;DX = CX
            mov bp,cx               ;BP = CX
            mov cx,19               ;CX = 19
            mov di,offset HiScores+1102 ;DI = HS #19
            mov si,offset HiScores+1044 ;SI = HS #18

HSMovLoop:  cmp cx,dx               ;CX > DX, done
            je HSMovDone
            push cx si si           ;Save offsets
            mov cx,29               ;Move hiscore
            rep movsw
            pop si di cx            ;DI = old SI
            sub si,58               ;SI = old SI - 58
            loop HSMovLoop

HSMovDone:  pop es                  ;Restore ES
            call ClearScreen        ;Clear the screen
            mov cx,4                ;Print 'You made a High Score'
            mov dx,11               ;at (4, 11), in cyan on blue
            mov si,offset YMHS
            mov al,9Bh
            call PutStr

            xor cx,cx               ;Print 'Enter your name:'
            mov dx,13               ;at (0, 13), in blue
            mov si,offset NameStr
            mov al,09h
            call PutStr

            mov bx,2114             ;Current position (17, 13)
            mov [word es:bx],0A5Fh  ;Show pseudo-cursor
            xor si,si               ;Cursor position 0
            imul bp,58              ;BP = offset of hiscore
            add bp,offset HiScores

HSKeyLoop:  xor ax,ax               ;Get a key
            int 16h

            cmp ah,BKSPACE          ;Backspace?
            je HSKeyBksp
            cmp ah,LEFT             ;Left-arrow?
            je HSKeyBksp
            cmp ah,ENTERK           ;Enter?
            je HSKeyEnter

            cmp al,20h              ;Is it a printable char?
            jb HSKeyLoop            ;Ignore it if it isn't
            cmp al,7Eh
            ja HSKeyLoop
            cmp si,49               ;Already at limit?
            je HSKeyLoop
            mov ah,0Ah              ;Color is green
            mov [es:bx],ax          ;Show character
            mov [bp+si+8],al        ;Put it in string
            inc si
            inc bx                  ;Advance cursor
            inc bx
            mov [word es:bx],0A5Fh  ;Show pseudo-cursor
            jmp HSKeyLoop

HSKeyBksp:  test si,si              ;Already at left?
            jz HSKeyLoop
            mov [byte es:bx],20h
            dec si
            dec bx                  ;Move cursor left
            dec bx
            mov [byte es:bx],5Fh    ;Show pseudo-cursor
            jmp HSKeyLoop

HSKeyEnter: mov [byte bp+si+8],0    ;Terminate string
            mov ax,[Score]          ;Put in values
            mov [bp],ax
            mov ax,[Score+2]
            mov [bp+2],ax
            mov ax,[Lines]
            mov [bp+4],ax
            mov ax,[Level]
            mov [bp+6],ax

            push es                 ;Dummy
AddDone:    pop es

;****************** Show High Scores

            call ClearScreen        ;Clear the screen
            mov cx,29               ;Display the title
            xor dx,dx
            mov si,offset HiStr
            mov al,0Fh
            call PutStr
            xor cx,cx               ;Display HS 'table-top'
            mov dx,3
            mov si,offset HST1
            call PutStr
            mov cx,61
            mov si,offset HST2
            call PutStr

            mov bp,1                ;Counter is 1
            mov bx,offset HiScores  ;Offset of first Hiscore
            mov si,offset Buffer    ;SI = buffer

ShowLoop:   mov ax,bp               ;AX = counter
            mov di,si               ;Convert into buffer
            call Cvt16
            mov [word di],002Eh     ;Add a period (like '15.')
            mov dx,bp               ;DX = row
            add dx,4
            mov cx,2                ;Column 2
            mov al,09h              ;in blue
            call PutStr

            push si
            mov si,bx               ;Display name
            add si,8
            mov cx,6                ;Column 6
            mov al,0Ah              ;in green
            call PutStr
            pop si

            push dx                 ;Save row
            mov ax,[bx]
            mov dx,[bx+2]
            mov di,si
            call Cvt32              ;Display score
            pop dx
            mov cx,66               ;Column 66
            add cx,si               ;on the right
            sub cx,di
            mov al,0Bh              ;in cyan
            call PutStr

            mov ax,[bx+4]           ;Display lines
            mov di,si
            call Cvt16
            mov cx,72               ;Column 72
            add cx,si               ;on the right
            sub cx,di
            mov al,0Bh              ;in cyan
            call PutStr

            mov ax,[bx+6]           ;Display level
            mov di,si
            call Cvt16
            mov cx,77               ;Column 77
            add cx,si               ;on the right
            sub cx,di
            mov al,0Bh              ;in cyan
            call PutStr

            inc bp
            add bx,58               ;Next score
            cmp bp,20               ;Loop back
            jbe ShowLoop

            call FlushBuffer        ;Flush key buffer
            xor ax,ax               ;Wait for a key
            int 16h

            popa                    ;Restore registers
            ret                     ;Return

EndP        AddHiScore

;**************************** ClearScreen -- Clear the screen

Proc        ClearScreen

            pusha                   ;Save all registers
            xor di,di               ;Zero DI
            mov ax,0720h            ;Fill with spaces
            mov cx,32768            ;32768 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

;**************************** Cvt32 -- Convert 32-bit binary to decimal

Proc        Cvt32
            ;Supply DX:AX = binary value, DS:DI = 5 bytes string space

            push ax bx dx           ;Save all registers
            mov bx,10000            ;Divide it by 10000
            div bx
            test ax,ax              ;Don't do first part if it's zero
            jz Cvt32Less
            call Cvt16              ;Convert first part
            xchg ax,dx              ;Remainder in AX
            call Cvt16B             ;Convert last 4
Cvt32Done:  pop dx bx ax            ;Restore all registers
            ret                     ;Return

Cvt32Less:  xchg ax,dx
            call Cvt16
            jmp Cvt32Done

EndP        Cvt32

;**************************** Cvt16B -- Convert 16-bit binary to decimal

Proc        Cvt16B
            ;Supply AX = binary value, DS:DI = 5 bytes string space

            pusha                   ;Save registers
            pop di
            mov si,10               ;SI = 10
            mov bp,4
            mov cx,bp
Z16DivLoop: 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
            dec bp                  ;Done?
            jnz Z16DivLoop          ;Loop back if not

Z16PopLoop: pop ax                  ;Pop digit
            mov [di],al             ;Store digit
            inc di
            loop Z16PopLoop         ;Loop back

            mov [byte di],0         ;Store ending null
            push di                 ;Restore registers
            popa
            ret                     ;Return

EndP        Cvt16B

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

;**************************** Sound -- Sound the speaker.

Proc        Sound
            ;Supply AX = delay, BX = frequency

            pusha                   ;Save all registers

            mov dx,12h              ;BX = 1193180 / freq.
            mov ax,34DCh
            div bx
            mov bx,ax

            mov al,0B6h             ;Set frequency
            out 43h,al
            mov ax,bx
            out 42h,al
            mov al,ah
            out 42h,al

            in al,61h               ;Turn on speaker
            or al,3
            out 61h,al

            popa                    ;Restore registers
            call Delay              ;Do the delay
            ret                     ;Return

EndP        Sound

;**************************** NoSound -- Turn off the speaker.

Proc        NoSound

            push ax                 ;Save AX
            in al,61h               ;Turn off speaker
            and al,0FCh
            out 61h,al
            pop ax                  ;Restore AX
            ret                     ;Return

EndP        NoSound

;**************************** Delay -- Delay.

Proc        Delay
            ;Supply AX = msecs * 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 2 dup(?)
BoxTop      db 17 dup(?)
GOTop       db 2 dup(?)
BoxBottom   db 17 dup(?)

RandNum     dw ?,?
Score       dw ?,?
Lines       dw ?
Level       dw ?
DelayTime   dw ?
LinesLeft   dw ?

Piece       dw ?
Rotate      dw ?
NxPiece     dw ?
X           dw ?
Y           dw ?

HiScores    dw 580 dup(?)

Buffer:

End Start
