-- Based on particles4 -- ported to miniraster by ats

#INCLUDE "sdlraster.module"
#INCLUDE "course.module"
#INCLUDE "rastergraphics.module"

VAL INT time.step IS 40000:

DATA TYPE COL.COORD
  RECORD
    INT row:
    INT col:
    INT colour:
    INT radius:
:

PROTOCOL PART.INFO IS INT; COL.COORD:
VAL INT no.of.particles IS 10000:

PROC particle (VAL INT id, VAL COL.COORD init, VAL INT height, width, init.seed, SHARED CHAN PART.INFO out!, BARRIER b,
               SHARED CHAN BYTE scr!, INT radius)
  INITIAL INT my.radius IS radius:
  INITIAL BOOL frozen.radius IS TRUE:
  COL.COORD co:
  VAL INT two.30 IS 2 << 30:
  INT seed:
  SEQ
    co := init
    seed := init.seed + id
    WHILE TRUE
      INT rand.num:
      SEQ
        rand.num, seed := random (two.30, seed)
        SEQ i = 0 FOR 10
          INT direction:
          SEQ
            IF
              frozen.radius
                co[radius] := radius
              TRUE
                SKIP
            frozen.radius := NOT frozen.radius

            direction, rand.num := rand.num /\ 7, rand.num >> 3
            CLAIM out!
              out ! id; co -- output the new co-ords
            SYNC b
            CASE direction
              0 -- Go North
                co[row] := co[row] - 1
              1 -- Go North-East
                SEQ
                  co[row] := co[row] - 1
                  co[col] := co[col] + 1
              2 -- Go East
                co[col] := co[col] + 1
              3 -- Go South-East
                SEQ
                  co[row] := co[row] + 1
                  co[col] := co[col] + 1
              4 -- Go South
                co[row] := co[row] + 1
              5 -- Go South-West
                SEQ            
                  co[row] := co[row] + 1
                  co[col] := co[col] - 1
              6 -- Go West
                co[col] := co[col] - 1
              7 -- Go North West
                SEQ
                  co[row] := co[row] - 1
                  co[col] := co[col] - 1
              ELSE
                CLAIM scr!
                  SEQ
                    out.string ("id ", 0, scr!)
                    out.int (id, 0, scr!)
                    out.string (" direction ", 0, scr!)
                    out.int (direction, 0, scr!)
                    out.string ("*c*n", 0, scr!)
            -- check not outside boundaries
            IF
              (co[row] - co[radius]) < 0
                co[row] := 1 + co[radius]
              (co[row] + co[radius]) >= height
                co[row] := height - (co[radius] + 1)
              TRUE
                SKIP
            IF 
              (co[col] - co[radius]) < 0
                co[col] := 1 + co[radius]
              (co[col] + co[radius]) >= width
                co[col] := width - (co[radius] + 1)
              TRUE
                SKIP

:

PROC radius.control (CHAN INT c?, INT radius, BARRIER b)
  WHILE TRUE
    SEQ
      SYNC b
      PRI ALT -- it is safe to change the global radius
        c ? radius
          SKIP
        SKIP
          SKIP
      SYNC b -- not changing radius, so the particles can look at it
:

PROC time.stepper (VAL INT time.step, BARRIER b, CHAN BYTE report!, pause?)
  WHILE TRUE
    BYTE ch:
    SEQ
      PRI ALT 
        pause ? ch
          pause ? ch
        SKIP
          SKIP
      SYNC b
      report ! 0
:

PROC timer (CHAN BYTE report?, SHARED CHAN BYTE scr!)
  TIMER tim:
  INT t:
  VAL INT second IS 1000000:
  INITIAL INT count IS 0:
  SEQ
    tim ? t
    t := t PLUS second
    WHILE TRUE
      PRI ALT
        tim ? AFTER t
          SEQ
            CLAIM scr!
              SEQ
                out.string ("****** counts per second = ", 0, scr!)
                out.int (count, 0, scr!)
                out.string ("*c*n", 0, scr!)
            t, count := t PLUS second, 0
        BYTE any:
        report ? any
          count := count + 1
