Elevator and stairs (Inform 6 example)

From IFWiki

This was more complicated than I expected, but anyway...

Constant Story "Elevator and stairs example";
Constant Headline "^By David Welbourn for IFWiki^";
Release 1;

Constant MAX_SCORE = 0;
Constant DIALECT_US = 1;
Constant DEATH_MENTION_UNDO;

! This helps me figure out when to insert an extra newline before or after
! tricky paragraphs built out of the following messages:
!   The X lights up. The X blinks off. The elevator door opens/closes.
Global inPara = 0;

Include "Parser";
Include "VerbLib";

!------------------CLASSES----------------------
! Use classes to reduce duplicate code and make a cleaner design.

Class Room
with each_turn [;
    if (inPara == 1) { new_line; inPara = 0; }
  ],
has light;

Class ElevatorDoor
with name 'elevator' 'door',
  description [; "The elevator door is ", (string) self.openname(),
    ", and there's a call button beside it."; ],
  openname [; if (self has open) return "open"; else return "closed"; ],
  door_dir n_to,
  door_to Elevator,
  before [;
    Open:
      if (self has open) "The elevator door is already open.";
      if (parent(self) == Elevator) "Try pushing a button.";
      "Try pushing the call button.";
    Close:
      if (self has open) "The elevator door will close automatically when it needs to.";
      "The elevator door is already closed.";
  ],
  makeClosed [;
    if (self hasnt open) return; ! precaution
    give self ~open;
    if (location == parent(self)) {
      if (inPara == 0) new_line;
      inPara = 0; "The elevator door closes. ";
    }
  ],
  makeOpen [;
    if (self has open) return; ! precaution
    give self open;
    if (location == parent(self)) {
      if (inPara == 0) new_line;
      inPara = 0; "The elevator door opens. ";
    }
  ],
has door scenery;

Class TouchButton
with name 'button',
  floor 1,
  litname [; if (self has on) return "lit"; else return "dark"; ],
  before [;
    Push, Touch:
      if (self has on) print_ret (The) self, " is already lit.";
      if (Elevator.floor == self.floor) {
        if (door_e has open) {
          door_e.daemonFuse = door_e.daemonFuse + 1; "Nothing happens.";
        }
        door_e.openEvent(); rtrue;
      }
      self.makeOn();
      Elevator.floortarget = self.floor;
      StartDaemon(Elevator); rtrue;
  ],
  makeOff [;
    if (self hasnt on) rfalse; ! precaution
    give self ~on;
    if (location == parent(self)) {
      if (inPara == 0) new_line;
      print (The) self, " blinks off. "; inPara = 1;
    }
  ],
  makeOn [;
    if (self has on) rfalse; ! precaution
    ! tricky ordering: We say this one goes on, then turn them all off, then turn this on.
    if (location == parent(self)) { print (The) self, " lights up. "; inPara = 1; }
    AllButtonsOff();
    give self on;
  ],
has scenery;

Class CallButton class TouchButton
with name 'call' 'button',
  description [; "Pushing this button summons the elevator to this floor. It's currently ",
    (string) self.litname(), "."; ];

Class PanelButton class TouchButton
with description [; print_ret (The) self, " is ", (string) self.litname(), "."; ];

Class Stairs
with name 'stairs', article "a set of",
has scenery;

!--------------EXTRA FUNCTIONS---------------

[ AllButtonsOff ;
  call_button_g.makeOff(); call_button_2.makeOff(); call_button_3.makeOff();
  pbutton_g.makeOff(); pbutton_2.makeOff(); pbutton_3.makeOff();
];

[ LongClimbTo rm;
  print "It takes longer than you'd expect to use the stairs...^";
  PlayerTo(Midstairs, true); AdvanceClock(); AdvanceClock();
  return rm;
];

[ AdvanceClock;  ! borrowed this code from timesys.h
  #ifdef InformLibrary;
    InformLibrary.End_Turn_Sequence();
  #ifnot;
    #ifdef EndTurnSequence;
      EndTurnSequence();
    #ifnot;
      Message fatalerror "[Can't advance the clock.]";
    #endif;
  #endif;
];

!-------------ROOMS AND OBJECTS----------------

Room Ground_Floor "Ground Floor"
with description [;
    "You are on the ground floor. The ", (string) door_g.openname(),
    " door to the elevator is north, and the stairs are up.";
  ],
  before [;
    TakeStairsUp: <<Go u_to>>;
  ],
  n_to door_g, in_to door_g, u_to [; return LongClimbTo(Second_Floor); ];

ElevatorDoor -> door_g "elevator door";
CallButton   -> call_button_g "call button";
Stairs       -> stairs_g "stairs" with description "They go up.",
  before [;
    Take: <<Go u_obj>>;
  ];

Room Second_Floor "Second Floor"
with description [;
   "You are on the second floor. The ", (string) door_2.openname(),
   " door to the elevator is north, and stairs go both up and down from here.";
  ],
  before [;
    TakeStairsUp:   <<Go u_to>>;
    TakeStairsDown: <<Go d_to>>;
  ],
  n_to door_2, in_to door_2,
  u_to [; return LongClimbTo(Third_Floor); ],
  d_to [; return LongClimbTo(Ground_Floor); ];

