\---------------------------------------------------------------------
\ Tetris Deluxe v1.01 -- Tetris game
\ Requires 186, EGA graphics (386 recommended)
\---------------------------------------------------------------------
\ v1.00 by Tenie Remmel (using assembler)       5747 bytes
\ v1.01 by Tylisha C. Andersen (using NEGA)     5272 bytes
\---------------------------------------------------------------------
\ To construct TETRIS.DAT (TETRIS.FNT must exist):
\   GEN_PCS
\   GEN_FONT
\   COPY /B TETRIS.FN1+TETRIS.PCS TETRIS.DAT
\   DEL TETRIS.FN1
\   DEL TETRIS.PCS
\---------------------------------------------------------------------
\ Design of font:
\   00-1C       Title chars
\   1E-2C       Countdown chars
\   2D-7A       Normal chars (mostly)
\   7B-7F       Fat chars (16x14 char set)
\   80-A1       3-D game pieces
\   A2-E7       Fat chars
\   E8-F6       Countdown chars
\   F8-FF       Inset box L-R-U-D-UL-UR-LL-LR
\---------------------------------------------------------------------

O       equ <Offset>;
B       equ <Byte Ptr>;
W       equ <Word Ptr>;
D       equ <Dword Ptr>;

.Model Tiny;
.Code;
.186;
Org 100h;

Start:
    =.Main; !20h;                       \ Call main proc, then quit

\---------------------------------------------------------------------

BLOCK_FREQ  = 3000;                     \ Random block freq.  | Divide these
PIECE_FREQ  = 8200;                     \ Bonus piece freq.   | frequencies
DISP_FREQ   = 11000;                    \ Disappearing freq.  | by round+8
LINE_FREQ   = 16400;                    \ Advancing line freq.| by round+8

\---------------------------------------------------------------------

PIECE_SIZE  = 6;                        \ Max size of piece
MAX_PIECES  = 37;                       \ Number of pieces
PIECE_DATA  = 12 * PIECE_SIZE * MAX_PIECES;

BOMB_PIECE  = 35;                       \ Bonus pieces
ACID_PIECE  = 36;

\---------------------------------------------------------------------

DFile$      db 'TETRIS.DAT',0;
FError$     db 'Error reading TETRIS.DAT$';

Title$1     db  1, 2, 3, 4, 5, 6, 1, 2, 3, 7, 8, 9,10,11,12,13,14,0;
Title$2     db 15,16,17,18,19,20,15,16,17,21,22,23,24,25,26,27,28,0;
Deluxe$     db 'D+E+L+U+X+E',0;
Copyright$  db 60,61,62,63,64,'T.C.Andersen',0;
Round$      db 'ROUND ',0;              \ The space is for NextLevel
LinesLeft$  db 'LINES LEFT',0;
Score$      db 'SCORE',0;
Lines$      db 'LINES',0;

Menu1$      db '1 -- 7 PCS.',0;         \ The initial menu
Menu2$      db '2 -- 14 PCS.',0;
Menu3$      db '3 -- 21 PCS.',0;
Menu4$      db '4 -- 28 PCS.',0;
Menu5$      db '5 -- 35 PCS.',0;
Menu6$      db 'S -- SOUND ';
SoundOn     db 'Y',0;
Menu7$      db 'G -- GRID    ';
GridOn      db 'N',0;
Menu8$      db 'R -- ROUND ';
StRound     db '1',0;
Menu9$      db 'Q -- QUIT',0;

Bonus$      db 'BONUS',0;
LowBonus$1  db ' BONUS FOR',0;
LowBonus$2  db 'LOW PUZZLE',0;

GameOver$1  db 6Ch,4 dup(65h),69h,0;    \ The 'Game Over' box;
GameOver$2  db 6Ah,'GAME',6Ah,0;
GameOver$3  db 6Ah,'OVER',6Ah,0;
GameOver$4  db 66h,4 dup(65h),63h,0;

HighScores$ db 'H I G H   S C O R E S',0;
Name$       db 'NAME',0;

\---------------------------------------------------------------------

BigChars    db 32, 92, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123;
            db 125,93, 124,32, 212,214,216,218,220,222,224,226;
            db 228,230,126,127,32, 32, 32, 160,32, 162,164,166;
            db 168,170,172,174,176,91, 178,180,182,184,186,188;
            db 190,192,194,196,198,200,202,204,206,208,210,32;
            db 32, 32, 32, 32, 128,130,132,134,136,138,140,142;
            db 144,146,148,150,152,154,156,158;

Palette     db 0,1,2,3,4,5,38,7,56,9,18,27,36,45,54,63,0;

Colors      db 06Eh,07Fh,05Dh,019h,02Ah,0C6h,04Ch;
            db 06Fh,07Eh,05Eh,01Ah,02Bh,0CFh,04Dh;
            db 01Bh,01Ch,01Dh,04Ah,04Bh,0CEh,02Eh;
            db 08Ch,08Ah,08Dh,09Eh,02Fh,05Fh,09Fh;
            db 087h,016h,05Bh,06Bh,07Bh,086h,08Bh;
            db 03Ch,03Ah;

LineVals    dw 0,50,150,400;            \ Line scores
            dw 1000,2000,4000;

PalVals     db 00h,00h,20h,04h;         \ Normal pulsation data
            db 24h,24h,04h,20h;

\-------------------------------------------------
\ Round Types:  Bit 0-1 --  Initial setup
\               Bit 2   --  Appearing blocks, bonus pieces
\               Bit 3   --  Advancing lines
\               Bit 4   --  Disappearing blocks
\-------------------------------------------------

RoundType   db 00000b,00000b,00001b;
            db 00010b,00011b,00100b;
            db 00101b,10110b,01100b;
            db 11101b,11110b,11100b;
            db 12 dup(11100b);

RoundTimes  db 99,87,77,68,60,53;       \ Delay times
            db 47,42,37,33,29,26;
            db 23,21,19,17,15,14;
            db 13,12,11,10,9, 8;

RoundLines  db 5, 8, 12,15,15,15;       \ Num. of lines needed
            db 15,15,18,21,24,26;       \ to complete each round
            db 28,30,32,34,36,38;
            db 40,42,44,46,48,50;

\-------------------------------------------------

BlockLists  dw offset BlockList0;       \ Pointers to block lists
            dw offset BlockList1;
            dw offset BlockList2;
            dw offset BlockList3;

BlockList1  dw 0000h,0100h,0200h;       \ Block list for ??01 rounds
            dw 0300h,0400h,0500h;
            dw 0600h,0700h,0009h;
            dw 0109h,0209h,0309h;
            dw 0409h,0509h,0609h;
            dw 0709h,(-1);

BlockList2  dw 0006h,0001h,0108h;       \ Block list for ??10 rounds
            dw 0103h,0205h,0307h;
            dw 0306h,0302h,0409h;
            dw 0404h,0400h,(-1);

BlockList3  dw 0000h,0001h,0003h;       \ Block list for ??11 rounds
            dw 0004h,0005h,0006h;
            dw 0008h,0009h,0101h;
            dw 0102h,0104h,0105h;
            dw 0107h,0108h,0202h;
            dw 0203h,0206h,0207h;
            dw 0303h,0304h,0305h;
            dw 0306h,0404h,0405h;
BlockList0  dw (-1);                    \ No blocks for ??00 rounds

\-------------------------------------------------

M_CHOICES   = 9;                        \ Total num. of choices
M_NUMBERS   = 5;                        \ Number of piece sets

Menu        dw offset Menu1$;           \ Menu string table
            dw offset Menu2$;
            dw offset Menu3$;
            dw offset Menu4$;
            dw offset Menu5$;
            dw offset Menu6$;
            dw offset Menu7$;
            dw offset Menu8$;
            dw offset Menu9$;

\---------------------------------------------------------------------

UDataSeg;                               \ Uninitialized data

ScoreOffs   dw ?;                       \ Offset of scores in file

RandNum     dw ?,?;                     \ Random number

PalCtr      dw ?;                       \ Counter for pulsation

RectColor   db ?;                       \ Color sweep data
RectCtr     db ?;
TitleXY     label word;                 \ Title box position
TitleX      db ?;
TitleY      db ?;

Score       dw ?,?;                     \ Score data
Round       dw ?;
Lines       dw ?;
LinesLeft   dw ?;

DelayTime   dw ?;                       \ Delay time in milliseconds
NumPieces   dw ?;                       \ Number of pieces in use

PieceW      label word;                 \ Label for loading CX
Piece       db ?;                       \ Piece data
PRot        db ?;
NxPiece     db ?;

BonusCtr    dw ?;                       \ Countdown for bonus box

CodeSeg;

\---------------------------------------------------------------------
\ Main procedure
\---------------------------------------------------------------------

