#include "game.h"
#include <SDL2/SDL_blendmode.h>	  // for SDL_BLENDMODE_BLEND
#include <SDL2/SDL_keycode.h>	  // for SDLK_1, SDLK_2, SDLK_3, SDLK_h
#include <SDL2/SDL_pixels.h>	  // for SDL_PIXELFORMAT_RGBA8888
#include <SDL2/SDL_rwops.h>		  // for SDL_RWFromFile, SDL_RWclose, SDL_R...
#include <SDL2/SDL_timer.h>		  // for SDL_GetTicks
#include <SDL2/SDL_video.h>		  // for SDL_WINDOWEVENT_FOCUS_GAINED, SDL_...
#include <stdlib.h>				  // for rand
#include <algorithm>			  // for min
#include <fstream>				  // for basic_ifstream
#include <iostream>				  // for char_traits, basic_istream, ifstream
#include "asset.h"				  // for Asset
#include "background.h"			  // for Background
#include "balloon.h"			  // for Balloon, BALLOON_SPEED_1, BALLOON_...
#include "bullet.h"				  // for Bullet, BulletType::LEFT, BulletType::RIGHT
#include "enemy_formations.h"	  // for stage_t, EnemyFormations, enemyIni...
#include "explosions.h"			  // for Explosions
#include "fade.h"				  // for Fade, FadeType::RANDOM_SQUARE, FADE_VEN...
#include "global_inputs.h"		  // for globalInputs::check
#include "input.h"				  // for inputs_e, Input, INPUT_DO_NOT_ALLO...
#include "item.h"				  // for Item, ITEM_COFFEE_MACHINE, ITEM_CLOCK
#include "jail_audio.h"			  // for JA_PlaySound, JA_DeleteSound, JA_L...
#include "lang.h"				  // for getText
#include "manage_hiscore_table.h" // for ManageHiScoreTable
#include "options.h"			  // for options
#include "param.h"				  // for param
#include "player.h"				  // for Player, PlayerStatus::PLAYING, PLA...
#include "scoreboard.h"			  // for Scoreboard, scoreboard_modes_e
#include "screen.h"				  // for Screen
#include "smart_sprite.h"		  // for SmartSprite
#include "text.h"				  // for Text, TXT_CENTER
#include "texture.h"			  // for Texture
#include "dbgtxt.h"
struct JA_Music_t;
struct JA_Sound_t;

#define GAME_OVER_COUNTER 350

// Constructor
Game::Game(int playerID, int currentStage, bool demo, JA_Music_t *music)
	: music(music)
{
	// Copia los punteros
	asset = Asset::get();
	input = Input::get();
	screen = Screen::get();
	renderer = screen->getRenderer();

	// Pasa variables
	this->demo.enabled = demo;
	this->currentStage = currentStage;
	lastStageReached = currentStage;
	difficulty = options.game.difficulty;

	// Crea los objetos
	Scoreboard::init(renderer);
	scoreboard = Scoreboard::get();
	eventHandler = std::make_unique<SDL_Event>();
	fade = std::make_unique<Fade>(renderer);

	background = std::make_unique<Background>(renderer);
	explosions = std::make_unique<Explosions>();
	enemyFormations = std::make_unique<EnemyFormations>();

	// Carga los recursos
	loadMedia();

	// Inicializa los vectores con los datos para la demo
	if (demo)
	{ // Aleatoriza la asignación del fichero
		const auto index1 = rand() % 2;
		const auto index2 = (index1 + 1) % 2;
		loadDemoFile(asset->get("demo1.bin"), &this->demo.dataFile[index1]);
		loadDemoFile(asset->get("demo2.bin"), &this->demo.dataFile[index2]);
	}

	background->setPos(param.game.play_area.rect);

	n1000Sprite = std::make_shared<SmartSprite>(gameTextTexture);
	n2500Sprite = std::make_shared<SmartSprite>(gameTextTexture);
	n5000Sprite = std::make_shared<SmartSprite>(gameTextTexture);

	explosions->addTexture(1, explosionsTextures[0], explosionsAnimations[0]);
	explosions->addTexture(2, explosionsTextures[1], explosionsAnimations[1]);
	explosions->addTexture(3, explosionsTextures[2], explosionsAnimations[2]);
	explosions->addTexture(4, explosionsTextures[3], explosionsAnimations[3]);

	canvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.play_area.rect.w, param.game.play_area.rect.h);
	SDL_SetTextureBlendMode(canvas, SDL_BLENDMODE_BLEND);

	// Inicializa las variables necesarias para la sección 'Game'
	init(playerID);
}

Game::~Game()
{
	// Guarda las puntuaciones en un fichero
	auto manager = std::make_unique<ManageHiScoreTable>(&options.game.hi_score_table);
	manager->saveToFile(asset->get("score.bin"));
#ifdef RECORDING
	saveDemoFile(asset->get("demo1.bin"));
#endif

	// Elimina todos los objetos contenidos en vectores
	deleteAllVectorObjects();

	// Libera los recursos
	unloadMedia();

	Scoreboard::destroy();

	SDL_DestroyTexture(canvas);
}

// Inicializa las variables necesarias para la sección 'Game'
void Game::init(int playerID)
{
	ticks = 0;
	ticksSpeed = 15;

	// Elimina qualquier jugador que hubiese antes de crear los nuevos
	players.clear();

	// Crea los dos jugadores
	auto player1 = std::make_unique<Player>(1, (param.game.play_area.first_quarter_x * ((0 * 2) + 1)) - 11, param.game.play_area.rect.h - 30, demo.enabled, &param.game.play_area.rect, playerTextures[0], playerAnimations);
	player1->setScoreBoardPanel(SCOREBOARD_LEFT_PANEL);
	player1->setName(lang::getText(53));
	const auto controller1 = getController(player1->getId());
	player1->setController(controller1);
	players.push_back(std::move(player1));

	auto player2 = std::make_unique<Player>(2, (param.game.play_area.first_quarter_x * ((1 * 2) + 1)) - 11, param.game.play_area.rect.h - 30, demo.enabled, &param.game.play_area.rect, playerTextures[1], playerAnimations);
	player2->setScoreBoardPanel(SCOREBOARD_RIGHT_PANEL);
	player2->setName(lang::getText(54));
	const auto controller2 = getController(player2->getId());
	player2->setController(controller2);
	players.push_back(std::move(player2));

	// Obtiene mediante "playerID" el jugador que va a empezar jugar
	auto player = getPlayer(playerID);

	// Cambia el estado del jugador seleccionado
	player->setStatusPlaying(PlayerStatus::PLAYING);

	// Como es el principio del juego, empieza sin inmunidad
	player->setInvulnerable(false);

	// Variables relacionadas con la dificultad
	switch (difficulty)
	{
	case GameDifficulty::EASY:
	{
		defaultEnemySpeed = BALLOON_SPEED_1;
		difficultyScoreMultiplier = 0.5f;
		difficultyColor = difficulty_easy_color;
		scoreboard->setColor(difficultyColor);
		break;
	}

	case GameDifficulty::NORMAL:
	{
		defaultEnemySpeed = BALLOON_SPEED_1;
		difficultyScoreMultiplier = 1.0f;
		difficultyColor = difficulty_normal_color;
		scoreboard->setColor(scoreboard_color);
		break;
	}

	case GameDifficulty::HARD:
	{
		defaultEnemySpeed = BALLOON_SPEED_5;
		difficultyScoreMultiplier = 1.5f;
		difficultyColor = difficulty_hard_color;
		scoreboard->setColor(difficultyColor);
		break;
	}

	default:
		break;
	}

	// Variables para el marcador
	scoreboard->setPos({param.scoreboard.x, param.scoreboard.y, param.scoreboard.w, param.scoreboard.h});
	for (auto &player : players)
	{
		scoreboard->setName(player->getScoreBoardPanel(), player->getName());
		if (player->isWaiting())
		{
			scoreboard->setMode(player->getScoreBoardPanel(), ScoreboardMode::WAITING);
		}
	}
	scoreboard->setMode(SCOREBOARD_CENTER_PANEL, ScoreboardMode::STAGE_INFO);

	// Resto de variables
	hiScore.score = options.game.hi_score_table[0].score;
	hiScore.name = options.game.hi_score_table[0].name;
	paused = false;
	gameCompleted = false;
	gameCompletedCounter = 0;
	section::name = section::NAME_GAME;
	section::options = section::OPTIONS_GAME_PLAY_1P;
	currentPower = 0;
	menaceCurrent = 0;
	menaceThreshold = 0;
	hiScoreAchieved = false;
	stageBitmapCounter = STAGE_COUNTER;
	gameOverCounter = GAME_OVER_COUNTER;
	timeStopped = false;
	timeStoppedCounter = 0;
	counter = 0;
	lastEnemyDeploy = 0;
	enemyDeployCounter = 0;
	enemySpeed = defaultEnemySpeed;
	helper.needCoffee = false;
	helper.needCoffeeMachine = false;
	helper.needPowerBall = false;
	helper.counter = HELP_COUNTER;
	helper.itemPoints1Odds = ITEM_POINTS_1_DISK_ODDS;
	helper.itemPoints2Odds = ITEM_POINTS_2_GAVINA_ODDS;
	helper.itemPoints3Odds = ITEM_POINTS_3_PACMAR_ODDS;
	helper.itemClockOdds = ITEM_CLOCK_ODDS;
	helper.itemCoffeeOdds = ITEM_COFFEE_ODDS;
	helper.itemCoffeeMachineOdds = ITEM_COFFEE_MACHINE_ODDS;
	powerBallEnabled = false;
	powerBallCounter = 0;
	coffeeMachineEnabled = false;
	balloonsPopped = 0;
#ifdef DEBUG
	autoPopBalloons = false;
#endif

	// Inicializa las variables para el modo DEMO
	if (demo.enabled)
	{
		// Selecciona una pantalla al azar
		constexpr auto demos = 3;
		const auto demo = rand() % demos;
		const int stages[demos] = {0, 3, 5};
		currentStage = stages[demo];

		// Actualiza el numero de globos explotados según la fase de la demo
		for (int i = 0; i < currentStage; ++i)
		{
			balloonsPopped += enemyFormations->getStage(i).powerToComplete;
		}

		// Activa o no al otro jugador
		if (rand() % 2 == 0)
		{
			const auto otherPlayer = playerID == 1 ? 2 : 1;
			auto player = getPlayer(otherPlayer);
			player->setStatusPlaying(PlayerStatus::PLAYING);
		}

		for (auto &player : players)
		{
			// Añade 0, 1 o 2 cafes al jugador
			for (int i = 0; i < rand() % 3; ++i)
			{
				player->giveExtraHit();
			}

			// Empieza sin inmunidad
			player->setInvulnerable(false);
		}

		// Deshabilita los sonidos
		JA_EnableSound(false);

		// Configura los marcadores
		scoreboard->setMode(SCOREBOARD_LEFT_PANEL, ScoreboardMode::DEMO);
		scoreboard->setMode(SCOREBOARD_RIGHT_PANEL, ScoreboardMode::DEMO);
	}

	initPaths();

	totalPowerToCompleteGame = 0;
	for (int i = 0; i < 10; ++i)
	{
		totalPowerToCompleteGame += enemyFormations->getStage(i).powerToComplete;
	}

	// Modo grabar demo
#ifdef RECORDING
	demo.recording = true;
#else
	demo.recording = false;
#endif
	demo.counter = 0;

	// Inicializa el objeto para el fundido
	fade->setColor(fade_color.r, fade_color.g, fade_color.b);
	fade->setPost(param.fade.post_duration);
	fade->setType(FadeType::VENETIAN);

	// Con los globos creados, calcula el nivel de amenaza
	evaluateAndSetMenace();

	// Inicializa el bitmap de 1000 puntos
	constexpr auto height = 15;
	constexpr auto sprite1Width = 35;
	constexpr auto sprite2Width = 38;
	constexpr auto sprite3Width = 39;
	n1000Sprite->setPosX(0);
	n1000Sprite->setPosY(0);
	n1000Sprite->setWidth(sprite1Width);
	n1000Sprite->setHeight(height);
	n1000Sprite->setVelX(0.0f);
	n1000Sprite->setVelY(-0.5f);
	n1000Sprite->setAccelX(0.0f);
	n1000Sprite->setAccelY(-0.1f);
	n1000Sprite->setSpriteClip(0, 0, sprite1Width, height);
	n1000Sprite->setEnabled(false);
	n1000Sprite->setFinishedCounter(0);
	n1000Sprite->setDestX(0);
	n1000Sprite->setDestY(0);

	// Inicializa el bitmap de 2500 puntos
	n2500Sprite->setPosX(0);
	n2500Sprite->setPosY(0);
	n2500Sprite->setWidth(sprite2Width);
	n2500Sprite->setHeight(height);
	n2500Sprite->setVelX(0.0f);
	n2500Sprite->setVelY(-0.5f);
	n2500Sprite->setAccelX(0.0f);
	n2500Sprite->setAccelY(-0.1f);
	n2500Sprite->setSpriteClip(sprite1Width, 0, sprite2Width, height);
	n2500Sprite->setEnabled(false);
	n2500Sprite->setFinishedCounter(0);
	n2500Sprite->setDestX(0);
	n2500Sprite->setDestY(0);

	// Inicializa el bitmap de 5000 puntos
	n5000Sprite->setPosX(0);
	n5000Sprite->setPosY(0);
	n5000Sprite->setWidth(sprite3Width);
	n5000Sprite->setHeight(height);
	n5000Sprite->setVelX(0.0f);
	n5000Sprite->setVelY(-0.5f);
	n5000Sprite->setAccelX(0.0f);
	n5000Sprite->setAccelY(-0.1f);
	n5000Sprite->setSpriteClip(sprite1Width + sprite2Width, 0, sprite3Width, height);
	n5000Sprite->setEnabled(false);
	n5000Sprite->setFinishedCounter(0);
	n5000Sprite->setDestX(0);
	n5000Sprite->setDestY(0);
}

