Constant Story "Bank of Zork";
Constant Headline "^An Interactive Queueing Model^\
            Copyright (c) 1997 by David Wildstrom^";

Constant DEBUG;
Constant MANUAL_PRONOUNS;

Global CID=1;
Global SID=1;

Constant MaxTellers=31;
Constant MaxCustomers=150;

Global WaitTimes=0;
Global CustsServed=0;
Array ServerUse table 100;
Array ServerLive table 100;

Global rhetoricflag=0;

#IFDEF DEBUG;
Replace XPurloinSub;
Replace XAbstractSub;
#ENDIF;

Replace PronounNotice;


Include "Parser";
Include "VerbLib";

Class Customer(MaxCustomers)
with parse_name [i j k isnum;
       isnum=0; i=0;
       if (self has male) j='man'; else j='woman';
       for(::) {
         k=NextWord(); wn--;
         if (k=='customer' or 'client' or 'person' or j)
           i++;
         else if  (TryNumber(wn)==self.number)
          { i++; isnum=1;}
         else
           {wn++; return (i*isnum);}
         wn++;
       } ],
     before [; Make:<<MakeHelp>>;
             Attack:
               remove self;
               Customer.destroy(self);
               "The customer falls dead by your hand. Several GUE \
                custodial staff wander in and remove the body.";],
     short_name [; print "Customer ", self.number; rtrue;],
     desc_head,
     description [;
       if (self in Queue)
         print_ret (string) self.desc_head, "is standing in line.";
       else
         print_ret (string) self.desc_head,"is at ",(the) parent(self),"."; ],
     time 0,
     create [;
       self.number=CID; CID++; self.time=turns;
       if (random(2)==1) {
         give self ~female male ~neuter;
         self.desc_head="He "; }
       else {
         give self female ~male ~neuter;
         self.desc_head="She "; }
       move self to Queue;
       print_ret "Customer ",self.number," enters the Bank.";
     ],
     destroy [;
               PronounFlush(self);
     ],
     life [;
       Ask, Tell, Answer: "The customer pretends not to hear you."; ],
     orders [; "The customer pretends not to hear you."; ],
     number,
   has proper animate scenery concealed;

Class Teller(MaxTellers)
     with parse_name [i j isnum; isnum=0; i=0;
       for(::) {
         j=NextWord(); wn--;
         if (j=='teller' or 'server' or 'clerk' or 'man' or 'woman' or 'epicene' or
                'gnome' or 'zurich' or 'window' or 'of')
           i++;
         else if  (TryNumber(wn)==self.number)
           {i++; isnum=1;}
         else
           {wn++; return (i*isnum);}
         wn++;
       } ],
     short_name [; print "Teller ", self.number; rtrue;],
     before [i; Make:<<MakeHelp>>;
             Attack: remove self;
               print "The teller falls backwards and lands ungracefully \
                behind the window. Seconds later, a screen is \
                pulled across the window by an unseen hand.";
               if (child(self)~=nothing) {
                 i=child(self);
                 print " ",(The) i," leaves the bank disgusted.^";
                 remove i;
                 Customer.destroy(i);}
               else print "^";
               StopDaemon(self);
               Teller.destroy(self);
               return true;
     ],
     create [;
       StartDaemon(self);
       self.number=SID; SID++;
       move self to Bank;
     ],
     destroy [;
               PronounFlush(self);
    ],
    description [; print "The teller is a completely average epicene gnome of \
                  Zurich (FrobozzCo supply catalog #423126709)";
                  if (child(self)~=nothing)
                     print ", currently serving ",(the) child(self);
                  "."; ],
     daemon [i j;
       (ServerLive-->(self.number))++;
       if (child(self)~=nothing) (ServerUse-->(self.number))++;
       if ((child(self)~=nothing) && (random(Job_Timer.number)==1)) {
         print "Customer ",child(self).number," finishes his business and leaves the \
               Bank. Teller ",self.number," is now available.^";
         i=child(self);
         remove i;
         Customer.destroy(i);
         }
       if ((child(self)==nothing) && (child(Queue)~=nothing)) {
                 i=child(Queue);
                 while (sibling(i)~=nothing) i=sibling(i);
         j=turns-i.time;
         CustsServed++;
         WaitTimes=WaitTimes+j;
         print "Customer ",i.number," walks up to Teller
               ",self.number,", after waiting ",j," turns.^";
         move i to self; }
     ],
     life [;
       Ask, Tell, Answer: "The teller pretends not to hear you.";
     ],
     number,
     orders [; "The teller pretends not to hear you."; ],
   has proper male neuter female animate container open scenery concealed;

