--
--	Parrot Attack game for Occade
--	Copyright (C) 2007  University of Kent
--
--	This program is free software; you can redistribute it and/or
--	modify it under the terms of the GNU General Public License as
--	published by the Free Software Foundation, either version 2 of the
--	License, or (at your option) any later version.
--
--	This program is distributed in the hope that it will be useful,
--	but WITHOUT ANY WARRANTY; without even the implied warranty of
--	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
--	General Public License for more details.
--
--	You should have received a copy of the GNU General Public License
--	along with this program.  If not, see
--	<http://www.gnu.org/licenses/>.
--

--** Shoot-em-up game for Occade.
--
-- @module occade.parrot_attack

#INCLUDE "occade.module"
#INCLUDE "maths.module"
#INCLUDE "shutdown.inc"
#USE "shutdown"

-- Define this if you're using CCSP.
#IF DEFINED (OCCBUILD.KROC)
#DEFINE USE.PROCESS.METER
#ENDIF

--*{{{ PRI Process priorities
VAL INT PRI.BUFFERS IS 10:
VAL INT PRI.ALIENS IS 17:
VAL INT PRI.MISSILES IS 15:
VAL INT PRI.SPARKS IS 19:
--*}}}

--{{{ miscellaneous constants
VAL REAL32 GRAVITY IS 0.01:
--}}}

--{{{ widths and heights
VAL INT WIDTH IS 640:
VAL INT HEIGHT IS 480:
VAL INT STARS.W IS 100:
VAL INT STARS.H IS 100:
VAL INT SHIP.W IS 20:
VAL INT SHIP.H IS 17:
VAL INT MISSILE.W IS 6:
VAL INT MISSILE.H IS 10:
VAL INT ALIEN.W IS 24:
VAL INT ALIEN.H IS 26:
VAL INT BOMB.W IS 6:
VAL INT BOMB.H IS 9:
--}}}

--{{{ sprite IDs
VAL INT SHIP.ID IS 1:
VAL INT MISSILE.ID IS 2:
VAL INT ALIEN.ID IS 3:
VAL INT BOMB.ID IS 4:
--}}}

--{{{ the alien fleet
VAL INT ALIENS.DX IS ALIEN.W + 10:
VAL INT ALIENS.DY IS ALIEN.H + 10:
VAL INT ALIENS.X IS ((6 * WIDTH) / 10) / ALIENS.DX:
VAL INT ALIENS.Y IS ((4 * WIDTH) / 10) / ALIENS.DY:
--}}}

--{{{ alien mind control
DATA TYPE ALIEN.CONTROL
  RECORD
    INT x, y:
    BOOL right:
:
--}}}

--{{{ PROTOCOL SCORE
--* Score reports.
PROTOCOL SCORE
  CASE
    score; INT
    quit
:
--}}}

--{{{ PROC scorekeeper
--* Keep track of the score, and render the score display.
PROC scorekeeper (SHARED OCCADE! occade, CHAN SCORE in?, INT score)
  OCCADE.SPRITE! score.display:
  SEQ
    occade.start.sprite (occade, score.display, -1)
    score.display[req] ! move; 10; 10; FALSE
    score.display[req] ! visible; TRUE

    INITIAL BOOL running IS TRUE:
    WHILE running
      MOBILE []BYTE s:
      SEQ
        s := "Score: "
        occade.append.int (s, score)
        score.display[req] ! load.text; s; "images/font10x20.png"

        in ? CASE
          INT n:
          score; n
            score := score + n
          quit
            running := FALSE

    occade.finish.sprite (score.display)
:
--}}}

#IF DEFINED (USE.PROCESS.METER)
#PRAGMA EXTERNAL "PROC C.ccsp.get.process.count (INT count) = 0"
--{{{ PROC process.meter
--* Show the count of running processes.
PROC process.meter (SHARED OCCADE! occade)
  VAL INT UPDATE.TIME IS 100000:

  OCCADE.SPRITE! process.display:
  TIMER tim:
  INT t:
  SEQ
    occade.start.sprite (occade, process.display, -1)
    process.display[req] ! visible; TRUE

    tim ? t
    t := t PLUS UPDATE.TIME

    INITIAL BOOL running IS TRUE:
    WHILE running
      INT count:
      MOBILE []BYTE s:
      SEQ
        C.ccsp.get.process.count (count)

        s := ""
        occade.append.int (s, count)
        occade.append.string (s, " processes")
        process.display[req] ! move; (WIDTH - 10) - (10 * (SIZE s)); 10; FALSE
        process.display[req] ! load.text; s; "images/font10x20.png"

        tim ? AFTER t
        t := t PLUS UPDATE.TIME

    occade.finish.sprite (process.display)
:
--}}}
#ENDIF

