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