!---------------------------------------------------------------------------!
!                                                                           !
! This file is intended as an illustration of the rather cryptic line in    !
! the Designer's Manual about using the "number" property of the compass    !
! directions to code a maze.  This maze copies the "Alike_Maze" from        !
! Adventure (specifically, from Graham Nelson's Inform port thereof) and    !
! implements it with two rooms: Maze_Room and Maze_Dump, and three          !
! routines:  LoadMaze, SetMaze and MoveInMaze.                              !
!                                                                           !
! This method uses the "number" property to keep track of the location of   !
! objects in the maze, which has two major implications:  1. every object   !
! which the player can drop in the maze MUST have the number property       !
! defined; and 2. the number property cannot be used for anything else.     !
! The latter is avoidable by defining another property, say "maze_number,"  !
! and using it instead.                                                     !
!                                                                           !
! Although this method works fairly well, there are three caveats:          !
! 1. "Places" will list the entire maze as one room (in this case,          !
! "Standard Maze").  This may or may not be desirable.                      !
! 2. "Objects" will list objects dropped in the last room visited properly, !
! but anything in Maze_Dump will be listed as "lost."  This is probably     !
! correctable.                                                              !
! 3.  This method is NOT recommended if you have any NPCs who can wander    !
! around in the maze.  While you COULD keep track of each NPC's location    !
! with his or her "number" property, you'd have to code the movement rules  !
! completely differently.                                                   !
!                                                                           !
! I don't have a great deal of experience with Inform (and in real life I   !
! use COBOL), so there may be better and easier alternatives to some of     !
! this.  I welcome any and all comments, which you can e-mail to me at:     !
! [email protected].                                                            !
!                   -- Steven Howard, 6/4/1996                              !
!---------------------------------------------------------------------------!

Switches dxs;

Constant DEBUG;

Constant Story "The Famous One-Room Maze";
Constant Headline "^An Interactive Labyrinth ^\
       Copyright 1996 by Steven Howard, but freely usable.  ^";

Include "Parser";
Include "Verblib";

!---------------------------------------------------------------------------!
!  First, the routines:  LoadMaze, SetMaze and MoveInMaze                   !
!---------------------------------------------------------------------------!

[ LoadMaze i obj;
 for (obj = selfobj + 1: obj <= top_object: obj++)
     {if (obj in Maze_Room)
       {if (obj.&number == 0)
               print "*** ERROR: Can't move ", (the) obj, ". ***";
        else
               {obj.number = Maze_Room.number;
                move obj to Maze_Dump;};
        };
      if (obj in Maze_Dump && obj.number == i)
          move obj to Maze_Room;
     };
 Maze_Room.number = i;
 ];

!---------------------------------------------------------------------------!
!  LoadMaze is called before the player moves into a new room in the        !
!  maze.  It moves items currently on the floor to the holding area         !
!  Maze_Dump, using each object's "number" property to store its            !
!  "real location".  Meanwhile, it moves any objects in Maze_Dump whose     !
!  number property is equal to the new room number into Maze_Room.          !
!  Finally, it sets the number property of Maze_Room to the new value.      !
!---------------------------------------------------------------------------!

[ SetMaze ;
 n_obj.number=0;
 ne_obj.number=0;
 e_obj.number=0;
 se_obj.number=0;
 s_obj.number=0;
 sw_obj.number=0;
 w_obj.number=0;
 nw_obj.number=0;
 u_obj.number=0;
 d_obj.number=0;
 out_obj.number=0;
 switch (Maze_Room.number) {
              1: {n_obj.number = 1;
                  e_obj.number = 2;
                  s_obj.number = 4;
                  w_obj.number = 11;
                  u_obj.number = 100;};
              2: {e_obj.number = 4;
                  s_obj.number = 3;
                  w_obj.number = 1;};
              3: {n_obj.number = 22;
                  e_obj.number = 2;
                  s_obj.number = 6;
                  d_obj.number = 17;};
              4: {n_obj.number = 2;
                  e_obj.number = 15;
                  s_obj.number = 16;
                  w_obj.number = 1;
                  u_obj.number = 14;
                  d_obj.number = 14;};
              5: {e_obj.number = 6;
                  w_obj.number = 7;};
              6: {e_obj.number = 3;
                  s_obj.number = 8;
                  w_obj.number = 5;
                  d_obj.number = 7;};
              7: {e_obj.number = 8;
                  s_obj.number = 9;
                  w_obj.number = 5;
                  u_obj.number = 6;};
              8: {n_obj.number = 10;
                  e_obj.number = 7;
                  s_obj.number = 8;
                  w_obj.number = 6;
                  u_obj.number = 9;
                  d_obj.number = 24;};
              9: {n_obj.number = 8;
                  s_obj.number = 18;
                  w_obj.number = 7;};
             10: {n_obj.number = 10;
                  e_obj.number = 101;
                  w_obj.number = 8;
                  d_obj.number = 19;};
             11: {n_obj.number = 1;
                  e_obj.number = 21;
                  s_obj.number = 11;
                  w_obj.number = 11;};
             12: {e_obj.number = 13;
                  s_obj.number = 101;
                  w_obj.number = 23;};
             13: {n_obj.number = 101;
                  w_obj.number = 12;
                  nw_obj.number = 25;};
             14: {u_obj.number = 4;
                  d_obj.number = 4;};
             15: {w_obj.number = 4;
                  out_obj.number = 4;};
             16: {w_obj.number = 4;
                  out_obj.number = 4;};
             17: {u_obj.number = 3;
                  out_obj.number = 3;};
             18: {w_obj.number = 9;
                  out_obj.number = 9;};
             19: {u_obj.number = 10;
                  out_obj.number = 10;};
             20: {e_obj.number = 101;
                  out_obj.number = 101;};
             21: {w_obj.number = 11;
                  out_obj.number = 11;};
             22: {s_obj.number = 3;
                  out_obj.number = 3;};
             23: {e_obj.number = 12;
                  out_obj.number = 12;};
             24: {u_obj.number = 8;
                  out_obj.number = 8;};
             25: {se_obj.number = 13;
                  out_obj.number = 13;};
            };
 ];
