6 Commits

Author SHA1 Message Date
Priec
42d07e9715 multithreading done 2025-12-11 12:15:20 +01:00
Priec
8382d1caec button press and blinking led on a button press 2025-12-11 11:30:56 +01:00
Priec
165540588f physical button press 2025-12-11 09:29:13 +01:00
Priec
0083fff73a fixed crawling 2025-12-11 08:31:39 +01:00
Filipriec
ca7eac70e4 movement class 2025-11-20 17:37:27 +01:00
Filipriec
3e47f855ed cleaning 2025-11-20 16:45:50 +01:00
17 changed files with 223 additions and 189 deletions

View File

@@ -49,7 +49,7 @@ static const int CHARACTER_CRAWL1_COLLISION_RIGHT_OFFSET = 1;
static const int CHARACTER_CRAWL1_COLLISION_WIDTH = CHARACTER_CRAWL1_FRAME_WIDTH - (CHARACTER_CRAWL1_COLLISION_LEFT_OFFSET + CHARACTER_CRAWL1_COLLISION_RIGHT_OFFSET); static const int CHARACTER_CRAWL1_COLLISION_WIDTH = CHARACTER_CRAWL1_FRAME_WIDTH - (CHARACTER_CRAWL1_COLLISION_LEFT_OFFSET + CHARACTER_CRAWL1_COLLISION_RIGHT_OFFSET);
static const int CHARACTER_CRAWL1_COLLISION_HEIGHT = CHARACTER_CRAWL1_FRAME_HEIGHT - 2; static const int CHARACTER_CRAWL1_COLLISION_HEIGHT = CHARACTER_CRAWL1_FRAME_HEIGHT - 2;
// Crawl2 collision box (lower posture smaller collision height) // Crawl2 collision box (lower posture - smaller collision height)
static const int CHARACTER_CRAWL2_FRAME_WIDTH = 8; static const int CHARACTER_CRAWL2_FRAME_WIDTH = 8;
static const int CHARACTER_CRAWL2_COLLISION_LEFT_OFFSET = 1; static const int CHARACTER_CRAWL2_COLLISION_LEFT_OFFSET = 1;
static const int CHARACTER_CRAWL2_COLLISION_RIGHT_OFFSET = 0; static const int CHARACTER_CRAWL2_COLLISION_RIGHT_OFFSET = 0;

View File

