Add platform file for Ipod 1G / 2G. Now only the front image is missing for building...
[Rockbox.git] / apps / plugins / minesweeper.c
blob4ea9aa15227e4a00f532003e71b0dff2a25e1c88
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_DOWN
112 # define MINESWP_PREV BUTTON_SCROLL_UP
113 # define MINESWP_TOGGLE BUTTON_REC
114 # define MINESWP_DISCOVER BUTTON_SELECT
115 # define MINESWP_INFO (BUTTON_REC|BUTTON_REPEAT)
117 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
118 # define MINESWP_UP BUTTON_SCROLL_UP
119 # define MINESWP_DOWN BUTTON_SCROLL_DOWN
120 # define MINESWP_QUIT BUTTON_POWER
121 # define MINESWP_TOGGLE BUTTON_PLAY
122 # define MINESWP_DISCOVER BUTTON_REW
123 # define MINESWP_INFO (BUTTON_REW | BUTTON_PLAY)
125 #else
126 # warning Missing key definitions for this keypad
127 #endif
129 /* here is a global api struct pointer. while not strictly necessary,
130 * it's nice not to have to pass the api pointer in all function calls
131 * in the plugin
133 static struct plugin_api *rb;
135 extern const fb_data minesweeper_tiles[];
137 #ifdef HAVE_LCD_COLOR
138 # if ( LCD_HEIGHT * LCD_WIDTH ) / ( 16 * 16 ) >= 130
139 /* We want to have at least 130 tiles on the screen */
140 # define TileSize 16
141 # else
142 # define TileSize 12
143 # endif
144 # define BackgroundColor LCD_RGBPACK( 128, 128, 128 )
145 #elif LCD_DEPTH > 1
146 # define TileSize 12
147 #else
148 # define TileSize 8
149 #endif
151 #define Mine 9
152 #define Flag 10
153 #define Unknown 11
154 #define ExplodedMine 12
156 #define draw_tile( num, x, y ) \
157 rb->lcd_bitmap_part( minesweeper_tiles, 0, num * TileSize, \
158 TileSize, left+x*TileSize, top+y*TileSize, \
159 TileSize, TileSize )
161 #define invert_tile( x, y ) \
162 rb->lcd_set_drawmode(DRMODE_COMPLEMENT); \
163 rb->lcd_fillrect( left+x*TileSize, top+y*TileSize, TileSize, TileSize ); \
164 rb->lcd_set_drawmode(DRMODE_SOLID);
167 /* the tile struct
168 * if there is a mine, mine is true
169 * if tile is known by player, known is true
170 * if tile has a flag, flag is true
171 * neighbors is the total number of mines arround tile
173 typedef struct tile
175 unsigned char mine : 1;
176 unsigned char known : 1;
177 unsigned char flag : 1;
178 unsigned char neighbors : 4;
179 } tile;
181 /* the height and width of the field */
182 #define MAX_HEIGHT (LCD_HEIGHT/TileSize)
183 #define MAX_WIDTH (LCD_WIDTH/TileSize)
184 int height = MAX_HEIGHT;
185 int width = MAX_WIDTH;
186 int top;
187 int left;
189 /* The Minefield. Caution it is defined as Y, X! Not the opposite. */
190 tile minefield[MAX_HEIGHT][MAX_WIDTH];
192 /* total number of mines on the game */
193 int mine_num = 0;
195 /* percentage of mines on minefield used during generation */
196 int p = 16;
198 /* number of tiles left on the game */
199 int tiles_left;
201 /* number of used flags on the game */
202 int flags_used;
204 /* Because mines are set after the first move... */
205 bool no_mines = true;
207 /* We need a stack (created on discover()) for the cascade algorithm. */
208 int stack_pos = 0;
210 /* a usefull string for snprintf */
211 char str[30];
214 void push( int *stack, int y, int x )
216 if( stack_pos <= height*width )
218 stack[++stack_pos] = y;
219 stack[++stack_pos] = x;
223 /* Unveil tiles and push them to stack if they are empty. */
224 void unveil( int *stack, int y, int x )
226 if( x < 0 || y < 0 || x > width - 1 || y > height - 1
227 || minefield[y][x].known
228 || minefield[y][x].mine || minefield[y][x].flag ) return;
230 minefield[y][x].known = 1;
232 if( minefield[y][x].neighbors == 0 )
233 push( stack, y, x );
236 void discover( int y, int x )
238 int stack[height*width];
240 /* Selected tile. */
241 if( x < 0 || y < 0 || x > width - 1 || y > height - 1
242 || minefield[y][x].known
243 || minefield[y][x].mine || minefield[y][x].flag ) return;
245 minefield[y][x].known = 1;
246 /* Exit if the tile is not empty. (no mines nearby) */
247 if( minefield[y][x].neighbors ) return;
249 push( stack, y, x );
251 /* Scan all nearby tiles. If we meet a tile with a number we just unveil
252 * it. If we meet an empty tile, we push the location in stack. For each
253 * location in stack we do the same thing. (scan again all nearby tiles)
255 while( stack_pos )
257 /* Pop x, y from stack. */
258 x = stack[stack_pos--];
259 y = stack[stack_pos--];
261 unveil( stack, y-1, x-1 );
262 unveil( stack, y-1, x );
263 unveil( stack, y-1, x+1 );
264 unveil( stack, y, x+1 );
265 unveil( stack, y+1, x+1 );
266 unveil( stack, y+1, x );
267 unveil( stack, y+1, x-1 );
268 unveil( stack, y, x-1 );
272 /* Reset the whole board for a new game. */
273 void minesweeper_init( void )
275 int i,j;
277 for( i = 0; i < MAX_HEIGHT; i++ )
279 for( j = 0; j < MAX_WIDTH; j++ )
281 minefield[i][j].known = 0;
282 minefield[i][j].flag = 0;
283 minefield[i][j].mine = 0;
284 minefield[i][j].neighbors = 0;
287 no_mines = true;
288 tiles_left = width*height;
292 /* put mines on the mine field */
293 /* there is p% chance that a tile is a mine */
294 /* if the tile has coordinates (x,y), then it can't be a mine */
295 void minesweeper_putmines( int p, int x, int y )
297 int i,j;
299 mine_num = 0;
300 for( i = 0; i < height; i++ )
302 for( j = 0; j < width; j++ )
304 if( rb->rand()%100 < p && !( y==i && x==j ) )
306 minefield[i][j].mine = 1;
307 mine_num++;
309 else
311 minefield[i][j].mine = 0;
313 minefield[i][j].neighbors = 0;
317 /* we need to compute the neighbor element for each tile */
318 for( i = 0; i < height; i++ )
320 for( j = 0; j < width; j++ )
322 if( i > 0 )
324 if( j > 0 )
325 minefield[i][j].neighbors += minefield[i-1][j-1].mine;
326 minefield[i][j].neighbors += minefield[i-1][j].mine;
327 if( j < width - 1 )
328 minefield[i][j].neighbors += minefield[i-1][j+1].mine;
330 if( j > 0 )
331 minefield[i][j].neighbors += minefield[i][j-1].mine;
332 if( j < width - 1 )
333 minefield[i][j].neighbors += minefield[i][j+1].mine;
334 if( i < height - 1 )
336 if( j > 0 )
337 minefield[i][j].neighbors += minefield[i+1][j-1].mine;
338 minefield[i][j].neighbors += minefield[i+1][j].mine;
339 if( j < width - 1 )
340 minefield[i][j].neighbors += minefield[i+1][j+1].mine;
345 no_mines = false;
347 /* In case the user is lucky and there are no mines positioned. */
348 if( !mine_num && height*width != 1 )
350 minesweeper_putmines(p, x, y);
354 /* A function that will uncover all the board, when the user wins or loses.
355 can easily be expanded, (just a call assigned to a button) as a solver. */
356 void mine_show( void )
358 int i, j, button;
360 for( i = 0; i < height; i++ )
362 for( j = 0; j < width; j++ )
364 if( minefield[i][j].mine )
366 if( minefield[i][j].known )
368 draw_tile( ExplodedMine, j, i );
370 else
372 draw_tile( Mine, j, i );
375 else
377 draw_tile( minefield[i][j].neighbors, j, i );
381 rb->lcd_update();
384 button = rb->button_get(true);
385 while( ( button == BUTTON_NONE )
386 || ( button & (BUTTON_REL|BUTTON_REPEAT) ) );
389 int count_tiles_left( void )
391 int tiles_left = 0;
392 int i, j;
393 for( i = 0; i < height; i++ )
394 for( j = 0; j < width; j++ )
395 if( minefield[i][j].known == 0 )
396 tiles_left++;
397 return tiles_left;
400 int count_flags( void )
402 int flags_used = 0;
403 int i, j;
404 for( i = 0; i < height; i++ )
405 for( j = 0; j < width; j++ )
406 if( minefield[i][j].flag == 1 )
407 flags_used++;
408 return flags_used;
411 /* welcome screen where player can chose mine percentage */
412 enum minesweeper_status menu( void )
414 int selection, result = MINESWEEPER_QUIT;
415 bool menu_quit = false;
417 MENUITEM_STRINGLIST( menu, "Minesweeper Menu", NULL, "Play Minesweeper",
418 "Mine Percentage", "Number of Rows",
419 "Number of Columns", "Quit" );
421 #ifdef HAVE_LCD_COLOR
422 rb->lcd_set_foreground( rb->global_settings->fg_color );
423 rb->lcd_set_background( rb->global_settings->bg_color );
424 #endif
426 while( !menu_quit )
428 switch( rb->do_menu( &menu, &selection ) )
430 case 0:
431 result = MINESWEEPER_WIN; /* start playing */
432 menu_quit = true;
433 break;
435 case 1:
436 rb->set_int( "Mine Percentage", "%", UNIT_INT, &p, NULL,
437 1, 2, 98, NULL );
438 break;
440 case 2:
441 rb->set_int( "Number of Rows", "", UNIT_INT, &height, NULL,
442 1, 1, MAX_HEIGHT, NULL );
443 break;
445 case 3:
446 rb->set_int( "Number of Columns", "", UNIT_INT, &width, NULL,
447 1, 1, MAX_WIDTH, NULL );
448 break;
450 default:
451 result = MINESWEEPER_QUIT; /* quit program */
452 menu_quit = true;
453 break;
457 return result;
460 /* the big and ugly game function */
461 enum minesweeper_status minesweeper( void )
463 int i, j;
464 int button;
465 int lastbutton = BUTTON_NONE;
467 /* the cursor coordinates */
468 int x=0, y=0;
471 * Show the menu
473 if( ( i = menu() ) != MINESWEEPER_WIN ) return i;
476 * Init game
478 top = (LCD_HEIGHT-height*TileSize)/2;
479 left = (LCD_WIDTH-width*TileSize)/2;
481 rb->srand( *rb->current_tick );
482 minesweeper_init();
483 x = 0;
484 y = 0;
487 * Play
489 while( true )
492 /* clear the screen buffer */
493 #ifdef HAVE_LCD_COLOR
494 rb->lcd_set_background( BackgroundColor );
495 #endif
496 rb->lcd_clear_display();
498 /* display the mine field */
499 for( i = 0; i < height; i++ )
501 for( j = 0; j < width; j++ )
503 if( minefield[i][j].known )
505 draw_tile( minefield[i][j].neighbors, j, i );
507 else if(minefield[i][j].flag)
509 draw_tile( Flag, j, i );
511 else
513 draw_tile( Unknown, j, i );
518 /* display the cursor */
519 invert_tile( x, y );
521 /* update the screen */
522 rb->lcd_update();
524 switch( button = rb->button_get( true ) )
526 /* quit minesweeper (you really shouldn't use this button ...) */
527 #ifdef MINESWP_RC_QUIT
528 case MINESWP_RC_QUIT:
529 #endif
530 case MINESWP_QUIT:
531 return MINESWEEPER_QUIT;
533 /* move cursor left */
534 case BUTTON_LEFT:
535 case BUTTON_LEFT|BUTTON_REPEAT:
536 x = ( x + width - 1 )%width;
537 break;
539 /* move cursor right */
540 case BUTTON_RIGHT:
541 case BUTTON_RIGHT|BUTTON_REPEAT:
542 x = ( x + 1 )%width;
543 break;
545 /* move cursor down */
546 case MINESWP_DOWN:
547 case MINESWP_DOWN|BUTTON_REPEAT:
548 y = ( y + 1 )%height;
549 break;
551 /* move cursor up */
552 case MINESWP_UP:
553 case MINESWP_UP|BUTTON_REPEAT:
554 y = ( y + height - 1 )%height;
555 break;
557 /*move cursor though the entire field*/
558 #ifdef MINESWP_SCROLLWHEEL
559 case MINESWP_NEXT:
560 case MINESWP_NEXT|BUTTON_REPEAT:
561 if (x == width -1 ) {
562 y = ( y + 1 )%height;
564 x = ( x + 1 )%width;
565 break;
567 case MINESWP_PREV:
568 case MINESWP_PREV|BUTTON_REPEAT:
569 if (x == 0) {
570 y = ( y + height - 1 )%height;
572 x = ( x + width - 1 )%width;
573 break;
574 #endif
575 /* discover a tile (and it's neighbors if .neighbors == 0) */
576 case MINESWP_DISCOVER:
577 #ifdef MINESWP_DISCOVER2
578 case MINESWP_DISCOVER2:
579 #endif
580 if( minefield[y][x].flag ) break;
581 /* we put the mines on the first "click" so that you don't
582 * lose on the first "click" */
583 if( tiles_left == width*height && no_mines )
584 minesweeper_putmines(p,x,y);
586 discover(y, x);
588 if( minefield[y][x].mine )
590 minefield[y][x].known = 1;
591 return MINESWEEPER_LOSE;
593 tiles_left = count_tiles_left();
594 if( tiles_left == mine_num )
596 return MINESWEEPER_WIN;
598 break;
600 /* toggle flag under cursor */
601 case MINESWP_TOGGLE:
602 #ifdef MINESWP_TOGGLE_PRE
603 if( lastbutton != MINESWP_TOGGLE_PRE )
604 break;
605 #endif
606 #ifdef MINESWP_TOGGLE2
607 case MINESWP_TOGGLE2:
608 #endif
609 minefield[y][x].flag = ( minefield[y][x].flag + 1 )%2;
610 break;
612 /* show how many mines you think you have found and how many
613 * there really are on the game */
614 case MINESWP_INFO:
615 if( no_mines )
616 break;
617 flags_used = count_flags();
618 if (flags_used == 1) {
619 rb->splash( HZ*2, "You marked 1 field. There are %d mines.",
620 mine_num );
622 else
624 rb->splash( HZ*2, "You marked %d fields. There are %d mines.",
625 flags_used, mine_num );
627 break;
629 default:
630 if( rb->default_event_handler( button ) == SYS_USB_CONNECTED )
631 return MINESWEEPER_USB;
632 break;
634 if( button != BUTTON_NONE )
635 lastbutton = button;
640 /* plugin entry point */
641 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
643 bool exit = false;
645 (void)parameter;
646 rb = api;
647 #if LCD_DEPTH > 1
648 rb->lcd_set_backdrop(NULL);
649 #endif
651 while( !exit )
653 switch( minesweeper() )
655 case MINESWEEPER_WIN:
656 rb->splash( HZ, "You Win!" );
657 rb->lcd_clear_display();
658 mine_show();
659 break;
661 case MINESWEEPER_LOSE:
662 rb->splash( HZ, "You Lose!" );
663 rb->lcd_clear_display();
664 mine_show();
665 break;
667 case MINESWEEPER_USB:
668 return PLUGIN_USB_CONNECTED;
670 case MINESWEEPER_QUIT:
671 exit = true;
672 break;
674 default:
675 break;
679 return PLUGIN_OK;
682 #endif