! ---------------------------------------------------------------------------- !
!   CheckOut.h
!       original 1.0  May00 by Roger Firth (roger.firth@tesco.net) 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;
! ---------------------------------------------------------------------------- !