Main:
    es = ds:[2Ch]; &di;                 \ ES = environment
    cx = (-1); &al;                     \ Set up for scan
    { <> *-? *-? }<>;                   \ Find double zero

    si === [di+2]; =es; ds=;            \ DS:SI = ptr. program name
    =cs; es=; di = O(FileName);         \ ES:DI = filename buffer
    { =*; *=; al? }<>;                  \ Copy string to buffer

    =cs; ds=;                           \ DS = CS
    di-; -; al = '\'; <> *-? +; **-?    \ Find last slash
    si = O(DFile$); cx = 11; <> *=*;    \ Set filename to 'TETRIS.DAT'

\-------------------------------------------------

    di = O(BufEnd); &ax;                \ Clear the buffer
    cx = 4096; <> **=;

    ax = 3D00h; dx = O(FileName);       \ Open the data file
    !21h; .<<Error; bx == ax;           \ BX = handle

    ah = 3Fh; dx = O(BufEnd);           \ Read the data file
    cx = 8192; !21h; .<<Error;
    si == ax;                           \ SI = size of data
    ah = 3Eh; !21h;                     \ Close the file

    =.Decode;                           \ Decode the data

\-------------------------------------------------

    ax = 1201h; bl = 30h; !10h;         \ Set EGA mode (if VGA)
    ax = 3; !10h;                       \ Set video mode 3
    ax = 1100h; bx = 0E00h; cx = 0100h; \ Load the custom font
    &dx; bp = O(Font); !10h;            \ (Tetris pieces, etc.)
    ax = 1003h; bh = 1; !10h;           \ Turn off blinking
    ax = 1002h; dx = O(Palette); !10h;  \ Set new palette
    ah = 2; &bh; dx = 1900h; !10h;      \ Put cursor off screen
    ax = 0305h; &bx; !16h;              \ Speed up keyboard

    es = ax = 0B800h;                   \ ES = video memory
    =.Tetris;                           \ Call main game proc

    ax = 1202h; bl = 30h; !10h;         \ Set VGA mode (if VGA)
    ax = 3; !10h;                       \ Reset video mode

\-------------------------------------------------

    ax = 3D01h; dx = O(FileName);       \ Open the data file
    !21h; <<Error; bx == ax;            \ BX = handle

    ax = 4200h; &cx;                    \ Move file pointer
    dx = ScoreOffs; !21h;
    cx = (O(BufEnd)-O(SC_Score));       \ CX = size of scores
    ah = 40h; dx = O(SC_Score); !21h;   \ Write out scores
    <<Error;

    ah = 3Eh; !21h; .=;                 \ Close the file and return

\---------------------------------------------------------------------
\ Error -- Output error message.
\---------------------------------------------------------------------

Error:
    ah = 9; dx = O(FError$);            \ Output error string and return
    !21h; .=;                       

\---------------------------------------------------------------------
\ Tetris -- Main game procedure
\---------------------------------------------------------------------

Tetris:
    =.Initialize;                       \ Initialize the game
    =.StartInt;                         \ Start interrupt handler

    =.Choose;                           \ Choose num. pieces or quit
    ax? .==t_Quit;                      \ Zero = quit

    ax * 7; NumPieces = ax;             \ Set piece count

\-------------------------------------------------

    t_Ctr = 0;                          \ Ctr = 0
    t_XY = 1505h;                       \ Set initial position

    ax = NumPieces;                     \ Set piece word
    =.Rand; PieceW = ax;
    ax = NumPieces;                     \ Set next piece
    =.Rand; NxPiece = al;

    bl = StRound; bx & 0Fh-;            \ Set initial round
    Round = bx;

    =.NextRound;                        \ Set up round vars
    =.ShowStatus; =.ShowNext;           \ Show initial status

    =.t_PieceOn;                        \ Display the piece

\-------------------------------------------------

    {
        ax = DelayTime; =.Delay;        \ Delay for current time
        t_Ctr+; t_Ctr & 3;              \ Increment modulo 4

        bx = Round;                     \ BP = round flags
        bp = W(RoundType[bx-1]);

        bp & 4?                         \ Random blocks & pieces allowed?
        <> {
            ax = PIECE_FREQ; =.t_GenRand;
            == {
                ax = 2; =.Rand;         \ Set next piece to be a bonus piece
                al + 35; NxPiece = al;
                =.ShowNext;
            };

            ax = BLOCK_FREQ; =.t_GenRand;
            == {
                bx = t_XY; =.RandBlock; \ Generate random block
            };
        };

        bp & 16?                        \ Disappearing blocks allowed?
        <> {
            ax = DISP_FREQ; =.t_GenRand;
            == {
                bx = t_XY; =.KillBlock; \ Remove a random block
            };
        };

        bp & 8 ?                        \ Advancing lines allowed?
        <> {
            ax = LINE_FREQ; =.t_GenRand;
            == {
                =.AdvanceLine; t_Y+;    \ Advance a line
            };
        };

\-------------------------------------------------

t_KeyLoop:                              \ Start of key loop
        {
            ah = 1; !16h; .==t_KeyDone; \ Key pressed?
            =.t_PieceOff;               \ Turn piece off
            &ah; !16h; al = ah;         \ Get the key

            al - 4Bh?                   \ Key is LEFT?
            == {
                bx = t_XY-; cx = PieceW;    \ Fits at (x-1, y)?
                =.Fits; t_X ++ (-1);        \ Subtract 1 if NC
            ..};                            \ Break

            al - 4Dh?                   \ Key is RIGHT?
            == {                        
                bx = t_XY+; cx = PieceW;    \ Fits at (x+1, y)?
                =.Fits; t_X -- (-1);        \ Add 1 if NC
            ..};                            \ Break

\-------------------------------------------------

            al - 50h?                   \ Key is DOWN?
            == {
                bp = DelayTime > 1 + 20;    \ BP = drop delay
                bx = t_XY; cx = PieceW;     \ Get piece data
                =.FlushKey;                 \ Flush key buffer
                {
                    bh-; =.Fits; <<;        \ Fits below?
                    t_Drop+; t_XY = bx;     \ Inc drop time

                    si = 0FFh & cx;         \ AL = color
                    al = Colors[si];
                    =.PutPiece;             \ Draw the piece
                    ax = bp; =.Delay;       \ Delay

                    bp+; bp * 29 > 5;       \ Reduce delay 10%

                    ah = 1; !16h;           \ Key pressed?
                    <> { ah - 50h? <> 3; }; \ Not DOWN, loop back

                    &al; =.PutPiece;        \ Erase the piece
                }.;                         \ Loop back

                .t_Lock;                    \ Lock the piece
            };

\-------------------------------------------------

            al - 39h?                   \ Key is ROTATE?
            == {
                bx = t_XY; cx = PieceW;     \ Get piece data
                ch+; ch & 3;                \ Rotate once
                {
                    =.Fits; >>=; == 2;      \ Piece fits?
                    > { bx-; },             \ Move in to center
                      { bx+; };             \ by 1 square
                }.;

                t_XY = bx; PieceW = cx;     \ Write out data
            .};

            al - 44h?                   \ Key is PAUSE?
            == {
                =.t_PieceOn;                \ Redraw piece
                =.FlushKey; &ah; !16h;      \ 
            .};

            al - 01h? == 1;             \ Key is QUIT?
            al - 10h? == 1;
        };

        =.t_PieceOn;                    \ Redraw piece
        ..t_KeyLoop;                    \ Loop...

\-------------------------------------------------

t_KeyDone:                              \ End of key loop
        t_Ctr - 0?                      \ If counter is zero, then
        == {
            =.t_PieceOff;               \ Erase the piece
            bh-; =.Fits;                \ Can it move down?
            >>= {
                t_XY = bx;                  \ Save the position
                =.t_PieceOn;                \ Redraw the piece
            },{
t_Lock:         =.t_PieceOn;                \ Redraw the piece
                bx = t_XY; al = t_Drop;     \ Lock the piece
                =.LockPiece;
                t_Drop = 0;                 \ Reset drop time

                bx = 1400h; cx = 10;        \ 10 blocks at (0, 20)
                {
                    =.GetBlock;             \ Get block
                    ah? <> 3;               \ Game over if block is on
                    bx+;                    \ Next square
                }-.;

                ax = NumPieces; =.Rand;     \ Get random piece
                al == NxPiece;              \ Set next piece
                PieceW = ax;                \ Set current piece
                t_XY = bx = 1505h;

                =.ShowStatus;               \ Update screen info
                =.ShowNext;

                cx == ax; si = cx;          \ Draw the new piece
                al = Colors[si];
                =.PutPiece;
            };
        };
    }.;

    ax = 1000; =.Delay;                 \ Delay 1 second
    =.GameOver;                         \ Display 'Game Over'

\-------------------------------------------------

    =.AddScore;                         \ Add to list if high enough
    =.StopInt;                          \ Stop interrupt handler
    ..Tetris;                           \ Loop...

t_Quit:
    =.StopInt; .=;                      \ Stop interrupt handler and return

\-------------------------------------------------

UDataSeg;                               \ Local variables

