1 /* $Id: mod_guessbin.c,v 1.9 2007/02/28 12:47:35 tamentis Exp $
2 *
3 * Copyright (c) 2007 Bertrand Janin <tamentis@neopulsar.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 *
27 */
28
29 #include <unistd.h>
30 #include <curses.h>
31 #include <stdlib.h>
32 #include <time.h>
33
34 #include "tbclock.h"
35
36 #define TIME_EASY 15
37 #define TIME_NORMAL 10
38 #define TIME_HARD 5
39 #define TICK 1000
40 #define SECOND 100000
41
42 #define NUMOFQUEST 20
43
44 /* prompt display */
45 #define QUESTION_LEFT 5
46 #define PROMPT_LEFT 22
47 #define PROMPT_TEXT "What time is it: "
48 #define PROMPT_BLANK " "
49
50 /* in-game comments */
51 #define COMMENT_BLANK " "
52 #define COMMENT_TIMEOUT "Time out! Next! Quick!"
53 #define COMMENT_BAD "Incorrect! Try harder!"
54 #define COMMENT_GOOD "Correct! Try this one!"
55
56 /* intro display */
57 #define INTRO_TITLE "tbclock/guessbin: Choose your difficulty"
58 #define INTRO_HELP1 "Use your keyboard arrows to select your difficulty,"
59 #define INTRO_HELP2 "the harder it get, the faster it goes but the more"
60 #define INTRO_HELP3 "points you receive! You will have to read the time"
61 #define INTRO_HELP4 "written with binary number, type the answer using"
62 #define INTRO_HELP5 "decimal number with the form HH:MM:SS."
63
64 /* score display */
65 #define SCORE_TOP 2
66 #define SCORE_TITLE "You scored %ld points!"
67 #define SCORE_TIME "TIME BONUS! +1000 pts"
68 #define SCORE_DIFF "Difficulty bonus! +%4u pts"
69 #define SCORE_GOOD "Good shots +%4u pts"
70 #define SCORE_OOPS "Mistakes -%4u pts"
71 #define SCORE_LINE "--------------------------------"
72 #define SCORE_FOOT "(hit 'r' to check your answers!)"
73
74 /* history display */
75 #define H_OFFSET 15
76 #define H_HEAD1 "Take a look at your answers..."
77 #define H_HEAD2 " Time Answer"
78 #define H_LINE "-----------------------------"
79 #define H_FOOT "(hit 'r' to go back to the score table)"
80
81
82
83 /* types */
84 struct guessbin {
85 unsigned int h, m, s;
86 time_t t_start, t_end, t_allowed, t_elapsed, t_used;
87 unsigned int q_tot, q_cur, q_ok, q_err;
88 unsigned int diff;
89 };
90
91 struct history {
92 unsigned int uh, um, us;
93 unsigned int gh, gm, gs;
94 int error;
95 };
96
97
98
99 /* global & extern vars */
100 extern struct tbclock_data tbc;
101
102 static char s_diff[4][7] = { "", "Easy", "Normal", "Hard" };
103 struct history history[NUMOFQUEST];
104 struct guessbin *g;
105
106
107
108 /* guessbin_score_history - displays the answers history */
109 void
guessbin_score_history()110 guessbin_score_history()
111 {
112 char sr[50];
113 int i, color;
114 unsigned int tm = tbc.format.height / 2 - (NUMOFQUEST + 7) / 2;
115
116 /* result table header */
117 bkgdset(COLOR_PAIR(TEXT_DEFAULT));
118 mvprintw(tm, tbc.format.width / 2 - H_OFFSET, H_HEAD1);
119 mvprintw(tm + 2, tbc.format.width / 2 - H_OFFSET, H_HEAD2);
120 mvprintw(tm + 3, tbc.format.width / 2 - H_OFFSET, H_LINE);
121
122 /* all answers */
123 for (i = 0; i < NUMOFQUEST; i++) {
124 color = TEXT_GREEN;
125
126 if (history[i].error)
127 color = TEXT_RED;
128
129 snprintf(sr, 50, "% 2d. %02d:%02d:%02d vs %02d:%02d:%02d",
130 i + 1,
131 history[i].gh, history[i].gm, history[i].gs,
132 history[i].uh, history[i].um, history[i].us);
133 bkgdset(COLOR_PAIR(color));
134 mvprintw(tm + 4 + i, tbc.format.width / 2 - 14, sr);
135 }
136
137 /* footer */
138 bkgdset(COLOR_PAIR(TEXT_DEFAULT));
139 mvprintw(tm + 4 + i, tbc.format.width / 2 - H_OFFSET, H_LINE);
140 mvprintw(tm + 6 + i, tbc.format.width / 2 - H_OFFSET - 4, H_FOOT);
141 }
142
143
144 /* guessbin_score_table - displays the score table */
145 void
guessbin_score_table()146 guessbin_score_table()
147 {
148 char s_score[32];
149 char s_bonus[32];
150 int l, line = 0;
151 long points = 0;
152
153 /* time bonus */
154 if ((g->t_used / g->q_tot) <= (g->t_allowed / 2)) {
155 points += 1000;
156 mvprintw(tbc.format.height / 2 - SCORE_TOP + line,
157 tbc.format.width / 2 - 15, SCORE_TIME);
158 line++;
159 }
160
161 /* difficulty bonus */
162 if (g->diff > 1 && g->q_ok > 0) {
163 points += 1000 * g->diff * (g->q_tot/g->q_ok);
164 snprintf(s_bonus, 32, SCORE_DIFF, 1000 * g->diff);
165 mvprintw(tbc.format.height / 2 - SCORE_TOP + line,
166 tbc.format.width / 2 - 15, s_bonus);
167 line++;
168 }
169
170 /* points for good answers */
171 if (g->q_ok > 0) {
172 points += 200 * g->q_ok;
173 snprintf(s_bonus, 32, SCORE_GOOD, 200 * g->q_ok);
174 mvprintw(tbc.format.height / 2 - SCORE_TOP + line,
175 tbc.format.width / 2 - 15, s_bonus);
176 line++;
177 }
178
179 /* points removed for bad answers */
180 if (g->q_err > 0) {
181 points -= 100 * g->q_err;
182 if (points < 0) points = 0;
183 snprintf(s_bonus, 32, SCORE_OOPS, 100 * g->q_err);
184 mvprintw(tbc.format.height / 2 - SCORE_TOP + line,
185 tbc.format.width / 2 - 15, s_bonus);
186 line++;
187 }
188
189 /* footer */
190 mvprintw(tbc.format.height / 2 - SCORE_TOP + line,
191 tbc.format.width / 2 - 16, SCORE_LINE);
192 l = snprintf(s_score, 32, SCORE_TITLE, points);
193 bkgdset(COLOR_PAIR(TEXT_GREEN));
194 mvprintw(tbc.format.height / 2 - SCORE_TOP + line + 1,
195 tbc.format.width / 2 - l / 2, s_score);
196
197 bkgdset(COLOR_PAIR(TEXT_DEFAULT));
198 mvprintw(tbc.format.height / 2 - SCORE_TOP + line + 4,
199 tbc.format.width / 2 - 16, SCORE_FOOT);
200 }
201
202
203 /* guessbin_score - final page which displays score or answers */
204 void
guessbin_score()205 guessbin_score()
206 {
207 int mode = 0;
208 signed char c;
209
210 for (;;) {
211 c = getch();
212
213 if (c == KB_R) {
214 tbc_clear();
215 mode = mode == 0 ? 1 : 0;
216 } else if (c > 0)
217 break;
218
219 if (mode == 1)
220 guessbin_score_history();
221 else
222 guessbin_score_table();
223
224 refresh();
225 usleep(TICK);
226 }
227
228 free(g);
229 }
230
231
232 /* guessbin_next_digit - takes a look at the first two chars of the
233 * provided string *c store (maybe) the 1 or 2 digit in *t */
234 unsigned int
guessbin_next_digit(unsigned char * c,unsigned int * t)235 guessbin_next_digit(unsigned char *c, unsigned int *t)
236 {
237 unsigned int offset = 0;
238
239 if (*(c+1) != ':' && *(c+1) != 0) {
240 *t = (*(c) - '0') * 10 + *(c+1) - '0';
241 offset = 3;
242 } else {
243 *t = *(c) - '0';
244 offset = 2;
245 }
246
247 return (offset);
248 }
249
250
251 /* guessbin_matches - check wether the string in *is (HH:MM:SS) is
252 * equal to the three integer provided as a time. */
253 int
guessbin_matches(unsigned char * is,int size)254 guessbin_matches(unsigned char *is, int size)
255 {
256 unsigned int uh, um, us;
257 int error = 0;
258
259 if (size < 5)
260 return (0);
261
262 if (*is == ':')
263 return (0);
264
265 /* Hours */
266 is += guessbin_next_digit(is, &uh);
267 if (uh != g->h) error++;
268
269 /* Minutes */
270 is += guessbin_next_digit(is, &um);
271 if (um != g->m) error++;
272
273 /* Seconds */
274 guessbin_next_digit(is, &us);
275 if (us != g->s) error++;
276
277 /* record history */
278 history[g->q_cur].uh = uh;
279 history[g->q_cur].um = um;
280 history[g->q_cur].us = us;
281 history[g->q_cur].gh = g->h;
282 history[g->q_cur].gm = g->m;
283 history[g->q_cur].gs = g->s;
284 history[g->q_cur].error = error;
285
286 if (error)
287 return (0);
288
289 return (1);
290 }
291
292
293 /* guessbin_shuffle - get a new time and draw the new blocks */
294 void
guessbin_shuffle()295 guessbin_shuffle()
296 {
297 srand(time(NULL));
298
299 g->h = rand() % 23;
300 g->m = rand() % 59;
301 g->s = rand() % 59;
302
303 tbc_draw_time(3, g->h, g->m, g->s, 0);
304 }
305
306
307 /* guessbin_menu_diff - draw the difficulty menu, highlighting the option
308 * given by 'highlight' */
309 void
guessbin_menu_diff(int highlight)310 guessbin_menu_diff(int highlight)
311 {
312
313 if (highlight == 1)
314 bkgdset(COLOR_PAIR(BACK_YELLOW));
315 else
316 bkgdset(COLOR_PAIR(BACK_DEFAULT));
317 mvprintw(tbc.format.height/2+1, tbc.format.width/2-14, "[ Easy ]");
318
319 bkgdset(COLOR_PAIR(0));
320 mvprintw(tbc.format.height/2+1, tbc.format.width/2-6, " ");
321
322 if (highlight == 2)
323 bkgdset(COLOR_PAIR(BACK_YELLOW));
324 else
325 bkgdset(COLOR_PAIR(BACK_DEFAULT));
326 mvprintw(tbc.format.height/2+1, tbc.format.width/2-5, "[ Normal ]");
327
328 bkgdset(COLOR_PAIR(0));
329 mvprintw(tbc.format.height/2+1, tbc.format.width/2+5, " ");
330
331 if (highlight == 3)
332 bkgdset(COLOR_PAIR(BACK_YELLOW));
333 else
334 bkgdset(COLOR_PAIR(BACK_DEFAULT));
335 mvprintw(tbc.format.height/2+1, tbc.format.width/2+6, "[ Hard ]");
336
337 bkgdset(COLOR_PAIR(BACK_DEFAULT));
338
339 }
340
341
342 /* guessbin_init - initialization, hiscore + ask for difficulty */
343 struct guessbin *
guessbin_init()344 guessbin_init()
345 {
346 struct guessbin *g;
347 signed char c;
348 int hh, hw;
349
350 /* Set up what never moves... */
351 bkgdset(COLOR_PAIR(TEXT_DEFAULT));
352 mvprintw(tbc.format.height-1, tbc.format.width-18, "Ctrl-C to Quit");
353
354 g = malloc(sizeof(struct guessbin));
355 g->q_tot = NUMOFQUEST;
356 g->q_cur = 0;
357 g->q_ok = 0;
358 g->q_err = 0;
359 g->diff = 1;
360
361 /* Ask for difficulty */
362 hh = tbc.format.height / 2;
363 hw = tbc.format.width / 2;
364 mvprintw(hh - 1, hw - 20, INTRO_TITLE);
365 mvprintw(hh + 3, hw - 25, INTRO_HELP1);
366 mvprintw(hh + 4, hw - 25, INTRO_HELP2);
367 mvprintw(hh + 5, hw - 25, INTRO_HELP3);
368 mvprintw(hh + 6, hw - 25, INTRO_HELP4);
369 mvprintw(hh + 7, hw - 25, INTRO_HELP5);
370 guessbin_menu_diff(g->diff);
371
372 for (;;) {
373 c = getch();
374
375 if (c == KB_LEFT)
376 guessbin_menu_diff(g->diff > 1 ? --(g->diff) : g->diff);
377 else if (c == KB_RIGHT)
378 guessbin_menu_diff(g->diff < 3 ? ++(g->diff) : g->diff);
379 else if (c == KB_RETURN) {
380 tbc_clear();
381 break;
382 }
383
384 refresh();
385
386 usleep(TICK);
387 }
388
389 g->t_start = time(NULL);
390 g->t_used = 0;
391 g->t_elapsed = 0;
392
393 switch (g->diff) {
394 case 3:
395 g->t_allowed = TIME_HARD;
396 g->t_end = g->t_start + TIME_HARD;
397 break;
398 case 2:
399 g->t_allowed = TIME_NORMAL;
400 g->t_end = g->t_start + TIME_NORMAL;
401 break;
402 default:
403 g->t_allowed = TIME_EASY;
404 g->t_end = g->t_start + TIME_EASY;
405 break;
406 }
407
408 return (g);
409 }
410
411
412 /* guessbin_time - this displays the two progressbars! */
413 void
guessbin_timeline(unsigned long current,unsigned long total)414 guessbin_timeline(unsigned long current, unsigned long total)
415 {
416 int i, sh;
417 float ratio;
418
419 ratio = (float) current / (float)total;
420 sh = (tbc.format.height - 2) * (1 - ratio);
421
422 for (i = 1; i < tbc.format.height - 1; i++) {
423 if (i < sh + 1)
424 bkgdset(COLOR_PAIR(BLOCK_DEFAULT));
425 else
426 bkgdset(COLOR_PAIR(BLOCK_GREEN));
427 mvprintw(i, tbc.format.width - 3, " ");
428 mvprintw(i, 1, " ");
429 }
430
431 bkgdset(COLOR_PAIR(BACK_DEFAULT));
432 }
433
434
435 /* guessbin_incorrect - what to do when input is incorrect */
436 void
guessbin_incorrect()437 guessbin_incorrect()
438 {
439 bkgdset(COLOR_PAIR(TEXT_RED));
440 mvprintw(tbc.format.height - 4, QUESTION_LEFT, COMMENT_BAD);
441 g->q_err++;
442 }
443
444
445 /* guessbin_correct - what to do when input is correct */
446 void
guessbin_correct()447 guessbin_correct()
448 {
449 time_t now;
450
451 now = time(NULL);
452
453 bkgdset(COLOR_PAIR(TEXT_GREEN));
454 mvprintw(tbc.format.height - 4, QUESTION_LEFT, COMMENT_GOOD);
455 guessbin_shuffle();
456
457 g->t_used += now - g->t_start;
458 g->t_elapsed = 0;
459 g->t_start = now;
460 g->t_end = g->t_start + g->t_allowed;
461 g->q_cur++;
462 g->q_ok++;
463 }
464
465
466 /* guessbin_timeout - what to do when player didn't answer in time */
467 void
guessbin_timeout(unsigned char * is,int size)468 guessbin_timeout(unsigned char *is, int size)
469 {
470 bkgdset(COLOR_PAIR(TEXT_RED));
471 mvprintw(tbc.format.height - 4, QUESTION_LEFT, COMMENT_TIMEOUT);
472 guessbin_shuffle();
473
474 history[g->q_cur].gh = g->h;
475 history[g->q_cur].gm = g->m;
476 history[g->q_cur].gs = g->s;
477 history[g->q_cur].error = 1;
478
479 g->t_used += g->t_allowed;
480 g->t_start = time(NULL);
481 g->t_end = g->t_start + g->t_allowed;
482 g->t_elapsed = 0;
483 g->q_cur++;
484 }
485
486
487 /* guessbin_clearprompt - reset size to 0 and clear the input area */
488 void
guessbin_clearprompt(int * size)489 guessbin_clearprompt(int *size)
490 {
491 bkgdset(COLOR_PAIR(TEXT_DEFAULT));
492 mvprintw(tbc.format.height-3, PROMPT_LEFT, PROMPT_BLANK);
493 move(tbc.format.height-3, 19);
494 *size = 0;
495 }
496
497
498 /* game_guessbin - setup and loop */
499 void
mod_guessbin()500 mod_guessbin()
501 {
502 unsigned char is[8];
503 unsigned char status[32];
504 signed char c;
505 int size = 0;
506 time_t now;
507
508 tbc.format.res_x = 6;
509
510 if (tbc.options.vertical)
511 tbc.format.res_y = 5;
512 else
513 tbc.format.res_y = 4;
514
515 tbc_configure();
516
517 g = guessbin_init();
518
519 tbc.options.helper = 0;
520 tbc.options.ampm = 0;
521
522 /* main loop */
523 mvprintw(tbc.format.height - 3, QUESTION_LEFT, PROMPT_TEXT);
524 guessbin_shuffle();
525 for (;;) {
526 c = getch();
527 now = time(NULL);
528
529 snprintf((char*)status, 32, "%s [%u/%u]", s_diff[g->diff],
530 g->q_cur, g->q_tot);
531 mvprintw(tbc.format.height-1, 4, (char*)status);
532
533 guessbin_timeline(g->t_end - now, g->t_end - g->t_start);
534
535 if (g->t_end <= now) {
536 guessbin_timeout(is, size);
537 guessbin_clearprompt(&size);
538 }
539
540 if (g->q_cur >= g->q_tot)
541 break;
542
543 if (c == KB_RETURN) {
544 if (guessbin_matches(is, size)) {
545 guessbin_correct();
546 } else {
547 guessbin_incorrect();
548 }
549
550 guessbin_clearprompt(&size);
551
552 } else if (c == KB_BACKSPACE) {
553 if (size < 1)
554 continue;
555 is[size-1] = 0;
556 size--;
557 bkgdset(COLOR_PAIR(TEXT_DEFAULT));
558 mvprintw(tbc.format.height - 3, PROMPT_LEFT,
559 " ");
560 mvprintw(tbc.format.height - 3, PROMPT_LEFT,
561 (char*)is);
562
563 } else if (c == KB_CLEAR) { /* ^U */
564 guessbin_clearprompt(&size);
565
566 } else if (c > 47 && c < 59 && size < 8) { /* number or ':' */
567 is[size] = c;
568 is[size+1] = 0;
569 size++;
570 bkgdset(COLOR_PAIR(TEXT_DEFAULT));
571 mvprintw(tbc.format.height - 4, QUESTION_LEFT,
572 COMMENT_BLANK);
573 mvprintw(tbc.format.height - 3, PROMPT_LEFT,
574 (char*)is);
575 }
576
577 refresh();
578
579 usleep(TICK);
580 }
581
582 /* show score */
583 tbc_clear();
584 guessbin_score();
585
586 }
587
588