/*
  Simple Tetris - game
  written in 1997 by Marko Kleine Berkenbusch
*/

?resize     TRUE
?use80386
?alignword  TRUE
?jumptomain NEAR

?include "VIDEO.H--"
?include "RANDOM.H--"
?include "KEYCODES.H--"
?include "SYSTEM.H--"
?include "VGA.H--"
?include "PORTS.H--"

?define PIT_CHANNEL_0   0x40
?define PIT_CHANNEL_2   0x42
?define PIT_CONTROL     0x43
?define PIT_FREQ    0x1234DD
?define TIMER_INT       0x08

?define PF_Width  12
?define PF_Height 22
?define BS_Width  11
?define BS_Height  9
?define Initialspeed 250

byte Tiles[7*16] = {  0, 0, 0, 0,
                      1, 1, 1, 1,
                      0, 0, 0, 0,
                      0, 0, 0, 0,

                      0, 0, 1, 0,
                      0, 1, 1, 0,
                      0, 1, 0, 0,
                      0, 0, 0, 0,

                      0, 1, 0, 0,
                      0, 1, 1, 0,
                      0, 0, 1, 0,
                      0, 0, 0, 0,

                      0, 1, 0, 0,
                      0, 1, 1, 0,
                      0, 1, 0, 0,
                      0, 0, 0, 0,

                      0, 0, 0, 0,
                      0, 1, 1, 0,
                      0, 1, 1, 0,
                      0, 0, 0, 0,

                      0, 0, 0, 0,
                      0, 1, 1, 1,
                      0, 1, 0, 0,
                      0, 0, 0, 0,

                      0, 0, 0, 0,
                      0, 1, 1, 1,
                      0, 0, 0, 1,
                      0, 0, 0, 0 } ;

byte Block[99] = { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
	           4, 9, 8, 7, 6, 5, 4, 3, 2, 1, 4,
                   4, 8, 7, 6, 5, 4, 3, 2, 1, 0, 4,
                   4, 8, 7, 6, 5, 4, 3, 2, 1, 0, 4,
                   4, 8, 7, 6, 5, 4, 3, 2, 1, 0, 4,
                   4, 8, 7, 6, 5, 4, 3, 2, 1, 0, 4,
                   4, 8, 7, 6, 5, 4, 3, 2, 1, 0, 4,
	           4, 9, 8, 7, 6, 5, 4, 3, 2, 1, 4,
                   4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 } ;

byte pal  = FROM "TETRIS.PAL" ;
byte logo = FROM "TETRLOGO.DAT" ;

byte PField[PF_Width*PF_Height], TempPF[PF_Width*PF_Height] ;

byte ActTile[16+1], NewTile[16+1] ;

int  TPos_x, TPos_y ;

byte up, down, left, right, space, enter, quit ;

word oldkeyboardhandle[2], oldtimerhandle[2] = {} ;

word Lines ;

word VScreen ;

long Time, speed, timercount, clockticks ;

interrupt timerhandle()
{
$ PUSH DS
$ PUSH AX
$ PUSH BX
 DS = CS ;
 Time++ ;
 clockticks += timercount ;
 IF (long clockticks >= 0x10000) {
  clockticks -= 0x10000 ;
  $ PUSHF
  $ CS:
  $ CALL FAR oldtimerhandle
 }
 EOI() ;
$ POP  BX
$ POP  AX
$ POP  DS
}

interrupt keyboardhandle()
{
$ PUSH DS
$ PUSH AX
$ PUSH BX
DS = CS ;
$ IN AL, 0x60
BL = TRUE ;
IF ( AL >= 128 )
    {AL -= 128;
    BL = FALSE;
    }
IF ( AL == s_left )
    left = BL;
ELSE IF ( AL == s_right )
    right = BL;
ELSE IF ( AL == s_up )
    up = BL;
ELSE IF ( AL == s_down )
    down = BL;
ELSE IF ( AL == s_space )
    space = BL;
ELSE IF ( AL == s_return )
    enter = BL ;
ELSE IF ( AL == s_esc )
    quit = TRUE ;
EATKEY();
EOI();
$ POP BX
$ POP AX
$ POP DS
}

