preamble
''
'' 				SIMFOOD
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''     This simulation models a simple fast food restaurant.  The restaurant
''     makes burgers, shakes, and fries (from raw materials).  Burgers are made 
''     from scratch by being sent through the broiler and put into the bun
''     steamer then later taken from the steamer and put together.
''     Fries are made by dropping a fry basket into the grease and upon being
''     cooked are dumped into the fry vat from where they will be scooped into
''     the fry boxes. Shakes are made when requested.  Resources include
''     knaves which rove between the broiler, meat catcher, sandwich boards,
''     shake machine and friers, and order takers which rove between the 
''     ordering area, the bagging area, and the pickup area.
''

processes include
    DUMM,
    BROIL.BURGER,	'' broils burger, puts in steamer
    MAKE.BURGER,	'' put vegtables and condoments on burger, put on rack
    MAKE.SHAKE,         '' make a shake and put in pickup area
    MAKE.FRY,           '' make a french fry and put on counter
    CALL.ORDERS,	'' bag burgers and hand to customer
    CUSTOMER,           '' places order with order taker
    GENERATOR and	'' generates customers
    STOP.SIM		'' invoked at end of simulation

    every TAKE.ORDER has
        a CUSTOMER.ORDER

    define CUSTOMER.ORDER as a pointer variable

resources 

    every ORDER.TAKER has	'' takes customer order, gives custormer food
        a ORDER.STATION, and
        a ORDER.TAKER.ICON

        define ORDER.STATION as an integer variable
        define ORDER.TAKER.ICON as a pointer variable

    every KNAVE has	        '' operates equipment to make food
        a KITCHEN.STATION, and	'' station knave is currently at
        a KNAVE.ICON

        define KITCHEN.STATION as a integer variable
        define KNAVE.ICON as a pointer variable

    every WORKSTATION has 
        a OCCUPIER,		'' knave or order taker that is occupying sta.
        a SIDE.OF.BROILER,	'' which side of the equipment is the station
        a XLOC,			'' x position in feet from ll corner of site
        a YLOC			'' y position of station

        define XLOC, YLOC as real variables
        define OCCUPIER as an integer variable monitored on the left
        define SIDE.OF.BROILER as an integer variable
            define .MIDDLE	to mean 0	'' not separeted by equipment
            define .RIGHT.SIDE 	to mean 1
            define .LEFT.SIDE 	to mean 2
    
temporary entities 

    every BURGER may belong to 
        a STEAMER,
        a HEAT.RACK		

    every SHAKE may belong to
        a SHAKE.COUNTER

    every FRY may belong to
        a FRY.COUNTER

    every ORDER has 
        a NUM.BURGERS,
        a NUM.SHAKES,
        a NUM.FRIES and
        a CUSTOMER.WAITING
        and may belong to an ORDER.RACK and a BAG.COUNTER 

        define NUM.BURGERS as an integer variable
        define NUM.SHAKES as an integer variable
        define NUM.FRIES as an integer variable
        define CUSTOMER.WAITING as a pointer variable

Dynamic graphic entities include ICON, CUSTOMER, BURGER, ORDER, SHAKE, FRY

the system owns
    the STEAMER,		'' holds unmade burgers and buns
    the HEAT.RACK,		'' holds completed burgers awaiting pick-up
    the SHAKE.COUNTER,	        '' holds completed shakes for pickup
    the FRY.COUNTER,            '' holds completed fries for pickup
    the BAG.COUNTER and		'' holds completed customer orders
    the ORDER.RACK 		'' holds uncompleted customer orders 

define STEAMER, HEAT.RACK, SHAKE.COUNTER, FRY.COUNTER and BAG.COUNTER 
    as lifo sets

'' global variables
define STOP.TIME as an integer variable  
define WAITING.TIME as a real variable
define BURGER.PRICE as a real variable
define SHAKE.PRICE as a real variable
define FRY.PRICE as a real variable
define BURGERS.SOLD as an integer variable 
define SHAKES.SOLD as an integer variable 
define FRIES.SOLD as an integer variable 
define TIMESCALE as an integer variable
define COMPRESS.TIME as an integer variable
define CLOCK.TIME as a double variable

'' statistic gathering 
accumulate 
    KNAVE.UTILIZATION as the average of N.X.KNAVE

accumulate 
    ORDER.TAKER.UTILIZATION as the average of N.X.ORDER.TAKER

accumulate 
    WORKSTATION.UTILIZATION as the average of N.X.WORKSTATION

accumulate 
    AVE.KNAVE.JOBS as the average, 
    MAX.KNAVE.JOBS as the maximum of N.Q.KNAVE

accumulate 
    AVE.ORDER.TAKER.JOBS as the average, 
    MAX.ORDER.TAKER.JOBS as the maximum of N.Q.ORDER.TAKER

accumulate 
    AVE.WORKSTATION.JOBS as the average, 
    MAX.WORKSTATION.JOBS as the maximum of N.Q.WORKSTATION

tally 
    TOTAL.CUSTOMERS as the number, 
    MEAN.WAITING.TIME as the average,
    MAX.WAITING.TIME as the maximum of WAITING.TIME

