when changing settings from the Talk and Voice window also update the main widgets...
[Rockbox.git] / apps / plugins / minesweeper.c
blob8b9fe85da91178ea8476e8c1ec05e859d78ac50a
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2004-2006 Antoine Cellerier <dionoea -at- videolan -dot- org>
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 ****************************************************************************/
20 #include "plugin.h"
22 #ifdef HAVE_LCD_BITMAP
24 PLUGIN_HEADER
26 /* what the minesweeper() function can return */
27 enum minesweeper_status {
28 MINESWEEPER_WIN,
29 MINESWEEPER_LOSE,
30 MINESWEEPER_QUIT,
31 MINESWEEPER_USB
34 /* variable button definitions */
35 #if CONFIG_KEYPAD == RECORDER_PAD
36 # define MINESWP_UP BUTTON_UP
37 # define MINESWP_DOWN BUTTON_DOWN
38 # define MINESWP_QUIT BUTTON_OFF
39 # define MINESWP_TOGGLE BUTTON_ON
40 # define MINESWP_TOGGLE2 BUTTON_F1
41 # define MINESWP_DISCOVER BUTTON_PLAY
42 # define MINESWP_DISCOVER2 BUTTON_F2
43 # define MINESWP_INFO BUTTON_F3
45 #elif CONFIG_KEYPAD == ARCHOS_AV300_PAD
46 # define MINESWP_UP BUTTON_UP
47 # define MINESWP_DOWN BUTTON_DOWN
48 # define MINESWP_QUIT BUTTON_OFF
49 # define MINESWP_TOGGLE BUTTON_ON
50 # define MINESWP_TOGGLE2 BUTTON_F1
51 # define MINESWP_DISCOVER BUTTON_SELECT
52 # define MINESWP_DISCOVER2 BUTTON_F2
53 # define MINESWP_INFO BUTTON_F3
55 #elif CONFIG_KEYPAD == ONDIO_PAD
56 # define MINESWP_UP BUTTON_UP
57 # define MINESWP_DOWN BUTTON_DOWN
58 # define MINESWP_QUIT BUTTON_OFF
59 # define MINESWP_TOGGLE_PRE BUTTON_MENU
60 # define MINESWP_TOGGLE (BUTTON_MENU | BUTTON_REL)
61 # define MINESWP_DISCOVER (BUTTON_MENU | BUTTON_REPEAT)
62 # define MINESWP_INFO (BUTTON_MENU | BUTTON_OFF)
64 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
65 (CONFIG_KEYPAD == IRIVER_H300_PAD)
66 # define MINESWP_UP BUTTON_UP
67 # define MINESWP_DOWN BUTTON_DOWN
68 # define MINESWP_QUIT BUTTON_OFF
69 # define MINESWP_TOGGLE BUTTON_ON
70 # define MINESWP_TOGGLE2 BUTTON_REC
71 # define MINESWP_DISCOVER BUTTON_SELECT
72 # define MINESWP_INFO BUTTON_MODE
74 # define MINESWP_RC_QUIT BUTTON_RC_STOP
76 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
77 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
78 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
79 # define MINESWP_SCROLLWHEEL
80 # define MINESWP_UP BUTTON_MENU
81 # define MINESWP_DOWN BUTTON_PLAY
82 # define MINESWP_NEXT BUTTON_SCROLL_FWD
83 # define MINESWP_PREV BUTTON_SCROLL_BACK
84 # define MINESWP_QUIT (BUTTON_SELECT | BUTTON_MENU)
85 # define MINESWP_TOGGLE_PRE BUTTON_SELECT
86 # define MINESWP_TOGGLE (BUTTON_SELECT | BUTTON_REL)
87 # define MINESWP_DISCOVER (BUTTON_SELECT | BUTTON_REPEAT)
88 # define MINESWP_INFO (BUTTON_SELECT | BUTTON_PLAY)
90 #elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD)
91 # define MINESWP_UP BUTTON_UP
92 # define MINESWP_DOWN BUTTON_DOWN
93 # define MINESWP_QUIT BUTTON_POWER
94 # define MINESWP_TOGGLE BUTTON_PLAY
95 # define MINESWP_DISCOVER BUTTON_SELECT
96 # define MINESWP_INFO BUTTON_REC
98 #elif (CONFIG_KEYPAD == GIGABEAT_PAD)
99 # define MINESWP_UP BUTTON_UP
100 # define MINESWP_DOWN BUTTON_DOWN
101 # define MINESWP_QUIT BUTTON_POWER
102 # define MINESWP_TOGGLE BUTTON_A
103 # define MINESWP_DISCOVER BUTTON_SELECT
104 # define MINESWP_INFO BUTTON_MENU
106 #elif (CONFIG_KEYPAD == SANSA_E200_PAD)
107 # define MINESWP_SCROLLWHEEL
108 # define MINESWP_UP BUTTON_UP
109 # define MINESWP_DOWN BUTTON_DOWN
110 # define MINESWP_QUIT BUTTON_POWER
111 # define MINESWP_NEXT BUTTON_SCROLL_FWD
112 # define MINESWP_PREV BUTTON_SCROLL_BACK
113 # define MINESWP_TOGGLE BUTTON_REC
114 # define MINESWP_DISCOVER BUTTON_SELECT
115 # define MINESWP_INFO (BUTTON_REC|BUTTON_REPEAT)
117 #elif (CONFIG_KEYPAD == SANSA_C200_PAD)
118 # define MINESWP_UP BUTTON_UP
119 # define MINESWP_DOWN BUTTON_DOWN
120 # define MINESWP_QUIT BUTTON_POWER
121 # define MINESWP_TOGGLE_PRE BUTTON_SELECT
122 # define MINESWP_TOGGLE (BUTTON_SELECT | BUTTON_REL)
123 # define MINESWP_TOGGLE2 BUTTON_VOL_DOWN
124 # define MINESWP_DISCOVER (BUTTON_SELECT | BUTTON_REPEAT)
125 # define MINESWP_DISCOVER2 BUTTON_VOL_UP
126 # define MINESWP_INFO BUTTON_REC
128 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
129 # define MINESWP_UP BUTTON_SCROLL_UP
130 # define MINESWP_DOWN BUTTON_SCROLL_DOWN
131 # define MINESWP_QUIT BUTTON_POWER
132 # define MINESWP_TOGGLE BUTTON_PLAY
133 # define MINESWP_DISCOVER BUTTON_REW
134 # define MINESWP_INFO (BUTTON_REW | BUTTON_PLAY)
136 #elif (CONFIG_KEYPAD == GIGABEAT_S_PAD)
137 # define MINESWP_UP BUTTON_UP
138 # define MINESWP_DOWN BUTTON_DOWN
139 # define MINESWP_QUIT BUTTON_BACK
140 # define MINESWP_TOGGLE BUTTON_PLAY
141 # define MINESWP_DISCOVER BUTTON_SELECT
142 # define MINESWP_INFO BUTTON_MENU
144 #elif (CONFIG_KEYPAD == MROBE100_PAD)
145 # define MINESWP_UP BUTTON_UP
146 # define MINESWP_DOWN BUTTON_DOWN
147 # define MINESWP_QUIT BUTTON_POWER
148 # define MINESWP_TOGGLE BUTTON_DISPLAY
149 # define MINESWP_DISCOVER BUTTON_SELECT
150 # define MINESWP_INFO BUTTON_MENU
152 #else
153 #error No keymap defined!
154 #endif
156 /* here is a global api struct pointer. while not strictly necessary,
157 * it's nice not to have to pass the api pointer in all function calls
158 * in the plugin
160 static struct plugin_api *rb;
162 extern const fb_data minesweeper_tiles[];
164 #ifdef HAVE_LCD_COLOR
165 # if ( LCD_HEIGHT * LCD_WIDTH ) / ( 16 * 16 ) >= 130
166 /* We want to have at least 130 tiles on the screen */
167 # define TileSize 16
168 # elif ( LCD_HEIGHT * LCD_WIDTH ) / ( 12 * 12 ) >= 130
169 # define TileSize 12
170 # else
171 # define TileSize 10
172 # endif
173 # define BackgroundColor LCD_RGBPACK( 128, 128, 128 )
174 #elif LCD_DEPTH > 1
175 # define TileSize 12
176 #else
177 # define TileSize 8
178 #endif
180 #define Mine 9
181 #define Flag 10
182 #define Unknown 11
183 #define ExplodedMine 12
185 #define draw_tile( num, x, y ) \
186 rb->lcd_bitmap_part( minesweeper_tiles, 0, num * TileSize, \
187 TileSize, left+x*TileSize, top+y*TileSize, \
188 TileSize, TileSize )
190 #define invert_tile( x, y ) \
191 rb->lcd_set_drawmode(DRMODE_COMPLEMENT); \
192 rb->lcd_fillrect( left+x*TileSize, top+y*TileSize, TileSize, TileSize ); \
193 rb->lcd_set_drawmode(DRMODE_SOLID);
196 /* the tile struct
197 * if there is a mine, mine is true
198 * if tile is known by player, known is true
199 * if tile has a flag, flag is true
200 * neighbors is the total number of mines arround tile
202 typedef struct tile
204 unsigned char mine : 1;
205 unsigned char known : 1;
206 unsigned char flag : 1;
207 unsigned char neighbors : 4;
208 } tile;
210 /* the height and width of the field */
211 #define MAX_HEIGHT (LCD_HEIGHT/TileSize)
212 #define MAX_WIDTH (LCD_WIDTH/TileSize)
213 int height = MAX_HEIGHT;
214 int width = MAX_WIDTH;
215 int top;
216 int left;
218 /* The Minefield. Caution it is defined as Y, X! Not the opposite. */
219 tile minefield[MAX_HEIGHT][MAX_WIDTH];
221 /* total number of mines on the game */
222 int mine_num = 0;
224 /* percentage of mines on minefield used during generation */
225 int p = 16;
227 /* number of tiles left on the game */
228 int tiles_left;
230 /* number of used flags on the game */
231 int flags_used;
233 /* Because mines are set after the first move... */
234 bool no_mines = true;
236 /* We need a stack (created on discover()) for the cascade algorithm. */
237 int stack_pos = 0;
239 /* a usefull string for snprintf */
240 char str[30];
243 void push( int *stack, int y, int x )
245 if( stack_pos <= height*width )
247 stack[++stack_pos] = y;
248 stack[++stack_pos] = x;
252 /* Unveil tiles and push them to stack if they are empty. */
253 void unveil( int *stack, int y, int x )
255 if( x < 0 || y < 0 || x > width - 1 || y > height - 1
256 || minefield[y][x].known
257 || minefield[y][x].mine || minefield[y][x].flag ) return;
259 minefield[y][x].known = 1;
261 if( minefield[y][x].neighbors == 0 )
262 push( stack, y, x );
265 void discover( int y, int x )
267 int stack[height*width];
269 /* Selected tile. */
270 if( x < 0 || y < 0 || x > width - 1 || y > height - 1
271 || minefield[y][x].known
272 || minefield[y][x].mine || minefield[y][x].flag ) return;
274 minefield[y][x].known = 1;
275 /* Exit if the tile is not empty. (no mines nearby) */
276 if( minefield[y][x].neighbors ) return;
278 push( stack, y, x );
280 /* Scan all nearby tiles. If we meet a tile with a number we just unveil
281 * it. If we meet an empty tile, we push the location in stack. For each
282 * location in stack we do the same thing. (scan again all nearby tiles)
284 while( stack_pos )
286 /* Pop x, y from stack. */
287 x = stack[stack_pos--];
288 y = stack[stack_pos--];
290 unveil( stack, y-1, x-1 );
291 unveil( stack, y-1, x );
292 unveil( stack, y-1, x+1 );
293 unveil( stack, y, x+1 );
294 unveil( stack, y+1, x+1 );
295 unveil( stack, y+1, x );
296 unveil( stack, y+1, x-1 );
297 unveil( stack, y, x-1 );
301 /* Reset the whole board for a new game. */
302 void minesweeper_init( void )
304 int i,j;
306 for( i = 0; i < MAX_HEIGHT; i++ )
308 for( j = 0; j < MAX_WIDTH; j++ )
310 minefield[i][j].known = 0;
311 minefield[i][j].flag = 0;
312 minefield[i][j].mine = 0;
313 minefield[i][j].neighbors = 0;
316 no_mines = true;
317 tiles_left = width*height;
321 /* put mines on the mine field */
322 /* there is p% chance that a tile is a mine */
323 /* if the tile has coordinates (x,y), then it can't be a mine */
324 void minesweeper_putmines( int p, int x, int y )
326 int i,j;
328 mine_num = 0;
329 for( i = 0; i < height; i++ )
331 for( j = 0; j < width; j++ )
333 if( rb->rand()%100 < p && !( y==i && x==j ) )
335 minefield[i][j].mine = 1;
336 mine_num++;
338 else
340 minefield[i][j].mine = 0;
342 minefield[i][j].neighbors = 0;
346 /* we need to compute the neighbor element for each tile */
347 for( i = 0; i < height; i++ )
349 for( j = 0; j < width; j++ )
351 if( i > 0 )
353 if( j > 0 )
354 minefield[i][j].neighbors += minefield[i-1][j-1].mine;
355 minefield[i][j].neighbors += minefield[i-1][j].mine;
356 if( j < width - 1 )
357 minefield[i][j].neighbors += minefield[i-1][j+1].mine;
359 if( j > 0 )
360 minefield[i][j].neighbors += minefield[i][j-1].mine;
361 if( j < width - 1 )
362 minefield[i][j].neighbors += minefield[i][j+1].mine;
363 if( i < height - 1 )
365 if( j > 0 )
366 minefield[i][j].neighbors += minefield[i+1][j-1].mine;
367 minefield[i][j].neighbors += minefield[i+1][j].mine;
368 if( j < width - 1 )
369 minefield[i][j].neighbors += minefield[i+1][j+1].mine;
374 no_mines = false;
376 /* In case the user is lucky and there are no mines positioned. */
377 if( !mine_num && height*width != 1 )
379 minesweeper_putmines(p, x, y);
383 /* A function that will uncover all the board, when the user wins or loses.
384 can easily be expanded, (just a call assigned to a button) as a solver. */
385 void mine_show( void )
387 int i, j, button;
389 for( i = 0; i < height; i++ )
391 for( j = 0; j < width; j++ )
393 if( minefield[i][j].mine )
395 if( minefield[i][j].known )
397 draw_tile( ExplodedMine, j, i );
399 else
401 draw_tile( Mine, j, i );
404 else
406 draw_tile( minefield[i][j].neighbors, j, i );
410 rb->lcd_update();
413 button = rb->button_get(true);
414 while( ( button == BUTTON_NONE )
415 || ( button & (BUTTON_REL|BUTTON_REPEAT) ) );
418 int count_tiles_left( void )
420 int tiles_left = 0;
421 int i, j;
422 for( i = 0; i < height; i++ )
423 for( j = 0; j < width; j++ )
424 if( minefield[i][j].known == 0 )
425 tiles_left++;
426 return tiles_left;
429 int count_flags( void )
431 int flags_used = 0;
432 int i, j;
433 for( i = 0; i < height; i++ )
434 for( j = 0; j < width; j++ )
435 if( minefield[i][j].flag == 1 )
436 flags_used++;
437 return flags_used;
440 /* welcome screen where player can chose mine percentage */
441 enum minesweeper_status menu( void )
443 int selection, result = MINESWEEPER_QUIT;
444 bool menu_quit = false;
446 MENUITEM_STRINGLIST( menu, "Minesweeper Menu", NULL, "Play Minesweeper",
447 "Mine Percentage", "Number of Rows",
448 "Number of Columns", "Quit" );
450 #ifdef HAVE_LCD_COLOR
451 rb->lcd_set_foreground( rb->global_settings->fg_color );
452 rb->lcd_set_background( rb->global_settings->bg_color );
453 #endif
455 while( !menu_quit )
457 switch( rb->do_menu( &menu, &selection ) )
459 case 0:
460 result = MINESWEEPER_WIN; /* start playing */
461 menu_quit = true;
462 break;
464 case 1:
465 rb->set_int( "Mine Percentage", "%", UNIT_INT, &p, NULL,
466 1, 2, 98, NULL );
467 break;
469 case 2:
470 rb->set_int( "Number of Rows", "", UNIT_INT, &height, NULL,
471 1, 1, MAX_HEIGHT, NULL );
472 break;
474 case 3:
475 rb->set_int( "Number of Columns", "", UNIT_INT, &width, NULL,
476 1, 1, MAX_WIDTH, NULL );
477 break;
479 default:
480 result = MINESWEEPER_QUIT; /* quit program */
481 menu_quit = true;
482 break;
486 return result;
489 /* the big and ugly game function */
490 enum minesweeper_status minesweeper( void )
492 int i, j;
493 int button;
494 int lastbutton = BUTTON_NONE;
496 /* the cursor coordinates */
497 int x=0, y=0;
500 * Show the menu
502 if( ( i = menu() ) != MINESWEEPER_WIN ) return i;
505 * Init game
507 top = (LCD_HEIGHT-height*TileSize)/2;
508 left = (LCD_WIDTH-width*TileSize)/2;
510 rb->srand( *rb->current_tick );
511 minesweeper_init();
512 x = 0;
513 y = 0;
516 * Play
518 while( true )
521 /* clear the screen buffer */
522 #ifdef HAVE_LCD_COLOR
523 rb->lcd_set_background( BackgroundColor );
524 #endif
525 rb->lcd_clear_display();
527 /* display the mine field */
528 for( i = 0; i < height; i++ )
530 for( j = 0; j < width; j++ )
532 if( minefield[i][j].known )
534 draw_tile( minefield[i][j].neighbors, j, i );
536 else if(minefield[i][j].flag)
538 draw_tile( Flag, j, i );
540 else
542 draw_tile( Unknown, j, i );
547 /* display the cursor */
548 invert_tile( x, y );
550 /* update the screen */
551 rb->lcd_update();
553 switch( button = rb->button_get( true ) )
555 /* quit minesweeper (you really shouldn't use this button ...) */
556 #ifdef MINESWP_RC_QUIT
557 case MINESWP_RC_QUIT:
558 #endif
559 case MINESWP_QUIT:
560 return MINESWEEPER_QUIT;
562 /* move cursor left */
563 case BUTTON_LEFT:
564 case BUTTON_LEFT|BUTTON_REPEAT:
565 x = ( x + width - 1 )%width;
566 break;
568 /* move cursor right */
569 case BUTTON_RIGHT:
570 case BUTTON_RIGHT|BUTTON_REPEAT:
571 x = ( x + 1 )%width;
572 break;
574 /* move cursor down */
575 case MINESWP_DOWN:
576 case MINESWP_DOWN|BUTTON_REPEAT:
577 y = ( y + 1 )%height;
578 break;
580 /* move cursor up */
581 case MINESWP_UP:
582 case MINESWP_UP|BUTTON_REPEAT:
583 y = ( y + height - 1 )%height;
584 break;
586 /*move cursor though the entire field*/
587 #ifdef MINESWP_SCROLLWHEEL
588 case MINESWP_NEXT:
589 case MINESWP_NEXT|BUTTON_REPEAT:
590 if (x == width -1 ) {
591 y = ( y + 1 )%height;
593 x = ( x + 1 )%width;
594 break;
596 case MINESWP_PREV:
597 case MINESWP_PREV|BUTTON_REPEAT:
598 if (x == 0) {
599 y = ( y + height - 1 )%height;
601 x = ( x + width - 1 )%width;
602 break;
603 #endif
604 /* discover a tile (and it's neighbors if .neighbors == 0) */
605 case MINESWP_DISCOVER:
606 #ifdef MINESWP_DISCOVER2
607 case MINESWP_DISCOVER2:
608 #endif
609 if( minefield[y][x].flag ) break;
610 /* we put the mines on the first "click" so that you don't
611 * lose on the first "click" */
612 if( tiles_left == width*height && no_mines )
613 minesweeper_putmines(p,x,y);
615 discover(y, x);
617 if( minefield[y][x].mine )
619 minefield[y][x].known = 1;
620 return MINESWEEPER_LOSE;
622 tiles_left = count_tiles_left();
623 if( tiles_left == mine_num )
625 return MINESWEEPER_WIN;
627 break;
629 /* toggle flag under cursor */
630 case MINESWP_TOGGLE:
631 #ifdef MINESWP_TOGGLE_PRE
632 if( lastbutton != MINESWP_TOGGLE_PRE )
633 break;
634 #endif
635 #ifdef MINESWP_TOGGLE2
636 case MINESWP_TOGGLE2:
637 #endif
638 minefield[y][x].flag = ( minefield[y][x].flag + 1 )%2;
639 break;
641 /* show how many mines you think you have found and how many
642 * there really are on the game */
643 case MINESWP_INFO:
644 if( no_mines )
645 break;
646 flags_used = count_flags();
647 if (flags_used == 1) {
648 rb->splash( HZ*2, "You marked 1 field. There are %d mines.",
649 mine_num );
651 else
653 rb->splash( HZ*2, "You marked %d fields. There are %d mines.",
654 flags_used, mine_num );
656 break;
658 default:
659 if( rb->default_event_handler( button ) == SYS_USB_CONNECTED )
660 return MINESWEEPER_USB;
661 break;
663 if( button != BUTTON_NONE )
664 lastbutton = button;
669 /* plugin entry point */
670 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
672 bool exit = false;
674 (void)parameter;
675 rb = api;
676 #if LCD_DEPTH > 1
677 rb->lcd_set_backdrop(NULL);
678 #endif
680 while( !exit )
682 switch( minesweeper() )
684 case MINESWEEPER_WIN:
685 rb->splash( HZ, "You Win!" );
686 rb->lcd_clear_display();
687 mine_show();
688 break;
690 case MINESWEEPER_LOSE:
691 rb->splash( HZ, "You Lose!" );
692 rb->lcd_clear_display();
693 mine_show();
694 break;
696 case MINESWEEPER_USB:
697 return PLUGIN_USB_CONNECTED;
699 case MINESWEEPER_QUIT:
700 exit = true;
701 break;
703 default:
704 break;
708 return PLUGIN_OK;
711 #endif