One describes the main parts of the code of a demo of a
small preys-predators system implemented for the GBA platform. A predator
moves using the arrow keys. Preys are running away from predators and
are killed when a predator is sufficiently close to them. New preys
are automatically created. A predator is put in an automatic chasing
mode with key 'a' and returns in the manual mode with 'b'. More
predators can be created with 'l', and more preys with 'r'. A
screenshot of the demo running on the VisualBoyAdvance emulator is
shown on figure Demo. The predator is the ghost and the
preys are the pacmans. This demo is available at the Web site
http://www.gbadev.org.
Fig. 1: Demo
One call
sprites the basic moving graphical objects to which
reactive behaviors are associated. Sprites are in limited number in the GBA
(actually, 128). The type of sprites
sprite_t is defined on figure
Sprites.
typedef struct
{
s16 x;
s16 y;
s16 sx;
s16 sy;
u8 index;
thread_t *behaviors;
u8 behav_num;
event_t destroy;
u8 dead;
}
struct_sprite_t, *sprite_t;
Fig. 2: Sprites
The types
s16 and
u8 are integer types (actually,
signed 16 bits, and unsigned 8 bits). Fields
x and
y are the sprite coordinates.
sx and
sy define
the speed vector of the sprite.
index is the index of the
sprite in the table where sprites are stored.
behaviors is
an array of threads in elementary behaviors of the
sprite can be stored. The size of
behaviors is stored in
behav_num. The event
destroy is used to signal the
destruction of the sprite. Finally, the boolean
dead is used
to note that the sprite is destroyed.
Destruction of a sprite
is performed using the module
destroy_sprite shown on figure
Destruction. First, all components of
behaviors are stopped. Then, at the next instant, they are
deallocated (by
thread_deallocate) with the sprite structure
itself. Finally, the executing thread (returned by
self) is
deallocated.
module destroy_sprite (sprite_t s)
{
int i;
sprite_t s = local(s);
for (i=0;i<s->behav_num;i++) stop (s->behaviors[i]);
}
cooperate;
{
int i;
sprite_t s = local(s);
suppress_sprite (s->index);
for (i=0;i<s->behav_num;i++) thread_deallocate (s->behaviors[i]);
event_deallocate (s->destroy);
free (s->behaviors);
free (s);
thread_deallocate (self ());
}
end module
Fig. 3: Destruction
Because of the
cooperate, threads are actually stopped at
instant N+1, and are deallocated at instant N+2 (remember thar calls to
stop and to
thread_deallocate produce orders which
are not immediately processed, but are delayed to the next instant).
Basically, keys are converted into events. The function
create_key_events of figure
Create Key Events creates the 8 events needed.
void create_key_events (void)
{
a_pressed = event_create ();
b_pressed = event_create ();
up_pressed = event_create ();
down_pressed = event_create ();
left_pressed = event_create ();
right_pressed = event_create ();
r_pressed = event_create ();
l_pressed = event_create ();
}
Fig. 4: Create Key Events
The purpose of the module
key_handler of figure
Key Handler
is to convert at each
instant, the key press (obtained through
the function
pressed_key) into the corresponding events
(remark that several keys can be simultaneously processed).
module key_handler ()
while (1) do
{
if (pressed_key (KEY_A)) generate (a_pressed);
if (pressed_key (KEY_B)) generate (b_pressed);
if (pressed_key (KEY_UP)) generate (up_pressed);
if (pressed_key (KEY_DOWN)) generate (down_pressed);
if (pressed_key (KEY_LEFT)) generate (left_pressed);
if (pressed_key (KEY_RIGHT)) generate (right_pressed);
if (pressed_key (KEY_R)) generate (r_pressed);
if (pressed_key (KEY_L)) generate (l_pressed);
}
cooperate;
end
end module
Fig. 5: Key Handler
The module
key_processing of
Key Processing
associates a callback
(of type
void (*callback_t) ()) to a key. 10 instants
are passed when a key press is detected, in order to give enough
time for the key to be released.
module key_processing (event_t key_pressed,callback_t callback)
while(1) do
await (local(key_pressed));
{
local(callback) ();
}
repeat (10) do cooperate; end
end
end module
Fig. 6: Key Processing
One describes several elementary behaviors which will be used by sprites:
inertia, moves in the 4 directions, and collision.
A sprite gets an inertial behavior
with the
inertia module of figure
Inertia. In it, the sprite given in
parameter increments at each instant its coordinates according to its
speed.
module inertia (sprite_t s)
while (1) do
{
sprite_t s = local(s);
s->x += s->sx/INERTIA_FACTOR;
s->y += s->sy/INERTIA_FACTOR;
}
cooperate;
end
end module
Fig. 7: Inertia
With the module
move_right of figure
Move Right, a sprite moves
when the event
right_pressed is present. Similar modules
are defined for the 3 other directions.
void go_right (sprite_t s,s16 dist)
{
s->x += dist;
if (s->x > MAX_BORDER_X) s->x = MAX_BORDER_X;
}
module move_right (sprite_t s,int dist)
while (1) do
await (right_pressed);
go_right (local(s),local(dist))
cooperate;
end
end module
Fig. 8: Move Right
The collision elementary
behavior of a sprite is described in the module
collide of
figure
Collision. Collision detection is related to a
special event which is generated by all sprites subject to
collision. This event is generated by all these sprites, at
each instant.
The body of the module collide is an infinite loop in
which the collision event collide_evt is generated at each
instant. The sprite which generates the event is passed as value to
the generate_value function. Then, all generations of collide_evt are considered in turn, and the function collide is called for all (except of course itself) the generated
values, that are the other sprites. The return code becomes different
from OK (more precisely: ENEXT) when all the sprites are processed. This is
detected at the next instant, and there is thus no need of an explicit
cooperate to prevent the loop to be instantaneous.
static void collision (sprite_t s1,sprite_t s2)
{
u16 dx = s2->x - s1->x;
u16 dy = s2->y - s1->y;
if (dist2 (s1,s2) > min_collision) return;
s1->sx = -s1->sx;
s1->sy = -s1->sy;
if (dx>0) go_left (s1,dx); else go_right (s1,-dx);
if (dy>0) go_up (s1,dy); else go_down (s1,-dy);
}
module collide (sprite_t me,event_t collide_evt)
local sprite_t other, int i, int exit;
while (1) do
{
local(i) = 0;
local(exit) = 0;
}
generate_value(local(collide_evt), (void*)local(me));
while(!local(exit)) do
get_value (local(collide_evt),local(i), (void**)&local(other));
{
if (return_code () == OK) {
if (local(me) != local(other)) collision (local(me),local(other));
local(i)++;
} else {
local(exit) = 1;
}
}
end
end
Fig. 9: Collision
The collision function implements a very basic
algorithm which leads to some unrealistic moves\; it can of
course be replaced by more realistic (but more expensive!) collision
treatments. Moreover, each sprite considers all others sprites, at each instant
which is inefficient (complexity in the square of the number of sprites). The
algorithm could certainly also be improved in this respect.
The basic behavior of a predator is defined as the module
predator
shown in figure
Predator Behavior.
module predator (sprite_t me,event_t pred_evt,event_t prey_evt)
local sprite_t other, sprite_t closest,
int min, int i, int finished;
while (1) do
{
local(i) = 0;
local(finished) = 0;
local(min) = predator_visibility;
generate_value (local(pred_evt), (void*)local(me));
}
while (!local(finished)) do
get_value (local(prey_evt),local(i), (void**)&local(other));
if (local(me)->dead) then return; end
{
if (return_code () == OK) {
int d2 = dist2 (local(me),local(other));
if (local(me) != local(other) && d2 < local(min)) {
local(min) = d2;
local(closest) = local(other);
}
local(i)++;
} else {
local(finished) = 1;
if (local(min) < predator_visibility && !local(closest)->dead)
chase (local(me),local(closest));
}
}
end
end
end module
Fig. 10: Predator Behavior
At each instant, the event
pred_evt is generated by each
predator. It is a valued generation, in which the sprite is passed as
value. Then, all values associated to the event
prey_event
are considered in turn. When a new value is available, one tests if
the sprite is dead (it could have been killed just before)\;
the behavior returns if it is the case. Otherwise, the return code
is considered:
- if it is OK, then
other
denotes the other sprite. The distance to it is computed (actually,
the square of it, returned by dist2), and if the other sprite is
distinct from the sprite and closer than the one in the
field closest, then closest is changed.
- otherwise, the closest prey is chased, provided it can be seen
by the predator (according to the global variable
predator_visibility) and provided it is still alive (according to
its death field). The destroy event of the prey is
generated when it is killed.
The module defining a predator sprite is called
predator_sprite and is defined in figure
Predator Sprite.
It holds a local variable
inertia which stores
the inertia thread giving the inertial behavior to the sprite.
Initially,
inertial is suspended and the predator is thus
immobile. When receving the event
a_pressed,
inertia is resumed, and it is suspended again with
b_pressed. Initially, an atomic action is first executed which
performs the following actions:
-
A new sprite
s is first created with a zero speed vector and a ghost aspect.
- An instance of the module
predator is created. The
sprite and the events pred_evt and prey_evt are
passed to it.
- Several threads are created, all of them taking
s as
parameter: an instance of sync_sprite to perform
synchronisation of the sprite with the corresponding entry in the
table of sprites\; four instances of move to move the sprite
in the 4 directions, according to 4 corresponding events\; an instance
of bounce_on_borders to let the sprite bounce on the borders
of the applet\; an instance of collide to let the sprite
collide with other predators.
- Finally, an instance of the module
inertia is created and
assigned to the local variable with the same name. The instance is immediately
suspended, using the suspend function.
Note that the various threads created are not stored in the
behaviors field of the sprite. Actually, only
inertia will
be accessed after creation, and one use a special local variable to store it.
module predator_sprite ()
local thread_t inertia;
{
sprite_t s = new_sprite (0,GHOST);
predator_create (s,pred_evt,prey_evt);
sync_sprite_create (s);
move_right_create (s,pred_run_step);
move_left_create (s,pred_run_step);
move_up_create (s,pred_run_step);
move_down_create (s,pred_run_step);
bounce_on_borders_create (s);
collide_create (s,pred_evt);
local(inertia) = inertia_create (s);
suspend (local(inertia));
}
while (1) do
await (a_pressed);
resume (local(inertia));
cooperate;
await (b_pressed);
suspend (local(inertia));
cooperate;
end
end module
Fig. 11: Predator Sprite
The prey sprite is defined in the module prey_sprite of
figure Prey Sprite. The overall behavior is made of the
specific one, called prey, an inertia behavior, a bouncing one, a
synchronisation behavior and a collision one. All these elementary
behaviors are stored in behaviors because they must be
destroyed when the prey is killed. The definition of the module
prey is very close to the one of predator, with 2 changes:
first, the events generated and observed are inverted\; second,
the chase function is changed by a function that make the prey
run away from the predator.
After creation of the elementary behaviors, the destroy
event is awaited (let us recall that it is generated when the prey is
killed by the the predator). Then, an instance of the destroy_sprite module is run in order to deallocate the various
behaviors of the prey, and finally the executing thread is
deallocated.
module prey_sprite ()
local sprite_t s;
{
sprite_t s = new_sprite (0,PACMAN);
alloc_behaviors (s,5);
s->behaviors[0] = inertia_create (s);
s->behaviors[1] = bounce_on_borders_create (s);
s->behaviors[2] = prey_create (s,pred_evt,prey_evt);
s->behaviors[3] = sync_sprite_create (s);
s->behaviors[4] = collide_create (s,prey_evt);
local(s) = s;
}
await (local(s)->destroy);
run destroy_sprite (local(s));
thread_deallocate (self ());
end module
Fig. 12: Prey Sprite
With the module
automatic
of figure
Automatic,
new preys are automatically created, after a while, when there is none
in the system. At each instant, the event generated by preys (
prey_evt) is observed, using the
get_value function. If
no value with index 0 is available, which means that no prey was
present during previous instant, then new preys are created.
Otherwise, the thread simply cooperates.
module automatic (int delay)
local sprite_t other;
while(1) do
get_value (prey_evt,0, (void**)&local(other));
if (return_code () == ENEXT) then
repeat (local(delay)) do cooperate; end
repeat (NUMBER_OF_PREYS) do prey_sprite_create (); end
cooperate;
end
cooperate;
end
end module
Fig. 13: Automatic
In the context of the GBA, the main module is called
GBA_main.
It is described in figure
Main.
First, the two events generated at each instant by preys and predators are
created. Then, events corresponding to keys are created with an instance
of the module key_handler which convert key presses into events.
An instance of automatic and one of predator_sprite are
created. Finally, some global variables are set to correctly parametrize the demo.
module GBA_main()
{
prey_evt = event_create ();
pred_evt = event_create ();
create_key_events ();
key_handler_create ();
automatic_create (CREATION_DELAY);
predator_sprite_create ();
key_processing_create (l_pressed,predator_sprite_create);
key_processing_create (r_pressed,prey_sprite_create);
max_pred_speed = 10;
kill_dist2 = 200;
predator_visibility = 40000;
pred_run_step = 2;
max_prey_speed = 30;
prey_visibility = 8000;
prey_run_step = 3;
}
end module
Fig. 14: Main