// Carga los recursos necesarios para la sección 'Game'
void Game::loadMedia()
{
#ifdef VERBOSE
	std::cout << std::endl
			  << "** LOADING RESOURCES FOR GAME SECTION" << std::endl;
#endif

	playerAnimations.clear();
	balloonAnimations.clear();
	itemAnimations.clear();
	player1Textures.clear();
	player2Textures.clear();
	itemTextures.clear();
	balloonTextures.clear();
	explosionsTextures.clear();

	// Texturas
	bulletTexture = std::make_shared<Texture>(renderer, asset->get("bullet.png"));
	gameTextTexture = std::make_shared<Texture>(renderer, asset->get("game_text.png"));

	// Texturas - Globos
	auto balloon1Texture = std::make_shared<Texture>(renderer, asset->get("balloon1.png"));
	balloonTextures.push_back(balloon1Texture);

	auto balloon2Texture = std::make_shared<Texture>(renderer, asset->get("balloon2.png"));
	balloonTextures.push_back(balloon2Texture);

	auto balloon3Texture = std::make_shared<Texture>(renderer, asset->get("balloon3.png"));
	balloonTextures.push_back(balloon3Texture);

	auto balloon4Texture = std::make_shared<Texture>(renderer, asset->get("balloon4.png"));
	balloonTextures.push_back(balloon4Texture);

	auto balloon5Texture = std::make_shared<Texture>(renderer, asset->get("powerball.png"));
	balloonTextures.push_back(balloon5Texture);

	// Texturas - Explosiones
	auto explosion1Texture = std::make_shared<Texture>(renderer, asset->get("explosion1.png"));
	explosionsTextures.push_back(explosion1Texture);

	auto explosion2Texture = std::make_shared<Texture>(renderer, asset->get("explosion2.png"));
	explosionsTextures.push_back(explosion2Texture);

	auto explosion3Texture = std::make_shared<Texture>(renderer, asset->get("explosion3.png"));
	explosionsTextures.push_back(explosion3Texture);

	auto explosion4Texture = std::make_shared<Texture>(renderer, asset->get("explosion4.png"));
	explosionsTextures.push_back(explosion4Texture);

	// Texturas - Items
	auto item1 = std::make_shared<Texture>(renderer, asset->get("item_points1_disk.png"));
	itemTextures.push_back(item1);

	auto item2 = std::make_shared<Texture>(renderer, asset->get("item_points2_gavina.png"));
	itemTextures.push_back(item2);

	auto item3 = std::make_shared<Texture>(renderer, asset->get("item_points3_pacmar.png"));
	itemTextures.push_back(item3);

	auto item4 = std::make_shared<Texture>(renderer, asset->get("item_clock.png"));
	itemTextures.push_back(item4);

	auto item5 = std::make_shared<Texture>(renderer, asset->get("item_coffee.png"));
	itemTextures.push_back(item5);

	auto item6 = std::make_shared<Texture>(renderer, asset->get("item_coffee_machine.png"));
	itemTextures.push_back(item6);

	// Texturas - Player1
	auto player1 = std::make_shared<Texture>(renderer, asset->get("player1.gif"));
	player1->addPalette(asset->get("player1_pal1.gif"));
	player1->addPalette(asset->get("player1_pal2.gif"));
	player1->addPalette(asset->get("player1_pal3.gif"));
	player1Textures.push_back(player1);

	auto player1Power = std::make_shared<Texture>(renderer, asset->get("player_power.gif"));
	player1Power->addPalette(asset->get("player_power_pal.gif"));
	player1Textures.push_back(player1Power);

	playerTextures.push_back(player1Textures);

	// Texturas - Player2
	auto player2 = std::make_shared<Texture>(renderer, asset->get("player2.gif"));
	player2->addPalette(asset->get("player2_pal1.gif"));
	player2->addPalette(asset->get("player2_pal2.gif"));
	player2->addPalette(asset->get("player2_pal3.gif"));
	player2Textures.push_back(player2);

	auto player2Power = std::make_shared<Texture>(renderer, asset->get("player_power.gif"));
	player2Power->addPalette(asset->get("player_power_pal.gif"));
	player2Power->setPalette(1);
	player2Textures.push_back(player2Power);

	playerTextures.push_back(player2Textures);

	// Animaciones -- Jugador
	{
		std::vector<std::string> *playerAnimation = new std::vector<std::string>;
		loadAnimations(asset->get("player.ani"), playerAnimation);
		playerAnimations.push_back(playerAnimation);

		std::vector<std::string> *playerPowerAnimation = new std::vector<std::string>;
		loadAnimations(asset->get("player_power.ani"), playerPowerAnimation);
		playerAnimations.push_back(playerPowerAnimation);
	}

	// Animaciones -- Globos
	{
		std::vector<std::string> *balloon1Animation = new std::vector<std::string>;
		loadAnimations(asset->get("balloon1.ani"), balloon1Animation);
		balloonAnimations.push_back(balloon1Animation);

		std::vector<std::string> *balloon2Animation = new std::vector<std::string>;
		loadAnimations(asset->get("balloon2.ani"), balloon2Animation);
		balloonAnimations.push_back(balloon2Animation);

		std::vector<std::string> *balloon3Animation = new std::vector<std::string>;
		loadAnimations(asset->get("balloon3.ani"), balloon3Animation);
		balloonAnimations.push_back(balloon3Animation);

		std::vector<std::string> *balloon4Animation = new std::vector<std::string>;
		loadAnimations(asset->get("balloon4.ani"), balloon4Animation);
		balloonAnimations.push_back(balloon4Animation);

		std::vector<std::string> *balloon5Animation = new std::vector<std::string>;
		loadAnimations(asset->get("powerball.ani"), balloon5Animation);
		balloonAnimations.push_back(balloon5Animation);
	}

	// Animaciones -- Explosiones
	std::vector<std::string> *explosions1Animation = new std::vector<std::string>;
	loadAnimations(asset->get("explosion1.ani"), explosions1Animation);
	explosionsAnimations.push_back(explosions1Animation);

	std::vector<std::string> *explosions2Animation = new std::vector<std::string>;
	loadAnimations(asset->get("explosion2.ani"), explosions2Animation);
	explosionsAnimations.push_back(explosions2Animation);

	std::vector<std::string> *explosions3Animation = new std::vector<std::string>;
	loadAnimations(asset->get("explosion3.ani"), explosions3Animation);
	explosionsAnimations.push_back(explosions3Animation);

	std::vector<std::string> *explosions4Animation = new std::vector<std::string>;
	loadAnimations(asset->get("explosion4.ani"), explosions4Animation);
	explosionsAnimations.push_back(explosions4Animation);

	// Animaciones -- Items
	std::vector<std::string> *item1Animation = new std::vector<std::string>;
	loadAnimations(asset->get("item_points1_disk.ani"), item1Animation);
	itemAnimations.push_back(item1Animation);

	std::vector<std::string> *item2Animation = new std::vector<std::string>;
	loadAnimations(asset->get("item_points2_gavina.ani"), item2Animation);
	itemAnimations.push_back(item2Animation);

	std::vector<std::string> *item3Animation = new std::vector<std::string>;
	loadAnimations(asset->get("item_points3_pacmar.ani"), item3Animation);
	itemAnimations.push_back(item3Animation);

	std::vector<std::string> *item4Animation = new std::vector<std::string>;
	loadAnimations(asset->get("item_clock.ani"), item4Animation);
	itemAnimations.push_back(item4Animation);

	std::vector<std::string> *item5Animation = new std::vector<std::string>;
	loadAnimations(asset->get("item_coffee.ani"), item5Animation);
	itemAnimations.push_back(item5Animation);

	std::vector<std::string> *item6Animation = new std::vector<std::string>;
	loadAnimations(asset->get("item_coffee_machine.ani"), item6Animation);
	itemAnimations.push_back(item6Animation);

	// Texto
	text = std::make_unique<Text>(asset->get("smb2.gif"), asset->get("smb2.txt"), renderer);
	textBig = std::make_unique<Text>(asset->get("smb2_big.png"), asset->get("smb2_big.txt"), renderer);
	textNokia2 = std::make_unique<Text>(asset->get("nokia2.png"), asset->get("nokia2.txt"), renderer);
	textNokiaBig2 = std::make_unique<Text>(asset->get("nokia_big2.png"), asset->get("nokia_big2.txt"), renderer);

	// Sonidos
	balloonSound = JA_LoadSound(asset->get("balloon.wav").c_str());
	bubble1Sound = JA_LoadSound(asset->get("bubble1.wav").c_str());
	bubble2Sound = JA_LoadSound(asset->get("bubble2.wav").c_str());
	bubble3Sound = JA_LoadSound(asset->get("bubble3.wav").c_str());
	bubble4Sound = JA_LoadSound(asset->get("bubble4.wav").c_str());
	bulletSound = JA_LoadSound(asset->get("bullet.wav").c_str());
	clockSound = JA_LoadSound(asset->get("clock.wav").c_str());
	coffeeOutSound = JA_LoadSound(asset->get("coffeeout.wav").c_str());
	hiScoreSound = JA_LoadSound(asset->get("hiscore.wav").c_str());
	itemDropSound = JA_LoadSound(asset->get("itemdrop.wav").c_str());
	itemPickUpSound = JA_LoadSound(asset->get("itempickup.wav").c_str());
	playerCollisionSound = JA_LoadSound(asset->get("player_collision.wav").c_str());
	powerBallSound = JA_LoadSound(asset->get("powerball.wav").c_str());
	stageChangeSound = JA_LoadSound(asset->get("stage_change.wav").c_str());
	coffeeMachineSound = JA_LoadSound(asset->get("title.wav").c_str());

#ifdef VERBOSE
	std::cout << "** RESOURCES FOR GAME SECTION LOADED" << std::endl
			  << std::endl;
#endif
}

