
--** The Dining Philosophers Animation.
--  
--  This file contains the code given in the notes for modelling the dining
--  philosophers.
--  
--  Your task is to animate this system.  Links will need to be made from
--  the college to its external environment, through which the philsophers
--  (and, maybe, the forks and the security guard) report their state.
--  These links are gathered by a fair-ALTing server that drives a screen
--  display in response to incoming information from the college.
--
--  Alternatively, the college links could be SHARED (output) ends of
--  a common channel whose unshared (input) end is serviced by display
--  process without ALTing.  CASE protocols and protocol inheritance yield
--  an elegant and efficient solution here.
--
--  Thinking and eating by the philosphers should be modelled by random
--  (finite) delays.
--
--  The animated display should show all relevant states of each philosopher,
--  fork and the security guard.  An initial display could just print one
--  line of text for each report received by any college process ... and this
--  should be done first.  Full marks will need something more interesting!
--  For instance, a 2D grid of characters with adjacent groups of rows
--  showing individual philosopher activity (including held forks) ... and
--  adjacent columns representing different states (thinking/hungry/etc.).
--  Forks - when on the table - could appear between philospher rows somewhere.
--  Security guard reports could appear above or below the grid.
--  
--  Extra marks will be awarded for more ambitious aninmations.  For example,
--  ones that reflect the round nature of the table, that animate the movement
--  of philosophers and forks, that offer user interaction (e.g. to freeze
--  the animation, change its speed, instruct the security guard to allow
--  in less or more philosophers at the same time, play space invaders, ...),
--  etc.


#INCLUDE "course.module"


VAL INT n.philosophers IS 5:    --* anything > 1 (depends on your animation)

VAL INT second IS 1000000:                  --* occam-pi 'ticks'

-- Note: you may want to experiment with
-- different values for the following constants:

VAL INT min.think.time IS 5*second:         --* for a philosopher
VAL INT max.think.time IS 15*second:        --* for a philosopher

VAL INT min.eat.time IS 4*second:           --* for a philosopher
VAL INT max.eat.time IS 20*second:          --* for a philosopher


--* The philospher process.
--
--  Note: 'left' and 'right' are relative to the philosopher.
--
-- @param left Pick up / Put down fork on the left
-- @param right Pick up / Put down fork on the right
--
-- @param down Get permission to sit down
-- @param up Get permission to stand up
--
PROC philosopher (CHAN BOOL left!, right!, down!, up!)
  --{{{  
  -- some local constants may be needed (e.g. thinking/eating time spans)
  SEQ
    -- some initialisation may be needed
    WHILE TRUE
      SEQ
        SKIP              -- thinking : change to a random time delay
        down ! TRUE       -- get permission to sit down
        PAR
          left ! TRUE     -- pick up the forks
          right ! TRUE
        SKIP              -- eating : change to a random time delay
        PAR
          left ! TRUE     -- put down the forks
          right ! TRUE
        up ! TRUE         -- notify security you have finished
  --}}}
:


--* The fork process.
--
--  Note: 'right' and 'left' are relative to the fork.
--
-- @param right Pick up / Put down by the philosopher on the right
-- @param left Pick up / Put down by the philosopher on the left
--
PROC fork (CHAN BOOL right?, left?)
  --{{{  
  WHILE TRUE
    ALT
      BOOL any:
      right ? any       -- philosopher right picks up fork
        right ? any     -- philosopher right puts down fork
      BOOL any:
      left ? any        -- philosopher left picks up fork
        left ? any      -- philosopher left puts down fork
  --}}}
:


--* The security guard process.
--
-- @param down Request to sit down channels
-- @param up Request to stand up channels
--
PROC security ([n.philosophers]CHAN BOOL down?, up?)
  --{{{  
  VAL INT max IS n.philosophers - 1:
  INITIAL INT n.sat.down IS 0:
  WHILE TRUE
    ALT i = 0 FOR n.philosophers
      ALT
        --{{{  philosopher wanting to sit down
        BOOL any:
        (n.sat.down < max) & down[i] ? any    -- only allow up to max at a time
          n.sat.down := n.sat.down + 1
        --}}}
        --{{{  philosopher wanting to stand up
        BOOL any:
        up[i] ? any                           -- always allow this
          n.sat.down := n.sat.down - 1
        --}}}
  --}}}