'' display variables
display variables include CLOCK.TIME

'' functions
define CHOICE.KNAVE as an integer function given 1 argument
define CHOICE.ORDER.TAKER as an integer function given 1 argument
define CHOICE.WORKSTATION as an integer function given 3 argument

'' codes for workstations
define .RBROILER	to mean 1
define .LBROILER	to mean 2
define .RSTEAMER	to mean 3
define .RBOARD1		to mean 4
define .RBOARD2		to mean 5
define .RBOARD3		to mean 6
define .RBOARD4		to mean 7
define .LSTEAMER	to mean 8
define .LBOARD1		to mean 9
define .LBOARD2		to mean 10
define .LBOARD3		to mean 11
define .LBOARD4		to mean 12
define .SHAKE 		to mean 13
define .FRY             to mean 14
define .PICKUP1		to mean 15
define .PICKUP2		to mean 16
define .PICKUP3		to mean 17
define .ORDER1		to mean 18
define .ORDER2		to mean 19
define .ORDER3		to mean 20
define .GIVE.ORDER      to mean 21
define .NUM.STATIONS	to mean 21

'' absolute and mean work times (in seconds) they do NOT include times to move
'' between stations
define .SEND.TIME	to mean 10  
define .BROIL.TIME	to mean 120	'' time to broil a burger
define .CATCH.TIME 	to mean 8	'' time to put meat in into steamer
define .MAKE1.TIME	to mean 5	'' time to put pickles and onions 	
define .MAKE2.TIME	to mean 3	'' time to put on ketsup and mustard
define .MAKE3.TIME	to mean 8	'' time to put mayo, lettuce and tomatoe
define .MAKE4.TIME      to mean 8	'' time to microwave, and wrap
define .BAG.ORDER.TIME  to mean 10	'' time to bag and call order
define .CHECK.ORDER.TIME to mean 3      '' time to look at order and food avail.
define .SHAKE.TIME	to mean 15      '' time to make a shake
define .FRY.TIME 	to mean 8	'' time to put fries in container
define .FRY.DROP.TIME	to mean 10      '' time to put fries into deep frier
define .FRY.COOK.TIME   to mean 120	'' time to cook fries in deep frier
define .FRY.DUMP.TIME	to mean 4       '' time to dump cooked fries into vat
define .MEAN.INTERARRIVAL.TIME 	to mean 120   '' customer interarrival
define .MEAN.ORDER.TIME 	to mean 30    '' time for a customer to order
define .CALL.ORDER.TIME		to mean 4     '' time to call order to knaves
define .GIVE.ORDER.TIME         to mean 4     '' time to give order to customer

'' costs, nominal prices, and average order
define .MEAN.BURGERS.ORDER 	to mean 2.2   '' ave. num shakes/order
define .MEAN.SHAKES.ORDER	to mean 1     '' ave. number of shakes in ord.
define .MEAN.FRIES.ORDER	to mean 1.5   '' ave. number of fries in ord.
define .EMPLOYEE.SPEED		to mean 4     '' walking speed (ft/sec) of emp.
define .BURGER.PRICE		to mean 1.99  '' burger price at mean int. time
define .SHAKE.PRICE		to mean .99  '' shake price at mean int. time
define .FRY.PRICE		to mean .99  '' shake price at mean int. time
define .BURGER.COST		to mean .65   '' cost (to us) of a burger
define .SHAKE.COST		to mean .33   '' cost (to us) of a shake
define .FRY.COST		to mean .23   '' cost (to us) of a fry
define .EMPLOYEE.WAGE		to mean 4.35  '' hourly wage of an employee

'' positions of queues and misc. equipment
define .HEAT.RACKX		to mean 8.0   '' start of heat rack queue
define .HEAT.RACKY		to mean 5.5   
define .STEAMERX		to mean 9.0   '' start of steamer queue
define .STEAMERY		to mean 14.5
define .ORDER.RACKX		to mean 8     '' start of order rack
define .ORDER.RACKY		to mean 5
define .BAG.COUNTERX		to mean 12
define .BAG.COUNTERY		to mean 0.5
define .SHAKE.COUNTERX		to mean 8.0   '' start of counter
define .SHAKE.COUNTERY		to mean 5.5
define .FRY.COUNTERX		to mean 12.0  '' start of where fries are kept
define .FRY.COUNTERY		to mean 5.5
define .FRY.BASKET1X		to mean 16.5  '' fry cooking oil
define .FRY.BASKET1Y		to mean 6.5
define .FRY.BASKET2X		to mean 17.5
define .FRY.BASKET2Y		to mean 6.5
define .FRYVATX			to mean 15    '' cooked fries holder
define .FRYVATY			to mean 6.5
define .WAITX			to mean 15    '' customer waiting area
define .WAITY			to mean -1

'' misc define to means
define .SELF 			to mean PROCESS.V
define .NOW			to mean TIME.V
define .END.SIMULATION 		to mean .NOW ge STOP.TIME
define .SECONDS			to mean units

end
process BROIL.BURGER
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''     This is the process for putting a meat pattie on the broiler.  
''     Meat is filed in the steamer set after it is cooked.
''

