1 //  This file is part of the computer game Kartofel.
2 //  Copyright (C) 2008 Pawel Aleksander Fedorynski <pfedor@fuw.edu.pl>
3 //
4 //  This program is free software; you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation; either version 2 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program; if not, write to the Free Software
16 //  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 
18 #include <algorithm>
19 #include <cassert>
20 #include <cerrno>
21 #include <cstdio>
22 #include <curl/curl.h>
23 #include <iomanip>
24 #include <iostream>
25 #include <sstream>
26 #include "SDL.h"
27 #include "SDL_gfxPrimitives.h"
28 #include "SDL_framerate.h"
29 #include "SDL_image.h"
30 #include "SDL_mixer.h"
31 #include "SDL_thread.h"
32 #include "SDL_ttf.h"
33 #include <unistd.h>
34 #include <err.h>
35 #include <sys/stat.h>
36 
37 #include "config.h"
38 #include "game.h"
39 #include "saved_game.h"
40 #include "submit_game.h"
41 #include "util.h"
42 
43 using namespace std;
44 
45 namespace kartofel {
46 
47 enum {
48   NUM_LEVELS = 21
49 };
50 
51 enum {
52     SCREEN_WIDTH = 1024,
53     SCREEN_HEIGHT = 768,
54     BOARD_X = 266,
55     BOARD_Y = 10,
56     BOARD_WIDTH = 748,
57     BOARD_HEIGHT = 748,
58     INFO_PANEL_X = 10,
59     INFO_PANEL_Y = 284,
60     INFO_PANEL_WIDTH = 246,
61     INFO_PANEL_HEIGHT = 200,
62     INFO_PANEL_BORDER_WIDTH = 2,
63     INFO_LEVEL_X = 15,
64     INFO_LEVEL_Y = 12,
65     INFO_SCORE_X = 15,
66     INFO_SCORE_Y = 38,
67     INFO_TIME_LEFT_X = 15,
68     INFO_TIME_LEFT_Y = 64,
69     INFO_SPEED_X = 15,
70     INFO_SPEED_Y = 90,
71     INFO_LIVES_X = 15,
72     INFO_LIVES_Y = 116,
73     MSG_BORDER_WIDTH = 2,
74     OUT_OF_TIME_MSG_BG_X = 330,
75     OUT_OF_TIME_MSG_BG_Y = 300,
76     OUT_OF_TIME_MSG_BG_WIDTH = 620,
77     OUT_OF_TIME_MSG_BG_HEIGHT = 150,
78     OUT_OF_TIME_MSG_X = 30,
79     OUT_OF_TIME_MSG_Y = 30,
80     GAME_OVER_MSG_BG_X = 330,
81     GAME_OVER_MSG_BG_Y = 300,
82     GAME_OVER_MSG_BG_WIDTH = 620,
83     GAME_OVER_MSG_BG_HEIGHT = 150,
84     GAME_OVER_MSG_X = 60,
85     GAME_OVER_MSG_Y = 30,
86     GAME_SUCCESS_MSG_BG_X = 70,
87     GAME_SUCCESS_MSG_BG_Y = 200,
88     GAME_SUCCESS_MSG_BG_WIDTH = 910,
89     GAME_SUCCESS_MSG_BG_HEIGHT = 400,
90     GAME_SUCCESS_MSG_X = 30,
91     GAME_SUCCESS_MSG_Y = 30,
92     END_LEVEL_BONUS_MSG_BG_X = 330,
93     END_LEVEL_BONUS_MSG_BG_Y = 285,
94     END_LEVEL_BONUS_MSG_BG_WIDTH = 620,
95     END_LEVEL_BONUS_MSG_BG_HEIGHT = 180,
96     END_LEVEL_BONUS_MSG_X = 100,
97     END_LEVEL_BONUS_MSG_LEVEL_MULTIPLIER_Y = 40,
98     END_LEVEL_BONUS_MSG_TIME_LEFT_Y = 70,
99     END_LEVEL_BONUS_MSG_MINIMUM_SPEED_USED_Y = 100,
100     END_LEVEL_BONUS_MSG_BONUS_Y = 130,
101     HIGH_SCORES_BG_X = 40,
102     HIGH_SCORES_BG_Y = 40,
103     HIGH_SCORES_BG_WIDTH = 944,
104     HIGH_SCORES_BG_HEIGHT = 688,
105     HIGH_SCORES_TITLE_X = 200,
106     HIGH_SCORES_TITLE_Y = 40,
107     HIGH_SCORES_TEXT_X = 140,
108     HIGH_SCORES_TEXT_Y = 200,
109     DEFINE_KEYS_BG_X = 40,
110     DEFINE_KEYS_BG_Y = 40,
111     DEFINE_KEYS_BG_WIDTH = 944,
112     DEFINE_KEYS_BG_HEIGHT = 688,
113     DEFINE_KEYS_TITLE_X = 200,
114     DEFINE_KEYS_TITLE_Y = 40,
115     DEFINE_KEYS_PROMPT_X = 200,
116     DEFINE_KEYS_KEY_X = 744,
117     DEFINE_KEYS_LEFT_Y = 250,
118     DEFINE_KEYS_RIGHT_Y = 290,
119     DEFINE_KEYS_ACCELERATE_Y = 330,
120     DEFINE_KEYS_DECCELERATE_Y = 370,
121     DEFINE_KEYS_START_Y = 410,
122     DEFINE_KEYS_FOOTER_X = 260,
123     DEFINE_KEYS_FOOTER_Y = 620,
124     ENTER_NAME_BG_X = 200,
125     ENTER_NAME_BG_Y = 285,
126     ENTER_NAME_BG_WIDTH = 620,
127     ENTER_NAME_BG_HEIGHT = 180,
128     ENTER_NAME_PROMPT_X = 30,
129     ENTER_NAME_PROMPT_Y = 50,
130     ENTER_NAME_X = 30,
131     ENTER_NAME_Y = 80,
132     ENTER_NAME_NOTE_X = 30,
133     ENTER_NAME_NOTE_Y = 140,
134     FLASHING_HALF_PERIOD = 18,
135     MAX_NAME_WIDTH = 566,
136     STARTER_LENGTH = 40,
137     SCRATCH_WIDTH = 100,
138     SCRATCH_HEIGHT = 100,
139     NUM_HIGH_SCORES = 10,
140 };
141 
142 enum Color {
143   CHROME,
144   BOARD,
145   INFO_PANEL,
146   INFO_PANEL_BORDER,
147   INFO_PANEL_TEXT,
148   MSG_BORDER,
149   OUT_OF_TIME_MSG_BACKGROUND,
150   OUT_OF_TIME_MSG,
151   GAME_OVER_MSG_BACKGROUND,
152   GAME_OVER_MSG,
153   GAME_SUCCESS_MSG_BACKGROUND,
154   GAME_SUCCESS_MSG,
155   END_LEVEL_BONUS_MSG_BACKGROUND,
156   END_LEVEL_BONUS_MSG,
157   HIGH_SCORES_BACKGROUND,
158   HIGH_SCORES_TITLE,
159   HIGH_SCORES_TEXT,
160   DEFINE_KEYS_BACKGROUND,
161   DEFINE_KEYS_TITLE,
162   DEFINE_KEYS_TEXT,
163   ENTER_NAME_BACKGROUND,
164   ENTER_NAME_PROMPT,
165   ENTER_NAME,
166   ENTER_NAME_NOTE,
167   DOT_LIT,
168   DOT,
169   DOT_BORDER,
170   DOT_LABEL,
171   DOT_EXTRA_LIFE_PICTURE,
172   LINE,
173   MENU_BACKGROUND,
174   MENU_ITEM,
175   SELECTED_MENU_ITEM,
176   MENU_TITLE,
177   COPYRIGHT,
178 };
179 
180 struct ColorDefinitionRGB {
181   Color c;
182   int r;
183   int g;
184   int b;
185 };
186 
187 ColorDefinitionRGB rgb[] = {
188   { CHROME, 0, 194, 197, },
189   { BOARD, 197, 194, 197, },
190   { INFO_PANEL, 197, 194, 197, },
191   { INFO_PANEL_BORDER, 0, 0, 0, },
192   { INFO_PANEL_TEXT, 0, 0, 0, },
193   { MSG_BORDER, 0, 0, 0, },
194   { OUT_OF_TIME_MSG_BACKGROUND, 200, 100, 100, },
195   { OUT_OF_TIME_MSG, 0, 0, 0, },
196   { GAME_OVER_MSG_BACKGROUND, 130, 194, 250, },
197   { GAME_OVER_MSG, 0, 0, 0, },
198   { GAME_SUCCESS_MSG_BACKGROUND, 250, 210, 250, },
199   { GAME_SUCCESS_MSG, 0, 0, 0, },
200   { END_LEVEL_BONUS_MSG_BACKGROUND, 130, 194, 250, },
201   { END_LEVEL_BONUS_MSG, 0, 0, 0, },
202   { HIGH_SCORES_BACKGROUND, 130, 194, 250, },
203   { HIGH_SCORES_TITLE, 0, 0, 0, },
204   { HIGH_SCORES_TEXT, 0, 0, 0, },
205   { DEFINE_KEYS_BACKGROUND, 130, 194, 250, },
206   { DEFINE_KEYS_TITLE, 0, 0, 0, },
207   { DEFINE_KEYS_TEXT, 0, 0, 0, },
208   { ENTER_NAME_BACKGROUND, 130, 194, 250, },
209   { ENTER_NAME_PROMPT, 0, 0, 0, },
210   { ENTER_NAME, 0, 0, 0, },
211   { ENTER_NAME_NOTE, 0, 0, 0, },
212   { DOT_LIT, 255, 255, 255, },
213   { DOT, 197, 194, 0, },
214   { DOT_BORDER, 0, 0, 0, },
215   { DOT_LABEL, 0, 0, 0, },
216   { DOT_EXTRA_LIFE_PICTURE, 0, 0, 0, },
217   { LINE, 0, 0, 0, },
218   { MENU_BACKGROUND, 0, 194, 197, },
219   { MENU_ITEM, 255, 255, 255, },
220   { SELECTED_MENU_ITEM, 0, 0, 255, },
221   { MENU_TITLE, 255, 255, 255, },
222   { COPYRIGHT, 210, 210, 200, },
223 };
224 
225 enum SoundEffects {
226   SOUND_MENU_NAV,
227   SOUND_MENU_CLICK,
228   SOUND_ROTATE,
229   SOUND_START,
230   SOUND_CRASH,
231   SOUND_DOT_LIT,
232   SOUND_LEVEL_SUCCESS,
233   SOUND_OUT_OF_TIME,
234   SOUND_ENTER_NAME_RETURN,
235 };
236 
237 struct SoundEffectData {
238   SoundEffects id;
239   string filename;
240   int volume;
241   Mix_Chunk* sound;
242 };
243 
244 SoundEffectData sound_effects[] = {
245   { SOUND_MENU_NAV, SOUND_MENU_NAV_FILENAME, 100, },
246   { SOUND_MENU_CLICK, SOUND_MENU_CLICK_FILENAME, 100, },
247   { SOUND_ROTATE, SOUND_ROTATE_FILENAME, 20, },
248   { SOUND_START, SOUND_START_FILENAME, 128, },
249   { SOUND_CRASH, SOUND_CRASH_FILENAME, 128, },
250   { SOUND_DOT_LIT, SOUND_DOT_LIT_FILENAME, 30, },
251   { SOUND_LEVEL_SUCCESS, SOUND_LEVEL_SUCCESS_FILENAME, 128, },
252   { SOUND_OUT_OF_TIME, SOUND_OUT_OF_TIME_FILENAME, 128, },
253   { SOUND_ENTER_NAME_RETURN, SOUND_ENTER_NAME_RETURN_FILENAME, 100, },
254 };
255 
256 class HighScoreTable {
257 public:
258   HighScoreTable();
259   // Returns true if "score" was high enough to be worthy of adding
260   // to the table.
261   bool IsHighEnough(int score);
262   void Add(const string& name, int score);
263   void Show(SDL_Surface* screen);
264   void Get(int nr, string* name, int* score);
265 
266 private:
267   vector<string> names;
268   vector<int> scores;
269 };
270 
271 Level levels[NUM_LEVELS];
272 
273 TTF_Font* menu_font;
274 TTF_Font* menu_title_font;
275 TTF_Font* info_font;
276 TTF_Font* info_font_small;
277 TTF_Font* msg_font;
278 Mix_Music* music;
279 SDL_Surface* menu_background_image;
280 SDL_Surface* physical_screen;
281 SDL_Surface* game_screen;
282 SDL_Surface* menu_screen;
283 SDL_Surface** current_screen;  // Points to either game_screen or menu_screen.
284 SDL_Surface* scratch;
285 int scratch_x1;
286 int scratch_y1;
287 int scratch_x2;
288 int scratch_y2;
289 bool scratch_in_use = false;
290 
291 FPSmanager fps_manager;
292 
293 bool fullscreen_on = false;
294 bool music_on = true;
295 bool sound_effects_on = true;
296 bool publish_scores = true;
297 
298 HighScoreTable high_scores;
299 int max_starting_level = 1;
300 
301 SavedGame saved_game;
302 bool libcurl_successfully_initialized = false;
303 
304 int key_accelerate = SDLK_UP;
305 int key_deccelerate = SDLK_DOWN;
306 int key_left = SDLK_LEFT;
307 int key_right = SDLK_RIGHT;
308 int key_start = SDLK_SPACE;
309 
LoadHighScoresAndMaxStartingLevel()310 void LoadHighScoresAndMaxStartingLevel()
311 {
312   const char* filename = HIGH_SCORES_FILENAME;
313   FILE* f = fopen(filename, "r");
314   if (!f) {
315     cerr << filename << ": " << strerror(errno) << endl;
316     return;
317   }
318   vector<string> lines;
319   char line[10000];
320   // Two lines per high score (the name and the score) plus one line
321   // for max starting level.
322   int expected_num_lines = 2 * NUM_HIGH_SCORES + 1;
323   for (int i = 0; i < expected_num_lines; i++) {
324     errno = 0;
325     if (!fgets(line, sizeof(line), f)) {
326       if (ferror(f)) {
327         cerr << filename << ": " << strerror(errno) << endl;
328       } else {
329         cerr << filename << ": Unexpected end of file.\n";
330       }
331       return;
332     }
333     int len = strlen(line);
334     if (len > 0 || line[len - 1] == '\n') {
335       line[len - 1] = '\0';
336     }
337     lines.push_back(line);
338   }
339   if (0 != fclose(f)) {
340     cerr << filename << ": " << strerror(errno) << endl;
341   }
342   assert(lines.size() == expected_num_lines);
343   max_starting_level = atoi(lines[0].c_str());
344   if (max_starting_level < 1) {
345     max_starting_level = 1;
346   }
347   if (max_starting_level > NUM_LEVELS) {
348     max_starting_level = NUM_LEVELS;
349   }
350   for (int i = 0; i < NUM_HIGH_SCORES; i++) {
351     high_scores.Add(lines[2 * i + 1], atoi(lines[2 * i + 2].c_str()));
352   }
353 }
354 
SaveHighScoresAndMaxStartingLevel()355 void SaveHighScoresAndMaxStartingLevel()
356 {
357   const char* filename = HIGH_SCORES_FILENAME;
358   FILE* f = fopen(filename, "w");
359   if (!f) {
360     cerr << filename << ": " << strerror(errno) << endl;
361     return;
362   }
363   if (0 > fprintf(f, "%d\n", max_starting_level)) {
364     cerr << filename << ": " << strerror(errno) << endl;
365     return;
366   }
367   for (int i = 0; i < NUM_HIGH_SCORES; i++) {
368     string name;
369     int score;
370     high_scores.Get(i, &name, &score);
371     if (0 > fprintf(f, "%s\n%d\n", name.c_str(), score)) {
372       cerr << filename << ": " << strerror(errno) << endl;
373       return;
374     }
375   }
376   if (0 != fclose(f)) {
377     cerr << filename << ": " << strerror(errno) << endl;
378   }
379 }
380 
Exit()381 void Exit()
382 {
383   SaveHighScoresAndMaxStartingLevel();
384   exit(0);
385 }
386 
GetColor(Color c,SDL_Surface * s=0)387 Uint32 GetColor(Color c, SDL_Surface* s = 0)
388 {
389   if (!s) {
390     s = physical_screen;
391   }
392   int i;
393   for (i = 0; i < sizeof(rgb)/sizeof(*rgb); ++i) {
394     if (rgb[i].c == c) {
395       return SDL_MapRGB(s->format, rgb[i].r, rgb[i].g, rgb[i].b);
396     }
397   }
398   assert(!"Invalid color.");
399 }
400 
GetSDLColor(Color c)401 SDL_Color GetSDLColor(Color c)
402 {
403   int i;
404   for (i = 0; i < sizeof(rgb)/sizeof(*rgb); ++i) {
405     if (rgb[i].c == c) {
406       SDL_Color retv = { rgb[i].r, rgb[i].g, rgb[i].b };
407       return retv;
408     }
409   }
410   assert(!"Invalid color.");
411 }
412 
GetGfxColor(Color c)413 Uint32 GetGfxColor(Color c)
414 {
415   for (int i = 0; i < sizeof(rgb)/sizeof(*rgb); ++i) {
416     if (rgb[i].c == c) {
417       return (static_cast<Uint32>(rgb[i].r) << 24) |
418         (static_cast<Uint32>(rgb[i].g) << 16) |
419         (static_cast<Uint32>(rgb[i].b) << 8) | static_cast<Uint32>(255);
420     }
421   }
422   assert(!"Invalid color.");
423 }
424 
PrintAt(SDL_Surface * target_screen,TTF_Font * font,int x,int y,const string & s,Color c,int * w=NULL,int * h=NULL,int max_width=-1,bool right_adjusted=false)425 bool PrintAt(SDL_Surface* target_screen, TTF_Font* font,
426              int x, int y, const string& s, Color c,
427              int* w = NULL, int* h = NULL, int max_width = -1,
428              bool right_adjusted = false)
429 {
430   if (s.empty()) {
431     if (w) {
432       *w = 0;
433     }
434     if (h) {
435       *h = 0;
436     }
437     return true;
438   }
439   SDL_Color color = GetSDLColor(c);
440   SDL_Surface* text = TTF_RenderUTF8_Blended(font, s.c_str(), color);
441   if (!text) {
442     cerr << "TTF_RenderUTF8_Blended() failed: " << TTF_GetError() << endl;
443     exit(-1);
444   }
445   if (max_width != -1) {
446     if (text->w > max_width) {
447       SDL_FreeSurface(text);
448       return false;
449     }
450   }
451   SDL_Rect src, dest;
452   src.x = 0;
453   src.y = 0;
454   src.w = text->w;
455   src.h = text->h;
456   if (w) {
457     *w = text->w;
458   }
459   if (h) {
460     *h = text->h;
461   }
462   dest.x = right_adjusted ? x - text->w : x;
463   dest.y = y;
464   dest.w = src.w;
465   dest.h = src.h;
466   SDL_BlitSurface(text, &src, target_screen, &dest);
467   SDL_FreeSurface(text);
468   return true;
469 }
470 
PlaySound(SoundEffects id)471 void PlaySound(SoundEffects id)
472 {
473   if (!sound_effects_on) {
474     return;
475   }
476   for (int i = 0; i < sizeof(sound_effects)/sizeof(*sound_effects); i++) {
477     if (sound_effects[i].id == id) {
478       int channel = Mix_PlayChannel(-1, sound_effects[i].sound, 0);
479       if (channel < 0) {
480         cerr << "Mix_PlayChannel() failed: " << Mix_GetError() << endl;
481         exit(-1);
482       }
483       Mix_Volume(channel, sound_effects[i].volume);
484     }
485   }
486 }
487 
CopyToScratch(int x1,int y1,int x2,int y2)488 void CopyToScratch(int x1, int y1, int x2, int y2)
489 {
490   assert(!scratch_in_use);
491   scratch_x1 = x1;
492   scratch_y1 = y1;
493   scratch_x2 = x2;
494   scratch_y2 = y2;
495   SDL_Rect src, dest;
496   src.x = x1;
497   src.y = y1;
498   src.w = x2 - x1 + 1;
499   src.h = y2 - y1 + 1;
500   dest.x = 0;
501   dest.y = 0;
502   SDL_BlitSurface(game_screen, &src, scratch, &dest);
503   scratch_in_use = true;
504 }
505 
RetrieveFromScratch()506 void RetrieveFromScratch()
507 {
508   assert(scratch_in_use);
509   SDL_Rect src, dest;
510   src.x = 0;
511   src.y = 0;
512   src.w = scratch_x2 - scratch_x1 + 1;
513   src.h = scratch_y2 - scratch_y1 + 1;
514   dest.x = scratch_x1;
515   dest.y = scratch_y1;
516   SDL_BlitSurface(scratch, &src, game_screen, &dest);
517   scratch_in_use = false;
518 }
519 
XLenToScr(int xlen)520 int XLenToScr(int xlen)
521 {
522   return DoubleToInt(1.0 * xlen * BOARD_WIDTH / LOGICAL_BOARD_WIDTH);
523 }
524 
XToScr(int x)525 int XToScr(int x)
526 {
527   return BOARD_X + XLenToScr(x);
528 }
529 
YLenToScr(int ylen)530 int YLenToScr(int ylen)
531 {
532   return DoubleToInt(1.0 * ylen * BOARD_HEIGHT / LOGICAL_BOARD_HEIGHT);
533 }
534 
YToScr(int y)535 int YToScr(int y)
536 {
537   return BOARD_Y + BOARD_HEIGHT - YLenToScr(y);
538 }
539 
Update()540 void Update()
541 {
542   SDL_Rect src, dest;
543   src.x = 0;
544   src.y = 0;
545   src.w = SCREEN_WIDTH;
546   src.h = SCREEN_HEIGHT;
547   dest.x = 0;
548   dest.y = 0;
549   SDL_BlitSurface(*current_screen, &src, physical_screen, &dest);
550   SDL_UpdateRect(physical_screen, 0, 0, 0, 0);
551 }
552 
ClearRect(SDL_Surface * screen,Color color,int x,int y,int w,int h)553 void ClearRect(SDL_Surface* screen, Color color,
554                int x, int y, int w, int h)
555 {
556   SDL_Rect dest;
557   dest.x = x;
558   dest.y = y;
559   dest.w = w;
560   dest.h = h;
561   if (-1 == SDL_FillRect(screen, &dest, GetColor(color))) {
562     cerr << "SDL_FillRect() failed: " << SDL_GetError() << endl;
563     exit(-1);
564   }
565 }
566 
ClearInfoPanel()567 void ClearInfoPanel()
568 {
569   ClearRect(game_screen, INFO_PANEL,
570             INFO_PANEL_X + INFO_PANEL_BORDER_WIDTH,
571             INFO_PANEL_Y + INFO_PANEL_BORDER_WIDTH,
572             INFO_PANEL_WIDTH - 2 * INFO_PANEL_BORDER_WIDTH,
573             INFO_PANEL_HEIGHT - 2 * INFO_PANEL_BORDER_WIDTH);
574 }
575 
DrawChrome()576 void DrawChrome()
577 {
578   ClearRect(game_screen, CHROME, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
579   ClearRect(game_screen, BOARD, BOARD_X, BOARD_Y, BOARD_HEIGHT, BOARD_WIDTH);
580   ClearRect(game_screen, INFO_PANEL_BORDER,
581             INFO_PANEL_X, INFO_PANEL_Y, INFO_PANEL_WIDTH, INFO_PANEL_HEIGHT);
582   ClearInfoPanel();
583 }
584 
SetVideoMode()585 void SetVideoMode()
586 {
587   physical_screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT,
588                                      0, fullscreen_on ? SDL_FULLSCREEN : 0);
589   if (!physical_screen) {
590     cerr << "Unable to set video mode: " << SDL_GetError() << endl;
591     exit(-1);
592   }
593   SDL_Surface* game_screen_tmp = SDL_DisplayFormat(game_screen);
594   if (!game_screen_tmp) {
595     cerr << "SDL_DisplayFormat() failed: " << SDL_GetError() << endl;
596     exit(-1);
597   }
598   SDL_FreeSurface(game_screen);
599   game_screen = game_screen_tmp;
600   SDL_Surface* menu_screen_tmp = SDL_DisplayFormat(menu_screen);
601   if (!menu_screen_tmp) {
602     cerr << "SDL_DisplayFormat() failed: " << SDL_GetError() << endl;
603     exit(-1);
604   }
605   SDL_FreeSurface(menu_screen);
606   menu_screen = menu_screen_tmp;
607 }
608 
LoadSoundEffects()609 void LoadSoundEffects()
610 {
611   for (int i = 0; i < sizeof(sound_effects)/sizeof(*sound_effects); i++) {
612     sound_effects[i].sound = Mix_LoadWAV(sound_effects[i].filename.c_str());
613     if (sound_effects[i].sound == NULL) {
614       cerr << "Unable to load file `" << sound_effects[i].filename << "': "
615            << Mix_GetError() << endl;
616       exit(-1);
617     }
618   }
619 }
620 
AutoRepeatOn()621 void AutoRepeatOn()
622 {
623   if (-1 == SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY,
624                                 SDL_DEFAULT_REPEAT_INTERVAL)) {
625     cerr << "SDL_EnableKeyRepeat() failed: " << SDL_GetError() << endl;
626   }
627 }
628 
AutoRepeatOff()629 void AutoRepeatOff()
630 {
631   if (-1 == SDL_EnableKeyRepeat(0, 0)) {
632     cerr << "SDL_EnableKeyRepeat() failed: " << SDL_GetError() << endl;
633   }
634 }
635 
Init()636 void Init()
637 {
638   CURLcode rc = curl_global_init(CURL_GLOBAL_ALL);
639   if (rc != 0) {
640     cerr << "curl_global_init() failed: " << curl_easy_strerror(rc) << endl;
641   } else {
642     libcurl_successfully_initialized = true;
643   }
644   if (0 != SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) {
645     cerr << "Unable to initialize SDL: " << SDL_GetError() << endl;
646     exit(-1);
647   }
648   atexit(SDL_Quit);
649   SDL_WM_SetIcon(IMG_Load(ICON_FILENAME), NULL);
650   SDL_WM_SetCaption("Kartofel", NULL);
651   if (-1 == TTF_Init()) {
652     cerr << "Unable to initialize SDL_ttf: " << TTF_GetError() << endl;
653     exit(-1);
654   }
655   atexit(TTF_Quit);
656   menu_font = TTF_OpenFont(MENU_FONT_FILENAME, 20);
657   if (!menu_font) {
658     cerr << "Unable to load font `" << MENU_FONT_FILENAME
659          << "': " << TTF_GetError() << endl;
660     exit(-1);
661   }
662   menu_title_font = TTF_OpenFont(MENU_TITLE_FONT_FILENAME, 100);
663   if (!menu_title_font) {
664     cerr << "Unable to load font `" << MENU_TITLE_FONT_FILENAME
665          << "': " << TTF_GetError() << endl;
666     exit(-1);
667   }
668   info_font = TTF_OpenFont(INFO_FONT_FILENAME, 20);
669   if (!info_font) {
670     cerr << "Unable to load font `" << INFO_FONT_FILENAME
671          << "': " << TTF_GetError() << endl;
672     exit(-1);
673   }
674   info_font_small = TTF_OpenFont(INFO_FONT_SMALL_FILENAME, 13);
675   if (!info_font_small) {
676     cerr << "Unable to load font `" << INFO_FONT_SMALL_FILENAME
677          << "': " << TTF_GetError() << endl;
678     exit(-1);
679   }
680   msg_font = TTF_OpenFont(MSG_FONT_FILENAME, 60);
681   if (!msg_font) {
682     cerr << "Unable to load font `" << MSG_FONT_FILENAME
683          << "': " << TTF_GetError() << endl;
684     exit(-1);
685   }
686   if (Mix_OpenAudio(44100, AUDIO_S16SYS, 2, 2048)) {
687     cerr << "Unable to open audio: " << Mix_GetError() << endl;
688     exit(-1);
689   }
690   atexit(Mix_CloseAudio);
691   music = Mix_LoadMUS(MUSIC_FILENAME);
692   if (!music) {
693     cerr << "Unable to load music from file `" << MUSIC_FILENAME << "': "
694          << Mix_GetError() << endl;
695     exit(-1);
696   }
697   LoadSoundEffects();
698   Mix_AllocateChannels(100);
699   menu_background_image = IMG_Load(MENU_BACKGROUND_IMAGE_FILENAME);
700   if (!menu_background_image) {
701     cerr << "Unable to load image from file `"
702          << MENU_BACKGROUND_IMAGE_FILENAME << "': " << IMG_GetError() << endl;
703   }
704 
705   game_screen = SDL_CreateRGBSurface(
706     SDL_SWSURFACE, SCREEN_WIDTH, SCREEN_HEIGHT, 32, 0, 0, 0, 0);
707   if (!game_screen) {
708     cerr << "SDL_CreateRGBSurface() failed: " << SDL_GetError() << endl;
709     exit(-1);
710   }
711   menu_screen = SDL_CreateRGBSurface(
712     SDL_SWSURFACE, SCREEN_WIDTH, SCREEN_HEIGHT, 32, 0, 0, 0, 0);
713   if (!menu_screen) {
714     cerr << "SDL_CreateRGBSurface() failed: " << SDL_GetError() << endl;
715     exit(-1);
716   }
717   scratch = SDL_CreateRGBSurface(
718     SDL_SWSURFACE, SCRATCH_WIDTH, SCRATCH_HEIGHT, 32, 0, 0, 0, 0);
719   if (!scratch) {
720     cerr << "SDL_CreateRGBSurface() failed: " << SDL_GetError() << endl;
721     exit(-1);
722   }
723   SetVideoMode();
724   current_screen = &menu_screen;
725   AutoRepeatOff();
726   LoadHighScoresAndMaxStartingLevel();
727   SDL_initFramerate(&fps_manager);
728   if (-1 == SDL_setFramerate(&fps_manager, 1000 / STEP_MILLISECONDS)) {
729     cerr << "SDL_setFramerate() failed.\n";
730     exit(-1);
731   }
732 }
733 
DrawDot(const Dot & dot,bool lit)734 void DrawDot(const Dot& dot, bool lit)
735 {
736   int xscr = XToScr(dot.x);
737   int yscr = YToScr(dot.y);
738   int rxscr = XLenToScr(dot.r);
739   int ryscr = YLenToScr(dot.r);
740   Uint32 color = GetGfxColor(lit ? DOT_LIT : DOT);
741   int rc = filledEllipseColor(game_screen, xscr, yscr, rxscr, ryscr, color);
742   assert(rc == 0);
743   rc = aaellipseColor(game_screen, xscr, yscr, rxscr - 1, ryscr - 1,
744                       GetGfxColor(DOT_BORDER));
745   rc |= aaellipseColor(game_screen, xscr, yscr, rxscr, ryscr,
746                        GetGfxColor(DOT_BORDER));
747   assert(rc == 0);
748 
749   if (dot.type == REGULAR) {
750     int dot_nr = dot.data.regular.nr;
751     stringstream ss;
752     ss << dot_nr;
753     int x = dot_nr >= 10 ? xscr - 8 : xscr - 3;
754     int y = yscr - 3;
755     rc = stringColor(game_screen, x, y, ss.str().c_str(),
756                      GetGfxColor(DOT_LABEL));
757     assert(rc == 0);
758   } else if (dot.type == EXTRA_LIFE) {
759     int u = dot.r / 12;
760 
761     // Head.
762     int xhead = XToScr(dot.x);
763     int yhead = YToScr(dot.y + 6 * u);
764     int rxhead = XLenToScr(2 * u);
765     int ryhead = YLenToScr(2 * u);
766 
767     // Hands.
768     int xlhf = XToScr(dot.x - 5 * u);
769     int ylhf = YToScr(dot.y);
770     int xlht = XToScr(dot.x);
771     int ylht = YToScr(dot.y + 3 * u);
772 
773     int xrhf = XToScr(dot.x + 5 * u);
774     int yrhf = YToScr(dot.y);
775     int xrht = XToScr(dot.x);
776     int yrht = YToScr(dot.y + 3 * u);
777 
778     // Neck and torso.
779     int xtf = XToScr(dot.x);
780     int ytf = YToScr(dot.y + 4 * u);
781     int xtt = XToScr(dot.x);
782     int ytt = YToScr(dot.y - u);
783 
784     // Legs.
785     int xllf = XToScr(dot.x - 4 * u);
786     int yllf = YToScr(dot.y - 8 * u);
787     int xllt = XToScr(dot.x);
788     int yllt = YToScr(dot.y - u);
789 
790     int xrlf = XToScr(dot.x + 4 * u);
791     int yrlf = YToScr(dot.y - 8 * u);
792     int xrlt = XToScr(dot.x);
793     int yrlt = YToScr(dot.y - u);
794 
795     rc = aaellipseColor(game_screen, xhead, yhead, rxhead, ryhead,
796                         GetGfxColor(DOT_EXTRA_LIFE_PICTURE));
797     assert(rc == 0);
798     rc = aalineColor(game_screen, xlhf, ylhf, xlht, ylht,
799                      GetGfxColor(DOT_EXTRA_LIFE_PICTURE));
800     assert(rc == 0);
801     rc = aalineColor(game_screen, xrhf, yrhf, xrht, yrht,
802                      GetGfxColor(DOT_EXTRA_LIFE_PICTURE));
803     assert(rc == 0);
804     rc = aalineColor(game_screen, xllf, yllf, xllt, yllt,
805                      GetGfxColor(DOT_EXTRA_LIFE_PICTURE));
806     assert(rc == 0);
807     rc = aalineColor(game_screen, xrlf, yrlf, xrlt, yrlt,
808                      GetGfxColor(DOT_EXTRA_LIFE_PICTURE));
809     assert(rc == 0);
810     rc = aalineColor(game_screen, xtf, ytf, xtt, ytt,
811                      GetGfxColor(DOT_EXTRA_LIFE_PICTURE));
812     assert(rc == 0);
813   }
814 }
815 
DrawLine(int x1,int y1,int x2,int y2,bool thick=true)816 void DrawLine(int x1, int y1, int x2, int y2, bool thick = true)
817 {
818   int x1scr = XToScr(x1);
819   int y1scr = YToScr(y1);
820   int x2scr = XToScr(x2);
821   int y2scr = YToScr(y2);
822   Uint32 color = GetGfxColor(LINE);
823   int rc = aalineColor(game_screen, x1scr, y1scr, x2scr, y2scr, color);
824   if (thick) {
825     rc |= aalineColor(game_screen, x1scr + 1, y1scr, x2scr + 1, y2scr, color);
826     rc |= aalineColor(game_screen, x1scr, y1scr + 1, x2scr, y2scr + 1, color);
827   }
828   assert(rc == 0);
829 }
830 
DrawLevel(const Level & level)831 void DrawLevel(const Level& level)
832 {
833   DrawChrome();
834   int x1, y1, x2, y2;
835   level.lines.Reset();
836   while (level.lines.GetNext(&x1, &y1, &x2, &y2)) {
837     DrawLine(x1, y1, x2, y2);
838   }
839   bool first_regular_found = false;
840   for (int i = 0; i < level.dots.size(); i++) {
841     bool lit = false;
842     if (level.dots[i].type == REGULAR && !first_regular_found) {
843       lit = true;
844       first_regular_found = true;
845     }
846     DrawDot(level.dots[i], lit);
847   }
848   //Update();
849 }
850 
ReadEvents(Input * input,bool * pause)851 void ReadEvents(Input* input, bool* pause)
852 {
853   //cout << "ReadEvents()\n";
854   SDL_Event e;
855   while (SDL_PollEvent(&e)) {
856     if (e.type == SDL_QUIT) {
857       cout << "Bye.\n";
858       Exit();
859     }
860   }
861   Uint8* keys = SDL_GetKeyState(NULL);
862   *pause = keys[SDLK_ESCAPE];
863   input->accelerate = keys[key_accelerate];
864   input->deccelerate = keys[key_deccelerate];
865   input->left = keys[key_left];
866   input->right = keys[key_right];
867   input->start = keys[key_start];
868   //cout << "Input: { " << "acc: " << input->accelerate
869   //     << ", decc: " << input->deccelerate
870   //     << ", left: " << input->left << ", right: " << input->right
871   //     << ", start: " << input->start << " }\n";
872 }
873 
HideStarter()874 void HideStarter()
875 {
876   RetrieveFromScratch();
877 }
878 
ShowStarter(const Game & game)879 void ShowStarter(const Game& game)
880 {
881   int x1 = game.GetX();
882   int y1 = game.GetY();
883   int phi = game.GetPhi();
884   int x2 = x1 + DoubleToInt(STARTER_LENGTH * cos(2 * PI * phi / FULL_ANGLE));
885   int y2 = y1 + DoubleToInt(STARTER_LENGTH * sin(2 * PI * phi / FULL_ANGLE));
886   bool is_intersection, is_dot_hit;
887   game.CheckDotHitOrIntersection(x1, y1, &x2, &y2, true,
888                                  &is_intersection, &is_dot_hit, NULL);
889   int x1scr = XToScr(x1);
890   int y1scr = YToScr(y1);
891   int x2scr = XToScr(x2);
892   int y2scr = YToScr(y2);
893   if (x1scr > x2scr) {
894     swap(x1scr, x2scr);
895   }
896   if (y1scr > y2scr) {
897     swap(y1scr, y2scr);
898   }
899   CopyToScratch(x1scr, y1scr, x2scr + 1, y2scr + 1);
900   DrawLine(x1, y1, x2, y2, !is_intersection && !is_dot_hit);
901 }
902 
903 struct EndLevel {
904   bool success;
905   bool death;
906 };
907 
MsgOutOfTime()908 void MsgOutOfTime()
909 {
910   ClearRect(game_screen, MSG_BORDER,
911             OUT_OF_TIME_MSG_BG_X, OUT_OF_TIME_MSG_BG_Y,
912             OUT_OF_TIME_MSG_BG_WIDTH, OUT_OF_TIME_MSG_BG_HEIGHT);
913   ClearRect(game_screen, OUT_OF_TIME_MSG_BACKGROUND,
914             OUT_OF_TIME_MSG_BG_X + MSG_BORDER_WIDTH,
915             OUT_OF_TIME_MSG_BG_Y + MSG_BORDER_WIDTH,
916             OUT_OF_TIME_MSG_BG_WIDTH - 2 * MSG_BORDER_WIDTH,
917             OUT_OF_TIME_MSG_BG_HEIGHT - 2 * MSG_BORDER_WIDTH);
918   PrintAt(game_screen, msg_font,
919           OUT_OF_TIME_MSG_BG_X + OUT_OF_TIME_MSG_X,
920           OUT_OF_TIME_MSG_BG_Y + OUT_OF_TIME_MSG_Y,
921           "OUT OF TIME", OUT_OF_TIME_MSG);
922 }
923 
MsgGameOver()924 void MsgGameOver()
925 {
926   ClearRect(game_screen, MSG_BORDER,
927             GAME_OVER_MSG_BG_X, GAME_OVER_MSG_BG_Y,
928             GAME_OVER_MSG_BG_WIDTH, GAME_OVER_MSG_BG_HEIGHT);
929   ClearRect(game_screen, GAME_OVER_MSG_BACKGROUND,
930             GAME_OVER_MSG_BG_X + MSG_BORDER_WIDTH,
931             GAME_OVER_MSG_BG_Y + MSG_BORDER_WIDTH,
932             GAME_OVER_MSG_BG_WIDTH - 2 * MSG_BORDER_WIDTH,
933             GAME_OVER_MSG_BG_HEIGHT - 2 * MSG_BORDER_WIDTH);
934   PrintAt(game_screen, msg_font,
935           GAME_OVER_MSG_BG_X + GAME_OVER_MSG_X,
936           GAME_OVER_MSG_BG_Y + GAME_OVER_MSG_Y,
937           "GAME OVER", GAME_OVER_MSG);
938 }
939 
MsgGameSuccess()940 void MsgGameSuccess()
941 {
942   ClearRect(game_screen, MSG_BORDER,
943             GAME_SUCCESS_MSG_BG_X, GAME_SUCCESS_MSG_BG_Y,
944             GAME_SUCCESS_MSG_BG_WIDTH, GAME_SUCCESS_MSG_BG_HEIGHT);
945   ClearRect(game_screen, GAME_SUCCESS_MSG_BACKGROUND,
946             GAME_SUCCESS_MSG_BG_X + MSG_BORDER_WIDTH,
947             GAME_SUCCESS_MSG_BG_Y + MSG_BORDER_WIDTH,
948             GAME_SUCCESS_MSG_BG_WIDTH - 2 * MSG_BORDER_WIDTH,
949             GAME_SUCCESS_MSG_BG_HEIGHT - 2 * MSG_BORDER_WIDTH);
950   PrintAt(game_screen, msg_font,
951           GAME_SUCCESS_MSG_BG_X + GAME_SUCCESS_MSG_X,
952           GAME_SUCCESS_MSG_BG_Y + GAME_SUCCESS_MSG_Y,
953           "CONGRATULATIONS!", GAME_SUCCESS_MSG);
954   PrintAt(game_screen, info_font,
955           GAME_SUCCESS_MSG_BG_X + GAME_SUCCESS_MSG_X + 210,
956           GAME_SUCCESS_MSG_BG_Y + GAME_SUCCESS_MSG_Y + 140,
957           "You have completed all levels.", GAME_SUCCESS_MSG);
958   PrintAt(game_screen, info_font,
959           GAME_SUCCESS_MSG_BG_X + GAME_SUCCESS_MSG_X + 210,
960           GAME_SUCCESS_MSG_BG_Y + GAME_SUCCESS_MSG_Y + 170,
961           "You are the master.", GAME_SUCCESS_MSG);
962   PrintAt(game_screen, info_font,
963           GAME_SUCCESS_MSG_BG_X + GAME_SUCCESS_MSG_X + 210,
964           GAME_SUCCESS_MSG_BG_Y + GAME_SUCCESS_MSG_Y + 200,
965           "Thank you for playing Kartofel.", GAME_SUCCESS_MSG);
966   PrintAt(game_screen, info_font,
967           GAME_SUCCESS_MSG_BG_X + GAME_SUCCESS_MSG_X + 210,
968           GAME_SUCCESS_MSG_BG_Y + GAME_SUCCESS_MSG_Y + 230,
969           "Press Enter to continue.", GAME_SUCCESS_MSG);
970 }
971 
ProcessAction(const Game & game,const Action & a,bool * starter_shown,EndLevel * end_level,bool * dot_lit)972 void ProcessAction(const Game& game, const Action& a,
973                    bool* starter_shown, EndLevel* end_level, bool* dot_lit)
974 {
975   //DebugPrintAction(a);
976   switch (a.type) {
977   case DRAW:
978     DrawLine(a.data.draw.start_x, a.data.draw.start_y,
979              a.data.draw.end_x, a.data.draw.end_y);
980     break;
981   case END_LEVEL:
982     end_level->success = true;
983     break;
984   case EXPLOSION:
985     PlaySound(SOUND_CRASH);
986     end_level->death = true;
987     break;
988   case OUT_OF_TIME:
989     PlaySound(SOUND_OUT_OF_TIME);
990     MsgOutOfTime();
991     end_level->death = true;
992     break;
993   case HIDE_STARTER:
994     HideStarter();
995     *starter_shown = false;
996     break;
997   case LIGHT_DOT:
998     Dot dot;
999     game.GetDot(a.data.dot.dot_index, &dot);
1000     DrawDot(dot, true);
1001     *dot_lit = true;
1002     break;
1003   case SHOW_STARTER:
1004     if (*starter_shown) {
1005       HideStarter();
1006     }
1007     ShowStarter(game);
1008     *starter_shown = true;
1009     break;
1010   default:
1011     break;
1012   }
1013 }
1014 
WaitForKey(int * unicode=NULL,string * name=NULL)1015 int WaitForKey(int* unicode = NULL, string* name = NULL)
1016 {
1017   int old_u;
1018   if (unicode) {
1019     old_u = SDL_EnableUNICODE(1);
1020   }
1021   SDL_Event e;
1022   for (;;) {
1023     if (0 == SDL_WaitEvent(&e)) {
1024       cerr <<  "Error.\n";
1025       exit(1);
1026     }
1027     switch (e.type) {
1028     case SDL_QUIT:
1029       cout << "Bye.\n";
1030       Exit();
1031     case SDL_KEYDOWN:
1032       //cout << "Key pressed: " << SDL_GetKeyName(e.key.keysym.sym) << endl;
1033       if (unicode) {
1034         *unicode = e.key.keysym.unicode;
1035         SDL_EnableUNICODE(old_u);
1036       }
1037       if (name) {
1038         *name = SDL_GetKeyName(e.key.keysym.sym);
1039       }
1040       return e.key.keysym.sym;
1041     default:
1042       break;
1043     }
1044   }
1045 }
1046 
1047 enum PlayLevelResult {
1048   LEVEL_SUCCESS = 0,
1049   LEVEL_DEATH = 1,
1050   LEVEL_PAUSE = 2,
1051 };
1052 
FormattedTimeLeft(int time_left,int * tlms_out=NULL)1053 string FormattedTimeLeft(int time_left, int* tlms_out = NULL)
1054 {
1055   int tlms = time_left * STEP_MILLISECONDS / STEP_TS;
1056   if (tlms_out) {
1057     *tlms_out = tlms;
1058   }
1059   int tlsec = tlms / 1000;
1060   int tlmin = tlsec / 60;
1061   char buf[100];
1062   snprintf(buf, sizeof(buf), "%02d:%02d.%d",
1063            tlmin, tlsec - tlmin * 60, (tlms - tlsec * 1000) / 100);
1064   return buf;
1065 }
1066 
PrintInfo(const Game & game)1067 void PrintInfo(const Game& game)
1068 {
1069   ClearInfoPanel();
1070   stringstream ss;
1071   ss << "Level: " << game.GetLevelNum();
1072   PrintAt(game_screen, info_font,
1073           INFO_PANEL_X + INFO_LEVEL_X,
1074           INFO_PANEL_Y + INFO_LEVEL_Y,
1075           ss.str(),
1076           INFO_PANEL_TEXT);
1077   ss.str("");
1078   ss << "Score: " << game.GetScore();
1079   PrintAt(game_screen, info_font,
1080           INFO_PANEL_X + INFO_SCORE_X,
1081           INFO_PANEL_Y + INFO_SCORE_Y,
1082           ss.str(),
1083           INFO_PANEL_TEXT);
1084   ss.str("");
1085   ss << "Time left: " << FormattedTimeLeft(game.GetTimeLeft());
1086   PrintAt(game_screen, info_font,
1087           INFO_PANEL_X + INFO_TIME_LEFT_X,
1088           INFO_PANEL_Y + INFO_TIME_LEFT_Y,
1089           ss.str(),
1090           INFO_PANEL_TEXT);
1091   ss.str("");
1092   ss << "Speed: " << game.GetSpeed();
1093   PrintAt(game_screen, info_font,
1094           INFO_PANEL_X + INFO_SPEED_X,
1095           INFO_PANEL_Y + INFO_SPEED_Y,
1096           ss.str(),
1097           INFO_PANEL_TEXT);
1098   ss.str("");
1099   ss << "Lives: " << game.GetLives();
1100   PrintAt(game_screen, info_font,
1101           INFO_PANEL_X + INFO_LIVES_X,
1102           INFO_PANEL_Y + INFO_LIVES_Y,
1103           ss.str(),
1104           INFO_PANEL_TEXT);
1105 }
1106 
PlayLevel(Game * game)1107 PlayLevelResult PlayLevel(Game* game)
1108 {
1109   Input input = { false, false, false, false, false };
1110   int timestamp = game->GetCurrentTimestamp();
1111   bool starter_shown = game->StarterShown();
1112   PrintInfo(*game);
1113   Update();
1114   if (game->AtLevelStart()) {
1115     // In case someone pressed the "Go" key after finishing level, we
1116     // don't want to interpret the still-pressed key as input and start
1117     // immediately.
1118     WaitForKey();
1119   }
1120   int old_phi = game->GetPhi();
1121   for (;;) {
1122     bool old_starter_shown = starter_shown;
1123     //SDL_Delay(STEP_MILLISECONDS);
1124     SDL_framerateDelay(&fps_manager);
1125     bool pause;
1126     ReadEvents(&input, &pause);
1127     if (pause) {
1128       return LEVEL_PAUSE;
1129     }
1130     timestamp += STEP_TS;
1131     //cout << "timestamp: " << timestamp << endl;
1132     //DebugPrintInput(input);
1133     saved_game.AddStep(input);
1134     vector<Action> actions;
1135     game->Advance(input, timestamp, &actions);
1136     bool dot_lit = false;
1137     EndLevel end_level = { false, false };
1138     for (int i = 0; i < actions.size(); i++) {
1139       ProcessAction(*game, actions[i], &starter_shown, &end_level, &dot_lit);
1140     }
1141     if (old_phi != game->GetPhi() && starter_shown) {
1142       PlaySound(SOUND_ROTATE);
1143     }
1144     if (old_starter_shown && !starter_shown) {
1145       PlaySound(SOUND_START);
1146     }
1147     if (dot_lit /* && !end_level.succes */) {
1148       PlaySound(SOUND_DOT_LIT);
1149     }
1150     PrintInfo(*game);
1151     Update();
1152     if (end_level.success) {
1153       PlaySound(SOUND_LEVEL_SUCCESS);
1154       return LEVEL_SUCCESS;
1155     } else if (end_level.death) {
1156       return LEVEL_DEATH;
1157     }
1158     old_phi = game->GetPhi();
1159   }
1160 }
1161 
1162 enum PlayGameResult {
1163   GAME_SUCCESS = 0,
1164   GAME_DEATH = 1,
1165   GAME_PAUSE = 2,
1166 };
1167 
WaitForEnterOrEsc()1168 int WaitForEnterOrEsc()
1169 {
1170   for (;;) {
1171     int key = WaitForKey();
1172     if (key == SDLK_RETURN || key == SDLK_ESCAPE) {
1173       return key;
1174     }
1175   }
1176 }
1177 
InitializeLevel(Game * game,int level)1178 void InitializeLevel(Game* game, int level)
1179 {
1180   // Levels in game are numbered from 1, but indices in the "levels"
1181   // array from 0.
1182   game->SetLevel(levels[level - 1], level);
1183   DrawLevel(levels[level - 1]);
1184   scratch_in_use = false;
1185   ShowStarter(*game);
1186 }
1187 
CheckForKey(int * key)1188 bool CheckForKey(int* key)
1189 {
1190   SDL_Event e;
1191   while (SDL_PollEvent(&e)) {
1192     if (e.type == SDL_QUIT) {
1193       cout << "Bye.\n";
1194       Exit();
1195     }
1196     if (e.type == SDL_KEYDOWN) {
1197       *key = e.key.keysym.sym;
1198       return true;
1199     }
1200   }
1201   return false;
1202 }
1203 
WaitForKeyWithFlashing(const Game * game)1204 int WaitForKeyWithFlashing(const Game* game)
1205 {
1206   const Level& level = levels[game->GetLevelNum() - 1];
1207   int key = SDLK_RETURN;
1208   for (int i = 0; i < 10; i++) {
1209     for (int j = 0; j < 2; j++) {
1210       for (int k = 0; k < level.dots.size(); k++) {
1211         Dot dot;
1212         game->GetDot(k, &dot);
1213         DrawDot(level.dots[k], j == 0 && dot.lit);
1214       }
1215       Update();
1216       for (int k = 0; k < 10; k++) {
1217         if (CheckForKey(&key)) {
1218           goto end;
1219         }
1220         SDL_Delay(FLASHING_HALF_PERIOD);
1221       }
1222     }
1223   }
1224 end:
1225   for (int i = 0; i < level.dots.size(); i++) {
1226     Dot dot;
1227     game->GetDot(i, &dot);
1228     DrawDot(level.dots[i], dot.lit);
1229   }
1230   Update();
1231   return key;
1232 }
1233 
MsgEndLevelBonus(const string & msg_level_multiplier,const string & msg_time_left,const string & msg_minimum_speed_used,const string & msg_bonus)1234 void MsgEndLevelBonus(const string& msg_level_multiplier,
1235                       const string& msg_time_left,
1236                       const string& msg_minimum_speed_used,
1237                       const string& msg_bonus)
1238 {
1239   ClearRect(game_screen, MSG_BORDER,
1240             END_LEVEL_BONUS_MSG_BG_X, END_LEVEL_BONUS_MSG_BG_Y,
1241             END_LEVEL_BONUS_MSG_BG_WIDTH, END_LEVEL_BONUS_MSG_BG_HEIGHT);
1242   ClearRect(game_screen, END_LEVEL_BONUS_MSG_BACKGROUND,
1243             END_LEVEL_BONUS_MSG_BG_X + MSG_BORDER_WIDTH,
1244             END_LEVEL_BONUS_MSG_BG_Y + MSG_BORDER_WIDTH,
1245             END_LEVEL_BONUS_MSG_BG_WIDTH - 2 * MSG_BORDER_WIDTH,
1246             END_LEVEL_BONUS_MSG_BG_HEIGHT - 2 * MSG_BORDER_WIDTH);
1247   PrintAt(game_screen, info_font,
1248           END_LEVEL_BONUS_MSG_BG_X + END_LEVEL_BONUS_MSG_X,
1249           END_LEVEL_BONUS_MSG_BG_Y + END_LEVEL_BONUS_MSG_LEVEL_MULTIPLIER_Y,
1250           msg_level_multiplier, END_LEVEL_BONUS_MSG);
1251   PrintAt(game_screen, info_font,
1252           END_LEVEL_BONUS_MSG_BG_X + END_LEVEL_BONUS_MSG_X,
1253           END_LEVEL_BONUS_MSG_BG_Y + END_LEVEL_BONUS_MSG_TIME_LEFT_Y,
1254           msg_time_left, END_LEVEL_BONUS_MSG);
1255   PrintAt(game_screen, info_font,
1256           END_LEVEL_BONUS_MSG_BG_X + END_LEVEL_BONUS_MSG_X,
1257           END_LEVEL_BONUS_MSG_BG_Y + END_LEVEL_BONUS_MSG_MINIMUM_SPEED_USED_Y,
1258           msg_minimum_speed_used, END_LEVEL_BONUS_MSG);
1259   PrintAt(game_screen, info_font,
1260           END_LEVEL_BONUS_MSG_BG_X + END_LEVEL_BONUS_MSG_X,
1261           END_LEVEL_BONUS_MSG_BG_Y + END_LEVEL_BONUS_MSG_BONUS_Y,
1262           msg_bonus, END_LEVEL_BONUS_MSG);
1263 }
1264 
EndLevelBonus(Game * game,bool show_msg)1265 int EndLevelBonus(Game* game, bool show_msg)
1266 {
1267   int min_speed = game->GetMinimumSpeedUsed();
1268   int tlms;
1269   string time_left_str = FormattedTimeLeft(game->GetTimeLeft(), &tlms);
1270   int tlds = tlms / 100;
1271   int level_multiplier = game->GetLevelMultiplier();
1272   int bonus = level_multiplier * tlds * min_speed * min_speed;
1273   game->AddScore(bonus);
1274   if (show_msg) {
1275     char msg_level_multiplier[1000];
1276     snprintf(msg_level_multiplier, sizeof(msg_level_multiplier),
1277              "Level multiplier: %d", game->GetLevelMultiplier());
1278     int tlsec = tlds / 10;
1279     char msg_time_left[1000];
1280     if (tlds > 600) {
1281       snprintf(msg_time_left, sizeof(msg_time_left),
1282                "Time left: %s = %d.%d seconds", time_left_str.c_str(),
1283                tlsec, tlds - tlsec * 10);
1284     } else {
1285       snprintf(msg_time_left, sizeof(msg_time_left),
1286                "Time left: %d.%d seconds", tlsec, tlds - tlsec * 10);
1287     }
1288     char msg_minimum_speed_used[1000];
1289     snprintf(msg_minimum_speed_used, sizeof(msg_minimum_speed_used),
1290              "Minimum speed used: %d", min_speed);
1291     char msg_bonus[1000];
1292     snprintf(
1293       msg_bonus, sizeof(msg_bonus),
1294       "Bonus: %d \xc3\x97 %d.%d \xc3\x97 %d \xc3\x97 %d \xc3\x97 10 = %d",
1295       level_multiplier, tlsec, tlds - tlsec * 10, min_speed, min_speed, bonus);
1296     MsgEndLevelBonus(msg_level_multiplier, msg_time_left,
1297                      msg_minimum_speed_used, msg_bonus);
1298     PrintInfo(*game);
1299     Update();
1300     return WaitForKey();
1301   }
1302   return SDLK_RETURN;
1303 }
1304 
PlayGame(Game * game,int starting_level=1)1305 PlayGameResult PlayGame(Game* game, int starting_level = 1)
1306 {
1307   assert(starting_level > 0 && starting_level <= NUM_LEVELS);
1308   if (!game->Initialized()) {
1309     InitializeLevel(game, starting_level);
1310   }
1311   for (;;) {
1312     PlayLevelResult res = PlayLevel(game);
1313     if (res == LEVEL_PAUSE) {
1314       return GAME_PAUSE;
1315     } else if (res == LEVEL_SUCCESS) {
1316       int current_level = game->GetLevelNum();
1317       if (current_level == NUM_LEVELS) {
1318         WaitForKeyWithFlashing(game);
1319         EndLevelBonus(game, false);
1320         return GAME_SUCCESS;
1321       } else {
1322         int key = WaitForKeyWithFlashing(game);
1323         int key2 = EndLevelBonus(game, key != SDLK_ESCAPE);
1324         current_level++;
1325         if (current_level > max_starting_level) {
1326           max_starting_level = current_level;
1327         }
1328         InitializeLevel(game, current_level);
1329         if (key == SDLK_ESCAPE || key2 == SDLK_ESCAPE) {
1330           return GAME_PAUSE;
1331         }
1332       }
1333     } else if (res == LEVEL_DEATH) {
1334       if (game->GetLives() == 0) {
1335         return GAME_DEATH;
1336       }
1337       int key = WaitForKey();
1338       InitializeLevel(game, game->GetLevelNum());
1339       if (key == SDLK_ESCAPE) {
1340         return GAME_PAUSE;
1341       }
1342     }
1343   }
1344 }
1345 
1346 // ====
1347 
DefineOneKey(const string & prompt,int y,int * k)1348 bool DefineOneKey(const string& prompt, int y, int* k)
1349 {
1350   PrintAt(menu_screen, info_font,
1351           DEFINE_KEYS_BG_X + DEFINE_KEYS_PROMPT_X,
1352           DEFINE_KEYS_BG_Y + y,
1353           prompt, DEFINE_KEYS_TEXT);
1354   Update();
1355   string key_name;
1356   int key = WaitForKey(NULL, &key_name);
1357   if (key == SDLK_ESCAPE) {
1358     return false;
1359   }
1360   *k = key;
1361   transform(key_name.begin(), key_name.end(), key_name.begin(), ::toupper);
1362   PrintAt(menu_screen, info_font,
1363           DEFINE_KEYS_BG_X + DEFINE_KEYS_KEY_X,
1364           DEFINE_KEYS_BG_Y + y,
1365           key_name,
1366           DEFINE_KEYS_TEXT,
1367           NULL, NULL, -1, true);
1368   PlaySound(SOUND_MENU_NAV);
1369   Update();
1370 
1371   return true;
1372 }
1373 
DefineKeys()1374 void DefineKeys()
1375 {
1376   int key_left_local, key_right_local, key_accelerate_local,
1377     key_deccelerate_local, key_start_local;
1378 
1379   ClearRect(menu_screen, MSG_BORDER,
1380             DEFINE_KEYS_BG_X, DEFINE_KEYS_BG_Y,
1381             DEFINE_KEYS_BG_WIDTH, DEFINE_KEYS_BG_HEIGHT);
1382   ClearRect(menu_screen, DEFINE_KEYS_BACKGROUND,
1383             DEFINE_KEYS_BG_X + MSG_BORDER_WIDTH,
1384             DEFINE_KEYS_BG_Y + MSG_BORDER_WIDTH,
1385             DEFINE_KEYS_BG_WIDTH - 2 * MSG_BORDER_WIDTH,
1386             DEFINE_KEYS_BG_HEIGHT - 2 * MSG_BORDER_WIDTH);
1387 
1388   PrintAt(menu_screen, msg_font,
1389           DEFINE_KEYS_BG_X + DEFINE_KEYS_TITLE_X,
1390           DEFINE_KEYS_BG_Y + DEFINE_KEYS_TITLE_Y,
1391           "Define Keys", DEFINE_KEYS_TITLE);
1392 
1393   if (!DefineOneKey("Turn left:", DEFINE_KEYS_LEFT_Y, &key_left_local)) {
1394     return;
1395   }
1396   if (!DefineOneKey("Turn right:", DEFINE_KEYS_RIGHT_Y, &key_right_local)) {
1397     return;
1398   }
1399   if (!DefineOneKey("Speed up:", DEFINE_KEYS_ACCELERATE_Y,
1400                     &key_accelerate_local)) {
1401     return;
1402   }
1403   if (!DefineOneKey("Slow down:", DEFINE_KEYS_DECCELERATE_Y,
1404                     &key_deccelerate_local)) {
1405     return;
1406   }
1407   if (!DefineOneKey("Go:", DEFINE_KEYS_START_Y, &key_start_local)) {
1408     return;
1409   }
1410   PrintAt(menu_screen, info_font,
1411           DEFINE_KEYS_BG_X + DEFINE_KEYS_FOOTER_X,
1412           DEFINE_KEYS_BG_Y + DEFINE_KEYS_FOOTER_Y,
1413           "Press Enter to confirm or Escape to cancel.",
1414           DEFINE_KEYS_TEXT);
1415   Update();
1416 
1417   int key = WaitForEnterOrEsc();
1418   if (key == SDLK_RETURN) {
1419     PlaySound(SOUND_MENU_CLICK);
1420     key_left = key_left_local;
1421     key_right = key_right_local;
1422     key_accelerate = key_accelerate_local;
1423     key_deccelerate = key_deccelerate_local;
1424     key_start = key_start_local;
1425   }
1426 }
1427 
ToggleFullscreen()1428 void ToggleFullscreen()
1429 {
1430   fullscreen_on = !fullscreen_on;
1431   SetVideoMode();
1432   Update();
1433 }
1434 
ToggleMusic()1435 void ToggleMusic()
1436 {
1437   music_on = !music_on;
1438   if (music_on) {
1439     Mix_ResumeMusic();
1440   } else {
1441     Mix_PauseMusic();
1442   }
1443 }
1444 
ToggleSoundEffects()1445 void ToggleSoundEffects()
1446 {
1447   sound_effects_on = !sound_effects_on;
1448 }
1449 
TogglePublishScores()1450 void TogglePublishScores()
1451 {
1452   publish_scores = !publish_scores;
1453 }
1454 
ShowHelp()1455 void ShowHelp()
1456 {
1457 
1458 }
1459 
HighScoreTable()1460 HighScoreTable::HighScoreTable()
1461 {
1462   for (int i = 0; i < NUM_HIGH_SCORES; i++) {
1463     names.push_back("..........");
1464     scores.push_back(0);
1465   }
1466 }
1467 
Get(int nr,string * name,int * score)1468 void HighScoreTable::Get(int nr, string* name, int* score)
1469 {
1470   assert(nr >= 0 && nr < NUM_HIGH_SCORES);
1471   *name = names[nr];
1472   *score = scores[nr];
1473 }
1474 
IsHighEnough(int score)1475 bool HighScoreTable::IsHighEnough(int score)
1476 {
1477   return score > scores.back();
1478 }
1479 
Add(const string & name,int score)1480 void HighScoreTable::Add(const string& name, int score)
1481 {
1482   int i;
1483   for (i = scores.size() - 1; i > 0 && scores[i - 1] < score; i--) {
1484     names[i] = names[i - 1];
1485     scores[i] = scores[i - 1];
1486   }
1487   names[i] = name;
1488   scores[i] = score;
1489 }
1490 
Show(SDL_Surface * screen)1491 void HighScoreTable::Show(SDL_Surface* screen)
1492 {
1493   ClearRect(screen, MSG_BORDER,
1494             HIGH_SCORES_BG_X, HIGH_SCORES_BG_Y,
1495             HIGH_SCORES_BG_WIDTH, HIGH_SCORES_BG_HEIGHT);
1496   ClearRect(screen, HIGH_SCORES_BACKGROUND,
1497             HIGH_SCORES_BG_X + MSG_BORDER_WIDTH,
1498             HIGH_SCORES_BG_Y + MSG_BORDER_WIDTH,
1499             HIGH_SCORES_BG_WIDTH - 2 * MSG_BORDER_WIDTH,
1500             HIGH_SCORES_BG_HEIGHT - 2 * MSG_BORDER_WIDTH);
1501 
1502   PrintAt(screen, msg_font,
1503           HIGH_SCORES_BG_X + HIGH_SCORES_TITLE_X,
1504           HIGH_SCORES_BG_Y + HIGH_SCORES_TITLE_Y,
1505           "High Scores", HIGH_SCORES_TITLE);
1506 
1507   int x = HIGH_SCORES_TEXT_X;
1508   int y = HIGH_SCORES_TEXT_Y;
1509   for (int i = 0; i < scores.size(); i++) {
1510     stringstream ss;
1511     ss << (i + 1) << ". ";
1512     PrintAt(screen, info_font,
1513             HIGH_SCORES_BG_X + x, HIGH_SCORES_BG_Y + y,
1514             ss.str(), HIGH_SCORES_TEXT, NULL, NULL, -1, true);
1515     PrintAt(screen, info_font,
1516             HIGH_SCORES_BG_X + x, HIGH_SCORES_BG_Y + y,
1517             names[i], HIGH_SCORES_TEXT);
1518     ss.str("");
1519     ss << scores[i];
1520     PrintAt(screen, info_font,
1521             HIGH_SCORES_BG_X + x + MAX_NAME_WIDTH + 120,
1522             HIGH_SCORES_BG_Y + y, ss.str(), HIGH_SCORES_TEXT,
1523             NULL, NULL, -1, true);
1524     y += 40;
1525   }
1526 }
1527 
1528 // - Start New Game (Level ...)
1529 // [Not always] - Continue Game
1530 // - Define Keys
1531 // - High Scores
1532 // - Fullscreen on/off
1533 // - Music on/off
1534 // - Sound Effects on/off
1535 // - Publish Scores yes/no
1536 // - Help
1537 // - Quit
1538 class Menu {
1539 public:
1540   Menu();
1541   void EnableContinue();
1542   void DisableContinue();
1543   int GetStartingLevel() const;
1544 
1545   enum Choice {
1546     CHOICE_NEW_GAME,
1547     CHOICE_CONTINUE,
1548   };
1549 
1550   Choice Run();
1551 
1552 private:
1553   enum Item {
1554     NEW_GAME = 0,
1555     CONTINUE_GAME = 1,
1556     DEFINE_KEYS = 2,
1557     HIGH_SCORES = 3,
1558     FULLSCREEN = 4,
1559     MUSIC = 5,
1560     SOUND_EFFECTS = 6,
1561     PUBLISH_SCORES = 7,
1562     HELP = 8,
1563     QUIT = 9,
1564     NUM_ITEMS = 10,
1565   };
1566   vector<string> text;
1567   vector<bool> active;
1568   int selected_item;
1569   vector<Point> pos;
1570   vector<int> item_width;
1571   vector<int> item_height;
1572   int starting_level;
1573 
1574   void Draw();
1575   void PrintItem(int item, bool selected, const char* more = NULL);
1576   void PrintItem(int item, Color color, const char* more = NULL);
1577   void EraseItem(int item);
1578 };
1579 
Menu()1580 Menu::Menu()
1581 {
1582   text.push_back("New Game");
1583   text.push_back("Continue Game");
1584   text.push_back("Define Keys");
1585   text.push_back("High Scores");
1586   text.push_back("Fullscreen: ");
1587   text.push_back("Music: ");
1588   text.push_back("Sound Effects: ");
1589   text.push_back("Publish Scores: ");
1590   text.push_back("Help");
1591   text.push_back("Quit");
1592   active.resize(NUM_ITEMS);
1593   for (int i = 0; i < NUM_ITEMS; i++) {
1594     active[i] = true;
1595   }
1596   active[CONTINUE_GAME] = false;
1597   // TODO: change it to true after implementing the ShowHelp() function.
1598   active[HELP] = false;
1599   selected_item = NEW_GAME;
1600   starting_level = 1;
1601 }
1602 
GetStartingLevel() const1603 int Menu::GetStartingLevel() const
1604 {
1605   return starting_level;
1606 }
1607 
EnableContinue()1608 void Menu::EnableContinue()
1609 {
1610   active[CONTINUE_GAME] = true;
1611   selected_item = CONTINUE_GAME;
1612 }
1613 
DisableContinue()1614 void Menu::DisableContinue()
1615 {
1616   active[CONTINUE_GAME] = false;
1617   selected_item = NEW_GAME;
1618 }
1619 
PrintItem(int i,Color color,const char * more)1620 void Menu::PrintItem(int i, Color color, const char* more)
1621 {
1622   string txt = text[i];
1623   if (more) {
1624     txt += more;
1625   } else if (i == FULLSCREEN) {
1626     txt += fullscreen_on ? "ON" : "OFF";
1627   } else if (i == MUSIC) {
1628     txt += music_on ? "ON" : "OFF";
1629   } else if (i == SOUND_EFFECTS) {
1630     txt += sound_effects_on ? "ON" : "OFF";
1631   } else if (i == PUBLISH_SCORES) {
1632     txt += publish_scores ? "YES" : "NO";
1633   } else if (i == NEW_GAME) {
1634     stringstream ss;
1635     ss << " (Level " << starting_level << ")";
1636     txt += ss.str();
1637   }
1638   int w, h;
1639   PrintAt(menu_screen, menu_font, pos[i].x, pos[i].y, txt, color, &w, &h);
1640   item_height[i] = h;
1641   item_width[i] = w;
1642 }
1643 
PrintItem(int i,bool selected,const char * more)1644 void Menu::PrintItem(int i, bool selected, const char* more)
1645 {
1646   PrintItem(i, selected ? SELECTED_MENU_ITEM : MENU_ITEM, more);
1647 }
1648 
EraseItem(int i)1649 void Menu::EraseItem(int i)
1650 {
1651   if (menu_background_image) {
1652     SDL_Rect src, dest;
1653     src.x = dest.x = pos[i].x;
1654     src.y = dest.y = pos[i].y;
1655     src.w = item_width[i];
1656     src.h = item_height[i];
1657     SDL_BlitSurface(menu_background_image, &src, menu_screen, &dest);
1658   } else {
1659     ClearRect(menu_screen, MENU_BACKGROUND, pos[i].x, pos[i].y,
1660               item_width[i], item_height[i]);
1661   }
1662 }
1663 
Draw()1664 void Menu::Draw()
1665 {
1666   if (menu_background_image) {
1667     SDL_BlitSurface(menu_background_image, NULL, menu_screen, NULL);
1668   } else {
1669     ClearRect(menu_screen, MENU_BACKGROUND, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
1670   }
1671   PrintAt(menu_screen, menu_title_font, 170, 50, "KARTOFEL", MENU_TITLE);
1672   int pos_x = 380;
1673   int pos_y = 280;
1674   pos.resize(NUM_ITEMS);
1675   // item_width and item_height are set inside PrintItem().
1676   item_width.resize(NUM_ITEMS);
1677   item_height.resize(NUM_ITEMS);
1678   for (int i = 0; i < NUM_ITEMS; i++) {
1679     if (!active[i]) {
1680       pos[i] = Point();
1681       continue;
1682     }
1683     Point p = { pos_x, pos_y };
1684     pos[i] = p;
1685     PrintItem(i, selected_item == i);
1686     pos_y += 30;
1687   }
1688   PrintAt(
1689     menu_screen, info_font, 50, 720,
1690     "\xc2\xa9 Copyright 2008 Pawe\xc5\x82 Aleksander Fedory\xc5\x84ski.  "
1691     "I dedicate this game to my beloved girlfriend Karen.",
1692     COPYRIGHT);
1693 }
1694 
Run()1695 Menu::Choice Menu::Run()
1696 {
1697   Draw();
1698   for (;;) {
1699     Update();
1700     int key;
1701     for (;;) {
1702       key = WaitForKey();
1703       if (key == SDLK_ESCAPE) {
1704         cout << "Bye.\n";
1705         Exit();
1706       }
1707       if (key == SDLK_LEFT || key == SDLK_RIGHT ||
1708           key == SDLK_UP || key == SDLK_DOWN || key == SDLK_RETURN) {
1709         break;
1710       }
1711     }
1712     if (key == SDLK_LEFT || key == SDLK_RIGHT) {
1713       // Change the starting level if we're on "New Game".
1714       if (selected_item == NEW_GAME) {
1715         if (key == SDLK_LEFT && starting_level > 1) {
1716           starting_level--;
1717         } else if (key == SDLK_RIGHT && starting_level < max_starting_level) {
1718           starting_level++;
1719         } else {
1720           continue;
1721         }
1722         PlaySound(SOUND_MENU_NAV);
1723         EraseItem(selected_item);
1724         PrintItem(selected_item, true);
1725       }
1726       continue;
1727     }
1728     if (key == SDLK_UP  ||  key == SDLK_DOWN) {
1729       PlaySound(SOUND_MENU_NAV);
1730       EraseItem(selected_item);
1731       PrintItem(selected_item, false);
1732       for (;;) {
1733         selected_item += key == SDLK_DOWN ? 1 : -1;
1734         if (selected_item >= NUM_ITEMS) {
1735           selected_item = 0;
1736         } else if (selected_item < 0) {
1737           selected_item = NUM_ITEMS - 1;
1738         }
1739         if (active[selected_item]) {
1740           break;
1741         }
1742       }
1743       PrintItem(selected_item, true);
1744       continue;
1745     }
1746     assert(key == SDLK_RETURN);
1747     if (selected_item != SOUND_EFFECTS) {
1748       PlaySound(SOUND_MENU_CLICK);
1749     }
1750     switch (selected_item) {
1751     case NEW_GAME:
1752       return CHOICE_NEW_GAME;
1753     case CONTINUE_GAME:
1754       return CHOICE_CONTINUE;
1755     case DEFINE_KEYS:
1756       DefineKeys();
1757       Draw();
1758       break;
1759     case HIGH_SCORES:
1760       high_scores.Show(menu_screen);
1761       Update();
1762       WaitForKey();
1763       Draw();
1764       break;
1765     case FULLSCREEN:
1766       EraseItem(FULLSCREEN);
1767       PrintItem(FULLSCREEN, true, fullscreen_on ? "OFF" : "ON");
1768       ToggleFullscreen();
1769       break;
1770     case MUSIC:
1771       EraseItem(MUSIC);
1772       PrintItem(MUSIC, true, music_on ? "OFF" : "ON");
1773       ToggleMusic();
1774       break;
1775     case SOUND_EFFECTS:
1776       EraseItem(SOUND_EFFECTS);
1777       PrintItem(SOUND_EFFECTS, true, sound_effects_on ? "OFF" : "ON");
1778       ToggleSoundEffects();
1779       PlaySound(SOUND_MENU_CLICK);
1780       break;
1781     case PUBLISH_SCORES:
1782       EraseItem(PUBLISH_SCORES);
1783       PrintItem(PUBLISH_SCORES, true,
1784                 publish_scores ? "NO" : "YES");
1785       TogglePublishScores();
1786       PlaySound(SOUND_MENU_CLICK);
1787       break;
1788     case HELP:
1789       ShowHelp();
1790       break;
1791     case QUIT:
1792       cout << "Bye.\n";
1793       Exit();
1794     default:
1795       assert(!"Impossible");
1796     }
1797   }
1798 }
1799 
LoadLevels()1800 void LoadLevels()
1801 {
1802   for (int i = 0; i < NUM_LEVELS; i++) {
1803     stringstream filename;
1804     filename << LEVELS_FILENAME_PREFIX << i + 1 << ".txt";
1805     LoadLevel(filename.str(), &levels[i]);
1806   }
1807 }
1808 
CodeToUTF8(int code)1809 string CodeToUTF8(int code)
1810 {
1811   char res[5] = { 0 };
1812   if (code <= 0x7f) {
1813     res[0] = code;
1814   } else if (code <= 0x7ff) {
1815     res[0] = 0xc0 | (code >> 6);
1816     res[1] = 0x80 | (code & 0x3f);
1817   } else if (code <= 0xffff) {
1818     res[0] = 0xe0 | (code >> 12);
1819     res[1] = 0x80 | ((code >> 6) & 0x3f);
1820     res[2] = 0x80 | (code & 0x3f);
1821   } else {
1822     res[0] = 0xf0 | (code >> 18);
1823     res[1] = 0x80 | ((code >> 12) & 0x3f);
1824     res[2] = 0x80 | ((code >> 6) & 0x3f);
1825     res[3] = 0x80 | (code & 0x3f);
1826   }
1827   return res;
1828 }
1829 
DrawAskNameBG()1830 void DrawAskNameBG()
1831 {
1832   ClearRect(*current_screen, MSG_BORDER,
1833             ENTER_NAME_BG_X, ENTER_NAME_BG_Y,
1834             ENTER_NAME_BG_WIDTH, ENTER_NAME_BG_HEIGHT);
1835   ClearRect(*current_screen, ENTER_NAME_BACKGROUND,
1836             ENTER_NAME_BG_X + MSG_BORDER_WIDTH,
1837             ENTER_NAME_BG_Y + MSG_BORDER_WIDTH,
1838             ENTER_NAME_BG_WIDTH - 2 * MSG_BORDER_WIDTH,
1839             ENTER_NAME_BG_HEIGHT - 2 * MSG_BORDER_WIDTH);
1840 
1841   PrintAt(*current_screen, info_font,
1842           ENTER_NAME_BG_X + ENTER_NAME_PROMPT_X,
1843           ENTER_NAME_BG_Y + ENTER_NAME_PROMPT_Y,
1844           "Please enter your name:", ENTER_NAME_PROMPT);
1845   PrintAt(
1846     *current_screen, info_font_small,
1847     ENTER_NAME_BG_X + ENTER_NAME_NOTE_X,
1848     ENTER_NAME_BG_Y + ENTER_NAME_NOTE_Y,
1849     "If the Publish Scores option is set, your name may"
1850     " be submitted to the Kartofel Hall of Fame page.",
1851     ENTER_NAME_NOTE);
1852 }
1853 
EraseName()1854 void EraseName()
1855 {
1856   DrawAskNameBG();
1857 }
1858 
PrintName(const string & name)1859 bool PrintName(const string& name)
1860 {
1861   return PrintAt(*current_screen, info_font,
1862                  ENTER_NAME_BG_X + ENTER_NAME_X,
1863                  ENTER_NAME_BG_Y + ENTER_NAME_Y,
1864                  name, ENTER_NAME, NULL, NULL,
1865                  MAX_NAME_WIDTH);
1866 }
1867 
AssembleName(const vector<string> & letters)1868 string AssembleName(const vector<string>& letters)
1869 {
1870   string s;
1871   for (int i = 0; i < letters.size(); i++) {
1872     s += letters[i];
1873   }
1874   return s;
1875 }
1876 
AskName(string * name)1877 bool AskName(string* name)
1878 {
1879   // static, so that we start from the name last entered.
1880   static vector<string> name_letters;
1881   DrawAskNameBG();
1882   int key;
1883   int unicode;
1884   for (;;) {
1885     EraseName();
1886     bool ok = PrintName(AssembleName(name_letters) + "_");
1887     if (!ok) {
1888       ok = PrintName(AssembleName(name_letters));
1889       if (!ok) {
1890         name_letters.pop_back();
1891         ok = PrintName(AssembleName(name_letters));
1892         assert(ok);
1893       }
1894     }
1895     Update();
1896     key = WaitForKey(&unicode);
1897     if (key == SDLK_DELETE || key == SDLK_BACKSPACE || key == SDLK_LEFT) {
1898       if (!name_letters.empty()) {
1899         name_letters.pop_back();
1900       }
1901     } else if (key == SDLK_RETURN) {
1902       PlaySound(SOUND_ENTER_NAME_RETURN);
1903       *name = AssembleName(name_letters);
1904       return true;
1905     } else if (key == SDLK_ESCAPE) {
1906       return false;
1907     } else if (unicode != 0) {
1908       name_letters.push_back(CodeToUTF8(unicode));
1909     } else {
1910       continue;
1911     }
1912   }
1913 }
1914 
SaveGameToFile()1915 void SaveGameToFile()
1916 {
1917   char filename[1000];
1918   snprintf(filename, sizeof(filename),
1919            "games/%d", static_cast<int>(time(NULL)));
1920   FILE* f = fopen(filename, "wb");
1921   if (!f) {
1922     cerr << "fopen(): " << filename << ": " << strerror(errno) << endl;
1923     exit(1);
1924   }
1925   string encoded;
1926   saved_game.Encode(&encoded);
1927   if (encoded.size() < fwrite(encoded.data(), encoded.size(), 1, f)) {
1928     cerr << "fwrite(): " << filename << ": " << strerror(errno) << endl;
1929     exit(1);
1930   }
1931   if (0 != fclose(f)) {
1932     cerr << "fclose(): " << filename << ": " << strerror(errno) << endl;
1933   }
1934 }
1935 
1936 struct ScheduleGameData {
1937   string name;
1938   int score;
1939   string encoded_game;
1940 };
1941 
SubmitGame(void * dataptr)1942 int SubmitGame(void* dataptr)
1943 {
1944   ScheduleGameData* data = reinterpret_cast<ScheduleGameData*>(dataptr);
1945   SubmitGame(data->name, data->score, VERSION, data->encoded_game);
1946   delete data;
1947   return 0;
1948 }
1949 
ScheduleGameSubmission(const string & name,int score)1950 void ScheduleGameSubmission(const string& name, int score)
1951 {
1952   ScheduleGameData* data = new ScheduleGameData;
1953   data->name = name;
1954   data->score = score;
1955   saved_game.Encode(&(data->encoded_game));
1956   SDL_Thread* thread = SDL_CreateThread(SubmitGame, data);
1957   if (!thread) {
1958     cerr << "SDL_CreateThread() failed: " << SDL_GetError() << endl;
1959     delete data;
1960     return;
1961   }
1962 }
1963 
MaybeScheduleGameSubmission(const string & name,int score)1964 void MaybeScheduleGameSubmission(const string& name, int score)
1965 {
1966   //SaveGameToFile();
1967   if (libcurl_successfully_initialized && publish_scores) {
1968     ScheduleGameSubmission(name, score);
1969   }
1970 }
1971 
Run()1972 void Run()
1973 {
1974   Init();
1975   LoadLevels();
1976 
1977   if(Mix_PlayMusic(music, -1) == -1) {
1978     cerr << "Mix_PlayMusic() failed: " << Mix_GetError() << endl;
1979     exit(-1);
1980   }
1981 
1982   Game game;
1983   Menu menu;
1984   string name;
1985   for (;;) {
1986     PlayGameResult game_res;
1987     current_screen = &menu_screen;
1988     AutoRepeatOn();
1989     Menu::Choice choice;
1990     for (;;) {
1991       choice = menu.Run();
1992       if (choice == Menu::CHOICE_NEW_GAME) {
1993         if (!AskName(&name)) {
1994           continue;
1995         }
1996       }
1997       break;
1998     }
1999     current_screen = &game_screen;
2000     AutoRepeatOff();
2001     switch (choice) {
2002     case Menu::CHOICE_NEW_GAME:
2003       game = Game();
2004       saved_game.Reset(menu.GetStartingLevel());
2005       game_res = PlayGame(&game, menu.GetStartingLevel());
2006       break;
2007     case Menu::CHOICE_CONTINUE:
2008       assert(game.Initialized());
2009       game_res = PlayGame(&game);
2010       break;
2011     default:
2012       assert(!"Impossible.");
2013     }
2014     if (game_res == GAME_PAUSE) {
2015       menu.EnableContinue();
2016     } else if (game_res == GAME_DEATH) {
2017       if (game.GetTimeLeft() <= 0) {
2018         WaitForKey();
2019       }
2020       MsgGameOver();
2021       Update();
2022       MaybeScheduleGameSubmission(name, game.GetScore());
2023       WaitForKey();
2024       menu.DisableContinue();
2025     } else if (game_res == GAME_SUCCESS) {
2026       MsgGameSuccess();
2027       Update();
2028       MaybeScheduleGameSubmission(name, game.GetScore());
2029       WaitForEnterOrEsc();
2030       menu.DisableContinue();
2031     }
2032     if ((game_res == GAME_DEATH || game_res == GAME_SUCCESS) &&
2033         high_scores.IsHighEnough(game.GetScore())) {
2034       high_scores.Add(name, game.GetScore());
2035       high_scores.Show(game_screen);
2036       Update();
2037       WaitForKey();
2038     }
2039   }
2040 }
2041 
2042 }  // namespace kartofel
2043 
main(int argc,char ** argv)2044 int main(int argc, char** argv)
2045 {
2046   {
2047     if (chdir(getenv("HOME")) != 0)
2048       err(1, "cannot cd to $HOME");
2049     if (mkdir(".kartofel", 0755) != 0 && errno != EEXIST)
2050       err(1, "cannot mkdir $HOME/.kartofel");
2051     if (chdir(".kartofel") != 0)
2052       err(1, "cannot cd to $HOME/.kartofel");
2053   }
2054 
2055   kartofel::Run();
2056   return 0;
2057 }
2058