// Libera los recursos previamente cargados
void Game::unloadMedia()
{
	// Texturas
	player1Textures.clear();
	player2Textures.clear();
	itemTextures.clear();
	balloonTextures.clear();
	explosionsTextures.clear();

	// Animaciones
	playerAnimations.clear();
	balloonAnimations.clear();
	explosionsAnimations.clear();
	itemAnimations.clear();

	// Sonidos
	JA_DeleteSound(balloonSound);
	JA_DeleteSound(bulletSound);
	JA_DeleteSound(playerCollisionSound);
	JA_DeleteSound(hiScoreSound);
	JA_DeleteSound(itemDropSound);
	JA_DeleteSound(itemPickUpSound);
	JA_DeleteSound(coffeeOutSound);
	JA_DeleteSound(stageChangeSound);
	JA_DeleteSound(bubble1Sound);
	JA_DeleteSound(bubble2Sound);
	JA_DeleteSound(bubble3Sound);
	JA_DeleteSound(bubble4Sound);
	JA_DeleteSound(clockSound);
	JA_DeleteSound(powerBallSound);
	JA_DeleteSound(coffeeMachineSound);
}

// Carga el fichero de datos para la demo
bool Game::loadDemoFile(std::string filePath, DemoKeys (*dataFile)[TOTAL_DEMO_DATA])
{
	// Indicador de éxito en la carga
	auto success = true;
	const std::string fileName = filePath.substr(filePath.find_last_of("\\/") + 1);

	auto file = SDL_RWFromFile(filePath.c_str(), "r+b");
	if (!file)
	{ // El fichero no existe
#ifdef VERBOSE
		std::cout << "Warning: Unable to open " << fileName.c_str() << " file" << std::endl;
#endif

		// Creamos el fichero para escritura
		file = SDL_RWFromFile(filePath.c_str(), "w+b");

		// Si ha creado el fichero
		if (file)
		{
#ifdef VERBOSE
			std::cout << "New file (" << fileName.c_str() << ") created!" << std::endl;
#endif

			// Inicializas los datos y los guarda en el fichero
			for (int i = 0; i < TOTAL_DEMO_DATA; ++i)
			{
				DemoKeys dk;
				dk.left = 0;
				dk.right = 0;
				dk.no_input = 0;
				dk.fire = 0;
				dk.fire_left = 0;
				dk.fire_right = 0;
				(*dataFile)[i] = dk;
				SDL_RWwrite(file, &dk, sizeof(DemoKeys), 1);
			}

			// Cerramos el fichero
			SDL_RWclose(file);
		}
		else
		{ // Si no puede crear el fichero
#ifdef VERBOSE
			std::cout << "Error: Unable to create file " << fileName.c_str() << std::endl;
#endif
			success = false;
		}
	}
	// El fichero existe
	else
	{
// Mensaje de proceder a la carga de los datos
#ifdef VERBOSE
		std::cout << "Reading file: " << fileName.c_str() << std::endl;
#endif

		// Lee todos los datos del fichero y los deja en el destino
		for (int i = 0; i < TOTAL_DEMO_DATA; ++i)
		{
			DemoKeys tmp;
			SDL_RWread(file, &tmp, sizeof(DemoKeys), 1);
			(*dataFile)[i] = tmp;
		}

		// Cierra el fichero
		SDL_RWclose(file);
	}

	return success;
}

#ifdef RECORDING
// Guarda el fichero de datos para la demo
bool Game::saveDemoFile(std::string filePath)
{
	auto success = true;
	const std::string filename = filePath.substr(filePath.find_last_of("\\/") + 1);

	auto file = SDL_RWFromFile(filePath.c_str(), "w+b");
	if (file)
	{
		// Guardamos los datos
		for (int i = 0; i < TOTAL_DEMO_DATA; ++i)
		{
			SDL_RWwrite(file, &demo.dataFile[0][i], sizeof(DemoKeys), 1);
		}

#ifdef VERBOSE
		std::cout << "Writing file " << filename.c_str() << std::endl;
#endif // VERBOSE

		// Cerramos el fichero
		SDL_RWclose(file);
	}
	else
	{
#ifdef VERBOSE
		std::cout << "Error: Unable to save " << filename.c_str() << " file! " << SDL_GetError() << std::endl;
#endif // VERBOSE
	}

	return success;
}
#endif // RECORDING

// Crea una formación de enemigos
void Game::deployEnemyFormation()
{
	// Solo despliega una formación enemiga si ha pasado cierto tiempo desde la última
	if (enemyDeployCounter == 0)
	{

		// En este punto se decide entre crear una powerball o una formación enemiga
		if ((rand() % 100 < 15) && (canPowerBallBeCreated()))
		{
			// Crea una powerball
			createPowerBall();

			// Da un poco de margen para que se creen mas enemigos
			enemyDeployCounter = 300;
		}
		else
		{
			// Decrementa el contador de despliegues enemigos de la PowerBall
			powerBallCounter > 0 ? powerBallCounter-- : powerBallCounter = 0;

			// Elige una formación enemiga la azar
			auto set = rand() % 10;

			// Evita repetir la ultima formación enemiga desplegada
			if (set == lastEnemyDeploy)
			{
				++set %= 10;
			}

			lastEnemyDeploy = set;

			const stage_t stage = enemyFormations->getStage(currentStage);
			const auto numEnemies = stage.enemyPool->set[set]->numberOfEnemies;
			for (int i = 0; i < numEnemies; ++i)
			{
				createBalloon(stage.enemyPool->set[set]->init[i].x,
							  stage.enemyPool->set[set]->init[i].y,
							  stage.enemyPool->set[set]->init[i].kind,
							  stage.enemyPool->set[set]->init[i].velX,
							  enemySpeed,
							  stage.enemyPool->set[set]->init[i].creationCounter);
			}

			enemyDeployCounter = 300;
		}
	}
}

// Aumenta el poder de la fase
void Game::increaseStageCurrentPower(int power)
{
	currentPower += power;
}

// Actualiza el valor de hiScore en caso necesario
void Game::updateHiScore()
{
	// Si la puntuación actual es mayor que la máxima puntuación
	for (auto &player : players)
	{
		if (player->getScore() > hiScore.score)
		{
			// Actualiza la máxima puntuación
			hiScore.score = player->getScore();

			// Si se supera la máxima puntuación emite sonido
			if (hiScoreAchieved == false)
			{
				hiScoreAchieved = true;
				JA_PlaySound(hiScoreSound);
			}
		}
	}
}

