#include "snake_game.h"
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
static snake_game_t g_game;
static int last_score = 0;
static int no_score_frames = 0;
static void snake_game_draw(void);
static void snake_game_timer_cb(lv_timer_t* timer);
static void snake_game_key_event_cb(lv_event_t* e);
static void generate_food(void);
static void auto_move(void);
static void start_button_event_cb(lv_event_t * e);
static void restart_button_event_cb(lv_event_t * e);
void snake_game_create(void)
{
lv_obj_t* root = lv_obj_create(lv_scr_act());
lv_obj_set_size(root, GAME_GRID_SIZE * GAME_GRID_WIDTH, GAME_GRID_SIZE * GAME_GRID_HEIGHT + 40);
lv_obj_center(root);
lv_obj_set_style_bg_color(root, lv_color_hex(0x000000), 0);
lv_obj_clear_flag(root, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(root, LV_OBJ_FLAG_CLICKABLE);
g_game.ui.canvas = lv_canvas_create(root);
lv_obj_align(g_game.ui.canvas, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_set_size(g_game.ui.canvas, GAME_GRID_SIZE * GAME_GRID_WIDTH, GAME_GRID_SIZE * GAME_GRID_HEIGHT);
lv_obj_set_style_bg_color(g_game.ui.canvas, lv_color_hex(0x000000), 0);
g_game.ui.score_label = lv_label_create(root);
lv_obj_align(g_game.ui.score_label, LV_ALIGN_BOTTOM_LEFT, 10, -5);
lv_obj_set_style_text_color(g_game.ui.score_label, lv_color_white(), 0);
g_game.ui.status_label = lv_label_create(root);
lv_obj_align(g_game.ui.status_label, LV_ALIGN_BOTTOM_RIGHT, -10, -5);
lv_obj_set_style_text_color(g_game.ui.status_label, lv_color_white(), 0);
g_game.ui.start_btn = lv_btn_create(root);
lv_obj_set_size(g_game.ui.start_btn, 200, 60);
lv_obj_center(g_game.ui.start_btn);
lv_obj_add_event_cb(g_game.ui.start_btn, start_button_event_cb, LV_EVENT_CLICKED, NULL);
lv_obj_t * start_label = lv_label_create(g_game.ui.start_btn);
lv_label_set_text(start_label, "START");
lv_obj_set_style_text_font(start_label, &lv_font_montserrat_24, 0);
lv_obj_center(start_label);
g_game.ui.restart_btn = lv_btn_create(root);
lv_obj_set_size(g_game.ui.restart_btn, 200, 60);
lv_obj_center(g_game.ui.restart_btn);
lv_obj_add_event_cb(g_game.ui.restart_btn, restart_button_event_cb, LV_EVENT_CLICKED, NULL);
lv_obj_add_flag(g_game.ui.restart_btn, LV_OBJ_FLAG_HIDDEN);
lv_obj_t * restart_label = lv_label_create(g_game.ui.restart_btn);
lv_label_set_text(restart_label, "RESTART");
lv_obj_set_style_text_font(restart_label, &lv_font_montserrat_24, 0);
lv_obj_center(restart_label);
g_game.status = GAME_STATUS_INIT;
g_game.game_timer = lv_timer_create(snake_game_timer_cb, 50, NULL);
lv_timer_pause(g_game.game_timer);
lv_label_set_text(g_game.ui.score_label, "Score: 0");
lv_label_set_text(g_game.ui.status_label, "Ready");
lv_obj_t* scr = lv_scr_act();
lv_obj_add_event_cb(scr, snake_game_key_event_cb, LV_EVENT_KEY, NULL);
lv_group_t* g = lv_group_create();
lv_group_add_obj(g, scr);
lv_indev_t* kb_indev = lv_indev_get_next(NULL);
while(kb_indev) {
if(lv_indev_get_type(kb_indev) == LV_INDEV_TYPE_KEYPAD) {
lv_indev_set_group(kb_indev, g);
break;
}
kb_indev = lv_indev_get_next(kb_indev);
}
lv_group_focus_obj(scr);
}
void snake_game_init(void)
{
srand(time(NULL));
g_game.snake_head = malloc(sizeof(snake_node_t));
g_game.snake_head->x = GAME_GRID_WIDTH / 2;
g_game.snake_head->y = GAME_GRID_HEIGHT / 2;
g_game.snake_head->next = NULL;
g_game.direction = DIR_RIGHT;
g_game.score = 0;
last_score = 0;
no_score_frames = 0;
generate_food();
lv_label_set_text(g_game.ui.score_label, "Score: 0");
}
static void snake_game_timer_cb(lv_timer_t* timer)
{
if (g_game.status != GAME_STATUS_RUNNING) {
return;
}
if (g_game.score == last_score) {
no_score_frames++;
if (no_score_frames > 300) {
g_game.status = GAME_STATUS_OVER;
lv_label_set_text(g_game.ui.status_label, "STUCK! GAME OVER");
lv_timer_pause(g_game.game_timer);
lv_obj_clear_flag(g_game.ui.restart_btn, LV_OBJ_FLAG_HIDDEN);
lv_obj_t* restart_label = lv_obj_get_child(g_game.ui.restart_btn, 0);
lv_label_set_text(restart_label, "RESTART");
return;
}
} else {
last_score = g_game.score;
no_score_frames = 0;
}
if (g_game.score >= 1000) {
lv_label_set_text(g_game.ui.status_label, "Victory!");
g_game.status = GAME_STATUS_OVER;
lv_timer_pause(g_game.game_timer);
lv_obj_clear_flag(g_game.ui.restart_btn, LV_OBJ_FLAG_HIDDEN);
lv_obj_t* restart_label = lv_obj_get_child(g_game.ui.restart_btn, 0);
lv_label_set_text(restart_label, "Victory!");
return;
}
auto_move();
int old_x = g_game.snake_head->x;
int old_y = g_game.snake_head->y;
switch (g_game.direction) {
case DIR_UP: g_game.snake_head->y--; break;
case DIR_DOWN: g_game.snake_head->y++; break;
case DIR_LEFT: g_game.snake_head->x--; break;
case DIR_RIGHT: g_game.snake_head->x++; break;
}
bool wall_collision = false;
if (g_game.snake_head->x < 0 || g_game.snake_head->x >= GAME_GRID_WIDTH ||
g_game.snake_head->y < 0 || g_game.snake_head->y >= GAME_GRID_HEIGHT) {
g_game.snake_head->x = old_x;
g_game.snake_head->y = old_y;
switch (g_game.direction) {
case DIR_UP: g_game.direction = DIR_DOWN; break;
case DIR_DOWN: g_game.direction = DIR_UP; break;
case DIR_LEFT: g_game.direction = DIR_RIGHT; break;
case DIR_RIGHT: g_game.direction = DIR_LEFT; break;
}
wall_collision = true;
switch (g_game.direction) {
case DIR_UP: g_game.snake_head->y--; break;
case DIR_DOWN: g_game.snake_head->y++; break;
case DIR_LEFT: g_game.snake_head->x--; break;
case DIR_RIGHT: g_game.snake_head->x++; break;
}
}
snake_node_t* check_node = g_game.snake_head->next;
bool self_collision = false;
while (check_node != NULL) {
if (check_node->x == g_game.snake_head->x && check_node->y == g_game.snake_head->y) {
self_collision = true;
break;
}
check_node = check_node->next;
}
if (self_collision) {
g_game.snake_head->x = old_x;
g_game.snake_head->y = old_y;
g_game.status = GAME_STATUS_OVER;
lv_label_set_text(g_game.ui.status_label, "GAME OVER");
lv_timer_pause(g_game.game_timer);
lv_obj_clear_flag(g_game.ui.restart_btn, LV_OBJ_FLAG_HIDDEN);
return;
}
if (g_game.snake_head->x == g_game.food_x && g_game.snake_head->y == g_game.food_y) {
g_game.score += 10;
char score_text[32];
sprintf(score_text, "Score: %d", g_game.score);
lv_label_set_text(g_game.ui.score_label, score_text);
generate_food();
snake_node_t* new_node = malloc(sizeof(snake_node_t));
new_node->x = old_x;
new_node->y = old_y;
new_node->next = g_game.snake_head->next;
g_game.snake_head->next = new_node;
} else {
snake_node_t* move_node = g_game.snake_head->next;
while (move_node != NULL) {
int temp_x = move_node->x;
int temp_y = move_node->y;
move_node->x = old_x;
move_node->y = old_y;
old_x = temp_x;
old_y = temp_y;
move_node = move_node->next;
}
}
if (wall_collision) {
auto_move();
}
snake_game_draw();
}
static void snake_game_key_event_cb(lv_event_t* e)
{
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_KEY) {
uint32_t key = lv_event_get_key(e);
switch (key) {
case LV_KEY_UP:
if (g_game.direction != DIR_DOWN) {
g_game.direction = DIR_UP;
}
break;
case LV_KEY_DOWN:
if (g_game.direction != DIR_UP) {
g_game.direction = DIR_DOWN;
}
break;
case LV_KEY_LEFT:
if (g_game.direction != DIR_RIGHT) {
g_game.direction = DIR_LEFT;
}
break;
case LV_KEY_RIGHT:
if (g_game.direction != DIR_LEFT) {
g_game.direction = DIR_RIGHT;
}
break;
case LV_KEY_ENTER:
if (g_game.status == GAME_STATUS_INIT || g_game.status == GAME_STATUS_OVER) {
snake_game_init();
g_game.status = GAME_STATUS_RUNNING;
lv_label_set_text(g_game.ui.status_label, "In Game");
lv_timer_resume(g_game.game_timer);
} else if (g_game.status == GAME_STATUS_RUNNING) {
g_game.status = GAME_STATUS_PAUSED;
lv_label_set_text(g_game.ui.status_label, "Paused");
lv_timer_pause(g_game.game_timer);
} else if (g_game.status == GAME_STATUS_PAUSED) {
g_game.status = GAME_STATUS_RUNNING;
lv_label_set_text(g_game.ui.status_label, "In Game");
lv_timer_resume(g_game.game_timer);
}
break;
}
}
}
static void snake_game_draw(void)
{
lv_obj_clean(g_game.ui.canvas);
lv_obj_set_style_bg_color(g_game.ui.canvas, lv_color_hex(0x000000), 0);
lv_obj_t* food = lv_obj_create(g_game.ui.canvas);
lv_obj_set_size(food, GAME_GRID_SIZE - 2, GAME_GRID_SIZE - 2);
lv_obj_set_pos(food, g_game.food_x * GAME_GRID_SIZE + 1, g_game.food_y * GAME_GRID_SIZE + 1);
lv_obj_set_style_bg_color(food, lv_color_hex(0xFF0000), 0);
snake_node_t* current = g_game.snake_head;
while (current != NULL) {
lv_obj_t* node = lv_obj_create(g_game.ui.canvas);
lv_obj_set_size(node, GAME_GRID_SIZE - 2, GAME_GRID_SIZE - 2);
lv_obj_set_pos(node, current->x * GAME_GRID_SIZE + 1, current->y * GAME_GRID_SIZE + 1);
if (current == g_game.snake_head) {
lv_obj_set_style_bg_color(node, lv_color_hex(0x00FF00), 0);
} else {
lv_obj_set_style_bg_color(node, lv_color_hex(0x008800), 0);
}
current = current->next;
}
}
static void generate_food(void)
{
bool valid;
int attempts = 0;
const int MAX_ATTEMPTS = 100;
const int MIN_BORDER_DISTANCE = 2;
do {
valid = true;
if (attempts < MAX_ATTEMPTS) {
g_game.food_x = MIN_BORDER_DISTANCE + (rand() % (GAME_GRID_WIDTH - 2 * MIN_BORDER_DISTANCE));
g_game.food_y = MIN_BORDER_DISTANCE + (rand() % (GAME_GRID_HEIGHT - 2 * MIN_BORDER_DISTANCE));
} else {
g_game.food_x = rand() % GAME_GRID_WIDTH;
g_game.food_y = rand() % GAME_GRID_HEIGHT;
}
snake_node_t* current = g_game.snake_head;
while (current != NULL) {
if (current->x == g_game.food_x && current->y == g_game.food_y) {
valid = false;
break;
}
current = current->next;
}
attempts++;
if (attempts > MAX_ATTEMPTS && !valid) {
g_game.food_x = rand() % GAME_GRID_WIDTH;
g_game.food_y = rand() % GAME_GRID_HEIGHT;
valid = true;
}
} while (!valid);
}
static bool is_position_safe(int x, int y) {
if (x < 0 || x >= GAME_GRID_WIDTH || y < 0 || y >= GAME_GRID_HEIGHT) {
return false;
}
snake_node_t* check_node = g_game.snake_head->next;
while (check_node != NULL) {
if (check_node->x == x && check_node->y == y) {
return false;
}
check_node = check_node->next;
}
return true;
}
static void auto_move(void)
{
int current_x = g_game.snake_head->x;
int current_y = g_game.snake_head->y;
struct {
direction_t dir;
int dx;
int dy;
bool opposite;
} directions[] = {
{DIR_UP, 0, -1, g_game.direction == DIR_DOWN},
{DIR_DOWN, 0, 1, g_game.direction == DIR_UP},
{DIR_LEFT, -1, 0, g_game.direction == DIR_RIGHT},
{DIR_RIGHT, 1, 0, g_game.direction == DIR_LEFT}
};
direction_t best_dir = g_game.direction;
bool found_safe_move = false;
int best_score = -1;
int food_distance = abs(g_game.food_x - current_x) + abs(g_game.food_y - current_y);
for (int i = 0; i < 4; i++) {
if (directions[i].opposite) continue;
int new_x = current_x + directions[i].dx;
int new_y = current_y + directions[i].dy;
if (!is_position_safe(new_x, new_y)) continue;
int score = 0;
int safe_spaces = 0;
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
if (dx == 0 && dy == 0) continue;
if (is_position_safe(new_x + dx, new_y + dy)) {
safe_spaces++;
}
}
}
score += safe_spaces * 3;
int new_food_distance = abs(g_game.food_x - new_x) + abs(g_game.food_y - new_y);
if (new_food_distance < food_distance) {
score += 10;
}
for (int j = 0; j < 4; j++) {
if (directions[j].opposite) continue;
int look_x = new_x + directions[j].dx;
int look_y = new_y + directions[j].dy;
if (is_position_safe(look_x, look_y)) {
score += 2;
}
}
if ((new_x == 0 || new_x == GAME_GRID_WIDTH - 1) &&
(new_y > 0 && new_y < GAME_GRID_HEIGHT - 1)) {
score -= 5;
}
if ((new_y == 0 || new_y == GAME_GRID_HEIGHT - 1) &&
(new_x > 0 && new_x < GAME_GRID_WIDTH - 1)) {
score -= 5;
}
if (directions[i].dir == g_game.direction) {
score += 1;
}
if (score > best_score) {
best_score = score;
best_dir = directions[i].dir;
found_safe_move = true;
}
}
if (found_safe_move) {
g_game.direction = best_dir;
}
}
static void start_button_event_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
lv_obj_t * btn = lv_event_get_target(e);
lv_obj_add_flag(btn, LV_OBJ_FLAG_HIDDEN);
snake_game_init();
g_game.status = GAME_STATUS_RUNNING;
lv_label_set_text(g_game.ui.status_label, "Auto Running");
lv_timer_resume(g_game.game_timer);
}
}
static void restart_button_event_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
lv_obj_t * btn = lv_event_get_target(e);
lv_obj_add_flag(btn, LV_OBJ_FLAG_HIDDEN);
snake_game_init();
g_game.status = GAME_STATUS_RUNNING;
lv_label_set_text(g_game.ui.status_label, "Auto Running");
lv_timer_resume(g_game.game_timer);
}
}
void snake_game_deinit(void)
{
snake_node_t* current = g_game.snake_head;
while (current != NULL) {
snake_node_t* next = current->next;
free(current);
current = next;
}
if (g_game.game_timer) {
lv_timer_del(g_game.game_timer);
g_game.game_timer = NULL;
}
}