program DeathMatch3D;
uses Crt, Gfx, KBHandler;

{
  Trig Ratios:

          opp           adj           opp                p  /|
    sin = ---     cos = ---     tan = ---              y  /  |
          hyp           hyp           adj            h  /    | opp
                                                      /      |
          hyp           adj           adj           /________|
    csc = ---     sec = ---     cot = ---          é    adj
          opp           hyp           opp
}

type
  tScreen = array[0..319, 0..199] of Byte;
  pScreen = ^tScreen;

const
  MapX = 12;
  MapY = 8;
  MaxDist = (MapX * MapX) + (MapY * MapY);
  MaxX : LongInt = MapX * 64;
  MaxY : LongInt = MapY * 64;
  Map : array[1..MapY, 1..MapX] of Byte = (
          (4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4),
          (4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4),
          (4, 0, 0, 0, 4, 4, 4, 4, 0, 4, 0, 4),
          (4, 0, 0, 4, 4, 0, 0, 4, 0, 0, 0, 4),
          (4, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 4),
          (4, 0, 0, 4, 0, 0, 0, 4, 4, 4, 4, 4),
          (4, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4),
          (4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4));
  ViewAngle = 60;
  HalfView = ViewAngle div 2;
  ScreenSeg = $a000;
  xDoor = 1;
  yDoor = 2;
  CharColors : array[0..7] of Byte = (15, 15, 15, 15, 15, 15, 15, 15);

var
  Precision : Word; { Read from Trig file }
  x, y, xLast, yLast : Word;
  Dir : Integer;
  DistToDraw : Word;
  SineTab, CoseTab, TanTab, CscTab, SecTab, AngleSteps : array[0..1919] of LongInt;
  StdAngles : array[0..359] of Word;
  ViewAdjust : array[0..319] of LongInt;
  BufferSeg : Word;
  DrawBuffer : pScreen;
  ViewPage, WritePage : Word;
  WallHeights : array[0..319] of Word;
  WallColumns : array[0..319] of Byte;
  Bitmaps : array[4..4] of array[0..63, 0..63] of Byte;
  ScaleFactor : Word;
  TimerTicks : LongInt absolute $0000:$046c;
  StartTime, EndTime : LongInt;
  MemCharSet : array[0..255] of array [0..7] of Byte;

procedure DrawMap;
var
  mx, my : Word;

begin
  DrawBox(9, 9, (MapX + 1) * 10, (MapY + 1) * 10, 1);
  for mx := 1 to MapX do begin
    for my := 1 to MapY do begin
      DrawBox(mx * 10, my * 10, mx * 10 + 9, my * 10 + 9, Map[my, mx]);
    end;
  end;
end;

procedure DoRayCast;
var
  x1, y1, x2, y2 : Integer;
  Wx, Wy, WDist : Word;
  RayAngle : Integer;
  i : Integer;
  WallDist, DrawDist : LongInt;
  Wall : Byte;
  yc : Word;
  DistColor : Byte;
  WallStart, WallEnd, WallPos : Word;
  WallColumn : Byte;
  RealStart : Word;
  ScreenPos : Word;
  Pixel : Byte;
  Angle : Integer;
  { Raycasting variables }
  xLook, yLook : LongInt;
  xInc, yInc : LongInt;
  xDist, yDist : LongInt;
  MapSeg, MapOfs : Word;
  xColumn, yColumn : Byte;
  DistEdge : Word;
  IllegalAngle : Boolean;

{
     This procedure is the heart of the engine.  The first incarnation of it
used bresenham's line drawing algorithm to trace a path through the map, and
report and walls encountered.  Since this was quite inefficient, a better way
had to be found.  The solution was this: walls always occur on a 64 unit
boundary.  Using this, we can skip the other 63 units in each square and go
straight to the next coordinate, once for x and once for y.  Even this has
some speed limitations, which were minimized (but not eliminted) in revision
.3.

       /|a     known: Xa, ab, a   (2 sides and a contained angle)
     /  |        2    2    2
   X    |       c  = a  + b  - 2ab cos C
     \  |
       \|b

List of Optimizations:

 .0 - Used Bresenham's algorithm
 .1 - Cast x and y rays every 64 units
 .2 - Converted to approx. 80% assembler
 .3 - Converted to approx. 99.9% assembler
 .4 - Moved RayCast to main body, eliminating extraneous "push" and "leave"
 .5 - Smoothed walls by using longints and reducing roundoff (small slowdown)
 .6 - Longint div turned to asm: signifigant speedup

}