t_XY        label word;
t_X         db ?;                       \ X, Y position
t_Y         db ?;
t_Ctr       db ?;                       \ Cycle counter
t_Drop      db ?;                       \ Drop distance

CodeSeg;

\-------------------------------------------------
\ t_PieceOn -- Draw the piece
\-------------------------------------------------

t_PieceOn:
    bx = cx = PieceW; &bh;          \ CX = piece
    al = Colors[bx]; bx = t_XY;     \ Get color, BX = position
    =.PutPiece; .=;                 \ Draw the piece and return

\-------------------------------------------------
\ t_PieceOff -- Erase the piece
\-------------------------------------------------

t_PieceOff:
    cx = PieceW; bx = t_XY;         \ CX = piece, BX = position
    &al; =.PutPiece; .=;            \ Erase the piece and return

\-------------------------------------------------
\ t_GenRand -- Random 1 in N
\-------------------------------------------------

t_GenRand:
    cwd; bx + 8; //bx;                  \ Convert to frequency
    =.Rand; ax? .=;                     \ Get random number, test and return

\---------------------------------------------------------------------
\ AddScore -- Add the score to the list if high enough, show the list
\---------------------------------------------------------------------

AddScore:
    pusha;                              \ Save all registers
    ax = Score[0]; dx = Score[2];       \ DX:AX = score
    dx - SC_Score[38]? >>as_add;        \ Score high enough?
    == { ax - SC_Score[36]? >>as_add; };
    ax = (-1); =.ShowScores;            \ Show scores

as_done:
    =.FlushKey; &ah; !16h; popa; .=;    \ Wait for key, then return

\-------------------------------------------------

as_add:
    bx = 8;                             \ BX = score number
    {
        si, bx * 4 + O(SC_Score);       \ SI = pointer
        dx - [si+2]? <<;                \ Compare to score
        == { ax - [si]? <<= };
        si = bx; di = bx+;              \ Copy score down
        =.CopyScore;
        bx-;
    }>=;

\-------------------------------------------------

    bx+; si, bx * 4;                    \ BX = pos, SI = pos * 4
    SC_Score[si] = ax;                  \ Set score field
    SC_Score[si+2] = dx;

    si > 1;                             \ SI = pos * 2
    SC_Lines[si] = ax = Lines;          \ Set lines field
    SC_Round[si] = ax = Round;          \ Set round field

    si < 3;                             \ SI = pos * 16
    B SC_Name[si] = 0;                  \ Clear name field

    ax == bx; =.ShowScores;             \ Show scores
    =.FlushKey;                         \ Flush keyboard

\-------------------------------------------------

    si + O(SC_Name); &bx;               \ SI = ptr. to string, zero BX
    ch = al + 13; cl = 11;              \ CX = position of string

as_eloop:
    pusha; dx = si; =.StrLenF;          \ Get string length
    dx == ax + cx;                      \ DX = cursor position
    ah = 2; &bh; !10h;                  \ Move cursor

    bx = cx; cl + 32;                   \ Clear score area
    al = 20h; =.RectC;
    dx = si; =.PutStrF; popa;           \ Output string

\-------------------------------------------------

as_key: 
    { &ah; !16h; al? }==;               \ Get a non-zero key

    al - 08h?                           \ Backspace?
    == {
        bx? ==as_key;                   \ Can't backspace empty string
        bx-; B si[bx] = 0;              \ Delete last char
        .as_eloop;                      \ Loop back
    };

    al - 0Dh? ==as_finish;              \ Enter = done

    al - 60h? >> { al - 20h; };         \ Make letters uppercase

    al - 'Z'? >>as_key;                 \ Out of range?
    al - 'A'? << {
        al - ' '? ==;
        al - '-'? <>as_key;
    };

    bx - 15? >>=as_key;                 \ No space left?
    &ah; si[bx] = ax; bx+;              \ Add the char
    .as_eloop;                          \ Loop back

\-------------------------------------------------

as_finish:
    ah = 2; &bh; dx = 1900h; !10h;      \ Put cursor off screen
    ..as_done;                          \ Return

\---------------------------------------------------------------------
\ AdvanceLine -- Insert a line at the bottom of the well
\---------------------------------------------------------------------

AdvanceLine:
    pusha;                              \ Save all registers
    dx = 1500h; cx = 10;                \ DX = initial pos, CX = count
    {
        bx = dx;                        \ BX = position
        ax === [bx+90]; &ah; ax < 2;    \ Sound at 360 + 4*X
        =.Sound;
        {
            bh-; =.GetBlock;            \ Get block
            bh+; =.PutBlock;            \ Move it up 1 square
            bh-;
        }<>;

        ax = 4; =.Rand; ax?             \ 3/4 chance of a block
        <> {
            ax = 28; =.Rand; si == ax;  \ Get block color, type
            ah = Colors[si]; &al;
        };

        =.PutBlock;                     \ Draw the block
        ax = 15; =.Delay;               \ Delay 15 msec.
        dx+;                            \ Next column
    }-.;

    =.NoSound; popa; .=;                \ Turn off speaker, return

\---------------------------------------------------------------------
\ Choose -- Display a menu and return the key pressed
\---------------------------------------------------------------------
\ returns AX = selection, 0 thru 4 (0 = quit)

Choose:
    pusha;                              \ Save all registers
    bx = 000Ah; cx = 151Dh;             \ Fill the well with a visible color
    ax = 0F20h; =.RectB;

ch_draw:
    si = O(Menu); bx = 030Bh;           \ SI = menu ptr., BX = position
    cx = M_CHOICES;                     \ CX = num. of choices
    {
        =**; dx == ax; =.PutStrF;       \ Display string
        bh + 2;                         \ Next line
    }-.;                                \ Loop...

    {                                   \ Start of key loop
        &ax; !16h;                      \ Get a key
    
        ah - 1Fh?                       \ S, sound?
        == {
            SoundOn ^ 17h; .ch_draw;    \ Flip sound flag
        };
    
        ah - 22h?                       \ G, grid?
        == {
            GridOn ^ 17h; .ch_draw;     \ Flip grid flag
        };
    
        ah - 13h?                       \ R, round?
        == {
            al = StRound & 0F7h;        \ Increment round
            ax+; StRound = al;
            .ch_draw;
        };
    
        ah - 10h? =={ &al; .};          \ Q, quit?

        al - '0' - M_NUMBERS?           \ Out of range?
    }>>;                                \ Loop...

    si = 0Fh & ax;                      \ Convert to number
    bx = 000Ah; cx = 151Dh;             \ Restore the well
    ax = 0080h; =.RectB;

    bp = sp; bp[14] = si;               \ Set pushed AX
    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ ClearWell -- Clear the well
\---------------------------------------------------------------------

ClearWell:
    pusha;                              \ Save all registers
    &ax; &bx;                           \ Set up for loop
    {
        &bl;                            \ Column = 0
        {
            =.PutBlock;                 \ Clear one block
            bx+; bl - 10?               \ Next column
        }<<;
        bh+; bh - 22?                   \ Next row
    }<<;

    popa; .=;                           \ Restore registers, return

\---------------------------------------------------------------------
\ CopyScore -- Copy a score
\---------------------------------------------------------------------
\ SI = source, DI = destination

CopyScore:
    pusha; =es; =ds; es=;               \ Save all registers, ES = DS

    si < 1; di < 1;                     \ SI = src * 2, DI = dest * 2
    SC_Lines[di] = ax = SC_Lines[si];   \ Copy lines value
    SC_Round[di] = ax = SC_Round[si];   \ Copy round value
    si < 1; di < 1;                     \ SI = src * 4, DI = dest * 4
    SC_Score[di] = ax = SC_Score[si];   \ Copy score value
    SC_Score[di+2] = ax = SC_Score[si+2];
    si < 2 + O(SC_Name);                \ SI = offset of src name
    di < 2 + O(SC_Name);                \ DI = offset of dest name
    cx = 16; <> *=*;                    \ Copy name string

    es=; popa; .=;                      \ Restore registers and return

\---------------------------------------------------------------------
\ Crescendo -- Make a crescendo sound
\---------------------------------------------------------------------
\ AX = start, BX = step, CX = count

Crescendo:
    pusha;                              \ Save all registers
    dx = 20;                            \ DX = delay time
    {
        =.Sound;                        \ Set sound
        ax == dx; =.Delay;              \ Delay 20 msec.
        ax == dx + bx;                  \ Change tone
    }-.;

    =.NoSound; popa; .=;                \ Turn off speaker, return

\---------------------------------------------------------------------
\ CvtInt - Convert int to string
\---------------------------------------------------------------------
\ AX = number, BX = string

CvtInt:
    pusha;                              \ Save all registers
    di = bx;                            \ DI = string
    =.CvtProc;                          \ Convert to string
    B [di] = 0;                         \ Add the null byte
    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ CvtLong - Convert long to string
\---------------------------------------------------------------------
\ DX:AX = number, BX = string