// Actualiza las variables del jugador
void Game::updatePlayers()
{
	for (auto &player : players)
	{
		player->update();

		if (player->isPlaying())
		{
			// Comprueba la colisión entre el jugador y los globos
			if (checkPlayerBalloonCollision(player))
			{
				killPlayer(player);

				if (demo.enabled && allPlayersAreNotPlaying())
				{
					fade->setType(FadeType::RANDOM_SQUARE);
					fade->activate();
				}
			}

			// Comprueba las colisiones entre el jugador y los items
			checkPlayerItemCollision(player);
		}
	}
}

// Dibuja a los jugadores
void Game::renderPlayers()
{
	for (auto &player : players)
	{
		if (!player->isWaiting())
		{
			player->render();
#ifdef DEBUG
			// SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
			// const Circle c = player->getCollider();
			// DrawCircle(renderer, c.x, c.y, c.r);
#endif
		}
	}
}

// Comprueba si hay cambio de fase y actualiza las variables
void Game::updateStage()
{
	if (currentPower >= enemyFormations->getStage(currentStage).powerToComplete)
	{
		// Cambio de fase
		currentStage++;
		currentPower = 0;
		lastStageReached = currentStage;
		if (currentStage == 10)
		{						  // Ha llegado al final el juego
			gameCompleted = true; // Marca el juego como completado
			currentStage = 9;	  // Deja el valor dentro de los limites
			destroyAllBalloons(); // Destruye a todos los enemigos
			currentPower = 0;	  // Vuelve a dejar el poder a cero, por lo que hubiera podido subir al destruir todos lo globos
			menaceCurrent = 255;  // Sube el nivel de amenaza para que no cree mas globos
			for (auto &player : players)
			{ // Añade un millon de puntos a los jugadores que queden vivos
				if (player->isPlaying())
				{
					player->addScore(1000000);
				}
			}
			updateHiScore();
			JA_StopMusic();
		}
		JA_PlaySound(stageChangeSound);
		stageBitmapCounter = 0;
		enemySpeed = defaultEnemySpeed;
		setBalloonSpeed(enemySpeed);
		screen->flash(flash_color, 5);
		screen->shake();
	}

	// Incrementa el contador del bitmap que aparece mostrando el cambio de fase
	if (stageBitmapCounter < STAGE_COUNTER)
	{
		stageBitmapCounter++;
	}

	// Si el juego se ha completado, el bitmap se detiene en el centro de la pantalla
	if (gameCompleted)
	{
		if (stageBitmapCounter > 100)
		{
			stageBitmapCounter = 100;
		}
	}
}

// Actualiza el estado de fin de la partida
void Game::updateGameOver()
{
	// Si todos estan en estado de Game Over
	if (allPlayersAreGameOver())
	{
		if (gameOverCounter > 0)
		{
			gameOverCounter--;

			if ((gameOverCounter == 250) || (gameOverCounter == 200) || (gameOverCounter == 180) || (gameOverCounter == 120) || (gameOverCounter == 60))
			{
				// Hace sonar aleatoriamente uno de los 4 sonidos de burbujas
				const auto index = rand() % 4;
				JA_Sound_t *sound[4] = {bubble1Sound, bubble2Sound, bubble3Sound, bubble4Sound};
				JA_PlaySound(sound[index], 0);
			}

			if (gameOverCounter == 150)
			{
				fade->activate();
			}
		}

		if (fade->hasEnded())
		{
			section::name = section::NAME_HI_SCORE_TABLE;
		}
	}
}

// Actualiza los globos
void Game::updateBalloons()
{
	for (auto balloon : balloons)
	{
		balloon->update();
	}
}

// Pinta en pantalla todos los globos activos
void Game::renderBalloons()
{
	for (auto &balloon : balloons)
	{
		balloon->render();
	}
}

// Crea un globo nuevo en el vector de globos
std::shared_ptr<Balloon> Game::createBalloon(float x, int y, int kind, float velx, float speed, int creationtimer)
{
	const auto index = (kind - 1) % 4;
	auto b = std::make_shared<Balloon>(x, y, kind, velx, speed, creationtimer, balloonTextures[index], balloonAnimations[index]);
	balloons.push_back(b);
	return b;
}

// Crea una PowerBall
void Game::createPowerBall()
{
	constexpr auto values = 6;
	constexpr auto posY = -BLOCK;

	const auto left = param.game.play_area.rect.x;
	const auto center = param.game.play_area.center_x - (BALLOON_WIDTH_4 / 2);
	const auto right = param.game.play_area.rect.w - BALLOON_WIDTH_4;

	const auto vpos = BALLOON_VELX_POSITIVE;
	const auto vneg = BALLOON_VELX_NEGATIVE;

	const auto luck = rand() % values;
	const int x[values] = {left, left, center, center, right, right};
	const float vx[values] = {vpos, vpos, vpos, vneg, vneg, vneg};

	auto b = std::make_unique<Balloon>(x[luck], posY, POWER_BALL, vx[luck], enemySpeed, 300, balloonTextures[4], balloonAnimations[4]);
	balloons.push_back(std::move(b));

	powerBallEnabled = true;
	powerBallCounter = POWERBALL_COUNTER;
}

// Establece la velocidad de los globos
void Game::setBalloonSpeed(float speed)
{
	for (auto &balloon : balloons)
	{
		if (balloon->isEnabled())
		{
			balloon->setSpeed(speed);
		}
	}
}

// Incrementa la velocidad de los globos
void Game::incBalloonSpeed()
{
	// La velocidad solo se incrementa en el modo normal
	if (difficulty == GameDifficulty::NORMAL)
	{
		if (enemySpeed == BALLOON_SPEED_1)
		{
			enemySpeed = BALLOON_SPEED_2;
		}

		else if (enemySpeed == BALLOON_SPEED_2)
		{
			enemySpeed = BALLOON_SPEED_3;
		}

		else if (enemySpeed == BALLOON_SPEED_3)
		{
			enemySpeed = BALLOON_SPEED_4;
		}

		else if (enemySpeed == BALLOON_SPEED_4)
		{
			enemySpeed = BALLOON_SPEED_5;
		}

		setBalloonSpeed(enemySpeed);
	}
}

// Decrementa la velocidad de los globos
void Game::decBalloonSpeed()
{
	// La velocidad solo se decrementa en el modo normal
	if (difficulty == GameDifficulty::NORMAL)
	{
		if (enemySpeed == BALLOON_SPEED_5)
		{
			enemySpeed = BALLOON_SPEED_4;
		}

		else if (enemySpeed == BALLOON_SPEED_4)
		{
			enemySpeed = BALLOON_SPEED_3;
		}

		else if (enemySpeed == BALLOON_SPEED_3)
		{
			enemySpeed = BALLOON_SPEED_2;
		}

		else if (enemySpeed == BALLOON_SPEED_2)
		{
			enemySpeed = BALLOON_SPEED_1;
		}

		setBalloonSpeed(enemySpeed);
	}
}

// Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
void Game::updateBalloonSpeed()
{
	const float percent = (float)currentPower / (float)enemyFormations->getStage(currentStage).powerToComplete;
	if (enemySpeed == BALLOON_SPEED_1)
	{
		if (percent > 0.2f)
		{
			incBalloonSpeed();
		}
	}

	else if (enemySpeed == BALLOON_SPEED_2)
	{
		if (percent > 0.4f)
		{
			incBalloonSpeed();
		}
	}

	else if (enemySpeed == BALLOON_SPEED_3)
	{
		if (percent > 0.6f)
		{
			incBalloonSpeed();
		}
	}

	else if (enemySpeed == BALLOON_SPEED_4)
	{
		if (percent > 0.8f)
		{
			incBalloonSpeed();
		}
	}
}

// Explosiona un globo. Lo destruye y crea otros dos si es el caso
void Game::popBalloon(std::shared_ptr<Balloon> balloon)
{
	// Aumenta el poder de la fase
	increaseStageCurrentPower(1);
	balloonsPopped++;

	const auto kind = balloon->getKind();
	if (kind == POWER_BALL)
	{
		destroyAllBalloons();
		powerBallEnabled = false;
		enemyDeployCounter = 20;
	}
	else
	{
		const auto size = balloon->getSize();
		if (size == BALLOON_SIZE_1)
		{ // Si es del tipo más pequeño, simplemente elimina el globo
			explosions->add(balloon->getPosX(), balloon->getPosY(), size);
			balloon->pop();
		}
		else
		{ // En cualquier otro caso, crea dos globos de un tipo inferior
			auto bLeft = createBalloon(0, balloon->getPosY(), balloon->getKind() - 1, BALLOON_VELX_NEGATIVE, enemySpeed, 0);
			bLeft->allignTo(balloon->getPosX() + (balloon->getWidth() / 2));
			bLeft->setVelY(bLeft->getClass() == BALLOON_CLASS ? -2.50f : BALLOON_VELX_NEGATIVE);

			auto bRight = createBalloon(0, balloon->getPosY(), balloon->getKind() - 1, BALLOON_VELX_POSITIVE, enemySpeed, 0);
			bRight->allignTo(balloon->getPosX() + (balloon->getWidth() / 2));
			bRight->setVelY(bRight->getClass() == BALLOON_CLASS ? -2.50f : BALLOON_VELX_NEGATIVE);

			// Elimina el globo
			explosions->add(balloon->getPosX(), balloon->getPosY(), size);
			balloon->pop();
		}
	}

	// Recalcula el nivel de amenaza
	evaluateAndSetMenace();
}

