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 }