--{{{ PROC starfield
--* Draw a slowly-scrolling starfield in the background.
PROC starfield (SHARED OCCADE! occade)
  TIMER tim:
  INT t:
  SEQ
    tim ? t
    INITIAL BOOL running IS TRUE:
    WHILE running
      SEQ i = 0 FOR STARS.H
        SEQ
          SEQ x = 0 FOR (WIDTH / STARS.W) + 1
            SEQ y = -1 FOR (HEIGHT / STARS.H) + 2
              occade.load.playfield (occade, "images/stars.png", x * STARS.W, (y * STARS.H) + i)
          t := t PLUS 50000
          tim ? AFTER t
:
--}}}

--{{{ PROC spark
--* Draw a spark from an explosion.
PROC spark (SHARED OCCADE! occade, VAL INT init.x, init.y, init.dx, init.dy)
  OCCADE.SPRITE! sprite:
  TIMER tim:
  INT t:
  INITIAL REAL32 x IS REAL32 ROUND init.x:
  INITIAL REAL32 y IS REAL32 ROUND init.y:
  INITIAL REAL32 dx IS (((REAL32 ROUND init.dx) / 99.0) - 0.5) * 2.0:
  INITIAL REAL32 dy IS (((REAL32 ROUND init.dy) / 99.0) - 0.5) * 2.0:
  SEQ
    occade.start.sprite (occade, sprite, -1)
    sprite[req] ! load.image; "images/spark.png"
    sprite[req] ! visible; TRUE

    tim ? t

    INITIAL BOOL running IS TRUE:
    WHILE running
      SEQ
        sprite[req] ! move; INT ROUND x; INT ROUND y; TRUE
        x := x + dx
        y := y + dy
        dy := dy + GRAVITY
        IF
          x < 0.0
            running := FALSE
          x >= (REAL32 ROUND WIDTH)
            running := FALSE
          y < 0.0
            running := FALSE
          y >= (REAL32 ROUND HEIGHT)
            running := FALSE
          TRUE
            SKIP
        t := t PLUS 5000
        tim ? AFTER t

    occade.finish.sprite (sprite)
:
--}}}

--{{{ PROC feather
--* Draw a feather from an explosion.
PROC feather (SHARED OCCADE! occade, VAL INT init.x, init.y, init.dx, init.dy)
  OCCADE.SPRITE! sprite:
  TIMER tim:
  INT t:
  INITIAL REAL32 x IS REAL32 ROUND init.x:
  INITIAL REAL32 y IS REAL32 ROUND init.y:
  INITIAL REAL32 dx IS (((REAL32 ROUND init.dx) / 99.0) - 0.5) * 0.6:
  INITIAL REAL32 dy IS (((REAL32 ROUND init.dy) / 99.0) - 0.5) * 1.0:
  SEQ
    occade.start.sprite (occade, sprite, -1)
    INT f:
    SEQ
      occade.random (occade, 2, f)
      INITIAL [19]BYTE s IS "images/featherX.png":
      SEQ
        s[14] := '1' + (BYTE f)
        sprite[req] ! load.image; s
    sprite[req] ! visible; TRUE

    tim ? t

    INITIAL BOOL running IS TRUE:
    WHILE running
      SEQ
        sprite[req] ! move; INT ROUND x; INT ROUND y; TRUE
        x := x + dx
        y := y + dy
        dy := dy + GRAVITY
        --{{{ feathers have a low terminal velocity...
        IF
          dy > 0.4
            dy := 0.4
          TRUE
            SKIP
        --}}}
        --{{{ ... and are affected by wind resistance horizontally
        IF
          ABS (dx) < 0.001
            dx := 0.0
          TRUE
            dx := dx * 0.995
        --}}}
        IF
          x < 0.0
            running := FALSE
          x >= (REAL32 ROUND WIDTH)
            running := FALSE
          y < 0.0
            running := FALSE
          y >= (REAL32 ROUND HEIGHT)
            running := FALSE
          TRUE
            SKIP
        t := t PLUS 5000
        tim ? AFTER t

    occade.finish.sprite (sprite)
:
--}}}

