Tweak themes for more color consistency.
[ntk.git] / test / sudoku.cxx
blob0b00beeb1526d62c29a6b6b453d2bf0f92f07918
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.
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.
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.
23 // Please report all bugs and problems on the following page:
25 // http://www.fltk.org/str.php
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>
45 #ifdef WIN32
46 # include "sudokurc.h"
47 #elif !defined(__APPLE__)
48 # include "pixmaps/sudoku.xbm"
49 #endif // WIN32
51 // Audio headers...
52 #include <config.h>
54 #ifndef WIN32
55 # include <unistd.h>
56 #endif // !WIN32
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
71 // Default sizes...
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__
83 // Sound class for Sudoku...
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.
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.
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;
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;
123 #else
124 # ifdef HAVE_ALSA_ASOUNDLIB_H
125 snd_pcm_t *handle;
126 # endif // HAVE_ALSA_ASOUNDLIB_H
127 #endif // __APPLE__
129 // Common data...
130 static int frequencies[9];
131 static short *sample_data[9];
132 static int sample_size;
134 public:
136 SudokuSound();
137 ~SudokuSound();
139 void play(char note);
143 // Sudoku cell class...
144 class SudokuCell : public Fl_Widget {
145 bool readonly_;
146 int value_;
147 int test_value_[9];
149 public:
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();
163 int value() const { return value_; }
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_;
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 *);
190 static Fl_Help_Dialog *help_dialog_;
191 static Fl_Preferences prefs_;
192 public:
194 Sudoku();
195 ~Sudoku();
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();
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)
220 short *SudokuSound::sample_data[9] = { 0 };
221 int SudokuSound::sample_size = 0;
224 // Initialize the SudokuSound class
225 SudokuSound::SudokuSound() {
226 sample_size = 0;
228 #ifdef __APPLE__
229 remaining = 0;
231 UInt32 size = sizeof(device);
233 if (AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,
234 &size, (void *)&device) != noErr) return;
236 size = sizeof(format);
237 if (AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyStreamFormat,
238 &size, &format) != noErr) return;
240 // Set up a format we like...
241 format.mSampleRate = 44100.0; // 44.1kHz
242 format.mChannelsPerFrame = 2; // stereo
244 if (AudioDeviceSetProperty(device, NULL, 0, false,
245 kAudioDevicePropertyStreamFormat,
246 sizeof(format), &format) != noErr) return;
248 // Check we got linear pcm - what to do if we did not ???
249 if (format.mFormatID != kAudioFormatLinearPCM) return;
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
260 sample_size = (int)format.mSampleRate / 20;
262 #elif defined(WIN32)
263 WAVEFORMATEX format;
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;
274 data_handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, format.nSamplesPerSec / 5);
275 if (!data_handle) return;
277 data_ptr = (LPSTR)GlobalLock(data_handle);
279 header_handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR));
280 if (!header_handle) return;
282 header_ptr = (WAVEHDR *)GlobalLock(header_handle);
284 header_ptr->lpData = data_ptr;
285 header_ptr->dwBufferLength = format.nSamplesPerSec / 5;
286 header_ptr->dwFlags = 0;
287 header_ptr->dwLoops = 0;
289 if (waveOutOpen(&device, WAVE_MAPPER, &format, 0, 0, WAVE_ALLOWSYNC)
290 != MMSYSERR_NOERROR) return;
292 waveOutPrepareHeader(device, header_ptr, sizeof(WAVEHDR));
294 sample_size = 44100 / 20;
296 #else
297 # ifdef HAVE_ALSA_ASOUNDLIB_H
298 handle = NULL;
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;
304 snd_pcm_hw_params_alloca(&params);
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);
315 sample_size = rate / 20;
317 if (snd_pcm_hw_params(handle, params) < 0) {
318 sample_size = 0;
319 snd_pcm_close(handle);
320 handle = NULL;
323 # endif // HAVE_ALSA_ASOUNDLIB_H
324 #endif // __APPLE__
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;
331 for (int i = 0; i < 9; i ++) {
332 sample_data[i] = new short[2 * sample_size];
334 short *sample_ptr = sample_data[i];
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;
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);
347 sample_ptr[1] = *sample_ptr;
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
367 #elif defined(WIN32)
368 if (sample_size) {
369 waveOutClose(device);
371 GlobalUnlock(header_handle);
372 GlobalFree(header_handle);
374 GlobalUnlock(data_handle);
375 GlobalFree(data_handle);
378 #else
379 # ifdef HAVE_ALSA_ASOUNDLIB_H
380 if (handle) {
381 snd_pcm_drain(handle);
382 snd_pcm_close(handle);
384 # endif // HAVE_ALSA_ASOUNDLIB_H
385 #endif // __APPLE__
387 if (sample_size) {
388 for (int i = 0; i < 9; i ++) {
389 delete[] sample_data[i];
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;
409 if (!ss->remaining) return noErr;
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;
418 while (count > 0) {
419 *buffer++ = 0.0;
420 count --;
423 return noErr;
425 #endif // __APPLE__
428 // Play a note for 250ms...
429 void SudokuSound::play(char note) {
430 Fl::check();
432 #ifdef __APPLE__
433 // Point to the next note...
434 data = sample_data[note - 'A'];
435 remaining = sample_size * 2;
437 // Wait for the sound to complete...
438 usleep(50000);
440 #elif defined(WIN32)
441 if (sample_size) {
442 memcpy(data_ptr, sample_data[note - 'A'], sample_size * 4);
444 waveOutWrite(device, header_ptr, sizeof(WAVEHDR));
446 Sleep(50);
447 } else Beep(frequencies[note - 'A'], 50);
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);
457 usleep(50000);
458 return;
460 # endif // HAVE_ALSA_ASOUNDLIB_H
462 // Just use standard X11 stuff...
463 XKeyboardState state;
464 XKeyboardControl control;
466 // Get original pitch and duration...
467 XGetKeyboardControl(fl_display, &state);
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;
474 XChangeKeyboardControl(fl_display,
475 KBBellPercent | KBBellPitch | KBBellDuration,
476 &control);
477 XBell(fl_display, 100);
478 XFlush(fl_display);
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;
485 XChangeKeyboardControl(fl_display,
486 KBBellPercent | KBBellPitch | KBBellDuration,
487 &control);
488 #endif // __APPLE__
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);
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
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());
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());
526 // Draw the cell value...
527 char s[2];
529 s[1] = '\0';
531 if (value_) {
532 s[0] = value_ + '0';
534 fl_font(FL_HELVETICA_BOLD, h() - 10);
535 fl_draw(s, x(), y(), w(), h(), FL_ALIGN_CENTER);
538 fl_font(FL_HELVETICA_BOLD, h() / 5);
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]);
549 // Handle events in cell
551 SudokuCell::handle(int event) {
552 switch (event) {
553 case FL_FOCUS :
554 Fl::focus(this);
555 redraw();
556 return 1;
558 case FL_UNFOCUS :
559 redraw();
560 return 1;
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));
572 Fl::focus(this);
573 redraw();
574 return 1;
576 break;
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;
588 if (Fl::event_state() & (FL_SHIFT | FL_CAPS_LOCK)) {
589 int i;
591 for (i = 0; i < 8; i ++)
592 if (test_value_[i] == key) {
593 test_value_[i] = 0;
594 break;
597 if (i >= 8) {
598 for (i = 0; i < 8; i ++)
599 if (!test_value_[i]) {
600 test_value_[i] = key;
601 break;
605 if (i >= 8) {
606 for (i = 0; i < 7; i ++) test_value_[i] = test_value_[i + 1];
607 test_value_[i] = key;
610 redraw();
611 } else {
612 value(key);
613 do_callback();
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;
623 value(0);
624 do_callback();
625 return 1;
627 break;
630 return Fl_Widget::handle(event);
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");
639 // Create a Sudoku game window...
640 Sudoku::Sudoku()
641 : Fl_Double_Window(GROUP_SIZE * 3, GROUP_SIZE * 3 + MENU_OFFSET, "Sudoku")
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 }
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();
677 // Menubar...
678 prefs_.get("difficulty", difficulty_, 0);
679 if (difficulty_ < 0 || difficulty_ > 3) difficulty_ = 0;
681 items[10 + difficulty_].flags |= FL_MENU_VALUE;
683 menubar_ = new Fl_Sys_Menu_Bar(0, 0, 3 * GROUP_SIZE, 25);
684 menubar_->menu(items);
686 // Create the grids...
687 grid_ = new Fl_Group(0, MENU_OFFSET, 3 * GROUP_SIZE, 3 * GROUP_SIZE);
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();
698 grid_groups_[j][k] = g;
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;
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
722 // Catch window close events...
723 callback(close_cb);
725 // Make the window resizable...
726 resizable(grid_);
727 size_range(3 * GROUP_SIZE, 3 * GROUP_SIZE + MENU_OFFSET, 0, 0, 5, 5, 1);
729 // Restore the previous window dimensions...
730 int X, Y, W, H;
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);
737 resize(X, Y, W, H);
740 set_title();
744 // Destroy the sudoku window...
745 Sudoku::~Sudoku() {
746 if (sound_) delete sound_;
750 // Check for a solution to the game...
751 void
752 Sudoku::check_cb(Fl_Widget *widget, void *) {
753 ((Sudoku *)(widget->window()))->check_game();
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;
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();
770 if (cell->readonly()) continue;
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;
778 if (m < 9) {
779 if (highlight) {
780 cell->color(FL_YELLOW);
781 cell->redraw();
784 correct = false;
785 } else if (highlight) {
786 cell->color(FL_LIGHT3);
787 cell->redraw();
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();
800 if (cell->readonly() || !val) continue;
802 int jjj;
804 for (jjj = 0; jjj < 3; jjj ++) {
805 int kkk;
807 for (kkk = 0; kkk < 3; kkk ++)
808 if (jj != jjj && kk != kkk &&
809 grid_cells_[j + jjj][k + kkk]->value() == val) break;
811 if (kkk < 3) break;
814 if (jjj < 3) {
815 if (highlight) {
816 cell->color(FL_YELLOW);
817 cell->redraw();
820 correct = false;
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);
833 if (sound_) sound_->play('A' + grid_cells_[j][8]->value() - 1);
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);
844 s->save_game();
845 s->hide();
847 if (help_dialog_) help_dialog_->hide();
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);
857 if (diff != s->difficulty_) {
858 s->difficulty_ = diff;
859 s->new_game(s->seed_);
860 s->set_title();
862 if (diff > 1)
864 // Display a message about the higher difficulty levels for the
865 // Sudoku zealots of the world...
866 int val;
868 prefs_.get("difficulty_warning", val, 0);
870 if (!val)
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.");
879 prefs_.set("difficulty", s->difficulty_);
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();
890 void
891 Sudoku::update_helpers() {
892 int j, k, m;
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);
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;
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;
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;
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 ++);
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();
949 help_dialog_->value(
950 "<HTML>\n"
951 "<HEAD>\n"
952 "<TITLE>Sudoku Help</TITLE>\n"
953 "</HEAD>\n"
954 "<BODY BGCOLOR='#ffffff'>\n"
956 "<H2>About the Game</H2>\n"
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"
965 "<P>This version of the puzzle is copyright 2005-2010 by Michael R\n"
966 "Sweet.</P>\n"
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"
977 "<H2>How to Play the Game</H2>\n"
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"
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"
997 help_dialog_->show();
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_));
1007 bool solved = true;
1009 for (int j = 0; j < 9; j ++)
1010 for (int k = 0; k < 9; k ++) {
1011 char name[255];
1012 int val;
1014 SudokuCell *cell = grid_cells_[j][k];
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;
1023 grid_values_[j][k] = val;
1025 sprintf(name, "state%d.%d", j, k);
1026 prefs_.get(name, val, 0);
1027 cell->value(val);
1029 sprintf(name, "readonly%d.%d", j, k);
1030 prefs_.get(name, val, 0);
1031 cell->readonly(val);
1033 if (val) cell->color(FL_GRAY);
1034 else {
1035 cell->color(FL_LIGHT3);
1036 solved = false;
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);
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);
1053 // Mute/unmute sound...
1054 void
1055 Sudoku::mute_cb(Fl_Widget *widget, void *) {
1056 Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget);
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);
1069 // Create a new game...
1070 void
1071 Sudoku::new_cb(Fl_Widget *widget, void *) {
1072 Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget);
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;
1080 s->new_game(time(NULL));
1084 // Create a new game...
1085 void
1086 Sudoku::new_game(time_t seed) {
1087 int j, k, m, n, t, count;
1090 // Generate a new (valid) Sudoku grid...
1091 seed_ = seed;
1092 srand(seed);
1094 memset(grid_values_, 0, sizeof(grid_values_));
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;
1105 for (mm = 0; mm < m; mm ++)
1106 if (grid_values_[mm][n] == t) break;
1108 if (mm < m) continue;
1110 int nn;
1112 for (nn = 0; nn < n; nn ++)
1113 if (grid_values_[m][nn] == t) break;
1115 if (nn < n) continue;
1117 grid_values_[m][n] = t;
1118 break;
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_));
1132 // Start by making all cells editable
1133 SudokuCell *cell;
1135 for (j = 0; j < 9; j ++)
1136 for (k = 0; k < 9; k ++) {
1137 cell = grid_cells_[j][k];
1139 cell->value(0);
1140 cell->readonly(0);
1141 cell->color(FL_LIGHT3);
1144 // Show N cells...
1145 count = 11 * (5 - difficulty_);
1147 int numbers[9];
1149 for (j = 0; j < 9; j ++) numbers[j] = j + 1;
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;
1160 for (j = 0; count > 0 && j < 9; j ++) {
1161 t = numbers[j];
1163 for (k = 0; count > 0 && k < 9; k ++) {
1164 cell = grid_cells_[j][k];
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);
1171 count --;
1172 break;
1180 // Return the next available value for a cell...
1182 Sudoku::next_value(SudokuCell *c) {
1183 int j, k, m, n;
1186 for (j = 0; j < 9; j ++) {
1187 for (k = 0; k < 9; k ++)
1188 if (grid_cells_[j][k] == c) break;
1190 if (k < 9) break;
1193 if (j == 9) return 1;
1195 j -= j % 3;
1196 k -= k % 3;
1198 int numbers[9];
1200 memset(numbers, 0, sizeof(numbers));
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;
1208 for (j = 0; j < 9; j ++)
1209 if (!numbers[j]) return j + 1;
1211 return 1;
1215 // Reset widget color to gray...
1216 void
1217 Sudoku::reset_cb(Fl_Widget *widget, void *) {
1218 widget->color(FL_LIGHT3);
1219 widget->redraw();
1221 ((Sudoku *)(widget->window()))->check_game(false);
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);
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);
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;
1245 for (int j = 0; j < 9; j ++)
1246 for (int k = 0; k < 9; k ++) {
1247 SudokuCell *cell = s->grid_cells_[j][k];
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);
1258 if (solved) s->new_game(s->seed_);
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];
1271 sprintf(name, "value%d.%d", j, k);
1272 prefs_.set(name, grid_values_[j][k]);
1274 sprintf(name, "state%d.%d", j, k);
1275 prefs_.set(name, cell->value());
1277 sprintf(name, "readonly%d.%d", j, k);
1278 prefs_.set(name, cell->readonly());
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));
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"
1298 label(titles[difficulty_]);
1302 // Solve the puzzle...
1303 void
1304 Sudoku::solve_cb(Fl_Widget *widget, void *) {
1305 ((Sudoku *)(widget->window()))->solve_game();
1309 // Solve the puzzle...
1310 void
1311 Sudoku::solve_game() {
1312 int j, k;
1314 for (j = 0; j < 9; j ++) {
1315 for (k = 0; k < 9; k ++) {
1316 SudokuCell *cell = grid_cells_[j][k];
1318 cell->value(grid_values_[j][k]);
1319 cell->readonly(1);
1320 cell->color(FL_GRAY);
1323 if (sound_) sound_->play('A' + grid_cells_[j][8]->value() - 1);
1328 // Main entry for game...
1330 main(int argc, char *argv[]) {
1331 Sudoku s;
1333 // Show the game...
1334 s.show(argc, argv);
1336 // Load the previous game...
1337 s.load_game();
1339 // Run until the user quits...
1340 return (Fl::run());
1345 // End of "$Id: sudoku.cxx 7903 2010-11-28 21:06:39Z matt $".