begin
  RayAngle := Dir - HalfView;
  if RayAngle < 0 then Inc(RayAngle, 360);
  RayAngle := StdAngles[RayAngle];
  for i := 0 to 319 do begin
    asm
      { Inc(RayAngle) }
      inc RayAngle
      { if RayAngle = 1920 then RayAngle := 0 }
      cmp RayAngle, 1920
      jne @@1
      mov RayAngle, 0
      @@1:
      mov ax, RayAngle
      mov Angle, ax
    end;
    {
      Moved the casting code here in v1.4.  There's not much room for
      "pretty-looking" in the world of efficient code...  (besides, there was
      and extra push, mov, xor and leave that were wasting precious ticks!) :)
    }
{
    +------+
    |      / *  * First point to look for x wall
    |     /|
    |   ./_|     yofs := DistEdge * Tan ViewAngle
    +------+
}
    asm
      mov word ptr xDist.[0], $ffff
      mov word ptr xDist.[2], $7fff
      { Check for 90 and 270 degrees }
      cmp Angle, 480
      je @@8
      cmp Angle, 1440
      je @@8
      cmp Angle, 480
      jng @@1
      cmp Angle, 1440
      jnl @@1
      { xInc := -64 }
      mov word ptr xInc.[0], -64
      mov word ptr xInc.[2], $ffff
      { xLook := x and $ffc0 - 1 }
      mov ax, x
      and ax, $ffc0
      dec ax
      mov word ptr xLook.[0], ax
      mov word ptr xLook.[2], 0
      { yInc := -AngleSteps[Angle] }
      mov bx, Angle
      shl bx, 2
      mov ax, word ptr AngleSteps[bx]
      inc bx
      inc bx
      mov dx, word ptr AngleSteps[bx]
      neg ax
      adc dx, $00
      neg dx
      mov word ptr yInc.[0], ax
      mov word ptr yInc.[2], dx
      { DistEdge := x and 63 + 1 }
      mov ax, x
      and ax, 63
{     inc ax <- Not sure if it is needed or not }
      mov DistEdge, ax
      jmp @@2
      @@1:
      { xInc := 64 }
      mov word ptr xInc.[0], 64
      mov word ptr xInc.[2], 0
      { xLook := x and $ffc0 + 64 }
      mov ax, x
      and ax, $ffc0
      add ax, 64
      mov word ptr xLook.[0], ax
      mov word ptr xLook.[2], 0
      { yInc := AngleSteps[Angle] }
      mov bx, Angle
      shl bx, 2
      mov ax, word ptr AngleSteps[bx]
      inc bx
      inc bx
      mov dx, word ptr AngleSteps[bx]
      mov word ptr yInc.[0], ax
      mov word ptr yInc.[2], dx
      { DistEdge := 64 - (x and 63) }
      mov ax, x
      and ax, 63
      sub ax, 64
      neg ax
      mov DistEdge, ax
      @@2:
      { yLook := LongInt(y) shl Precision - TanTab[Angle] * DistEdge }
      push bx
      dec bx
      dec bx
      mov ax, word ptr TanTab[bx] { ax = lo word of TanTab[Angle] }
      mov bx, DistEdge { bx = DistEdge }
      mul bx
      mov word ptr yLook.[0], ax
      mov cx, dx
      xchg dx, bx
      pop bx
      mov ax, word ptr TanTab[bx]
      xchg dx, bx
      mul bx
      add ax, cx
      mov word ptr yLook.[2], ax
      { dx:ax = result, cx:bx = y }
      mov dx, word ptr yLook.[0]
      xchg ax, dx
      mov cx, y
      mov bx, 0
      cmp word ptr xInc.[0], 64
      je @@3
      sub bx, ax { lo order }
      sbb cx, dx { hi order, with borrow }
      jmp @@4
      @@3:
      add bx, ax { lo order }
      adc cx, dx { hi order, with carry }
      @@4:
      mov word ptr yLook.[0], bx
      mov word ptr yLook.[2], cx
      @@5:
      { while Map[...] <> 0 do }
      mov ax, word ptr yLook.[2]
      shr ax, 6
      mov bx, MapX
      mul bx
      mov bx, word ptr xLook
      shr bx, 6
      add bx, ax
      mov ah, byte ptr Map[bx]
      cmp ah, 0
      jne @@6
      { Inc(xLook, xInc) }
      mov ax, word ptr xInc.[0]
      mov dx, word ptr xInc.[2]
      add word ptr xLook.[0], ax
      adc word ptr xLook.[2], dx
      { Inc(yLook, yInc) }
      mov ax, word ptr yInc.[0]
      mov dx, word ptr yInc.[2]
      add word ptr yLook.[0], ax
      adc word ptr yLook.[2], dx
      jmp @@5
      @@6:
      cmp ah, yDoor
      jne @@NoDoor
      cmp word ptr xInc.[0], 64
      jne @@RightSide
      add word ptr xLook.[0], 32
      adc word ptr xLook.[2], 0
      jmp @@NoDoor
      @@RightSide:
      sub word ptr xLook.[0], 32
      sbb word ptr xLook.[2], 0
      @@NoDoor:
      { cos View = (xLook - x) / xDist }
      { xDist := ((LongInt(xLook) - x) * SecTab[Angle]) shr Precision }
      mov bx, Angle
      shl bx, 2
      mov ax, word ptr SecTab[bx] { ax = lo word of SecTab[Angle] }
      mov dx, word ptr SecTab[bx + 2] { dx = hi word of SecTab[Angle] }
      mov cx, word ptr xLook
      sub cx, x
      test cx, $8000
      je @@7
      neg ax { negative * negative = positive }
      inc dx
      neg dx
      inc cx
      neg cx
      @@7:
      push dx
      mul cx
      mov word ptr xDist.[0], ax
      mov word ptr xDist.[2], dx { dx = hi word of result }
      pop ax { ax = hi word of SecTab[Angle] }
      mul cx
      add word ptr xDist.[2], ax
      { xColumn := (yLook shr Precision) and 63 }
      mov ax, word ptr yLook.[2]
      and ax, 63
      mov xColumn, al
      @@8:
    end;
{        *
    +-+--/-+
    | | /  |   * First point to look for y wall
    | ./   |
    |      |     yofs := DistEdge * Tan ViewAngle
    +------+
}
    Angle := 480 - Angle;
    if Angle < 0 then Inc(Angle, 1920);
    asm
      mov word ptr yDist.[0], $ffff
      mov word ptr yDist.[2], $7fff
      { Check for 0 and 180 degrees }
      cmp Angle, 480
      je @@8
      cmp Angle, 1440
      je @@8
      cmp Angle, 480
      jng @@1
      cmp Angle, 1440
      jnl @@1
      { yInc := -64 }
      mov word ptr yInc.[0], -64
      mov word ptr yInc.[2], $ffff
      { yLook := y and $ffc0 - 1 }
      mov ax, y
      and ax, $ffc0
      dec ax
      mov word ptr yLook.[0], ax
      mov word ptr yLook.[2], 0
      { xInc := -AngleSteps[Angle] }
      mov bx, Angle
      shl bx, 2
      mov ax, word ptr AngleSteps[bx]
      inc bx
      inc bx
      mov dx, word ptr AngleSteps[bx]
      neg ax
      adc dx, $00
      neg dx
      mov word ptr xInc.[0], ax
      mov word ptr xInc.[2], dx
      { DistEdge := y and 63 + 1 }
      mov ax, y
      and ax, 63
      inc ax
      mov DistEdge, ax
      jmp @@2
      @@1:
      { yInc := 64 }
      mov word ptr yInc.[0], 64
      mov word ptr yInc.[2], 0
      { yLook := y and $ffc0 + 64 }
      mov ax, y
      and ax, $ffc0
      add ax, 64
      mov word ptr yLook.[0], ax
      mov word ptr yLook.[2], 0
      { xInc := AngleSteps[Angle] }
      mov bx, Angle
      shl bx, 2
      mov ax, word ptr AngleSteps[bx]
      inc bx
      inc bx
      mov dx, word ptr AngleSteps[bx]
      mov word ptr xInc.[0], ax
      mov word ptr xInc.[2], dx
      { DistEdge := 64 - (y and 63) }
      mov ax, y
      and ax, 63
      sub ax, 64
      neg ax
      mov DistEdge, ax
      @@2:
      { xLook := LongInt(x) shl Precision +/- TanTab[Angle] * DistEdge }
      push bx
      dec bx
      dec bx
      mov ax, word ptr TanTab[bx] { ax = lo byte of TanTab[Angle] }
      mov bx, DistEdge { bx = DistEdge }
      mul bx
      mov word ptr xLook.[0], ax
      mov cx, dx
      xchg dx, bx
      pop bx
      mov ax, word ptr TanTab[bx]
      xchg dx, bx
      mul bx
      add ax, cx
      mov word ptr xLook.[2], ax
      { dx:ax = result, cx:bx = x }
      mov dx, word ptr xLook.[0]
      xchg ax, dx
      mov cx, x
      mov bx, 0
      cmp word ptr yInc.[0], 64
      je @@3
      sub bx, ax { lo order }
      sbb cx, dx { hi order, with borrow }
      jmp @@4
      @@3:
      add bx, ax { lo order }
      adc cx, dx { hi order, with carry }
      @@4:
      mov word ptr xLook.[0], bx
      mov word ptr xLook.[2], cx
      mov ah, 0
      @@5:
      { while Map[...] <> 0 do }
      mov ax, word ptr yLook
      shr ax, 6
      mov bx, MapX
      mul bx
      mov bx, word ptr xLook.[2]
      shr bx, 6
      add bx, ax
      mov ah, byte ptr Map[bx]
      cmp ah, 0
      jne @@6
      { Inc(xLook, xInc) }
      mov ax, word ptr xInc.[0]
      mov dx, word ptr xInc.[2]
      add word ptr xLook.[0], ax
      adc word ptr xLook.[2], dx
      { Inc(yLook, yInc) }
      mov ax, word ptr yInc.[0]
      mov dx, word ptr yInc.[2]
      add word ptr yLook.[0], ax
      adc word ptr yLook.[2], dx
      jmp @@5
      @@6:
      cmp ah, xDoor
      jne @@NoDoor
      cmp word ptr yInc.[0], 64
      jne @@TopSide
      add word ptr xLook.[0], 32
      adc word ptr xLook.[2], 0
      jmp @@NoDoor
      @@TopSide:
      sub word ptr xLook.[0], 32
      sbb word ptr xLook.[2], 0
      @@NoDoor:
      { cos View = (yLook - y) / yDist }
      { yDist := ((LongInt(yLook) - y) * SecTab[Angle]) shr Precision }
      mov bx, Angle
      shl bx, 2
      mov ax, word ptr SecTab[bx] { ax = lo word of SecTab[Angle] }
      mov dx, word ptr SecTab[bx + 2] { dx = hi word of SecTab[Angle] }
      mov cx, word ptr yLook
      sub cx, y
      test cx, $8000
      je @@7
      neg ax { negative * negative = positive }
      inc dx
      neg dx
      inc cx
      neg cx
      @@7:
      push dx
      mul cx
      mov word ptr yDist.[0], ax { ax = lo word of result }
      mov word ptr yDist.[2], dx { dx = hi word of result }
      pop ax { ax = hi word of SecTab[Angle] }
      mul cx
      add word ptr yDist.[2], ax
      { yColumn := (xLook shr Precision) and 63 }
      mov ax, word ptr xLook.[2]
      and ax, 63
      mov bx, 63
      sub bx, ax { Fixes reversed wall bug }
      mov yColumn, bl
      @@8:
    end;

    { Check for negative distances }
    asm
      test word ptr yDist.[2], $8000
      je @@1
      mov word ptr yDist.[2], $7fff
      @@1:
      test word ptr xDist.[2], $8000
      je @@2
      mov word ptr xDist.[2], $7fff
      @@2:
    end;

    asm
      { if xDist > yDist then }
      mov ax, word ptr yDist.[2]
      cmp word ptr xDist.[2], ax
      jg @@1
      jne @@2
      mov ax, word ptr yDist.[0]
      cmp word ptr xDist.[0], ax
      jng @@2
      @@1:
      { Wall := 1 }
      mov Wall, 1
      { WallDist := yDist }
      mov ax, word ptr yDist.[0]
      mov dx, word ptr yDist.[2]
      mov word ptr WallDist.[0], ax
      mov word ptr WallDist.[2], dx
      { WallColumn := yColumn }
      mov al, yColumn
      mov byte ptr WallColumn, al
      jmp @@3
      { else }
      @@2:
      { Wall := 0 }
      mov Wall, 0
      { WallDist := xDist }
      mov ax, word ptr xDist.[0]
      mov dx, word ptr xDist.[2]
      mov word ptr WallDist.[0], ax
      mov word ptr WallDist.[2], dx
      { WallColumn := xColumn }
      mov al, xColumn
      mov byte ptr WallColumn, al
      @@3:
      { if WallDist = 0 then WallDist := 1 }
      cmp word ptr WallDist.[2], 0
      jne @@4
      mov word ptr WallDist.[0], $ffff
      mov word ptr WallDist.[2], 1
      @@4:
    end;

    { Serious slowdown in program: *MUST* optimize }
{    DrawDist := ((8000 * ViewAdjust[i]) div WallDist);}
    DrawDist := (8000 * ViewAdjust[i]) div WallDist;
    asm
      { Serious kludging here!  Must fix soon... }
{      mov ax, word ptr DrawDist.[0]
      mov dx, word ptr DrawDist.[2]
      mov bx, word ptr WallDist.[2]
      div bx}
(*      mov cx, word ptr DrawDist
      shr cx, 1
      cmp dx, cx  { Check for remainder > denominator / 2 }
      jng @@1
      inc ax { If so, round up }
      @@1:                      *)
      mov word ptr DrawDist, ax
    end;

    asm
      { WallColumns[i] := WallColumn }
      mov bx, i
      mov al, WallColumn
      mov byte ptr WallColumns[bx], al
      { WallHeights[i] := DrawDist }
      mov ax, word ptr DrawDist.[0]
      shl bx, 1
      mov word ptr WallHeights[bx], ax
    end;
  end;

{
  (* Uncomment for sound *after* calculations and *before* drawing *)
  Sound(200);
  Delay(10);
  Nosound;
}
  asm
    { FillChar(Mem[$a000 : 0], 51200, 0) }
    mov ax, $a000 {BufferSeg}
    mov es, ax
    sub di, di
    sub ax, ax
    mov cx, 25600
    rep stosw
  end;

  for i := 0 to 319 do begin
    asm
      { ScaleFactor := 16384 div (WallHeights[i] shl 1) }
      mov bx, i
      shl bx, 1
      mov dx, word ptr WallHeights.[bx]
      push dx
      mov bx, dx
      mov ax, 16384
      xor dx, dx
      shl bx, 1
      div bx
      mov ScaleFactor, ax
      pop dx
      { if WallHeights[i] < 89 then begin }
      cmp dx, 79
      jng @@1
      mov WallStart, 0
      mov WallEnd, 159
      mov bx, dx
      sub bx, 79
      mov ax, ScaleFactor
      mul bx
      mov RealStart, ax
      jmp @@2
      { end else begin }
      @@1:
      mov ax, dx
      mov bx, 80
      sub bx, ax
      mov WallStart, bx
      mov bx, 80
      add bx, ax
      mov WallEnd, bx
      mov RealStart, 0
      { end }
      @@2:
      { ScreenPos := WallStart * 320 + i }
      mov ax, WallStart
      mov bx, WallStart
      shl ax, 6
      shl bx, 8
      add ax, bx
      add ax, i
      mov ScreenPos, ax
      { Draw the wall }
      mov cx, WallEnd
      sub cx, WallStart
      @@3:
      { Pixel := Bitmaps[4][WallColumns[i], RealStart shr 8]; }
      mov bx, i
      xor ah, ah
      mov al, byte ptr WallColumns[bx]
      shl ax, 6
      xor bl, bl
      mov bh, 0 { 0 shl 4 }
      add bx, ax
      xor ah, ah
      mov al, byte ptr RealStart.[1]
      add bx, ax
      mov ah, byte ptr Bitmaps[bx]
      mov Pixel, ah
      { Mem[BufferSeg : ScreenPos] := Pixel }
      push ds
      mov ax, $a000 { BufferSeg }
      mov ds, ax
      mov ah, Pixel
      mov bx, ScreenPos
      mov byte ptr [bx], ah
      pop ds
      { Inc(RealStart, ScaleFactor) }
      mov ax, ScaleFactor
      add RealStart, ax
      { Inc(ScreenPos, 320) }
      add ScreenPos, 320
      { if RealStart > 16383 then RealStart := 16383 }
      cmp RealStart, 16383
      jng @@4
      mov RealStart, 16383
      @@4:
      loop @@3
    end;
  end;
  asm
    { Move(DrawBuffer^, Mem[$a000 : 0], 51200) }
{    push ds
    cld
    mov si, word ptr DrawBuffer.[0]
    mov ds, word ptr DrawBuffer.[2]
    mov ax, $a000
    mov es, ax
    mov di, 0
    mov cx, 25600
    rep movsw
    pop ds}
    { xLast := x }
    mov ax, x
    mov xLast, ax
    { yLast := y }
    mov ax, y
    mov yLast, ax
  end;
end;

function GetKey : Word;
var
  Ch : Char;
begin
  Ch := ReadKey;
  if Ch = #0 then begin
    Ch := ReadKey;
    GetKey := Byte(Ch) * 256;
  end else begin
    GetKey := Byte(Ch);
  end;
end;

procedure WriteTextXY(x, y : Word; Message : String);
var y1, x1 : Word;
    MemLoc : Word;
    cSeg, cOfs, nOfs : Word;
begin
  cSeg := Seg(MemCharSet);
  cOfs := Ofs(MemCharSet);
  for y1 := 0 to 7 do begin
    MemLoc := (y1 + y) * 320;
    for x1 := 0 to Length(Message) * 8 - 1 do begin
      if (MemCharSet[Byte(Message[x1 shr 3 + 1])][y1] shr (8 - (x1 and 7))) and 1 <> 0 then
        Mem[$a000 : MemLoc] := 15;
      Inc(MemLoc);
    end;
  end;
end; 

var
  Ch : Word;
  TrigFile, BitmapFile, CharSetFile : File;
  Dist : Word;

begin
  Assign(CharSetFile, 'CHARSET.DAT');
  Reset(CharSetFile, 1);
  BlockRead(CharSetFile, MemCharSet, SizeOf(MemCharSet));
  Close(CharSetFile);
  Assign(TrigFile, 'TRIG.DAT');
  Reset(TrigFile, 1);
  BlockRead(TrigFile, Precision, SizeOf(Precision));
  BlockRead(TrigFile, SineTab, SizeOf(SineTab));
  BlockRead(TrigFile, CoseTab, SizeOf(CoseTab));
  BlockRead(TrigFile, TanTab, SizeOf(TanTab));
  BlockRead(TrigFile, CscTab, SizeOf(CscTab));
  BlockRead(TrigFile, SecTab, SizeOf(SecTab));
  BlockRead(TrigFile, AngleSteps, SizeOf(AngleSteps));
  BlockRead(TrigFile, StdAngles, SizeOf(StdAngles));
  BlockRead(TrigFile, ViewAdjust, SizeOf(ViewAdjust));
  Close(TrigFile);

  MaxX := MaxX shl 16;
  MaxY := MaxY shl 16;
  DistToDraw := Round(Sqrt(MaxDist) * 64);

  x := 128;
  y := 233;
  Dir := 270;
  xLast := 0;
  yLast := 0;

  New(DrawBuffer);
  BufferSeg := Seg(DrawBuffer^);
  InitMode13h;
  WriteTextXY(0, 0, 'Hey all...  This is the semi-complete');
  WriteTextXY(0, 8, 'version of Death Match 3D.  Enjoy...');
  repeat until IsKey(1, false, false);
  LoadPalette('DM3D.PAL');
  Assign(BitmapFile, 'BITMAPS.DAT');
  Reset(BitmapFile, 1);
  BlockRead(BitmapFile, Bitmaps, SizeOf(Bitmaps));
  Close(BitmapFile);
{  DrawMap;
  ReadKey;}
  LoadImage('STATBAR.PCX');

  {$IFDEF SpeedTest}
  { Runs a speed test on the engine and reports the statistics }
  StartTime := TimerTicks;
  for y := 95 to 104 do DoRayCast;
  EndTime := TimerTicks;
  asm
    mov ax, $0003
    int $10
  end;
  WriteLn('Statistics:');
  WriteLn('');
  WriteLn('Time for 10 frames: ', (EndTime - StartTime) / 18.2 : 4 : 4, 's');
  WriteLn('Time per frame: ', ((EndTime - StartTime) / 10 / 18.2) : 4 : 4, 's');
  WriteLn('Frames per second: ', 1 / ((EndTime - StartTime) / 10 / 18.2) : 4 : 4);
  Halt;
  {$ENDIF}

  repeat
    DoRayCast;
    if IsKey(75, True, False) then begin
      Dec(Dir, 10);
      if Dir < 0 then Dir := Dir + 360;
    end;
    if IsKey(77, True, False) then begin
      Inc(Dir, 10);
      if Dir > 359 then Dir := Dir - 360;
    end;
    if IsKey(80, True, False) then begin
      x := x - Integer((CoseTab[StdAngles[Dir]] shl 4) shr Precision);
      y := y - Integer((SineTab[StdAngles[Dir]] shl 4) shr Precision);
      if x mod 64 = 32 then x := x - 1;
      if x mod 64 = 32 then y := y - 1;
    end;
    if IsKey(72, True, False) then begin
      x := x + Integer((CoseTab[StdAngles[Dir]] shl 4) shr Precision);
      y := y + Integer((SineTab[StdAngles[Dir]] shl 4) shr Precision);
      if x mod 64 = 32 then x := x + 1;
      if x mod 64 = 32 then y := y + 1;
    end;
    if IsKey(57, False, False) then begin
      Move(Mem[$a000 : 0], DrawBuffer^, 64000);
      asm
        mov ax, $0003
        int $10
      end;
      GotoXY(1, 1);
      WriteLn('Location: (', x, ', ', y, ')');
      WriteLn('Direction: ', Dir);
      WriteLn('AngleStep[Angle]: ', AngleSteps[StdAngles[Dir]]);
      repeat until not IsKey(57, False, False);
      asm
        mov ax, $0013
        int $10
      end;
      LoadPalette('DM3D.PAL');
      Move(DrawBuffer^, Mem[$a000 : 0], 64000);
    end;
    if Map[y shr 6 + 1, x shr 6 + 1] <> 0 then begin
      x := xLast;
      y := yLast;
    end;
    Mem[$b000:0] := 219;
  until IsKey(1, False, False);
  Dispose(DrawBuffer);
  asm
    { TextMode(3) }
    mov ax, $0003
    int $10
  end;
  RemoveHandler;
end.