!  This is not a game in the real sense.
!  This is only a demonstration of one way to implement an
!  AutoMap Feature in Inform.

!  Feel free to copy and paste large portions of this code into
!  your game.  Feel free to translate this demonstration for
!  Inform 6 or for other versions of Inform.  Feel free to
!  add the name of the translator to the Copyright line in
!  that case, as well as additional comments.

!  Feel free to just improve this demonstration, tagging on
!  "Edited by (whoever)" in an appropriate place.

!  Feel free to make a standard library out of it, too.
!  Feel free to post any of the above (including just this
!    source code) to the if-archive.

!  Basically, consider this to be pretty much public domain.

!  The original author may be contacted via [email protected]
!  or the following address:
!         Nathan Eady
!         307 Gill Ave
!         Galion OH  44833
!         United States of America



Switches pv5rsxz;         !  Everyday use.

Constant Story "^Automatic Mapping";
Constant Headline "^A Somewhat Interactive Demonstration.^\
            Copyright (c) 1998 by Belly Laugh Software and Nathan Eady.^";
Release 2;  Serial "980717";


!              Constant DEBUG 1;
             Constant MAX_CARRIED 100;

Property area 0;

    Constant NO_AREA 0;
    Constant MAIN_AREA 1;
    Constant UPSTAIRS_AREA 2;

Constant MAX_MAP_X 5;
Constant MAX_MAP_Y 5;

Property MapNum; ! Holds the number of the room on the map.

Property MapDir;
    ! MapDir should contain, if anything, a routine, which should
    !    examine automapfeature.number for a direction property
    !    (*_to) and return
    !    true if the direction should be mapped as a travellable
    !    path, false if not, and -1 to say
    !    just check the property with ValueOrRun() to see
    !    if it goes anywhere.
    !    There is also the option to print any single character
    !    and then return -2 to so indicate.
    !    However, when doing this for up, down, in, or out,
    !    (which are all the same), it should be handled under
    !    in_to (which should print a character and return -2),
    !    and out_to should return -2,
    !    and u_to and d_to must return either 0 or -2.

include "parser";

[ MapLoc secnum x y;
    switch (secnum)
     {
         NO_AREA:  return -1;
         MAIN_AREA:
              switch (10*y + x)
                {
                   12:  return RoomOne;
                   21:  return RoomTwo;
                   22:  return junction;
                   23:  return EastOne;
                   24:  return RoomFive;
                   31:  return RoomFour;
                   32:  return SouthOne;
                   34:  return RoomEight;
                   42:  return SouthTwo;
                   43:  return RoomThree;
                   11, 41, 33:  return 0;
                   default:  return -1;
                  }
         UPSTAIRS_AREA:
              switch (10*y + x)
               {   11:  return RoomSeven;
                   12:  return RoomSix;
                   13:  return FiveTop;
                   default:  return -1;
                  }
         "Can't Happen:  MapLoc fell through!";
       }
    ];

!  Main Floor:
!
!       1
!       |
!       |
!  2 -- hallway-- 5*
!       a         |
!       l         |
!  4 -- l         8
!       w
!       a
!       y -- 3
!
!=========================================


Class boring
    with short_name "Boring, Empty Room",
         description "There's nothing to do here; the room only \
              exists to ~flesh out~ the map.",
         area MAIN_AREA,
    has  light;

Object junction "Junction"
    with description "Hallways lead east and south.  \
              There are rooms to the north and west.",
         n_to RoomOne,
         w_to RoomTwo,
         e_to EastOne,
         s_to SouthOne,
         area MAIN_AREA,
         MapNum [; print " h"; return -2; ],
         MapDir [;
              switch (automapfeature.number)
                {
                   n_to, w_to:  rtrue;
                   s_to, in_to: print "a"; return -2;
                   e_to:  print "l"; return -2;
                   out_to:  return -2;
                   default:  return 0;
                  }
              ],
         after [;
              MapInfo:
                   if (noun~=self) rfalse;
                   "(Where you started the ~game~.)";
              ],
    has  light;

Nearby sign "sign"
    with name "sign",
         description "~To see a map of the area nearby (so far as you \
              have explored it), type MAP at the prompt.~",
    has  static;

Object EastOne "East End of Hallway"
    with description "There is a room to the east.  The hallway leads west.",
         name "crystal" "staircase",
         e_to RoomFive,
         w_to junction,
         area MAIN_AREA,
         MapNum [; print "wa"; return -2; ],
         MapDir [;
              switch (automapfeature.number)
                {
                   w_to:     print "l"; return -2;
                   in_to:   print "y"; return -2;
                   out_to:   return -2;
                   e_to:     rtrue;
                   default:  rfalse;
                  }
              ],
    has  light;