CvtLong:
    pusha;                              \ Save all registers
    dx? =={ =.CvtInt; },                \ DX zero, use CvtInt.
    {
        di = bx;                        \ DI = string pointer
        bx = 10000; //bx; =dx;          \ Divide by 10000, save remainder
        =.CvtProc;                      \ Convert first half

        ax=; cl = 4;                    \ AX = low part, 4 digits
        {
            &dx; //si; dl + '0';        \ Divide by 10, convert to digit
            =dx;                        \ Push digit
        }-.;                            \ Loop...

        cl = 4;                         \ 4 digits
        { ax=; [di+] = al; }-.;         \ Pop and store digits
        B [di] = 0;                     \ Add the null byte
    };

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ CvtProc - for CvtInt/CvtLong: Send AX as string to DI
\---------------------------------------------------------------------
\ AX = number, DI = string, returns SI = 10, CX = 0

CvtProc:
    si = 10; &cx;                       \ SI = 10 (base), CX = 0
    {
        &dx; //si; dl + '0';            \ Divide by 10, convert to digit
        =dx; cx+; ax?                   \ Push digit, check number
    }<>;                                \ Loop while NZ...
    { ax=; [di+] = al; }-.;             \ Pop and store digits
    .=;                                 \ Return

\---------------------------------------------------------------------
\ Decode -- Decode the file (from BufEnd) to the main buffer
\---------------------------------------------------------------------
\ ES = DS

Decode:
    pusha;                              \ Save all registers
    si = O(BufEnd); di = O(Buffer);     \ SI = in, DI = out for font
    &cx;                                \ Zero CX
    {
        =*; al? <>de_store;             \ Get byte, if NZ store 1 copy
        =*; al? ==;                     \ Get run count, 0 = break
        cl == al;                       \ CX = count, AL = 0
de_store equ $+1;                       \ Jump here skips REP
        <> *=;                          \ Store byte(s)
    }.;                                 \ Loop...

    cx = (PIECE_DATA/2);                \ CX = piece data size
    {
        =*;                             \ Get byte
        ah = al; ax +-> 4; al +-> 4;    \ Unpack into 2 bytes
        **=;                            \ Store 2 bytes
    }-.;                                \ Loop...

    ScoreOffs = ax = si - O(BufEnd);    \ Set score offset
    cx = (O(BufEnd) - O(SC_Score));     \ Copy the high scores
    <> *=*;

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ Delay -- Millisecond resolution delay
\---------------------------------------------------------------------
\ AX = time in milliseconds

Delay:
    pusha;                              \ Save all registers
    dx = 1000; **dx;                    \ DX:AX = time in microseconds
    ax == dx; ax == cx; ah = 86h; !15h; \ Call BIOS delay service
    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ DelBlock -- Delete a block from the well, fix neighbors
\---------------------------------------------------------------------
\ BL = X, BH = Y of block to delete

DelBlock:
    pusha;                              \ Save all registers

    &ax; =.PutBlock;                    \ Kill this block

    bh-;      =.GetBlock;               \ Fix block below
    al & 0Dh; =.PutBlock;
    bh + 2;   =.GetBlock;               \ Fix block above
    al & 07h; =.PutBlock;
    bh-; bx-; =.GetBlock;               \ Fix block at left
    al & 0Bh; =.PutBlock;
    bx++;     =.GetBlock;               \ Fix block at right
    al & 0Eh; =.PutBlock;

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ DelLine -- Delete a specific line from the well
\---------------------------------------------------------------------
\ AX = line to delete

DelLine:
    pusha;                              \ Save all registers

    dh = al; &dl; cx = 10;              \ DX = initial pos, 10 columns
    {
        bx = dx;                        \ BX = position
        al = bh < 2 + bl + 70;          \ AX = sound freq.
        &ah; ax < 2; =.Sound;           \ Set sound

        {
            bh+; =.GetBlock;            \ Move block down
            bh-; =.PutBlock;
            bh+; bh - 22?               \ Next line
        }<<;

        bx = dx; =.GetBlock;            \ Fix type on top
        al & 07h; =.PutBlock;
        bh-; =.GetBlock;                \ Fix type on bottom
        al & 0Dh; =.PutBlock;

        ax = 15; =.Delay;               \ Delay 15 msec.
        dx+;                            \ Next column
    }-.;                                \ Loop...

    =.NoSound;                          \ Turn off speaker
    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ DelLines -- Delete all completed lines from the well
\---------------------------------------------------------------------
\ returns AX = num. of lines deleted

DelLines:
    pusha;                              \ Save all registers

    &si; ax = 21;                       \ Zero SI, AX = top line
    {
        =.IsFull;                       \ If line is full, delete it
        << { =.DelLine; si+; };
        ax-;                            \ Next line
    }>=;                                \ Loop...

    bp = sp; bp[14] = si;               \ Set pushed AX
    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ Disp -- Display character and/or attribute
\---------------------------------------------------------------------
\ BL = X, BH = Y, AL = char, AH = attr, displayed if nonzero

Disp:
    pusha;                              \ Save all registers

    &cx; cl == bh;                      \ Split X from Y
    di, cx * 160 + bx + bx;             \ DI = offset
    al? <>{ *=; di-; };                 \ Set char if NZ
    di+; al = ah? <>{ *=; };            \ Set attr if NZ

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ DrawBox - Draw an inset box using F8-FF charset
\---------------------------------------------------------------------
\ BL = X1, BH = Y1, CL = X2, CH = Y2

DrawBox:
    pusha;                              \ Save all registers
    si = bx; di = cx;                   \ Save BX, CX

    bh+; ch-; =cx; cl = bl;             \ Draw left side
    ax = 00F8h; =.RectC; cx=;
    bl = cl; ax+; =.RectC;              \ Draw right side

    bx = si; cx = di;                   \ Get old values
    bx+; cx-; =cx; ch = bh;             \ Draw top side
    ax+; =.RectC; cx=;
    bh = ch; ax+; =.RectC;              \ Draw bottom side

    cx = si; dx = di;                   \ CX = UL corner, DX = UR corner
    bx = cx; ax+;  =.Disp;              \ Draw UL corner
    bl = dl; ax+;  =.Disp;              \ Draw UR corner
    bh = dh; ax++; =.Disp;              \ Draw LR corner
    bl = cl; ax-;  =.Disp;              \ Draw LL corner

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ DrawTitle -- Draw title box
\---------------------------------------------------------------------
\ BX = position of UL corner

DrawTitle:
    pusha;                              \ Save all registers
    cx = bx + 0312h;                    \ Clear title area
    ax = 0F20h; =.RectB;

    bx+; dx = O(Title$1); =.PutStr;     \ Display strings
    bh+; dx = O(Title$2); =.PutStr;
    bh+; dx = O(Deluxe$); =.PutStrF;
    bh+; dx = O(Copyright$); =.PutStr;

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ Fits -- Check whether a piece fits
\---------------------------------------------------------------------
\ BL = X, BH = Y, CL = piece, CH = rotation
\ returns NC = fits, CY = doesn't fit:
\ L = off left side, G = off right side, Z = hit something

Fits:
    pusha;                              \ Save all registers
    &dx; dl == ch;                      \ Separate piece data

    bp, dx * PIECE_SIZE;                \ SI = piece offset
    si, cx * (12*PIECE_SIZE);
    si === Pieces[si+bp];
    cx = PIECE_SIZE;                    \ CX = piece size
    {
        =bx; bl + [si];                 \ Save BX, add in offsets
        bh + si[4*PIECE_SIZE];
        =.GetBlock; bx=;                \ Get block, fix BX
        ah?                             \ Block exists?
        <> {
            ah - (-2)?                  \ Normal cases --> 0
            =={ ah = 1; };              \ Off left (-1) --> -1
            <<{ &ah; };                 \ Off right (-2) --> -2
            ah ?; |;                    \ Set flags, set carry flag
        .};                             \ Break...

        si+;                            \ Next block
    }-.;                                \ Loop...

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ FlushKey -- Flush the BIOS keyboard buffer
\---------------------------------------------------------------------

FlushKey:
    =es =ax;                            \ Save registers
    =40h; es=;                          \ ES = BIOS segment
    es:[1Ah] = ax = es:[1Ch];           \ Set head = tail
    es= ax=; .=;                        \ Restore registers and return

\---------------------------------------------------------------------
\ GameOver -- Draw the 'Game Over' box
\---------------------------------------------------------------------

GameOver:
    pusha;                              \ Save all registers

    bx = 090Ch; cx = 0E1Bh;             \ Clear a rectangle
    ax = 0F20h; =.RectB;
    bx = 0A0Eh; cx = 0D19h;
    ah = 4Fh; =.RectB;

    dx = O(GameOver$1); =.PutStrF; bh+; \ Display 'Game Over' box
    dx = O(GameOver$2); =.PutStrF; bh+;
    dx = O(GameOver$3); =.PutStrF; bh+;
    dx = O(GameOver$4); =.PutStrF;

    =.FlushKey; &ax; !16h;              \ Wait for a key
    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ GetBlock -- Return the status of a block in the well
