Use the AMS_LOWMEM define to indicate memory size as the .lds files do in config...
[kugel-rb.git] / apps / plugins / sudoku / sudoku.c
blob413be298ea5d238b24f1bd122820ee851a23052a
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"
63 #include "lib/oldmenuapi.h"
65 #ifdef HAVE_LCD_BITMAP
67 #include <lib/playback_control.h>
68 #include "sudoku.h"
69 #include "generator.h"
71 /* The bitmaps */
72 #include "pluginbitmaps/sudoku_normal.h"
73 #include "pluginbitmaps/sudoku_inverse.h"
74 #include "pluginbitmaps/sudoku_start.h"
76 #define BITMAP_HEIGHT (BMPHEIGHT_sudoku_normal/10)
77 #define BITMAP_STRIDE BMPWIDTH_sudoku_normal
79 PLUGIN_HEADER
81 /* Default game - used to initialise sudoku.ss if it doesn't exist. */
82 static const char default_game[9][9] =
84 { '0','1','0', '3','0','7', '0','0','4' },
85 { '0','0','0', '0','6','0', '1','0','2' },
86 { '0','0','0', '0','8','0', '5','6','0' },
88 { '0','6','0', '0','0','0', '0','2','9' },
89 { '0','0','0', '5','0','3', '0','0','0' },
90 { '7','9','0', '0','0','0', '0','3','0' },
92 { '0','8','5', '0','3','0', '0','0','0' },
93 { '1','0','2', '0','7','0', '0','0','0' },
94 { '0','0','0', '4','0','8', '0','5','0' },
97 #if LCD_HEIGHT <= LCD_WIDTH /* Horizontal layout, scratchpad at the left */
99 #if (LCD_HEIGHT==64) && (LCD_WIDTH==112 || LCD_WIDTH==128)
100 /* Archos Recorders and Ondios - 112x64, 9 cells @ 8x6 with 10 border lines */
102 /* Internal dimensions of a cell */
103 #define CELL_WIDTH 8
104 #define CELL_HEIGHT 6
105 #define SMALL_BOARD
106 #define MARK_OFFS 1 /* Pixels between border and mark */
107 #define MARK_SPACE 1 /* Pixels between two marks */
108 #define MARK_SIZE 1 /* Mark width and height */
110 #elif ((LCD_HEIGHT==80) && (LCD_WIDTH==132))
111 /* C200, 9 cells @ 8x8 with 8 border lines */
113 /* Internal dimensions of a cell */
114 #define CELL_WIDTH 8
115 #define CELL_HEIGHT 8
116 #define SMALL_BOARD
117 #define MARK_OFFS 1 /* Pixels between border and mark */
118 #define MARK_SPACE 1 /* Pixels between two marks */
119 #define MARK_SIZE 1 /* Mark width and height */
121 #elif ((LCD_HEIGHT==96) && (LCD_WIDTH==128))
122 /* iAudio M3, 9 cells @ 9x9 with 14 border lines */
124 /* Internal dimensions of a cell */
125 #define CELL_WIDTH 9
126 #define CELL_HEIGHT 9
127 #define MARK_OFFS 1 /* Pixels between border and mark */
128 #define MARK_SPACE 2 /* Pixels between two marks */
129 #define MARK_SIZE 1 /* Mark width and height */
131 #elif (LCD_HEIGHT==110) && (LCD_WIDTH==138) \
132 || (LCD_HEIGHT==128) && (LCD_WIDTH==128)
133 /* iPod Mini - 138x110, 9 cells @ 10x10 with 14 border lines */
134 /* iriver H10 5-6GB - 128x128, 9 cells @ 10x10 with 14 border lines */
136 /* Internal dimensions of a cell */
137 #define CELL_WIDTH 10
138 #define CELL_HEIGHT 10
139 #define MARK_OFFS 1 /* Pixels between border and mark */
140 #define MARK_SPACE 1 /* Pixels between two marks */
141 #define MARK_SIZE 2 /* Mark width and height */
143 #elif ((LCD_HEIGHT==128) && (LCD_WIDTH==160)) \
144 || ((LCD_HEIGHT==132) && (LCD_WIDTH==176))
145 /* iAudio X5, Iriver H1x0, iPod G3, G4 - 160x128; */
146 /* iPod Nano - 176x132, 9 cells @ 12x12 with 14 border lines */
148 /* Internal dimensions of a cell */
149 #define CELL_WIDTH 12
150 #define CELL_HEIGHT 12
151 #define MARK_OFFS 1 /* Pixels between border and mark */
152 #define MARK_SPACE 2 /* Pixels between two marks */
153 #define MARK_SIZE 2 /* Mark width and height */
155 #elif ((LCD_HEIGHT==176) && (LCD_WIDTH==220))
156 /* Iriver h300, iPod Color/Photo - 220x176, 9 cells @ 16x16 with 14 border lines */
158 /* Internal dimensions of a cell */
159 #define CELL_WIDTH 16
160 #define CELL_HEIGHT 16
161 #define MARK_OFFS 1 /* Pixels between border and mark */
162 #define MARK_SPACE 1 /* Pixels between two marks */
163 #define MARK_SIZE 4 /* Mark width and height */
165 #elif (LCD_HEIGHT>=240) && (LCD_WIDTH>=320)
166 /* iPod Video - 320x240, 9 cells @ 24x24 with 14 border lines */
168 /* Internal dimensions of a cell */
169 #define CELL_WIDTH 24
170 #define CELL_HEIGHT 24
171 #define MARK_OFFS 1 /* Pixels between border and mark */
172 #define MARK_SPACE 2 /* Pixels between two marks */
173 #define MARK_SIZE 6 /* Mark width and height */
175 #else
176 #error SUDOKU: Unsupported LCD size
177 #endif
179 #else /* Vertical layout, scratchpad at the bottom */
180 #define VERTICAL_LAYOUT
182 #if ((LCD_HEIGHT==220) && (LCD_WIDTH==176))
183 /* e200, 9 cells @ 16x16 with 14 border lines */
185 /* Internal dimensions of a cell */
186 #define CELL_WIDTH 16
187 #define CELL_HEIGHT 16
188 #define MARK_OFFS 1 /* Pixels between border and mark */
189 #define MARK_SPACE 1 /* Pixels between two marks */
190 #define MARK_SIZE 4 /* Mark width and height */
192 #elif (LCD_HEIGHT>=320) && (LCD_WIDTH>=240)
193 /* Gigabeat - 240x320, 9 cells @ 24x24 with 14 border lines */
195 /* Internal dimensions of a cell */
196 #define CELL_WIDTH 24
197 #define CELL_HEIGHT 24
198 #define MARK_OFFS 1 /* Pixels between border and mark */
199 #define MARK_SPACE 2 /* Pixels between two marks */
200 #define MARK_SIZE 6 /* Mark width and height */
202 #else
203 #error SUDOKU: Unsupported LCD size
204 #endif
206 #endif /* Layout */
208 #ifdef SUDOKU_BUTTON_CHANGEDIR
209 int invertdir=0;
210 #else
211 #define invertdir 0
212 #endif
214 #define CFGFILE_VERSION 0 /* Current config file version */
215 #define CFGFILE_MINVERSION 0 /* Minimum config file version to accept */
217 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
218 /* settings */
219 struct sudoku_config {
220 #ifdef HAVE_LCD_COLOR
221 int number_display;
222 #endif
223 #ifdef SUDOKU_BUTTON_POSSIBLE
224 int show_markings;
225 #endif
228 struct sudoku_config sudcfg_disk = {
229 #ifdef HAVE_LCD_COLOR
231 #endif
232 #ifdef SUDOKU_BUTTON_POSSIBLE
234 #endif
236 struct sudoku_config sudcfg;
238 static const char cfg_filename[] = "sudoku.cfg";
239 #ifdef HAVE_LCD_COLOR
240 static char *number_str[2] = { "black", "coloured" };
241 #endif
242 #ifdef SUDOKU_BUTTON_POSSIBLE
243 static char *mark_str[2] = { "hide", "show" };
244 #endif
246 struct configdata disk_config[] = {
247 #ifdef HAVE_LCD_COLOR
248 { TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.number_display }, "numbers",
249 number_str },
250 #endif
251 #ifdef SUDOKU_BUTTON_POSSIBLE
252 { TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.show_markings }, "markings",
253 mark_str },
254 #endif
256 #endif
257 #ifdef HAVE_LCD_COLOR
258 #define NUMBER_TYPE (sudcfg.number_display*CELL_WIDTH)
259 #else
260 #define NUMBER_TYPE 0
261 #endif
263 /* Size dependent build-time calculations */
264 #ifdef SMALL_BOARD
265 #define BOARD_WIDTH (CELL_WIDTH*9+10)
266 #define BOARD_HEIGHT (CELL_HEIGHT*9+10)
267 static unsigned char cellxpos[9]={
268 1, (CELL_WIDTH+2), (2*CELL_WIDTH+3),
269 (3*CELL_WIDTH+4), (4*CELL_WIDTH+5), (5*CELL_WIDTH+6),
270 (6*CELL_WIDTH+7), (7*CELL_WIDTH+8), (8*CELL_WIDTH+9)
272 static unsigned char cellypos[9]={
273 1, (CELL_HEIGHT+2), (2*CELL_HEIGHT+3),
274 (3*CELL_HEIGHT+4), (4*CELL_HEIGHT+5), (5*CELL_HEIGHT+6),
275 (6*CELL_HEIGHT+7), (7*CELL_HEIGHT+8), (8*CELL_HEIGHT+9)
277 #else /* !SMALL_BOARD */
278 #define BOARD_WIDTH (CELL_WIDTH*9+10+4)
279 #define BOARD_HEIGHT (CELL_HEIGHT*9+10+4)
280 static unsigned char cellxpos[9]={
281 2, (CELL_WIDTH +3), (2*CELL_WIDTH +4),
282 (3*CELL_WIDTH +6), (4*CELL_WIDTH +7), (5*CELL_WIDTH +8),
283 (6*CELL_WIDTH+10), (7*CELL_WIDTH+11), (8*CELL_WIDTH+12)
285 static unsigned char cellypos[9]={
286 2, (CELL_HEIGHT +3), (2*CELL_HEIGHT +4),
287 (3*CELL_HEIGHT +6), (4*CELL_HEIGHT +7), (5*CELL_HEIGHT +8),
288 (6*CELL_HEIGHT+10), (7*CELL_HEIGHT+11), (8*CELL_HEIGHT+12)
290 #endif
292 #ifdef VERTICAL_LAYOUT
293 #define XOFS ((LCD_WIDTH-BOARD_WIDTH)/2)
294 #define YOFS ((LCD_HEIGHT-(BOARD_HEIGHT+CELL_HEIGHT*2+2))/2)
295 #define YOFSSCRATCHPAD (YOFS+BOARD_HEIGHT+CELL_WIDTH)
296 #else
297 #define XOFSSCRATCHPAD ((LCD_WIDTH-(BOARD_WIDTH+CELL_WIDTH*2+2))/2)
298 #define XOFS (XOFSSCRATCHPAD+CELL_WIDTH*2+2)
299 #define YOFS ((LCD_HEIGHT-BOARD_HEIGHT)/2)
300 #endif
302 /****** Solver routine by Tom Shackell <shackell@cs.york.ac.uk>
304 Downloaded from:
306 http://www-users.cs.york.ac.uk/~shackell/sudoku/Sudoku.html
308 Released under GPLv2
312 typedef unsigned int Bitset;
314 #define BLOCK 3
315 #define SIZE (BLOCK*BLOCK)
317 #define true 1
318 #define false 0
320 typedef struct _Sudoku {
321 Bitset table[SIZE][SIZE];
322 }Sudoku;
324 typedef struct _Stats {
325 int numTries;
326 int backTracks;
327 int numEmpty;
328 bool solutionFound;
329 }Stats;
331 typedef struct _Options {
332 bool allSolutions;
333 bool uniquenessCheck;
334 }Options;
336 void sudoku_init(Sudoku* sud);
337 void sudoku_set(Sudoku* sud, int x, int y, int num, bool original);
338 int sudoku_get(Sudoku* sud, int x, int y, bool* original);
340 #define BIT(n) ((Bitset)BIT_N(n))
341 #define BIT_TEST(v,n) ((((Bitset)v) & BIT(n)) != 0)
342 #define BIT_CLEAR(v,n) (v) &= ~BIT(n)
343 #define MARK_BIT BIT(0)
344 #define ORIGINAL_BIT BIT(SIZE+1)
346 #define ALL_BITS (BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8) | BIT(9))
348 /* initialize a sudoku problem, should be called before using set or get */
349 void sudoku_init(Sudoku* sud)
351 int y, x;
352 for (y = 0; y < SIZE; y++){
353 for (x = 0; x < SIZE; x++){
354 sud->table[x][y] = ALL_BITS;
359 /* set the number at a particular x and y column */
360 void sudoku_set(Sudoku* sud, int x, int y, int num, bool original)
362 int i, j;
363 int bx, by;
364 Bitset orig;
366 /* clear the row and columns */
367 for (i = 0; i < SIZE; i++){
368 BIT_CLEAR(sud->table[i][y], num);
369 BIT_CLEAR(sud->table[x][i], num);
371 /* clear the block */
372 bx = x - (x % BLOCK);
373 by = y - (y % BLOCK);
374 for (i = 0; i < BLOCK; i++){
375 for (j = 0; j < BLOCK; j++){
376 BIT_CLEAR(sud->table[bx+j][by+i], num);
379 /* mark the table */
380 orig = original ? ORIGINAL_BIT : 0;
381 sud->table[x][y] = BIT(num) | MARK_BIT | orig;
384 /* get the number at a particular x and y column, if this
385 is not unique return 0 */
386 int sudoku_get(Sudoku* sud, int x, int y, bool* original)
388 Bitset val = sud->table[x][y];
389 int result = 0;
390 int i;
392 if (original) {
393 *original = val & ORIGINAL_BIT;
395 for (i = 1; i <= SIZE; i++){
396 if (BIT_TEST(val, i)){
397 if (result != 0){
398 return 0;
400 result = i;
403 return result;
406 /* returns true if this is a valid problem, this is necessary because the input
407 problem might be degenerate which breaks the solver algorithm. */
408 static bool is_valid(const Sudoku* sud)
410 int x, y;
412 for (y = 0; y < SIZE; y++){
413 for (x = 0; x < SIZE; x++){
414 if ((sud->table[x][y] & ALL_BITS) == 0){
415 return false;
419 return true;
422 /* scan the table for the most constrained item, giving all it's options, sets
423 the best x and y coordinates, the number of options and the options for
424 that coordinate and returns true if the puzzle is finished */
425 static bool scan(const Sudoku* sud, int* rX, int* rY, int *num, int* options)
427 int x, y, i, j;
428 int bestCount = SIZE+1;
429 Bitset val;
430 bool allMarked = true;
432 for (y = 0; y < SIZE; y++){
433 for (x = 0; x < SIZE; x++){
434 Bitset val = sud->table[x][y];
435 int i;
436 int count = 0;
438 if (val & MARK_BIT) {
439 /* already set */
440 continue;
442 allMarked = false;
443 for (i = 1; i <= SIZE; i++){
444 if (BIT_TEST(val, i)){
445 count++;
448 if (count < bestCount){
449 bestCount = count;
450 *rX = x;
451 *rY = y;
452 if (count == 0){
453 /* can't possibly be beaten */
454 *num = 0;
455 return false;
460 /* now copy into options */
461 *num = bestCount;
462 val = sud->table[*rX][*rY];
463 for (i = 1, j = 0; i <= SIZE; i++){
464 if (BIT_TEST(val, i)){
465 options[j++] = i;
468 return allMarked;
471 static bool solve(Sudoku* sud, Stats* stats, const Options* options);
473 /* try a particular option and return true if that gives a solution or false
474 if it doesn't, restores board on backtracking */
475 static bool spawn_option(Sudoku* sud, Stats* stats, const Options* options,
476 int x, int y, int num)
478 Sudoku copy;
480 rb->memcpy(&copy,sud,sizeof(Sudoku));
481 sudoku_set(&copy, x, y, num, false);
482 stats->numTries += 1;
483 if (solve(&copy, stats, options)){
484 if (!options->allSolutions && stats->solutionFound){
485 rb->memcpy(sud,&copy,sizeof(Sudoku));
487 return true;
488 }else{
489 stats->backTracks++;
491 return false;
494 /* solve a sudoku problem, returns true if there is a solution and false
495 otherwise. stats is used to track statisticss */
496 static bool solve(Sudoku* sud, Stats* stats, const Options* options)
498 while (true){
499 int x = 0;
500 int y = 0;
501 int i, num;
502 int places[SIZE];
504 if (scan(sud, &x, &y, &num, places)){
505 /* a solution was found! */
506 if (options->uniquenessCheck && stats->solutionFound){
507 /*printf("\n\t... But the solution is not unique!\n"); */
508 return true;
510 stats->solutionFound = true;
511 if (options->allSolutions || options->uniquenessCheck){
512 /*printf("\n\tSolution after %d iterations\n", stats->numTries); */
513 /*sudoku_print(sud); */
514 return false;
516 else{
517 return true;
520 if (num == 0){
521 /* can't be satisfied */
522 return false;
524 /* try all the places (except the last one) */
525 for (i = 0; i < num-1; i++){
526 if (spawn_option(sud, stats, options, x, y, places[i])){
527 /* solution found! */
528 if (!options->allSolutions && stats->solutionFound){
529 return true;
533 /* take the last place ourself */
534 stats->numTries += 1;
535 sudoku_set(sud, x, y, places[num-1], false);
539 /******** END OF IMPORTED CODE */
542 /* A wrapper function between the Sudoku plugin and the above solver code */
543 void sudoku_solve(struct sudoku_state_t* state)
545 bool ret;
546 Stats stats;
547 Options options;
548 Sudoku sud;
549 bool original;
550 int r,c;
552 /* Initialise the parameters */
553 sudoku_init(&sud);
554 rb->memset(&stats,0,sizeof(stats));
555 options.allSolutions=false;
556 options.uniquenessCheck=false;
558 /* Convert Rockbox format into format for solver */
559 for (r=0;r<9;r++) {
560 for (c=0;c<9;c++) {
561 if (state->startboard[r][c]!='0') {
562 sudoku_set(&sud, c, r, state->startboard[r][c]-'0', true);
567 /* need to check for degenerate input problems ... */
568 if (is_valid(&sud)){
569 ret = solve(&sud, &stats, &options);
570 } else {
571 ret = false;
574 if (ret) {
575 /* Populate the board with the solution. */
576 for (r=0;r<9;r++) {
577 for (c=0;c<9;c++) {
578 state->currentboard[r][c]='0'+
579 sudoku_get(&sud, c, r, &original);
582 } else {
583 rb->splash(HZ*2, "Solve failed");
586 return;
589 /* Copies the current to the saved board */
590 static void save_state(struct sudoku_state_t *state)
592 rb->memcpy(state->savedboard, state->currentboard,
593 sizeof(state->savedboard));
596 /* Copies the saved to the current board */
597 static void restore_state(struct sudoku_state_t *state)
599 rb->memcpy(state->currentboard, state->savedboard,
600 sizeof(state->savedboard));
603 void default_state(struct sudoku_state_t* state)
605 int r,c;
607 rb->strncpy(state->filename,GAME_FILE,MAX_PATH);
608 for (r=0;r<9;r++) {
609 for (c=0;c<9;c++) {
610 state->startboard[r][c]=default_game[r][c];
611 state->currentboard[r][c]=default_game[r][c];
612 #ifdef SUDOKU_BUTTON_POSSIBLE
613 state->possiblevals[r][c]=0;
614 #endif
618 /* initialize the saved board so reload function works */
619 save_state(state);
621 state->x=0;
622 state->y=0;
623 state->editmode=0;
626 void clear_state(struct sudoku_state_t* state)
628 int r,c;
630 rb->strncpy(state->filename,GAME_FILE,MAX_PATH);
631 for (r=0;r<9;r++) {
632 for (c=0;c<9;c++) {
633 state->startboard[r][c]='0';
634 state->currentboard[r][c]='0';
635 #ifdef SUDOKU_BUTTON_POSSIBLE
636 state->possiblevals[r][c]=0;
637 #endif
641 state->x=0;
642 state->y=0;
643 state->editmode=0;
646 /* Check the status of the board, assuming a change at the cursor location */
647 bool check_status(struct sudoku_state_t* state)
649 int check[9];
650 int r,c;
651 int r1,c1;
652 int cell;
654 /* First, check the column */
655 for (cell=0;cell<9;cell++) {
656 check[cell]=0;
658 for (r=0;r<9;r++) {
659 cell=state->currentboard[r][state->x];
660 if (cell!='0') {
661 if (check[cell-'1']==1) {
662 return true;
664 check[cell-'1']=1;
668 /* Second, check the row */
669 for (cell=0;cell<9;cell++) {
670 check[cell]=0;
672 for (c=0;c<9;c++) {
673 cell=state->currentboard[state->y][c];
674 if (cell!='0') {
675 if (check[cell-'1']==1) {
676 return true;
678 check[cell-'1']=1;
682 /* Finally, check the 3x3 sub-grid */
683 for (cell=0;cell<9;cell++) {
684 check[cell]=0;
686 r1=(state->y/3)*3;
687 c1=(state->x/3)*3;
688 for (r=r1;r<r1+3;r++) {
689 for (c=c1;c<c1+3;c++) {
690 cell=state->currentboard[r][c];
691 if (cell!='0') {
692 if (check[cell-'1']==1) {
693 return true;
695 check[cell-'1']=1;
700 /* We passed all the checks :) */
702 return false;
705 /* Load game - only ".ss" is officially supported, but any sensible
706 text representation (one line per row) may load.
708 bool load_sudoku(struct sudoku_state_t* state, char* filename)
710 int fd;
711 size_t n;
712 int r = 0, c = 0;
713 unsigned int i;
714 int valid=0;
715 char buf[300]; /* A buffer to read a sudoku board from */
717 fd=rb->open(filename, O_RDONLY);
718 if (fd < 0) {
719 LOGF("Invalid sudoku file: %s\n",filename);
720 return(false);
723 rb->strncpy(state->filename,filename,MAX_PATH);
724 n=rb->read(fd,buf,300);
725 if (n <= 0) {
726 return(false);
728 rb->close(fd);
730 r=0;
731 c=0;
732 i=0;
733 while ((i < n) && (r < 9)) {
734 switch (buf[i]){
735 case ' ': case '\t':
736 if (c > 0)
737 valid=1;
738 break;
739 case '|':
740 case '*':
741 case '-':
742 case '\r':
743 break;
744 case '\n':
745 if (valid) {
746 r++;
747 valid=0;
749 c = 0;
750 break;
751 case '_': case '.':
752 valid=1;
753 if (c >= SIZE || r >= SIZE){
754 LOGF("ERROR: sudoku problem is the wrong size (%d,%d)\n",
755 c, r);
756 return(false);
758 c++;
759 break;
760 default:
761 if (((buf[i]>='A') && (buf[i]<='I')) ||
762 ((buf[i]>='0') && (buf[i]<='9'))) {
763 valid=1;
764 if (r >= SIZE || c >= SIZE){
765 LOGF("ERROR: sudoku problem is the wrong size "
766 "(%d,%d)\n", c, r);
767 return(false);
769 if ((buf[i]>='0') && (buf[i]<='9')) {
770 state->startboard[r][c]=buf[i];
771 state->currentboard[r][c]=buf[i];
772 } else {
773 state->currentboard[r][c]='1'+(buf[i]-'A');
775 c++;
777 /* Ignore any other characters */
778 break;
780 i++;
783 /* Check that the board is valid - we need to check every row/column
784 individually, so we check the diagonal from top-left to bottom-right */
785 for (state->x = 0; state->x < 9; state->x++) {
786 state->y = state->x;
787 if (check_status(state)) return false;
789 state->x = 0;
790 state->y = 0;
792 /* Save a copy of the saved state - so we can reload without using the
793 disk */
794 save_state(state);
795 return(true);
798 bool save_sudoku(struct sudoku_state_t* state)
800 int fd;
801 int r,c;
802 int i;
803 char line[13];
804 char sep[13];
806 rb->splash(0, "Saving...");
807 rb->memcpy(line,"...|...|...\r\n",13);
808 rb->memcpy(sep,"-----------\r\n",13);
810 if (state->filename[0]==0) {
811 return false;
814 fd=rb->open(state->filename, O_WRONLY|O_CREAT);
815 if (fd >= 0) {
816 for (r=0;r<9;r++) {
817 i=0;
818 for (c=0;c<9;c++) {
819 if (state->startboard[r][c]!='0') {
820 line[i]=state->startboard[r][c];
821 } else if (state->currentboard[r][c]!='0') {
822 line[i]='A'+(state->currentboard[r][c]-'1');
823 } else {
824 line[i]='.';
826 i++;
827 if ((c==2) || (c==5)) {
828 i++;
831 rb->write(fd,line,sizeof(line));
832 if ((r==2) || (r==5)) {
833 rb->write(fd,sep,sizeof(sep));
836 /* Add a blank line at end */
837 rb->write(fd,"\r\n",2);
838 rb->close(fd);
839 rb->reload_directory();
840 /* Save a copy of the saved state - so we can reload without
841 using the disk */
842 save_state(state);
843 return true;
844 } else {
845 return false;
849 void clear_board(struct sudoku_state_t* state)
851 int r,c;
853 for (r=0;r<9;r++) {
854 for (c=0;c<9;c++) {
855 state->currentboard[r][c]=state->startboard[r][c];
858 state->x=0;
859 state->y=0;
862 void update_cell(struct sudoku_state_t* state, int r, int c)
864 /* We have four types of cell:
865 1) User-entered number
866 2) Starting number
867 3) Cursor in cell
870 if ((r==state->y) && (c==state->x)) {
871 rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE,
872 BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
873 BITMAP_STRIDE,
874 XOFS+cellxpos[c],YOFS+cellypos[r],CELL_WIDTH,
875 CELL_HEIGHT);
876 } else {
877 if (state->startboard[r][c]!='0') {
878 rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE,
879 BITMAP_HEIGHT*(state->startboard[r][c]-'0'),
880 BITMAP_STRIDE,
881 XOFS+cellxpos[c],YOFS+cellypos[r],
882 CELL_WIDTH,CELL_HEIGHT);
883 } else {
884 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,
885 BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
886 BITMAP_STRIDE,
887 XOFS+cellxpos[c],YOFS+cellypos[r],
888 CELL_WIDTH,CELL_HEIGHT);
892 rb->lcd_update_rect(cellxpos[c],cellypos[r],CELL_WIDTH,CELL_HEIGHT);
896 void display_board(struct sudoku_state_t* state)
898 int r,c;
899 #ifdef SUDOKU_BUTTON_POSSIBLE
900 int i;
901 #endif
903 /* Clear the display buffer */
904 rb->lcd_clear_display();
906 /* Draw the gridlines - differently for different targets */
908 #ifdef SMALL_BOARD
909 /* Small targets - draw dotted/single lines */
910 for (r=0;r<9;r++) {
911 if ((r % 3)==0) {
912 /* Solid Line */
913 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
914 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
915 } else {
916 /* Dotted line */
917 for (c=XOFS;c<XOFS+BOARD_WIDTH;c+=2) {
918 rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
920 for (c=YOFS;c<YOFS+BOARD_HEIGHT;c+=2) {
921 rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
925 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
926 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
927 #else
928 /* Large targets - draw single/double lines */
929 for (r=0;r<9;r++) {
930 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
931 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
932 if ((r % 3)==0) {
933 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-2);
934 rb->lcd_vline(XOFS+cellxpos[r]-2,YOFS,YOFS+BOARD_HEIGHT-1);
937 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
938 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT+1);
939 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
940 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
941 #endif
943 #ifdef SUDOKU_BUTTON_POSSIBLE
944 #ifdef VERTICAL_LAYOUT
945 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD);
946 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD+CELL_HEIGHT+1);
947 for (r=0;r<9;r++) {
948 #ifdef SMALL_BOARD
949 /* Small targets - draw dotted/single lines */
950 if ((r % 3)==0) {
951 /* Solid Line */
952 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
953 YOFSSCRATCHPAD+CELL_HEIGHT+1);
954 } else {
955 /* Dotted line */
956 for (c=YOFSSCRATCHPAD;c<YOFSSCRATCHPAD+CELL_HEIGHT+1;c+=2) {
957 rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
960 #else
961 /* Large targets - draw single/double lines */
962 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
963 YOFSSCRATCHPAD+CELL_HEIGHT+1);
964 if ((r % 3)==0)
965 rb->lcd_vline(XOFS+cellxpos[r]-2,YOFSSCRATCHPAD,
966 YOFSSCRATCHPAD+CELL_HEIGHT+1);
967 #endif
968 if ((r>0) && state->possiblevals[state->y][state->x]&BIT_N(r))
969 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
970 BITMAP_STRIDE,XOFS+cellxpos[r-1],
971 YOFSSCRATCHPAD+1,CELL_WIDTH,CELL_HEIGHT);
973 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFSSCRATCHPAD,
974 YOFSSCRATCHPAD+CELL_HEIGHT+1);
975 #ifndef SMALL_BOARD
976 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFSSCRATCHPAD,
977 YOFSSCRATCHPAD+CELL_HEIGHT+1);
978 #endif
979 if (state->possiblevals[state->y][state->x]&BIT_N(r))
980 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
981 BITMAP_STRIDE,XOFS+cellxpos[8],YOFSSCRATCHPAD+1,
982 CELL_WIDTH,CELL_HEIGHT);
983 #else /* Horizontal layout */
984 rb->lcd_vline(XOFSSCRATCHPAD,YOFS,YOFS+BOARD_HEIGHT-1);
985 rb->lcd_vline(XOFSSCRATCHPAD+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
986 for (r=0;r<9;r++) {
987 #ifdef SMALL_BOARD
988 /* Small targets - draw dotted/single lines */
989 if ((r % 3)==0) {
990 /* Solid Line */
991 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
992 YOFS+cellypos[r]-1);
993 } else {
994 /* Dotted line */
995 for (c=XOFSSCRATCHPAD;c<XOFSSCRATCHPAD+CELL_WIDTH+1;c+=2) {
996 rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
999 #else
1000 /* Large targets - draw single/double lines */
1001 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
1002 YOFS+cellypos[r]-1);
1003 if ((r % 3)==0)
1004 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
1005 YOFS+cellypos[r]-2);
1006 #endif
1007 if ((r>0) && state->possiblevals[state->y][state->x]&BIT_N(r))
1008 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
1009 BITMAP_STRIDE,XOFSSCRATCHPAD+1,
1010 YOFS+cellypos[r-1],CELL_WIDTH,CELL_HEIGHT);
1012 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
1013 YOFS+cellypos[8]+CELL_HEIGHT);
1014 #ifndef SMALL_BOARD
1015 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
1016 YOFS+cellypos[8]+CELL_HEIGHT+1);
1017 #endif
1018 if (state->possiblevals[state->y][state->x]&BIT_N(r))
1019 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
1020 BITMAP_STRIDE,XOFSSCRATCHPAD+1,YOFS+cellypos[8],
1021 CELL_WIDTH,CELL_HEIGHT);
1022 #endif /* Layout */
1023 #endif /* SUDOKU_BUTTON_POSSIBLE */
1025 /* Draw the numbers */
1026 for (r=0;r<9;r++) {
1027 for (c=0;c<9;c++) {
1028 /* We have four types of cell:
1029 1) User-entered number
1030 2) Starting number
1031 3) Cursor in cell
1034 if ((r==state->y) && (c==state->x)) {
1035 rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE,
1036 BITMAP_HEIGHT*(state->currentboard[r][c]-
1037 '0'),
1038 BITMAP_STRIDE,
1039 XOFS+cellxpos[c],YOFS+cellypos[r],
1040 CELL_WIDTH,CELL_HEIGHT);
1041 } else {
1042 if (state->startboard[r][c]!='0') {
1043 rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE,
1044 BITMAP_HEIGHT*(state->startboard[r][c]-
1045 '0'),
1046 BITMAP_STRIDE,
1047 XOFS+cellxpos[c],YOFS+cellypos[r],
1048 CELL_WIDTH,CELL_HEIGHT);
1049 } else {
1050 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,
1051 BITMAP_HEIGHT*
1052 (state->currentboard[r][c]-'0'),
1053 BITMAP_STRIDE,
1054 XOFS+cellxpos[c],YOFS+cellypos[r],
1055 CELL_WIDTH,CELL_HEIGHT);
1058 #ifdef SUDOKU_BUTTON_POSSIBLE
1059 /* Draw the possible number markings on the board */
1060 if(sudcfg.show_markings && state->startboard[r][c]=='0'
1061 && state->currentboard[r][c]=='0') {
1062 for(i=0;i<9;i++) {
1063 if(state->possiblevals[r][c]&(2<<i)) {
1064 #if LCD_DEPTH > 1
1065 /* draw markings in dark grey */
1066 rb->lcd_set_foreground(LCD_DARKGRAY);
1067 #endif
1068 rb->lcd_fillrect(XOFS+cellxpos[c]+MARK_OFFS
1069 +(i%3)*(MARK_SIZE+MARK_SPACE),
1070 YOFS+cellypos[r]+MARK_OFFS
1071 +(i/3)*(MARK_SIZE+MARK_SPACE),
1072 MARK_SIZE,
1073 MARK_SIZE);
1074 #if LCD_DEPTH > 1
1075 rb->lcd_set_foreground(LCD_BLACK);
1076 #endif
1080 #endif /* SUDOKU_BUTTON_POSSIBLE */
1085 /* update the screen */
1086 rb->lcd_update();
1089 bool sudoku_generate(struct sudoku_state_t* state)
1091 char* difficulty;
1092 char str[80];
1093 bool res;
1094 struct sudoku_state_t new_state;
1096 clear_state(&new_state);
1097 display_board(&new_state);
1098 rb->splash(0, "Generating...");
1100 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1101 rb->cpu_boost(true);
1102 #endif
1104 res = sudoku_generate_board(&new_state,&difficulty);
1106 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1107 rb->cpu_boost(false);
1108 #endif
1110 if (res) {
1111 rb->memcpy(state,&new_state,sizeof(new_state));
1112 rb->snprintf(str,sizeof(str),"Difficulty: %s",difficulty);
1113 display_board(state);
1114 rb->splash(HZ*3, str);
1115 rb->strncpy(state->filename,GAME_FILE,MAX_PATH);
1116 } else {
1117 display_board(&new_state);
1118 rb->splash(HZ*2, "Aborted");
1120 /* initialize the saved board so reload function works */
1121 save_state(state);
1122 return res;
1125 #ifdef HAVE_LCD_COLOR
1126 static bool numdisplay_setting(void)
1128 static const struct opt_items names[] = {
1129 {"Black", -1},
1130 {"Coloured", -1},
1133 return rb->set_option("Number Display", &sudcfg.number_display, INT, names,
1134 sizeof(names) / sizeof(names[0]), NULL);
1136 #endif
1138 #ifdef SUDOKU_BUTTON_POSSIBLE
1139 static bool showmarkings_setting(void)
1141 static const struct opt_items names[] = {
1142 {"Hide", -1},
1143 {"Show", -1},
1146 return rb->set_option("Show Markings", &sudcfg.show_markings, INT, names,
1147 sizeof(names) / sizeof(names[0]), NULL);
1149 #endif
1151 enum {
1152 SM_AUDIO_PLAYBACK = 0,
1153 #ifdef HAVE_LCD_COLOR
1154 SM_NUMBER_DISPLAY,
1155 #endif
1156 #ifdef SUDOKU_BUTTON_POSSIBLE
1157 SM_SHOW_MARKINGS,
1158 #endif
1159 SM_SAVE,
1160 SM_RELOAD,
1161 SM_CLEAR,
1162 SM_SOLVE,
1163 SM_GENERATE,
1164 SM_NEW,
1165 SM_QUIT,
1168 bool sudoku_menu(struct sudoku_state_t* state)
1170 int m;
1171 int result;
1173 static const struct menu_item items[] = {
1174 [SM_AUDIO_PLAYBACK] = { "Audio Playback", NULL },
1175 #ifdef HAVE_LCD_COLOR
1176 [SM_NUMBER_DISPLAY] = { "Number Display", NULL },
1177 #endif
1178 #ifdef SUDOKU_BUTTON_POSSIBLE
1179 [SM_SHOW_MARKINGS] = { "Show Markings", NULL },
1180 #endif
1181 [SM_SAVE] = { "Save", NULL },
1182 [SM_RELOAD] = { "Reload", NULL },
1183 [SM_CLEAR] = { "Clear", NULL },
1184 [SM_SOLVE] = { "Solve", NULL },
1185 [SM_GENERATE] = { "Generate", NULL },
1186 [SM_NEW] = { "New", NULL },
1187 [SM_QUIT] = { "Quit", NULL },
1190 m = menu_init(items, sizeof(items) / sizeof(*items),
1191 NULL, NULL, NULL, NULL);
1193 result=menu_show(m);
1195 switch (result) {
1196 case SM_AUDIO_PLAYBACK:
1197 playback_control(NULL);
1198 break;
1200 #ifdef HAVE_LCD_COLOR
1201 case SM_NUMBER_DISPLAY:
1202 numdisplay_setting();
1203 break;
1204 #endif
1206 #ifdef SUDOKU_BUTTON_POSSIBLE
1207 case SM_SHOW_MARKINGS:
1208 showmarkings_setting();
1209 break;
1210 #endif
1211 case SM_SAVE:
1212 save_sudoku(state);
1213 break;
1215 case SM_RELOAD:
1216 restore_state(state);
1217 break;
1219 case SM_CLEAR:
1220 clear_board(state);
1221 break;
1223 case SM_SOLVE:
1224 sudoku_solve(state);
1225 break;
1227 case SM_GENERATE:
1228 sudoku_generate(state);
1229 break;
1231 case SM_NEW:
1232 clear_state(state);
1233 state->editmode=1;
1234 break;
1236 case SM_QUIT:
1237 save_sudoku(state);
1238 menu_exit(m);
1239 return true;
1240 break;
1242 default:
1243 break;
1246 menu_exit(m);
1248 return (result==MENU_ATTACHED_USB);
1251 /* Menu used when user is in edit mode - i.e. creating a new game manually */
1252 int sudoku_edit_menu(struct sudoku_state_t* state)
1254 int m;
1255 int result;
1257 static const struct menu_item items[] = {
1258 { "Save as", NULL },
1259 { "Quit", NULL },
1262 m = menu_init(items, sizeof(items) / sizeof(*items),
1263 NULL, NULL, NULL, NULL);
1265 result=menu_show(m);
1267 switch (result) {
1268 case 0: /* Save new game */
1269 rb->kbd_input(state->filename,MAX_PATH);
1270 if (save_sudoku(state)) {
1271 state->editmode=0;
1272 } else {
1273 rb->splash(HZ*2, "Save failed");
1275 break;
1277 case 1: /* Quit */
1278 break;
1280 default:
1281 break;
1284 menu_exit(m);
1286 return result;
1289 void move_cursor(struct sudoku_state_t* state, int newx, int newy)
1291 int oldx, oldy;
1293 /* Check that the character at the cursor position is legal */
1294 if (check_status(state)) {
1295 rb->splash(HZ*2, "Illegal move!");
1296 /* Ignore any button presses during the splash */
1297 rb->button_clear_queue();
1298 return;
1301 /* Move Cursor */
1302 oldx=state->x;
1303 oldy=state->y;
1304 state->x=newx;
1305 state->y=newy;
1307 /* Redraw current and old cells */
1308 update_cell(state,oldx,oldy);
1309 update_cell(state,newx,newy);
1312 /* plugin entry point */
1313 enum plugin_status plugin_start(const void* parameter)
1315 bool exit;
1316 int button;
1317 int lastbutton = BUTTON_NONE;
1318 int res;
1319 int rc = PLUGIN_OK;
1320 long ticks;
1321 struct sudoku_state_t state;
1323 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
1324 configfile_load(cfg_filename, disk_config,
1325 sizeof(disk_config) / sizeof(disk_config[0]),
1326 CFGFILE_MINVERSION);
1327 rb->memcpy(&sudcfg, &sudcfg_disk, sizeof(sudcfg)); /* copy to running config */
1328 #endif
1330 #if LCD_DEPTH > 1
1331 rb->lcd_set_backdrop(NULL);
1332 rb->lcd_set_foreground(LCD_BLACK);
1333 rb->lcd_set_background(LCD_WHITE);
1334 #endif
1336 clear_state(&state);
1338 if (parameter==NULL) {
1339 /* We have been started as a plugin - try default sudoku.ss */
1340 if (!load_sudoku(&state,GAME_FILE)) {
1341 /* No previous game saved, use the default */
1342 default_state(&state);
1344 } else {
1345 if (!load_sudoku(&state,(char*)parameter)) {
1346 rb->splash(HZ*2, "Load error");
1347 return(PLUGIN_ERROR);
1352 display_board(&state);
1354 /* The main game loop */
1355 exit=false;
1356 ticks=0;
1357 while(!exit) {
1358 button = rb->button_get(true);
1360 switch(button){
1361 #ifdef SUDOKU_BUTTON_QUIT
1362 /* Exit game */
1363 case SUDOKU_BUTTON_QUIT:
1364 if (check_status(&state)) {
1365 rb->splash(HZ*2, "Illegal move!");
1366 /* Ignore any button presses during the splash */
1367 rb->button_clear_queue();
1368 } else {
1369 save_sudoku(&state);
1370 exit=true;
1372 break;
1373 #endif
1375 /* Increment digit */
1376 #ifdef SUDOKU_BUTTON_ALTTOGGLE
1377 case SUDOKU_BUTTON_ALTTOGGLE | BUTTON_REPEAT:
1378 #endif
1379 case SUDOKU_BUTTON_TOGGLE | BUTTON_REPEAT:
1380 /* Slow down the repeat speed to 1/3 second */
1381 if ((*rb->current_tick-ticks) < (HZ/3)) {
1382 break;
1385 #ifdef SUDOKU_BUTTON_ALTTOGGLE
1386 case SUDOKU_BUTTON_ALTTOGGLE:
1387 #endif
1388 case SUDOKU_BUTTON_TOGGLE:
1389 #ifdef SUDOKU_BUTTON_TOGGLE_PRE
1390 if ((button == SUDOKU_BUTTON_TOGGLE)
1391 && (lastbutton != SUDOKU_BUTTON_TOGGLE_PRE))
1392 break;
1393 #endif
1394 /* Increment digit */
1395 ticks=*rb->current_tick;
1396 if (state.editmode) {
1397 if (state.startboard[state.y][state.x]=='9') {
1398 state.startboard[state.y][state.x]='0';
1399 state.currentboard[state.y][state.x]='0';
1400 } else {
1401 state.startboard[state.y][state.x]++;
1402 state.currentboard[state.y][state.x]++;
1404 } else {
1405 if (state.startboard[state.y][state.x]=='0') {
1406 if (state.currentboard[state.y][state.x]=='9') {
1407 state.currentboard[state.y][state.x]='0';
1408 } else {
1409 state.currentboard[state.y][state.x]++;
1413 update_cell(&state,state.y,state.x);
1414 break;
1416 #ifdef SUDOKU_BUTTON_TOGGLEBACK
1417 case SUDOKU_BUTTON_TOGGLEBACK | BUTTON_REPEAT:
1418 /* Slow down the repeat speed to 1/3 second */
1419 if ((*rb->current_tick-ticks) < (HZ/3)) {
1420 break;
1423 case SUDOKU_BUTTON_TOGGLEBACK:
1424 /* Decrement digit */
1425 ticks=*rb->current_tick;
1426 if (state.editmode) {
1427 if (state.startboard[state.y][state.x]=='0') {
1428 state.startboard[state.y][state.x]='9';
1429 state.currentboard[state.y][state.x]='9';
1430 } else {
1431 state.startboard[state.y][state.x]--;
1432 state.currentboard[state.y][state.x]--;
1434 } else {
1435 if (state.startboard[state.y][state.x]=='0') {
1436 if (state.currentboard[state.y][state.x]=='0') {
1437 state.currentboard[state.y][state.x]='9';
1438 } else {
1439 state.currentboard[state.y][state.x]--;
1443 update_cell(&state,state.y,state.x);
1444 break;
1445 #endif
1447 /* move cursor left */
1448 case SUDOKU_BUTTON_LEFT:
1449 case (SUDOKU_BUTTON_LEFT | BUTTON_REPEAT):
1450 if ( (state.x==0&&invertdir==0) || (state.y==0&&invertdir==1) ) {
1451 #ifndef SUDOKU_BUTTON_UP
1452 if ( (state.y==0&&invertdir==0) || (state.x==0&&invertdir==1)) {
1453 move_cursor(&state,8,8);
1454 } else {
1455 if (invertdir==0) {
1456 move_cursor(&state,8,state.y-1);
1457 } else {
1458 move_cursor(&state,state.x-1,8);
1461 #else
1462 move_cursor(&state,8,state.y);
1463 #endif
1464 } else {
1465 if (invertdir==0) {
1466 move_cursor(&state,state.x-1,state.y);
1467 } else {
1468 move_cursor(&state,state.x,state.y-1);
1471 break;
1473 /* move cursor right */
1474 case SUDOKU_BUTTON_RIGHT:
1475 case (SUDOKU_BUTTON_RIGHT | BUTTON_REPEAT):
1476 if ( (state.x==8&&invertdir==0) || (state.y==8&&invertdir==1) ) {
1477 #ifndef SUDOKU_BUTTON_DOWN
1478 if ( (state.y==8&&invertdir==0) || (state.x==8&&invertdir==1) ) {
1479 move_cursor(&state,0,0);
1480 } else {
1481 if (invertdir==0) {
1482 move_cursor(&state,0,state.y+1);
1483 } else {
1484 move_cursor(&state,state.x+1,0);
1487 #else
1488 move_cursor(&state,0,state.y);
1489 #endif
1490 } else {
1491 if (invertdir==0) {
1492 move_cursor(&state,state.x+1,state.y);
1493 } else {
1494 move_cursor(&state,state.x,state.y+1);
1497 break;
1499 #ifdef SUDOKU_BUTTON_UP
1500 /* move cursor up */
1501 case SUDOKU_BUTTON_UP:
1502 case (SUDOKU_BUTTON_UP | BUTTON_REPEAT):
1503 if (state.y==0) {
1504 move_cursor(&state,state.x,8);
1505 } else {
1506 move_cursor(&state,state.x,state.y-1);
1508 break;
1509 #endif
1511 #ifdef SUDOKU_BUTTON_DOWN
1512 /* move cursor down */
1513 case SUDOKU_BUTTON_DOWN:
1514 case (SUDOKU_BUTTON_DOWN | BUTTON_REPEAT):
1515 if (state.y==8) {
1516 move_cursor(&state,state.x,0);
1517 } else {
1518 move_cursor(&state,state.x,state.y+1);
1520 break;
1521 #endif
1523 case SUDOKU_BUTTON_MENU:
1524 #ifdef SUDOKU_BUTTON_MENU_PRE
1525 if (lastbutton != SUDOKU_BUTTON_MENU_PRE)
1526 break;
1527 #endif
1528 /* Don't let the user leave a game in a bad state */
1529 if (check_status(&state)) {
1530 rb->splash(HZ*2, "Illegal move!");
1531 /* Ignore any button presses during the splash */
1532 rb->button_clear_queue();
1533 } else {
1534 if (state.editmode) {
1535 res = sudoku_edit_menu(&state);
1536 if (res == MENU_ATTACHED_USB) {
1537 rc = PLUGIN_USB_CONNECTED;
1538 exit = true;
1539 } else if (res == 1) { /* Quit */
1540 exit = true;
1542 } else {
1543 if (sudoku_menu(&state)) {
1544 rc = PLUGIN_USB_CONNECTED;
1545 exit = true;
1549 break;
1550 #ifdef SUDOKU_BUTTON_POSSIBLE
1551 case SUDOKU_BUTTON_POSSIBLE:
1552 /* Toggle current number in the possiblevals structure */
1553 if (state.currentboard[state.y][state.x]!='0') {
1554 state.possiblevals[state.y][state.x]^=
1555 BIT_N(state.currentboard[state.y][state.x] - '0');
1557 break;
1558 #endif
1560 #ifdef SUDOKU_BUTTON_CHANGEDIR
1561 case SUDOKU_BUTTON_CHANGEDIR:
1562 /* Change scroll wheel direction */
1563 invertdir=!invertdir;
1564 break;
1565 #endif
1566 default:
1567 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
1568 /* Quit if USB has been connected */
1569 rc = PLUGIN_USB_CONNECTED;
1570 exit = true;
1572 break;
1574 if (button != BUTTON_NONE)
1575 lastbutton = button;
1577 display_board(&state);
1579 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
1580 if (rb->memcmp(&sudcfg, &sudcfg_disk, sizeof(sudcfg))) /* save settings if changed */
1582 rb->memcpy(&sudcfg_disk, &sudcfg, sizeof(sudcfg));
1583 configfile_save(cfg_filename, disk_config,
1584 sizeof(disk_config) / sizeof(disk_config[0]),
1585 CFGFILE_VERSION);
1587 #endif
1588 return rc;
1591 #endif