:

PROC drawing (CHAN PART.INFO graphics?, CHAN RASTER in?, out!, SHARED CHAN BYTE scr!)
  RASTER r:
  [no.of.particles]COL.COORD co:
  INT id, t:
  TIMER tim:
  SEQ
    tim ? t
    SEQ i = 0 FOR SIZE co
      co[i] := [0, 0, 0, 0]
    WHILE TRUE
      PRI ALT
        tim ? AFTER t
          SEQ
            in ? r
            clear.raster (r, #000000)
            SEQ i = 0 FOR SIZE co
              VAL COL.COORD co.i IS co[i]:
              IF 
                (co.i[radius] > 0)
                  fill.circle (co.i[col], co.i[row], co.i[radius], co.i[colour], r)
                (co.i[radius] = 0)
                  r[co.i[row]][co.i[col]] := co.i[colour] -- draw a particle

            out ! r
            tim ? t
            t := t PLUS (1000000 / 60)
        graphics ? id; co[id]
          SKIP
:

PROC keyboard.monitor (CHAN BYTE keyboard?, pause!, timer.pause!, 
                       CHAN INT change!)
  BYTE ch:
  WHILE TRUE 
    SEQ
      keyboard ? ch
      CASE ch
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
          change ! INT (ch - '0')

        'f'
          SEQ
            pause ! ch
            timer.pause ! ch
            keyboard ? ch
            timer.pause ! ch
            pause ! ch
        ELSE
          SKIP
:


PROC init.coords ([]COL.COORD co, VAL INT height, width, size, INT seed)
  VAL INT max.colour IS 1 << 24:
  INT n:
  SEQ i = 0 FOR SIZE co
    SEQ
      co[i][colour], seed := random (max.colour, seed)
      n, seed := random (size, seed)
      n := n - (size >> 1)
      co[i][row] := (height >> 1) + n
      n, seed := random (size, seed)
      n := n - (size >> 1)
      co[i][col] := (width >> 1) + n
      
:

PROC particle.main (CHAN BYTE keyboard?, SHARED CHAN BYTE screen!)
  
  [no.of.particles]COL.COORD co:
  
  INT screen.width, screen.height, init.size, seed:
  VAL INT max.digits IS 4:
  SEQ
    
    #IF FALSE
    CLAIM screen!
      SEQ --to be added later, once the program is working properly. 
        ask.int ("Please enter a screen width: ",
                  screen.width, max.digits, keyboard?, screen!)
        ask.int ("Please enter a screen height: ",
                  screen.height, max.digits, keyboard?, screen!)
        ask.int ("Please enter a size for the start area: ",
                  init.size, max.digits, keyboard?, screen!)
    #ELSE
    screen.width, screen.height, init.size := 640, 480, 2
    #ENDIF
  
    TIMER tim:
    SEQ
      tim ? seed
      seed := (seed >> 2) + 123
  
    init.coords (co, screen.height, screen.width, init.size, seed)

    SETPRI (6)
    
    BARRIER b:
    INITIAL INT radius IS 0: 
    #PRAGMA SHARED radius
    CHAN RASTER c:
    CHAN RASTER raster.0, raster.0a:
    CHAN RASTER raster.1:
    CHAN INT update, control:
    SHARED ! CHAN PART.INFO draw:
    CHAN BYTE report, pause, timer.pause:
     

    PAR  -- run the gridlink, and the G process similtaneoulsy. 
      raster.display.simple ("Particles", screen.width, screen.height, 2, raster.1?, raster.0!)
      drawing (draw?, raster.0?, raster.1!, screen!)
      SEQ
        SETPRI (5)
        keyboard.monitor (keyboard?, pause!, timer.pause!, control!)

      timer (report?, screen!)        
      PAR BARRIER b
        radius.control (control?, radius, b)
        time.stepper (time.step, b, report!, timer.pause?)  
        PAR i = 0 FOR no.of.particles BARRIER b
          particle (i, co[i], screen.height, screen.width, seed,
                    draw!, b, screen!, radius)

:
