Mercurial > projects > rantaiwarna
comparison rantaiwarna.c @ 0:a9a7ad180c3b version-1
Initial revision
author | Guido Berhoerster <guido+rantaiwarna@berhoerster.name> |
---|---|
date | Sat, 15 Mar 2014 18:41:03 +0100 |
parents | |
children | 4f6bf50dbc4a |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:a9a7ad180c3b |
---|---|
1 /* | |
2 * Copyright (C) 2014 Guido Berhoerster <guido+rantaiwarna@berhoerster.name> | |
3 * | |
4 * Permission is hereby granted, free of charge, to any person obtaining | |
5 * a copy of this software and associated documentation files (the | |
6 * "Software"), to deal in the Software without restriction, including | |
7 * without limitation the rights to use, copy, modify, merge, publish, | |
8 * distribute, sublicense, and/or sell copies of the Software, and to | |
9 * permit persons to whom the Software is furnished to do so, subject to | |
10 * the following conditions: | |
11 * | |
12 * The above copyright notice and this permission notice shall be included | |
13 * in all copies or substantial portions of the Software. | |
14 * | |
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
22 */ | |
23 | |
24 #define _XOPEN_SOURCE 600 | |
25 | |
26 #include <stdlib.h> | |
27 #include <stdio.h> | |
28 #include <signal.h> | |
29 #include <errno.h> | |
30 #include <string.h> | |
31 #include <stdint.h> | |
32 #include <inttypes.h> | |
33 #include <math.h> | |
34 #include <time.h> | |
35 #include <sys/time.h> | |
36 #include <unistd.h> | |
37 #include <fcntl.h> | |
38 #include <locale.h> | |
39 #include <curses.h> | |
40 #include <term.h> | |
41 | |
42 #include "rantaiwarna.h" | |
43 #include "compat.h" | |
44 #include "util.h" | |
45 #include "board.h" | |
46 #include "highscore.h" | |
47 | |
48 #define EXIT_USAGE 2 | |
49 #define HEIGHT_MAX INT16_MAX | |
50 #define HEIGHT_MIN 5 | |
51 #define WIDTH_MAX INT16_MAX | |
52 #define WIDTH_MIN 5 | |
53 #define DEFAULT_WIDTH 20 | |
54 #define DEFAULT_HEIGHT 10 | |
55 #define COLORS_MAX 6 | |
56 #define DEFAULT_COLORS 4 | |
57 #define ELEMENT_WIDTH 2 | |
58 #define SCORE_WIN_WIDTH 6 | |
59 #define HIGHSCORE_FMT "%2d) %s %6" PRId32 | |
60 #define TIMESTR_MAX_SIZE 64 | |
61 #define MESSAGE_MAX_SIZE 64 | |
62 | |
63 enum { | |
64 PIPE_R_FD = 0, | |
65 PIPE_W_FD | |
66 }; | |
67 | |
68 enum { | |
69 GAME_SCREEN, | |
70 HIGHSCORE_SCREEN, | |
71 HELP_SCREEN | |
72 }; | |
73 | |
74 struct rantaiwarna_ctx { | |
75 struct board_ctx *board; | |
76 struct highscore_entry *highscore; | |
77 WINDOW *game_header_win; | |
78 WINDOW *message_win; | |
79 WINDOW *score_win; | |
80 WINDOW *board_container_win; | |
81 WINDOW *board_win; | |
82 WINDOW *highscore_pad; | |
83 WINDOW *highscore_header_win; | |
84 WINDOW *help_pad; | |
85 WINDOW *help_header_win; | |
86 WINDOW *input_win; | |
87 char message[MESSAGE_MAX_SIZE]; | |
88 char dummy_timestr[TIMESTR_MAX_SIZE]; | |
89 const chtype *elements_chs; | |
90 int board_cursor_y; | |
91 int board_cursor_x; | |
92 int highscore_pos_y; | |
93 int highscore_pos_x; | |
94 int help_pos_y; | |
95 int help_pos_x; | |
96 int active_screen; | |
97 bool use_colors; | |
98 bool too_small; | |
99 }; | |
100 | |
101 char *progname; | |
102 bool curses_init = FALSE; | |
103 static int signal_pipe_fd[2] = { -1, -1 }; | |
104 static const chtype elements_chs[COLORS_MAX] = { ' ', '@', '#', '$', 'X', 'O' }; | |
105 static const char *help_strs[] = { | |
106 "rantaiwarna is a tile-matching puzzle game which is also known under " | |
107 "the names ", | |
108 "\"Chain Shot!\" or \"SameGame\". It is played on a rectangular board " | |
109 "which is ", | |
110 "initially filled with elements of several different colors. Two or " | |
111 "more ", | |
112 "adjacent elements of the same color may be eliminated, the score " | |
113 "resulting from ", | |
114 "the elimination of elements depends on the number of elements " | |
115 "eliminated at ", | |
116 "once. The goal of the game is to eliminate as many elements as " | |
117 "possible until ", | |
118 "there are no more adjacent elements of the same color left or the " | |
119 "board is ", | |
120 "completely cleared. Vertical gaps resulting from the elimination of " | |
121 "elements ", | |
122 "are filled by sliding down elements from above the gap, column gaps " | |
123 "are filled ", | |
124 "by sliding columns on the right side of the column gap to the left.", | |
125 "", | |
126 "Game Screen Commands", | |
127 "", | |
128 " Arrow up, right, down, left", | |
129 " move the cursor around", | |
130 "", | |
131 " h, j, k, l", | |
132 " same as arrow keys", | |
133 "", | |
134 " Space", | |
135 " eliminate elements under the cursor", | |
136 "", | |
137 " Enter", | |
138 " same as Space", | |
139 "", | |
140 " Left mouse button", | |
141 " same as Space", | |
142 "", | |
143 " Ctrl+l", | |
144 " refresh the screen", | |
145 "", | |
146 " H", | |
147 " switch to the help screen", | |
148 "", | |
149 " i", | |
150 " switch to the highscore screen", | |
151 "", | |
152 " n", | |
153 " start a new game", | |
154 "", | |
155 " q", | |
156 " quit the game", | |
157 "", | |
158 "Highscore Screen Commands", | |
159 "", | |
160 " Arrow up, right, down, left", | |
161 " scroll on line up or down or one character to the left or" | |
162 "right", | |
163 "", | |
164 " h, j, k, l", | |
165 " same as arrow keys", | |
166 "", | |
167 " Space", | |
168 " scroll down one line", | |
169 "", | |
170 " Enter", | |
171 " same as Space", | |
172 "", | |
173 " Page up, Page down", | |
174 " scroll up or down one screenful", | |
175 "", | |
176 " b, f", | |
177 " same as Page up or Page down", | |
178 "", | |
179 " Ctrl+b, Ctrl+f", | |
180 " same as Page up or Page down", | |
181 "", | |
182 " Home, End", | |
183 " go to the first or last line", | |
184 "", | |
185 " g, G,", | |
186 " same as Home or End", | |
187 "", | |
188 " Ctrl+l", | |
189 " refresh the screen", | |
190 "", | |
191 " a", | |
192 " switch to the game screen", | |
193 "", | |
194 " H", | |
195 " switch to the help screen", | |
196 "", | |
197 " q", | |
198 " quit the game", | |
199 "", | |
200 "Help Screen Commands", | |
201 "", | |
202 " Arrow up, right, down, left", | |
203 " scroll on line up or down or one character to the left or " | |
204 "right", | |
205 "", | |
206 " h, j, k, l", | |
207 " same as arrow keys", | |
208 "", | |
209 " Space", | |
210 " scroll down one line", | |
211 "", | |
212 " Enter", | |
213 " same as Space", | |
214 "", | |
215 " Page up, Page down", | |
216 " scroll up or down one screenful", | |
217 "", | |
218 " b, f", | |
219 " same as Page up or Page down", | |
220 "", | |
221 " Ctrl+b, Ctrl+f", | |
222 " same as Page up or Page down", | |
223 "", | |
224 " Home, End", | |
225 " go to the first or last line", | |
226 "", | |
227 " g, G,", | |
228 " same as Home or End", | |
229 "", | |
230 " Ctrl+l", | |
231 " refresh the screen", | |
232 "", | |
233 " a", | |
234 " switch to the game screen", | |
235 "", | |
236 " i", | |
237 " switch to the highscore screen", | |
238 "", | |
239 " q", | |
240 " quit the game", | |
241 "", | |
242 NULL | |
243 }; | |
244 | |
245 static void | |
246 on_signal(int signo) | |
247 { | |
248 int old_errno = errno; | |
249 ssize_t n; | |
250 sigset_t sigset; | |
251 | |
252 /* try to read unread signals from the pipe and add the new one to it */ | |
253 n = read(signal_pipe_fd[PIPE_R_FD], &sigset, sizeof (sigset)); | |
254 if (n == -1 || (size_t)n < sizeof (sigset)) { | |
255 sigemptyset(&sigset); | |
256 } | |
257 sigaddset(&sigset, signo); | |
258 write(signal_pipe_fd[PIPE_W_FD], &sigset, sizeof (sigset)); | |
259 | |
260 errno = old_errno; | |
261 } | |
262 | |
263 static int | |
264 rantaiwarna_doupdate(struct rantaiwarna_ctx *ctx) | |
265 { | |
266 /* | |
267 * ensure the physical cursor is a sensible place in case it could not | |
268 * be made invisible | |
269 */ | |
270 if ((ctx->active_screen == GAME_SCREEN) && (ctx->board_win != NULL)) { | |
271 wmove(ctx->board_win, ctx->board_cursor_y, | |
272 ctx->board_cursor_x * ELEMENT_WIDTH); | |
273 wnoutrefresh(ctx->board_win); | |
274 } | |
275 return (doupdate()); | |
276 } | |
277 | |
278 static void | |
279 board_win_render_cursor(struct rantaiwarna_ctx *ctx, bool visible) | |
280 { | |
281 /* highlighting color pairs are base color + COLORS_MAX */ | |
282 short color = ctx->board->elements[ctx->board_cursor_y * | |
283 ctx->board->width + ctx->board_cursor_x] + | |
284 (visible ? COLORS_MAX : 0); | |
285 int i; | |
286 chtype ch; | |
287 | |
288 for (i = 0; i < ELEMENT_WIDTH; i++) { | |
289 ch = mvwinch(ctx->board_win, ctx->board_cursor_y, | |
290 ctx->board_cursor_x * ELEMENT_WIDTH + i); | |
291 if (visible) { | |
292 /* use reverse and underline for highlighting */ | |
293 ch = (ch & ~A_COLOR) | A_REVERSE | A_UNDERLINE; | |
294 } else { | |
295 ch &= ~(A_COLOR | A_REVERSE | A_UNDERLINE); | |
296 } | |
297 if (ctx->use_colors) { | |
298 ch |= COLOR_PAIR(color); | |
299 } | |
300 mvwaddch(ctx->board_win, ctx->board_cursor_y, | |
301 ctx->board_cursor_x * ELEMENT_WIDTH + i, ch); | |
302 } | |
303 } | |
304 | |
305 static void | |
306 board_win_update_cursor(struct rantaiwarna_ctx *ctx, bool visible) | |
307 { | |
308 board_win_render_cursor(ctx, visible); | |
309 touchwin(ctx->board_container_win); | |
310 wnoutrefresh(ctx->board_win); | |
311 } | |
312 | |
313 static int | |
314 board_win_move_cursor(struct rantaiwarna_ctx *ctx, int y, int x) | |
315 { | |
316 if ((x < 0) || (x >= ctx->board->width) || (y < 0) || | |
317 (y >= ctx->board->height)) { | |
318 return (ERR); | |
319 } | |
320 | |
321 board_win_update_cursor(ctx, FALSE); | |
322 ctx->board_cursor_x = x; | |
323 ctx->board_cursor_y = y; | |
324 board_win_update_cursor(ctx, TRUE); | |
325 | |
326 return (OK); | |
327 } | |
328 | |
329 static void | |
330 score_win_render(struct rantaiwarna_ctx *ctx) | |
331 { | |
332 mvwprintw(ctx->score_win, 0, 0, "%*" PRId32, SCORE_WIN_WIDTH, | |
333 ctx->board->score); | |
334 } | |
335 | |
336 static void | |
337 score_win_update(struct rantaiwarna_ctx *ctx) | |
338 { | |
339 score_win_render(ctx); | |
340 touchwin(ctx->game_header_win); | |
341 wnoutrefresh(ctx->score_win); | |
342 } | |
343 | |
344 static void | |
345 set_message(struct rantaiwarna_ctx *ctx, const char *message) | |
346 { | |
347 snprintf(ctx->message, MESSAGE_MAX_SIZE, "%s", message); | |
348 } | |
349 | |
350 static void | |
351 message_win_render(struct rantaiwarna_ctx *ctx) | |
352 { | |
353 int game_header_win_height; | |
354 int game_header_win_width; | |
355 int message_win_begy; | |
356 int message_win_begx; | |
357 | |
358 getmaxyx(ctx->game_header_win, game_header_win_height, | |
359 game_header_win_width); | |
360 getbegyx(ctx->message_win, message_win_begy, message_win_begx); | |
361 wclear(ctx->message_win); | |
362 mvwaddstr(ctx->message_win, 0, (game_header_win_width - | |
363 (int)strlen(ctx->message)) / 2 - message_win_begx, ctx->message); | |
364 } | |
365 | |
366 static void | |
367 message_win_update(struct rantaiwarna_ctx *ctx) | |
368 { | |
369 message_win_render(ctx); | |
370 touchwin(ctx->game_header_win); | |
371 wnoutrefresh(ctx->message_win); | |
372 } | |
373 | |
374 static void | |
375 board_win_render(struct rantaiwarna_ctx *ctx) | |
376 { | |
377 chtype ch; | |
378 int x; | |
379 int y; | |
380 short color; | |
381 int i; | |
382 | |
383 for (x = 0; x < ctx->board->width; x++) { | |
384 for (y = 0; y < ctx->board->height; y++) { | |
385 color = ctx->board->elements[y * ctx->board->width + x]; | |
386 ch = ctx->elements_chs[color]; | |
387 if (ctx->use_colors) { | |
388 ch |= COLOR_PAIR(color); | |
389 } | |
390 for (i = 0; i < ELEMENT_WIDTH; i++) { | |
391 mvwaddch(ctx->board_win, y, | |
392 x * ELEMENT_WIDTH + i, ch); | |
393 } | |
394 } | |
395 } | |
396 } | |
397 | |
398 static void | |
399 board_win_update(struct rantaiwarna_ctx *ctx) | |
400 { | |
401 board_win_render(ctx); | |
402 if ((ctx->board->status & GAME_OVER) == 0) { | |
403 board_win_update_cursor(ctx, TRUE); | |
404 } | |
405 touchwin(ctx->board_container_win); | |
406 wnoutrefresh(ctx->board_win); | |
407 } | |
408 | |
409 static void | |
410 highscore_pad_update(struct rantaiwarna_ctx *ctx) | |
411 { | |
412 struct highscore_entry *entry; | |
413 int i; | |
414 int32_t score; | |
415 char timestr[TIMESTR_MAX_SIZE]; | |
416 char *timestrp; | |
417 int height; | |
418 int width; | |
419 int highscore_pad_height; | |
420 int highscore_pad_width; | |
421 | |
422 for (entry = ctx->highscore, i = 0; i < 10; i++) { | |
423 if (entry != NULL) { | |
424 score = entry->score; | |
425 timestrp = timestr; | |
426 if (strftime(timestr, sizeof (timestr), "%x", | |
427 &entry->time) == 0) { | |
428 timestr[0] = '\0'; | |
429 } | |
430 entry = entry->next; | |
431 } else { | |
432 score = 0; | |
433 timestrp = ctx->dummy_timestr; | |
434 } | |
435 mvwprintw(ctx->highscore_pad, i, 0, HIGHSCORE_FMT, i + 1, | |
436 timestrp, score); | |
437 } | |
438 | |
439 getmaxyx(stdscr, height, width); | |
440 getmaxyx(ctx->highscore_pad, highscore_pad_height, highscore_pad_width); | |
441 if (ctx->highscore_pos_y + height > highscore_pad_height) { | |
442 ctx->highscore_pos_y = MAX(highscore_pad_height - height, 0); | |
443 } | |
444 if (ctx->highscore_pos_x + width > highscore_pad_width) { | |
445 ctx->highscore_pos_x = MAX(highscore_pad_width - width, 0); | |
446 } | |
447 } | |
448 | |
449 static int | |
450 highscore_pad_refresh(struct rantaiwarna_ctx *ctx) | |
451 { | |
452 int height; | |
453 int width; | |
454 int highscore_pad_height; | |
455 int highscore_pad_width; | |
456 | |
457 getmaxyx(stdscr, height, width); | |
458 getmaxyx(ctx->highscore_pad, highscore_pad_height, highscore_pad_width); | |
459 | |
460 return (pnoutrefresh(ctx->highscore_pad, ctx->highscore_pos_y, | |
461 ctx->highscore_pos_x, 1, 0, MIN(height - 1, highscore_pad_height - | |
462 1), MIN(width - 1, highscore_pad_width - 1))); | |
463 } | |
464 | |
465 static int | |
466 help_pad_refresh(struct rantaiwarna_ctx *ctx) | |
467 { | |
468 int height; | |
469 int width; | |
470 int help_pad_height; | |
471 int help_pad_width; | |
472 | |
473 getmaxyx(stdscr, height, width); | |
474 getmaxyx(ctx->help_pad, help_pad_height, help_pad_width); | |
475 | |
476 return (pnoutrefresh(ctx->help_pad, ctx->help_pos_y, ctx->help_pos_x, | |
477 1, 0, MIN(height - 1, help_pad_height - ctx->help_pos_y - 1), | |
478 MIN(width - 1, help_pad_width - ctx->help_pos_x - 1))); | |
479 } | |
480 | |
481 static void | |
482 active_screen_switch(struct rantaiwarna_ctx *ctx) | |
483 { | |
484 clear(); | |
485 wnoutrefresh(stdscr); | |
486 switch (ctx->active_screen) { | |
487 case GAME_SCREEN: | |
488 touchwin(ctx->game_header_win); | |
489 wnoutrefresh(ctx->game_header_win); | |
490 touchwin(ctx->board_container_win); | |
491 wnoutrefresh(ctx->board_container_win); | |
492 touchwin(ctx->board_win); | |
493 wnoutrefresh(ctx->board_win); | |
494 ctx->input_win = ctx->board_win; | |
495 break; | |
496 case HIGHSCORE_SCREEN: | |
497 touchwin(ctx->highscore_header_win); | |
498 wnoutrefresh(ctx->highscore_header_win); | |
499 highscore_pad_refresh(ctx); | |
500 ctx->input_win = stdscr; | |
501 break; | |
502 case HELP_SCREEN: | |
503 touchwin(ctx->help_header_win); | |
504 wnoutrefresh(ctx->help_header_win); | |
505 help_pad_refresh(ctx); | |
506 ctx->input_win = stdscr; | |
507 break; | |
508 } | |
509 flushinp(); | |
510 } | |
511 | |
512 static int | |
513 scrolled_win_handle_input(WINDOW *pad, int *pos_yp, int *pos_xp, int c) | |
514 { | |
515 int y = *pos_yp; | |
516 int x = *pos_xp; | |
517 int height; | |
518 int width; | |
519 int pad_height; | |
520 int pad_width; | |
521 | |
522 getmaxyx(stdscr, height, width); | |
523 getmaxyx(pad, pad_height, pad_width); | |
524 | |
525 switch (c) { | |
526 case 'k': | |
527 case KEY_UP: | |
528 /* scroll line up */ | |
529 y--; | |
530 break; | |
531 case 'l': | |
532 case KEY_RIGHT: | |
533 /* scroll right */ | |
534 x++; | |
535 break; | |
536 case 'j': | |
537 case ' ': | |
538 case '\n': | |
539 case '\r': | |
540 case KEY_ENTER: | |
541 case KEY_DOWN: | |
542 /* scroll line down */ | |
543 y++; | |
544 break; | |
545 case 'h': | |
546 case KEY_LEFT: | |
547 /* scroll left */ | |
548 x--; | |
549 break; | |
550 case 'b': | |
551 case '\002': /* ^B */ | |
552 case KEY_PPAGE: | |
553 /* scroll up one screenful */ | |
554 y -= height; | |
555 break; | |
556 case 'f': | |
557 case '\006': /* ^F */ | |
558 case KEY_NPAGE: | |
559 /* scroll down one screenful */ | |
560 y += height; | |
561 break; | |
562 case 'g': | |
563 case KEY_HOME: | |
564 /* go to the first line */ | |
565 y = 0; | |
566 break; | |
567 case 'G': | |
568 case KEY_END: | |
569 /* go to the line */ | |
570 y = MAX(pad_height - height, y); | |
571 break; | |
572 } | |
573 | |
574 if (y < 0) { | |
575 y = 0; | |
576 } else if (y + height > pad_height) { | |
577 y = MAX(pad_height - height, 0); | |
578 } | |
579 if (x < 0) { | |
580 x = 0; | |
581 } else if (x + width > pad_width) { | |
582 x = MAX(pad_width - width, 0); | |
583 } | |
584 | |
585 if ((x != *pos_xp) || (y != *pos_yp)) { | |
586 prefresh(pad, y, x, 1, 0, MIN(height - 1, pad_height - 1), | |
587 MIN(width - 1, pad_width - 1)); | |
588 *pos_yp = y; | |
589 *pos_xp = x; | |
590 } | |
591 | |
592 return (OK); | |
593 } | |
594 | |
595 static int | |
596 highscore_handle_input(struct rantaiwarna_ctx *ctx, int c) | |
597 { | |
598 switch (c) { | |
599 case 'a': | |
600 /* switch to game screen */ | |
601 ctx->active_screen = GAME_SCREEN; | |
602 active_screen_switch(ctx); | |
603 rantaiwarna_doupdate(ctx); | |
604 return (OK); | |
605 case 'H': | |
606 /* switch to help screen */ | |
607 ctx->active_screen = HELP_SCREEN; | |
608 active_screen_switch(ctx); | |
609 rantaiwarna_doupdate(ctx); | |
610 return (OK); | |
611 default: | |
612 /* delegate input handling to scrolled window handler */ | |
613 return (scrolled_win_handle_input(ctx->highscore_pad, | |
614 &ctx->highscore_pos_y, &ctx->highscore_pos_x, c)); | |
615 } | |
616 } | |
617 | |
618 static int | |
619 help_handle_input(struct rantaiwarna_ctx *ctx, int c) | |
620 { | |
621 switch (c) { | |
622 case 'a': | |
623 /* switch to game screen */ | |
624 ctx->active_screen = GAME_SCREEN; | |
625 active_screen_switch(ctx); | |
626 rantaiwarna_doupdate(ctx); | |
627 return (OK); | |
628 case 'i': | |
629 /* switch to highscore screen */ | |
630 ctx->active_screen = HIGHSCORE_SCREEN; | |
631 active_screen_switch(ctx); | |
632 rantaiwarna_doupdate(ctx); | |
633 return (OK); | |
634 default: | |
635 /* delegate input handling to scrolled window handler */ | |
636 return (scrolled_win_handle_input(ctx->help_pad, | |
637 &ctx->help_pos_y, &ctx->help_pos_x, c)); | |
638 } | |
639 } | |
640 | |
641 static int | |
642 game_handle_input(struct rantaiwarna_ctx *ctx, int c) | |
643 { | |
644 int y = ctx->board_cursor_y; | |
645 int x = ctx->board_cursor_x; | |
646 int active_screen = ctx->active_screen; | |
647 int removed = 0; | |
648 #ifdef NCURSES_MOUSE_VERSION | |
649 MEVENT event; | |
650 int mx; | |
651 int my; | |
652 #endif /* NCURSES_MOUSE_VERSION */ | |
653 | |
654 switch (c) { | |
655 case 'k': | |
656 case KEY_UP: | |
657 /* move cursor up */ | |
658 y--; | |
659 break; | |
660 case 'l': | |
661 case KEY_RIGHT: | |
662 /* move cursor right */ | |
663 x++; | |
664 break; | |
665 case 'j': | |
666 case KEY_DOWN: | |
667 /* move cursor down */ | |
668 y++; | |
669 break; | |
670 case 'h': | |
671 case KEY_LEFT: | |
672 /* move cursor left */ | |
673 x--; | |
674 break; | |
675 #ifdef NCURSES_MOUSE_VERSION | |
676 case KEY_MOUSE: | |
677 if (getmouse(&event) == OK) { | |
678 mx = event.x; | |
679 my = event.y; | |
680 | |
681 /* only handle mouse input inside the board window */ | |
682 if (wmouse_trafo(ctx->board_win, &my, &mx, FALSE)) { | |
683 mx = (int)(floor(mx / ELEMENT_WIDTH)); | |
684 | |
685 if (event.bstate & BUTTON1_RELEASED) { | |
686 /* move cursor and remove element */ | |
687 y = my; | |
688 x = mx; | |
689 removed = | |
690 board_remove_elements(ctx->board, | |
691 y, x); | |
692 } else if (event.bstate & | |
693 REPORT_MOUSE_POSITION) { | |
694 /* move cursor to mouse position */ | |
695 y = my; | |
696 x = mx; | |
697 } | |
698 } | |
699 } | |
700 break; | |
701 #endif /* NCURSES_MOUSE_VERSION */ | |
702 case KEY_ENTER: | |
703 case '\n': | |
704 case '\r': | |
705 case ' ': | |
706 /* remove current element */ | |
707 removed = board_remove_elements(ctx->board, y, x); | |
708 break; | |
709 case 'n': | |
710 /* start a new game */ | |
711 ctx->board_cursor_x = ctx->board_cursor_y = 0; | |
712 set_message(ctx, "rantaiwarna"); | |
713 if (board_generate(ctx->board) == OK) { | |
714 score_win_update(ctx); | |
715 message_win_update(ctx); | |
716 board_win_update(ctx); | |
717 rantaiwarna_doupdate(ctx); | |
718 flash(); | |
719 return (OK); | |
720 } else { | |
721 return (ERR); | |
722 } | |
723 case 'H': | |
724 /* switch to help screen */ | |
725 active_screen = HELP_SCREEN; | |
726 break; | |
727 case 'i': | |
728 /* switch to highscore screen */ | |
729 active_screen = HIGHSCORE_SCREEN; | |
730 break; | |
731 } | |
732 | |
733 if (active_screen != ctx->active_screen) { | |
734 ctx->active_screen = active_screen; | |
735 active_screen_switch(ctx); | |
736 rantaiwarna_doupdate(ctx); | |
737 } else if (removed > 0) { | |
738 score_win_update(ctx); | |
739 board_win_update(ctx); | |
740 if (ctx->board->status & GAME_OVER) { | |
741 highscore_update(&ctx->highscore, | |
742 (int16_t)ctx->board->width, | |
743 (int16_t)ctx->board->height, | |
744 (int16_t)ctx->board->colors, ctx->board->score, | |
745 time(NULL)); | |
746 highscore_save(ctx->highscore, ctx->board->height, | |
747 ctx->board->width, ctx->board->colors); | |
748 board_win_update_cursor(ctx, FALSE); | |
749 ctx->board_cursor_y = ctx->board_cursor_x = 0; | |
750 set_message(ctx, "GAME OVER"); | |
751 message_win_update(ctx); | |
752 highscore_pad_update(ctx); | |
753 flash(); | |
754 } else if ((y != ctx->board_cursor_y) || | |
755 (x != ctx->board_cursor_x)) { | |
756 board_win_move_cursor(ctx, y, x); | |
757 } | |
758 rantaiwarna_doupdate(ctx); | |
759 } else if (((ctx->board->status & GAME_OVER) == 0) && | |
760 ((y != ctx->board_cursor_y) || (x != ctx->board_cursor_x))) { | |
761 if (board_win_move_cursor(ctx, y, x) == OK) { | |
762 rantaiwarna_doupdate(ctx); | |
763 } | |
764 } | |
765 | |
766 return (OK); | |
767 } | |
768 | |
769 static void | |
770 all_windows_delete(struct rantaiwarna_ctx *ctx) | |
771 { | |
772 if (ctx == NULL) { | |
773 return; | |
774 } | |
775 | |
776 if (ctx->message_win != NULL) { | |
777 delwin(ctx->message_win); | |
778 ctx->message_win = NULL; | |
779 } | |
780 | |
781 if (ctx->score_win != NULL) { | |
782 delwin(ctx->score_win); | |
783 ctx->score_win = NULL; | |
784 } | |
785 | |
786 if (ctx->game_header_win != NULL) { | |
787 delwin(ctx->game_header_win); | |
788 ctx->game_header_win = NULL; | |
789 } | |
790 | |
791 if (ctx->board_win != NULL) { | |
792 delwin(ctx->board_win); | |
793 ctx->board_win = NULL; | |
794 } | |
795 | |
796 if (ctx->board_container_win != NULL) { | |
797 delwin(ctx->board_container_win); | |
798 ctx->board_container_win = NULL; | |
799 } | |
800 | |
801 if (ctx->highscore_header_win != NULL) { | |
802 delwin(ctx->highscore_header_win); | |
803 ctx->highscore_header_win = NULL; | |
804 } | |
805 | |
806 if (ctx->highscore_pad != NULL) { | |
807 delwin(ctx->highscore_pad); | |
808 ctx->highscore_pad = NULL; | |
809 } | |
810 | |
811 if (ctx->help_header_win != NULL) { | |
812 delwin(ctx->help_header_win); | |
813 ctx->help_header_win = NULL; | |
814 } | |
815 | |
816 if (ctx->help_pad != NULL) { | |
817 delwin(ctx->help_pad); | |
818 ctx->help_pad = NULL; | |
819 } | |
820 | |
821 } | |
822 | |
823 static int | |
824 all_windows_create(struct rantaiwarna_ctx *ctx) | |
825 { | |
826 int width; | |
827 int height; | |
828 char score_label[] = "Score: "; | |
829 int score_label_len; | |
830 int header_min_width; | |
831 char highscore_title[] = "Highscore"; | |
832 int highscore_entry_len; | |
833 char help_title[] = "Help"; | |
834 int help_pad_height; | |
835 int help_pad_width; | |
836 int i; | |
837 int line_len; | |
838 int help_width; | |
839 int help_height; | |
840 time_t dummy_time = (time_t)0; | |
841 | |
842 getmaxyx(stdscr, height, width); | |
843 | |
844 /* enforce minimum width, both board and headers must be visble */ | |
845 score_label_len = (int)strlen(score_label); | |
846 header_min_width = 20 + 1 + score_label_len + SCORE_WIN_WIDTH; | |
847 header_min_width = MAX(header_min_width, (int)strlen(highscore_title)); | |
848 header_min_width = MAX(header_min_width, (int)strlen(help_title)); | |
849 if ((width < ctx->board->width * ELEMENT_WIDTH + 2) || | |
850 (height < 1 + ctx->board->height + 2) || | |
851 (width < header_min_width)) { | |
852 ctx->too_small = TRUE; | |
853 ctx->input_win = stdscr; | |
854 mvaddstr(0, 0, "terminal size is too small"); | |
855 refresh(); | |
856 return (ERR); | |
857 } else { | |
858 ctx->too_small = FALSE; | |
859 } | |
860 | |
861 /* set up game screen */ | |
862 ctx->game_header_win = newwin(1, width, 0, 0); | |
863 if (ctx->game_header_win == NULL) { | |
864 rantaiwarna_errx(1, "could not create window"); | |
865 } | |
866 wbkgd(ctx->game_header_win, A_UNDERLINE | A_BOLD); | |
867 mvwaddstr(ctx->game_header_win, 0, width - (score_label_len + | |
868 SCORE_WIN_WIDTH), score_label); | |
869 | |
870 ctx->score_win = derwin(ctx->game_header_win, 1, SCORE_WIN_WIDTH, 0, | |
871 width - SCORE_WIN_WIDTH); | |
872 if (ctx->score_win == NULL) { | |
873 rantaiwarna_errx(1, "could not create window"); | |
874 } | |
875 score_win_render(ctx); | |
876 | |
877 ctx->message_win = derwin(ctx->game_header_win, 1, width - (1 + | |
878 score_label_len + SCORE_WIN_WIDTH), 0, 0); | |
879 if (ctx->message_win == NULL) { | |
880 rantaiwarna_errx(1, "could not create window"); | |
881 } | |
882 message_win_render(ctx); | |
883 | |
884 ctx->board_container_win = newwin(ctx->board->height + 2, | |
885 ctx->board->width * ELEMENT_WIDTH + 2, (height - 1 - | |
886 (ctx->board->height + 2)) / 2, (width - (ctx->board->width * | |
887 ELEMENT_WIDTH + 2)) / 2); | |
888 if (ctx->board_container_win == NULL) { | |
889 rantaiwarna_errx(1, "could not create window"); | |
890 } | |
891 box(ctx->board_container_win, 0, 0); | |
892 | |
893 ctx->board_win = derwin(ctx->board_container_win, ctx->board->height, | |
894 ctx->board->width * ELEMENT_WIDTH, 1, 1); | |
895 if (ctx->board_win == NULL) { | |
896 rantaiwarna_errx(1, "could not create window"); | |
897 } | |
898 ctx->input_win = ctx->board_win; | |
899 wbkgd(ctx->board_win, A_BOLD); | |
900 wtimeout(ctx->board_win, 100); | |
901 keypad(ctx->board_win, TRUE); | |
902 board_win_render(ctx); | |
903 | |
904 board_win_render_cursor(ctx, TRUE); | |
905 | |
906 /* set up highscore screen */ | |
907 ctx->highscore_header_win = newwin(1, width, 0, 0); | |
908 if (ctx->highscore_header_win == NULL) { | |
909 rantaiwarna_errx(1, "could not create window"); | |
910 } | |
911 wbkgd(ctx->highscore_header_win, A_UNDERLINE | A_BOLD); | |
912 mvwaddstr(ctx->highscore_header_win, 0, 0, highscore_title); | |
913 | |
914 if (strftime(ctx->dummy_timestr, sizeof (ctx->dummy_timestr), "%x", | |
915 gmtime(&dummy_time)) == 0) { | |
916 ctx->dummy_timestr[0] = '\0'; | |
917 } | |
918 highscore_entry_len = snprintf(NULL, 0, HIGHSCORE_FMT, 1, | |
919 ctx->dummy_timestr, 0); | |
920 if (highscore_entry_len < 0) { | |
921 rantaiwarna_err(1, NULL); | |
922 } | |
923 ctx->highscore_pad = newpad(10, highscore_entry_len); | |
924 if (ctx->highscore_pad == NULL) { | |
925 rantaiwarna_errx(1, "could not create pad"); | |
926 } | |
927 highscore_pad_update(ctx); | |
928 | |
929 /* set up help screen */ | |
930 ctx->help_header_win = newwin(1, width, 0, 0); | |
931 if (ctx->help_header_win == NULL) { | |
932 rantaiwarna_errx(1, "could not create window"); | |
933 } | |
934 wbkgd(ctx->help_header_win, A_UNDERLINE | A_BOLD); | |
935 mvwaddstr(ctx->help_header_win, 0, 0, help_title); | |
936 | |
937 for (i = 0, help_width = 1; help_strs[i] != NULL; i++) { | |
938 line_len = (int)strlen(help_strs[i]); | |
939 if (line_len > help_width) { | |
940 help_width = line_len; | |
941 } | |
942 } | |
943 help_height = i; | |
944 ctx->help_pad = newpad(help_height, help_width); | |
945 if (ctx->help_pad == NULL) { | |
946 rantaiwarna_errx(1, "could not create pad"); | |
947 } | |
948 | |
949 for (i = 0; help_strs[i] != NULL; i++) { | |
950 mvwaddstr(ctx->help_pad, i, 0, help_strs[i]); | |
951 } | |
952 getmaxyx(ctx->help_pad, help_pad_height, help_pad_width); | |
953 if (ctx->help_pos_y + height - 1 > help_height) { | |
954 ctx->help_pos_y = MAX(help_height - height - 1, 0); | |
955 } | |
956 if (ctx->help_pos_x + width > help_width) { | |
957 ctx->help_pos_x = MAX(help_width - width, 0); | |
958 } | |
959 | |
960 active_screen_switch(ctx); | |
961 | |
962 return (OK); | |
963 } | |
964 | |
965 static void | |
966 handle_resize(struct rantaiwarna_ctx *ctx) | |
967 { | |
968 all_windows_delete(ctx); | |
969 clear(); | |
970 endwin(); | |
971 refresh(); | |
972 if (all_windows_create(ctx) == OK) { | |
973 rantaiwarna_doupdate(ctx); | |
974 } | |
975 } | |
976 | |
977 static int | |
978 main_loop(struct rantaiwarna_ctx *ctx) | |
979 { | |
980 sigset_t sigset; | |
981 sigset_t old_sigset; | |
982 ssize_t n; | |
983 int saved_errno; | |
984 int c; | |
985 | |
986 while (TRUE) { | |
987 /* | |
988 * deal with pending signals previously received in the signal | |
989 * handler, try to read a sigset from the pipe, avoid partial | |
990 * reads by blocking all signals during the read operation | |
991 */ | |
992 sigfillset(&sigset); | |
993 sigprocmask(SIG_BLOCK, &sigset, &old_sigset); | |
994 n = read(signal_pipe_fd[PIPE_R_FD], &sigset, sizeof (sigset)); | |
995 saved_errno = errno; | |
996 sigprocmask(SIG_SETMASK, &old_sigset, NULL); | |
997 if (n == -1) { | |
998 if (saved_errno != EAGAIN) { | |
999 /* unknown error */ | |
1000 return (ERR); | |
1001 } | |
1002 } else if ((size_t)n != sizeof (sigset)) { | |
1003 /* short read, should not happen */ | |
1004 return (ERR); | |
1005 } else { | |
1006 if ((sigismember(&sigset, SIGINT) == 1) || | |
1007 (sigismember(&sigset, SIGTERM) == 1) || | |
1008 (sigismember(&sigset, SIGQUIT) == 1) || | |
1009 (sigismember(&sigset, SIGHUP) == 1)) { | |
1010 return (ERR); | |
1011 } | |
1012 #ifdef SIGWINCH | |
1013 if (sigismember(&sigset, SIGWINCH) == 1) { | |
1014 /* handle terminal resize */ | |
1015 handle_resize(ctx); | |
1016 } | |
1017 #endif /* SIGWINCH */ | |
1018 } | |
1019 | |
1020 /* handle keyboard and mouse input */ | |
1021 c = wgetch(ctx->input_win); | |
1022 /* only allow to quit if the terminal is too small */ | |
1023 if (ctx->too_small) { | |
1024 if (c == 'q') { | |
1025 return (OK); | |
1026 } else { | |
1027 continue; | |
1028 } | |
1029 } else { | |
1030 /* handle global keys */ | |
1031 switch (c) { | |
1032 case ERR: | |
1033 /* no input */ | |
1034 continue; | |
1035 case 'q': | |
1036 /* quit */ | |
1037 return (OK); | |
1038 case '\f': | |
1039 /* refresh */ | |
1040 handle_resize(ctx); | |
1041 continue; | |
1042 default: | |
1043 /* | |
1044 * delegate input handling to screen-specific | |
1045 * handler | |
1046 */ | |
1047 switch (ctx->active_screen) { | |
1048 case GAME_SCREEN: | |
1049 if (game_handle_input(ctx, c) == ERR) { | |
1050 return (ERR); | |
1051 } | |
1052 break; | |
1053 case HIGHSCORE_SCREEN: | |
1054 if (highscore_handle_input(ctx, c) == | |
1055 ERR) { | |
1056 return (ERR); | |
1057 } | |
1058 break; | |
1059 case HELP_SCREEN: | |
1060 if (help_handle_input(ctx, c) == ERR) { | |
1061 return (ERR); | |
1062 } | |
1063 break; | |
1064 } | |
1065 } | |
1066 } | |
1067 } | |
1068 } | |
1069 | |
1070 int | |
1071 main(int argc, char *argv[]) | |
1072 { | |
1073 int status = EXIT_FAILURE; | |
1074 struct rantaiwarna_ctx *ctx = NULL; | |
1075 int c; | |
1076 bool errflag = FALSE; | |
1077 char *endptr; | |
1078 long colors = DEFAULT_COLORS; | |
1079 long height = DEFAULT_HEIGHT; | |
1080 long width = DEFAULT_WIDTH; | |
1081 int flags; | |
1082 struct sigaction sigact; | |
1083 | |
1084 setlocale(LC_ALL, ""); | |
1085 | |
1086 if ((progname = strrchr(argv[0], '/')) == NULL) { | |
1087 progname = argv[0]; | |
1088 } else { | |
1089 progname++; | |
1090 } | |
1091 | |
1092 while (!errflag && (c = getopt(argc, argv, "c:h:w:")) != -1) { | |
1093 switch (c) { | |
1094 case 'c': | |
1095 errno = 0; | |
1096 colors = strtol(optarg, &endptr, 10); | |
1097 if ((errno != 0) || (*endptr != '\0') || (colors < 2) || | |
1098 (colors > COLORS_MAX - 1)) { | |
1099 errflag = TRUE; | |
1100 } | |
1101 break; | |
1102 case 'h': | |
1103 errno = 0; | |
1104 height = strtol(optarg, &endptr, 10); | |
1105 if ((errno != 0) || (*endptr != '\0') || | |
1106 (height < HEIGHT_MIN) || (height > HEIGHT_MAX)) { | |
1107 errflag = TRUE; | |
1108 } | |
1109 break; | |
1110 case 'w': | |
1111 errno = 0; | |
1112 width = strtol(optarg, &endptr, 10); | |
1113 if ((errno != 0) || (*endptr != '\0') || | |
1114 (width < WIDTH_MIN) || (width > WIDTH_MAX)) { | |
1115 errflag = TRUE; | |
1116 } | |
1117 break; | |
1118 default: | |
1119 errflag = TRUE; | |
1120 } | |
1121 } | |
1122 if (errflag) { | |
1123 fprintf(stderr, "usage: %s [-c colors] [-w width] " | |
1124 "[-h height]\n", progname); | |
1125 status = EXIT_USAGE; | |
1126 goto out; | |
1127 } | |
1128 | |
1129 ctx = rantaiwarna_malloc(sizeof (struct rantaiwarna_ctx)); | |
1130 ctx->board = board_create((int)height, (int)width, (short)colors); | |
1131 if (ctx->board->elements == NULL) { | |
1132 goto out; | |
1133 } | |
1134 ctx->active_screen = GAME_SCREEN; | |
1135 ctx->highscore = NULL; | |
1136 ctx->use_colors = FALSE; | |
1137 ctx->too_small = FALSE; | |
1138 ctx->game_header_win = NULL; | |
1139 ctx->message_win = NULL; | |
1140 ctx->score_win = NULL; | |
1141 ctx->board_container_win = NULL; | |
1142 ctx->board_win = NULL; | |
1143 ctx->highscore_pad = NULL; | |
1144 ctx->highscore_header_win = NULL; | |
1145 ctx->help_pad = NULL; | |
1146 ctx->help_header_win = NULL; | |
1147 ctx->input_win = stdscr; | |
1148 ctx->board_cursor_x = ctx->board_cursor_y = 0; | |
1149 ctx->highscore_pos_x = ctx->highscore_pos_y = 0; | |
1150 ctx->help_pos_x = ctx->help_pos_y = 0; | |
1151 ctx->message[0] = '\0'; | |
1152 ctx->dummy_timestr[0] = '\0'; | |
1153 ctx->elements_chs = elements_chs; | |
1154 | |
1155 highscore_load(&ctx->highscore, ctx->board->height, ctx->board->width, | |
1156 ctx->board->colors); | |
1157 | |
1158 /* create pipe for delivering signals to a listener in the main loop */ | |
1159 if (pipe(signal_pipe_fd) == -1) { | |
1160 goto out; | |
1161 } | |
1162 flags = fcntl(signal_pipe_fd[PIPE_R_FD], F_GETFL, 0); | |
1163 if ((flags == -1) || (fcntl(signal_pipe_fd[PIPE_R_FD], F_SETFL, | |
1164 flags | O_NONBLOCK) == -1)) { | |
1165 goto out; | |
1166 } | |
1167 flags = fcntl(signal_pipe_fd[PIPE_W_FD], F_GETFL, 0); | |
1168 if ((flags == -1) || (fcntl(signal_pipe_fd[PIPE_W_FD], F_SETFL, | |
1169 flags | O_NONBLOCK) == -1)) { | |
1170 goto out; | |
1171 } | |
1172 | |
1173 /* set up signal handler */ | |
1174 sigact.sa_handler = on_signal; | |
1175 sigact.sa_flags = SA_RESTART; | |
1176 sigemptyset(&sigact.sa_mask); | |
1177 if ((sigaction(SIGINT, &sigact, NULL) < 0) || | |
1178 (sigaction(SIGTERM, &sigact, NULL) < 0) || | |
1179 (sigaction(SIGQUIT, &sigact, NULL) < 0) || | |
1180 (sigaction(SIGHUP, &sigact, NULL) < 0)) { | |
1181 goto out; | |
1182 } | |
1183 #ifdef SIGWINCH | |
1184 if (sigaction(SIGWINCH, &sigact, NULL) < 0) { | |
1185 goto out; | |
1186 } | |
1187 #endif /* SIGWINCH */ | |
1188 | |
1189 /* initialize curses */ | |
1190 initscr(); | |
1191 curses_init = TRUE; | |
1192 cbreak(); | |
1193 noecho(); | |
1194 intrflush(stdscr, FALSE); | |
1195 nonl(); | |
1196 curs_set(0); | |
1197 timeout(100); | |
1198 keypad(stdscr, TRUE); | |
1199 #ifdef NCURSES_MOUSE_VERSION | |
1200 mousemask(REPORT_MOUSE_POSITION | ALL_MOUSE_EVENTS, NULL); | |
1201 mouseinterval(0); | |
1202 #endif /* NCURSES_MOUSE_VERSION */ | |
1203 if (has_colors()) { | |
1204 start_color(); | |
1205 if ((COLORS >= 8) && (COLOR_PAIRS >= 12)) { | |
1206 ctx->use_colors = TRUE; | |
1207 /* colors 0 -- COLORS_MAX correspond to elements */ | |
1208 init_pair(1, COLOR_RED, COLOR_RED); | |
1209 init_pair(2, COLOR_GREEN, COLOR_GREEN); | |
1210 init_pair(3, COLOR_YELLOW, COLOR_YELLOW); | |
1211 init_pair(4, COLOR_BLUE, COLOR_BLUE); | |
1212 init_pair(5, COLOR_MAGENTA, COLOR_MAGENTA); | |
1213 /* | |
1214 * colors COLORS_MAX -- (COLORS_MAX + COLORS_MAX) | |
1215 * are used for the cursor to highlight elements | |
1216 */ | |
1217 init_pair(6, COLOR_WHITE, COLOR_BLACK); | |
1218 init_pair(7, COLOR_WHITE, COLOR_RED); | |
1219 init_pair(8, COLOR_WHITE, COLOR_GREEN); | |
1220 init_pair(9, COLOR_WHITE, COLOR_YELLOW); | |
1221 init_pair(10, COLOR_WHITE, COLOR_BLUE); | |
1222 init_pair(11, COLOR_WHITE, COLOR_MAGENTA); | |
1223 } | |
1224 } | |
1225 | |
1226 set_message(ctx, "rantaiwarna"); | |
1227 if (board_generate(ctx->board) == ERR) { | |
1228 goto out; | |
1229 } | |
1230 | |
1231 if (all_windows_create(ctx) == ERR) { | |
1232 goto out; | |
1233 } | |
1234 rantaiwarna_doupdate(ctx); | |
1235 | |
1236 if (main_loop(ctx) == OK) { | |
1237 status = EXIT_SUCCESS; | |
1238 } | |
1239 | |
1240 out: | |
1241 restore_term(); | |
1242 all_windows_delete(ctx); | |
1243 | |
1244 if (signal_pipe_fd[PIPE_R_FD] != -1) { | |
1245 close(signal_pipe_fd[PIPE_R_FD]); | |
1246 } | |
1247 if (signal_pipe_fd[PIPE_W_FD] != -1) { | |
1248 close(signal_pipe_fd[PIPE_W_FD]); | |
1249 } | |
1250 | |
1251 if (ctx != NULL) { | |
1252 board_free(ctx->board); | |
1253 highscore_free(ctx->highscore); | |
1254 } | |
1255 free(ctx); | |
1256 | |
1257 exit(status); | |
1258 } |