define CHOICE as an integer variable
define STATION as an integer variable
define NEW.BURGER as a pointer variable

'' get a knave over to the station
let STATION = CHOICE.WORKSTATION(.RBROILER, .LBROILER, 0)
request 1 WORKSTATION(STATION)
let CHOICE = CHOICE.KNAVE(STATION)
request 1 KNAVE(CHOICE)
let OCCUPIER(STATION) = CHOICE

work .SEND.TIME .SECONDS

'' the knave is done sending
relinquish 1 KNAVE(CHOICE)
relinquish 1 WORKSTATION(STATION)

work .BROIL.TIME .SECONDS
create a BURGER called NEW.BURGER

'' get a knave over to the free steamer station
let STATION = CHOICE.WORKSTATION(.RSTEAMER, .LSTEAMER, 0)
request 1 WORKSTATION(STATION)
let CHOICE = CHOICE.KNAVE(STATION)
request 1 KNAVE(CHOICE)
let OCCUPIER(STATION) = CHOICE

work .CATCH.TIME .SECONDS
file NEW.BURGER in STEAMER
display NEW.BURGER with "burger1.icn" at (.STEAMERX+N.STEAMER*.30, .STEAMERY)

relinquish 1 KNAVE(CHOICE)
relinquish 1 WORKSTATION(STATION)

return
end
process CALL.ORDERS
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''    This process runs continuously looking for completed orders that
''    need to be given to customers.   
''

define CURRENT.ORDER as a pointer variable
define CHOICE as an integer variable
define STATION as an integer variable

'' get a workstation and order taker
let STATION = CHOICE.WORKSTATION(.PICKUP1, .PICKUP2, .PICKUP3)
request 1 WORKSTATION(STATION)
let CHOICE = CHOICE.ORDER.TAKER(STATION)
request 1 ORDER.TAKER(CHOICE)
let OCCUPIER(STATION) = CHOICE

if ORDER.RACK not empty

    '' remove the first order and see if there is enough food for it
    remove first CURRENT.ORDER from ORDER.RACK
    work .CHECK.ORDER.TIME .SECONDS
    if NUM.BURGERS(CURRENT.ORDER) le N.HEAT.RACK and
       NUM.SHAKES(CURRENT.ORDER) le N.SHAKE.COUNTER and
       NUM.FRIES(CURRENT.ORDER) le N.FRY.COUNTER

        '' get the burgers off the rack
        for I = 1 to NUM.BURGERS(CURRENT.ORDER) do
            remove first BURGER from HEAT.RACK
            destroy this BURGER   '' put in bag
        loop

        '' get the fries off the counter
        for I = 1 to NUM.FRIES(CURRENT.ORDER) do
            remove first FRY from FRY.COUNTER
            destroy this FRY   '' put in bag
        loop

        
        '' get the shakes off the counter
        for I = 1 to NUM.SHAKES(CURRENT.ORDER) do
            remove first SHAKE from SHAKE.COUNTER
            destroy this SHAKE   '' put in bag
        loop

        '' bag the order
        work .BAG.ORDER.TIME .SECONDS

        '' add up number of burgers and shakes sold
        add NUM.BURGERS(CURRENT.ORDER) to BURGERS.SOLD
        add NUM.SHAKES(CURRENT.ORDER) to SHAKES.SOLD
	add NUM.FRIES(CURRENT.ORDER) to FRIES.SOLD

        relinquish 1 WORKSTATION(STATION)

        '' move to the order giving station
        let STATION = .GIVE.ORDER
        request 1 WORKSTATION(STATION)
        erase CURRENT.ORDER
        let OCCUPIER(STATION) = CHOICE

        '' put bag onto counter
        file CURRENT.ORDER in BAG.COUNTER
        display CURRENT.ORDER with "bag.icn" at 
          (.BAG.COUNTERX+N.BAG.COUNTER*.4, .BAG.COUNTERY)
        work .GIVE.ORDER.TIME .SECONDS

        '' tell the customer that his food is ready
        reactivate this CUSTOMER called CUSTOMER.WAITING(CURRENT.ORDER) now
    else
        '' put back the order
        file CURRENT.ORDER first in ORDER.RACK
    always
always

relinquish 1 ORDER.TAKER(CHOICE)
relinquish 1 WORKSTATION(STATION)

return
end
function CHOICE.KNAVE given STATION.ID
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''    This function returns with the least busy knave that is closest to
''    the current workstation
''

define STATION.ID as an integer variable
define MY.CHOICE as an integer variable

for each KNAVE, 
    with N.X.KNAVE(KNAVE) eq 0, find the first case

