Nwagyu!Nwagyu!
App installer
  • English
  • Français
GitHub
App installer
  • English
  • Français
GitHub
  • Documentation Index
  • Tutorial
    • Part 1: Project creation
    • Part 2: Cleaning up the template
    • Part 3: Moving the snake
    • Part 4: Handling snake length
    • Part 5: Eating fruits
    • Part 6: Game over
    • Part 7: Map edge handling
    • Part 8: Frame limiter
    • Part 9: Progressive fruits spawning
    • Part 10: Score calculator and saving
    • Part 11: Map support
    • Part 12: App icon
    • Part 13: Increasing speed
    • Part 14: Conclusion
  • External apps
    • Creating your own application
    • Accessing storage
    • On/Off and Home keys
    • Syscalls
  • Firmware
    • Boot process
    • Slots
    • Bootloader
    • Kernel
    • Userland
    • Addresses and structures
  • Others
    • Communication with the computer (DFU)
    • Downloading Epsilon from NumWorks' website
  • Developers tips
    • [Rust] Using a heap allocator when developing apps
  • User documentation

Part 5: Eating fruits

A moving snake is interesting, but there is no point to playing snake if there are no fruits!

Fruits will be also stored on a statically allocated list (they can be capped) and each one will have to be checked against the snake head.

The structure will be similar to the snake file, so I will skip on some details and just give the code.

List initialization

In fruits.h, we add the maximum fruit count and struct:

#include <stdint.h>

#define SNAKE_MAX_FRUITS 5

typedef struct {
  uint16_t x;
  uint16_t y;
} fruit_t;

void init_fruits();

In fruits.c, we define the list and its initialization code:

#include "fruits.h"

static fruit_t FRUITS[SNAKE_MAX_FRUITS] = { 0 };

void init_fruits() {
    for (int i = 0; i < SNAKE_MAX_FRUITS ; i++) {
        FRUITS[i] = (fruit_t){UINT16_MAX, UINT16_MAX };
    }
}

And we can add the new file to the Makefile:

src = $(addprefix src/,\
  main.c \
  snake.c \
  fruits.c \
)

Now call init_fruits() after init_snake() in main() (the order will be important in the future).

#include "fruits.h"

init_fruits();

Adding fruits

We now need to actually spawn fruits on screen, so let's add a new function: add_fruit.

The most important part is to generate random coordinates. In Python, you would use randint:

import random
x = random.randint(0, 320 / SNAKE_SIZE)
y = random.randint(0, 240 / SNAKE_SIZE)

In the NumWorks C API, there is no function such as randint. Instead, you have eadk_random() which returns 32 bits of random data, which can be stored as a float (to get a random floating-point number) or an int/uint to get a (signed or not) integer.

Once you have an integer, you can use a modulo (the rest of a Euclidean division, or if you prefer, congruences) to cap its value.

The Python code will translate this way in C:

uint16_t x = eadk_random() % (320 / SNAKE_SIZE);
uint16_t y = eadk_random() % (240 / SNAKE_SIZE);

We will need the 320 / SNAKE_SIZE later in our code when handling snake going out of the screen, so I will suggest you to add it to main.h as a constant:

#include "eadk.h"

#define SNAKE_SIZE 10
#define SNAKE_MAX_X_COORDINATE EADK_SCREEN_WIDTH / SNAKE_SIZE - 1
#define SNAKE_MAX_Y_COORDINATE EADK_SCREEN_HEIGHT / SNAKE_SIZE - 1

void error(char * message);

In fruits.c, we now need to include main.h:

#include "main.h"

And simplify our code using the new constants:

void add_fruit() {
    uint16_t x = eadk_random() % (SNAKE_MAX_X_COORDINATE);
    uint16_t y = eadk_random() % (SNAKE_MAX_Y_COORDINATE);

    for (int i = 0; i < SNAKE_MAX_FRUITS ; i++) {
        if ((FRUITS[i].x == UINT16_MAX) && (FRUITS[i].y == UINT16_MAX)) {
            FRUITS[i] = (fruit_t){x, y };
            eadk_display_push_rect_uniform((eadk_rect_t){x * SNAKE_SIZE, y * SNAKE_SIZE, SNAKE_SIZE, SNAKE_SIZE}, eadk_color_red);
            return;
        }
    }
}

You can add a call to add_fruit() at the end of init_fruits() to always start the game with one fruit.

Checking collisions

We have a snake, and we have fruits. How about making both interact?

The idea is to have the main.c asking snake.c about its location, then asking fruits.c to know whether a fruit is present at this location. If a fruit is present, main.c will increase the snake length and ask fruits.c to remove the current fruit (and spawn a new fruit if no fruit is present anymore).

This will also allow main.c to keep track of the number of eaten fruits in the future as a score.

Let's implement it:

In snake.c:

void add_snake_element() {
    max_snake_size++;
}

snake_element_t get_snake_location() {
    return SNAKE[0];
}

In snake.h, we add the corresponding declaration:

void add_snake_element();
snake_element_t get_snake_location();

In fruits, we start by adding some headers:

#include <stdbool.h>

Then create a new function to automatically add a fruit if the list is empty:

void add_fruit_if_empty() {
    for (int i = 0; i < SNAKE_MAX_FRUITS ; i++) {
        if ((FRUITS[i].x != UINT16_MAX) && (FRUITS[i].y != UINT16_MAX)) {
            return;
        }
    }
    add_fruit();
}

And finally the most useful function: collision checking


bool check_fruit_collision(snake_element_t snake_location) {
    for (int i = 0; i < SNAKE_MAX_FRUITS ; i++) {
        if ((FRUITS[i].x == snake_location.x) && (FRUITS[i].y == snake_location.y)) {
            FRUITS[i] = (fruit_t){UINT16_MAX, UINT16_MAX};
            add_fruit_if_empty();

            return true;
        }
    }

    return false;
}

We need to add the declaration to the header file:

#include "snake.h"
#include <stdbool.h>


bool check_fruit_collision(snake_element_t snake_location);

Unfortunately, we now get compilation errors about conflicting types. This is due to the way I wrote the header files: currently, if a file is included twice, it will lead to conflicts. To work around this problem, we can add header guards.

It's just a ifndef checking if a constant isn't defined, and defining it if it wasn't the case to prevent the file from being included again. For fruits.h, the code with header guards looks this way.

#ifndef FRUITS_H
#define FRUITS_H

#include "snake.h"

#include <stdbool.h>
#include <stdint.h>

#define SNAKE_MAX_FRUITS 5

typedef struct {
  uint16_t x;
  uint16_t y;
} fruit_t;

void init_fruits();
bool check_fruit_collision(snake_element_t snake_location);

#endif

I'll let you add the header guards in the other files.

Now all the infrastructure is implemented, let's add the glue in main.c.

snake_element_t snake_location = get_snake_location();
if (check_fruit_collision(snake_location)) {
    add_snake_element();
}

Our snake can now eat fruits and grow, we can actually start calling this game "Snake"! 🎉

I suggest you to do a git commit right now (as for each new feature) to save your progress. In general, you should do one commit per tutorial part.

Edit this page
Last Updated: 3/6/26, 10:31 PM
Contributors: Yaya-Cout
Prev
Part 4: Handling snake length
Next
Part 6: Game over