! ---------------------------------------------------------------------------- !
!   CheckOut.h
!       original 1.0  May00 by Roger Firth ([email protected]) for Inform 6
!
! ---------------------------------------------------------------------------- !
!   Installation: add the line:
!
!       Include "CheckOut";
!
!   anywhere in your game AFTER the Include "Parser" statement.
!
! ---------------------------------------------------------------------------- !
!   Implements a debugging verb CHECKOUT which attempts to test all your rooms
!   for asymmetric and one-way paths, on the basis that these /may/ represent
!   errors in setting up the inter-room connections.
!
!   Given two rooms Beach and Cliffs, the path between them is 'symmetric' if
!   you can move (for example) NORTH from the Beach to get to the Cliffs, and
!   then SOUTH from the Cliffs to return to the Beach. An 'asymmetric' path
!   would be (for example) NORTH from the Beach to the Cliffs, while SOUTHEAST
!   or WEST or DOWN (but not SOUTH) would take you back. Asymmetric paths are
!   reported only where no symmetric path exists; if SOUTH, SOUTHEAST, WEST and
!   DOWN all lead from the Cliffs to the Beach, no output is generated.
!
!   A one-way path is (for example) NORTH from Beach to Cliffs, with no path
!   returning to the Beach.
!
!   All of this works by analysing the properties N_TO, NE_TO ... and DOOR_TO.
!   Inform allows any of these to be a routine, but CheckOut has no way of
!   determining what such a routine might return, and so treats it as though
!   no exit was defined. Therefore, be prepared for some misleading reports.
!
!   You can define a list of rooms to be skipped when analysing the paths;
!   for example, a maze might deliberately contain many asymmetric paths which
!   you don't wish to report on. Include a line like this immediately before
!   you Include the file (don't forget the zero at the end of the list):
!
!       Array SkipRoom --> room1 room2 ... roomN 0;
!
! ---------------------------------------------------------------------------- !
#ifdef DEBUG; message "Compiling CheckOut.h";

Constant ExitCount  12;

Array ExitProp -->  n_to ne_to e_to se_to u_to in_to
                   s_to sw_to w_to nw_to d_to out_to;

Array ExitName -->  "North" "NorthEast" "East" "SouthEast" "Up"   "In"
                   "South" "SouthWest" "West" "NorthWest" "Down" "Out";

#ifndef SkipRoom;
Array SkipRoom -->  1;
#endif;

[ CheckOutSub obj dest i k;
   objectloop(obj ofclass Object) {    ! Loop through all Objects.

       if ((obj <= InformLibrary) ||
           (obj == LibraryMessages) || ! Ignore system objects,
           (obj in 1) ||               ! those with a parent,
           (parent(obj)) ||            ! and those in SkipRoom,
           (OmitRoom(obj))) continue;  ! leaving (mostly) rooms.

       for (i=0 : i<ExitCount : i++) { ! Test all possible exit properties.

           dest = GetExit(obj, i);
           if (dest == 0) continue;    ! Not a (checkable) exit.
           if (dest == obj) { PrintExit(obj, i); print "itself^"; continue; }
           if (GetExit(dest, (i+(ExitCount/2))%ExitCount) == obj) continue;

           k = ScanExits(obj, dest); if (k < 0) continue;
           PrintExit(obj, i); print (name) dest, ": ";
           switch (k) {
               0:  print "no apparent return^";
               1:  print "one asymmetric return^";
               default: print (number) k, " asymmetric returns^";
               }

           } ! end_FOR

       } ! end_OBJECTLOOP
   ];


[ ScanExits obj dest j k;
   k = 0;
   for (j=0 : j<ExitCount : j++ )
       if (GetExit(dest, j) == obj)
           if (GetExit(obj, (j+(ExitCount/2))%ExitCount) == dest) return -1; else k++;
   return k;
   ];

[ GetExit obj i;
   switch (ExitType(obj, ExitProp-->i)) {
       0:  return 0;                       ! nowhere
       1:  return 0;                       ! routine
       2:  switch (ExitType(obj.(ExitProp-->i), door_to)) {
               0:  return 0;               ! door to nowhere
               1:  return 0;               ! door to routine
               2:  return 0;               ! door to door!
               3:  return ((obj.(ExitProp-->i)).door_to);  ! door to room
               }
       3:  return obj.(ExitProp-->i);      ! room
       }
   ];

[ ExitType obj prop;
   if (obj == 0 || prop == 0) return 0;
   if (obj.#prop > 2 || obj.prop == NULL) return 0;
   switch(metaclass(obj.prop)) {
       nothing, Class, String: return 0;
       Routine: return 1;
       Object: if (obj.prop has door) return 2; else return 3;
       }
   ];

[ PrintExit obj i;
   print (name) obj, "->", (string) ExitName-->i, "->";
   ];

[ OmitRoom obj i;
   i = 0;
   while (SkipRoom-->i) if (SkipRoom-->(i++) == obj) rtrue;
   rfalse;
   ];

Verb 'checkout'     *   -> CheckOut;

#endif;
! ---------------------------------------------------------------------------- !