if found
    '' find the closest of the free knaves
    for each KNAVE with N.X.KNAVE(KNAVE) eq 0 do
        if KITCHEN.STATION(KNAVE) ne 0
            if SIDE.OF.BROILER(STATION.ID) ne 
              SIDE.OF.BROILER(KITCHEN.STATION(KNAVE))
                let DX = XLOC(KITCHEN.STATION(KNAVE)) - XLOC(.RBROILER)
                let DY = YLOC(KITCHEN.STATION(KNAVE)) - YLOC(.RBROILER)
                let DIST = SQRT.F(DX * DX + DY * DY)
                let DX = XLOC(STATION.ID) - XLOC(.RBROILER)
                let DY = YLOC(STATION.ID) - YLOC(.RBROILER)
                add SQRT.F(DX * DX + DY * DY) to DIST
            else
                let DX = XLOC(KITCHEN.STATION(KNAVE)) - XLOC(STATION.ID)
                let DY = YLOC(KITCHEN.STATION(KNAVE)) - YLOC(STATION.ID)
                let DIST = SQRT.F(DX * DX + DY * DY)
            always
        else
            let DIST = 0
        always
        compute MY.CHOICE as the minimum(KNAVE) of DIST
    loop
else
    for each KNAVE,
        compute MY.CHOICE as the minimum(KNAVE) of N.Q.KNAVE(KNAVE)
always

return with MY.CHOICE
end
function CHOICE.ORDER.TAKER given STATION.ID
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''    This function returns with the least busy order taker that is closest to
''    the current workstation
''

define STATION.ID as an integer variable
define MY.CHOICE as an integer variable

let STATION.ID = STATION.ID

for each ORDER.TAKER, 
    with N.X.ORDER.TAKER(ORDER.TAKER) eq 0, find the first case

if found
    let MY.CHOICE = ORDER.TAKER
else
    for each ORDER.TAKER, compute MY.CHOICE as the 
        minimum(ORDER.TAKER) of N.Q.ORDER.TAKER(ORDER.TAKER)
always

return with MY.CHOICE
end
function CHOICE.WORKSTATION given CAND1,CAND2,CAND3
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''    This function returns with the least busy workstation out of 3 cands. 
''

define CAND1,CAND2,CAND3 as an integer variable
define MY.CHOICE as an integer variable

for each WORKSTATION with 
   (WORKSTATION eq CAND1 or WORKSTATION eq CAND2 or WORKSTATION eq CAND3) and
    N.X.WORKSTATION(WORKSTATION) eq 0, find the first case

if found
    let MY.CHOICE = WORKSTATION
else
    for each WORKSTATION with 
      WORKSTATION eq CAND1 or WORKSTATION eq CAND2 or WORKSTATION eq CAND3
        compute MY.CHOICE as the minimum(WORKSTATION) of 
          N.Q.WORKSTATION(WORKSTATION)
always

return with MY.CHOICE
end
Routine CLOCK Given TIME Yielding NEWTIME
''
'' AUTHOR: Steve Bailey
'' CREATED: 8-5-90
'' DESCRIPTION:
''     This is the time syncronization routine which is called periodically
''     by the system timing routine.  The CLOCK.TIME display variable will
''     be updated here
''

define TIME as a double variable
define NEWTIME as a double variable

let CLOCK.TIME = TIME / (24*60*60)
let NEWTIME = TIME

return
end
process CUSTOMER
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''     This process simulates a customer placing an order.  Orders are
''     based on number of burgers, and shakes.
''

define ARRIVAL.TIME as a real variable
define MY.ORDER as a pointer variable
define CHOICE as an integer variable
define STATION as an integer variable
define WAITING as a pointer variable
define WAITING.AREA as a 1-dim pointer saved array
define MY.SPOT as an integer variable
define MEAN.BURGERS.ORDER as a real saved variable
define MEAN.SHAKES.ORDER as a real saved variable
define MEAN.FRIES.ORDER as a real saved variable
define I as an integer variable
define .CONSUMPTION.POWER to mean 1.5

'' if first time here
if WAITING.AREA(*) eq 0
    reserve WAITING.AREA(*) as 25

    '' compute the mean order amounts based on current pricing 
    let MEAN.BURGERS.ORDER = ((.BURGER.PRICE / BURGER.PRICE) ** 
      .CONSUMPTION.POWER) * .MEAN.BURGERS.ORDER
    let MEAN.SHAKES.ORDER = ((.SHAKE.PRICE / SHAKE.PRICE) ** 
      .CONSUMPTION.POWER) * .MEAN.SHAKES.ORDER
    let MEAN.FRIES.ORDER = ((.FRY.PRICE / FRY.PRICE) ** 
      .CONSUMPTION.POWER) * .MEAN.FRIES.ORDER
always

let ARRIVAL.TIME = .NOW

'' get in the shortest line, and wait for an order taker and register
let STATION = CHOICE.WORKSTATION(.ORDER1, .ORDER2, .ORDER3)
display .SELF with CONCAT.F("customer", ITOT.F(RANDI.F(1,4,1)), ".icn") at 
  (XLOC(STATION),YLOC(STATION)-(N.Q.WORKSTATION(STATION)+1)*0.5-1)
request 1 WORKSTATION(STATION)
let CHOICE = CHOICE.ORDER.TAKER(STATION)
request 1 ORDER.TAKER(CHOICE)
let OCCUPIER(STATION) = CHOICE

'' create an order for order taker
create an ORDER called MY.ORDER
let NUM.BURGERS(MY.ORDER) = EXPONENTIAL.F(MEAN.BURGERS.ORDER, 3)
let NUM.SHAKES(MY.ORDER) = EXPONENTIAL.F(MEAN.SHAKES.ORDER, 4)
let NUM.FRIES(MY.ORDER) = EXPONENTIAL.F(MEAN.FRIES.ORDER, 5)
let CUSTOMER.WAITING(MY.ORDER) = .SELF

