#include "game_logic.h" #include #include #include #include void update_snake(GameState &state) { std::array headpos = state.snake.front(); std::array newHead = headpos; if (state.snakeDirection == 0) { newHead = {headpos[0], headpos[1] - 1, state.snakeDirection}; } else if (state.snakeDirection == 1) { newHead = {headpos[0] + 1, headpos[1], state.snakeDirection}; } else if (state.snakeDirection == 2) { newHead = {headpos[0], headpos[1] + 1, state.snakeDirection}; } else if (state.snakeDirection == 3) { newHead = {headpos[0] - 1, headpos[1], state.snakeDirection}; } if (newHead[0] > X_MAX) { newHead[0] = 0; } if (newHead[1] > Y_MAX) { newHead[1] = 0; } if (newHead[0] < 0) { newHead[0] = X_MAX; } if (newHead[1] < 0) { newHead[1] = Y_MAX; } state.snake.push_front(newHead); state.snake.resize(state.snakelength); } bool check_collision_self(GameState &state) { if (state.snake.size() < 2 || state.gameOver) { return false; } const std::array headpos = state.snake.front(); for (auto it = state.snake.begin() + 1; it != state.snake.end(); ++it) { if ((*it)[0] == headpos[0] && (*it)[1] == headpos[1]) { state.gameOver = true; return true; } } return false; } bool check_collision_apples(GameState &state) { const std::array headpos = state.snake.front(); auto it = std::find_if(state.apples.begin(), state.apples.end(), [&](const std::array &apple) { return apple[0] == headpos[0] && apple[1] == headpos[1]; }); if (it == state.apples.end()) { return false; } state.score += APPLE_SCORE; state.snakelength++; state.applesEaten++; state.apples.erase(it); return true; } bool check_collision_apples_rotten(GameState &state) { const std::array headpos = state.snake.front(); auto it = std::find_if(state.bad_apples.begin(), state.bad_apples.end(), [&](const EvilItem &apple) { return apple.x == headpos[0] && apple.y == headpos[1]; }); if (it == state.bad_apples.end()) { return false; } state.snakelength--; state.bad_apples.erase(it); if (state.snakelength < 2) { state.gameOver = true; } return true; } bool check_collision_apples_cursed(GameState &state) { const std::array headpos = state.snake.front(); auto it = std::find_if(state.cursed_apples.begin(), state.cursed_apples.end(), [&](const EvilItem &apple) { return apple.x == headpos[0] && apple.y == headpos[1]; }); if (it == state.cursed_apples.end()) { return false; } state.cursed_apples.erase(it); state.curseTicksRemaining = CURSE_DURATION_TICKS; return true; } void spawn_apples(GameState &state) { if (state.apples.empty()) { std::array apple = {rand() % (X_MAX + 1), rand() % (Y_MAX + 1)}; // Don't place the apple inside a (pending) barrier, it would be unreachable for (int attempt = 0; attempt < 16 && is_barrier_cell(state, apple[0], apple[1], true); attempt++) { apple = {rand() % (X_MAX + 1), rand() % (Y_MAX + 1)}; } state.apples.push_back(apple); } } void spawn_apples_rotten(GameState &state) { if ((state.ticks % 10 == 0) && state.hardMode && (state.bad_apples.size() < 20)) { int x = rand() % (X_MAX + 1); int y = rand() % (Y_MAX + 1); for (int attempt = 0; attempt < 16 && (is_barrier_cell(state, x, y, true) || (!state.snake.empty() && state.snake.front()[0] == x && state.snake.front()[1] == y)); attempt++) { x = rand() % (X_MAX + 1); y = rand() % (Y_MAX + 1); } state.bad_apples.push_back({static_cast(x), static_cast(y), BAD_APPLE_LIFETIME_TICKS}); } } void spawn_apples_cursed(GameState &state) { if (state.cursed_apples.empty() && (state.ticks % 20 == 0) && ((rand() % 5) == 0)) { int x = rand() % (X_MAX + 1); int y = rand() % (Y_MAX + 1); for (int attempt = 0; attempt < 16 && (is_barrier_cell(state, x, y, true) || (!state.snake.empty() && state.snake.front()[0] == x && state.snake.front()[1] == y)); attempt++) { x = rand() % (X_MAX + 1); y = rand() % (Y_MAX + 1); } state.cursed_apples.push_back({static_cast(x), static_cast(y), CURSED_APPLE_LIFETIME_TICKS}); } } void tick_evil_items(GameState &state) { for (auto it = state.bad_apples.begin(); it != state.bad_apples.end();) { if (it->ticksRemaining > 0) { it->ticksRemaining--; } if (it->ticksRemaining == 0) { it = state.bad_apples.erase(it); } else { ++it; } } for (auto it = state.cursed_apples.begin(); it != state.cursed_apples.end();) { if (it->ticksRemaining > 0) { it->ticksRemaining--; } if (it->ticksRemaining == 0) { it = state.cursed_apples.erase(it); } else { ++it; } } } void tick_status_effects(GameState &state) { if (state.curseTicksRemaining > 0) { state.curseTicksRemaining--; } } uint8_t calculate_intensity_31(const GameState &state) { const uint16_t current_apples = state.applesEaten; const uint16_t apples = std::min( current_apples, static_cast(SPEED_APPLES_CAP)); return static_cast((static_cast(apples) * 31U) / static_cast(SPEED_APPLES_CAP)); } uint16_t calculate_timer_freq_mhz(const GameState &state) { const uint16_t base_mhz = state.hardMode ? SPEED_BASE_HARD_MHZ : SPEED_BASE_NORMAL_MHZ; const uint16_t max_mhz = state.hardMode ? SPEED_MAX_HARD_MHZ : SPEED_MAX_NORMAL_MHZ; const uint16_t current_apples = state.applesEaten; const uint16_t apples = std::min( current_apples, static_cast(SPEED_APPLES_CAP)); const uint32_t diff = static_cast(max_mhz - base_mhz); return static_cast(base_mhz + ((static_cast(apples) * diff) / static_cast(SPEED_APPLES_CAP))); } uint16_t calculate_timer_reload(const GameState &state) { const uint32_t freq_mhz = calculate_timer_freq_mhz(state); // Timer clock with ClockDivider_1024 is BUS_CLOCK >> 10 (32728 Hz) const uint32_t timer_clock_mhz = (BUS_CLOCK >> 10U) * 1000U; const uint32_t period_ticks = timer_clock_mhz / freq_mhz; return static_cast(-static_cast(period_ticks)); } // --------------------------------------------------------------------------- // Hard-mode temporary barriers // --------------------------------------------------------------------------- namespace { bool barrier_contains(const Barrier &barrier, int x, int y) { if (barrier.vertical) { return x == barrier.x && y >= barrier.y && y < barrier.y + barrier.length; } return y == barrier.y && x >= barrier.x && x < barrier.x + barrier.length; } // Manhattan distance on the wrapping playfield int wrapped_distance(int x1, int y1, int x2, int y2) { int dx = std::abs(x1 - x2); int dy = std::abs(y1 - y2); dx = std::min(dx, (X_MAX + 1) - dx); dy = std::min(dy, (Y_MAX + 1) - dy); return dx + dy; } bool cell_in_list(const std::vector> &list, int x, int y) { return std::any_of(list.begin(), list.end(), [&](const std::array &item) { return item[0] == x && item[1] == y; }); } bool cell_in_list(const std::vector &list, int x, int y) { return std::any_of(list.begin(), list.end(), [&](const EvilItem &item) { return item.x == x && item.y == y; }); } bool cell_blocked_for_barrier(const GameState &state, int x, int y) { if (is_barrier_cell(state, x, y, true)) { return true; } if (cell_in_list(state.apples, x, y) || cell_in_list(state.bad_apples, x, y) || cell_in_list(state.cursed_apples, x, y)) { return true; } if (!state.snake.empty()) { const auto &head = state.snake.front(); if (wrapped_distance(head[0], head[1], x, y) < BARRIER_HEAD_SAFE_DISTANCE) { return true; } } return std::any_of(state.snake.begin(), state.snake.end(), [&](const std::array &seg) { return seg[0] == x && seg[1] == y; }); } int max_concurrent_barriers(const GameState &state) { if (state.applesEaten < BARRIER_START_APPLES) { return 0; } const int apples = std::min(state.applesEaten, SPEED_APPLES_CAP); const int apple_span = SPEED_APPLES_CAP - BARRIER_START_APPLES; if (apple_span <= 0) { return BARRIER_MAX_COUNT; } const int progress = apples - BARRIER_START_APPLES; return 1 + ((progress * (BARRIER_MAX_COUNT - 1)) / apple_span); } } // namespace void reset_barriers(GameState &state) { // Called from the main thread; keep the VBlank/Timer IRQs out while resetting const int old_ime = enterCriticalSection(); for (Barrier &barrier : state.barriers) { barrier = Barrier{}; } state.barrierSpawnCooldown = BARRIER_FIRST_SPAWN_FRAMES; leaveCriticalSection(old_ime); } bool is_barrier_cell(const GameState &state, int x, int y, bool include_warning) { return std::any_of( state.barriers.begin(), state.barriers.end(), [&](const Barrier &b) { const bool relevant = (b.phase == BarrierPhase::Active) || (include_warning && b.phase == BarrierPhase::Warning); return relevant && barrier_contains(b, x, y); }); } uint8_t calculate_barrier_max_length(const GameState &state) { if (state.applesEaten <= BARRIER_START_APPLES) { return static_cast(BARRIER_BASE_MAX_LENGTH); } const int apples = std::min(state.applesEaten, SPEED_APPLES_CAP); const int apple_span = SPEED_APPLES_CAP - BARRIER_START_APPLES; if (apple_span <= 0) { return static_cast(BARRIER_MAX_LENGTH); } const int length_span = BARRIER_MAX_LENGTH - BARRIER_BASE_MAX_LENGTH; const int progress = apples - BARRIER_START_APPLES; return static_cast(BARRIER_BASE_MAX_LENGTH + ((progress * length_span) / apple_span)); } uint8_t calculate_barrier_max_batch(const GameState &state) { if (state.applesEaten >= BARRIER_MULTI_SPAWN_APPLES_3) { return 3; } if (state.applesEaten >= BARRIER_MULTI_SPAWN_APPLES_2) { return 2; } return 1; } uint16_t calculate_barrier_interval_frames(const GameState &state) { if (state.applesEaten <= BARRIER_START_APPLES) { return BARRIER_INTERVAL_MAX_FRAMES; } const int apples = std::min(state.applesEaten, SPEED_APPLES_CAP); const int apple_span = SPEED_APPLES_CAP - BARRIER_START_APPLES; if (apple_span <= 0) { return BARRIER_INTERVAL_MIN_FRAMES; } const int span = BARRIER_INTERVAL_MAX_FRAMES - BARRIER_INTERVAL_MIN_FRAMES; const int progress = apples - BARRIER_START_APPLES; return static_cast(BARRIER_INTERVAL_MAX_FRAMES - ((progress * span) / apple_span)); } void tick_barriers_frame(GameState &state) { if (state.applesEaten < BARRIER_START_APPLES) { state.barrierSpawnCooldown = BARRIER_FIRST_SPAWN_FRAMES; return; } if (state.barrierSpawnCooldown > 0) { state.barrierSpawnCooldown = static_cast(state.barrierSpawnCooldown - 1U); } for (Barrier &barrier : state.barriers) { if (barrier.phase == BarrierPhase::Inactive) { continue; } if (barrier.framesLeft > 0) { barrier.framesLeft--; continue; } if (barrier.phase == BarrierPhase::Warning) { barrier.phase = BarrierPhase::Active; barrier.framesLeft = BARRIER_ACTIVE_FRAMES; } else { barrier.phase = BarrierPhase::Inactive; } } } void spawn_barriers(GameState &state) { if (!state.hardMode || state.applesEaten < BARRIER_START_APPLES || state.barrierSpawnCooldown > 0) { return; } const int in_use = static_cast(std::count_if( state.barriers.begin(), state.barriers.end(), [](const Barrier &b) { return b.phase != BarrierPhase::Inactive; })); const int max_allowed = max_concurrent_barriers(state); const int available = max_allowed - in_use; if (available <= 0) { state.barrierSpawnCooldown = BARRIER_RETRY_FRAMES; return; } const int max_batch = calculate_barrier_max_batch(state); int to_spawn = 1; if (max_batch > 1 && available > 1) { const int max_possible = std::min(max_batch, available); to_spawn = 1 + (rand() % max_possible); } int spawned_count = 0; const int current_max = calculate_barrier_max_length(state); const int length_range = current_max - BARRIER_MIN_LENGTH + 1; for (int spawn_idx = 0; spawn_idx < to_spawn; spawn_idx++) { auto free_slot = std::find_if( state.barriers.begin(), state.barriers.end(), [](const Barrier &b) { return b.phase == BarrierPhase::Inactive; }); if (free_slot == state.barriers.end()) { break; } bool spawned = false; for (int attempt = 0; attempt < 48; attempt++) { Barrier candidate; candidate.vertical = (rand() % 2) == 0; candidate.length = static_cast(BARRIER_MIN_LENGTH + (rand() % length_range)); // Keep the whole line on screen (barriers never wrap around edges) const int max_x = candidate.vertical ? X_MAX : (X_MAX + 1 - candidate.length); const int max_y = candidate.vertical ? (Y_MAX + 1 - candidate.length) : Y_MAX; candidate.x = static_cast(rand() % (max_x + 1)); candidate.y = static_cast(rand() % (max_y + 1)); bool valid = true; for (int i = 0; i < candidate.length && valid; i++) { const int cx = candidate.x + (candidate.vertical ? 0 : i); const int cy = candidate.y + (candidate.vertical ? i : 0); valid = !cell_blocked_for_barrier(state, cx, cy); } if (valid) { candidate.phase = BarrierPhase::Warning; candidate.framesLeft = BARRIER_WARN_FRAMES; *free_slot = candidate; spawned = true; spawned_count++; break; } } if (!spawned) { break; } } if (spawned_count > 0) { state.barrierSpawnCooldown = static_cast(calculate_barrier_interval_frames(state) + (rand() % (BARRIER_INTERVAL_JITTER + 1))); } else { state.barrierSpawnCooldown = BARRIER_RETRY_FRAMES; } } bool check_collision_barriers(GameState &state) { if (state.gameOver || state.snake.empty()) { return false; } const auto &head = state.snake.front(); if (is_barrier_cell(state, head[0], head[1], false)) { state.gameOver = true; return true; } return false; }