lcd-m6sp.c: remove \r
[kugel-rb.git] / apps / plugins / sudoku / sudoku.c
blobf3d9b1e75ff251e2d371d7f810820f2c939c8c12
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2005 Dave Chapman
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 /***
23 Sudoku by Dave Chapman
25 User instructions
26 -----------------
28 Use the arrow keys to move cursor, and press SELECT/ON/F2 to increment
29 the number under the cursor.
31 At any time during the game, press On to bring up the game menu with
32 further options:
34 Save
35 Reload
36 Clear
37 Solve
39 Sudoku is implemented as a "viewer" for a ".ss" file, as generated by
40 Simple Sudoku and other applications - http://angusj.com/sudoku/
42 In-progress game positions are saved in the original .ss file, with
43 A-I used to indicate numbers entered by the user.
45 Example ".ss" file, and one with a saved state:
47 ...|...|... ...|...|...
48 2..|8.4|9.1 2.C|8.4|9.1
49 ...|1.6|32. E..|1.6|32.
50 ----------- -----------
51 ...|..5|.4. ...|..5|.4.
52 8..|423|..6 8..|423|..6
53 .3.|9..|... .3D|9..|A..
54 ----------- -----------
55 .63|7.9|... .63|7.9|...
56 4.9|5.2|..8 4.9|5.2|.C8
57 ...|...|... ...|...|...
61 #include "plugin.h"
62 #include "lib/configfile.h"
64 #ifdef HAVE_LCD_BITMAP
66 #include <lib/playback_control.h>
67 #include "sudoku.h"
68 #include "generator.h"
70 /* The bitmaps */
71 #include "pluginbitmaps/sudoku_normal.h"
72 #include "pluginbitmaps/sudoku_inverse.h"
73 #include "pluginbitmaps/sudoku_start.h"
75 #define BITMAP_HEIGHT (BMPHEIGHT_sudoku_normal/10)
76 #define BITMAP_STRIDE STRIDE(SCREEN_MAIN, BMPWIDTH_sudoku_normal, BMPHEIGHT_sudoku_normal)
78 #if (LCD_DEPTH>2)
79 #define BITMAP_WIDTH (BMPWIDTH_sudoku_normal/2)
80 #else
81 #define BITMAP_WIDTH BMPWIDTH_sudoku_normal
82 #endif
84 PLUGIN_HEADER
86 /* Default game - used to initialise sudoku.ss if it doesn't exist. */
87 static const char default_game[9][9] =
89 { '0','1','0', '3','0','7', '0','0','4' },
90 { '0','0','0', '0','6','0', '1','0','2' },
91 { '0','0','0', '0','8','0', '5','6','0' },
93 { '0','6','0', '0','0','0', '0','2','9' },
94 { '0','0','0', '5','0','3', '0','0','0' },
95 { '7','9','0', '0','0','0', '0','3','0' },
97 { '0','8','5', '0','3','0', '0','0','0' },
98 { '1','0','2', '0','7','0', '0','0','0' },
99 { '0','0','0', '4','0','8', '0','5','0' },
102 #if LCD_HEIGHT <= LCD_WIDTH /* Horizontal layout, scratchpad at the left */
104 #if (LCD_HEIGHT==64) && (LCD_WIDTH==112 || LCD_WIDTH==128)
105 /* Archos Recorders and Ondios - 112x64, 9 cells @ 8x6 with 10 border lines */
106 #define SMALL_BOARD
107 #define MARK_OFFS 1 /* Pixels between border and mark */
108 #define MARK_SPACE 1 /* Pixels between two marks */
109 #define MARK_SIZE 1 /* Mark width and height */
111 #elif ((LCD_HEIGHT==80) && (LCD_WIDTH==132))
112 /* C200, 9 cells @ 8x8 with 8 border lines */
113 #define SMALL_BOARD
114 #define MARK_OFFS 1 /* Pixels between border and mark */
115 #define MARK_SPACE 1 /* Pixels between two marks */
116 #define MARK_SIZE 1 /* Mark width and height */
118 #elif ((LCD_HEIGHT==96) && (LCD_WIDTH==128))
119 /* iAudio M3, 9 cells @ 9x9 with 14 border lines */
120 #define MARK_OFFS 1 /* Pixels between border and mark */
121 #define MARK_SPACE 2 /* Pixels between two marks */
122 #define MARK_SIZE 1 /* Mark width and height */
124 #elif (LCD_HEIGHT==110) && (LCD_WIDTH==138) \
125 || (LCD_HEIGHT==128) && (LCD_WIDTH==128)
126 /* iPod Mini - 138x110, 9 cells @ 10x10 with 14 border lines */
127 /* iriver H10 5-6GB - 128x128, 9 cells @ 10x10 with 14 border lines */
128 #define MARK_OFFS 1 /* Pixels between border and mark */
129 #define MARK_SPACE 1 /* Pixels between two marks */
130 #define MARK_SIZE 2 /* Mark width and height */
132 #elif ((LCD_HEIGHT==128) && (LCD_WIDTH==160)) \
133 || ((LCD_HEIGHT==132) && (LCD_WIDTH==176))
134 /* iAudio X5, Iriver H1x0, iPod G3, G4 - 160x128; */
135 /* iPod Nano - 176x132, 9 cells @ 12x12 with 14 border lines */
136 #define MARK_OFFS 1 /* Pixels between border and mark */
137 #define MARK_SPACE 2 /* Pixels between two marks */
138 #define MARK_SIZE 2 /* Mark width and height */
140 #elif ((LCD_HEIGHT==176) && (LCD_WIDTH==220))
141 /* Iriver h300, iPod Color/Photo - 220x176, 9 cells @ 16x16 with 14 border lines */
142 #define MARK_OFFS 1 /* Pixels between border and mark */
143 #define MARK_SPACE 1 /* Pixels between two marks */
144 #define MARK_SIZE 4 /* Mark width and height */
146 #elif (LCD_HEIGHT==240) && (LCD_WIDTH==320)
147 /* iPod Video - 320x240, 9 cells @ 24x24 with 14 border lines */
148 #define MARK_OFFS 1 /* Pixels between border and mark */
149 #define MARK_SPACE 2 /* Pixels between two marks */
150 #define MARK_SIZE 6 /* Mark width and height */
152 #elif (LCD_HEIGHT==480) && (LCD_WIDTH==640)
153 /* M:Robe 500 - 640x480, 9 cells @ 48x48 with 14 border lines */
154 #define MARK_OFFS 1 /* Pixels between border and mark */
155 #define MARK_SPACE 2 /* Pixels between two marks */
156 #define MARK_SIZE 6 /* Mark width and height */
158 #else
159 #error SUDOKU: Unsupported LCD size
160 #endif
162 #else /* Vertical layout, scratchpad at the bottom */
163 #define VERTICAL_LAYOUT
165 #if ((LCD_HEIGHT==220) && (LCD_WIDTH==176))
166 /* e200, 9 cells @ 16x16 with 14 border lines */
167 #define MARK_OFFS 1 /* Pixels between border and mark */
168 #define MARK_SPACE 1 /* Pixels between two marks */
169 #define MARK_SIZE 4 /* Mark width and height */
171 #elif (LCD_HEIGHT>=320) && (LCD_WIDTH>=240)
172 /* Gigabeat - 240x320, 9 cells @ 24x24 with 14 border lines */
173 #define MARK_OFFS 1 /* Pixels between border and mark */
174 #define MARK_SPACE 2 /* Pixels between two marks */
175 #define MARK_SIZE 6 /* Mark width and height */
177 #elif ((LCD_HEIGHT==160) && (LCD_WIDTH==128))
178 /* Philips GoGear SA9200 - 128x160, 9 cells @ 10x10 with 14 border tiles */
179 #define MARK_OFFS 1 /* Pixels between border and mark */
180 #define MARK_SPACE 1 /* Pixels between two marks */
181 #define MARK_SIZE 2 /* Mark width and height */
183 #else
184 #error SUDOKU: Unsupported LCD size
185 #endif
187 #endif /* Layout */
189 #define CELL_WIDTH BITMAP_WIDTH
190 #define CELL_HEIGHT BITMAP_HEIGHT
192 #ifdef SUDOKU_BUTTON_CHANGEDIR
193 int invertdir=0;
194 #else
195 #define invertdir 0
196 #endif
198 #define CFGFILE_VERSION 0 /* Current config file version */
199 #define CFGFILE_MINVERSION 0 /* Minimum config file version to accept */
201 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
202 /* settings */
203 struct sudoku_config {
204 #ifdef HAVE_LCD_COLOR
205 int number_display;
206 #endif
207 #ifdef SUDOKU_BUTTON_POSSIBLE
208 int show_markings;
209 #endif
212 struct sudoku_config sudcfg_disk = {
213 #ifdef HAVE_LCD_COLOR
215 #endif
216 #ifdef SUDOKU_BUTTON_POSSIBLE
218 #endif
220 struct sudoku_config sudcfg;
222 static const char cfg_filename[] = "sudoku.cfg";
223 #ifdef HAVE_LCD_COLOR
224 static char *number_str[2] = { "black", "coloured" };
225 #endif
226 #ifdef SUDOKU_BUTTON_POSSIBLE
227 static char *mark_str[2] = { "hide", "show" };
228 #endif
230 struct configdata disk_config[] = {
231 #ifdef HAVE_LCD_COLOR
232 { TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.number_display }, "numbers",
233 number_str },
234 #endif
235 #ifdef SUDOKU_BUTTON_POSSIBLE
236 { TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.show_markings }, "markings",
237 mark_str },
238 #endif
240 #endif
241 #ifdef HAVE_LCD_COLOR
242 #define NUMBER_TYPE (sudcfg.number_display*CELL_WIDTH)
243 #else
244 #define NUMBER_TYPE 0
245 #endif
247 /* Size dependent build-time calculations */
248 #ifdef SMALL_BOARD
249 #define BOARD_WIDTH (CELL_WIDTH*9+10)
250 #define BOARD_HEIGHT (CELL_HEIGHT*9+10)
251 static unsigned int cellxpos[9]={
252 1, (CELL_WIDTH+2), (2*CELL_WIDTH+3),
253 (3*CELL_WIDTH+4), (4*CELL_WIDTH+5), (5*CELL_WIDTH+6),
254 (6*CELL_WIDTH+7), (7*CELL_WIDTH+8), (8*CELL_WIDTH+9)
256 static unsigned int cellypos[9]={
257 1, (CELL_HEIGHT+2), (2*CELL_HEIGHT+3),
258 (3*CELL_HEIGHT+4), (4*CELL_HEIGHT+5), (5*CELL_HEIGHT+6),
259 (6*CELL_HEIGHT+7), (7*CELL_HEIGHT+8), (8*CELL_HEIGHT+9)
261 #else /* !SMALL_BOARD */
262 #define BOARD_WIDTH (CELL_WIDTH*9+10+4)
263 #define BOARD_HEIGHT (CELL_HEIGHT*9+10+4)
264 static unsigned int cellxpos[9]={
265 2, (CELL_WIDTH +3), (2*CELL_WIDTH +4),
266 (3*CELL_WIDTH +6), (4*CELL_WIDTH +7), (5*CELL_WIDTH +8),
267 (6*CELL_WIDTH+10), (7*CELL_WIDTH+11), (8*CELL_WIDTH+12)
269 static unsigned int cellypos[9]={
270 2, (CELL_HEIGHT +3), (2*CELL_HEIGHT +4),
271 (3*CELL_HEIGHT +6), (4*CELL_HEIGHT +7), (5*CELL_HEIGHT +8),
272 (6*CELL_HEIGHT+10), (7*CELL_HEIGHT+11), (8*CELL_HEIGHT+12)
274 #endif
276 #ifdef VERTICAL_LAYOUT
277 #define XOFS ((LCD_WIDTH-BOARD_WIDTH)/2)
278 #define YOFS ((LCD_HEIGHT-(BOARD_HEIGHT+CELL_HEIGHT*2+2))/2)
279 #define YOFSSCRATCHPAD (YOFS+BOARD_HEIGHT+CELL_WIDTH)
280 #else
281 #define XOFSSCRATCHPAD ((LCD_WIDTH-(BOARD_WIDTH+CELL_WIDTH*2+2))/2)
282 #define XOFS (XOFSSCRATCHPAD+CELL_WIDTH*2+2)
283 #define YOFS ((LCD_HEIGHT-BOARD_HEIGHT)/2)
284 #endif
286 #define BLOCK 3
287 #define SIZE (BLOCK*BLOCK)
289 void sudoku_solve(struct sudoku_state_t* state)
291 bool ret = sudoku_solve_board(state);
293 if (!ret) {
294 rb->splash(HZ*2, "Solve failed");
297 return;
300 /* Copies the current to the saved board */
301 static void save_state(struct sudoku_state_t *state)
303 rb->memcpy(state->savedboard, state->currentboard,
304 sizeof(state->savedboard));
305 #ifdef SUDOKU_BUTTON_POSSIBLE
306 rb->memcpy(state->savedpossible, state->possiblevals,
307 sizeof(state->savedpossible));
308 #endif
311 /* Copies the saved to the current board */
312 static void restore_state(struct sudoku_state_t *state)
314 rb->memcpy(state->currentboard, state->savedboard,
315 sizeof(state->savedboard));
316 #ifdef SUDOKU_BUTTON_POSSIBLE
317 rb->memcpy(state->possiblevals, state->savedpossible,
318 sizeof(state->possiblevals));
319 #endif
322 void default_state(struct sudoku_state_t* state)
324 int r,c;
326 rb->strlcpy(state->filename,GAME_FILE,MAX_PATH);
327 for (r=0;r<9;r++) {
328 for (c=0;c<9;c++) {
329 state->startboard[r][c]=default_game[r][c];
330 state->currentboard[r][c]=default_game[r][c];
331 #ifdef SUDOKU_BUTTON_POSSIBLE
332 state->possiblevals[r][c]=0;
333 #endif
337 /* initialize the saved board so reload function works */
338 save_state(state);
340 state->x=0;
341 state->y=0;
342 state->editmode=0;
345 void clear_state(struct sudoku_state_t* state)
347 int r,c;
349 rb->strlcpy(state->filename,GAME_FILE,MAX_PATH);
350 for (r=0;r<9;r++) {
351 for (c=0;c<9;c++) {
352 state->startboard[r][c]='0';
353 state->currentboard[r][c]='0';
354 #ifdef SUDOKU_BUTTON_POSSIBLE
355 state->possiblevals[r][c]=0;
356 #endif
360 state->x=0;
361 state->y=0;
362 state->editmode=0;
365 /* Check the status of the board, assuming a change at the cursor location */
366 bool check_status(struct sudoku_state_t* state)
368 int check[9];
369 int r,c;
370 int r1,c1;
371 int cell;
373 /* First, check the column */
374 for (cell=0;cell<9;cell++) {
375 check[cell]=0;
377 for (r=0;r<9;r++) {
378 cell=state->currentboard[r][state->x];
379 if (cell!='0') {
380 if (check[cell-'1']==1) {
381 return true;
383 check[cell-'1']=1;
387 /* Second, check the row */
388 for (cell=0;cell<9;cell++) {
389 check[cell]=0;
391 for (c=0;c<9;c++) {
392 cell=state->currentboard[state->y][c];
393 if (cell!='0') {
394 if (check[cell-'1']==1) {
395 return true;
397 check[cell-'1']=1;
401 /* Finally, check the 3x3 sub-grid */
402 for (cell=0;cell<9;cell++) {
403 check[cell]=0;
405 r1=(state->y/3)*3;
406 c1=(state->x/3)*3;
407 for (r=r1;r<r1+3;r++) {
408 for (c=c1;c<c1+3;c++) {
409 cell=state->currentboard[r][c];
410 if (cell!='0') {
411 if (check[cell-'1']==1) {
412 return true;
414 check[cell-'1']=1;
419 /* We passed all the checks :) */
421 return false;
424 /* Load game - only ".ss" is officially supported, but any sensible
425 text representation (one line per row) may load.
427 bool load_sudoku(struct sudoku_state_t* state, char* filename)
429 int fd;
430 size_t n;
431 int r = 0, c = 0, d = 0;
432 unsigned int i;
433 int valid=0;
434 char buf[500]; /* A buffer to read a sudoku board from */
436 fd=rb->open(filename, O_RDONLY);
437 if (fd < 0) {
438 LOGF("Invalid sudoku file: %s\n",filename);
439 return(false);
442 rb->strlcpy(state->filename,filename,MAX_PATH);
443 n=rb->read(fd,buf,500);
444 if (n <= 0) {
445 return(false);
447 rb->close(fd);
448 r=0;
449 c=0;
450 i=0;
451 d=0;
452 while ((i < n) && (r < 9)) {
453 switch (buf[i]){
454 case ' ': case '\t':
455 if (c > 0)
456 valid=1;
457 break;
458 case '|':
459 case '*':
460 case '-':
461 case '\r':
462 break;
463 case '\n':
464 if (valid) {
465 r++;
466 valid=0;
468 c = 0;
469 d = 0;
470 break;
471 case '_': case '.':
472 valid=1;
473 if (c >= SIZE || r >= SIZE){
474 LOGF("ERROR: sudoku problem is the wrong size (%d,%d)\n",
475 c, r);
476 return(false);
478 c++;
479 break;
480 default:
481 if (((buf[i]>='A') && (buf[i]<='I')) ||
482 ((buf[i]>='0') && (buf[i]<='9'))) {
483 valid=1;
484 if (r >= SIZE || c >= SIZE){
485 LOGF("ERROR: sudoku problem is the wrong size "
486 "(%d,%d)\n", c, r);
487 return(false);
489 if ((buf[i]>='0') && (buf[i]<='9')) {
490 state->startboard[r][c]=buf[i];
491 state->currentboard[r][c]=buf[i];
492 } else {
493 state->currentboard[r][c]='1'+(buf[i]-'A');
495 c++;
497 if((buf[i]>='a' && buf[i] <= 'z') && i < (n-1)
498 && (buf[i+1] >= 'a' && buf[i+1] <= 'z')) {
499 state->possiblevals[r][d]
500 = (((buf[i]-'a') * 26 + buf[i+1]-'a')<<1);
501 i++;
502 d++;
504 /* Ignore any other characters */
505 break;
507 i++;
510 /* Check that the board is valid - we need to check every row/column
511 and block individually */
512 for (state->y = 0; state->y < 9; state->y++) {
513 state->x = (state->y%3)*3 + (state->y/3);
514 if (check_status(state)) return false;
516 state->x = 0;
517 state->y = 0;
519 /* Save a copy of the saved state - so we can reload without using the
520 disk */
521 save_state(state);
522 return(true);
525 bool save_sudoku(struct sudoku_state_t* state)
527 int fd;
528 int r,c;
529 int i;
530 #ifdef SUDOKU_BUTTON_POSSIBLE
531 int x;
532 char line[41]="...|...|... ; \r\n";
533 #else
534 char line[13]="...|...|...\r\n";
535 #endif
536 char sep[13]="-----------\r\n";
538 rb->splash(0, "Saving...");
540 if (state->filename[0]==0) {
541 return false;
544 fd=rb->open(state->filename, O_WRONLY|O_CREAT, 0666);
545 if (fd >= 0) {
546 for (r=0;r<9;r++) {
547 i=0;
548 for (c=0;c<9;c++) {
549 if (state->startboard[r][c]!='0') {
550 line[i]=state->startboard[r][c];
551 } else if (state->currentboard[r][c]!='0') {
552 line[i]='A'+(state->currentboard[r][c]-'1');
553 } else {
554 line[i]='.';
556 i++;
557 if ((c==2) || (c==5)) {
558 i++;
561 #ifdef SUDOKU_BUTTON_POSSIBLE
562 i+=2;
563 for(c=0; c<9; c++) {
564 x = ((state->possiblevals[r][c]>>1)/26);
565 line[i++] = x + 'a';
566 x = ((state->possiblevals[r][c]>>1)%26);
567 line[i++] = x + 'a';
569 #endif
570 rb->write(fd,line,sizeof(line));
571 if ((r==2) || (r==5)) {
572 rb->write(fd,sep,sizeof(sep));
575 /* Add a blank line at end */
576 rb->write(fd,"\r\n",2);
577 rb->close(fd);
578 rb->reload_directory();
579 /* Save a copy of the saved state - so we can reload without
580 using the disk */
581 save_state(state);
582 return true;
583 } else {
584 return false;
588 void clear_board(struct sudoku_state_t* state)
590 int r,c;
592 for (r=0;r<9;r++) {
593 for (c=0;c<9;c++) {
594 state->currentboard[r][c]=state->startboard[r][c];
597 state->x=0;
598 state->y=0;
601 void update_cell(struct sudoku_state_t* state, int r, int c)
603 /* We have four types of cell:
604 1) User-entered number
605 2) Starting number
606 3) Cursor in cell
609 if ((r==state->y) && (c==state->x)) {
610 rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE,
611 BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
612 BITMAP_STRIDE,
613 XOFS+cellxpos[c],YOFS+cellypos[r],CELL_WIDTH,
614 CELL_HEIGHT);
615 } else {
616 if (state->startboard[r][c]!='0') {
617 rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE,
618 BITMAP_HEIGHT*(state->startboard[r][c]-'0'),
619 BITMAP_STRIDE,
620 XOFS+cellxpos[c],YOFS+cellypos[r],
621 CELL_WIDTH,CELL_HEIGHT);
622 } else {
623 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,
624 BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
625 BITMAP_STRIDE,
626 XOFS+cellxpos[c],YOFS+cellypos[r],
627 CELL_WIDTH,CELL_HEIGHT);
631 rb->lcd_update_rect(cellxpos[c],cellypos[r],CELL_WIDTH,CELL_HEIGHT);
635 void display_board(struct sudoku_state_t* state)
637 int r,c;
638 #ifdef SUDOKU_BUTTON_POSSIBLE
639 int i;
640 #endif
642 /* Clear the display buffer */
643 rb->lcd_clear_display();
645 /* Draw the gridlines - differently for different targets */
647 #ifdef SMALL_BOARD
648 /* Small targets - draw dotted/single lines */
649 for (r=0;r<9;r++) {
650 if ((r % 3)==0) {
651 /* Solid Line */
652 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
653 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
654 } else {
655 /* Dotted line */
656 for (c=XOFS;c<XOFS+BOARD_WIDTH;c+=2) {
657 rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
659 for (c=YOFS;c<YOFS+BOARD_HEIGHT;c+=2) {
660 rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
664 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
665 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
666 #else
667 /* Large targets - draw single/double lines */
668 for (r=0;r<9;r++) {
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 if ((r % 3)==0) {
672 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-2);
673 rb->lcd_vline(XOFS+cellxpos[r]-2,YOFS,YOFS+BOARD_HEIGHT-1);
676 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
677 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT+1);
678 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
679 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
680 #endif
682 #ifdef SUDOKU_BUTTON_POSSIBLE
683 #ifdef VERTICAL_LAYOUT
684 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD);
685 rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD+CELL_HEIGHT+1);
686 for (r=0;r<9;r++) {
687 #ifdef SMALL_BOARD
688 /* Small targets - draw dotted/single lines */
689 if ((r % 3)==0) {
690 /* Solid Line */
691 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
692 YOFSSCRATCHPAD+CELL_HEIGHT+1);
693 } else {
694 /* Dotted line */
695 for (c=YOFSSCRATCHPAD;c<YOFSSCRATCHPAD+CELL_HEIGHT+1;c+=2) {
696 rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
699 #else
700 /* Large targets - draw single/double lines */
701 rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
702 YOFSSCRATCHPAD+CELL_HEIGHT+1);
703 if ((r % 3)==0)
704 rb->lcd_vline(XOFS+cellxpos[r]-2,YOFSSCRATCHPAD,
705 YOFSSCRATCHPAD+CELL_HEIGHT+1);
706 #endif
707 if ((r>0) && state->possiblevals[state->y][state->x]&BIT_N(r))
708 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
709 BITMAP_STRIDE,XOFS+cellxpos[r-1],
710 YOFSSCRATCHPAD+1,CELL_WIDTH,CELL_HEIGHT);
712 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFSSCRATCHPAD,
713 YOFSSCRATCHPAD+CELL_HEIGHT+1);
714 #ifndef SMALL_BOARD
715 rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFSSCRATCHPAD,
716 YOFSSCRATCHPAD+CELL_HEIGHT+1);
717 #endif
718 if (state->possiblevals[state->y][state->x]&BIT_N(r))
719 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
720 BITMAP_STRIDE,XOFS+cellxpos[8],YOFSSCRATCHPAD+1,
721 CELL_WIDTH,CELL_HEIGHT);
722 #else /* Horizontal layout */
723 rb->lcd_vline(XOFSSCRATCHPAD,YOFS,YOFS+BOARD_HEIGHT-1);
724 rb->lcd_vline(XOFSSCRATCHPAD+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
725 for (r=0;r<9;r++) {
726 #ifdef SMALL_BOARD
727 /* Small targets - draw dotted/single lines */
728 if ((r % 3)==0) {
729 /* Solid Line */
730 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
731 YOFS+cellypos[r]-1);
732 } else {
733 /* Dotted line */
734 for (c=XOFSSCRATCHPAD;c<XOFSSCRATCHPAD+CELL_WIDTH+1;c+=2) {
735 rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
738 #else
739 /* Large targets - draw single/double lines */
740 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
741 YOFS+cellypos[r]-1);
742 if ((r % 3)==0)
743 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
744 YOFS+cellypos[r]-2);
745 #endif
746 if ((r>0) && state->possiblevals[state->y][state->x]&BIT_N(r))
747 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
748 BITMAP_STRIDE,XOFSSCRATCHPAD+1,
749 YOFS+cellypos[r-1],CELL_WIDTH,CELL_HEIGHT);
751 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
752 YOFS+cellypos[8]+CELL_HEIGHT);
753 #ifndef SMALL_BOARD
754 rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
755 YOFS+cellypos[8]+CELL_HEIGHT+1);
756 #endif
757 if (state->possiblevals[state->y][state->x]&BIT_N(r))
758 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
759 BITMAP_STRIDE,XOFSSCRATCHPAD+1,YOFS+cellypos[8],
760 CELL_WIDTH,CELL_HEIGHT);
761 #endif /* Layout */
762 #endif /* SUDOKU_BUTTON_POSSIBLE */
764 /* Draw the numbers */
765 for (r=0;r<9;r++) {
766 for (c=0;c<9;c++) {
767 /* We have four types of cell:
768 1) User-entered number
769 2) Starting number
770 3) Cursor in cell
773 if ((r==state->y) && (c==state->x)) {
774 rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE,
775 BITMAP_HEIGHT*(state->currentboard[r][c]-
776 '0'),
777 BITMAP_STRIDE,
778 XOFS+cellxpos[c],YOFS+cellypos[r],
779 CELL_WIDTH,CELL_HEIGHT);
780 } else {
781 if (state->startboard[r][c]!='0') {
782 rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE,
783 BITMAP_HEIGHT*(state->startboard[r][c]-
784 '0'),
785 BITMAP_STRIDE,
786 XOFS+cellxpos[c],YOFS+cellypos[r],
787 CELL_WIDTH,CELL_HEIGHT);
788 } else {
789 rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,
790 BITMAP_HEIGHT*
791 (state->currentboard[r][c]-'0'),
792 BITMAP_STRIDE,
793 XOFS+cellxpos[c],YOFS+cellypos[r],
794 CELL_WIDTH,CELL_HEIGHT);
797 #ifdef SUDOKU_BUTTON_POSSIBLE
798 /* Draw the possible number markings on the board */
799 if(sudcfg.show_markings && state->startboard[r][c]=='0'
800 && state->currentboard[r][c]=='0') {
801 for(i=0;i<9;i++) {
802 if(state->possiblevals[r][c]&(2<<i)) {
803 #if LCD_DEPTH > 1
804 /* draw markings in dark grey */
805 rb->lcd_set_foreground(LCD_DARKGRAY);
806 #endif
807 rb->lcd_fillrect(XOFS+cellxpos[c]+MARK_OFFS
808 +(i%3)*(MARK_SIZE+MARK_SPACE),
809 YOFS+cellypos[r]+MARK_OFFS
810 +(i/3)*(MARK_SIZE+MARK_SPACE),
811 MARK_SIZE,
812 MARK_SIZE);
813 #if LCD_DEPTH > 1
814 rb->lcd_set_foreground(LCD_BLACK);
815 #endif
819 #endif /* SUDOKU_BUTTON_POSSIBLE */
824 /* update the screen */
825 rb->lcd_update();
828 bool sudoku_generate(struct sudoku_state_t* state)
830 char* difficulty;
831 char str[80];
832 bool res;
833 struct sudoku_state_t new_state;
835 clear_state(&new_state);
836 display_board(&new_state);
837 rb->splash(0, "Generating...");
839 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
840 rb->cpu_boost(true);
841 #endif
843 res = sudoku_generate_board(&new_state,&difficulty);
845 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
846 rb->cpu_boost(false);
847 #endif
849 if (res) {
850 rb->memcpy(state,&new_state,sizeof(new_state));
851 rb->snprintf(str,sizeof(str),"Difficulty: %s",difficulty);
852 display_board(state);
853 rb->splash(HZ*3, str);
854 rb->strlcpy(state->filename,GAME_FILE,MAX_PATH);
855 } else {
856 display_board(&new_state);
857 rb->splash(HZ*2, "Aborted");
859 /* initialize the saved board so reload function works */
860 save_state(state);
861 return res;
864 #ifdef HAVE_LCD_COLOR
865 static bool numdisplay_setting(void)
867 static const struct opt_items names[] = {
868 {"Black", -1},
869 {"Coloured", -1},
872 return rb->set_option("Number Display", &sudcfg.number_display, INT, names,
873 sizeof(names) / sizeof(names[0]), NULL);
875 #endif
877 #ifdef SUDOKU_BUTTON_POSSIBLE
878 static bool showmarkings_setting(void)
880 static const struct opt_items names[] = {
881 {"Hide", -1},
882 {"Show", -1},
885 return rb->set_option("Show Markings", &sudcfg.show_markings, INT, names,
886 sizeof(names) / sizeof(names[0]), NULL);
888 #endif
890 enum {
891 SM_AUDIO_PLAYBACK = 0,
892 #ifdef HAVE_LCD_COLOR
893 SM_NUMBER_DISPLAY,
894 #endif
895 #ifdef SUDOKU_BUTTON_POSSIBLE
896 SM_SHOW_MARKINGS,
897 #endif
898 SM_SAVE,
899 SM_RELOAD,
900 SM_CLEAR,
901 SM_SOLVE,
902 SM_GENERATE,
903 SM_NEW,
904 SM_QUIT,
907 int sudoku_menu(struct sudoku_state_t* state)
909 int result;
911 MENUITEM_STRINGLIST(menu, "Sudoku Menu", NULL,
912 "Audio Playback",
913 #ifdef HAVE_LCD_COLOR
914 "Number Display",
915 #endif
916 #ifdef SUDOKU_BUTTON_POSSIBLE
917 "Show Markings",
918 #endif
919 "Save", "Reload", "Clear", "Solve",
920 "Generate", "New", "Quit");
922 result = rb->do_menu(&menu, NULL, NULL, false);
924 switch (result) {
925 case SM_AUDIO_PLAYBACK:
926 playback_control(NULL);
927 break;
929 #ifdef HAVE_LCD_COLOR
930 case SM_NUMBER_DISPLAY:
931 numdisplay_setting();
932 break;
933 #endif
935 #ifdef SUDOKU_BUTTON_POSSIBLE
936 case SM_SHOW_MARKINGS:
937 showmarkings_setting();
938 break;
939 #endif
940 case SM_SAVE:
941 save_sudoku(state);
942 break;
944 case SM_RELOAD:
945 restore_state(state);
946 break;
948 case SM_CLEAR:
949 clear_board(state);
950 break;
952 case SM_SOLVE:
953 sudoku_solve(state);
954 break;
956 case SM_GENERATE:
957 sudoku_generate(state);
958 break;
960 case SM_NEW:
961 clear_state(state);
962 state->editmode=1;
963 break;
965 case SM_QUIT:
966 save_sudoku(state);
967 break;
969 default:
970 break;
973 return result;
976 /* Menu used when user is in edit mode - i.e. creating a new game manually */
977 int sudoku_edit_menu(struct sudoku_state_t* state)
979 int result;
981 MENUITEM_STRINGLIST(menu, "Edit Menu", NULL,
982 "Save as", "Quit");
984 result = rb->do_menu(&menu, NULL, NULL, false);
986 switch (result) {
987 case 0: /* Save new game */
988 rb->kbd_input(state->filename,MAX_PATH);
989 if (save_sudoku(state)) {
990 state->editmode=0;
991 } else {
992 rb->splash(HZ*2, "Save failed");
994 break;
996 case 1: /* Quit */
997 break;
999 default:
1000 break;
1003 return result;
1006 void move_cursor(struct sudoku_state_t* state, int newx, int newy)
1008 int oldx, oldy;
1010 /* Check that the character at the cursor position is legal */
1011 if (check_status(state)) {
1012 rb->splash(HZ*2, "Illegal move!");
1013 /* Ignore any button presses during the splash */
1014 rb->button_clear_queue();
1015 return;
1018 /* Move Cursor */
1019 oldx=state->x;
1020 oldy=state->y;
1021 state->x=newx;
1022 state->y=newy;
1024 /* Redraw current and old cells */
1025 update_cell(state,oldx,oldy);
1026 update_cell(state,newx,newy);
1029 /* plugin entry point */
1030 enum plugin_status plugin_start(const void* parameter)
1032 bool exit;
1033 int button;
1034 int lastbutton = BUTTON_NONE;
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 (button != BUTTON_NONE)
1295 lastbutton = button;
1297 display_board(&state);
1299 #if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
1300 if (rb->memcmp(&sudcfg, &sudcfg_disk, sizeof(sudcfg))) /* save settings if changed */
1302 rb->memcpy(&sudcfg_disk, &sudcfg, sizeof(sudcfg));
1303 configfile_save(cfg_filename, disk_config,
1304 sizeof(disk_config) / sizeof(disk_config[0]),
1305 CFGFILE_VERSION);
1307 #endif
1308 return rc;
1311 #endif