!---------------------------------------------------------------------------!
!  SetMaze is called whenever the player enters a new room in the maze.     !
!  The number property for the compass directions follow a simple code:     !
!  0 means movement in this direction is not allowed; 1 - 99 means          !
!  movement in this direction leads into room 1 - 99 of the maze, as        !
!  appropriate; 100 or more means movement in this direction leads out      !
!  of the maze.  Obviously, a gigantic maze of more than 99 rooms would     !
!  require changing this scheme somewhat.                                   !
!---------------------------------------------------------------------------!

[ MoveInMaze ;
       if (noun.number == 0) return 0;
       if (noun.number == 100) return Starting_Room;
       if (noun.number == 101) return At_Brink_Of_Pit;
       return Maze_Room;
];

!---------------------------------------------------------------------------!
! MoveInMaze "translates" the numbers assigned by SetMaze.  Making it       !
! a separate routine like this saves typing.                                !
!---------------------------------------------------------------------------!


Object Starting_Room "Starting Room"
       with name "starting" "room" "cavern" "stone" "suspiciously"
           "twisty" "passage" "south",
       description "You are standing in a stone cavern.  A \
           suspiciously twisty passage leads south.",
       s_to Maze_Room,
       in_to Maze_Room,
       before [ ;
               Go:  if (noun == s_obj or in_obj
                               && Maze_Room.number ~= 1)
                       {LoadMaze(1);
                        rfalse;};
              ];

!---------------------------------------------------------------------------!
! An "ordinary" room just outside the maze.  The before rule makes sure the !
! player enters the right room in the maze.  It's not strictly necessary    !
! here, since the maze is defined with number = 1 and the only way to get   !
! here is from Room 1, but this situation is unusual.  At_Brink_of_Pit,     !
! below, is an example of why this type of rule is needed.                  !
!---------------------------------------------------------------------------!

!---------------------------------------------------------------------------!
! To help map the maze, we give the player a bunch of "things" to drop:     !
!---------------------------------------------------------------------------!

Nearby red_thing "red thing"
       with name "red" "thing",
       description "It's a red thing.  You wouldn't understand.",
       number 0;

Nearby blue_thing "blue thing"
       with name "blue" "thing",
       description "It's a blue thing.  You wouldn't understand.",
       number 0;

Nearby yellow_thing "yellow thing"
       with name "yellow" "thing",
       description "It's a yellow thing.  You wouldn't understand.",
       number 0;

Nearby green_thing "green thing"
       with name "green" "thing",
       description "It's a green thing.  You wouldn't understand.",
       number 0;

Nearby purple_thing "purple thing"
       with name "purple" "thing",
       description "It's a purple thing.  You wouldn't understand.",
       number 0;

Nearby orange_thing "orange thing"
       with name "orange" "thing", article "an",
       description "It's an orange thing.  You wouldn't understand.",
       number 0;

!---------------------------------------------------------------------------!
! And now, the maze itself:                                                 !
!---------------------------------------------------------------------------!