Object Bank "Bank of Zork"
 with name "door" "doors""carpet" "rug" "carpeting",
      description "Huge glass doors flank the southern exit of the awe-inspiring \
                   Bank of Zork. Plush red carpeting runs the full length of this tremendous hall \
                   up to the narrow, glass windows lining the northern end of the hall. From all \
                   around, although not emanating from a clear source, you hear the muted ringing \
                   of telephones and taps of typing.",
      before [; Go: if (noun==s_obj) {rhetoricflag=2; "Leaving so soon?";}
                     if (noun==n_obj) "The windows are locked and reinforced against burglars like \
                                       you.";
                 Exit: rhetoricflag=2; "Leaving so soon?";
                 Yes: if (rhetoricflag>0) "No, you're not. I'm the game and you're just the player. \
                                           So if I don't want you to leave, you don't leave. If you \
                                           don't like it, then just quit. But you're not leaving.";
                 No: if (rhetoricflag>0) "Good.";
      ],
      daemon [i;
       rhetoricflag--;
       if (random(Arrival_Timer.number)==1) {
         if (Customer.remaining()==0) "A customer almost enters the bank, but thinks better of \
                    it after seeing the length of the line.";
         i=Customer.create();}
      ],
 has light;

Object -> Queue "queue"
 with name "queue" "line" "posts" "post" "rope",
      initial [; if (children(self)==0) "An empty grid of posts and rope \
                                       indicates a queue.";
                 if (children(self)<5) "A queue contains a few people here.";
                 if (children(self)<10) "A queue contains several people here.";
                 "A queue contains many people here.";
               ],
      before [; Enter: "You aren't a customer.";
                Search: <<Examine self>>;
                ],
      description [j x;
        if (children(self)==0) "There are no customers in the queue.";
        if (children(self)==1) {
          print "The customer currently in the queue is numbered ",
                 child(self).number;
          ".";
        }
        print "The customers currently in the queue are numbered ";
        if (children(self)==2) {
          j=0;
          objectloop (x in self) {
            j++;
            if (j==1) print x.number," and ";
            else print x.number;
          }
          ".";
        }
        j=0;
          objectloop (x in self) {
            j++;
            if (j<children(self)-1) print x.number,", ";
            else if (j==children(self)-1) print x.number,", and ";
            else print x.number;
          }
          ".";
      ],
 has static open container;

Object -> Gen_Customer "customers"
 with name "customer" "client" "person" "man" "woman" "customers" "clients"
           "people" "men" "women",
      description [; <<Push self>>;],
      before [i j x; Make: print "With a crackle of eldritch energy...^";
                          if (Customer.remaining()==0) "A customer almost enters the bank, but thinks better of \
                             it after seeing the length of the line.";
                           i=Customer.create();
                           rtrue;
                     default:
        j=0;
        objectloop (x ofclass Customer) if (x.number~=0) j++;
        if (j==0) "There are no customers in the bank.";
        print "Please refer to customers by number. ";
        if (j==1) {
          print "The customer currently in the Bank is numbered ";
          objectloop (x ofclass Customer) {
            if (x.number~=0) print x.number;
          }
          ".";
        }
        if (j==2) {
          print "The customers currently in the Bank are numbered ";
          i=0;
          objectloop (x ofclass Customer) {
            if (x.number~=0) { i++;
              if (i==1) print x.number," and ";
              else print x.number; }
          }
          ".";
        }
        print "The customers currently in the Bank are numbered ";
        i=0;
          objectloop (x ofclass Customer) {
            if (x.number~=0) { i++;
              if (i<j-1) print x.number,", ";
              else if (i==j-1) print x.number,", and ";
              else print x.number;
            }}
          ".";
      ],
 has static scenery concealed pluralname;

Object -> Non_Customer "nonexistent customer"
 with parse_name [i k isnum;
       isnum=0; i=0;
       for(::) {
         k=NextWordStopped(); wn--;
         if (k=='customer' or 'client' or 'person' or 'man' or 'woman')
           i++;
         else if  ((k~=-1) && (TryNumber(wn)~=-1000))
          { i++; isnum=1;}
         else
           {wn++; return (i*isnum);}
         print k," ",i,"^";
         wn++;
       } ],
      before [; Make:<<MakeHelp>>;
                default: "That customer is not in the Bank.";
      ],
 has static scenery concealed;

