BEGIN ! Parameters: ; INTEGER N_lifts = 3, Max_passengers_in_lift = 6, Bottom_floor = 0, Top_floor = 10, Main_entry_floor = 2; REAL door_open_time = 1.8, door_close_time = 2.2, passenger_enter_time = 1, passenger_leave_time = 1, lift_start_time = 1.6, lift_stop_time = 1.8, lift_pass_time = 2.8, passengers_per_hour = 120, passengers_per_rush_hour = 240; Simulation BEGIN ! Global variables: ; REF(Floor) ARRAY floors(Bottom_floor:Top_floor); REF(Lift) ARRAY lifts(1:N_lifts); REF(Direction) Up, Down, Unspecified; REF(OutFile) log_file; REAL Max_travel_time, Sum_travel_times; INTEGER N_passengers, N_travels, U, ix; ! Global procedures: ; PROCEDURE Log (message); TEXT message; BEGIN comment Make an entry in the time log. ; INSPECT log_file DO BEGIN INTEGER t, hours, mins, secs; t := Time; hours := t // 3600; mins := Mod(t,3600) // 60; secs := Mod(t,60); OutInt(hours, 2); OutChar(':'); IF mins < 10 THEN BEGIN OutChar('0'); OutInt(mins, 1); END ELSE BEGIN OutInt(mins, 2); END If; OutChar(':'); IF secs < 10 THEN BEGIN OutChar('0'); OutInt(secs, 1); END ELSE BEGIN OutInt(secs, 2); END If; OutText(": " & message); OutImage; END INSPECT; END PROCEDURE Log; TEXT PROCEDURE stripT (t); TEXT t; BEGIN comment Remove all leading and trailing spaces in t. ; IF t == NOTEXT OR ELSE t.Length=0 OR ELSE t.Strip.Length=0 THEN BEGIN stripT :- ""; END ELSE BEGIN t :- t.Strip; IF t.Sub(1,1) = " " THEN BEGIN stripT :- stripT(t.Sub(2,t.Length-1)); END ELSE BEGIN stripT :- t; END IF; END IF; END PROCEDURE stripT; TEXT PROCEDURE intT (v); INTEGER v; BEGIN comment Convert integer value to text. ; TEXT t; t :- Blanks(10); t.PutInt(v); intT :- stripT(t); END PROCEDURE intT; TEXT PROCEDURE realT (v); REAL v; BEGIN comment Convert real value to text. ; TEXT t; t :- Blanks(10); t.PutFix(v, 1); realT :- stripT(t); END PROCEDURE realT; ! class Lift ! ========== ; Process CLASS Lift (id); INTEGER id; BEGIN TEXT idT; REF(Head) passengers; REF(Direction) lift_dir; BOOLEAN door_open; BOOLEAN ARRAY selected_floors (Bottom_floor:Top_floor); INTEGER floor; comment Lift simulation description: The lift will run in an infinite loop going through these stages: 1. Passive: The lift is idle, and the doors are open. 2. Entering: Passengers are entering. 3. Starting: The doors are closing, and the lift has started moving. 4. Running: The lift is moving. 5. Stopping: The lift is slowing down, and the doors open. 6. Leaving: The passengers are leaving. ; PROCEDURE select_lift_direction; BEGIN comment Should the lift move up or down? ; IF NOT passengers.Empty THEN BEGIN ! If there already are passengers, we must keep our direction, or they will be very frustrated. ; END ELSE IF NOT floors(floor).up_queue.Empty AND NOT floors(floor).down_queue.Empty THEN BEGIN ! There are passengers wanting to go up as well as go down -- select one direction by random. Note! If draw is based on queue length, we might get starvation. ; lift_dir :- IF Draw(0.5,u) THEN Up ELSE Down; Log("Lift " & idT & " has randomly selected to go " & lift_dir.id & "."); END ELSE IF floors(floor).num_waiting > 0 THEN begin ! We change the direction according to passengers' request. ; lift_dir :- IF floors(floor).down_queue.Empty THEN Up ELSE Down; Log("Lift " & idT & " has selected to go " & lift_dir.id & "."); END ELSE IF lift_dir == Unspecified THEN BEGIN lift_dir :- IF any_requests_above THEN Up ELSE Down; Log("Lift " & idT & " is requested to go " & lift_dir.id & "."); END ELSE IF NOT any_further_requests THEN BEGIN ! A change of direction is required. ; lift_dir :- lift_dir.opposite; Log("Lift " & idT & " has changed direction to go " & lift_dir.id & "."); END ELSE BEGIN ! The lift continues in the current direction. ; END IF; END PROCEDURE select_lift_direction; BOOLEAN PROCEDURE anything_to_do; BEGIN comment Is there anything more this lift might do? ; anything_to_do := NOT passengers.Empty OR Total_passengers_waiting > 0; END PROCEDURE anything_to_do; BOOLEAN PROCEDURE any_further_requests; BEGIN comment Are there any further requests in the present direction? ; any_further_requests := lift_dir==Up AND any_requests_above OR lift_dir==Down AND any_requests_below; END PROCEDURE any_further_requests; BOOLEAN PROCEDURE any_requests_above; BEGIN comment Is there any reason while the lift should go up? ; INTEGER ix; FOR ix := floor+1 STEP 1 UNTIL Top_floor DO IF selected_floors(ix) OR floors(ix).num_waiting>0 THEN any_requests_above := TRUE; END PROCEDURE any_requests_above; BOOLEAN PROCEDURE any_requests_below; BEGIN comment Is there any reason while the lift should go down? ; INTEGER ix; FOR ix := floor-1 STEP -1 UNTIL Bottom_floor DO IF selected_floors(ix) OR floors(ix).num_waiting>0 THEN any_requests_below := TRUE; END PROCEDURE any_requests_below; PROCEDURE move_to_next_floor; BEGIN comment The lift moves to the next floor. ; floor := floor + (IF lift_dir==Up THEN 1 ELSE -1); END PROCEDURE move_to_next_floor; BOOLEAN PROCEDURE stop_on_this_floor; BEGIN comment Should the lift stop on THIS floor? ; IF selected_floors(floor) THEN BEGIN stop_on_this_floor := TRUE; END ELSE IF lift_dir==Up THEN BEGIN stop_on_this_floor := NOT floors(floor).up_queue.Empty OR NOT any_requests_above AND NOT floors(floor).down_queue.Empty; END ELSE BEGIN stop_on_this_floor := NOT floors(floor).down_queue.Empty OR NOT any_requests_below AND NOT floors(floor).up_queue.Empty; END IF; END PROCEDURE stop_on_this_floor; PROCEDURE let_passengers_enter; BEGIN comment Waiting passengers will enter the lift, but only if a) the lift is moving in the desired direction and b) there is enough room in the lift. ; REF(Head) q; q :- IF lift_dir==Up THEN floors(floor).up_queue ELSE floors(floor).down_queue; WHILE NOT q.Empty AND passengers.Cardinal < Max_passengers_in_lift DO BEGIN REF(Passenger) p; p :- q.First; p.Out; ACTIVATE p; Hold(passenger_enter_time); END WHILE; END PROCEDURE let_passengers_enter; PROCEDURE let_passengers_leave; BEGIN comment Let all passengers wanting to get to this floor, leave. ; REF(Passenger) p, p2; p :- passengers.First; WHILE p =/= NONE DO BEGIN p2 :- p.Suc; IF p.end_floor = floor THEN BEGIN p.Out; ACTIVATE p; Hold(passenger_leave_time); END IF; p :- p2; END WHILE; END PROCEDURE let_passengers_leave; TEXT PROCEDURE List_of_passengers; BEGIN comment FOR the log File: list all the passengers. ; REF(Passenger) p; INTEGER n, ix; TEXT res; n := passengers.Cardinal; p :- passengers.First; res :- ""; FOR ix := 1 STEP 1 UNTIL n DO BEGIN IF ix=n AND n>=2 THEN res :- res & " and " ELSE IF ix > 1 THEN res :- res & ", "; res :- res & p.idT; p :- p.Suc; END FOR; List_of_passengers :- Res; END PROCEDURE List_of_passengers; ! Initialize the Lift: ; idT :- intT(id); floor := Main_entry_floor; lift_dir :- Unspecified; door_open := TRUE; passengers :- NEW Head; ! Run the lift simulation: ; WHILE TRUE DO BEGIN comment Stage 1: Passive. The lift will passivate if nothing to do. It will be activated by arriving passengers. ; IF NOT anything_to_do THEN BEGIN Log("Lift " & idT & " is passivated on floor " & intT(floor) & "."); lift_dir :- Unspecified; Passivate; Log("Lift " & idT & " is activated."); END IF; comment Stage 2: Entering. Waiting passengers will enter the lift. ; select_lift_direction; let_passengers_enter; comment Stage 3: Starting. Close the doors and start moving. ; door_open := FALSE; Hold(door_close_time); Log("Lift " & idT & " starts from floor " & intT(floor) & " going " & lift_dir.id & (IF passengers.Empty THEN "; the lift is empty." ELSE "; " & List_of_passengers & " inside.")); Hold(lift_start_time); move_to_next_floor; comment Stage 4: Running. IF there is no reason to stop, continue. ; WHILE any_further_requests AND NOT stop_on_this_floor DO BEGIN Log("Lift " & idT & " passes floor " & intT(floor) & " going " & lift_dir.id & "."); Hold(lift_pass_time); move_to_next_floor; END WHILE; comment Stage 5: Stopping. The lift slows down and stops here. ; Hold(lift_stop_time); Log("Lift " & idT & " arrives at floor " & intT(floor) & "."); Hold(door_open_time); door_open := TRUE; comment Stage 6. Passengers bound for this floor leave. ; let_passengers_leave; selected_floors(floor) := FALSE; END WHILE; END CLASS Lift; ! Global procedures ! ================= ; REF(Lift) PROCEDURE find_closest_lift (act_floor); INTEGER act_floor; BEGIN comment Find the idle lift closest to the given floor. ; REF(Lift) best; INTEGER ix; FOR ix := 1 STEP 1 UNTIL N_lifts DO BEGIN IF lifts(ix).Idle AND (best == NONE OR ELSE Abs(lifts(ix).floor-act_floor) < Abs(best.floor-act_floor)) THEN best :- lifts(ix); END FOR; find_closest_lift :- best; END PROCEDURE find_closest_lift; REF(Lift) PROCEDURE find_a_lift (floor, wanted_dir); INTEGER floor; REF(Direction) wanted_dir; BEGIN comment Find a lift on the the given floor with its door open. Note! We know there will be one. ; REF(Lift) res; INTEGER ix; FOR ix := N_lifts STEP -1 UNTIL 1 DO BEGIN IF lifts(ix).floor = floor AND lifts(ix).door_open AND (lifts(ix).lift_dir == wanted_dir OR lifts(ix).lift_dir == Unspecified) THEN res :- lifts(ix); END FOR; IF res =/= NONE AND THEN res.lift_dir == Unspecified THEN res.lift_dir :- wanted_dir; find_a_lift :- res; END PROCEDURE find_a_lift; ! class Passenger ! =============== ; Process CLASS Passenger (start_floor, end_floor); INTEGER start_floor, end_floor; BEGIN TEXT idT; REF(Direction) passenger_dir; REAL start_time, end_time, travel_time; REF(Lift) selected_lift; comment Passenger simulation description: 1) The passenger activates an idle lift (IF required). 2) The passenger waits in the proper queue. 3) When a lift going in the desired direction arrives, the passenger enters. 4) When the wanted floor is reached, the passenger leaves. ; ! Initialize the passenger: ; N_passengers := N_passengers + 1; idT :- "#" & intT(N_passengers); start_time := Time; passenger_dir :- IF start_floor < end_floor then Up ELSE Down; Log("Passenger " & idT & " arrives on floor " & intT(start_floor) & " and wants to go " & passenger_dir.id & " to floor " & intT(end_floor) & "."); ! Run the simulation: ; ! Activate a lift. ; IF floors(start_floor).num_waiting = 0 THEN ACTIVATE find_closest_lift(start_floor) AFTER Current; ! Wait for a lift. ; Log("Passenger " & idT & " presses the " & passenger_dir.id & " button on floor " & intT(start_floor) & "."); IF passenger_dir == Up THEN Wait(floors(start_floor).up_queue) ELSE Wait(floors(start_floor).down_queue); ! Enter the lift: ; selected_lift :- find_a_lift(start_floor, passenger_dir); Log("Passenger " & idT & " enters lift " & selected_lift.idT & " and presses button " & intT(end_floor) & "."); selected_lift.selected_floors(end_floor) := TRUE; Wait(selected_lift.passengers); ! Leaves the lift: ; end_time := Time; travel_time := end_time - start_time; Sum_travel_times := Sum_travel_times + travel_time; N_travels := N_travels + 1; Max_travel_time := Max(Max_travel_time, travel_time); Log("Passenger " & idT & " leaves lift " & selected_lift.idT & " on floor " & intT(end_floor) & ". Travel time is " & realT(travel_time) & " s."); END class Passenger; CLASS Floor (id); INTEGER id; BEGIN INTEGER PROCEDURE num_waiting; BEGIN num_waiting := up_queue.Cardinal + down_queue.Cardinal; END PROCEDURE num_waiting; REF(Head) up_queue, down_queue; up_queue :- NEW Head; down_queue :- NEW Head; END class Floor; CLASS Direction (id); VALUE id; TEXT id; BEGIN REF(Direction) PROCEDURE opposite; BEGIN opposite :- IF THIS Direction==Up THEN Down ELSE IF THIS Direction==Down THEN Up ELSE Unspecified; END PROCEDURE opposite; END CLASS Direction; INTEGER PROCEDURE Total_passengers_waiting; BEGIN comment How many passengers are waiting to use the lift? ; INTEGER n, ix; FOR ix := Bottom_floor STEP 1 UNTIL Top_floor DO n := n + floors(ix).num_waiting; Total_passengers_waiting := n; END PROCEDURE Total_passengers_waiting; ! Initialize the global variables: ; Up :- NEW Direction("up"); Down :- NEW Direction("down"); Unspecified :- NEW Direction("unspecified"); log_file :- NEW OutFile("heistesting.log"); log_file.Open(Blanks(120)); U := 2*ClockTime+1; ! ... an odd random value, really. ; ! Generate the floors: ; FOR ix := Bottom_floor STEP 1 UNTIL Top_floor DO floors(ix) :- NEW Floor(ix); ! Generate the lifts: ; FOR ix := 1 STEP 1 UNTIL N_lifts DO BEGIN lifts(ix) :- NEW Lift(ix); ACTIVATE lifts(ix); END FOR; ! Generate the passengers: ; BEGIN REAL tx; INTEGER floor1, floor2; ! There is a steady flow of passengers all day between random floors. Nearly all want to use the elevator. ; tx := 8*60*60; WHILE tx <= 17*60*60 DO BEGIN tx := tx + NegExp(passengers_per_hour/3600,U); floor1 := RandInt(Bottom_floor,Top_floor,U); floor2 := RandInt(Bottom_floor,Top_floor,U); IF floor1 <> floor2 THEN ACTIVATE NEW Passenger(floor1,floor2) AT tx; END WHILE; ! Lots of extra passengers arrive in the morning rush hour 8-9. They all arrive on the main entry floor. ; tx := 8*60*60; WHILE tx <= 9*60*60 DO BEGIN tx := tx + NegExp(passengers_per_rush_hour/3600,U); floor2 := RandInt(Bottom_floor,Top_floor,U); IF floor2 <> Main_entry_floor THEN ACTIVATE NEW Passenger(Main_entry_floor,floor2) AT tx; END WHILE; ! Lots of extra passengers leave in the afternoon rush hour 16-17. They all want to travel to the main entry floor. ; tx := 16*60*60; WHILE tx <= 17*60*60 DO BEGIN tx := tx + NegExp(passengers_per_rush_hour/3600,U); floor1 := RandInt(Bottom_floor,Top_floor,U); IF floor1 <> Main_entry_floor THEN ACTIVATE NEW Passenger(floor1,Main_entry_floor) AT tx; END WHILE; END; ! Start the simulation at 8 o'clock: ; REACTIVATE Current AT 8 * 60 * 60; Log("Simulation started."); ! Terrminate the simulation at 6 o'clock: ; REACTIVATE Current AT 18 * 60 * 60; Log("Simulation terminated."); log_file.Close; OutText("Average travel time for " & intT(N_travels) & " passengers: "); OutFix(Sum_travel_times/Max(N_travels,1), 1, 0); OutText(" s."); OutText(" Max travel time is "); OutFix(Max_travel_time, 1, 0); OutText(" s."); OutImage; END Simulation; END.