summaryrefslogtreecommitdiff
path: root/apps/plugins/reversi/reversi-gui.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/reversi/reversi-gui.c')
-rw-r--r--apps/plugins/reversi/reversi-gui.c669
1 files changed, 669 insertions, 0 deletions
diff --git a/apps/plugins/reversi/reversi-gui.c b/apps/plugins/reversi/reversi-gui.c
new file mode 100644
index 0000000000..e543563729
--- /dev/null
+++ b/apps/plugins/reversi/reversi-gui.c
@@ -0,0 +1,669 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (c) 2006 Alexander Levin
11 *
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
14 *
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
17 *
18 ****************************************************************************/
19
20/*
21GUI part of reversi. Code is inspired by sudoku code by Dave Chapman
22which is copyright (c) 2005 Dave Chapman and is released under the
23GNU General Public License.
24
25
26User instructions
27-----------------
28
29Use the arrow keys to move cursor, and press TOGGLE to place a stone.
30
31At any time during the game, press MENU to bring up the game menu with
32further options:
33
34 - Save
35 - Reload
36 - Clear
37
38*/
39
40#include "plugin.h"
41
42#ifdef HAVE_LCD_BITMAP
43
44#include "reversi-game.h"
45#include "reversi-strategy.h"
46#include "reversi-gui.h"
47
48#include "../lib/oldmenuapi.h"
49
50PLUGIN_HEADER
51
52/* The global api struct pointer. While not strictly necessary,
53 it's nice not to have to pass the api pointer in all function
54 calls in the plugin */
55static struct plugin_api* rb;
56
57/* Thickness of the grid lines */
58#define LINE_THCK 1
59
60#if LCD_HEIGHT <= LCD_WIDTH /* Horizontal layout */
61
62#if (LCD_HEIGHT==64) && (LCD_WIDTH==112)
63/* Archos Recorders and Ondios - 112x64, 8 cells @ 8x6 with 9 border lines */
64
65/* Internal dimensions of a cell */
66#define CELL_WIDTH 8
67#define CELL_HEIGHT 6
68#define SMALL_BOARD
69
70#elif (LCD_HEIGHT==110) && (LCD_WIDTH==138)
71/* iPod Mini - 138x110, 8 cells @ 10x10 with 9 border lines */
72
73/* Internal dimensions of a cell */
74#define CELL_WIDTH 10
75#define CELL_HEIGHT 10
76
77#elif (LCD_HEIGHT==128) && (LCD_WIDTH==128)
78/* iriver H10 5-6GB - 128x128, 8 cells @ 10x10 with 9 border lines */
79
80/* Internal dimensions of a cell */
81#define CELL_WIDTH 10
82#define CELL_HEIGHT 10
83
84#elif ((LCD_HEIGHT==128) && (LCD_WIDTH==160)) || \
85 ((LCD_HEIGHT==132) && (LCD_WIDTH==176))
86/* iAudio X5, Iriver H1x0, iPod G3, G4 - 160x128; */
87/* iPod Nano - 176x132, 8 cells @ 12x12 with 9 border lines */
88
89/* Internal dimensions of a cell */
90#define CELL_WIDTH 12
91#define CELL_HEIGHT 12
92
93#elif ((LCD_HEIGHT==176) && (LCD_WIDTH==220)) || \
94 ((LCD_HEIGHT==220) && (LCD_WIDTH==176))
95/* Iriver h300, iPod Color/Photo - 220x176, 8 cells @ 16x16 with 9 border lines */
96
97/* Internal dimensions of a cell */
98#define CELL_WIDTH 16
99#define CELL_HEIGHT 16
100
101#elif (LCD_HEIGHT>=240) && (LCD_WIDTH>=320)
102/* iPod Video - 320x240, 8 cells @ 24x24 with 9 border lines */
103
104/* Internal dimensions of a cell */
105#define CELL_WIDTH 24
106#define CELL_HEIGHT 24
107
108#else
109 #error REVERSI: Unsupported LCD size
110#endif
111
112#else /* Vertical layout */
113#define VERTICAL_LAYOUT
114
115#if (LCD_HEIGHT>=320) && (LCD_WIDTH>=240)
116/* Gigabeat - 240x320, 8 cells @ 24x24 with 9 border lines */
117
118/* Internal dimensions of a cell */
119#define CELL_WIDTH 24
120#define CELL_HEIGHT 24
121
122#elif (LCD_HEIGHT>=220) && (LCD_WIDTH>=176)
123/* e200 - 176x220, 8 cells @ 12x12 with 9 border lines */
124
125/* Internal dimensions of a cell */
126#define CELL_WIDTH 18
127#define CELL_HEIGHT 18
128
129#else
130 #error REVERSI: Unsupported LCD size
131#endif
132
133#endif /* Layout */
134
135
136/* Where the board begins */
137#define XOFS 4
138#define YOFS 4
139
140/* Total width and height of the board without enclosing box */
141#define BOARD_WIDTH (CELL_WIDTH*BOARD_SIZE + LINE_THCK*(BOARD_SIZE+1))
142#define BOARD_HEIGHT (CELL_HEIGHT*BOARD_SIZE + LINE_THCK*(BOARD_SIZE+1))
143
144/* Thickness of the white cells' lines */
145#if (CELL_WIDTH >= 15) && (CELL_HEIGHT >= 15)
146#define CELL_LINE_THICKNESS 2
147#else
148#define CELL_LINE_THICKNESS 1
149#endif
150
151/* Margins within a cell */
152#if (CELL_WIDTH >= 10) && (CELL_HEIGHT >= 10)
153#define STONE_MARGIN 2
154#else
155#define STONE_MARGIN 1
156#endif
157
158#define CURSOR_MARGIN (STONE_MARGIN + CELL_LINE_THICKNESS)
159
160/* Upper left corner of a cell */
161#define CELL_X(c) (XOFS + (c)*CELL_WIDTH + ((c)+1)*LINE_THCK)
162#define CELL_Y(r) (YOFS + (r)*CELL_HEIGHT + ((r)+1)*LINE_THCK)
163
164
165#ifdef VERTICAL_LAYOUT
166#define LEGEND_X(lc) (CELL_X(lc))
167#define LEGEND_Y(lr) (CELL_Y(BOARD_SIZE+(lr)) + CELL_HEIGHT/2)
168#else
169#define LEGEND_X(lc) (CELL_X(BOARD_SIZE+(lc)) + CELL_WIDTH/2)
170#define LEGEND_Y(lr) (CELL_Y(lr))
171#endif
172
173
174/* Board state */
175static reversi_board_t game;
176
177/* --- Setting values --- */
178
179/* Playing strategies used by white and black players */
180const game_strategy_t *white_strategy;
181const game_strategy_t *black_strategy;
182
183/* Cursor position */
184static int cur_row, cur_col;
185
186/* Color for the next move (BLACK/WHITE) */
187static int cur_player;
188
189/* Active cursor wrapping mode */
190static cursor_wrap_mode_t cursor_wrap_mode;
191
192static bool quit_plugin;
193
194
195/* Initialises the state of the game (starts a new game) */
196static void reversi_gui_init(void) {
197 reversi_init_game(&game);
198 white_strategy = &strategy_human;
199 black_strategy = &strategy_human;
200 cur_player = BLACK;
201
202 /* Place the cursor so that WHITE can make a move */
203 cur_row = 2;
204 cur_col = 3;
205}
206
207
208/* Draws the cursor in the specified cell. Cursor is drawn in the complement
209 * mode, i.e. drawing it twice will result in no changes on the screen.
210 */
211static void reversi_gui_display_cursor(int row, int col) {
212 int old_mode, x, y;
213 old_mode = rb->lcd_get_drawmode();
214 x = CELL_X(col);
215 y = CELL_Y(row);
216
217 rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
218 rb->lcd_drawline(x+CURSOR_MARGIN, y+CURSOR_MARGIN,
219 x+CELL_WIDTH-CURSOR_MARGIN-1, y+CELL_HEIGHT-CURSOR_MARGIN-1);
220 rb->lcd_drawline(x+CURSOR_MARGIN, y+CELL_HEIGHT-CURSOR_MARGIN-1,
221 x+CELL_WIDTH-CURSOR_MARGIN-1, y+CURSOR_MARGIN);
222
223 /* Draw the shadows */
224 rb->lcd_hline(x, x+CELL_WIDTH-1, YOFS-3);
225 rb->lcd_hline(x, x+CELL_WIDTH-1, YOFS+BOARD_HEIGHT+2);
226 rb->lcd_vline(XOFS-3, y, y+CELL_HEIGHT-1);
227 rb->lcd_vline(XOFS+BOARD_WIDTH+2, y, y+CELL_HEIGHT-1);
228
229 rb->lcd_set_drawmode(old_mode);
230 rb->lcd_update();
231}
232
233
234/* Draws the cell of the specified color (WHITE/BLACK) assuming that
235 * the upper left corner of the cell is at (x, y) */
236static void reversi_gui_draw_cell(int x, int y, int color) {
237 int i;
238 if (color == WHITE) {
239 for (i = 0; i < CELL_LINE_THICKNESS; i++) {
240 rb->lcd_drawrect(x+STONE_MARGIN+i, y+STONE_MARGIN+i,
241 CELL_WIDTH-2*(STONE_MARGIN+i), CELL_HEIGHT-2*(STONE_MARGIN+i));
242 }
243 } else if (color == BLACK) {
244 rb->lcd_fillrect(x+STONE_MARGIN, y+STONE_MARGIN,
245 CELL_WIDTH-2*STONE_MARGIN, CELL_HEIGHT-2*STONE_MARGIN);
246 } else {
247 /* Cell is free -> nothing to do */
248 }
249}
250
251
252/* Draws the complete screen */
253static void reversi_gui_display_board(void) {
254 int x, y, r, c, x_width, x_height;
255 char buf[8];
256
257 /* Clear the display buffer */
258 rb->lcd_clear_display();
259 rb->lcd_set_drawmode(DRMODE_FG);
260
261 /* Thicker board box */
262 rb->lcd_drawrect(XOFS-1, YOFS-1, BOARD_WIDTH+2, BOARD_HEIGHT+2);
263
264 /* Draw the gridlines */
265 for (r=0, x=XOFS, y=YOFS; r<=BOARD_SIZE;
266 r++, x+=CELL_WIDTH+LINE_THCK, y+=CELL_HEIGHT+LINE_THCK) {
267 rb->lcd_hline(XOFS, XOFS+BOARD_WIDTH-1, y);
268 rb->lcd_vline(x, YOFS, YOFS+BOARD_HEIGHT-1);
269 }
270
271 /* Draw the stones. This is not the most efficient way but more readable */
272 for (r=0; r<BOARD_SIZE; r++) {
273 y = CELL_Y(r);
274 for (c=0; c<BOARD_SIZE; c++) {
275 x = CELL_X(c);
276 reversi_gui_draw_cell(x, y, game.board[r][c]);
277 }
278 }
279
280 /* Draw the cursor */
281 reversi_gui_display_cursor(cur_row, cur_col);
282
283 /* Draw the current score */
284 reversi_count_occupied_cells(&game, &r, &c);
285 rb->lcd_getstringsize("x", &x_width, &x_height);
286
287 x = LEGEND_X(0);
288 y = LEGEND_Y(0);
289 reversi_gui_draw_cell(x, y, BLACK);
290 rb->snprintf(buf, sizeof(buf), "%d", c);
291 y += (CELL_HEIGHT-x_height) / 2;
292 rb->lcd_putsxy(x + CELL_WIDTH + CELL_WIDTH/2, y, buf);
293
294 y = LEGEND_Y(1);
295 reversi_gui_draw_cell(x, y, WHITE);
296 rb->snprintf(buf, sizeof(buf), "%d", r);
297 y += (CELL_HEIGHT-x_height) / 2;
298 rb->lcd_putsxy(x + CELL_WIDTH + CELL_WIDTH/2, y, buf);
299
300 /* Draw the box around the current player */
301 r = (cur_player == BLACK ? 0 : 1);
302 y = LEGEND_Y(r);
303 rb->lcd_drawrect(x-1, y-1, CELL_WIDTH+2, CELL_HEIGHT+2);
304
305 /* Update the screen */
306 rb->lcd_update();
307}
308
309
310/*
311 * Menu related stuff
312 */
313
314/* Menu entries and the corresponding values for cursor wrap mode */
315#define MENU_TEXT_WRAP_MODE "Cursor wrap mode"
316static const struct opt_items cursor_wrap_mode_settings[] = {
317 { "Flat board", NULL },
318 { "Sphere", NULL },
319 { "Torus", NULL },
320};
321static const cursor_wrap_mode_t cursor_wrap_mode_values[3] = {
322 WRAP_FLAT, WRAP_SPHERE, WRAP_TORUS };
323
324
325/* Menu entries and the corresponding values for available strategies */
326#define MENU_TEXT_STRAT_WHITE "Strategy for white"
327#define MENU_TEXT_STRAT_BLACK "Strategy for black"
328
329static struct opt_items strategy_settings[] = {
330 { "Human", NULL },
331 { "Silly robot", NULL },
332 { "Smart robot", NULL },
333};
334static const game_strategy_t * const strategy_values[] = {
335 &strategy_human, &strategy_novice, &strategy_expert };
336
337
338/* Sets the strategy for the specified player. 'player' is the
339 pointer to the player to set the strategy for (actually,
340 either white_strategy or black_strategy). propmpt is the
341 text to show as the prompt in the menu */
342static bool reversi_gui_choose_strategy(
343 const game_strategy_t **player, const char *prompt) {
344 int index = 0, i;
345 int num_items = sizeof(strategy_settings)/sizeof(strategy_settings[0]);
346 bool result;
347
348 for (i = 0; i < num_items; i++) {
349 if ((*player) == strategy_values[i]) {
350 index = i;
351 break;
352 }
353 }
354 result = rb->set_option(prompt, &index, INT, strategy_settings, num_items, NULL);
355 (*player) = strategy_values[index];
356
357 return result;
358}
359
360
361/* Returns true iff USB ws connected while in the menu */
362static bool reversi_gui_menu(void) {
363 int m, index, num_items, i;
364 int result;
365
366 static const struct menu_item items[] = {
367 { "Start new game", NULL },
368 { "Pass the move", NULL },
369 { MENU_TEXT_STRAT_BLACK, NULL },
370 { MENU_TEXT_STRAT_WHITE, NULL },
371 { MENU_TEXT_WRAP_MODE, NULL },
372 { "Quit", NULL },
373 };
374
375 m = menu_init(rb, items, sizeof(items) / sizeof(*items),
376 NULL, NULL, NULL, NULL);
377
378 result = menu_show(m);
379
380 switch (result) {
381 case 0: /* Start a new game */
382 reversi_gui_init();
383 break;
384
385 case 1: /* Pass the move to the partner */
386 cur_player = reversi_flipped_color(cur_player);
387 break;
388
389 case 2: /* Strategy for black */
390 reversi_gui_choose_strategy(&black_strategy, MENU_TEXT_STRAT_BLACK);
391 break;
392
393 case 3: /* Strategy for white */
394 reversi_gui_choose_strategy(&white_strategy, MENU_TEXT_STRAT_WHITE);
395 break;
396
397 case 4: /* Cursor wrap mode */
398 num_items = sizeof(cursor_wrap_mode_values)/sizeof(cursor_wrap_mode_values[0]);
399 index = 0;
400 for (i = 0; i < num_items; i++) {
401 if (cursor_wrap_mode == cursor_wrap_mode_values[i]) {
402 index = i;
403 break;
404 }
405 }
406 rb->set_option(MENU_TEXT_WRAP_MODE, &index, INT,
407 cursor_wrap_mode_settings, 3, NULL);
408 cursor_wrap_mode = cursor_wrap_mode_values[index];
409 break;
410
411 case 5: /* Quit */
412 quit_plugin = true;
413 break;
414 }
415
416 menu_exit(m);
417
418 return (result == MENU_ATTACHED_USB);
419}
420
421
422/* Calculates the new cursor position if the user wants to move it
423 * vertically as specified by delta. Current wrap mode is respected.
424 * The cursor is not actually moved.
425 *
426 * Returns true iff the cursor would be really moved. In any case, the
427 * new cursor position is stored in (new_row, new_col).
428 */
429static bool reversi_gui_cursor_pos_vmove(int row_delta, int *new_row, int *new_col) {
430 *new_row = cur_row + row_delta;
431 *new_col = cur_col;
432
433 if (*new_row < 0) {
434 switch (cursor_wrap_mode) {
435 case WRAP_FLAT:
436 *new_row = cur_row;
437 break;
438 case WRAP_SPHERE:
439 *new_row = BOARD_SIZE - 1;
440 break;
441 case WRAP_TORUS:
442 *new_row = BOARD_SIZE - 1;
443 (*new_col)--;
444 if (*new_col < 0) {
445 *new_col = BOARD_SIZE - 1;
446 }
447 break;
448 }
449 } else if (*new_row >= BOARD_SIZE) {
450 switch (cursor_wrap_mode) {
451 case WRAP_FLAT:
452 *new_row = cur_row;
453 break;
454 case WRAP_SPHERE:
455 *new_row = 0;
456 break;
457 case WRAP_TORUS:
458 *new_row = 0;
459 (*new_col)++;
460 if (*new_col >= BOARD_SIZE) {
461 *new_col = 0;
462 }
463 break;
464 }
465 }
466
467 return (cur_row != (*new_row)) || (cur_col != (*new_col));
468}
469
470
471/* Calculates the new cursor position if the user wants to move it
472 * horisontally as specified by delta. Current wrap mode is respected.
473 * The cursor is not actually moved.
474 *
475 * Returns true iff the cursor would be really moved. In any case, the
476 * new cursor position is stored in (new_row, new_col).
477 */
478static bool reversi_gui_cursor_pos_hmove(int col_delta, int *new_row, int *new_col) {
479 *new_row = cur_row;
480 *new_col = cur_col + col_delta;
481
482 if (*new_col < 0) {
483 switch (cursor_wrap_mode) {
484 case WRAP_FLAT:
485 *new_col = cur_col;
486 break;
487 case WRAP_SPHERE:
488 *new_col = BOARD_SIZE - 1;
489 break;
490 case WRAP_TORUS:
491 *new_col = BOARD_SIZE - 1;
492 (*new_row)--;
493 if (*new_row < 0) {
494 *new_row = BOARD_SIZE - 1;
495 }
496 break;
497 }
498 } else if (*new_col >= BOARD_SIZE) {
499 switch (cursor_wrap_mode) {
500 case WRAP_FLAT:
501 *new_col = cur_col;
502 break;
503 case WRAP_SPHERE:
504 *new_col = 0;
505 break;
506 case WRAP_TORUS:
507 *new_col = 0;
508 (*new_row)++;
509 if (*new_row >= BOARD_SIZE) {
510 *new_row = 0;
511 }
512 break;
513 }
514 }
515
516 return (cur_row != (*new_row)) || (cur_col != (*new_col));
517}
518
519
520/* Actually moves the cursor to the new position and updates the screen */
521static void reversi_gui_move_cursor(int new_row, int new_col) {
522 int old_row, old_col;
523
524 old_row = cur_row;
525 old_col = cur_col;
526
527 cur_row = new_row;
528 cur_col = new_col;
529
530 /* Only update the changed cells since there are no global changes */
531 reversi_gui_display_cursor(old_row, old_col);
532 reversi_gui_display_cursor(new_row, new_col);
533}
534
535
536/* plugin entry point */
537enum plugin_status plugin_start(struct plugin_api *api, void *parameter) {
538 bool exit, draw_screen;
539 int button;
540 int lastbutton = BUTTON_NONE;
541 int row, col;
542 int w_cnt, b_cnt;
543 char msg_buf[30];
544
545 /* plugin init */
546 rb = api;
547 /* end of plugin init */
548
549#if LCD_DEPTH > 1
550 rb->lcd_set_backdrop(NULL);
551 rb->lcd_set_foreground(LCD_BLACK);
552 rb->lcd_set_background(LCD_WHITE);
553#endif
554
555 /* Avoid compiler warnings */
556 (void)parameter;
557
558 reversi_gui_init();
559 cursor_wrap_mode = WRAP_FLAT;
560
561 /* The main game loop */
562 exit = false;
563 quit_plugin = false;
564 draw_screen = true;
565 while (!exit && !quit_plugin) {
566 if (draw_screen) {
567 reversi_gui_display_board();
568 draw_screen = false;
569 }
570 button = rb->button_get(true);
571
572 switch (button) {
573#ifdef REVERSI_BUTTON_QUIT
574 /* Exit game */
575 case REVERSI_BUTTON_QUIT:
576 exit = true;
577 break;
578#endif
579
580#ifdef REVERSI_BUTTON_ALT_MAKE_MOVE
581 case REVERSI_BUTTON_ALT_MAKE_MOVE:
582#endif
583 case REVERSI_BUTTON_MAKE_MOVE:
584#ifdef REVERSI_BUTTON_MAKE_MOVE_PRE
585 if ((button == REVERSI_BUTTON_MAKE_MOVE)
586 && (lastbutton != REVERSI_BUTTON_MAKE_MOVE_PRE))
587 break;
588#endif
589 if (reversi_make_move(&game, cur_row, cur_col, cur_player) > 0) {
590 /* Move was made. Global changes on the board are possible */
591 draw_screen = true; /* Redraw the screen next time */
592 cur_player = reversi_flipped_color(cur_player);
593 if (reversi_game_is_finished(&game)) {
594 reversi_count_occupied_cells(&game, &w_cnt, &b_cnt);
595 rb->snprintf(msg_buf, sizeof(msg_buf),
596 "Game over. %s have won.",
597 (w_cnt>b_cnt?"WHITE":"BLACK"));
598 rb->splash(HZ*2, msg_buf);
599 draw_screen = true; /* Must update screen after splash */
600 }
601 } else {
602 /* An attempt to make an invalid move */
603 rb->splash(HZ/2, "Illegal move!");
604 draw_screen = true;
605 /* Ignore any button presses during the splash */
606 rb->button_clear_queue();
607 }
608 break;
609
610 /* Move cursor left */
611 case REVERSI_BUTTON_LEFT:
612 case (REVERSI_BUTTON_LEFT | BUTTON_REPEAT):
613 if (reversi_gui_cursor_pos_hmove(-1, &row, &col)) {
614 reversi_gui_move_cursor(row, col);
615 }
616 break;
617
618 /* Move cursor right */
619 case REVERSI_BUTTON_RIGHT:
620 case (REVERSI_BUTTON_RIGHT | BUTTON_REPEAT):
621 if (reversi_gui_cursor_pos_hmove(1, &row, &col)) {
622 reversi_gui_move_cursor(row, col);
623 }
624 break;
625
626 /* Move cursor up */
627 case REVERSI_BUTTON_UP:
628 case (REVERSI_BUTTON_UP | BUTTON_REPEAT):
629 if (reversi_gui_cursor_pos_vmove(-1, &row, &col)) {
630 reversi_gui_move_cursor(row, col);
631 }
632 break;
633
634 /* Move cursor down */
635 case REVERSI_BUTTON_DOWN:
636 case (REVERSI_BUTTON_DOWN | BUTTON_REPEAT):
637 if (reversi_gui_cursor_pos_vmove(1, &row, &col)) {
638 reversi_gui_move_cursor(row, col);
639 }
640 break;
641
642 case REVERSI_BUTTON_MENU:
643#ifdef REVERSI_BUTTON_MENU_PRE
644 if (lastbutton != REVERSI_BUTTON_MENU_PRE) {
645 break;
646 }
647#endif
648 if (reversi_gui_menu()) {
649 return PLUGIN_USB_CONNECTED;
650 }
651 draw_screen = true;
652 break;
653
654 default:
655 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
656 /* Quit if USB has been connected */
657 return PLUGIN_USB_CONNECTED;
658 }
659 break;
660 }
661 if (button != BUTTON_NONE) {
662 lastbutton = button;
663 }
664 }
665
666 return PLUGIN_OK;
667}
668
669#endif