2 // "$Id: sudoku.cxx 7903 2010-11-28 21:06:39Z matt $"
4 // Sudoku game using the Fast Light Tool Kit (FLTK).
6 // Copyright 2005-2010 by Michael Sweet.
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
23 // Please report all bugs and problems on the following page:
25 // http://www.fltk.org/str.php
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>
46 # include "sudokurc.h"
47 #elif !defined(__APPLE__)
48 # include "pixmaps/sudoku.xbm"
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
63 # include <CoreAudio/AudioHardware.h>
66 # include <mmsystem.h>
74 #define GROUP_SIZE 160
78 # define MENU_OFFSET 0
80 # define MENU_OFFSET 25
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!
99 // Private, OS-specific data...
101 AudioDeviceID device
;
102 # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
103 AudioDeviceIOProcID audio_proc_id
;
105 AudioStreamBasicDescription format
;
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
,
118 HGLOBAL header_handle
;
119 LPWAVEHDR header_ptr
;
124 # ifdef HAVE_ALSA_ASOUNDLIB_H
126 # endif // HAVE_ALSA_ASOUNDLIB_H
130 static int frequencies
[9];
131 static short *sample_data
[9];
132 static int sample_size
;
139 void play(char note
);
143 // Sudoku cell class...
144 class SudokuCell
: public Fl_Widget
{
151 SudokuCell(int X
, int Y
, int W
, int H
);
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
]; }
160 for (int i
= 0; i
< 8; i
++) test_value_
[i
] = 0;
163 int value() const { return value_
; }
167 // Sudoku window class...
168 class Sudoku
: public Fl_Double_Window
{
169 Fl_Sys_Menu_Bar
*menubar_
;
172 char grid_values_
[9][9];
173 SudokuCell
*grid_cells_
[9][9];
174 Fl_Group
*grid_groups_
[3][3];
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 *);
188 static void solve_cb(Fl_Widget
*widget
, void *);
190 static Fl_Help_Dialog
*help_dialog_
;
191 static Fl_Preferences prefs_
;
197 void check_game(bool highlight
= true);
199 void new_game(time_t seed
);
200 int next_value(SudokuCell
*c
);
201 void resize(int X
, int Y
, int W
, int H
);
204 void update_helpers();
208 // Sound class globals...
209 int SudokuSound::frequencies
[9] = {
220 short *SudokuSound::sample_data
[9] = { 0 };
221 int SudokuSound::sample_size
= 0;
224 // Initialize the SudokuSound class
225 SudokuSound::SudokuSound() {
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
);
256 if (AudioDeviceAddIOProc(device
, audio_cb
, (void *)this) != noErr
) return;
257 AudioDeviceStart(device
, audio_cb
);
260 sample_size
= (int)format
.mSampleRate
/ 20;
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;
297 # ifdef HAVE_ALSA_ASOUNDLIB_H
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(¶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;
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) {
319 snd_pcm_close(handle
);
323 # endif // HAVE_ALSA_ASOUNDLIB_H
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;
341 *sample_ptr
= (int)(32767 * val
* j
/ attack
);
342 } else if (j
> decay
) {
343 *sample_ptr
= (int)(32767 * val
* (sample_size
- j
+ decay
) /
345 } else *sample_ptr
= (int)(32767 * val
);
347 sample_ptr
[1] = *sample_ptr
;
354 // Cleanup the SudokuSound class
355 SudokuSound::~SudokuSound() {
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
);
362 AudioDeviceStop(device
, audio_cb
);
363 AudioDeviceRemoveIOProc(device
, audio_cb
);
369 waveOutClose(device
);
371 GlobalUnlock(header_handle
);
372 GlobalFree(header_handle
);
374 GlobalUnlock(data_handle
);
375 GlobalFree(data_handle
);
379 # ifdef HAVE_ALSA_ASOUNDLIB_H
381 snd_pcm_drain(handle
);
382 snd_pcm_close(handle
);
384 # endif // HAVE_ALSA_ASOUNDLIB_H
388 for (int i
= 0; i
< 9; i
++) {
389 delete[] sample_data
[i
];
396 // Callback function for writing audio data...
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
,
405 SudokuSound
*ss
= (SudokuSound
*)client_data
;
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;
428 // Play a note for 250ms...
429 void SudokuSound::play(char note
) {
433 // Point to the next note...
434 data
= sample_data
[note
- 'A'];
435 remaining
= sample_size
* 2;
437 // Wait for the sound to complete...
442 memcpy(data_ptr
, sample_data
[note
- 'A'], sample_size
* 4);
444 waveOutWrite(device
, header_ptr
, sizeof(WAVEHDR
));
447 } else Beep(frequencies
[note
- 'A'], 50);
450 # ifdef HAVE_ALSA_ASOUNDLIB_H
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
);
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
,
477 XBell(fl_display
, 100);
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
,
492 // Create a cell widget
493 SudokuCell::SudokuCell(int X
, int Y
, int W
, int H
)
494 : Fl_Widget(X
, Y
, W
, H
, 0) {
502 static Fl_Align align
[8] = {
507 FL_ALIGN_BOTTOM_RIGHT
,
509 FL_ALIGN_BOTTOM_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
);
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...
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
) {
563 if (!readonly() && Fl::event_inside(this)) {
564 if (Fl::event_clicks()) {
565 // 2+ clicks increments/sets value
567 if (value() < 9) value(value() + 1);
569 } else value(((Sudoku
*)window())->next_value(this));
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) {
584 fl_beep(FL_BEEP_ERROR
);
588 if (Fl::event_state() & (FL_SHIFT
| FL_CAPS_LOCK
)) {
591 for (i
= 0; i
< 8; i
++)
592 if (test_value_
[i
] == key
) {
598 for (i
= 0; i
< 8; i
++)
599 if (!test_value_
[i
]) {
600 test_value_
[i
] = key
;
606 for (i
= 0; i
< 7; i
++) test_value_
[i
] = test_value_
[i
+ 1];
607 test_value_
[i
] = key
;
616 } else if (key
== 0 || Fl::event_key() == FL_BackSpace
||
617 Fl::event_key() == FL_Delete
) {
619 fl_beep(FL_BEEP_ERROR
);
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...
641 : Fl_Double_Window(GROUP_SIZE
* 3, GROUP_SIZE
* 3 + MENU_OFFSET
, "Sudoku")
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 },
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
},
662 { "&Help", 0, 0, 0, FL_SUBMENU
},
663 { "&About Sudoku", FL_F
+ 1, help_cb
, 0, 0 },
669 // Setup sound output...
670 prefs_
.get("mute_sound", j
, 0);
674 items
[6].flags
|= FL_MENU_VALUE
;
675 } else sound_
= new SudokuSound();
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
);
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...)
714 icon((char *)LoadIcon(fl_display
, MAKEINTRESOURCE(IDI_ICON
)));
715 #elif !defined(__APPLE__)
717 icon((char *)XCreateBitmapFromData(fl_display
, DefaultRootWindow(fl_display
),
718 (char *)sudoku_bits
, sudoku_width
,
722 // Catch window close events...
725 // Make the window resizable...
727 size_range(3 * GROUP_SIZE
, 3 * GROUP_SIZE
+ MENU_OFFSET
, 0, 0, 5, 5, 1);
729 // Restore the previous window dimensions...
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
);
744 // Destroy the sudoku window...
746 if (sound_
) delete sound_
;
750 // Check for a solution to the game...
752 Sudoku::check_cb(Fl_Widget
*widget
, void *) {
753 ((Sudoku
*)(widget
->window()))->check_game();
757 // Check if the user has correctly solved the game...
759 Sudoku::check_game(bool highlight
) {
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;
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;
780 cell
->color(FL_YELLOW
);
785 } else if (highlight
) {
786 cell
->color(FL_LIGHT3
);
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;
804 for (jjj
= 0; jjj
< 3; jjj
++) {
807 for (kkk
= 0; kkk
< 3; kkk
++)
808 if (jj
!= jjj
&& kk
!= kkk
&&
809 grid_cells_
[j
+ jjj
][k
+ kkk
]->value() == val
) break;
816 cell
->color(FL_YELLOW
);
824 if (!empty
&& correct
) {
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
);
833 if (sound_
) sound_
->play('A' + grid_cells_
[j
][8]->value() - 1);
839 // Close the window, saving the game first...
841 Sudoku::close_cb(Fl_Widget
*widget
, void *) {
842 Sudoku
*s
= (Sudoku
*)(widget
->window() ? widget
->window() : widget
);
847 if (help_dialog_
) help_dialog_
->hide();
851 // Set the level of difficulty...
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_
);
864 // Display a message about the higher difficulty levels for the
865 // Sudoku zealots of the world...
868 prefs_
.get("difficulty_warning", val
, 0);
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
885 Sudoku::update_helpers_cb(Fl_Widget
*widget
, void *) {
886 Sudoku
*s
= (Sudoku
*)(widget
->window() ? widget
->window() : widget
);
891 Sudoku::update_helpers() {
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
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();
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();
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();
934 // transfer our findings to the markers
935 for (m
= 1, k
= 0; m
<= 9; m
++) {
937 dst_cell
->test_value(m
, k
++);
943 // Show the on-line help...
945 Sudoku::help_cb(Fl_Widget
*, void *) {
947 help_dialog_
= new Fl_Help_Dialog();
952 "<TITLE>Sudoku Help</TITLE>\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"
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"
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"
997 help_dialog_
->show();
1001 // Load the game from saved preferences...
1003 Sudoku::load_game() {
1004 // Load the current values and state of each grid...
1005 memset(grid_values_
, 0, sizeof(grid_values_
));
1009 for (int j
= 0; j
< 9; j
++)
1010 for (int k
= 0; k
< 9; k
++) {
1014 SudokuCell
*cell
= grid_cells_
[j
][k
];
1016 sprintf(name
, "value%d.%d", j
, k
);
1017 if (!prefs_
.get(name
, val
, 0)) {
1019 grid_values_
[0][0] = 0;
1023 grid_values_
[j
][k
] = val
;
1025 sprintf(name
, "state%d.%d", j
, k
);
1026 prefs_
.get(name
, val
, 0);
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
);
1035 cell
->color(FL_LIGHT3
);
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...
1055 Sudoku::mute_cb(Fl_Widget
*widget
, void *) {
1056 Sudoku
*s
= (Sudoku
*)(widget
->window() ? widget
->window() : widget
);
1061 prefs_
.set("mute_sound", 1);
1063 s
->sound_
= new SudokuSound();
1064 prefs_
.set("mute_sound", 0);
1069 // Create a new game...
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",
1080 s
->new_game(time(NULL
));
1084 // Create a new game...
1086 Sudoku::new_game(time_t seed
) {
1087 int j
, k
, m
, n
, t
, count
;
1090 // Generate a new (valid) Sudoku grid...
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
]) {
1105 for (mm
= 0; mm
< m
; mm
++)
1106 if (grid_values_
[mm
][n
] == t
) break;
1108 if (mm
< m
) continue;
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
;
1123 // Unable to find a valid puzzle so far, so start over...
1126 memset(grid_values_
, 0, sizeof(grid_values_
));
1132 // Start by making all cells editable
1135 for (j
= 0; j
< 9; j
++)
1136 for (k
= 0; k
< 9; k
++) {
1137 cell
= grid_cells_
[j
][k
];
1141 cell
->color(FL_LIGHT3
);
1145 count
= 11 * (5 - difficulty_
);
1149 for (j
= 0; j
< 9; j
++) numbers
[j
] = j
+ 1;
1152 for (j
= 0; j
< 20; j
++) {
1156 numbers
[k
] = numbers
[m
];
1160 for (j
= 0; count
> 0 && j
< 9; 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
]);
1169 cell
->color(FL_GRAY
);
1180 // Return the next available value for a cell...
1182 Sudoku::next_value(SudokuCell
*c
) {
1186 for (j
= 0; j
< 9; j
++) {
1187 for (k
= 0; k
< 9; k
++)
1188 if (grid_cells_
[j
][k
] == c
) break;
1193 if (j
== 9) return 1;
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;
1215 // Reset widget color to gray...
1217 Sudoku::reset_cb(Fl_Widget
*widget
, void *) {
1218 widget
->color(FL_LIGHT3
);
1221 ((Sudoku
*)(widget
->window()))->check_game(false);
1225 // Resize the window...
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...
1234 prefs_
.set("width", W
);
1235 prefs_
.set("height", H
);
1239 // Restart game from beginning...
1241 Sudoku::restart_cb(Fl_Widget
*widget
, void *) {
1242 Sudoku
*s
= (Sudoku
*)(widget
->window());
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()) {
1251 int v
= cell
->value();
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...
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
++) {
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...
1290 Sudoku::set_title() {
1291 static const char * const titles
[] = {
1295 "Sudoku - Impossible"
1298 label(titles
[difficulty_
]);
1302 // Solve the puzzle...
1304 Sudoku::solve_cb(Fl_Widget
*widget
, void *) {
1305 ((Sudoku
*)(widget
->window()))->solve_game();
1309 // Solve the puzzle...
1311 Sudoku::solve_game() {
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
]);
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
[]) {
1336 // Load the previous game...
1339 // Run until the user quits...
1345 // End of "$Id: sudoku.cxx 7903 2010-11-28 21:06:39Z matt $".