summaryrefslogtreecommitdiff
path: root/apps/plugins
diff options
context:
space:
mode:
authorDave Chapman <dave@dchapman.com>2005-09-22 08:53:04 +0000
committerDave Chapman <dave@dchapman.com>2005-09-22 08:53:04 +0000
commit6afb017642a82aabbcd6f4ea5c31ec5d3945f5f8 (patch)
treee05eb6de10354abcb17f2e3229f53649837c0e61 /apps/plugins
parent1b79209ea7d02956cf27ce719516dcf495e26f98 (diff)
downloadrockbox-6afb017642a82aabbcd6f4ea5c31ec5d3945f5f8.tar.gz
rockbox-6afb017642a82aabbcd6f4ea5c31ec5d3945f5f8.zip
First version of Sudoku plugin
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7532 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/plugins')
-rw-r--r--apps/plugins/SOURCES1
-rw-r--r--apps/plugins/sudoku.c1180
-rw-r--r--apps/plugins/viewers.config1
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
40sokoban.c 40sokoban.c
41solitaire.c 41solitaire.c
42star.c 42star.c
43sudoku.c
43#if CONFIG_LCD == LCD_SSD1815 44#if CONFIG_LCD == LCD_SSD1815
44video.c 45video.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/***
21Sudoku by Dave Chapman
22
23User instructions
24-----------------
25
26Use the arrow keys to move cursor, and press SELECT/ON/F2 to increment
27the number under the cursor.
28
29At any time during the game, press On to bring up the game menu with
30further options:
31
32 Save
33 Reload
34 Clear
35 Solve
36
37Sudoku is implemented as a "viewer" for a ".ss" file, as generated by
38Simple Sudoku and other applications - http://angusj.com/sudoku/
39
40In-progress game positions are saved in the original .ss file, with
41A-I used to indicate numbers entered by the user.
42
43Example ".ss" file, and one with a saved state:
44
45...|...|... ...|...|...
462..|8.4|9.1 2.C|8.4|9.1
47...|1.6|32. E..|1.6|32.
48----------- -----------
49...|..5|.4. ...|..5|.4.
508..|423|..6 8..|423|..6
51.3.|9..|... .3D|9..|A..
52----------- -----------
53.63|7.9|... .63|7.9|...
544.9|5.2|..8 4.9|5.2|.C8
55...|...|... ...|...|...
56
57*/
58
59#include "plugin.h"
60#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 */
103static unsigned char cellxpos[9]={ 2, 15, 28, 42, 55, 68, 82, 95, 108 };
104static 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 */
107static 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
162static 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
215static 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 */
284static unsigned char cellxpos[9]={ 1, 10, 19, 28, 37, 46, 55, 64, 73 };
285static unsigned char cellypos[9]={ 1, 8, 15, 22, 29, 36, 43, 50, 57 };
286
287static 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
312static 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
335static 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 */
374static struct plugin_api* rb;
375
376struct 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
386Downloaded from:
387
388http://www-users.cs.york.ac.uk/~shackell/sudoku/Sudoku.html
389
390Released under GPLv2
391
392*/
393
394typedef unsigned int Bitset;
395
396#define BLOCK 3
397#define SIZE (BLOCK*BLOCK)
398
399#define true 1
400#define false 0
401
402typedef struct _Sudoku {
403 Bitset table[SIZE][SIZE];
404}Sudoku;
405
406typedef struct _Stats {
407 int numTries;
408 int backTracks;
409 int numEmpty;
410 bool solutionFound;
411}Stats;
412
413typedef struct _Options {
414 bool allSolutions;
415 bool uniquenessCheck;
416}Options;
417
418void sudoku_init(Sudoku* sud);
419void sudoku_set(Sudoku* sud, int x, int y, int num, bool original);
420int 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 */
431void 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 */
441void 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 */
466int 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. */
487static 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 */
503static 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
548static 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 */
552static bool spawn_option(Sudoku* sud, Stats* stats, const Options* options, int x, int y, int num){
553 Sudoku copy;
554
555 rb->memcpy(&copy,sud,sizeof(Sudoku));
556 sudoku_set(&copy, x, y, num, false);
557 stats->numTries += 1;
558 if (solve(&copy, stats, options)){
559 if (!options->allSolutions && stats->solutionFound){
560 rb->memcpy(sud,&copy,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 */
571static 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 */
614sudoku_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
659void 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*/
677bool 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
755bool 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
798void restore_state(struct sudoku_state_t* state)
799{
800 rb->memcpy(state->currentboard,state->savedboard,81);
801}
802
803void 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
816void 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
841void 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 */
916bool 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
967int 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
990bool 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
1033void 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 */
1056enum 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
20mpc,mpc2wav.rock, 00 00 00 00 00 00 20mpc,mpc2wav.rock, 00 00 00 00 00 00
21mid,midi2wav.rock, 20 70 70 3F 00 00 21mid,midi2wav.rock, 20 70 70 3F 00 00
22rsp,searchengine.rock, 0e 11 11 31 7e 60 22rsp,searchengine.rock, 0e 11 11 31 7e 60
23ss,sudoku.rock, 55 55 55 55 55 55
23wav,wav2wv.rock, 00 00 00 00 00 00 24wav,wav2wv.rock, 00 00 00 00 00 00
24 25