Game.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. /*
  2. * This source file is part of RmlUi, the HTML/CSS Interface Middleware
  3. *
  4. * For the latest information, see http://github.com/mikke89/RmlUi
  5. *
  6. * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
  7. * Copyright (c) 2019 The RmlUi Team, and contributors
  8. *
  9. * Permission is hereby granted, free of charge, to any person obtaining a copy
  10. * of this software and associated documentation files (the "Software"), to deal
  11. * in the Software without restriction, including without limitation the rights
  12. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. * copies of the Software, and to permit persons to whom the Software is
  14. * furnished to do so, subject to the following conditions:
  15. *
  16. * The above copyright notice and this permission notice shall be included in
  17. * all copies or substantial portions of the Software.
  18. *
  19. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. * THE SOFTWARE.
  26. *
  27. */
  28. #include "Game.h"
  29. #include <RmlUi/Core.h>
  30. #include <Shell.h>
  31. #include <ShellOpenGL.h>
  32. #include "Defender.h"
  33. #include "EventManager.h"
  34. #include "GameDetails.h"
  35. #include "HighScores.h"
  36. #include "Invader.h"
  37. #include "Mothership.h"
  38. #include "Shield.h"
  39. const int WINDOW_WIDTH = 1024;
  40. const int WINDOW_HEIGHT = 768;
  41. const int NUM_LIVES = 3;
  42. const int NUM_INVADER_ROWS = 5;
  43. const int NUM_INVADERS_PER_ROW = 11;
  44. const int NUM_INVADERS = (NUM_INVADER_ROWS * NUM_INVADERS_PER_ROW);
  45. const int INVADER_SPACING_X = 64;
  46. const int INVADER_SPACING_Y = 48;
  47. const int INVADER_START_Y = 96;
  48. const float INVADER_MOVEMENT = 10;
  49. const float INVADER_UPDATE_MODIFIER = 0.7f;
  50. const int MOTHERSHIP = NUM_INVADERS;
  51. const int NUM_SHIELD_ARRAYS = 4;
  52. const int NUM_SHIELDS_PER_ARRAY = 10;
  53. const int NUM_SHIELDS = (NUM_SHIELD_ARRAYS * NUM_SHIELDS_PER_ARRAY);
  54. const int SHIELD_SIZE = (NUM_SHIELD_CELLS * PIXEL_SIZE);
  55. const int SHIELD_SPACING_X = 192;
  56. const int SHIELD_START_X = 176;
  57. const int SHIELD_START_Y = 600;
  58. // The game's element context (declared in main.cpp).
  59. extern Rml::Core::Context* context;
  60. Game::Game()
  61. {
  62. initialized = false;
  63. invader_frame_start = 0;
  64. defender_lives = 3;
  65. current_invader_direction = 1.0f;
  66. invaders = new Invader*[NUM_INVADERS + 1];
  67. for (int i = 0; i < NUM_INVADERS + 1; i++)
  68. invaders[i] = nullptr;
  69. shields = new Shield*[NUM_SHIELDS];
  70. for (int i = 0; i < NUM_SHIELDS; i++)
  71. shields[i] = nullptr;
  72. // Use the OpenGL render interface to load our texture.
  73. Rml::Core::Vector2i texture_dimensions;
  74. Rml::Core::GetRenderInterface()->LoadTexture(texture, texture_dimensions, "invaders/data/invaders.tga");
  75. defender = new Defender(this);
  76. }
  77. Game::~Game()
  78. {
  79. delete defender;
  80. for (int i = 0; i < NUM_INVADERS + 1; i++)
  81. delete invaders[i];
  82. delete [] invaders;
  83. for (int i = 0; i < NUM_SHIELDS; i++)
  84. delete shields[i];
  85. delete [] shields;
  86. }
  87. void Game::Initialise()
  88. {
  89. // Initialise the scores and wave information
  90. SetScore(0);
  91. SetWave(1);
  92. SetHighScore(HighScores::GetHighScore());
  93. SetLives(NUM_LIVES);
  94. // Initialise the shields.
  95. InitialiseShields();
  96. // Create a new wave
  97. InitialiseWave();
  98. initialized = true;
  99. }
  100. void Game::Update()
  101. {
  102. if (!GameDetails::GetPaused() && initialized)
  103. {
  104. if (defender_lives <= 0)
  105. return;
  106. // Determine if we should advance the invaders
  107. if (Shell::GetElapsedTime() - invader_frame_start >= invader_move_freq)
  108. {
  109. MoveInvaders();
  110. invader_frame_start = Shell::GetElapsedTime();
  111. }
  112. // Update all invaders
  113. for (int i = 0; i < NUM_INVADERS + 1; i++)
  114. invaders[i]->Update();
  115. defender->Update();
  116. }
  117. }
  118. void Game::Render()
  119. {
  120. if (defender_lives <= 0)
  121. return;
  122. // Render all available shields
  123. for (int i = 0; i < NUM_SHIELDS; i++)
  124. {
  125. shields[i]->Render();
  126. }
  127. glEnable(GL_TEXTURE_2D);
  128. glBindTexture(GL_TEXTURE_2D, (GLuint) texture);
  129. glColor4ub(255, 255, 255, 255);
  130. glBegin(GL_QUADS);
  131. // Render all available invaders
  132. for (int i = 0; i < NUM_INVADERS + 1; i++)
  133. {
  134. invaders[i]->Render();
  135. }
  136. defender->Render();
  137. glEnd();
  138. }
  139. Defender* Game::GetDefender()
  140. {
  141. return defender;
  142. }
  143. Invader* Game::GetInvader(int index)
  144. {
  145. return invaders[index];
  146. }
  147. int Game::GetNumInvaders()
  148. {
  149. return NUM_INVADERS + 1;
  150. }
  151. bool Game::CanDropBomb(int index)
  152. {
  153. // Determines if the given invader can drop a bomb by checking if theres any other invaders under it
  154. int y = (index / NUM_INVADERS_PER_ROW);
  155. int x = index - (y * NUM_INVADERS_PER_ROW);
  156. // The mothership can never drop bombs
  157. if (index == MOTHERSHIP)
  158. return false;
  159. // Check each row under the given invader to see if there is space
  160. for (int i = y + 1; i < NUM_INVADER_ROWS; i++)
  161. {
  162. if (invaders[(i * NUM_INVADERS_PER_ROW) + x]->GetState() == Invader::ALIVE)
  163. return false;
  164. }
  165. return true;
  166. }
  167. // Access the shields.
  168. Shield* Game::GetShield(int index)
  169. {
  170. return shields[index];
  171. }
  172. // Get the total number of shields
  173. int Game::GetNumShields()
  174. {
  175. return NUM_SHIELDS;
  176. }
  177. // Adds a score onto the player's score.
  178. void Game::AddScore(int score)
  179. {
  180. SetScore(GameDetails::GetScore() + score);
  181. }
  182. // Sets the player's score.
  183. void Game::SetScore(int score)
  184. {
  185. GameDetails::SetScore(score);
  186. Rml::Core::Element* score_element = context->GetDocument("game_window")->GetElementById("score");
  187. if (score_element != nullptr)
  188. score_element->SetInnerRML(Rml::Core::CreateString(128, "%d", score).c_str());
  189. // Update the high score if we've beaten it.
  190. if (score > HighScores::GetHighScore())
  191. SetHighScore(score);
  192. }
  193. // Sets the player's high-score.
  194. void Game::SetHighScore(int score)
  195. {
  196. Rml::Core::Element* high_score_element = context->GetDocument("game_window")->GetElementById("hiscore");
  197. if (high_score_element != nullptr)
  198. high_score_element->SetInnerRML(Rml::Core::CreateString(128, "%d", score).c_str());
  199. }
  200. void Game::SetLives(int lives)
  201. {
  202. defender_lives = lives;
  203. Rml::Core::Element* score_element = context->GetDocument("game_window")->GetElementById("lives");
  204. if (score_element != nullptr)
  205. score_element->SetInnerRML(Rml::Core::CreateString(128, "%d", defender_lives).c_str());
  206. }
  207. void Game::SetWave(int wave)
  208. {
  209. GameDetails::SetWave(wave);
  210. Rml::Core::Element* waves_element = context->GetDocument("game_window")->GetElementById("waves");
  211. if (waves_element != nullptr)
  212. waves_element->SetInnerRML(Rml::Core::CreateString(128, "%d", wave).c_str());
  213. }
  214. void Game::RemoveLife()
  215. {
  216. if (defender_lives > 0)
  217. {
  218. SetLives(defender_lives - 1);
  219. if (defender_lives == 0)
  220. {
  221. OnGameOver();
  222. }
  223. }
  224. }
  225. const Rml::Core::Vector2f Game::GetWindowDimensions()
  226. {
  227. return Rml::Core::Vector2f((float) WINDOW_WIDTH, (float) WINDOW_HEIGHT);
  228. }
  229. void Game::MoveInvaders()
  230. {
  231. Rml::Core::Vector2f new_positions[NUM_INVADERS];
  232. // We loop through all invaders, calculating their new positions, if any of them go over the screen bounds,
  233. // then we switch direction, move the invaders down and start at 0 again
  234. for (int i = 0; i < NUM_INVADERS; i++)
  235. {
  236. if (invaders[i]->GetState() == Invader::DEAD)
  237. continue;
  238. new_positions[i] = invaders[i]->GetPosition();
  239. new_positions[i].x += INVADER_MOVEMENT * current_invader_direction;
  240. if (new_positions[i].x < 0 || new_positions[i].x + INVADER_SPACING_X > WINDOW_WIDTH)
  241. {
  242. // Switch direction and start back at 0 (-1 as the for loop increments)
  243. current_invader_direction *= -1.0f;
  244. i = -1;
  245. // Move all invaders down
  246. for (int j = 0; j < NUM_INVADERS; j++)
  247. {
  248. if (invaders[j]->GetState() == Invader::DEAD)
  249. continue;
  250. Rml::Core::Vector2f position = invaders[j]->GetPosition();
  251. position.y += INVADER_SPACING_Y;
  252. invaders[j]->SetPosition(position);
  253. }
  254. // Increase speed of invaders
  255. invader_move_freq *= INVADER_UPDATE_MODIFIER;
  256. }
  257. }
  258. // Assign invaders their new position and advance their animation frame
  259. bool invaders_alive = false;
  260. for (int i = 0; i < NUM_INVADERS; i++)
  261. {
  262. if (invaders[i]->GetState() == Invader::DEAD)
  263. continue;
  264. invaders[i]->SetPosition(new_positions[i]);
  265. invaders[i]->UpdateAnimation();
  266. // If an invader hits the bottom, instant death
  267. if (new_positions[i].y >= GetWindowDimensions().y - INVADER_SPACING_Y)
  268. {
  269. OnGameOver();
  270. return;
  271. }
  272. invaders_alive = true;
  273. }
  274. if (!invaders_alive)
  275. {
  276. SetWave(GameDetails::GetWave() + 1);
  277. InitialiseWave();
  278. }
  279. }
  280. void Game::OnGameOver()
  281. {
  282. // Set lives to zero and continue to the high scores
  283. defender_lives = 0;
  284. EventManager::LoadWindow("high_score");
  285. }
  286. void Game::InitialiseShields()
  287. {
  288. Rml::Core::Vector2f shield_array_start_position((float) SHIELD_START_X, (float) SHIELD_START_Y);
  289. for (int x = 0; x < NUM_SHIELD_ARRAYS; x++)
  290. {
  291. // Top row (row of 4)
  292. for (int i = 0; i < 4; i++)
  293. {
  294. Shield::ShieldType type = Shield::REGULAR;
  295. if (i == 0)
  296. type = Shield::TOP_LEFT;
  297. else if (i == 3)
  298. {
  299. type = Shield::TOP_RIGHT;
  300. }
  301. shields[(x * NUM_SHIELDS_PER_ARRAY) + i] = new Shield(this, type);
  302. shields[(x * NUM_SHIELDS_PER_ARRAY) + i]->SetPosition(shield_array_start_position + Rml::Core::Vector2f((float) SHIELD_SIZE * i, 0));
  303. }
  304. // Middle row (row of 4)
  305. for (int i = 0; i < 4; i++)
  306. {
  307. Shield::ShieldType type = Shield::REGULAR;
  308. if (i == 1)
  309. type = Shield::BOTTOM_RIGHT;
  310. else if (i == 2)
  311. {
  312. type = Shield::BOTTOM_LEFT;
  313. }
  314. shields[(x * NUM_SHIELDS_PER_ARRAY) + 4 + i] = new Shield(this, type);
  315. shields[(x * NUM_SHIELDS_PER_ARRAY) + 4 + i]->SetPosition(shield_array_start_position + Rml::Core::Vector2f((float) SHIELD_SIZE * i, (float) SHIELD_SIZE));
  316. }
  317. // Bottom row (2, one on each end)
  318. shields[(x * NUM_SHIELDS_PER_ARRAY) + 8] = new Shield(this, Shield::REGULAR);
  319. shields[(x * NUM_SHIELDS_PER_ARRAY) + 8]->SetPosition(shield_array_start_position + Rml::Core::Vector2f(0, (float) SHIELD_SIZE * 2));
  320. shields[(x * NUM_SHIELDS_PER_ARRAY) + 9] = new Shield(this, Shield::REGULAR);
  321. shields[(x * NUM_SHIELDS_PER_ARRAY) + 9]->SetPosition(shield_array_start_position + Rml::Core::Vector2f((float) SHIELD_SIZE * 3, (float) SHIELD_SIZE * 2));
  322. shield_array_start_position.x += SHIELD_SPACING_X;
  323. }
  324. }
  325. void Game::InitialiseWave()
  326. {
  327. // Set up the rows
  328. for (int y = 0; y < NUM_INVADER_ROWS; y++)
  329. {
  330. // Determine invader type
  331. Invader::InvaderType type = Invader::UNKNOWN;
  332. switch (y)
  333. {
  334. case 0:
  335. type = Invader::RANK3; break;
  336. case 1:
  337. case 2:
  338. type = Invader::RANK2; break;
  339. default:
  340. type = Invader::RANK1; break;
  341. }
  342. // Determine position of top left invader
  343. Rml::Core::Vector2f invader_position((float) (WINDOW_WIDTH - (NUM_INVADERS_PER_ROW * INVADER_SPACING_X)) / 2, (float) (INVADER_START_Y + (y * INVADER_SPACING_Y)));
  344. for (int x = 0; x < NUM_INVADERS_PER_ROW; x++)
  345. {
  346. // Determine invader type based on row position
  347. int index = (y * NUM_INVADERS_PER_ROW) + x;
  348. delete invaders[index];
  349. invaders[index] = new Invader(this, type, (y * NUM_INVADERS_PER_ROW) + x);
  350. invaders[index]->SetPosition(invader_position);
  351. // Increase invader position
  352. invader_position.x += INVADER_SPACING_X;
  353. }
  354. }
  355. // Reset mother ship
  356. delete invaders[MOTHERSHIP];
  357. invaders[MOTHERSHIP] = new Mothership(this, MOTHERSHIP);
  358. // Update the move frequency
  359. invader_move_freq = ((((float)GameDetails::GetWave())-100.0f)/140.0f);
  360. invader_move_freq *= invader_move_freq;
  361. }