Object RoomFive "Staircase Room (bottom)"
    with name "crystal" "staircase",
         description "A crystal staircase leads up from here.",
         w_to EastOne,
         u_to FiveTop,
         s_to RoomEight,
         area MAIN_AREA,
         MapNum 5,
         before [;
              MapInfo:
                   if (noun~=self) rfalse;
              ! I've put this in the before rule just to illustrate.
              ! The usual information isn't printed, only this:
                   "The runes on the map next to that location indicate \
                        a staircase.";
              ],
    has  light;

Object SouthOne "North-South Hallway"
    with description "There is a room to the west.",
         n_to junction,
         s_to SouthTwo,
         w_to RoomFour,
         MapNum [; print " l"; return -2; ],
         MapDir [;
              switch (automapfeature.number)
                {  w_to: rtrue;
                   n_to: print "l"; return -2;
                   s_to: print "w"; return -2;
                   return 0;
                  }
              ],
         area MAIN_AREA,
    has  light;

Object SouthTwo "South End of Hallway"
    with description "The hallway leads north, and there is a room \
              to the east.",
         n_to SouthOne,
         e_to RoomThree,
         MapNum [; print " y"; return -2; ],
         MapDir [;
              switch (automapfeature.number)
                {  e_to: rtrue;
                   n_to: print "a"; return -2;
                   return 0;
                  }
              ],
         area MAIN_AREA,
    has  light;


Object RoomOne "r"
    class boring,
    with  MapNum 1,
          s_to junction;

Object RoomTwo "r"
    class boring,
    with  MapNum 2,
          e_to junction;

Object RoomThree "r"
    class boring,
    with  MapNum 3,
          w_to SouthTwo;

Object RoomFour "r"
    class boring,
    with  MapNum 4,
          e_to SouthOne;

Object RoomEight "r"
    class boring,
    with  MapNum 8,
          n_to RoomFive;

!  Upstairs:
!
!  7 -- 6*-- 5*
!
!=========================================

Object FiveTop "Staircase Room (top)"
    with area UPSTAIRS_AREA,
         description "A crystal staircase leads down.",
         name "crystal" "staircase",
         d_to RoomFive,
         w_to RoomSix,
         MapNum 5,
         after [;
              MapInfo:
              if (noun~=self) rfalse;
              ! This appears in an after routine, so the usual
              ! information (room short name, but you could change
              ! MapInfoSub) is printed first, followed by this:
                   "There is also a symbol indicating a staircase.";
               ],
    has  light;

Object RoomSix "East-West Hallway"
    with area UPSTAIRS_AREA,
         description "Aren't this game's descriptions great?",
         in_to ClosetDoor,
         e_to FiveTop, w_to RoomSeven,
         MapNum 6,
    has  light;

Object ClosetDoor "closet door" RoomSix
    with name "closet" "door",
         door_dir in_to, door_to Closet,
    has  open enterable door;

Object Closet "Closet"
    with out_to RoomSix,
         description "It's a closet.";

Nearby thepoint "point"
    with name "point",
         description "Did you get the point yet?",
         each_turn [; if (score>0) deadflag = 2; ],
    has  scored;

Object RoomSeven "Lamp Room"
    with area UPSTAIRS_AREA,
         MapNum 7,
         e_to RoomSix,
         description "Everything glows brightly here.",
         after [; Go:  move lamp to player; ],
    has  light;

Object lamp "lamp"
    with name "lamp",
         description "It's a lamp.  (This game has a talent for stating \
                        the obvious, doesn't it?)",
    has  light;


! ---------------------------------------


[ Initialise;
    location = junction;
    give SouthOne visited;
    give SouthTwo visited;
    give EastOne visited;
    ];

include "verblib";


!------------------------------------------------ MapHead

[ MapHead horf a;
    switch (a)
      {
         NO_AREA:  if (horf==1)
                       { style bold;
                         print "^MAP OF UNMAPPED REGION:";
                         style roman;
                         "^";
                         }
                   "^(You are here.)^";
         MAIN_AREA:
              if (horf==1)
                   { style bold; print "MAP OF MAIN FLOOR:"; style roman; "^"; }
              "^Bold represents your location; An * indicates that \
                   non-compass (up, down, in, out) travel may be \
                   possible.  Type MAP INFO ## to look up additional \
                   information about room number ##.";
         UPSTAIRS_AREA:
              if (horf==1)
                   { style bold; print "MAP OF UPSTAIRS:"; style roman; "^"; }
              "^[This footer intentionally left blank.]^";
        }
    ];


[ AboutSub;
Print "This is not a game in the real sense.\
      ^This is only a demonstration of one way to implement an\
      ^AutoMap Feature in Inform.\
      ^\
      ^Feel free to copy and paste large portions of this code into\
      ^your game.  Feel free to translate this demonstration for\
      ^Inform 6 or for other versions of Inform.  Feel free to\
      ^add the name of the translator to the Copyright line in\
      ^that case, as well as additional comments.\
      ^";
Print "Feel free to just improve this demonstration, tagging on\
      ^~Edited by (whoever)~ in an appropriate place.\
      ^\
      ^Feel free to make a standard library out of it, too.\
      ^Feel free to post to the if-archive either this source \
      ^code or anything derived from it.\
      ^\
      ^Basically, consider this to be pretty much public domain.\
      ^\
      ^The original author may be contacted via jonadab@@64bright.net\
      ^or the following address:\
      ^       Nathan Eady\
      ^       307 Gill Ave\
      ^       Galion OH  44833\
      ^       United States of America";
Print "^^This is Release 1.  I plan to implement an ~information~ feature \
       to allow the user to look up any of the nodes on the map by \
       number and be told (at least) the short name of that location.  \
       This can be implemented with a fake action so that the location's \
       before (or after) rule can then trap it and provide any additional \
       information the author wants to give.  However, this is not \
       included in this first release, since I wanted to get some \
       feedback before implementing it, if possible.";
      ];


!------------------------------------------------ MapDirX

    ! MapDir should contain, if anything, a routine, which should
    !    examine automapfeature.number for a direction object (*_obj)
    !    and return
    !    true if the direction should be mapped as a travellable
    !    path, false if not, and -1 to say
    !    just check the property with ValueOrRun() to see
    !    if it goes anywhere.

