--
--	Occade bouncing-balls example
--	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/>.
--

--** Simple demo application for Occade.
--
-- @module occade.bounce

#INCLUDE "occade.module"

--{{{ constants
VAL REAL32 BALL.RADIUS IS 9.0 / 2.0:
VAL REAL32 BANG.RADIUS IS 20.0 / 2.0:
VAL INT INITIAL.BALLS IS 500:
VAL INT EVIL.COUNT IS 10:
VAL REAL32 BOUNCE IS 0.75:
--}}}

--{{{ PROTOCOL BALL
--* Command to create a new ball.
-- @item x, y Initial position
-- @item dx, dy Initial velocity
PROTOCOL BALL
  CASE
    start; REAL32; REAL32; REAL32; REAL32
    stop
:
--}}}

--{{{ PROC ball
--* Animate a ball bouncing around the screen.
PROC ball (SHARED OCCADE! occade, VAL OCCADE.PARAMS params, VAL INT id,
           VAL REAL32 n.x, n.y, n.dx, n.dy,
           SHARED CHAN BALL new.ball!)
  OCCADE.SPRITE! sprite:
  INT n, life:
  INITIAL REAL32 x IS n.x:
  INITIAL REAL32 y IS n.y:
  INITIAL REAL32 dx IS n.dx:
  INITIAL REAL32 dy IS n.dy:
  VAL REAL32 max.x IS (REAL32 ROUND params[width]) - BALL.RADIUS:
  VAL REAL32 max.y IS (REAL32 ROUND params[height]) - BALL.RADIUS:
  TIMER tim:
  INT start, t:
  SEQ
    occade.start.sprite (occade, sprite, id)
    sprite[req] ! load.image; "images/green-ball.png"
    sprite[req] ! visible; TRUE
    sprite[req] ! collisions; TRUE

    occade.random (occade, 10000000, life)
    occade.random (occade, 400, n)
    dx := dx + ((REAL32 ROUND (n - 200)) / 100.0)
    occade.random (occade, 400, n)
    dy := dy + ((REAL32 ROUND (n - 200)) / 100.0)

    tim ? start
    t := start

    INITIAL INT stage IS 0:
    INITIAL INT remaining IS life:
    WHILE remaining > 0
      SEQ
        sprite[req] ! move; INT ROUND x; INT ROUND y; TRUE

        CASE stage
          0
            IF
              remaining < 5000000
                SEQ
                  sprite[req] ! load.image; "images/orange-ball.png"
                  stage := 1
              TRUE
                SKIP
          1
            IF
              remaining < 2000000
                SEQ
                  sprite[req] ! load.image; "images/red-ball.png"
                  stage := 2
              TRUE
                SKIP
          ELSE
            SKIP

        x, y := x + dx, y + dy
        IF
          x <= BALL.RADIUS
            x, dx, dy := BALL.RADIUS, ABS (dx) * BOUNCE, dy * BOUNCE
          x >= max.x
            x, dx, dy := max.x, (-ABS (dx)) * BOUNCE, dy * BOUNCE
          TRUE
            SKIP
        IF
          y <= BALL.RADIUS
            y, dx, dy := BALL.RADIUS, dx * BOUNCE, ABS (dy) * BOUNCE
          y >= max.y
            y, dx, dy := max.y, dx * BOUNCE, (-ABS (dy)) * BOUNCE
          TRUE
            SKIP

        --{{{ delay, while reading collision events
        -- This is one way of handling collision reporting -- most sprite
        -- behaviours are going to have a delay in them somewhere, so while
        -- your process would otherwise be twiddling its thumbs it can catch up
        -- on all the collisions that have happened lately.
        tim ? t
        INITIAL BOOL waiting IS TRUE:
        WHILE waiting
          PRI ALT
            OCCADE.COLLISION coll:
            sprite[collisions] ? coll
              #IF FALSE
              SEQ
                occade.write ("collide: ")
                occade.write.int (id)
                occade.write (" with ")
                occade.write.int (coll[id])
                occade.write (" ")
                occade.write.int (coll[x])
                occade.write ("*n")
              #ELSE
              SKIP
              #ENDIF
            tim ? AFTER t PLUS 10000
              waiting := FALSE
        --}}}

        remaining := life - (t MINUS start)

    --{{{ make the occasional evil ball explode
    occade.random (occade, EVIL.COUNT, n)
    IF
      n = 0
        SEQ
          sprite[req] ! move; INT ROUND x; INT ROUND y; TRUE
          sprite[req] ! load.image; "images/bang.png"
          tim ? t
          tim ? AFTER t PLUS 500000
          --{{{ ... and fire off some new balls
          CLAIM new.ball!
            SEQ i = 0 FOR EVIL.COUNT
              new.ball ! start; x; y; dx; dy
          --}}}
          tim ? t
          tim ? AFTER t PLUS 500000
      TRUE
        SKIP
    --}}}

    occade.finish.sprite (sprite)

    CLAIM new.ball!
      new.ball ! stop
:
--}}}