:


--* The secure (i.e. deadlock-free) college process.
--
PROC secure.college ()
  --{{{  
  [n.philosophers]CHAN BOOL left, right, up, down:
  PAR
    security (down?, up?)
    PAR i = 0 FOR n.philosophers
      PAR
        philosopher (left[i]!, right[i]!, down[i]!, up[i]!)
        fork (right[i]?, left[(i+1)\n.philosophers]?)
  --}}}
:


--* The main process.
--
-- @param keyboard The standard input channel (stdin, in Unix-speak)
-- @param screen The standard output channel (stdout, in Unix-speak)
-- @param error The standard error channel (stderr, in Unix-speak)
--
PROC q7 (CHAN BYTE keyboard?, screen!, error!)
  --{{{  

  -- You don't have to use any of the following code ...

  VAL BYTE title.col IS 14:
  VAL BYTE title.row IS 3:
  
  VAL BYTE seed.col IS 1:
  VAL BYTE seed.row IS 7:
  
  INT seed:

  SEQ

    --{{{  clear screen
    SEQ
      erase.screen (screen!)
    --}}}

    --{{{  welcome message
    SEQ
      cursor.x.y (title.col, title.row, screen!)
      out.string ("Welcome to the Dining Philosophers Animation ", 0, screen!)
      cursor.x.y (title.col, title.row + 1, screen!)
      out.string ("====================================================", 0, screen!)
      screen ! FLUSH
    --}}}
    
    --{{{  choose random number seed
    BOOL automatic:
    SEQ
      cursor.x.y (seed.col, seed.row, screen!)
      ask.bool ("Set seed from the TIMER? ", automatic, keyboard?, screen!)
      IF
        automatic
          TIMER tim:
          SEQ
            tim ? seed
            seed := (seed >> 2) + 10000
        TRUE
          VAL INT min.seed IS 10:
          VAL INT max.seed IS 99999999:
          SEQ
            ask.int ("Seed = ", seed, 8, keyboard?, screen!)
            WHILE (seed < min.seed) OR (seed > max.seed)
              SEQ
                out.string ("  ****** Seed must be between ", 0, screen!)
                out.int (min.seed, 0,  screen!)
                out.string (" and ", 0, screen!)
                out.int (max.seed, 0,  screen!)
                out.string (" inclusive*c*n", 0, screen!)
                ask.int ("Seed = ", seed, 8, keyboard?, screen!)
    --}}}

    --{{{  clear screen and remove cursor
    SEQ
      erase.screen (screen!)
      cursor.invisible (screen!)
      screen ! FLUSH
    --}}}

    
    -- get things going ...
    --
    -- declare channels ...
    --

    PAR

      secure.college ()         -- some parameters will be needed ...

      SKIP                      -- and a 'display' process ...

      SKIP                      -- maybe a 'keyboard?' handler ...

      TIMER tim:                -- probably delete this process ...  :)
      INT timeout:
      VAL INT second IS 1000000:
      VAL BYTE godot.col IS 14:
      VAL BYTE godot.row IS 3:
      SEQ
        tim ? timeout
        WHILE TRUE
          SEQ
            cursor.x.y (godot.col, godot.row, screen!)
            out.string ("Waiting for Godot ...", 0, screen!)
            cursor.x.y (1, godot.row + 3, screen!)            -- park cursor
            screen ! FLUSH
            timeout := timeout PLUS (1*second)
            tim ? AFTER timeout
            cursor.x.y (godot.col, godot.row, screen!)
            erase.eol (screen!)
            cursor.x.y (1, godot.row + 3, screen!)            -- park cursor
            screen ! FLUSH
            timeout := timeout PLUS (1*second)
            tim ? AFTER timeout

  --}}}
:
