/*************************************************************************** * __________ __ ___. * 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" #ifdef HAVE_LCD_BITMAP #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 BMPWIDTH_sudoku_normal #if (LCD_DEPTH>2) #define BITMAP_WIDTH (BMPWIDTH_sudoku_normal/2) #else #define BITMAP_WIDTH BMPWIDTH_sudoku_normal #endif PLUGIN_HEADER /* 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) /* 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, 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 */ #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 /****** Solver routine by Tom Shackell Downloaded from: http://www-users.cs.york.ac.uk/~shackell/sudoku/Sudoku.html Released under GPLv2 */ typedef unsigned int Bitset; #define BLOCK 3 #define SIZE (BLOCK*BLOCK) #define true 1 #define false 0 typedef struct _Sudoku { Bitset table[SIZE][SIZE]; }Sudoku; typedef struct _Stats { int numTries; int backTracks; int numEmpty; bool solutionFound; }Stats; typedef struct _Options { bool allSolutions; bool uniquenessCheck; }Options; void sudoku_init(Sudoku* sud); void sudoku_set(Sudoku* sud, int x, int y, int num, bool original); int sudoku_get(Sudoku* sud, int x, int y, bool* original); #define BIT(n) ((Bitset)BIT_N(n)) #define BIT_TEST(v,n) ((((Bitset)v) & BIT(n)) != 0) #define BIT_CLEAR(v,n) (v) &= ~BIT(n) #define MARK_BIT BIT(0) #define ORIGINAL_BIT BIT(SIZE+1) #define ALL_BITS (BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8) | BIT(9)) /* initialize a sudoku problem, should be called before using set or get */ void sudoku_init(Sudoku* sud) { int y, x; for (y = 0; y < SIZE; y++){ for (x = 0; x < SIZE; x++){ sud->table[x][y] = ALL_BITS; } } } /* set the number at a particular x and y column */ void sudoku_set(Sudoku* sud, int x, int y, int num, bool original) { int i, j; int bx, by; Bitset orig; /* clear the row and columns */ for (i = 0; i < SIZE; i++){ BIT_CLEAR(sud->table[i][y], num); BIT_CLEAR(sud->table[x][i], num); } /* clear the block */ bx = x - (x % BLOCK); by = y - (y % BLOCK); for (i = 0; i < BLOCK; i++){ for (j = 0; j < BLOCK; j++){ BIT_CLEAR(sud->table[bx+j][by+i], num); } } /* mark the table */ orig = original ? ORIGINAL_BIT : 0; sud->table[x][y] = BIT(num) | MARK_BIT | orig; } /* get the number at a particular x and y column, if this is not unique return 0 */ int sudoku_get(Sudoku* sud, int x, int y, bool* original) { Bitset val = sud->table[x][y]; int result = 0; int i; if (original) { *original = val & ORIGINAL_BIT; } for (i = 1; i <= SIZE; i++){ if (BIT_TEST(val, i)){ if (result != 0){ return 0; } result = i; } } return result; } /* returns true if this is a valid problem, this is necessary because the input problem might be degenerate which breaks the solver algorithm. */ static bool is_valid(const Sudoku* sud) { int x, y; for (y = 0; y < SIZE; y++){ for (x = 0; x < SIZE; x++){ if ((sud->table[x][y] & ALL_BITS) == 0){ return false; } } } return true; } /* scan the table for the most constrained item, giving all it's options, sets the best x and y coordinates, the number of options and the options for that coordinate and returns true if the puzzle is finished */ static bool scan(const Sudoku* sud, int* rX, int* rY, int *num, int* options) { int x, y, i, j; int bestCount = SIZE+1; Bitset val; bool allMarked = true; for (y = 0; y < SIZE; y++){ for (x = 0; x < SIZE; x++){ Bitset val = sud->table[x][y]; int i; int count = 0; if (val & MARK_BIT) { /* already set */ continue; } allMarked = false; for (i = 1; i <= SIZE; i++){ if (BIT_TEST(val, i)){ count++; } } if (count < bestCount){ bestCount = count; *rX = x; *rY = y; if (count == 0){ /* can't possibly be beaten */ *num = 0; return false; } } } } /* now copy into options */ *num = bestCount; val = sud->table[*rX][*rY]; for (i = 1, j = 0; i <= SIZE; i++){ if (BIT_TEST(val, i)){ options[j++] = i; } } return allMarked; } static bool solve(Sudoku* sud, Stats* stats, const Options* options); /* try a particular option and return true if that gives a solution or false if it doesn't, restores board on backtracking */ static bool spawn_option(Sudoku* sud, Stats* stats, const Options* options, int x, int y, int num) { Sudoku copy; rb->memcpy(©,sud,sizeof(Sudoku)); sudoku_set(©, x, y, num, false); stats->numTries += 1; if (solve(©, stats, options)){ if (!options->allSolutions && stats->solutionFound){ rb->memcpy(sud,©,sizeof(Sudoku)); } return true; }else{ stats->backTracks++; } return false; } /* solve a sudoku problem, returns true if there is a solution and false otherwise. stats is used to track statisticss */ static bool solve(Sudoku* sud, Stats* stats, const Options* options) { while (true){ int x = 0; int y = 0; int i, num; int places[SIZE]; if (scan(sud, &x, &y, &num, places)){ /* a solution was found! */ if (options->uniquenessCheck && stats->solutionFound){ /*printf("\n\t... But the solution is not unique!\n"); */ return true; } stats->solutionFound = true; if (options->allSolutions || options->uniquenessCheck){ /*printf("\n\tSolution after %d iterations\n", stats->numTries); */ /*sudoku_print(sud); */ return false; } else{ return true; } } if (num == 0){ /* can't be satisfied */ return false; } /* try all the places (except the last one) */ for (i = 0; i < num-1; i++){ if (spawn_option(sud, stats, options, x, y, places[i])){ /* solution found! */ if (!options->allSolutions && stats->solutionFound){ return true; } } } /* take the last place ourself */ stats->numTries += 1; sudoku_set(sud, x, y, places[num-1], false); } } /******** END OF IMPORTED CODE */ /* A wrapper function between the Sudoku plugin and the above solver code */ void sudoku_solve(struct sudoku_state_t* state) { bool ret; Stats stats; Options options; Sudoku sud; bool original; int r,c; /* Initialise the parameters */ sudoku_init(&sud); rb->memset(&stats,0,sizeof(stats)); options.allSolutions=false; options.uniquenessCheck=false; /* Convert Rockbox format into format for solver */ for (r=0;r<9;r++) { for (c=0;c<9;c++) { if (state->startboard[r][c]!='0') { sudoku_set(&sud, c, r, state->startboard[r][c]-'0', true); } } } /* need to check for degenerate input problems ... */ if (is_valid(&sud)){ ret = solve(&sud, &stats, &options); } else { ret = false; } if (ret) { /* Populate the board with the solution. */ for (r=0;r<9;r++) { for (c=0;c<9;c++) { state->currentboard[r][c]='0'+ sudoku_get(&sud, c, r, &original); } } } else { 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)); } /* 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)); } 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; } 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 */ 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. */ bool load_sudoku(struct sudoku_state_t* state, char* filename) { int fd; size_t n; int r = 0, c = 0; unsigned int i; int valid=0; char buf[300]; /* 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,300); if (n <= 0) { return(false); } rb->close(fd); r=0; c=0; i=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; 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++; } /* Ignore any other characters */ break; } i++; } /* Check that the board is valid - we need to check every row/column individually, so we check the diagonal from top-left to bottom-right */ for (state->x = 0; state->x < 9; state->x++) { state->y = state->x; 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); } bool save_sudoku(struct sudoku_state_t* state) { int fd; int r,c; int i; char line[13]; char sep[13]; rb->splash(0, "Saving..."); rb->memcpy(line,"...|...|...\r\n",13); rb->memcpy(sep,"-----------\r\n",13); if (state->filename[0]==0) { return false; } fd=rb->open(state->filename, O_WRONLY|O_CREAT); 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++; } } 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; } } 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; } 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); } 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(); } 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, }; 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 */ 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; } 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; int lastbutton = BUTTON_NONE; 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 (button != BUTTON_NONE) lastbutton = button; 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; } #endif