--{{{ PROC explosion
--* Spawn an explosion at the given location.
PROC explosion (SHARED OCCADE! occade, VAL INT x, y, num.sparks)
  SEQ i = 0 FOR num.sparks
    INT dx, dy, f:
    SEQ
      SETPRI (PRI.SPARKS)
      occade.random (occade, 100, dx)
      occade.random (occade, 100, dy)
      occade.random (occade, 3, f)
      IF
        f = 0
          FORK feather (occade, x, y, dx, dy)
        TRUE
          FORK spark (occade, x, y, dx, dy)
:
--}}}

--{{{ PROC alien.controller
--* Control the position of the alien fleet.
PROC alien.controller (CHAN ALIEN.CONTROL out!, CHAN BOOL ack?, VAL INT start.time, level)
  VAL INT x.range IS ((8 * WIDTH) / 10) - (ALIENS.X * ALIENS.DX):
  VAL INT left IS (WIDTH - (x.range + (ALIENS.X * ALIENS.DX))) / 2:
  VAL INT y.step IS ALIENS.DY:
  VAL INT top IS 2 * y.step:
  VAL INT row.time IS occade.clamp.int (4000000 - (400000 * level), 100000, 4000000):
  VAL INT step.time IS row.time / x.range:
  TIMER tim:
  INITIAL INT t IS start.time:

  --{{{ group behaviour
  SEQ
    INT x, y:
    INITIAL BOOL right IS TRUE:
    INITIAL BOOL running IS TRUE:
    PROC send ()
      IF
        running
          SEQ
            tim ? AFTER t
            t := t PLUS step.time
            out ! [x, y, right]
            ack ? running
        TRUE
          SKIP
    :
    INITIAL INT row IS 0:
    WHILE running
      SEQ
        x := left
        y := top + (row * y.step)
        right := TRUE
        --{{{ first you march from left to right
        SEQ i = 0 FOR x.range
          SEQ
            send ()
            x := x + 1
        --}}}
        --{{{ then down a row
        SEQ i = 0 FOR y.step
          SEQ
            send ()
            y := y + 1
        row := row + 1
        --}}}
        right := FALSE
        --{{{ then from right to left
        SEQ i = 0 FOR x.range
          SEQ
            send ()
            x := x - 1
        --}}}
        --{{{ then down another row
        SEQ i = 0 FOR y.step
          SEQ
            send ()
            y := y + 1
        row := row + 1
        --}}}
  --}}}
:
--}}}

--{{{ PROC bomb
--* Draw a bomb dropped by an alien.
PROC bomb (SHARED OCCADE! occade, VAL INT init.x, init.y)
  OCCADE.SPRITE! sprite:
  TIMER tim:
  INT last.update:
  SEQ
    SETPRI (PRI.MISSILES)

    occade.start.sprite (occade, sprite, BOMB.ID)
    sprite[req] ! load.image; "images/egg.png"
    sprite[req] ! visible; TRUE
    sprite[req] ! collisions; TRUE

    tim ? last.update

    VAL INT update.time IS 3000:
    INITIAL INT x IS init.x:
    INITIAL INT y IS init.y:
    INITIAL BOOL running IS TRUE:
    WHILE running
      SEQ
        sprite[req] ! move; x; y; TRUE

        PRI ALT
          OCCADE.COLLISION coll:
          sprite[collisions] ? coll
            --{{{
            CASE coll[id]
              SHIP.ID, MISSILE.ID
                SEQ
                  explosion (occade, x, y, 60)
                  running := FALSE
              ELSE
                SKIP
            --}}}
          tim ? AFTER last.update PLUS update.time
            --{{{
            SEQ
              y := y + 1
              last.update := last.update PLUS update.time
            --}}}

        IF
          y > (HEIGHT + BOMB.H)
            running := FALSE
          TRUE
            SKIP

    occade.finish.sprite (sprite)
:
--}}}

