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