// src/render/loop.cpp #include "loop.h" #include "../assets/background_frame.h" #include "player_positioning.h" #include "background.h" #include "mbed.h" #include "player.h" #include "background.h" #include "../game/game_over.h" #include "../game/game_state.h" #include "../game/state.h" #include "../game/animation.h" #include "../game/collision.h" #include "../hardware/button.h" #include "../render/player.h" #include "../render/obstacle.h" #include "../timing/speed_controller.h" #include "../timing/movement_controller.h" #include "../game/obstacle_system.h" extern DigitalOut led; ButtonHandler button(BUTTON1, LED1); // Constants // constexpr int PLAYER_X = 9; constexpr int PLAYER_X = 29; constexpr int PLAYER_Y = 6; constexpr int STACK_SIZE = 4096; void logic_loop(void *arg) { int speed = *(int *)arg; MovementState player_state; AnimationController animation; MovementController mover(PLAYER_X, VIEW_WIDTH); SpeedController timing; timing.set_ground_speed(speed); CharacterPosition pos = {PLAYER_X, PLAYER_Y}; Timer spawn_timer; spawn_timer.start(); Timer speed_timer; speed_timer.start(); int anim_tick_counter = 0; int tick_counter = 0; bool game_over = false; Timer game_timer; game_timer.start(); // Spawn first obstacle if (speed >= 5) spawn_obstacle(CrawlObstacleType::Crawl1, VIEW_WIDTH + 10); while (!game_over) { tick_counter++; if (speed_timer.elapsed_time() >= 6s) { speed++; timing.set_ground_speed(speed); speed_timer.reset(); // update player's animation mode if (speed < 4) player_state.set_state(PlayerState::Walk); else player_state.set_state(PlayerState::Run); } mover.update_position(speed, timing.get_ground_speed()); pos.x = mover.get_position(); // Spawn periodically if (speed >= 5 && spawn_timer.elapsed_time() > 6s) { spawn_timer.reset(); spawn_obstacle(CrawlObstacleType::Crawl1, VIEW_WIDTH); } // Handle input if (button.poll() == ButtonEvent::Pressed) { PlayerState current = player_state.get_state(); if (current == PlayerState::Walk || current == PlayerState::Run) player_state.start_crawl(PlayerState::Crawl1); else if (current == PlayerState::Crawl1) player_state.start_crawl(PlayerState::Crawl2); } player_state.update(); bool anim_tick = animation.tick(speed); if (anim_tick) { anim_tick_counter++; player_state.toggle_walk_frame(speed, anim_tick_counter); FrameSelection frame = player_state.get_frame_selection(); CharacterPosition draw_pos = get_aligned_frame_position(pos, frame.movement, frame.frame_index); int ground_speed = timing.get_ground_speed(); bool collision = false; for (int i = 0; i < MAX_OBSTACLES; i++) { if (!obstacle_pool[i].active) continue; // Keep original position int old_x = obstacle_pool[i].data.x; int new_x = old_x - ground_speed; // Remove if fully offscreen after movement if (new_x + obstacle_pool[i].data.width < 0) { obstacle_pool[i].active = false; continue; } // Compute swept region = min..max int left_swept = (new_x < old_x) ? new_x : old_x; int right_swept = ((new_x + obstacle_pool[i].data.width) > (old_x + obstacle_pool[i].data.width)) ? (new_x + obstacle_pool[i].data.width) : (old_x + obstacle_pool[i].data.width); // Temporarily create a synthetic obstacle covering the swept range Obstacle swept_obs{}; swept_obs.x = left_swept; swept_obs.y = obstacle_pool[i].data.y; swept_obs.width = right_swept - left_swept; swept_obs.height = obstacle_pool[i].data.height; // Perform continuous collision check if (check_collision(pos, frame.movement, swept_obs)) { collision = true; break; } // Apply the actual movement after check obstacle_pool[i].data.x = new_x; } g_state_mutex.lock(); g_state.player_pos = draw_pos; g_state.current_speed = speed; g_state.movement = frame.movement; g_state.frame_index = frame.frame_index; g_state.background_shift = animation.get_shift(); g_state.need_redraw = true; g_state.game_over = collision; for (int i = 0; i < MAX_OBSTACLES; i++) { g_state.obstacles[i].x = obstacle_pool[i].data.x; g_state.obstacles[i].y = obstacle_pool[i].data.y; g_state.obstacles[i].type = obstacle_pool[i].type; g_state.obstacles[i].active = obstacle_pool[i].active; } g_state_mutex.unlock(); if (collision) game_over = true; } ThisThread::sleep_for(25ms); } game_timer.stop(); int seconds = static_cast(game_timer.elapsed_time().count() / 1000000); g_state_mutex.lock(); g_state.elapsed_seconds = seconds; g_state.game_over = true; g_state_mutex.unlock(); } void render_loop_thread() { while (true) { g_state_mutex.lock(); bool need = g_state.need_redraw; bool over = g_state.game_over; GameState local = g_state; g_state.need_redraw = false; g_state_mutex.unlock(); if (over) { show_game_over_screen(local.elapsed_seconds, button); break; } if (need) { draw_mask("background", local.background_shift); draw_character(local.player_pos.x, local.player_pos.y, local.movement, local.frame_index); if (local.current_speed >= 5) { for (int i = 0; i < MAX_RENDER_OBSTACLES; i++) { if (local.obstacles[i].active) draw_obstacle(local.obstacles[i].x, local.obstacles[i].y, local.obstacles[i].type); } } } ThisThread::sleep_for(50ms); } } void render_loop(int speed) { g_state_mutex.lock(); g_state.need_redraw = false; g_state.game_over = false; g_state_mutex.unlock(); int *speed_ptr = new int(speed); Thread logic_thread(osPriorityNormal, STACK_SIZE); Thread render_thread(osPriorityNormal, STACK_SIZE); logic_thread.start(callback(logic_loop, (void *)speed_ptr)); render_thread.start(render_loop_thread); logic_thread.join(); render_thread.join(); delete speed_ptr; }