| /*************************************************************************** |
| * __________ __ ___. |
| * 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 "lib/oldmenuapi.h" |
| |
| #ifdef HAVE_LCD_BITMAP |
| |
| #include <lib/playback_control.h> |
| #include "sudoku.h" |
| #include "generator.h" |
| |
| /* The bitmaps */ |
| #include "sudoku_normal.h" |
| #include "sudoku_inverse.h" |
| #include "sudoku_start.h" |
| |
| #define BITMAP_HEIGHT (BMPHEIGHT_sudoku_normal/10) |
| #define BITMAP_STRIDE BMPWIDTH_sudoku_normal |
| |
| PLUGIN_HEADER |
| |
| /* 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 */ |
| |
| const struct plugin_api* rb; |
| |
| /* 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) |
| /* Archos Recorders and Ondios - 112x64, 9 cells @ 8x6 with 10 border lines */ |
| |
| /* Internal dimensions of a cell */ |
| #define CELL_WIDTH 8 |
| #define CELL_HEIGHT 6 |
| #define SMALL_BOARD |
| |
| #elif ((LCD_HEIGHT==80) && (LCD_WIDTH==132)) |
| /* C200, 9 cells @ 8x8 with 8 border lines */ |
| |
| /* Internal dimensions of a cell */ |
| #define CELL_WIDTH 8 |
| #define CELL_HEIGHT 8 |
| #define SMALL_BOARD |
| |
| #elif ((LCD_HEIGHT==96) && (LCD_WIDTH==128)) |
| /* iAudio M3, 9 cells @ 9x9 with 14 border lines */ |
| |
| /* Internal dimensions of a cell */ |
| #define CELL_WIDTH 9 |
| #define CELL_HEIGHT 9 |
| |
| #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 */ |
| |
| /* Internal dimensions of a cell */ |
| #define CELL_WIDTH 10 |
| #define CELL_HEIGHT 10 |
| |
| #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 */ |
| |
| /* Internal dimensions of a cell */ |
| #define CELL_WIDTH 12 |
| #define CELL_HEIGHT 12 |
| |
| #elif ((LCD_HEIGHT==176) && (LCD_WIDTH==220)) |
| /* Iriver h300, iPod Color/Photo - 220x176, 9 cells @ 16x16 with 14 border lines */ |
| |
| /* Internal dimensions of a cell */ |
| #define CELL_WIDTH 16 |
| #define CELL_HEIGHT 16 |
| |
| #elif (LCD_HEIGHT>=240) && (LCD_WIDTH>=320) |
| /* iPod Video - 320x240, 9 cells @ 24x24 with 14 border lines */ |
| |
| /* Internal dimensions of a cell */ |
| #define CELL_WIDTH 24 |
| #define CELL_HEIGHT 24 |
| |
| #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 */ |
| |
| /* Internal dimensions of a cell */ |
| #define CELL_WIDTH 16 |
| #define CELL_HEIGHT 16 |
| |
| #elif (LCD_HEIGHT>=320) && (LCD_WIDTH>=240) |
| /* Gigabeat - 240x320, 9 cells @ 24x24 with 14 border lines */ |
| |
| /* Internal dimensions of a cell */ |
| #define CELL_WIDTH 24 |
| #define CELL_HEIGHT 24 |
| |
| #else |
| #error SUDOKU: Unsupported LCD size |
| #endif |
| |
| #endif /* Layout */ |
| |
| #define CFGFILE_VERSION 0 /* Current config file version */ |
| #define CFGFILE_MINVERSION 0 /* Minimum config file version to accept */ |
| |
| #ifdef HAVE_LCD_COLOR |
| /* settings */ |
| struct sudoku_config { |
| int number_display; |
| }; |
| struct sudoku_config sudcfg_disk = { 0 }; |
| struct sudoku_config sudcfg; |
| |
| static const char cfg_filename[] = "sudoku.cfg"; |
| static char *number_str[2] = { "black", "coloured" }; |
| |
| struct configdata disk_config[] = { |
| { TYPE_ENUM, 0, 2, &sudcfg_disk.number_display, "numbers", number_str, NULL }, |
| }; |
| #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 char 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 char 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 char 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 char 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 <shackell@cs.york.ac.uk> |
| |
| 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)(1<<(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; |
| } |
| |
| void default_state(struct sudoku_state_t* state) |
| { |
| int r,c; |
| |
| rb->strncpy(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 |
| } |
| } |
| |
| state->x=0; |
| state->y=0; |
| state->editmode=0; |
| } |
| |
| void clear_state(struct sudoku_state_t* state) |
| { |
| int r,c; |
| |
| rb->strncpy(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;r<r1+3;r++) { |
| for (c=c1;c<c1+3;c++) { |
| cell=state->currentboard[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->strncpy(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 */ |
| rb->memcpy(state->savedboard,state->currentboard,81); |
| 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 */ |
| rb->memcpy(state->savedboard,state->currentboard,81); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| void restore_state(struct sudoku_state_t* state) |
| { |
| rb->memcpy(state->currentboard,state->savedboard,81); |
| } |
| |
| 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; |
| |
| /* 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;c<XOFS+BOARD_WIDTH;c+=2) { |
| rb->lcd_drawpixel(c,YOFS+cellypos[r]-1); |
| } |
| for (c=YOFS;c<YOFS+BOARD_HEIGHT;c+=2) { |
| rb->lcd_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;c<YOFSSCRATCHPAD+CELL_HEIGHT+1;c+=2) { |
| rb->lcd_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]&(1<<(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]&(1<<(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;c<XOFSSCRATCHPAD+CELL_WIDTH+1;c+=2) { |
| rb->lcd_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]&(1<<(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]&(1<<(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); |
| } |
| } |
| } |
| } |
| |
| /* 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->strncpy(state->filename,GAME_FILE,MAX_PATH); |
| } else { |
| display_board(&new_state); |
| rb->splash(HZ*2, "Aborted"); |
| } |
| 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 |
| |
| enum { |
| SM_AUDIO_PLAYBACK = 0, |
| #ifdef HAVE_LCD_COLOR |
| SM_NUMBER_DISPLAY, |
| #endif |
| SM_SAVE, |
| SM_RELOAD, |
| SM_CLEAR, |
| SM_SOLVE, |
| SM_GENERATE, |
| SM_NEW, |
| SM_QUIT, |
| }; |
| |
| bool sudoku_menu(struct sudoku_state_t* state) |
| { |
| int m; |
| int result; |
| |
| static const struct menu_item items[] = { |
| [SM_AUDIO_PLAYBACK] = { "Audio Playback", NULL }, |
| #ifdef HAVE_LCD_COLOR |
| [SM_NUMBER_DISPLAY] = { "Number Display", NULL }, |
| #endif |
| [SM_SAVE] = { "Save", NULL }, |
| [SM_RELOAD] = { "Reload", NULL }, |
| [SM_CLEAR] = { "Clear", NULL }, |
| [SM_SOLVE] = { "Solve", NULL }, |
| [SM_GENERATE] = { "Generate", NULL }, |
| [SM_NEW] = { "New", NULL }, |
| [SM_QUIT] = { "Quit", NULL }, |
| }; |
| |
| m = menu_init(rb,items, sizeof(items) / sizeof(*items), |
| NULL, NULL, NULL, NULL); |
| |
| result=menu_show(m); |
| |
| switch (result) { |
| case SM_AUDIO_PLAYBACK: |
| playback_control(rb, NULL); |
| break; |
| |
| #ifdef HAVE_LCD_COLOR |
| case SM_NUMBER_DISPLAY: |
| numdisplay_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); |
| menu_exit(m); |
| return true; |
| break; |
| |
| default: |
| break; |
| } |
| |
| menu_exit(m); |
| |
| return (result==MENU_ATTACHED_USB); |
| } |
| |
| /* 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 m; |
| int result; |
| |
| static const struct menu_item items[] = { |
| { "Save as", NULL }, |
| { "Quit", NULL }, |
| }; |
| |
| m = menu_init(rb,items, sizeof(items) / sizeof(*items), |
| NULL, NULL, NULL, NULL); |
| |
| result=menu_show(m); |
| |
| 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; |
| } |
| |
| menu_exit(m); |
| |
| 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 struct plugin_api* api, const void* parameter) |
| { |
| bool exit; |
| int button; |
| int lastbutton = BUTTON_NONE; |
| int res; |
| int rc = PLUGIN_OK; |
| long ticks; |
| struct sudoku_state_t state; |
| |
| /* plugin init */ |
| rb = api; |
| /* end of plugin init */ |
| |
| #ifdef HAVE_LCD_COLOR |
| configfile_init(rb); |
| 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) { |
| #ifndef SUDOKU_BUTTON_UP |
| if (state.y==0) { |
| move_cursor(&state,8,8); |
| } else { |
| move_cursor(&state,8,state.y-1); |
| } |
| #else |
| move_cursor(&state,8,state.y); |
| #endif |
| } else { |
| move_cursor(&state,state.x-1,state.y); |
| } |
| break; |
| |
| /* move cursor right */ |
| case SUDOKU_BUTTON_RIGHT: |
| case (SUDOKU_BUTTON_RIGHT | BUTTON_REPEAT): |
| if (state.x==8) { |
| #ifndef SUDOKU_BUTTON_DOWN |
| if (state.y==8) { |
| move_cursor(&state,0,0); |
| } else { |
| move_cursor(&state,0,state.y+1); |
| } |
| #else |
| move_cursor(&state,0,state.y); |
| #endif |
| } else { |
| move_cursor(&state,state.x+1,state.y); |
| } |
| 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 { |
| if (sudoku_menu(&state)) { |
| rc = PLUGIN_USB_CONNECTED; |
| 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]^= |
| (1 << (state.currentboard[state.y][state.x] - '0')); |
| } |
| 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); |
| } |
| #ifdef HAVE_LCD_COLOR |
| 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 |