!  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 jonadab@bright.net
!  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;
