Remove leftover backslash from macro conversion in FRACTMUL_SHL
[maemo-rb.git] / apps / plugins / sudoku / sudoku.c
blobf0bff4d2a42de93cac94a3b0407371b1ca5b3e5b
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2005 Dave Chapman
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 /***
23 Sudoku by Dave Chapman
25 User instructions
26 -----------------
28 Use the arrow keys to move cursor, and press SELECT/ON/F2 to increment
29 the number under the cursor.
31 At any time during the game, press On to bring up the game menu with
32 further options:
34 Save
35 Reload
36 Clear
37 Solve
39 Sudoku is implemented as a "viewer" for a ".ss" file, as generated by
40 Simple Sudoku and other applications - http://angusj.com/sudoku/
42 In-progress game positions are saved in the original .ss file, with
43 A-I used to indicate numbers entered by the user.
45 Example ".ss" file, and one with a saved state:
47 ...|...|... ...|...|...
48 2..|8.4|9.1 2.C|8.4|9.1
49 ...|1.6|32. E..|1.6|32.
50 ----------- -----------
51 ...|..5|.4. ...|..5|.4.
52 8..|423|..6 8..|423|..6
53 .3.|9..|... .3D|9..|A..
54 ----------- -----------
55 .63|7.9|... .63|7.9|...
56 4.9|5.2|..8 4.9|5.2|.C8
57 ...|...|... ...|...|...
61 #include "plugin.h"
62 #include "lib/configfile.h"
64 #include <lib/playback_control.h>
65 #include "sudoku.h"
66 #include "generator.h"
68 /* The bitmaps */
69 #include "pluginbitmaps/sudoku_normal.h"
70 #include "pluginbitmaps/sudoku_inverse.h"
71 #include "pluginbitmaps/sudoku_start.h"
73 #define BITMAP_HEIGHT (BMPHEIGHT_sudoku_normal/10)
74 #define BITMAP_STRIDE STRIDE(SCREEN_MAIN, BMPWIDTH_sudoku_normal, BMPHEIGHT_sudoku_normal)
76 #if (LCD_DEPTH>2)
77 #define BITMAP_WIDTH (BMPWIDTH_sudoku_normal/2)
78 #else
79 #define BITMAP_WIDTH BMPWIDTH_sudoku_normal
80 #endif
84 /* Default game - used to initialise sudoku.ss if it doesn't exist. */
85 static const char default_game[9][9] =
87 { '0','1','0', '3','0','7', '0','0','4' },
88 { '0','0','0', '0','6','0', '1','0','2' },
89 { '0','0','0', '0','8','0', '5','6','0' },
91 { '0','6','0', '0','0','0', '0','2','9' },
92 { '0','0','0', '5','0','3', '0','0','0' },
93 { '7','9','0', '0','0','0', '0','3','0' },
95 { '0','8','5', '0','3','0', '0','0','0' },
96 { '1','0','2', '0','7','0', '0','0','0' },
97 { '0','0','0', '4','0','8', '0','5','0' },
100 #if LCD_HEIGHT <= LCD_WIDTH /* Horizontal layout, scratchpad at the left */
102 #if (LCD_HEIGHT==64) && (LCD_WIDTH==112 || LCD_WIDTH==128)
103 /* Archos Recorders and Ondios - 112x64, 9 cells @ 8x6 with 10 border lines */
104 #define SMALL_BOARD
105 #define MARK_OFFS 1 /* Pixels between border and mark */
106 #define MARK_SPACE 1 /* Pixels between two marks */
107 #define MARK_SIZE 1 /* Mark width and height */
109 #elif ((LCD_HEIGHT==80) && (LCD_WIDTH==132))
110 /* C200, 9 cells @ 8x8 with 8 border lines */
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==96) && (LCD_WIDTH==128))
117 /* iAudio M3, 9 cells @ 9x9 with 14 border lines */
118 #define MARK_OFFS 1 /* Pixels between border and mark */
119 #define MARK_SPACE 2 /* Pixels between two marks */
120 #define MARK_SIZE 1 /* Mark width and height */
122 #elif (LCD_HEIGHT==110) && (LCD_WIDTH==138) \
123 || (LCD_HEIGHT==128) && (LCD_WIDTH==128)
124 /* iPod Mini - 138x110, 9 cells @ 10x10 with 14 border lines */
125 /* iriver H10 5-6GB - 128x128, 9 cells @ 10x10 with 14 border lines */
126 #define MARK_OFFS 1 /* Pixels between border and mark */
127 #define MARK_SPACE 1 /* Pixels between two marks */
128 #define MARK_SIZE 2 /* Mark width and height */
130 #elif ((LCD_HEIGHT==128) && (LCD_WIDTH==160)) \
131 || ((LCD_HEIGHT==132) && (LCD_WIDTH==176))
132 /* iAudio X5, Iriver H1x0, iPod G3, G4 - 160x128; */
133 /* iPod Nano - 176x132, 9 cells @ 12x12 with 14 border lines */
134 #define MARK_OFFS 1 /* Pixels between border and mark */
135 #define MARK_SPACE 2 /* Pixels between two marks */
136 #define MARK_SIZE 2 /* Mark width and height */
138 #elif ((LCD_HEIGHT==176) && (LCD_WIDTH==220))
139 /* Iriver h300, iPod Color/Photo - 220x176, 9 cells @ 16x16 with 14 border lines */
140 #define MARK_OFFS 1 /* Pixels between border and mark */
141 #define MARK_SPACE 1 /* Pixels between two marks */
142 #define MARK_SIZE 4 /* Mark width and height */
144 #elif (LCD_HEIGHT==240) && (LCD_WIDTH==320)
145 /* iPod Video - 320x240, 9 cells @ 24x24 with 14 border lines */
146 #define MARK_OFFS 1 /* Pixels between border and mark */
147 #define MARK_SPACE 2 /* Pixels between two marks */
148 #define MARK_SIZE 6 /* Mark width and height */
150 #elif (LCD_HEIGHT==480) && (LCD_WIDTH==640)
151 /* M:Robe 500 - 640x480, 9 cells @ 48x48 with 14 border lines */
152 #define MARK_OFFS 1 /* Pixels between border and mark */
153 #define MARK_SPACE 2 /* Pixels between two marks */
154 #define MARK_SIZE 6 /* Mark width and height */
156 #else
157 #error SUDOKU: Unsupported LCD size
158 #endif
160 #else /* Vertical layout, scratchpad at the bottom */
161 #define VERTICAL_LAYOUT
163 #if ((LCD_HEIGHT==220) && (LCD_WIDTH==176))
164 /* e200, 9 cells @ 16x16 with 14 border lines */
165 #define MARK_OFFS 1 /* Pixels between border and mark */
166 #define MARK_SPACE 1 /* Pixels between two marks */
167 #define MARK_SIZE 4 /* Mark width and height */
169 #elif (LCD_HEIGHT>=320) && (LCD_WIDTH>=240)
170 /* Gigabeat - 240x320, 9 cells @ 24x24 with 14 border lines */
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 #elif ((LCD_HEIGHT==160) && (LCD_WIDTH==128))
176 /* Philips GoGear SA9200 - 128x160, 9 cells @ 10x10 with 14 border tiles */
177 #define MARK_OFFS 1 /* Pixels between border and mark */
178 #define MARK_SPACE 1 /* Pixels between two marks */
179 #define MARK_SIZE 2 /* Mark width and height */
181 #else
182 #error SUDOKU: Unsupported LCD size
183 #endif
185 #endif /* Layout */
187 #define CELL_WIDTH BITMAP_WIDTH
188 #define CELL_HEIGHT BITMAP_HEIGHT
190 #ifdef SUDOKU_BUTTON_CHANGEDIR
191 int invertdir=0;
192 #else
193 #define invertdir 0
194 #endif
196 #define CFGFILE_VERSION 0 /* Current config file version */
197 #define CFGFILE_MINVERSION 0 /* Minimum config file version to accept */
199 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
200 /* settings */
201 struct sudoku_config {
202 #ifdef HAVE_LCD_COLOR
203 int number_display;
204 #endif
205 #ifdef SUDOKU_BUTTON_POSSIBLE
206 int show_markings;
207 #endif
210 struct sudoku_config sudcfg_disk = {
211 #ifdef HAVE_LCD_COLOR
213 #endif
214 #ifdef SUDOKU_BUTTON_POSSIBLE
216 #endif
218 struct sudoku_config sudcfg;
220 static const char cfg_filename[] = "sudoku.cfg";
221 #ifdef HAVE_LCD_COLOR
222 static char *number_str[2] = { "black", "coloured" };
223 #endif
224 #ifdef SUDOKU_BUTTON_POSSIBLE
225 static char *mark_str[2] = { "hide", "show" };
226 #endif
228 struct configdata disk_config[] = {
229 #ifdef HAVE_LCD_COLOR
230 { TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.number_display }, "numbers",
231 number_str },
232 #endif
233 #ifdef SUDOKU_BUTTON_POSSIBLE
234 { TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.show_markings }, "markings",
235 mark_str },
236 #endif
238 #endif
239 #ifdef HAVE_LCD_COLOR
240 #define NUMBER_TYPE (sudcfg.number_display*CELL_WIDTH)
241 #else
242 #define NUMBER_TYPE 0
243 #endif
245 /* Size dependent build-time calculations */
246 #ifdef SMALL_BOARD
247 #define BOARD_WIDTH (CELL_WIDTH*9+10)
248 #define BOARD_HEIGHT (CELL_HEIGHT*9+10)
249 static unsigned int cellxpos[9]={
250 1, (CELL_WIDTH+2), (2*CELL_WIDTH+3),
251 (3*CELL_WIDTH+4), (4*CELL_WIDTH+5), (5*CELL_WIDTH+6),
252 (6*CELL_WIDTH+7), (7*CELL_WIDTH+8), (8*CELL_WIDTH+9)
254 static unsigned int cellypos[9]={
255 1, (CELL_HEIGHT+2), (2*CELL_HEIGHT+3),
256 (3*CELL_HEIGHT+4), (4*CELL_HEIGHT+5), (5*CELL_HEIGHT+6),
257 (6*CELL_HEIGHT+7), (7*CELL_HEIGHT+8), (8*CELL_HEIGHT+9)
259 #else /* !SMALL_BOARD */
260 #define BOARD_WIDTH (CELL_WIDTH*9+10+4)
261 #define BOARD_HEIGHT (CELL_HEIGHT*9+10+4)
262 static unsigned int cellxpos[9]={
263 2, (CELL_WIDTH +3), (2*CELL_WIDTH +4),
264 (3*CELL_WIDTH +6), (4*CELL_WIDTH +7), (5*CELL_WIDTH +8),
265 (6*CELL_WIDTH+10), (7*CELL_WIDTH+11), (8*CELL_WIDTH+12)
267 static unsigned int cellypos[9]={
268 2, (CELL_HEIGHT +3), (2*CELL_HEIGHT +4),
269 (3*CELL_HEIGHT +6), (4*CELL_HEIGHT +7), (5*CELL_HEIGHT +8),
270 (6*CELL_HEIGHT+10), (7*CELL_HEIGHT+11), (8*CELL_HEIGHT+12)
272 #endif
274 #ifdef VERTICAL_LAYOUT
275 #define XOFS ((LCD_WIDTH-BOARD_WIDTH)/2)
276 #define YOFS ((LCD_HEIGHT-(BOARD_HEIGHT+CELL_HEIGHT*2+2))/2)
277 #define YOFSSCRATCHPAD (YOFS+BOARD_HEIGHT+CELL_WIDTH)
278 #else
279 #define XOFSSCRATCHPAD ((LCD_WIDTH-(BOARD_WIDTH+CELL_WIDTH*2+2))/2)
280 #define XOFS (XOFSSCRATCHPAD+CELL_WIDTH*2+2)
281 #define YOFS ((LCD_HEIGHT-BOARD_HEIGHT)/2)
282 #endif
284 #define BLOCK 3
285 #define SIZE (BLOCK*BLOCK)
287 void sudoku_solve(struct sudoku_state_t* state)
289 bool ret = sudoku_solve_board(state);
291 if (!ret) {
292 rb->splash(HZ*2, "Solve failed");
295 return;
298 /* Copies the current to the saved board */
299 static void save_state(struct sudoku_state_t *state)
301 rb->memcpy(state->savedboard, state->currentboard,
302 sizeof(state->savedboard));
303 #ifdef SUDOKU_BUTTON_POSSIBLE
304 rb->memcpy(state->savedpossible, state->possiblevals,
305 sizeof(state->savedpossible));
306 #endif
309 /* Copies the saved to the current board */
310 static void restore_state(struct sudoku_state_t *state)
312 rb->memcpy(state->currentboard, state->savedboard,
313 sizeof(state->savedboard));
314 #ifdef SUDOKU_BUTTON_POSSIBLE
315 rb->memcpy(state->possiblevals, state->savedpossible,
316 sizeof(state->possiblevals));
317 #endif
320 void default_state(struct sudoku_state_t* state)
322 int r,c;
324 rb->strlcpy(state->filename,GAME_FILE,MAX_PATH);
325 for (r=0;r<9;r++) {
326 for (c=0;c<9;c++) {
327 state->startboard[r][c]=default_game[r][c];
328 state->currentboard[r][c]=default_game[r][c];
329 #ifdef SUDOKU_BUTTON_POSSIBLE
330 state->possiblevals[r][c]=0;
331 #endif
335 /* initialize the saved board so reload function works */
336 save_state(state);
338 state->x=0;
339 state->y=0;
340 state->editmode=0;
343 void clear_state(struct sudoku_state_t* state)
345 int r,c;
347 rb->strlcpy(state->filename,GAME_FILE,MAX_PATH);
348 for (r=0;r<9;r++) {
349 for (c=0;c<9;c++) {
350 state->startboard[r][c]='0';
351 state->currentboard[r][c]='0';
352 #ifdef SUDOKU_BUTTON_POSSIBLE
353 state->possiblevals[r][c]=0;
354 #endif
358 state->x=0;
359 state->y=0;
360 state->editmode=0;
363 /* Check the status of the board, assuming a change at the cursor location */
364 bool check_status(struct sudoku_state_t* state)
366 int check[9];
367 int r,c;
368 int r1,c1;
369 int cell;
371 /* First, check the column */
372 for (cell=0;cell<9;cell++) {
373 check[cell]=0;
375 for (r=0;r<9;r++) {
376 cell=state->currentboard[r][state->x];
377 if (cell!='0') {
378 if (check[cell-'1']==1) {
379 return true;
381 check[cell-'1']=1;
385 /* Second, check the row */
386 for (cell=0;cell<9;cell++) {
387 check[cell]=0;
389 for (c=0;c<9;c++) {
390 cell=state->currentboard[state->y][c];
391 if (cell!='0') {
392 if (check[cell-'1']==1) {
393 return true;
395 check[cell-'1']=1;
399 /* Finally, check the 3x3 sub-grid */
400 for (cell=0;cell<9;cell++) {
401 check[cell]=0;
403 r1=(state->y/3)*3;
404 c1=(state->x/3)*3;
405 for (r=r1;r<r1+3;r++) {
406 for (c=c1;c<c1+3;c++) {
407 cell=state->currentboard[r][c];
408 if (cell!='0') {
409 if (check[cell-'1']==1) {
410 return true;
412 check[cell-'1']=1;
417 /* We passed all the checks :) */
419 return false;
422 /* Load game - only ".ss" is officially supported, but any sensible
423 text representation (one line per row) may load.
425 bool load_sudoku(struct sudoku_state_t* state, char* filename)
427 int fd;
428 size_t n;
429 int r = 0, c = 0, d = 0;
430 unsigned int i;
431 int valid=0;
432 char buf[500]; /* A buffer to read a sudoku board from */
434 fd=rb->open(filename, O_RDONLY);
435 if (fd < 0) {
436 LOGF("Invalid sudoku file: %s\n",filename);
437 return(false);
440 rb->strlcpy(state->filename,filename,MAX_PATH);
441 n=rb->read(fd,buf,500);
442 if (n <= 0) {
443 return(false);
445 rb->close(fd);
446 r=0;
447 c=0;
448 i=0;
449 d=0;
450 while ((i < n) && (r < 9)) {
451 switch (buf[i]){
452 case ' ': case '\t':
453 if (c > 0)
454 valid=1;
455 break;
456 case '|':
457 case '*':
458 case '-':
459 case '\r':
460 break;
461 case '\n':
462 if (valid) {
463 r++;
464 valid=0;
466 c = 0;
467 d = 0;
468 break;
469 case '_': case '.':
470 valid=1;
471 if (c >= SIZE || r >= SIZE){
472 LOGF("ERROR: sudoku problem is the wrong size (%d,%d)\n",
473 c, r);
474 return(false);
476 c++;
477 break;
478 default:
479 if (((buf[i]>='A') && (buf[i]<='I')) ||
480 ((buf[i]>='0') && (buf[i]<='9'))) {
481 valid=1;
482 if (r >= SIZE || c >= SIZE){
483 LOGF("ERROR: sudoku problem is the wrong size "
484 "(%d,%d)\n", c, r);
485 return(false);
487 if ((buf[i]>='0') && (buf[i]<='9')) {
488 state->startboard[r][c]=buf[i];
489 state->currentboard[r][c]=buf[i];
490 } else {
491 state->currentboard[r][c]='1'+(buf[i]-'A');
493 c++;
495 if((buf[i]>='a' && buf[i] <= 'z') && i < (n-1)
496 && (buf[i+1] >= 'a' && buf[i+1] <= 'z')) {
497 state->possiblevals[r][d]
498 = (((buf[i]-'a') * 26 + buf[i+1]-'a')<<1);
499 i++;
500 d++;
502 /* Ignore any other characters */
503 break;
505 i++;
508 /* Check that the board is valid - we need to check every row/column
509 and block individually */
510 for (state->y = 0; state->y < 9; state->y++) {
511 state->x = (state->y%3)*3 + (state->y/3);
512 if (check_status(state)) return false;
514 state->x = 0;
515 state->y = 0;
517 /* Save a copy of the saved state - so we can reload without using the
518 disk */
519 save_state(state);
520 return(true);
523 bool save_sudoku(struct sudoku_state_t* state)
525 int fd;
526 int r,c;
527 int i;
528 #ifdef SUDOKU_BUTTON_POSSIBLE
529 int x;
530 char line[41]="...|...|... ; \r\n";
531 #else
532 char line[13]="...|...|...\r\n";
533 #endif
534 char sep[13]="-----------\r\n";
536 rb->splash(0, "Saving...");
538 if (state->filename[0]==0) {
539 return false;
542 fd=rb->open(state->filename, O_WRONLY|O_CREAT, 0666);
543 if (fd >= 0) {
544 for (r=0;r<9;r++) {
545 i=0;
546 for (c=0;c<9;c++) {
547 if (state->startboard[r][c]!='0') {
548 line[i]=state->startboard[r][c];
549 } else if (state->currentboard[r][c]!='0') {
550 line[i]='A'+(state->currentboard[r][c]-'1');
551 } else {
552 line[i]='.';
554 i++;
555 if ((c==2) || (c==5)) {
556 i++;
559 #ifdef SUDOKU_BUTTON_POSSIBLE
560 i+=2;
561 for(c=0; c<9; c++) {
562 x = ((state->possiblevals[r][c]>>1)/26);
563 line[i++] = x + 'a';
564 x = ((state->possiblevals[r][c]>>1)%26);
565 line[i++] = x + 'a';
567 #endif
568 rb->write(fd,line,sizeof(line));
569 if ((r==2) || (r==5)) {
570 rb->write(fd,sep,sizeof(sep));
573 /* Add a blank line at end */
574 rb->write(fd,"\r\n",2);
575 rb->close(fd);
576 rb->reload_directory();
577 /* Save a copy of the saved state - so we can reload without
578 using the disk */
579 save_state(state);
580 return true;
581 } else {
582 return false;
586 void clear_board(struct sudoku_state_t* state)
588 int r,c;
590 for (r=0;r<9;r++) {
591 for (c=0;c<9;c++) {
592 state->currentboard[r][c]=state->startboard[r][c];
595 state->x=0;
596 state->y=0;
599 void update_cell(struct sudoku_state_t* state, int r, int c)
601 /* We have four types of cell:
602 1) User-entered number
603 2) Starting number
604 3) Cursor in cell
607 if ((r==state->y) && (c==state->x)) {
608 rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE,
609 BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
610 BITMAP_STRIDE,
611 XOFS+cellxpos[c],YOFS+cellypos[r],CELL_WIDTH,
612 CELL_HEIGHT);
613 } else {
614 if (state->startboard[r][c]!='0') {
615 rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE,
616 BITMAP_HEIGHT*(state->startboard[r][c]-'0'),
617 BITMAP_STRIDE,
618 XOFS+cellxpos[c],YOFS+cellypos[r],
619 CELL_WIDTH,CELL_HEIGHT);
620 } else {
621 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,
622 BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
623 BITMAP_STRIDE,
624 XOFS+cellxpos[c],YOFS+cellypos[r],
625 CELL_WIDTH,CELL_HEIGHT);
629 rb->lcd_update_rect(cellxpos[c],cellypos[r],CELL_WIDTH,CELL_HEIGHT);
633 void display_board(struct sudoku_state_t* state)
635 int r,c;
636 #ifdef SUDOKU_BUTTON_POSSIBLE
637 int i;
638 #endif
640 /* Clear the display buffer */
641 rb->lcd_clear_display();
643 /* Draw the gridlines - differently for different targets */
645 #ifdef SMALL_BOARD
646 /* Small targets - draw dotted/single lines */
647 for (r=0;r<9;r++) {
648 if ((r % 3)==0) {
649 /* Solid Line */
650 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
651 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
652 } else {
653 /* Dotted line */
654 for (c=XOFS;c<XOFS+BOARD_WIDTH;c+=2) {
655 rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
657 for (c=YOFS;c<YOFS+BOARD_HEIGHT;c+=2) {
658 rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
662 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
663 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
664 #else
665 /* Large targets - draw single/double lines */
666 for (r=0;r<9;r++) {
667 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
668 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
669 if ((r % 3)==0) {
670 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-2);
671 rb->lcd_vline(XOFS+cellxpos[r]-2,YOFS,YOFS+BOARD_HEIGHT-1);
674 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
675 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT+1);
676 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
677 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
678 #endif
680 #ifdef SUDOKU_BUTTON_POSSIBLE
681 #ifdef VERTICAL_LAYOUT
682 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD);
683 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD+CELL_HEIGHT+1);
684 for (r=0;r<9;r++) {
685 #ifdef SMALL_BOARD
686 /* Small targets - draw dotted/single lines */
687 if ((r % 3)==0) {
688 /* Solid Line */
689 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
690 YOFSSCRATCHPAD+CELL_HEIGHT+1);
691 } else {
692 /* Dotted line */
693 for (c=YOFSSCRATCHPAD;c<YOFSSCRATCHPAD+CELL_HEIGHT+1;c+=2) {
694 rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
697 #else
698 /* Large targets - draw single/double lines */
699 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
700 YOFSSCRATCHPAD+CELL_HEIGHT+1);
701 if ((r % 3)==0)
702 rb->lcd_vline(XOFS+cellxpos[r]-2,YOFSSCRATCHPAD,
703 YOFSSCRATCHPAD+CELL_HEIGHT+1);
704 #endif
705 if ((r>0) && state->possiblevals[state->y][state->x]&BIT_N(r))
706 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
707 BITMAP_STRIDE,XOFS+cellxpos[r-1],
708 YOFSSCRATCHPAD+1,CELL_WIDTH,CELL_HEIGHT);
710 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFSSCRATCHPAD,
711 YOFSSCRATCHPAD+CELL_HEIGHT+1);
712 #ifndef SMALL_BOARD
713 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFSSCRATCHPAD,
714 YOFSSCRATCHPAD+CELL_HEIGHT+1);
715 #endif
716 if (state->possiblevals[state->y][state->x]&BIT_N(r))
717 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
718 BITMAP_STRIDE,XOFS+cellxpos[8],YOFSSCRATCHPAD+1,
719 CELL_WIDTH,CELL_HEIGHT);
720 #else /* Horizontal layout */
721 rb->lcd_vline(XOFSSCRATCHPAD,YOFS,YOFS+BOARD_HEIGHT-1);
722 rb->lcd_vline(XOFSSCRATCHPAD+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
723 for (r=0;r<9;r++) {
724 #ifdef SMALL_BOARD
725 /* Small targets - draw dotted/single lines */
726 if ((r % 3)==0) {
727 /* Solid Line */
728 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
729 YOFS+cellypos[r]-1);
730 } else {
731 /* Dotted line */
732 for (c=XOFSSCRATCHPAD;c<XOFSSCRATCHPAD+CELL_WIDTH+1;c+=2) {
733 rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
736 #else
737 /* Large targets - draw single/double lines */
738 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
739 YOFS+cellypos[r]-1);
740 if ((r % 3)==0)
741 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
742 YOFS+cellypos[r]-2);
743 #endif
744 if ((r>0) && state->possiblevals[state->y][state->x]&BIT_N(r))
745 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
746 BITMAP_STRIDE,XOFSSCRATCHPAD+1,
747 YOFS+cellypos[r-1],CELL_WIDTH,CELL_HEIGHT);
749 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
750 YOFS+cellypos[8]+CELL_HEIGHT);
751 #ifndef SMALL_BOARD
752 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
753 YOFS+cellypos[8]+CELL_HEIGHT+1);
754 #endif
755 if (state->possiblevals[state->y][state->x]&BIT_N(r))
756 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
757 BITMAP_STRIDE,XOFSSCRATCHPAD+1,YOFS+cellypos[8],
758 CELL_WIDTH,CELL_HEIGHT);
759 #endif /* Layout */
760 #endif /* SUDOKU_BUTTON_POSSIBLE */
762 /* Draw the numbers */
763 for (r=0;r<9;r++) {
764 for (c=0;c<9;c++) {
765 /* We have four types of cell:
766 1) User-entered number
767 2) Starting number
768 3) Cursor in cell
771 if ((r==state->y) && (c==state->x)) {
772 rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE,
773 BITMAP_HEIGHT*(state->currentboard[r][c]-
774 '0'),
775 BITMAP_STRIDE,
776 XOFS+cellxpos[c],YOFS+cellypos[r],
777 CELL_WIDTH,CELL_HEIGHT);
778 } else {
779 if (state->startboard[r][c]!='0') {
780 rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE,
781 BITMAP_HEIGHT*(state->startboard[r][c]-
782 '0'),
783 BITMAP_STRIDE,
784 XOFS+cellxpos[c],YOFS+cellypos[r],
785 CELL_WIDTH,CELL_HEIGHT);
786 } else {
787 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,
788 BITMAP_HEIGHT*
789 (state->currentboard[r][c]-'0'),
790 BITMAP_STRIDE,
791 XOFS+cellxpos[c],YOFS+cellypos[r],
792 CELL_WIDTH,CELL_HEIGHT);
795 #ifdef SUDOKU_BUTTON_POSSIBLE
796 /* Draw the possible number markings on the board */
797 if(sudcfg.show_markings && state->startboard[r][c]=='0'
798 && state->currentboard[r][c]=='0') {
799 for(i=0;i<9;i++) {
800 if(state->possiblevals[r][c]&(2<<i)) {
801 #if LCD_DEPTH > 1
802 /* draw markings in dark grey */
803 rb->lcd_set_foreground(LCD_DARKGRAY);
804 #endif
805 rb->lcd_fillrect(XOFS+cellxpos[c]+MARK_OFFS
806 +(i%3)*(MARK_SIZE+MARK_SPACE),
807 YOFS+cellypos[r]+MARK_OFFS
808 +(i/3)*(MARK_SIZE+MARK_SPACE),
809 MARK_SIZE,
810 MARK_SIZE);
811 #if LCD_DEPTH > 1
812 rb->lcd_set_foreground(LCD_BLACK);
813 #endif
817 #endif /* SUDOKU_BUTTON_POSSIBLE */
822 /* update the screen */
823 rb->lcd_update();
826 bool sudoku_generate(struct sudoku_state_t* state)
828 char* difficulty;
829 char str[80];
830 bool res;
831 struct sudoku_state_t new_state;
833 clear_state(&new_state);
834 display_board(&new_state);
835 rb->splash(0, "Generating...");
837 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
838 rb->cpu_boost(true);
839 #endif
841 res = sudoku_generate_board(&new_state,&difficulty);
843 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
844 rb->cpu_boost(false);
845 #endif
847 if (res) {
848 rb->memcpy(state,&new_state,sizeof(new_state));
849 rb->snprintf(str,sizeof(str),"Difficulty: %s",difficulty);
850 display_board(state);
851 rb->splash(HZ*3, str);
852 rb->strlcpy(state->filename,GAME_FILE,MAX_PATH);
853 } else {
854 display_board(&new_state);
855 rb->splash(HZ*2, "Aborted");
857 /* initialize the saved board so reload function works */
858 save_state(state);
859 return res;
862 #ifdef HAVE_LCD_COLOR
863 static bool numdisplay_setting(void)
865 static const struct opt_items names[] = {
866 {"Black", -1},
867 {"Coloured", -1},
870 return rb->set_option("Number Display", &sudcfg.number_display, INT, names,
871 sizeof(names) / sizeof(names[0]), NULL);
873 #endif
875 #ifdef SUDOKU_BUTTON_POSSIBLE
876 static bool showmarkings_setting(void)
878 static const struct opt_items names[] = {
879 {"Hide", -1},
880 {"Show", -1},
883 return rb->set_option("Show Markings", &sudcfg.show_markings, INT, names,
884 sizeof(names) / sizeof(names[0]), NULL);
886 #endif
888 enum {
889 SM_AUDIO_PLAYBACK = 0,
890 #ifdef HAVE_LCD_COLOR
891 SM_NUMBER_DISPLAY,
892 #endif
893 #ifdef SUDOKU_BUTTON_POSSIBLE
894 SM_SHOW_MARKINGS,
895 #endif
896 SM_SAVE,
897 SM_RELOAD,
898 SM_CLEAR,
899 SM_SOLVE,
900 SM_GENERATE,
901 SM_NEW,
902 SM_QUIT,
905 int sudoku_menu(struct sudoku_state_t* state)
907 int result;
909 MENUITEM_STRINGLIST(menu, "Sudoku Menu", NULL,
910 "Audio Playback",
911 #ifdef HAVE_LCD_COLOR
912 "Number Display",
913 #endif
914 #ifdef SUDOKU_BUTTON_POSSIBLE
915 "Show Markings",
916 #endif
917 "Save", "Reload", "Clear", "Solve",
918 "Generate", "New", "Quit");
920 result = rb->do_menu(&menu, NULL, NULL, false);
922 switch (result) {
923 case SM_AUDIO_PLAYBACK:
924 playback_control(NULL);
925 break;
927 #ifdef HAVE_LCD_COLOR
928 case SM_NUMBER_DISPLAY:
929 numdisplay_setting();
930 break;
931 #endif
933 #ifdef SUDOKU_BUTTON_POSSIBLE
934 case SM_SHOW_MARKINGS:
935 showmarkings_setting();
936 break;
937 #endif
938 case SM_SAVE:
939 save_sudoku(state);
940 break;
942 case SM_RELOAD:
943 restore_state(state);
944 break;
946 case SM_CLEAR:
947 clear_board(state);
948 break;
950 case SM_SOLVE:
951 sudoku_solve(state);
952 break;
954 case SM_GENERATE:
955 sudoku_generate(state);
956 break;
958 case SM_NEW:
959 clear_state(state);
960 state->editmode=1;
961 break;
963 case SM_QUIT:
964 save_sudoku(state);
965 break;
967 default:
968 break;
971 return result;
974 /* Menu used when user is in edit mode - i.e. creating a new game manually */
975 int sudoku_edit_menu(struct sudoku_state_t* state)
977 int result;
979 MENUITEM_STRINGLIST(menu, "Edit Menu", NULL,
980 "Save as", "Quit");
982 result = rb->do_menu(&menu, NULL, NULL, false);
984 switch (result) {
985 case 0: /* Save new game */
986 rb->kbd_input(state->filename,MAX_PATH);
987 if (save_sudoku(state)) {
988 state->editmode=0;
989 } else {
990 rb->splash(HZ*2, "Save failed");
992 break;
994 case 1: /* Quit */
995 break;
997 default:
998 break;
1001 return result;
1004 void move_cursor(struct sudoku_state_t* state, int newx, int newy)
1006 int oldx, oldy;
1008 /* Check that the character at the cursor position is legal */
1009 if (check_status(state)) {
1010 rb->splash(HZ*2, "Illegal move!");
1011 /* Ignore any button presses during the splash */
1012 rb->button_clear_queue();
1013 return;
1016 /* Move Cursor */
1017 oldx=state->x;
1018 oldy=state->y;
1019 state->x=newx;
1020 state->y=newy;
1022 /* Redraw current and old cells */
1023 update_cell(state,oldx,oldy);
1024 update_cell(state,newx,newy);
1027 /* plugin entry point */
1028 enum plugin_status plugin_start(const void* parameter)
1030 bool exit;
1031 int button;
1032 #if defined(SUDOKU_BUTTON_TOGGLE_PRE) || defined(SUDOKU_BUTTON_MENU_PRE)
1033 int lastbutton = BUTTON_NONE;
1034 #endif
1035 int res;
1036 int rc = PLUGIN_OK;
1037 long ticks;
1038 struct sudoku_state_t state;
1040 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
1041 configfile_load(cfg_filename, disk_config,
1042 sizeof(disk_config) / sizeof(disk_config[0]),
1043 CFGFILE_MINVERSION);
1044 rb->memcpy(&sudcfg, &sudcfg_disk, sizeof(sudcfg)); /* copy to running config */
1045 #endif
1047 #if LCD_DEPTH > 1
1048 rb->lcd_set_backdrop(NULL);
1049 rb->lcd_set_foreground(LCD_BLACK);
1050 rb->lcd_set_background(LCD_WHITE);
1051 #endif
1053 clear_state(&state);
1055 if (parameter==NULL) {
1056 /* We have been started as a plugin - try default sudoku.ss */
1057 if (!load_sudoku(&state,GAME_FILE)) {
1058 /* No previous game saved, use the default */
1059 default_state(&state);
1061 } else {
1062 if (!load_sudoku(&state,(char*)parameter)) {
1063 rb->splash(HZ*2, "Load error");
1064 return(PLUGIN_ERROR);
1069 display_board(&state);
1071 /* The main game loop */
1072 exit=false;
1073 ticks=0;
1074 while(!exit) {
1075 button = rb->button_get(true);
1077 switch(button){
1078 #ifdef SUDOKU_BUTTON_QUIT
1079 /* Exit game */
1080 case SUDOKU_BUTTON_QUIT:
1081 if (check_status(&state)) {
1082 rb->splash(HZ*2, "Illegal move!");
1083 /* Ignore any button presses during the splash */
1084 rb->button_clear_queue();
1085 } else {
1086 save_sudoku(&state);
1087 exit=true;
1089 break;
1090 #endif
1092 /* Increment digit */
1093 #ifdef SUDOKU_BUTTON_ALTTOGGLE
1094 case SUDOKU_BUTTON_ALTTOGGLE | BUTTON_REPEAT:
1095 #endif
1096 case SUDOKU_BUTTON_TOGGLE | BUTTON_REPEAT:
1097 /* Slow down the repeat speed to 1/3 second */
1098 if ((*rb->current_tick-ticks) < (HZ/3)) {
1099 break;
1102 #ifdef SUDOKU_BUTTON_ALTTOGGLE
1103 case SUDOKU_BUTTON_ALTTOGGLE:
1104 #endif
1105 case SUDOKU_BUTTON_TOGGLE:
1106 #ifdef SUDOKU_BUTTON_TOGGLE_PRE
1107 if ((button == SUDOKU_BUTTON_TOGGLE)
1108 && (lastbutton != SUDOKU_BUTTON_TOGGLE_PRE))
1109 break;
1110 #endif
1111 /* Increment digit */
1112 ticks=*rb->current_tick;
1113 if (state.editmode) {
1114 if (state.startboard[state.y][state.x]=='9') {
1115 state.startboard[state.y][state.x]='0';
1116 state.currentboard[state.y][state.x]='0';
1117 } else {
1118 state.startboard[state.y][state.x]++;
1119 state.currentboard[state.y][state.x]++;
1121 } else {
1122 if (state.startboard[state.y][state.x]=='0') {
1123 if (state.currentboard[state.y][state.x]=='9') {
1124 state.currentboard[state.y][state.x]='0';
1125 } else {
1126 state.currentboard[state.y][state.x]++;
1130 update_cell(&state,state.y,state.x);
1131 break;
1133 #ifdef SUDOKU_BUTTON_TOGGLEBACK
1134 case SUDOKU_BUTTON_TOGGLEBACK | BUTTON_REPEAT:
1135 /* Slow down the repeat speed to 1/3 second */
1136 if ((*rb->current_tick-ticks) < (HZ/3)) {
1137 break;
1140 case SUDOKU_BUTTON_TOGGLEBACK:
1141 /* Decrement digit */
1142 ticks=*rb->current_tick;
1143 if (state.editmode) {
1144 if (state.startboard[state.y][state.x]=='0') {
1145 state.startboard[state.y][state.x]='9';
1146 state.currentboard[state.y][state.x]='9';
1147 } else {
1148 state.startboard[state.y][state.x]--;
1149 state.currentboard[state.y][state.x]--;
1151 } else {
1152 if (state.startboard[state.y][state.x]=='0') {
1153 if (state.currentboard[state.y][state.x]=='0') {
1154 state.currentboard[state.y][state.x]='9';
1155 } else {
1156 state.currentboard[state.y][state.x]--;
1160 update_cell(&state,state.y,state.x);
1161 break;
1162 #endif
1164 /* move cursor left */
1165 case SUDOKU_BUTTON_LEFT:
1166 case (SUDOKU_BUTTON_LEFT | BUTTON_REPEAT):
1167 if ( (state.x==0&&invertdir==0) || (state.y==0&&invertdir==1) ) {
1168 #ifndef SUDOKU_BUTTON_UP
1169 if ( (state.y==0&&invertdir==0) || (state.x==0&&invertdir==1)) {
1170 move_cursor(&state,8,8);
1171 } else {
1172 if (invertdir==0) {
1173 move_cursor(&state,8,state.y-1);
1174 } else {
1175 move_cursor(&state,state.x-1,8);
1178 #else
1179 move_cursor(&state,8,state.y);
1180 #endif
1181 } else {
1182 if (invertdir==0) {
1183 move_cursor(&state,state.x-1,state.y);
1184 } else {
1185 move_cursor(&state,state.x,state.y-1);
1188 break;
1190 /* move cursor right */
1191 case SUDOKU_BUTTON_RIGHT:
1192 case (SUDOKU_BUTTON_RIGHT | BUTTON_REPEAT):
1193 if ( (state.x==8&&invertdir==0) || (state.y==8&&invertdir==1) ) {
1194 #ifndef SUDOKU_BUTTON_DOWN
1195 if ( (state.y==8&&invertdir==0) || (state.x==8&&invertdir==1) ) {
1196 move_cursor(&state,0,0);
1197 } else {
1198 if (invertdir==0) {
1199 move_cursor(&state,0,state.y+1);
1200 } else {
1201 move_cursor(&state,state.x+1,0);
1204 #else
1205 move_cursor(&state,0,state.y);
1206 #endif
1207 } else {
1208 if (invertdir==0) {
1209 move_cursor(&state,state.x+1,state.y);
1210 } else {
1211 move_cursor(&state,state.x,state.y+1);
1214 break;
1216 #ifdef SUDOKU_BUTTON_UP
1217 /* move cursor up */
1218 case SUDOKU_BUTTON_UP:
1219 case (SUDOKU_BUTTON_UP | BUTTON_REPEAT):
1220 if (state.y==0) {
1221 move_cursor(&state,state.x,8);
1222 } else {
1223 move_cursor(&state,state.x,state.y-1);
1225 break;
1226 #endif
1228 #ifdef SUDOKU_BUTTON_DOWN
1229 /* move cursor down */
1230 case SUDOKU_BUTTON_DOWN:
1231 case (SUDOKU_BUTTON_DOWN | BUTTON_REPEAT):
1232 if (state.y==8) {
1233 move_cursor(&state,state.x,0);
1234 } else {
1235 move_cursor(&state,state.x,state.y+1);
1237 break;
1238 #endif
1240 case SUDOKU_BUTTON_MENU:
1241 #ifdef SUDOKU_BUTTON_MENU_PRE
1242 if (lastbutton != SUDOKU_BUTTON_MENU_PRE)
1243 break;
1244 #endif
1245 /* Don't let the user leave a game in a bad state */
1246 if (check_status(&state)) {
1247 rb->splash(HZ*2, "Illegal move!");
1248 /* Ignore any button presses during the splash */
1249 rb->button_clear_queue();
1250 } else {
1251 if (state.editmode) {
1252 res = sudoku_edit_menu(&state);
1253 if (res == MENU_ATTACHED_USB) {
1254 rc = PLUGIN_USB_CONNECTED;
1255 exit = true;
1256 } else if (res == 1) { /* Quit */
1257 exit = true;
1259 } else {
1260 res = sudoku_menu(&state);
1261 if (res == MENU_ATTACHED_USB) {
1262 rc = PLUGIN_USB_CONNECTED;
1263 exit = true;
1264 } else if (res == SM_QUIT) {
1265 exit = true;
1269 break;
1270 #ifdef SUDOKU_BUTTON_POSSIBLE
1271 case SUDOKU_BUTTON_POSSIBLE:
1272 /* Toggle current number in the possiblevals structure */
1273 if (state.currentboard[state.y][state.x]!='0') {
1274 state.possiblevals[state.y][state.x]^=
1275 BIT_N(state.currentboard[state.y][state.x] - '0');
1277 break;
1278 #endif
1280 #ifdef SUDOKU_BUTTON_CHANGEDIR
1281 case SUDOKU_BUTTON_CHANGEDIR:
1282 /* Change scroll wheel direction */
1283 invertdir=!invertdir;
1284 break;
1285 #endif
1286 default:
1287 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
1288 /* Quit if USB has been connected */
1289 rc = PLUGIN_USB_CONNECTED;
1290 exit = true;
1292 break;
1294 #if defined(SUDOKU_BUTTON_TOGGLE_PRE) || defined(SUDOKU_BUTTON_MENU_PRE)
1295 if (button != BUTTON_NONE)
1296 lastbutton = button;
1297 #endif
1299 display_board(&state);
1301 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
1302 if (rb->memcmp(&sudcfg, &sudcfg_disk, sizeof(sudcfg))) /* save settings if changed */
1304 rb->memcpy(&sudcfg_disk, &sudcfg, sizeof(sudcfg));
1305 configfile_save(cfg_filename, disk_config,
1306 sizeof(disk_config) / sizeof(disk_config[0]),
1307 CFGFILE_VERSION);
1309 #endif
1310 return rc;