--{{{ PROC set.alien.shape
--* Cycle through the possible shapes for an alien.
PROC set.alien.shape (OCCADE.SPRITE! sprite, INT shape, VAL BOOL right)
  SEQ
    IF
      right
        CASE shape
          0
            sprite[req] ! load.image; "images/parrot1.png"
          1
            sprite[req] ! load.image; "images/parrot2.png"
      TRUE
        CASE shape
          0
            sprite[req] ! load.image; "images/parrot3.png"
          1
            sprite[req] ! load.image; "images/parrot4.png"
    shape := (shape + 1) \ 2
:
--}}}

--{{{ PROC alien
--* Draw an alien.
PROC alien (SHARED OCCADE! occade, SHARED SHUTDOWN.CTL! shutdown,
            SHARED CHAN SCORE scores!, VAL INT start.time,
            VAL INT level, my.x, my.y, BOOL status)
  CHAN ALIEN.CONTROL ctrl:
  CHAN BOOL ctrl.ack:
  SHUTDOWN! shutdown.cli:
  OCCADE.SPRITE! sprite:
  INITIAL INT shape IS 0:
  TIMER tim:
  INT shape.delay, last.shape.time:
  INT next.bomb.time, n:
  PROC load.bomb ()
    VAL INT max.time IS occade.clamp.int (60000000 - (level * 4000000), 200000, 60000000):
    SEQ
      occade.random (occade, max.time, n)
      next.bomb.time := next.bomb.time PLUS n
  :
  ALIEN.CONTROL ac:
  PAR
    alien.controller (ctrl!, ctrl.ack?, start.time, level)
    SEQ
      SETPRI (PRI.ALIENS)

      --{{{ start up
      shutdown.connect (shutdown, shutdown.cli)

      ctrl ? ac
      ctrl.ack ! TRUE

      occade.start.sprite (occade, sprite, ALIEN.ID)
      set.alien.shape (sprite, shape, ac[right])
      sprite[req] ! visible; TRUE
      sprite[req] ! collisions; TRUE

      occade.random (occade, 300000, shape.delay)
      shape.delay := shape.delay + 200000
      tim ? last.shape.time

      tim ? next.bomb.time
      load.bomb ()

      status := FALSE
      --}}}
      --{{{ main behaviour
      INITIAL BOOL running IS TRUE:
      WHILE running
        INITIAL INT home.x IS my.x + ac[x]:
        INITIAL INT home.y IS my.y + ac[y]:
        SEQ
          sprite[req] ! move; home.x; home.y; TRUE

          PRI ALT
            BOOL b:
            shutdown.cli[kill] ? b
              running := FALSE
            OCCADE.COLLISION coll:
            sprite[collisions] ? coll
              --{{{
              SEQ
                CASE coll[id]
                  MISSILE.ID
                    SEQ
                      sprite[req] ! visible; FALSE
                      explosion (occade, home.x, home.y, 30)
                      status := TRUE
                      CLAIM scores!
                        scores ! score; (1000 * (HEIGHT - home.y)) / HEIGHT
                      running := FALSE
                  ELSE
                    SKIP
              --}}}
            ctrl ? ac
              ctrl.ack ! TRUE
            tim ? AFTER last.shape.time PLUS shape.delay
              --{{{
              SEQ
                set.alien.shape (sprite, shape, ac[right])
                last.shape.time := last.shape.time PLUS shape.delay
              --}}}
            tim ? AFTER next.bomb.time
              --{{{
              SEQ
                FORK bomb (occade, home.x, home.y)
                load.bomb ()
              --}}}

          --{{{ have we reached the bottom of the screen?
          IF
            home.y >= (HEIGHT - (2 * SHIP.H))
              --{{{ yes -- stop the game, the invasion has been successful
              CLAIM shutdown
                shutdown[req] ! kill
              --}}}
            TRUE
              SKIP
          --}}}
      --}}}

      shutdown.finish (shutdown.cli)
      occade.finish.sprite (sprite)

      ctrl ? ac
      ctrl.ack ! FALSE
:
--}}}

