summaryrefslogtreecommitdiff
path: root/apps/plugins/sudoku/sudoku.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/sudoku/sudoku.c')
-rw-r--r--apps/plugins/sudoku/sudoku.c1124
1 files changed, 1124 insertions, 0 deletions
diff --git a/apps/plugins/sudoku/sudoku.c b/apps/plugins/sudoku/sudoku.c
new file mode 100644
index 0000000000..798936b384
--- /dev/null
+++ b/apps/plugins/sudoku/sudoku.c
@@ -0,0 +1,1124 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2005 Dave Chapman
11 *
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
14 *
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
17 *
18 ****************************************************************************/
19
20/***
21Sudoku by Dave Chapman
22
23User instructions
24-----------------
25
26Use the arrow keys to move cursor, and press SELECT/ON/F2 to increment
27the number under the cursor.
28
29At any time during the game, press On to bring up the game menu with
30further options:
31
32 Save
33 Reload
34 Clear
35 Solve
36
37Sudoku is implemented as a "viewer" for a ".ss" file, as generated by
38Simple Sudoku and other applications - http://angusj.com/sudoku/
39
40In-progress game positions are saved in the original .ss file, with
41A-I used to indicate numbers entered by the user.
42
43Example ".ss" file, and one with a saved state:
44
45...|...|... ...|...|...
462..|8.4|9.1 2.C|8.4|9.1
47...|1.6|32. E..|1.6|32.
48----------- -----------
49...|..5|.4. ...|..5|.4.
508..|423|..6 8..|423|..6
51.3.|9..|... .3D|9..|A..
52----------- -----------
53.63|7.9|... .63|7.9|...
544.9|5.2|..8 4.9|5.2|.C8
55...|...|... ...|...|...
56
57*/
58
59#include "plugin.h"
60
61#ifdef HAVE_LCD_BITMAP
62
63#include "sudoku.h"
64#include "generator.h"
65
66PLUGIN_HEADER
67
68struct plugin_api* rb;
69
70/* The bitmaps */
71extern const fb_data sudoku_normal[];
72extern const fb_data sudoku_start[];
73extern const fb_data sudoku_inverse[];
74
75#if (LCD_HEIGHT==128) && (LCD_WIDTH==160)
76/* For iriver H1x0 - 160x128, 9 cells @ 12x12 with 14 border lines*/
77
78/* Internal dimensions of a cell */
79#define CELL_WIDTH 12
80#define CELL_HEIGHT 12
81
82#define BOARD_WIDTH (CELL_WIDTH*9+10+4)
83#define BOARD_HEIGHT (CELL_HEIGHT*9+10+4)
84
85#define XOFS (((LCD_WIDTH-BOARD_WIDTH)/2)+10)
86#define YOFS ((LCD_HEIGHT-BOARD_HEIGHT)/2)
87
88#define XOFSSCRATCHPAD 3
89
90/* Locations of each cell */
91static unsigned char cellxpos[9]={ 2, 15, 28, 42, 55, 68, 82, 95, 108 };
92static unsigned char cellypos[9]={ 2, 15, 28, 42, 55, 68, 82, 95, 108 };
93
94/* The height of one cell in the bitmap */
95#define BITMAP_HEIGHT 12
96#define BITMAP_STRIDE 12
97
98#elif (LCD_HEIGHT==64) && (LCD_WIDTH==112)
99/* For Archos Recorder, FM and Ondio (112x64):
100 9 cells @ 8x6 with 10 border lines
101*/
102
103/* Internal dimensions of a cell */
104#define CELL_WIDTH 8
105#define CELL_HEIGHT 6
106
107#define BOARD_WIDTH (CELL_WIDTH*9+10)
108#define BOARD_HEIGHT (CELL_HEIGHT*9+10)
109
110#define XOFS (((LCD_WIDTH-BOARD_WIDTH)/2)+7)
111#define YOFS ((LCD_HEIGHT-BOARD_HEIGHT)/2)
112
113#define XOFSSCRATCHPAD 2
114
115/* Locations of each cell */
116static unsigned char cellxpos[9]={ 1, 10, 19, 28, 37, 46, 55, 64, 73 };
117static unsigned char cellypos[9]={ 1, 8, 15, 22, 29, 36, 43, 50, 57 };
118
119/* The height of one cell in the bitmap */
120#define BITMAP_HEIGHT 8
121#define BITMAP_STRIDE 8
122
123#elif (LCD_HEIGHT>=176) && (LCD_WIDTH>=220)
124/* iriver h300 */
125
126/* Internal dimensions of a cell */
127#define CELL_WIDTH 16
128#define CELL_HEIGHT 16
129
130#define BOARD_WIDTH (CELL_WIDTH*9+10+4)
131#define BOARD_HEIGHT (CELL_HEIGHT*9+10+4)
132
133#define XOFS (((LCD_WIDTH-BOARD_WIDTH)/2)+15)
134#define YOFS ((LCD_HEIGHT-BOARD_HEIGHT)/2)
135
136#define XOFSSCRATCHPAD 10
137
138/* Locations of each cell */
139static unsigned char cellxpos[9]={ 2, 19, 36, 54, 71, 88, 106, 123, 140 };
140static unsigned char cellypos[9]={ 2, 19, 36, 54, 71, 88, 106, 123, 140 };
141
142/* The height of one cell in the bitmap */
143#define BITMAP_HEIGHT 16
144#define BITMAP_STRIDE 16
145
146#else
147 #error SUDOKU: Unsupported LCD size
148#endif
149
150/****** Solver routine by Tom Shackell <shackell@cs.york.ac.uk>
151
152Downloaded from:
153
154http://www-users.cs.york.ac.uk/~shackell/sudoku/Sudoku.html
155
156Released under GPLv2
157
158*/
159
160typedef unsigned int Bitset;
161
162#define BLOCK 3
163#define SIZE (BLOCK*BLOCK)
164
165#define true 1
166#define false 0
167
168typedef struct _Sudoku {
169 Bitset table[SIZE][SIZE];
170}Sudoku;
171
172typedef struct _Stats {
173 int numTries;
174 int backTracks;
175 int numEmpty;
176 bool solutionFound;
177}Stats;
178
179typedef struct _Options {
180 bool allSolutions;
181 bool uniquenessCheck;
182}Options;
183
184void sudoku_init(Sudoku* sud);
185void sudoku_set(Sudoku* sud, int x, int y, int num, bool original);
186int sudoku_get(Sudoku* sud, int x, int y, bool* original);
187
188#define BIT(n) ((Bitset)(1<<(n)))
189#define BIT_TEST(v,n) ((((Bitset)v) & BIT(n)) != 0)
190#define BIT_CLEAR(v,n) (v) &= ~BIT(n)
191#define MARK_BIT BIT(0)
192#define ORIGINAL_BIT BIT(SIZE+1)
193
194#define ALL_BITS (BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8) | BIT(9))
195
196/* initialize a sudoku problem, should be called before using set or get */
197void sudoku_init(Sudoku* sud)
198{
199 int y, x;
200 for (y = 0; y < SIZE; y++){
201 for (x = 0; x < SIZE; x++){
202 sud->table[x][y] = ALL_BITS;
203 }
204 }
205}
206
207/* set the number at a particular x and y column */
208void sudoku_set(Sudoku* sud, int x, int y, int num, bool original)
209{
210 int i, j;
211 int bx, by;
212 Bitset orig;
213
214 /* clear the row and columns */
215 for (i = 0; i < SIZE; i++){
216 BIT_CLEAR(sud->table[i][y], num);
217 BIT_CLEAR(sud->table[x][i], num);
218 }
219 /* clear the block */
220 bx = x - (x % BLOCK);
221 by = y - (y % BLOCK);
222 for (i = 0; i < BLOCK; i++){
223 for (j = 0; j < BLOCK; j++){
224 BIT_CLEAR(sud->table[bx+j][by+i], num);
225 }
226 }
227 /* mark the table */
228 orig = original ? ORIGINAL_BIT : 0;
229 sud->table[x][y] = BIT(num) | MARK_BIT | orig;
230}
231
232/* get the number at a particular x and y column, if this
233 is not unique return 0 */
234int sudoku_get(Sudoku* sud, int x, int y, bool* original)
235{
236 Bitset val = sud->table[x][y];
237 int result = 0;
238 int i;
239
240 if (original) {
241 *original = val & ORIGINAL_BIT;
242 }
243 for (i = 1; i <= SIZE; i++){
244 if (BIT_TEST(val, i)){
245 if (result != 0){
246 return 0;
247 }
248 result = i;
249 }
250 }
251 return result;
252}
253
254/* returns true if this is a valid problem, this is necessary because the input
255 problem might be degenerate which breaks the solver algorithm. */
256static bool is_valid(const Sudoku* sud)
257{
258 int x, y;
259
260 for (y = 0; y < SIZE; y++){
261 for (x = 0; x < SIZE; x++){
262 if ((sud->table[x][y] & ALL_BITS) == 0){
263 return false;
264 }
265 }
266 }
267 return true;
268}
269
270/* scan the table for the most constrained item, giving all it's options, sets
271 the best x and y coordinates, the number of options and the options for
272 that coordinate and returns true if the puzzle is finished */
273static bool scan(const Sudoku* sud, int* rX, int* rY, int *num, int* options)
274{
275 int x, y, i, j;
276 int bestCount = SIZE+1;
277 Bitset val;
278 bool allMarked = true;
279
280 for (y = 0; y < SIZE; y++){
281 for (x = 0; x < SIZE; x++){
282 Bitset val = sud->table[x][y];
283 int i;
284 int count = 0;
285
286 if (val & MARK_BIT) {
287 /* already set */
288 continue;
289 }
290 allMarked = false;
291 for (i = 1; i <= SIZE; i++){
292 if (BIT_TEST(val, i)){
293 count++;
294 }
295 }
296 if (count < bestCount){
297 bestCount = count;
298 *rX = x;
299 *rY = y;
300 if (count == 0){
301 /* can't possibly be beaten */
302 *num = 0;
303 return false;
304 }
305 }
306 }
307 }
308 /* now copy into options */
309 *num = bestCount;
310 val = sud->table[*rX][*rY];
311 for (i = 1, j = 0; i <= SIZE; i++){
312 if (BIT_TEST(val, i)){
313 options[j++] = i;
314 }
315 }
316 return allMarked;
317}
318
319static bool solve(Sudoku* sud, Stats* stats, const Options* options);
320
321/* try a particular option and return true if that gives a solution or false
322 if it doesn't, restores board on backtracking */
323static bool spawn_option(Sudoku* sud, Stats* stats, const Options* options,
324 int x, int y, int num)
325{
326 Sudoku copy;
327
328 rb->memcpy(&copy,sud,sizeof(Sudoku));
329 sudoku_set(&copy, x, y, num, false);
330 stats->numTries += 1;
331 if (solve(&copy, stats, options)){
332 if (!options->allSolutions && stats->solutionFound){
333 rb->memcpy(sud,&copy,sizeof(Sudoku));
334 }
335 return true;
336 }else{
337 stats->backTracks++;
338 }
339 return false;
340}
341
342/* solve a sudoku problem, returns true if there is a solution and false
343 otherwise. stats is used to track statisticss */
344static bool solve(Sudoku* sud, Stats* stats, const Options* options)
345{
346 while (true){
347 int x, y, i, num;
348 int places[SIZE];
349
350 if (scan(sud, &x, &y, &num, places)){
351 /* a solution was found! */
352 if (options->uniquenessCheck && stats->solutionFound){
353 /*printf("\n\t... But the solution is not unique!\n"); */
354 return true;
355 }
356 stats->solutionFound = true;
357 if (options->allSolutions || options->uniquenessCheck){
358 /*printf("\n\tSolution after %d iterations\n", stats->numTries); */
359 /*sudoku_print(sud); */
360 return false;
361 }
362 else{
363 return true;
364 }
365 }
366 if (num == 0){
367 /* can't be satisfied */
368 return false;
369 }
370 /* try all the places (except the last one) */
371 for (i = 0; i < num-1; i++){
372 if (spawn_option(sud, stats, options, x, y, places[i])){
373 /* solution found! */
374 if (!options->allSolutions && stats->solutionFound){
375 return true;
376 }
377 }
378 }
379 /* take the last place ourself */
380 stats->numTries += 1;
381 sudoku_set(sud, x, y, places[num-1], false);
382 }
383}
384
385/******** END OF IMPORTED CODE */
386
387
388/* A wrapper function between the Sudoku plugin and the above solver code */
389void sudoku_solve(struct sudoku_state_t* state)
390{
391 bool ret;
392 Stats stats;
393 Options options;
394 Sudoku sud;
395 bool original;
396 int r,c;
397
398 /* Initialise the parameters */
399 sudoku_init(&sud);
400 rb->memset(&stats,0,sizeof(stats));
401 options.allSolutions=false;
402 options.uniquenessCheck=false;
403
404 /* Convert Rockbox format into format for solver */
405 for (r=0;r<9;r++) {
406 for (c=0;c<9;c++) {
407 if (state->startboard[r][c]!='0') {
408 sudoku_set(&sud, c, r, state->startboard[r][c]-'0', true);
409 }
410 }
411 }
412
413 /* need to check for degenerate input problems ... */
414 if (is_valid(&sud)){
415 ret = solve(&sud, &stats, &options);
416 } else {
417 ret = false;
418 }
419
420 if (ret) {
421 /* Populate the board with the solution. */
422 for (r=0;r<9;r++) {
423 for (c=0;c<9;c++) {
424 state->currentboard[r][c]='0'+
425 sudoku_get(&sud, c, r, &original);
426 }
427 }
428 } else {
429 rb->splash(HZ*2, true, "Solve failed");
430 }
431
432 return;
433}
434
435
436void clear_state(struct sudoku_state_t* state)
437{
438 int r,c;
439
440 state->filename[0]=0;
441 for (r=0;r<9;r++) {
442 for (c=0;c<9;c++) {
443 state->startboard[r][c]='0';
444 state->currentboard[r][c]='0';
445#ifdef SUDOKU_BUTTON_POSSIBLE
446 state->possiblevals[r][c]=0;
447#endif
448 }
449 }
450
451 state->x=0;
452 state->y=0;
453 state->editmode=0;
454}
455
456/* Load game - only ".ss" is officially supported, but any sensible
457 text representation (one line per row) may load.
458*/
459bool load_sudoku(struct sudoku_state_t* state, char* filename)
460{
461 int fd;
462 size_t n;
463 int r = 0, c = 0;
464 unsigned int i;
465 int valid=0;
466 char buf[300]; /* A buffer to read a sudoku board from */
467
468 fd=rb->open(filename, O_RDONLY);
469 if (fd < 0) {
470 LOGF("Invalid sudoku file: %s\n",filename);
471 return(false);
472 }
473
474 rb->strncpy(state->filename,filename,MAX_PATH);
475 n=rb->read(fd,buf,300);
476 if (n <= 0) {
477 return(false);
478 }
479 rb->close(fd);
480
481 r=0;
482 c=0;
483 i=0;
484 while ((i < n) && (r < 9)) {
485 switch (buf[i]){
486 case ' ': case '\t':
487 if (c > 0)
488 valid=1;
489 break;
490 case '|':
491 case '*':
492 case '-':
493 case '\r':
494 break;
495 case '\n':
496 if (valid) {
497 r++;
498 valid=0;
499 }
500 c = 0;
501 break;
502 case '_': case '.':
503 valid=1;
504 if (c >= SIZE || r >= SIZE){
505 LOGF("ERROR: sudoku problem is the wrong size (%d,%d)\n",
506 c, r);
507 return(false);
508 }
509 c++;
510 break;
511 default:
512 if (((buf[i]>='A') && (buf[i]<='I')) ||
513 ((buf[i]>='0') && (buf[i]<='9'))) {
514 valid=1;
515 if (r >= SIZE || c >= SIZE){
516 LOGF("ERROR: sudoku problem is the wrong size "
517 "(%d,%d)\n", c, r);
518 return(false);
519 }
520 if ((buf[i]>='0') && (buf[i]<='9')) {
521 state->startboard[r][c]=buf[i];
522 state->currentboard[r][c]=buf[i];
523 } else {
524 state->currentboard[r][c]='1'+(buf[i]-'A');
525 }
526 c++;
527 }
528 /* Ignore any other characters */
529 break;
530 }
531 i++;
532 }
533
534 /* Save a copy of the saved state - so we can reload without using the
535 disk */
536 rb->memcpy(state->savedboard,state->currentboard,81);
537 return(true);
538}
539
540bool save_sudoku(struct sudoku_state_t* state)
541{
542 int fd;
543 int r,c;
544 int i;
545 char line[13];
546 char sep[13];
547
548 rb->memcpy(line,"...|...|...\r\n",13);
549 rb->memcpy(sep,"-----------\r\n",13);
550
551 if (state->filename[0]==0) {
552 return false;
553 }
554
555 fd=rb->open(state->filename, O_WRONLY|O_CREAT);
556 if (fd >= 0) {
557 for (r=0;r<9;r++) {
558 i=0;
559 for (c=0;c<9;c++) {
560 if (state->startboard[r][c]!='0') {
561 line[i]=state->startboard[r][c];
562 } else if (state->currentboard[r][c]!='0') {
563 line[i]='A'+(state->currentboard[r][c]-'1');
564 } else {
565 line[i]='.';
566 }
567 i++;
568 if ((c==2) || (c==5)) {
569 i++;
570 }
571 }
572 rb->write(fd,line,sizeof(line));
573 if ((r==2) || (r==5)) {
574 rb->write(fd,sep,sizeof(sep));
575 }
576 }
577 /* Add a blank line at end */
578 rb->write(fd,"\r\n",2);
579 rb->close(fd);
580 /* Save a copy of the saved state - so we can reload without
581 using the disk */
582 rb->memcpy(state->savedboard,state->currentboard,81);
583 return true;
584 } else {
585 return false;
586 }
587}
588
589void restore_state(struct sudoku_state_t* state)
590{
591 rb->memcpy(state->currentboard,state->savedboard,81);
592}
593
594void clear_board(struct sudoku_state_t* state)
595{
596 int r,c;
597
598 for (r=0;r<9;r++) {
599 for (c=0;c<9;c++) {
600 state->currentboard[r][c]=state->startboard[r][c];
601 }
602 }
603 state->x=0;
604 state->y=0;
605}
606
607void update_cell(struct sudoku_state_t* state, int r, int c)
608{
609 /* We have four types of cell:
610 1) User-entered number
611 2) Starting number
612 3) Cursor in cell
613 */
614
615 if ((r==state->y) && (c==state->x)) {
616 rb->lcd_bitmap_part(sudoku_inverse,0,
617 BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
618 BITMAP_STRIDE,
619 XOFS+cellxpos[c],YOFS+cellypos[r],CELL_WIDTH,
620 CELL_HEIGHT);
621 } else {
622 if (state->startboard[r][c]!='0') {
623 rb->lcd_bitmap_part(sudoku_start,0,
624 BITMAP_HEIGHT*(state->startboard[r][c]-'0'),
625 BITMAP_STRIDE,
626 XOFS+cellxpos[c],YOFS+cellypos[r],
627 CELL_WIDTH,CELL_HEIGHT);
628 } else {
629 rb->lcd_bitmap_part(sudoku_normal,0,
630 BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
631 BITMAP_STRIDE,
632 XOFS+cellxpos[c],YOFS+cellypos[r],
633 CELL_WIDTH,CELL_HEIGHT);
634 }
635 }
636
637 rb->lcd_update_rect(cellxpos[c],cellypos[r],CELL_WIDTH,CELL_HEIGHT);
638}
639
640
641void display_board(struct sudoku_state_t* state)
642{
643 int r,c;
644
645 /* Clear the display buffer */
646 rb->lcd_clear_display();
647
648 /* Draw the gridlines - differently for different targets */
649
650#if LCD_HEIGHT > 64
651 /* Large targets - draw single/double lines */
652 for (r=0;r<9;r++) {
653 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
654 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
655 if ((r % 3)==0) {
656 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-2);
657 rb->lcd_vline(XOFS+cellxpos[r]-2,YOFS,YOFS+BOARD_HEIGHT-1);
658 }
659 }
660 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
661 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT+1);
662 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
663 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
664#elif (LCD_HEIGHT==64)
665 /* Small targets - draw dotted/single lines */
666 for (r=0;r<9;r++) {
667 if ((r % 3)==0) {
668 /* Solid Line */
669 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
670 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
671 } else {
672 /* Dotted line */
673 for (c=XOFS;c<XOFS+BOARD_WIDTH;c+=2) {
674 rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
675 }
676 for (c=YOFS;c<YOFS+BOARD_HEIGHT;c+=2) {
677 rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
678 }
679 }
680 }
681 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
682 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
683#else
684#error SUDOKU: Unsupported LCD height
685#endif
686
687#ifdef SUDOKU_BUTTON_POSSIBLE
688 rb->lcd_vline(XOFSSCRATCHPAD,YOFS,YOFS+BOARD_HEIGHT-1);
689 rb->lcd_vline(XOFSSCRATCHPAD+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
690 for (r=0;r<9;r++) {
691#if LCD_HEIGHT > 64
692 /* Large targets - draw single/double lines */
693 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
694 YOFS+cellypos[r]-1);
695 if ((r % 3)==0)
696 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
697 YOFS+cellypos[r]-2);
698#elif LCD_HEIGHT == 64
699 /* Small targets - draw dotted/single lines */
700 if ((r % 3)==0) {
701 /* Solid Line */
702 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
703 YOFS+cellypos[r]-1);
704 } else {
705 /* Dotted line */
706 for (c=XOFSSCRATCHPAD;c<XOFSSCRATCHPAD+CELL_WIDTH+1;c+=2) {
707 rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
708 }
709 }
710#endif
711 if ((r>0) && state->possiblevals[state->y][state->x]&(1<<(r)))
712 rb->lcd_bitmap_part(sudoku_normal,0,BITMAP_HEIGHT*r,BITMAP_STRIDE,
713 XOFSSCRATCHPAD+1,YOFS+cellypos[r-1],
714 CELL_WIDTH,CELL_HEIGHT);
715 }
716 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
717 YOFS+cellypos[8]+CELL_HEIGHT);
718#if LCD_HEIGHT > 64
719 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
720 YOFS+cellypos[8]+CELL_HEIGHT+1);
721#endif
722 if (state->possiblevals[state->y][state->x]&(1<<(r)))
723 rb->lcd_bitmap_part(sudoku_normal,0,BITMAP_HEIGHT*r,BITMAP_STRIDE,
724 XOFSSCRATCHPAD+1,YOFS+cellypos[8],
725 CELL_WIDTH,CELL_HEIGHT);
726#endif
727
728 /* Draw the numbers */
729 for (r=0;r<9;r++) {
730 for (c=0;c<9;c++) {
731 /* We have four types of cell:
732 1) User-entered number
733 2) Starting number
734 3) Cursor in cell
735 */
736
737 if ((r==state->y) && (c==state->x)) {
738 rb->lcd_bitmap_part(sudoku_inverse,0,
739 BITMAP_HEIGHT*(state->currentboard[r][c]-
740 '0'),
741 BITMAP_STRIDE,
742 XOFS+cellxpos[c],YOFS+cellypos[r],
743 CELL_WIDTH,CELL_HEIGHT);
744 } else {
745 if (state->startboard[r][c]!='0') {
746 rb->lcd_bitmap_part(sudoku_start,0,
747 BITMAP_HEIGHT*(state->startboard[r][c]-
748 '0'),
749 BITMAP_STRIDE,
750 XOFS+cellxpos[c],YOFS+cellypos[r],
751 CELL_WIDTH,CELL_HEIGHT);
752 } else {
753 rb->lcd_bitmap_part(sudoku_normal,0,
754 BITMAP_HEIGHT*
755 (state->currentboard[r][c]-'0'),
756 BITMAP_STRIDE,
757 XOFS+cellxpos[c],YOFS+cellypos[r],
758 CELL_WIDTH,CELL_HEIGHT);
759 }
760 }
761 }
762 }
763
764 /* update the screen */
765 rb->lcd_update();
766}
767
768/* Check the status of the board, assuming a change at the cursor location */
769bool check_status(struct sudoku_state_t* state)
770{
771 int check[9];
772 int r,c;
773 int r1,c1;
774 int cell;
775
776 /* First, check the column */
777 for (cell=0;cell<9;cell++) {
778 check[cell]=0;
779 }
780 for (r=0;r<9;r++) {
781 cell=state->currentboard[r][state->x];
782 if (cell!='0') {
783 if (check[cell-'1']==1) {
784 return true;
785 }
786 check[cell-'1']=1;
787 }
788 }
789
790 /* Second, check the row */
791 for (cell=0;cell<9;cell++) {
792 check[cell]=0;
793 }
794 for (c=0;c<9;c++) {
795 cell=state->currentboard[state->y][c];
796 if (cell!='0') {
797 if (check[cell-'1']==1) {
798 return true;
799 }
800 check[cell-'1']=1;
801 }
802 }
803
804 /* Finally, check the 3x3 sub-grid */
805 for (cell=0;cell<9;cell++) {
806 check[cell]=0;
807 }
808 r1=(state->y/3)*3;
809 c1=(state->x/3)*3;
810 for (r=r1;r<r1+3;r++) {
811 for (c=c1;c<c1+3;c++) {
812 cell=state->currentboard[r][c];
813 if (cell!='0') {
814 if (check[cell-'1']==1) {
815 return true;
816 }
817 check[cell-'1']=1;
818 }
819 }
820 }
821
822 /* We passed all the checks :) */
823
824 return false;
825}
826
827void sudoku_generate(struct sudoku_state_t* state)
828{
829 char* difficulty;
830 char str[80];
831
832 clear_state(state);
833 display_board(state);
834 rb->splash(0, true, "Generating...");
835
836 sudoku_generate_board(state,&difficulty);
837
838 rb->snprintf(str,sizeof(str),"Difficulty: %s",difficulty);
839 display_board(state);
840 rb->splash(3*HZ, true, str);
841 rb->strncpy(state->filename,GAME_FILE,MAX_PATH);
842}
843
844int sudoku_menu_cb(int key, int m)
845{
846 (void)m;
847 switch(key)
848 {
849#ifdef MENU_ENTER2
850 case MENU_ENTER2:
851#endif
852 case MENU_ENTER:
853 key = BUTTON_NONE; /* eat the downpress, next menu reacts on release */
854 break;
855
856#ifdef MENU_ENTER2
857 case MENU_ENTER2 | BUTTON_REL:
858#endif
859 case MENU_ENTER | BUTTON_REL:
860 key = MENU_ENTER; /* fake downpress, next menu doesn't like release */
861 break;
862 }
863
864 return key;
865}
866
867bool sudoku_menu(struct sudoku_state_t* state)
868{
869 int m;
870 int result;
871
872 static const struct menu_item items[] = {
873 { "Save", NULL },
874 { "Reload", NULL },
875 { "Clear", NULL },
876 { "Solve", NULL },
877 { "Generate", NULL },
878 { "New", NULL },
879 { "Quit", NULL },
880 };
881
882 m = rb->menu_init(items, sizeof(items) / sizeof(*items),
883 sudoku_menu_cb, NULL, NULL, NULL);
884
885 result=rb->menu_show(m);
886
887 switch (result) {
888 case 0: /* Save state */
889 save_sudoku(state);
890 break;
891
892 case 1: /* Restore state */
893 restore_state(state);
894 break;
895
896 case 2: /* Clear all */
897 clear_board(state);
898 break;
899
900 case 3: /* Solve */
901 sudoku_solve(state);
902 break;
903
904 case 4: /* Generate Game */
905 sudoku_generate(state);
906 break;
907
908 case 5: /* Create a new game manually */
909 clear_state(state);
910 state->editmode=1;
911 break;
912
913 case 6: /* Quit */
914 save_sudoku(state);
915 rb->menu_exit(m);
916 return true;
917 break;
918
919 default:
920 break;
921 }
922
923 rb->menu_exit(m);
924
925 return (result==MENU_ATTACHED_USB);
926}
927
928void move_cursor(struct sudoku_state_t* state, int newx, int newy)
929{
930 int oldx, oldy;
931
932 /* Check that the character at the cursor position is legal */
933 if (check_status(state)) {
934 rb->splash(HZ*2, true, "Illegal move!");
935 /* Ignore any button presses during the splash */
936 rb->button_clear_queue();
937 return;
938 }
939
940 /* Move Cursor */
941 oldx=state->x;
942 oldy=state->y;
943 state->x=newx;
944 state->y=newy;
945
946 /* Redraw current and old cells */
947 update_cell(state,oldx,oldy);
948 update_cell(state,newx,newy);
949}
950
951/* plugin entry point */
952enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
953{
954 bool exit;
955 int button;
956 int lastbutton = BUTTON_NONE;
957 long ticks;
958 struct sudoku_state_t state;
959
960 /* plugin init */
961 rb = api;
962 /* end of plugin init */
963
964 clear_state(&state);
965
966 if (parameter==NULL) {
967 /* We have been started as a plugin - try default sudoku.ss */
968 if (!load_sudoku(&state,GAME_FILE)) {
969 /* No previous game saved, generate one */
970 sudoku_generate(&state);
971 }
972 } else {
973 if (!load_sudoku(&state,(char*)parameter)) {
974 rb->splash(HZ*2, true, "Load error");
975 return(PLUGIN_ERROR);
976 }
977 }
978
979 display_board(&state);
980
981 /* The main game loop */
982 exit=false;
983 ticks=0;
984 while(!exit) {
985 button = rb->button_get(true);
986
987 switch(button){
988 /* Exit game */
989 case SUDOKU_BUTTON_QUIT:
990 exit=1;
991 break;
992
993 /* Increment digit */
994#ifdef SUDOKU_BUTTON_ALTTOGGLE
995 case SUDOKU_BUTTON_ALTTOGGLE | BUTTON_REPEAT:
996#endif
997 case SUDOKU_BUTTON_TOGGLE | BUTTON_REPEAT:
998 /* Slow down the repeat speed to 1/3 second */
999 if ((*rb->current_tick-ticks) < (HZ/3)) {
1000 break;
1001 }
1002
1003#ifdef SUDOKU_BUTTON_ALTTOGGLE
1004 case SUDOKU_BUTTON_ALTTOGGLE:
1005#endif
1006 case SUDOKU_BUTTON_TOGGLE:
1007#ifdef SUDOKU_BUTTON_TOGGLE_PRE
1008 if ((button == SUDOKU_BUTTON_TOGGLE)
1009 && (lastbutton != SUDOKU_BUTTON_TOGGLE_PRE))
1010 break;
1011#endif
1012 /* Increment digit */
1013 ticks=*rb->current_tick;
1014 if (state.editmode) {
1015 if (state.startboard[state.y][state.x]=='9') {
1016 state.startboard[state.y][state.x]='0';
1017 state.currentboard[state.y][state.x]='0';
1018 } else {
1019 state.startboard[state.y][state.x]++;
1020 state.currentboard[state.y][state.x]++;
1021 }
1022 } else {
1023 if (state.startboard[state.y][state.x]=='0') {
1024 if (state.currentboard[state.y][state.x]=='9') {
1025 state.currentboard[state.y][state.x]='0';
1026 } else {
1027 state.currentboard[state.y][state.x]++;
1028 }
1029 }
1030 }
1031 update_cell(&state,state.y,state.x);
1032 break;
1033
1034 /* move cursor left */
1035 case BUTTON_LEFT:
1036 case (BUTTON_LEFT | BUTTON_REPEAT):
1037 if (state.x==0) {
1038 move_cursor(&state,8,state.y);
1039 } else {
1040 move_cursor(&state,state.x-1,state.y);
1041 }
1042 break;
1043
1044 /* move cursor right */
1045 case BUTTON_RIGHT:
1046 case (BUTTON_RIGHT | BUTTON_REPEAT):
1047 if (state.x==8) {
1048 move_cursor(&state,0,state.y);
1049 } else {
1050 move_cursor(&state,state.x+1,state.y);
1051 }
1052 break;
1053
1054 /* move cursor up */
1055 case SUDOKU_BUTTON_UP:
1056 case (SUDOKU_BUTTON_UP | BUTTON_REPEAT):
1057 if (state.y==0) {
1058 move_cursor(&state,state.x,8);
1059 } else {
1060 move_cursor(&state,state.x,state.y-1);
1061 }
1062 break;
1063
1064 /* move cursor down */
1065 case SUDOKU_BUTTON_DOWN:
1066 case (SUDOKU_BUTTON_DOWN | BUTTON_REPEAT):
1067 if (state.y==8) {
1068 move_cursor(&state,state.x,0);
1069 } else {
1070 move_cursor(&state,state.x,state.y+1);
1071 }
1072 break;
1073
1074 case SUDOKU_BUTTON_MENU:
1075#ifdef SUDOKU_BUTTON_MENU_PRE
1076 if (lastbutton != SUDOKU_BUTTON_MENU_PRE)
1077 break;
1078#endif
1079 /* Don't let the user leave a game in a bad state */
1080 if (check_status(&state)) {
1081 rb->splash(HZ*2, true, "Illegal move!");
1082 /* Ignore any button presses during the splash */
1083 rb->button_clear_queue();
1084 } else {
1085 if (state.editmode) {
1086 rb->kbd_input(state.filename,MAX_PATH);
1087 if (save_sudoku(&state)) {
1088 state.editmode=0;
1089 } else {
1090 rb->splash(HZ*2, true, "Save failed");
1091 }
1092 } else {
1093 if (sudoku_menu(&state)) {
1094 return PLUGIN_USB_CONNECTED;
1095 }
1096 }
1097 }
1098 break;
1099#ifdef SUDOKU_BUTTON_POSSIBLE
1100 case SUDOKU_BUTTON_POSSIBLE:
1101 /* Toggle current number in the possiblevals structure */
1102 if (state.currentboard[state.y][state.x]!='0') {
1103 state.possiblevals[state.y][state.x]^=
1104 (1 << (state.currentboard[state.y][state.x] - '0'));
1105 }
1106 break;
1107#endif
1108 default:
1109 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
1110 /* Quit if USB has been connected */
1111 return PLUGIN_USB_CONNECTED;
1112 }
1113 break;
1114 }
1115 if (button != BUTTON_NONE)
1116 lastbutton = button;
1117
1118 display_board(&state);
1119 }
1120
1121 return PLUGIN_OK;
1122}
1123
1124#endif