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