--{{{ PROC ball.factory
PROC ball.factory (SHARED OCCADE! occade, VAL OCCADE.PARAMS params,
                   SHARED CHAN BALL out!, CHAN BALL in?)
  OCCADE.SPRITE! count:
  SEQ
    occade.start.sprite (occade, count, -1)
    count[req] ! load.text; "Hello!"; "images/font10x20.png"
    count[req] ! move; 10; 10; FALSE
    count[req] ! visible; TRUE

    INITIAL INT id IS 0:
    INITIAL INT num.balls IS 0:
    MOBILE []BYTE s:
    INITIAL BOOL running IS TRUE:
    WHILE running
      SEQ
        in ? CASE
          REAL32 x, y, dx, dy:
          start; x; y; dx; dy
            SEQ
              FORK ball (occade, params, id, x, y, dx, dy, out!)
              id := id PLUS 1
              num.balls := num.balls + 1
          stop
            num.balls := num.balls - 1

        s := ""
        occade.append.int (s, num.balls)
        occade.append.string (s, " balls")
        count[req] ! load.text; s; "images/font10x20.png"

        running := num.balls > 0

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

--{{{ PROC input.handler
PROC input.handler (SHARED OCCADE! occade)
  OCCADE.INPUT! input:
  SEQ
    occade.start.input (occade, input)
    input[req] ! enable; (RASTER.ET.KEYDOWN \/ RASTER.ET.KEYUP) \/ RASTER.ET.QUIT

    INITIAL BOOL running IS TRUE:
    WHILE running
      RASTER.EVENT event:
      SEQ
        input[events] ? event
        occade.write ("got event: ")
        occade.write.int (event[type])
        occade.write (", ")
        occade.write.int (event[id])
        occade.write ("*n")
:
--}}}

--{{{ PROC main
--* Entry point.
PROC main ()
  SHARED OCCADE! occade:
  INITIAL OCCADE.PARAMS params IS occade.default.params:
  SEQ
    params[width] := 640
    params[height] := 480
    occade.start (occade, params, "Occade demo")

    occade.load.playfield (occade, "images/sky.png", 0, 0)
    SEQ x = 75 FOR 5 STEP 60
      occade.load.playfield (occade, "images/red-spot.png", 100 + x, x)

    FORKING
      SHARED! CHAN BALL new.ball:
      PAR
        ball.factory (occade, params, new.ball!, new.ball?)
        CLAIM new.ball!
          SEQ i = 0 FOR INITIAL.BALLS
            INT n:
            REAL32 x, y:
            SEQ
              occade.random (occade, params[width], n)
              x := REAL32 ROUND n
              occade.random (occade, params[height], n)
              y := REAL32 ROUND n
              new.ball ! start; x; y; 0.0; 0.0
        input.handler (occade)

    occade.check.sdl (FALSE, "demo done")
:
--}}}