--{{{ PROC missile
--* Draw a missile fired by the player.
PROC missile (SHARED OCCADE! occade, VAL INT init.x, init.y)
  OCCADE.SPRITE! sprite:
  TIMER tim:
  INT last.update:
  SEQ
    SETPRI (PRI.MISSILES)

    occade.start.sprite (occade, sprite, MISSILE.ID)
    sprite[req] ! load.image; "images/missile.png"
    sprite[req] ! visible; TRUE
    sprite[req] ! collisions; TRUE

    tim ? last.update

    VAL INT update.time IS 2000:
    INITIAL INT x IS init.x:
    INITIAL INT y IS init.y:
    INITIAL BOOL running IS TRUE:
    WHILE running
      SEQ
        sprite[req] ! move; x; y; TRUE

        PRI ALT
          OCCADE.COLLISION coll:
          sprite[collisions] ? coll
            --{{{
            CASE coll[id]
              ALIEN.ID, BOMB.ID
                SEQ
                  explosion (occade, x, y, 10)
                  running := FALSE
              ELSE
                SKIP
            --}}}
          tim ? AFTER last.update PLUS update.time
            --{{{
            SEQ
              y := y - 1
              last.update := last.update PLUS update.time
            --}}}

        IF
          y < (-MISSILE.H)
            running := FALSE
          TRUE
            SKIP

    occade.finish.sprite (sprite)
:
--}}}

--{{{ PROC ship
--* Draw the player's ship.
PROC ship (SHARED OCCADE! occade, SHARED SHUTDOWN.CTL! shutdown)
  SHUTDOWN! shutdown.cli:
  OCCADE.INPUT! input:
  OCCADE.SPRITE! sprite, shield.display:
  INITIAL INT shields IS 100:
  TIMER tim:
  INT last.update:
  SEQ
    occade.start.input (occade, input)
    input[req] ! enable; RASTER.ET.KEYDOWN \/ RASTER.ET.KEYUP

    occade.start.sprite (occade, sprite, SHIP.ID)
    sprite[req] ! load.image; "images/ship.png"
    sprite[req] ! visible; TRUE
    sprite[req] ! collisions; TRUE

    occade.start.sprite (occade, shield.display, -1)
    shield.display[req] ! visible; TRUE

    shutdown.connect (shutdown, shutdown.cli)

    tim ? last.update

    VAL INT update.time IS 3000:
    INITIAL INT x IS WIDTH / 2:
    INITIAL INT y IS HEIGHT - SHIP.H:
    INITIAL INT dir IS 0:
    INITIAL BOOL running IS TRUE:
    INITIAL INT last.shields IS -1:
    WHILE running
      PROC move ()
        SEQ
          x := occade.clamp.int (x + dir, SHIP.W / 2, WIDTH - SHIP.W)
          sprite[req] ! move; x; y; TRUE

          IF
            shields <> last.shields
              MOBILE []BYTE s:
              SEQ
                s := "Shields: "
                occade.append.int (s, shields)
                occade.append.string (s, "%")
                shield.display[req] ! load.text; s; "images/font10x20.png"
                shield.display[req] ! move; WIDTH / 2; 20; TRUE
                last.shields := shields
            TRUE
              SKIP
      :
      PRI ALT
        RASTER.EVENT event:
        input[events] ? event
          --{{{
          CASE event[type]
            RASTER.ET.KEYDOWN
              --{{{
              CASE event[id]
                SDLK.LEFT
                  SEQ
                    dir := -1
                    move ()
                SDLK.RIGHT
                  SEQ
                    dir := 1
                    move ()
                ' '
                  FORK missile (occade, x, y)
                ELSE
                  SKIP
              --}}}
            RASTER.ET.KEYUP
              --{{{
              CASE event[id]
                SDLK.LEFT
                  IF
                    dir = (-1)
                      dir := 0
                    TRUE
                      SKIP
                SDLK.RIGHT
                  IF
                    dir = 1
                      dir := 0
                    TRUE
                      SKIP
                ELSE
                  SKIP
              --}}}
          --}}}
        OCCADE.COLLISION coll:
        sprite[collisions] ? coll
          --{{{
          CASE coll[id]
            BOMB.ID
              INT n:
              SEQ
                occade.random (occade, 10, n)
                n := n + 10
                shields := occade.clamp.int (shields - n, 0, 100)
                IF
                  shields = 0
                    --{{{ blow up
                    SEQ
                      explosion (occade, x, y, 50)
                      CLAIM shutdown
                        shutdown[req] ! kill
                    --}}}
                  TRUE
                    SKIP
            ELSE
              SKIP
          --}}}
        BOOL b:
        shutdown.cli[kill] ? b
          running := FALSE
        tim ? AFTER last.update PLUS update.time
          --{{{
          SEQ
            move ()
            last.update := last.update PLUS update.time
          --}}}

    shutdown.finish (shutdown.cli)
    occade.finish.sprite (sprite)
    occade.finish.sprite (shield.display)
    occade.finish.input (input)
:
--}}}