[ MapDirX loc d dir answer;
    switch (d)
      {  w_obj:  dir=w_to;
         e_obj:  dir=e_to;
         n_obj:  dir=n_to;
         s_obj:  dir=s_to;
         u_obj:  dir=u_to;
         d_obj:  dir=d_to;
         se_obj:  dir=se_to;
         ne_obj:  dir=ne_to;
         sw_obj:  dir=sw_to;
         nw_obj:  dir=nw_to;
         in_obj:  dir=in_to;
         out_obj:  dir=out_to;
       }
    automapfeature.number = dir; ! MapDir routines read this value

    answer = -1; ! If there's no MapDir routine, it's the same
                 ! as if it returns -1.
    if (loc.#MapDir~=0)
      { ! The room provides a routine to tell us.
        answer = ValueOrRun(loc, MapDir);
        }
    if (answer~=-1) return answer; ! Thus, -2 will be returned.

       ! So if we get here the MapDir routine doesn't help.
       !  We'll have to examine the property manually.

       if (loc.#dir==0) return 0;

       if (loc.dir>0) return 1;  ! Note that this will cause
                                 !  serious run-time problems if
                                 !  that property has anything in it
                                 !  other than a value.
    ];

!------------------------------------------------ AutoMapSub

[ AutoMapSub a h v l n p;
    a = ValueOrRun(location, area);
    MapHead(1, a); ! Print Header
    if ( (0->33)<(5*MAX_MAP_X+1) ) ! Check the screen width
                                   ! (as the interpreter reports it
                                   !  in the header)
         print "[Some (or all) maps may be distorted by wrapping on narrow \
         displays.  Sorry.]";
    for (v=1:v<=MAX_MAP_Y:v++) ! VPass, the single vertical line-for-line pass.
      {
        ! First HPass: Upper Connectors
        for (h=1:h<=MAX_MAP_X:h++)
          {
            l = MapLoc (a, h, v);
            if ((h==1)&&(l~=-1)) print "^";
            if ((l~=0 or -1)&&(l has visited))
              {
                if (l==location) style bold; else style roman;
                p = MapDirX(l, nw_obj);
                if (p==1) print "@@92"; else { if (p~=-2) print " "; }
                print " ";
                p = MapDirX(l, n_obj);
                if (p==1) print "|"; else { if (p~=-2) print " "; }
                print " ";
                p = MapDirX(l, ne_obj);
                if (p==1) print "/"; else { if (p~=-2) print " "; }
                }
            else print "     ";
            }
         ! Second HPass:  Items & Horiz Connectors
        for (h=1:h<=MAX_MAP_X:h++)
          {
            l = MapLoc (a, h, v);
            if ((h==1)&&(l~=-1)) print "^";
            if ((l~=0 or -1)&&(l has visited))
              {
                if (l==location) style bold; else style roman;
                p = MapDirX(l, w_obj);
                if (p==1) print "-"; else { if (p~=-2) print " "; }
                n = ValueOrRun(l, MapNum);
                if (n~=-2)
                  {  if (n<10) print " "; ! MUST NOT BE > 99
                     ! (If you need room numbers from 100 to 999,
                     !  it wouldn't be hard to adjust for that,
                     !  but the depiction of each node would be
                     !  a character wider.)
                     print n;
                     }
                n = 0;
                if (MapDirX(l, u_obj)==1) n++;
                if (MapDirX(l, d_obj)==1) n++;
                if (MapDirX(l, in_obj)==1) n++;
                if (MapDirX(l, out_obj)==1) n++;
                if (n>0) print "*";
                    else { if (MapDirX(l, out_obj)==0) print " "; }
                p = MapDirX(l, e_obj);
                if (p==1) print "-"; else { if (p~=-2) print " "; }
                }
            else print "     ";
            }
         ! Third HPass:  Lower Connectors
        for (h=1:h<=MAX_MAP_X:h++)
          {
            l = MapLoc (a, h, v);
            if ((h==1)&&(l~=-1)) print "^";
            if ((l~=0 or -1)&&(l has visited))
              {
                if (l==location) style bold; else style roman;
                p = MapDirX(l, sw_obj);
                if (p==1) print "/"; else { if (p~=-2) print " "; }
                print " ";
                p = MapDirX(l, s_obj);
                if (p==1) print "|"; else { if (p~=-2) print " "; }
                print " ";
                p = MapDirX(l, se_obj);
                if (p==1) print "@@92"; else { if (p~=-2) print " "; }
                }
            else print "     ";
            }
        }
    style roman;
    MapHead(2, a); ! Print Footer
    ];

!------------------------------------------------ MapInfoSub

[ MapInfoSub;
   if (BeforeRoutines()==1) rfalse; ! we have to do this
                                    ! explicitly since AutoMapSub
                                    ! is on the same verb & we want
                                    ! that to be meta.  If your
                                    ! verb isn't meta, remove
                                    ! this call to BeforeRoutines.
   if (noun==0)
      "There's no such number on the map at the moment.";
   print "Map Lookup for Location ";
   Print ValueOrRun(noun, mapnum);
!    print " in area "; Print ValueOrRun(noun, area);
   print ":^";
   PrintShortName(noun); print "^";
!     ObviousExitsSub(noun); Diary of a Text Adventurer has
!              a utility to list the obvious exits of a
!              room, which I haven't included here.
   if (AfterRoutines()==1) rfalse;
    ! So you can trap MapInfo in the after routine for
    !    any room and provide additional information.
   "There is no further information for this location.";
   ];

!------------------------------------------------ MapInfoNumber

[ MapInfoNumber a n v h q t; ! A number-parsing routine for
                            !  MapInfoSub's command line.

   a = ValueOrRun(location, area);
   n = TryNumber(wn);

   if (n<=0) rfalse;

   ! if (n > 0) We have something.

   wn++;

   v = 1; h = 1;
   while ((q==0)&&(v<=MAX_MAP_Y))
     {
       t = MapLoc(a, h, v);
       ! the following line was incorrect:
       ! if ((ValueOrRun(t, mapnum)==n)&&(t has visited)) q = t;
       ! Because it runs every MapNum routine on the map.
       ! Instead:
       if ((t>selfobj)     ! else t isn't a reasonable object
          &&(t.#mapnum==2) ! else it's not a value or routine
          &&((t.&mapnum)-->0>0)     ! else it's likely a routine
          &&((t.&mapnum)-->0<100)   ! else it's likely a routine
          &&((t.&mapnum)-->0==n)    ! room object we want
          &&(t has visited)) q = t; ! only on map if visited.
#IFDEF DEBUG;
    DefArt(t);
    print ":^ValueOrRun(object,mapnum):  ";
    print ValueOrRun(t, mapnum);
    print "^object.#mapnum:  ";
    print t.#mapnum;
    print "^(object.&mapnum)-->0:  ";
    print (t.&mapnum)-->0;
#ENDIF;
       h++;
       if (h>MAX_MAP_X) { h = 1; v++; }
       }

   return q;
 ];

!------------------------------------------------ MapScope

object automapfeature "map"
    with name "map" "automap",
         number 0; ! used instead of a global to hold
                   ! the direction requested of MapDir
                   ! routines.

[ MapScope; PlaceInScope(automapfeature); ];

!------------------------------------------------      grammar lines

include "grammar";

! You may not want this to be meta in your game --
! That's a debatable matter.
! I've made it meta here because that's how it's
! going to by in Diary of a Text Adventurer, the
! game for which I wrote the mapping system.
! If your verb isn't meta, remove the call to
! BeforeRoutines from MapInfoSub, above.

Extend "examine"
    * scope=MapScope         -> AutoMap;

Extend "look"
    * "at" scope=MapScope    -> AutoMap;

verb meta "map" "automap" "whereami"
    *                        -> AutoMap
    * "info" MapInfoNumber   -> MapInfo
    * MapInfoNumber          -> MapInfo;

verb meta "help" "about" "hint" "hints"
    *                        -> About;

end;