'' give order to order taker
work EXPONENTIAL.F(.MEAN.ORDER.TIME, 6) .SECONDS
activate a TAKE.ORDER given MY.ORDER now

relinquish 1 ORDER.TAKER(CHOICE)
relinquish 1 WORKSTATION(STATION)

'' move into a free spot in the waiting area
for I = 1 to 25 do
    if WAITING.AREA(I) eq 0
        let MY.SPOT = I
        let WAITING.AREA(I) = .SELF
        call MOVE.TO(.SELF, REAL.F(.WAITX+MOD.F(I-1,5)), 
          REAL.F(.WAITY-TRUNC.F((I-1)/5)))
        leave
    always
loop

'' move all customers waiting in line up 1
let WAITING = F.Q.WORKSTATION(STATION)
for I = 1 to N.Q.WORKSTATION(STATION) do
    display WHO.A(WAITING) at (XLOC(STATION),YLOC(STATION)-I*0.5-1)
    wait 1 .SECONDS
    let WAITING = S.Q.WORKSTATION(WAITING)
loop

'' now suspend until order is ready
suspend

let WAITING.TIME = .NOW - ARRIVAL.TIME

'' get up
work 4 .SECONDS

'' clear out this spot
if MY.SPOT ne 0
    let WAITING.AREA(MY.SPOT) = 0
always

'' go to the order pickup station
call MOVE.TO(.SELF, XLOC(.GIVE.ORDER), YLOC(.GIVE.ORDER)-1.0)

'' take the order
remove MY.ORDER from BAG.COUNTER
destroy this ORDER called MY.ORDER

'' leave the order area
call MOVE.TO(.SELF, XLOC(.GIVE.ORDER), -5.0)

return
end
process GENERATOR
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''      Generates customers based on an expomentially distributed arrival
''	time.   
''
define MEAN.INTERARRIVAL as a real variable
define .CONSUMPTION.POWER to mean 3.0

'' compute the mean int. time based on current pricing and past history
let MEAN.INTERARRIVAL = ((BURGER.PRICE+SHAKE.PRICE) / 
  (.BURGER.PRICE+.SHAKE.PRICE)) ** .CONSUMPTION.POWER * .MEAN.INTERARRIVAL.TIME

'' keep generating customers
until .END.SIMULATION do
    activate a CUSTOMER now
    wait EXPONENTIAL.F(MEAN.INTERARRIVAL, 1) .SECONDS
loop

return
end
process DUMM
until .END.SIMULATION do
    wait 0.01 units
loop
return
end
main
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''     main routine for SIMFOOD.  See preamble for description.
''

define FORM as a pointer variable
define I as an integer variable

'' assign global parameters to simulation
let STOP.TIME = 15 * 60
let N.KNAVE = 4
let N.ORDER.TAKER = 2
let N.WORKSTATION = .NUM.STATIONS
let BURGER.PRICE = 1.99
let SHAKE.PRICE = .99
let FRY.PRICE = .99

'' bring up the dialog to let the user decide prices, and number of workers
show FORM with "food.frm"
select case ACCEPT.F(FORM, 0)
  case "OK"
    let STOP.TIME = 60 * DDVAL.A(DFIELD.F("STOP TIME", FORM))
    let N.KNAVE = DDVAL.A(DFIELD.F("NUM KNAVES", FORM))
    let N.ORDER.TAKER = DDVAL.A(DFIELD.F("NUM ORDER TAKERS", FORM))
    let BURGER.PRICE = DDVAL.A(DFIELD.F("BURGER PRICE", FORM))
    let SHAKE.PRICE = DDVAL.A(DFIELD.F("SHAKE PRICE", FORM))
    let FRY.PRICE = DDVAL.A(DFIELD.F("FRY PRICE", FORM))
    let TIMESCALE = 100 / DDVAL.A(DFIELD.F("SPEED", FORM)) 
    let COMPRESS.TIME = DDVAL.A(DFIELD.F("COMPRESS TIME", FORM)) 
  default
endselect

'' initialize graphics
''call SET.ANIMATION.R(1)
let VXFORM.V = 1
let TIMESYNC.V = 'CLOCK'
call SETWORLD.R(0.0,20.0,-5.0,20.0)
create an ICON
display ICON with "floor.icn" at (10.0, 7.0)
display CLOCK.TIME with "fclock.grf"

'' init knave resources
create every KNAVE
for each KNAVE do
    let U.KNAVE(KNAVE) = 1
    create an ICON called KNAVE.ICON(KNAVE)
    show KNAVE.ICON(KNAVE) with "knave.icn"
loop

'' init order taker resources
create every ORDER.TAKER
for each ORDER.TAKER do
    let U.ORDER.TAKER(ORDER.TAKER) = 1
    create an ICON called ORDER.TAKER.ICON(ORDER.TAKER)
    show ORDER.TAKER.ICON(ORDER.TAKER) with "ot.icn"
loop

