Correct return value when quit sudoku from the menu.
[kugel-rb.git] / apps / plugins / sudoku / sudoku.c
blobf8a438592d017ab18f3e1d34fcb72e2274cd36cd
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2005 Dave Chapman
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 /***
23 Sudoku by Dave Chapman
25 User instructions
26 -----------------
28 Use the arrow keys to move cursor, and press SELECT/ON/F2 to increment
29 the number under the cursor.
31 At any time during the game, press On to bring up the game menu with
32 further options:
34 Save
35 Reload
36 Clear
37 Solve
39 Sudoku is implemented as a "viewer" for a ".ss" file, as generated by
40 Simple Sudoku and other applications - http://angusj.com/sudoku/
42 In-progress game positions are saved in the original .ss file, with
43 A-I used to indicate numbers entered by the user.
45 Example ".ss" file, and one with a saved state:
47 ...|...|... ...|...|...
48 2..|8.4|9.1 2.C|8.4|9.1
49 ...|1.6|32. E..|1.6|32.
50 ----------- -----------
51 ...|..5|.4. ...|..5|.4.
52 8..|423|..6 8..|423|..6
53 .3.|9..|... .3D|9..|A..
54 ----------- -----------
55 .63|7.9|... .63|7.9|...
56 4.9|5.2|..8 4.9|5.2|.C8
57 ...|...|... ...|...|...
61 #include "plugin.h"
62 #include "lib/configfile.h"
64 #ifdef HAVE_LCD_BITMAP
66 #include <lib/playback_control.h>
67 #include "sudoku.h"
68 #include "generator.h"
70 /* The bitmaps */
71 #include "pluginbitmaps/sudoku_normal.h"
72 #include "pluginbitmaps/sudoku_inverse.h"
73 #include "pluginbitmaps/sudoku_start.h"
75 #define BITMAP_HEIGHT (BMPHEIGHT_sudoku_normal/10)
76 #define BITMAP_STRIDE BMPWIDTH_sudoku_normal
78 PLUGIN_HEADER
80 /* Default game - used to initialise sudoku.ss if it doesn't exist. */
81 static const char default_game[9][9] =
83 { '0','1','0', '3','0','7', '0','0','4' },
84 { '0','0','0', '0','6','0', '1','0','2' },
85 { '0','0','0', '0','8','0', '5','6','0' },
87 { '0','6','0', '0','0','0', '0','2','9' },
88 { '0','0','0', '5','0','3', '0','0','0' },
89 { '7','9','0', '0','0','0', '0','3','0' },
91 { '0','8','5', '0','3','0', '0','0','0' },
92 { '1','0','2', '0','7','0', '0','0','0' },
93 { '0','0','0', '4','0','8', '0','5','0' },
96 #if LCD_HEIGHT <= LCD_WIDTH /* Horizontal layout, scratchpad at the left */
98 #if (LCD_HEIGHT==64) && (LCD_WIDTH==112 || LCD_WIDTH==128)
99 /* Archos Recorders and Ondios - 112x64, 9 cells @ 8x6 with 10 border lines */
101 /* Internal dimensions of a cell */
102 #define CELL_WIDTH 8
103 #define CELL_HEIGHT 6
104 #define SMALL_BOARD
105 #define MARK_OFFS 1 /* Pixels between border and mark */
106 #define MARK_SPACE 1 /* Pixels between two marks */
107 #define MARK_SIZE 1 /* Mark width and height */
109 #elif ((LCD_HEIGHT==80) && (LCD_WIDTH==132))
110 /* C200, 9 cells @ 8x8 with 8 border lines */
112 /* Internal dimensions of a cell */
113 #define CELL_WIDTH 8
114 #define CELL_HEIGHT 8
115 #define SMALL_BOARD
116 #define MARK_OFFS 1 /* Pixels between border and mark */
117 #define MARK_SPACE 1 /* Pixels between two marks */
118 #define MARK_SIZE 1 /* Mark width and height */
120 #elif ((LCD_HEIGHT==96) && (LCD_WIDTH==128))
121 /* iAudio M3, 9 cells @ 9x9 with 14 border lines */
123 /* Internal dimensions of a cell */
124 #define CELL_WIDTH 9
125 #define CELL_HEIGHT 9
126 #define MARK_OFFS 1 /* Pixels between border and mark */
127 #define MARK_SPACE 2 /* Pixels between two marks */
128 #define MARK_SIZE 1 /* Mark width and height */
130 #elif (LCD_HEIGHT==110) && (LCD_WIDTH==138) \
131 || (LCD_HEIGHT==128) && (LCD_WIDTH==128)
132 /* iPod Mini - 138x110, 9 cells @ 10x10 with 14 border lines */
133 /* iriver H10 5-6GB - 128x128, 9 cells @ 10x10 with 14 border lines */
135 /* Internal dimensions of a cell */
136 #define CELL_WIDTH 10
137 #define CELL_HEIGHT 10
138 #define MARK_OFFS 1 /* Pixels between border and mark */
139 #define MARK_SPACE 1 /* Pixels between two marks */
140 #define MARK_SIZE 2 /* Mark width and height */
142 #elif ((LCD_HEIGHT==128) && (LCD_WIDTH==160)) \
143 || ((LCD_HEIGHT==132) && (LCD_WIDTH==176))
144 /* iAudio X5, Iriver H1x0, iPod G3, G4 - 160x128; */
145 /* iPod Nano - 176x132, 9 cells @ 12x12 with 14 border lines */
147 /* Internal dimensions of a cell */
148 #define CELL_WIDTH 12
149 #define CELL_HEIGHT 12
150 #define MARK_OFFS 1 /* Pixels between border and mark */
151 #define MARK_SPACE 2 /* Pixels between two marks */
152 #define MARK_SIZE 2 /* Mark width and height */
154 #elif ((LCD_HEIGHT==176) && (LCD_WIDTH==220))
155 /* Iriver h300, iPod Color/Photo - 220x176, 9 cells @ 16x16 with 14 border lines */
157 /* Internal dimensions of a cell */
158 #define CELL_WIDTH 16
159 #define CELL_HEIGHT 16
160 #define MARK_OFFS 1 /* Pixels between border and mark */
161 #define MARK_SPACE 1 /* Pixels between two marks */
162 #define MARK_SIZE 4 /* Mark width and height */
164 #elif (LCD_HEIGHT>=240) && (LCD_WIDTH>=320)
165 /* iPod Video - 320x240, 9 cells @ 24x24 with 14 border lines */
167 /* Internal dimensions of a cell */
168 #define CELL_WIDTH 24
169 #define CELL_HEIGHT 24
170 #define MARK_OFFS 1 /* Pixels between border and mark */
171 #define MARK_SPACE 2 /* Pixels between two marks */
172 #define MARK_SIZE 6 /* Mark width and height */
174 #else
175 #error SUDOKU: Unsupported LCD size
176 #endif
178 #else /* Vertical layout, scratchpad at the bottom */
179 #define VERTICAL_LAYOUT
181 #if ((LCD_HEIGHT==220) && (LCD_WIDTH==176))
182 /* e200, 9 cells @ 16x16 with 14 border lines */
184 /* Internal dimensions of a cell */
185 #define CELL_WIDTH 16
186 #define CELL_HEIGHT 16
187 #define MARK_OFFS 1 /* Pixels between border and mark */
188 #define MARK_SPACE 1 /* Pixels between two marks */
189 #define MARK_SIZE 4 /* Mark width and height */
191 #elif (LCD_HEIGHT>=320) && (LCD_WIDTH>=240)
192 /* Gigabeat - 240x320, 9 cells @ 24x24 with 14 border lines */
194 /* Internal dimensions of a cell */
195 #define CELL_WIDTH 24
196 #define CELL_HEIGHT 24
197 #define MARK_OFFS 1 /* Pixels between border and mark */
198 #define MARK_SPACE 2 /* Pixels between two marks */
199 #define MARK_SIZE 6 /* Mark width and height */
201 #else
202 #error SUDOKU: Unsupported LCD size
203 #endif
205 #endif /* Layout */
207 #ifdef SUDOKU_BUTTON_CHANGEDIR
208 int invertdir=0;
209 #else
210 #define invertdir 0
211 #endif
213 #define CFGFILE_VERSION 0 /* Current config file version */
214 #define CFGFILE_MINVERSION 0 /* Minimum config file version to accept */
216 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
217 /* settings */
218 struct sudoku_config {
219 #ifdef HAVE_LCD_COLOR
220 int number_display;
221 #endif
222 #ifdef SUDOKU_BUTTON_POSSIBLE
223 int show_markings;
224 #endif
227 struct sudoku_config sudcfg_disk = {
228 #ifdef HAVE_LCD_COLOR
230 #endif
231 #ifdef SUDOKU_BUTTON_POSSIBLE
233 #endif
235 struct sudoku_config sudcfg;
237 static const char cfg_filename[] = "sudoku.cfg";
238 #ifdef HAVE_LCD_COLOR
239 static char *number_str[2] = { "black", "coloured" };
240 #endif
241 #ifdef SUDOKU_BUTTON_POSSIBLE
242 static char *mark_str[2] = { "hide", "show" };
243 #endif
245 struct configdata disk_config[] = {
246 #ifdef HAVE_LCD_COLOR
247 { TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.number_display }, "numbers",
248 number_str },
249 #endif
250 #ifdef SUDOKU_BUTTON_POSSIBLE
251 { TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.show_markings }, "markings",
252 mark_str },
253 #endif
255 #endif
256 #ifdef HAVE_LCD_COLOR
257 #define NUMBER_TYPE (sudcfg.number_display*CELL_WIDTH)
258 #else
259 #define NUMBER_TYPE 0
260 #endif
262 /* Size dependent build-time calculations */
263 #ifdef SMALL_BOARD
264 #define BOARD_WIDTH (CELL_WIDTH*9+10)
265 #define BOARD_HEIGHT (CELL_HEIGHT*9+10)
266 static unsigned char cellxpos[9]={
267 1, (CELL_WIDTH+2), (2*CELL_WIDTH+3),
268 (3*CELL_WIDTH+4), (4*CELL_WIDTH+5), (5*CELL_WIDTH+6),
269 (6*CELL_WIDTH+7), (7*CELL_WIDTH+8), (8*CELL_WIDTH+9)
271 static unsigned char cellypos[9]={
272 1, (CELL_HEIGHT+2), (2*CELL_HEIGHT+3),
273 (3*CELL_HEIGHT+4), (4*CELL_HEIGHT+5), (5*CELL_HEIGHT+6),
274 (6*CELL_HEIGHT+7), (7*CELL_HEIGHT+8), (8*CELL_HEIGHT+9)
276 #else /* !SMALL_BOARD */
277 #define BOARD_WIDTH (CELL_WIDTH*9+10+4)
278 #define BOARD_HEIGHT (CELL_HEIGHT*9+10+4)
279 static unsigned char cellxpos[9]={
280 2, (CELL_WIDTH +3), (2*CELL_WIDTH +4),
281 (3*CELL_WIDTH +6), (4*CELL_WIDTH +7), (5*CELL_WIDTH +8),
282 (6*CELL_WIDTH+10), (7*CELL_WIDTH+11), (8*CELL_WIDTH+12)
284 static unsigned char cellypos[9]={
285 2, (CELL_HEIGHT +3), (2*CELL_HEIGHT +4),
286 (3*CELL_HEIGHT +6), (4*CELL_HEIGHT +7), (5*CELL_HEIGHT +8),
287 (6*CELL_HEIGHT+10), (7*CELL_HEIGHT+11), (8*CELL_HEIGHT+12)
289 #endif
291 #ifdef VERTICAL_LAYOUT
292 #define XOFS ((LCD_WIDTH-BOARD_WIDTH)/2)
293 #define YOFS ((LCD_HEIGHT-(BOARD_HEIGHT+CELL_HEIGHT*2+2))/2)
294 #define YOFSSCRATCHPAD (YOFS+BOARD_HEIGHT+CELL_WIDTH)
295 #else
296 #define XOFSSCRATCHPAD ((LCD_WIDTH-(BOARD_WIDTH+CELL_WIDTH*2+2))/2)
297 #define XOFS (XOFSSCRATCHPAD+CELL_WIDTH*2+2)
298 #define YOFS ((LCD_HEIGHT-BOARD_HEIGHT)/2)
299 #endif
301 /****** Solver routine by Tom Shackell <shackell@cs.york.ac.uk>
303 Downloaded from:
305 http://www-users.cs.york.ac.uk/~shackell/sudoku/Sudoku.html
307 Released under GPLv2
311 typedef unsigned int Bitset;
313 #define BLOCK 3
314 #define SIZE (BLOCK*BLOCK)
316 #define true 1
317 #define false 0
319 typedef struct _Sudoku {
320 Bitset table[SIZE][SIZE];
321 }Sudoku;
323 typedef struct _Stats {
324 int numTries;
325 int backTracks;
326 int numEmpty;
327 bool solutionFound;
328 }Stats;
330 typedef struct _Options {
331 bool allSolutions;
332 bool uniquenessCheck;
333 }Options;
335 void sudoku_init(Sudoku* sud);
336 void sudoku_set(Sudoku* sud, int x, int y, int num, bool original);
337 int sudoku_get(Sudoku* sud, int x, int y, bool* original);
339 #define BIT(n) ((Bitset)BIT_N(n))
340 #define BIT_TEST(v,n) ((((Bitset)v) & BIT(n)) != 0)
341 #define BIT_CLEAR(v,n) (v) &= ~BIT(n)
342 #define MARK_BIT BIT(0)
343 #define ORIGINAL_BIT BIT(SIZE+1)
345 #define ALL_BITS (BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8) | BIT(9))
347 /* initialize a sudoku problem, should be called before using set or get */
348 void sudoku_init(Sudoku* sud)
350 int y, x;
351 for (y = 0; y < SIZE; y++){
352 for (x = 0; x < SIZE; x++){
353 sud->table[x][y] = ALL_BITS;
358 /* set the number at a particular x and y column */
359 void sudoku_set(Sudoku* sud, int x, int y, int num, bool original)
361 int i, j;
362 int bx, by;
363 Bitset orig;
365 /* clear the row and columns */
366 for (i = 0; i < SIZE; i++){
367 BIT_CLEAR(sud->table[i][y], num);
368 BIT_CLEAR(sud->table[x][i], num);
370 /* clear the block */
371 bx = x - (x % BLOCK);
372 by = y - (y % BLOCK);
373 for (i = 0; i < BLOCK; i++){
374 for (j = 0; j < BLOCK; j++){
375 BIT_CLEAR(sud->table[bx+j][by+i], num);
378 /* mark the table */
379 orig = original ? ORIGINAL_BIT : 0;
380 sud->table[x][y] = BIT(num) | MARK_BIT | orig;
383 /* get the number at a particular x and y column, if this
384 is not unique return 0 */
385 int sudoku_get(Sudoku* sud, int x, int y, bool* original)
387 Bitset val = sud->table[x][y];
388 int result = 0;
389 int i;
391 if (original) {
392 *original = val & ORIGINAL_BIT;
394 for (i = 1; i <= SIZE; i++){
395 if (BIT_TEST(val, i)){
396 if (result != 0){
397 return 0;
399 result = i;
402 return result;
405 /* returns true if this is a valid problem, this is necessary because the input
406 problem might be degenerate which breaks the solver algorithm. */
407 static bool is_valid(const Sudoku* sud)
409 int x, y;
411 for (y = 0; y < SIZE; y++){
412 for (x = 0; x < SIZE; x++){
413 if ((sud->table[x][y] & ALL_BITS) == 0){
414 return false;
418 return true;
421 /* scan the table for the most constrained item, giving all it's options, sets
422 the best x and y coordinates, the number of options and the options for
423 that coordinate and returns true if the puzzle is finished */
424 static bool scan(const Sudoku* sud, int* rX, int* rY, int *num, int* options)
426 int x, y, i, j;
427 int bestCount = SIZE+1;
428 Bitset val;
429 bool allMarked = true;
431 for (y = 0; y < SIZE; y++){
432 for (x = 0; x < SIZE; x++){
433 Bitset val = sud->table[x][y];
434 int i;
435 int count = 0;
437 if (val & MARK_BIT) {
438 /* already set */
439 continue;
441 allMarked = false;
442 for (i = 1; i <= SIZE; i++){
443 if (BIT_TEST(val, i)){
444 count++;
447 if (count < bestCount){
448 bestCount = count;
449 *rX = x;
450 *rY = y;
451 if (count == 0){
452 /* can't possibly be beaten */
453 *num = 0;
454 return false;
459 /* now copy into options */
460 *num = bestCount;
461 val = sud->table[*rX][*rY];
462 for (i = 1, j = 0; i <= SIZE; i++){
463 if (BIT_TEST(val, i)){
464 options[j++] = i;
467 return allMarked;
470 static bool solve(Sudoku* sud, Stats* stats, const Options* options);
472 /* try a particular option and return true if that gives a solution or false
473 if it doesn't, restores board on backtracking */
474 static bool spawn_option(Sudoku* sud, Stats* stats, const Options* options,
475 int x, int y, int num)
477 Sudoku copy;
479 rb->memcpy(&copy,sud,sizeof(Sudoku));
480 sudoku_set(&copy, x, y, num, false);
481 stats->numTries += 1;
482 if (solve(&copy, stats, options)){
483 if (!options->allSolutions && stats->solutionFound){
484 rb->memcpy(sud,&copy,sizeof(Sudoku));
486 return true;
487 }else{
488 stats->backTracks++;
490 return false;
493 /* solve a sudoku problem, returns true if there is a solution and false
494 otherwise. stats is used to track statisticss */
495 static bool solve(Sudoku* sud, Stats* stats, const Options* options)
497 while (true){
498 int x = 0;
499 int y = 0;
500 int i, num;
501 int places[SIZE];
503 if (scan(sud, &x, &y, &num, places)){
504 /* a solution was found! */
505 if (options->uniquenessCheck && stats->solutionFound){
506 /*printf("\n\t... But the solution is not unique!\n"); */
507 return true;
509 stats->solutionFound = true;
510 if (options->allSolutions || options->uniquenessCheck){
511 /*printf("\n\tSolution after %d iterations\n", stats->numTries); */
512 /*sudoku_print(sud); */
513 return false;
515 else{
516 return true;
519 if (num == 0){
520 /* can't be satisfied */
521 return false;
523 /* try all the places (except the last one) */
524 for (i = 0; i < num-1; i++){
525 if (spawn_option(sud, stats, options, x, y, places[i])){
526 /* solution found! */
527 if (!options->allSolutions && stats->solutionFound){
528 return true;
532 /* take the last place ourself */
533 stats->numTries += 1;
534 sudoku_set(sud, x, y, places[num-1], false);
538 /******** END OF IMPORTED CODE */
541 /* A wrapper function between the Sudoku plugin and the above solver code */
542 void sudoku_solve(struct sudoku_state_t* state)
544 bool ret;
545 Stats stats;
546 Options options;
547 Sudoku sud;
548 bool original;
549 int r,c;
551 /* Initialise the parameters */
552 sudoku_init(&sud);
553 rb->memset(&stats,0,sizeof(stats));
554 options.allSolutions=false;
555 options.uniquenessCheck=false;
557 /* Convert Rockbox format into format for solver */
558 for (r=0;r<9;r++) {
559 for (c=0;c<9;c++) {
560 if (state->startboard[r][c]!='0') {
561 sudoku_set(&sud, c, r, state->startboard[r][c]-'0', true);
566 /* need to check for degenerate input problems ... */
567 if (is_valid(&sud)){
568 ret = solve(&sud, &stats, &options);
569 } else {
570 ret = false;
573 if (ret) {
574 /* Populate the board with the solution. */
575 for (r=0;r<9;r++) {
576 for (c=0;c<9;c++) {
577 state->currentboard[r][c]='0'+
578 sudoku_get(&sud, c, r, &original);
581 } else {
582 rb->splash(HZ*2, "Solve failed");
585 return;
588 /* Copies the current to the saved board */
589 static void save_state(struct sudoku_state_t *state)
591 rb->memcpy(state->savedboard, state->currentboard,
592 sizeof(state->savedboard));
595 /* Copies the saved to the current board */
596 static void restore_state(struct sudoku_state_t *state)
598 rb->memcpy(state->currentboard, state->savedboard,
599 sizeof(state->savedboard));
602 void default_state(struct sudoku_state_t* state)
604 int r,c;
606 rb->strncpy(state->filename,GAME_FILE,MAX_PATH);
607 for (r=0;r<9;r++) {
608 for (c=0;c<9;c++) {
609 state->startboard[r][c]=default_game[r][c];
610 state->currentboard[r][c]=default_game[r][c];
611 #ifdef SUDOKU_BUTTON_POSSIBLE
612 state->possiblevals[r][c]=0;
613 #endif
617 /* initialize the saved board so reload function works */
618 save_state(state);
620 state->x=0;
621 state->y=0;
622 state->editmode=0;
625 void clear_state(struct sudoku_state_t* state)
627 int r,c;
629 rb->strncpy(state->filename,GAME_FILE,MAX_PATH);
630 for (r=0;r<9;r++) {
631 for (c=0;c<9;c++) {
632 state->startboard[r][c]='0';
633 state->currentboard[r][c]='0';
634 #ifdef SUDOKU_BUTTON_POSSIBLE
635 state->possiblevals[r][c]=0;
636 #endif
640 state->x=0;
641 state->y=0;
642 state->editmode=0;
645 /* Check the status of the board, assuming a change at the cursor location */
646 bool check_status(struct sudoku_state_t* state)
648 int check[9];
649 int r,c;
650 int r1,c1;
651 int cell;
653 /* First, check the column */
654 for (cell=0;cell<9;cell++) {
655 check[cell]=0;
657 for (r=0;r<9;r++) {
658 cell=state->currentboard[r][state->x];
659 if (cell!='0') {
660 if (check[cell-'1']==1) {
661 return true;
663 check[cell-'1']=1;
667 /* Second, check the row */
668 for (cell=0;cell<9;cell++) {
669 check[cell]=0;
671 for (c=0;c<9;c++) {
672 cell=state->currentboard[state->y][c];
673 if (cell!='0') {
674 if (check[cell-'1']==1) {
675 return true;
677 check[cell-'1']=1;
681 /* Finally, check the 3x3 sub-grid */
682 for (cell=0;cell<9;cell++) {
683 check[cell]=0;
685 r1=(state->y/3)*3;
686 c1=(state->x/3)*3;
687 for (r=r1;r<r1+3;r++) {
688 for (c=c1;c<c1+3;c++) {
689 cell=state->currentboard[r][c];
690 if (cell!='0') {
691 if (check[cell-'1']==1) {
692 return true;
694 check[cell-'1']=1;
699 /* We passed all the checks :) */
701 return false;
704 /* Load game - only ".ss" is officially supported, but any sensible
705 text representation (one line per row) may load.
707 bool load_sudoku(struct sudoku_state_t* state, char* filename)
709 int fd;
710 size_t n;
711 int r = 0, c = 0;
712 unsigned int i;
713 int valid=0;
714 char buf[300]; /* A buffer to read a sudoku board from */
716 fd=rb->open(filename, O_RDONLY);
717 if (fd < 0) {
718 LOGF("Invalid sudoku file: %s\n",filename);
719 return(false);
722 rb->strncpy(state->filename,filename,MAX_PATH);
723 n=rb->read(fd,buf,300);
724 if (n <= 0) {
725 return(false);
727 rb->close(fd);
729 r=0;
730 c=0;
731 i=0;
732 while ((i < n) && (r < 9)) {
733 switch (buf[i]){
734 case ' ': case '\t':
735 if (c > 0)
736 valid=1;
737 break;
738 case '|':
739 case '*':
740 case '-':
741 case '\r':
742 break;
743 case '\n':
744 if (valid) {
745 r++;
746 valid=0;
748 c = 0;
749 break;
750 case '_': case '.':
751 valid=1;
752 if (c >= SIZE || r >= SIZE){
753 LOGF("ERROR: sudoku problem is the wrong size (%d,%d)\n",
754 c, r);
755 return(false);
757 c++;
758 break;
759 default:
760 if (((buf[i]>='A') && (buf[i]<='I')) ||
761 ((buf[i]>='0') && (buf[i]<='9'))) {
762 valid=1;
763 if (r >= SIZE || c >= SIZE){
764 LOGF("ERROR: sudoku problem is the wrong size "
765 "(%d,%d)\n", c, r);
766 return(false);
768 if ((buf[i]>='0') && (buf[i]<='9')) {
769 state->startboard[r][c]=buf[i];
770 state->currentboard[r][c]=buf[i];
771 } else {
772 state->currentboard[r][c]='1'+(buf[i]-'A');
774 c++;
776 /* Ignore any other characters */
777 break;
779 i++;
782 /* Check that the board is valid - we need to check every row/column
783 individually, so we check the diagonal from top-left to bottom-right */
784 for (state->x = 0; state->x < 9; state->x++) {
785 state->y = state->x;
786 if (check_status(state)) return false;
788 state->x = 0;
789 state->y = 0;
791 /* Save a copy of the saved state - so we can reload without using the
792 disk */
793 save_state(state);
794 return(true);
797 bool save_sudoku(struct sudoku_state_t* state)
799 int fd;
800 int r,c;
801 int i;
802 char line[13];
803 char sep[13];
805 rb->splash(0, "Saving...");
806 rb->memcpy(line,"...|...|...\r\n",13);
807 rb->memcpy(sep,"-----------\r\n",13);
809 if (state->filename[0]==0) {
810 return false;
813 fd=rb->open(state->filename, O_WRONLY|O_CREAT);
814 if (fd >= 0) {
815 for (r=0;r<9;r++) {
816 i=0;
817 for (c=0;c<9;c++) {
818 if (state->startboard[r][c]!='0') {
819 line[i]=state->startboard[r][c];
820 } else if (state->currentboard[r][c]!='0') {
821 line[i]='A'+(state->currentboard[r][c]-'1');
822 } else {
823 line[i]='.';
825 i++;
826 if ((c==2) || (c==5)) {
827 i++;
830 rb->write(fd,line,sizeof(line));
831 if ((r==2) || (r==5)) {
832 rb->write(fd,sep,sizeof(sep));
835 /* Add a blank line at end */
836 rb->write(fd,"\r\n",2);
837 rb->close(fd);
838 rb->reload_directory();
839 /* Save a copy of the saved state - so we can reload without
840 using the disk */
841 save_state(state);
842 return true;
843 } else {
844 return false;
848 void clear_board(struct sudoku_state_t* state)
850 int r,c;
852 for (r=0;r<9;r++) {
853 for (c=0;c<9;c++) {
854 state->currentboard[r][c]=state->startboard[r][c];
857 state->x=0;
858 state->y=0;
861 void update_cell(struct sudoku_state_t* state, int r, int c)
863 /* We have four types of cell:
864 1) User-entered number
865 2) Starting number
866 3) Cursor in cell
869 if ((r==state->y) && (c==state->x)) {
870 rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE,
871 BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
872 BITMAP_STRIDE,
873 XOFS+cellxpos[c],YOFS+cellypos[r],CELL_WIDTH,
874 CELL_HEIGHT);
875 } else {
876 if (state->startboard[r][c]!='0') {
877 rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE,
878 BITMAP_HEIGHT*(state->startboard[r][c]-'0'),
879 BITMAP_STRIDE,
880 XOFS+cellxpos[c],YOFS+cellypos[r],
881 CELL_WIDTH,CELL_HEIGHT);
882 } else {
883 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,
884 BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
885 BITMAP_STRIDE,
886 XOFS+cellxpos[c],YOFS+cellypos[r],
887 CELL_WIDTH,CELL_HEIGHT);
891 rb->lcd_update_rect(cellxpos[c],cellypos[r],CELL_WIDTH,CELL_HEIGHT);
895 void display_board(struct sudoku_state_t* state)
897 int r,c;
898 #ifdef SUDOKU_BUTTON_POSSIBLE
899 int i;
900 #endif
902 /* Clear the display buffer */
903 rb->lcd_clear_display();
905 /* Draw the gridlines - differently for different targets */
907 #ifdef SMALL_BOARD
908 /* Small targets - draw dotted/single lines */
909 for (r=0;r<9;r++) {
910 if ((r % 3)==0) {
911 /* Solid Line */
912 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
913 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
914 } else {
915 /* Dotted line */
916 for (c=XOFS;c<XOFS+BOARD_WIDTH;c+=2) {
917 rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
919 for (c=YOFS;c<YOFS+BOARD_HEIGHT;c+=2) {
920 rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
924 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
925 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
926 #else
927 /* Large targets - draw single/double lines */
928 for (r=0;r<9;r++) {
929 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
930 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
931 if ((r % 3)==0) {
932 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-2);
933 rb->lcd_vline(XOFS+cellxpos[r]-2,YOFS,YOFS+BOARD_HEIGHT-1);
936 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
937 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT+1);
938 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
939 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
940 #endif
942 #ifdef SUDOKU_BUTTON_POSSIBLE
943 #ifdef VERTICAL_LAYOUT
944 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD);
945 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD+CELL_HEIGHT+1);
946 for (r=0;r<9;r++) {
947 #ifdef SMALL_BOARD
948 /* Small targets - draw dotted/single lines */
949 if ((r % 3)==0) {
950 /* Solid Line */
951 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
952 YOFSSCRATCHPAD+CELL_HEIGHT+1);
953 } else {
954 /* Dotted line */
955 for (c=YOFSSCRATCHPAD;c<YOFSSCRATCHPAD+CELL_HEIGHT+1;c+=2) {
956 rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
959 #else
960 /* Large targets - draw single/double lines */
961 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
962 YOFSSCRATCHPAD+CELL_HEIGHT+1);
963 if ((r % 3)==0)
964 rb->lcd_vline(XOFS+cellxpos[r]-2,YOFSSCRATCHPAD,
965 YOFSSCRATCHPAD+CELL_HEIGHT+1);
966 #endif
967 if ((r>0) && state->possiblevals[state->y][state->x]&BIT_N(r))
968 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
969 BITMAP_STRIDE,XOFS+cellxpos[r-1],
970 YOFSSCRATCHPAD+1,CELL_WIDTH,CELL_HEIGHT);
972 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFSSCRATCHPAD,
973 YOFSSCRATCHPAD+CELL_HEIGHT+1);
974 #ifndef SMALL_BOARD
975 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFSSCRATCHPAD,
976 YOFSSCRATCHPAD+CELL_HEIGHT+1);
977 #endif
978 if (state->possiblevals[state->y][state->x]&BIT_N(r))
979 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
980 BITMAP_STRIDE,XOFS+cellxpos[8],YOFSSCRATCHPAD+1,
981 CELL_WIDTH,CELL_HEIGHT);
982 #else /* Horizontal layout */
983 rb->lcd_vline(XOFSSCRATCHPAD,YOFS,YOFS+BOARD_HEIGHT-1);
984 rb->lcd_vline(XOFSSCRATCHPAD+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
985 for (r=0;r<9;r++) {
986 #ifdef SMALL_BOARD
987 /* Small targets - draw dotted/single lines */
988 if ((r % 3)==0) {
989 /* Solid Line */
990 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
991 YOFS+cellypos[r]-1);
992 } else {
993 /* Dotted line */
994 for (c=XOFSSCRATCHPAD;c<XOFSSCRATCHPAD+CELL_WIDTH+1;c+=2) {
995 rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
998 #else
999 /* Large targets - draw single/double lines */
1000 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
1001 YOFS+cellypos[r]-1);
1002 if ((r % 3)==0)
1003 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
1004 YOFS+cellypos[r]-2);
1005 #endif
1006 if ((r>0) && state->possiblevals[state->y][state->x]&BIT_N(r))
1007 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
1008 BITMAP_STRIDE,XOFSSCRATCHPAD+1,
1009 YOFS+cellypos[r-1],CELL_WIDTH,CELL_HEIGHT);
1011 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
1012 YOFS+cellypos[8]+CELL_HEIGHT);
1013 #ifndef SMALL_BOARD
1014 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
1015 YOFS+cellypos[8]+CELL_HEIGHT+1);
1016 #endif
1017 if (state->possiblevals[state->y][state->x]&BIT_N(r))
1018 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
1019 BITMAP_STRIDE,XOFSSCRATCHPAD+1,YOFS+cellypos[8],
1020 CELL_WIDTH,CELL_HEIGHT);
1021 #endif /* Layout */
1022 #endif /* SUDOKU_BUTTON_POSSIBLE */
1024 /* Draw the numbers */
1025 for (r=0;r<9;r++) {
1026 for (c=0;c<9;c++) {
1027 /* We have four types of cell:
1028 1) User-entered number
1029 2) Starting number
1030 3) Cursor in cell
1033 if ((r==state->y) && (c==state->x)) {
1034 rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE,
1035 BITMAP_HEIGHT*(state->currentboard[r][c]-
1036 '0'),
1037 BITMAP_STRIDE,
1038 XOFS+cellxpos[c],YOFS+cellypos[r],
1039 CELL_WIDTH,CELL_HEIGHT);
1040 } else {
1041 if (state->startboard[r][c]!='0') {
1042 rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE,
1043 BITMAP_HEIGHT*(state->startboard[r][c]-
1044 '0'),
1045 BITMAP_STRIDE,
1046 XOFS+cellxpos[c],YOFS+cellypos[r],
1047 CELL_WIDTH,CELL_HEIGHT);
1048 } else {
1049 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,
1050 BITMAP_HEIGHT*
1051 (state->currentboard[r][c]-'0'),
1052 BITMAP_STRIDE,
1053 XOFS+cellxpos[c],YOFS+cellypos[r],
1054 CELL_WIDTH,CELL_HEIGHT);
1057 #ifdef SUDOKU_BUTTON_POSSIBLE
1058 /* Draw the possible number markings on the board */
1059 if(sudcfg.show_markings && state->startboard[r][c]=='0'
1060 && state->currentboard[r][c]=='0') {
1061 for(i=0;i<9;i++) {
1062 if(state->possiblevals[r][c]&(2<<i)) {
1063 #if LCD_DEPTH > 1
1064 /* draw markings in dark grey */
1065 rb->lcd_set_foreground(LCD_DARKGRAY);
1066 #endif
1067 rb->lcd_fillrect(XOFS+cellxpos[c]+MARK_OFFS
1068 +(i%3)*(MARK_SIZE+MARK_SPACE),
1069 YOFS+cellypos[r]+MARK_OFFS
1070 +(i/3)*(MARK_SIZE+MARK_SPACE),
1071 MARK_SIZE,
1072 MARK_SIZE);
1073 #if LCD_DEPTH > 1
1074 rb->lcd_set_foreground(LCD_BLACK);
1075 #endif
1079 #endif /* SUDOKU_BUTTON_POSSIBLE */
1084 /* update the screen */
1085 rb->lcd_update();
1088 bool sudoku_generate(struct sudoku_state_t* state)
1090 char* difficulty;
1091 char str[80];
1092 bool res;
1093 struct sudoku_state_t new_state;
1095 clear_state(&new_state);
1096 display_board(&new_state);
1097 rb->splash(0, "Generating...");
1099 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1100 rb->cpu_boost(true);
1101 #endif
1103 res = sudoku_generate_board(&new_state,&difficulty);
1105 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1106 rb->cpu_boost(false);
1107 #endif
1109 if (res) {
1110 rb->memcpy(state,&new_state,sizeof(new_state));
1111 rb->snprintf(str,sizeof(str),"Difficulty: %s",difficulty);
1112 display_board(state);
1113 rb->splash(HZ*3, str);
1114 rb->strncpy(state->filename,GAME_FILE,MAX_PATH);
1115 } else {
1116 display_board(&new_state);
1117 rb->splash(HZ*2, "Aborted");
1119 /* initialize the saved board so reload function works */
1120 save_state(state);
1121 return res;
1124 #ifdef HAVE_LCD_COLOR
1125 static bool numdisplay_setting(void)
1127 static const struct opt_items names[] = {
1128 {"Black", -1},
1129 {"Coloured", -1},
1132 return rb->set_option("Number Display", &sudcfg.number_display, INT, names,
1133 sizeof(names) / sizeof(names[0]), NULL);
1135 #endif
1137 #ifdef SUDOKU_BUTTON_POSSIBLE
1138 static bool showmarkings_setting(void)
1140 static const struct opt_items names[] = {
1141 {"Hide", -1},
1142 {"Show", -1},
1145 return rb->set_option("Show Markings", &sudcfg.show_markings, INT, names,
1146 sizeof(names) / sizeof(names[0]), NULL);
1148 #endif
1150 enum {
1151 SM_AUDIO_PLAYBACK = 0,
1152 #ifdef HAVE_LCD_COLOR
1153 SM_NUMBER_DISPLAY,
1154 #endif
1155 #ifdef SUDOKU_BUTTON_POSSIBLE
1156 SM_SHOW_MARKINGS,
1157 #endif
1158 SM_SAVE,
1159 SM_RELOAD,
1160 SM_CLEAR,
1161 SM_SOLVE,
1162 SM_GENERATE,
1163 SM_NEW,
1164 SM_QUIT,
1167 int sudoku_menu(struct sudoku_state_t* state)
1169 int result;
1171 MENUITEM_STRINGLIST(menu, "Sudoku Menu", NULL,
1172 "Audio Playback",
1173 #ifdef HAVE_LCD_COLOR
1174 "Number Display",
1175 #endif
1176 #ifdef SUDOKU_BUTTON_POSSIBLE
1177 "Show Markings",
1178 #endif
1179 "Save", "Reload", "Clear", "Solve",
1180 "Generate", "New", "Quit");
1182 result = rb->do_menu(&menu, NULL, NULL, false);
1184 switch (result) {
1185 case SM_AUDIO_PLAYBACK:
1186 playback_control(NULL);
1187 break;
1189 #ifdef HAVE_LCD_COLOR
1190 case SM_NUMBER_DISPLAY:
1191 numdisplay_setting();
1192 break;
1193 #endif
1195 #ifdef SUDOKU_BUTTON_POSSIBLE
1196 case SM_SHOW_MARKINGS:
1197 showmarkings_setting();
1198 break;
1199 #endif
1200 case SM_SAVE:
1201 save_sudoku(state);
1202 break;
1204 case SM_RELOAD:
1205 restore_state(state);
1206 break;
1208 case SM_CLEAR:
1209 clear_board(state);
1210 break;
1212 case SM_SOLVE:
1213 sudoku_solve(state);
1214 break;
1216 case SM_GENERATE:
1217 sudoku_generate(state);
1218 break;
1220 case SM_NEW:
1221 clear_state(state);
1222 state->editmode=1;
1223 break;
1225 case SM_QUIT:
1226 save_sudoku(state);
1227 break;
1229 default:
1230 break;
1233 return result;
1236 /* Menu used when user is in edit mode - i.e. creating a new game manually */
1237 int sudoku_edit_menu(struct sudoku_state_t* state)
1239 int result;
1241 MENUITEM_STRINGLIST(menu, "Edit Menu", NULL,
1242 "Save as", "Quit");
1244 result = rb->do_menu(&menu, NULL, NULL, false);
1246 switch (result) {
1247 case 0: /* Save new game */
1248 rb->kbd_input(state->filename,MAX_PATH);
1249 if (save_sudoku(state)) {
1250 state->editmode=0;
1251 } else {
1252 rb->splash(HZ*2, "Save failed");
1254 break;
1256 case 1: /* Quit */
1257 break;
1259 default:
1260 break;
1263 return result;
1266 void move_cursor(struct sudoku_state_t* state, int newx, int newy)
1268 int oldx, oldy;
1270 /* Check that the character at the cursor position is legal */
1271 if (check_status(state)) {
1272 rb->splash(HZ*2, "Illegal move!");
1273 /* Ignore any button presses during the splash */
1274 rb->button_clear_queue();
1275 return;
1278 /* Move Cursor */
1279 oldx=state->x;
1280 oldy=state->y;
1281 state->x=newx;
1282 state->y=newy;
1284 /* Redraw current and old cells */
1285 update_cell(state,oldx,oldy);
1286 update_cell(state,newx,newy);
1289 /* plugin entry point */
1290 enum plugin_status plugin_start(const void* parameter)
1292 bool exit;
1293 int button;
1294 int lastbutton = BUTTON_NONE;
1295 int res;
1296 int rc = PLUGIN_OK;
1297 long ticks;
1298 struct sudoku_state_t state;
1300 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
1301 configfile_load(cfg_filename, disk_config,
1302 sizeof(disk_config) / sizeof(disk_config[0]),
1303 CFGFILE_MINVERSION);
1304 rb->memcpy(&sudcfg, &sudcfg_disk, sizeof(sudcfg)); /* copy to running config */
1305 #endif
1307 #if LCD_DEPTH > 1
1308 rb->lcd_set_backdrop(NULL);
1309 rb->lcd_set_foreground(LCD_BLACK);
1310 rb->lcd_set_background(LCD_WHITE);
1311 #endif
1313 clear_state(&state);
1315 if (parameter==NULL) {
1316 /* We have been started as a plugin - try default sudoku.ss */
1317 if (!load_sudoku(&state,GAME_FILE)) {
1318 /* No previous game saved, use the default */
1319 default_state(&state);
1321 } else {
1322 if (!load_sudoku(&state,(char*)parameter)) {
1323 rb->splash(HZ*2, "Load error");
1324 return(PLUGIN_ERROR);
1329 display_board(&state);
1331 /* The main game loop */
1332 exit=false;
1333 ticks=0;
1334 while(!exit) {
1335 button = rb->button_get(true);
1337 switch(button){
1338 #ifdef SUDOKU_BUTTON_QUIT
1339 /* Exit game */
1340 case SUDOKU_BUTTON_QUIT:
1341 if (check_status(&state)) {
1342 rb->splash(HZ*2, "Illegal move!");
1343 /* Ignore any button presses during the splash */
1344 rb->button_clear_queue();
1345 } else {
1346 save_sudoku(&state);
1347 exit=true;
1349 break;
1350 #endif
1352 /* Increment digit */
1353 #ifdef SUDOKU_BUTTON_ALTTOGGLE
1354 case SUDOKU_BUTTON_ALTTOGGLE | BUTTON_REPEAT:
1355 #endif
1356 case SUDOKU_BUTTON_TOGGLE | BUTTON_REPEAT:
1357 /* Slow down the repeat speed to 1/3 second */
1358 if ((*rb->current_tick-ticks) < (HZ/3)) {
1359 break;
1362 #ifdef SUDOKU_BUTTON_ALTTOGGLE
1363 case SUDOKU_BUTTON_ALTTOGGLE:
1364 #endif
1365 case SUDOKU_BUTTON_TOGGLE:
1366 #ifdef SUDOKU_BUTTON_TOGGLE_PRE
1367 if ((button == SUDOKU_BUTTON_TOGGLE)
1368 && (lastbutton != SUDOKU_BUTTON_TOGGLE_PRE))
1369 break;
1370 #endif
1371 /* Increment digit */
1372 ticks=*rb->current_tick;
1373 if (state.editmode) {
1374 if (state.startboard[state.y][state.x]=='9') {
1375 state.startboard[state.y][state.x]='0';
1376 state.currentboard[state.y][state.x]='0';
1377 } else {
1378 state.startboard[state.y][state.x]++;
1379 state.currentboard[state.y][state.x]++;
1381 } else {
1382 if (state.startboard[state.y][state.x]=='0') {
1383 if (state.currentboard[state.y][state.x]=='9') {
1384 state.currentboard[state.y][state.x]='0';
1385 } else {
1386 state.currentboard[state.y][state.x]++;
1390 update_cell(&state,state.y,state.x);
1391 break;
1393 #ifdef SUDOKU_BUTTON_TOGGLEBACK
1394 case SUDOKU_BUTTON_TOGGLEBACK | BUTTON_REPEAT:
1395 /* Slow down the repeat speed to 1/3 second */
1396 if ((*rb->current_tick-ticks) < (HZ/3)) {
1397 break;
1400 case SUDOKU_BUTTON_TOGGLEBACK:
1401 /* Decrement digit */
1402 ticks=*rb->current_tick;
1403 if (state.editmode) {
1404 if (state.startboard[state.y][state.x]=='0') {
1405 state.startboard[state.y][state.x]='9';
1406 state.currentboard[state.y][state.x]='9';
1407 } else {
1408 state.startboard[state.y][state.x]--;
1409 state.currentboard[state.y][state.x]--;
1411 } else {
1412 if (state.startboard[state.y][state.x]=='0') {
1413 if (state.currentboard[state.y][state.x]=='0') {
1414 state.currentboard[state.y][state.x]='9';
1415 } else {
1416 state.currentboard[state.y][state.x]--;
1420 update_cell(&state,state.y,state.x);
1421 break;
1422 #endif
1424 /* move cursor left */
1425 case SUDOKU_BUTTON_LEFT:
1426 case (SUDOKU_BUTTON_LEFT | BUTTON_REPEAT):
1427 if ( (state.x==0&&invertdir==0) || (state.y==0&&invertdir==1) ) {
1428 #ifndef SUDOKU_BUTTON_UP
1429 if ( (state.y==0&&invertdir==0) || (state.x==0&&invertdir==1)) {
1430 move_cursor(&state,8,8);
1431 } else {
1432 if (invertdir==0) {
1433 move_cursor(&state,8,state.y-1);
1434 } else {
1435 move_cursor(&state,state.x-1,8);
1438 #else
1439 move_cursor(&state,8,state.y);
1440 #endif
1441 } else {
1442 if (invertdir==0) {
1443 move_cursor(&state,state.x-1,state.y);
1444 } else {
1445 move_cursor(&state,state.x,state.y-1);
1448 break;
1450 /* move cursor right */
1451 case SUDOKU_BUTTON_RIGHT:
1452 case (SUDOKU_BUTTON_RIGHT | BUTTON_REPEAT):
1453 if ( (state.x==8&&invertdir==0) || (state.y==8&&invertdir==1) ) {
1454 #ifndef SUDOKU_BUTTON_DOWN
1455 if ( (state.y==8&&invertdir==0) || (state.x==8&&invertdir==1) ) {
1456 move_cursor(&state,0,0);
1457 } else {
1458 if (invertdir==0) {
1459 move_cursor(&state,0,state.y+1);
1460 } else {
1461 move_cursor(&state,state.x+1,0);
1464 #else
1465 move_cursor(&state,0,state.y);
1466 #endif
1467 } else {
1468 if (invertdir==0) {
1469 move_cursor(&state,state.x+1,state.y);
1470 } else {
1471 move_cursor(&state,state.x,state.y+1);
1474 break;
1476 #ifdef SUDOKU_BUTTON_UP
1477 /* move cursor up */
1478 case SUDOKU_BUTTON_UP:
1479 case (SUDOKU_BUTTON_UP | BUTTON_REPEAT):
1480 if (state.y==0) {
1481 move_cursor(&state,state.x,8);
1482 } else {
1483 move_cursor(&state,state.x,state.y-1);
1485 break;
1486 #endif
1488 #ifdef SUDOKU_BUTTON_DOWN
1489 /* move cursor down */
1490 case SUDOKU_BUTTON_DOWN:
1491 case (SUDOKU_BUTTON_DOWN | BUTTON_REPEAT):
1492 if (state.y==8) {
1493 move_cursor(&state,state.x,0);
1494 } else {
1495 move_cursor(&state,state.x,state.y+1);
1497 break;
1498 #endif
1500 case SUDOKU_BUTTON_MENU:
1501 #ifdef SUDOKU_BUTTON_MENU_PRE
1502 if (lastbutton != SUDOKU_BUTTON_MENU_PRE)
1503 break;
1504 #endif
1505 /* Don't let the user leave a game in a bad state */
1506 if (check_status(&state)) {
1507 rb->splash(HZ*2, "Illegal move!");
1508 /* Ignore any button presses during the splash */
1509 rb->button_clear_queue();
1510 } else {
1511 if (state.editmode) {
1512 res = sudoku_edit_menu(&state);
1513 if (res == MENU_ATTACHED_USB) {
1514 rc = PLUGIN_USB_CONNECTED;
1515 exit = true;
1516 } else if (res == 1) { /* Quit */
1517 exit = true;
1519 } else {
1520 res = sudoku_menu(&state);
1521 if (res == MENU_ATTACHED_USB) {
1522 rc = PLUGIN_USB_CONNECTED;
1523 exit = true;
1524 } else if (res == SM_QUIT) {
1525 exit = true;
1529 break;
1530 #ifdef SUDOKU_BUTTON_POSSIBLE
1531 case SUDOKU_BUTTON_POSSIBLE:
1532 /* Toggle current number in the possiblevals structure */
1533 if (state.currentboard[state.y][state.x]!='0') {
1534 state.possiblevals[state.y][state.x]^=
1535 BIT_N(state.currentboard[state.y][state.x] - '0');
1537 break;
1538 #endif
1540 #ifdef SUDOKU_BUTTON_CHANGEDIR
1541 case SUDOKU_BUTTON_CHANGEDIR:
1542 /* Change scroll wheel direction */
1543 invertdir=!invertdir;
1544 break;
1545 #endif
1546 default:
1547 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
1548 /* Quit if USB has been connected */
1549 rc = PLUGIN_USB_CONNECTED;
1550 exit = true;
1552 break;
1554 if (button != BUTTON_NONE)
1555 lastbutton = button;
1557 display_board(&state);
1559 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
1560 if (rb->memcmp(&sudcfg, &sudcfg_disk, sizeof(sudcfg))) /* save settings if changed */
1562 rb->memcpy(&sudcfg_disk, &sudcfg, sizeof(sudcfg));
1563 configfile_save(cfg_filename, disk_config,
1564 sizeof(disk_config) / sizeof(disk_config[0]),
1565 CFGFILE_VERSION);
1567 #endif
1568 return rc;
1571 #endif