Table of Contents
- Compiling and degubbing code for Tiva
- Hardware Abstraction Layer
- Debugging, heap, and display
- Analog Digital Converter and Timers
- Playing Nokia Tunes
- Random Number Generator, Rendering Engine, and the Game
- A real-time operating system
Random Number Generator
To make the game more engaging, we introduce some randomness into it. We don't need anything cryptographically secure, so a Linear Congruential Generator will do just fine. We count the time from the start-up in millisecond-long jiffies and wait for a first button press to select the seed.
1void button_event(IO_io *io, uint16_t event)
2{
3 uint64_t btn;
4 IO_get(io, &btn);
5 if(btn)
6 button_value = 1;
7
8 if(!rng_initialized) {
9 rng_initialized = 1;
10 IO_rng_seed(IO_time());
11 }
12}
Rendering Engine
The rendering engine takes a scene descriptor, a display device, and a timer. Based on this information it computes new positions of objects, draws them on the screen if necessary and checks for collisions.
1struct SI_scene {
2 SI_object **objects;
3 void (*pre_render)(struct SI_scene *);
4 void (*collision)(SI_object *obj1, SI_object *obj2);
5 uint8_t fps;
6 uint8_t num_objects;
7 uint8_t flags;
8};
9
10void SI_scene_render(SI_scene *scene, IO_io *display, IO_io *timer);
Each SI_scene
holds a list of "polymorphic" objects that should be rendered, a
pointer to a pre_render
function that calculates a new position of each
object, and a pointer to a collision
callback that is invoked when the scene
renderer detects an overlap between two objects. The SI_scene_render
function
runs after every interrupt:
1 while(1) {
2 SI_scene_render(&scenes[current_scene].scene, &display, &scene_timer);
3 IO_wait_for_interrupt();
4 }
Whether it gets executed or not, depends on the flag
parameter of the scene.
If it's set to SI_SCENE_IGNORE
, the renderer returns immediately. On the other
hand, if it's set to SI_SCENE_RENDER
, the renderer calls the pre_render
callback, draws the objects on the screen, and computes the object overlaps
notifying the collision
callback if necessary. After each frame, the scene is
disabled (SI_SCENE_IGNORE
). It is re-enabled by the timer interrupt in a time
quantum that depends on the fps
parameter.
See SI_scene.h and SI_scene.c.
Each object has a draw
function that enables the renderer to draw it on the
screen. There are three types of objects: a generic object, a bitmap object, and
a text object:
1struct SI_object {
2 uint16_t x;
3 uint16_t y;
4 uint16_t width;
5 uint16_t height;
6 uint8_t flags;
7 uint8_t user_flags;
8 void (*draw)(struct SI_object *this, IO_io *display);
9};
10
11struct SI_object_bitmap {
12 SI_object obj;
13 const IO_bitmap *bmp;
14};
15
16struct SI_object_text {
17 SI_object obj;
18 const char *text;
19 const IO_font *font;
20};
The object array in the scene is initialized with the SI_object
pointers:
1static SI_object score_obj;
2static SI_object_bitmap invader_obj[5];
3scene->objects[1] = &score_obj;
4scene->objects[i+5] = &invader_obj[i].obj;
See SI_scene_game.c.
The renderer calls the draw
function of each SI_OBJECT_VISIBLE
object:
1obj->draw(obj, display);
Finally, each draw method uses the CONTAINER_OF
macro to compute the pointer
to the actual object of concrete type:
1#define CONTAINER_OF(TYPE, MEMBER, MEMBER_ADDR) \
2 ((TYPE *) ( (char *)MEMBER_ADDR - offsetof(TYPE, MEMBER)))
3
4void SI_object_bitmap_draw(SI_object *obj, IO_io *display)
5{
6 SI_object_bitmap *this = CONTAINER_OF(SI_object_bitmap, obj, obj);
7 IO_display_print_bitmap(display, obj->x, obj->y, this->bmp);
8}
9
10void SI_object_text_draw(SI_object *obj, IO_io *display)
11{
12 SI_object_text *this = CONTAINER_OF(SI_object_text, obj, obj);
13 IO_display_set_font(display, this->font);
14 IO_display_cursor_goto(display, obj->x, obj->y);
15 IO_print(display, "%s", this->text);
16}
The Game
All this seems to work pretty well when put together:
See silly-invaders.c.
If you like this kind of content, you can subscribe to my newsletter, follow me on Twitter, or subscribe to my RSS channel.