Object -> Gen_Teller "tellers"
 with name "teller" "server" "clerk" "person" "man" "woman" "tellers" "servers"
           "window" "windows" "people" "men" "women" "gnomes" "gnome"
           "epicene" "of" "zurich",
      description [; <<Push self>>;],
      before [i j x; Make: print "With a crackle of eldritch energy...^";
                           if (Teller.remaining()==0) "A teller window tries to form out of a \
                            vortex in time and space, but doesn't quite materialize.";
                           x=Teller.create();
                           "A new teller window slides open.";
                     default:
        j=0;
        objectloop (x ofclass Teller) if (x.number~=0) j++;
        if (j==0) "There are no tellers in the bank.";
        print "Please refer to tellers by number. ";
        if (j==1) {
          print "The teller currently in the Bank is numbered ";
          objectloop (x ofclass Teller) {
            if (x.number~=0) print x.number;
          }
          ".";
        }
        if (j==2) {
          print "The tellers currently in the Bank are numbered ";
          i=0;
          objectloop (x ofclass Teller) {
            if (x.number~=0) { i++;
              if (i==1) print x.number," and ";
              else print x.number; }
          }
          ".";
        }
        print "The tellers currently in the Bank are numbered ";
        i=0;
          objectloop (x ofclass Teller) {
            if (x.number~=0) { i++;
              if (i<j-1) print x.number,", ";
              else if (i==j-1) print x.number,", and ";
              else print x.number;
            }}
          ".";
      ],
 has static scenery concealed pluralname;

Object -> Non_Teller "nonexistent teller"
     with parse_name [i j isnum; isnum=0; i=0;
       for(::) {
         j=NextWordStopped(); wn--;
         if (j=='teller' or 'server' or 'clerk' or 'man' or 'woman' or 'epicene' or
                'gnome' or 'zurich' or 'window' or 'of')
           i++;
         else if  ((j~=-1) && (TryNumber(wn)~=-1000))
           {i++; isnum=1;}
         else
           {wn++; return (i*isnum);}
         wn++;
       } ],
      before [; Make:<<MakeHelp>>;
                default: "That teller is not in the Bank.";
      ],
 has static scenery concealed;

Object -> Arrival_Timer "Arrival Timer"
 with name "arrival" "timer" "frequency" "control",
      number 2,
      before [;
        AdjustSetting: self.number=second;
                       if (second~=0)
                         print_ret "Arrivals are now every ",second," time steps.";
                       "Arrivals are disabled.";
        default: "The arrival timer can only be ~set~.";
      ],
 has static scenery concealed;

Object -> Job_Timer "Job Timer"
 with name "job" "timer" "frequency" "control" "length" "end",
      number 5,
      before [;
        AdjustSetting: self.number=second;
                       if (second~=0)
                         print_ret "Jobs now take, on average, ",second," time steps.";
                       "Jobs now take infinitely long.";
        default: "The job timer can only be ~set~.";
      ],
 has static scenery concealed;

Object notepad "notepad"
 with name "notepad" "pad" "note" "notes" "paper" "papers" "usage",
      description [i; print "The daily statistics are as follows:^";
                       font off;
                       print "Customers served:           ",CustsServed,"^";
   if (CustsServed~=0) print "Average customer wait time: ",WaitTimes/CustsServed,".",
                                                       ((WaitTimes*10)/CustsServed)%10,"^^";
                       for(i=1:i<=SID:i++) if ((ServerLive-->i)~=0) {
                         print "Teller ";
                         if ((SID>=10) && (i<10)) print " ";
                         print i, " utilization rate: ";
                         if (SID<10) print " ";
                         print ((ServerUse-->i)*100)/(ServerLive-->i),"%^";
                       }
                       font on;
                       "^This is another fine product of the Frobozz Magic Paper Company.";
      ];

[ChooseObjects obj code;
 if (code<2) { if (obj~=Queue) return 0; rfalse;}
 if (obj ofclass Customer or Teller) return 1;
 if ((obj==Gen_Teller) && (Teller.remaining()==MaxTellers)) return 1;
 if ((obj==Gen_Customer) && (Customer.remaining()==MaxCustomers)) return 1;
 if (obj==Non_Teller or Non_Customer) return 0;
 return 5;
];

