// src/render/loop.cpp #include "loop.h" #include "../background_dark_inverted.h" #include "player_positioning.h" #include "background.h" #include "mbed.h" #include "player.h" #include "player_mask.h" #include extern BufferedSerial serial_port; extern DigitalOut led; // Constants constexpr size_t BUFFER_SIZE = 64; constexpr auto CRAWL_DURATION = 300ms; constexpr auto ANIMATION_TICK = 170ms; constexpr auto MESSAGE_DISPLAY_DURATION = 1s; constexpr int PLAYER_X = 9; constexpr int PLAYER_Y = 6; // PlayerState is what user pressed. We are mapping CharacterState to it enum class PlayerState { Walk1, Walk2, Crawl1 }; struct WalkingState { PlayerState current_state = PlayerState::Walk1; Timer state_timer; }; static char rx_buffer[BUFFER_SIZE]; static char message[BUFFER_SIZE]; static bool message_active = false; void draw_mask(const char *unused_filename, int shift, const char *text); // Returns: // 0 = no change - walk1 // 1 = normal redraw - walk2 // 2 = button triggered - crawl1 static int read_uart() { bool changed = false; bool triggered = false; // cita spravu z uartu if (serial_port.readable()) { memset(rx_buffer, 0, sizeof(rx_buffer)); ssize_t num = serial_port.read(rx_buffer, sizeof(rx_buffer) - 1); if (num > 0) { led = !led; // toogle ledky strncpy(message, rx_buffer, sizeof(message) - 1); message_active = true; changed = true; triggered = true; } } // casovac na 1s zobrazenia spravy static Timer msg_timer; static bool timer_started = false; if (!timer_started) { msg_timer.start(); timer_started = true; } // po jednu sekundu sa sprava zobrazi if (message_active && msg_timer.elapsed_time() > MESSAGE_DISPLAY_DURATION) { message_active = false; memset(message, 0, sizeof(message)); msg_timer.reset(); changed = true; } if (triggered) return 2; // LED toggled press if (changed) return 1; return 0; } static bool update_animation(Timer &anim_timer, int &shift, int speed) { if (anim_timer.elapsed_time() >= ANIMATION_TICK) { // speed determines scroll steps per tick shift += speed; anim_timer.reset(); return true; } return false; } static CharacterFrame get_character_frame(PlayerState state) { switch (state) { case PlayerState::Walk1: return CharacterFrame::Walk1; case PlayerState::Walk2: return CharacterFrame::Walk2; case PlayerState::Crawl1: return CharacterFrame::Crawl1; default: return CharacterFrame::Walk1; } } static void update_player_state(WalkingState &state) { // stop crawling if (state.current_state == PlayerState::Crawl1 && state.state_timer.elapsed_time() >= CRAWL_DURATION) { state.current_state = PlayerState::Walk1; state.state_timer.stop(); } } static void toggle_walk_frame(WalkingState &state) { if (state.current_state == PlayerState::Walk1) { state.current_state = PlayerState::Walk2; } else if (state.current_state == PlayerState::Walk2) { state.current_state = PlayerState::Walk1; } } static void start_crawl(WalkingState &state) { state.current_state = PlayerState::Crawl1; state.state_timer.reset(); state.state_timer.start(); } void render_loop(int speed) { WalkingState player_state; Timer anim_timer; anim_timer.start(); int shift = 0; CharacterPosition pos = {PLAYER_X, PLAYER_Y}; const char *bg_file = "background_dark_inverted.txt"; bool need_redraw = false; while (true) { need_redraw = false; int uart_state = read_uart(); // returns 0/1/2 if (uart_state == 1) { need_redraw = true; } if (uart_state == 2) { // start crawl frame for x time start_crawl(player_state); need_redraw = true; } // Check if crawl duration expired update_player_state(player_state); if (update_animation(anim_timer, shift, speed)) { need_redraw = true; } if (need_redraw) { draw_mask(bg_file, shift, message_active ? message : nullptr); CharacterFrame player_frame = get_character_frame(player_state.current_state); CharacterPosition draw_pos = get_aligned_frame_position(pos, player_frame); draw_character(draw_pos.x, draw_pos.y, player_frame); // alternate between frame 0 and 1 when not crawling if (player_state.current_state != PlayerState::Crawl1) { toggle_walk_frame(player_state); } ThisThread::sleep_for(50ms); } ThisThread::sleep_for(25ms); } }