1 | // |
---|
2 | // "$Id$" |
---|
3 | // |
---|
4 | // Sudoku game using the Fast Light Tool Kit (FLTK). |
---|
5 | // |
---|
6 | // Copyright 2005-2007 by Michael Sweet. |
---|
7 | // |
---|
8 | // This library is free software; you can redistribute it and/or |
---|
9 | // modify it under the terms of the GNU Library General Public |
---|
10 | // License as published by the Free Software Foundation; either |
---|
11 | // version 2 of the License, or (at your option) any later version. |
---|
12 | // |
---|
13 | // This library is distributed in the hope that it will be useful, |
---|
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
---|
16 | // Library General Public License for more details. |
---|
17 | // |
---|
18 | // You should have received a copy of the GNU Library General Public |
---|
19 | // License along with this library; if not, write to the Free Software |
---|
20 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
---|
21 | // USA. |
---|
22 | // |
---|
23 | // Please report all bugs and problems on the following page: |
---|
24 | // |
---|
25 | // http://www.fltk.org/str.php |
---|
26 | // |
---|
27 | |
---|
28 | #include <FL/Fl.H> |
---|
29 | #include <FL/Enumerations.H> |
---|
30 | #include <FL/Fl_Window.H> |
---|
31 | #include <FL/Fl_Button.H> |
---|
32 | #include <FL/Fl_Group.H> |
---|
33 | #include <FL/fl_ask.H> |
---|
34 | #include <FL/fl_draw.H> |
---|
35 | #include <FL/Fl_Help_Dialog.H> |
---|
36 | #include <FL/Fl_Preferences.H> |
---|
37 | #include <FL/Fl_Sys_Menu_Bar.H> |
---|
38 | #include <FL/x.H> |
---|
39 | #include <stdio.h> |
---|
40 | #include <stdlib.h> |
---|
41 | #include <string.h> |
---|
42 | #include <time.h> |
---|
43 | #include <FL/math.h> |
---|
44 | |
---|
45 | #ifdef WIN32 |
---|
46 | # include "sudokurc.h" |
---|
47 | #elif !defined(__APPLE__) |
---|
48 | # include "pixmaps/sudoku.xbm" |
---|
49 | #endif // WIN32 |
---|
50 | |
---|
51 | // Audio headers... |
---|
52 | #include <config.h> |
---|
53 | |
---|
54 | #ifndef WIN32 |
---|
55 | # include <unistd.h> |
---|
56 | #endif // !WIN32 |
---|
57 | |
---|
58 | #ifdef HAVE_ALSA_ASOUNDLIB_H |
---|
59 | # define ALSA_PCM_NEW_HW_PARAMS_API |
---|
60 | # include <alsa/asoundlib.h> |
---|
61 | #endif // HAVE_ALSA_ASOUNDLIB_H |
---|
62 | #ifdef __APPLE__ |
---|
63 | # include <CoreAudio/AudioHardware.h> |
---|
64 | #endif // __APPLE__ |
---|
65 | #ifdef WIN32 |
---|
66 | # include <mmsystem.h> |
---|
67 | #endif // WIN32 |
---|
68 | |
---|
69 | |
---|
70 | // |
---|
71 | // Default sizes... |
---|
72 | // |
---|
73 | |
---|
74 | #define GROUP_SIZE 160 |
---|
75 | #define CELL_SIZE 50 |
---|
76 | #define CELL_OFFSET 5 |
---|
77 | #ifdef __APPLE__ |
---|
78 | # define MENU_OFFSET 0 |
---|
79 | #else |
---|
80 | # define MENU_OFFSET 25 |
---|
81 | #endif // __APPLE__ |
---|
82 | |
---|
83 | // Sound class for Sudoku... |
---|
84 | // |
---|
85 | // There are MANY ways to implement sound in a FLTK application. |
---|
86 | // The approach we are using here is to conditionally compile OS- |
---|
87 | // specific code into the application - CoreAudio for MacOS X, the |
---|
88 | // standard Win32 API stuff for Windows, ALSA or X11 for Linux, and |
---|
89 | // X11 for all others. We have to support ALSA on Linux because the |
---|
90 | // current Xorg releases no longer support XBell() or the PC speaker. |
---|
91 | // |
---|
92 | // There are several good cross-platform audio libraries we could also |
---|
93 | // use, such as OpenAL, PortAudio, and SDL, however they were not chosen |
---|
94 | // for this application because of our limited use of sound. |
---|
95 | // |
---|
96 | // Many thanks to Ian MacArthur who provided sample code that led to |
---|
97 | // the CoreAudio implementation you see here! |
---|
98 | class SudokuSound { |
---|
99 | // Private, OS-specific data... |
---|
100 | #ifdef __APPLE__ |
---|
101 | AudioDeviceID device; |
---|
102 | AudioStreamBasicDescription format; |
---|
103 | short *data; |
---|
104 | int remaining; |
---|
105 | |
---|
106 | static OSStatus audio_cb(AudioDeviceID device, |
---|
107 | const AudioTimeStamp *current_time, |
---|
108 | const AudioBufferList *data_in, |
---|
109 | const AudioTimeStamp *time_in, |
---|
110 | AudioBufferList *data_out, |
---|
111 | const AudioTimeStamp *time_out, |
---|
112 | void *client_data); |
---|
113 | #elif defined(WIN32) |
---|
114 | HWAVEOUT device; |
---|
115 | HGLOBAL header_handle; |
---|
116 | LPWAVEHDR header_ptr; |
---|
117 | HGLOBAL data_handle; |
---|
118 | LPSTR data_ptr; |
---|
119 | |
---|
120 | #else |
---|
121 | # ifdef HAVE_ALSA_ASOUNDLIB_H |
---|
122 | snd_pcm_t *handle; |
---|
123 | # endif // HAVE_ALSA_ASOUNDLIB_H |
---|
124 | #endif // __APPLE__ |
---|
125 | |
---|
126 | // Common data... |
---|
127 | static int frequencies[9]; |
---|
128 | static short *sample_data[9]; |
---|
129 | static int sample_size; |
---|
130 | |
---|
131 | public: |
---|
132 | |
---|
133 | SudokuSound(); |
---|
134 | ~SudokuSound(); |
---|
135 | |
---|
136 | void play(char note); |
---|
137 | }; |
---|
138 | |
---|
139 | |
---|
140 | // Sudoku cell class... |
---|
141 | class SudokuCell : public Fl_Widget { |
---|
142 | bool readonly_; |
---|
143 | int value_; |
---|
144 | int test_value_[9]; |
---|
145 | |
---|
146 | public: |
---|
147 | |
---|
148 | SudokuCell(int X, int Y, int W, int H); |
---|
149 | void draw(); |
---|
150 | int handle(int event); |
---|
151 | void readonly(bool r) { readonly_ = r; redraw(); } |
---|
152 | bool readonly() const { return readonly_; } |
---|
153 | void test_value(int v, int n) { test_value_[n] = v; redraw(); } |
---|
154 | int test_value(int n) const { return test_value_[n]; } |
---|
155 | void value(int v) { |
---|
156 | value_ = v; |
---|
157 | for (int i = 0; i < 8; i ++) test_value_[i] = 0; |
---|
158 | redraw(); |
---|
159 | } |
---|
160 | int value() const { return value_; } |
---|
161 | }; |
---|
162 | |
---|
163 | |
---|
164 | // Sudoku window class... |
---|
165 | class Sudoku : public Fl_Window { |
---|
166 | Fl_Sys_Menu_Bar *menubar_; |
---|
167 | Fl_Group *grid_; |
---|
168 | time_t seed_; |
---|
169 | char grid_values_[9][9]; |
---|
170 | SudokuCell *grid_cells_[9][9]; |
---|
171 | Fl_Group *grid_groups_[3][3]; |
---|
172 | int difficulty_; |
---|
173 | SudokuSound *sound_; |
---|
174 | |
---|
175 | static void check_cb(Fl_Widget *widget, void *); |
---|
176 | static void close_cb(Fl_Widget *widget, void *); |
---|
177 | static void diff_cb(Fl_Widget *widget, void *d); |
---|
178 | static void update_helpers_cb(Fl_Widget *, void *); |
---|
179 | static void help_cb(Fl_Widget *, void *); |
---|
180 | static void mute_cb(Fl_Widget *widget, void *); |
---|
181 | static void new_cb(Fl_Widget *widget, void *); |
---|
182 | static void reset_cb(Fl_Widget *widget, void *); |
---|
183 | static void restart_cb(Fl_Widget *widget, void *); |
---|
184 | void set_title(); |
---|
185 | static void solve_cb(Fl_Widget *widget, void *); |
---|
186 | |
---|
187 | static Fl_Help_Dialog *help_dialog_; |
---|
188 | static Fl_Preferences prefs_; |
---|
189 | public: |
---|
190 | |
---|
191 | Sudoku(); |
---|
192 | ~Sudoku(); |
---|
193 | |
---|
194 | void check_game(bool highlight = true); |
---|
195 | void load_game(); |
---|
196 | void new_game(time_t seed); |
---|
197 | int next_value(SudokuCell *c); |
---|
198 | void resize(int X, int Y, int W, int H); |
---|
199 | void save_game(); |
---|
200 | void solve_game(); |
---|
201 | void update_helpers(); |
---|
202 | }; |
---|
203 | |
---|
204 | |
---|
205 | // Sound class globals... |
---|
206 | int SudokuSound::frequencies[9] = { |
---|
207 | 880, // A(5) |
---|
208 | 988, // B(5) |
---|
209 | 1046, // C(5) |
---|
210 | 1174, // D(5) |
---|
211 | 1318, // E(5) |
---|
212 | 1396, // F(5) |
---|
213 | 1568, // G(5) |
---|
214 | 1760, // H (A6) |
---|
215 | 1976 // I (B6) |
---|
216 | }; |
---|
217 | short *SudokuSound::sample_data[9] = { 0 }; |
---|
218 | int SudokuSound::sample_size = 0; |
---|
219 | |
---|
220 | |
---|
221 | // Initialize the SudokuSound class |
---|
222 | SudokuSound::SudokuSound() { |
---|
223 | sample_size = 0; |
---|
224 | |
---|
225 | #ifdef __APPLE__ |
---|
226 | remaining = 0; |
---|
227 | |
---|
228 | UInt32 size = sizeof(device); |
---|
229 | |
---|
230 | if (AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, |
---|
231 | &size, (void *)&device) != noErr) return; |
---|
232 | |
---|
233 | size = sizeof(format); |
---|
234 | if (AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyStreamFormat, |
---|
235 | &size, &format) != noErr) return; |
---|
236 | |
---|
237 | // Set up a format we like... |
---|
238 | format.mSampleRate = 44100.0; // 44.1kHz |
---|
239 | format.mChannelsPerFrame = 2; // stereo |
---|
240 | |
---|
241 | if (AudioDeviceSetProperty(device, NULL, 0, false, |
---|
242 | kAudioDevicePropertyStreamFormat, |
---|
243 | sizeof(format), &format) != noErr) return; |
---|
244 | |
---|
245 | // Check we got linear pcm - what to do if we did not ??? |
---|
246 | if (format.mFormatID != kAudioFormatLinearPCM) return; |
---|
247 | |
---|
248 | // Attach the callback |
---|
249 | if (AudioDeviceAddIOProc(device, audio_cb, (void *)this) != noErr) return; |
---|
250 | |
---|
251 | // Start the device... |
---|
252 | AudioDeviceStart(device, audio_cb); |
---|
253 | |
---|
254 | sample_size = (int)format.mSampleRate / 20; |
---|
255 | |
---|
256 | #elif defined(WIN32) |
---|
257 | WAVEFORMATEX format; |
---|
258 | |
---|
259 | memset(&format, 0, sizeof(format)); |
---|
260 | format.cbSize = sizeof(format); |
---|
261 | format.wFormatTag = WAVE_FORMAT_PCM; |
---|
262 | format.nChannels = 2; |
---|
263 | format.nSamplesPerSec = 44100; |
---|
264 | format.nAvgBytesPerSec = 44100 * 4; |
---|
265 | format.nBlockAlign = 4; |
---|
266 | format.wBitsPerSample = 16; |
---|
267 | |
---|
268 | data_handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, format.nSamplesPerSec / 5); |
---|
269 | if (!data_handle) return; |
---|
270 | |
---|
271 | data_ptr = (LPSTR)GlobalLock(data_handle); |
---|
272 | |
---|
273 | header_handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR)); |
---|
274 | if (!header_handle) return; |
---|
275 | |
---|
276 | header_ptr = (WAVEHDR *)GlobalLock(header_handle); |
---|
277 | |
---|
278 | header_ptr->lpData = data_ptr; |
---|
279 | header_ptr->dwBufferLength = format.nSamplesPerSec / 5; |
---|
280 | header_ptr->dwFlags = 0; |
---|
281 | header_ptr->dwLoops = 0; |
---|
282 | |
---|
283 | if (waveOutOpen(&device, WAVE_MAPPER, &format, 0, 0, WAVE_ALLOWSYNC) |
---|
284 | != MMSYSERR_NOERROR) return; |
---|
285 | |
---|
286 | waveOutPrepareHeader(device, header_ptr, sizeof(WAVEHDR)); |
---|
287 | |
---|
288 | sample_size = 44100 / 20; |
---|
289 | |
---|
290 | #else |
---|
291 | # ifdef HAVE_ALSA_ASOUNDLIB_H |
---|
292 | handle = NULL; |
---|
293 | |
---|
294 | if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) >= 0) { |
---|
295 | // Initialize PCM sound stuff... |
---|
296 | snd_pcm_hw_params_t *params; |
---|
297 | |
---|
298 | snd_pcm_hw_params_alloca(¶ms); |
---|
299 | snd_pcm_hw_params_any(handle, params); |
---|
300 | snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); |
---|
301 | snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16); |
---|
302 | snd_pcm_hw_params_set_channels(handle, params, 2); |
---|
303 | unsigned rate = 44100; |
---|
304 | int dir; |
---|
305 | snd_pcm_hw_params_set_rate_near(handle, params, &rate, &dir); |
---|
306 | snd_pcm_uframes_t period = (int)rate / 4; |
---|
307 | snd_pcm_hw_params_set_period_size_near(handle, params, &period, &dir); |
---|
308 | |
---|
309 | sample_size = rate / 20; |
---|
310 | |
---|
311 | if (snd_pcm_hw_params(handle, params) < 0) { |
---|
312 | sample_size = 0; |
---|
313 | snd_pcm_close(handle); |
---|
314 | handle = NULL; |
---|
315 | } |
---|
316 | } |
---|
317 | # endif // HAVE_ALSA_ASOUNDLIB_H |
---|
318 | #endif // __APPLE__ |
---|
319 | |
---|
320 | if (sample_size) { |
---|
321 | // Make each of the notes using a combination of sine and sawtooth waves |
---|
322 | int attack = sample_size / 10; |
---|
323 | int decay = 4 * sample_size / 5; |
---|
324 | |
---|
325 | for (int i = 0; i < 9; i ++) { |
---|
326 | sample_data[i] = new short[2 * sample_size]; |
---|
327 | |
---|
328 | short *sample_ptr = sample_data[i]; |
---|
329 | |
---|
330 | for (int j = 0; j < sample_size; j ++, sample_ptr += 2) { |
---|
331 | double theta = 0.05 * frequencies[i] * j / sample_size; |
---|
332 | double val = 0.5 * sin(2.0 * M_PI * theta) + theta - (int)theta - 0.5; |
---|
333 | |
---|
334 | if (j < attack) { |
---|
335 | *sample_ptr = (int)(32767 * val * j / attack); |
---|
336 | } else if (j > decay) { |
---|
337 | *sample_ptr = (int)(32767 * val * (sample_size - j + decay) / |
---|
338 | sample_size); |
---|
339 | } else *sample_ptr = (int)(32767 * val); |
---|
340 | |
---|
341 | sample_ptr[1] = *sample_ptr; |
---|
342 | } |
---|
343 | } |
---|
344 | } |
---|
345 | } |
---|
346 | |
---|
347 | |
---|
348 | // Cleanup the SudokuSound class |
---|
349 | SudokuSound::~SudokuSound() { |
---|
350 | #ifdef __APPLE__ |
---|
351 | if (sample_size) { |
---|
352 | AudioDeviceStop(device, audio_cb); |
---|
353 | AudioDeviceRemoveIOProc(device, audio_cb); |
---|
354 | } |
---|
355 | |
---|
356 | #elif defined(WIN32) |
---|
357 | if (sample_size) { |
---|
358 | waveOutClose(device); |
---|
359 | |
---|
360 | GlobalUnlock(header_handle); |
---|
361 | GlobalFree(header_handle); |
---|
362 | |
---|
363 | GlobalUnlock(data_handle); |
---|
364 | GlobalFree(data_handle); |
---|
365 | } |
---|
366 | |
---|
367 | #else |
---|
368 | # ifdef HAVE_ALSA_ASOUNDLIB_H |
---|
369 | if (handle) { |
---|
370 | snd_pcm_drain(handle); |
---|
371 | snd_pcm_close(handle); |
---|
372 | } |
---|
373 | # endif // HAVE_ALSA_ASOUNDLIB_H |
---|
374 | #endif // __APPLE__ |
---|
375 | |
---|
376 | if (sample_size) { |
---|
377 | for (int i = 0; i < 9; i ++) { |
---|
378 | delete[] sample_data[i]; |
---|
379 | } |
---|
380 | } |
---|
381 | } |
---|
382 | |
---|
383 | |
---|
384 | #ifdef __APPLE__ |
---|
385 | // Callback function for writing audio data... |
---|
386 | OSStatus |
---|
387 | SudokuSound::audio_cb(AudioDeviceID device, |
---|
388 | const AudioTimeStamp *current_time, |
---|
389 | const AudioBufferList *data_in, |
---|
390 | const AudioTimeStamp *time_in, |
---|
391 | AudioBufferList *data_out, |
---|
392 | const AudioTimeStamp *time_out, |
---|
393 | void *client_data) { |
---|
394 | SudokuSound *ss = (SudokuSound *)client_data; |
---|
395 | int count; |
---|
396 | float *buffer; |
---|
397 | |
---|
398 | if (!ss->remaining) return noErr; |
---|
399 | |
---|
400 | for (count = data_out->mBuffers[0].mDataByteSize / sizeof(float), |
---|
401 | buffer = (float*) data_out->mBuffers[0].mData; |
---|
402 | ss->remaining > 0 && count > 0; |
---|
403 | count --, ss->data ++, ss->remaining --) { |
---|
404 | *buffer++ = *(ss->data) / 32767.0; |
---|
405 | } |
---|
406 | |
---|
407 | while (count > 0) { |
---|
408 | *buffer++ = 0.0; |
---|
409 | count --; |
---|
410 | } |
---|
411 | |
---|
412 | return noErr; |
---|
413 | } |
---|
414 | #endif // __APPLE__ |
---|
415 | |
---|
416 | |
---|
417 | // Play a note for 250ms... |
---|
418 | void SudokuSound::play(char note) { |
---|
419 | Fl::check(); |
---|
420 | |
---|
421 | #ifdef __APPLE__ |
---|
422 | // Point to the next note... |
---|
423 | data = sample_data[note - 'A']; |
---|
424 | remaining = sample_size * 2; |
---|
425 | |
---|
426 | // Wait for the sound to complete... |
---|
427 | usleep(50000); |
---|
428 | |
---|
429 | #elif defined(WIN32) |
---|
430 | if (sample_size) { |
---|
431 | memcpy(data_ptr, sample_data[note - 'A'], sample_size * 4); |
---|
432 | |
---|
433 | waveOutWrite(device, header_ptr, sizeof(WAVEHDR)); |
---|
434 | |
---|
435 | Sleep(50); |
---|
436 | } else Beep(frequencies[note - 'A'], 50); |
---|
437 | |
---|
438 | #else |
---|
439 | # ifdef HAVE_ALSA_ASOUNDLIB_H |
---|
440 | if (handle) { |
---|
441 | // Use ALSA to play the sound... |
---|
442 | if (snd_pcm_writei(handle, sample_data[note - 'A'], sample_size) < 0) { |
---|
443 | snd_pcm_prepare(handle); |
---|
444 | snd_pcm_writei(handle, sample_data[note - 'A'], sample_size); |
---|
445 | } |
---|
446 | usleep(50000); |
---|
447 | return; |
---|
448 | } |
---|
449 | # endif // HAVE_ALSA_ASOUNDLIB_H |
---|
450 | |
---|
451 | // Just use standard X11 stuff... |
---|
452 | XKeyboardState state; |
---|
453 | XKeyboardControl control; |
---|
454 | |
---|
455 | // Get original pitch and duration... |
---|
456 | XGetKeyboardControl(fl_display, &state); |
---|
457 | |
---|
458 | // Sound a tone for the given note... |
---|
459 | control.bell_percent = 100; |
---|
460 | control.bell_pitch = frequencies[note - 'A']; |
---|
461 | control.bell_duration = 50; |
---|
462 | |
---|
463 | XChangeKeyboardControl(fl_display, |
---|
464 | KBBellPercent | KBBellPitch | KBBellDuration, |
---|
465 | &control); |
---|
466 | XBell(fl_display, 100); |
---|
467 | XFlush(fl_display); |
---|
468 | |
---|
469 | // Restore original pitch and duration... |
---|
470 | control.bell_percent = state.bell_percent; |
---|
471 | control.bell_pitch = state.bell_pitch; |
---|
472 | control.bell_duration = state.bell_duration; |
---|
473 | |
---|
474 | XChangeKeyboardControl(fl_display, |
---|
475 | KBBellPercent | KBBellPitch | KBBellDuration, |
---|
476 | &control); |
---|
477 | #endif // __APPLE__ |
---|
478 | } |
---|
479 | |
---|
480 | |
---|
481 | // Create a cell widget |
---|
482 | SudokuCell::SudokuCell(int X, int Y, int W, int H) |
---|
483 | : Fl_Widget(X, Y, W, H, 0) { |
---|
484 | value(0); |
---|
485 | } |
---|
486 | |
---|
487 | |
---|
488 | // Draw cell |
---|
489 | void |
---|
490 | SudokuCell::draw() { |
---|
491 | static Fl_Align align[8] = { |
---|
492 | FL_ALIGN_TOP_LEFT, |
---|
493 | FL_ALIGN_TOP, |
---|
494 | FL_ALIGN_TOP_RIGHT, |
---|
495 | FL_ALIGN_RIGHT, |
---|
496 | FL_ALIGN_BOTTOM_RIGHT, |
---|
497 | FL_ALIGN_BOTTOM, |
---|
498 | FL_ALIGN_BOTTOM_LEFT, |
---|
499 | FL_ALIGN_LEFT |
---|
500 | }; |
---|
501 | |
---|
502 | |
---|
503 | // Draw the cell box... |
---|
504 | if (readonly()) fl_draw_box(FL_UP_BOX, x(), y(), w(), h(), color()); |
---|
505 | else fl_draw_box(FL_DOWN_BOX, x(), y(), w(), h(), color()); |
---|
506 | |
---|
507 | // Draw the cell background... |
---|
508 | if (Fl::focus() == this) { |
---|
509 | Fl_Color c = fl_color_average(FL_SELECTION_COLOR, color(), 0.5f); |
---|
510 | fl_color(c); |
---|
511 | fl_rectf(x() + 4, y() + 4, w() - 8, h() - 8); |
---|
512 | fl_color(fl_contrast(labelcolor(), c)); |
---|
513 | } else fl_color(labelcolor()); |
---|
514 | |
---|
515 | // Draw the cell value... |
---|
516 | char s[2]; |
---|
517 | |
---|
518 | s[1] = '\0'; |
---|
519 | |
---|
520 | if (value_) { |
---|
521 | s[0] = value_ + '0'; |
---|
522 | |
---|
523 | fl_font(FL_HELVETICA_BOLD, h() - 10); |
---|
524 | fl_draw(s, x(), y(), w(), h(), FL_ALIGN_CENTER); |
---|
525 | } |
---|
526 | |
---|
527 | fl_font(FL_HELVETICA_BOLD, h() / 5); |
---|
528 | |
---|
529 | for (int i = 0; i < 8; i ++) { |
---|
530 | if (test_value_[i]) { |
---|
531 | s[0] = test_value_[i] + '0'; |
---|
532 | fl_draw(s, x() + 5, y() + 5, w() - 10, h() - 10, align[i]); |
---|
533 | } |
---|
534 | } |
---|
535 | } |
---|
536 | |
---|
537 | |
---|
538 | // Handle events in cell |
---|
539 | int |
---|
540 | SudokuCell::handle(int event) { |
---|
541 | switch (event) { |
---|
542 | case FL_FOCUS : |
---|
543 | Fl::focus(this); |
---|
544 | redraw(); |
---|
545 | return 1; |
---|
546 | |
---|
547 | case FL_UNFOCUS : |
---|
548 | redraw(); |
---|
549 | return 1; |
---|
550 | |
---|
551 | case FL_PUSH : |
---|
552 | if (!readonly() && Fl::event_inside(this)) { |
---|
553 | if (Fl::event_clicks()) { |
---|
554 | // 2+ clicks increments/sets value |
---|
555 | if (value()) { |
---|
556 | if (value() < 9) value(value() + 1); |
---|
557 | else value(1); |
---|
558 | } else value(((Sudoku *)window())->next_value(this)); |
---|
559 | } |
---|
560 | |
---|
561 | Fl::focus(this); |
---|
562 | redraw(); |
---|
563 | return 1; |
---|
564 | } |
---|
565 | break; |
---|
566 | |
---|
567 | case FL_KEYDOWN : |
---|
568 | if (Fl::event_state() & FL_CTRL) break; |
---|
569 | int key = Fl::event_key() - '0'; |
---|
570 | if (key < 0 || key > 9) key = Fl::event_key() - FL_KP - '0'; |
---|
571 | if (key > 0 && key <= 9) { |
---|
572 | if (readonly()) { |
---|
573 | fl_beep(FL_BEEP_ERROR); |
---|
574 | return 1; |
---|
575 | } |
---|
576 | |
---|
577 | if (Fl::event_state() & (FL_SHIFT | FL_CAPS_LOCK)) { |
---|
578 | int i; |
---|
579 | |
---|
580 | for (i = 0; i < 8; i ++) |
---|
581 | if (test_value_[i] == key) { |
---|
582 | test_value_[i] = 0; |
---|
583 | break; |
---|
584 | } |
---|
585 | |
---|
586 | if (i >= 8) { |
---|
587 | for (i = 0; i < 8; i ++) |
---|
588 | if (!test_value_[i]) { |
---|
589 | test_value_[i] = key; |
---|
590 | break; |
---|
591 | } |
---|
592 | } |
---|
593 | |
---|
594 | if (i >= 8) { |
---|
595 | for (i = 0; i < 7; i ++) test_value_[i] = test_value_[i + 1]; |
---|
596 | test_value_[i] = key; |
---|
597 | } |
---|
598 | |
---|
599 | redraw(); |
---|
600 | } else { |
---|
601 | value(key); |
---|
602 | do_callback(); |
---|
603 | } |
---|
604 | return 1; |
---|
605 | } else if (key == 0 || Fl::event_key() == FL_BackSpace || |
---|
606 | Fl::event_key() == FL_Delete) { |
---|
607 | if (readonly()) { |
---|
608 | fl_beep(FL_BEEP_ERROR); |
---|
609 | return 1; |
---|
610 | } |
---|
611 | |
---|
612 | value(0); |
---|
613 | do_callback(); |
---|
614 | return 1; |
---|
615 | } |
---|
616 | break; |
---|
617 | } |
---|
618 | |
---|
619 | return Fl_Widget::handle(event); |
---|
620 | } |
---|
621 | |
---|
622 | |
---|
623 | // Sudoku class globals... |
---|
624 | Fl_Help_Dialog *Sudoku::help_dialog_ = (Fl_Help_Dialog *)0; |
---|
625 | Fl_Preferences Sudoku::prefs_(Fl_Preferences::USER, "fltk.org", "sudoku"); |
---|
626 | |
---|
627 | |
---|
628 | // Create a Sudoku game window... |
---|
629 | Sudoku::Sudoku() |
---|
630 | : Fl_Window(GROUP_SIZE * 3, GROUP_SIZE * 3 + MENU_OFFSET, "Sudoku") |
---|
631 | { |
---|
632 | int j, k; |
---|
633 | Fl_Group *g; |
---|
634 | SudokuCell *cell; |
---|
635 | static Fl_Menu_Item items[] = { |
---|
636 | { "&Game", 0, 0, 0, FL_SUBMENU }, |
---|
637 | { "&New Game", FL_COMMAND | 'n', new_cb, 0, FL_MENU_DIVIDER }, |
---|
638 | { "&Check Game", FL_COMMAND | 'c', check_cb, 0, 0 }, |
---|
639 | { "&Restart Game", FL_COMMAND | 'r', restart_cb, 0, 0 }, |
---|
640 | { "&Solve Game", FL_COMMAND | 's', solve_cb, 0, FL_MENU_DIVIDER }, |
---|
641 | { "&Update Helpers", 0, update_helpers_cb, 0, 0 }, |
---|
642 | { "&Mute Sound", FL_COMMAND | 'm', mute_cb, 0, FL_MENU_TOGGLE | FL_MENU_DIVIDER }, |
---|
643 | { "&Quit", FL_COMMAND | 'q', close_cb, 0, 0 }, |
---|
644 | { 0 }, |
---|
645 | { "&Difficulty", 0, 0, 0, FL_SUBMENU }, |
---|
646 | { "&Easy", 0, diff_cb, (void *)"0", FL_MENU_RADIO }, |
---|
647 | { "&Medium", 0, diff_cb, (void *)"1", FL_MENU_RADIO }, |
---|
648 | { "&Hard", 0, diff_cb, (void *)"2", FL_MENU_RADIO }, |
---|
649 | { "&Impossible", 0, diff_cb, (void *)"3", FL_MENU_RADIO }, |
---|
650 | { 0 }, |
---|
651 | { "&Help", 0, 0, 0, FL_SUBMENU }, |
---|
652 | { "&About Sudoku", FL_F + 1, help_cb, 0, 0 }, |
---|
653 | { 0 }, |
---|
654 | { 0 } |
---|
655 | }; |
---|
656 | |
---|
657 | |
---|
658 | // Setup sound output... |
---|
659 | prefs_.get("mute_sound", j, 0); |
---|
660 | if (j) { |
---|
661 | // Mute sound? |
---|
662 | sound_ = NULL; |
---|
663 | items[6].flags |= FL_MENU_VALUE; |
---|
664 | } else sound_ = new SudokuSound(); |
---|
665 | |
---|
666 | // Menubar... |
---|
667 | prefs_.get("difficulty", difficulty_, 0); |
---|
668 | if (difficulty_ < 0 || difficulty_ > 3) difficulty_ = 0; |
---|
669 | |
---|
670 | items[10 + difficulty_].flags |= FL_MENU_VALUE; |
---|
671 | |
---|
672 | menubar_ = new Fl_Sys_Menu_Bar(0, 0, 3 * GROUP_SIZE, 25); |
---|
673 | menubar_->menu(items); |
---|
674 | |
---|
675 | // Create the grids... |
---|
676 | grid_ = new Fl_Group(0, MENU_OFFSET, 3 * GROUP_SIZE, 3 * GROUP_SIZE); |
---|
677 | |
---|
678 | for (j = 0; j < 3; j ++) |
---|
679 | for (k = 0; k < 3; k ++) { |
---|
680 | g = new Fl_Group(k * GROUP_SIZE, j * GROUP_SIZE + MENU_OFFSET, |
---|
681 | GROUP_SIZE, GROUP_SIZE); |
---|
682 | g->box(FL_BORDER_BOX); |
---|
683 | if ((int)(j == 1) ^ (int)(k == 1)) g->color(FL_DARK3); |
---|
684 | else g->color(FL_DARK2); |
---|
685 | g->end(); |
---|
686 | |
---|
687 | grid_groups_[j][k] = g; |
---|
688 | } |
---|
689 | |
---|
690 | for (j = 0; j < 9; j ++) |
---|
691 | for (k = 0; k < 9; k ++) { |
---|
692 | cell = new SudokuCell(k * CELL_SIZE + CELL_OFFSET + |
---|
693 | (k / 3) * (GROUP_SIZE - 3 * CELL_SIZE), |
---|
694 | j * CELL_SIZE + CELL_OFFSET + MENU_OFFSET + |
---|
695 | (j / 3) * (GROUP_SIZE - 3 * CELL_SIZE), |
---|
696 | CELL_SIZE, CELL_SIZE); |
---|
697 | cell->callback(reset_cb); |
---|
698 | grid_cells_[j][k] = cell; |
---|
699 | } |
---|
700 | |
---|
701 | // Set icon for window (MacOS uses app bundle for icon...) |
---|
702 | #ifdef WIN32 |
---|
703 | icon((char *)LoadIcon(fl_display, MAKEINTRESOURCE(IDI_ICON))); |
---|
704 | #elif !defined(__APPLE__) |
---|
705 | fl_open_display(); |
---|
706 | icon((char *)XCreateBitmapFromData(fl_display, DefaultRootWindow(fl_display), |
---|
707 | (char *)sudoku_bits, sudoku_width, |
---|
708 | sudoku_height)); |
---|
709 | #endif // WIN32 |
---|
710 | |
---|
711 | // Catch window close events... |
---|
712 | callback(close_cb); |
---|
713 | |
---|
714 | // Make the window resizable... |
---|
715 | resizable(grid_); |
---|
716 | size_range(3 * GROUP_SIZE, 3 * GROUP_SIZE + MENU_OFFSET, 0, 0, 5, 5, 1); |
---|
717 | |
---|
718 | // Restore the previous window dimensions... |
---|
719 | int X, Y, W, H; |
---|
720 | |
---|
721 | if (prefs_.get("x", X, -1)) { |
---|
722 | prefs_.get("y", Y, -1); |
---|
723 | prefs_.get("width", W, 3 * GROUP_SIZE); |
---|
724 | prefs_.get("height", H, 3 * GROUP_SIZE + MENU_OFFSET); |
---|
725 | |
---|
726 | resize(X, Y, W, H); |
---|
727 | } |
---|
728 | |
---|
729 | set_title(); |
---|
730 | } |
---|
731 | |
---|
732 | |
---|
733 | // Destroy the sudoku window... |
---|
734 | Sudoku::~Sudoku() { |
---|
735 | if (sound_) delete sound_; |
---|
736 | } |
---|
737 | |
---|
738 | |
---|
739 | // Check for a solution to the game... |
---|
740 | void |
---|
741 | Sudoku::check_cb(Fl_Widget *widget, void *) { |
---|
742 | ((Sudoku *)(widget->window()))->check_game(); |
---|
743 | } |
---|
744 | |
---|
745 | |
---|
746 | // Check if the user has correctly solved the game... |
---|
747 | void |
---|
748 | Sudoku::check_game(bool highlight) { |
---|
749 | bool empty = false; |
---|
750 | bool correct = true; |
---|
751 | int j, k, m; |
---|
752 | |
---|
753 | // Check the game for right/wrong answers... |
---|
754 | for (j = 0; j < 9; j ++) |
---|
755 | for (k = 0; k < 9; k ++) { |
---|
756 | SudokuCell *cell = grid_cells_[j][k]; |
---|
757 | int val = cell->value(); |
---|
758 | |
---|
759 | if (cell->readonly()) continue; |
---|
760 | |
---|
761 | if (!val) empty = true; |
---|
762 | else { |
---|
763 | for (m = 0; m < 9; m ++) |
---|
764 | if ((j != m && grid_cells_[m][k]->value() == val) || |
---|
765 | (k != m && grid_cells_[j][m]->value() == val)) break; |
---|
766 | |
---|
767 | if (m < 9) { |
---|
768 | if (highlight) { |
---|
769 | cell->color(FL_YELLOW); |
---|
770 | cell->redraw(); |
---|
771 | } |
---|
772 | |
---|
773 | correct = false; |
---|
774 | } else if (highlight) { |
---|
775 | cell->color(FL_LIGHT3); |
---|
776 | cell->redraw(); |
---|
777 | } |
---|
778 | } |
---|
779 | } |
---|
780 | |
---|
781 | // Check subgrids for duplicate numbers... |
---|
782 | for (j = 0; j < 9; j += 3) |
---|
783 | for (k = 0; k < 9; k += 3) |
---|
784 | for (int jj = 0; jj < 3; jj ++) |
---|
785 | for (int kk = 0; kk < 3; kk ++) { |
---|
786 | SudokuCell *cell = grid_cells_[j + jj][k + kk]; |
---|
787 | int val = cell->value(); |
---|
788 | |
---|
789 | if (cell->readonly() || !val) continue; |
---|
790 | |
---|
791 | int jjj; |
---|
792 | |
---|
793 | for (jjj = 0; jjj < 3; jjj ++) { |
---|
794 | int kkk; |
---|
795 | |
---|
796 | for (kkk = 0; kkk < 3; kkk ++) |
---|
797 | if (jj != jjj && kk != kkk && |
---|
798 | grid_cells_[j + jjj][k + kkk]->value() == val) break; |
---|
799 | |
---|
800 | if (kkk < 3) break; |
---|
801 | } |
---|
802 | |
---|
803 | if (jjj < 3) { |
---|
804 | if (highlight) { |
---|
805 | cell->color(FL_YELLOW); |
---|
806 | cell->redraw(); |
---|
807 | } |
---|
808 | |
---|
809 | correct = false; |
---|
810 | } |
---|
811 | } |
---|
812 | |
---|
813 | if (!empty && correct) { |
---|
814 | // Success! |
---|
815 | for (j = 0; j < 9; j ++) { |
---|
816 | for (k = 0; k < 9; k ++) { |
---|
817 | SudokuCell *cell = grid_cells_[j][k]; |
---|
818 | cell->color(FL_GREEN); |
---|
819 | cell->readonly(1); |
---|
820 | } |
---|
821 | |
---|
822 | if (sound_) sound_->play('A' + grid_cells_[j][8]->value() - 1); |
---|
823 | } |
---|
824 | } |
---|
825 | } |
---|
826 | |
---|
827 | |
---|
828 | // Close the window, saving the game first... |
---|
829 | void |
---|
830 | Sudoku::close_cb(Fl_Widget *widget, void *) { |
---|
831 | Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget); |
---|
832 | |
---|
833 | s->save_game(); |
---|
834 | s->hide(); |
---|
835 | |
---|
836 | if (help_dialog_) help_dialog_->hide(); |
---|
837 | } |
---|
838 | |
---|
839 | |
---|
840 | // Set the level of difficulty... |
---|
841 | void |
---|
842 | Sudoku::diff_cb(Fl_Widget *widget, void *d) { |
---|
843 | Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget); |
---|
844 | int diff = atoi((char *)d); |
---|
845 | |
---|
846 | if (diff != s->difficulty_) { |
---|
847 | s->difficulty_ = diff; |
---|
848 | s->new_game(s->seed_); |
---|
849 | s->set_title(); |
---|
850 | |
---|
851 | if (diff > 1) |
---|
852 | { |
---|
853 | // Display a message about the higher difficulty levels for the |
---|
854 | // Sudoku zealots of the world... |
---|
855 | int val; |
---|
856 | |
---|
857 | prefs_.get("difficulty_warning", val, 0); |
---|
858 | |
---|
859 | if (!val) |
---|
860 | { |
---|
861 | prefs_.set("difficulty_warning", 1); |
---|
862 | fl_alert("Note: 'Hard' and 'Impossible' puzzles may have more than " |
---|
863 | "one possible solution.\n" |
---|
864 | "This is not an error or bug."); |
---|
865 | } |
---|
866 | } |
---|
867 | |
---|
868 | prefs_.set("difficulty", s->difficulty_); |
---|
869 | } |
---|
870 | } |
---|
871 | |
---|
872 | // Update the little marker numbers in all cells |
---|
873 | void |
---|
874 | Sudoku::update_helpers_cb(Fl_Widget *widget, void *) { |
---|
875 | Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget); |
---|
876 | s->update_helpers(); |
---|
877 | } |
---|
878 | |
---|
879 | void |
---|
880 | Sudoku::update_helpers() { |
---|
881 | int j, k, m; |
---|
882 | |
---|
883 | // First we delete any entries that the user may have made |
---|
884 | for (j = 0; j < 9; j ++) { |
---|
885 | for (k = 0; k < 9; k ++) { |
---|
886 | SudokuCell *cell = grid_cells_[j][k]; |
---|
887 | for (m = 0; m < 8; m ++) { |
---|
888 | cell->test_value(0, m); |
---|
889 | } |
---|
890 | } |
---|
891 | } |
---|
892 | |
---|
893 | // Now go through all cells and find out, what we can not be |
---|
894 | for (j = 0; j < 81; j ++) { |
---|
895 | char taken[10] = { 0 }; |
---|
896 | // Find our destination cell |
---|
897 | int row = j / 9; |
---|
898 | int col = j % 9; |
---|
899 | SudokuCell *dst_cell = grid_cells_[row][col]; |
---|
900 | if (dst_cell->value()) continue; |
---|
901 | // Find all values already taken in this row |
---|
902 | for (k = 0; k < 9; k ++) { |
---|
903 | SudokuCell *cell = grid_cells_[row][k]; |
---|
904 | int v = cell->value(); |
---|
905 | if (v) taken[v] = 1; |
---|
906 | } |
---|
907 | // Find all values already taken in this column |
---|
908 | for (k = 0; k < 9; k ++) { |
---|
909 | SudokuCell *cell = grid_cells_[k][col]; |
---|
910 | int v = cell->value(); |
---|
911 | if (v) taken[v] = 1; |
---|
912 | } |
---|
913 | // Now find all values already taken in this square |
---|
914 | int ro = (row / 3) * 3; |
---|
915 | int co = (col / 3) * 3; |
---|
916 | for (k = 0; k < 3; k ++) { |
---|
917 | for (m = 0; m < 3; m ++) { |
---|
918 | SudokuCell *cell = grid_cells_[ro + k][co + m]; |
---|
919 | int v = cell->value(); |
---|
920 | if (v) taken[v] = 1; |
---|
921 | } |
---|
922 | } |
---|
923 | // transfer our findings to the markers |
---|
924 | for (m = 1, k = 0; m <= 9; m ++) { |
---|
925 | if (!taken[m]) |
---|
926 | dst_cell->test_value(m, k ++); |
---|
927 | } |
---|
928 | } |
---|
929 | } |
---|
930 | |
---|
931 | |
---|
932 | // Show the on-line help... |
---|
933 | void |
---|
934 | Sudoku::help_cb(Fl_Widget *, void *) { |
---|
935 | if (!help_dialog_) { |
---|
936 | help_dialog_ = new Fl_Help_Dialog(); |
---|
937 | |
---|
938 | help_dialog_->value( |
---|
939 | "<HTML>\n" |
---|
940 | "<HEAD>\n" |
---|
941 | "<TITLE>Sudoku Help</TITLE>\n" |
---|
942 | "</HEAD>\n" |
---|
943 | "<BODY BGCOLOR='#ffffff'>\n" |
---|
944 | |
---|
945 | "<H2>About the Game</H2>\n" |
---|
946 | |
---|
947 | "<P>Sudoku (pronounced soo-dough-coo with the emphasis on the\n" |
---|
948 | "first syllable) is a simple number-based puzzle/game played on a\n" |
---|
949 | "9x9 grid that is divided into 3x3 subgrids. The goal is to enter\n" |
---|
950 | "a number from 1 to 9 in each cell so that each number appears\n" |
---|
951 | "only once in each column and row. In addition, each 3x3 subgrid\n" |
---|
952 | "may only contain one of each number.</P>\n" |
---|
953 | |
---|
954 | "<P>This version of the puzzle is copyright 2005-2006 by Michael R\n" |
---|
955 | "Sweet.</P>\n" |
---|
956 | |
---|
957 | "<P><B>Note:</B> The 'Hard' and 'Impossible' difficulty\n" |
---|
958 | "levels generate Sudoku puzzles with multiple possible solutions.\n" |
---|
959 | "While some purists insist that these cannot be called 'Sudoku'\n" |
---|
960 | "puzzles, the author (me) has personally solved many such puzzles\n" |
---|
961 | "in published/printed Sudoku books and finds them far more\n" |
---|
962 | "interesting than the simple single solution variety. If you don't\n" |
---|
963 | "like it, don't play with the difficulty set to 'High' or\n" |
---|
964 | "'Impossible'.</P>\n" |
---|
965 | |
---|
966 | "<H2>How to Play the Game</H2>\n" |
---|
967 | |
---|
968 | "<P>At the start of a new game, Sudoku fills in a random selection\n" |
---|
969 | "of cells for you - the number of cells depends on the difficulty\n" |
---|
970 | "level you use. Click in any of the empty cells or use the arrow\n" |
---|
971 | "keys to highlight individual cells and press a number from 1 to 9\n" |
---|
972 | "to fill in the cell. To clear a cell, press 0, Delete, or\n" |
---|
973 | "Backspace. When you have successfully completed all subgrids, the\n" |
---|
974 | "entire puzzle is highlighted in green until you start a new\n" |
---|
975 | "game.</P>\n" |
---|
976 | |
---|
977 | "<P>As you work to complete the puzzle, you can display possible\n" |
---|
978 | "solutions inside each cell by holding the Shift key and pressing\n" |
---|
979 | "each number in turn. Repeat the process to remove individual\n" |
---|
980 | "numbers, or press a number without the Shift key to replace them\n" |
---|
981 | "with the actual number to use.</P>\n" |
---|
982 | "</BODY>\n" |
---|
983 | ); |
---|
984 | } |
---|
985 | |
---|
986 | help_dialog_->show(); |
---|
987 | } |
---|
988 | |
---|
989 | |
---|
990 | // Load the game from saved preferences... |
---|
991 | void |
---|
992 | Sudoku::load_game() { |
---|
993 | // Load the current values and state of each grid... |
---|
994 | memset(grid_values_, 0, sizeof(grid_values_)); |
---|
995 | |
---|
996 | bool solved = true; |
---|
997 | |
---|
998 | for (int j = 0; j < 9; j ++) |
---|
999 | for (int k = 0; k < 9; k ++) { |
---|
1000 | char name[255]; |
---|
1001 | int val; |
---|
1002 | |
---|
1003 | SudokuCell *cell = grid_cells_[j][k]; |
---|
1004 | |
---|
1005 | sprintf(name, "value%d.%d", j, k); |
---|
1006 | if (!prefs_.get(name, val, 0)) { |
---|
1007 | j = 9; |
---|
1008 | grid_values_[0][0] = 0; |
---|
1009 | break; |
---|
1010 | } |
---|
1011 | |
---|
1012 | grid_values_[j][k] = val; |
---|
1013 | |
---|
1014 | sprintf(name, "state%d.%d", j, k); |
---|
1015 | prefs_.get(name, val, 0); |
---|
1016 | cell->value(val); |
---|
1017 | |
---|
1018 | sprintf(name, "readonly%d.%d", j, k); |
---|
1019 | prefs_.get(name, val, 0); |
---|
1020 | cell->readonly(val); |
---|
1021 | |
---|
1022 | if (val) cell->color(FL_GRAY); |
---|
1023 | else { |
---|
1024 | cell->color(FL_LIGHT3); |
---|
1025 | solved = false; |
---|
1026 | } |
---|
1027 | |
---|
1028 | for (int m = 0; m < 8; m ++) { |
---|
1029 | sprintf(name, "test%d%d.%d", m, j, k); |
---|
1030 | prefs_.get(name, val, 0); |
---|
1031 | cell->test_value(val, m); |
---|
1032 | } |
---|
1033 | } |
---|
1034 | |
---|
1035 | // If we didn't load any values or the last game was solved, then |
---|
1036 | // create a new game automatically... |
---|
1037 | if (solved || !grid_values_[0][0]) new_game(time(NULL)); |
---|
1038 | else check_game(false); |
---|
1039 | } |
---|
1040 | |
---|
1041 | |
---|
1042 | // Mute/unmute sound... |
---|
1043 | void |
---|
1044 | Sudoku::mute_cb(Fl_Widget *widget, void *) { |
---|
1045 | Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget); |
---|
1046 | |
---|
1047 | if (s->sound_) { |
---|
1048 | delete s->sound_; |
---|
1049 | s->sound_ = NULL; |
---|
1050 | prefs_.set("mute_sound", 1); |
---|
1051 | } else { |
---|
1052 | s->sound_ = new SudokuSound(); |
---|
1053 | prefs_.set("mute_sound", 0); |
---|
1054 | } |
---|
1055 | } |
---|
1056 | |
---|
1057 | |
---|
1058 | // Create a new game... |
---|
1059 | void |
---|
1060 | Sudoku::new_cb(Fl_Widget *widget, void *) { |
---|
1061 | Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget); |
---|
1062 | |
---|
1063 | if (s->grid_cells_[0][0]->color() != FL_GREEN) { |
---|
1064 | if (!fl_choice("Are you sure you want to change the difficulty level and " |
---|
1065 | "discard the current game?", "Keep Current Game", "Start New Game", |
---|
1066 | NULL)) return; |
---|
1067 | } |
---|
1068 | |
---|
1069 | s->new_game(time(NULL)); |
---|
1070 | } |
---|
1071 | |
---|
1072 | |
---|
1073 | // Create a new game... |
---|
1074 | void |
---|
1075 | Sudoku::new_game(time_t seed) { |
---|
1076 | int j, k, m, n, t, count; |
---|
1077 | |
---|
1078 | |
---|
1079 | // Generate a new (valid) Sudoku grid... |
---|
1080 | seed_ = seed; |
---|
1081 | srand(seed); |
---|
1082 | |
---|
1083 | memset(grid_values_, 0, sizeof(grid_values_)); |
---|
1084 | |
---|
1085 | for (j = 0; j < 9; j += 3) { |
---|
1086 | for (k = 0; k < 9; k += 3) { |
---|
1087 | for (t = 1; t <= 9; t ++) { |
---|
1088 | for (count = 0; count < 20; count ++) { |
---|
1089 | m = j + (rand() % 3); |
---|
1090 | n = k + (rand() % 3); |
---|
1091 | if (!grid_values_[m][n]) { |
---|
1092 | int mm; |
---|
1093 | |
---|
1094 | for (mm = 0; mm < m; mm ++) |
---|
1095 | if (grid_values_[mm][n] == t) break; |
---|
1096 | |
---|
1097 | if (mm < m) continue; |
---|
1098 | |
---|
1099 | int nn; |
---|
1100 | |
---|
1101 | for (nn = 0; nn < n; nn ++) |
---|
1102 | if (grid_values_[m][nn] == t) break; |
---|
1103 | |
---|
1104 | if (nn < n) continue; |
---|
1105 | |
---|
1106 | grid_values_[m][n] = t; |
---|
1107 | break; |
---|
1108 | } |
---|
1109 | } |
---|
1110 | |
---|
1111 | if (count == 20) { |
---|
1112 | // Unable to find a valid puzzle so far, so start over... |
---|
1113 | k = 9; |
---|
1114 | j = -3; |
---|
1115 | memset(grid_values_, 0, sizeof(grid_values_)); |
---|
1116 | } |
---|
1117 | } |
---|
1118 | } |
---|
1119 | } |
---|
1120 | |
---|
1121 | // Start by making all cells editable |
---|
1122 | SudokuCell *cell; |
---|
1123 | |
---|
1124 | for (j = 0; j < 9; j ++) |
---|
1125 | for (k = 0; k < 9; k ++) { |
---|
1126 | cell = grid_cells_[j][k]; |
---|
1127 | |
---|
1128 | cell->value(0); |
---|
1129 | cell->readonly(0); |
---|
1130 | cell->color(FL_LIGHT3); |
---|
1131 | } |
---|
1132 | |
---|
1133 | // Show N cells... |
---|
1134 | count = 11 * (5 - difficulty_); |
---|
1135 | |
---|
1136 | int numbers[9]; |
---|
1137 | |
---|
1138 | for (j = 0; j < 9; j ++) numbers[j] = j + 1; |
---|
1139 | |
---|
1140 | while (count > 0) { |
---|
1141 | for (j = 0; j < 20; j ++) { |
---|
1142 | k = rand() % 9; |
---|
1143 | m = rand() % 9; |
---|
1144 | t = numbers[k]; |
---|
1145 | numbers[k] = numbers[m]; |
---|
1146 | numbers[m] = t; |
---|
1147 | } |
---|
1148 | |
---|
1149 | for (j = 0; count > 0 && j < 9; j ++) { |
---|
1150 | t = numbers[j]; |
---|
1151 | |
---|
1152 | for (k = 0; count > 0 && k < 9; k ++) { |
---|
1153 | cell = grid_cells_[j][k]; |
---|
1154 | |
---|
1155 | if (grid_values_[j][k] == t && !cell->readonly()) { |
---|
1156 | cell->value(grid_values_[j][k]); |
---|
1157 | cell->readonly(1); |
---|
1158 | cell->color(FL_GRAY); |
---|
1159 | |
---|
1160 | count --; |
---|
1161 | break; |
---|
1162 | } |
---|
1163 | } |
---|
1164 | } |
---|
1165 | } |
---|
1166 | } |
---|
1167 | |
---|
1168 | |
---|
1169 | // Return the next available value for a cell... |
---|
1170 | int |
---|
1171 | Sudoku::next_value(SudokuCell *c) { |
---|
1172 | int j, k, m, n; |
---|
1173 | |
---|
1174 | |
---|
1175 | for (j = 0; j < 9; j ++) { |
---|
1176 | for (k = 0; k < 9; k ++) |
---|
1177 | if (grid_cells_[j][k] == c) break; |
---|
1178 | |
---|
1179 | if (k < 9) break; |
---|
1180 | } |
---|
1181 | |
---|
1182 | if (j == 9) return 1; |
---|
1183 | |
---|
1184 | j -= j % 3; |
---|
1185 | k -= k % 3; |
---|
1186 | |
---|
1187 | int numbers[9]; |
---|
1188 | |
---|
1189 | memset(numbers, 0, sizeof(numbers)); |
---|
1190 | |
---|
1191 | for (m = 0; m < 3; m ++) |
---|
1192 | for (n = 0; n < 3; n ++) { |
---|
1193 | c = grid_cells_[j + m][k + n]; |
---|
1194 | if (c->value()) numbers[c->value() - 1] = 1; |
---|
1195 | } |
---|
1196 | |
---|
1197 | for (j = 0; j < 9; j ++) |
---|
1198 | if (!numbers[j]) return j + 1; |
---|
1199 | |
---|
1200 | return 1; |
---|
1201 | } |
---|
1202 | |
---|
1203 | |
---|
1204 | // Reset widget color to gray... |
---|
1205 | void |
---|
1206 | Sudoku::reset_cb(Fl_Widget *widget, void *) { |
---|
1207 | widget->color(FL_LIGHT3); |
---|
1208 | widget->redraw(); |
---|
1209 | |
---|
1210 | ((Sudoku *)(widget->window()))->check_game(false); |
---|
1211 | } |
---|
1212 | |
---|
1213 | |
---|
1214 | // Resize the window... |
---|
1215 | void |
---|
1216 | Sudoku::resize(int X, int Y, int W, int H) { |
---|
1217 | // Resize the window... |
---|
1218 | Fl_Window::resize(X, Y, W, H); |
---|
1219 | |
---|
1220 | // Save the new window geometry... |
---|
1221 | prefs_.set("x", X); |
---|
1222 | prefs_.set("y", Y); |
---|
1223 | prefs_.set("width", W); |
---|
1224 | prefs_.set("height", H); |
---|
1225 | } |
---|
1226 | |
---|
1227 | |
---|
1228 | // Restart game from beginning... |
---|
1229 | void |
---|
1230 | Sudoku::restart_cb(Fl_Widget *widget, void *) { |
---|
1231 | Sudoku *s = (Sudoku *)(widget->window()); |
---|
1232 | bool solved = true; |
---|
1233 | |
---|
1234 | for (int j = 0; j < 9; j ++) |
---|
1235 | for (int k = 0; k < 9; k ++) { |
---|
1236 | SudokuCell *cell = s->grid_cells_[j][k]; |
---|
1237 | |
---|
1238 | if (!cell->readonly()) { |
---|
1239 | solved = false; |
---|
1240 | int v = cell->value(); |
---|
1241 | cell->value(0); |
---|
1242 | cell->color(FL_LIGHT3); |
---|
1243 | if (v && s->sound_) s->sound_->play('A' + v - 1); |
---|
1244 | } |
---|
1245 | } |
---|
1246 | |
---|
1247 | if (solved) s->new_game(s->seed_); |
---|
1248 | } |
---|
1249 | |
---|
1250 | |
---|
1251 | // Save the current game state... |
---|
1252 | void |
---|
1253 | Sudoku::save_game() { |
---|
1254 | // Save the current values and state of each grid... |
---|
1255 | for (int j = 0; j < 9; j ++) |
---|
1256 | for (int k = 0; k < 9; k ++) { |
---|
1257 | char name[255]; |
---|
1258 | SudokuCell *cell = grid_cells_[j][k]; |
---|
1259 | |
---|
1260 | sprintf(name, "value%d.%d", j, k); |
---|
1261 | prefs_.set(name, grid_values_[j][k]); |
---|
1262 | |
---|
1263 | sprintf(name, "state%d.%d", j, k); |
---|
1264 | prefs_.set(name, cell->value()); |
---|
1265 | |
---|
1266 | sprintf(name, "readonly%d.%d", j, k); |
---|
1267 | prefs_.set(name, cell->readonly()); |
---|
1268 | |
---|
1269 | for (int m = 0; m < 8; m ++) { |
---|
1270 | sprintf(name, "test%d%d.%d", m, j, k); |
---|
1271 | prefs_.set(name, cell->test_value(m)); |
---|
1272 | } |
---|
1273 | } |
---|
1274 | } |
---|
1275 | |
---|
1276 | |
---|
1277 | // Set title of window... |
---|
1278 | void |
---|
1279 | Sudoku::set_title() { |
---|
1280 | static const char * const titles[] = { |
---|
1281 | "Sudoku - Easy", |
---|
1282 | "Sudoku - Medium", |
---|
1283 | "Sudoku - Hard", |
---|
1284 | "Sudoku - Impossible" |
---|
1285 | }; |
---|
1286 | |
---|
1287 | label(titles[difficulty_]); |
---|
1288 | } |
---|
1289 | |
---|
1290 | |
---|
1291 | // Solve the puzzle... |
---|
1292 | void |
---|
1293 | Sudoku::solve_cb(Fl_Widget *widget, void *) { |
---|
1294 | ((Sudoku *)(widget->window()))->solve_game(); |
---|
1295 | } |
---|
1296 | |
---|
1297 | |
---|
1298 | // Solve the puzzle... |
---|
1299 | void |
---|
1300 | Sudoku::solve_game() { |
---|
1301 | int j, k; |
---|
1302 | |
---|
1303 | for (j = 0; j < 9; j ++) { |
---|
1304 | for (k = 0; k < 9; k ++) { |
---|
1305 | SudokuCell *cell = grid_cells_[j][k]; |
---|
1306 | |
---|
1307 | cell->value(grid_values_[j][k]); |
---|
1308 | cell->readonly(1); |
---|
1309 | cell->color(FL_GRAY); |
---|
1310 | } |
---|
1311 | |
---|
1312 | if (sound_) sound_->play('A' + grid_cells_[j][8]->value() - 1); |
---|
1313 | } |
---|
1314 | } |
---|
1315 | |
---|
1316 | |
---|
1317 | // Main entry for game... |
---|
1318 | int |
---|
1319 | main(int argc, char *argv[]) { |
---|
1320 | Sudoku s; |
---|
1321 | |
---|
1322 | // Show the game... |
---|
1323 | s.show(argc, argv); |
---|
1324 | |
---|
1325 | // Load the previous game... |
---|
1326 | s.load_game(); |
---|
1327 | |
---|
1328 | // Run until the user quits... |
---|
1329 | return (Fl::run()); |
---|
1330 | } |
---|
1331 | |
---|
1332 | |
---|
1333 | // |
---|
1334 | // End of "$Id$". |
---|
1335 | // |
---|