'' init workstation resources
open unit 1 for input, name is "worksta.dat"
use unit 1 for input
create every WORKSTATION
for each WORKSTATION do
    let U.WORKSTATION(WORKSTATION) = 1
    read XLOC(WORKSTATION), YLOC(WORKSTATION), SIDE.OF.BROILER(WORKSTATION)
    create an ICON
    display ICON with "worksta.icn" at (XLOC(WORKSTATION), YLOC(WORKSTATION))
loop
close unit 1

'' broil 4 burgers to start off with to fill up the steamer
for I = 1 to 4 do
    activate a BROIL.BURGER now
loop

'' start generating customers after weve had a chance to set up
activate a GENERATOR in .BROIL.TIME + 4 * (.CATCH.TIME + .SEND.TIME) .SECONDS
activate a STOP.SIM in STOP.TIME .SECONDS

start simulation

stop
end
process MAKE.BURGER
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''     This process takes a burger out of the steamer and builds it.
''

define WORK.BURGER as a pointer variable
define CHOICE as an integer variable
define STATION as an integer variable
define BURGER.READY as an integer variable
define BOFF as a real variable

'' broil a meat pattie
activate a BROIL.BURGER now

'' keep checking steamer until it has a burger in it
until BURGER.READY is not zero do

    '' move the closest free knave to that station
    let STATION = CHOICE.WORKSTATION(.RBOARD1, .LBOARD1, 0)
    request 1 WORKSTATION(STATION)
    let CHOICE = CHOICE.KNAVE(STATION)
    request 1 KNAVE(CHOICE)
    let OCCUPIER(STATION) = CHOICE

    '' if steamer is empty, then start over
    if STEAMER is empty
        relinquish 1 KNAVE(CHOICE)
        relinquish 1 WORKSTATION(STATION)
        wait 20 .SECONDS
        cycle
    otherwise

    let BURGER.READY = 1
loop

let BOFF = 3.0 * (SIDE.OF.BROILER(STATION) - 1.5)

remove first WORK.BURGER from STEAMER
erase WORK.BURGER
display WORK.BURGER with "burger2.icn" at (XLOC(STATION)+BOFF,YLOC(STATION))

'' put pickles and onions on burger
work .MAKE1.TIME .SECONDS
relinquish 1 WORKSTATION(STATION)

'' put ketsup and mustard on burger
request 1 WORKSTATION(STATION+1)
let OCCUPIER(STATION+1) = CHOICE
erase WORK.BURGER
display WORK.BURGER with "burger3.icn" at (XLOC(STATION+1)+BOFF,YLOC(STATION+1))
work .MAKE2.TIME .SECONDS
relinquish 1 WORKSTATION(STATION+1)

'' put on mayo, lettuce and tomatoe
request 1 WORKSTATION(STATION+2)
let OCCUPIER(STATION+2) = CHOICE
erase WORK.BURGER
display WORK.BURGER with "burger4.icn" at (XLOC(STATION+2)+BOFF,YLOC(STATION+2))
work .MAKE3.TIME .SECONDS
relinquish 1 WORKSTATION(STATION+2)

'' wrap burger and microwave
request 1 WORKSTATION(STATION+3)
let OCCUPIER(STATION+3) = CHOICE
erase WORK.BURGER
display WORK.BURGER with "burger5.icn" at (XLOC(STATION+3)+BOFF,YLOC(STATION+3))
work .MAKE4.TIME .SECONDS
relinquish 1 WORKSTATION(STATION+3)

file WORK.BURGER in HEAT.RACK
erase WORK.BURGER
display WORK.BURGER with "burger6.icn" at 
  (.HEAT.RACKX + N.HEAT.RACK*0.35, .HEAT.RACKY)

relinquish 1 KNAVE(CHOICE)
activate a CALL.ORDERS now

return
end
            
process MAKE.FRY
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''     This process makes a french fry.  If the number of potential french fry
''     baskets left in the fry catcher is less than 4, then a fry basket is
''     put down.  If a fry basket is down and there are no fries left, 
''     another is put down.
''

define CHOICE as an integer variable
define NEW.FRY as a pointer variable
define FRY.BASKET1, FRY.BASKET2, FRY.VAT as pointer saved variables
define BASKET1.DOWN,BASKET2.DOWN,FRIES.LEFT as integer saved variables
define FRIES.DROPPED as a pointer variable

'' if first time here, create icons
if FRY.BASKET1 eq 0
    create an ICON called FRY.BASKET1
    show FRY.BASKET1 with "frybask.icn"
    create an ICON called FRY.BASKET2
    show FRY.BASKET2 with "frybask.icn"
    create an ICON called FRY.VAT
    show FRY.VAT with "fryvat.icn"
always

'' move a knave to this station
request 1 WORKSTATION(.FRY)
let CHOICE = CHOICE.KNAVE(.FRY)
request 1 KNAVE(CHOICE)
let OCCUPIER(.FRY) = CHOICE

'CHECK.FRIES'

