#include #include #include #include #include #include #include #include #include #include #include #include namespace { void writeFinalScoreFile(int score) { std::ofstream file("score.txt", std::ios::trunc); if (!file.is_open()) { WARN("Neuspešno odpiranje score.txt za pisanje"); return; } const auto now = std::chrono::system_clock::now(); const std::time_t nowTime = std::chrono::system_clock::to_time_t(now); std::tm localTime{}; #if defined(_WIN32) localtime_s(&localTime, &nowTime); #else localtime_r(&nowTime, &localTime); #endif std::string playerName = Game::GameManager::getSharedData("playerName"); if (playerName.empty()) playerName = "Player"; file << "Končna statistika igre:\n"; file << "Igralec: " << playerName << "\n"; file << "Točke: " << score << "\n"; file << "Datum: " << std::put_time(&localTime, "%Y-%m-%d %H:%M:%S") << "\n"; } } namespace Game::AGame { void Background::start() { mSeaTex = std::make_shared("../resources/l3sea.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "seaTex"); mEnemyTex = std::make_shared("../resources/l3enemy.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "enemyTex"); mTrashTex = std::make_shared("../resources/l3trash.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "trashTex"); mFriendlyTex = std::make_shared("../resources/l3friendly.png", SDL_GetRenderer(Window::Window::getSDLWindowBackend()), "friendlyTex"); GameManager::setSharedData("enemyActiveCount", 0); GameManager::setSharedData("trashActiveCount", 0); GameManager::setSharedData("friendlyActiveCount", 0); GameManager::setSharedData("gameStage", 1); GameManager::setSharedData("gameWon", false); GameManager::setSharedData("gameLost", false); mZIndex = -1; // Ensure background renders behind other entities mTex->setTiled(true); // Set the background texture to be tiled if (mSeaTex) { mSeaTex->setTiled(true); } mTiledScale = 0.5f; // Use logical world dimensions (1280×720) not actual screen size mW = 1280; mH = 720; // Land boundary: left 1/3 of map in centered coordinates // For 1280px window: -640 (left) + 426.67 (1/3) = -213.33 mLandBoundaryX = -static_cast(mW) / 2.f + static_cast(mW) / 3.f; GameManager::setSharedData("terrainLandBoundaryX", mLandBoundaryX); GameManager::setSharedData("enemyRevealRadius", 260.f); mTransform.scaleX *= 10.f; mTransform.scaleY *= 10.f; mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 2.f; mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f; LOG("W: " << mW << " H: " << mH); spawnLevel(1); } void Background::render(Game::Renderer::Renderer* renderer, Game::Renderer::RendererConfig config) { if (!renderer || !mTex || !mSeaTex) { return; } const float worldLeft = -static_cast(mW) / 2.f; const float worldRight = static_cast(mW) / 2.f; const float worldTop = -static_cast(mH) / 2.f; const float worldBottom = static_cast(mH) / 2.f; const float landLeft = worldLeft; const float landRight = mLandBoundaryX; const float seaLeft = mLandBoundaryX; const float seaRight = worldRight; auto drawSection = [&](const std::shared_ptr& tex, float startX, float endX) { if (!tex) { return; } float tileW = 0.f; float tileH = 0.f; SDL_GetTextureSize(tex->getSDLTexture(), &tileW, &tileH); tileW *= mTiledScale; tileH *= mTiledScale; if (tileW <= 0.f || tileH <= 0.f) { return; } const float screenStartX = startX - config.camX + config.screenW / 2.f; const float screenEndX = endX - config.camX + config.screenW / 2.f; const float screenStartY = worldTop - config.camY + config.screenH / 2.f; const float screenEndY = worldBottom - config.camY + config.screenH / 2.f; for (float x = screenStartX; x < screenEndX; x += tileW) { for (float y = screenStartY; y < screenEndY; y += tileH) { SDL_FRect dst{ x, y, tileW, tileH }; SDL_RenderTexture(renderer->getSDLRenderer(), tex->getSDLTexture(), nullptr, &dst); } } }; drawSection(mTex, landLeft, landRight); drawSection(mSeaTex, seaLeft, seaRight); } void Background::spawnFriendly(int stage, int count) { const float viewLeft = -mW / 2.f; const float viewRight = mW / 2.f; const float viewTop = -mH / 2.f; const float viewBottom = mH / 2.f; Object::Transform tS; tS.x = 0.f; tS.y = 0.f; tS.rotation = 0.f; tS.scaleX = 4.0f; tS.scaleY = 4.0f; const float halfFriendlyW = mFriendlyTex->getWidth() * tS.adjustedScaleX() / 2.f; const float halfFriendlyH = mFriendlyTex->getHeight() * tS.adjustedScaleY() / 2.f; // Split friendlies: most on land, a smaller number may appear on sea // Decide how many friendlies appear on the sea. // For stage 1 keep them on land; for later stages allow at least one on sea int seaCount = 0; if (stage > 1) { seaCount = std::max(1, count / 3); // roughly one third on sea for later stages } int landCount = count - seaCount; // Spawn land friendlies (left side) for (int i = 0; i < landCount; ++i) { tS.x = static_cast(Utils::getUtils().rirng32(static_cast(viewLeft + halfFriendlyW + 25.f), static_cast(mLandBoundaryX - halfFriendlyW - 25.f))); tS.y = static_cast(Utils::getUtils().rirng32(static_cast(viewTop + halfFriendlyH + 100.f), static_cast(viewBottom - halfFriendlyH - 100.f))); auto* friendly = State::GameState::getInstance().addEntity(std::make_unique("Friendly" + std::to_string(stage) + "_L" + std::to_string(i + 1), mFriendlyTex, tS)); if (friendly) { if (auto* collider = friendly->getComponent()) { collider->setScale(0.75f); } } } // Spawn a smaller number of friendlies on the sea (right side) for (int i = 0; i < seaCount; ++i) { tS.x = static_cast(Utils::getUtils().rirng32(static_cast(mLandBoundaryX + halfFriendlyW + 25.f), static_cast(viewRight - halfFriendlyW - 25.f))); tS.y = static_cast(Utils::getUtils().rirng32(static_cast(viewTop + halfFriendlyH + 100.f), static_cast(viewBottom - halfFriendlyH - 100.f))); auto* friendly = State::GameState::getInstance().addEntity(std::make_unique("Friendly" + std::to_string(stage) + "_S" + std::to_string(i + 1), mFriendlyTex, tS)); if (friendly) { if (auto* collider = friendly->getComponent()) { collider->setScale(0.75f); } } } } void Background::spawnLevel(int stage) { const float viewLeft = -mW / 2.f; const float viewRight = mW / 2.f; const float viewTop = -mH / 2.f; const float viewBottom = mH / 2.f; const int enemyCount = 2 + stage * 2; const int trashCount = 4 + stage * 3; const int friendlyCount = 1 + (stage - 1); GameManager::setSharedData("gameStage", stage); GameManager::setSharedData("enemyActiveCount", enemyCount); GameManager::setSharedData("trashActiveCount", trashCount); GameManager::setSharedData("friendlyActiveCount", friendlyCount); if (stage > 1) { auto* player = GameManager::getEntityByName("Player"); if (player) { player->respawnRandomSea(mLandBoundaryX); } } Object::Transform tS; tS.rotation = 0.f; tS.scaleX = 4.7f; tS.scaleY = 4.7f; const float halfEnemyW = mEnemyTex->getWidth() * tS.adjustedScaleX() / 2.f; const float halfEnemyH = mEnemyTex->getHeight() * tS.adjustedScaleY() / 2.f; for (int i = 0; i < enemyCount; ++i) { tS.x = static_cast(Utils::getUtils().rirng32(static_cast(viewLeft + halfEnemyW + 25.f), static_cast(mLandBoundaryX - halfEnemyW - 25.f))); tS.y = static_cast(Utils::getUtils().rirng32(static_cast(viewTop + halfEnemyH + 100.f), static_cast(viewBottom - halfEnemyH - 100.f))); GameManager::instantiateEntity(std::make_unique("Enemy" + std::to_string(stage) + "_" + std::to_string(i + 1), mEnemyTex, tS)); } tS.scaleX = 5.5f; tS.scaleY = 5.5f; const float halfTrashW = mTrashTex->getWidth() * tS.adjustedScaleX() / 2.f; const float halfTrashH = mTrashTex->getHeight() * tS.adjustedScaleY() / 2.f; for (int i = 0; i < trashCount; ++i) { tS.x = static_cast(Utils::getUtils().rirng32(static_cast(mLandBoundaryX + halfTrashW + 25.f), static_cast(viewRight - halfTrashW - 25.f))); tS.y = static_cast(Utils::getUtils().rirng32(static_cast(viewTop + halfTrashH + 100.f), static_cast(viewBottom - halfTrashH - 100.f))); GameManager::instantiateEntity(std::make_unique("Trash" + std::to_string(stage) + "_" + std::to_string(i + 1), mTrashTex, tS)); } spawnFriendly(stage, friendlyCount); } void Background::spawnTrashAt(const Object::Transform& tS, bool seaOnly) { // Prepare transform and ensure sea-only pop is placed on the sea side Object::Transform t = tS; t.rotation = 0.f; t.scaleX = 5.5f; t.scaleY = 5.5f; const float halfTrashW = mTrashTex->getWidth() * t.adjustedScaleX() / 2.f; if (seaOnly) { const float minSeaX = mLandBoundaryX + halfTrashW + 25.f; if (t.x < minSeaX) t.x = minSeaX; } // Create a unique-ish name for the auto-spawned trash const int id = Utils::getUtils().rirng32(0, 1000000); const std::string name = "Trash_Auto_" + std::to_string(id); GameManager::instantiateEntity(std::make_unique(name, mTrashTex, t)); // If requested, mark the spawned trash as sea-only if (seaOnly) { auto* tr = GameManager::getEntityByName(name); if (tr) tr->setSeaOnly(true); } GameManager::setSharedData("trashActiveCount", GameManager::getSharedData("trashActiveCount") + 1); } void Background::update(float deltaTime) { (void)deltaTime; const int enemyCount = GameManager::getSharedData("enemyActiveCount"); const int trashCount = GameManager::getSharedData("trashActiveCount"); const int stage = GameManager::getSharedData("gameStage"); // Periodically spawn a friendly on land or sea with a small probability // evaluated each update using deltaTime so the average interval is respected. const int activeFriendlies = GameManager::getSharedData("friendlyActiveCount"); if (activeFriendlies < mMaxAutoFriendlies) { // Compute chance = deltaTime / avgInterval const float chance = deltaTime / std::max(0.0001f, mFriendlySpawnAvgInterval); const int thresh = static_cast(chance * 10000.f); if (thresh > 0) { const int roll = Utils::getUtils().rirng32(0, 9999); if (roll < thresh) { // Decide side: sea probability increases with stage float seaProb = (stage > 1) ? 0.3f : 0.1f; const int sideRoll = Utils::getUtils().rirng32(0, 99); const bool spawnSea = sideRoll < static_cast(seaProb * 100.f); Object::Transform tS; tS.x = 0.f; tS.y = 0.f; tS.rotation = 0.f; tS.scaleX = 4.0f; tS.scaleY = 4.0f; const float viewLeft = -mW / 2.f; const float viewRight = mW / 2.f; const float viewTop = -mH / 2.f; const float viewBottom = mH / 2.f; const float halfFriendlyW = mFriendlyTex->getWidth() * tS.adjustedScaleX() / 2.f; const float halfFriendlyH = mFriendlyTex->getHeight() * tS.adjustedScaleY() / 2.f; if (!spawnSea) { tS.x = static_cast(Utils::getUtils().rirng32(static_cast(viewLeft + halfFriendlyW + 25.f), static_cast(mLandBoundaryX - halfFriendlyW - 25.f))); } else { tS.x = static_cast(Utils::getUtils().rirng32(static_cast(mLandBoundaryX + halfFriendlyW + 25.f), static_cast(viewRight - halfFriendlyW - 25.f))); } tS.y = static_cast(Utils::getUtils().rirng32(static_cast(viewTop + halfFriendlyH + 100.f), static_cast(viewBottom - halfFriendlyH - 100.f))); const int id = Utils::getUtils().rirng32(0, 1000000); const std::string name = std::string("Friendly_Auto_") + std::to_string(id); auto* friendly = State::GameState::getInstance().addEntity(std::make_unique(name, mFriendlyTex, tS)); if (friendly) { if (auto* collider = friendly->getComponent()) { collider->setScale(0.75f); } GameManager::setSharedData("friendlyActiveCount", GameManager::getSharedData("friendlyActiveCount") + 1); } } } } if (mPendingLevelSpawn) { if (enemyCount <= 0 && trashCount <= 0) { GameManager::processPendingEntityRemovals(); mPendingLevelSpawn = false; spawnLevel(mPendingLevelStage); } return; } if (enemyCount <= 0 && trashCount <= 0) { if (stage < mMaxLevels) { mPendingLevelSpawn = true; mPendingLevelStage = stage + 1; } else if (!GameManager::getSharedData("gameWon")) { writeFinalScoreFile(GameManager::getSharedData("gameScore")); GameManager::setSharedData("gameWon", true); LOG("Vsi nivoji so zaključeni"); } } } void Background::onWindowResized(int newWidth, int newHeight) { // Always maintain logical world dimensions (1280×720) mW = 1280; mH = 720; } }