1 /* highscore.c
2
3 Implementation of high score tables for tuxmath.
4
5 Copyright 2009, 2010.
6 Authors: David Bruce, Akash Gangil, Brendan Luchen.
7 Project email: <tuxmath-devel@lists.sourceforge.net>
8 Project website: http://tux4kids.alioth.debian.org
9
10 highscore.c is part of "Tux, of Math Command", a.k.a. "tuxmath".
11
12 Tuxmath is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 3 of the License, or
15 (at your option) any later version.
16
17 Tuxmath is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with this program. If not, see <http://www.gnu.org/licenses/>. */
24
25
26
27
28 #include "tuxmath.h"
29 #include "highscore.h"
30 #include "titlescreen.h"
31 #include "fileops.h"
32 #include "setup.h"
33 #include "options.h"
34
35 #include <string.h>
36
37 typedef struct high_score_entry {
38 int score;
39 char name[HIGH_SCORE_NAME_LENGTH];
40 } high_score_entry;
41
42
43 high_score_entry high_scores[NUM_HIGH_SCORE_LEVELS][HIGH_SCORES_SAVED];
44
45 /* Local function prototypes: */
46
47
48
49 /* Display high scores: */
DisplayHighScores(int level)50 void DisplayHighScores(int level)
51 {
52 int i = 0;
53 int finished = 0;
54 Uint32 frame = 0;
55 Uint32 timer = 0;
56
57 int diff_level = level;
58 int old_diff_level = -1; //So table gets refreshed first time through
59 /* Surfaces, char buffers, and rects for table: */
60 SDL_Surface* score_surfs[HIGH_SCORES_SAVED] = {NULL};
61
62 /* 10 spaces should be enough room for place and score on each line: */
63 char score_strings[HIGH_SCORES_SAVED][HIGH_SCORE_NAME_LENGTH + 10] = {{'\0'}};
64
65 SDL_Rect score_rects[HIGH_SCORES_SAVED];
66 SDL_Rect table_bg;
67
68 const int max_width = 300;
69 int score_table_y = 100;
70
71 const int title_font_size = 32;
72 const int player_font_size = 14;
73
74 while (!finished)
75 {
76 /* Check for user events: */
77 while (SDL_PollEvent(&event))
78 {
79 switch (event.type)
80 {
81 case SDL_QUIT:
82 {
83 cleanup();
84 }
85
86 case SDL_MOUSEBUTTONDOWN:
87 /* "Stop" button - go to main menu: */
88 {
89 if (T4K_inRect(stop_rect, event.button.x, event.button.y ))
90 {
91 finished = 1;
92 playsound(SND_TOCK);
93 }
94
95 /* "Left" button - go to previous page: */
96 if (T4K_inRect(prev_rect, event.button.x, event.button.y))
97 {
98 if (diff_level > CADET_HIGH_SCORE)
99 {
100 diff_level--;
101 if (Opts_GetGlobalOpt(MENU_SOUND))
102 {
103 playsound(SND_TOCK);
104 }
105 }
106 }
107
108 /* "Right" button - go to next page: */
109 if (T4K_inRect(next_rect, event.button.x, event.button.y ))
110 {
111 if (diff_level < (NUM_HIGH_SCORE_LEVELS-1))
112 {
113 diff_level++;
114 if (Opts_GetGlobalOpt(MENU_SOUND))
115 {
116 playsound(SND_TOCK);
117 }
118 }
119 }
120 break;
121 }
122
123
124 case SDL_KEYDOWN:
125 {
126 finished = 1;
127 playsound(SND_TOCK);
128 }
129 }
130 }
131
132
133 /* If needed, redraw: */
134 if (diff_level != old_diff_level)
135 {
136 DrawTitleScreen();
137 /* Draw controls: */
138 if (stop_button)
139 SDL_BlitSurface(stop_button, NULL, screen, &stop_rect);
140 /* Draw regular or grayed-out left arrow: */
141 if (diff_level == CADET_HIGH_SCORE)
142 {
143 if (prev_gray)
144 SDL_BlitSurface(prev_gray, NULL, screen, &prev_rect);
145 }
146 else
147 {
148 if (prev_arrow)
149 SDL_BlitSurface(prev_arrow, NULL, screen, &prev_rect);
150 }
151 /* Draw regular or grayed-out right arrow: */
152 if (diff_level == NUM_HIGH_SCORE_LEVELS - 1)
153 {
154 if (next_gray)
155 SDL_BlitSurface(next_gray, NULL, screen, &next_rect);
156 }
157 else
158 {
159 if (next_arrow)
160 SDL_BlitSurface(next_arrow, NULL, screen, &next_rect);
161 }
162
163 /* Draw background shading for table: */
164 table_bg.x = (screen->w)/2 - (max_width + 20)/2 + 50; //don't draw over Tux
165 table_bg.y = 5;
166 table_bg.w = max_width + 20;
167 table_bg.h = screen->h - 10 - images[IMG_RIGHT]->h;
168 T4K_DrawButton(&table_bg, 25, SEL_RGBA);
169
170 /* Draw difficulty level heading: */
171 {
172 SDL_Surface* srfc = NULL;
173 SDL_Rect text_rect, button_rect;
174
175 srfc = T4K_BlackOutline(_("Hall Of Fame"), title_font_size, &yellow);
176 if (srfc)
177 {
178 button_rect.x = text_rect.x = (screen->w)/2 - (srfc->w)/2 + 50;
179 button_rect.y = text_rect.y = 10;
180 button_rect.w = text_rect.w = srfc->w;
181 button_rect.h = text_rect.h = srfc->h;
182 /* add margin to button and draw: */
183 button_rect.x -= 10;
184 button_rect.w += 20;
185 T4K_DrawButton(&button_rect, 15, 0, 0, 32, 192);
186 /* Now blit text and free surface: */
187 SDL_BlitSurface(srfc, NULL, screen, &text_rect);
188 SDL_FreeSurface(srfc);
189 srfc = NULL;
190 }
191
192 switch (diff_level)
193 {
194 case CADET_HIGH_SCORE:
195 srfc = T4K_BlackOutline(_("Space Cadet"), title_font_size, &white);
196 break;
197 case SCOUT_HIGH_SCORE:
198 srfc = T4K_BlackOutline(_("Scout"), title_font_size, &white);
199 break;
200 case RANGER_HIGH_SCORE:
201 srfc = T4K_BlackOutline(_("Ranger"), title_font_size, &white);
202 break;
203 case ACE_HIGH_SCORE:
204 srfc = T4K_BlackOutline(_("Ace"), title_font_size, &white);
205 break;
206 case COMMANDO_HIGH_SCORE:
207 srfc = T4K_BlackOutline(_("Commando"), title_font_size, &white);
208 break;
209 case FACTORS_HIGH_SCORE:
210 srfc = T4K_BlackOutline(_("Factors"), title_font_size, &white);
211 break;
212 case FRACTIONS_HIGH_SCORE:
213 srfc = T4K_BlackOutline(_("Fractions"), title_font_size, &white);
214 break;
215 default:
216 srfc = T4K_BlackOutline(_("Space Cadet"), title_font_size, &white);
217 }
218
219 if (srfc)
220 {
221 text_rect.x = (screen->w)/2 - (srfc->w)/2 + 50;
222 text_rect.y += text_rect.h; /* go to bottom of first line */
223 text_rect.w = srfc->w;
224 text_rect.h = srfc->h;
225 SDL_BlitSurface(srfc, NULL, screen, &text_rect);
226 SDL_FreeSurface(srfc);
227 srfc = NULL;
228 /* note where score table will start: */
229 score_table_y = text_rect.y + text_rect.h;
230 }
231 }
232
233
234 /* Generate and draw desired table: */
235
236 for (i = 0; i < HIGH_SCORES_SAVED; i++)
237 {
238 /* Get data for entries: */
239 sprintf(score_strings[i],
240 "%d. %d %s",
241 i + 1, /* Add one to get common-language place number */
242 HS_Score(diff_level, i),
243 HS_Name(diff_level, i));
244
245 /* Clear out old surfaces and update: */
246 if (score_surfs[i]) /* this should not happen! */
247 SDL_FreeSurface(score_surfs[i]);
248 if (HS_Score(diff_level, i) == Opts_LastScore() && frame % 5 < 2)
249 score_surfs[i] = T4K_BlackOutline(N_(score_strings[i]), player_font_size, &yellow);
250 else
251 score_surfs[i] = T4K_BlackOutline(N_(score_strings[i]), player_font_size, &white);
252
253 /* Get out if T4K_BlackOutline() fails: */
254 if (!score_surfs[i])
255 continue;
256 /* Set up entries in vertical column: */
257 if (0 == i)
258 score_rects[i].y = score_table_y;
259 else
260 score_rects[i].y = score_rects[i - 1].y + score_rects[i - 1].h;
261
262 score_rects[i].x = (screen->w)/2 - max_width/2 + 50;
263 score_rects[i].h = score_surfs[i]->h;
264 score_rects[i].w = max_width;
265
266 SDL_BlitSurface(score_surfs[i], NULL, screen, &score_rects[i]);
267 SDL_FreeSurface(score_surfs[i]);
268 score_surfs[i] = NULL;
269 }
270 /* Update screen: */
271 SDL_UpdateRect(screen, 0, 0, 0, 0);
272
273 old_diff_level = diff_level;
274 }
275
276 HandleTitleScreenAnimations();
277
278 /* Wait so we keep frame rate constant: */
279 T4K_Throttle(20, &timer);
280 frame++;
281 } // End of while (!finished) loop
282 }
283
284
285 /* Display screen to allow player to enter name for high score table: */
286 /* The pl_name argument *must* point to a validly allocated string array */
287 /* at least three times HIGH_SCORE_NAME_LENGTH because UTF-8 is a */
288 /* multibyte encoding. */
HighScoreNameEntry(char * pl_name)289 void HighScoreNameEntry(char* pl_name)
290 {
291 NameEntry(pl_name, _("You Are In The Hall of Fame!"), _("Enter Your Name:"), NULL);
292 }
293
294 /* Get pl_name from user; other strings are text displayed by dialog: */
NameEntry(char * pl_name,const char * s1,const char * s2,const char * s3)295 void NameEntry(char* pl_name, const char* s1, const char* s2, const char* s3)
296 {
297 char UTF8_buf[HIGH_SCORE_NAME_LENGTH * 3] = {'\0'};
298
299 SDL_Rect loc;
300 SDL_Rect redraw_rect;
301
302 int redraw = 0;
303 int first_draw = 1;
304 int finished = 0;
305 Uint32 frame = 0;
306 Uint32 start = 0;
307 wchar_t wchar_buf[HIGH_SCORE_NAME_LENGTH + 1] = {'\0'};
308 const int NAME_FONT_SIZE = 32;
309 const int BG_Y = 100;
310 const int BG_WIDTH = 400;
311 const int BG_HEIGHT = 200;
312
313 if (!pl_name)
314 return;
315
316 /* We need to get Unicode vals from SDL keysyms */
317 SDL_EnableUNICODE(SDL_ENABLE);
318
319 DEBUGMSG(debug_highscore, "Enter NameEntry()\n" );
320
321 DrawTitleScreen();
322
323 /* Red "Stop" circle in upper right corner to go back to main menu: */
324 if (stop_button)
325 {
326 SDL_BlitSurface(stop_button, NULL, screen, &stop_rect);
327 }
328
329 /* Draw translucent background for text: */
330 {
331 SDL_Rect bg_rect;
332 bg_rect.x = (screen->w)/2 - BG_WIDTH/2;
333 bg_rect.y = BG_Y;
334 bg_rect.w = BG_WIDTH;
335 bg_rect.h = BG_HEIGHT;
336 T4K_DrawButton(&bg_rect, 15, REG_RGBA);
337
338 bg_rect.x += 10;
339 bg_rect.y += 10;
340 bg_rect.w -= 20;
341 bg_rect.h = 60;
342 T4K_DrawButton(&bg_rect, 10, SEL_RGBA);
343 }
344
345 /* Draw headings: */
346 {
347 SDL_Surface* surf = T4K_BlackOutline(_(s1),
348 DEFAULT_MENU_FONT_SIZE, &white);
349 if (surf)
350 {
351 loc.x = (screen->w/2) - (surf->w/2);
352 loc.y = 110;
353 SDL_BlitSurface(surf, NULL, screen, &loc);
354 SDL_FreeSurface(surf);
355 }
356
357 surf = T4K_BlackOutline(_(s2),
358 DEFAULT_MENU_FONT_SIZE, &white);
359 if (surf)
360 {
361 loc.x = (screen->w/2) - (surf->w/2);
362 loc.y = 140;
363 SDL_BlitSurface(surf, NULL, screen, &loc);
364 SDL_FreeSurface(surf);
365 }
366
367 surf = T4K_BlackOutline(_(s3),
368 DEFAULT_MENU_FONT_SIZE, &white);
369 if (surf)
370 {
371 loc.x = (screen->w/2) - (surf->w/2);
372 loc.y = 170;
373 SDL_BlitSurface(surf, NULL, screen, &loc);
374 SDL_FreeSurface(surf);
375 }
376
377 }
378
379 /* and update: */
380 SDL_UpdateRect(screen, 0, 0, 0, 0);
381
382
383 while (!finished)
384 {
385 start = SDL_GetTicks();
386
387 while (SDL_PollEvent(&event))
388 {
389 switch (event.type)
390 {
391 case SDL_QUIT:
392 {
393 cleanup();
394 }
395
396 case SDL_MOUSEBUTTONDOWN:
397 /* "Stop" button - go to main menu: */
398 {
399 if (T4K_inRect(stop_rect, event.button.x, event.button.y ))
400 {
401 finished = 1;
402 playsound(SND_TOCK);
403 break;
404 }
405 }
406 case SDL_KEYDOWN:
407 {
408 DEBUGMSG(debug_highscore, "Before keypress, string is %S\tlength = %d\n",
409 wchar_buf, (int)wcslen(wchar_buf));
410 switch (event.key.keysym.sym)
411 {
412 case SDLK_ESCAPE:
413 case SDLK_RETURN:
414 case SDLK_KP_ENTER:
415 {
416 finished = 1;
417 playsound(SND_TOCK);
418 break;
419 }
420 case SDLK_BACKSPACE:
421 {
422 if (wcslen(wchar_buf) > 0)
423 wchar_buf[(int)wcslen(wchar_buf) - 1] = '\0';
424 redraw = 1;
425 break;
426 }
427
428 /* For any other keys, if the key has a Unicode value, */
429 /* we add it to our string: */
430 default:
431 {
432 if ((event.key.keysym.unicode > 0)
433 && (wcslen(wchar_buf) < HIGH_SCORE_NAME_LENGTH))
434 {
435 wchar_buf[(int)wcslen(wchar_buf)] = event.key.keysym.unicode;
436 redraw = 1;
437 }
438 }
439 } /* end 'switch (event.key.keysym.sym)' */
440
441 DEBUGMSG(debug_highscore, "After keypress, string is %S\tlength = %d\n",
442 wchar_buf, (int)wcslen(wchar_buf));
443 /* Now draw name, if needed: */
444 if (redraw)
445 {
446 SDL_Surface* s = NULL;
447 redraw = 0;
448
449 /* Convert text to UTF-8 so T4K_BlackOutline() can handle it: */
450 // wcstombs((char*) UTF8_buf, wchar_buf, HIGH_SCORE_NAME_LENGTH * 3);
451 T4K_ConvertToUTF8(wchar_buf, UTF8_buf, HIGH_SCORE_NAME_LENGTH * 3);
452 /* Redraw background and shading in area where we drew text last time: */
453 if (!first_draw)
454 {
455 SDL_BlitSurface(current_bkg(), &redraw_rect, screen, &redraw_rect);
456 T4K_DrawButton(&redraw_rect, 0, REG_RGBA);
457 SDL_UpdateRect(screen,
458 redraw_rect.x,
459 redraw_rect.y,
460 redraw_rect.w,
461 redraw_rect.h);
462 }
463
464 s = T4K_BlackOutline(UTF8_buf, NAME_FONT_SIZE, &yellow);
465 if (s)
466 {
467 /* set up loc and blit: */
468 loc.x = (screen->w/2) - (s->w/2);
469 loc.y = 230;
470 SDL_BlitSurface(s, NULL, screen, &loc);
471
472 /* Remember where we drew so we can update background next time through: */
473 /* (for some reason we need to update a wider area to get clean image) */
474 redraw_rect.x = loc.x - 20;
475 redraw_rect.y = loc.y - 10;
476 redraw_rect.h = s->h + 20;
477 redraw_rect.w = s->w + 40;
478 first_draw = 0;
479
480 SDL_UpdateRect(screen,
481 redraw_rect.x,
482 redraw_rect.y,
483 redraw_rect.w,
484 redraw_rect.h);
485 SDL_FreeSurface(s);
486 s = NULL;
487 }
488 }
489 }
490 }
491 }
492
493 HandleTitleScreenAnimations();
494
495 /* Wait so we keep frame rate constant: */
496 while ((SDL_GetTicks() - start) < 33)
497 {
498 SDL_Delay(20);
499 }
500 frame++;
501 } // End of while (!finished) loop
502
503 /* Turn off SDL Unicode lookup (because has some overhead): */
504 SDL_EnableUNICODE(SDL_DISABLE);
505
506 /* Now copy name into location pointed to by arg: */
507 strncpy(pl_name, UTF8_buf, HIGH_SCORE_NAME_LENGTH * 3);
508
509 DEBUGMSG(debug_highscore, "Leaving NameEntry(), final string is: %s\n",
510 pl_name);
511 }
512
513
514
515
516 /* Zero-out the array before use: */
initialize_scores(void)517 void initialize_scores(void)
518 {
519 int i, j;
520 for (i = 0; i < NUM_HIGH_SCORE_LEVELS; i++)
521 {
522 for (j = 0; j < HIGH_SCORES_SAVED; j++)
523 {
524 high_scores[i][j].score = 0;
525 strcpy(high_scores[i][j].name, "");
526 }
527 }
528 }
529
530 /* Test to see where a new score ranks on the list. */
531 /* The return value is the index value - add one to get */
532 /* the common-language place on the list. */
check_score_place(int diff_level,int new_score)533 int check_score_place(int diff_level, int new_score)
534 {
535 int i = 0;
536
537 /* Make sure diff_level is valid: */
538 if (diff_level < 0
539 || diff_level >= NUM_HIGH_SCORE_LEVELS)
540 {
541 fprintf(stderr, "In insert_score(), diff_level invalid!\n");
542 return 0;
543 }
544
545 /* Find correct place in list: */
546 for (i = 0; i < HIGH_SCORES_SAVED; i++)
547 {
548 if (new_score > high_scores[diff_level][i].score)
549 break;
550 }
551
552 return i; /* So if we return HIGH_SCORES_SAVED, the score did not */
553 /* make the list. */
554 }
555
556 /* Put a new high score entry into the table for the corresponding */
557 /* difficulty level - returns 1 if successful. */
insert_score(char * playername,int diff_level,int new_score)558 int insert_score(char* playername, int diff_level, int new_score)
559 {
560 int i = 0;
561 int insert_place;
562
563 insert_place = check_score_place(diff_level, new_score);
564
565 if (HIGH_SCORES_SAVED == insert_place) /* Score didn't make the top 10 */
566 {
567 return 0;
568 }
569
570 /* Move lower entries down: */
571 for (i = HIGH_SCORES_SAVED - 1; i > insert_place; i--)
572 {
573 high_scores[diff_level][i].score =
574 high_scores[diff_level][i - 1].score;
575 strncpy(high_scores[diff_level][i].name,
576 high_scores[diff_level][i - 1].name,
577 HIGH_SCORE_NAME_LENGTH);
578 }
579
580 /* Now put in new entry: */
581 high_scores[diff_level][insert_place].score = new_score;
582 strncpy(high_scores[diff_level][insert_place].name,
583 playername,
584 HIGH_SCORE_NAME_LENGTH);
585 return 1;
586 }
587
588
print_high_scores(FILE * fp)589 void print_high_scores(FILE* fp)
590 {
591 int i, j;
592
593 fprintf(fp, "\nHigh Scores:\n");
594
595 for (i = 0; i < NUM_HIGH_SCORE_LEVELS; i++)
596 {
597 switch(i)
598 {
599 case CADET_HIGH_SCORE:
600 {
601 fprintf(fp, "\nSpace Cadet:\n");
602 break;
603 }
604 case SCOUT_HIGH_SCORE:
605 {
606 fprintf(fp, "\nScout:\n");
607 break;
608 }
609 case RANGER_HIGH_SCORE:
610 {
611 fprintf(fp, "\nRanger:\n");
612 break;
613 }
614 case ACE_HIGH_SCORE:
615 {
616 fprintf(fp, "\nAce:\n");
617 break;
618 }
619 case COMMANDO_HIGH_SCORE:
620 {
621 fprintf(fp, "\nCommando:\n");
622 break;
623 }
624 case FACTORS_HIGH_SCORE:
625 {
626 fprintf(fp, "\nFactors:\n");
627 break;
628 }
629 case FRACTIONS_HIGH_SCORE:
630 {
631 fprintf(fp, "\nFractions:\n");
632 break;
633 }
634 }
635
636 for (j = 0; j < HIGH_SCORES_SAVED; j++)
637 {
638 fprintf(fp, "%d.\t%s\t%d\n",
639 j + 1, //Convert to common-language ordinals
640 high_scores[i][j].name,
641 high_scores[i][j].score);
642 }
643 }
644 }
645
646
read_high_scores_fp(FILE * fp)647 int read_high_scores_fp(FILE* fp)
648 {
649 char buf[PATH_MAX];
650 char* token;
651 const char delimiters[] = "\t";
652
653 char* name_read;
654 int score_read;
655 int diff_level;
656
657 DEBUGMSG(debug_highscore, "Entering read_high_scores_fp()\n");
658
659 /* get out if file pointer invalid: */
660 if(!fp)
661 {
662 fprintf(stderr, "In read_high_scores_fp(), file pointer invalid!\n");
663 return 0;
664 }
665
666 /* make sure we start at beginning: */
667 rewind(fp);
668
669 /* read in a line at a time: */
670 while (fgets (buf, PATH_MAX, fp))
671 {
672 /* Ignore comment lines: */
673 if ((buf[0] == ';') || (buf[0] == '#'))
674 {
675 continue;
676 }
677 /* Split up line with strtok()to get needed values, */
678 /* then call insert_score() for each line. */
679 token = strtok(buf, delimiters);
680 if (!token)
681 continue;
682 diff_level = atoi(token);
683 if (diff_level >= NUM_HIGH_SCORE_LEVELS)
684 continue;
685
686 token = strtok(NULL, delimiters);
687 if (!token)
688 continue;
689 score_read = atoi(token);
690 /* Note that name can contain spaces - \t is only delimiter: */
691 name_read = strtok(NULL, delimiters);
692 /* Now insert entry: */
693 insert_score(name_read, diff_level, score_read);
694 }
695 return 1;
696 }
697
698
699 /* Return the score associated with a table entry: */
700 /* Note: the place is given as the array index, i.e. */
701 /* 0 for the top of the list. */
HS_Score(int diff_level,int place)702 int HS_Score(int diff_level, int place)
703 {
704 /* Make sure diff_level is valid: */
705 if (diff_level < 0
706 || diff_level >= NUM_HIGH_SCORE_LEVELS)
707 {
708 fprintf(stderr, "In HS_Score(), diff_level = %d, invalid!\n", diff_level);
709 return -1;
710 }
711
712 /* Make sure place is valid: */
713 if (place < 0
714 || place >= HIGH_SCORES_SAVED)
715 {
716 fprintf(stderr, "In HS_Score(), place invalid!\n");
717 return -1;
718 }
719
720 return high_scores[diff_level][place].score;
721 }
722
723
724 /* Return (pointer to) the name associated with a table entry: */
HS_Name(int diff_level,int place)725 char* HS_Name(int diff_level, int place)
726 {
727 /* Make sure diff_level is valid: */
728 if (diff_level < 0
729 || diff_level >= NUM_HIGH_SCORE_LEVELS)
730 {
731 fprintf(stderr, "In HS_Name(), diff_level invalid!\n");
732 return NULL;
733 }
734
735 /* Make sure place is valid: */
736 if (place < 0
737 || place >= HIGH_SCORES_SAVED)
738 {
739 fprintf(stderr, "In HS_Name(), place invalid!\n");
740 return NULL;
741 }
742
743 return high_scores[diff_level][place].name;
744 }
745
746
747
748
749
750
751