--{{{ PROC start.text
--* Scroll a sprite into place that's displaying some text.
PROC start.text (SHARED OCCADE! occade, RESULT OCCADE.SPRITE! sprite, VAL []BYTE text, font, VAL INT x, y)
  TIMER tim:
  INT t:
  SEQ
    occade.start.sprite (occade, sprite, -1)
    sprite[req] ! load.text; text; font
    tim ? t
    SEQ i = -30 FOR y + 30
      SEQ
        sprite[req] ! move; x; i; TRUE
        t := t PLUS 700
        tim ? AFTER t
    sprite[req] ! visible; TRUE
:
--}}}

--{{{ PROC wait.for.key
--* Wait for a key to be pressed.
PROC wait.for.key (SHARED OCCADE! occade, VAL INT key, VAL []BYTE message)
  OCCADE.INPUT! input:
  OCCADE.SPRITE! sprite:
  SEQ
    start.text (occade, sprite, message,
                "images/font10x20.png", WIDTH / 2, (HEIGHT * 7) / 8)

    occade.start.input (occade, input)
    input[req] ! enable; RASTER.ET.KEYDOWN
    INITIAL BOOL waiting IS TRUE:
    WHILE waiting
      RASTER.EVENT event:
      SEQ
        input[events] ? event
        waiting := (event[id] <> key)
    occade.finish.input (input)

    occade.finish.sprite (sprite)
:
--}}}

--{{{ PROC title.screen
--* Draw the title screen.
PROC title.screen (SHARED OCCADE! occade)
  OCCADE.SPRITE! title, byline, keys, alien, ship:
  INITIAL INT shape IS 0:
  SEQ
    start.text (occade, title, "P A R R O T   A T T A C K",
                "images/font10x20.png", WIDTH / 2, (HEIGHT / 4) - 12)
    start.text (occade, byline, "Concurrent programming with occam-pi",
                "images/font10x20.png", WIDTH / 2, (HEIGHT / 4) + 12)
    start.text (occade, keys, "Left and right to move - Space bar to fire",
                "images/font10x20.png", WIDTH / 2, (HEIGHT * 5) / 8)

    occade.start.sprite (occade, alien, -1)
    set.alien.shape (alien, shape, TRUE)
    alien[req] ! visible; TRUE

    occade.start.sprite (occade, ship, -1)
    ship[req] ! load.image; "images/ship.png"
    ship[req] ! visible; TRUE

    CHAN BOOL stop:
    PAR
      --{{{ wait for a keypress
      SEQ
        wait.for.key (occade, 's', "Hit s to start")
        stop ! FALSE
      --}}}
      --{{{ animate the characters
      VAL REAL32 xr IS REAL32 ROUND ((3 * WIDTH) / 8):
      VAL REAL32 yr IS REAL32 ROUND (HEIGHT / 5):
      TIMER tim:
      INT t, last.shape.time:
      SEQ
        tim ? t
        tim ? last.shape.time
        INITIAL REAL32 theta IS 0.0:
        INITIAL BOOL running IS TRUE:
        WHILE running
          SEQ
            theta := theta + 0.001
            IF
              theta > (2.0 * PI)
                theta := theta - (2.0 * PI)
              TRUE
                SKIP

            VAL INT x IS INT ROUND (xr * COS (theta)):
            VAL INT y IS INT ROUND (yr * SIN (theta)):
            SEQ
              alien[req] ! move; (WIDTH / 2) + x; (HEIGHT / 4) + y; TRUE
              ship[req] ! move; (WIDTH / 2) - x; (HEIGHT / 4) - y; TRUE

            PRI ALT
              stop ? running
                SKIP
              VAL INT shape.time IS 300000:
              tim ? AFTER last.shape.time PLUS shape.time
                SEQ
                  set.alien.shape (alien, shape, theta > PI)
                  last.shape.time := last.shape.time PLUS shape.time
              tim ? AFTER t PLUS 1000
                t := t PLUS 1000
      --}}}

    occade.finish.sprite (title)
    occade.finish.sprite (byline)
    occade.finish.sprite (keys)
    occade.finish.sprite (alien)
    occade.finish.sprite (ship)
:
--}}}