@@ -5,7 +5,7 @@
#include "../assets/character_run_frames.h" #include "../assets/character_run_frames.h"
#include "../assets/character_crawl_frames.h" #include "../assets/character_crawl_frames.h"
// Simple obstacle representation // Obstacle representation
struct Obstacle { struct Obstacle {
int x; int x;
int y; int y;
@@ -13,7 +13,6 @@ struct Obstacle {
int height; int height;
}; };
// Axis-aligned bounding-box collision
inline bool check_collision(const CharacterPosition& player, inline bool check_collision(const CharacterPosition& player,
MovementType movement, MovementType movement,
const Obstacle& obs) { const Obstacle& obs) {
@@ -48,7 +47,7 @@ inline bool check_collision(const CharacterPosition& player,
break; break;
} }
// Player's bounding box // Player bounding box
const int player_left = box_x; const int player_left = box_x;
const int player_right = player_left + box_w; const int player_right = player_left + box_w;
const int player_top = player_bottom + box_h; const int player_top = player_bottom + box_h;
@@ -59,7 +58,7 @@ inline bool check_collision(const CharacterPosition& player,
const int obs_bottom = obs.y; const int obs_bottom = obs.y;
const int obs_top = obs_bottom + obs.height; const int obs_top = obs_bottom + obs.height;
// Simple overlap check // Overlap check
bool horizontal = player_left < obs_right && player_right > obs_left; bool horizontal = player_left < obs_right && player_right > obs_left;
bool vertical = player_bottom < obs_top && player_top > obs_bottom; bool vertical = player_bottom < obs_top && player_top > obs_bottom;
return horizontal && vertical; return horizontal && vertical;

View File

@@ -0,0 +1,6 @@
// src/game/game_state.cpp
#include "game_state.h"
GameState g_state;
Mutex g_state_mutex;

View File

@@ -0,0 +1,34 @@
// src/game/game_state.h
#pragma once
#include "mbed.h"
#include "../render/player.h"
#include "../render/player_positioning.h"
#include "../assets/obstacle_crawl_frames.h"
// POZOR Z OBSTACLE MANAGERA
constexpr int MAX_RENDER_OBSTACLES = 6;
struct ObstacleRenderData {
int x;
int y;
CrawlObstacleType type;
bool active;
};
// All data needed to render one frame
struct GameState {
CharacterPosition player_pos;
MovementType movement;
int frame_index;
int background_shift;
ObstacleRenderData obstacles[MAX_RENDER_OBSTACLES];
bool game_over;
bool need_redraw;
};
// Global shared state and mutex
extern GameState g_state;
extern Mutex g_state_mutex;

View File

@@ -4,7 +4,7 @@
#include "../assets/obstacle_crawl_frames.h" #include "../assets/obstacle_crawl_frames.h"
#include "../assets/background_frame.h" #include "../assets/background_frame.h"
// One movable obstacle // Movable obstacle
struct MovingObstacle { struct MovingObstacle {
Obstacle data; Obstacle data;
CrawlObstacleType type; CrawlObstacleType type;
@@ -26,7 +26,7 @@ inline int spawn_obstacle(CrawlObstacleType type, int x_start) {
obstacle_pool[i].data.width = (type == CrawlObstacleType::Crawl1) ? OBSTACLE_CRAWL1_COLLISION_WIDTH : OBSTACLE_CRAWL2_COLLISION_WIDTH; obstacle_pool[i].data.width = (type == CrawlObstacleType::Crawl1) ? OBSTACLE_CRAWL1_COLLISION_WIDTH : OBSTACLE_CRAWL2_COLLISION_WIDTH;
obstacle_pool[i].data.height = (type == CrawlObstacleType::Crawl1) ? OBSTACLE_CRAWL1_COLLISION_HEIGHT : OBSTACLE_CRAWL2_COLLISION_HEIGHT; obstacle_pool[i].data.height = (type == CrawlObstacleType::Crawl1) ? OBSTACLE_CRAWL1_COLLISION_HEIGHT : OBSTACLE_CRAWL2_COLLISION_HEIGHT;
// Default Y so that the SPRITE touches the top of the screen. // Default Y so that the character touches the top of the screen.
// We store Y as the bottom of the COLLISION BOX in bottom-based world coords. // We store Y as the bottom of the COLLISION BOX in bottom-based world coords.
int top_offset = 0; int top_offset = 0;
if (type == CrawlObstacleType::Crawl1) { if (type == CrawlObstacleType::Crawl1) {

View File

@@ -6,7 +6,6 @@
#include "collision.h" #include "collision.h"
#include "mbed.h" #include "mbed.h"
// Small helper for minimal mbed-safe clamp
inline int clamp_min(int a, int b) { return (a > b) ? a : b; } inline int clamp_min(int a, int b) { return (a > b) ? a : b; }
inline int clamp_max(int a, int b) { return (a < b) ? a : b; } inline int clamp_max(int a, int b) { return (a < b) ? a : b; }
@@ -26,12 +25,8 @@ public:
// Periodically spawn new obstacle // Periodically spawn new obstacle
if (tick_counter_ % SPAWN_EVERY_TICKS == 0) { if (tick_counter_ % SPAWN_EVERY_TICKS == 0) {
auto type = (spawn_index_++ % 2 == 0) auto type = (spawn_index_++ % 2 == 0) ? CrawlObstacleType::Crawl1 : CrawlObstacleType::Crawl2;
? CrawlObstacleType::Crawl1 const int w = (type == CrawlObstacleType::Crawl1) ? OBSTACLE_CRAWL1_FRAME_WIDTH : OBSTACLE_CRAWL2_FRAME_WIDTH;
: CrawlObstacleType::Crawl2;
const int w = (type == CrawlObstacleType::Crawl1)
? OBSTACLE_CRAWL1_FRAME_WIDTH
: OBSTACLE_CRAWL2_FRAME_WIDTH;
spawn_obstacle(type, VIEW_WIDTH - w - 1); spawn_obstacle(type, VIEW_WIDTH - w - 1);
} }

View File

@@ -91,19 +91,20 @@ static inline int compute_frame_index(int frame_count,
} }
} // namespace } // namespace
void WalkingState::update() { void MovementState::update() {
// stop crawling after duration // stop crawling after duration
if ((current_state == PlayerState::Crawl1 || if ((current_state == PlayerState::Crawl1 ||
current_state == PlayerState::Crawl2) && current_state == PlayerState::Crawl2) &&
state_timer.elapsed_time() >= CRAWL_DURATION) { state_timer.elapsed_time() >= CRAWL_DURATION) {
current_state = PlayerState::Walk; current_state = previous_state;
state_timer.stop(); state_timer.stop();
} }
} }
void WalkingState::start_crawl(PlayerState crawl_type) { void MovementState::start_crawl(PlayerState crawl_type) {
if (crawl_type != PlayerState::Crawl1 && crawl_type != PlayerState::Crawl2) if (current_state != PlayerState::Crawl1 && current_state != PlayerState::Crawl2) {
return; previous_state = current_state;
}
current_state = crawl_type; current_state = crawl_type;
state_timer.stop(); state_timer.stop();
@@ -111,7 +112,7 @@ void WalkingState::start_crawl(PlayerState crawl_type) {
state_timer.start(); state_timer.start();
} }
void WalkingState::set_state(PlayerState s) { void MovementState::set_state(PlayerState s) {
if (current_state != s) { if (current_state != s) {
walk_index = run_index = crawl1_index = crawl2_index = 0; walk_index = run_index = crawl1_index = crawl2_index = 0;
phase = 0.0f; phase = 0.0f;
@@ -119,7 +120,7 @@ void WalkingState::set_state(PlayerState s) {
current_state = s; current_state = s;
} }
void WalkingState::set_motion_state_for_speed(int player_speed, int ground_speed) { void MovementState::set_motion_state_for_speed(int player_speed, int ground_speed) {
int relative = player_speed - ground_speed; int relative = player_speed - ground_speed;
PlayerState new_state = (relative > 1) ? PlayerState::Run : PlayerState::Walk; PlayerState new_state = (relative > 1) ? PlayerState::Run : PlayerState::Walk;
if (new_state != current_state) { if (new_state != current_state) {
@@ -130,7 +131,7 @@ void WalkingState::set_motion_state_for_speed(int player_speed, int ground_speed
} }
// TODO THIS NEEDS REDESIGN FOR WALK/RUN // TODO THIS NEEDS REDESIGN FOR WALK/RUN
void WalkingState::toggle_walk_frame(float player_speed, int tick_counter) { void MovementState::toggle_walk_frame(float player_speed, int tick_counter) {
switch (current_state) { switch (current_state) {
case PlayerState::Run: case PlayerState::Run:
run_index = compute_frame_index(CHARACTER_RUN_FRAME_COUNT, player_speed, tick_counter); run_index = compute_frame_index(CHARACTER_RUN_FRAME_COUNT, player_speed, tick_counter);
@@ -150,7 +151,7 @@ void WalkingState::toggle_walk_frame(float player_speed, int tick_counter) {
} }
} }
FrameSelection WalkingState::get_frame_selection() const { FrameSelection MovementState::get_frame_selection() const {
FrameSelection f{}; FrameSelection f{};
switch (current_state) { switch (current_state) {
case PlayerState::Walk: case PlayerState::Walk:

View File

@@ -12,9 +12,10 @@ struct FrameSelection {
int frame_index; int frame_index;
}; };
class WalkingState { class MovementState {
private: private:
PlayerState current_state = PlayerState::Walk; PlayerState current_state = PlayerState::Walk;
PlayerState previous_state = PlayerState::Walk;
Timer state_timer; Timer state_timer;
int walk_index = 0; // cycles 06 int walk_index = 0; // cycles 06
int run_index = 0; // cycles 06 int run_index = 0; // cycles 06

View File

@@ -0,0 +1,33 @@
// src/hardware/button.cpp
#include "button.h"
ButtonHandler::ButtonHandler(PinName pin, PinName led_pin) : button(pin), led(led_pin, 0) {
// ISR for button press (falling edge)
button.fall(callback(this, &ButtonHandler::on_pressed));
}
void ButtonHandler::on_pressed() {
pressed_flag = true;
}
ButtonEvent ButtonHandler::poll() {
static Timer blink_timer;
static bool blinking = false;
if (pressed_flag) {
pressed_flag = false;
led = 1;
blink_timer.reset();
blink_timer.start();
blinking = true;
return ButtonEvent::Pressed;
}
if (blinking && blink_timer.elapsed_time() >= 500ms) {
led = 0;
blink_timer.stop();
blinking = false;
}
return ButtonEvent::None;
}

View File

@@ -0,0 +1,23 @@
// src/hardware/button.h
#pragma once
#include "mbed.h"
enum class ButtonEvent {
None = 0,
Pressed = 1,
};
class ButtonHandler {
private:
InterruptIn button;
DigitalOut led;
volatile bool pressed_flag = false;
volatile bool blink_flag = false;
void on_pressed();
public:
ButtonHandler(PinName pin = BUTTON1, PinName led_pin = LED1);
ButtonEvent poll();
};

View File

@@ -1,49 +0,0 @@
// src/hardware/uart.cpp
#include "uart.h"
#include <cstring>
UartReader::UartReader(BufferedSerial &serial) : serial_port(serial) {
memset(rx_buffer, 0, sizeof(rx_buffer));
memset(message, 0, sizeof(message));
}
UartEvent UartReader::poll() {
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) {
strncpy(message, rx_buffer, sizeof(message) - 1);
message_active = true;
changed = true;
triggered = true;
}
}
// casovac na 1s zobrazenia spravy
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 UartEvent::Triggered;
if (changed)
return UartEvent::MessageUpdate;
return UartEvent::NoChange;
}
const char* UartReader::get_message() const {
return message_active ? message : nullptr;
}

View File

@@ -1,35 +0,0 @@
// src/hardware/uart.h
#pragma once
#include "mbed.h"
#include <cstddef>
constexpr size_t UART_BUFFER_SIZE = 64;
constexpr auto MESSAGE_DISPLAY_DURATION = 1s;
enum class UartEvent {
NoChange = 0, // No new data
MessageUpdate = 1, // Message changed (received or expired)
Triggered = 2 // New message received (trigger action)
};
class UartReader {
private:
BufferedSerial &serial_port;
char rx_buffer[UART_BUFFER_SIZE];
char message[UART_BUFFER_SIZE];
bool message_active = false;
Timer msg_timer;
bool timer_started = false;
public:
UartReader(BufferedSerial &serial);
// Poll for UART events
UartEvent poll();
// Get current message (nullptr if no active message)
const char* get_message() const;
bool has_message() const { return message_active; }
};

View File

@@ -21,6 +21,5 @@ int main(void) {
serial_port.set_format(8, BufferedSerial::None, 1); serial_port.set_format(8, BufferedSerial::None, 1);
printf("Baud: %d, Format: 8-N-1\r\n", BAUD_RATE); printf("Baud: %d, Format: 8-N-1\r\n", BAUD_RATE);
// Just call into render feature
render_loop(6); render_loop(6);
} }

View File

@@ -5,7 +5,7 @@
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
void draw_mask(const char *unused_filename, int shift, const char *text) { void draw_mask(const char *unused_filename, int shift) {
// Terminal clear + home // Terminal clear + home
printf("\033[2J\033[H"); printf("\033[2J\033[H");
@@ -25,9 +25,5 @@ void draw_mask(const char *unused_filename, int shift, const char *text) {
printf("\r\n"); printf("\r\n");
} }
if (text && text[0] != '\0') {
printf("\r\n[RX] %s\r\n", text);
}
fflush(stdout); fflush(stdout);
} }

View File

@@ -4,4 +4,4 @@
constexpr int VIEW_WIDTH = 90; constexpr int VIEW_WIDTH = 90;
// Draws the ASCII art background with optional message // Draws the ASCII art background with optional message
void draw_mask(const char *unused_filename, int shift, const char *text = nullptr); void draw_mask(const char *unused_filename, int shift);

View File

@@ -5,137 +5,170 @@
#include "background.h" #include "background.h"
#include "mbed.h" #include "mbed.h"
#include "player.h" #include "player.h"
#include "background.h"
#include "../game/game_state.h"
#include "../game/state.h" #include "../game/state.h"
#include "../game/animation.h" #include "../game/animation.h"
#include "../game/collision.h" #include "../game/collision.h"
#include "../hardware/uart.h" #include "../hardware/button.h"
#include "../render/player.h" #include "../render/player.h"
#include "../render/obstacle.h" #include "../render/obstacle.h"
#include "../timing/speed_controller.h" #include "../timing/speed_controller.h"
#include "../timing/movement_controller.h" #include "../timing/movement_controller.h"
#include "../game/obstacle_system.h" #include "../game/obstacle_system.h"
extern BufferedSerial serial_port;
extern DigitalOut led; extern DigitalOut led;
// Constants // Constants
// constexpr int PLAYER_X = 9; // constexpr int PLAYER_X = 9;
constexpr int PLAYER_X = 29; constexpr int PLAYER_X = 29;
constexpr int PLAYER_Y = 6; constexpr int PLAYER_Y = 6;
constexpr int STACK_SIZE = 4096;
void draw_mask(const char *unused_filename, int shift, const char *text); Thread logic_thread(osPriorityNormal, STACK_SIZE);
Thread render_thread(osPriorityNormal, STACK_SIZE);
void render_loop(int speed) { void logic_loop() {
WalkingState player_state; MovementState player_state;
AnimationController animation; AnimationController animation;
UartReader uart(serial_port); ButtonHandler button;
MovementController mover(PLAYER_X, VIEW_WIDTH); MovementController mover(PLAYER_X, VIEW_WIDTH);
SpeedController timing; SpeedController timing;
timing.set_ground_speed(speed); timing.set_ground_speed(6);
CharacterPosition pos = {PLAYER_X, PLAYER_Y}; CharacterPosition pos = {PLAYER_X, PLAYER_Y};
player_state.set_state(PlayerState::Walk);
const char *bg_file = "background_dark_inverted.txt";
bool need_redraw = false;
int anim_tick_counter = 0;
int tick_counter = 0;
int player_speed = 6;
bool game_over = false;
player_state.set_state(PlayerState::Run);
CrawlObstacleType type = CrawlObstacleType::Crawl1;
int start_x = VIEW_WIDTH + 10;
spawn_obstacle(type, start_x);
Timer spawn_timer; Timer spawn_timer;
spawn_timer.start(); spawn_timer.start();
int anim_tick_counter = 0;
int tick_counter = 0;
bool game_over = false;
// Spawn first obstacle
spawn_obstacle(CrawlObstacleType::Crawl1, VIEW_WIDTH + 10);
while (!game_over) { while (!game_over) {
tick_counter++; tick_counter++;
mover.update_position(player_speed, timing.get_ground_speed()); mover.update_position(6, timing.get_ground_speed());
pos.x = mover.get_position(); pos.x = mover.get_position();
// Spawn periodically
if (spawn_timer.elapsed_time() > 2s) { if (spawn_timer.elapsed_time() > 2s) {
spawn_timer.reset(); spawn_timer.reset();
CrawlObstacleType s_type = CrawlObstacleType::Crawl1; spawn_obstacle(CrawlObstacleType::Crawl1, VIEW_WIDTH);
int spawn_x = VIEW_WIDTH;
int idx = spawn_obstacle(s_type, spawn_x);
if (idx >= 0) {
obstacle_pool[idx].active = true;
}
} }
need_redraw = false; // Handle input
UartEvent uart_event = uart.poll(); if (button.poll() == ButtonEvent::Pressed) {
if (uart_event == UartEvent::MessageUpdate) {
need_redraw = true;
}
if (uart_event == UartEvent::Triggered) {
PlayerState current = player_state.get_state(); PlayerState current = player_state.get_state();
if (current == PlayerState::Walk || current == PlayerState::Run)
if (current == PlayerState::Walk || current == PlayerState::Run) {
player_state.start_crawl(PlayerState::Crawl1); player_state.start_crawl(PlayerState::Crawl1);
} else if (current == PlayerState::Crawl1)
else if (current == PlayerState::Crawl1) {
player_state.start_crawl(PlayerState::Crawl2); player_state.start_crawl(PlayerState::Crawl2);
}
need_redraw = true;
} }
// Check if crawl duration expired
player_state.update(); player_state.update();
if (animation.tick(speed)) { bool anim_tick = animation.tick(6);
if (anim_tick) {
anim_tick_counter++; anim_tick_counter++;
// move obstacles
int ground_speed = timing.get_ground_speed(); int ground_speed = timing.get_ground_speed();
for (int i = 0; i < MAX_OBSTACLES; i++) { for (int i = 0; i < MAX_OBSTACLES; i++) {
if (obstacle_pool[i].active) { if (obstacle_pool[i].active) {
obstacle_pool[i].data.x -= ground_speed; obstacle_pool[i].data.x -= ground_speed;
// deactivate when offscreen
if (obstacle_pool[i].data.x + obstacle_pool[i].data.width < 0) if (obstacle_pool[i].data.x + obstacle_pool[i].data.width < 0)
obstacle_pool[i].active = false; obstacle_pool[i].active = false;
} }
} }
need_redraw = true; player_state.toggle_walk_frame(6, anim_tick_counter);
}
if (need_redraw) {
player_state.toggle_walk_frame(player_speed, anim_tick_counter);
draw_mask(bg_file, animation.get_shift(), uart.get_message());
FrameSelection frame = player_state.get_frame_selection(); FrameSelection frame = player_state.get_frame_selection();
CharacterPosition draw_pos =
get_aligned_frame_position(pos, frame.movement, frame.frame_index);
CharacterPosition draw_pos = get_aligned_frame_position(pos, frame.movement, frame.frame_index); // check collision
draw_character(draw_pos.x, draw_pos.y, frame.movement, frame.frame_index); bool collision = false;
for (int i = 0; i < MAX_OBSTACLES; i++) { for (int i = 0; i < MAX_OBSTACLES; i++) {
if (!obstacle_pool[i].active) if (obstacle_pool[i].active &&
continue; check_collision(pos, frame.movement, obstacle_pool[i].data)) {
collision = true;
draw_obstacle(obstacle_pool[i].data.x, break;
obstacle_pool[i].data.y, }
obstacle_pool[i].type);
if (check_collision(pos, frame.movement, obstacle_pool[i].data)) {
printf("\033[2J\033[H");
printf("GAME OVER\r\n");
game_over = true;
break;
}
} }
if (game_over) g_state_mutex.lock();
break;
ThisThread::sleep_for(50ms); g_state.player_pos = draw_pos;
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); ThisThread::sleep_for(25ms);
} }
// final update
g_state_mutex.lock();
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) {
printf("\033[2J\033[H");
printf("GAME OVER\r\n");
break;
}
if (need) {
draw_mask("background", local.background_shift);
draw_character(local.player_pos.x, local.player_pos.y, local.movement,
local.frame_index);
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();
logic_thread.start(logic_loop);
render_thread.start(render_loop_thread);
logic_thread.join();
render_thread.join();
} }

View File

@@ -13,7 +13,5 @@ public:
int get_ground_speed() const { return ground_speed; } int get_ground_speed() const { return ground_speed; }
// Calculates how many frames to advance for current tick. // Calculates how many frames to advance for current tick.
// Takes objects current speed and a global tick counter
// to automatically handle slower "wait" behavior.
int frame_advance_for(int object_speed, int tick_counter) const; int frame_advance_for(int object_speed, int tick_counter) const;
}; };