In this lesson, we will program a ball which bounces against the edges
of a box, and which is duplicated on every emission of a signal split
split
. Here is an example of what we aim to achieve:
lessons/lesson2/split.rml
We first define the data structure to represent the bounds of a box.
type box = { left: float; right: float; top: float; bot: float; }
We create a box.
let box = { left = 0.; right = 400.; bot = 0.; top = 400.; }
We display the box.
let () = let g = " " ^ (string_of_int (int_of_float (box.right -. box.left))) ^ "x" ^ (string_of_int (int_of_float (box.top -. box.bot))) in Graphics.open_graph g
We now define the data structure to represent the state of a ball.
type state = { pos: (float * float, float * float) event; speed: (float * float, float * float) event; radius: float; color: Graphics.color; }
It is a record whose
fields pos
, speed
, radius
and color
represent respectively the position, velocity,
radius and color of a ball.
The type of the field pos
is (float * float, float
* float) event
. That is, it is an event on which we can emit and
receive a tuple of floating numbers. It will represent the flow of
positions.
To observe the balls, we use a global signal named draw
on
which each ball will emit its state. All the emitted states are
collected into a list.
signal draw default [] gather (fun x y -> x :: y) ;;
The behavior of a ball bouncing into the limit of the box can be programmed as follows.
let process move state = loop (* emit the position *) emit draw state; (* compute the new position *) let pre_vx, pre_vy = last ?state.speed in let pre_x, pre_y = last ?state.pos in let vx = if box.left < pre_x && pre_x < box.right then pre_vx else -. pre_vx in let vy = if box.bot < pre_y && pre_y < box.top then pre_vy else -. pre_vy in let x, y = (pre_x +. vx, pre_y +. vy) in (* update the state *) emit state.speed (vx, vy); emit state.pos (x, y); pause end
The process is an infinite loop that first emits the current state, then computes the new position and then finally updates the state.
Let us now create a state. To do that, we have to define auxiliary functions. The first one associates a color to an integer.
let color_of_int n = match n mod 7 with | 0 -> Graphics.rgb 220 20 60 | 1 -> Graphics.blue | 2 -> Graphics.rgb 34 139 34 | 3 -> Graphics.red | 4 -> Graphics.rgb 150 150 150 | 5 -> Graphics.black | 6 -> Graphics.magenta | _ -> Graphics.black
The second function creates a vector of norm k
.
let random_speed k = let alpha = Random.float 7. in (k *. cos alpha, k *. sin alpha)
Now, a function which creates a value of type state
can be
defined as follows.
let new_state () = signal pos default ((box.right -. box.left) /. 2., (box.top -. box.bot) /. 2.) gather (fun x _ -> x) in signal speed default random_speed 2. gather (fun x _ -> x) in let color = color_of_int (Random.int 7) in { pos = pos; speed = speed; radius = 25.; color = color; }
The default value of the signal pos
is the center of the
box. The combination function only keeps one of the values emitted
during an instant.
Let us create a ball:
#run (move (new_state ())) ;;
To observe the position of the ball, we program a process which
displays the value of the draw
signal.
let process window = loop await draw (all) in Graphics.clear_graph(); List.iter (fun state -> let x, y = last ?state.pos in Graphics.set_color state.color; Graphics.fill_circle (int_of_float x) (int_of_float y) (int_of_float state.radius)) all end
#run window ;;
Now we want to create a ball which is duplicated each time a
signal split
is emitted.
signal split default () gather (fun () () -> ()) ;;
We first define a function which creates a new state from an existing one.
let new_state' state = signal pos default last ?state.pos gather fun x _ -> x in signal speed default random_speed 2. gather fun x _ -> x in let radius = max 1. (state.radius -. state.radius /. 5.) in let color = color_of_int (Random.int 7) in { pos = pos; speed = speed; radius = radius; color = color; }
Dynamic creation is achieved by combining recursion and parallel composition.
let rec process ball state = do run (move state) until split -> run (ball (new_state' state)) || run (ball (new_state' state)) done
#run (ball (new_state ())) ;;
Finally, each time we emit the split
event, the ball is duplicated.
emit split ;;
emit split ;;
Warning the JavaScript interpreter can stop if there is to many balls.