if FRIES.LEFT gt 0 and NEW.FRY eq 0
    subtract 1 from FRIES.LEFT
    if FRIES.LEFT eq 0
        erase FRY.VAT
    always

    '' make the fry
    work .FRY.TIME .SECONDS
    create a FRY called NEW.FRY
    file NEW.FRY in FRY.COUNTER
    display NEW.FRY with "fry.icn" at 
      (.FRY.COUNTERX+N.FRY.COUNTER*.30, .FRY.COUNTERY)

    '' drop another basket if needed
    if FRIES.LEFT lt 4 and BASKET1.DOWN eq 0
        go 'DROP.FRIES'
    always
    go 'DONE'
always

'DROP.FRIES'

'' drop first basket if not down
if BASKET1.DOWN eq 0 
    let BASKET1.DOWN = 1
    let FRIES.DROPPED = FRY.BASKET1
    work .FRY.DROP.TIME .SECONDS
    display FRY.BASKET1 at (.FRY.BASKET1X, .FRY.BASKET1Y)
else
    '' if basket 2 isn't down and there are a lot of orders drop another
    if BASKET2.DOWN eq 0 and N.ORDER.RACK gt 2
        let BASKET2.DOWN = 1
        let FRIES.DROPPED = FRY.BASKET2
        work .FRY.DROP.TIME .SECONDS
        display FRY.BASKET2 at (.FRY.BASKET2X, .FRY.BASKET2Y)
    always
always

'' let the knave and workstation go
relinquish 1 KNAVE(CHOICE)
relinquish 1 WORKSTATION(.FRY)

'' wait until fries are done cooking
if FRIES.DROPPED ne 0
    work .FRY.COOK.TIME .SECONDS
else
    wait 20 .SECONDS
always

'' move a knave to this station
request 1 WORKSTATION(.FRY)
let CHOICE = CHOICE.KNAVE(.FRY)
request 1 KNAVE(CHOICE)
let OCCUPIER(.FRY) = CHOICE

'' dump the cooked fries into the fry vat
if FRIES.DROPPED ne 0
    work .FRY.DUMP.TIME .SECONDS
    erase FRIES.DROPPED
    if FRIES.DROPPED eq FRY.BASKET1
        let BASKET1.DOWN = 0
    else
        let BASKET2.DOWN = 0
    always
    display FRY.VAT at (.FRYVATX, .FRYVATY)
    add 10 to FRIES.LEFT
always

if NEW.FRY eq 0
    go 'CHECK.FRIES'
always

'DONE'

relinquish 1 KNAVE(CHOICE)
relinquish 1 WORKSTATION(.FRY)
 
activate a CALL.ORDERS now

return 
end
process MAKE.SHAKE
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''     This process takes a burger out of the steamer and builds it.
''

define CHOICE as an integer variable

'' move a knave to this station
request 1 WORKSTATION(.SHAKE)
let CHOICE = CHOICE.KNAVE(.SHAKE)
request 1 KNAVE(CHOICE)
let OCCUPIER(.SHAKE) = CHOICE

'' make the shake
create a SHAKE
file SHAKE in SHAKE.COUNTER
display SHAKE with "shake.icn" at 
  (.SHAKE.COUNTERX-N.SHAKE.COUNTER*.35, .SHAKE.COUNTERY)
work .SHAKE.TIME .SECONDS

relinquish 1 KNAVE(CHOICE)
relinquish 1 WORKSTATION(.SHAKE)

activate a CALL.ORDERS now

return
end
routine MOVE.TO given ICON, X, Y
''
'' AUTHOR: Steve Bailey
'' CREATED: 8-7-90
'' DESCRIPTION:
''     This routine moves an icon from one location to another
''

define ICON as a pointer variable
define X,Y as real variables
define DX,DY as integer variables
define NUM.MOVING as an integer saved variable

let DX = X - LOCATION.X(ICON)
let DY = Y - LOCATION.Y(ICON)

'' move icon to station
if DX ne 0 or DY ne 0
    let DISTANCE = SQRT.F(DX*DX + DY*DY)
    add 1 to NUM.MOVING
    let TIMESCALE.V = TIMESCALE
    let VELOCITY.A(ICON) = VELOCITY.F(.EMPLOYEE.SPEED,ARCTAN.F(DY,DX))
    work DISTANCE / .EMPLOYEE.SPEED .SECONDS
    let VELOCITY.A(ICON) = 0
    subtract 1 from NUM.MOVING
    if NUM.MOVING eq 0 and COMPRESS.TIME ne 0
        let TIMESCALE.V = 0
    always
always

display ICON at (X, Y)

return
end
left routine OCCUPIER given STATION
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''     This routine moves the given knave to the given station.  The knave
''     is actually moved to the station (elapsing simulated time).
''

define STATION as an integer variable
define EMPLOYEE as an integer variable

enter with EMPLOYEE

