1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Copyright (c) 2006 Alexander Levin
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.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
21 GUI part of reversi. Code is inspired by sudoku code by Dave Chapman
22 which is copyright (c) 2005 Dave Chapman and is released under the
23 GNU General Public License.
29 Use the arrow keys to move cursor, and press TOGGLE to place a stone.
31 At any time during the game, press MENU to bring up the game menu with
42 #ifdef HAVE_LCD_BITMAP
44 #include "reversi-game.h"
45 #include "reversi-strategy.h"
46 #include "reversi-gui.h"
48 #include "../lib/oldmenuapi.h"
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 */
55 static struct plugin_api
* rb
;
57 /* Thickness of the grid lines */
60 #if LCD_HEIGHT <= LCD_WIDTH /* Horizontal layout */
62 #if (LCD_HEIGHT==64) && (LCD_WIDTH==112)
63 /* Archos Recorders and Ondios - 112x64, 8 cells @ 8x6 with 9 border lines */
65 /* Internal dimensions of a cell */
70 #elif (LCD_HEIGHT==110) && (LCD_WIDTH==138)
71 /* iPod Mini - 138x110, 8 cells @ 10x10 with 9 border lines */
73 /* Internal dimensions of a cell */
75 #define CELL_HEIGHT 10
77 #elif (LCD_HEIGHT==128) && (LCD_WIDTH==128)
78 /* iriver H10 5-6GB - 128x128, 8 cells @ 10x10 with 9 border lines */
80 /* Internal dimensions of a cell */
82 #define CELL_HEIGHT 10
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 */
89 /* Internal dimensions of a cell */
91 #define CELL_HEIGHT 12
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 */
97 /* Internal dimensions of a cell */
99 #define CELL_HEIGHT 16
101 #elif (LCD_HEIGHT>=240) && (LCD_WIDTH>=320)
102 /* iPod Video - 320x240, 8 cells @ 24x24 with 9 border lines */
104 /* Internal dimensions of a cell */
105 #define CELL_WIDTH 24
106 #define CELL_HEIGHT 24
109 #error REVERSI: Unsupported LCD size
112 #else /* Vertical layout */
113 #define VERTICAL_LAYOUT
115 #if (LCD_HEIGHT>=320) && (LCD_WIDTH>=240)
116 /* Gigabeat - 240x320, 8 cells @ 24x24 with 9 border lines */
118 /* Internal dimensions of a cell */
119 #define CELL_WIDTH 24
120 #define CELL_HEIGHT 24
122 #elif (LCD_HEIGHT>=220) && (LCD_WIDTH>=176)
123 /* e200 - 176x220, 8 cells @ 12x12 with 9 border lines */
125 /* Internal dimensions of a cell */
126 #define CELL_WIDTH 18
127 #define CELL_HEIGHT 18
130 #error REVERSI: Unsupported LCD size
136 /* Where the board begins */
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))
144 /* Thickness of the white cells' lines */
145 #if (CELL_WIDTH >= 15) && (CELL_HEIGHT >= 15)
146 #define CELL_LINE_THICKNESS 2
148 #define CELL_LINE_THICKNESS 1
151 /* Margins within a cell */
152 #if (CELL_WIDTH >= 10) && (CELL_HEIGHT >= 10)
153 #define STONE_MARGIN 2
155 #define STONE_MARGIN 1
158 #define CURSOR_MARGIN (STONE_MARGIN + CELL_LINE_THICKNESS)
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)
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)
169 #define LEGEND_X(lc) (CELL_X(BOARD_SIZE+(lc)) + CELL_WIDTH/2)
170 #define LEGEND_Y(lr) (CELL_Y(lr))
175 static reversi_board_t game
;
177 /* --- Setting values --- */
179 /* Playing strategies used by white and black players */
180 const game_strategy_t
*white_strategy
;
181 const game_strategy_t
*black_strategy
;
183 /* Cursor position */
184 static int cur_row
, cur_col
;
186 /* Color for the next move (BLACK/WHITE) */
187 static int cur_player
;
189 /* Active cursor wrapping mode */
190 static cursor_wrap_mode_t cursor_wrap_mode
;
192 static bool quit_plugin
;
193 static bool game_finished
;
196 /* Initialises the state of the game (starts a new game) */
197 static void reversi_gui_init(void) {
198 reversi_init_game(&game
);
199 game_finished
= false;
202 /* Place the cursor so that WHITE can make a move */
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.
211 static void reversi_gui_display_cursor(int row
, int col
) {
213 old_mode
= rb
->lcd_get_drawmode();
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
);
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);
229 rb
->lcd_set_drawmode(old_mode
);
234 /* Draws the cell of the specified color (WHITE/BLACK) assuming that
235 * the upper left corner of the cell is at (x, y) */
236 static void reversi_gui_draw_cell(int x
, int y
, int color
) {
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
));
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
);
247 /* Cell is free -> nothing to do */
252 /* Draws the complete screen */
253 static void reversi_gui_display_board(void) {
254 int x
, y
, r
, c
, x_width
, x_height
;
257 /* Clear the display buffer */
258 rb
->lcd_clear_display();
259 rb
->lcd_set_drawmode(DRMODE_FG
);
261 /* Thicker board box */
262 rb
->lcd_drawrect(XOFS
-1, YOFS
-1, BOARD_WIDTH
+2, BOARD_HEIGHT
+2);
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);
271 /* Draw the stones. This is not the most efficient way but more readable */
272 for (r
=0; r
<BOARD_SIZE
; r
++) {
274 for (c
=0; c
<BOARD_SIZE
; c
++) {
276 reversi_gui_draw_cell(x
, y
, game
.board
[r
][c
]);
280 /* Draw the cursor */
281 reversi_gui_display_cursor(cur_row
, cur_col
);
283 /* Draw the current score */
284 reversi_count_occupied_cells(&game
, &r
, &c
);
285 rb
->lcd_getstringsize("x", &x_width
, &x_height
);
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
);
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
);
300 /* Draw the box around the current player */
301 r
= (cur_player
== BLACK
? 0 : 1);
303 rb
->lcd_drawrect(x
-1, y
-1, CELL_WIDTH
+2, CELL_HEIGHT
+2);
305 /* Update the screen */
314 /* Menu entries and the corresponding values for cursor wrap mode */
315 #define MENU_TEXT_WRAP_MODE "Cursor wrap mode"
316 static const struct opt_items cursor_wrap_mode_settings
[] = {
317 { "Flat board", NULL
},
321 static const cursor_wrap_mode_t cursor_wrap_mode_values
[3] = {
322 WRAP_FLAT
, WRAP_SPHERE
, WRAP_TORUS
};
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"
329 static struct opt_items strategy_settings
[] = {
331 { "Naive robot", NULL
},
332 { "Simple robot", NULL
},
333 //{ "AB robot", NULL },
335 static const game_strategy_t
* const strategy_values
[] = {
336 &strategy_human
, &strategy_naive
, &strategy_simple
, /*&strategy_ab*/ };
339 /* Sets the strategy for the specified player. 'player' is the
340 pointer to the player to set the strategy for (actually,
341 either white_strategy or black_strategy). propmpt is the
342 text to show as the prompt in the menu */
343 static bool reversi_gui_choose_strategy(
344 const game_strategy_t
**player
, const char *prompt
) {
346 int num_items
= sizeof(strategy_settings
)/sizeof(strategy_settings
[0]);
349 for (i
= 0; i
< num_items
; i
++) {
350 if ((*player
) == strategy_values
[i
]) {
355 result
= rb
->set_option(prompt
, &index
, INT
, strategy_settings
, num_items
, NULL
);
356 (*player
) = strategy_values
[index
];
358 if((*player
)->init_func
)
359 (*player
)->init_func(&game
);
365 /* Returns true iff USB ws connected while in the menu */
366 static bool reversi_gui_menu(void) {
367 int m
, index
, num_items
, i
;
370 static const struct menu_item items
[] = {
371 { "Start new game", NULL
},
372 { "Pass the move", NULL
},
373 { MENU_TEXT_STRAT_BLACK
, NULL
},
374 { MENU_TEXT_STRAT_WHITE
, NULL
},
375 { MENU_TEXT_WRAP_MODE
, NULL
},
379 m
= menu_init(rb
, items
, sizeof(items
) / sizeof(*items
),
380 NULL
, NULL
, NULL
, NULL
);
382 result
= menu_show(m
);
385 case 0: /* Start a new game */
389 case 1: /* Pass the move to the partner */
390 cur_player
= reversi_flipped_color(cur_player
);
393 case 2: /* Strategy for black */
394 reversi_gui_choose_strategy(&black_strategy
, MENU_TEXT_STRAT_BLACK
);
397 case 3: /* Strategy for white */
398 reversi_gui_choose_strategy(&white_strategy
, MENU_TEXT_STRAT_WHITE
);
401 case 4: /* Cursor wrap mode */
402 num_items
= sizeof(cursor_wrap_mode_values
)/sizeof(cursor_wrap_mode_values
[0]);
404 for (i
= 0; i
< num_items
; i
++) {
405 if (cursor_wrap_mode
== cursor_wrap_mode_values
[i
]) {
410 rb
->set_option(MENU_TEXT_WRAP_MODE
, &index
, INT
,
411 cursor_wrap_mode_settings
, 3, NULL
);
412 cursor_wrap_mode
= cursor_wrap_mode_values
[index
];
422 return (result
== MENU_ATTACHED_USB
);
426 /* Calculates the new cursor position if the user wants to move it
427 * vertically as specified by delta. Current wrap mode is respected.
428 * The cursor is not actually moved.
430 * Returns true iff the cursor would be really moved. In any case, the
431 * new cursor position is stored in (new_row, new_col).
433 static bool reversi_gui_cursor_pos_vmove(int row_delta
, int *new_row
, int *new_col
) {
434 *new_row
= cur_row
+ row_delta
;
438 switch (cursor_wrap_mode
) {
443 *new_row
= BOARD_SIZE
- 1;
446 *new_row
= BOARD_SIZE
- 1;
449 *new_col
= BOARD_SIZE
- 1;
453 } else if (*new_row
>= BOARD_SIZE
) {
454 switch (cursor_wrap_mode
) {
464 if (*new_col
>= BOARD_SIZE
) {
471 return (cur_row
!= (*new_row
)) || (cur_col
!= (*new_col
));
475 /* Calculates the new cursor position if the user wants to move it
476 * horisontally as specified by delta. Current wrap mode is respected.
477 * The cursor is not actually moved.
479 * Returns true iff the cursor would be really moved. In any case, the
480 * new cursor position is stored in (new_row, new_col).
482 static bool reversi_gui_cursor_pos_hmove(int col_delta
, int *new_row
, int *new_col
) {
484 *new_col
= cur_col
+ col_delta
;
487 switch (cursor_wrap_mode
) {
492 *new_col
= BOARD_SIZE
- 1;
495 *new_col
= BOARD_SIZE
- 1;
498 *new_row
= BOARD_SIZE
- 1;
502 } else if (*new_col
>= BOARD_SIZE
) {
503 switch (cursor_wrap_mode
) {
513 if (*new_row
>= BOARD_SIZE
) {
520 return (cur_row
!= (*new_row
)) || (cur_col
!= (*new_col
));
524 /* Actually moves the cursor to the new position and updates the screen */
525 static void reversi_gui_move_cursor(int new_row
, int new_col
) {
526 int old_row
, old_col
;
534 /* Only update the changed cells since there are no global changes */
535 reversi_gui_display_cursor(old_row
, old_col
);
536 reversi_gui_display_cursor(new_row
, new_col
);
540 /* plugin entry point */
541 enum plugin_status
plugin_start(struct plugin_api
*api
, void *parameter
) {
542 bool exit
, draw_screen
;
544 int lastbutton
= BUTTON_NONE
;
551 /* end of plugin init */
554 rb
->lcd_set_backdrop(NULL
);
555 rb
->lcd_set_foreground(LCD_BLACK
);
556 rb
->lcd_set_background(LCD_WHITE
);
559 /* Avoid compiler warnings */
563 rb
->srand(*rb
->current_tick
); /* Some AIs use rand() */
564 white_strategy
= &strategy_human
;
565 black_strategy
= &strategy_human
;
568 cursor_wrap_mode
= WRAP_FLAT
;
570 /* The main game loop */
574 while (!exit
&& !quit_plugin
) {
575 const game_strategy_t
*cur_strategy
= NULL
;
577 reversi_gui_display_board();
582 cur_strategy
= black_strategy
;
585 cur_strategy
= white_strategy
;
589 if(cur_strategy
->is_robot
&& !game_finished
) {
590 move_t m
= cur_strategy
->move_func(&game
, cur_player
);
591 reversi_make_move(&game
, MOVE_ROW(m
), MOVE_COL(m
), cur_player
);
592 cur_player
= reversi_flipped_color(cur_player
);
594 /* TODO: Add some delay to prevent it from being too fast ? */
595 /* TODO: Don't duplicate end of game check */
596 if (reversi_game_is_finished(&game
, cur_player
)) {
597 reversi_count_occupied_cells(&game
, &w_cnt
, &b_cnt
);
598 rb
->snprintf(msg_buf
, sizeof(msg_buf
),
599 "Game over. %s have won.",
600 (w_cnt
>b_cnt
?"WHITE":"BLACK"));
601 rb
->splash(HZ
*2, msg_buf
);
602 draw_screen
= true; /* Must update screen after splash */
603 game_finished
= true;
608 button
= rb
->button_get(true);
611 #ifdef REVERSI_BUTTON_QUIT
613 case REVERSI_BUTTON_QUIT
:
618 #ifdef REVERSI_BUTTON_ALT_MAKE_MOVE
619 case REVERSI_BUTTON_ALT_MAKE_MOVE
:
621 case REVERSI_BUTTON_MAKE_MOVE
:
622 #ifdef REVERSI_BUTTON_MAKE_MOVE_PRE
623 if ((button
== REVERSI_BUTTON_MAKE_MOVE
)
624 && (lastbutton
!= REVERSI_BUTTON_MAKE_MOVE_PRE
))
627 if (game_finished
) break;
628 if (reversi_make_move(&game
, cur_row
, cur_col
, cur_player
) > 0) {
629 /* Move was made. Global changes on the board are possible */
630 draw_screen
= true; /* Redraw the screen next time */
631 cur_player
= reversi_flipped_color(cur_player
);
632 if (reversi_game_is_finished(&game
, cur_player
)) {
633 reversi_count_occupied_cells(&game
, &w_cnt
, &b_cnt
);
634 rb
->snprintf(msg_buf
, sizeof(msg_buf
),
635 "Game over. %s have won.",
636 (w_cnt
>b_cnt
?"WHITE":"BLACK"));
637 rb
->splash(HZ
*2, msg_buf
);
638 draw_screen
= true; /* Must update screen after splash */
639 game_finished
= true;
642 /* An attempt to make an invalid move */
643 rb
->splash(HZ
/2, "Illegal move!");
645 /* Ignore any button presses during the splash */
646 rb
->button_clear_queue();
650 /* Move cursor left */
651 case REVERSI_BUTTON_LEFT
:
652 case (REVERSI_BUTTON_LEFT
| BUTTON_REPEAT
):
653 if (reversi_gui_cursor_pos_hmove(-1, &row
, &col
)) {
654 reversi_gui_move_cursor(row
, col
);
658 /* Move cursor right */
659 case REVERSI_BUTTON_RIGHT
:
660 case (REVERSI_BUTTON_RIGHT
| BUTTON_REPEAT
):
661 if (reversi_gui_cursor_pos_hmove(1, &row
, &col
)) {
662 reversi_gui_move_cursor(row
, col
);
667 case REVERSI_BUTTON_UP
:
668 case (REVERSI_BUTTON_UP
| BUTTON_REPEAT
):
669 if (reversi_gui_cursor_pos_vmove(-1, &row
, &col
)) {
670 reversi_gui_move_cursor(row
, col
);
674 /* Move cursor down */
675 case REVERSI_BUTTON_DOWN
:
676 case (REVERSI_BUTTON_DOWN
| BUTTON_REPEAT
):
677 if (reversi_gui_cursor_pos_vmove(1, &row
, &col
)) {
678 reversi_gui_move_cursor(row
, col
);
682 case REVERSI_BUTTON_MENU
:
683 #ifdef REVERSI_BUTTON_MENU_PRE
684 if (lastbutton
!= REVERSI_BUTTON_MENU_PRE
) {
688 if (reversi_gui_menu()) {
689 return PLUGIN_USB_CONNECTED
;
695 if (rb
->default_event_handler(button
) == SYS_USB_CONNECTED
) {
696 /* Quit if USB has been connected */
697 return PLUGIN_USB_CONNECTED
;
701 if (button
!= BUTTON_NONE
) {