\---------------------------------------------------------------------
\ BL = X, BH = Y, returns AH = color, AL = type
\ color: -1 = off left, -2 = off right, -3 = off bottom

GetBlock:
    =bx =cx =di;                        \ Save registers
    ax = 0FD00h;                        \ AH:AL = -3:0
    {
        bh?      <; ah+;                \ Off bottom = -3
        bl - 9?  >; ah+;                \ Off right = -2
        bl?      <; ah+;                \ Off left = -1
        bh - 21? >;                     \ Off top =  0
    
        &cx; cl == bh;                  \ Separate X from Y
        di, cx * (-160);                \ DI = offset
        bx < 2; di === [di+bx+0D34h];

        ax = es:[di]; al - 80h > 1;     \ AX = value, fix type
        al - 10h ? =={ &ax; };          \ Patch for grid, ??10h -> 0000h
    };

    bx= cx= di=; .=;                    \ Restore registers and return

\---------------------------------------------------------------------
\ Initialize -- Reset all variables, display initial screen
\---------------------------------------------------------------------

Initialize:
    pusha;                              \ Save all registers
    &ax;                                \ Zero AX
    Score[0] = ax; Score[2] = ax;       \ Zero score, lines, etc.
    Lines = ax; BonusCtr = ax;
    PalCtr = ax; RectColor = al;
    RectCtr = al;

    =.InitScreen;                       \ Show the initial screen
    =.SRand;                            \ Seed RNG
    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ InitScreen -- Display the initial screen
\---------------------------------------------------------------------

InitScreen:
    pusha;                              \ Save all registers

    bx = 0000h; cx = 184Fh;             \ Clear screen to 7/F dither
    ax = 7F60h; =.RectB;
    ch = 01h; al = 00h; =.RectA;        \ Clear top 2 lines
    =.ClearWell;                        \ Draw the well

    bx = 0300h; cx = 1700h;             \ Draw vertical 3-D sides
    al = 94h; =.RectC;
    bl = 1Eh; cx = 151Eh; =.RectC;
    bl = 4Fh; cx = 174Fh; ax+; =.RectC;
    bl = 09h; cx = 1509h; =.RectC;

    bx = 1801h; cx = 184Eh;             \ Draw horizontal 3-D sides
    al = 0FAh; =.RectC;
    bh = 02h; cx = 0208h; ax+; =.RectC;
    bl = 1Fh; cl = 4Eh; =.RectC;

    ax = 98h; bx = 0200h; =.Disp;       \ Draw 3-D corners
    bl = 1Eh; =.Disp;
    al = 93h; bl = 09h; =.Disp;
    bl = 4Fh; =.Disp;
    ax = 8Ch; bx = 1800h; =.Disp;
    ax = 87h; bl = 4Fh; =.Disp;

    bx = 0338h; cx = 084Ch; =.DrawBox;  \ Draw title box
    bx = 0439h; =.DrawTitle;
    TitleXY = bx;

\-------------------------------------------------

    bx = 160Ah; cx = 161Dh;             \ Draw bonus box
    al = 8Ah; =.RectC;
    bh = ch = 18h; =.RectC;
    bh = ch = 17h; ax = 0E20h; =.RectB;
    ax = 9Fh; bx = 1609h; =.Disp;
    ax-; bl = 1Eh; =.Disp;
    ax = 95h; bx = 1709h; =.Disp;
    ax-; bl = 1Eh; =.Disp;
    ax = 8Fh; bx = 1809h; =.Disp;
    ax-; bl = 1Eh; =.Disp;

\-------------------------------------------------

    bx = 0302h; cx = 0606h; dx = 5;     \ Draw count boxes
    {                                   \ Borders...
        =.DrawBox;
        bh + 4; ch + 4; dx-;
    }<>;

    bx = 0403h; cx = 0505h;             \ Insides...
    ax = 7020h; dx = 5;
    {
        =.RectB;
        bh + 4; ch + 4; dx-;
    }<>;

\-------------------------------------------------

    bx = 0321h; cx = 0930h; =.DrawBox;  \ Draw next-piece box
    bx = 0422h; cx = 082Fh;
    ax = 0080h; =.RectB;

    bx = 0B24h; cx = 0E3Ah; dx = 4;     \ Draw score boxes
    {                                   \ borders...
        =.DrawBox;
        bh + 3; ch + 3; dx-;
    }<>;

    bx = 0C25h; cx = 0D39h;             \ insides...
    ax = 8A20h; dx = 4;
    {
        =.RectB;
        bh + 3; ch + 3; dx-;
    }<>;

    bx = 0E25h; cx = 0E39h; dx = 3;    \ dividers...
    {
        pusha; ax = 008Ah; =.RectC;
        bx-; al = 9Fh; =.Disp;
        cx+; bx = cx; ax-; =.Disp;
        popa; bh + 3; ch = bh; dx-;
    }<>;

\-------------------------------------------------

    bx = 0C2Ah; dx = O(Round$);         \ Draw score headers
    =.PutStrF;
    bx = 0F27h; dx = O(LinesLeft$);
    =.PutStrF;
    bx = 122Ah; dx = O(Score$);
    =.PutStrF;
    bx = 152Bh; dx = O(Lines$);
    =.PutStrF;

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ IsEmpty -- Check whether a line is empty
\---------------------------------------------------------------------
\ AX = line to check, returns CY = yes, NC = no

IsEmpty:
    pusha;                              \ Save all registers

    cx = 10; bh = al; &bl;              \ 10 blocks, BX = position
    {
        {
            =.GetBlock; bx+;            \ Get block
            ah? <> 1;                   \ Break if full
        }-.;

        |;                              \ Set carry flag
    };

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ IsFull -- Check whether a line is full
\---------------------------------------------------------------------
\ AX = line to check, returns CY = yes, NC = no

IsFull:
    pusha;                              \ Save all registers

    cx = 10; bh = al; &bl;              \ 10 blocks, BX = position
    {
        {
            =.GetBlock; bx+;            \ Get block
            ah? == 1;                   \ Break if empty
        }-.;

        |;                              \ Set carry flag
    };

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ KillBlock -- Remove a random block
\---------------------------------------------------------------------
\ BL = X, BH = Y of area to avoid

KillBlock:
    pusha;                              \ Save all registers

    cx = 500; dx = bx;                  \ 500 max. iters, DX = position
    {
        {
            ax = 8; =.Rand; bx == ax+;  \ Generate random block
            ax = 20; =.Rand; bh = al;
        
            al = dl - bl + 3 - 7?       \ If within 3 of both the X and
            << {                        \ and Y pos. of the piece,
                al = dh - bh + 3 - 7?   \ don't put a block here.
                << 1;
            };
        
            =.GetBlock; ah? ==;         \ Block empty?
            =.DelBlock;                 \ Otherwise, delete it
        .};
    }-.;

    popa; .=;                           \ Restore registers and return
        
\---------------------------------------------------------------------
\ LockPiece -- Lock a piece in the well
\---------------------------------------------------------------------
\ AL = drop distance, BL = X position, BH = Y position

LockPiece:
    pusha;                              \ Save all registers
    bp = bx; di = bx > 8;               \ BP = (X, Y), DI = Y
    &ah; si == ax;                      \ SI = D

    ax = 40; =.Rand; ax * 10 + 440;     \ AX = random freq.
    bx = 110; cx = 3; =.Crescendo;      \ Make a noise

    ax, Round * 10; dx === [di+22];     \ Calculate score for piece:
    **dx; dx === [si+22] - di; **dx;    \          22+Y   22-Y+D
    bx, di * (-22) + (22*22); //bx;     \ 10 * R * ---- * ------
                                        \           22     22-Y

    bx = 9; =.ShowBonus;                \ Show for 1/2 second
    Score[0] + ax; Score[2] ++ 0;       \ Add into score
    =.ShowStatus;                       \ Update screen info

\-------------------------------------------------

    Piece - ACID_PIECE?                 \ Acid piece?
    == {
        dx = bp; dl - 2; dh + 2; &bp;   \ DX = position, BP = 0
        {
            bx = dx; cx = 5;            \ Set up for inner loop
            {
                =.DelBlock;             \ Delete this block
                ax = 880; =.Rand;       \ Make a random noise
                ax + 220; =.Sound;
                ax = 10; =.Delay;       \ Delay 10 msec.
                bl+;                    \ Next block
            }-.;                        \ Loop...

            ax, Round * 5 + bp;         \ Add in and display bonus
            bp = ax; bx = 9; =.ShowBonus;
            dh-;                        \ Next line
        }>=;                            \ Loop...

        Score[0] + bp; Score[2] ++ 0;   \ Add to score
        =.NoSound;                      \ Sound off
    };

