#include "game.h" #include // Constructor Game::Game(SDL_Renderer *renderer, Screen *screen, Resource *resource, Asset *asset, options_t *options, Input *input, Debug *debug) { // Inicia algunas variables board.iniClock = SDL_GetTicks(); currentRoom = "03.room"; const int x = 25; const int y = 13; spawnPoint = {x * 8, y * 8, 0, 0, 0, s_standing, SDL_FLIP_HORIZONTAL}; // Copia los punteros this->resource = resource; this->renderer = renderer; this->asset = asset; this->screen = screen; this->input = input; this->debug = debug; this->options = options; #ifdef DEBUG currentRoom = "01.room"; const int x1 = 25; const int y1 = 13; spawnPoint = {x1 * 8, y1 * 8, 0, 0, 0, s_standing, SDL_FLIP_HORIZONTAL}; #endif // Crea los objetos scoreboard = new ScoreBoard(renderer, resource, asset, options, &board); itemTracker = new ItemTracker(); roomTracker = new RoomTracker(); room = new Room(resource->getRoom(currentRoom), renderer, screen, asset, options, itemTracker, &board.items, false, debug); const std::string playerPNG = options->cheat.altSkin ? "player2.png" : "player.png"; const std::string playerANI = options->cheat.altSkin ? "player2.ani" : "player.ani"; const player_t player = {spawnPoint, playerPNG, playerANI, renderer, resource, asset, options, input, room, debug}; this->player = new Player(player); eventHandler = new SDL_Event(); text = new Text(resource->getOffset("smb2.txt"), resource->getTexture("smb2.png"), renderer); music = JA_LoadMusic(asset->get("game.ogg").c_str()); deathSound = JA_LoadSound(asset->get("death.wav").c_str()); stats = new Stats(asset->get("stats.csv"), asset->get("stats_buffer.csv"), options); // Inicializa el resto de variables ticks = 0; ticksSpeed = 15; board.lives = 9; #ifdef DEBUG board.lives = 9; #endif board.items = 0; board.rooms = 1; board.music = true; board.jailEnabled = options->cheat.jailEnabled; setScoreBoardColor(); roomTracker->addRoom(currentRoom); paused = false; blackScreen = false; blackScreenCounter = 0; totalItems = getTotalItems(); initStats(); stats->addVisit(room->getName()); section.name = SECTION_PROG_GAME; section.subsection = 0; } Game::~Game() { // Libera la memoria de los objetos delete scoreboard; delete itemTracker; delete roomTracker; delete room; delete player; delete eventHandler; delete text; delete stats; JA_DeleteMusic(music); JA_DeleteSound(deathSound); } // Comprueba los eventos de la cola void Game::checkEventHandler() { // Comprueba los eventos que hay en la cola while (SDL_PollEvent(eventHandler) != 0) { // Evento de salida de la aplicación if (eventHandler->type == SDL_QUIT) { section.name = SECTION_PROG_QUIT; screen->setBorderColor(stringToColor(options->palette, "black")); break; } if (eventHandler->type == SDL_RENDER_DEVICE_RESET || eventHandler->type == SDL_RENDER_TARGETS_RESET) { reLoadTextures(); } if ((eventHandler->type == SDL_KEYDOWN) and (eventHandler->key.repeat == 0)) { switch (eventHandler->key.keysym.scancode) { case SDL_SCANCODE_ESCAPE: section.name = SECTION_PROG_TITLE; break; #ifdef DEBUG case SDL_SCANCODE_G: debug->switchEnabled(); options->cheat.invincible = debug->getEnabled(); board.music = !debug->getEnabled(); board.music ? JA_ResumeMusic() : JA_PauseMusic(); break; case SDL_SCANCODE_R: resource->reLoad(); break; case SDL_SCANCODE_W: goToRoom(BORDER_TOP); break; case SDL_SCANCODE_A: goToRoom(BORDER_LEFT); break; case SDL_SCANCODE_S: goToRoom(BORDER_BOTTOM); break; case SDL_SCANCODE_D: goToRoom(BORDER_RIGHT); break; case SDL_SCANCODE_F6: screen->showNotification("MAMA MIRA! SIN MANOS!"); break; #endif case SDL_SCANCODE_M: board.music = !board.music; board.music ? JA_ResumeMusic() : JA_PauseMusic(); break; case SDL_SCANCODE_H: switchPause(); break; case SDL_SCANCODE_B: screen->switchBorder(); reLoadTextures(); break; case SDL_SCANCODE_F: screen->switchVideoMode(); reLoadTextures(); break; case SDL_SCANCODE_F1: screen->setWindowSize(1); reLoadTextures(); break; case SDL_SCANCODE_F2: screen->setWindowSize(2); reLoadTextures(); break; case SDL_SCANCODE_F3: screen->setWindowSize(3); reLoadTextures(); break; case SDL_SCANCODE_F4: screen->setWindowSize(4); reLoadTextures(); break; case SDL_SCANCODE_F5: switchPalette(); break; default: break; } } } } // Bucle para el juego section_t Game::run() { JA_PlayMusic(music); if (!board.music) { JA_PauseMusic(); } while (section.name == SECTION_PROG_GAME) { update(); render(); } JA_StopMusic(); return section; } // Actualiza el juego, las variables, comprueba la entrada, etc. void Game::update() { // Comprueba que la diferencia de ticks sea mayor a la velocidad del juego if (SDL_GetTicks() - ticks > ticksSpeed) { // Actualiza el contador de ticks ticks = SDL_GetTicks(); // Comprueba los eventos de la cola checkEventHandler(); #ifdef DEBUG debug->clear(); #endif // Actualiza los objetos room->update(); player->update(); checkPlayerOnBorder(); checkPlayerAndItems(); checkPlayerAndEnemies(); checkIfPlayerIsAlive(); checkGameOver(); checkEndGame(); checkRestoringJail(); scoreboard->update(); input->update(); updateBlackScreen(); // Actualiza las notificaciones screen->updateNotifier(); #ifdef DEBUG updateDebugInfo(); #endif } } // Pinta los objetos en pantalla void Game::render() { // Prepara para dibujar el frame screen->start(); // Dibuja los elementos del juego en orden room->renderMap(); room->renderEnemies(); room->renderItems(); player->render(); renderRoomName(); scoreboard->render(); renderBlackScreen(); #ifdef DEBUG // Debug info renderDebugInfo(); #endif // Actualiza la pantalla screen->blit(); } #ifdef DEBUG // Pasa la información de debug void Game::updateDebugInfo() { debug->add("X = " + std::to_string((int)player->x) + ", Y = " + std::to_string((int)player->y)); debug->add("VX = " + std::to_string(player->vx).substr(0, 4) + ", VY = " + std::to_string(player->vy).substr(0, 4)); debug->add("STATE = " + std::to_string(player->state)); } // Pone la información de debug en pantalla void Game::renderDebugInfo() { if (!debug->getEnabled()) { return; } // Borra el marcador SDL_Rect rect = {0, 18 * BLOCK, PLAY_AREA_WIDTH, GAMECANVAS_HEIGHT - PLAY_AREA_HEIGHT}; SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderFillRect(renderer, &rect); // Pinta la rejilla SDL_SetRenderDrawColor(renderer, 255, 255, 255, 32); for (int i = 0; i < PLAY_AREA_BOTTOM; i += 8) { // Lineas horizontales SDL_RenderDrawLine(renderer, 0, i, PLAY_AREA_RIGHT, i); } for (int i = 0; i < PLAY_AREA_RIGHT; i += 8) { // Lineas verticales SDL_RenderDrawLine(renderer, i, 0, i, PLAY_AREA_BOTTOM - 1); } // Pinta el texto debug->setPos({1, 18 * 8}); debug->render(); } #endif // Escribe el nombre de la pantalla void Game::renderRoomName() { // Texto en el centro de la pantalla SDL_Rect rect = {0, 16 * BLOCK, PLAY_AREA_WIDTH, BLOCK * 2}; color_t color = stringToColor(options->palette, "white"); SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, 0xFF); SDL_RenderFillRect(renderer, &rect); text->writeDX(TXT_CENTER | TXT_COLOR, GAMECANVAS_CENTER_X, 16 * 8 + 4, room->getName(), 1, room->getBGColor()); } // Cambia de habitación bool Game::changeRoom(std::string file) { // En las habitaciones los limites tienen la cadena del fichero o un 0 en caso de no limitar con nada if (file == "0") { return false; } // Verifica que exista el fichero que se va a cargar if (asset->get(file) != "") { // Elimina la habitación actual delete room; room = nullptr; // Crea un objeto habitación nuevo a partir del fichero room = new Room(resource->getRoom(file), renderer, screen, asset, options, itemTracker, &board.items, board.jailEnabled, debug); // Pone el color del marcador en función del color del borde de la habitación setScoreBoardColor(); if (roomTracker->addRoom(file)) { // Incrementa el contador de habitaciones visitadas board.rooms++; options->stats.rooms = board.rooms; // Actualiza las estadisticas stats->addVisit(room->getName()); } // Pasa la nueva habitación al jugador player->setRoom(room); return true; } return false; } // Comprueba si el jugador esta en el borde de la pantalla void Game::checkPlayerOnBorder() { if (player->getOnBorder()) { const std::string roomName = room->getRoom(player->getBorder()); if (changeRoom(roomName)) { player->switchBorders(); currentRoom = roomName; spawnPoint = player->getSpawnParams(); } } } // Comprueba las colisiones del jugador con los enemigos bool Game::checkPlayerAndEnemies() { const bool death = room->enemyCollision(player->getCollider()); if (death) { killPlayer(); } return death; } // Comprueba las colisiones del jugador con los objetos void Game::checkPlayerAndItems() { room->itemCollision(player->getCollider()); } // Comprueba si el jugador esta vivo void Game::checkIfPlayerIsAlive() { if (!player->isAlive()) { killPlayer(); } } // Comprueba si ha terminado la partida void Game::checkGameOver() { if (board.lives < 0 && blackScreenCounter > 17) { section.name = SECTION_PROG_GAME_OVER; } } // Mata al jugador void Game::killPlayer() { if (options->cheat.invincible) { return; } // Resta una vida al jugador if (!options->cheat.infiniteLives) { board.lives--; } // Actualiza las estadisticas stats->addDeath(room->getName()); // Destruye la habitacion y el jugador delete room; delete this->player; // Sonido JA_PlaySound(deathSound); // Pone la pantalla en negro un tiempo setBlackScreen(); // Crea la nueva habitación y el nuevo jugador room = new Room(resource->getRoom(currentRoom), renderer, screen, asset, options, itemTracker, &board.items, board.jailEnabled, debug); const std::string playerPNG = options->cheat.altSkin ? "player2.png" : "player.png"; const std::string playerANI = options->cheat.altSkin ? "player2.ani" : "player.ani"; const player_t player = {spawnPoint, playerPNG, playerANI, renderer, resource, asset, options, input, room, debug}; this->player = new Player(player); // Pone los objetos en pausa mientras esta la habitación en negro room->pause(); this->player->pause(); } // Recarga todas las texturas void Game::reLoadTextures() { if (options->console) { std::cout << "** RELOAD REQUESTED" << std::endl; } player->reLoadTexture(); room->reLoadTexture(); scoreboard->reLoadTexture(); text->reLoadTexture(); } // Cambia la paleta void Game::switchPalette() { if (options->console) { std::cout << "** PALETTE SWITCH REQUESTED" << std::endl; } // Modifica la variable options->palette = (options->palette == p_zxspectrum) ? p_zxarne : p_zxspectrum; // Recarga las paletas room->reLoadPalette(); player->reLoadPalette(); scoreboard->reLoadPalette(); // Pone el color del marcador en función del color del borde de la habitación setScoreBoardColor(); } // Establece la pantalla en negro void Game::setBlackScreen() { blackScreen = true; } // Actualiza las variables relativas a la pantalla en negro void Game::updateBlackScreen() { if (blackScreen) { blackScreenCounter++; if (blackScreenCounter > 20) { blackScreen = false; blackScreenCounter = 0; player->resume(); room->resume(); screen->setBorderColor(room->getBorderColor()); } } } // Dibuja la pantalla negra void Game::renderBlackScreen() { if (blackScreen) { screen->clean(); screen->setBorderColor(stringToColor(options->palette, "black")); } } // Pone el color del marcador en función del color del borde de la habitación void Game::setScoreBoardColor() { // Obtiene el color del borde const color_t colorBorder = room->getBorderColor(); const bool isBlack = colorAreEqual(colorBorder, stringToColor(options->palette, "black")); const bool isBrightBlack = colorAreEqual(colorBorder, stringToColor(options->palette, "bright_black")); // Si el color del borde es negro o negro brillante cambia el texto del marcador a blanco board.color = isBlack || isBrightBlack ? stringToColor(options->palette, "white") : colorBorder; } // Comprueba si ha finalizado el juego bool Game::checkEndGame() { const bool isOnTheRoom = room->getName() == "THE JAIL"; // Estar en la habitación que toca const bool haveTheItems = board.items >= int(totalItems * 0.9f) || options->cheat.jailEnabled; // Con mas del 90% de los items recogidos const bool isOnTheDoor = player->getRect().x <= 128; // Y en la ubicación que toca (En la puerta) if (haveTheItems) { board.jailEnabled = true; } if (haveTheItems && isOnTheRoom && isOnTheDoor) { section.name = SECTION_PROG_ENDING; return true; } return false; } // Obtiene la cantidad total de items que hay en el mapeado del juego int Game::getTotalItems() { int items = 0; std::vector *rooms = new std::vector; rooms = resource->getAllRooms(); for (auto room : *rooms) { items += room.room->items.size(); } return items; } // Va a la habitación designada void Game::goToRoom(int border) { const std::string roomName = room->getRoom(border); if (changeRoom(roomName)) { currentRoom = roomName; } } // Pone el juego en pausa void Game::switchPause() { if (paused) { player->resume(); room->resume(); scoreboard->resume(); paused = false; } else { player->pause(); room->pause(); scoreboard->pause(); paused = true; } } // Da vidas al jugador cuando está en la Jail void Game::checkRestoringJail() { if (room->getName() != "THE JAIL" || board.lives == 9) { return; } static int counter = 0; if (!paused) { counter++; } if (counter == 100) { counter = 0; board.lives++; JA_PlaySound(deathSound); } } // Inicializa el diccionario de las estadísticas void Game::initStats() { std::vector *rooms = new std::vector; rooms = resource->getAllRooms(); for (auto room : *rooms) { stats->addDictionary(room.room->number, room.room->name); } stats->init(); }