[Initialise i;
 location=Bank;
 i=Teller.create();
 i=Teller.create();
 i=Teller.create();
 move notepad to player;
 StartDaemon(Bank);
];

! Some special hermaphrodite control.

[ PronounNotice obj x bm;

  if (obj == player) return;

  #ifdef EnglishNaturalLanguage;
  PronounOldEnglish();
  #endif;

  if (obj ofclass Teller)
    bm = $$111000000000;
  else if (obj==Non_Teller or Non_Customer)
    bm = 0;
  else
    bm = PowersOfTwo_TB-->(GetGNAOfObject(obj));
  #ifdef DEBUG;
  if (parser_trace>=1)
    print "[Pronoun Notice Bitmask ",bm,"]^";
  #endif;

  for (x = 1 : x <= LanguagePronouns-->0: x = x+3)
      if (bm & (LanguagePronouns-->(x+1)) ~= 0)
          LanguagePronouns-->(x+2) = obj;

  #ifdef EnglishNaturalLanguage;
  itobj  = PronounValue('it');  old_itobj  = itobj;
  himobj = PronounValue('him'); old_himobj = himobj;
  herobj = PronounValue('her'); old_herobj = herobj;
  #endif;
];

! And dynamic object control....

[PronounFlush obj x;
  for (x = 1 : x <= LanguagePronouns-->0: x = x+3)
    if ((LanguagePronouns-->(x+2))==obj)
      LanguagePronouns-->(x+2)=NULL;
  #ifdef EnglishNaturalLanguage;
  itobj  = PronounValue('it');  old_itobj  = itobj;
  himobj = PronounValue('him'); old_himobj = himobj;
  herobj = PronounValue('her'); old_herobj = herobj;
  #endif;
];

#IFDEF DEBUG;
[XPurloinSub;
"[The purloin command has been specifically disabled in this program.]";
];

[XAbstractSub;
"[The abstract command has been specifically disabled in this program.]";
];
#ENDIF;

Include "Grammar";

[AboutSub;
 "The Bank of Zork was born as a project for a high school modeling and simulation class. \
  This particular project was eventually completed in Pascal, due mostly to limitations on \
  floating-point representations in Inform. Since then, I have turned the queueing model core \
  (which was by far one of the simplest parts of the project) into a full-fledged parser-hacking \
  exercise. Some particularly interesting hacks are hermaphroditic tellers, numbered people \
  (a code snippet stolen shamelessly from Graham Nelson's ~Balances~), pronoun management for \
  disappearing objects, and a few other interesting exercises. This is my first full program in \
  Inform, so I'm glad it was such a learning experience. Below is a little more information on \
  specifics of this project.^^\
  VERBS OF NOTE:^\
  If you ~make~, ~summon~, or a few other synonyms, you can create tellers or customers. The same \
  is true, in reverse, of ~kill~, ~destroy~, and its ilk. You can also ~set~ the simulation \
  parameters, the job timer and arrival timer.^^\
  FEATURES (PATCHED BUGS):^\
  No more than 31 tellers or 150 customers can be active at once. If there is an arrival and/or \
  summoning to exceed this number, the program will reply in form.^^\
  BUGS:^\
  After about 367 active turns, the teller statistics on the notepad will become unreliable as \
  integers overflow . The same is true of the tenths digit of the customer wait time after \
  3678 turns. I don't really see any way to fix this bug without recreating integer arithmetic.^^\
  FEEDBACK:^\
  If you've found a particularly interesting bug or typo or strangeness of this program, or just \
  feel like telling me something, you can write me at dwildstr@@64mbhs.edu or dwildstr@@64mit.edu. I'm \
  not sure how long either of these accounts will be active: wait for release 2 for deatails.";
];

[MakeSub;
 "That isn't something you can create.";];

[MakeHelpSub;
 "You cannot make a numbered object. Please refer to objects in general \
  (i.e. ~make teller~ instead of ~make teller 4~).";];

[AdjustSettingSub;
 "That isn't an adjustable simulation parameter.";];

[AdjustHelpSub;
 "You can only set simulation parameters to numbers.";];

Verb meta "about" "help" "author" "info" "information"
  * special -> About;

Verb "create" "make" "summon" "construct" "build"
  * noun -> Make
  * noun number -> MakeHelp;

Extend "set" first
  * noun "to" number -> AdjustSetting
  * noun "to" special -> AdjustHelp;