// Explosiona un globo. Lo destruye = no crea otros globos
void Game::destroyBalloon(std::shared_ptr<Balloon> &balloon)
{
	auto score = 0;

	// Calcula la puntuación y el poder que generaria el globo en caso de romperlo a él y a sus hijos
	const auto size = balloon->getSize();
	switch (size)
	{
	case BALLOON_SIZE_4:
	{
		score = BALLOON_SCORE_4 + (2 * BALLOON_SCORE_3) + (4 * BALLOON_SCORE_2) + (8 * BALLOON_SCORE_1);
		break;
	}

	case BALLOON_SIZE_3:
	{
		score = BALLOON_SCORE_3 + (2 * BALLOON_SCORE_2) + (4 * BALLOON_SCORE_1);
		break;
	}

	case BALLOON_SIZE_2:
	{
		score = BALLOON_SCORE_2 + (2 * BALLOON_SCORE_1);
		break;
	}

	case BALLOON_SIZE_1:
	{
		score = BALLOON_SCORE_1;
		break;
	}

	default:
	{
		score = 0;
		break;
	}
	}

	// Otorga los puntos correspondientes al globo
	for (auto &player : players)
	{
		player->addScore(score * player->getScoreMultiplier() * difficultyScoreMultiplier);
	}
	updateHiScore();

	// Aumenta el poder de la fase
	const auto power = balloon->getPower();
	increaseStageCurrentPower(power);
	balloonsPopped += power;

	// Destruye el globo
	explosions->add(balloon->getPosX(), balloon->getPosY(), size);
	balloon->pop();

	// Recalcula el nivel de amenaza
	evaluateAndSetMenace();
}

// Explosiona todos los globos
void Game::popAllBalloons()
{
	for (auto &balloon : balloons)
	{
		if (balloon->canBePopped())
		{
			popBalloon(balloon);
		}
	}

	JA_PlaySound(balloonSound);
}

// Destruye todos los globos
void Game::destroyAllBalloons()
{
	for (auto &balloon : balloons)
	{
		if (balloon->canBeDestroyed())
		{
			destroyBalloon(balloon);
		}
	}

	enemyDeployCounter = 300;
	JA_PlaySound(powerBallSound);
	screen->flash(flash_color, 5);
	screen->shake();
}

// Detiene todos los globos
void Game::stopAllBalloons(int time)
{
	for (auto &balloon : balloons)
	{
		if (balloon->isEnabled())
		{
			balloon->setStop(true);
			balloon->setStoppedTimer(time);
		}
	}
}

// Pone en marcha todos los globos
void Game::startAllBalloons()
{
	for (auto &balloon : balloons)
	{
		if ((balloon->isEnabled()) && (!balloon->isBeingCreated()))
		{
			balloon->setStop(false);
			balloon->setStoppedTimer(0);
		}
	}
}

// Obtiene el número de globos activos
int Game::countBalloons()
{
	auto num = 0;

	for (auto &balloon : balloons)
	{
		if (balloon->isEnabled())
		{
			num++;
		}
	}

	return num;
}

// Vacia del vector de globos los globos que ya no sirven
void Game::freeBalloons()
{
	auto it = std::remove_if(balloons.begin(), balloons.end(),
							 [](const auto &balloon)
							 { return !balloon->isEnabled(); });
	balloons.erase(it, balloons.end());
}

// Comprueba la colisión entre el jugador y los globos activos
bool Game::checkPlayerBalloonCollision(std::shared_ptr<Player> &player)
{
	for (auto &balloon : balloons)
	{
		if ((balloon->isEnabled()) && !(balloon->isStopped()) && !(balloon->isInvulnerable()))
		{
			if (checkCollision(player->getCollider(), balloon->getCollider()))
			{
				return true;
			}
		}
	}

	return false;
}

// Comprueba la colisión entre el jugador y los items
void Game::checkPlayerItemCollision(std::shared_ptr<Player> &player)
{
	if (!player->isPlaying())
	{
		return;
	}

	for (auto &item : items)
	{
		if (item->isEnabled())
		{
			if (checkCollision(player->getCollider(), item->getCollider()))
			{
				switch (item->getClass())
				{
				case ITEM_POINTS_1_DISK:
				{
					player->addScore(1000);
					createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (n1000Sprite->getWidth() / 2), player->getPosY(), n1000Sprite);
					break;
				}

				case ITEM_POINTS_2_GAVINA:
				{
					player->addScore(2500);
					createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (n2500Sprite->getWidth() / 2), player->getPosY(), n2500Sprite);
					break;
				}

				case ITEM_POINTS_3_PACMAR:
				{
					player->addScore(5000);
					createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (n5000Sprite->getWidth() / 2), player->getPosY(), n5000Sprite);
					break;
				}

				case ITEM_CLOCK:
				{
					enableTimeStopItem();
					break;
				}

				case ITEM_COFFEE:
				{
					if (player->getCoffees() == 2)
					{
						player->addScore(5000);
						createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (n5000Sprite->getWidth() / 2), player->getPosY(), n5000Sprite);
					}
					player->giveExtraHit();
					break;
				}

				case ITEM_COFFEE_MACHINE:
				{
					player->setPowerUp();
					coffeeMachineEnabled = false;
					break;
				}

				default:
					break;
				}

				updateHiScore();
				JA_PlaySound(itemPickUpSound);
				item->disable();
			}
		}
	}
}

// Comprueba y procesa la colisión entre las balas y los globos
void Game::checkBulletBalloonCollision()
{
	for (auto &bullet : bullets)
	{
		for (auto &balloon : balloons)
		{
			if (balloon->isEnabled() && (!balloon->isInvulnerable()) && bullet->isEnabled())
			{
				if (checkCollision(balloon->getCollider(), bullet->getCollider()))
				{
					// Otorga los puntos correspondientes al globo al jugador que disparó la bala
					auto player = getPlayer(bullet->getOwner());
					if (!player)
					{
						return;
					}
					player->incScoreMultiplier();
					player->addScore(balloon->getScore() * player->getScoreMultiplier() * difficultyScoreMultiplier);
					updateHiScore();

					// Suelta el item si se da el caso
					const auto droppeditem = dropItem();
					if (droppeditem != ITEM_NULL && !demo.recording)
					{
						if (droppeditem != ITEM_COFFEE_MACHINE)
						{
							createItem(droppeditem, balloon->getPosX(), balloon->getPosY());
							JA_PlaySound(itemDropSound);
						}
						else
						{
							createItem(droppeditem, player->getPosX(), 0);
							coffeeMachineEnabled = true;
						}
					}

					// Explota el globo
					popBalloon(balloon);

					// Sonido de explosión
					JA_PlaySound(balloonSound);

					// Deshabilita la bala
					bullet->disable();

					break;
				}
			}
		}
	}
}

// Mueve las balas activas
void Game::moveBullets()
{
	for (auto &bullet : bullets)
	{
		if (bullet->isEnabled())
		{
			if (bullet->move() == BulletMoveStatus::OUT)
			{
				getPlayer(bullet->getOwner())->decScoreMultiplier();
			}
		}
	}
}

// Pinta las balas activas
void Game::renderBullets()
{
	for (auto &bullet : bullets)
	{
		if (bullet->isEnabled())
		{
			bullet->render();
		}
	}
}

// Crea un objeto bala
void Game::createBullet(int x, int y, BulletType kind, bool poweredUp, int owner)
{
	auto b = std::make_unique<Bullet>(x, y, kind, poweredUp, owner, &(param.game.play_area.rect), bulletTexture);
	bullets.push_back(std::move(b));
}

// Vacia el vector de balas
void Game::freeBullets()
{
	if (!bullets.empty())
	{
		for (int i = bullets.size() - 1; i >= 0; --i)
		{
			if (!bullets[i]->isEnabled())
			{
				bullets.erase(bullets.begin() + i);
			}
		}
	}
}

// Actualiza los items
void Game::updateItems()
{
	for (auto &item : items)
	{
		if (item->isEnabled())
		{
			item->update();
			if (item->isOnFloor())
			{
				JA_PlaySound(coffeeMachineSound);
				screen->shake();
			}
		}
	}
}

// Pinta los items activos
void Game::renderItems()
{
	for (auto &item : items)
	{
		item->render();
	}
}

// Devuelve un item al azar y luego segun sus probabilidades
int Game::dropItem()
{
	const auto luckyNumber = rand() % 100;
	const auto item = rand() % 6;

	switch (item)
	{
	case 0:
		if (luckyNumber < helper.itemPoints1Odds)
		{
			return ITEM_POINTS_1_DISK;
		}
		break;

	case 1:
		if (luckyNumber < helper.itemPoints2Odds)
		{
			return ITEM_POINTS_2_GAVINA;
		}
		break;

	case 2:
		if (luckyNumber < helper.itemPoints3Odds)
		{
			return ITEM_POINTS_3_PACMAR;
		}
		break;

	case 3:
		if (luckyNumber < helper.itemClockOdds)
		{
			return ITEM_CLOCK;
		}
		break;

	case 4:
		if (luckyNumber < helper.itemCoffeeOdds)
		{
			helper.itemCoffeeOdds = ITEM_COFFEE_ODDS;
			return ITEM_COFFEE;
		}
		else
		{
			if (helper.needCoffee)
			{
				helper.itemCoffeeOdds++;
			}
		}
		break;

	case 5:
		if (luckyNumber < helper.itemCoffeeMachineOdds)
		{
			helper.itemCoffeeMachineOdds = ITEM_COFFEE_MACHINE_ODDS;
			if (!coffeeMachineEnabled && helper.needCoffeeMachine)
			{
				return ITEM_COFFEE_MACHINE;
			}
		}
		else
		{
			if (helper.needCoffeeMachine)
			{
				helper.itemCoffeeMachineOdds++;
			}
		}
		break;

	default:
		break;
	}

	return ITEM_NULL;
}

// Crea un objeto item
void Game::createItem(int kind, float x, float y)
{
	auto item = std::make_unique<Item>(kind, x, y, &(param.game.play_area.rect), itemTextures[kind - 1], itemAnimations[kind - 1]);
	items.push_back(std::move(item));
}

// Vacia el vector de items
void Game::freeItems()
{
	if (!items.empty())
	{
		for (int i = items.size() - 1; i >= 0; --i)
		{
			if (!items[i]->isEnabled())
			{
				items.erase(items.begin() + i);
			}
		}
	}
}

// Crea un objeto SmartSprite para mostrar la puntuación al coger un objeto
void Game::createItemScoreSprite(int x, int y, std::shared_ptr<SmartSprite> sprite)
{
	auto ss = new SmartSprite(nullptr);
	smartSprites.push_back(ss);

	// Crea una copia del objeto
	*ss = *sprite;
	ss->setPosX(x);
	ss->setPosY(y);
	ss->setDestX(x);
	ss->setDestY(y - 25);
	ss->setEnabled(true);
	ss->setFinishedCounter(100);
}