ElevatorDoor -> door_2 "elevator door";
CallButton   -> call_button_2 "call button" with floor 2;
Stairs       -> stairs_2 "stairs" with description "They go up and down.",
  before [;
    Take: "Do you want to take the stairs up, or take the stairs down?";
  ];

Room Third_Floor "Third Floor"
with description [;
    "You are on the third floor. The ", (string) door_3.openname(),
    " door to the elevator is north, or you can take the stairs down.";
  ],
  before [;
    TakeStairsDown: <<Go d_to>>;
  ],
  n_to door_3, in_to door_3, d_to [; return LongClimbTo(Second_Floor); ];

ElevatorDoor -> door_3 "elevator door";
CallButton   -> call_button_3 "call button" with floor 3;
Stairs       -> stairs_3 "stairs" with description "They go down.",
  before [;
    Take: <<Go d_obj>>;
  ];

Room Elevator "Elevator"
with description [;
    "You are inside the elevator. The door to the south is ", (string) door_e.openname(),
    ", and there's the usual panel of buttons beside it.";
  ],
  floor 1,  ! Which floor the elevator car is currently on.
  floortarget 1,  ! Which floor the elevator is trying to get to.
  daemon [;
    ! Either close the doors, move the car, or open the doors.
    if (door_e has open) door_e.closeEvent();
    else if (self.floor < self.floortarget) {
      self.floor = self.floor + 1;
      if (location == Elevator) {
        if (inPara == 1) { new_line; inPara = 0; }
        print "^You feel the elevator go up";
        if (self.floor == self.floortarget) print " and come to a stop";
        print ". The LED changes to "; led.readout(); ".";
      }
    }
    else if (self.floor > self.floortarget) {
      self.floor = self.floor - 1;
      if (location == Elevator) {
        if (inPara == 1) { new_line; inPara = 0; }
        print "^You feel the elevator go down";
        if (self.floor == self.floortarget) print " and come to a stop";
        print ". The LED changes to "; led.readout(); ".";
      }
    }
    else door_e.openEvent();
  ],
  s_to door_e, out_to door_e;

ElevatorDoor -> door_e "elevator door" 
with description [; "The elevator door is ", (string) self.openname(), "."; ],
  door_dir s_to,
  door_to [;
    switch (Elevator.floor) {
      1: return Ground_Floor;
      2: return Second_Floor;
      3: return Third_Floor;
    }
  ],
  openEvent [;
    if (self has open) return;  ! A precaution against opening an open door.
    switch (Elevator.floor) {
      1: call_button_g.makeOff(); pbutton_g.makeOff(); door_g.makeOpen(); 
      2: call_button_2.makeOff(); pbutton_2.makeOff(); door_2.makeOpen();
      3: call_button_3.makeOff(); pbutton_3.makeOff(); door_3.makeOpen();
    }
    self.makeOpen();
    StopDaemon(Elevator);
    self.daemonFuse = 3; StartDaemon(self);
  ],
  closeEvent [;
    if (self hasnt open) return; ! A precaution against closing a closed door.
    switch (Elevator.floor) {
      1: door_g.makeClosed();
      2: door_2.makeClosed();
      3: door_3.makeClosed();
    }
    self.makeClosed(); self.daemonFuse = 0; StopDaemon(self);
  ],
  daemonFuse 0,  ! how long until the doors auto-close?
  daemon [;  ! 
    self.daemonFuse = self.daemonFuse - 1;
    if (self.daemonFuse <= 0) self.closeEvent();
  ];

Object -> panel "panel of buttons"
with name "panel" "of" "buttons",
  description [; print "The panel has three buttons: G, 2, and 3.
    There is also a LED display that reads ~"; led.readout(); "~."; ],
has scenery;

PanelButton -> pbutton_g "G button" with name 'ground' 'g//';
PanelButton -> pbutton_2 "2 button" with name '2//' 'two', floor 2;
PanelButton -> pbutton_3 "3 button" with name '3//' 'three', floor 3;

Object -> led "LED display"
with name 'led' 'display',
  description [; print "The LED display is showing a large red "; self.readout(); "."; ],
  readout [;
    if (Elevator.floor == 1) print "G"; else print Elevator.floor;
  ],
has scenery;

! Midstairs is a temporary room to move the player to, so event messages
! won't reach him/her during the first two turns of the 3-turn movement.
Room midstairs "Midstairs";

[ Initialise;
  location = Ground_Floor;
  lookmode = 2; ! verbose mode
  score = 0;
];

[ TakeStairsUpSub;
  "There are no stairs going up here.";
];

[ TakeStairsDownSub;
  "There are no stairs going down here.";
];

Include "Grammar";

Extend only 'take' first
  * 'stairs' 'up'    -> TakeStairsUp
  * 'stairs' 'down'  -> TakeStairsDown;