New makefile solution: A single invocation of 'make' to build the entire tree. Fully...
[kugel-rb.git] / apps / plugins / sudoku / sudoku.c
blob4430778cac045341c360c8193560d7b4ec2132d7
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 /* here is a global api struct pointer. while not strictly necessary,
82 it's nice not to have to pass the api pointer in all function calls
83 in the plugin */
85 const struct plugin_api* rb;
87 /* Default game - used to initialise sudoku.ss if it doesn't exist. */
88 static const char default_game[9][9] =
90 { '0','1','0', '3','0','7', '0','0','4' },
91 { '0','0','0', '0','6','0', '1','0','2' },
92 { '0','0','0', '0','8','0', '5','6','0' },
94 { '0','6','0', '0','0','0', '0','2','9' },
95 { '0','0','0', '5','0','3', '0','0','0' },
96 { '7','9','0', '0','0','0', '0','3','0' },
98 { '0','8','5', '0','3','0', '0','0','0' },
99 { '1','0','2', '0','7','0', '0','0','0' },
100 { '0','0','0', '4','0','8', '0','5','0' },
103 #if LCD_HEIGHT <= LCD_WIDTH /* Horizontal layout, scratchpad at the left */
105 #if (LCD_HEIGHT==64) && (LCD_WIDTH==112)
106 /* Archos Recorders and Ondios - 112x64, 9 cells @ 8x6 with 10 border lines */
108 /* Internal dimensions of a cell */
109 #define CELL_WIDTH 8
110 #define CELL_HEIGHT 6
111 #define SMALL_BOARD
112 #define MARK_OFFS 1 /* Pixels between border and mark */
113 #define MARK_SPACE 1 /* Pixels between two marks */
114 #define MARK_SIZE 1 /* Mark width and height */
116 #elif ((LCD_HEIGHT==80) && (LCD_WIDTH==132))
117 /* C200, 9 cells @ 8x8 with 8 border lines */
119 /* Internal dimensions of a cell */
120 #define CELL_WIDTH 8
121 #define CELL_HEIGHT 8
122 #define SMALL_BOARD
123 #define MARK_OFFS 1 /* Pixels between border and mark */
124 #define MARK_SPACE 1 /* Pixels between two marks */
125 #define MARK_SIZE 1 /* Mark width and height */
127 #elif ((LCD_HEIGHT==96) && (LCD_WIDTH==128))
128 /* iAudio M3, 9 cells @ 9x9 with 14 border lines */
130 /* Internal dimensions of a cell */
131 #define CELL_WIDTH 9
132 #define CELL_HEIGHT 9
133 #define MARK_OFFS 1 /* Pixels between border and mark */
134 #define MARK_SPACE 2 /* Pixels between two marks */
135 #define MARK_SIZE 1 /* Mark width and height */
137 #elif (LCD_HEIGHT==110) && (LCD_WIDTH==138) \
138 || (LCD_HEIGHT==128) && (LCD_WIDTH==128)
139 /* iPod Mini - 138x110, 9 cells @ 10x10 with 14 border lines */
140 /* iriver H10 5-6GB - 128x128, 9 cells @ 10x10 with 14 border lines */
142 /* Internal dimensions of a cell */
143 #define CELL_WIDTH 10
144 #define CELL_HEIGHT 10
145 #define MARK_OFFS 1 /* Pixels between border and mark */
146 #define MARK_SPACE 1 /* Pixels between two marks */
147 #define MARK_SIZE 2 /* Mark width and height */
149 #elif ((LCD_HEIGHT==128) && (LCD_WIDTH==160)) \
150 || ((LCD_HEIGHT==132) && (LCD_WIDTH==176))
151 /* iAudio X5, Iriver H1x0, iPod G3, G4 - 160x128; */
152 /* iPod Nano - 176x132, 9 cells @ 12x12 with 14 border lines */
154 /* Internal dimensions of a cell */
155 #define CELL_WIDTH 12
156 #define CELL_HEIGHT 12
157 #define MARK_OFFS 1 /* Pixels between border and mark */
158 #define MARK_SPACE 2 /* Pixels between two marks */
159 #define MARK_SIZE 2 /* Mark width and height */
161 #elif ((LCD_HEIGHT==176) && (LCD_WIDTH==220))
162 /* Iriver h300, iPod Color/Photo - 220x176, 9 cells @ 16x16 with 14 border lines */
164 /* Internal dimensions of a cell */
165 #define CELL_WIDTH 16
166 #define CELL_HEIGHT 16
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>=240) && (LCD_WIDTH>=320)
172 /* iPod Video - 320x240, 9 cells @ 24x24 with 14 border lines */
174 /* Internal dimensions of a cell */
175 #define CELL_WIDTH 24
176 #define CELL_HEIGHT 24
177 #define MARK_OFFS 1 /* Pixels between border and mark */
178 #define MARK_SPACE 2 /* Pixels between two marks */
179 #define MARK_SIZE 6 /* Mark width and height */
181 #else
182 #error SUDOKU: Unsupported LCD size
183 #endif
185 #else /* Vertical layout, scratchpad at the bottom */
186 #define VERTICAL_LAYOUT
188 #if ((LCD_HEIGHT==220) && (LCD_WIDTH==176))
189 /* e200, 9 cells @ 16x16 with 14 border lines */
191 /* Internal dimensions of a cell */
192 #define CELL_WIDTH 16
193 #define CELL_HEIGHT 16
194 #define MARK_OFFS 1 /* Pixels between border and mark */
195 #define MARK_SPACE 1 /* Pixels between two marks */
196 #define MARK_SIZE 4 /* Mark width and height */
198 #elif (LCD_HEIGHT>=320) && (LCD_WIDTH>=240)
199 /* Gigabeat - 240x320, 9 cells @ 24x24 with 14 border lines */
201 /* Internal dimensions of a cell */
202 #define CELL_WIDTH 24
203 #define CELL_HEIGHT 24
204 #define MARK_OFFS 1 /* Pixels between border and mark */
205 #define MARK_SPACE 2 /* Pixels between two marks */
206 #define MARK_SIZE 6 /* Mark width and height */
208 #else
209 #error SUDOKU: Unsupported LCD size
210 #endif
212 #endif /* Layout */
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, &sudcfg_disk.number_display, "numbers", number_str, NULL },
249 #endif
250 #ifdef SUDOKU_BUTTON_POSSIBLE
251 { TYPE_ENUM, 0, 2, &sudcfg_disk.show_markings, "markings", mark_str, NULL },
252 #endif
254 #endif
255 #ifdef HAVE_LCD_COLOR
256 #define NUMBER_TYPE (sudcfg.number_display*CELL_WIDTH)
257 #else
258 #define NUMBER_TYPE 0
259 #endif
261 /* Size dependent build-time calculations */
262 #ifdef SMALL_BOARD
263 #define BOARD_WIDTH (CELL_WIDTH*9+10)
264 #define BOARD_HEIGHT (CELL_HEIGHT*9+10)
265 static unsigned char cellxpos[9]={
266 1, (CELL_WIDTH+2), (2*CELL_WIDTH+3),
267 (3*CELL_WIDTH+4), (4*CELL_WIDTH+5), (5*CELL_WIDTH+6),
268 (6*CELL_WIDTH+7), (7*CELL_WIDTH+8), (8*CELL_WIDTH+9)
270 static unsigned char cellypos[9]={
271 1, (CELL_HEIGHT+2), (2*CELL_HEIGHT+3),
272 (3*CELL_HEIGHT+4), (4*CELL_HEIGHT+5), (5*CELL_HEIGHT+6),
273 (6*CELL_HEIGHT+7), (7*CELL_HEIGHT+8), (8*CELL_HEIGHT+9)
275 #else /* !SMALL_BOARD */
276 #define BOARD_WIDTH (CELL_WIDTH*9+10+4)
277 #define BOARD_HEIGHT (CELL_HEIGHT*9+10+4)
278 static unsigned char cellxpos[9]={
279 2, (CELL_WIDTH +3), (2*CELL_WIDTH +4),
280 (3*CELL_WIDTH +6), (4*CELL_WIDTH +7), (5*CELL_WIDTH +8),
281 (6*CELL_WIDTH+10), (7*CELL_WIDTH+11), (8*CELL_WIDTH+12)
283 static unsigned char cellypos[9]={
284 2, (CELL_HEIGHT +3), (2*CELL_HEIGHT +4),
285 (3*CELL_HEIGHT +6), (4*CELL_HEIGHT +7), (5*CELL_HEIGHT +8),
286 (6*CELL_HEIGHT+10), (7*CELL_HEIGHT+11), (8*CELL_HEIGHT+12)
288 #endif
290 #ifdef VERTICAL_LAYOUT
291 #define XOFS ((LCD_WIDTH-BOARD_WIDTH)/2)
292 #define YOFS ((LCD_HEIGHT-(BOARD_HEIGHT+CELL_HEIGHT*2+2))/2)
293 #define YOFSSCRATCHPAD (YOFS+BOARD_HEIGHT+CELL_WIDTH)
294 #else
295 #define XOFSSCRATCHPAD ((LCD_WIDTH-(BOARD_WIDTH+CELL_WIDTH*2+2))/2)
296 #define XOFS (XOFSSCRATCHPAD+CELL_WIDTH*2+2)
297 #define YOFS ((LCD_HEIGHT-BOARD_HEIGHT)/2)
298 #endif
300 /****** Solver routine by Tom Shackell <shackell@cs.york.ac.uk>
302 Downloaded from:
304 http://www-users.cs.york.ac.uk/~shackell/sudoku/Sudoku.html
306 Released under GPLv2
310 typedef unsigned int Bitset;
312 #define BLOCK 3
313 #define SIZE (BLOCK*BLOCK)
315 #define true 1
316 #define false 0
318 typedef struct _Sudoku {
319 Bitset table[SIZE][SIZE];
320 }Sudoku;
322 typedef struct _Stats {
323 int numTries;
324 int backTracks;
325 int numEmpty;
326 bool solutionFound;
327 }Stats;
329 typedef struct _Options {
330 bool allSolutions;
331 bool uniquenessCheck;
332 }Options;
334 void sudoku_init(Sudoku* sud);
335 void sudoku_set(Sudoku* sud, int x, int y, int num, bool original);
336 int sudoku_get(Sudoku* sud, int x, int y, bool* original);
338 #define BIT(n) ((Bitset)(1<<(n)))
339 #define BIT_TEST(v,n) ((((Bitset)v) & BIT(n)) != 0)
340 #define BIT_CLEAR(v,n) (v) &= ~BIT(n)
341 #define MARK_BIT BIT(0)
342 #define ORIGINAL_BIT BIT(SIZE+1)
344 #define ALL_BITS (BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8) | BIT(9))
346 /* initialize a sudoku problem, should be called before using set or get */
347 void sudoku_init(Sudoku* sud)
349 int y, x;
350 for (y = 0; y < SIZE; y++){
351 for (x = 0; x < SIZE; x++){
352 sud->table[x][y] = ALL_BITS;
357 /* set the number at a particular x and y column */
358 void sudoku_set(Sudoku* sud, int x, int y, int num, bool original)
360 int i, j;
361 int bx, by;
362 Bitset orig;
364 /* clear the row and columns */
365 for (i = 0; i < SIZE; i++){
366 BIT_CLEAR(sud->table[i][y], num);
367 BIT_CLEAR(sud->table[x][i], num);
369 /* clear the block */
370 bx = x - (x % BLOCK);
371 by = y - (y % BLOCK);
372 for (i = 0; i < BLOCK; i++){
373 for (j = 0; j < BLOCK; j++){
374 BIT_CLEAR(sud->table[bx+j][by+i], num);
377 /* mark the table */
378 orig = original ? ORIGINAL_BIT : 0;
379 sud->table[x][y] = BIT(num) | MARK_BIT | orig;
382 /* get the number at a particular x and y column, if this
383 is not unique return 0 */
384 int sudoku_get(Sudoku* sud, int x, int y, bool* original)
386 Bitset val = sud->table[x][y];
387 int result = 0;
388 int i;
390 if (original) {
391 *original = val & ORIGINAL_BIT;
393 for (i = 1; i <= SIZE; i++){
394 if (BIT_TEST(val, i)){
395 if (result != 0){
396 return 0;
398 result = i;
401 return result;
404 /* returns true if this is a valid problem, this is necessary because the input
405 problem might be degenerate which breaks the solver algorithm. */
406 static bool is_valid(const Sudoku* sud)
408 int x, y;
410 for (y = 0; y < SIZE; y++){
411 for (x = 0; x < SIZE; x++){
412 if ((sud->table[x][y] & ALL_BITS) == 0){
413 return false;
417 return true;
420 /* scan the table for the most constrained item, giving all it's options, sets
421 the best x and y coordinates, the number of options and the options for
422 that coordinate and returns true if the puzzle is finished */
423 static bool scan(const Sudoku* sud, int* rX, int* rY, int *num, int* options)
425 int x, y, i, j;
426 int bestCount = SIZE+1;
427 Bitset val;
428 bool allMarked = true;
430 for (y = 0; y < SIZE; y++){
431 for (x = 0; x < SIZE; x++){
432 Bitset val = sud->table[x][y];
433 int i;
434 int count = 0;
436 if (val & MARK_BIT) {
437 /* already set */
438 continue;
440 allMarked = false;
441 for (i = 1; i <= SIZE; i++){
442 if (BIT_TEST(val, i)){
443 count++;
446 if (count < bestCount){
447 bestCount = count;
448 *rX = x;
449 *rY = y;
450 if (count == 0){
451 /* can't possibly be beaten */
452 *num = 0;
453 return false;
458 /* now copy into options */
459 *num = bestCount;
460 val = sud->table[*rX][*rY];
461 for (i = 1, j = 0; i <= SIZE; i++){
462 if (BIT_TEST(val, i)){
463 options[j++] = i;
466 return allMarked;
469 static bool solve(Sudoku* sud, Stats* stats, const Options* options);
471 /* try a particular option and return true if that gives a solution or false
472 if it doesn't, restores board on backtracking */
473 static bool spawn_option(Sudoku* sud, Stats* stats, const Options* options,
474 int x, int y, int num)
476 Sudoku copy;
478 rb->memcpy(&copy,sud,sizeof(Sudoku));
479 sudoku_set(&copy, x, y, num, false);
480 stats->numTries += 1;
481 if (solve(&copy, stats, options)){
482 if (!options->allSolutions && stats->solutionFound){
483 rb->memcpy(sud,&copy,sizeof(Sudoku));
485 return true;
486 }else{
487 stats->backTracks++;
489 return false;
492 /* solve a sudoku problem, returns true if there is a solution and false
493 otherwise. stats is used to track statisticss */
494 static bool solve(Sudoku* sud, Stats* stats, const Options* options)
496 while (true){
497 int x = 0;
498 int y = 0;
499 int i, num;
500 int places[SIZE];
502 if (scan(sud, &x, &y, &num, places)){
503 /* a solution was found! */
504 if (options->uniquenessCheck && stats->solutionFound){
505 /*printf("\n\t... But the solution is not unique!\n"); */
506 return true;
508 stats->solutionFound = true;
509 if (options->allSolutions || options->uniquenessCheck){
510 /*printf("\n\tSolution after %d iterations\n", stats->numTries); */
511 /*sudoku_print(sud); */
512 return false;
514 else{
515 return true;
518 if (num == 0){
519 /* can't be satisfied */
520 return false;
522 /* try all the places (except the last one) */
523 for (i = 0; i < num-1; i++){
524 if (spawn_option(sud, stats, options, x, y, places[i])){
525 /* solution found! */
526 if (!options->allSolutions && stats->solutionFound){
527 return true;
531 /* take the last place ourself */
532 stats->numTries += 1;
533 sudoku_set(sud, x, y, places[num-1], false);
537 /******** END OF IMPORTED CODE */
540 /* A wrapper function between the Sudoku plugin and the above solver code */
541 void sudoku_solve(struct sudoku_state_t* state)
543 bool ret;
544 Stats stats;
545 Options options;
546 Sudoku sud;
547 bool original;
548 int r,c;
550 /* Initialise the parameters */
551 sudoku_init(&sud);
552 rb->memset(&stats,0,sizeof(stats));
553 options.allSolutions=false;
554 options.uniquenessCheck=false;
556 /* Convert Rockbox format into format for solver */
557 for (r=0;r<9;r++) {
558 for (c=0;c<9;c++) {
559 if (state->startboard[r][c]!='0') {
560 sudoku_set(&sud, c, r, state->startboard[r][c]-'0', true);
565 /* need to check for degenerate input problems ... */
566 if (is_valid(&sud)){
567 ret = solve(&sud, &stats, &options);
568 } else {
569 ret = false;
572 if (ret) {
573 /* Populate the board with the solution. */
574 for (r=0;r<9;r++) {
575 for (c=0;c<9;c++) {
576 state->currentboard[r][c]='0'+
577 sudoku_get(&sud, c, r, &original);
580 } else {
581 rb->splash(HZ*2, "Solve failed");
584 return;
587 /* Copies the current to the saved board */
588 static void save_state(struct sudoku_state_t *state)
590 rb->memcpy(state->savedboard, state->currentboard,
591 sizeof(state->savedboard));
594 /* Copies the saved to the current board */
595 static void restore_state(struct sudoku_state_t *state)
597 rb->memcpy(state->currentboard, state->savedboard,
598 sizeof(state->savedboard));
601 void default_state(struct sudoku_state_t* state)
603 int r,c;
605 rb->strncpy(state->filename,GAME_FILE,MAX_PATH);
606 for (r=0;r<9;r++) {
607 for (c=0;c<9;c++) {
608 state->startboard[r][c]=default_game[r][c];
609 state->currentboard[r][c]=default_game[r][c];
610 #ifdef SUDOKU_BUTTON_POSSIBLE
611 state->possiblevals[r][c]=0;
612 #endif
616 /* initialize the saved board so reload function works */
617 save_state(state);
619 state->x=0;
620 state->y=0;
621 state->editmode=0;
624 void clear_state(struct sudoku_state_t* state)
626 int r,c;
628 rb->strncpy(state->filename,GAME_FILE,MAX_PATH);
629 for (r=0;r<9;r++) {
630 for (c=0;c<9;c++) {
631 state->startboard[r][c]='0';
632 state->currentboard[r][c]='0';
633 #ifdef SUDOKU_BUTTON_POSSIBLE
634 state->possiblevals[r][c]=0;
635 #endif
639 state->x=0;
640 state->y=0;
641 state->editmode=0;
644 /* Check the status of the board, assuming a change at the cursor location */
645 bool check_status(struct sudoku_state_t* state)
647 int check[9];
648 int r,c;
649 int r1,c1;
650 int cell;
652 /* First, check the column */
653 for (cell=0;cell<9;cell++) {
654 check[cell]=0;
656 for (r=0;r<9;r++) {
657 cell=state->currentboard[r][state->x];
658 if (cell!='0') {
659 if (check[cell-'1']==1) {
660 return true;
662 check[cell-'1']=1;
666 /* Second, check the row */
667 for (cell=0;cell<9;cell++) {
668 check[cell]=0;
670 for (c=0;c<9;c++) {
671 cell=state->currentboard[state->y][c];
672 if (cell!='0') {
673 if (check[cell-'1']==1) {
674 return true;
676 check[cell-'1']=1;
680 /* Finally, check the 3x3 sub-grid */
681 for (cell=0;cell<9;cell++) {
682 check[cell]=0;
684 r1=(state->y/3)*3;
685 c1=(state->x/3)*3;
686 for (r=r1;r<r1+3;r++) {
687 for (c=c1;c<c1+3;c++) {
688 cell=state->currentboard[r][c];
689 if (cell!='0') {
690 if (check[cell-'1']==1) {
691 return true;
693 check[cell-'1']=1;
698 /* We passed all the checks :) */
700 return false;
703 /* Load game - only ".ss" is officially supported, but any sensible
704 text representation (one line per row) may load.
706 bool load_sudoku(struct sudoku_state_t* state, char* filename)
708 int fd;
709 size_t n;
710 int r = 0, c = 0;
711 unsigned int i;
712 int valid=0;
713 char buf[300]; /* A buffer to read a sudoku board from */
715 fd=rb->open(filename, O_RDONLY);
716 if (fd < 0) {
717 LOGF("Invalid sudoku file: %s\n",filename);
718 return(false);
721 rb->strncpy(state->filename,filename,MAX_PATH);
722 n=rb->read(fd,buf,300);
723 if (n <= 0) {
724 return(false);
726 rb->close(fd);
728 r=0;
729 c=0;
730 i=0;
731 while ((i < n) && (r < 9)) {
732 switch (buf[i]){
733 case ' ': case '\t':
734 if (c > 0)
735 valid=1;
736 break;
737 case '|':
738 case '*':
739 case '-':
740 case '\r':
741 break;
742 case '\n':
743 if (valid) {
744 r++;
745 valid=0;
747 c = 0;
748 break;
749 case '_': case '.':
750 valid=1;
751 if (c >= SIZE || r >= SIZE){
752 LOGF("ERROR: sudoku problem is the wrong size (%d,%d)\n",
753 c, r);
754 return(false);
756 c++;
757 break;
758 default:
759 if (((buf[i]>='A') && (buf[i]<='I')) ||
760 ((buf[i]>='0') && (buf[i]<='9'))) {
761 valid=1;
762 if (r >= SIZE || c >= SIZE){
763 LOGF("ERROR: sudoku problem is the wrong size "
764 "(%d,%d)\n", c, r);
765 return(false);
767 if ((buf[i]>='0') && (buf[i]<='9')) {
768 state->startboard[r][c]=buf[i];
769 state->currentboard[r][c]=buf[i];
770 } else {
771 state->currentboard[r][c]='1'+(buf[i]-'A');
773 c++;
775 /* Ignore any other characters */
776 break;
778 i++;
781 /* Check that the board is valid - we need to check every row/column
782 individually, so we check the diagonal from top-left to bottom-right */
783 for (state->x = 0; state->x < 9; state->x++) {
784 state->y = state->x;
785 if (check_status(state)) return false;
787 state->x = 0;
788 state->y = 0;
790 /* Save a copy of the saved state - so we can reload without using the
791 disk */
792 save_state(state);
793 return(true);
796 bool save_sudoku(struct sudoku_state_t* state)
798 int fd;
799 int r,c;
800 int i;
801 char line[13];
802 char sep[13];
804 rb->splash(0, "Saving...");
805 rb->memcpy(line,"...|...|...\r\n",13);
806 rb->memcpy(sep,"-----------\r\n",13);
808 if (state->filename[0]==0) {
809 return false;
812 fd=rb->open(state->filename, O_WRONLY|O_CREAT);
813 if (fd >= 0) {
814 for (r=0;r<9;r++) {
815 i=0;
816 for (c=0;c<9;c++) {
817 if (state->startboard[r][c]!='0') {
818 line[i]=state->startboard[r][c];
819 } else if (state->currentboard[r][c]!='0') {
820 line[i]='A'+(state->currentboard[r][c]-'1');
821 } else {
822 line[i]='.';
824 i++;
825 if ((c==2) || (c==5)) {
826 i++;
829 rb->write(fd,line,sizeof(line));
830 if ((r==2) || (r==5)) {
831 rb->write(fd,sep,sizeof(sep));
834 /* Add a blank line at end */
835 rb->write(fd,"\r\n",2);
836 rb->close(fd);
837 rb->reload_directory();
838 /* Save a copy of the saved state - so we can reload without
839 using the disk */
840 save_state(state);
841 return true;
842 } else {
843 return false;
847 void clear_board(struct sudoku_state_t* state)
849 int r,c;
851 for (r=0;r<9;r++) {
852 for (c=0;c<9;c++) {
853 state->currentboard[r][c]=state->startboard[r][c];
856 state->x=0;
857 state->y=0;
860 void update_cell(struct sudoku_state_t* state, int r, int c)
862 /* We have four types of cell:
863 1) User-entered number
864 2) Starting number
865 3) Cursor in cell
868 if ((r==state->y) && (c==state->x)) {
869 rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE,
870 BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
871 BITMAP_STRIDE,
872 XOFS+cellxpos[c],YOFS+cellypos[r],CELL_WIDTH,
873 CELL_HEIGHT);
874 } else {
875 if (state->startboard[r][c]!='0') {
876 rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE,
877 BITMAP_HEIGHT*(state->startboard[r][c]-'0'),
878 BITMAP_STRIDE,
879 XOFS+cellxpos[c],YOFS+cellypos[r],
880 CELL_WIDTH,CELL_HEIGHT);
881 } else {
882 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,
883 BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
884 BITMAP_STRIDE,
885 XOFS+cellxpos[c],YOFS+cellypos[r],
886 CELL_WIDTH,CELL_HEIGHT);
890 rb->lcd_update_rect(cellxpos[c],cellypos[r],CELL_WIDTH,CELL_HEIGHT);
894 void display_board(struct sudoku_state_t* state)
896 int r,c;
897 #ifdef SUDOKU_BUTTON_POSSIBLE
898 int i;
899 #endif
901 /* Clear the display buffer */
902 rb->lcd_clear_display();
904 /* Draw the gridlines - differently for different targets */
906 #ifdef SMALL_BOARD
907 /* Small targets - draw dotted/single lines */
908 for (r=0;r<9;r++) {
909 if ((r % 3)==0) {
910 /* Solid Line */
911 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
912 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
913 } else {
914 /* Dotted line */
915 for (c=XOFS;c<XOFS+BOARD_WIDTH;c+=2) {
916 rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
918 for (c=YOFS;c<YOFS+BOARD_HEIGHT;c+=2) {
919 rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
923 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
924 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
925 #else
926 /* Large targets - draw single/double lines */
927 for (r=0;r<9;r++) {
928 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
929 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
930 if ((r % 3)==0) {
931 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-2);
932 rb->lcd_vline(XOFS+cellxpos[r]-2,YOFS,YOFS+BOARD_HEIGHT-1);
935 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
936 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT+1);
937 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
938 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
939 #endif
941 #ifdef SUDOKU_BUTTON_POSSIBLE
942 #ifdef VERTICAL_LAYOUT
943 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD);
944 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD+CELL_HEIGHT+1);
945 for (r=0;r<9;r++) {
946 #ifdef SMALL_BOARD
947 /* Small targets - draw dotted/single lines */
948 if ((r % 3)==0) {
949 /* Solid Line */
950 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
951 YOFSSCRATCHPAD+CELL_HEIGHT+1);
952 } else {
953 /* Dotted line */
954 for (c=YOFSSCRATCHPAD;c<YOFSSCRATCHPAD+CELL_HEIGHT+1;c+=2) {
955 rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
958 #else
959 /* Large targets - draw single/double lines */
960 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
961 YOFSSCRATCHPAD+CELL_HEIGHT+1);
962 if ((r % 3)==0)
963 rb->lcd_vline(XOFS+cellxpos[r]-2,YOFSSCRATCHPAD,
964 YOFSSCRATCHPAD+CELL_HEIGHT+1);
965 #endif
966 if ((r>0) && state->possiblevals[state->y][state->x]&(1<<(r)))
967 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
968 BITMAP_STRIDE,XOFS+cellxpos[r-1],
969 YOFSSCRATCHPAD+1,CELL_WIDTH,CELL_HEIGHT);
971 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFSSCRATCHPAD,
972 YOFSSCRATCHPAD+CELL_HEIGHT+1);
973 #ifndef SMALL_BOARD
974 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFSSCRATCHPAD,
975 YOFSSCRATCHPAD+CELL_HEIGHT+1);
976 #endif
977 if (state->possiblevals[state->y][state->x]&(1<<(r)))
978 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
979 BITMAP_STRIDE,XOFS+cellxpos[8],YOFSSCRATCHPAD+1,
980 CELL_WIDTH,CELL_HEIGHT);
981 #else /* Horizontal layout */
982 rb->lcd_vline(XOFSSCRATCHPAD,YOFS,YOFS+BOARD_HEIGHT-1);
983 rb->lcd_vline(XOFSSCRATCHPAD+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
984 for (r=0;r<9;r++) {
985 #ifdef SMALL_BOARD
986 /* Small targets - draw dotted/single lines */
987 if ((r % 3)==0) {
988 /* Solid Line */
989 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
990 YOFS+cellypos[r]-1);
991 } else {
992 /* Dotted line */
993 for (c=XOFSSCRATCHPAD;c<XOFSSCRATCHPAD+CELL_WIDTH+1;c+=2) {
994 rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
997 #else
998 /* Large targets - draw single/double lines */
999 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
1000 YOFS+cellypos[r]-1);
1001 if ((r % 3)==0)
1002 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
1003 YOFS+cellypos[r]-2);
1004 #endif
1005 if ((r>0) && state->possiblevals[state->y][state->x]&(1<<(r)))
1006 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
1007 BITMAP_STRIDE,XOFSSCRATCHPAD+1,
1008 YOFS+cellypos[r-1],CELL_WIDTH,CELL_HEIGHT);
1010 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
1011 YOFS+cellypos[8]+CELL_HEIGHT);
1012 #ifndef SMALL_BOARD
1013 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
1014 YOFS+cellypos[8]+CELL_HEIGHT+1);
1015 #endif
1016 if (state->possiblevals[state->y][state->x]&(1<<(r)))
1017 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
1018 BITMAP_STRIDE,XOFSSCRATCHPAD+1,YOFS+cellypos[8],
1019 CELL_WIDTH,CELL_HEIGHT);
1020 #endif /* Layout */
1021 #endif /* SUDOKU_BUTTON_POSSIBLE */
1023 /* Draw the numbers */
1024 for (r=0;r<9;r++) {
1025 for (c=0;c<9;c++) {
1026 /* We have four types of cell:
1027 1) User-entered number
1028 2) Starting number
1029 3) Cursor in cell
1032 if ((r==state->y) && (c==state->x)) {
1033 rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE,
1034 BITMAP_HEIGHT*(state->currentboard[r][c]-
1035 '0'),
1036 BITMAP_STRIDE,
1037 XOFS+cellxpos[c],YOFS+cellypos[r],
1038 CELL_WIDTH,CELL_HEIGHT);
1039 } else {
1040 if (state->startboard[r][c]!='0') {
1041 rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE,
1042 BITMAP_HEIGHT*(state->startboard[r][c]-
1043 '0'),
1044 BITMAP_STRIDE,
1045 XOFS+cellxpos[c],YOFS+cellypos[r],
1046 CELL_WIDTH,CELL_HEIGHT);
1047 } else {
1048 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,
1049 BITMAP_HEIGHT*
1050 (state->currentboard[r][c]-'0'),
1051 BITMAP_STRIDE,
1052 XOFS+cellxpos[c],YOFS+cellypos[r],
1053 CELL_WIDTH,CELL_HEIGHT);
1056 #ifdef SUDOKU_BUTTON_POSSIBLE
1057 /* Draw the possible number markings on the board */
1058 if(sudcfg.show_markings && state->startboard[r][c]=='0'
1059 && state->currentboard[r][c]=='0') {
1060 for(i=0;i<9;i++) {
1061 if(state->possiblevals[r][c]&(2<<i)) {
1062 #if LCD_DEPTH > 1
1063 /* draw markings in dark grey */
1064 rb->lcd_set_foreground(LCD_DARKGRAY);
1065 #endif
1066 rb->lcd_fillrect(XOFS+cellxpos[c]+MARK_OFFS
1067 +(i%3)*(MARK_SIZE+MARK_SPACE),
1068 YOFS+cellypos[r]+MARK_OFFS
1069 +(i/3)*(MARK_SIZE+MARK_SPACE),
1070 MARK_SIZE,
1071 MARK_SIZE);
1072 #if LCD_DEPTH > 1
1073 rb->lcd_set_foreground(LCD_BLACK);
1074 #endif
1078 #endif /* SUDOKU_BUTTON_POSSIBLE */
1083 /* update the screen */
1084 rb->lcd_update();
1087 bool sudoku_generate(struct sudoku_state_t* state)
1089 char* difficulty;
1090 char str[80];
1091 bool res;
1092 struct sudoku_state_t new_state;
1094 clear_state(&new_state);
1095 display_board(&new_state);
1096 rb->splash(0, "Generating...");
1098 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1099 rb->cpu_boost(true);
1100 #endif
1102 res = sudoku_generate_board(&new_state,&difficulty);
1104 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1105 rb->cpu_boost(false);
1106 #endif
1108 if (res) {
1109 rb->memcpy(state,&new_state,sizeof(new_state));
1110 rb->snprintf(str,sizeof(str),"Difficulty: %s",difficulty);
1111 display_board(state);
1112 rb->splash(HZ*3, str);
1113 rb->strncpy(state->filename,GAME_FILE,MAX_PATH);
1114 } else {
1115 display_board(&new_state);
1116 rb->splash(HZ*2, "Aborted");
1118 /* initialize the saved board so reload function works */
1119 save_state(state);
1120 return res;
1123 #ifdef HAVE_LCD_COLOR
1124 static bool numdisplay_setting(void)
1126 static const struct opt_items names[] = {
1127 {"Black", -1},
1128 {"Coloured", -1},
1131 return rb->set_option("Number Display", &sudcfg.number_display, INT, names,
1132 sizeof(names) / sizeof(names[0]), NULL);
1134 #endif
1136 #ifdef SUDOKU_BUTTON_POSSIBLE
1137 static bool showmarkings_setting(void)
1139 static const struct opt_items names[] = {
1140 {"Hide", -1},
1141 {"Show", -1},
1144 return rb->set_option("Show Markings", &sudcfg.show_markings, INT, names,
1145 sizeof(names) / sizeof(names[0]), NULL);
1147 #endif
1149 enum {
1150 SM_AUDIO_PLAYBACK = 0,
1151 #ifdef HAVE_LCD_COLOR
1152 SM_NUMBER_DISPLAY,
1153 #endif
1154 #ifdef SUDOKU_BUTTON_POSSIBLE
1155 SM_SHOW_MARKINGS,
1156 #endif
1157 SM_SAVE,
1158 SM_RELOAD,
1159 SM_CLEAR,
1160 SM_SOLVE,
1161 SM_GENERATE,
1162 SM_NEW,
1163 SM_QUIT,
1166 bool sudoku_menu(struct sudoku_state_t* state)
1168 int m;
1169 int result;
1171 static const struct menu_item items[] = {
1172 [SM_AUDIO_PLAYBACK] = { "Audio Playback", NULL },
1173 #ifdef HAVE_LCD_COLOR
1174 [SM_NUMBER_DISPLAY] = { "Number Display", NULL },
1175 #endif
1176 #ifdef SUDOKU_BUTTON_POSSIBLE
1177 [SM_SHOW_MARKINGS] = { "Show Markings", NULL },
1178 #endif
1179 [SM_SAVE] = { "Save", NULL },
1180 [SM_RELOAD] = { "Reload", NULL },
1181 [SM_CLEAR] = { "Clear", NULL },
1182 [SM_SOLVE] = { "Solve", NULL },
1183 [SM_GENERATE] = { "Generate", NULL },
1184 [SM_NEW] = { "New", NULL },
1185 [SM_QUIT] = { "Quit", NULL },
1188 m = menu_init(rb,items, sizeof(items) / sizeof(*items),
1189 NULL, NULL, NULL, NULL);
1191 result=menu_show(m);
1193 switch (result) {
1194 case SM_AUDIO_PLAYBACK:
1195 playback_control(rb, NULL);
1196 break;
1198 #ifdef HAVE_LCD_COLOR
1199 case SM_NUMBER_DISPLAY:
1200 numdisplay_setting();
1201 break;
1202 #endif
1204 #ifdef SUDOKU_BUTTON_POSSIBLE
1205 case SM_SHOW_MARKINGS:
1206 showmarkings_setting();
1207 break;
1208 #endif
1209 case SM_SAVE:
1210 save_sudoku(state);
1211 break;
1213 case SM_RELOAD:
1214 restore_state(state);
1215 break;
1217 case SM_CLEAR:
1218 clear_board(state);
1219 break;
1221 case SM_SOLVE:
1222 sudoku_solve(state);
1223 break;
1225 case SM_GENERATE:
1226 sudoku_generate(state);
1227 break;
1229 case SM_NEW:
1230 clear_state(state);
1231 state->editmode=1;
1232 break;
1234 case SM_QUIT:
1235 save_sudoku(state);
1236 menu_exit(m);
1237 return true;
1238 break;
1240 default:
1241 break;
1244 menu_exit(m);
1246 return (result==MENU_ATTACHED_USB);
1249 /* Menu used when user is in edit mode - i.e. creating a new game manually */
1250 int sudoku_edit_menu(struct sudoku_state_t* state)
1252 int m;
1253 int result;
1255 static const struct menu_item items[] = {
1256 { "Save as", NULL },
1257 { "Quit", NULL },
1260 m = menu_init(rb,items, sizeof(items) / sizeof(*items),
1261 NULL, NULL, NULL, NULL);
1263 result=menu_show(m);
1265 switch (result) {
1266 case 0: /* Save new game */
1267 rb->kbd_input(state->filename,MAX_PATH);
1268 if (save_sudoku(state)) {
1269 state->editmode=0;
1270 } else {
1271 rb->splash(HZ*2, "Save failed");
1273 break;
1275 case 1: /* Quit */
1276 break;
1278 default:
1279 break;
1282 menu_exit(m);
1284 return result;
1287 void move_cursor(struct sudoku_state_t* state, int newx, int newy)
1289 int oldx, oldy;
1291 /* Check that the character at the cursor position is legal */
1292 if (check_status(state)) {
1293 rb->splash(HZ*2, "Illegal move!");
1294 /* Ignore any button presses during the splash */
1295 rb->button_clear_queue();
1296 return;
1299 /* Move Cursor */
1300 oldx=state->x;
1301 oldy=state->y;
1302 state->x=newx;
1303 state->y=newy;
1305 /* Redraw current and old cells */
1306 update_cell(state,oldx,oldy);
1307 update_cell(state,newx,newy);
1310 /* plugin entry point */
1311 enum plugin_status plugin_start(const struct plugin_api* api, const void* parameter)
1313 bool exit;
1314 int button;
1315 int lastbutton = BUTTON_NONE;
1316 int res;
1317 int rc = PLUGIN_OK;
1318 long ticks;
1319 struct sudoku_state_t state;
1321 /* plugin init */
1322 rb = api;
1323 /* end of plugin init */
1325 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
1326 configfile_init(rb);
1327 configfile_load(cfg_filename, disk_config,
1328 sizeof(disk_config) / sizeof(disk_config[0]),
1329 CFGFILE_MINVERSION);
1330 rb->memcpy(&sudcfg, &sudcfg_disk, sizeof(sudcfg)); /* copy to running config */
1331 #endif
1333 #if LCD_DEPTH > 1
1334 rb->lcd_set_backdrop(NULL);
1335 rb->lcd_set_foreground(LCD_BLACK);
1336 rb->lcd_set_background(LCD_WHITE);
1337 #endif
1339 clear_state(&state);
1341 if (parameter==NULL) {
1342 /* We have been started as a plugin - try default sudoku.ss */
1343 if (!load_sudoku(&state,GAME_FILE)) {
1344 /* No previous game saved, use the default */
1345 default_state(&state);
1347 } else {
1348 if (!load_sudoku(&state,(char*)parameter)) {
1349 rb->splash(HZ*2, "Load error");
1350 return(PLUGIN_ERROR);
1355 display_board(&state);
1357 /* The main game loop */
1358 exit=false;
1359 ticks=0;
1360 while(!exit) {
1361 button = rb->button_get(true);
1363 switch(button){
1364 #ifdef SUDOKU_BUTTON_QUIT
1365 /* Exit game */
1366 case SUDOKU_BUTTON_QUIT:
1367 if (check_status(&state)) {
1368 rb->splash(HZ*2, "Illegal move!");
1369 /* Ignore any button presses during the splash */
1370 rb->button_clear_queue();
1371 } else {
1372 save_sudoku(&state);
1373 exit=true;
1375 break;
1376 #endif
1378 /* Increment digit */
1379 #ifdef SUDOKU_BUTTON_ALTTOGGLE
1380 case SUDOKU_BUTTON_ALTTOGGLE | BUTTON_REPEAT:
1381 #endif
1382 case SUDOKU_BUTTON_TOGGLE | BUTTON_REPEAT:
1383 /* Slow down the repeat speed to 1/3 second */
1384 if ((*rb->current_tick-ticks) < (HZ/3)) {
1385 break;
1388 #ifdef SUDOKU_BUTTON_ALTTOGGLE
1389 case SUDOKU_BUTTON_ALTTOGGLE:
1390 #endif
1391 case SUDOKU_BUTTON_TOGGLE:
1392 #ifdef SUDOKU_BUTTON_TOGGLE_PRE
1393 if ((button == SUDOKU_BUTTON_TOGGLE)
1394 && (lastbutton != SUDOKU_BUTTON_TOGGLE_PRE))
1395 break;
1396 #endif
1397 /* Increment digit */
1398 ticks=*rb->current_tick;
1399 if (state.editmode) {
1400 if (state.startboard[state.y][state.x]=='9') {
1401 state.startboard[state.y][state.x]='0';
1402 state.currentboard[state.y][state.x]='0';
1403 } else {
1404 state.startboard[state.y][state.x]++;
1405 state.currentboard[state.y][state.x]++;
1407 } else {
1408 if (state.startboard[state.y][state.x]=='0') {
1409 if (state.currentboard[state.y][state.x]=='9') {
1410 state.currentboard[state.y][state.x]='0';
1411 } else {
1412 state.currentboard[state.y][state.x]++;
1416 update_cell(&state,state.y,state.x);
1417 break;
1419 #ifdef SUDOKU_BUTTON_TOGGLEBACK
1420 case SUDOKU_BUTTON_TOGGLEBACK | BUTTON_REPEAT:
1421 /* Slow down the repeat speed to 1/3 second */
1422 if ((*rb->current_tick-ticks) < (HZ/3)) {
1423 break;
1426 case SUDOKU_BUTTON_TOGGLEBACK:
1427 /* Decrement digit */
1428 ticks=*rb->current_tick;
1429 if (state.editmode) {
1430 if (state.startboard[state.y][state.x]=='0') {
1431 state.startboard[state.y][state.x]='9';
1432 state.currentboard[state.y][state.x]='9';
1433 } else {
1434 state.startboard[state.y][state.x]--;
1435 state.currentboard[state.y][state.x]--;
1437 } else {
1438 if (state.startboard[state.y][state.x]=='0') {
1439 if (state.currentboard[state.y][state.x]=='0') {
1440 state.currentboard[state.y][state.x]='9';
1441 } else {
1442 state.currentboard[state.y][state.x]--;
1446 update_cell(&state,state.y,state.x);
1447 break;
1448 #endif
1450 /* move cursor left */
1451 case SUDOKU_BUTTON_LEFT:
1452 case (SUDOKU_BUTTON_LEFT | BUTTON_REPEAT):
1453 if (state.x==0) {
1454 #ifndef SUDOKU_BUTTON_UP
1455 if (state.y==0) {
1456 move_cursor(&state,8,8);
1457 } else {
1458 move_cursor(&state,8,state.y-1);
1460 #else
1461 move_cursor(&state,8,state.y);
1462 #endif
1463 } else {
1464 move_cursor(&state,state.x-1,state.y);
1466 break;
1468 /* move cursor right */
1469 case SUDOKU_BUTTON_RIGHT:
1470 case (SUDOKU_BUTTON_RIGHT | BUTTON_REPEAT):
1471 if (state.x==8) {
1472 #ifndef SUDOKU_BUTTON_DOWN
1473 if (state.y==8) {
1474 move_cursor(&state,0,0);
1475 } else {
1476 move_cursor(&state,0,state.y+1);
1478 #else
1479 move_cursor(&state,0,state.y);
1480 #endif
1481 } else {
1482 move_cursor(&state,state.x+1,state.y);
1484 break;
1486 #ifdef SUDOKU_BUTTON_UP
1487 /* move cursor up */
1488 case SUDOKU_BUTTON_UP:
1489 case (SUDOKU_BUTTON_UP | BUTTON_REPEAT):
1490 if (state.y==0) {
1491 move_cursor(&state,state.x,8);
1492 } else {
1493 move_cursor(&state,state.x,state.y-1);
1495 break;
1496 #endif
1498 #ifdef SUDOKU_BUTTON_DOWN
1499 /* move cursor down */
1500 case SUDOKU_BUTTON_DOWN:
1501 case (SUDOKU_BUTTON_DOWN | BUTTON_REPEAT):
1502 if (state.y==8) {
1503 move_cursor(&state,state.x,0);
1504 } else {
1505 move_cursor(&state,state.x,state.y+1);
1507 break;
1508 #endif
1510 case SUDOKU_BUTTON_MENU:
1511 #ifdef SUDOKU_BUTTON_MENU_PRE
1512 if (lastbutton != SUDOKU_BUTTON_MENU_PRE)
1513 break;
1514 #endif
1515 /* Don't let the user leave a game in a bad state */
1516 if (check_status(&state)) {
1517 rb->splash(HZ*2, "Illegal move!");
1518 /* Ignore any button presses during the splash */
1519 rb->button_clear_queue();
1520 } else {
1521 if (state.editmode) {
1522 res = sudoku_edit_menu(&state);
1523 if (res == MENU_ATTACHED_USB) {
1524 rc = PLUGIN_USB_CONNECTED;
1525 exit = true;
1526 } else if (res == 1) { /* Quit */
1527 exit = true;
1529 } else {
1530 if (sudoku_menu(&state)) {
1531 rc = PLUGIN_USB_CONNECTED;
1532 exit = true;
1536 break;
1537 #ifdef SUDOKU_BUTTON_POSSIBLE
1538 case SUDOKU_BUTTON_POSSIBLE:
1539 /* Toggle current number in the possiblevals structure */
1540 if (state.currentboard[state.y][state.x]!='0') {
1541 state.possiblevals[state.y][state.x]^=
1542 (1 << (state.currentboard[state.y][state.x] - '0'));
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