nsnake
Classic snake game for the terminal
Game.cpp
1 #include <Game/Game.hpp>
2 #include <Config/Globals.hpp>
3 #include <Misc/Utils.hpp>
4 #include <Interface/LayoutGame.hpp>
5 #include <Flow/InputManager.hpp>
6 #include <Game/BoardParser.hpp>
7 
8 #include <stdlib.h>
9 
10 // Options of the Pause Menu
11 enum NamesToEasilyIdentifyTheMenuItemsInsteadOfRawNumbers
12 {
13  RESUME, RESTART, QUIT_MENU, QUIT_GAME
14 };
15 
16 Game::Game():
17  scores(NULL),
18  currentScore(NULL),
19  layout(NULL),
20  gameOver(false),
21  isPaused(false),
22  showPauseMenu(false),
23  showHelp(false),
24  pauseMenu(NULL),
25  player(NULL),
26  board(NULL),
27  fruits(NULL)
28 { }
29 Game::~Game()
30 {
31  SAFE_DELETE(this->layout);
32  SAFE_DELETE(this->scores);
33  SAFE_DELETE(this->currentScore);
34  SAFE_DELETE(this->pauseMenu);
35  SAFE_DELETE(this->player);
36  SAFE_DELETE(this->board);
37  SAFE_DELETE(this->fruits);
38 }
39 void Game::start(std::string levelName)
40 {
41  // Cleaning things from the previous game (if any)
42  SAFE_DELETE(this->layout);
43  SAFE_DELETE(this->scores);
44  SAFE_DELETE(this->currentScore);
45  SAFE_DELETE(this->pauseMenu);
46  SAFE_DELETE(this->player);
47  SAFE_DELETE(this->board);
48  SAFE_DELETE(this->fruits);
49 
50  this->userAskedToQuit = false;
51  this->userAskedToGoToMenu = false;
52  this->gameOver = false;
53  this->isPaused = false;
54 
55  this->scores = new ScoreFile(levelName);
56  // will load the scores on `GameStateGame`
57 
58  this->currentScore = new ScoreEntry();
59  this->currentScore->level = levelName;
60  this->currentScore->speed = Globals::Game::starting_speed;
61  this->currentScore->fruits = Globals::Game::fruits_at_once;
62  this->currentScore->random_walls = Globals::Game::random_walls;
63  this->currentScore->teleport = Globals::Game::teleport;
64  this->currentScore->board_size = Globals::Game::board_size;
65  this->currentScore->board_scroll_delay = Globals::Game::board_scroll_delay;
66  this->currentScore->board_scroll_left = Globals::Game::board_scroll_left;
67  this->currentScore->board_scroll_right = Globals::Game::board_scroll_right;
68  this->currentScore->board_scroll_up = Globals::Game::board_scroll_up;
69  this->currentScore->board_scroll_down = Globals::Game::board_scroll_down;
70 
71  // Defaults to large
72  int boardw = Board::large_width;
73  int boardh = Board::large_height;
74 
75  if (Globals::Game::board_size == Globals::Game::SMALL)
76  {
77  boardw = Board::small_width;
78  boardh = Board::small_height;
79  }
80  else if (Globals::Game::board_size == Globals::Game::MEDIUM)
81  {
82  boardw = Board::medium_width;
83  boardh = Board::medium_height;
84  }
85 
86  if (! levelName.empty())
87  this->board = BoardParser::load(levelName);
88 
89  else
90  {
91  // If no level name is specified, silently
92  // fall back to a default one.
93 
94  this->board = new Board(boardw,
95  boardh,
96  ((Globals::Game::teleport) ?
97  Board::TELEPORT :
98  Board::SOLID));
99  }
100 
101  // the player!
102  this->player = new Player(this->board->getStartX(),
103  this->board->getStartY());
104 
105  if (Globals::Game::random_walls)
106  this->board->randomlyFillExceptBy(this->player->getX(),
107  this->player->getY());
108 
109  // fruits beibeh
110  this->fruits = new FruitManager(Globals::Game::fruits_at_once);
111  this->fruits->update(this->player, this->board);
112 
113  // Finally, the interface
114  //
115  // NOTE: It depends on the `currentScore` level name!
116  // Do not initialize it before!
117  this->layout = new LayoutGame(this, 80, 24);
118 
119  // Creating the menu and adding each item
120  this->pauseMenu = new Menu(1,
121  1,
122  this->layout->pause->getW() - 2,
123  this->layout->pause->getH() - 2);
124 
125  MenuItem* item;
126 
127  item = new MenuItem("Resume", RESUME);
128  this->pauseMenu->add(item);
129 
130  item = new MenuItem("Restart", RESTART);
131  this->pauseMenu->add(item);
132 
133  this->pauseMenu->addBlank();
134 
135  item = new MenuItem("Quit to Main Menu", QUIT_MENU);
136  this->pauseMenu->add(item);
137 
138  item = new MenuItem("Quit Game", QUIT_GAME);
139  this->pauseMenu->add(item);
140 
141  // Starting timers
142  this->timerSnake.start();
143  this->timerBoard.start();
144  this->timer.start();
145 }
146 void Game::handleInput()
147 {
148  if (InputManager::noKeyPressed())
149  return;
150 
151  // The only two absolute inputs are to quit and pause.
152  // Others depend if the game is paused or not.
153  if (InputManager::isPressed("quit"))
154  {
155  this->userAskedToQuit = true;
156  }
157  else if (InputManager::isPressed("pause"))
158  {
159  (this->isPaused) ?
160  this->pause(false) :
161  this->pause(true);
162 
163  return;
164  }
165  else if (InputManager::isPressed('\n') ||
166  InputManager::isPressed(KEY_ENTER))
167  {
168  if (! this->isPaused)
169  {
170  this->pause(true);
171  return;
172  // This needs to be here otherwise
173  // ENTER goes to the menu and immediately
174  // unpauses the game.
175  }
176  }
177  else if (InputManager::isPressed("help"))
178  {
179  // Toggling Pause and Help window
180  if (this->isPaused)
181  {
182  this->showHelp = false;
183  this->timer.unpause();
184  this->timerSnake.unpause();
185  this->timerBoard.unpause();
186  }
187  else
188  {
189  this->showHelp = true;
190  this->timer.pause();
191  this->timerSnake.pause();
192  this->timerBoard.pause();
193  }
194  }
195 
196  // Other keys are not used when paused.
197  if (this->isPaused || this->showHelp)
198  {
199  this->pauseMenu->handleInput();
200  return;
201  }
202 
203  if (InputManager::isPressed("left"))
204  {
205  this->player->move(Player::LEFT);
206  }
207  else if (InputManager::isPressed("right"))
208  {
209  this->player->move(Player::RIGHT);
210  }
211  else if (InputManager::isPressed("up"))
212  {
213  this->player->move(Player::UP);
214  }
215  else if (InputManager::isPressed("down"))
216  {
217  this->player->move(Player::DOWN);
218  }
219 }
220 void Game::update()
221 {
222  if (this->gameOver)
223  return;
224 
225  // If we're paused, only handle the menu.
226  if (this->isPaused)
227  {
228  if (this->pauseMenu->willQuit())
229  {
230  int option = this->pauseMenu->currentID();
231 
232  switch(option)
233  {
234  case RESUME:
235  this->pause(false);
236  break;
237 
238  case RESTART:
239  this->start(Globals::Game::current_level);
240  return;
241 
242  case QUIT_MENU:
243  this->userAskedToGoToMenu = true;
244  break;
245 
246  case QUIT_GAME:
247  this->userAskedToQuit = true;
248  break;
249  }
250  this->pauseMenu->reset();
251  }
252  return;
253  }
254 
255  // Forcing Snake to move if enough time has passed
256  // (time based on current level)
257  this->timerSnake.pause();
258  int delta = this->getDelay(this->currentScore->speed);
259 
260  if (this->timerSnake.delta_ms() >= delta)
261  {
262  // Checking if on the previous frame
263  // the Snake died.
264  if (! this->player->isAlive())
265  {
266  this->gameOver = true;
267 
268  // Check the return value and warns the player
269  // if he just beat the high score
270  this->scores->handle(this->currentScore);
271  }
272  else
273  {
274  // Actually move the player
275  this->player->update(this->board);
276 
277  while (this->fruits->eatenFruit(this->player))
278  {
279  this->player->increase();
280 
281  // Score formula is kinda random and
282  // scattered all over this file.
283  // TODO: Center it all on the Score class.
284  this->currentScore->points += this->currentScore->speed * 2;
285  }
286 
287  this->fruits->update(this->player, this->board);
288  }
289  this->timerSnake.start();
290  }
291  else
292  this->timerSnake.unpause();
293 
294  // Hey, can we scroll the Board?
295  // If yes, on which direction should we do it?
296  this->timerBoard.pause();
297  delta = Globals::Game::board_scroll_delay;
298 
299  if (this->timerBoard.delta_ms() >= delta)
300  {
301  if (Globals::Game::board_scroll_up) this->board->scrollUp();
302  if (Globals::Game::board_scroll_down) this->board->scrollDown();
303  if (Globals::Game::board_scroll_left) this->board->scrollLeft();
304  if (Globals::Game::board_scroll_right) this->board->scrollRight();
305 
306  this->timerBoard.start();
307  }
308  else
309  this->timerBoard.unpause();
310 }
311 void Game::draw()
312 {
313  this->layout->draw(this->pauseMenu);
314 }
315 bool Game::isOver()
316 {
317  return (this->gameOver);
318 }
320 {
321  return this->userAskedToQuit;
322 }
324 {
325  return this->userAskedToGoToMenu;
326 }
327 int Game::getDelay(int speed)
328 {
329  // returning delay in milliseconds
330  if (speed < 1) return 800;
331 
332  switch (speed)
333  {
334  case 1: return 800;
335  case 2: return 600;
336  case 3: return 500;
337  case 4: return 300;
338  case 5: return 200;
339  case 6: return 150;
340  case 7: return 125;
341  case 8: return 100;
342  case 9: return 80;
343  case 10: return 50;
344  }
345  return 50;
346 }
347 void Game::pause(bool option)
348 {
349  if (option)
350  {
351  if (this->isPaused)
352  return;
353 
354  this->isPaused = true;
355  this->showPauseMenu = true;
356  this->timer.pause();
357  this->timerSnake.pause();
358  }
359  else
360  {
361  if (! this->isPaused)
362  return;
363 
364  this->isPaused = false;
365  this->showPauseMenu = false;
366  this->timer.unpause();
367  this->timerSnake.unpause();
368  }
369 }
370 
Stores points the player made on the game.
Definition: ScoreFile.hpp:87
void start(std::string levelName="")
Starts game, optionally loading a level.
Definition: Game.cpp:39
A level where the snake runs and eats fruits.
Definition: Board.hpp:32
bool willReturnToMenu()
If we&#39;ll return to the main menu.
Definition: Game.cpp:323
static Board * load(std::string filename)
Loads and parses level with name.
Definition: BoardParser.cpp:18
A single entry on the high-score file.
Definition: ScoreFile.hpp:27
Simplest type of item possible, with a label and user-defined id.
Definition: MenuItem.hpp:11
List of selectable items.
Definition: Menu.hpp:28
int getDelay(int speed)
Returns how much time (millisseconds) we need to wait for a specific #speed.
Definition: Game.cpp:327
FruitManager(int amount)
Creates a Fruit container that has at most #amount fruits at once on the screen.
Definition: FruitManager.cpp:4
bool willQuit()
If we&#39;ll quit the game right away.
Definition: Game.cpp:319