// Vacia el vector de smartsprites
void Game::freeSmartSprites()
{
	if (!smartSprites.empty())
	{
		for (int i = smartSprites.size() - 1; i >= 0; --i)
		{
			if (smartSprites[i]->hasFinished())
			{
				smartSprites.erase(smartSprites.begin() + i);
			}
		}
	}
}

// Crea un SmartSprite para arrojar el item café al recibir un impacto
void Game::throwCoffee(int x, int y)
{
	auto ss = new SmartSprite(itemTextures[4]);
	smartSprites.push_back(ss);

	ss->setPosX(x - 8);
	ss->setPosY(y - 8);
	ss->setWidth(param.game.item_size);
	ss->setHeight(param.game.item_size);
	ss->setVelX(-1.0f + ((rand() % 5) * 0.5f));
	ss->setVelY(-4.0f);
	ss->setAccelX(0.0f);
	ss->setAccelY(0.2f);
	ss->setDestX(x + (ss->getVelX() * 50));
	ss->setDestY(param.game.height + 1);
	ss->setEnabled(true);
	ss->setFinishedCounter(1);
	ss->setSpriteClip(0, param.game.item_size, param.game.item_size, param.game.item_size);
	ss->setRotate(true);
	ss->setRotateSpeed(10);
	ss->setRotateAmount(90.0);
}

// Actualiza los SmartSprites
void Game::updateSmartSprites()
{
	for (auto ss : smartSprites)
	{
		ss->update();
	}
}

// Pinta los SmartSprites activos
void Game::renderSmartSprites()
{
	for (auto ss : smartSprites)
	{
		ss->render();
	}
}

// Acciones a realizar cuando el jugador muere
void Game::killPlayer(std::shared_ptr<Player> &player)
{
	if (!player->isPlaying() || player->isInvulnerable())
	{ // Si no está jugando o tiene inmunidad, no hace nada
		return;
	}

	// Si tiene cafes
	if (player->hasExtraHit())
	{ // Lo pierde
		player->removeExtraHit();
		throwCoffee(player->getPosX() + (player->getWidth() / 2), player->getPosY() + (player->getHeight() / 2));
		JA_PlaySound(coffeeOutSound);
		screen->shake();
	}
	else
	{ // Si no tiene cafes, muere
		if (!demo.enabled)
		{
			JA_PauseMusic();
		}
		stopAllBalloons(10);
		JA_PlaySound(playerCollisionSound);
		screen->shake();
		JA_PlaySound(coffeeOutSound);
		player->setStatusPlaying(PlayerStatus::DYING);
		if (!demo.enabled)
		{ // En el modo DEMO ni se para la musica ni se añade la puntuación a la tabla
			allPlayersAreNotPlaying() ? JA_StopMusic() : JA_ResumeMusic();
		}
	}
}

// Calcula y establece el valor de amenaza en funcion de los globos activos
void Game::evaluateAndSetMenace()
{
	menaceCurrent = 0;
	for (auto &balloon : balloons)
	{
		if (balloon->isEnabled())
		{
			menaceCurrent += balloon->getMenace();
		}
	}
}

// Obtiene el valor de la variable
int Game::getMenace() const
{
	return menaceCurrent;
}

// Establece el valor de la variable
void Game::setTimeStopped(bool value)
{
	timeStopped = value;
}

// Obtiene el valor de la variable
bool Game::isTimeStopped() const
{
	return timeStopped;
}

// Establece el valor de la variable
void Game::setTimeStoppedCounter(int value)
{
	timeStoppedCounter = value;
}

// Incrementa el valor de la variable
void Game::incTimeStoppedCounter(int value)
{
	timeStoppedCounter += value;
}

// Actualiza y comprueba el valor de la variable
void Game::updateTimeStoppedCounter()
{
	if (isTimeStopped())
	{
		if (timeStoppedCounter > 0)
		{
			timeStoppedCounter--;
			stopAllBalloons(TIME_STOPPED_COUNTER);
		}
		else
		{
			disableTimeStopItem();
		}
	}
}

// Actualiza la variable enemyDeployCounter
void Game::updateEnemyDeployCounter()
{
	if (enemyDeployCounter > 0)
	{
		enemyDeployCounter--;
	}
}

// Actualiza el juego
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();

		// Actualiza el contador de juego
		counter++;

		if (demo.enabled)
		{
			// Incrementa el contador de la demo
			if (demo.counter < TOTAL_DEMO_DATA)
			{
				demo.counter++;
			}

			// Activa el fundido antes de acabar con los datos de la demo
			if (demo.counter == TOTAL_DEMO_DATA - 200)
			{
				fade->setType(FadeType::RANDOM_SQUARE);
				fade->activate();
			}

			// Si ha terminado el fundido, cambia de sección
			if (fade->hasEnded())
			{
				section::name = section::NAME_HI_SCORE_TABLE;
				return;
			}
		}

#ifdef RECORDING
		// Solo mira y guarda el input en cada update
		checkInput();

		// Incrementa el contador de la demo
		if (demo.counter < TOTAL_DEMO_DATA)
		{
			demo.counter++;
		}

		// Si se ha llenado el vector con datos, sale del programa
		else
		{
			section::name = section::NAME_QUIT;
			return;
		}
#endif
#ifdef DEBUG
		if (autoPopBalloons && !gameCompleted)
		{
			balloonsPopped++;
			increaseStageCurrentPower(1);
		}
#endif
		if (!paused)
		{
			// Actualiza el objeto fade
			fade->update();

			// Actualiza las variables del jugador
			updatePlayers();

			// Actualiza el marcador
			checkPlayersStatusPlaying();
			updateScoreboard();

			// Actualiza el fondo
			updateBackground();

			// Mueve los globos
			updateBalloons();

			// Actualiza el objeto encargado de las explosiones
			explosions->update();

			// Mueve las balas
			moveBullets();

			// Actualiza los items
			updateItems();

			// Comprueba si hay cambio de fase y actualiza las variables
			updateStage();

			// Actualiza el estado de muerte
			updateGameOver();

			// Actualiza los SmartSprites
			updateSmartSprites();

			// Actualiza los contadores de estado y efectos
			updateTimeStoppedCounter();
			updateEnemyDeployCounter();

			// Actualiza el ayudante
			updateHelper();

			// Comprueba las colisiones entre globos y balas
			checkBulletBalloonCollision();

			// Comprueba el nivel de amenaza para ver si se han de crear nuevos enemigos
			updateMenace();

			// Actualiza la velocidad de los enemigos
			updateBalloonSpeed();

			// Actualiza el tramo final de juego, una vez completado
			updateGameCompleted();

			// Vacia los vectores
			freeBullets();
			freeBalloons();
			freeItems();
			freeSmartSprites();
		}

		// Comprueba si la música ha de estar sonando
		checkMusicStatus();

		// Actualiza el objeto screen
		screen->update();

		// Dibuja los graficos de la zona de juego en la textura
		fillCanvas();
	}
}

// Actualiza el fondo
void Game::updateBackground()
{
	// Si el juego está completado, se reduce la velocidad de las nubes
	if (gameCompleted)
	{
		balloonsPopped = (balloonsPopped > 400) ? (balloonsPopped - 25) : 200;
	}

	// Calcula la velocidad en función de los globos explotados y el total de globos a explotar para acabar el juego
	constexpr auto cloudsInitialSpeed = 0.05f;
	constexpr auto cloudsFinalSpeed = 2.00f - cloudsInitialSpeed;
	const float cloudsSpeed = (-cloudsInitialSpeed) + (-cloudsFinalSpeed * ((float)balloonsPopped / (float)totalPowerToCompleteGame));
	background->setCloudsSpeed(cloudsSpeed);

	// Calcula la transición de los diferentes fondos
	const float gradientNumber = std::min(((float)balloonsPopped / 1250.0f), 3.0f);
	const float percent = gradientNumber - (int)gradientNumber;
	background->setGradientNumber((int)gradientNumber);
	background->setTransition(percent);

	// Actualiza el objeto
	background->update();
}

// Dibuja los elementos de la zona de juego en su textura
void Game::fillCanvas()
{
	// Dibujamos el contenido de la zona de juego en su textura
	auto temp = SDL_GetRenderTarget(renderer);
	SDL_SetRenderTarget(renderer, canvas);

	// Dibuja los objetos
	background->render();
	renderItems();
	renderSmartSprites();
	explosions->render();
	renderBalloons();
	renderBullets();
	renderMessages();
	renderPlayers();

	// Deja el renderizador apuntando donde estaba
	SDL_SetRenderTarget(renderer, temp);
}

// Dibuja el juego
void Game::render()
{
	// Prepara para empezar a dibujar en la textura de juego
	screen->start();

	// Copia la textura con la zona de juego a la pantalla
	SDL_RenderCopy(renderer, canvas, nullptr, &param.game.play_area.rect);

	// Dibuja el marcador
	scoreboard->render();

	// Dibuja el fade
	fade->render();

	// Vuelca el contenido del renderizador en pantalla
	screen->blit();
}

// Gestiona el nivel de amenaza
void Game::updateMenace()
{
	if (gameCompleted)
	{
		return;
	}

	const auto stage = enemyFormations->getStage(currentStage);
	const float percent = currentPower / stage.powerToComplete;
	const int difference = stage.maxMenace - stage.minMenace;

	// Aumenta el nivel de amenaza en función de la puntuación
	menaceThreshold = stage.minMenace + (difference * percent);

	// Si el nivel de amenza es inferior al umbral
	if (menaceCurrent < menaceThreshold)
	{
		// Crea una formación de enemigos
		deployEnemyFormation();

		// Recalcula el nivel de amenaza con el nuevo globo
		evaluateAndSetMenace();
	}
}