\-------------------------------------------------

    Piece - BOMB_PIECE?                 \ Bomb piece?
    == {
        dx = bp; dl - 3; dh + 3;        \ DX = position
        si = 7;                         \ 7 lines
        {
            bx = dx; cx = 7;            \ Set up for inner loop
            {
                =.DelBlock;             \ Delete this block
                ax = 220; =.Rand;       \ Make a random noise
                ax + 110; =.Sound;
                ax = 5; =.Delay;        \ Delay 5 msec.
                bl+;                    \ Next block
            }-.;                        \ Loop...

            dh-; si-;                   \ Next line
        }<>;                            \ Loop...

        =.NoSound; ax, Round * 150;     \ Sound off, add in bonus
        Score[0] + ax; Score[2] ++ 0;
    };

\-------------------------------------------------

    =.DelLines; ax?                     \ Delete all lines
    <> {                                \ If lines were deleted, then
        =ax; bx == ax + bx;             \ Save AX, BX = 2*AX
        ax = LineVals[bx];              \ AX = value of line
        dx = Round+; dx > 1; **dx;      \ Multiply by (round/2)

        bx = 170Ah; dx = O(Bonus$);     \ Show 'BONUS' string
        =.PutStrF;
        bx = 18; =.ShowBonus;           \ Show for 1 second
        Score[0] + ax; Score[2] ++ 0;   \ Add into score
        ax=; Lines + ax;                \ Restore AX, update line count
    };

    =.ShowStatus;                       \ Show status
    LinesLeft - ax;                     \ Update lines-left count
    <= { =.NextRound; };                \ Zero = go to next round

    =.ShowStatus; =.FlushKey;           \ Show status, flush key buffer
    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ LowBonus -- Add bonus for low puzzle
\---------------------------------------------------------------------

LowBonus:
    pusha;                              \ Save all registers
    bx = 000Ah; cx = 011Dh;             \ Clear top section
    ax = 0320h; =.RectB;

    bx+; dx = O(LowBonus$1); =.PutStrF; \ Display header
    bh+; dx = O(LowBonus$2); =.PutStrF;
    ax = 1000; =.Delay;                 \ Delay 1 second

    bx = 170Ah; dx = O(Bonus$);         \ Display bonus string
    =.PutStrF;

    bx = 000Ah; cx = 011Dh;             \ Set color of top section
    al = 0Fh; =.RectA;                  \ to white on black

    di = 19;                            \ Start with top line
    &si; &bp;                           \ Bonus, increment = 0
    {
        ax = si; &bx; =.ShowBonus;      \ Display bonus
        ax, di * (-20) + 840; bx = 15;  \ Make a noise
        cx = 10; =.Crescendo;

        ax = di; =.IsEmpty; >>=;        \ Break if line isn't empty
        bh = al; &bl; ax = 3804h;       \ BX = position, AX = color
        =.PutBlock; cx = 8; ax+;        \ Put first block, CX = 8
        { bx+; =.PutBlock; }-.;         \ Put middle blocks
        bx+; al = 01h; =.PutBlock;      \ Put last block

        dx, Round * 10;                 \ Increment bonus
        bp + dx; si + bp;
        di-;                            \ Next line
    }.;                                 \ Loop...

\-------------------------------------------------

    Score[0] + si; Score[2] ++ 0;       \ Add to score
    ax = 400; =.Delay;                  \ Delay 400 msec.
    ax = 440; bx = cx = 20;             \ Make a noise
    =.Crescendo;
    =.ShowStatus; BonusCtr = 1;         \ Show status, reduce bonus timer

\-------------------------------------------------

    dx === [di+1]; dl == dh;            \ DX = line
    {
        bx = dx; &ax;                   \ Set up for loop
        {
            =.PutBlock;                 \ Clear block
            bx+; bl - 10?               \ Next block
        }<<;                            \ Loop...

        ax = 30; =.Delay;               \ Delay 30 msec.
        dh+; dh - 20?                   \ Next line
    }<<;                                \ Loop...

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ NewInt1C -- Timer tick handler
\---------------------------------------------------------------------

NewInt1C:
    =??;                                \ Simulate an interrupt
            db 09Ah;                    \ CALL FAR opcode
OldInt1C    dw ?,?;                     \ Old interrupt handler

    pusha; =ds =es;                     \ Save all registers
    =cs; ds=; =0B800h; es=;             \ DS = CS, ES = video memory

    bx = PalCtr+; bx & 07h;             \ BX = palette counter
    PalCtr = bx;                        \ Increment modulo 8

    dx = 03DAh; { =@; al & 8? }==;      \ Wait for VRT

    dx = 03C0h; al = 03h; @=;           \ DX = AC port, setting color 3
    =.ni_ret; al = PalVals[bx]; @=;     \ Delay, then set color
    =.ni_ret; al = 20h; @=;             \ Delay, then restart screen

\-------------------------------------------------

    bl = RectCtr; al = RectColor;       \ Get rectangle info
    bx+; bl - 19?                       \ Increment, check for wrap
    >>= {
        &bl;                            \ Wrap to start
        ax+; al - 6? >>= { &al; };      \ Increment color modulo 12
    };

    RectCtr = bl; RectColor = al;       \ Save rectangle info
    al + 9; bl + TitleX; bh = TitleY;   \ AL = color, BX:CX = position
    cx = bx; ch + 03h; =.RectA;         \ Draw attr. rectangle

\-------------------------------------------------

    BonusCtr - 0?                       \ Check bonus counter
    <> {                                \ If nonzero, then
        BonusCtr-; <>;                  \ Decrement, break if still NZ
        bx = 170Ah; cx = 171Dh;         \ Clear the bonus box
        ax = 0E20h; =.RectB;
    };

    ds= es=; popa; !.=;                 \ Restore registers, then IRET

\-------------------------------------------------

ni_ret: .=;                             \ Null proc. for I/O delay

\---------------------------------------------------------------------
\ NextRound -- Advance to the next round
\---------------------------------------------------------------------

NextRound:
    pusha;                              \ Save all registers

    LinesLeft = 0; =.ShowStatus;        \ Show with LinesLeft = 0
    al = StRound; ax - Round - '1'?     \ If it's not the first round,
    <> { =.LowBonus; };                 \ do the bonus sequence
    =.ClearWell;                        \ Clear the well

    Round+;                             \ Next round
    bx = Round; al = RoundTimes[bx];    \ Increase speed
    &ah; DelayTime = ax;
    al = RoundLines[bx-1];              \ Set new LinesLeft
    LinesLeft = ax;

\-------------------------------------------------

    bx = 070Ah; cx = 0B1Dh;             \ Clear a rectangle
    ax = 0F20h; =.RectB;

    =es; =ds; es=;                      \ Save ES, ES = DS
    si = O(Round$); di = O(BufEnd);     \ Copy 'ROUND ' plus the
    cx = 6; <> *=*; ax = Round;         \ round number into the buffer
    bx = O(BufEnd+6); =.CvtInt;

    ax = LinesLeft; bx = O(BufEnd+10);  \ Convert LinesLeft
    =.CvtInt;
    di = O(BufEnd+13); ax - 10?         \ DI = offset, add a space
    di -- 0; [di-1] = al = ' ';
    si = O(Lines$); cx = 6; <> *=*;     \ Copy lines string
    es=;                                \ Restore ES

    dx = O(BufEnd); bx = 080Dh;         \ Display strings
    =.PutStrF; dx = O(BufEnd+10);
    bh = 0Ah; =.PutStrF;
    =.FlushKey; &ax; !16h;              \ Wait for a key

\------------------------------------------------

    =.ClearWell;                        \ Clear the well
    bx = Round; bl = RoundType[bx-1];   \ BX = round type
    bx & 3 + bx; si = BlockLists[bx];   \ SI = block list
    {
        =**; ax - (-1)? ==;             \ Load word, break if -1
        bx == ax; ax = 8F00h;           \ Draw this block
        =.PutBlock;
    }.;                                 \ Loop...

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ NoSound -- Turn off the speaker
\---------------------------------------------------------------------

NoSound:
    =ax;                                \ Save AX
    =@61h; al & 0FCh; @61h=;            \ Turn off speaker
    ax=; .=;                            \ Restore AX, return

\---------------------------------------------------------------------
\ PutBlock -- Display a block in the well
\---------------------------------------------------------------------
\ BL = X, BH = Y, AH = color, AL = type

PutBlock:
    pusha;                              \ Save all registers
    {
        bl - 10? >>=;                   \ Out of range?
        bh - 22? >>=;
        al & 0Fh;                       \ Mask type to 0-15

        GridOn - 'Y'?                   \ If the grid is on and this piece
        == {                            \ is empty, make it a grid block
            ah? =={ ax = 0810h; };
        };

        &cx; cl == bh;                  \ Separate X from Y
        di, cx * (-160); bx < 2;        \ DI = offset
        di === [di+bx+0D34h];

        al + al + 80h;                  \ AL = char
        **=; ax+; **=;                  \ Write to screen
    };

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ PutPiece -- Display a Tetris piece in the well
\---------------------------------------------------------------------
\ BL = X, BH = Y, CL = piece, CH = rotation, AL = color