if EMPLOYEE ne 0
    '' see if we are talking about an order station or a kitchen station
    select case STATION
      case .ORDER1, .ORDER2, .ORDER3, .PICKUP1, .PICKUP2, .PICKUP3, .GIVE.ORDER
        if ORDER.STATION(EMPLOYEE) ne 0
            call MOVE.TO given 
              ORDER.TAKER.ICON(EMPLOYEE), XLOC(STATION), YLOC(STATION)
        always
        let ORDER.STATION(EMPLOYEE) = STATION
        display ORDER.TAKER.ICON(EMPLOYEE) at (XLOC(STATION), YLOC(STATION))
      default
        if KITCHEN.STATION(EMPLOYEE) ne 0
            '' move employee around broiler
            if SIDE.OF.BROILER(KITCHEN.STATION(EMPLOYEE)) ne 
              SIDE.OF.BROILER(STATION) 
                if SIDE.OF.BROILER(STATION) eq .RIGHT.SIDE and (STATION ne 
                  .RBROILER or KITCHEN.STATION(EMPLOYEE) ne .LBROILER)
                    let OCCUPIER(.LBROILER) = EMPLOYEE
                    let OCCUPIER(.RBROILER) = EMPLOYEE
                always
                if SIDE.OF.BROILER(STATION) eq .LEFT.SIDE and (STATION ne 
                  .LBROILER or KITCHEN.STATION(EMPLOYEE) ne .RBROILER)
                    let OCCUPIER(.RBROILER) = EMPLOYEE
                    let OCCUPIER(.LBROILER) = EMPLOYEE
                always
            always
            call MOVE.TO given KNAVE.ICON(EMPLOYEE),XLOC(STATION),YLOC(STATION)
        always
        let KITCHEN.STATION(EMPLOYEE) = STATION
        display KNAVE.ICON(EMPLOYEE) at (XLOC(STATION), YLOC(STATION))
    endselect
always

return
end
process STOP.SIM
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''     This process stops the simulation and prints out vital statistics.
''

define I as an integer variable
define GROSS, NET1, NET2 as a real variable

let GROSS = BURGERS.SOLD*BURGER.PRICE + SHAKES.SOLD*SHAKE.PRICE +
  FRIES.SOLD*FRY.PRICE
let NET1 = GROSS - (BURGERS.SOLD*.BURGER.COST + SHAKES.SOLD*.SHAKE.COST +
  FRIES.SOLD*.FRY.COST)
let NET2 = NET1 - ((N.KNAVE+N.ORDER.TAKER) * .EMPLOYEE.WAGE)

skip 2 lines
print 2 lines like this
			FAST FOOD SIMULATION RESULTS	
			---- ---- ---------- -------
skip 1 line

print 6 lines with 
    .NOW / 3600, TOTAL.CUSTOMERS, MEAN.WAITING.TIME / 60, 
    MOD.F(MEAN.WAITING.TIME, 60), MAX.WAITING.TIME / 60, 
    MOD.F(MAX.WAITING.TIME, 60), GROSS, NET2
like this
    The fast food simulation ended after **.* hours 
    The number of customers that were served was ***
    The average waiting time for food was ***:** minutes
    The maximum waiting time for food was ***:** minutes
    We grossed ***.** dollars
    After paying wages and costs we netted ***.** dollars

skip 1 line

print 2 lines like this
 The queues for the knaves were as follows:
 Knave       Average        Maximum        Utilization

skip 1 line

for I = 1 to N.KNAVE do
    print 1 line with 
      I, AVE.KNAVE.JOBS(I), MAX.KNAVE.JOBS(I), KNAVE.UTILIZATION(I) * 100
      like this
   **         **.*            **              **.*  Percent
loop

skip 1 line

print 2 lines like this
 The queues for the order takers were as follows:
 Order taker       Average        Maximum        Utilization

for I = 1 to N.ORDER.TAKER do
    print 1 line with 
      I, AVE.ORDER.TAKER.JOBS(I), MAX.ORDER.TAKER.JOBS(I), 
      ORDER.TAKER.UTILIZATION(I) * 100 like this
   **               **.*            **              **.*  Percent
loop

skip 1 line

print 2 lines like this
 The queues for the workstations were as follows:
 Workstation       Average        Maximum        Utilization

for I = 1 to N.WORKSTATION do
    print 1 line with 
      I, AVE.WORKSTATION.JOBS(I), MAX.WORKSTATION.JOBS(I), 
      WORKSTATION.UTILIZATION(I) * 100 like this
   **               **.*            **              **.*  Percent
loop

stop
end

process TAKE.ORDER given CUSTOMER.ORDER
''
'' AUTHOR: Steve Bailey
'' CREATED: 7-25-90
'' DESCRIPTION:
''     This process simulates a the calling out of an order. 
''

define I as an integer variable
define CUSTOMER.ORDER as a pointer variable

'' call the order out to the knaves
work .CALL.ORDER.TIME .SECONDS
file CUSTOMER.ORDER in ORDER.RACK
display CUSTOMER.ORDER with "order.icn" at 
  (.ORDER.RACKX+N.ORDER.RACK*.3, .ORDER.RACKY)

'' tell knaves to send however many burgers
for I = 1 TO NUM.BURGERS(CUSTOMER.ORDER) do
    activate a MAKE.BURGER now
loop

'' tell knaves to make however many shakes
for I = 1 TO NUM.SHAKES(CUSTOMER.ORDER) do
    activate a MAKE.SHAKE now
loop

'' tell knaves to make fries
for I = 1 to NUM.FRIES(CUSTOMER.ORDER) do
    activate a MAKE.FRY now
loop

return
end
