// gcc snake.c -o snake_game $(pkg-config --cflags --libs sdl3) #include #include #include #include #include #include #include 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; }