--{{{ PROC level.screen
--* Draw the start-of-level message.
PROC level.screen (SHARED OCCADE! occade, VAL INT level)
  OCCADE.SPRITE! text:
  MOBILE []BYTE s:
  SEQ
    s := "Level "
    occade.append.int (s, level)
    start.text (occade, text, s,
                "images/font10x20.png", WIDTH / 2, HEIGHT / 2)
    occade.delay (500000)
    occade.finish.sprite (text)
:
--}}}

--{{{ PROC death.screen
--* Draw the death screen.
PROC death.screen (SHARED OCCADE! occade, VAL INT score)
  OCCADE.INPUT! input:
  OCCADE.SPRITE! message, score.display:
  MOBILE []BYTE s:
  SEQ
    start.text (occade, message, "The alien invasion was successful.",
                "images/font10x20.png", WIDTH / 2, HEIGHT / 4)

    s := "You scored "
    occade.append.int (s, score)
    occade.append.string (s, " points")
    start.text (occade, score.display, s,
                "images/font10x20.png", WIDTH / 2, (HEIGHT * 5) / 8)

    explosion (occade, WIDTH / 2, HEIGHT / 2, 200)

    wait.for.key (occade, 's', "Hit s to continue")

    occade.finish.sprite (message)
    occade.finish.sprite (score.display)
:
--}}}

--{{{ PROC play.round
--* Play a round of the game (i.e. until the user wins or loses).
PROC play.round (SHARED OCCADE! occade, VAL INT level,
                 SHARED CHAN SCORE scores!, BOOL result)
  SHARED SHUTDOWN.CTL! shutdown:
  [ALIENS.X][ALIENS.Y]BOOL alien.status:
  SEQ
    start.shutdown.server (shutdown)

    PAR
      ship (occade, shutdown)
      --{{{ aliens
      TIMER tim:
      INT start.time:
      SEQ
        tim ? start.time
        PAR x = 0 FOR ALIENS.X
          PAR y = 0 FOR ALIENS.Y
            alien (occade, shutdown, scores!, start.time,
                   level, x * ALIENS.DX, y * ALIENS.DY, alien.status[x][y])
        CLAIM shutdown
          shutdown[req] ! kill
    --}}}

    --{{{ did the player shoot all the aliens?
    IF
      IF x = 0 FOR ALIENS.X
        IF y = 0 FOR ALIENS.Y
          alien.status[x][y] = FALSE
            result := FALSE
      TRUE
        result := TRUE
    --}}}

    CLAIM shutdown
      shutdown[req] ! quit
:
--}}}

--{{{ PROC main
--* Entry point.
PROC main ()
  SHARED OCCADE! occade:
  INITIAL OCCADE.PARAMS params IS occade.default.params:

  FORKING
    SEQ
      params[width] := WIDTH
      params[height] := HEIGHT
      occade.start (occade, params, "Parrot Attack")

      PAR
        starfield (occade)
        #IF DEFINED (USE.PROCESS.METER)
        process.meter (occade)
        #ENDIF
        WHILE TRUE
          INITIAL INT score IS 0:
          SEQ
            title.screen (occade)

            SHARED! CHAN SCORE scores:
            PAR
              scorekeeper (occade, scores?, score)
              SEQ
                INITIAL BOOL alive IS TRUE:
                INITIAL INT level IS 1:
                WHILE alive
                  SEQ
                    level.screen (occade, level)
                    play.round (occade, level, scores!, alive)
                    level := level + 1
                CLAIM scores!
                  scores ! quit

            death.screen (occade, score)
:
--}}}
