Program Maze;                   { version 2.0  by Dave Cote }

   {


        Procedure & Function list

        Introduction
        Open_File       Open a new maze input file.
        Display_Maze    Decode input maze into line drawing chars & display.
        Rb_kbd.inc      Get a key from user
        Check_Move      Check move validity:  hitting a wall?
        Move_It         Move the piece from old to new location.
        Move_Piece      Decode movement commands.

   }

{$I RB_KBD.INC}         { Deciphers single keystroke inputs }

  Const
    Beep	= #7;		        { Ring the bell Homer! }
    ESC		= #27;
    M_Width     = 80;                   { Maze width in character positions }
                                        { Must be an even number }
    M_Depth     = 24;                   { Maze depth in lines }
    Namlgt	= 255;		        { Max char length of a file name }
    ShiftOut    = 14;                   { ^N to invoke char set G1 }
    ShiftIn     = 15;                   { ^O to invoke char set G0 }

  Type
    Filename	= String[Namlgt];
    MazeLine    = String[M_Width];

  Var
    Bumps       : Integer;              { Keep track of times bumped into wall}
    Endx        : Integer;              { Ending column position }
    Endy        : Integer;              { Ending line position }
    Hor_Flip    : Boolean;              { Flip maze mirror image style }
    In_Line     : MazeLine;
    Maze        : Array[0..M_Depth] of MazeLine;
    Maze_File   : Filename;             { input file contianing maze }
    Maze_Lun    : Text;                 { Maze file handle }
    Maze_Num    : Char;                 { Actually the letter designation }
    Moves       : Integer;              { Keep track of number of moves }
    NoMaze      : Boolean;              { Controls drawing of maze on screen }
    OK          : Boolean;
    Py          : Integer;              { Current player line position }
    Px          : Integer;              { Current player col position }
    Vert_Flip   : Boolean;              { Flip maze upside down }

{*****************************************************************************}
Procedure Introduction;
{*****************************************************************************}

  var Ch          : char;         { Character Pressed  }
      var F_Key   : F_Key_Type;   { Function Key Value }
      var Ctrl_Key,               {\                   }
      Shift_Key,                  { >  True If On      }
      Lock_Key    : boolean;      {/                   }

  Begin
    Writeln( ESC, '[2J', ESC,'[1;1H' );
    Writeln( ESC, '#6 Welcome to Maze ver. 2,  by  Dave Cote' );

    Writeln;
    Writeln;
    Writeln('           A  Maze1a                  M  Maze4a' );
    Writeln('           B  Maze1b                  N  Maze4b' );
    Writeln('           C  Maze1c                  O  Maze4c' );
    Writeln('           D  Maze1d                  P  Maze4d' );
    Writeln('           E  Maze2a                  Q  Maze5a' );
    Writeln('           F  Maze2b                  R  Maze5b' );
    Writeln('           G  Maze2c                  S  Maze5c' );
    Writeln('           H  Maze2d                  T  Maze5d' );
    Writeln('           I  Maze3a                  U  Maze6a' );
    Writeln('           J  Maze3b                  V  Maze6b' );
    Writeln('           K  Maze3c                  W  Maze6c' );
    Writeln('           L  Maze3d                  X  Maze6d' );

    Writeln;
    Write('      Select a Maze, (A through  X): ' );

    RB_Kbd (Ch, F_Key, Ctrl_Key, Shift_Key, Lock_Key);          

    Maze_File := 'Maze1.maz';
    Vert_Flip := False;
    Hor_Flip  := False;

    If (Ch >= 'a') and (Ch <= 'z') Then Ch := chr( ord(Ch) - 32);  { upcase }
    Maze_Num  := Ch;

    If ((Ch >= 'E') and (Ch <= 'H')) Then Maze_File := 'Maze2.maz';
    If ((Ch >= 'I') and (Ch <= 'L')) Then Maze_File := 'Maze3.maz';
    If ((Ch >= 'M') and (Ch <= 'P')) Then Maze_File := 'Maze4.maz';
    If ((Ch >= 'Q') and (Ch <= 'T')) Then Maze_File := 'Maze5.maz';
    If ((Ch >= 'U') and (Ch <= 'X')) Then Maze_File := 'Maze6.maz';

    Case ((ord(Ch) - ord('A')) mod 4) of
      1:  Vert_Flip := True;
      2:  Hor_Flip  := True;
      3:  Begin
            Vert_Flip := True;
            Hor_Flip  := True;
          End;
      End;

    Writeln;
    Write('      Do you want to play without drawing the maze (Y/N)? ');
    RB_Kbd (Ch, F_Key, Ctrl_Key, Shift_Key, Lock_Key);          
    If (Ch = 'Y') or (Ch = 'y') Then NoMaze := True;

  End;  { of Proc. Introduction }

{*****************************************************************************}
Function Open_File( Filenm:Filename; Var File_Lun:Text ):Boolean;
{*****************************************************************************}

  { Generic input-file open/check routine. Given a filename in string 'Filenm'
    try to open it, report failure if any, return logical name in 'File_Lun'.}

  Var   OK : Integer;

  Begin

        ASSIGN ( File_Lun, Filenm );
{$I-}
        RESET ( File_Lun );
{$I+};
        OK := IORESULT;
        IF (OK = 0)
           Then Open_File := True
           Else
		BEGIN
                  Open_File  := False;
		  Write('The input data file ',Filenm );
		  Writeln(' could not be opened. IORESULT=',OK);
		END;

  End;  { of Open_File Func. }


{*****************************************************************************}
Procedure Display_Maze;
{*****************************************************************************}


  { set P_Line & P_Column to the current player position where 'S' is found }

  Var
    Line        : Integer;
    Ch          : Char;
    Col, Col2   : Integer;
    N,S,E,W     : Boolean;

  Begin
    Clrscr;
    Writeln( ESC,'[2;1H', ESC,'[?8l' ); { place cursor, turnoff autorepeat }

    If Vert_Flip                        { Read input file into internal array }
      Then For Line := M_Depth-1 downto 1 do    { Upside down }
            Readln( Maze_Lun, Maze[Line] )
      Else For Line := 1 to M_Depth do          { Rightside up }
            Readln( Maze_Lun, Maze[Line] );

    If Hor_Flip                                 { Make mirror image }
      Then For Line := 1 to M_Depth do
           Begin
             Col2 := M_Width;
             For Col := 1 to M_Width div 2 do
                Begin
                  Ch := Maze[Line][Col];
                  Maze[Line][Col] := Maze[Line][Col2];
                  Maze[Line][Col2] := Ch;
                  Col2 := Col2 -1;
                End;
           End;

    For Col := 1 to M_Width do
      Begin
        Maze[0][Col] := ' ';            { Clear out line #0 }
        Maze[M_Depth][Col] := ' ';      { Clear out bottom line }
      End;

    For Line := 0 to M_Depth do
      Begin
        Maze[Line][1] := ' ';           { Clear out leftmost column }
        Maze[Line][M_Width] := ' ';     { Clear out rightmost column }
      End;

    { Convert '@' signs into appropriate line drawing characters }
    { at same time look for 'S'tart  and  'E'nd locations }

    For Line := 1 to M_Depth-1 do
      For Col := 2 to M_Width-1 do
        Begin
          Ch := Maze[ Line ][ Col ];
          If Ch <> ' ' Then                     { ignore spaces }
            Begin
              If (Ch = 'S') or (Ch = 's')           { 'S'tart ? }
                Then Begin   Px := Col;
                             Py := Line;
                             Maze[ Line ][ Col ] := ' ';
                     End
              Else If (Ch = 'E') or (Ch = 'e')      { 'E'nd ? }
                Then Begin   Endx := Col;
                             Endy := Line;
                             Maze[ Line ][ Col ] := ' ';
                     End
              Else                              { line segment, decode }
                Begin
                  N := Maze[ Line-1 ][ Col ] <> ' ';    { Check above pos }
                  S := Maze[ Line+1 ][ Col ] <> ' ';    { Check below pos }
                  E := Maze[ Line ][ Col+1 ] <> ' ';    { Check rightpos }
                  W := Maze[ Line ][ Col-1 ] <> ' ';    { Check left  pos }

                  If (N and S and E and W) Then Maze[Line][Col] := 'n'
                  Else If (N and S and E) Then Maze[Line][Col] := 't'
                  Else If (N and S and W) Then Maze[Line][Col] := 'u'
                  Else If (N and E and W) Then Maze[Line][Col] := 'v'
                  Else If (S and E and W) Then Maze[Line][Col] := 'w'
                  Else If (N and E) Then Maze[Line][Col] := 'm'
                  Else If (N and W) Then Maze[Line][Col] := 'j'
                  Else If (S and E) Then Maze[Line][Col] := 'l'
                  Else If (S and W) Then Maze[Line][Col] := 'k'
                  Else If (E or W) Then Maze[Line][Col] := 'q'
                  Else Maze[Line][Col] := 'x';

                End;
            End;        { of "If Ch <> ' '" }
        End;          { of "For Col ..." loop }

    Write( ESC, ')0', chr(ShiftOut) );  { invoke Line drawing set }
    If not NoMaze
       Then For Line := 1 to M_Depth-1 do       { write Maze out to screen }
              Writeln( Maze[ Line ] );

    Write( ESC,'[',Endy:1,';',Endx:1,'H', ESC,'[5ma', ESC,'[0m' );  { End }
    Write( ESC,'[',Py:1,';',Px:1,'H`' );        { Draw player's position }
    Write( ESC,'[',Py:1,';',Px:1,'H' );

  End;  { of Proc. Display_Maze }

{*****************************************************************************}
Function Check_Move( Newx, Newy : Integer ) : Boolean;
{*****************************************************************************}

  { Check that new location doesn't bump into a wall }

  Begin

    Check_Move := False;
    If      (Newx < 2) or (Newx > M_Width-1) Then      { out of bounds }
    Else If (Newy < 1) or (Newy > M_Depth-1) Then      { out of bounds }
    Else If Maze[Newy][Newx] = ' ' Then Check_Move := True;

  End;  { of Func Check_Move }

{*****************************************************************************}
Procedure Move_It( Var Px, Py : Integer; Newx, Newy : Integer;
                   Direction : F_Key_Type; Var Done : Boolean );
{*****************************************************************************}

  Var   N       : Integer;

  Begin
    Done := False;

    Write( ESC,'[',Py:1,';',Px:1,'H.' );     { drop breadcrumb on previous pos }
    Write( ESC,'[',Newy:1,';',Newx:1,'H`' );        { draw new position }
    Write( ESC,'[',Newy:1,';',Newx:1,'H' );        { Reset cursor }

    Px := Newx;
    Py := Newy;

    If (Px = Endx) and (Py = Endy)              { are we done ? }
       Then Begin
              Done := True;
              For N := 1 to 15 do Write(ESC,'[?5h', Beep, ESC,'[?5l');
            End
       Else Begin                               { stretch move }
              If (Direction = _UpArrow)
              Then Begin
                     If (Maze[Py-1][Px] = ' ') and (Py > 2) and
                       (Maze[Py][Px-1] <> ' ') and (Maze[Py][Px+1] <> ' ')
                       Then Move_It( Px, Py, Newx, Newy-1, Direction, Done );
                   End
              Else If (Direction = _DownArrow)
              Then Begin
                     If (Maze[Py+1][Px] = ' ') and (Py < M_Depth-1) and
                       (Maze[Py][Px-1] <> ' ') and (Maze[Py][Px+1] <> ' ')
                       Then Move_It( Px, Py, Newx, Newy+1, Direction, Done );
                   End
              Else If (Direction = _LeftArrow)
              Then Begin
                     If (Maze[Py][Px-1] = ' ') and (Px > 2) and
                       (Maze[Py-1][Px] <> ' ') and (Maze[Py+1][Px] <> ' ')
                       Then Move_It( Px, Py, Newx-1, Newy, Direction, Done );
                   End
              Else If (Direction = _RightArrow)
              Then Begin
                     If (Maze[Py][Px+1] = ' ') and (Px < M_Width-1) and
                       (Maze[Py-1][Px] <> ' ') and (Maze[Py+1][Px] <> ' ')
                       Then Move_It( Px, Py, Newx+1, Newy, Direction, Done );
                   End;

            End;
  End;  { Of Proc. Move_It }

{*****************************************************************************}
Procedure Move_Piece;
{*****************************************************************************}

  { Get arrow key inputs or <EXIT> and process these commands }
  { F_Key will equal _Exit, _UpArrow,  _DownArrow, _RightArrow, or _LeftArrow }

  var Ch          : char;         { Character Pressed  }
      var F_Key   : F_Key_Type;   { Function Key Value }
      var Ctrl_Key,               {\                   }
      Shift_Key,                  { >  True If On      }
      Lock_Key    : boolean;      {/                   }
      Done:     Boolean;
      Newx, Newy  : Integer;
      OK          : Boolean;

  Begin
    Done := False;

    Repeat
      Newx := Px;
      Newy := Py;

      RB_Kbd (Ch, F_Key, Ctrl_Key, Shift_Key, Lock_Key);          

      Case F_Key of
        _Exit:          Done := True;
        _UpArrow:       Newy := Newy -1;
        _DownArrow:     Newy := Newy +1;
        _RightArrow:    Newx := Newx +1;
        _LeftArrow:     Newx := Newx -1;
        end;

      If not done
        Then Begin
               Moves := Moves + 1;
               OK := Check_Move( Newx, Newy );
               If OK Then Move_It( Px, Py, Newx, Newy, F_Key, Done )
                     Else Begin
                            Write( Beep );
                            Bumps := Bumps + 1;
                          End;
             End;
    until Done;

  End;  { of Proc. Move_Piece }

{*****************************************************************************}
{                       Main body of Maze program                             }
{*****************************************************************************}

  Begin
  
    Bumps  := 0;        { How many times did we bump into a wall ? }
    Moves  := 0;        { How many moves have we made totally ? }
    NoMaze := False;

    Introduction;

    OK := Open_File( Maze_File, Maze_Lun );

    Display_Maze;

    Move_Piece;

    Write( ESC, ')B', chr(ShiftIn) );        { turn off Line drawing set }
    Write( ESC,'[24;1H', ESC,'[?8h' );    { Reset cursor, turn on autorepeat }
    Write( 'While playing maze ',Maze_Num );
    Write(' you''ve bumped into ',Bumps:3,' walls during ',Moves:3,' moves!');
  End.  { ye absolute end of program }