// Gestiona la entrada durante el juego
void Game::checkInput()
{
	// Comprueba si se pulsa el botón de pausa
	for (int i = 0; i < input->getNumControllers(); ++i)
	{
		// Comprueba si se va a pausar el juego
		if (input->checkModInput(input_service, input_pause, INPUT_DO_NOT_ALLOW_REPEAT, INPUT_USE_GAMECONTROLLER, i))
		{
			pause(!paused);
			return;
		}
	}

	// Modo Demo activo
	if (demo.enabled)
	{
		auto i = 0;
		for (auto &player : players)
		{
			if (player->isPlaying())
			{
				// Comprueba direcciones
				if (demo.dataFile[i][demo.counter].left == 1)
				{
					player->setInput(input_left);
				}

				else if (demo.dataFile[i][demo.counter].right == 1)
				{
					player->setInput(input_right);
				}

				else if (demo.dataFile[i][demo.counter].no_input == 1)
				{
					player->setInput(input_null);
				}

				// Comprueba botones
				if (demo.dataFile[i][demo.counter].fire == 1)
				{
					if (player->canFire())
					{
						player->setInput(input_fire_center);
						createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::UP, player->isPowerUp(), player->getId());
						player->setFireCooldown(10);
					}
				}

				else if (demo.dataFile[i][demo.counter].fire_left == 1)
				{
					if (player->canFire())
					{
						player->setInput(input_fire_left);
						createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::LEFT, player->isPowerUp(), player->getId());
						player->setFireCooldown(10);
					}
				}

				else if (demo.dataFile[i][demo.counter].fire_right == 1)
				{
					if (player->canFire())
					{
						player->setInput(input_fire_right);
						createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::RIGHT, player->isPowerUp(), player->getId());
						player->setFireCooldown(10);
					}
				}

				// Si se pulsa cualquier tecla, se sale del modo demo
				if (input->checkAnyButtonPressed())
				{
					section::name = section::NAME_TITLE;
					return;
				}
			}

			i++;
		}
	}
	// Modo Demo no activo
	else
	{
#ifdef RECORDING
		// Resetea el teclado
		demo.keys.left = 0;
		demo.keys.right = 0;
		demo.keys.no_input = 0;
		demo.keys.fire = 0;
		demo.keys.fire_left = 0;
		demo.keys.fire_right = 0;
#endif
		for (auto &player : players)
		{
			const auto controllerIndex = player->getController();
			const auto autofire = player->isPowerUp() || options.game.autofire;
			if (player->isPlaying())
			{
				// Input a la izquierda
				if (input->checkInput(input_left, INPUT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
				{
					player->setInput(input_left);
#ifdef RECORDING
					demo.keys.left = 1;
#endif
				}
				else
				{
					// Input a la derecha
					if (input->checkInput(input_right, INPUT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
					{
						player->setInput(input_right);
#ifdef RECORDING
						demo.keys.right = 1;
#endif
					}
					else
					{
						// Ninguno de los dos inputs anteriores
						player->setInput(input_null);
#ifdef RECORDING
						demo.keys.no_input = 1;
#endif
					}
				}
				// Comprueba el input de disparar al centro
				if (input->checkInput(input_fire_center, autofire, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
				{
					if (player->canFire())
					{
						player->setInput(input_fire_center);
						createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::UP, player->isPowerUp(), player->getId());
						player->setFireCooldown(10);

						// Reproduce el sonido de disparo
						JA_PlaySound(bulletSound);
#ifdef RECORDING
						demo.keys.fire = 1;
#endif
					}
				}

				// Comprueba el input de disparar a la izquierda
				else if (input->checkInput(input_fire_left, autofire, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
				{
					if (player->canFire())
					{
						player->setInput(input_fire_left);
						createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::LEFT, player->isPowerUp(), player->getId());
						player->setFireCooldown(10);

						// Reproduce el sonido de disparo
						JA_PlaySound(bulletSound);
#ifdef RECORDING
						demo.keys.fire_left = 1;
#endif
					}
				}

				// Comprueba el input de disparar a la derecha
				else if (input->checkInput(input_fire_right, autofire, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
				{
					if (player->canFire())
					{
						player->setInput(input_fire_right);
						createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::RIGHT, player->isPowerUp(), player->getId());
						player->setFireCooldown(10);

						// Reproduce el sonido de disparo
						JA_PlaySound(bulletSound);
#ifdef RECORDING
						demo.keys.fire_right = 1;
#endif
					}
				}
#ifdef RECORDING
				if (demo.recording)
				{
					if (demo.counter < TOTAL_DEMO_DATA)
					{
						demo.dataFile[0][demo.counter] = demo.keys;
					}
				}
#endif
			}
			else if (player->isContinue() || player->isWaiting())
			{
				// Si no está jugando, el botón de start le permite continuar jugando
				if (input->checkInput(input_start, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
				{
					player->setStatusPlaying(PlayerStatus::PLAYING);
				}

				// Si está continuando, los botones de fuego hacen decrementar el contador
				const auto fire1 = input->checkInput(input_fire_left, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index);
				const auto fire2 = input->checkInput(input_fire_center, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index);
				const auto fire3 = input->checkInput(input_fire_right, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index);
				if (fire1 || fire2 || fire3)
				{
					player->decContinueCounter();
				}
			}
			else if (player->isEnteringName())
			{
				const auto fire1 = input->checkInput(input_fire_left, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index);
				const auto fire2 = input->checkInput(input_fire_center, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index);
				const auto fire3 = input->checkInput(input_fire_right, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index);
				if (fire1 || fire2 || fire3)
				{
					if (player->getRecordNamePos() == 7)
					{
						player->setInput(input_start);
						addScoreToScoreBoard(player->getRecordName(), player->getScore());
						player->setStatusPlaying(PlayerStatus::CONTINUE);
					}
					else
					{
						player->setInput(input_right);
					}
				}
				else if (input->checkInput(input_up, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
				{
					player->setInput(input_up);
				}
				else if (input->checkInput(input_down, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
				{
					player->setInput(input_down);
				}
				else if (input->checkInput(input_left, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
				{
					player->setInput(input_left);
				}
				else if (input->checkInput(input_right, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
				{
					player->setInput(input_right);
				}
				else if (input->checkInput(input_start, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
				{
					player->setInput(input_start);
					addScoreToScoreBoard(player->getRecordName(), player->getScore());
					player->setStatusPlaying(PlayerStatus::CONTINUE);
				}
			}
		}
	}

	// Comprueba el input para el resto de objetos
	screen->checkInput();

	// Comprueba los inputs que se pueden introducir en cualquier sección del juego
	globalInputs::check();
}

// Pinta diferentes mensajes en la pantalla
void Game::renderMessages()
{
	// GetReady
	if (counter < STAGE_COUNTER && !demo.enabled)
	{
		textNokiaBig2->write((int)getReadyBitmapPath[counter], param.game.play_area.center_y - 8, lang::getText(75), -2);
	}

	// Time Stopped
	if (timeStopped)
	{
		if (timeStoppedCounter > 100 || timeStoppedCounter % 10 > 4)
		{
			textNokia2->writeDX(TXT_CENTER, param.game.play_area.center_x, param.game.play_area.first_quarter_y, lang::getText(36) + std::to_string(timeStoppedCounter / 10), -1, no_color, 1, shdw_txt_color);
		}

		if (timeStoppedCounter > 100)
		{
			if (timeStoppedCounter % 30 == 0)
			{
				JA_PlaySound(clockSound);
			}
		}
		else
		{
			if (timeStoppedCounter % 15 == 0)
			{
				JA_PlaySound(clockSound);
			}
		}
	}

	// STAGE NUMBER
	if (stageBitmapCounter < STAGE_COUNTER)
	{
		const auto stageNum = enemyFormations->getStage(currentStage).number;
		std::string text = "";

		if (stageNum == 10)
		{ // Ultima fase
			text = lang::getText(79);
		}
		else
		{ // X fases restantes
			text = std::to_string(11 - stageNum) + lang::getText(38);
		}

		if (!gameCompleted)
		{ // Escribe el número de fases restantes
			textNokiaBig2->writeDX(TXT_CENTER, param.game.play_area.center_x, stageBitmapPath[stageBitmapCounter], text, -2, no_color, 2, shdw_txt_color);
		}
		else
		{ // Escribe el texto de juego completado
			text = lang::getText(50);
			textNokiaBig2->writeDX(TXT_CENTER, param.game.play_area.center_x, stageBitmapPath[stageBitmapCounter], text, -2, no_color, 1, shdw_txt_color);
			textNokia2->writeDX(TXT_CENTER, param.game.play_area.center_x, stageBitmapPath[stageBitmapCounter] + textNokiaBig2->getCharacterSize() + 2, lang::getText(76), -1, no_color, 1, shdw_txt_color);
		}
	}
}

// Habilita el efecto del item de detener el tiempo
void Game::enableTimeStopItem()
{
	stopAllBalloons(TIME_STOPPED_COUNTER);
	setTimeStopped(true);
	incTimeStoppedCounter(TIME_STOPPED_COUNTER);
	if (JA_GetMusicState() == JA_MUSIC_PLAYING && !demo.enabled)
	{
		JA_PauseMusic();
	}
}

// Deshabilita el efecto del item de detener el tiempo
void Game::disableTimeStopItem()
{
	timeStopped = false;
	setTimeStoppedCounter(0);
	startAllBalloons();
	if (JA_GetMusicState() == JA_MUSIC_PAUSED && !demo.enabled)
	{
		JA_ResumeMusic();
	}
}

// Comprueba si la música ha de estar sonando
void Game::checkMusicStatus()
{
	// Si la música no está sonando
	if (JA_GetMusicState() == JA_MUSIC_INVALID || JA_GetMusicState() == JA_MUSIC_STOPPED)
	{
		// Si se ha completado el juego o los jugadores han terminado, detiene la música
		gameCompleted || allPlayersAreGameOver() ? JA_StopMusic() : JA_PlayMusic(music);
	}
}

// Bucle para el juego
void Game::run()
{
	while (section::name == section::NAME_GAME)
	{
#ifndef RECORDING
		checkInput();
#endif
		update();
		checkEvents(); // Tiene que ir antes del render
		render();
	}

	// Vuelve a dejar el sonido como estaba
	(demo.enabled) ? JA_EnableSound(options.audio.sound.enabled) : JA_StopMusic();
}

// Indica si se puede crear una powerball
bool Game::canPowerBallBeCreated()
{
	if ((!powerBallEnabled) && (calculateScreenPower() > POWERBALL_SCREENPOWER_MINIMUM) && (powerBallCounter == 0))
	{
		return true;
	}

	return false;
}

// Calcula el poder actual de los globos en pantalla
int Game::calculateScreenPower()
{
	auto power = 0;

	for (auto &balloon : balloons)
	{
		if (balloon->isEnabled())
		{
			power += balloon->getPower();
		}
	}

	return power;
}

// Inicializa las variables que contienen puntos de ruta para mover objetos
void Game::initPaths()
{
	// Vector con los valores del seno para 360 grados
	float sin[360];
	for (int i = 0; i < 360; ++i)
	{
		sin[i] = SDL_sinf((float)i * 3.14f / 180.0f);
	}

	// Letrero de STAGE #
	constexpr auto firstPart = STAGE_COUNTER / 4; // 50
	constexpr auto secondPart = firstPart * 3;	  // 150
	const auto centerPoint = param.game.play_area.center_y - (BLOCK * 2);
	const auto distance = (param.game.play_area.rect.h) - (param.game.play_area.center_y - 16);

	for (int i = 0; i < STAGE_COUNTER; ++i)
	{
		if (i < firstPart)
		{
			stageBitmapPath[i] = (sin[(int)((i * 1.8f) + 90)] * (distance) + centerPoint);
		}

		else if (i < secondPart)
		{
			stageBitmapPath[i] = (int)centerPoint;
		}

		else
		{
			stageBitmapPath[i] = (sin[(int)(((i - 149) * 1.8f) + 90)] * (centerPoint + 17) - 17);
		}
	}

	// Letrero de GetReady
	const auto size = textNokiaBig2->lenght(lang::getText(75), -2);

	const float start1 = param.game.play_area.rect.x - size;
	const float finish1 = param.game.play_area.center_x - (size / 2);

	const float start2 = finish1;
	const float finish2 = param.game.play_area.rect.w;

	const float distance1 = finish1 - start1;
	const float distance2 = finish2 - start2;

	for (int i = 0; i < STAGE_COUNTER; ++i)
	{
		if (i < firstPart)
		{
			getReadyBitmapPath[i] = sin[(int)(i * 1.8f)];
			getReadyBitmapPath[i] *= distance1;
			getReadyBitmapPath[i] -= size;
		}

		else if (i < secondPart)
		{
			getReadyBitmapPath[i] = (int)finish1;
		}

		else
		{
			getReadyBitmapPath[i] = sin[(int)((i - 150) * 1.8f)];
			getReadyBitmapPath[i] *= distance2;
			getReadyBitmapPath[i] += finish1;
		}
	}
}

// Actualiza el tramo final de juego, una vez completado
void Game::updateGameCompleted()
{
	if (gameCompleted)
	{
		gameCompletedCounter++;
	}

	if (gameCompletedCounter == GAME_COMPLETED_END)
	{
		section::name = section::NAME_TITLE;
		section::options = section::OPTIONS_TITLE_1;
	}
}

// Actualiza las variables de ayuda
void Game::updateHelper()
{
	// Solo ofrece ayuda cuando la amenaza es elevada
	if (menaceCurrent > 15)
	{
		for (auto &player : players)
		{
			helper.needCoffee = (player->getCoffees() == 0);
			helper.needCoffeeMachine = (!player->isPowerUp());
		}
	}
	else
	{
		helper.needCoffee = helper.needCoffeeMachine = false;
	}
}

// Comprueba si todos los jugadores han terminado de jugar
bool Game::allPlayersAreWaitingOrGameOver()
{
	auto success = true;
	for (auto &player : players)
	{
		success &= player->isWaiting() || player->isGameOver();
	}

	return success;
}

// Comprueba si todos los jugadores han terminado de jugar
bool Game::allPlayersAreGameOver()
{
	auto success = true;
	for (auto &player : players)
	{
		success &= player->isGameOver();
	}

	return success;
}

// Comprueba si todos los jugadores han terminado de jugar
bool Game::allPlayersAreNotPlaying()
{
	auto success = true;
	for (auto &player : players)
	{
		success &= !player->isPlaying();
	}

	return success;
}

// Comprueba los eventos que hay en cola
void Game::checkEvents()
{
	SDL_Event event;
	while (SDL_PollEvent(&event))
	{
		// Evento de salida de la aplicación
		if (event.type == SDL_QUIT)
		{
			section::name = section::NAME_QUIT;
			break;
		}

		else if (event.type == SDL_WINDOWEVENT)
		{
			switch (event.window.event)
			{
			case SDL_WINDOWEVENT_FOCUS_LOST:
			{
				if (!demo.enabled)
				{
					pause(true);
				}
				break;
			}

			case SDL_WINDOWEVENT_FOCUS_GAINED:
			{
				pause(false);
				break;
			}

			case SDL_WINDOWEVENT_SIZE_CHANGED:
			{
				reloadTextures();
				break;
			}

			default:
				break;
			}
		}

#ifdef DEBUG
		else if (event.type == SDL_KEYDOWN)
		{
			switch (event.key.keysym.sym)
			{
			// Crea una powerball
			case SDLK_1:
			{
				createPowerBall();
				break;
			}

			// Crea dos BALLON4
			case SDLK_2:
			{
				const auto set = 0;
				const auto stage = enemyFormations->getStage(0);
				const auto numEnemies = stage.enemyPool->set[set]->numberOfEnemies;
				for (int i = 0; i < numEnemies; ++i)
				{
					createBalloon(stage.enemyPool->set[set]->init[i].x,
								  stage.enemyPool->set[set]->init[i].y,
								  stage.enemyPool->set[set]->init[i].kind,
								  stage.enemyPool->set[set]->init[i].velX,
								  enemySpeed,
								  stage.enemyPool->set[set]->init[i].creationCounter);
				}
			}
			break;

			// Activa el modo para pasar el juego automaticamente
			case SDLK_3:
			{
				autoPopBalloons = !autoPopBalloons;
				screen->showNotification("autoPopBalloons " + boolToString(autoPopBalloons));
				break;
			}

			// Ralentiza mucho la lógica
			case SDLK_4:
			{
				ticksSpeed *= 10;
				break;
			}

			// Acelera mucho la lógica
			case SDLK_5:
			{
				ticksSpeed /= 10;
				break;
			}

			default:
				break;
			}
		}
#endif
	}
}

// Carga las animaciones
void Game::loadAnimations(std::string filePath, std::vector<std::string> *buffer)
{
	std::ifstream file(filePath);
	std::string line;

	if (file)
	{

#ifdef VERBOSE
		std::cout << "Animation loaded: " << filePath.substr(filePath.find_last_of("\\/") + 1).c_str() << std::endl;
#endif
		while (std::getline(file, line))
		{
			buffer->push_back(line);
		}
		file.close();
	}
}

// Elimina todos los objetos contenidos en vectores
void Game::deleteAllVectorObjects()
{
	players.clear();
	balloons.clear();
	bullets.clear();
	items.clear();
	smartSprites.clear();
}

// Recarga las texturas
void Game::reloadTextures()
{
	for (auto texture : itemTextures)
	{
		texture->reLoad();
	}

	for (auto texture : balloonTextures)
	{
		texture->reLoad();
	}

	for (auto texture : player1Textures)
	{
		texture->reLoad();
	}

	for (auto texture : player2Textures)
	{
		texture->reLoad();
	}

	bulletTexture->reLoad();
	gameTextTexture->reLoad();
	background->reloadTextures();
}

// Actualiza el marcador
void Game::updateScoreboard()
{
	for (auto &player : players)
	{
		scoreboard->setScore(player->getScoreBoardPanel(), player->getScore());
		scoreboard->setMult(player->getScoreBoardPanel(), player->getScoreMultiplier());
	}

	// Resto de marcador
	scoreboard->setStage(enemyFormations->getStage(currentStage).number);
	scoreboard->setPower((float)currentPower / (float)enemyFormations->getStage(currentStage).powerToComplete);
	scoreboard->setHiScore(hiScore.score);
	scoreboard->setHiScoreName(hiScore.name);

	// Lógica del marcador
	scoreboard->update();
}

// Pausa el juego
void Game::pause(bool value)
{
	paused = value;
	screen->attenuate(paused);
}

// Añade una puntuación a la tabla de records
void Game::addScoreToScoreBoard(std::string name, int score)
{
	const auto entry = (HiScoreEntry){trim(name), score};
	auto manager = std::make_unique<ManageHiScoreTable>(&options.game.hi_score_table);
	manager->add(entry);
	manager->saveToFile(asset->get("score.bin"));
}

// Saca del estado de GAME OVER al jugador si el otro está activo
void Game::checkAndUpdatePlayerStatus(int activePlayerIndex, int inactivePlayerIndex)
{
	if (players[activePlayerIndex]->isGameOver() &&
		!players[inactivePlayerIndex]->isGameOver() &&
		!players[inactivePlayerIndex]->isWaiting())
	{
		players[activePlayerIndex]->setStatusPlaying(PlayerStatus::WAITING);
	}
}

// Comprueba el estado de los jugadores
void Game::checkPlayersStatusPlaying()
{
	if (demo.enabled)
	{
		return;
	}

	// Comprueba si todos los jugadores estan esperando
	if (allPlayersAreWaitingOrGameOver())
	{
		// Entonces los pone en estado de Game Over
		for (auto &player : players)
		{
			player->setStatusPlaying(PlayerStatus::GAME_OVER);
		}
	}

	// Comprobar estado de ambos jugadores
	checkAndUpdatePlayerStatus(0, 1);
	checkAndUpdatePlayerStatus(1, 0);
}

// Obtiene un jugador a partir de su "id"
std::shared_ptr<Player> Game::getPlayer(int id)
{
	for (auto &player : players)
	{
		if (player->getId() == id)
		{
			return player;
		}
	}

	return nullptr;
}

// Obtiene un controlador a partir del "id" del jugador
int Game::getController(int player_id)
{
	for (int i = 0; i < (int)options.controller.size(); ++i)
	{
		if (options.controller[i].player_id == player_id)
		{
			return i;
		}
	}

	return -1;
}