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