snake_compiler git · main
Snake compiled to a bare-metal RISC-V microcontroller
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.gzsnake_SDL/editor/main.c 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;
}