Introduce __attribute__((unused)) (#defined to UNUSED_ATTR) to mark possibly unused...
[kugel-rb.git] / apps / plugins / sudoku / sudoku.c
blob2ffd20d6662ed53b598aa295c46ebf2d14992dfe
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 #if (LCD_DEPTH>2)
79 #define BITMAP_WIDTH (BMPWIDTH_sudoku_normal/2)
80 #else
81 #define BITMAP_WIDTH BMPWIDTH_sudoku_normal
82 #endif
84 PLUGIN_HEADER
86 /* Default game - used to initialise sudoku.ss if it doesn't exist. */
87 static const char default_game[9][9] =
89 { '0','1','0', '3','0','7', '0','0','4' },
90 { '0','0','0', '0','6','0', '1','0','2' },
91 { '0','0','0', '0','8','0', '5','6','0' },
93 { '0','6','0', '0','0','0', '0','2','9' },
94 { '0','0','0', '5','0','3', '0','0','0' },
95 { '7','9','0', '0','0','0', '0','3','0' },
97 { '0','8','5', '0','3','0', '0','0','0' },
98 { '1','0','2', '0','7','0', '0','0','0' },
99 { '0','0','0', '4','0','8', '0','5','0' },
102 #if LCD_HEIGHT <= LCD_WIDTH /* Horizontal layout, scratchpad at the left */
104 #if (LCD_HEIGHT==64) && (LCD_WIDTH==112 || LCD_WIDTH==128)
105 /* Archos Recorders and Ondios - 112x64, 9 cells @ 8x6 with 10 border lines */
106 #define SMALL_BOARD
107 #define MARK_OFFS 1 /* Pixels between border and mark */
108 #define MARK_SPACE 1 /* Pixels between two marks */
109 #define MARK_SIZE 1 /* Mark width and height */
111 #elif ((LCD_HEIGHT==80) && (LCD_WIDTH==132))
112 /* C200, 9 cells @ 8x8 with 8 border lines */
113 #define SMALL_BOARD
114 #define MARK_OFFS 1 /* Pixels between border and mark */
115 #define MARK_SPACE 1 /* Pixels between two marks */
116 #define MARK_SIZE 1 /* Mark width and height */
118 #elif ((LCD_HEIGHT==96) && (LCD_WIDTH==128))
119 /* iAudio M3, 9 cells @ 9x9 with 14 border lines */
120 #define MARK_OFFS 1 /* Pixels between border and mark */
121 #define MARK_SPACE 2 /* Pixels between two marks */
122 #define MARK_SIZE 1 /* Mark width and height */
124 #elif (LCD_HEIGHT==110) && (LCD_WIDTH==138) \
125 || (LCD_HEIGHT==128) && (LCD_WIDTH==128)
126 /* iPod Mini - 138x110, 9 cells @ 10x10 with 14 border lines */
127 /* iriver H10 5-6GB - 128x128, 9 cells @ 10x10 with 14 border lines */
128 #define MARK_OFFS 1 /* Pixels between border and mark */
129 #define MARK_SPACE 1 /* Pixels between two marks */
130 #define MARK_SIZE 2 /* Mark width and height */
132 #elif ((LCD_HEIGHT==128) && (LCD_WIDTH==160)) \
133 || ((LCD_HEIGHT==132) && (LCD_WIDTH==176))
134 /* iAudio X5, Iriver H1x0, iPod G3, G4 - 160x128; */
135 /* iPod Nano - 176x132, 9 cells @ 12x12 with 14 border lines */
136 #define MARK_OFFS 1 /* Pixels between border and mark */
137 #define MARK_SPACE 2 /* Pixels between two marks */
138 #define MARK_SIZE 2 /* Mark width and height */
140 #elif ((LCD_HEIGHT==176) && (LCD_WIDTH==220))
141 /* Iriver h300, iPod Color/Photo - 220x176, 9 cells @ 16x16 with 14 border lines */
142 #define MARK_OFFS 1 /* Pixels between border and mark */
143 #define MARK_SPACE 1 /* Pixels between two marks */
144 #define MARK_SIZE 4 /* Mark width and height */
146 #elif (LCD_HEIGHT==240) && (LCD_WIDTH==320)
147 /* iPod Video - 320x240, 9 cells @ 24x24 with 14 border lines */
148 #define MARK_OFFS 1 /* Pixels between border and mark */
149 #define MARK_SPACE 2 /* Pixels between two marks */
150 #define MARK_SIZE 6 /* Mark width and height */
152 #elif (LCD_HEIGHT==480) && (LCD_WIDTH==640)
153 /* M:Robe 500 - 640x480, 9 cells @ 48x48 with 14 border lines */
154 #define MARK_OFFS 1 /* Pixels between border and mark */
155 #define MARK_SPACE 2 /* Pixels between two marks */
156 #define MARK_SIZE 6 /* Mark width and height */
158 #else
159 #error SUDOKU: Unsupported LCD size
160 #endif
162 #else /* Vertical layout, scratchpad at the bottom */
163 #define VERTICAL_LAYOUT
165 #if ((LCD_HEIGHT==220) && (LCD_WIDTH==176))
166 /* e200, 9 cells @ 16x16 with 14 border lines */
167 #define MARK_OFFS 1 /* Pixels between border and mark */
168 #define MARK_SPACE 1 /* Pixels between two marks */
169 #define MARK_SIZE 4 /* Mark width and height */
171 #elif (LCD_HEIGHT>=320) && (LCD_WIDTH>=240)
172 /* Gigabeat - 240x320, 9 cells @ 24x24 with 14 border lines */
173 #define MARK_OFFS 1 /* Pixels between border and mark */
174 #define MARK_SPACE 2 /* Pixels between two marks */
175 #define MARK_SIZE 6 /* Mark width and height */
177 #else
178 #error SUDOKU: Unsupported LCD size
179 #endif
181 #endif /* Layout */
183 #define CELL_WIDTH BITMAP_WIDTH
184 #define CELL_HEIGHT BITMAP_HEIGHT
186 #ifdef SUDOKU_BUTTON_CHANGEDIR
187 int invertdir=0;
188 #else
189 #define invertdir 0
190 #endif
192 #define CFGFILE_VERSION 0 /* Current config file version */
193 #define CFGFILE_MINVERSION 0 /* Minimum config file version to accept */
195 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
196 /* settings */
197 struct sudoku_config {
198 #ifdef HAVE_LCD_COLOR
199 int number_display;
200 #endif
201 #ifdef SUDOKU_BUTTON_POSSIBLE
202 int show_markings;
203 #endif
206 struct sudoku_config sudcfg_disk = {
207 #ifdef HAVE_LCD_COLOR
209 #endif
210 #ifdef SUDOKU_BUTTON_POSSIBLE
212 #endif
214 struct sudoku_config sudcfg;
216 static const char cfg_filename[] = "sudoku.cfg";
217 #ifdef HAVE_LCD_COLOR
218 static char *number_str[2] = { "black", "coloured" };
219 #endif
220 #ifdef SUDOKU_BUTTON_POSSIBLE
221 static char *mark_str[2] = { "hide", "show" };
222 #endif
224 struct configdata disk_config[] = {
225 #ifdef HAVE_LCD_COLOR
226 { TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.number_display }, "numbers",
227 number_str },
228 #endif
229 #ifdef SUDOKU_BUTTON_POSSIBLE
230 { TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.show_markings }, "markings",
231 mark_str },
232 #endif
234 #endif
235 #ifdef HAVE_LCD_COLOR
236 #define NUMBER_TYPE (sudcfg.number_display*CELL_WIDTH)
237 #else
238 #define NUMBER_TYPE 0
239 #endif
241 /* Size dependent build-time calculations */
242 #ifdef SMALL_BOARD
243 #define BOARD_WIDTH (CELL_WIDTH*9+10)
244 #define BOARD_HEIGHT (CELL_HEIGHT*9+10)
245 static unsigned int cellxpos[9]={
246 1, (CELL_WIDTH+2), (2*CELL_WIDTH+3),
247 (3*CELL_WIDTH+4), (4*CELL_WIDTH+5), (5*CELL_WIDTH+6),
248 (6*CELL_WIDTH+7), (7*CELL_WIDTH+8), (8*CELL_WIDTH+9)
250 static unsigned int cellypos[9]={
251 1, (CELL_HEIGHT+2), (2*CELL_HEIGHT+3),
252 (3*CELL_HEIGHT+4), (4*CELL_HEIGHT+5), (5*CELL_HEIGHT+6),
253 (6*CELL_HEIGHT+7), (7*CELL_HEIGHT+8), (8*CELL_HEIGHT+9)
255 #else /* !SMALL_BOARD */
256 #define BOARD_WIDTH (CELL_WIDTH*9+10+4)
257 #define BOARD_HEIGHT (CELL_HEIGHT*9+10+4)
258 static unsigned int cellxpos[9]={
259 2, (CELL_WIDTH +3), (2*CELL_WIDTH +4),
260 (3*CELL_WIDTH +6), (4*CELL_WIDTH +7), (5*CELL_WIDTH +8),
261 (6*CELL_WIDTH+10), (7*CELL_WIDTH+11), (8*CELL_WIDTH+12)
263 static unsigned int cellypos[9]={
264 2, (CELL_HEIGHT +3), (2*CELL_HEIGHT +4),
265 (3*CELL_HEIGHT +6), (4*CELL_HEIGHT +7), (5*CELL_HEIGHT +8),
266 (6*CELL_HEIGHT+10), (7*CELL_HEIGHT+11), (8*CELL_HEIGHT+12)
268 #endif
270 #ifdef VERTICAL_LAYOUT
271 #define XOFS ((LCD_WIDTH-BOARD_WIDTH)/2)
272 #define YOFS ((LCD_HEIGHT-(BOARD_HEIGHT+CELL_HEIGHT*2+2))/2)
273 #define YOFSSCRATCHPAD (YOFS+BOARD_HEIGHT+CELL_WIDTH)
274 #else
275 #define XOFSSCRATCHPAD ((LCD_WIDTH-(BOARD_WIDTH+CELL_WIDTH*2+2))/2)
276 #define XOFS (XOFSSCRATCHPAD+CELL_WIDTH*2+2)
277 #define YOFS ((LCD_HEIGHT-BOARD_HEIGHT)/2)
278 #endif
280 /****** Solver routine by Tom Shackell <shackell@cs.york.ac.uk>
282 Downloaded from:
284 http://www-users.cs.york.ac.uk/~shackell/sudoku/Sudoku.html
286 Released under GPLv2
290 typedef unsigned int Bitset;
292 #define BLOCK 3
293 #define SIZE (BLOCK*BLOCK)
295 #define true 1
296 #define false 0
298 typedef struct _Sudoku {
299 Bitset table[SIZE][SIZE];
300 }Sudoku;
302 typedef struct _Stats {
303 int numTries;
304 int backTracks;
305 int numEmpty;
306 bool solutionFound;
307 }Stats;
309 typedef struct _Options {
310 bool allSolutions;
311 bool uniquenessCheck;
312 }Options;
314 void sudoku_init(Sudoku* sud);
315 void sudoku_set(Sudoku* sud, int x, int y, int num, bool original);
316 int sudoku_get(Sudoku* sud, int x, int y, bool* original);
318 #define BIT(n) ((Bitset)BIT_N(n))
319 #define BIT_TEST(v,n) ((((Bitset)v) & BIT(n)) != 0)
320 #define BIT_CLEAR(v,n) (v) &= ~BIT(n)
321 #define MARK_BIT BIT(0)
322 #define ORIGINAL_BIT BIT(SIZE+1)
324 #define ALL_BITS (BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8) | BIT(9))
326 /* initialize a sudoku problem, should be called before using set or get */
327 void sudoku_init(Sudoku* sud)
329 int y, x;
330 for (y = 0; y < SIZE; y++){
331 for (x = 0; x < SIZE; x++){
332 sud->table[x][y] = ALL_BITS;
337 /* set the number at a particular x and y column */
338 void sudoku_set(Sudoku* sud, int x, int y, int num, bool original)
340 int i, j;
341 int bx, by;
342 Bitset orig;
344 /* clear the row and columns */
345 for (i = 0; i < SIZE; i++){
346 BIT_CLEAR(sud->table[i][y], num);
347 BIT_CLEAR(sud->table[x][i], num);
349 /* clear the block */
350 bx = x - (x % BLOCK);
351 by = y - (y % BLOCK);
352 for (i = 0; i < BLOCK; i++){
353 for (j = 0; j < BLOCK; j++){
354 BIT_CLEAR(sud->table[bx+j][by+i], num);
357 /* mark the table */
358 orig = original ? ORIGINAL_BIT : 0;
359 sud->table[x][y] = BIT(num) | MARK_BIT | orig;
362 /* get the number at a particular x and y column, if this
363 is not unique return 0 */
364 int sudoku_get(Sudoku* sud, int x, int y, bool* original)
366 Bitset val = sud->table[x][y];
367 int result = 0;
368 int i;
370 if (original) {
371 *original = val & ORIGINAL_BIT;
373 for (i = 1; i <= SIZE; i++){
374 if (BIT_TEST(val, i)){
375 if (result != 0){
376 return 0;
378 result = i;
381 return result;
384 /* returns true if this is a valid problem, this is necessary because the input
385 problem might be degenerate which breaks the solver algorithm. */
386 static bool is_valid(const Sudoku* sud)
388 int x, y;
390 for (y = 0; y < SIZE; y++){
391 for (x = 0; x < SIZE; x++){
392 if ((sud->table[x][y] & ALL_BITS) == 0){
393 return false;
397 return true;
400 /* scan the table for the most constrained item, giving all it's options, sets
401 the best x and y coordinates, the number of options and the options for
402 that coordinate and returns true if the puzzle is finished */
403 static bool scan(const Sudoku* sud, int* rX, int* rY, int *num, int* options)
405 int x, y, i, j;
406 int bestCount = SIZE+1;
407 Bitset val;
408 bool allMarked = true;
410 for (y = 0; y < SIZE; y++){
411 for (x = 0; x < SIZE; x++){
412 Bitset val = sud->table[x][y];
413 int i;
414 int count = 0;
416 if (val & MARK_BIT) {
417 /* already set */
418 continue;
420 allMarked = false;
421 for (i = 1; i <= SIZE; i++){
422 if (BIT_TEST(val, i)){
423 count++;
426 if (count < bestCount){
427 bestCount = count;
428 *rX = x;
429 *rY = y;
430 if (count == 0){
431 /* can't possibly be beaten */
432 *num = 0;
433 return false;
438 /* now copy into options */
439 *num = bestCount;
440 val = sud->table[*rX][*rY];
441 for (i = 1, j = 0; i <= SIZE; i++){
442 if (BIT_TEST(val, i)){
443 options[j++] = i;
446 return allMarked;
449 static bool solve(Sudoku* sud, Stats* stats, const Options* options);
451 /* try a particular option and return true if that gives a solution or false
452 if it doesn't, restores board on backtracking */
453 static bool spawn_option(Sudoku* sud, Stats* stats, const Options* options,
454 int x, int y, int num)
456 Sudoku copy;
458 rb->memcpy(&copy,sud,sizeof(Sudoku));
459 sudoku_set(&copy, x, y, num, false);
460 stats->numTries += 1;
461 if (solve(&copy, stats, options)){
462 if (!options->allSolutions && stats->solutionFound){
463 rb->memcpy(sud,&copy,sizeof(Sudoku));
465 return true;
466 }else{
467 stats->backTracks++;
469 return false;
472 /* solve a sudoku problem, returns true if there is a solution and false
473 otherwise. stats is used to track statisticss */
474 static bool solve(Sudoku* sud, Stats* stats, const Options* options)
476 while (true){
477 int x = 0;
478 int y = 0;
479 int i, num;
480 int places[SIZE];
482 if (scan(sud, &x, &y, &num, places)){
483 /* a solution was found! */
484 if (options->uniquenessCheck && stats->solutionFound){
485 /*printf("\n\t... But the solution is not unique!\n"); */
486 return true;
488 stats->solutionFound = true;
489 if (options->allSolutions || options->uniquenessCheck){
490 /*printf("\n\tSolution after %d iterations\n", stats->numTries); */
491 /*sudoku_print(sud); */
492 return false;
494 else{
495 return true;
498 if (num == 0){
499 /* can't be satisfied */
500 return false;
502 /* try all the places (except the last one) */
503 for (i = 0; i < num-1; i++){
504 if (spawn_option(sud, stats, options, x, y, places[i])){
505 /* solution found! */
506 if (!options->allSolutions && stats->solutionFound){
507 return true;
511 /* take the last place ourself */
512 stats->numTries += 1;
513 sudoku_set(sud, x, y, places[num-1], false);
517 /******** END OF IMPORTED CODE */
520 /* A wrapper function between the Sudoku plugin and the above solver code */
521 void sudoku_solve(struct sudoku_state_t* state)
523 bool ret;
524 Stats stats;
525 Options options;
526 Sudoku sud;
527 bool original;
528 int r,c;
530 /* Initialise the parameters */
531 sudoku_init(&sud);
532 rb->memset(&stats,0,sizeof(stats));
533 options.allSolutions=false;
534 options.uniquenessCheck=false;
536 /* Convert Rockbox format into format for solver */
537 for (r=0;r<9;r++) {
538 for (c=0;c<9;c++) {
539 if (state->startboard[r][c]!='0') {
540 sudoku_set(&sud, c, r, state->startboard[r][c]-'0', true);
545 /* need to check for degenerate input problems ... */
546 if (is_valid(&sud)){
547 ret = solve(&sud, &stats, &options);
548 } else {
549 ret = false;
552 if (ret) {
553 /* Populate the board with the solution. */
554 for (r=0;r<9;r++) {
555 for (c=0;c<9;c++) {
556 state->currentboard[r][c]='0'+
557 sudoku_get(&sud, c, r, &original);
560 } else {
561 rb->splash(HZ*2, "Solve failed");
564 return;
567 /* Copies the current to the saved board */
568 static void save_state(struct sudoku_state_t *state)
570 rb->memcpy(state->savedboard, state->currentboard,
571 sizeof(state->savedboard));
574 /* Copies the saved to the current board */
575 static void restore_state(struct sudoku_state_t *state)
577 rb->memcpy(state->currentboard, state->savedboard,
578 sizeof(state->savedboard));
581 void default_state(struct sudoku_state_t* state)
583 int r,c;
585 rb->strlcpy(state->filename,GAME_FILE,MAX_PATH);
586 for (r=0;r<9;r++) {
587 for (c=0;c<9;c++) {
588 state->startboard[r][c]=default_game[r][c];
589 state->currentboard[r][c]=default_game[r][c];
590 #ifdef SUDOKU_BUTTON_POSSIBLE
591 state->possiblevals[r][c]=0;
592 #endif
596 /* initialize the saved board so reload function works */
597 save_state(state);
599 state->x=0;
600 state->y=0;
601 state->editmode=0;
604 void clear_state(struct sudoku_state_t* state)
606 int r,c;
608 rb->strlcpy(state->filename,GAME_FILE,MAX_PATH);
609 for (r=0;r<9;r++) {
610 for (c=0;c<9;c++) {
611 state->startboard[r][c]='0';
612 state->currentboard[r][c]='0';
613 #ifdef SUDOKU_BUTTON_POSSIBLE
614 state->possiblevals[r][c]=0;
615 #endif
619 state->x=0;
620 state->y=0;
621 state->editmode=0;
624 /* Check the status of the board, assuming a change at the cursor location */
625 bool check_status(struct sudoku_state_t* state)
627 int check[9];
628 int r,c;
629 int r1,c1;
630 int cell;
632 /* First, check the column */
633 for (cell=0;cell<9;cell++) {
634 check[cell]=0;
636 for (r=0;r<9;r++) {
637 cell=state->currentboard[r][state->x];
638 if (cell!='0') {
639 if (check[cell-'1']==1) {
640 return true;
642 check[cell-'1']=1;
646 /* Second, check the row */
647 for (cell=0;cell<9;cell++) {
648 check[cell]=0;
650 for (c=0;c<9;c++) {
651 cell=state->currentboard[state->y][c];
652 if (cell!='0') {
653 if (check[cell-'1']==1) {
654 return true;
656 check[cell-'1']=1;
660 /* Finally, check the 3x3 sub-grid */
661 for (cell=0;cell<9;cell++) {
662 check[cell]=0;
664 r1=(state->y/3)*3;
665 c1=(state->x/3)*3;
666 for (r=r1;r<r1+3;r++) {
667 for (c=c1;c<c1+3;c++) {
668 cell=state->currentboard[r][c];
669 if (cell!='0') {
670 if (check[cell-'1']==1) {
671 return true;
673 check[cell-'1']=1;
678 /* We passed all the checks :) */
680 return false;
683 /* Load game - only ".ss" is officially supported, but any sensible
684 text representation (one line per row) may load.
686 bool load_sudoku(struct sudoku_state_t* state, char* filename)
688 int fd;
689 size_t n;
690 int r = 0, c = 0;
691 unsigned int i;
692 int valid=0;
693 char buf[300]; /* A buffer to read a sudoku board from */
695 fd=rb->open(filename, O_RDONLY);
696 if (fd < 0) {
697 LOGF("Invalid sudoku file: %s\n",filename);
698 return(false);
701 rb->strlcpy(state->filename,filename,MAX_PATH);
702 n=rb->read(fd,buf,300);
703 if (n <= 0) {
704 return(false);
706 rb->close(fd);
708 r=0;
709 c=0;
710 i=0;
711 while ((i < n) && (r < 9)) {
712 switch (buf[i]){
713 case ' ': case '\t':
714 if (c > 0)
715 valid=1;
716 break;
717 case '|':
718 case '*':
719 case '-':
720 case '\r':
721 break;
722 case '\n':
723 if (valid) {
724 r++;
725 valid=0;
727 c = 0;
728 break;
729 case '_': case '.':
730 valid=1;
731 if (c >= SIZE || r >= SIZE){
732 LOGF("ERROR: sudoku problem is the wrong size (%d,%d)\n",
733 c, r);
734 return(false);
736 c++;
737 break;
738 default:
739 if (((buf[i]>='A') && (buf[i]<='I')) ||
740 ((buf[i]>='0') && (buf[i]<='9'))) {
741 valid=1;
742 if (r >= SIZE || c >= SIZE){
743 LOGF("ERROR: sudoku problem is the wrong size "
744 "(%d,%d)\n", c, r);
745 return(false);
747 if ((buf[i]>='0') && (buf[i]<='9')) {
748 state->startboard[r][c]=buf[i];
749 state->currentboard[r][c]=buf[i];
750 } else {
751 state->currentboard[r][c]='1'+(buf[i]-'A');
753 c++;
755 /* Ignore any other characters */
756 break;
758 i++;
761 /* Check that the board is valid - we need to check every row/column
762 individually, so we check the diagonal from top-left to bottom-right */
763 for (state->x = 0; state->x < 9; state->x++) {
764 state->y = state->x;
765 if (check_status(state)) return false;
767 state->x = 0;
768 state->y = 0;
770 /* Save a copy of the saved state - so we can reload without using the
771 disk */
772 save_state(state);
773 return(true);
776 bool save_sudoku(struct sudoku_state_t* state)
778 int fd;
779 int r,c;
780 int i;
781 char line[13];
782 char sep[13];
784 rb->splash(0, "Saving...");
785 rb->memcpy(line,"...|...|...\r\n",13);
786 rb->memcpy(sep,"-----------\r\n",13);
788 if (state->filename[0]==0) {
789 return false;
792 fd=rb->open(state->filename, O_WRONLY|O_CREAT);
793 if (fd >= 0) {
794 for (r=0;r<9;r++) {
795 i=0;
796 for (c=0;c<9;c++) {
797 if (state->startboard[r][c]!='0') {
798 line[i]=state->startboard[r][c];
799 } else if (state->currentboard[r][c]!='0') {
800 line[i]='A'+(state->currentboard[r][c]-'1');
801 } else {
802 line[i]='.';
804 i++;
805 if ((c==2) || (c==5)) {
806 i++;
809 rb->write(fd,line,sizeof(line));
810 if ((r==2) || (r==5)) {
811 rb->write(fd,sep,sizeof(sep));
814 /* Add a blank line at end */
815 rb->write(fd,"\r\n",2);
816 rb->close(fd);
817 rb->reload_directory();
818 /* Save a copy of the saved state - so we can reload without
819 using the disk */
820 save_state(state);
821 return true;
822 } else {
823 return false;
827 void clear_board(struct sudoku_state_t* state)
829 int r,c;
831 for (r=0;r<9;r++) {
832 for (c=0;c<9;c++) {
833 state->currentboard[r][c]=state->startboard[r][c];
836 state->x=0;
837 state->y=0;
840 void update_cell(struct sudoku_state_t* state, int r, int c)
842 /* We have four types of cell:
843 1) User-entered number
844 2) Starting number
845 3) Cursor in cell
848 if ((r==state->y) && (c==state->x)) {
849 rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE,
850 BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
851 BITMAP_STRIDE,
852 XOFS+cellxpos[c],YOFS+cellypos[r],CELL_WIDTH,
853 CELL_HEIGHT);
854 } else {
855 if (state->startboard[r][c]!='0') {
856 rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE,
857 BITMAP_HEIGHT*(state->startboard[r][c]-'0'),
858 BITMAP_STRIDE,
859 XOFS+cellxpos[c],YOFS+cellypos[r],
860 CELL_WIDTH,CELL_HEIGHT);
861 } else {
862 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,
863 BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
864 BITMAP_STRIDE,
865 XOFS+cellxpos[c],YOFS+cellypos[r],
866 CELL_WIDTH,CELL_HEIGHT);
870 rb->lcd_update_rect(cellxpos[c],cellypos[r],CELL_WIDTH,CELL_HEIGHT);
874 void display_board(struct sudoku_state_t* state)
876 int r,c;
877 #ifdef SUDOKU_BUTTON_POSSIBLE
878 int i;
879 #endif
881 /* Clear the display buffer */
882 rb->lcd_clear_display();
884 /* Draw the gridlines - differently for different targets */
886 #ifdef SMALL_BOARD
887 /* Small targets - draw dotted/single lines */
888 for (r=0;r<9;r++) {
889 if ((r % 3)==0) {
890 /* Solid Line */
891 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
892 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
893 } else {
894 /* Dotted line */
895 for (c=XOFS;c<XOFS+BOARD_WIDTH;c+=2) {
896 rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
898 for (c=YOFS;c<YOFS+BOARD_HEIGHT;c+=2) {
899 rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
903 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
904 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
905 #else
906 /* Large targets - draw single/double lines */
907 for (r=0;r<9;r++) {
908 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
909 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
910 if ((r % 3)==0) {
911 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-2);
912 rb->lcd_vline(XOFS+cellxpos[r]-2,YOFS,YOFS+BOARD_HEIGHT-1);
915 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
916 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT+1);
917 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
918 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
919 #endif
921 #ifdef SUDOKU_BUTTON_POSSIBLE
922 #ifdef VERTICAL_LAYOUT
923 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD);
924 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD+CELL_HEIGHT+1);
925 for (r=0;r<9;r++) {
926 #ifdef SMALL_BOARD
927 /* Small targets - draw dotted/single lines */
928 if ((r % 3)==0) {
929 /* Solid Line */
930 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
931 YOFSSCRATCHPAD+CELL_HEIGHT+1);
932 } else {
933 /* Dotted line */
934 for (c=YOFSSCRATCHPAD;c<YOFSSCRATCHPAD+CELL_HEIGHT+1;c+=2) {
935 rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
938 #else
939 /* Large targets - draw single/double lines */
940 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
941 YOFSSCRATCHPAD+CELL_HEIGHT+1);
942 if ((r % 3)==0)
943 rb->lcd_vline(XOFS+cellxpos[r]-2,YOFSSCRATCHPAD,
944 YOFSSCRATCHPAD+CELL_HEIGHT+1);
945 #endif
946 if ((r>0) && state->possiblevals[state->y][state->x]&BIT_N(r))
947 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
948 BITMAP_STRIDE,XOFS+cellxpos[r-1],
949 YOFSSCRATCHPAD+1,CELL_WIDTH,CELL_HEIGHT);
951 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFSSCRATCHPAD,
952 YOFSSCRATCHPAD+CELL_HEIGHT+1);
953 #ifndef SMALL_BOARD
954 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFSSCRATCHPAD,
955 YOFSSCRATCHPAD+CELL_HEIGHT+1);
956 #endif
957 if (state->possiblevals[state->y][state->x]&BIT_N(r))
958 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
959 BITMAP_STRIDE,XOFS+cellxpos[8],YOFSSCRATCHPAD+1,
960 CELL_WIDTH,CELL_HEIGHT);
961 #else /* Horizontal layout */
962 rb->lcd_vline(XOFSSCRATCHPAD,YOFS,YOFS+BOARD_HEIGHT-1);
963 rb->lcd_vline(XOFSSCRATCHPAD+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
964 for (r=0;r<9;r++) {
965 #ifdef SMALL_BOARD
966 /* Small targets - draw dotted/single lines */
967 if ((r % 3)==0) {
968 /* Solid Line */
969 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
970 YOFS+cellypos[r]-1);
971 } else {
972 /* Dotted line */
973 for (c=XOFSSCRATCHPAD;c<XOFSSCRATCHPAD+CELL_WIDTH+1;c+=2) {
974 rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
977 #else
978 /* Large targets - draw single/double lines */
979 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
980 YOFS+cellypos[r]-1);
981 if ((r % 3)==0)
982 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
983 YOFS+cellypos[r]-2);
984 #endif
985 if ((r>0) && state->possiblevals[state->y][state->x]&BIT_N(r))
986 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
987 BITMAP_STRIDE,XOFSSCRATCHPAD+1,
988 YOFS+cellypos[r-1],CELL_WIDTH,CELL_HEIGHT);
990 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
991 YOFS+cellypos[8]+CELL_HEIGHT);
992 #ifndef SMALL_BOARD
993 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
994 YOFS+cellypos[8]+CELL_HEIGHT+1);
995 #endif
996 if (state->possiblevals[state->y][state->x]&BIT_N(r))
997 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
998 BITMAP_STRIDE,XOFSSCRATCHPAD+1,YOFS+cellypos[8],
999 CELL_WIDTH,CELL_HEIGHT);
1000 #endif /* Layout */
1001 #endif /* SUDOKU_BUTTON_POSSIBLE */
1003 /* Draw the numbers */
1004 for (r=0;r<9;r++) {
1005 for (c=0;c<9;c++) {
1006 /* We have four types of cell:
1007 1) User-entered number
1008 2) Starting number
1009 3) Cursor in cell
1012 if ((r==state->y) && (c==state->x)) {
1013 rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE,
1014 BITMAP_HEIGHT*(state->currentboard[r][c]-
1015 '0'),
1016 BITMAP_STRIDE,
1017 XOFS+cellxpos[c],YOFS+cellypos[r],
1018 CELL_WIDTH,CELL_HEIGHT);
1019 } else {
1020 if (state->startboard[r][c]!='0') {
1021 rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE,
1022 BITMAP_HEIGHT*(state->startboard[r][c]-
1023 '0'),
1024 BITMAP_STRIDE,
1025 XOFS+cellxpos[c],YOFS+cellypos[r],
1026 CELL_WIDTH,CELL_HEIGHT);
1027 } else {
1028 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,
1029 BITMAP_HEIGHT*
1030 (state->currentboard[r][c]-'0'),
1031 BITMAP_STRIDE,
1032 XOFS+cellxpos[c],YOFS+cellypos[r],
1033 CELL_WIDTH,CELL_HEIGHT);
1036 #ifdef SUDOKU_BUTTON_POSSIBLE
1037 /* Draw the possible number markings on the board */
1038 if(sudcfg.show_markings && state->startboard[r][c]=='0'
1039 && state->currentboard[r][c]=='0') {
1040 for(i=0;i<9;i++) {
1041 if(state->possiblevals[r][c]&(2<<i)) {
1042 #if LCD_DEPTH > 1
1043 /* draw markings in dark grey */
1044 rb->lcd_set_foreground(LCD_DARKGRAY);
1045 #endif
1046 rb->lcd_fillrect(XOFS+cellxpos[c]+MARK_OFFS
1047 +(i%3)*(MARK_SIZE+MARK_SPACE),
1048 YOFS+cellypos[r]+MARK_OFFS
1049 +(i/3)*(MARK_SIZE+MARK_SPACE),
1050 MARK_SIZE,
1051 MARK_SIZE);
1052 #if LCD_DEPTH > 1
1053 rb->lcd_set_foreground(LCD_BLACK);
1054 #endif
1058 #endif /* SUDOKU_BUTTON_POSSIBLE */
1063 /* update the screen */
1064 rb->lcd_update();
1067 bool sudoku_generate(struct sudoku_state_t* state)
1069 char* difficulty;
1070 char str[80];
1071 bool res;
1072 struct sudoku_state_t new_state;
1074 clear_state(&new_state);
1075 display_board(&new_state);
1076 rb->splash(0, "Generating...");
1078 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1079 rb->cpu_boost(true);
1080 #endif
1082 res = sudoku_generate_board(&new_state,&difficulty);
1084 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1085 rb->cpu_boost(false);
1086 #endif
1088 if (res) {
1089 rb->memcpy(state,&new_state,sizeof(new_state));
1090 rb->snprintf(str,sizeof(str),"Difficulty: %s",difficulty);
1091 display_board(state);
1092 rb->splash(HZ*3, str);
1093 rb->strlcpy(state->filename,GAME_FILE,MAX_PATH);
1094 } else {
1095 display_board(&new_state);
1096 rb->splash(HZ*2, "Aborted");
1098 /* initialize the saved board so reload function works */
1099 save_state(state);
1100 return res;
1103 #ifdef HAVE_LCD_COLOR
1104 static bool numdisplay_setting(void)
1106 static const struct opt_items names[] = {
1107 {"Black", -1},
1108 {"Coloured", -1},
1111 return rb->set_option("Number Display", &sudcfg.number_display, INT, names,
1112 sizeof(names) / sizeof(names[0]), NULL);
1114 #endif
1116 #ifdef SUDOKU_BUTTON_POSSIBLE
1117 static bool showmarkings_setting(void)
1119 static const struct opt_items names[] = {
1120 {"Hide", -1},
1121 {"Show", -1},
1124 return rb->set_option("Show Markings", &sudcfg.show_markings, INT, names,
1125 sizeof(names) / sizeof(names[0]), NULL);
1127 #endif
1129 enum {
1130 SM_AUDIO_PLAYBACK = 0,
1131 #ifdef HAVE_LCD_COLOR
1132 SM_NUMBER_DISPLAY,
1133 #endif
1134 #ifdef SUDOKU_BUTTON_POSSIBLE
1135 SM_SHOW_MARKINGS,
1136 #endif
1137 SM_SAVE,
1138 SM_RELOAD,
1139 SM_CLEAR,
1140 SM_SOLVE,
1141 SM_GENERATE,
1142 SM_NEW,
1143 SM_QUIT,
1146 int sudoku_menu(struct sudoku_state_t* state)
1148 int result;
1150 MENUITEM_STRINGLIST(menu, "Sudoku Menu", NULL,
1151 "Audio Playback",
1152 #ifdef HAVE_LCD_COLOR
1153 "Number Display",
1154 #endif
1155 #ifdef SUDOKU_BUTTON_POSSIBLE
1156 "Show Markings",
1157 #endif
1158 "Save", "Reload", "Clear", "Solve",
1159 "Generate", "New", "Quit");
1161 result = rb->do_menu(&menu, NULL, NULL, false);
1163 switch (result) {
1164 case SM_AUDIO_PLAYBACK:
1165 playback_control(NULL);
1166 break;
1168 #ifdef HAVE_LCD_COLOR
1169 case SM_NUMBER_DISPLAY:
1170 numdisplay_setting();
1171 break;
1172 #endif
1174 #ifdef SUDOKU_BUTTON_POSSIBLE
1175 case SM_SHOW_MARKINGS:
1176 showmarkings_setting();
1177 break;
1178 #endif
1179 case SM_SAVE:
1180 save_sudoku(state);
1181 break;
1183 case SM_RELOAD:
1184 restore_state(state);
1185 break;
1187 case SM_CLEAR:
1188 clear_board(state);
1189 break;
1191 case SM_SOLVE:
1192 sudoku_solve(state);
1193 break;
1195 case SM_GENERATE:
1196 sudoku_generate(state);
1197 break;
1199 case SM_NEW:
1200 clear_state(state);
1201 state->editmode=1;
1202 break;
1204 case SM_QUIT:
1205 save_sudoku(state);
1206 break;
1208 default:
1209 break;
1212 return result;
1215 /* Menu used when user is in edit mode - i.e. creating a new game manually */
1216 int sudoku_edit_menu(struct sudoku_state_t* state)
1218 int result;
1220 MENUITEM_STRINGLIST(menu, "Edit Menu", NULL,
1221 "Save as", "Quit");
1223 result = rb->do_menu(&menu, NULL, NULL, false);
1225 switch (result) {
1226 case 0: /* Save new game */
1227 rb->kbd_input(state->filename,MAX_PATH);
1228 if (save_sudoku(state)) {
1229 state->editmode=0;
1230 } else {
1231 rb->splash(HZ*2, "Save failed");
1233 break;
1235 case 1: /* Quit */
1236 break;
1238 default:
1239 break;
1242 return result;
1245 void move_cursor(struct sudoku_state_t* state, int newx, int newy)
1247 int oldx, oldy;
1249 /* Check that the character at the cursor position is legal */
1250 if (check_status(state)) {
1251 rb->splash(HZ*2, "Illegal move!");
1252 /* Ignore any button presses during the splash */
1253 rb->button_clear_queue();
1254 return;
1257 /* Move Cursor */
1258 oldx=state->x;
1259 oldy=state->y;
1260 state->x=newx;
1261 state->y=newy;
1263 /* Redraw current and old cells */
1264 update_cell(state,oldx,oldy);
1265 update_cell(state,newx,newy);
1268 /* plugin entry point */
1269 enum plugin_status plugin_start(UNUSED_ATTR const void* parameter)
1271 bool exit;
1272 int button;
1273 int lastbutton = BUTTON_NONE;
1274 int res;
1275 int rc = PLUGIN_OK;
1276 long ticks;
1277 struct sudoku_state_t state;
1279 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
1280 configfile_load(cfg_filename, disk_config,
1281 sizeof(disk_config) / sizeof(disk_config[0]),
1282 CFGFILE_MINVERSION);
1283 rb->memcpy(&sudcfg, &sudcfg_disk, sizeof(sudcfg)); /* copy to running config */
1284 #endif
1286 #if LCD_DEPTH > 1
1287 rb->lcd_set_backdrop(NULL);
1288 rb->lcd_set_foreground(LCD_BLACK);
1289 rb->lcd_set_background(LCD_WHITE);
1290 #endif
1292 clear_state(&state);
1294 if (parameter==NULL) {
1295 /* We have been started as a plugin - try default sudoku.ss */
1296 if (!load_sudoku(&state,GAME_FILE)) {
1297 /* No previous game saved, use the default */
1298 default_state(&state);
1300 } else {
1301 if (!load_sudoku(&state,(char*)parameter)) {
1302 rb->splash(HZ*2, "Load error");
1303 return(PLUGIN_ERROR);
1308 display_board(&state);
1310 /* The main game loop */
1311 exit=false;
1312 ticks=0;
1313 while(!exit) {
1314 button = rb->button_get(true);
1316 switch(button){
1317 #ifdef SUDOKU_BUTTON_QUIT
1318 /* Exit game */
1319 case SUDOKU_BUTTON_QUIT:
1320 if (check_status(&state)) {
1321 rb->splash(HZ*2, "Illegal move!");
1322 /* Ignore any button presses during the splash */
1323 rb->button_clear_queue();
1324 } else {
1325 save_sudoku(&state);
1326 exit=true;
1328 break;
1329 #endif
1331 /* Increment digit */
1332 #ifdef SUDOKU_BUTTON_ALTTOGGLE
1333 case SUDOKU_BUTTON_ALTTOGGLE | BUTTON_REPEAT:
1334 #endif
1335 case SUDOKU_BUTTON_TOGGLE | BUTTON_REPEAT:
1336 /* Slow down the repeat speed to 1/3 second */
1337 if ((*rb->current_tick-ticks) < (HZ/3)) {
1338 break;
1341 #ifdef SUDOKU_BUTTON_ALTTOGGLE
1342 case SUDOKU_BUTTON_ALTTOGGLE:
1343 #endif
1344 case SUDOKU_BUTTON_TOGGLE:
1345 #ifdef SUDOKU_BUTTON_TOGGLE_PRE
1346 if ((button == SUDOKU_BUTTON_TOGGLE)
1347 && (lastbutton != SUDOKU_BUTTON_TOGGLE_PRE))
1348 break;
1349 #endif
1350 /* Increment digit */
1351 ticks=*rb->current_tick;
1352 if (state.editmode) {
1353 if (state.startboard[state.y][state.x]=='9') {
1354 state.startboard[state.y][state.x]='0';
1355 state.currentboard[state.y][state.x]='0';
1356 } else {
1357 state.startboard[state.y][state.x]++;
1358 state.currentboard[state.y][state.x]++;
1360 } else {
1361 if (state.startboard[state.y][state.x]=='0') {
1362 if (state.currentboard[state.y][state.x]=='9') {
1363 state.currentboard[state.y][state.x]='0';
1364 } else {
1365 state.currentboard[state.y][state.x]++;
1369 update_cell(&state,state.y,state.x);
1370 break;
1372 #ifdef SUDOKU_BUTTON_TOGGLEBACK
1373 case SUDOKU_BUTTON_TOGGLEBACK | BUTTON_REPEAT:
1374 /* Slow down the repeat speed to 1/3 second */
1375 if ((*rb->current_tick-ticks) < (HZ/3)) {
1376 break;
1379 case SUDOKU_BUTTON_TOGGLEBACK:
1380 /* Decrement digit */
1381 ticks=*rb->current_tick;
1382 if (state.editmode) {
1383 if (state.startboard[state.y][state.x]=='0') {
1384 state.startboard[state.y][state.x]='9';
1385 state.currentboard[state.y][state.x]='9';
1386 } else {
1387 state.startboard[state.y][state.x]--;
1388 state.currentboard[state.y][state.x]--;
1390 } else {
1391 if (state.startboard[state.y][state.x]=='0') {
1392 if (state.currentboard[state.y][state.x]=='0') {
1393 state.currentboard[state.y][state.x]='9';
1394 } else {
1395 state.currentboard[state.y][state.x]--;
1399 update_cell(&state,state.y,state.x);
1400 break;
1401 #endif
1403 /* move cursor left */
1404 case SUDOKU_BUTTON_LEFT:
1405 case (SUDOKU_BUTTON_LEFT | BUTTON_REPEAT):
1406 if ( (state.x==0&&invertdir==0) || (state.y==0&&invertdir==1) ) {
1407 #ifndef SUDOKU_BUTTON_UP
1408 if ( (state.y==0&&invertdir==0) || (state.x==0&&invertdir==1)) {
1409 move_cursor(&state,8,8);
1410 } else {
1411 if (invertdir==0) {
1412 move_cursor(&state,8,state.y-1);
1413 } else {
1414 move_cursor(&state,state.x-1,8);
1417 #else
1418 move_cursor(&state,8,state.y);
1419 #endif
1420 } else {
1421 if (invertdir==0) {
1422 move_cursor(&state,state.x-1,state.y);
1423 } else {
1424 move_cursor(&state,state.x,state.y-1);
1427 break;
1429 /* move cursor right */
1430 case SUDOKU_BUTTON_RIGHT:
1431 case (SUDOKU_BUTTON_RIGHT | BUTTON_REPEAT):
1432 if ( (state.x==8&&invertdir==0) || (state.y==8&&invertdir==1) ) {
1433 #ifndef SUDOKU_BUTTON_DOWN
1434 if ( (state.y==8&&invertdir==0) || (state.x==8&&invertdir==1) ) {
1435 move_cursor(&state,0,0);
1436 } else {
1437 if (invertdir==0) {
1438 move_cursor(&state,0,state.y+1);
1439 } else {
1440 move_cursor(&state,state.x+1,0);
1443 #else
1444 move_cursor(&state,0,state.y);
1445 #endif
1446 } else {
1447 if (invertdir==0) {
1448 move_cursor(&state,state.x+1,state.y);
1449 } else {
1450 move_cursor(&state,state.x,state.y+1);
1453 break;
1455 #ifdef SUDOKU_BUTTON_UP
1456 /* move cursor up */
1457 case SUDOKU_BUTTON_UP:
1458 case (SUDOKU_BUTTON_UP | BUTTON_REPEAT):
1459 if (state.y==0) {
1460 move_cursor(&state,state.x,8);
1461 } else {
1462 move_cursor(&state,state.x,state.y-1);
1464 break;
1465 #endif
1467 #ifdef SUDOKU_BUTTON_DOWN
1468 /* move cursor down */
1469 case SUDOKU_BUTTON_DOWN:
1470 case (SUDOKU_BUTTON_DOWN | BUTTON_REPEAT):
1471 if (state.y==8) {
1472 move_cursor(&state,state.x,0);
1473 } else {
1474 move_cursor(&state,state.x,state.y+1);
1476 break;
1477 #endif
1479 case SUDOKU_BUTTON_MENU:
1480 #ifdef SUDOKU_BUTTON_MENU_PRE
1481 if (lastbutton != SUDOKU_BUTTON_MENU_PRE)
1482 break;
1483 #endif
1484 /* Don't let the user leave a game in a bad state */
1485 if (check_status(&state)) {
1486 rb->splash(HZ*2, "Illegal move!");
1487 /* Ignore any button presses during the splash */
1488 rb->button_clear_queue();
1489 } else {
1490 if (state.editmode) {
1491 res = sudoku_edit_menu(&state);
1492 if (res == MENU_ATTACHED_USB) {
1493 rc = PLUGIN_USB_CONNECTED;
1494 exit = true;
1495 } else if (res == 1) { /* Quit */
1496 exit = true;
1498 } else {
1499 res = sudoku_menu(&state);
1500 if (res == MENU_ATTACHED_USB) {
1501 rc = PLUGIN_USB_CONNECTED;
1502 exit = true;
1503 } else if (res == SM_QUIT) {
1504 exit = true;
1508 break;
1509 #ifdef SUDOKU_BUTTON_POSSIBLE
1510 case SUDOKU_BUTTON_POSSIBLE:
1511 /* Toggle current number in the possiblevals structure */
1512 if (state.currentboard[state.y][state.x]!='0') {
1513 state.possiblevals[state.y][state.x]^=
1514 BIT_N(state.currentboard[state.y][state.x] - '0');
1516 break;
1517 #endif
1519 #ifdef SUDOKU_BUTTON_CHANGEDIR
1520 case SUDOKU_BUTTON_CHANGEDIR:
1521 /* Change scroll wheel direction */
1522 invertdir=!invertdir;
1523 break;
1524 #endif
1525 default:
1526 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
1527 /* Quit if USB has been connected */
1528 rc = PLUGIN_USB_CONNECTED;
1529 exit = true;
1531 break;
1533 if (button != BUTTON_NONE)
1534 lastbutton = button;
1536 display_board(&state);
1538 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
1539 if (rb->memcmp(&sudcfg, &sudcfg_disk, sizeof(sudcfg))) /* save settings if changed */
1541 rb->memcpy(&sudcfg_disk, &sudcfg, sizeof(sudcfg));
1542 configfile_save(cfg_filename, disk_config,
1543 sizeof(disk_config) / sizeof(disk_config[0]),
1544 CFGFILE_VERSION);
1546 #endif
1547 return rc;
1550 #endif