Minesweeper and Solitaire plugins by Antoine Cellerier
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@4997 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/plugins/minesweeper.c b/apps/plugins/minesweeper.c
new file mode 100644
index 0000000..b155c3f
--- /dev/null
+++ b/apps/plugins/minesweeper.c
@@ -0,0 +1,436 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2004 dionoea (Antoine Cellerier)
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+/*****************************************************************************
+Mine Sweeper by dionoea
+
+use arrow keys to move cursor
+use ON or F2 to clear a tile
+use PLAY or F1 to put a flag on a tile
+use F3 to see how many mines are left (supposing all your flags are correct)
+
+*****************************************************************************/
+
+#include "plugin.h"
+#include "button.h"
+#include "lcd.h"
+
+#ifdef HAVE_LCD_BITMAP
+
+//what the minesweeper() function can return
+#define MINESWEEPER_QUIT 2
+#define MINESWEEPER_LOSE 1
+#define MINESWEEPER_WIN 0
+
+
+/* here is a global api struct pointer. while not strictly necessary,
+ it's nice not to have to pass the api pointer in all function calls
+ in the plugin */
+static struct plugin_api* rb;
+
+
+//define how numbers are displayed (that way we don't have to
+//worry about fonts)
+static unsigned char num[9][8] = {
+ /*reading the sprites:
+ on screen f123
+ 4567
+ 890a
+ bcde
+
+ in binary b84f
+ c951
+ d062
+ ea73
+ */
+
+ //0
+ {0x00, //........
+ 0x00, //........
+ 0x00, //........
+ 0x00, //........
+ 0x00, //........
+ 0x00, //........
+ 0x00, //........
+ 0x00},//........
+ //1
+ {0x00, //........
+ 0x00, //........
+ 0x00, //...OO...
+ 0x44, //....O...
+ 0x7c, //....O...
+ 0x40, //....O...
+ 0x00, //...OOO..
+ 0x00},//........
+ //2
+ {0x00, //........
+ 0x00, //........
+ 0x48, //...OO...
+ 0x64, //..O..O..
+ 0x54, //....O...
+ 0x48, //...O....
+ 0x00, //..OOOO..
+ 0x00},//........
+ //3
+ {0x00, //........
+ 0x00, //........
+ 0x44, //..OOO...
+ 0x54, //.....O..
+ 0x54, //...OO...
+ 0x28, //.....O..
+ 0x00, //..OOO...
+ 0x00},//........
+ //4
+ {0x00, //........
+ 0x00, //........
+ 0x1c, //..O.....
+ 0x10, //..O.....
+ 0x70, //..OOOO..
+ 0x10, //....O...
+ 0x00, //....O...
+ 0x00},//........
+ //5
+ {0x00, //........
+ 0x00, //........
+ 0x5c, //..OOOO..
+ 0x54, //..O.....
+ 0x54, //..OOO...
+ 0x24, //.....O..
+ 0x00, //..OOO...
+ 0x00},//........
+ //6
+ {0x00, //........
+ 0x00, //........
+ 0x38, //...OOO..
+ 0x54, //..O.....
+ 0x54, //..OOO...
+ 0x24, //..O..O..
+ 0x00, //...OO...
+ 0x00},//........
+ //7
+ {0x00, //........
+ 0x00, //........
+ 0x44, //..OOOO..
+ 0x24, //.....O..
+ 0x14, //....O...
+ 0x0c, //...O....
+ 0x00, //..O.....
+ 0x00},//........
+ //8
+ {0x00, //........
+ 0x00, //........
+ 0x28, //...OO...
+ 0x54, //..O..O..
+ 0x54, //...OO...
+ 0x28, //..O..O..
+ 0x00, //...OO...
+ 0x00},//........
+};
+
+/* the tile struct
+if there is a mine, mine is true
+if tile is known by player, known is true
+if tile has a flag, flag is true
+neighbors is the total number of mines arround tile
+*/
+typedef struct tile {
+ unsigned char mine : 1;
+ unsigned char known : 1;
+ unsigned char flag : 1;
+ unsigned char neighbors : 4;
+} tile;
+
+//the height and width of the field
+//could be variable if malloc worked in the API :)
+const int height = LCD_HEIGHT/8;
+const int width = LCD_WIDTH/8;
+
+//the minefield
+tile minefield[LCD_HEIGHT/8][LCD_WIDTH/8];
+
+//total number of mines on the game
+int mine_num = 0;
+
+//discovers the tile when player clears one of them
+//a chain reaction (of discovery) occurs if tile has no mines
+//as neighbors
+void discover(int, int);
+void discover(int x, int y){
+
+ if(x<0) return;
+ if(y<0) return;
+ if(x>width-1) return;
+ if(y>height-1) return;
+ if(minefield[y][x].known) return;
+
+ minefield[y][x].known = 1;
+ if(minefield[y][x].neighbors == 0){
+ discover(x-1,y-1);
+ discover(x,y-1);
+ discover(x+1,y-1);
+ discover(x+1,y);
+ discover(x+1,y+1);
+ discover(x,y+1);
+ discover(x-1,y+1);
+ discover(x-1,y);
+ }
+ return;
+}
+
+
+//init not mine related elements of the mine field
+void minesweeper_init(void){
+ int i,j;
+
+ for(i=0;i<height;i++){
+ for(j=0;j<width;j++){
+ minefield[i][j].known = 0;
+ minefield[i][j].flag = 0;
+ }
+ }
+}
+
+
+//put mines on the mine field
+//there is p% chance that a tile is a mine
+//if the tile has coordinates (x,y), then it can't be a mine
+void minesweeper_putmines(int p, int x, int y){
+ int i,j;
+
+ for(i=0;i<height;i++){
+ for(j=0;j<width;j++){
+ if(rb->rand()%100<p && !(y==i && x==j)){
+ minefield[i][j].mine = 1;
+ mine_num++;
+ } else {
+ minefield[i][j].mine = 0;
+ }
+ minefield[i][j].neighbors = 0;
+ }
+ }
+
+ //we need to compute the neighbor element for each tile
+ for(i=0;i<height;i++){
+ for(j=0;j<width;j++){
+ if(i>0){
+ if(j>0) minefield[i][j].neighbors += minefield[i-1][j-1].mine;
+ minefield[i][j].neighbors += minefield[i-1][j].mine;
+ if(j<width-1) minefield[i][j].neighbors += minefield[i-1][j+1].mine;
+ }
+ if(j>0) minefield[i][j].neighbors += minefield[i][j-1].mine;
+ if(j<width-1) minefield[i][j].neighbors += minefield[i][j+1].mine;
+ if(i<height-1){
+ if(j>0) minefield[i][j].neighbors += minefield[i+1][j-1].mine;
+ minefield[i][j].neighbors += minefield[i+1][j].mine;
+ if(j<width-1) minefield[i][j].neighbors += minefield[i+1][j+1].mine;
+ }
+ }
+ }
+}
+
+//the big and ugly function that is the game
+int minesweeper(void){
+
+
+ int i,j;
+
+ //the cursor coordinates
+ int x=0,y=0;
+
+ //number of tiles left on the game
+ int tiles_left=width*height;
+
+ //percentage of mines on minefield used durring generation
+ int p=16;
+
+ //a usefull string for snprintf
+ char str[30];
+
+ //welcome screen where player can chose mine percentage
+ i = 0;
+ while(true){
+ rb->lcd_clear_display();
+
+ rb->lcd_putsxy(1,1,"Mine Sweeper");
+
+ rb->snprintf(str, 20, "%d%% mines", p);
+ rb->lcd_putsxy(1,19,str);
+ rb->lcd_putsxy(1,28,"down / up");
+ rb->lcd_putsxy(1,44,"ON to start");
+
+ rb->lcd_update();
+
+
+ switch(rb->button_get(true)){
+ case BUTTON_DOWN:
+ case BUTTON_LEFT:
+ p = (p + 98)%100;
+ break;
+
+ case BUTTON_UP:
+ case BUTTON_RIGHT:
+ p = (p + 2)%100;
+ break;
+
+ case BUTTON_ON://start playing
+ i = 1;
+ break;
+
+ case BUTTON_OFF://quit program
+ return MINESWEEPER_QUIT;
+ }
+ if(i==1) break;
+ }
+
+
+ /********************
+ * init *
+ ********************/
+
+ minesweeper_init();
+
+ /**********************
+ * play *
+ **********************/
+
+ while(true){
+
+ //clear the screen buffer
+ rb->lcd_clear_display();
+
+ //display the mine field
+ for(i=0;i<height;i++){
+ for(j=0;j<width;j++){
+ rb->lcd_drawrect(j*8,i*8,8,8);
+ if(minefield[i][j].known){
+ if(minefield[i][j].mine){
+ rb->lcd_putsxy(j*8+1,i*8+1,"b");
+ } else if(minefield[i][j].neighbors){
+ rb->lcd_bitmap(num[minefield[i][j].neighbors],j*8,i*8,8,8,false);
+ }
+ } else if(minefield[i][j].flag) {
+ rb->lcd_drawline(j*8+2,i*8+2,j*8+5,i*8+5);
+ rb->lcd_drawline(j*8+2,i*8+5,j*8+5,i*8+2);
+ } else {
+ rb->lcd_fillrect(j*8+2,i*8+2,4,4);
+ }
+ }
+ }
+
+ //display the cursor
+ rb->lcd_invertrect(x*8,y*8,8,8);
+
+ //update the screen
+ rb->lcd_update();
+
+ switch(rb->button_get(true)){
+ //quit minesweeper (you really shouldn't use this button ...)
+ case BUTTON_OFF:
+ return MINESWEEPER_QUIT;
+
+ //move cursor left
+ case BUTTON_LEFT:
+ x = (x + width - 1)%width;
+ break;
+
+ //move cursor right
+ case BUTTON_RIGHT:
+ x = (x + 1)%width;
+ break;
+
+ //move cursor down
+ case BUTTON_DOWN:
+ y = (y + 1)%height;
+ break;
+
+ //move cursor up
+ case BUTTON_UP:
+ y = (y + height - 1)%height;
+ break;
+
+ //discover a tile (and it's neighbors if .neighbors == 0)
+ case BUTTON_ON:
+ case BUTTON_F2:
+ if(minefield[y][x].flag) break;
+ //we put the mines on the first "click" so that you don't
+ //lose on the first "click"
+ if(tiles_left == width*height) minesweeper_putmines(p,x,y);
+ discover(x,y);
+ if(minefield[y][x].mine){
+ return MINESWEEPER_LOSE;
+ }
+ tiles_left = 0;
+ for(i=0;i<height;i++){
+ for(j=0;j<width;j++){
+ if(minefield[i][j].known == 0) tiles_left++;
+ }
+ }
+ if(tiles_left == mine_num){
+ return MINESWEEPER_WIN;
+ }
+ break;
+
+ //toggle flag under cursor
+ case BUTTON_PLAY:
+ case BUTTON_F1:
+ minefield[y][x].flag = (minefield[y][x].flag + 1)%2;
+ break;
+
+ //show how many mines you think you have found and how many
+ //there really are on the game
+ case BUTTON_F3:
+ tiles_left = 0;
+ for(i=0;i<height;i++){
+ for(j=0;j<width;j++){
+ if(minefield[i][j].flag) tiles_left++;
+ }
+ }
+ rb->splash(HZ*2, true, "You found %d mines out of %d", tiles_left, mine_num);
+ break;
+ }
+ }
+
+}
+
+//plugin entry point
+enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
+{
+ //plugin init
+ TEST_PLUGIN_API(api);
+ (void)parameter;
+ rb = api;
+ //end of plugin init
+
+ switch(minesweeper()){
+ case MINESWEEPER_WIN:
+ rb->splash(HZ*2, true, "You Win :)");
+ break;
+
+ case MINESWEEPER_LOSE:
+ rb->splash(HZ*2, true, "You Lost :(");
+ break;
+
+ default:
+ break;
+ }
+
+ return PLUGIN_OK;
+}
+
+#endif
diff --git a/apps/plugins/solitaire.c b/apps/plugins/solitaire.c
new file mode 100644
index 0000000..2833443
--- /dev/null
+++ b/apps/plugins/solitaire.c
@@ -0,0 +1,867 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2004 dionoea (Antoine Cellerier)
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+/*****************************************************************************
+Solitaire by dionoea
+
+use arrows to move the cursor
+use ON to select cards in the columns, move cards inside the columns,
+ reveal hidden cards, ...
+use PLAY to move a card from the remains' stack to the top of the cursor
+use F1 to put card under cursor on one of the 4 final color stacks
+use F2 to un-select card if a card was selected, else draw 3 new cards
+ out of the remains' stack
+use F3 to put card on top of the remains' stack on one of the 4 final color
+ stacks
+
+*****************************************************************************/
+
+#include "plugin.h"
+#include "button.h"
+#include "lcd.h"
+
+#ifdef HAVE_LCD_BITMAP
+
+/* here is a global api struct pointer. while not strictly necessary,
+ it's nice not to have to pass the api pointer in all function calls
+ in the plugin */
+static struct plugin_api* rb;
+
+#define min(a,b) (a<b?a:b)
+
+#define HELP_CASE( key ) case BUTTON_ ## key: \
+ rb->splash(HZ*1, true, # key " : " HELP_BUTTON_ ## key); \
+ break;
+
+#define HELP_BUTTON_UP "Move the cursor up in the column."
+#define HELP_BUTTON_DOWN "Move the cursor down in the column."
+#define HELP_BUTTON_LEFT "Move the cursor to the previous column."
+#define HELP_BUTTON_RIGHT "Move the cursor to the next column."
+#define HELP_BUTTON_F1 "Put the card under the cursor on one of the 4 final color stacks."
+#define HELP_BUTTON_F2 "Un-select a card if it was selected. Else, draw 3 new cards out of the remains' stack."
+#define HELP_BUTTON_F3 "Put the card on top of the remains' stack on one of the 4 final color stacks."
+#define HELP_BUTTON_PLAY "Put the card on top of the remains' stack on top of the cursor."
+#define HELP_BUTTON_ON "Select cards in the columns, Move cards inside the columns, reveal hidden cards ..."
+
+static unsigned char colors[4][8] = {
+//Spades
+{0x00, //........
+ 0x18, //...O....
+ 0x1c, //..OOO...
+ 0x3e, //.OOOOO..
+ 0x1c, //.OOOOO..
+ 0x18, //...O....
+ 0x00, //........
+ 0x00},//........
+//Hearts
+{0x00, //........
+ 0x0c, //..O.O...
+ 0x1e, //.OOOOO..
+ 0x3c, //.OOOOO..
+ 0x1e, //..OOO...
+ 0x0c, //...O....
+ 0x00, //........
+ 0x00},//........
+//Clubs
+{0x00, //........
+ 0x18, //..OOO...
+ 0x0a, //...O....
+ 0x3e, //.OOOOO..
+ 0x0a, //.O.O.O..
+ 0x18, //...O....
+ 0x00, //........
+ 0x00},//........
+//Diamonds
+{0x00, //........
+ 0x08, //...O....
+ 0x1c, //..OOO...
+ 0x3e, //.OOOOO..
+ 0x1c, //..OOO...
+ 0x08, //...O....
+ 0x00, //........
+ 0x00} //........
+};
+
+static unsigned char numbers[13][8] = {
+//Ace
+{0x00, //........
+ 0x38, //...O....
+ 0x14, //..O.O...
+ 0x12, //.O...O..
+ 0x14, //.OOOOO..
+ 0x38, //.O...O..
+ 0x00, //........
+ 0x00},//........
+//2
+{0x00, //........
+ 0x24, //..OOO...
+ 0x32, //.O...O..
+ 0x32, //....O...
+ 0x2a, //..OO....
+ 0x24, //.OOOOO..
+ 0x00, //........
+ 0x00},//........
+//3
+{0x00, //........
+ 0x22, //.OOOO...
+ 0x2a, //.....O..
+ 0x2a, //..OOO...
+ 0x2a, //.....O..
+ 0x14, //.OOOO...
+ 0x00, //........
+ 0x00},//........
+//4
+{0x00, //........
+ 0x10, //....O...
+ 0x18, //...O....
+ 0x34, //..O.....
+ 0x12, //.OOOOO..
+ 0x10, //...O....
+ 0x00, //........
+ 0x00},//........
+//5
+{0x00, //........
+ 0x2e, //.OOOOO..
+ 0x2a, //.O......
+ 0x2a, //.OOOO...
+ 0x2a, //.....O..
+ 0x12, //.OOOO...
+ 0x00, //........
+ 0x00},//........
+//6
+{0x00, //........
+ 0x1c, //..OOO...
+ 0x2a, //.O......
+ 0x2a, //.OOOO...
+ 0x2a, //.O...O..
+ 0x10, //..OOO...
+ 0x00, //........
+ 0x00},//........
+//7
+{0x00, //........
+ 0x22, //.OOOOO..
+ 0x12, //....O...
+ 0x0a, //...O....
+ 0x06, //..O.....
+ 0x02, //.O......
+ 0x00, //........
+ 0x00},//........
+//8
+{0x00, //........
+ 0x14, //..OOO...
+ 0x2a, //.O...O..
+ 0x2a, //..OOO...
+ 0x2a, //.O...O..
+ 0x14, //..OOO...
+ 0x00, //........
+ 0x00},//........
+//9
+{0x00, //........
+ 0x04, //..OOO...
+ 0x2a, //.O...O..
+ 0x2a, //..OOOO..
+ 0x2a, //.....O..
+ 0x1c, //..OOO...
+ 0x00, //........
+ 0x00},//........
+//10
+{0x00, //........
+ 0x3e, //.O..O...
+ 0x00, //.O.O.O..
+ 0x1c, //.O.O.O..
+ 0x22, //.O.O.O..
+ 0x1c, //.O..O...
+ 0x00, //........
+ 0x00},//........
+//Jack
+{0x00, //........
+ 0x12, //.OOOOO..
+ 0x22, //...O....
+ 0x1e, //...O....
+ 0x02, //.O.O....
+ 0x02, //..O.....
+ 0x00, //........
+ 0x00},//........
+//Queen
+{0x00, //........
+ 0x1c, //..OOO...
+ 0x22, //.O...O..
+ 0x32, //.O...O..
+ 0x22, //.O.O.O..
+ 0x1c, //..OOO...
+ 0x00, //........
+ 0x00},//........
+//King
+{0x00, //........
+ 0x3e, //.O...O..
+ 0x08, //.O..O...
+ 0x08, //.OOO....
+ 0x14, //.O..O...
+ 0x22, //.O...O..
+ 0x00, //........
+ 0x00} //........
+};
+
+#define NOT_A_CARD 255
+
+//number of cards per color
+#define CARDS_PER_COLOR 13
+
+//number of colors
+#define COLORS 4
+
+//number of columns
+#define COL_NUM 7
+
+//number of cards that are drawn on the remains' stack (by pressing F2)
+#define CARDS_PER_DRAW 3
+
+//size of a card on the screen
+#define CARD_WIDTH 14
+#define CARD_HEIGHT 10
+
+typedef struct card {
+ unsigned char color : 2;
+ unsigned char num : 4;
+ unsigned char known : 1;
+ unsigned char used : 1;//this is what is used when dealing cards
+ unsigned char next;
+} card;
+
+unsigned char next_random_card(card *deck){
+ unsigned char i,r;
+
+ r = rb->rand()%(COLORS * CARDS_PER_COLOR)+1;
+ i = 0;
+
+ while(r>0){
+ i = (i + 1)%(COLORS * CARDS_PER_COLOR);
+ if(!deck[i].used) r--;
+ }
+
+ deck[i].used = 1;
+
+ return i;
+}
+
+//help for the not so intuitive interface
+void solitaire_help(void){
+
+ rb->lcd_clear_display();
+
+ rb->lcd_putsxy(0, 0, "Press a key to see");
+ rb->lcd_putsxy(0, 7, "it's role.");
+ rb->lcd_putsxy(0, 21, "Press OFF to");
+ rb->lcd_putsxy(0, 28, "return to menu");
+
+ rb->lcd_update();
+
+ while(1){
+
+ switch(rb->button_get(true)){
+ HELP_CASE( UP );
+ HELP_CASE( DOWN );
+ HELP_CASE( LEFT );
+ HELP_CASE( RIGHT );
+ HELP_CASE( F1 );
+ HELP_CASE( F2 );
+ HELP_CASE( F3 );
+ HELP_CASE( PLAY );
+ HELP_CASE( ON );
+
+ case BUTTON_OFF:
+ return;
+ }
+ }
+}
+
+//menu return codes
+#define MENU_RESUME 0
+#define MENU_RESTART 1
+#define MENU_HELP 2
+#define MENU_QUIT 3
+
+//menu item number
+#define MENU_LENGTH 4
+
+//different menu behaviors
+#define MENU_BEFOREGAME 0
+#define MENU_DURINGGAME 1
+
+//the menu
+//text displayed changes depending on the 'when' parameter
+int solitaire_menu(unsigned char when){
+
+ static char menu[2][MENU_LENGTH][13] =
+ { { "Start Game",
+ "",
+ "Help",
+ "Quit" },
+ { "Resume Game",
+ "Restart Game",
+ "Help",
+ "Quit"} };
+
+ int i;
+ int cursor=0;
+
+ if(when!=MENU_BEFOREGAME && when!=MENU_DURINGGAME) when = MENU_DURINGGAME;
+
+ while(1){
+
+ rb->lcd_clear_display();
+
+ rb->lcd_putsxy(20, 1, "Solitaire");
+
+ for(i = 0; i<MENU_LENGTH; i++){
+ rb->lcd_putsxy(1, 17+9*i, menu[when][i]);
+ if(cursor == i)
+ rb->lcd_invertrect(0,17-1+9*i, LCD_WIDTH, 9);
+ }
+
+ rb->lcd_update();
+
+ switch(rb->button_get(true)){
+ case BUTTON_UP:
+ cursor = (cursor + MENU_LENGTH - 1)%MENU_LENGTH;
+ break;
+
+ case BUTTON_DOWN:
+ cursor = (cursor + 1)%MENU_LENGTH;
+ break;
+
+ case BUTTON_LEFT:
+ return MENU_RESUME;
+
+ case BUTTON_PLAY:
+ case BUTTON_RIGHT:
+ switch(cursor){
+ case MENU_RESUME:
+ case MENU_RESTART:
+ case MENU_QUIT:
+ return cursor;
+
+ case MENU_HELP:
+ solitaire_help();
+ break;
+ }
+
+ case BUTTON_F1:
+ case BUTTON_F2:
+ case BUTTON_F3:
+ rb->splash(HZ, true, "Solitaire for Rockbox by dionoea");
+ break;
+
+ case BUTTON_OFF:
+ return MENU_QUIT;
+
+ default:
+ break;
+ }
+ }
+}
+
+//player's cursor
+unsigned char cur_card;
+//player's cursor column num
+unsigned char cur_col;
+
+//selected card
+unsigned char sel_card;
+
+//the deck
+card deck[COLORS * CARDS_PER_COLOR];
+
+//the remaining cards
+unsigned char rem;
+unsigned char cur_rem;
+
+//the 7 game columns
+unsigned char cols[COL_NUM];
+
+//the 4 final color stacks
+unsigned char stacks[COLORS];
+
+//initialize the game
+void solitaire_init(void){
+ unsigned char c;
+ int i,j;
+
+ //init deck
+ for(i=0;i<COLORS;i++){
+ for(j=0;j<CARDS_PER_COLOR;j++){
+ deck[i*CARDS_PER_COLOR+j].color = i;
+ deck[i*CARDS_PER_COLOR+j].num = j;
+ deck[i*CARDS_PER_COLOR+j].known = 0;
+ deck[i*CARDS_PER_COLOR+j].used = 0;
+ deck[i*CARDS_PER_COLOR+j].next = NOT_A_CARD;
+ }
+ }
+
+ //deal the cards ...
+ //... in the columns
+ for(i=0; i<COL_NUM; i++){
+ c = NOT_A_CARD;
+ for(j=0; j<=i; j++){
+ if(c == NOT_A_CARD){
+ cols[i] = next_random_card(deck);
+ c = cols[i];
+ } else {
+ deck[c].next = next_random_card(deck);
+ c = deck[c].next;
+ }
+ if(j==i) deck[c].known = 1;
+ }
+ }
+
+ //... shuffle what's left of the deck
+ rem = next_random_card(deck);
+ c = rem;
+
+ for(i=1; i<COLORS * CARDS_PER_COLOR - COL_NUM * (COL_NUM + 1)/2; i++){
+ deck[c].next = next_random_card(deck);
+ c = deck[c].next;
+ }
+
+ //we now finished dealing the cards. The game can start ! (at last)
+
+ //init the stack
+ for(i = 0; i<COL_NUM;i++){
+ stacks[i] = NOT_A_CARD;
+ }
+
+ //the cursor starts on upper left card
+ cur_card = cols[0];
+ cur_col = 0;
+
+ //no card is selected
+ sel_card = NOT_A_CARD;
+
+ //init the remainder
+ cur_rem = NOT_A_CARD;
+}
+
+
+//the game
+void solitaire(void){
+
+ int i,j;
+ unsigned char c;
+ int biggest_col_length;
+
+ if(solitaire_menu(MENU_BEFOREGAME) == MENU_QUIT) return;
+ solitaire_init();
+
+ while(true){
+
+ rb->lcd_clear_display();
+
+ //get the biggest column length so that display can be "optimized"
+ biggest_col_length = 0;
+
+ for(i=0;i<COL_NUM;i++){
+ j = 0;
+ c = cols[i];
+ while(c != NOT_A_CARD){
+ j++;
+ c = deck[c].next;
+ }
+ if(j>biggest_col_length) biggest_col_length = j;
+ }
+
+ //check if there are cards remaining in the game.
+ //if there aren't any, that means you won :)
+ if(biggest_col_length == 0 && rem == NOT_A_CARD){
+ rb->splash(HZ*2, true, "You Won :)");
+ return;
+ }
+
+ //draw the columns
+ for(i=0;i<COL_NUM;i++){
+ c = cols[i];
+ j = 0;
+ while(true){
+ if(c==NOT_A_CARD) break;
+ //clear the card's spot
+ rb->lcd_clearrect(i*(LCD_WIDTH - CARD_WIDTH)/COL_NUM, j+1, CARD_WIDTH, CARD_HEIGHT-1);
+ //known card
+ if(deck[c].known){
+ rb->lcd_bitmap(numbers[deck[c].num], i*(LCD_WIDTH - CARD_WIDTH)/COL_NUM+1, j, 8, 8, true);
+ rb->lcd_bitmap(colors[deck[c].color], i*(LCD_WIDTH - CARD_WIDTH)/COL_NUM+7, j, 8, 8, true);
+ }
+ //draw top line of the card
+ rb->lcd_drawline(i*(LCD_WIDTH - CARD_WIDTH)/COL_NUM+1,j,i*(LCD_WIDTH - CARD_WIDTH)/COL_NUM+CARD_WIDTH-1,j);
+ //selected card
+ if(c == sel_card && sel_card != NOT_A_CARD){
+ rb->lcd_drawrect(i*(LCD_WIDTH - CARD_WIDTH)/COL_NUM+1, j+1, CARD_WIDTH-1, CARD_HEIGHT-1);
+ }
+ //cursor (or not)
+ if(c == cur_card){
+ rb->lcd_invertrect(i*(LCD_WIDTH - CARD_WIDTH)/COL_NUM+1, j+1, CARD_WIDTH-1, CARD_HEIGHT-1);
+ //go to the next card
+ c = deck[c].next;
+ if(c == NOT_A_CARD) break;
+ j += CARD_HEIGHT - 2;
+ } else {
+ //go to the next card
+ c = deck[c].next;
+ if(c == NOT_A_CARD) break;
+ j += min(CARD_HEIGHT - 2, (LCD_HEIGHT - CARD_HEIGHT)/biggest_col_length);
+ }
+ }
+ //draw line to the left of the column
+ rb->lcd_drawline(i*(LCD_WIDTH - CARD_WIDTH)/COL_NUM,1,i*(LCD_WIDTH - CARD_WIDTH)/COL_NUM,j+CARD_HEIGHT-1);
+ //draw line to the right of the column
+ rb->lcd_drawline(i*(LCD_WIDTH - CARD_WIDTH)/COL_NUM+CARD_WIDTH,1,i*(LCD_WIDTH - CARD_WIDTH)/COL_NUM+CARD_WIDTH,j+CARD_HEIGHT-1);
+ //draw bottom of the last card
+ rb->lcd_drawline(i*(LCD_WIDTH - CARD_WIDTH)/COL_NUM+1,j+CARD_HEIGHT,i*(LCD_WIDTH - CARD_WIDTH)/COL_NUM+CARD_WIDTH-1,j+CARD_HEIGHT);
+ }
+
+ //draw the stacks
+ for(i=0; i<COLORS; i++){
+ c = stacks[i];
+ if(c!=NOT_A_CARD){
+ while(deck[c].next != NOT_A_CARD){
+ c = deck[c].next;
+ }
+ }
+ if(c != NOT_A_CARD) {
+ rb->lcd_bitmap(numbers[deck[c].num], LCD_WIDTH - CARD_WIDTH+1, i*CARD_HEIGHT, 8, 8, true);
+ }
+ rb->lcd_bitmap(colors[i], LCD_WIDTH - CARD_WIDTH+7, i*CARD_HEIGHT, 8, 8, true);
+ rb->lcd_drawline(LCD_WIDTH - CARD_WIDTH+1,i*CARD_HEIGHT,LCD_WIDTH - 1,i*CARD_HEIGHT);
+ rb->lcd_drawline(LCD_WIDTH - CARD_WIDTH+1,(i+1)*CARD_HEIGHT,LCD_WIDTH - 1,(i+1)*CARD_HEIGHT);
+ }
+
+ //draw the remains
+ rb->lcd_drawline(LCD_WIDTH - CARD_WIDTH+1,LCD_HEIGHT-CARD_HEIGHT-1,LCD_WIDTH - 1,LCD_HEIGHT-CARD_HEIGHT-1);
+ rb->lcd_drawline(LCD_WIDTH - CARD_WIDTH+1,LCD_HEIGHT-1,LCD_WIDTH - 1,LCD_HEIGHT-1);
+ if(cur_rem != NOT_A_CARD){
+ rb->lcd_bitmap(numbers[deck[cur_rem].num], LCD_WIDTH - CARD_WIDTH+1, LCD_HEIGHT-CARD_HEIGHT, 8, 8, true);
+ rb->lcd_bitmap(colors[deck[cur_rem].color], LCD_WIDTH - CARD_WIDTH+7, LCD_HEIGHT-CARD_HEIGHT, 8, 8, true);
+ }
+
+ rb->lcd_update();
+
+ //what to do when a key is pressed ...
+ switch(rb->button_get(true)){
+
+ //move cursor to the last card of the previous column
+ case BUTTON_RIGHT:
+ cur_col = (cur_col+1)%COL_NUM;
+ cur_card = cols[cur_col];
+ if(cur_card != NOT_A_CARD){
+ while(deck[cur_card].next != NOT_A_CARD){
+ cur_card = deck[cur_card].next;
+ }
+ }
+ break;
+
+ //move cursor to the last card of the next column
+ case BUTTON_LEFT:
+ cur_col = (cur_col + COL_NUM - 1)%COL_NUM;
+ cur_card = cols[cur_col];
+ if(cur_card != NOT_A_CARD){
+ while(deck[cur_card].next != NOT_A_CARD){
+ cur_card = deck[cur_card].next;
+ }
+ }
+ break;
+
+ //move cursor to card that's bellow
+ case BUTTON_DOWN:
+ if(cur_card == NOT_A_CARD) break;
+ if(deck[cur_card].next != NOT_A_CARD){
+ cur_card = deck[cur_card].next;
+ } else {
+ cur_card = cols[cur_col];
+ }
+ break;
+
+ //move cursor to card that's above
+ case BUTTON_UP:
+ if(cur_card == NOT_A_CARD) break;
+ if(cols[cur_col] == cur_card){
+ while(deck[cur_card].next != NOT_A_CARD){
+ cur_card = deck[cur_card].next;
+ }
+ } else {
+ c = cols[cur_col];
+ while(deck[c].next != cur_card){
+ c = deck[c].next;
+ }
+ cur_card = c;
+ }
+ break;
+
+ //Try to put card under cursor on one of the stacks
+ case BUTTON_F1:
+ //check if a card is selected
+ //else there would be nothing to move on the stacks !
+ if(cur_card != NOT_A_CARD){
+ //find the last card in the color's stack and put it's number in 'c'.
+ c = stacks[deck[cur_card].color];
+ if(c!=NOT_A_CARD){
+ while(deck[c].next!=NOT_A_CARD){
+ c = deck[c].next;
+ }
+ }
+ //if 'c' isn't a card, that means that the stack is empty
+ //which implies that only an ace can be moved
+ if(c == NOT_A_CARD){
+ //check if the selected card is an ace
+ //we don't have to check if any card is in the *.next
+ //position since the ace is the last possible card
+ if(deck[cur_card].num == 0){
+ //remove 'cur_card' from any *.next postition ...
+ //... by looking in the columns
+ for(i=0;i<COL_NUM;i++){
+ if(cols[i]==cur_card) cols[i] = NOT_A_CARD;
+ }
+ //... and in the entire deck
+ //TODO : check if looking in the cols is really needed
+ for(i=0;i<COLORS*CARDS_PER_COLOR;i++){
+ if(deck[i].next==cur_card) deck[i].next = NOT_A_CARD;
+ }
+ //move cur_card on top of the stack
+ stacks[deck[cur_card].color] = cur_card;
+ //assign the card at the bottom of cur_col to cur_card
+ cur_card = cols[cur_col];
+ if(cur_card != NOT_A_CARD){
+ while(deck[cur_card].next != NOT_A_CARD){
+ cur_card = deck[cur_card].next;
+ }
+ }
+ //clear the selection indicator
+ sel_card = NOT_A_CARD;
+ }
+ }
+ //the stack is not empty
+ //so we can move any card other than an ace
+ //we thus check that the card we are moving is the next on the stack and that it isn't under any card
+ else if(deck[cur_card].num == deck[c].num + 1 && deck[cur_card].next == NOT_A_CARD){
+ //same as above
+ for(i=0;i<COL_NUM;i++){
+ if(cols[i]==cur_card) cols[i] = NOT_A_CARD;
+ }
+ //re same
+ for(i=0;i<COLORS*CARDS_PER_COLOR;i++){
+ if(deck[i].next==cur_card) deck[i].next = NOT_A_CARD;
+ }
+ //...
+ deck[c].next = cur_card;
+ cur_card = cols[cur_col];
+ if(cur_card != NOT_A_CARD){
+ while(deck[cur_card].next != NOT_A_CARD){
+ cur_card = deck[cur_card].next;
+ }
+ }
+ sel_card = NOT_A_CARD;
+ }
+ }
+ break;
+
+ //Move cards arround, Uncover cards, ...
+ case BUTTON_ON:
+ if(sel_card == NOT_A_CARD) {
+ if((cur_card != NOT_A_CARD?deck[cur_card].next == NOT_A_CARD && deck[cur_card].known==0:0)){
+ deck[cur_card].known = 1;
+ } else {
+ sel_card = cur_card;
+ }
+ } else if(sel_card == cur_card) {
+ sel_card = NOT_A_CARD;
+ } else if(cur_card != NOT_A_CARD){
+ if(deck[cur_card].num == deck[sel_card].num + 1 && (deck[cur_card].color + deck[sel_card].color)%2 == 1 ){
+ for(i=0;i<COL_NUM;i++){
+ if(cols[i]==sel_card) cols[i] = NOT_A_CARD;
+ }
+ for(i=0;i<COLORS*CARDS_PER_COLOR;i++){
+ if(deck[i].next==sel_card) deck[i].next = NOT_A_CARD;
+ }
+ deck[cur_card].next = sel_card;
+ sel_card = NOT_A_CARD;
+ }
+ } else if(cur_card == NOT_A_CARD){
+ if(deck[sel_card].num == CARDS_PER_COLOR - 1){
+ for(i=0;i<COL_NUM;i++){
+ if(cols[i]==sel_card) cols[i] = NOT_A_CARD;
+ }
+ for(i=0;i<COLORS*CARDS_PER_COLOR;i++){
+ if(deck[i].next==sel_card) deck[i].next = NOT_A_CARD;
+ }
+ cols[cur_col] = sel_card;
+ sel_card = NOT_A_CARD;
+ }
+ }
+ break;
+
+ //If the card on the top of the remains can be put where
+ //the cursor is, go ahead
+ case BUTTON_PLAY:
+ //check if a card is face up on the remains' stack
+ if(cur_rem != NOT_A_CARD){
+ //if no card is selected, it means the col is empty
+ //thus, only a king can be moved
+ if(cur_card == NOT_A_CARD){
+ //check if selcted card is a king
+ if(deck[cur_rem].num == CARDS_PER_COLOR - 1){
+ //find the previous card on the remains' stack
+ c = rem;
+ //if the current card on the remains' stack
+ //is the first card of the stack, then ...
+ if(c == cur_rem){
+ c = NOT_A_CARD;
+ rem = deck[cur_rem].next;
+ }
+ //else ...
+ else {
+ while(deck[c].next != cur_rem){
+ c = deck[c].next;
+ }
+ deck[c].next = deck[cur_rem].next;
+ }
+ cols[cur_col] = cur_rem;
+ deck[cur_rem].next = NOT_A_CARD;
+ deck[cur_rem].known = 1;
+ cur_rem = c;
+ }
+ } else if(deck[cur_rem].num + 1 == deck[cur_card].num && (deck[cur_rem].color + deck[cur_card].color)%2==1) {
+ c = rem;
+ if(c == cur_rem){
+ c = NOT_A_CARD;
+ rem = deck[cur_rem].next;
+ } else {
+ while(deck[c].next != cur_rem){
+ c = deck[c].next;
+ }
+ deck[c].next = deck[cur_rem].next;
+ }
+ deck[cur_card].next = cur_rem;
+ deck[cur_rem].next = NOT_A_CARD;
+ deck[cur_rem].known = 1;
+ cur_rem = c;
+ }
+ }
+ break;
+
+ //If the card on top of the remains can be put on one
+ //of the stacks, do so
+ case BUTTON_F3:
+ if(cur_rem != NOT_A_CARD){
+ if(deck[cur_rem].num == 0){
+ c = rem;
+ if(c == cur_rem){
+ c = NOT_A_CARD;
+ rem = deck[cur_rem].next;
+ } else {
+ while(deck[c].next != cur_rem){
+ c = deck[c].next;
+ }
+ deck[c].next = deck[cur_rem].next;
+ }
+ deck[cur_rem].next = NOT_A_CARD;
+ deck[cur_rem].known = 1;
+ stacks[deck[cur_rem].color] = cur_rem;
+ cur_rem = c;
+ } else {
+
+ i = stacks[deck[cur_rem].color];
+ if(i==NOT_A_CARD) break;
+ while(deck[i].next != NOT_A_CARD){
+ i = deck[i].next;
+ }
+ if(deck[i].num + 1 == deck[cur_rem].num){
+ c = rem;
+ if(c == cur_rem){
+ c = NOT_A_CARD;
+ rem = deck[cur_rem].next;
+ } else {
+ while(deck[c].next != cur_rem){
+ c = deck[c].next;
+ }
+ deck[c].next = deck[cur_rem].next;
+ }
+ deck[i].next = cur_rem;
+ deck[cur_rem].next = NOT_A_CARD;
+ deck[cur_rem].known = 1;
+ cur_rem = c;
+ }
+ }
+ }
+ break;
+
+ //unselect selected card or ...
+ //draw new cards from the remains of the deck
+ case BUTTON_F2:
+ if(sel_card != NOT_A_CARD){
+ //unselect selected card
+ sel_card = NOT_A_CARD;
+ } else if(rem != NOT_A_CARD) {
+ //draw new cards form the remains of the deck
+ if(cur_rem == NOT_A_CARD){
+ cur_rem = rem;
+ i = CARDS_PER_DRAW - 1;
+ } else {
+ i = CARDS_PER_DRAW;
+ }
+ while(i>0 && deck[cur_rem].next != NOT_A_CARD){
+ cur_rem = deck[cur_rem].next;
+ i--;
+ }
+ //test if any cards are really left on the remains' stack
+ if(i == CARDS_PER_DRAW){
+ cur_rem = NOT_A_CARD;
+ }
+ }
+ break;
+
+ //Show the menu
+ case BUTTON_OFF:
+ switch(solitaire_menu(MENU_DURINGGAME)){
+ case MENU_QUIT:
+ return;
+
+ case MENU_RESTART:
+ solitaire_init();
+ break;
+ }
+ }
+ }
+}
+
+enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
+{
+ //plugin init
+ TEST_PLUGIN_API(api);
+ (void)parameter;
+ rb = api;
+ //end of plugin init
+
+ //Welcome to Solitaire !
+ rb->splash(HZ*2, true, "Welcome to Solitaire !");
+
+ //play the game :)
+ solitaire();
+
+ //Exit the plugin
+ return PLUGIN_OK;
+}
+
+#endif
diff --git a/docs/CREDITS b/docs/CREDITS
index 2237772..0a49e6a 100644
--- a/docs/CREDITS
+++ b/docs/CREDITS
@@ -90,3 +90,4 @@
Steve Cundari
Mat Holton
Jan Gajdos
+Antoine Cellerier