#include "lvgl.h"
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
/* --- SCREEN SETTINGS --- */
#define SCREEN_WIDTH 480
#define SCREEN_HEIGHT 272
#define PLAYER_SIZE 45
#define ENEMY_SIZE 30
#define MOVE_STEP 40
/* --- DIFFICULTY SETTINGS --- */
#define MAX_ENEMIES 3
#define ENEMY_SPACING 120
#define MAX_LEADERBOARD 3
#define LANE_WIDTH 40
/* --- GLOBAL OBJECTS --- */
static lv_obj_t *player = NULL;
static lv_obj_t *enemies[MAX_ENEMIES];
static lv_obj_t *score_label = NULL;
static lv_obj_t *game_over_panel = NULL;
static lv_obj_t *start_menu_panel = NULL;
static lv_obj_t *left_btn = NULL;
static lv_obj_t *right_btn = NULL;
/* --- TIMERS --- */
static lv_timer_t *game_timer_handle = NULL;
static lv_timer_t *level_timer_handle = NULL;
/* --- GAME VARIABLES --- */
static int player_x = (SCREEN_WIDTH / 2) - (PLAYER_SIZE / 2);
static int enemies_x[MAX_ENEMIES];
static float enemies_y[MAX_ENEMIES]; // Changed to float for smooth varying speeds
static float enemies_individual_speed[MAX_ENEMIES]; // Individual speed variance
// History buffer to prevent clumping
static int spawn_history[2] = {-1, -1};
static int base_speed = 4;
static int seconds_elapsed = 0;
static int score = 0;
static int prev_score = -1;
static int high_scores[MAX_LEADERBOARD] = {0, 0, 0};
/* --- SHAPES --- */
static lv_point_t star_points[] = {
{22, 0}, {45, 45}, {22, 35}, {0, 45}, {22, 0}
};
/* --- STYLES --- */
static lv_style_t style_box;
static lv_style_t style_star;
static lv_style_t style_text_large;
/* --- FORWARD DECLARATIONS --- */
void start_gameplay_setup(void);
void show_game_over(void);
void game_loop(lv_timer_t *timer);
void level_timer(lv_timer_t *t);
void update_leaderboard(int new_score);
/* --- IMPROVED RANDOMIZER --- */
static int get_new_enemy_x(void) {
// 1. Calculate how many lanes fit
int total_lanes = (SCREEN_WIDTH - ENEMY_SIZE) / LANE_WIDTH;
// 2. Calculate offset to PERFECTLY CENTER the grid
int grid_width = total_lanes * LANE_WIDTH;
int x_offset = (SCREEN_WIDTH - grid_width) / 2;
int new_lane;
bool is_duplicate;
// 3. Smart Retry Loop
// Keep picking a lane until it is NOT in our recent history
do {
new_lane = rand() % total_lanes;
is_duplicate = false;
if(new_lane == spawn_history[0] || new_lane == spawn_history[1]) {
is_duplicate = true;
}
} while (is_duplicate);
// Update History (Shift old values)
spawn_history[1] = spawn_history[0];
spawn_history[0] = new_lane;
// Return centered pixel coordinate + small jitter
return x_offset + (new_lane * LANE_WIDTH) + (rand() % 5);
}
/* --- LEADERBOARD --- */
void update_leaderboard(int new_score) {
for(int i = 0; i < MAX_LEADERBOARD; i++) {
if(new_score > high_scores[i]) {
for(int j = MAX_LEADERBOARD - 1; j > i; j--) {
high_scores[j] = high_scores[j-1];
}
high_scores[i] = new_score;
break;
}
}
}
/* --- GAMEPLAY LOOP --- */
void game_loop(lv_timer_t *timer)
{
(void)timer;
if(!player) return;
bool collision_detected = false;
for(int i = 0; i < MAX_ENEMIES; i++) {
if(enemies[i] == NULL) continue;
// Move with individual speed variance
enemies_y[i] += enemies_individual_speed[i];
// Respawn logic
if (enemies_y[i] > SCREEN_HEIGHT) {
enemies_y[i] = -40;
enemies_x[i] = get_new_enemy_x();
// Randomize individual speed slightly (Base speed + random 0.0 to 1.5)
enemies_individual_speed[i] = base_speed + ((rand() % 15) / 10.0f);
score++;
}
lv_obj_set_pos(enemies[i], enemies_x[i], (int)enemies_y[i]);
// Collision
if (abs(player_x - enemies_x[i]) < (PLAYER_SIZE/2 + ENEMY_SIZE/2 - 8) &&
abs((SCREEN_HEIGHT - PLAYER_SIZE - 10) - (int)enemies_y[i]) < (PLAYER_SIZE/2 + ENEMY_SIZE/2 - 8))
{
collision_detected = true;
}
}
lv_obj_set_pos(player, player_x, SCREEN_HEIGHT - PLAYER_SIZE - 10);
if (score != prev_score) {
char buf[32];
sprintf(buf, "Score: %d", score);
lv_label_set_text(score_label, buf);
prev_score = score;
}
if (collision_detected) {
show_game_over();
}
}
void level_timer(lv_timer_t *t) {
(void)t;
seconds_elapsed++;
if (seconds_elapsed > 0 && seconds_elapsed % 10 == 0) {
if (base_speed < 15) base_speed += 1; // Increase base speed
}
}
/* --- CONTROLS --- */
static void move_left(lv_event_t *e) {
(void)e;
player_x -= MOVE_STEP;
if (player_x < 0) player_x = 0;
}
static void move_right(lv_event_t *e) {
(void)e;
player_x += MOVE_STEP;
if (player_x > (SCREEN_WIDTH - PLAYER_SIZE)) {
player_x = (SCREEN_WIDTH - PLAYER_SIZE);
}
}
/* --- MENUS --- */
static void start_btn_handler(lv_event_t * e)
{
if(start_menu_panel) {
lv_obj_del(start_menu_panel);
start_menu_panel = NULL;
}
start_gameplay_setup();
}
static void restart_event_handler(lv_event_t * e)
{
if(game_over_panel) {
lv_obj_del(game_over_panel);
game_over_panel = NULL;
}
score = 0;
prev_score = -1;
base_speed = 4;
seconds_elapsed = 0;
player_x = (SCREEN_WIDTH / 2) - (PLAYER_SIZE / 2);
spawn_history[0] = -1;
spawn_history[1] = -1;
for(int i = 0; i < MAX_ENEMIES; i++) {
enemies_x[i] = get_new_enemy_x();
enemies_y[i] = -50 - (i * ENEMY_SPACING);
enemies_individual_speed[i] = base_speed;
}
lv_label_set_text(score_label, "Score: 0");
lv_timer_resume(game_timer_handle);
lv_timer_resume(level_timer_handle);
}
/* --- SETUP --- */
void start_gameplay_setup(void)
{
score_label = lv_label_create(lv_scr_act());
lv_label_set_text(score_label, "Score: 0");
lv_obj_set_style_text_color(score_label, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(score_label, LV_ALIGN_TOP_MID, 0, 10);
player = lv_obj_create(lv_scr_act());
lv_obj_set_size(player, PLAYER_SIZE, PLAYER_SIZE);
lv_obj_set_style_bg_opa(player, LV_OPA_0, 0);
lv_obj_set_style_border_width(player, 0, 0);
lv_obj_clear_flag(player, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_pos(player, player_x, SCREEN_HEIGHT - PLAYER_SIZE - 10);
lv_obj_t * star_line = lv_line_create(player);
lv_line_set_points(star_line, star_points, 5);
lv_obj_add_style(star_line, &style_star, 0);
lv_obj_center(star_line);
for(int i = 0; i < MAX_ENEMIES; i++) {
enemies[i] = lv_obj_create(lv_scr_act());
lv_obj_add_style(enemies[i], &style_box, 0);
lv_obj_set_size(enemies[i], ENEMY_SIZE, ENEMY_SIZE);
lv_obj_clear_flag(enemies[i], LV_OBJ_FLAG_SCROLLABLE);
enemies_x[i] = get_new_enemy_x();
enemies_y[i] = -50 - (i * ENEMY_SPACING);
enemies_individual_speed[i] = base_speed; // Start synced
lv_obj_set_pos(enemies[i], enemies_x[i], (int)enemies_y[i]);
}
left_btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(left_btn, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
lv_obj_set_pos(left_btn, 0, 0);
lv_obj_set_style_bg_opa(left_btn, LV_OPA_0, 0);
lv_obj_add_event_cb(left_btn, move_left, LV_EVENT_CLICKED, NULL);
right_btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(right_btn, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
lv_obj_set_pos(right_btn, SCREEN_WIDTH / 2, 0);
lv_obj_set_style_bg_opa(right_btn, LV_OPA_0, 0);
lv_obj_add_event_cb(right_btn, move_right, LV_EVENT_CLICKED, NULL);
game_timer_handle = lv_timer_create(game_loop, 20, NULL);
level_timer_handle = lv_timer_create(level_timer, 1000, NULL);
}
void show_game_over(void)
{
lv_timer_pause(game_timer_handle);
lv_timer_pause(level_timer_handle);
update_leaderboard(score);
game_over_panel = lv_obj_create(lv_scr_act());
lv_obj_set_size(game_over_panel, 300, 240);
lv_obj_center(game_over_panel);
lv_obj_set_style_bg_color(game_over_panel, lv_color_hex(0x1a1a1a), 0);
lv_obj_set_style_border_color(game_over_panel, lv_color_hex(0xFF0000), 0);
lv_obj_set_style_border_width(game_over_panel, 3, 0);
lv_obj_t * title = lv_label_create(game_over_panel);
lv_label_set_text(title, "GAME OVER");
lv_obj_add_style(title, &style_text_large, 0);
lv_obj_set_style_text_color(title, lv_color_hex(0xFF4444), 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 5);
lv_obj_t * score_msg = lv_label_create(game_over_panel);
char buf[64];
sprintf(buf, "Your Score: %d", score);
lv_label_set_text(score_msg, buf);
lv_obj_set_style_text_color(score_msg, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(score_msg, LV_ALIGN_TOP_MID, 0, 45);
lv_obj_t * lb_list = lv_label_create(game_over_panel);
char lb_buf[128];
sprintf(lb_buf, "1. %d\n2. %d\n3. %d", high_scores[0], high_scores[1], high_scores[2]);
lv_label_set_text(lb_list, lb_buf);
lv_obj_set_style_text_color(lb_list, lv_color_hex(0x00EEFF), 0);
lv_obj_align(lb_list, LV_ALIGN_TOP_MID, 0, 80);
lv_obj_t * btn = lv_btn_create(game_over_panel);
lv_obj_set_size(btn, 140, 40);
lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -5);
lv_obj_set_style_bg_color(btn, lv_color_hex(0x00AACC), 0);
lv_obj_add_event_cb(btn, restart_event_handler, LV_EVENT_CLICKED, NULL);
lv_obj_t * btn_label = lv_label_create(btn);
lv_label_set_text(btn_label, "TRY AGAIN");
lv_obj_center(btn_label);
}
void create_start_menu(void)
{
start_menu_panel = lv_obj_create(lv_scr_act());
lv_obj_set_size(start_menu_panel, LV_PCT(100), LV_PCT(100));
lv_obj_set_style_bg_color(start_menu_panel, lv_color_hex(0x101010), 0);
lv_obj_set_style_border_width(start_menu_panel, 0, 0);
lv_obj_clear_flag(start_menu_panel, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t * title = lv_label_create(start_menu_panel);
lv_label_set_text(title, "AMAN DODGER");
lv_obj_add_style(title, &style_text_large, 0);
lv_obj_set_style_text_color(title, lv_color_hex(0x00EEFF), 0);
lv_obj_align(title, LV_ALIGN_CENTER, 0, -50);
lv_obj_t * btn = lv_btn_create(start_menu_panel);
lv_obj_set_size(btn, 160, 60);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(0x00CC00), 0);
lv_obj_add_event_cb(btn, start_btn_handler, LV_EVENT_CLICKED, NULL);
lv_obj_t * btn_label = lv_label_create(btn);
lv_label_set_text(btn_label, "START GAME");
lv_obj_set_style_text_font(btn_label, &lv_font_montserrat_16, 0);
lv_obj_center(btn_label);
}
void game_start(void)
{
srand((unsigned)time(NULL));
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_hex(0x101010), 0);
lv_style_init(&style_box);
lv_style_set_radius(&style_box, 2);
lv_style_set_bg_color(&style_box, lv_color_hex(0xFF3333));
lv_style_set_border_width(&style_box, 2);
lv_style_set_border_color(&style_box, lv_color_hex(0x880000));
lv_style_init(&style_star);
lv_style_set_line_width(&style_star, 3);
lv_style_set_line_color(&style_star, lv_color_hex(0x00EEFF));
lv_style_set_line_rounded(&style_star, true);
lv_style_init(&style_text_large);
lv_style_set_text_font(&style_text_large, &lv_font_montserrat_16);
create_start_menu();
}
Comments