/
del00n
/
Space_Lab
Обзор
Документация
Войти
/
del00n
/
Space_Lab
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
Game.cpp
665 строк
23 KB
del00n
upload files
22 янв 2026, 13:52
Верифицирован
22 янв 2026, 13:52
4016b42
Код
Авторство
О чём код?
#include "Game.h" #include <cmath> #include <algorithm> #include <iostream> #include <memory> #ifdef WIN32 #include <windows.h> #endif static void drawArrow(sf::RenderWindow& win, sf::Vector2f from, sf::Vector2f to, sf::Color col, float width = 4.f) { sf::Vector2f dir = to - from; float len = std::sqrt(dir.x * dir.x + dir.y * dir.y); if (len < 1.f) return; sf::RectangleShape shaft(sf::Vector2f(len - 8.f, width)); shaft.setOrigin(0, width / 2); shaft.setPosition(from); shaft.setRotation(std::atan2(dir.y, dir.x) * 180.f / 3.14159f); shaft.setFillColor(col); win.draw(shaft); sf::ConvexShape head(3); head.setPoint(0, sf::Vector2f(0, 0)); head.setPoint(1, sf::Vector2f(-8.f, width * 1.2f)); head.setPoint(2, sf::Vector2f(-8.f, -width * 1.2f)); head.setFillColor(col); head.setPosition(to); head.setRotation(shaft.getRotation()); win.draw(head); } static void drawDashed(sf::RenderWindow& w, sf::Vector2f a, sf::Vector2f b, float dash = 8.f, float gap = 6.f, sf::Color c = sf::Color(120, 120, 120, 120)) { sf::Vector2f dir = b - a; float len = std::sqrt(dir.x * dir.x + dir.y * dir.y); if (len < 1.f) return; sf::Vector2f n = dir / len; float flown = 0.f; while (flown < len) { float seg = std::min(dash, len - flown); sf::Vertex v[2] = { sf::Vertex(a + n * flown, c), sf::Vertex(a + n * (flown + seg), c) }; w.draw(v, 2, sf::Lines); flown += dash + gap; } } Game::Game(const LevelConfig& config) : win(sf::VideoMode(800, 600), "Space Lab - Landing Challenge") , gameState(STATEFLYING) { win.setFramerateLimit(60); // Planets (any amount) if (!config.planets.empty()) { for (const auto& p : config.planets) { world.addBody(Body{Vec2{p.x, p.y}, p.mass, p.radius}); } } else { // Safety fallback world.addBody(Body{Vec2{400.f, 300.f}, 2000.f, 30.f}); } Spacecraft sc(Vec2{50, 550}); sc.setMass(5.f); sc.setMaxThrust(400.f); world.setShip(sc); ap.setCourse(sc.getPos(), {750, 50}, 40.f, config.waypointCount); font.loadFromFile("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); txtSpeed.setFont(font); txtSpeed.setCharacterSize(18); txtSpeed.setFillColor(sf::Color::White); txtSpeed.setPosition(5, 5); txtFinish.setFont(font); txtFinish.setString("MISSION COMPLETE!"); txtFinish.setCharacterSize(24); txtFinish.setFillColor(sf::Color::Green); txtFinish.setStyle(sf::Text::Bold); sf::FloatRect bounds = txtFinish.getLocalBounds(); txtFinish.setOrigin(bounds.width / 2.f, bounds.height / 2.f); txtFinish.setPosition(400, 300); txtLandingMessage.setFont(font); txtLandingMessage.setString("LANDING SEQUENCE INITIATED"); txtLandingMessage.setCharacterSize(20); txtLandingMessage.setFillColor(sf::Color::Cyan); txtLandingMessage.setStyle(sf::Text::Bold); sf::FloatRect landingBounds = txtLandingMessage.getLocalBounds(); txtLandingMessage.setOrigin(landingBounds.width / 2.f, landingBounds.height / 2.f); txtLandingMessage.setPosition(400, 100); txtCrash.setFont(font); txtCrash.setString("CRASHED!"); txtCrash.setCharacterSize(32); txtCrash.setFillColor(sf::Color::Red); txtCrash.setStyle(sf::Text::Bold); sf::FloatRect crashBounds = txtCrash.getLocalBounds(); txtCrash.setOrigin(crashBounds.width / 2.f, crashBounds.height / 2.f); txtCrash.setPosition(400, 300); txtWind.setFont(font); txtWind.setCharacterSize(14); txtWind.setFillColor(sf::Color::Yellow); txtWind.setPosition(10, 45); txtWindControl.setFont(font); txtWindControl.setCharacterSize(14); txtWindControl.setFillColor(sf::Color::Cyan); txtWindControl.setPosition(10, 560); } void Game::processEvents() { sf::Event e; while (win.pollEvent(e)) { if (e.type == sf::Event::Closed) { win.close(); return; } if (e.type == sf::Event::KeyPressed) { if (e.key.code == sf::Keyboard::RBracket) { float m = world.getShip().getMaxThrust(); world.getShip().setMaxThrust(m + 10.f); } if (e.key.code == sf::Keyboard::LBracket) { float m = world.getShip().getMaxThrust(); world.getShip().setMaxThrust(std::max(10.f, m - 10.f)); } if (e.key.code == sf::Keyboard::Space) { if (gameState == STATEFLYING) { startLandingSequence(); } else if (gameState == STATECRASHED) { startLandingSequence(); } } if (gameState == STATELANDING) { if (e.key.code == sf::Keyboard::Left) { if (windStrength > 0.f) { windStrength = std::max(0.f, windStrength - WIND_CHANGE_RATE); } else { windStrength = std::max(-MAX_WIND_STRENGTH, windStrength - WIND_CHANGE_RATE); } } if (e.key.code == sf::Keyboard::Right) { if (windStrength < 0.f) { windStrength = std::min(0.f, windStrength + WIND_CHANGE_RATE); } else { windStrength = std::min(MAX_WIND_STRENGTH, windStrength + WIND_CHANGE_RATE); } } } } } } void Game::startLandingSequence() { try { trail.clear(); missionComplete = false; landingWorld = std::make_unique<World>(); if (!landingWorld) { std::cerr << "ERROR: Failed to create landingWorld!" << std::endl; gameState = STATECRASHED; return; } landingWorld->addBody(Body{Vec2{400, 480}, 6000.f, 60.f}); const auto& checkBodies = landingWorld->getBodies(); if (checkBodies.empty()) { std::cerr << "ERROR: Planet was not added to world!" << std::endl; gameState = STATECRASHED; return; } Spacecraft landingShip(Vec2{400, 50}); landingShip.setMass(5.f); landingShip.setMaxThrust(2400.f); landingWorld->setShip(landingShip); landingAutopilot.setCourse(Vec2{400, 50}, Vec2{400, 420}, 25); landingAutopilot.waypoints.clear(); landingAutopilot.waypoints.push_back(Vec2{400, 50}); landingAutopilot.waypoints.push_back(Vec2{400, 420}); landingAutopilot.currentWaypoint = 0; landingAutopilot.predictedTrajectory.clear(); landingAutopilot.predictedTrajectory.push_back({Vec2{400, 50}, 0.f}); landingAutopilot.predictedTrajectory.push_back({Vec2{400, 420}, 1.f}); gameState = STATELANDING; landingTime = 0.f; manualControlMode = false; manualRotationInput = 0.f; manualThrustInput = 0.f; windStrength = 0.f; windVelocity = {0.f, 0.f}; } catch (const std::exception& e) { gameState = STATECRASHED; } catch (...) { gameState = STATECRASHED; } } void Game::updateLandingScene(float dt) { if (gameState != STATELANDING || !landingWorld) { if (!landingWorld) { std::cerr << "WARNING: landingWorld is null in updateLandingScene" << std::endl; } return; } landingTime += dt; Spacecraft& ship = landingWorld->getShip(); const auto& bodies = landingWorld->getBodies(); Vec2 shipPos = ship.getPos(); Vec2 shipVel = ship.getVel(); Vec2 planetPos{400, 480}; float distToPlanet = (shipPos - planetPos).len(); const bool inAtmosphere = (distToPlanet < ATMOSPHERE_RADIUS + PLANET_RADIUS); Vec2 externalForce{0.f, 0.f}; if (inAtmosphere) { const float dragCoefficient = 0.15f; Vec2 dragForce = shipVel * (-dragCoefficient * shipVel.len()); Vec2 windForce{windStrength * 0.5f, 0.f}; externalForce = dragForce + windForce; } const float G = 100.f; auto [trajThrust, trajDesiredRot] = landingAutopilot.computeThrustAndRotation( shipPos, shipVel, ship.getMaxThrust(), ship.getMass(), bodies, G, dt); const float TARGET_LANDING_SPEED = 10.f; Vec2 brakeThrust{0.f, 0.f}; float speed = shipVel.len(); if (speed > TARGET_LANDING_SPEED + 0.5f && speed > 0.01f) { const float BRAKE_K = 10.0f; float brakeMag = std::min(ship.getMaxThrust(), (speed - TARGET_LANDING_SPEED) * BRAKE_K); brakeThrust = shipVel.norm() * (-brakeMag); } Vec2 antiWindThrust{0.f, 0.f}; if (inAtmosphere && std::abs(windStrength) > 1.f) { const float WIND_THRUST_K = 0.8f; antiWindThrust = Vec2{-windStrength, 0.f} * WIND_THRUST_K; } Vec2 engineThrust = trajThrust + brakeThrust + antiWindThrust; float engineMag = engineThrust.len(); if (engineMag > ship.getMaxThrust()) { engineThrust = engineThrust * (ship.getMaxThrust() / engineMag); } float desiredRot = trajDesiredRot; if (engineThrust.len() > 0.01f) { desiredRot = landingAutopilot.getDesiredRotation(engineThrust); } landingWorld->updateManual(landingAutopilot, dt, landingTime, engineThrust, desiredRot, externalForce); shipPos = ship.getPos(); if (shipPos.x < -50 || shipPos.x > 850 || shipPos.y < -50 || shipPos.y > 650) { gameState = STATECRASHED; missionComplete = false; return; } for (const auto& body : bodies) { float dist = (shipPos - body.getPos()).len(); if (dist <= body.getRadius()) { shipVel = ship.getVel(); float impactSpeed = shipVel.len(); if (impactSpeed <= 50.0f) { gameState = STATELANDED; missionComplete = true; } else { gameState = STATECRASHED; missionComplete = false; } return; } } if (!bodies.empty()) { Vec2 planetCenter = bodies[0].getPos(); float distToCenter = (shipPos - planetCenter).len(); float speedToCenter = ship.getVel().len(); if (distToCenter < 100.f && speedToCenter < 15.f) { gameState = STATELANDED; missionComplete = true; } } } void Game::renderLandingScene() { win.clear(sf::Color(15, 15, 30)); if (!landingWorld) { win.display(); return; } Vec2 shipPos = landingWorld->getShip().getPos(); const auto& bodies = landingWorld->getBodies(); Vec2 planetCenter{400, 480}; if (!bodies.empty()) { sf::CircleShape atmosphere(ATMOSPHERE_RADIUS + PLANET_RADIUS); atmosphere.setOrigin(ATMOSPHERE_RADIUS + PLANET_RADIUS, ATMOSPHERE_RADIUS + PLANET_RADIUS); atmosphere.setPosition(planetCenter.x, planetCenter.y); atmosphere.setFillColor(sf::Color(100, 150, 255, 30)); atmosphere.setOutlineThickness(2.f); atmosphere.setOutlineColor(sf::Color(100, 150, 255, 120)); win.draw(atmosphere); } if (std::abs(windStrength) > 5.f) { float distToPlanet = (shipPos - planetCenter).len(); if (distToPlanet < ATMOSPHERE_RADIUS + PLANET_RADIUS) { int windCount = 8; for (int i = 0; i < windCount; i++) { float yOffset = -60.f + i * (120.f / windCount); float windMagnitude = std::abs(windStrength) * 0.15f; sf::Vector2f windStart = sf::Vector2f(shipPos.x - windMagnitude, shipPos.y + yOffset); sf::Vector2f windEnd; if (windStrength < 0) { windEnd = sf::Vector2f(shipPos.x - windMagnitude * 2.f, shipPos.y + yOffset); } else { windEnd = sf::Vector2f(shipPos.x + windMagnitude * 2.f, shipPos.y + yOffset); } sf::Vertex windLine[] = { sf::Vertex(windStart, sf::Color(255, 200, 100, 200)), sf::Vertex(windEnd, sf::Color(255, 200, 100, 200)) }; win.draw(windLine, 2, sf::Lines); sf::Vector2f dir = windEnd - windStart; float len = std::sqrt(dir.x * dir.x + dir.y * dir.y); if (len > 1.f) { dir = dir / len; sf::ConvexShape arrow(3); arrow.setPoint(0, sf::Vector2f(0, 0)); arrow.setPoint(1, sf::Vector2f(-4.f, 3.f)); arrow.setPoint(2, sf::Vector2f(-4.f, -3.f)); arrow.setFillColor(sf::Color(255, 200, 100, 200)); arrow.setPosition(windEnd); arrow.setRotation(std::atan2(dir.y, dir.x) * 180.f / 3.14159f); win.draw(arrow); } } } } const auto& predictedTraj = landingWorld->getPredictedTrajectory(); if (!predictedTraj.empty() && predictedTraj.size() > 1) { for (size_t i = 0; i < predictedTraj.size() - 1; i++) { sf::Vertex v[2] = { sf::Vertex(sf::Vector2f(predictedTraj[i].pos.x, predictedTraj[i].pos.y), sf::Color(100, 150, 255, 150)), sf::Vertex(sf::Vector2f(predictedTraj[i+1].pos.x, predictedTraj[i+1].pos.y), sf::Color(100, 150, 255, 150)) }; win.draw(v, 2, sf::Lines); } } if (predictedTraj.size() == 2) { for (size_t i = 0; i < predictedTraj.size(); i++) { sf::CircleShape waypoint(8); waypoint.setOrigin(8, 8); waypoint.setPosition(predictedTraj[i].pos.x, predictedTraj[i].pos.y); if (i == 0) { waypoint.setFillColor(sf::Color::Yellow); } else { waypoint.setFillColor(sf::Color::Green); } win.draw(waypoint); } } if (!bodies.empty()) { const Body& planet = bodies[0]; sf::CircleShape planetShape(planet.getRadius()); planetShape.setOrigin(planet.getRadius(), planet.getRadius()); planetShape.setPosition(planet.getPos().x, planet.getPos().y); planetShape.setFillColor(sf::Color(150, 100, 200)); planetShape.setOutlineThickness(2.f); planetShape.setOutlineColor(sf::Color(100, 50, 150)); win.draw(planetShape); } landingWorld->getShip().draw(win); Vec2 lastFg = landingWorld->lastFg(); Vec2 lastFth = landingWorld->lastFth(); drawArrow(win, {shipPos.x, shipPos.y}, {shipPos.x + lastFth.x * 0.1f, shipPos.y + lastFth.y * 0.1f}, sf::Color::Red, 3.f); drawArrow(win, {shipPos.x, shipPos.y}, {shipPos.x + lastFg.x * 0.1f, shipPos.y + lastFg.y * 0.1f}, sf::Color::Blue, 3.f); sf::Text statusText; statusText.setFont(font); statusText.setCharacterSize(14); statusText.setFillColor(sf::Color::Yellow); statusText.setPosition(10, 10); if (!bodies.empty()) { float distToPlanet = (shipPos - bodies[0].getPos()).len(); float altitude = std::max(0.f, distToPlanet - bodies[0].getRadius()); float speed = landingWorld->getShip().getVel().len(); statusText.setString("Distance: " + std::to_string((int)altitude) + "\n" + "Speed: " + std::to_string((int)speed) + "\n"); } win.draw(statusText); std::string windStatus; if (windStrength < -5.f) { windStatus = "Wind: LEFT " + std::to_string((int)std::abs(windStrength)) + " px/s"; } else if (windStrength > 5.f) { windStatus = "Wind: RIGHT " + std::to_string((int)windStrength) + " px/s"; } else { windStatus = "Wind: NONE"; } txtWind.setString(windStatus); if (windStrength < -5.f) { txtWind.setFillColor(sf::Color::Magenta); } else if (windStrength > 5.f) { txtWind.setFillColor(sf::Color::Cyan); } else { txtWind.setFillColor(sf::Color::Yellow); } win.draw(txtWind); txtWindControl.setString("LEFT arrow: increase LEFT wind | RIGHT arrow: increase RIGHT wind"); win.draw(txtWindControl); if (gameState == STATELANDED) { sf::RectangleShape overlay(sf::Vector2f(800, 600)); overlay.setFillColor(sf::Color(0, 0, 0, 150)); win.draw(overlay); sf::Text landedText; landedText.setFont(font); landedText.setString("SUCCESSFUL LANDING!"); landedText.setCharacterSize(40); landedText.setFillColor(sf::Color::Green); landedText.setStyle(sf::Text::Bold); sf::FloatRect bounds = landedText.getLocalBounds(); landedText.setOrigin(bounds.width / 2.f, bounds.height / 2.f); landedText.setPosition(400, 250); win.draw(landedText); sf::Text restartText; restartText.setFont(font); restartText.setString("Press SPACE to return or close window"); restartText.setCharacterSize(16); restartText.setFillColor(sf::Color::Yellow); sf::FloatRect restartBounds = restartText.getLocalBounds(); restartText.setOrigin(restartBounds.width / 2.f, restartBounds.height / 2.f); restartText.setPosition(400, 350); win.draw(restartText); } if (gameState == STATECRASHED) { sf::RectangleShape overlay(sf::Vector2f(800, 600)); overlay.setFillColor(sf::Color(0, 0, 0, 180)); win.draw(overlay); sf::Text crashText; crashText.setFont(font); crashText.setString("LANDING FAILED!\nCRASHED!"); crashText.setCharacterSize(40); crashText.setFillColor(sf::Color::Red); crashText.setStyle(sf::Text::Bold); sf::FloatRect bounds = crashText.getLocalBounds(); crashText.setOrigin(bounds.width / 2.f, bounds.height / 2.f); crashText.setPosition(400, 250); win.draw(crashText); sf::Text restartText; restartText.setFont(font); restartText.setString("Press SPACE to try again or close window"); restartText.setCharacterSize(16); restartText.setFillColor(sf::Color::Yellow); sf::FloatRect restartBounds = restartText.getLocalBounds(); restartText.setOrigin(restartBounds.width / 2.f, restartBounds.height / 2.f); restartText.setPosition(400, 350); win.draw(restartText); } win.display(); } void Game::update(float dt) { if (gameState == STATELANDING) { updateLandingScene(dt); return; } if (gameState == STATELANDED) return; if (gameState == STATECRASHED) return; if (world.hasReachedFinish()) { startLandingSequence(); return; } simTime += dt; world.update(ap, dt, simTime); Vec2 p = world.getShip().getPos(); trail.emplace_back(sf::Vector2f(p.x, p.y), sf::Color(255, 200, 0)); if (trail.size() > MaxTrail) trail.erase(trail.begin()); int v = static_cast<int>(world.getShip().getVel().len()); txtSpeed.setString(std::to_string(v)); if (world.getShip().isCrashed() && !pendingClose) { #ifdef WIN32 MessageBoxA(nullptr, "The spacecraft has collided with a planet!", "CRASHED!", MB_ICONERROR | MB_OK); #else std::cerr << "CRASHED! closing..." << std::endl; #endif pendingClose = true; win.close(); } if (world.getShip().isOutOfBounds() && !pendingClose) { #ifdef WIN32 MessageBoxA(nullptr, "The spacecraft has left the area!", "OUT OF BOUNDS!", MB_ICONERROR | MB_OK); #else std::cerr << "OUT OF BOUNDS! closing..." << std::endl; #endif pendingClose = true; win.close(); } } void Game::render() { if (gameState == STATELANDING || gameState == STATELANDED || gameState == STATECRASHED) { renderLandingScene(); return; } win.clear(sf::Color(15, 15, 30)); sf::RectangleShape boundaryRect(sf::Vector2f(800.f, 600.f)); boundaryRect.setPosition(0, 0); boundaryRect.setFillColor(sf::Color::Transparent); boundaryRect.setOutlineThickness(2.f); boundaryRect.setOutlineColor(sf::Color::Red); win.draw(boundaryRect); drawDashed(win, {ap.start.x, ap.start.y}, {ap.finish.x, ap.finish.y}, 8.f, 6.f, sf::Color(120, 120, 120, 80)); const auto& predictedTraj = world.getPredictedTrajectory(); if (!predictedTraj.empty() && predictedTraj.size() > 1) { for (size_t i = 0; i < predictedTraj.size() - 1; i++) { sf::Vertex v[2] = { sf::Vertex(sf::Vector2f(predictedTraj[i].pos.x, predictedTraj[i].pos.y), sf::Color(100, 150, 255, 255)), sf::Vertex(sf::Vector2f(predictedTraj[i+1].pos.x, predictedTraj[i+1].pos.y), sf::Color(100, 150, 255, 255)) }; win.draw(v, 2, sf::Lines); } } for (size_t i = 0; i < predictedTraj.size(); i++) { sf::CircleShape waypoint(8); waypoint.setOrigin(8, 8); waypoint.setPosition(predictedTraj[i].pos.x, predictedTraj[i].pos.y); if (i == 0 || i == predictedTraj.size() - 1) { waypoint.setFillColor(sf::Color::Yellow); } else { waypoint.setFillColor(sf::Color::Green); } win.draw(waypoint); } if (trail.size() > 1) { win.draw(&trail[0], static_cast<unsigned int>(trail.size()), sf::LineStrip); } world.drawBodies(win, font); world.getShip().draw(win); auto shipPos = world.getShip().getPos(); drawArrow(win, {shipPos.x, shipPos.y}, {shipPos.x + world.lastFth().x, shipPos.y + world.lastFth().y}, sf::Color::Red, 4.f); drawArrow(win, {shipPos.x, shipPos.y}, {shipPos.x + world.lastFg().x, shipPos.y + world.lastFg().y}, sf::Color::Blue, 4.f); float fuelPerc = world.getShip().getFuel() / 400.f; sf::RectangleShape back(sf::Vector2f(120, 10)); back.setPosition(650, 10); back.setFillColor(sf::Color(60, 60, 60)); win.draw(back); sf::RectangleShape bar(sf::Vector2f(120 * fuelPerc, 10)); bar.setPosition(650, 10); bar.setFillColor(sf::Color(40, 200, 40)); win.draw(bar); Vec2 toFinish = ap.finish - world.getShip().getPos(); sf::Text distText; distText.setFont(font); distText.setCharacterSize(14); distText.setFillColor(sf::Color::Yellow); distText.setPosition(5, 30); distText.setString("Distance: " + std::to_string(static_cast<int>(toFinish.len()))); win.draw(distText); win.draw(txtSpeed); if (world.getShip().isCrashed()) { win.draw(txtCrash); } if (missionComplete && gameState == STATEFLYING) { sf::RectangleShape bg(sf::Vector2f(800, 600)); bg.setFillColor(sf::Color(0, 0, 0, 150)); win.draw(bg); win.draw(txtFinish); } win.display(); } void Game::run() { const float dt = 1.f / 60.f; while (win.isOpen()) { processEvents(); update(dt); render(); } }