/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2005 Dave Chapman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ /*** Sudoku by Dave Chapman User instructions ----------------- Use the arrow keys to move cursor, and press SELECT/ON/F2 to increment the number under the cursor. At any time during the game, press On to bring up the game menu with further options: Save Reload Clear Solve Sudoku is implemented as a "viewer" for a ".ss" file, as generated by Simple Sudoku and other applications - http://angusj.com/sudoku/ In-progress game positions are saved in the original .ss file, with A-I used to indicate numbers entered by the user. Example ".ss" file, and one with a saved state: ...|...|... ...|...|... 2..|8.4|9.1 2.C|8.4|9.1 ...|1.6|32. E..|1.6|32. ----------- ----------- ...|..5|.4. ...|..5|.4. 8..|423|..6 8..|423|..6 .3.|9..|... .3D|9..|A.. ----------- ----------- .63|7.9|... .63|7.9|... 4.9|5.2|..8 4.9|5.2|.C8 ...|...|... ...|...|... */ #include "plugin.h" #include "lib/configfile.h" #include #include "sudoku.h" #include "generator.h" /* The bitmaps */ #include "pluginbitmaps/sudoku_normal.h" #include "pluginbitmaps/sudoku_inverse.h" #include "pluginbitmaps/sudoku_start.h" #define BITMAP_HEIGHT (BMPHEIGHT_sudoku_normal/10) #define BITMAP_STRIDE STRIDE(SCREEN_MAIN, BMPWIDTH_sudoku_normal, BMPHEIGHT_sudoku_normal) #if (LCD_DEPTH>2) #define BITMAP_WIDTH (BMPWIDTH_sudoku_normal/2) #else #define BITMAP_WIDTH BMPWIDTH_sudoku_normal #endif /* Default game - used to initialise sudoku.ss if it doesn't exist. */ static const char default_game[9][9] = { { '0','1','0', '3','0','7', '0','0','4' }, { '0','0','0', '0','6','0', '1','0','2' }, { '0','0','0', '0','8','0', '5','6','0' }, { '0','6','0', '0','0','0', '0','2','9' }, { '0','0','0', '5','0','3', '0','0','0' }, { '7','9','0', '0','0','0', '0','3','0' }, { '0','8','5', '0','3','0', '0','0','0' }, { '1','0','2', '0','7','0', '0','0','0' }, { '0','0','0', '4','0','8', '0','5','0' }, }; #if LCD_HEIGHT <= LCD_WIDTH /* Horizontal layout, scratchpad at the left */ #if ((LCD_HEIGHT==64 && (LCD_WIDTH==112 || LCD_WIDTH==128)) || (LCD_HEIGHT == 96 && LCD_WIDTH == 96)) /* Archos Recorders and Ondios - 112x64, 9 cells @ 8x6 with 10 border lines */ #define SMALL_BOARD #define MARK_OFFS 1 /* Pixels between border and mark */ #define MARK_SPACE 1 /* Pixels between two marks */ #define MARK_SIZE 1 /* Mark width and height */ #elif ((LCD_HEIGHT==80) && (LCD_WIDTH==132)) /* C200, 9 cells @ 8x8 with 8 border lines */ #define SMALL_BOARD #define MARK_OFFS 1 /* Pixels between border and mark */ #define MARK_SPACE 1 /* Pixels between two marks */ #define MARK_SIZE 1 /* Mark width and height */ #elif ((LCD_HEIGHT==96) && (LCD_WIDTH==128)) /* iAudio M3, Samsung YH820, 9 cells @ 9x9 with 14 border lines */ #define MARK_OFFS 1 /* Pixels between border and mark */ #define MARK_SPACE 2 /* Pixels between two marks */ #define MARK_SIZE 1 /* Mark width and height */ #elif (LCD_HEIGHT==110) && (LCD_WIDTH==138) \ || (LCD_HEIGHT==128) && (LCD_WIDTH==128) /* iPod Mini - 138x110, 9 cells @ 10x10 with 14 border lines */ /* iriver H10 5-6GB - 128x128, 9 cells @ 10x10 with 14 border lines */ #define MARK_OFFS 1 /* Pixels between border and mark */ #define MARK_SPACE 1 /* Pixels between two marks */ #define MARK_SIZE 2 /* Mark width and height */ #elif ((LCD_HEIGHT==128) && (LCD_WIDTH==160)) \ || ((LCD_HEIGHT==132) && (LCD_WIDTH==176)) /* iAudio X5, Iriver H1x0, iPod G3, G4 - 160x128; */ /* iPod Nano - 176x132, 9 cells @ 12x12 with 14 border lines */ #define MARK_OFFS 1 /* Pixels between border and mark */ #define MARK_SPACE 2 /* Pixels between two marks */ #define MARK_SIZE 2 /* Mark width and height */ #elif ((LCD_HEIGHT==176) && (LCD_WIDTH==220)) /* Iriver h300, iPod Color/Photo - 220x176, 9 cells @ 16x16 with 14 border lines */ #define MARK_OFFS 1 /* Pixels between border and mark */ #define MARK_SPACE 1 /* Pixels between two marks */ #define MARK_SIZE 4 /* Mark width and height */ #elif (LCD_HEIGHT==240) && (LCD_WIDTH==320) /* iPod Video - 320x240, 9 cells @ 24x24 with 14 border lines */ #define MARK_OFFS 1 /* Pixels between border and mark */ #define MARK_SPACE 2 /* Pixels between two marks */ #define MARK_SIZE 6 /* Mark width and height */ #elif (LCD_HEIGHT==480) && (LCD_WIDTH==640) /* M:Robe 500 - 640x480, 9 cells @ 48x48 with 14 border lines */ #define MARK_OFFS 1 /* Pixels between border and mark */ #define MARK_SPACE 2 /* Pixels between two marks */ #define MARK_SIZE 6 /* Mark width and height */ #else #error SUDOKU: Unsupported LCD size #endif #else /* Vertical layout, scratchpad at the bottom */ #define VERTICAL_LAYOUT #if ((LCD_HEIGHT==220) && (LCD_WIDTH==176)) /* e200, 9 cells @ 16x16 with 14 border lines */ #define MARK_OFFS 1 /* Pixels between border and mark */ #define MARK_SPACE 1 /* Pixels between two marks */ #define MARK_SIZE 4 /* Mark width and height */ #elif (LCD_HEIGHT>=320) && (LCD_WIDTH>=240) /* Gigabeat - 240x320, 9 cells @ 24x24 with 14 border lines */ #define MARK_OFFS 1 /* Pixels between border and mark */ #define MARK_SPACE 2 /* Pixels between two marks */ #define MARK_SIZE 6 /* Mark width and height */ #elif ((LCD_HEIGHT==160) && (LCD_WIDTH==128)) /* Philips GoGear SA9200 - 128x160, 9 cells @ 10x10 with 14 border tiles */ #define MARK_OFFS 1 /* Pixels between border and mark */ #define MARK_SPACE 1 /* Pixels between two marks */ #define MARK_SIZE 2 /* Mark width and height */ #else #error SUDOKU: Unsupported LCD size #endif #endif /* Layout */ #define CELL_WIDTH BITMAP_WIDTH #define CELL_HEIGHT BITMAP_HEIGHT #ifdef SUDOKU_BUTTON_CHANGEDIR int invertdir=0; #else #define invertdir 0 #endif #define CFGFILE_VERSION 0 /* Current config file version */ #define CFGFILE_MINVERSION 0 /* Minimum config file version to accept */ #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE) /* settings */ struct sudoku_config { #ifdef HAVE_LCD_COLOR int number_display; #endif #ifdef SUDOKU_BUTTON_POSSIBLE int show_markings; #endif }; struct sudoku_config sudcfg_disk = { #ifdef HAVE_LCD_COLOR 0, #endif #ifdef SUDOKU_BUTTON_POSSIBLE 1, #endif }; struct sudoku_config sudcfg; static const char cfg_filename[] = "sudoku.cfg"; #ifdef HAVE_LCD_COLOR static char *number_str[2] = { "black", "coloured" }; #endif #ifdef SUDOKU_BUTTON_POSSIBLE static char *mark_str[2] = { "hide", "show" }; #endif struct configdata disk_config[] = { #ifdef HAVE_LCD_COLOR { TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.number_display }, "numbers", number_str }, #endif #ifdef SUDOKU_BUTTON_POSSIBLE { TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.show_markings }, "markings", mark_str }, #endif }; #endif #ifdef HAVE_LCD_COLOR #define NUMBER_TYPE (sudcfg.number_display*CELL_WIDTH) #else #define NUMBER_TYPE 0 #endif /* Size dependent build-time calculations */ #ifdef SMALL_BOARD #define BOARD_WIDTH (CELL_WIDTH*9+10) #define BOARD_HEIGHT (CELL_HEIGHT*9+10) static unsigned int cellxpos[9]={ 1, (CELL_WIDTH+2), (2*CELL_WIDTH+3), (3*CELL_WIDTH+4), (4*CELL_WIDTH+5), (5*CELL_WIDTH+6), (6*CELL_WIDTH+7), (7*CELL_WIDTH+8), (8*CELL_WIDTH+9) }; static unsigned int cellypos[9]={ 1, (CELL_HEIGHT+2), (2*CELL_HEIGHT+3), (3*CELL_HEIGHT+4), (4*CELL_HEIGHT+5), (5*CELL_HEIGHT+6), (6*CELL_HEIGHT+7), (7*CELL_HEIGHT+8), (8*CELL_HEIGHT+9) }; #else /* !SMALL_BOARD */ #define BOARD_WIDTH (CELL_WIDTH*9+10+4) #define BOARD_HEIGHT (CELL_HEIGHT*9+10+4) static unsigned int cellxpos[9]={ 2, (CELL_WIDTH +3), (2*CELL_WIDTH +4), (3*CELL_WIDTH +6), (4*CELL_WIDTH +7), (5*CELL_WIDTH +8), (6*CELL_WIDTH+10), (7*CELL_WIDTH+11), (8*CELL_WIDTH+12) }; static unsigned int cellypos[9]={ 2, (CELL_HEIGHT +3), (2*CELL_HEIGHT +4), (3*CELL_HEIGHT +6), (4*CELL_HEIGHT +7), (5*CELL_HEIGHT +8), (6*CELL_HEIGHT+10), (7*CELL_HEIGHT+11), (8*CELL_HEIGHT+12) }; #endif #ifdef VERTICAL_LAYOUT #define XOFS ((LCD_WIDTH-BOARD_WIDTH)/2) #define YOFS ((LCD_HEIGHT-(BOARD_HEIGHT+CELL_HEIGHT*2+2))/2) #define YOFSSCRATCHPAD (YOFS+BOARD_HEIGHT+CELL_WIDTH) #else #define XOFSSCRATCHPAD ((LCD_WIDTH-(BOARD_WIDTH+CELL_WIDTH*2+2))/2) #define XOFS (XOFSSCRATCHPAD+CELL_WIDTH*2+2) #define YOFS ((LCD_HEIGHT-BOARD_HEIGHT)/2) #endif #define BLOCK 3 #define SIZE (BLOCK*BLOCK) static void sudoku_solve(struct sudoku_state_t* state) { bool ret = sudoku_solve_board(state); if (!ret) { rb->splash(HZ*2, "Solve failed"); } return; } /* Copies the current to the saved board */ static void save_state(struct sudoku_state_t *state) { rb->memcpy(state->savedboard, state->currentboard, sizeof(state->savedboard)); #ifdef SUDOKU_BUTTON_POSSIBLE rb->memcpy(state->savedpossible, state->possiblevals, sizeof(state->savedpossible)); #endif } /* Copies the saved to the current board */ static void restore_state(struct sudoku_state_t *state) { rb->memcpy(state->currentboard, state->savedboard, sizeof(state->savedboard)); #ifdef SUDOKU_BUTTON_POSSIBLE rb->memcpy(state->possiblevals, state->savedpossible, sizeof(state->possiblevals)); #endif } static void default_state(struct sudoku_state_t* state) { int r,c; rb->strlcpy(state->filename,GAME_FILE,MAX_PATH); for (r=0;r<9;r++) { for (c=0;c<9;c++) { state->startboard[r][c]=default_game[r][c]; state->currentboard[r][c]=default_game[r][c]; #ifdef SUDOKU_BUTTON_POSSIBLE state->possiblevals[r][c]=0; #endif } } /* initialize the saved board so reload function works */ save_state(state); state->x=0; state->y=0; state->editmode=0; } static void clear_state(struct sudoku_state_t* state) { int r,c; rb->strlcpy(state->filename,GAME_FILE,MAX_PATH); for (r=0;r<9;r++) { for (c=0;c<9;c++) { state->startboard[r][c]='0'; state->currentboard[r][c]='0'; #ifdef SUDOKU_BUTTON_POSSIBLE state->possiblevals[r][c]=0; #endif } } state->x=0; state->y=0; state->editmode=0; } /* Check the status of the board, assuming a change at the cursor location */ static bool check_status(struct sudoku_state_t* state) { int check[9]; int r,c; int r1,c1; int cell; /* First, check the column */ for (cell=0;cell<9;cell++) { check[cell]=0; } for (r=0;r<9;r++) { cell=state->currentboard[r][state->x]; if (cell!='0') { if (check[cell-'1']==1) { return true; } check[cell-'1']=1; } } /* Second, check the row */ for (cell=0;cell<9;cell++) { check[cell]=0; } for (c=0;c<9;c++) { cell=state->currentboard[state->y][c]; if (cell!='0') { if (check[cell-'1']==1) { return true; } check[cell-'1']=1; } } /* Finally, check the 3x3 sub-grid */ for (cell=0;cell<9;cell++) { check[cell]=0; } r1=(state->y/3)*3; c1=(state->x/3)*3; for (r=r1;rcurrentboard[r][c]; if (cell!='0') { if (check[cell-'1']==1) { return true; } check[cell-'1']=1; } } } /* We passed all the checks :) */ return false; } /* Load game - only ".ss" is officially supported, but any sensible text representation (one line per row) may load. */ static bool load_sudoku(struct sudoku_state_t* state, char* filename) { int fd; size_t n; int r = 0, c = 0, d = 0; unsigned int i; int valid=0; char buf[500]; /* A buffer to read a sudoku board from */ fd=rb->open(filename, O_RDONLY); if (fd < 0) { LOGF("Invalid sudoku file: %s\n",filename); return(false); } rb->strlcpy(state->filename,filename,MAX_PATH); n=rb->read(fd,buf,500); if (n <= 0) { return(false); } rb->close(fd); r=0; c=0; i=0; d=0; while ((i < n) && (r < 9)) { switch (buf[i]){ case ' ': case '\t': if (c > 0) valid=1; break; case '|': case '*': case '-': case '\r': break; case '\n': if (valid) { r++; valid=0; } c = 0; d = 0; break; case '_': case '.': valid=1; if (c >= SIZE || r >= SIZE){ LOGF("ERROR: sudoku problem is the wrong size (%d,%d)\n", c, r); return(false); } c++; break; default: if (((buf[i]>='A') && (buf[i]<='I')) || ((buf[i]>='0') && (buf[i]<='9'))) { valid=1; if (r >= SIZE || c >= SIZE){ LOGF("ERROR: sudoku problem is the wrong size " "(%d,%d)\n", c, r); return(false); } if ((buf[i]>='0') && (buf[i]<='9')) { state->startboard[r][c]=buf[i]; state->currentboard[r][c]=buf[i]; } else { state->currentboard[r][c]='1'+(buf[i]-'A'); } c++; } if((buf[i]>='a' && buf[i] <= 'z') && i < (n-1) && (buf[i+1] >= 'a' && buf[i+1] <= 'z')) { state->possiblevals[r][d] = (((buf[i]-'a') * 26 + buf[i+1]-'a')<<1); i++; d++; } /* Ignore any other characters */ break; } i++; } /* Check that the board is valid - we need to check every row/column and block individually */ for (state->y = 0; state->y < 9; state->y++) { state->x = (state->y%3)*3 + (state->y/3); if (check_status(state)) return false; } state->x = 0; state->y = 0; /* Save a copy of the saved state - so we can reload without using the disk */ save_state(state); return(true); } static bool save_sudoku(struct sudoku_state_t* state) { int fd; int r,c; int i; #ifdef SUDOKU_BUTTON_POSSIBLE int x; char line[41]="...|...|... ; \r\n"; #else char line[13]="...|...|...\r\n"; #endif char sep[13]="-----------\r\n"; rb->splash(0, "Saving..."); if (state->filename[0]==0) { return false; } fd=rb->open(state->filename, O_WRONLY|O_CREAT, 0666); if (fd >= 0) { for (r=0;r<9;r++) { i=0; for (c=0;c<9;c++) { if (state->startboard[r][c]!='0') { line[i]=state->startboard[r][c]; } else if (state->currentboard[r][c]!='0') { line[i]='A'+(state->currentboard[r][c]-'1'); } else { line[i]='.'; } i++; if ((c==2) || (c==5)) { i++; } } #ifdef SUDOKU_BUTTON_POSSIBLE i+=2; for(c=0; c<9; c++) { x = ((state->possiblevals[r][c]>>1)/26); line[i++] = x + 'a'; x = ((state->possiblevals[r][c]>>1)%26); line[i++] = x + 'a'; } #endif rb->write(fd,line,sizeof(line)); if ((r==2) || (r==5)) { rb->write(fd,sep,sizeof(sep)); } } /* Add a blank line at end */ rb->write(fd,"\r\n",2); rb->close(fd); rb->reload_directory(); /* Save a copy of the saved state - so we can reload without using the disk */ save_state(state); return true; } else { return false; } } static void clear_board(struct sudoku_state_t* state) { int r,c; for (r=0;r<9;r++) { for (c=0;c<9;c++) { state->currentboard[r][c]=state->startboard[r][c]; } } state->x=0; state->y=0; } static void update_cell(struct sudoku_state_t* state, int r, int c) { /* We have four types of cell: 1) User-entered number 2) Starting number 3) Cursor in cell */ if ((r==state->y) && (c==state->x)) { rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE, BITMAP_HEIGHT*(state->currentboard[r][c]-'0'), BITMAP_STRIDE, XOFS+cellxpos[c],YOFS+cellypos[r],CELL_WIDTH, CELL_HEIGHT); } else { if (state->startboard[r][c]!='0') { rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE, BITMAP_HEIGHT*(state->startboard[r][c]-'0'), BITMAP_STRIDE, XOFS+cellxpos[c],YOFS+cellypos[r], CELL_WIDTH,CELL_HEIGHT); } else { rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE, BITMAP_HEIGHT*(state->currentboard[r][c]-'0'), BITMAP_STRIDE, XOFS+cellxpos[c],YOFS+cellypos[r], CELL_WIDTH,CELL_HEIGHT); } } rb->lcd_update_rect(cellxpos[c],cellypos[r],CELL_WIDTH,CELL_HEIGHT); } static void display_board(struct sudoku_state_t* state) { int r,c; #ifdef SUDOKU_BUTTON_POSSIBLE int i; #endif /* Clear the display buffer */ rb->lcd_clear_display(); /* Draw the gridlines - differently for different targets */ #ifdef SMALL_BOARD /* Small targets - draw dotted/single lines */ for (r=0;r<9;r++) { if ((r % 3)==0) { /* Solid Line */ rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1); rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1); } else { /* Dotted line */ for (c=XOFS;clcd_drawpixel(c,YOFS+cellypos[r]-1); } for (c=YOFS;clcd_drawpixel(XOFS+cellxpos[r]-1,c); } } } rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT); rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1); #else /* Large targets - draw single/double lines */ for (r=0;r<9;r++) { rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1); rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1); if ((r % 3)==0) { rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-2); rb->lcd_vline(XOFS+cellxpos[r]-2,YOFS,YOFS+BOARD_HEIGHT-1); } } rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT); rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT+1); rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1); rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1); #endif #ifdef SUDOKU_BUTTON_POSSIBLE #ifdef VERTICAL_LAYOUT rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD); rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD+CELL_HEIGHT+1); for (r=0;r<9;r++) { #ifdef SMALL_BOARD /* Small targets - draw dotted/single lines */ if ((r % 3)==0) { /* Solid Line */ rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD, YOFSSCRATCHPAD+CELL_HEIGHT+1); } else { /* Dotted line */ for (c=YOFSSCRATCHPAD;clcd_drawpixel(XOFS+cellxpos[r]-1,c); } } #else /* Large targets - draw single/double lines */ rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD, YOFSSCRATCHPAD+CELL_HEIGHT+1); if ((r % 3)==0) rb->lcd_vline(XOFS+cellxpos[r]-2,YOFSSCRATCHPAD, YOFSSCRATCHPAD+CELL_HEIGHT+1); #endif if ((r>0) && state->possiblevals[state->y][state->x]&BIT_N(r)) rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r, BITMAP_STRIDE,XOFS+cellxpos[r-1], YOFSSCRATCHPAD+1,CELL_WIDTH,CELL_HEIGHT); } rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFSSCRATCHPAD, YOFSSCRATCHPAD+CELL_HEIGHT+1); #ifndef SMALL_BOARD rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFSSCRATCHPAD, YOFSSCRATCHPAD+CELL_HEIGHT+1); #endif if (state->possiblevals[state->y][state->x]&BIT_N(r)) rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r, BITMAP_STRIDE,XOFS+cellxpos[8],YOFSSCRATCHPAD+1, CELL_WIDTH,CELL_HEIGHT); #else /* Horizontal layout */ rb->lcd_vline(XOFSSCRATCHPAD,YOFS,YOFS+BOARD_HEIGHT-1); rb->lcd_vline(XOFSSCRATCHPAD+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1); for (r=0;r<9;r++) { #ifdef SMALL_BOARD /* Small targets - draw dotted/single lines */ if ((r % 3)==0) { /* Solid Line */ rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1, YOFS+cellypos[r]-1); } else { /* Dotted line */ for (c=XOFSSCRATCHPAD;clcd_drawpixel(c,YOFS+cellypos[r]-1); } } #else /* Large targets - draw single/double lines */ rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1, YOFS+cellypos[r]-1); if ((r % 3)==0) rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1, YOFS+cellypos[r]-2); #endif if ((r>0) && state->possiblevals[state->y][state->x]&BIT_N(r)) rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r, BITMAP_STRIDE,XOFSSCRATCHPAD+1, YOFS+cellypos[r-1],CELL_WIDTH,CELL_HEIGHT); } rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1, YOFS+cellypos[8]+CELL_HEIGHT); #ifndef SMALL_BOARD rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1, YOFS+cellypos[8]+CELL_HEIGHT+1); #endif if (state->possiblevals[state->y][state->x]&BIT_N(r)) rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r, BITMAP_STRIDE,XOFSSCRATCHPAD+1,YOFS+cellypos[8], CELL_WIDTH,CELL_HEIGHT); #endif /* Layout */ #endif /* SUDOKU_BUTTON_POSSIBLE */ /* Draw the numbers */ for (r=0;r<9;r++) { for (c=0;c<9;c++) { /* We have four types of cell: 1) User-entered number 2) Starting number 3) Cursor in cell */ if ((r==state->y) && (c==state->x)) { rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE, BITMAP_HEIGHT*(state->currentboard[r][c]- '0'), BITMAP_STRIDE, XOFS+cellxpos[c],YOFS+cellypos[r], CELL_WIDTH,CELL_HEIGHT); } else { if (state->startboard[r][c]!='0') { rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE, BITMAP_HEIGHT*(state->startboard[r][c]- '0'), BITMAP_STRIDE, XOFS+cellxpos[c],YOFS+cellypos[r], CELL_WIDTH,CELL_HEIGHT); } else { rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE, BITMAP_HEIGHT* (state->currentboard[r][c]-'0'), BITMAP_STRIDE, XOFS+cellxpos[c],YOFS+cellypos[r], CELL_WIDTH,CELL_HEIGHT); } #ifdef SUDOKU_BUTTON_POSSIBLE /* Draw the possible number markings on the board */ if(sudcfg.show_markings && state->startboard[r][c]=='0' && state->currentboard[r][c]=='0') { for(i=0;i<9;i++) { if(state->possiblevals[r][c]&(2< 1 /* draw markings in dark grey */ rb->lcd_set_foreground(LCD_DARKGRAY); #endif rb->lcd_fillrect(XOFS+cellxpos[c]+MARK_OFFS +(i%3)*(MARK_SIZE+MARK_SPACE), YOFS+cellypos[r]+MARK_OFFS +(i/3)*(MARK_SIZE+MARK_SPACE), MARK_SIZE, MARK_SIZE); #if LCD_DEPTH > 1 rb->lcd_set_foreground(LCD_BLACK); #endif } } } #endif /* SUDOKU_BUTTON_POSSIBLE */ } } } /* update the screen */ rb->lcd_update(); } static bool sudoku_generate(struct sudoku_state_t* state) { char* difficulty; char str[80]; bool res; struct sudoku_state_t new_state; clear_state(&new_state); display_board(&new_state); rb->splash(0, "Generating..."); #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(true); #endif res = sudoku_generate_board(&new_state,&difficulty); #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(false); #endif if (res) { rb->memcpy(state,&new_state,sizeof(new_state)); rb->snprintf(str,sizeof(str),"Difficulty: %s",difficulty); display_board(state); rb->splash(HZ*3, str); rb->strlcpy(state->filename,GAME_FILE,MAX_PATH); } else { display_board(&new_state); rb->splash(HZ*2, "Aborted"); } /* initialize the saved board so reload function works */ save_state(state); return res; } #ifdef HAVE_LCD_COLOR static bool numdisplay_setting(void) { static const struct opt_items names[] = { {"Black", -1}, {"Coloured", -1}, }; return rb->set_option("Number Display", &sudcfg.number_display, INT, names, sizeof(names) / sizeof(names[0]), NULL); } #endif #ifdef SUDOKU_BUTTON_POSSIBLE static bool showmarkings_setting(void) { static const struct opt_items names[] = { {"Hide", -1}, {"Show", -1}, }; return rb->set_option("Show Markings", &sudcfg.show_markings, INT, names, sizeof(names) / sizeof(names[0]), NULL); } #endif enum { SM_AUDIO_PLAYBACK = 0, #ifdef HAVE_LCD_COLOR SM_NUMBER_DISPLAY, #endif #ifdef SUDOKU_BUTTON_POSSIBLE SM_SHOW_MARKINGS, #endif SM_SAVE, SM_RELOAD, SM_CLEAR, SM_SOLVE, SM_GENERATE, SM_NEW, SM_QUIT, }; static int sudoku_menu(struct sudoku_state_t* state) { int result; MENUITEM_STRINGLIST(menu, "Sudoku Menu", NULL, "Audio Playback", #ifdef HAVE_LCD_COLOR "Number Display", #endif #ifdef SUDOKU_BUTTON_POSSIBLE "Show Markings", #endif "Save", "Reload", "Clear", "Solve", "Generate", "New", "Quit"); result = rb->do_menu(&menu, NULL, NULL, false); switch (result) { case SM_AUDIO_PLAYBACK: playback_control(NULL); break; #ifdef HAVE_LCD_COLOR case SM_NUMBER_DISPLAY: numdisplay_setting(); break; #endif #ifdef SUDOKU_BUTTON_POSSIBLE case SM_SHOW_MARKINGS: showmarkings_setting(); break; #endif case SM_SAVE: save_sudoku(state); break; case SM_RELOAD: restore_state(state); break; case SM_CLEAR: clear_board(state); break; case SM_SOLVE: sudoku_solve(state); break; case SM_GENERATE: sudoku_generate(state); break; case SM_NEW: clear_state(state); state->editmode=1; break; case SM_QUIT: save_sudoku(state); break; default: break; } return result; } /* Menu used when user is in edit mode - i.e. creating a new game manually */ static int sudoku_edit_menu(struct sudoku_state_t* state) { int result; MENUITEM_STRINGLIST(menu, "Edit Menu", NULL, "Save as", "Quit"); result = rb->do_menu(&menu, NULL, NULL, false); switch (result) { case 0: /* Save new game */ rb->kbd_input(state->filename,MAX_PATH); if (save_sudoku(state)) { state->editmode=0; } else { rb->splash(HZ*2, "Save failed"); } break; case 1: /* Quit */ break; default: break; } return result; } static void move_cursor(struct sudoku_state_t* state, int newx, int newy) { int oldx, oldy; /* Check that the character at the cursor position is legal */ if (check_status(state)) { rb->splash(HZ*2, "Illegal move!"); /* Ignore any button presses during the splash */ rb->button_clear_queue(); return; } /* Move Cursor */ oldx=state->x; oldy=state->y; state->x=newx; state->y=newy; /* Redraw current and old cells */ update_cell(state,oldx,oldy); update_cell(state,newx,newy); } /* plugin entry point */ enum plugin_status plugin_start(const void* parameter) { bool exit; int button; #if defined(SUDOKU_BUTTON_TOGGLE_PRE) || defined(SUDOKU_BUTTON_MENU_PRE) int lastbutton = BUTTON_NONE; #endif int res; int rc = PLUGIN_OK; long ticks; struct sudoku_state_t state; #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE) configfile_load(cfg_filename, disk_config, sizeof(disk_config) / sizeof(disk_config[0]), CFGFILE_MINVERSION); rb->memcpy(&sudcfg, &sudcfg_disk, sizeof(sudcfg)); /* copy to running config */ #endif #if LCD_DEPTH > 1 rb->lcd_set_backdrop(NULL); rb->lcd_set_foreground(LCD_BLACK); rb->lcd_set_background(LCD_WHITE); #endif clear_state(&state); if (parameter==NULL) { /* We have been started as a plugin - try default sudoku.ss */ if (!load_sudoku(&state,GAME_FILE)) { /* No previous game saved, use the default */ default_state(&state); } } else { if (!load_sudoku(&state,(char*)parameter)) { rb->splash(HZ*2, "Load error"); return(PLUGIN_ERROR); } } display_board(&state); /* The main game loop */ exit=false; ticks=0; while(!exit) { button = rb->button_get(true); switch(button){ #ifdef SUDOKU_BUTTON_QUIT /* Exit game */ case SUDOKU_BUTTON_QUIT: if (check_status(&state)) { rb->splash(HZ*2, "Illegal move!"); /* Ignore any button presses during the splash */ rb->button_clear_queue(); } else { save_sudoku(&state); exit=true; } break; #endif /* Increment digit */ #ifdef SUDOKU_BUTTON_ALTTOGGLE case SUDOKU_BUTTON_ALTTOGGLE | BUTTON_REPEAT: #endif case SUDOKU_BUTTON_TOGGLE | BUTTON_REPEAT: /* Slow down the repeat speed to 1/3 second */ if ((*rb->current_tick-ticks) < (HZ/3)) { break; } #ifdef SUDOKU_BUTTON_ALTTOGGLE case SUDOKU_BUTTON_ALTTOGGLE: #endif case SUDOKU_BUTTON_TOGGLE: #ifdef SUDOKU_BUTTON_TOGGLE_PRE if ((button == SUDOKU_BUTTON_TOGGLE) && (lastbutton != SUDOKU_BUTTON_TOGGLE_PRE)) break; #endif /* Increment digit */ ticks=*rb->current_tick; if (state.editmode) { if (state.startboard[state.y][state.x]=='9') { state.startboard[state.y][state.x]='0'; state.currentboard[state.y][state.x]='0'; } else { state.startboard[state.y][state.x]++; state.currentboard[state.y][state.x]++; } } else { if (state.startboard[state.y][state.x]=='0') { if (state.currentboard[state.y][state.x]=='9') { state.currentboard[state.y][state.x]='0'; } else { state.currentboard[state.y][state.x]++; } } } update_cell(&state,state.y,state.x); break; #ifdef SUDOKU_BUTTON_TOGGLEBACK case SUDOKU_BUTTON_TOGGLEBACK | BUTTON_REPEAT: /* Slow down the repeat speed to 1/3 second */ if ((*rb->current_tick-ticks) < (HZ/3)) { break; } case SUDOKU_BUTTON_TOGGLEBACK: /* Decrement digit */ ticks=*rb->current_tick; if (state.editmode) { if (state.startboard[state.y][state.x]=='0') { state.startboard[state.y][state.x]='9'; state.currentboard[state.y][state.x]='9'; } else { state.startboard[state.y][state.x]--; state.currentboard[state.y][state.x]--; } } else { if (state.startboard[state.y][state.x]=='0') { if (state.currentboard[state.y][state.x]=='0') { state.currentboard[state.y][state.x]='9'; } else { state.currentboard[state.y][state.x]--; } } } update_cell(&state,state.y,state.x); break; #endif /* move cursor left */ case SUDOKU_BUTTON_LEFT: case (SUDOKU_BUTTON_LEFT | BUTTON_REPEAT): if ( (state.x==0&&invertdir==0) || (state.y==0&&invertdir==1) ) { #ifndef SUDOKU_BUTTON_UP if ( (state.y==0&&invertdir==0) || (state.x==0&&invertdir==1)) { move_cursor(&state,8,8); } else { if (invertdir==0) { move_cursor(&state,8,state.y-1); } else { move_cursor(&state,state.x-1,8); } } #else move_cursor(&state,8,state.y); #endif } else { if (invertdir==0) { move_cursor(&state,state.x-1,state.y); } else { move_cursor(&state,state.x,state.y-1); } } break; /* move cursor right */ case SUDOKU_BUTTON_RIGHT: case (SUDOKU_BUTTON_RIGHT | BUTTON_REPEAT): if ( (state.x==8&&invertdir==0) || (state.y==8&&invertdir==1) ) { #ifndef SUDOKU_BUTTON_DOWN if ( (state.y==8&&invertdir==0) || (state.x==8&&invertdir==1) ) { move_cursor(&state,0,0); } else { if (invertdir==0) { move_cursor(&state,0,state.y+1); } else { move_cursor(&state,state.x+1,0); } } #else move_cursor(&state,0,state.y); #endif } else { if (invertdir==0) { move_cursor(&state,state.x+1,state.y); } else { move_cursor(&state,state.x,state.y+1); } } break; #ifdef SUDOKU_BUTTON_UP /* move cursor up */ case SUDOKU_BUTTON_UP: case (SUDOKU_BUTTON_UP | BUTTON_REPEAT): if (state.y==0) { move_cursor(&state,state.x,8); } else { move_cursor(&state,state.x,state.y-1); } break; #endif #ifdef SUDOKU_BUTTON_DOWN /* move cursor down */ case SUDOKU_BUTTON_DOWN: case (SUDOKU_BUTTON_DOWN | BUTTON_REPEAT): if (state.y==8) { move_cursor(&state,state.x,0); } else { move_cursor(&state,state.x,state.y+1); } break; #endif case SUDOKU_BUTTON_MENU: #ifdef SUDOKU_BUTTON_MENU_PRE if (lastbutton != SUDOKU_BUTTON_MENU_PRE) break; #endif /* Don't let the user leave a game in a bad state */ if (check_status(&state)) { rb->splash(HZ*2, "Illegal move!"); /* Ignore any button presses during the splash */ rb->button_clear_queue(); } else { if (state.editmode) { res = sudoku_edit_menu(&state); if (res == MENU_ATTACHED_USB) { rc = PLUGIN_USB_CONNECTED; exit = true; } else if (res == 1) { /* Quit */ exit = true; } } else { res = sudoku_menu(&state); if (res == MENU_ATTACHED_USB) { rc = PLUGIN_USB_CONNECTED; exit = true; } else if (res == SM_QUIT) { exit = true; } } } break; #ifdef SUDOKU_BUTTON_POSSIBLE case SUDOKU_BUTTON_POSSIBLE: /* Toggle current number in the possiblevals structure */ if (state.currentboard[state.y][state.x]!='0') { state.possiblevals[state.y][state.x]^= BIT_N(state.currentboard[state.y][state.x] - '0'); } break; #endif #ifdef SUDOKU_BUTTON_CHANGEDIR case SUDOKU_BUTTON_CHANGEDIR: /* Change scroll wheel direction */ invertdir=!invertdir; break; #endif default: if (rb->default_event_handler(button) == SYS_USB_CONNECTED) { /* Quit if USB has been connected */ rc = PLUGIN_USB_CONNECTED; exit = true; } break; } #if defined(SUDOKU_BUTTON_TOGGLE_PRE) || defined(SUDOKU_BUTTON_MENU_PRE) if (button != BUTTON_NONE) lastbutton = button; #endif display_board(&state); } #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE) if (rb->memcmp(&sudcfg, &sudcfg_disk, sizeof(sudcfg))) /* save settings if changed */ { rb->memcpy(&sudcfg_disk, &sudcfg, sizeof(sudcfg)); configfile_save(cfg_filename, disk_config, sizeof(disk_config) / sizeof(disk_config[0]), CFGFILE_VERSION); } #endif return rc; }