*: Enhance minesweeper graphics
[maemo-rb.git] / apps / plugins / minesweeper.c
blob317a969fb0d03fa0f14c1bf17aab67f9f4b53a16
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_START BUTTON_ON
40 # define MINESWP_TOGGLE BUTTON_PLAY
41 # define MINESWP_TOGGLE2 BUTTON_F1
42 # define MINESWP_DISCOVER BUTTON_ON
43 # define MINESWP_DISCOVER2 BUTTON_F2
44 # define MINESWP_INFO BUTTON_F3
45 # define MINESWP_RIGHT (BUTTON_F1 | BUTTON_RIGHT)
46 # define MINESWP_LEFT (BUTTON_F1 | BUTTON_LEFT)
48 #elif CONFIG_KEYPAD == ONDIO_PAD
49 # define MINESWP_UP BUTTON_UP
50 # define MINESWP_DOWN BUTTON_DOWN
51 # define MINESWP_QUIT BUTTON_OFF
52 # define MINESWP_START BUTTON_MENU
53 # define MINESWP_TOGGLE_PRE BUTTON_MENU
54 # define MINESWP_TOGGLE (BUTTON_MENU | BUTTON_REL)
55 # define MINESWP_DISCOVER (BUTTON_MENU | BUTTON_REPEAT)
56 # define MINESWP_INFO (BUTTON_MENU | BUTTON_OFF)
57 # define MINESWP_RIGHT (BUTTON_MENU | BUTTON_RIGHT)
58 # define MINESWP_LEFT (BUTTON_MENU | BUTTON_LEFT)
60 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
61 (CONFIG_KEYPAD == IRIVER_H300_PAD)
62 # define MINESWP_UP BUTTON_UP
63 # define MINESWP_DOWN BUTTON_DOWN
64 # define MINESWP_QUIT BUTTON_OFF
65 # define MINESWP_START BUTTON_SELECT
66 # define MINESWP_TOGGLE BUTTON_ON
67 # define MINESWP_DISCOVER BUTTON_SELECT
68 # define MINESWP_INFO BUTTON_MODE
69 # define MINESWP_RIGHT (BUTTON_ON | BUTTON_RIGHT)
70 # define MINESWP_LEFT (BUTTON_ON | BUTTON_LEFT)
72 # define MINESWP_RC_QUIT BUTTON_RC_STOP
74 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
75 (CONFIG_KEYPAD == IPOD_3G_PAD)
76 # define MINESWP_UP BUTTON_SCROLL_BACK
77 # define MINESWP_DOWN BUTTON_SCROLL_FWD
78 # define MINESWP_QUIT BUTTON_MENU
79 # define MINESWP_START BUTTON_SELECT
80 # define MINESWP_TOGGLE BUTTON_PLAY
81 # define MINESWP_DISCOVER (BUTTON_SELECT | BUTTON_PLAY)
82 # define MINESWP_INFO (BUTTON_SELECT | BUTTON_MENU)
83 # define MINESWP_RIGHT (BUTTON_SELECT | BUTTON_RIGHT)
84 # define MINESWP_LEFT (BUTTON_SELECT | BUTTON_LEFT)
86 #elif (CONFIG_KEYPAD == IAUDIO_X5_PAD)
87 # define MINESWP_UP BUTTON_UP
88 # define MINESWP_DOWN BUTTON_DOWN
89 # define MINESWP_QUIT BUTTON_POWER
90 # define MINESWP_START BUTTON_REC
91 # define MINESWP_TOGGLE BUTTON_PLAY
92 # define MINESWP_DISCOVER BUTTON_SELECT
93 # define MINESWP_INFO (BUTTON_REC | BUTTON_PLAY)
94 # define MINESWP_RIGHT (BUTTON_PLAY | BUTTON_RIGHT)
95 # define MINESWP_LEFT (BUTTON_PLAY | BUTTON_LEFT)
97 #elif (CONFIG_KEYPAD == GIGABEAT_PAD)
98 # define MINESWP_UP BUTTON_UP
99 # define MINESWP_DOWN BUTTON_DOWN
100 # define MINESWP_QUIT BUTTON_A
101 # define MINESWP_START BUTTON_SELECT
102 # define MINESWP_TOGGLE BUTTON_POWER
103 # define MINESWP_DISCOVER BUTTON_SELECT
104 # define MINESWP_INFO BUTTON_MENU
105 # define MINESWP_RIGHT (BUTTON_SELECT | BUTTON_RIGHT)
106 # define MINESWP_LEFT (BUTTON_SELECT | BUTTON_LEFT)
108 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
109 # define MINESWP_UP BUTTON_SCROLL_UP
110 # define MINESWP_DOWN BUTTON_SCROLL_DOWN
111 # define MINESWP_QUIT BUTTON_POWER
112 # define MINESWP_START BUTTON_FF
113 # define MINESWP_TOGGLE BUTTON_PLAY
114 # define MINESWP_DISCOVER BUTTON_REW
115 # define MINESWP_INFO (BUTTON_REW | BUTTON_PLAY)
116 # define MINESWP_RIGHT (BUTTON_RIGHT | BUTTON_PLAY)
117 # define MINESWP_LEFT (BUTTON_LEFT | BUTTON_PLAY)
119 #else
120 # warning Missing key definitions for this keypad
121 #endif
123 /* here is a global api struct pointer. while not strictly necessary,
124 * it's nice not to have to pass the api pointer in all function calls
125 * in the plugin
127 static struct plugin_api *rb;
129 extern const fb_data minesweeper_tiles[];
131 #ifdef HAVE_LCD_COLOR
132 # if ( LCD_HEIGHT * LCD_WIDTH ) / ( 16 * 16 ) >= 130
133 /* We want to have at least 130 tiles on the screen */
134 # define TileSize 16
135 # else
136 # define TileSize 12
137 # endif
138 # define BackgroundColor LCD_RGBPACK( 128, 128, 128 )
139 #elif LCD_DEPTH > 1
140 # define TileSize 12
141 #else
142 # define TileSize 8
143 #endif
145 #define Mine 9
146 #define Flag 10
147 #define Unknown 11
148 #define ExplodedMine 12
150 #define draw_tile( num, x, y ) \
151 rb->lcd_bitmap_part( minesweeper_tiles, 0, num * TileSize, \
152 TileSize, left+x*TileSize, top+y*TileSize, \
153 TileSize, TileSize )
155 #define invert_tile( x, y ) \
156 rb->lcd_set_drawmode(DRMODE_COMPLEMENT); \
157 rb->lcd_fillrect( left+x*TileSize, top+y*TileSize, TileSize, TileSize ); \
158 rb->lcd_set_drawmode(DRMODE_SOLID);
161 /* the tile struct
162 * if there is a mine, mine is true
163 * if tile is known by player, known is true
164 * if tile has a flag, flag is true
165 * neighbors is the total number of mines arround tile
167 typedef struct tile
169 unsigned char mine : 1;
170 unsigned char known : 1;
171 unsigned char flag : 1;
172 unsigned char neighbors : 4;
173 } tile;
175 /* the height and width of the field */
176 #define MAX_HEIGHT (LCD_HEIGHT/TileSize)
177 #define MAX_WIDTH (LCD_WIDTH/TileSize)
178 int height = MAX_HEIGHT;
179 int width = MAX_WIDTH;
180 int top;
181 int left;
183 /* The Minefield. Caution it is defined as Y, X! Not the opposite. */
184 tile minefield[MAX_HEIGHT][MAX_WIDTH];
186 /* total number of mines on the game */
187 int mine_num = 0;
189 /* percentage of mines on minefield used during generation */
190 int p = 16;
192 /* number of tiles left on the game */
193 int tiles_left;
195 /* Because mines are set after the first move... */
196 bool no_mines = true;
198 /* We need a stack (created on discover()) for the cascade algorithm. */
199 int stack_pos = 0;
201 /* a usefull string for snprintf */
202 char str[30];
205 void push( int *stack, int y, int x )
207 if( stack_pos <= height*width )
209 stack[++stack_pos] = y;
210 stack[++stack_pos] = x;
214 /* Unveil tiles and push them to stack if they are empty. */
215 void unveil( int *stack, int y, int x )
217 if( x < 0 || y < 0 || x > width - 1 || y > height - 1
218 || minefield[y][x].known
219 || minefield[y][x].mine || minefield[y][x].flag ) return;
221 minefield[y][x].known = 1;
223 if( minefield[y][x].neighbors == 0 )
224 push( stack, y, x );
227 void discover( int y, int x )
229 int stack[height*width];
231 /* Selected tile. */
232 if( x < 0 || y < 0 || x > width - 1 || y > height - 1
233 || minefield[y][x].known
234 || minefield[y][x].mine || minefield[y][x].flag ) return;
236 minefield[y][x].known = 1;
237 /* Exit if the tile is not empty. (no mines nearby) */
238 if( minefield[y][x].neighbors ) return;
240 push( stack, y, x );
242 /* Scan all nearby tiles. If we meet a tile with a number we just unveil
243 * it. If we meet an empty tile, we push the location in stack. For each
244 * location in stack we do the same thing. (scan again all nearby tiles)
246 while( stack_pos )
248 /* Pop x, y from stack. */
249 x = stack[stack_pos--];
250 y = stack[stack_pos--];
252 unveil( stack, y-1, x-1 );
253 unveil( stack, y-1, x );
254 unveil( stack, y-1, x+1 );
255 unveil( stack, y, x+1 );
256 unveil( stack, y+1, x+1 );
257 unveil( stack, y+1, x );
258 unveil( stack, y+1, x-1 );
259 unveil( stack, y, x-1 );
263 /* Reset the whole board for a new game. */
264 void minesweeper_init( void )
266 int i,j;
268 for( i = 0; i < MAX_HEIGHT; i++ )
270 for( j = 0; j < MAX_WIDTH; j++ )
272 minefield[i][j].known = 0;
273 minefield[i][j].flag = 0;
274 minefield[i][j].mine = 0;
275 minefield[i][j].neighbors = 0;
278 no_mines = true;
279 tiles_left = width*height;
283 /* put mines on the mine field */
284 /* there is p% chance that a tile is a mine */
285 /* if the tile has coordinates (x,y), then it can't be a mine */
286 void minesweeper_putmines( int p, int x, int y )
288 int i,j;
290 mine_num = 0;
291 for( i = 0; i < height; i++ )
293 for( j = 0; j < width; j++ )
295 if( rb->rand()%100 < p && !( y==i && x==j ) )
297 minefield[i][j].mine = 1;
298 mine_num++;
300 else
302 minefield[i][j].mine = 0;
304 minefield[i][j].neighbors = 0;
308 /* we need to compute the neighbor element for each tile */
309 for( i = 0; i < height; i++ )
311 for( j = 0; j < width; j++ )
313 if( i > 0 )
315 if( j > 0 )
316 minefield[i][j].neighbors += minefield[i-1][j-1].mine;
317 minefield[i][j].neighbors += minefield[i-1][j].mine;
318 if( j < width - 1 )
319 minefield[i][j].neighbors += minefield[i-1][j+1].mine;
321 if( j > 0 )
322 minefield[i][j].neighbors += minefield[i][j-1].mine;
323 if( j < width - 1 )
324 minefield[i][j].neighbors += minefield[i][j+1].mine;
325 if( i < height - 1 )
327 if( j > 0 )
328 minefield[i][j].neighbors += minefield[i+1][j-1].mine;
329 minefield[i][j].neighbors += minefield[i+1][j].mine;
330 if( j < width - 1 )
331 minefield[i][j].neighbors += minefield[i+1][j+1].mine;
336 no_mines = false;
338 /* In case the user is lucky and there are no mines positioned. */
339 if( !mine_num && height*width != 1 )
341 minesweeper_putmines(p, x, y);
345 /* A function that will uncover all the board, when the user wins or loses.
346 can easily be expanded, (just a call assigned to a button) as a solver. */
347 void mine_show( void )
349 int i, j, button;
351 for( i = 0; i < height; i++ )
353 for( j = 0; j < width; j++ )
355 if( minefield[i][j].mine )
357 if( minefield[i][j].known )
359 draw_tile( ExplodedMine, j, i );
361 else
363 draw_tile( Mine, j, i );
366 else
368 draw_tile( minefield[i][j].neighbors, j, i );
372 rb->lcd_update();
375 button = rb->button_get(true);
376 while( ( button == BUTTON_NONE )
377 || ( button & (BUTTON_REL|BUTTON_REPEAT) ) );
380 int count_tiles_left( void )
382 int tiles_left = 0;
383 int i, j;
384 for( i = 0; i < height; i++ )
385 for( j = 0; j < width; j++ )
386 if( minefield[i][j].known == 0 )
387 tiles_left++;
388 return tiles_left;
391 /* welcome screen where player can chose mine percentage */
392 enum minesweeper_status menu( void )
394 int button;
396 while( true )
398 #ifdef HAVE_LCD_COLOR
399 rb->lcd_set_background( LCD_WHITE );
400 rb->lcd_set_foreground( LCD_BLACK );
401 #endif
402 rb->lcd_clear_display();
404 rb->lcd_puts( 0, 0, "Mine Sweeper" );
406 rb->snprintf( str, 20, "%d%% mines", p );
407 rb->lcd_puts( 0, 2, str );
408 rb->lcd_puts( 0, 3, "down / up" );
409 rb->snprintf( str, 20, "%d cols x %d rows", width, height );
410 rb->lcd_puts( 0, 4, str );
411 rb->lcd_puts( 0, 5, "left x right" );
412 rb->lcd_puts( 0, 6,
413 #if CONFIG_KEYPAD == RECORDER_PAD
414 "ON to start"
415 #elif CONFIG_KEYPAD == ONDIO_PAD
416 "MODE to start"
417 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) \
418 || (CONFIG_KEYPAD == IRIVER_H300_PAD ) \
419 || (CONFIG_KEYPAD == IPOD_4G_PAD)
420 "SELECT to start"
421 #elif CONFIG_KEYPAD == IAUDIO_X5_PAD
422 "REC to start"
423 #else
425 # warning Please define help string for this keypad.
426 #endif
428 rb->lcd_update();
430 switch( button = rb->button_get( true ) )
432 case MINESWP_DOWN:
433 case MINESWP_DOWN|BUTTON_REPEAT:
434 p = (p + 94)%98 + 2;
435 break;
437 case MINESWP_UP:
438 case MINESWP_UP|BUTTON_REPEAT:
439 p = p%98 + 2;
440 break;
442 case BUTTON_RIGHT:
443 case BUTTON_RIGHT|BUTTON_REPEAT:
444 height = height%MAX_HEIGHT + 1;
445 break;
447 case BUTTON_LEFT:
448 case BUTTON_LEFT|BUTTON_REPEAT:
449 width = width%MAX_WIDTH + 1;
450 break;
452 case MINESWP_RIGHT:
453 case MINESWP_RIGHT|BUTTON_REPEAT:
454 height--;
455 if( height < 1 ) height = MAX_HEIGHT;
456 break;
458 case MINESWP_LEFT:
459 case MINESWP_LEFT|BUTTON_REPEAT:
460 width--;
461 if( width < 1 ) width = MAX_WIDTH;
462 break;
464 case MINESWP_START:/* start playing */
465 return MINESWEEPER_WIN;
467 #ifdef MINESWP_RC_QUIT
468 case MINESWP_RC_QUIT:
469 #endif
470 case MINESWP_QUIT:/* quit program */
471 return MINESWEEPER_QUIT;
473 default:
474 if( rb->default_event_handler(button) == SYS_USB_CONNECTED )
475 return MINESWEEPER_USB;
476 break;
481 /* the big and ugly game function */
482 enum minesweeper_status minesweeper( void )
484 int i, j;
485 int button;
486 int lastbutton = BUTTON_NONE;
488 /* the cursor coordinates */
489 int x=0, y=0;
492 * Show the menu
494 if( ( i = menu() ) != MINESWEEPER_WIN ) return i;
497 * Init game
499 top = (LCD_HEIGHT-height*TileSize)/2;
500 left = (LCD_WIDTH-width*TileSize)/2;
502 rb->srand( *rb->current_tick );
503 minesweeper_init();
504 x = 0;
505 y = 0;
508 * Play
510 while( true )
513 /* clear the screen buffer */
514 #ifdef HAVE_LCD_COLOR
515 rb->lcd_set_background( BackgroundColor );
516 #endif
517 rb->lcd_clear_display();
519 /* display the mine field */
520 for( i = 0; i < height; i++ )
522 for( j = 0; j < width; j++ )
524 if( minefield[i][j].known )
526 draw_tile( minefield[i][j].neighbors, j, i );
528 else if(minefield[i][j].flag)
530 draw_tile( Flag, j, i );
532 else
534 draw_tile( Unknown, j, i );
539 /* display the cursor */
540 invert_tile( x, y );
542 /* update the screen */
543 rb->lcd_update();
545 switch( button = rb->button_get( true ) )
547 /* quit minesweeper (you really shouldn't use this button ...) */
548 #ifdef MINESWP_RC_QUIT
549 case MINESWP_RC_QUIT:
550 #endif
551 case MINESWP_QUIT:
552 return MINESWEEPER_QUIT;
554 /* move cursor left */
555 case BUTTON_LEFT:
556 case BUTTON_LEFT|BUTTON_REPEAT:
557 x = ( x + width - 1 )%width;
558 break;
560 /* move cursor right */
561 case BUTTON_RIGHT:
562 case BUTTON_RIGHT|BUTTON_REPEAT:
563 x = ( x + 1 )%width;
564 break;
566 /* move cursor down */
567 case MINESWP_DOWN:
568 case MINESWP_DOWN|BUTTON_REPEAT:
569 y = ( y + 1 )%height;
570 break;
572 /* move cursor up */
573 case MINESWP_UP:
574 case MINESWP_UP|BUTTON_REPEAT:
575 y = ( y + height - 1 )%height;
576 break;
578 /* discover a tile (and it's neighbors if .neighbors == 0) */
579 case MINESWP_DISCOVER:
580 #ifdef MINESWP_DISCOVER2
581 case MINESWP_DISCOVER2:
582 #endif
583 if( minefield[y][x].flag ) break;
584 /* we put the mines on the first "click" so that you don't
585 * lose on the first "click" */
586 if( tiles_left == width*height && no_mines )
587 minesweeper_putmines(p,x,y);
589 discover(y, x);
591 if( minefield[y][x].mine )
593 minefield[y][x].known = 1;
594 return MINESWEEPER_LOSE;
596 tiles_left = count_tiles_left();
597 if( tiles_left == mine_num )
599 return MINESWEEPER_WIN;
601 break;
603 /* toggle flag under cursor */
604 case MINESWP_TOGGLE:
605 #ifdef MINESWP_TOGGLE_PRE
606 if( lastbutton != MINESWP_TOGGLE_PRE )
607 break;
608 #endif
609 #ifdef MINESWP_TOGGLE2
610 case MINESWP_TOGGLE2:
611 #endif
612 minefield[y][x].flag = ( minefield[y][x].flag + 1 )%2;
613 break;
615 /* show how many mines you think you have found and how many
616 * there really are on the game */
617 case MINESWP_INFO:
618 if( no_mines )
619 break;
620 tiles_left = count_tiles_left();
621 rb->splash( HZ*2, true, "You found %d mines out of %d",
622 tiles_left, mine_num );
623 break;
625 default:
626 if( rb->default_event_handler( button ) == SYS_USB_CONNECTED )
627 return MINESWEEPER_USB;
628 break;
630 if( button != BUTTON_NONE )
631 lastbutton = button;
636 /* plugin entry point */
637 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
639 bool exit = false;
641 (void)parameter;
642 rb = api;
644 while( !exit )
646 switch( minesweeper() )
648 case MINESWEEPER_WIN:
649 rb->splash( HZ, true, "You Win!" );
650 rb->lcd_clear_display();
651 mine_show();
652 break;
654 case MINESWEEPER_LOSE:
655 rb->splash( HZ, true, "You Lose!" );
656 rb->lcd_clear_display();
657 mine_show();
658 break;
660 case MINESWEEPER_USB:
661 return PLUGIN_USB_CONNECTED;
663 case MINESWEEPER_QUIT:
664 exit = true;
665 break;
667 default:
668 break;
672 return PLUGIN_OK;
675 #endif