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.