PutPiece:
    pusha;                              \ Save all registers
    &dx; dl == ch;                      \ Separate piece data
    bp, dx * PIECE_SIZE;                \ SI = piece offset
    si, cx * (12*PIECE_SIZE);
    si === Pieces[si+bp];

    cx = PIECE_SIZE; ah = al;           \ CX = num. of blocks, AH = color
    {
        =bx; bl + [si];                 \ Save BX, add in offsets
        bh + si[4*PIECE_SIZE];
        al = si[8*PIECE_SIZE];          \ AL = type
        =.PutBlock; bx=;                \ Display block, restore BX
        si+;                            \ Next block
    }-.;                                \ Loop...

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ PutStr -- Display normal string
\---------------------------------------------------------------------
\ BL = X, BH = Y, DX = ptr. to ASCIIZ string

PutStr:
    pusha;                              \ Save all registers
    si = dx; &cx; cl == bh;             \ SI = string, split X from Y
    di, cx * 160 + bx + bx;             \ DI = offset
    {
        =*; al? ==;                     \ Load byte, break if null
        *=; di+;                        \ Write to screen
    }.;

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ PutStrF -- Display fat string
\---------------------------------------------------------------------
\ BL = X, BH = Y, DX = ptr. to ASCIIZ string

PutStrF:
    pusha;                              \ Save all registers
    si = dx; &cx; cl == bh;             \ SI = string, split X from Y
    di, cx * 160 + bx + bx;             \ DI = offset
    bx = O(BigChars);                   \ BX = fat char table
    {
        =*; al? ==;                     \ Load byte, break if null
        al - 20h - 50h? >>= { &al; };   \ Scale to table, too big --> 0
        ><; *=; di+;                    \ Get font char, write to screen
        al? --{ ax+; *=; di+; };        \ If 2-byte char, write second half
    }.;

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ Rand -- Generate random number
\---------------------------------------------------------------------
\ AX (N) = range, returns AX = random number below N

