Nimbin[12]?Embedded / snake_compiler / snake_SDL/editor/main.c

snake_compiler git · main

Snake compiled to a bare-metal RISC-V microcontroller

riscv embedded bare-metal c snake · first commit 2026-01-18 · last commit 2026-01-18 (8 months ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/compilerbau/snake_compiler

C 95.2% Markdown 3.3%
git clone https://git.christianimmanuel.de/embedded/snake_compiler.gitwget https://git.christianimmanuel.de/embedded/snake_compiler/archive/snake_compiler.tar.gz
snake_SDL/editor/main.c 16.1 KB · 545 lines raw
// gcc snake.c -o snake_game $(pkg-config --cflags --libs sdl3)
#include <SDL3/SDL.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>


FILE *popen(const char *command, const char *type);
char *strdup(const char *s);
int pclose(FILE *stream);

#define GRID_SIZE_X 40
#define GRID_SIZE_Y 30
#define TILE_SIZE 30
#define WINDOW_SIZE_X TILE_SIZE*GRID_SIZE_X
#define WINDOW_SIZE_Y TILE_SIZE*GRID_SIZE_Y
#define MAX_SNAKE_LENGTH (GRID_SIZE_X* GRID_SIZE_Y)
#define MAX_BRIGHTNESS_STEPS 7  // Fixed number of brightness levels (0=off, 1-7=on)

#define MATRIX_WIDTH  16
#define MATRIX_HEIGHT 16
#define LED_SIZE 30
#define LED_SPACING 2
#define MATRIX_WINDOW_WIDTH (MATRIX_WIDTH * (LED_SIZE + LED_SPACING) + LED_SPACING)
#define MATRIX_WINDOW_HEIGHT (MATRIX_HEIGHT * (LED_SIZE + LED_SPACING) + LED_SPACING)

typedef struct {
    int x, y;
} Point;

typedef struct {
    Point segments[MAX_SNAKE_LENGTH];
    int length;
    Point direction;
    Point next_direction;
} Snake;

typedef struct {
    Point pos;
    SDL_Color color;
    bool active;
} Button;

typedef struct {
    Point pos;          // top-left tile position
    int width;          // width in tiles (should be 8: 1 for off + 7 for brightness levels)
    int height;         // height in tiles
    SDL_Color base_color;  // base color at full brightness
    int brightness;     // current brightness level (0 to MAX_BRIGHTNESS_STEPS)
    bool last_overlap;
} Light;

typedef struct {
    SDL_Color matrix[MATRIX_HEIGHT][MATRIX_WIDTH];
    FILE *pipe;
    bool running;
    pthread_mutex_t mutex;
} MatrixData;

void init_snake(Snake *snake) {
    snake->length = 3;
    snake->segments[0] = (Point){GRID_SIZE_X / 2    , GRID_SIZE_Y / 2};
    snake->segments[1] = (Point){GRID_SIZE_X / 2 - 1, GRID_SIZE_Y / 2};
    snake->segments[2] = (Point){GRID_SIZE_X / 2 - 2, GRID_SIZE_Y / 2};
    snake->direction = (Point){1, 0}; // moving right
    snake->next_direction = (Point){1, 0};
}

int max_brightness = MAX_BRIGHTNESS_STEPS;

void move_snake(Snake *snake, bool grow, bool* collided_with_wall) {
    // Update direction
    snake->direction = snake->next_direction;

    // Move body segments
    if (!grow) {
        for (int i = snake->length - 1; i > 0; i--) {
            snake->segments[i] = snake->segments[i - 1];
        }
    } else {
        // Growing: shift all segments and add new head
        for (int i = snake->length; i > 0; i--) {
            snake->segments[i] = snake->segments[i - 1];
        }
        snake->length++;
    }

    // Move head
    snake->segments[0].x += snake->direction.x;
    snake->segments[0].y += snake->direction.y;

    if (snake->segments[0].x < 0 ||
        snake->segments[0].x >= GRID_SIZE_X ||
        snake->segments[0].y < 0 ||
        snake->segments[0].y >= GRID_SIZE_Y)
        *collided_with_wall = true;
}

bool check_button_collision(Snake *snake, Button *button) {
    if (!button->active) return false;
    return snake->segments[0].x == button->pos.x &&
           snake->segments[0].y == button->pos.y;
}

void check_light_brightness(Light lights[3], Light *light, Snake *snake) {
    Point head = snake->segments[0];
    bool currently_overlap = false;

    // Check if head is within light's vertical bounds
    if (head.y >= light->pos.y && head.y < light->pos.y + light->height) {
        // Check if head is within light's horizontal bounds
        if (head.x >= light->pos.x && head.x < light->pos.x + light->width) {
            currently_overlap = true;

            // Calculate relative position from left (0 = leftmost column)
            int relative_x = head.x - light->pos.x;

            // Map column to brightness: column 0 = off (0), columns 1-7 = brightness 1-7
            int target_brightness = relative_x;

            // Clamp to valid range
            if (target_brightness > MAX_BRIGHTNESS_STEPS) {
                target_brightness = MAX_BRIGHTNESS_STEPS;
            }

            // Set the brightness directly
            light->brightness = target_brightness;
        }
    }

    light->last_overlap = currently_overlap;

    // Ensure at least one light is on
    bool any_on = false;
    for (int i = 0; i < 3; i++) {
        if (lights[i].brightness > 0) {
            any_on = true;
            break;
        }
    }
    if (!any_on) {
        // If all off, force this light on at minimum brightness
        light->brightness = 1;
    }
}

bool check_self_collision(Snake *snake) {
    for (int i = 1; i < snake->length; i++) {
        if (snake->segments[0].x == snake->segments[i].x &&
            snake->segments[0].y == snake->segments[i].y) {
            return true;
        }
    }
    return false;
}

void render_snake(SDL_Renderer *ren, Snake *snake) {
    for (int i = 0; i < snake->length; i++) {
        SDL_FRect rect = {
            snake->segments[i].x * TILE_SIZE,
            snake->segments[i].y * TILE_SIZE,
            TILE_SIZE - 2,
            TILE_SIZE - 2
        };

        // Head is brighter
        if (i == 0) {
            SDL_SetRenderDrawColor(ren, 0, 255, 0, 255);
        } else {
            SDL_SetRenderDrawColor(ren, 0, 200, 0, 255);
        }
        SDL_RenderFillRect(ren, &rect);
    }
}

void render_button(SDL_Renderer *ren, Button *button) {
    if (!button->active) return;

    SDL_FRect rect = {
        button->pos.x * TILE_SIZE + 2,
        button->pos.y * TILE_SIZE + 2,
        TILE_SIZE - 4,
        TILE_SIZE - 4
    };

    SDL_SetRenderDrawColor(ren, button->color.r, button->color.g, button->color.b, 255);
    SDL_RenderFillRect(ren, &rect);
}

void render_light(SDL_Renderer *ren, Light *light) {
    // Draw each column of the light with visual indication
    for (int col = 0; col < light->width; col++) {
        SDL_FRect rect = {
            (light->pos.x + col) * TILE_SIZE,
            light->pos.y * TILE_SIZE,
            TILE_SIZE,
            light->height * TILE_SIZE
        };

        SDL_Color c = light->base_color;

        // Column 0 is "off", columns 1-7 show increasing brightness
        if (col == 0) {
            // Off column - very dark
            c.r /= 10;
            c.g /= 10;
            c.b /= 10;
        } else {
            // Brightness columns - show the brightness level for that column
            float intensity = (float)light->brightness / (float)MAX_BRIGHTNESS_STEPS;
            c.r = (uint8_t)(c.r * intensity);
            c.g = (uint8_t)(c.g * intensity);
            c.b = (uint8_t)(c.b * intensity);
        }

        // Highlight the current brightness level
        if (col == light->brightness) {
            SDL_SetRenderDrawColor(ren, light->base_color.r, light->base_color.g, light->base_color.b, 140);
            SDL_RenderFillRect(ren, &rect);
            rect.x += 2;
            rect.y += 2;
            rect.w -= 4;
            rect.h -= 4;
        }

        SDL_SetRenderDrawColor(ren, c.r, c.g, c.b, 255);
        SDL_RenderFillRect(ren, &rect);
    }
}

void render_grid(SDL_Renderer *ren) {
    SDL_SetRenderDrawColor(ren, 30, 30, 30, 255);

    // Vertical lines
    for (int i = 0; i <= GRID_SIZE_X; i++) {
        SDL_RenderLine(ren, i * TILE_SIZE, 0, i * TILE_SIZE, GRID_SIZE_Y * TILE_SIZE);
    }

    // Horizontal lines
    for (int i = 0; i <= GRID_SIZE_Y; i++) {
        SDL_RenderLine(ren, 0, i * TILE_SIZE, GRID_SIZE_X * TILE_SIZE, i * TILE_SIZE);
    }
}

void* matrix_reader_thread(void* arg)
{
    MatrixData *md = (MatrixData*)arg;
    char line[512];

    enum {
        WAIT_START,
        READ_ROWS
    } state = WAIT_START;

    int y = 0;

    while (md->running && md->pipe && fgets(line, sizeof(line), md->pipe)) {

        /* Always forward ESP output to terminal */
        fputs(line, stdout);
        fflush(stdout);

        if (state == WAIT_START) {
            if (strncmp(line, "## START MATRIX", 15) == 0) {
                y = 0;
                state = READ_ROWS;
            }
            continue;
        }

        if (state == READ_ROWS) {
            if (line[0] == '\n' || line[0] == '\r')
                continue;

            char *tile = strtok(line, " \t\n\r");
            int x = 0;

            pthread_mutex_lock(&md->mutex);
            while (tile && x < MATRIX_WIDTH) {
                int r, g, b;

                if (sscanf(tile, "%d|%d|%d", &r, &g, &b) != 3) {
                    /* corrupted tile → resync */
                    state = WAIT_START;
                    break;
                }

                md->matrix[y][x].r = r * 255 / 7;
                md->matrix[y][x].g = g * 255 / 7;
                md->matrix[y][x].b = b * 255 / 7;

                tile = strtok(NULL, " \t\n\r");
                x++;
            }
            pthread_mutex_unlock(&md->mutex);

            if (state != WAIT_START) {
                y++;
                if (y >= MATRIX_HEIGHT) {
                    state = WAIT_START;
                }
            }
        }
    }

    return NULL;
}

void render_matrix(SDL_Renderer *ren, MatrixData *data) {
    pthread_mutex_lock(&data->mutex);

    for (int y = 0; y < MATRIX_HEIGHT; y++) {
        for (int x = 0; x < MATRIX_WIDTH; x++) {
            uint8_t r = data->matrix[y][x].r;
            uint8_t g = data->matrix[y][x].g;
            uint8_t b = data->matrix[y][x].b;

            SDL_FRect rect = {
                LED_SPACING + x * (LED_SIZE + LED_SPACING),
                LED_SPACING + y * (LED_SIZE + LED_SPACING),
                LED_SIZE,
                LED_SIZE
            };

            SDL_SetRenderDrawColor(ren, r, g, b, 255);
            SDL_RenderFillRect(ren, &rect);

            if (r+g+b > 0) {
                SDL_FRect highlight = {
                    rect.x + 2,
                    rect.y + 2,
                    rect.w / 3,
                    rect.h / 3
                };
                uint8_t hr = r + (255 - r) / 2;
                uint8_t hg = g + (255 - g) / 2;
                uint8_t hb = b + (255 - b) / 2;
                SDL_SetRenderDrawColor(ren, hr, hg, hb, 200);
                SDL_RenderFillRect(ren, &highlight);
            }
        }
    }

    pthread_mutex_unlock(&data->mutex);
}

int main(void) {
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("SDL_Init failed: %s", SDL_GetError());
        return 1;
    }

    // Create control window
    SDL_Window *control_win = SDL_CreateWindow("Snake Game - WASD to move",
                                       WINDOW_SIZE_X, WINDOW_SIZE_Y, 0);
    if (!control_win) {
        SDL_Log("SDL_CreateWindow failed: %s", SDL_GetError());
        SDL_Quit();
        return 1;
    }

    SDL_Renderer *control_ren = SDL_CreateRenderer(control_win, "opengl");
    if (!control_ren) {
        SDL_Log("SDL_CreateRenderer failed: %s", SDL_GetError());
        SDL_DestroyWindow(control_win);
        SDL_Quit();
        return 1;
    }

    // Create matrix visualization window

    Snake snake;
    init_snake(&snake);

    // Create buttons
    Button buttons[5] = {
        {{5 ,  5}, {255, 0  , 0  , 255}, true},
        {{GRID_SIZE_X-6,  5}, {0  , 0  , 255, 255}, true},
        {{5 , GRID_SIZE_Y-6}, {255, 255, 0  , 255}, true},
        {{GRID_SIZE_X-6, GRID_SIZE_Y-6}, {255, 0  , 255, 255}, true},
        {{GRID_SIZE_X/2, GRID_SIZE_Y/2+5}, {0  , 255, 255, 255}, true}
    };

    // Create lights
    int light_width = MAX_BRIGHTNESS_STEPS + 1;
    int total_light_width = 3 * light_width + 2;
    int start_x = (GRID_SIZE_X - total_light_width) / 2;

    Light lights[3] = {
        {{start_x, 5}, light_width, 2, {255, 0, 0, 255}, 0, false},
        {{start_x + light_width + 1, 5}, light_width, 2, {0, 255, 0, 255}, 4, false},
        {{start_x + 2*(light_width + 1), 5}, light_width, 2, {0, 0, 255, 255}, 0, false}
    };

    bool running = true;
    uint64_t last_move = SDL_GetTicks();
    const uint64_t move_delay = 250;
    bool collided_with_wall = false;

    // Matrix data and thread
    MatrixData matrix_data = {0};
    matrix_data.running = false;
    pthread_mutex_init(&matrix_data.mutex, NULL);

    while (running) {
        SDL_Event e;
        while (SDL_PollEvent(&e)) {
            if (e.type == SDL_EVENT_QUIT) {
                running = false;
            }

            if (e.type == SDL_EVENT_KEY_DOWN) {
                switch (e.key.scancode) {
                    case SDL_SCANCODE_W:
                        if (snake.direction.y == 0)
                            snake.next_direction = (Point){0, -1};
                        break;
                    case SDL_SCANCODE_S:
                        if (snake.direction.y == 0)
                            snake.next_direction = (Point){0, 1};
                        break;
                    case SDL_SCANCODE_A:
                        if (snake.direction.x == 0)
                            snake.next_direction = (Point){-1, 0};
                        break;
                    case SDL_SCANCODE_D:
                        if (snake.direction.x == 0)
                            snake.next_direction = (Point){1, 0};
                        break;
                    default:
                        break;
                }
            }
        }

        // Move snake at regular intervals
        uint64_t now = SDL_GetTicks();
        if (now - last_move >= move_delay) {
            bool grow = false;

            for (int i = 0; i < 5; i++) {
                if (check_button_collision(&snake, &buttons[i])) {
                    buttons[i].active = false;
                    grow = true;
                    break;
                }
            }

            move_snake(&snake, grow, &collided_with_wall);

            for (int i = 0; i < 3; i++) {
                check_light_brightness(lights, &lights[i], &snake);
            }

            if (check_self_collision(&snake) || collided_with_wall) {
                printf("Game Over! Length: %d\n", snake.length);
                running = false;
            }

            last_move = now;
        }

        // Render control window
        SDL_SetRenderDrawColor(control_ren, 0, 0, 0, 255);
        SDL_RenderClear(control_ren);
        render_grid(control_ren);
        for (int i = 0; i < 3; i++) {
            render_light(control_ren, &lights[i]);
        }
        for (int i = 0; i < 5; i++) {
            render_button(control_ren, &buttons[i]);
        }
        render_snake(control_ren, &snake);
        SDL_RenderPresent(control_ren);

        SDL_Delay(16);
    }

    // Flash to ESP
    system("export MDK=/usr/src/u_esp/baremetal/mdk");
    system("export ARCH=esp32c3");
    system("export PORT=/dev/ttyUSB0");

    int red   = 0;
    int green = 0;
    int blue  = 0;

    if (lights[0].brightness > 0) red   = lights[0].brightness;
    if (lights[1].brightness > 0) green = lights[1].brightness;
    if (lights[2].brightness > 0) blue  = lights[2].brightness;

    char cmd[1024];
    snprintf(cmd, sizeof(cmd),
            "make -C ../src/ clean && "
            "make -C ../src/ build "
            "EXTRA_CFLAGS='-DWALL_COLLISION=%d -DSNAKE_START_LENGTH=%d -DMAX_BRIGHTNESS=%d -DSNAKE_COLOR_R=%d -DSNAKE_COLOR_G=%d -DSNAKE_COLOR_B=%d' && "
            "make -C ../src/ flash",
            collided_with_wall, snake.length, max_brightness, red, green, blue);
    system(cmd);

    // Now start the monitor and matrix reader thread
    char monitor_cmd[512];
    snprintf(monitor_cmd, sizeof(monitor_cmd),
            "make -C ../src/ monitor");

    matrix_data.pipe = popen(monitor_cmd, "r");
    if (matrix_data.pipe) {
        matrix_data.running = true;

        pthread_t reader_thread;
        pthread_create(&reader_thread, NULL, matrix_reader_thread, &matrix_data);

        // Continue rendering until user closes
        running = true;
        while (running) {
            SDL_Event e;
            while (SDL_PollEvent(&e)) {
                if (e.type == SDL_EVENT_QUIT) {
                    running = false;
                }
            }

            SDL_SetRenderDrawColor(control_ren, 10, 10, 10, 255);
            SDL_RenderClear(control_ren);
            render_matrix(control_ren, &matrix_data);
            SDL_RenderPresent(control_ren);

            SDL_Delay(16);
        }

        matrix_data.running = false;
        pthread_join(reader_thread, NULL);
        pclose(matrix_data.pipe);
    }

    pthread_mutex_destroy(&matrix_data.mutex);
    SDL_DestroyRenderer(control_ren);
    SDL_DestroyWindow(control_win);
    SDL_Quit();

    return 0;
}