Object Maze_Room "Standard Maze"
       with name "maze" "passage" "passages" "little" "twisty"
               "standard" "dead" "end",
       description
               [;
               if (self.number < 15)
                     "You are in the standard maze of twisty little \
                     passages.";
               if (self.number == 25)
                     "This is the pirate's dead end.";
               "You have reached a dead end.";],
       short_name [ ;
               if (action == ##Places or ##Objects) rfalse;
               if (self.number < 15) {print "Standard Maze";
                       #IFDEF DEBUG; print " (", self.number, ")";#ENDIF;
                       rtrue};
               print "Dead End";
               #IFDEF DEBUG; print " (", self.number, ")";#ENDIF;
               rtrue;
               ],
       number 1,
       n_to   MoveInMaze,
       ne_to  MoveInMaze,
       e_to   MoveInMaze,
       se_to  MoveInMaze,
       s_to   MoveInMaze,
       sw_to  MoveInMaze,
       w_to   MoveInMaze,
       nw_to  MoveInMaze,
       u_to   MoveInMaze,
       d_to   MoveInMaze,
       out_to MoveInMaze,
       cant_go [; if (noun==out_obj && self.number < 15)
                       "Easier said than done.";
                  if (self.number > 14)
                       "You'll have to go back the way you came.";
                  "You can't go that way.";],
       before [ ;
               Go:   if (noun.number == 0 || noun.number > 99) rfalse;
                     if (self.number ~= noun.number)
                        LoadMaze (noun.number);
              ],
       after [ ;
               Go: SetMaze();
                   if (self has general) <<Look>>;
                   give self general;
             ];

!---------------------------------------------------------------------------!
! number holds the current room number in the maze.  Rooms 1 through 14     !
! represent the maze proper (i.e. those rooms whose description in the      !
! original is "You are in a maze of twisty little passages, all alike").    !
! Rooms 15 through 24 are the ordinary dead ends (i.e. those rooms whose    !
! description is "You have reached a dead end" and where "out" takes you    !
! to the previous room).  Room 25 is the pirate's dead end.                 !
!                                                                           !
! The before rule on "Go" calls "LoadMaze" if the player is moving          !
! within the maze.                                                          !
!                                                                           !
! Somewhat deceptively, moving within the maze also causes a "Look"         !
! action, making every room appear to have never been visited before.       !
! This could be improved by keeping track of which rooms have already       !
! been visited and only doing a <<Look>> in those which have not.           !
!---------------------------------------------------------------------------!

Object Maze_Dump "Maze Dump";

Nearby plaid_thing "plaid thing"
       with name "plaid" "thing",
       description "It's a plaid thing.  You wouldn't understand.",
       number 2;

Nearby treasure_chest "treasure chest"
       with name "strangely" "familiar" "treasure" "chest",
       initial "You have found the treasure chest!",
       description "It's strangely familiar . . .",
       number 25;

!---------------------------------------------------------------------------!
! Maze_Dump isn't connected to any other rooms in the game.  It's just a    !
! holding place for items dropped in the maze which aren't in the current   !
! room.                                                                     !
! There are two things left lying around in the maze.  A "plaid thing"      !
! has been dropped in the second room, and the pirate's chest is in its     !
! expected location.                                                        !
!---------------------------------------------------------------------------!

Object At_Brink_of_Pit "At Brink of Pit"
       with name "twisty" "passages" "passage" "maze" "guard" "rail",
       description "You are at the brink of a thirty-foot pit. \
               A newly-installed guard rail prevents climbing down.  \
               Those twisty passages continue off in all directions.",
       d_to "The guard rail prevents it.",
       w_to Maze_Room,
       n_to Maze_Room,
       e_to Maze_Room,
       s_to Maze_Room,
       before [ ;
               Go:  if (noun == s_obj && Maze_Room.number ~= 20)
                       {LoadMaze(20);
                        rfalse;};
                    if (noun == n_obj && Maze_Room.number ~= 12)
                       {LoadMaze (12);
                        rfalse;};
                    if (noun == e_obj && Maze_Room.number ~= 13)
                       {LoadMaze (13);
                        rfalse;};
                    if (noun == w_obj && Maze_Room.number ~= 10)
                       {LoadMaze (10);
                        rfalse;};
               ];

!---------------------------------------------------------------------------!
! This illustrates the use of the before rule on "Go" to handle the case    !
! where the player leaves the maze and later enters a different room.       !
!---------------------------------------------------------------------------!

Nearby pit "pit"
       with name "brink" "of" "pit",
       description "It's a long way down.  Luckily, the sturdy \
               guard rail prevents anyone from trying to climb down.",
       before [ ;
               Climb:  "The guard rail prevents it.";
              ],
       has scenery;

!---------------------------------------------------------------------------!
! In Adventure the pit is an exit.  Here, it's just window dressing.        !
!---------------------------------------------------------------------------!

Object glow_stick "glow stick"
       with name "glow" "stick" "luminous" "tube" "green" "fluid"
       "plastic" "of",
       description "A plastic tube of luminous green fluid.",
       number 0,
       has light;

!---------------------------------------------------------------------------!
! Of course, the player needs to see.  This also illustrates why it's       !
! necessary to actually move the player from Maze_Room to Maze_Room.  At    !
! first thought, it seems we could accomplish the same thing by calling     !
! LoadMaze, calling SetMaze, performing a Look action and returning true    !
! in the before routine for Maze_Room.  But that wouldn't handle the player !
! dropping the only light source in the maze.                               !
!---------------------------------------------------------------------------!

Include "Grammar";

[ Initialise;
       location = Starting_Room;
       move glow_stick to player;
       "^^^^^^^You've seen this maze before.  Now, go find that \
        pirate chest.^";

];

end;