Rand:
    =bx =cx =dx; =ax;                   \ Save registers, push AX (max)

    ax = RandNum[0]; dx = 660Dh; **dx;  \ CX:BX = RandNum * 19660Dh
    cx = dx; bx == ax;
    ax, RandNum[0] * 0019h;
    dx, RandNum[2] * 660Dh;
    cx + ax + dx;
    bx + 0DCDh; cx + 0001h;             \ RandNum * 19660Dh + 10DCDh
    RandNum[0] = bx; RandNum[2] = cx;   \ Save random number

    bh & 80h; ax == cx; ah | bh;        \ AX = bits 15-30
    bx=; &dx;                           \ Get max, zero DX
    bx? <>{ //bx; };                    \ If max <> 0, divide by max
    ax == dx;                           \ AX = result

    bx= cx= dx=; .=;                    \ Restore registers and return

\---------------------------------------------------------------------
\ RandBlock -- Add a random block
\---------------------------------------------------------------------
\ BL = X, BH = Y of area to avoid

RandBlock:
    pusha;                              \ Save all registers

    cx = 500; dx = bx;                  \ 500 max. iters, DX = position
    {
        {
            ax = 8; =.Rand; bx == ax+;  \ Generate random block
            ax = 20; =.Rand; bh = al;
        
            al = dl - bl + 3 - 7?       \ If within 3 of both the X and
            << {                        \ and Y pos. of the piece,
                al = dh - bh + 3 - 7?   \ don't put a block here.
                << 1;
            };
        
            =.GetBlock; ah? <>;         \ Block not empty?
        
            bx-; =.GetBlock; bx+; ah? <> rb_add;  \ If any surrounding block
            bx+; =.GetBlock; bx-; ah? <> rb_add;  \ exists, then it's OK to
            bh-; =.GetBlock; bh+; ah? <> rb_add;  \ add this block.
            bh+; =.GetBlock;      ah? <> rb_add;
        };
    }-.;

    popa; .=;                           \ Restore registers and return

rb_add:
    ax = 3800h; =.PutBlock;             \ Add this block
    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ RectA -- Rectangle of attributes
\---------------------------------------------------------------------
\ BL = X1, BH = Y1, CL = X2, CH = Y2, AL = attr

RectA:
    pusha;                              \ Save all registers

    &dx; dl == bh;                      \ Split X1 from Y1
    bp, dx * 160 + bx + bx+;            \ BP = offset
    =ax; &ax; al == ch;                 \ Push AX, split X2 from Y2
    .RectX;                             \ Draw the rectangle

\---------------------------------------------------------------------
\ RectB -- Rectangle of both chars and attrs
\---------------------------------------------------------------------
\ BL = X1, BH = Y1, CL = X2, CH = Y2, AL = char, AH = attr

RectB:
    pusha;                              \ Save all registers

    &dx; dl == bh;                      \ Split X1 from Y1
    bp, dx * 160 + bx + bx;             \ BP = offset
    =ax; &ax; al == ch;                 \ Push AX, split X2 from Y2

    cx - bx; bx = cx+;                  \ BX = X distance
    ax - dx; dx == ax;                  \ DX = Y distance
    ax=;                                \ Get data byte
    {
        di = bp; cx = bx;               \ DI = offset, CX = X size
        <> **=;                         \ Write one row
        bp + 160; dx-;                  \ Next line
    }>=;                                \ Loop...

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ RectC -- Rectangle of characters
\---------------------------------------------------------------------
\ BL = X1, BH = Y1, CL = X2, CH = Y2, AL = char

RectC:
    pusha;                              \ Save all registers

    &dx; dl == bh;                      \ Split X1 from Y1
    bp, dx * 160 + bx + bx;             \ BP = offset
    =ax; &ax; al == ch;                 \ Push AX, split X2 from Y2
    .RectX;                             \ Draw the rectangle

\---------------------------------------------------------------------
\ RectX -- Drawing tail for RectA, RectC
\---------------------------------------------------------------------
\ BX = X1, DX = Y1, CX = X2, AX = Y2, BP = offset, data byte on stack

RectX:
    cx - bx; bx = cx+;                  \ BX = X distance
    ax - dx; dx == ax;                  \ DX = Y distance
    ax=;                                \ Get data byte
    {
        di = bp; cx = bx;               \ DI = offset, CX = X size
        { *=; di+; }-.;                 \ Write one row
        bp + 160; dx-;                  \ Next line
    }>=;                                \ Loop...

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ ShowBonus -- Display a bonus value
\---------------------------------------------------------------------
\ AX = value, BX = display time

ShowBonus:
    pusha; =bx;                         \ Save all registers, push BX

    bx = O(BufEnd); =.CvtInt;           \ Convert value to string
    dx = bx; =.StrLenF;                 \ Get length
    bx = 171Eh; bl - al; =.PutStrF;     \ Display in bonus box
    cx = 171Dh; al = 0Fh; =.RectA;      \ Set color to white
    BonusCtr=;                          \ Set bonus time

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ ShowNext -- Show next piece
\---------------------------------------------------------------------

ShowNext:
    pusha;                              \ Save all registers

    bx = 0422h; cx = 082Fh; ax = 80h;   \ Clear next-piece box
    =.RectB;
    bl = NxPiece; &bh;                  \ BX = piece

    si, bx * (12*PIECE_SIZE);           \ SI = piece offset
    si + O(Pieces);
    ah = Colors[bx]; bp == ax;          \ BP = piece color
    cx = PIECE_SIZE;                    \ CX = num. of blocks
    {
        al = [si]; cbw; bx == ax;       \ BX = X value
        al = si[4*PIECE_SIZE]; cbw;     \ AX = Y value

        di, ax * (-160);                \ DI = offset
        bx < 2; di === [di+bx+0410h];

        ax = bp;                        \ AH = color
        al = si[8*PIECE_SIZE] & 0Fh;    \ AL = type
        al + al + 80h;                  \ Convert to char
        **=; ax+; **=;                  \ Write to screen
        si+;                            \ Next block
    }-.;                                \ Loop...

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ ShowScores -- Display the high scores
\---------------------------------------------------------------------
\ AX = score to highlight (-1 for none)

ShowScores:
    pusha; =ax;                         \ Save all registers, push AX
    =.StopInt;                          \ Stop interrupt handler

    &bx; cx = 184Fh; ax = 7F60h;        \ Clear the screen
    =.RectB;

    bx = 011Eh; cx = 0632h; =.DrawBox;  \ Draw title box
    bx = 021Fh; =.DrawTitle;
    TitleXY = bx;
    RectCtr = 0; =.StartInt;            \ Restart interrupt handler

    bx = 0100h; cx = 1700h;             \ Draw vertical 3-D sides
    al = 94h; =.RectC;
    bl = cl = 4Fh; ax+; =.RectC;

    bx = 0001h; cx = 004Eh;             \ Draw horizontal 3-D sides
    al = 0FBh; =.RectC;
    bh = ch = 18h; ax-; =.RectC;

    ax = 98h; &bx; =.Disp;              \ Draw 3-D corners
    al = 93h; bl = 4Fh; =.Disp;
    al = 8Ch; bx = 1800h; =.Disp;
    al = 87h; bl = 4Fh; =.Disp;

    bx = 0717h; cx = 0938h; =.DrawBox;  \ Draw 'High Scores' box
    bx = 0818h; cx = 0837h; ax = 0E20h;
    =.RectB; bx+; dx = O(HighScores$);
    =.PutStrF;

    bx = 0A03h; cx = 174Ch; =.DrawBox;  \ Draw main box
    bx = 0B04h; cx = 164Bh; ax = 0A20h;
    =.RectB; ch = bh; al = 0Fh; =.RectA;

\-------------------------------------------------

    dx = O(Name$);  bl = 0Ch; =.PutStr; \ Display header strings
    dx = O(Score$); bl = 34h; =.PutStr;
    dx = O(Lines$); bl = 3Eh; =.PutStr;
    dx = O(Round$); bl = 46h; =.PutStr;

\-------------------------------------------------

    &cx;                                \ Zero CX
    {
        ax = cx+; bx = O(BufEnd);       \ Put line number in buffer
        =.CvtInt;
        cx - 9? bx -- (-1);             \ Put period at end
        [bx+1] = ax = 002Eh;

        bh = cl + 13; bl = 5;           \ Output string
        dx = O(BufEnd); =.PutStrF;

        dx = cx < 4 + O(SC_Name);       \ Output name string
        bl = 11; =.PutStrF;

\-------------------------------------------------

        si = cx < 2;                    \ Get score
        ax = SC_Score[si]; dx = SC_Score[si+2];
        bx = O(BufEnd); =.CvtLong;      \ Convert and display
        dx = bx; =.StrLenF; bl = 57; =.sc_disp;

        si > 1; ax = SC_Lines[si];      \ Get lines
        bx = O(BufEnd); =.CvtInt;       \ Convert to string
        dx = bx; bl = 67; =.sc_disp;    \ Display string

        ax = SC_Round[si];              \ Get round
        bx = O(BufEnd); =.CvtInt;       \ Convert to string
        dx = bx; bl = 75; =.sc_disp;    \ Display string

        cx+; cx - 10?                   \ Next line
    }<<;

\-------------------------------------------------

    ax=; ax?                            \ AX = highlight
    >= {
        ch = bh = 13 + al; bl = 04h;    \ Turn the line yellow
        cl = 4Bh; al = 0Eh; =.RectA;
    };

    popa; .=;                           \ Restore registers and return

\-------------------------------------------------
\ sc_disp -- Display string right-justified
\-------------------------------------------------
\ BL = position, DX = string

sc_disp:
    =.StrLenF; bh = cl + 13;            \ Get position
    bl - al; =.PutStrF; .=;             \ Display string and return

\---------------------------------------------------------------------
\ ShowStatus -- Display score, round, etc.
\---------------------------------------------------------------------

ShowStatus:
    pusha;                              \ Save all registers

    bx = 0D25h; cx = 0D39h;             \ Clear score boxes
    ax = 8F20h; dx = 4;
    {
        =.RectB;
        bh + 3; ch + 3; dx-;
    }<>;

    bx = 0403h; cx = 0505h;             \ Clear count boxes
    ax = 7020h; dx = 5;
    {
        =.RectB;
        bh + 4; ch + 4; dx-;
    }<>;

\-------------------------------------------------

    ax = Round; bx = O(BufEnd);         \ Convert 'Round'
    =.CvtInt; dx = bx; =.StrLen;
    bx = 0D2Fh; bl - al; =.PutStrF;     \ Display 'Round'

    ax = LinesLeft; bx = O(BufEnd);     \ Convert 'Lines Left'
    =.CvtInt; dx = bx; =.StrLen;
    bx = 102Fh; bl - al; =.PutStrF;     \ Display 'Lines Left'

    ax = Score[0]; dx = Score[2];       \ Convert 'Score'
    bx = O(BufEnd); =.CvtLong;
    dx = bx; =.StrLen;
    bx = 132Fh; bl - al; =.PutStrF;     \ Display 'Score'

    ax = Lines; bx = O(BufEnd);         \ Convert 'Lines'
    =.CvtInt; dx = bx; =.StrLen;
    bx = 162Fh; bl - al; =.PutStrF;     \ Display 'Lines'

\-------------------------------------------------

    ax = LinesLeft - 5?                 \ If LinesLeft < 5, then display
    <<= {                               \ the count in a box at the left
        ax? ==;

        ah = 0Ch;                       \ AH = red on black color
        bl = al; al + al + bl + 1Bh;    \ AL = 1st font char
        bh = 6 - bl < 2; bl = 3;        \ BX = position

        =.ss_disp2;                     \ Draw top half
        bx + 00FEh; al + 0C8h;          \ Go to LL corner
        =.ss_disp2;                     \ Draw bottom half
    };

    popa; .=;                           \ Restore registers and return

\-------------------------------------------------
\ ss_disp2 -- disp 3 chars moving right
\-------------------------------------------------

ss_disp2:
    =.Disp; ax+; bx+;                   \ Disp char 1, next char
    =.Disp; ax+; bx+;                   \ Disp char 2, next char
    =.Disp; .=;                         \ Disp char 3 and return

\---------------------------------------------------------------------
\ Sound -- Sound the speaker
\---------------------------------------------------------------------
\ AX = frequency

Sound:
    pusha;                              \ Save all registers

    SoundOn - 'Y'?                      \ Sound must be on
    == {
        bx == ax;                       \ BX = freq.
        dx = 12h; ax = 34DCh;           \ BX = 1193180 / freq.
        //bx; bx == ax;

        al = 0B6h; @43h=;               \ Set frequency
        al = bl; @42h=;
        al = bh; @42h=;

        =@61h; al | 3; @61h=;           \ Turn on speaker
    };

    popa; .=;                           \ Restore registers and return

\---------------------------------------------------------------------
\ SRand -- Seed random number generator
\---------------------------------------------------------------------

SRand:
    =es =ax;                            \ Save registers
    =40h; es=;                          \ ES = BIOS segment
    RandNum[0] = ax = es:[6Ch];         \ Seed random number using
    RandNum[2] = ax = es:[6Eh];         \ BIOS timer tick counter
    es= ax=; .=;                        \ Restore registers and return

\---------------------------------------------------------------------
\ StartInt -- Start the interrupt handler
\---------------------------------------------------------------------

StartInt:
    pusha; =es;                         \ Save all registers
    ax = 351Ch; !21h;                   \ Save old Int 1Ch
    OldInt1C[0] = bx; OldInt1C[2] = es;
    dx = O(NewInt1C); ah = 25h; !21h;   \ Set new Int 1Ch
    es=; popa; .=;                      \ Restore registers and return

\---------------------------------------------------------------------
\ StopInt -- Stop the interrupt handler
\---------------------------------------------------------------------

StopInt:
    pusha; =ds;                         \ Save all registers
    dx = OldInt1C[0]; ds = OldInt1C[2]; \ Restore old Int 1Ch
    ax = 251Ch; !21h;
    ds=; popa; .=;                      \ Restore registers and return

\---------------------------------------------------------------------
\ StrLen -- Get length of string
\---------------------------------------------------------------------
\ DX = string, returns AX = length

StrLen:
    =dx =si;                            \ Save registers
    si = dx; &dx;                       \ SI = string, DX = 0
    {
        =*; al? ==;                     \ Get char, break if null
        dx+;                            \ Increment count
    }.;

    ax == dx;                           \ AX = length
    dx= si=; .=;                        \ Restore registers and return

\---------------------------------------------------------------------
\ StrLenF -- Get length of fat string
\---------------------------------------------------------------------
\ DX = string, returns AX = length

StrLenF:
    =bx =dx =si;                        \ Save registers
    si = dx; &dx; bx = O(BigChars);     \ SI = string, BX = fat char table
    {
        =*; al? ==;                     \ Get char, break if null
        dx+; al - 20h;                  \ Increment count, scale to table
        ><; al? --{ dx+; };             \ If 2-byte char, increment count
    }.;

    ax == dx;                           \ AX = length
    bx= dx= si=; .=;                    \ Restore registers and return

\---------------------------------------------------------------------

UDataSeg;                           \ Uninitialized data

FileName    db 100 dup(?);          \ Filename buffer

Buffer:                             \ Buffer for TETRIS.DAT

Font        db 3584 dup(?);         \ Font data

Pieces      db PIECE_DATA dup(?);   \ Piece data

SC_Score    dw 20 dup(?);           \ High-score data
SC_Lines    dw 10 dup(?);
SC_Round    dw 10 dup(?);
SC_Name     db 160 dup(?);

BufEnd:

End Start;