void SetTimer (word HandlerSeg, HandlerOfs, Freq)
dword counter ;
{
 IF (word Freq > 0) counter = PIT_FREQ / Freq ;
	       ELSE counter = 0 ;
 timercount = counter ;
 OUTPORTB(0x34, , , PIT_CONTROL) ;
 OUTPORTB(counter, , , PIT_CHANNEL_0) ;
 counter = counter >> 8 ;
 OUTPORTB(counter, , , PIT_CHANNEL_0) ;
 OUTPORTB(0x90, , , PIT_CONTROL) ;
 GETINTVECT(#oldtimerhandle, TIMER_INT) ;
 clockticks = 0 ;
 SETINTVECT(, TIMER_INT, HandlerSeg, HandlerOfs) ;
}

void InitPField (word pfaddr)
{
 AX = PF_Width*PF_Height ; DI = AX ; BX = pfaddr ;
 loop (DI) DSBYTE[DI+BX-0x01] = 0 ;
 SI = PF_Width ;
 loop (SI) {
  AX = SI + pfaddr ; BX = AX ;
  DSBYTE[BX-0x01] = -1 ;
  DSBYTE[PF_Width*PF_Height-PF_Width+BX-0x01] = -1 ;
 }
 SI = PF_Height ;
 loop (SI) {
  AX = SI-1*PF_Width+pfaddr ; DI = AX ;
  DSBYTE[DI] = -1 ;
  DSBYTE[DI+PF_Width-0x01] = -1 ;
 }
}

void OutTextXY (word Str, xb, yb; byte c)
byte h ;
word x, y ;
{
 DI = Str ; FS = 0xF000 ; SI = 0xFA6E ;
 do {
  y = 8 ;
  loop (y) {
   AL = DSBYTE[DI] ; AH = 0 ; AX = AX << 3 + y - 1 ; BX = AX ;
   h = FSBYTE[SI+BX] ;
   x = 8 ;
$ PUSH DI
   loop (x) {
    AL = h & 1 ;
    IF (AL <> 0) putpixel19(xb+x-1, yb+y, byte c) ;
    h >>= 1 ;
   }
$ POP  DI
  }
  xb += 8 ; DI++ ;
 } while (DSBYTE[DI] <> 0) ;
}

void InitScreen ()
int i, x, y ;
{
 SETVIDEOMODE(0x13) ;
 SETVGADAC(0, , 768, , , #pal) ;
 FILLSCREEN19(0x01) ;
 i = 120 ;
 loop (i) {
  putpixel19(100+i, 7, 0x02) ; putpixel19(100+i, 8, 0x02) ;
  putpixel19(100+i, 9, 0x02) ; putpixel19(100+i, 10, 0x02) ;
  putpixel19(100+i, 191, 0x02) ; putpixel19(100+i, 192, 0x02) ;
  putpixel19(100+i, 193, 0x02) ; putpixel19(100+i, 194, 0x02) ;
 }
 i = 188 ;
 loop (i) {
  putpixel19(101, i+6, 0x02) ; putpixel19(102, i+6, 0x02) ;
  putpixel19(103, i+6, 0x02) ; putpixel19(104, i+6, 0x02) ; putpixel19(105, i+6, 0x02) ;
  putpixel19(216, i+6, 0x02) ; putpixel19(217, i+6, 0x02) ;
  putpixel19(218, i+6, 0x02) ; putpixel19(219, i+6, 0x02) ; putpixel19(220, i+6, 0x02) ;
 }
 OutTextXY("Next:", 245, 8, 4) ;
 OutTextXY("Lines:", 241, 110, 5) ;
 SI = 0 ; y = 47 ;
 loop (y) {
  x = 88 ;
  loop (x) {
   putpixel19(94-x, 70-y, byte logo[SI]) ;
   SI++ ;
  }
 }
 OutTextXY("von", 38, 96, 6) ;
 OutTextXY("Marko Kleine", 3, 122, 3) ;
 OutTextXY("Berkenbusch", 7, 134, 3) ;
}

void ClearBuffer ()
{
 $ MOV  AX, VScreen
 $ MOV  ES, AX
 $ MOV  CX, PF_Width*PF_Height*BS_Width*BS_Height
 $ SHR  CX, 1
 $ CLD
 $ MOV  DI, 0
 $ XOR  AX, AX
 $ REPZ
 $ STOSW
}

void InitAll ()
{
 InitScreen() ;
 VScreen = GETMEM(2000) ;
 ClearBuffer() ;
 @RANDOMIZE() ; @RAND() ;
 InitPField(#PField) ;
 left = FALSE ; up = FALSE ; down = FALSE ; right = FALSE ; space = FALSE ;
 quit = FALSE ; enter = FALSE ; Time = 0 ; TPos_x = 3 ; TPos_y = 1 ; Lines = 0 ;
 speed = Initialspeed ;
 SetTimer(CS, #timerhandle, 1000) ;
 GETINTVECT(#oldkeyboardhandle, KEYBOARD_INT) ;
 SETINTVECT(, KEYBOARD_INT, CS, #keyboardhandle) ;
}

void CleanUp ()
{
 SetTimer(oldtimerhandle[2], oldtimerhandle[0], 0) ;
 FREEMEM(VScreen) ;
 SETINTVECT(, KEYBOARD_INT, oldkeyboardhandle[2], oldkeyboardhandle[0]) ;
 SETVIDEOMODE(0x03) ;
}

void CopyBuffer ()
{
 $ MOV  DI, 3626
 $ MOV  SI, 0
 $ MOV  AX, VScreen
 $ PUSH DS
 $ MOV  DS, AX
 $ MOV  AX, 0xA000
 $ MOV  ES, AX
 $ MOV  BX, 180
L1:
 $ MOV  CX, 55
 $ REPZ
 $ MOVSW
 $ ADD  DI, 210
 $ DEC  BX
 $ JNZ  L1
 $ POP  DS
}

void DrawBlock (word x; word y; byte c)
{
 $ MOV  AX, VScreen
 $ MOV  ES, AX
 $ MOV  AX, y
 $ MOV  BX, 110
 $ MUL  BX
 $ ADD  AX, x
 $ MOV  DI, AX
 $ MOV  BX, 9
 $ MOV  SI, #Block
 $ CLD
L1:
 $ MOV  CX, 11
L2:
 $ MOV  AL, DSBYTE[SI]
 $ ADD  AL, c
 $ STOSB
 $ INC  SI
 $ DEC  CX
 $ JNZ  L2
 $ ADD  DI, 99
 $ DEC  BX
 $ JNZ  L1
}

void DrawPlayField ()
word i, h ;
byte c ;
{
 i = 200 ;
 loop (i) {
  h = i-1 / 10 + 1 * PF_Width ;
  h += i-1 % 10 + 1 ;
  BX = h ;
  c = PField[BX] ;
  IF (c <> 0) DrawBlock(i-1 % 10 * BS_Width, i-1 / 10 * BS_Height, byte c) ;
 }
}

void SetTile (word x, y; byte c)
int  cx, cy, h ;
byte hc ;
{
 cx = 4 ;
 loop (cx) {
  cy = 4 ;
  loop (cy) {
   AX = cy-1*4 + cx - 1 ; BX = AX ;
   hc = ActTile[BX] ;
   IF (byte hc <> 0) {
    h = cy+y-1 * PF_Width + cx + x - 1 ; BX = h ;
    IF (PField[BX] == 0) PField[BX] = c ;
   }
  }
 }
}

void DrawTile (word x, y; byte c)
int cx, cy;
byte hc ;
{
 cx = 4 ;
 loop (cx) {
  cy = 4 ;
  loop (cy) {
   AX = cy-1*4 + cx - 1 ; BX = AX ;
   hc = ActTile[BX] ;
   IF (byte hc <> 0) DrawBlock(cx-1*11+x, cy-1*9+y, byte c) ;
  }
 }
}

byte Move_possible (word x, y)
int  cx, cy, h ;
byte hb ;
{
 hb = TRUE ; cx = 4 ;
 loop (cx) {
  cy = 4 ;
  loop (cy) {
   AX = cy-1*4 + cx - 1 ; BX = AX ;
   IF (byte ActTile[BX] <> 0) {
    h = cy+y-1 * PF_Width + cx + x - 1 ; BX = h ;
    IF (PField[BX] <> 0) hb = FALSE ;
   }
  }
 }
 return(hb) ;
}

void CopyTile (word src, dest)
{
 SI = src ; DI = dest ;
 BX = 17 ;
 loop (BX) DSBYTE[DI+BX-0x01] = DSBYTE[SI+BX-0x01] ;
}

void CopyPF (word src, dest)
{
 SI = src ; DI = dest ;
 BX = PF_Width*PF_Height ;
 loop (BX) DSBYTE[DI+BX-0x01] = DSBYTE[SI+BX-0x01] ;
}

void RotateTile (word taddr)
byte buf[17], c ;
int  x, y ;
{
 CopyTile(taddr, #buf) ;
 SI = #buf ; DI = taddr ; x = 4 ;
 loop (x) {
  y = 4 ;
  loop (y) {
   AX = y-1 * 4 + x-1 ; BX = AX ;
   c = DSBYTE[SI+BX] ;
   AX = 4-x * 4 + y-1 ; BX = AX ;
   DSBYTE[DI+BX] = c ;
  }
 }
}

void CreateTile (word taddr)
{
 @RAND() ;
 $ MOV  BX, 7
 $ MUL  BX
 $ PUSH DX
 $ SHL  DX, 4
 $ ADD  DX, #Tiles
 $ PUSH DX
 $ PUSH taddr
 $ CALL CopyTile
 $ POP  DX
 $ MOV  BX, taddr
 $ MOV  DSBYTE[BX+16], DL
 @RAND() ;
 $ AND  AX, 0x03
 $ MOV  CX, AX
L1:
 $ PUSH CX
 $ PUSH taddr
 $ CALL RotateTile
 $ POP  CX
 $ DEC  CX
 $ JNS  L1
}

void DrawBlockScreen (word x; word y; byte c)
{
 $ MOV  AX, 0xA000
 $ MOV  ES, AX
 $ MOV  AX, y
 $ MOV  BX, 320
 $ MUL  BX
 $ ADD  AX, x
 $ MOV  DI, AX
 $ MOV  BX, 9
 $ MOV  SI, #Block
 $ CLD
L1:
 $ MOV  CX, 11
L2:
 $ MOV  AL, DSBYTE[SI]
 $ ADD  AL, c
 $ STOSB
 $ INC  SI
 $ DEC  CX
 $ JNZ  L2
 $ ADD  DI, 309
 $ DEC  BX
 $ JNZ  L1
}

void ShowNext (word xoff, yoff)
int  x, y ;
word h ;
{
 bar19(xoff, yoff, 44, 36, 1) ;
 x = 4 ;
 loop (x) {
  y = 4 ;
  loop (y) {
   h = y-1 << 2 + x - 1 ; BX = h ;
   IF (byte NewTile[BX] <> 0)
    DrawBlockScreen(x-1*11+xoff, y-1*9+yoff, byte NewTile[16]*10+32) ;
  }
 }
}

void ShowLines (word xoff, yoff)
byte str[8], start ;
{
 bar19(xoff-20, yoff, 40, 8, 1) ;
 WORDTODIGITS(Lines, #str) ;
 IF (Lines > 0) do { start++ ; AX = #str+start ; BX = AX ; } while (DSBYTE[BX] == '0') ;
                ELSE start = 4 ;
 OutTextXY(#str+start, start-5*4+xoff, yoff, 7) ;
 speed = -Lines*3 + Initialspeed ;
 IF (long speed <= 0) speed = 5 ;
}

byte CheckRows ()
int  x, y, cy ;
byte hb, hc, rc ;
word h ;
{
 rc = FALSE ; y = 20 ;
 loop (y) {
  hb = TRUE ; x = 10 ;
  loop (x) {
   h = y*PF_Width + x ; BX = h ;
   IF (byte PField[BX] == 0) hb = FALSE ;
  }
  IF (hb == TRUE) {
   Lines++ ; rc = TRUE ; Time = 0 ;
   do {} while (long Time < 70) ;
   x = 10 ;
   loop (x) {
    cy = y-1 ;
    loop (cy) {
     h = cy*PF_Width + x ; BX = h ; hc = PField[BX] ;
     h += PF_Width ; BX = h ; PField[BX] = hc ;
    }
   }
  }
 }
 return(rc) ;
}

void Game ()
byte Tiledown ;
long hy, hx ;
{
 do {
  CreateTile(#NewTile) ;
  do {
   CopyPF(#PField, #TempPF) ;
   CopyTile(#NewTile, #ActTile) ;
   CreateTile(#NewTile) ;
   ShowNext(235, 25) ;
   ShowLines(265, 130) ;
   TPos_x = 3 ; TPos_y = 1 ; Tiledown = FALSE ; hx = 0 ; Time = 0 ;
   do {
    CopyPF(#TempPF, #PField) ;
    IF (up == TRUE) {
     RotateTile(#ActTile) ; up = FALSE ;
     IF (Move_possible(TPos_x, TPos_y) == FALSE)
     {
      RotateTile(#ActTile) ; RotateTile(#ActTile) ; RotateTile(#ActTile) ;
     }
    }
    IF (left == TRUE)
     IF (Move_possible(TPos_x-1, TPos_y) == TRUE) {
      TPos_x-- ; hx = 10 ; left = FALSE ;
     }
    IF (right == TRUE)
     IF (Move_possible(TPos_x+1, TPos_y) == TRUE) {
      TPos_x++ ; hx = -10 ; right = FALSE ;
     }
    IF (down == TRUE) {
     IF (Move_possible(TPos_x, TPos_y+1) == FALSE) Tiledown = TRUE ;
     ELSE TPos_y++ ;
     Time = 0 ; down = FALSE ;
    }
    IF (space == TRUE) {
     do {
      IF (Move_possible(TPos_x, TPos_y+1) == TRUE) TPos_y++ ;
     } while (Move_possible(TPos_x, TPos_y+1) == TRUE) ;
     Tiledown = TRUE ; space = FALSE ;
    }
    IF (Time >= speed) {
     IF (Move_possible(TPos_x, TPos_y+1) == FALSE) Tiledown = TRUE ;
     ELSE TPos_y++ ; Time = 0 ;
    }
    IF (quit == TRUE) Tiledown = TRUE ;
    ClearBuffer() ;
    IF (long hx < 0) hx++ ;
    IF (long hx > 0) hx-- ;
    hy = Time*9 / speed - 9 ;
    DrawTile(TPos_x-1*11+hx, TPos_y-1*9+hy, byte ActTile[16]*10+32) ;
    DrawPlayField() ;
    CopyBuffer() ;
   } while (Tiledown == FALSE) ;
   SetTile(TPos_x, TPos_y, byte ActTile[16]*10+32) ;
   ClearBuffer() ; DrawPlayField() ; CopyBuffer() ;
   do {} while (CheckRows() == TRUE) ;
   IF (TPos_y == 1) { quit = TRUE ; Tiledown = FALSE ; }
  } while (quit == FALSE) ;
  bar19(120, 80, 81, 9, 2) ;
  OutTextXY("Game over!", 121, 80, 4) ;
  OutTextXY("<ESC> quit", 121, 96, 7) ;
  OutTextXY("ENTER restart", 109, 108, 7) ;
  quit = FALSE ;
  do {
   CL = 0 ;
   IF (enter == TRUE) CL = 1 ;
   IF (quit == TRUE) CL = 2 ;
  } while (CL == 0) ;
  IF (CL == 1) {
   quit = FALSE ; Lines = 0 ;
   InitPField(#PField) ;
  } ELSE quit = TRUE ;
 } while (quit == FALSE) ;
}

void main ()
{
 InitAll() ;
 Game() ;
 CleanUp() ;
}
