nSnake
A ncurses implementation of the classic Snake game
 All Data Structures Files Functions Variables Enumerations Macros Pages
engine.c
Go to the documentation of this file.
1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
2  * nSnake - The classic snake game with ncurses. *
3  * Copyright (C) 2011-2012 Alexandre Dantas (kure) *
4  * *
5  * This file is part of nSnake. *
6  * *
7  * nSnake is free software: you can redistribute it and/or modify *
8  * it under the terms of the GNU General Public License as published by *
9  * the Free Software Foundation, either version 3 of the License, or *
10  * any later version. *
11  * *
12  * This program is distributed in the hope that it will be useful, *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15  * GNU General Public License for more details. *
16  * *
17  * You should have received a copy of the GNU General Public License *
18  * along with this program. If not, see <http://www.gnu.org/licenses/>. *
19  * *
20  * homepage: http://sourceforge.net/projects/nsnake/ *
21  * *
22 \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
23 
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <unistd.h> // for the usleep () function
36 
37 #include "engine.h"
38 #include "fruit.h"
39 #include "player.h"
40 #include "hscores.h"
41 
42 
43 #if (defined __WIN32__) && (!defined __CYGWIN__)
44  // In Windows, the console has fixed width
45  #define FIXED_WIDTH 80
46  #define FIXED_HEIGHT 25
47 #else
48  // Now here we don't have to worry about that
49  #define FIXED_WIDTH 80
50  #define FIXED_HEIGHT 24
51 #endif
52 
53 
54 
62 #define REFRESH_DELAY (20000 + ((9 - game.level) * 25000))
63 
64 
65 
66 static char PLAYER_HEAD_CHAR = '@';
67 static char PLAYER_CHAR = 'o';
68 static char FRUIT_CHAR = '$';
69 static char BORDER_CHAR = '#';
70 static char NO_BORDER_CHAR = '.';
71 static char MENU_BORDER_CHAR = '*';
72 
73 
74 
76 enum Colors { BLACK_WHITE = 1, CYAN_BLACK, BLUE_BLACK,
77  WHITE_BLACK, GREEN_BLACK, RED_BLACK, };
78 
79 
80 
82 
83 
84 
88 {
89  /* clear(); */
90  erase();
91 }
92 
93 
96 void draw_borders ()
97 {
98  int i;
99 
100  start_atrribute (COLOR_PAIR (WHITE_BLACK));
101  if (game.mode == BORDERS_ON)
102  {
103  for (i = 0; i <= (screen.width-1); i++) //upper
104  {
105  mvaddch (1, i, BORDER_CHAR);
106  mvaddch ((screen.height-1), i, BORDER_CHAR);
107  }
108  for (i = 1; i <= (screen.height-1); i++) //lower
109  {
110  mvaddch (i, 0, BORDER_CHAR);
111  mvaddch (i, (screen.width-1), BORDER_CHAR);
112  }
113  }
114  else if (game.mode == BORDERS_OFF)
115  {
116  for (i = 0; i <= (screen.width-1); i++)
117  {
118  mvaddch (1, i, NO_BORDER_CHAR);
119  mvaddch ((screen.height-1), i, NO_BORDER_CHAR);
120  }
121  for (i = 1; i <= (screen.height-1); i++)
122  {
123  mvaddch (i, 0, NO_BORDER_CHAR);
124  mvaddch (i, (screen.width-1), NO_BORDER_CHAR);
125  }
126  }
127 }
128 
129 
132 void draw_fruit ()
133 {
134  start_atrribute (COLOR_PAIR (CYAN_BLACK));
135  mvaddch (fruit.y, fruit.x, FRUIT_CHAR);
136 }
137 
138 
142 {
143  start_atrribute (COLOR_PAIR (WHITE_BLACK));
144  mvprintw (0, (screen.width-1)/2, "Bonus: %d", fruit.bonus);
145 }
146 
147 
150 void draw_player ()
151 {
152  start_atrribute (COLOR_PAIR (GREEN_BLACK));
153  mvaddch (snake.body[0].y, snake.body[0].x, PLAYER_HEAD_CHAR);
154 
155  int i;
156  for (i = 1; i < snake.size; i++)
157  mvaddch (snake.body[i].y, snake.body[i].x, PLAYER_CHAR);
158 }
159 
160 
163 void draw_score ()
164 {
165  start_atrribute (COLOR_PAIR (CYAN_BLACK));
166  mvprintw (0, 0, "nSnake v");
167  mvprintw (0, 9, VERSION);
168 
169  start_atrribute (COLOR_PAIR (WHITE_BLACK));
170  mvprintw (0, 17, "Lv%d", game.level);
171  mvprintw (0, 23, "Score: %d", snake.score);
172 
173  if (game.mode == BORDERS_ON)
174  mvprintw (0, 60, "High Score: %d", HIGH_SCORE_BORDERS);
175  else if (game.mode == BORDERS_OFF)
176  mvprintw (0, 60, "High Score: %d", HIGH_SCORE_BORDERS_OFF);
177 }
178 
179 
182 void engine_exit ()
183 {
184  /* clear (); */
185  erase();
186  refresh ();
187  // Effectively ends ncurses mode
188  endwin ();
189 }
190 
191 
195 {
196  // The input variable MUST be int to accept non-ascii characters
197  return getch ();
198 }
199 
200 
205 void engine_init ()
206 {
207  screen.width = FIXED_WIDTH;
208  screen.height = FIXED_HEIGHT;
209 
210  initscr (); // Starts the ncurses mode
211 
212  if (has_colors () == TRUE)
213  {
214  int bg_color;
215 
216  start_color (); // Start color support
217 
218  // Let's try grabbing the current terminal background color
219  if (use_default_colors () == ERR)
220  bg_color = COLOR_BLACK;
221  else
222  bg_color = -1;
223 
224  // Start support for colors (Name, Foreground, Background)
225  init_pair (GREEN_BLACK, COLOR_GREEN, bg_color);
226  init_pair (CYAN_BLACK, COLOR_CYAN, bg_color);
227  init_pair (WHITE_BLACK, COLOR_WHITE, bg_color);
228  init_pair (RED_BLACK, COLOR_RED, bg_color);
229  init_pair (BLUE_BLACK, COLOR_BLUE, bg_color);
230  init_pair (BLACK_WHITE, COLOR_BLACK, bg_color);
231  }
232 
233  int current_height, current_width;
234  // Gets the current width and height of the terminal
235  getmaxyx (stdscr, current_height, current_width);
236 
237  if ((current_width < screen.width) || (current_height < screen.height))
238  nsnake_abort ("Your console screen is smaller than 80x24\n"
239  "Please resize your window and try again\n\n");
240 
241  raw (); // Character input doesnt require the <enter> key anymore
242  curs_set (0); // Makes the blinking cursor invisible
243  noecho (); // Wont print the keys received through input
244  nodelay (stdscr, TRUE); // Wont wait for input - the game will run instantaneously
245  keypad (stdscr, TRUE); // Support for extra keys (life F1, F2, ... )
246  refresh (); // Refresh the screen (prints whats in the screen buffer)
247 
250  game.mode = BORDERS_ON;
251 }
252 
253 
259 {
260  int game_over_logo_height = 16;
261  char* game_over_logo[] =
262  {
263  " _______ _______ __ __ _______ ",
264  "| || _ || |_| || |",
265  "| ___|| |_| || || ___|",
266  "| | __ | || || |___ ",
267  "| || || || || ___|",
268  "| |_| || _ || ||_|| || |___ ",
269  "|_______||__| |__||_| |_||_______|",
270  " _______ __ __ _______ ______ ",
271  "| || | | || || _ | ",
272  "| _ || |_| || ___|| | || ",
273  "| | | || || |___ | |_|| ",
274  "| |_| || || ___|| __ |",
275  "| | | | | |___ | | ||",
276  "|_______| |___| |_______||___| ||",
277 #if (defined __WIN32__) && (!defined __CYGWIN__)
278  " Press <space> to retry",
279 #else
280  " Press <enter> to retry",
281 #endif
282  " <m> to Main Menu"
283  };
284  int i;
285 
286  start_atrribute (COLOR_PAIR (RED_BLACK));
287  mvaddch (snake.body[0].y, snake.body[0].x, 'x');
288  for (i = 0; i < game_over_logo_height; i++)
289  mvaddstr (3 + i, 22, game_over_logo[i]);
290 
291  draw_score ();
292  refresh ();
293  nodelay (stdscr, FALSE);
294 }
295 
296 void engine_clean_game_over ()
297 {
298  nodelay (stdscr, TRUE);
299 }
300 
307 {
308  int speed_cur_option = 1;
309  char speed_cur_options[9] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'};
310 
311  int menu_row_pos = 13;
312  int option_row_pos = menu_row_pos + 17;
313 
314  int wait = TRUE;
315  int i;
316  int j;
317 
318  clear ();
319 
320  while (wait == TRUE)
321  {
322  // The borders
323  start_atrribute (COLOR_PAIR (WHITE_BLACK));
324  for (i = 0; i < screen.width; i++)
325  {
326  mvaddch (0, i, MENU_BORDER_CHAR);
327  mvaddch (screen.height - 1, i, MENU_BORDER_CHAR);
328  }
329  for (i = 0; i < screen.height; i++)
330  {
331  mvaddch (i, 0, MENU_BORDER_CHAR);
332  mvaddch (i, screen.width - 1, MENU_BORDER_CHAR);
333  }
334 
335  start_atrribute (COLOR_PAIR (GREEN_BLACK));
336  mvprintw(1, 3, " ,d8888b. 888");
337  mvprintw(2, 3, " d88P Y88b 888");
338  mvprintw(3, 3, " Y88b. 888");
339  mvprintw(4, 3, "88888b. Y888b. 88888b. 8888b. 888 888 .d88b.");
340  mvprintw(5, 3, "888 88b 8Y88b. 888 88b 88b 888 .88P d8P Y8b");
341  mvprintw(6, 3, "888 888 888b 888 888 .d888888 888888K 88888888");
342  mvprintw(7, 3, "888 888 Y88b d88P 888 888 888 888 888 88b Y8b.");
343  mvprintw(8, 3, "888 888 Y8888P 888 888 Y888888 888 888 Y88888");
344 
345  start_atrribute (COLOR_PAIR (CYAN_BLACK));
346  mvaddch (8, 59, 'v');
347  mvprintw (8, 60, VERSION);
348 
349  start_atrribute (COLOR_PAIR (BLUE_BLACK));
350  mvprintw (10, 5, " ___________________________________________________ ");
351  mvprintw (11, 5, "| |");
352  mvprintw (12, 5, "| |");
353  mvprintw (13, 5, "| |");
354  mvprintw (14, 5, "| |");
355  mvprintw (15, 5, "| |");
356  mvprintw (16, 5, "| |");
357  mvprintw (17, 5, "| |");
358  mvprintw (18, 5, "| |");
359  mvprintw (19, 5, "|___________________________________________________|");
360 #if (defined __WIN32__) && (!defined __CYGWIN__)
361  mvprintw (12, menu_row_pos, "Press <space> to start game");
362 #else
363  mvprintw (12, menu_row_pos, "Press <enter> or <space> to start game");
364 #endif
365  mvprintw (13, menu_row_pos, "Press <q> to quit game");
366 
367  // Here we draw the game mode
368  mvprintw (15, menu_row_pos, "Game Mode:");
369  if (game.mode == BORDERS_ON)
370  {
371  start_atrribute (COLOR_PAIR (WHITE_BLACK));
372  mvprintw (15, option_row_pos, "Borders On");
373 
374  start_atrribute (COLOR_PAIR (BLUE_BLACK));
375  mvprintw (16, option_row_pos, "Borders Off");
376  }
377  else
378  {
379  start_atrribute (COLOR_PAIR (BLUE_BLACK));
380  mvprintw (15, option_row_pos, "Borders On");
381 
382  start_atrribute (COLOR_PAIR (WHITE_BLACK));
383  mvprintw (16, option_row_pos, "Borders Off");
384  }
385 
386  // And here we draw the level numbers
387  start_atrribute (COLOR_PAIR (BLUE_BLACK));
388  mvprintw (18, menu_row_pos, "Starting speed:");
389 
390  // Tricky, draw the options with the right colors
391  for (i = 0, j = 0; i < 9; i++)
392  {
393  if (i == (speed_cur_option-1))
394  start_atrribute (COLOR_PAIR (WHITE_BLACK));
395  else
396  start_atrribute (COLOR_PAIR (BLUE_BLACK));
397 
398  mvprintw (18, option_row_pos+j, "%c", speed_cur_options [i]);
399  j += 2;
400  }
401 
402  start_atrribute (COLOR_PAIR (WHITE_BLACK));
403  mvprintw (screen.height-2, 2, "Use --help for guidelines");
404 
405  // Now we wait for orders
406  wait = get_main_menu_input (&speed_cur_option);
407 
408  // This function is so refreshing...
409  refresh ();
410  }
411 
412  game.level = speed_cur_option;
413 }
414 
415 
419 {
420  curs_set (1); // Makes the cursor visible
421  nodelay (stdscr, FALSE); // We'll wait for input again
422 
423  start_atrribute (COLOR_PAIR (RED_BLACK));
424 
425  mvprintw ((screen.height-1)/2, ((screen.width-1)/2)-5, "Game Paused ");
426  mvprintw (((screen.height-1)/2)+1, ((screen.width-1)/2)-11, "Press <p> to Continue...");
427 }
428 
429 
439 {
440  draw_background ();
441  draw_borders ();
442  draw_fruit ();
443  draw_player ();
445  draw_score ();
446 
447  usleep (REFRESH_DELAY);
448 
449  refresh();
450 }
451 
452 
456 {
457  int wait = TRUE;
458 
459  nodelay (stdscr, FALSE);
460  while (wait == TRUE)
461  {
462  int input = getch();
463 
464  switch (input)
465  {
466  case 'q': case 'Q':
467  engine_exit ();
468  nsnake_exit ();
469  break;
470 
471  case 'm': case 'M':
472  wait = FALSE;
474 
475 #if (defined __WIN32__) && (!defined __CYGWIN__)
476  case ' ':
477 #else
478  case '\n':
479 #endif
480  wait = FALSE;
481  break;
482 
483  default:
484  break;
485  }
486  }
487  nodelay (stdscr, TRUE);
488 }
489 
490 
493 int get_main_menu_input (int* speed_cur_option)
494 {
495  nodelay (stdscr, FALSE);
496 
497  int input = getch();
498  switch (input)
499  {
500  case '\n': case ' ':
501  nodelay (stdscr, TRUE);
502  return FALSE;
503  break;
504 
505  case 'q': case 'Q':
506  engine_exit();
507  nsnake_exit();
508  break;
509 
510  case KEY_UP: case 'k':
511  if (game.mode == BORDERS_OFF)
512  game.mode = BORDERS_ON;
513  break;
514 
515  case KEY_DOWN: case 'j':
516  if (game.mode == BORDERS_ON)
517  game.mode = BORDERS_OFF;
518  break;
519 
520  case KEY_LEFT: case 'h':
521  if (*speed_cur_option > 1)
522  (*speed_cur_option)--;
523  break;
524 
525  case KEY_RIGHT: case 'l':
526  if (*speed_cur_option < 9)
527  (*speed_cur_option)++;
528  break;
529 
530  case '1': case '2': case '3': case '4': case '5':
531  case '6': case '7': case '8': case '9':
532  *speed_cur_option = (input - '0'); // ASCII table value
533  break;
534 
535  default:
536  break;
537  }
538 
539  return TRUE;
540 }
541 
542 
546 {
547  nodelay (stdscr, TRUE); // We'll no longer wait for input
548  curs_set (0); // And here it becomes invisible again
549 }
550 
551 
555 void start_atrribute (int attr)
556 {
557  if (has_colors () == TRUE)
558  {
559  attron (attr);
560  }
561 }
562