#include "ui/ScreenSaver.h" #include "data/roads.h" #include "data/vehicles.h" #include ScreenSaver::ScreenSaver(menu_options_t *options) : Widget(options->u8g2), m_options(options), m_animationCounter(0), m_lastSpawnTime(0), m_leftVehicleCount(0), m_rightVehicleCount(0) { initVehicles(); } void ScreenSaver::initVehicles() { m_vehicles.resize(MAX_VEHICLES); for (auto &vehicle : m_vehicles) { vehicle.active = false; } } void ScreenSaver::update(const uint64_t dt) { m_animationCounter += dt; m_lastSpawnTime += dt; m_sceneShiftTimer += dt; // Shift entire scene every 30 seconds if (m_sceneShiftTimer > 30000) { m_sceneOffsetX = (random() % 7) - 3; // -3 to +3 pixels m_sceneOffsetY = (random() % 7) - 3; // -3 to +3 pixels m_sceneShiftTimer = 0; } // Try to spawn a new vehicle every few seconds if (m_lastSpawnTime > VEHICLE_SPAWN_DELAY) { trySpawnVehicle(); m_lastSpawnTime = 0; } // Update vehicle positions if (m_animationCounter > 16) // ~60 FPS { m_animationCounter = 0; for (auto &vehicle : m_vehicles) { if (!vehicle.active) continue; // Move vehicle if (vehicle.direction == Direction::LEFT) { vehicle.x -= static_cast(vehicle.speed); // Remove the vehicle if it goes off-screen if (vehicle.x <= -32) // Allow for largest vehicle width { vehicle.active = false; m_leftVehicleCount--; } } else // Direction::RIGHT { vehicle.x += static_cast(vehicle.speed); // Remove the vehicle if it goes off-screen if (vehicle.x >= (u8g2->width + 32)) // Allow for largest vehicle width { vehicle.active = false; m_rightVehicleCount--; } } } } } bool ScreenSaver::canSpawnInDirection(Direction direction) const { // Minimalen Abstand zwischen 48 und 64 Pixel zufällig wählen int requiredDistance = MIN_SAME_DIRECTION_DISTANCE + (random() % (MAX_SAME_DIRECTION_DISTANCE - MIN_SAME_DIRECTION_DISTANCE + 1)); for (const auto &vehicle : m_vehicles) { if (!vehicle.active || vehicle.direction != direction) continue; // Abstand zum nächsten Fahrzeug in gleicher Richtung prüfen if (direction == Direction::LEFT) { // Fahrzeuge fahren von rechts nach links // Neues Fahrzeug würde bei u8g2->width + 16 starten int newVehicleX = u8g2->width + 16; // Prüfen ob genug Abstand zum existierenden Fahrzeug if (newVehicleX - vehicle.x < requiredDistance) return false; } else // Direction::RIGHT { // Fahrzeuge fahren von links nach rechts // Neues Fahrzeug würde bei -32 starten int newVehicleX = -32; // Prüfen ob genug Abstand zum existierenden Fahrzeug if (vehicle.x - newVehicleX < requiredDistance) return false; } } return true; } void ScreenSaver::trySpawnVehicle() { // Check if we can spawn a new vehicle int activeVehicles = 0; int availableSlot = -1; for (int i = 0; i < MAX_VEHICLES; i++) { if (m_vehicles[i].active) { activeVehicles++; } else if (availableSlot == -1) { availableSlot = i; } } // Don't spawn if we're at max capacity or no slot available if (activeVehicles >= MAX_VEHICLES || availableSlot == -1) { return; } Direction direction = getRandomDirection(); // Check direction constraints if ((direction == Direction::LEFT && m_leftVehicleCount >= MAX_LEFT_VEHICLES) || (direction == Direction::RIGHT && m_rightVehicleCount >= MAX_RIGHT_VEHICLES)) { return; } if (!canSpawnInDirection(direction)) { return; } // Create new vehicle Vehicle &newVehicle = m_vehicles[availableSlot]; newVehicle.type = getRandomVehicleType(); newVehicle.direction = direction; newVehicle.speed = MIN_SPEED + (static_cast(random()) / RAND_MAX) * (MAX_SPEED - MIN_SPEED); // Set Y position based on a direction (simulate opposing traffic lanes) const int halfHeight = u8g2->height / 2; if (direction == Direction::RIGHT) { // Vehicles going LEFT use bottom half of screen newVehicle.y = halfHeight + 8 + (random() % (halfHeight - 24)); m_rightVehicleCount++; } else // Direction::RIGHT { // Vehicles going RIGHT use top half of screen newVehicle.y = 8 + (random() % (halfHeight - 24)); m_leftVehicleCount++; } // Set the starting X position based on the direction if (direction == Direction::LEFT) { // Vehicles going LEFT (from right to left) start from RIGHT side of screen newVehicle.x = u8g2->width + 16; } else // Direction::RIGHT { // Vehicles going RIGHT (from left to right) start from LEFT side of screen newVehicle.x = -32; // Account for the largest vehicle width } newVehicle.active = true; } ScreenSaver::VehicleType ScreenSaver::getRandomVehicleType() { switch (random() % 5) { case 0: return VehicleType::CAR; case 1: return VehicleType::CONVERTABLE; case 2: return VehicleType::SUV; case 3: return VehicleType::LORRY; case 4: return VehicleType::TRUCK; default: return VehicleType::CAR; } } ScreenSaver::Direction ScreenSaver::getRandomDirection() { // Simple 50/50 chance for each direction return (random() % 2 == 0) ? Direction::LEFT : Direction::RIGHT; } void ScreenSaver::render() { // Clear screen with a black background u8g2_SetDrawColor(u8g2, 0); u8g2_DrawBox(u8g2, 0, 0, u8g2->width, u8g2->height); u8g2_SetDrawColor(u8g2, 1); // Calculate offsets const int roadOffset = (m_animationCounter / 100) % road_horizontal_width; // Draw all active vehicles with a scene offset for (const auto &vehicle : m_vehicles) { if (vehicle.active) { Vehicle offsetVehicle = vehicle; offsetVehicle.x += m_sceneOffsetX; offsetVehicle.y += m_sceneOffsetY; drawVehicle(offsetVehicle); } } // Draw road with offsets const int y = u8g2->height / 2 - road_horizontal_height / 2 + m_sceneOffsetY; for (int x = -road_horizontal_width + roadOffset + m_sceneOffsetX; x <= u8g2->width; x += road_horizontal_width) { drawTransparentBitmap(x, y, road_horizontal_width, road_horizontal_height, road_horizontal_bits); } } void ScreenSaver::drawVehicle(const Vehicle &vehicle) const { int width, height; if (const unsigned char *bitmap = getVehicleBitmap(vehicle.type, vehicle.direction, width, height)) { drawTransparentBitmap(vehicle.x, vehicle.y, width, height, bitmap); // u8g2_DrawXBM(u8g2, vehicle.x, vehicle.y, width, height, bitmap); } } void ScreenSaver::drawTransparentBitmap(const int x, const int y, const int width, const int height, const unsigned char *bitmap) const { for (int py = 0; py < height; py++) { for (int px = 0; px < width; px++) { // Calculate byte and a bit of position in bitmap const int byteIndex = (py * ((width + 7) / 8)) + (px / 8); // Check if the pixel is set (white) if (const int bitIndex = px % 8; bitmap[byteIndex] & (1 << bitIndex)) { // Only draw white pixels, skip black (transparent) pixels const int screenX = x + px; // Bounds checking if (const int screenY = y + py; screenX >= 0 && screenX < u8g2->width && screenY >= 0 && screenY < u8g2->height) { u8g2_DrawPixel(u8g2, screenX, screenY); } } // Black pixels are simply not drawn (transparent) } } } const unsigned char *ScreenSaver::getVehicleBitmap(const VehicleType type, const Direction direction, int &width, int &height) { switch (type) { case VehicleType::CAR: width = car_width; height = car_height; return (direction == Direction::LEFT) ? car_left_bits : car_right_bits; case VehicleType::CONVERTABLE: width = convertable_width; height = convertable_height; return (direction == Direction::LEFT) ? convertable_left_bits : convertable_right_bits; case VehicleType::SUV: width = suv_width; height = suv_height; return (direction == Direction::LEFT) ? suv_left_bits : suv_right_bits; case VehicleType::LORRY: width = lorry_width; height = lorry_height; return (direction == Direction::LEFT) ? lorry_left_bits : lorry_right_bits; case VehicleType::TRUCK: width = truck_width; height = truck_height; return (direction == Direction::LEFT) ? truck_left_bits : truck_right_bits; default: width = car_width; height = car_height; return car_left_bits; } } void ScreenSaver::onButtonClicked(ButtonType button) { if (m_options && m_options->popScreen) { m_options->popScreen(); } }