mirror of https://github.com/fltk/fltk
1334 lines
34 KiB
C++
1334 lines
34 KiB
C++
//
|
|
// Sudoku game using the Fast Light Tool Kit (FLTK).
|
|
//
|
|
// Copyright 2005-2018 by Michael Sweet.
|
|
//
|
|
// This library is free software. Distribution and use rights are outlined in
|
|
// the file "COPYING" which should have been included with this file. If this
|
|
// file is missing or damaged, see the license at:
|
|
//
|
|
// https://www.fltk.org/COPYING.php
|
|
//
|
|
// Please see the following page on how to report bugs and issues:
|
|
//
|
|
// https://www.fltk.org/bugs.php
|
|
//
|
|
|
|
#include <FL/Fl.H>
|
|
#include <FL/Enumerations.H>
|
|
#include <FL/Fl_Double_Window.H>
|
|
#include <FL/Fl_Button.H>
|
|
#include <FL/Fl_Group.H>
|
|
#include <FL/fl_ask.H>
|
|
#include <FL/fl_draw.H>
|
|
#include <FL/Fl_Help_Dialog.H>
|
|
#include <FL/Fl_Preferences.H>
|
|
#include <FL/Fl_Sys_Menu_Bar.H>
|
|
#include <FL/platform.H>
|
|
#include <FL/Fl_Image_Surface.H>
|
|
#include <FL/Fl_Bitmap.H>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <FL/math.h>
|
|
|
|
#include "pixmaps/sudoku.xbm"
|
|
|
|
// Audio headers...
|
|
#include <config.h>
|
|
|
|
#ifndef _WIN32
|
|
# include <unistd.h>
|
|
#endif // !_WIN32
|
|
|
|
#ifdef HAVE_ALSA_ASOUNDLIB_H
|
|
# define ALSA_PCM_NEW_HW_PARAMS_API
|
|
# include <alsa/asoundlib.h>
|
|
#endif // HAVE_ALSA_ASOUNDLIB_H
|
|
#ifdef __APPLE__
|
|
# include <CoreAudio/AudioHardware.h>
|
|
#endif // __APPLE__
|
|
#ifdef _WIN32
|
|
# include <mmsystem.h>
|
|
#endif // _WIN32
|
|
|
|
|
|
//
|
|
// Default sizes...
|
|
//
|
|
|
|
#define GROUP_SIZE 160
|
|
#define CELL_SIZE 50
|
|
#define CELL_OFFSET 5
|
|
#ifdef __APPLE__
|
|
# define MENU_OFFSET 0
|
|
#else
|
|
# define MENU_OFFSET 25
|
|
#endif // __APPLE__
|
|
|
|
// Sound class for Sudoku...
|
|
//
|
|
// There are MANY ways to implement sound in a FLTK application.
|
|
// The approach we are using here is to conditionally compile OS-
|
|
// specific code into the application - CoreAudio for MacOS X, the
|
|
// standard Win32 API stuff for Windows, ALSA or X11 for Linux, and
|
|
// X11 for all others. We have to support ALSA on Linux because the
|
|
// current Xorg releases no longer support XBell() or the PC speaker.
|
|
//
|
|
// There are several good cross-platform audio libraries we could also
|
|
// use, such as OpenAL, PortAudio, and SDL, however they were not chosen
|
|
// for this application because of our limited use of sound.
|
|
//
|
|
// Many thanks to Ian MacArthur who provided sample code that led to
|
|
// the CoreAudio implementation you see here!
|
|
class SudokuSound {
|
|
// Private, OS-specific data...
|
|
#ifdef __APPLE__
|
|
AudioDeviceID device;
|
|
#ifndef MAC_OS_X_VERSION_10_5
|
|
#define MAC_OS_X_VERSION_10_5 1050
|
|
#endif
|
|
# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
|
AudioDeviceIOProcID audio_proc_id;
|
|
# endif
|
|
AudioStreamBasicDescription format;
|
|
short *data;
|
|
int remaining;
|
|
|
|
static OSStatus audio_cb(AudioDeviceID device,
|
|
const AudioTimeStamp *current_time,
|
|
const AudioBufferList *data_in,
|
|
const AudioTimeStamp *time_in,
|
|
AudioBufferList *data_out,
|
|
const AudioTimeStamp *time_out,
|
|
void *client_data);
|
|
#elif defined(_WIN32)
|
|
HWAVEOUT device;
|
|
HGLOBAL header_handle;
|
|
LPWAVEHDR header_ptr;
|
|
HGLOBAL data_handle;
|
|
LPSTR data_ptr;
|
|
|
|
#else
|
|
# ifdef HAVE_ALSA_ASOUNDLIB_H
|
|
snd_pcm_t *handle;
|
|
# endif // HAVE_ALSA_ASOUNDLIB_H
|
|
#endif // __APPLE__
|
|
|
|
// Common data...
|
|
static int frequencies[9];
|
|
static short *sample_data[9];
|
|
static int sample_size;
|
|
|
|
public:
|
|
|
|
SudokuSound();
|
|
~SudokuSound();
|
|
|
|
void play(char note);
|
|
};
|
|
|
|
|
|
// Sudoku cell class...
|
|
class SudokuCell : public Fl_Widget {
|
|
bool readonly_;
|
|
int value_;
|
|
int test_value_[9];
|
|
|
|
public:
|
|
|
|
SudokuCell(int X, int Y, int W, int H);
|
|
void draw();
|
|
int handle(int event);
|
|
void readonly(bool r) { readonly_ = r; redraw(); }
|
|
bool readonly() const { return readonly_; }
|
|
void test_value(int v, int n) { test_value_[n] = v; redraw(); }
|
|
int test_value(int n) const { return test_value_[n]; }
|
|
void value(int v) {
|
|
value_ = v;
|
|
for (int i = 0; i < 8; i ++) test_value_[i] = 0;
|
|
redraw();
|
|
}
|
|
int value() const { return value_; }
|
|
};
|
|
|
|
|
|
// Sudoku window class...
|
|
class Sudoku : public Fl_Double_Window {
|
|
Fl_Sys_Menu_Bar *menubar_;
|
|
Fl_Group *grid_;
|
|
time_t seed_;
|
|
char grid_values_[9][9];
|
|
SudokuCell *grid_cells_[9][9];
|
|
Fl_Group *grid_groups_[3][3];
|
|
int difficulty_;
|
|
SudokuSound *sound_;
|
|
|
|
static void check_cb(Fl_Widget *widget, void *);
|
|
static void close_cb(Fl_Widget *widget, void *);
|
|
static void diff_cb(Fl_Widget *widget, void *d);
|
|
static void update_helpers_cb(Fl_Widget *, void *);
|
|
static void help_cb(Fl_Widget *, void *);
|
|
static void mute_cb(Fl_Widget *widget, void *);
|
|
static void new_cb(Fl_Widget *widget, void *);
|
|
static void reset_cb(Fl_Widget *widget, void *);
|
|
static void restart_cb(Fl_Widget *widget, void *);
|
|
void set_title();
|
|
static void solve_cb(Fl_Widget *widget, void *);
|
|
|
|
static Fl_Help_Dialog *help_dialog_;
|
|
static Fl_Preferences prefs_;
|
|
public:
|
|
|
|
Sudoku();
|
|
~Sudoku();
|
|
|
|
void check_game(bool highlight = true);
|
|
void load_game();
|
|
void new_game(time_t seed);
|
|
int next_value(SudokuCell *c);
|
|
void resize(int X, int Y, int W, int H);
|
|
void save_game();
|
|
void solve_game();
|
|
void update_helpers();
|
|
};
|
|
|
|
|
|
// Sound class globals...
|
|
int SudokuSound::frequencies[9] = {
|
|
880, // A(5)
|
|
988, // B(5)
|
|
1046, // C(5)
|
|
1174, // D(5)
|
|
1318, // E(5)
|
|
1396, // F(5)
|
|
1568, // G(5)
|
|
1760, // H (A6)
|
|
1976 // I (B6)
|
|
};
|
|
short *SudokuSound::sample_data[9] = { 0 };
|
|
int SudokuSound::sample_size = 0;
|
|
|
|
|
|
// Initialize the SudokuSound class
|
|
SudokuSound::SudokuSound() {
|
|
sample_size = 0;
|
|
|
|
#ifdef __APPLE__
|
|
remaining = 0;
|
|
|
|
UInt32 size = sizeof(device);
|
|
|
|
if (AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,
|
|
&size, (void *)&device) != noErr) return;
|
|
|
|
size = sizeof(format);
|
|
if (AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyStreamFormat,
|
|
&size, &format) != noErr) return;
|
|
|
|
// Set up a format we like...
|
|
format.mSampleRate = 44100.0; // 44.1kHz
|
|
format.mChannelsPerFrame = 2; // stereo
|
|
|
|
if (AudioDeviceSetProperty(device, NULL, 0, false,
|
|
kAudioDevicePropertyStreamFormat,
|
|
sizeof(format), &format) != noErr) return;
|
|
|
|
// Check we got linear pcm - what to do if we did not ???
|
|
if (format.mFormatID != kAudioFormatLinearPCM) return;
|
|
|
|
// Attach the callback and start the device
|
|
# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
|
if (AudioDeviceCreateIOProcID(device, audio_cb, (void *)this, &audio_proc_id) != noErr) return;
|
|
AudioDeviceStart(device, audio_proc_id);
|
|
# else
|
|
if (AudioDeviceAddIOProc(device, audio_cb, (void *)this) != noErr) return;
|
|
AudioDeviceStart(device, audio_cb);
|
|
# endif
|
|
|
|
sample_size = (int)format.mSampleRate / 20;
|
|
|
|
#elif defined(_WIN32)
|
|
WAVEFORMATEX format;
|
|
|
|
memset(&format, 0, sizeof(format));
|
|
format.cbSize = sizeof(format);
|
|
format.wFormatTag = WAVE_FORMAT_PCM;
|
|
format.nChannels = 2;
|
|
format.nSamplesPerSec = 44100;
|
|
format.nAvgBytesPerSec = 44100 * 4;
|
|
format.nBlockAlign = 4;
|
|
format.wBitsPerSample = 16;
|
|
|
|
data_handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, format.nSamplesPerSec / 5);
|
|
if (!data_handle) return;
|
|
|
|
data_ptr = (LPSTR)GlobalLock(data_handle);
|
|
|
|
header_handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR));
|
|
if (!header_handle) return;
|
|
|
|
header_ptr = (WAVEHDR *)GlobalLock(header_handle);
|
|
|
|
header_ptr->lpData = data_ptr;
|
|
header_ptr->dwBufferLength = format.nSamplesPerSec / 5;
|
|
header_ptr->dwFlags = 0;
|
|
header_ptr->dwLoops = 0;
|
|
|
|
if (waveOutOpen(&device, WAVE_MAPPER, &format, 0, 0, WAVE_ALLOWSYNC)
|
|
!= MMSYSERR_NOERROR) return;
|
|
|
|
waveOutPrepareHeader(device, header_ptr, sizeof(WAVEHDR));
|
|
|
|
sample_size = 44100 / 20;
|
|
|
|
#else
|
|
# ifdef HAVE_ALSA_ASOUNDLIB_H
|
|
handle = NULL;
|
|
|
|
if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) >= 0) {
|
|
// Initialize PCM sound stuff...
|
|
snd_pcm_hw_params_t *params;
|
|
|
|
snd_pcm_hw_params_alloca(¶ms);
|
|
snd_pcm_hw_params_any(handle, params);
|
|
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
|
|
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16);
|
|
snd_pcm_hw_params_set_channels(handle, params, 2);
|
|
unsigned rate = 44100;
|
|
int dir;
|
|
snd_pcm_hw_params_set_rate_near(handle, params, &rate, &dir);
|
|
snd_pcm_uframes_t period = (int)rate / 4;
|
|
snd_pcm_hw_params_set_period_size_near(handle, params, &period, &dir);
|
|
|
|
sample_size = rate / 20;
|
|
|
|
if (snd_pcm_hw_params(handle, params) < 0) {
|
|
sample_size = 0;
|
|
snd_pcm_close(handle);
|
|
handle = NULL;
|
|
}
|
|
}
|
|
# endif // HAVE_ALSA_ASOUNDLIB_H
|
|
#endif // __APPLE__
|
|
|
|
if (sample_size) {
|
|
// Make each of the notes using a combination of sine and sawtooth waves
|
|
int attack = sample_size / 10;
|
|
int decay = 4 * sample_size / 5;
|
|
|
|
for (int i = 0; i < 9; i ++) {
|
|
sample_data[i] = new short[2 * sample_size];
|
|
|
|
short *sample_ptr = sample_data[i];
|
|
|
|
for (int j = 0; j < sample_size; j ++, sample_ptr += 2) {
|
|
double theta = 0.05 * frequencies[i] * j / sample_size;
|
|
double val = 0.5 * sin(2.0 * M_PI * theta) + theta - (int)theta - 0.5;
|
|
|
|
if (j < attack) {
|
|
*sample_ptr = (int)(32767 * val * j / attack);
|
|
} else if (j > decay) {
|
|
*sample_ptr = (int)(32767 * val * (sample_size - j + decay) /
|
|
sample_size);
|
|
} else *sample_ptr = (int)(32767 * val);
|
|
|
|
sample_ptr[1] = *sample_ptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Cleanup the SudokuSound class
|
|
SudokuSound::~SudokuSound() {
|
|
#ifdef __APPLE__
|
|
if (sample_size) {
|
|
# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
|
AudioDeviceStop(device, audio_proc_id);
|
|
AudioDeviceDestroyIOProcID(device, audio_proc_id);
|
|
# else
|
|
AudioDeviceStop(device, audio_cb);
|
|
AudioDeviceRemoveIOProc(device, audio_cb);
|
|
# endif
|
|
}
|
|
|
|
#elif defined(_WIN32)
|
|
if (sample_size) {
|
|
waveOutClose(device);
|
|
|
|
GlobalUnlock(header_handle);
|
|
GlobalFree(header_handle);
|
|
|
|
GlobalUnlock(data_handle);
|
|
GlobalFree(data_handle);
|
|
}
|
|
|
|
#else
|
|
# ifdef HAVE_ALSA_ASOUNDLIB_H
|
|
if (handle) {
|
|
snd_pcm_drain(handle);
|
|
snd_pcm_close(handle);
|
|
}
|
|
# endif // HAVE_ALSA_ASOUNDLIB_H
|
|
#endif // __APPLE__
|
|
|
|
if (sample_size) {
|
|
for (int i = 0; i < 9; i ++) {
|
|
delete[] sample_data[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef __APPLE__
|
|
// Callback function for writing audio data...
|
|
OSStatus
|
|
SudokuSound::audio_cb(AudioDeviceID device,
|
|
const AudioTimeStamp *current_time,
|
|
const AudioBufferList *data_in,
|
|
const AudioTimeStamp *time_in,
|
|
AudioBufferList *data_out,
|
|
const AudioTimeStamp *time_out,
|
|
void *client_data) {
|
|
SudokuSound *ss = (SudokuSound *)client_data;
|
|
int count;
|
|
float *buffer;
|
|
|
|
if (!ss->remaining) return noErr;
|
|
|
|
for (count = data_out->mBuffers[0].mDataByteSize / sizeof(float),
|
|
buffer = (float*) data_out->mBuffers[0].mData;
|
|
ss->remaining > 0 && count > 0;
|
|
count --, ss->data ++, ss->remaining --) {
|
|
*buffer++ = *(ss->data) / 32767.0;
|
|
}
|
|
|
|
while (count > 0) {
|
|
*buffer++ = 0.0;
|
|
count --;
|
|
}
|
|
|
|
return noErr;
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
#define NOTE_DURATION 50
|
|
|
|
// Play a note for <NOTE_DURATION> ms...
|
|
void SudokuSound::play(char note) {
|
|
Fl::check();
|
|
|
|
#ifdef __APPLE__
|
|
// Point to the next note...
|
|
data = sample_data[note - 'A'];
|
|
remaining = sample_size * 2;
|
|
|
|
// Wait for the sound to complete...
|
|
usleep(NOTE_DURATION*1000);
|
|
|
|
#elif defined(_WIN32)
|
|
if (sample_size) {
|
|
memcpy(data_ptr, sample_data[note - 'A'], sample_size * 4);
|
|
|
|
waveOutWrite(device, header_ptr, sizeof(WAVEHDR));
|
|
|
|
Sleep(NOTE_DURATION);
|
|
} else Beep(frequencies[note - 'A'], NOTE_DURATION);
|
|
|
|
#else
|
|
# ifdef HAVE_ALSA_ASOUNDLIB_H
|
|
if (handle) {
|
|
// Use ALSA to play the sound...
|
|
if (snd_pcm_writei(handle, sample_data[note - 'A'], sample_size) < 0) {
|
|
snd_pcm_prepare(handle);
|
|
snd_pcm_writei(handle, sample_data[note - 'A'], sample_size);
|
|
}
|
|
usleep(NOTE_DURATION*1000);
|
|
return;
|
|
}
|
|
# endif // HAVE_ALSA_ASOUNDLIB_H
|
|
|
|
// Just use standard X11 stuff...
|
|
XKeyboardState state;
|
|
XKeyboardControl control;
|
|
|
|
// Get original pitch and duration...
|
|
XGetKeyboardControl(fl_display, &state);
|
|
|
|
// Sound a tone for the given note...
|
|
control.bell_percent = 100;
|
|
control.bell_pitch = frequencies[note - 'A'];
|
|
control.bell_duration = NOTE_DURATION;
|
|
|
|
XChangeKeyboardControl(fl_display,
|
|
KBBellPercent | KBBellPitch | KBBellDuration,
|
|
&control);
|
|
XBell(fl_display, 100);
|
|
XFlush(fl_display);
|
|
|
|
// Restore original pitch and duration...
|
|
control.bell_percent = state.bell_percent;
|
|
control.bell_pitch = state.bell_pitch;
|
|
control.bell_duration = state.bell_duration;
|
|
|
|
XChangeKeyboardControl(fl_display,
|
|
KBBellPercent | KBBellPitch | KBBellDuration,
|
|
&control);
|
|
#endif // __APPLE__
|
|
}
|
|
|
|
|
|
// Create a cell widget
|
|
SudokuCell::SudokuCell(int X, int Y, int W, int H)
|
|
: Fl_Widget(X, Y, W, H, 0) {
|
|
value(0);
|
|
}
|
|
|
|
|
|
// Draw cell
|
|
void
|
|
SudokuCell::draw() {
|
|
static Fl_Align align[8] = {
|
|
FL_ALIGN_TOP_LEFT,
|
|
FL_ALIGN_TOP,
|
|
FL_ALIGN_TOP_RIGHT,
|
|
FL_ALIGN_RIGHT,
|
|
FL_ALIGN_BOTTOM_RIGHT,
|
|
FL_ALIGN_BOTTOM,
|
|
FL_ALIGN_BOTTOM_LEFT,
|
|
FL_ALIGN_LEFT
|
|
};
|
|
|
|
|
|
// Draw the cell box...
|
|
if (readonly()) fl_draw_box(FL_UP_BOX, x(), y(), w(), h(), color());
|
|
else fl_draw_box(FL_DOWN_BOX, x(), y(), w(), h(), color());
|
|
|
|
// Draw the cell background...
|
|
if (Fl::focus() == this) {
|
|
Fl_Color c = fl_color_average(FL_SELECTION_COLOR, color(), 0.5f);
|
|
fl_color(c);
|
|
fl_rectf(x() + 4, y() + 4, w() - 8, h() - 8);
|
|
fl_color(fl_contrast(labelcolor(), c));
|
|
} else fl_color(labelcolor());
|
|
|
|
// Draw the cell value...
|
|
char s[2];
|
|
|
|
s[1] = '\0';
|
|
|
|
if (value_) {
|
|
s[0] = value_ + '0';
|
|
|
|
fl_font(FL_HELVETICA_BOLD, h() - 10);
|
|
fl_draw(s, x(), y(), w(), h(), FL_ALIGN_CENTER);
|
|
}
|
|
|
|
fl_font(FL_HELVETICA_BOLD, h() / 5);
|
|
|
|
for (int i = 0; i < 8; i ++) {
|
|
if (test_value_[i]) {
|
|
s[0] = test_value_[i] + '0';
|
|
fl_draw(s, x() + 5, y() + 5, w() - 10, h() - 10, align[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Handle events in cell
|
|
int
|
|
SudokuCell::handle(int event) {
|
|
switch (event) {
|
|
case FL_FOCUS :
|
|
Fl::focus(this);
|
|
redraw();
|
|
return 1;
|
|
|
|
case FL_UNFOCUS :
|
|
redraw();
|
|
return 1;
|
|
|
|
case FL_PUSH :
|
|
if (!readonly() && Fl::event_inside(this)) {
|
|
if (Fl::event_clicks()) {
|
|
// 2+ clicks increments/sets value
|
|
if (value()) {
|
|
if (value() < 9) value(value() + 1);
|
|
else value(1);
|
|
} else value(((Sudoku *)window())->next_value(this));
|
|
}
|
|
|
|
Fl::focus(this);
|
|
redraw();
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
case FL_KEYDOWN :
|
|
if (Fl::event_state() & FL_CTRL) break;
|
|
int key = Fl::event_key() - '0';
|
|
if (key < 0 || key > 9) key = Fl::event_key() - FL_KP - '0';
|
|
if (key > 0 && key <= 9) {
|
|
if (readonly()) {
|
|
fl_beep(FL_BEEP_ERROR);
|
|
return 1;
|
|
}
|
|
|
|
if (Fl::event_state() & (FL_SHIFT | FL_CAPS_LOCK)) {
|
|
int i;
|
|
|
|
for (i = 0; i < 8; i ++)
|
|
if (test_value_[i] == key) {
|
|
test_value_[i] = 0;
|
|
break;
|
|
}
|
|
|
|
if (i >= 8) {
|
|
for (i = 0; i < 8; i ++)
|
|
if (!test_value_[i]) {
|
|
test_value_[i] = key;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i >= 8) {
|
|
for (i = 0; i < 7; i ++) test_value_[i] = test_value_[i + 1];
|
|
test_value_[i] = key;
|
|
}
|
|
|
|
redraw();
|
|
} else {
|
|
value(key);
|
|
do_callback();
|
|
}
|
|
return 1;
|
|
} else if (key == 0 || Fl::event_key() == FL_BackSpace ||
|
|
Fl::event_key() == FL_Delete) {
|
|
if (readonly()) {
|
|
fl_beep(FL_BEEP_ERROR);
|
|
return 1;
|
|
}
|
|
|
|
value(0);
|
|
do_callback();
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return Fl_Widget::handle(event);
|
|
}
|
|
|
|
|
|
// Sudoku class globals...
|
|
Fl_Help_Dialog *Sudoku::help_dialog_ = (Fl_Help_Dialog *)0;
|
|
Fl_Preferences Sudoku::prefs_(Fl_Preferences::USER, "fltk.org", "sudoku");
|
|
|
|
|
|
// Create a Sudoku game window...
|
|
Sudoku::Sudoku()
|
|
: Fl_Double_Window(GROUP_SIZE * 3, GROUP_SIZE * 3 + MENU_OFFSET, "Sudoku")
|
|
{
|
|
int j, k;
|
|
Fl_Group *g;
|
|
SudokuCell *cell;
|
|
static Fl_Menu_Item items[] = {
|
|
{ "&Game", 0, 0, 0, FL_SUBMENU },
|
|
{ "&New Game", FL_COMMAND | 'n', new_cb, 0, FL_MENU_DIVIDER },
|
|
{ "&Check Game", FL_COMMAND | 'c', check_cb, 0, 0 },
|
|
{ "&Restart Game", FL_COMMAND | 'r', restart_cb, 0, 0 },
|
|
{ "&Solve Game", FL_COMMAND | 's', solve_cb, 0, FL_MENU_DIVIDER },
|
|
{ "&Update Helpers", 0, update_helpers_cb, 0, 0 },
|
|
{ "&Mute Sound", FL_COMMAND | 'm', mute_cb, 0, FL_MENU_TOGGLE | FL_MENU_DIVIDER },
|
|
{ "&Quit", FL_COMMAND | 'q', close_cb, 0, 0 },
|
|
{ 0 },
|
|
{ "&Difficulty", 0, 0, 0, FL_SUBMENU },
|
|
{ "&Easy", 0, diff_cb, (void *)"0", FL_MENU_RADIO },
|
|
{ "&Medium", 0, diff_cb, (void *)"1", FL_MENU_RADIO },
|
|
{ "&Hard", 0, diff_cb, (void *)"2", FL_MENU_RADIO },
|
|
{ "&Impossible", 0, diff_cb, (void *)"3", FL_MENU_RADIO },
|
|
{ 0 },
|
|
{ "&Help", 0, 0, 0, FL_SUBMENU },
|
|
{ "&About Sudoku", FL_F + 1, help_cb, 0, 0 },
|
|
{ 0 },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
// Setup sound output...
|
|
prefs_.get("mute_sound", j, 0);
|
|
if (j) {
|
|
// Mute sound?
|
|
sound_ = NULL;
|
|
items[6].flags |= FL_MENU_VALUE;
|
|
} else sound_ = new SudokuSound();
|
|
|
|
// Menubar...
|
|
prefs_.get("difficulty", difficulty_, 0);
|
|
if (difficulty_ < 0 || difficulty_ > 3) difficulty_ = 0;
|
|
|
|
items[10 + difficulty_].flags |= FL_MENU_VALUE;
|
|
|
|
menubar_ = new Fl_Sys_Menu_Bar(0, 0, 3 * GROUP_SIZE, 25);
|
|
menubar_->menu(items);
|
|
|
|
// Create the grids...
|
|
grid_ = new Fl_Group(0, MENU_OFFSET, 3 * GROUP_SIZE, 3 * GROUP_SIZE);
|
|
|
|
for (j = 0; j < 3; j ++)
|
|
for (k = 0; k < 3; k ++) {
|
|
g = new Fl_Group(k * GROUP_SIZE, j * GROUP_SIZE + MENU_OFFSET,
|
|
GROUP_SIZE, GROUP_SIZE);
|
|
g->box(FL_BORDER_BOX);
|
|
if ((int)(j == 1) ^ (int)(k == 1)) g->color(FL_DARK3);
|
|
else g->color(FL_DARK2);
|
|
g->end();
|
|
|
|
grid_groups_[j][k] = g;
|
|
}
|
|
|
|
for (j = 0; j < 9; j ++)
|
|
for (k = 0; k < 9; k ++) {
|
|
cell = new SudokuCell(k * CELL_SIZE + CELL_OFFSET +
|
|
(k / 3) * (GROUP_SIZE - 3 * CELL_SIZE),
|
|
j * CELL_SIZE + CELL_OFFSET + MENU_OFFSET +
|
|
(j / 3) * (GROUP_SIZE - 3 * CELL_SIZE),
|
|
CELL_SIZE, CELL_SIZE);
|
|
cell->callback(reset_cb);
|
|
grid_cells_[j][k] = cell;
|
|
}
|
|
|
|
// Set icon for window
|
|
Fl_Bitmap bm(sudoku_bits, sudoku_width, sudoku_height);
|
|
Fl_Image_Surface surf(sudoku_width, sudoku_height, 1);
|
|
Fl_Surface_Device::push_current(&surf);
|
|
fl_color(FL_WHITE);
|
|
fl_rectf(0, 0, sudoku_width, sudoku_height);
|
|
fl_color(FL_BLACK);
|
|
bm.draw(0, 0);
|
|
Fl_Surface_Device::pop_current();
|
|
icon(surf.image());
|
|
|
|
// Catch window close events...
|
|
callback(close_cb);
|
|
|
|
// Make the window resizable...
|
|
resizable(grid_);
|
|
size_range(3 * GROUP_SIZE, 3 * GROUP_SIZE + MENU_OFFSET, 0, 0, 5, 5, 1);
|
|
|
|
// Restore the previous window dimensions...
|
|
int X, Y, W, H;
|
|
|
|
if (prefs_.get("x", X, -1)) {
|
|
prefs_.get("y", Y, -1);
|
|
prefs_.get("width", W, 3 * GROUP_SIZE);
|
|
prefs_.get("height", H, 3 * GROUP_SIZE + MENU_OFFSET);
|
|
|
|
resize(X, Y, W, H);
|
|
}
|
|
|
|
set_title();
|
|
}
|
|
|
|
|
|
// Destroy the sudoku window...
|
|
Sudoku::~Sudoku() {
|
|
if (sound_) delete sound_;
|
|
}
|
|
|
|
|
|
// Check for a solution to the game...
|
|
void
|
|
Sudoku::check_cb(Fl_Widget *widget, void *) {
|
|
((Sudoku *)(widget->window()))->check_game();
|
|
}
|
|
|
|
|
|
// Check if the user has correctly solved the game...
|
|
void
|
|
Sudoku::check_game(bool highlight) {
|
|
bool empty = false;
|
|
bool correct = true;
|
|
int j, k, m;
|
|
|
|
// Check the game for right/wrong answers...
|
|
for (j = 0; j < 9; j ++)
|
|
for (k = 0; k < 9; k ++) {
|
|
SudokuCell *cell = grid_cells_[j][k];
|
|
int val = cell->value();
|
|
|
|
if (cell->readonly()) continue;
|
|
|
|
if (!val) empty = true;
|
|
else {
|
|
for (m = 0; m < 9; m ++)
|
|
if ((j != m && grid_cells_[m][k]->value() == val) ||
|
|
(k != m && grid_cells_[j][m]->value() == val)) break;
|
|
|
|
if (m < 9) {
|
|
if (highlight) {
|
|
cell->color(FL_YELLOW);
|
|
cell->redraw();
|
|
}
|
|
|
|
correct = false;
|
|
} else if (highlight) {
|
|
cell->color(FL_LIGHT3);
|
|
cell->redraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check subgrids for duplicate numbers...
|
|
for (j = 0; j < 9; j += 3)
|
|
for (k = 0; k < 9; k += 3)
|
|
for (int jj = 0; jj < 3; jj ++)
|
|
for (int kk = 0; kk < 3; kk ++) {
|
|
SudokuCell *cell = grid_cells_[j + jj][k + kk];
|
|
int val = cell->value();
|
|
|
|
if (cell->readonly() || !val) continue;
|
|
|
|
int jjj;
|
|
|
|
for (jjj = 0; jjj < 3; jjj ++) {
|
|
int kkk;
|
|
|
|
for (kkk = 0; kkk < 3; kkk ++)
|
|
if (jj != jjj && kk != kkk &&
|
|
grid_cells_[j + jjj][k + kkk]->value() == val) break;
|
|
|
|
if (kkk < 3) break;
|
|
}
|
|
|
|
if (jjj < 3) {
|
|
if (highlight) {
|
|
cell->color(FL_YELLOW);
|
|
cell->redraw();
|
|
}
|
|
|
|
correct = false;
|
|
}
|
|
}
|
|
|
|
if (!empty && correct) {
|
|
// Success!
|
|
for (j = 0; j < 9; j ++) {
|
|
for (k = 0; k < 9; k ++) {
|
|
SudokuCell *cell = grid_cells_[j][k];
|
|
cell->color(FL_GREEN);
|
|
cell->readonly(1);
|
|
}
|
|
|
|
if (sound_) sound_->play('A' + grid_cells_[j][8]->value() - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Close the window, saving the game first...
|
|
void
|
|
Sudoku::close_cb(Fl_Widget *widget, void *) {
|
|
Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget);
|
|
|
|
s->save_game();
|
|
s->hide();
|
|
|
|
if (help_dialog_) help_dialog_->hide();
|
|
}
|
|
|
|
|
|
// Set the level of difficulty...
|
|
void
|
|
Sudoku::diff_cb(Fl_Widget *widget, void *d) {
|
|
Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget);
|
|
int diff = atoi((char *)d);
|
|
|
|
if (diff != s->difficulty_) {
|
|
s->difficulty_ = diff;
|
|
s->new_game(s->seed_);
|
|
s->set_title();
|
|
|
|
if (diff > 1)
|
|
{
|
|
// Display a message about the higher difficulty levels for the
|
|
// Sudoku zealots of the world...
|
|
int val;
|
|
|
|
prefs_.get("difficulty_warning", val, 0);
|
|
|
|
if (!val)
|
|
{
|
|
prefs_.set("difficulty_warning", 1);
|
|
fl_alert("Note: 'Hard' and 'Impossible' puzzles may have more than "
|
|
"one possible solution.\n"
|
|
"This is not an error or bug.");
|
|
}
|
|
}
|
|
|
|
prefs_.set("difficulty", s->difficulty_);
|
|
}
|
|
}
|
|
|
|
// Update the little marker numbers in all cells
|
|
void
|
|
Sudoku::update_helpers_cb(Fl_Widget *widget, void *) {
|
|
Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget);
|
|
s->update_helpers();
|
|
}
|
|
|
|
void
|
|
Sudoku::update_helpers() {
|
|
int j, k, m;
|
|
|
|
// First we delete any entries that the user may have made
|
|
for (j = 0; j < 9; j ++) {
|
|
for (k = 0; k < 9; k ++) {
|
|
SudokuCell *cell = grid_cells_[j][k];
|
|
for (m = 0; m < 8; m ++) {
|
|
cell->test_value(0, m);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now go through all cells and find out, what we can not be
|
|
for (j = 0; j < 81; j ++) {
|
|
char taken[10] = { 0 };
|
|
// Find our destination cell
|
|
int row = j / 9;
|
|
int col = j % 9;
|
|
SudokuCell *dst_cell = grid_cells_[row][col];
|
|
if (dst_cell->value()) continue;
|
|
// Find all values already taken in this row
|
|
for (k = 0; k < 9; k ++) {
|
|
SudokuCell *cell = grid_cells_[row][k];
|
|
int v = cell->value();
|
|
if (v) taken[v] = 1;
|
|
}
|
|
// Find all values already taken in this column
|
|
for (k = 0; k < 9; k ++) {
|
|
SudokuCell *cell = grid_cells_[k][col];
|
|
int v = cell->value();
|
|
if (v) taken[v] = 1;
|
|
}
|
|
// Now find all values already taken in this square
|
|
int ro = (row / 3) * 3;
|
|
int co = (col / 3) * 3;
|
|
for (k = 0; k < 3; k ++) {
|
|
for (m = 0; m < 3; m ++) {
|
|
SudokuCell *cell = grid_cells_[ro + k][co + m];
|
|
int v = cell->value();
|
|
if (v) taken[v] = 1;
|
|
}
|
|
}
|
|
// transfer our findings to the markers
|
|
for (m = 1, k = 0; m <= 9; m ++) {
|
|
if (!taken[m])
|
|
dst_cell->test_value(m, k ++);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Show the on-line help...
|
|
void
|
|
Sudoku::help_cb(Fl_Widget *, void *) {
|
|
if (!help_dialog_) {
|
|
help_dialog_ = new Fl_Help_Dialog();
|
|
|
|
help_dialog_->value(
|
|
"<HTML>\n"
|
|
"<HEAD>\n"
|
|
"<TITLE>Sudoku Help</TITLE>\n"
|
|
"</HEAD>\n"
|
|
"<BODY BGCOLOR='#ffffff'>\n"
|
|
|
|
"<H2>About the Game</H2>\n"
|
|
|
|
"<P>Sudoku (pronounced soo-dough-coo with the emphasis on the\n"
|
|
"first syllable) is a simple number-based puzzle/game played on a\n"
|
|
"9x9 grid that is divided into 3x3 subgrids. The goal is to enter\n"
|
|
"a number from 1 to 9 in each cell so that each number appears\n"
|
|
"only once in each column and row. In addition, each 3x3 subgrid\n"
|
|
"may only contain one of each number.</P>\n"
|
|
|
|
"<P>This version of the puzzle is copyright 2005-2010 by Michael R\n"
|
|
"Sweet.</P>\n"
|
|
|
|
"<P><B>Note:</B> The 'Hard' and 'Impossible' difficulty\n"
|
|
"levels generate Sudoku puzzles with multiple possible solutions.\n"
|
|
"While some purists insist that these cannot be called 'Sudoku'\n"
|
|
"puzzles, the author (me) has personally solved many such puzzles\n"
|
|
"in published/printed Sudoku books and finds them far more\n"
|
|
"interesting than the simple single solution variety. If you don't\n"
|
|
"like it, don't play with the difficulty set to 'High' or\n"
|
|
"'Impossible'.</P>\n"
|
|
|
|
"<H2>How to Play the Game</H2>\n"
|
|
|
|
"<P>At the start of a new game, Sudoku fills in a random selection\n"
|
|
"of cells for you - the number of cells depends on the difficulty\n"
|
|
"level you use. Click in any of the empty cells or use the arrow\n"
|
|
"keys to highlight individual cells and press a number from 1 to 9\n"
|
|
"to fill in the cell. To clear a cell, press 0, Delete, or\n"
|
|
"Backspace. When you have successfully completed all subgrids, the\n"
|
|
"entire puzzle is highlighted in green until you start a new\n"
|
|
"game.</P>\n"
|
|
|
|
"<P>As you work to complete the puzzle, you can display possible\n"
|
|
"solutions inside each cell by holding the Shift key and pressing\n"
|
|
"each number in turn. Repeat the process to remove individual\n"
|
|
"numbers, or press a number without the Shift key to replace them\n"
|
|
"with the actual number to use.</P>\n"
|
|
"</BODY>\n"
|
|
);
|
|
}
|
|
|
|
help_dialog_->show();
|
|
}
|
|
|
|
|
|
// Load the game from saved preferences...
|
|
void
|
|
Sudoku::load_game() {
|
|
// Load the current values and state of each grid...
|
|
memset(grid_values_, 0, sizeof(grid_values_));
|
|
|
|
bool solved = true;
|
|
|
|
for (int j = 0; j < 9; j ++)
|
|
for (int k = 0; k < 9; k ++) {
|
|
char name[255];
|
|
int val;
|
|
|
|
SudokuCell *cell = grid_cells_[j][k];
|
|
|
|
sprintf(name, "value%d.%d", j, k);
|
|
if (!prefs_.get(name, val, 0)) {
|
|
j = 9;
|
|
grid_values_[0][0] = 0;
|
|
break;
|
|
}
|
|
|
|
grid_values_[j][k] = val;
|
|
|
|
sprintf(name, "state%d.%d", j, k);
|
|
prefs_.get(name, val, 0);
|
|
cell->value(val);
|
|
|
|
sprintf(name, "readonly%d.%d", j, k);
|
|
prefs_.get(name, val, 0);
|
|
cell->readonly(val != 0);
|
|
|
|
if (val) cell->color(FL_GRAY);
|
|
else {
|
|
cell->color(FL_LIGHT3);
|
|
solved = false;
|
|
}
|
|
|
|
for (int m = 0; m < 8; m ++) {
|
|
sprintf(name, "test%d%d.%d", m, j, k);
|
|
prefs_.get(name, val, 0);
|
|
cell->test_value(val, m);
|
|
}
|
|
}
|
|
|
|
// If we didn't load any values or the last game was solved, then
|
|
// create a new game automatically...
|
|
if (solved || !grid_values_[0][0]) new_game(time(NULL));
|
|
else check_game(false);
|
|
}
|
|
|
|
|
|
// Mute/unmute sound...
|
|
void
|
|
Sudoku::mute_cb(Fl_Widget *widget, void *) {
|
|
Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget);
|
|
|
|
if (s->sound_) {
|
|
delete s->sound_;
|
|
s->sound_ = NULL;
|
|
prefs_.set("mute_sound", 1);
|
|
} else {
|
|
s->sound_ = new SudokuSound();
|
|
prefs_.set("mute_sound", 0);
|
|
}
|
|
}
|
|
|
|
|
|
// Create a new game...
|
|
void
|
|
Sudoku::new_cb(Fl_Widget *widget, void *) {
|
|
Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget);
|
|
|
|
if (s->grid_cells_[0][0]->color() != FL_GREEN) {
|
|
if (!fl_choice("Are you sure you want to change the difficulty level and "
|
|
"discard the current game?", "Keep Current Game", "Start New Game",
|
|
NULL)) return;
|
|
}
|
|
|
|
s->new_game(time(NULL));
|
|
}
|
|
|
|
|
|
// Create a new game...
|
|
void
|
|
Sudoku::new_game(time_t seed) {
|
|
int j, k, m, n, t, count;
|
|
|
|
|
|
// Generate a new (valid) Sudoku grid...
|
|
seed_ = seed;
|
|
srand((unsigned int)seed);
|
|
|
|
memset(grid_values_, 0, sizeof(grid_values_));
|
|
|
|
for (j = 0; j < 9; j += 3) {
|
|
for (k = 0; k < 9; k += 3) {
|
|
for (t = 1; t <= 9; t ++) {
|
|
for (count = 0; count < 20; count ++) {
|
|
m = j + (rand() % 3);
|
|
n = k + (rand() % 3);
|
|
if (!grid_values_[m][n]) {
|
|
int mm;
|
|
|
|
for (mm = 0; mm < m; mm ++)
|
|
if (grid_values_[mm][n] == t) break;
|
|
|
|
if (mm < m) continue;
|
|
|
|
int nn;
|
|
|
|
for (nn = 0; nn < n; nn ++)
|
|
if (grid_values_[m][nn] == t) break;
|
|
|
|
if (nn < n) continue;
|
|
|
|
grid_values_[m][n] = t;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (count == 20) {
|
|
// Unable to find a valid puzzle so far, so start over...
|
|
k = 9;
|
|
j = -3;
|
|
memset(grid_values_, 0, sizeof(grid_values_));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Start by making all cells editable
|
|
SudokuCell *cell;
|
|
|
|
for (j = 0; j < 9; j ++)
|
|
for (k = 0; k < 9; k ++) {
|
|
cell = grid_cells_[j][k];
|
|
|
|
cell->value(0);
|
|
cell->readonly(0);
|
|
cell->color(FL_LIGHT3);
|
|
}
|
|
|
|
// Show N cells...
|
|
count = 11 * (5 - difficulty_);
|
|
|
|
int numbers[9];
|
|
|
|
for (j = 0; j < 9; j ++) numbers[j] = j + 1;
|
|
|
|
while (count > 0) {
|
|
for (j = 0; j < 20; j ++) {
|
|
k = rand() % 9;
|
|
m = rand() % 9;
|
|
t = numbers[k];
|
|
numbers[k] = numbers[m];
|
|
numbers[m] = t;
|
|
}
|
|
|
|
for (j = 0; count > 0 && j < 9; j ++) {
|
|
t = numbers[j];
|
|
|
|
for (k = 0; count > 0 && k < 9; k ++) {
|
|
cell = grid_cells_[j][k];
|
|
|
|
if (grid_values_[j][k] == t && !cell->readonly()) {
|
|
cell->value(grid_values_[j][k]);
|
|
cell->readonly(1);
|
|
cell->color(FL_GRAY);
|
|
|
|
count --;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Return the next available value for a cell...
|
|
int
|
|
Sudoku::next_value(SudokuCell *c) {
|
|
int j = 0, k = 0, m = 0, n = 0;
|
|
|
|
|
|
for (j = 0; j < 9; j ++) {
|
|
for (k = 0; k < 9; k ++)
|
|
if (grid_cells_[j][k] == c) break;
|
|
|
|
if (k < 9) break;
|
|
}
|
|
|
|
if (j == 9) return 1;
|
|
|
|
j -= j % 3;
|
|
k -= k % 3;
|
|
|
|
int numbers[9];
|
|
|
|
memset(numbers, 0, sizeof(numbers));
|
|
|
|
for (m = 0; m < 3; m ++)
|
|
for (n = 0; n < 3; n ++) {
|
|
c = grid_cells_[j + m][k + n];
|
|
if (c->value()) numbers[c->value() - 1] = 1;
|
|
}
|
|
|
|
for (j = 0; j < 9; j ++)
|
|
if (!numbers[j]) return j + 1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
// Reset widget color to gray...
|
|
void
|
|
Sudoku::reset_cb(Fl_Widget *widget, void *) {
|
|
widget->color(FL_LIGHT3);
|
|
widget->redraw();
|
|
|
|
((Sudoku *)(widget->window()))->check_game(false);
|
|
}
|
|
|
|
|
|
// Resize the window...
|
|
void
|
|
Sudoku::resize(int X, int Y, int W, int H) {
|
|
// Resize the window...
|
|
Fl_Double_Window::resize(X, Y, W, H);
|
|
|
|
// Save the new window geometry...
|
|
prefs_.set("x", X);
|
|
prefs_.set("y", Y);
|
|
prefs_.set("width", W);
|
|
prefs_.set("height", H);
|
|
}
|
|
|
|
|
|
// Restart game from beginning...
|
|
void
|
|
Sudoku::restart_cb(Fl_Widget *widget, void *) {
|
|
Sudoku *s = (Sudoku *)(widget->window());
|
|
bool solved = true;
|
|
|
|
for (int j = 0; j < 9; j ++)
|
|
for (int k = 0; k < 9; k ++) {
|
|
SudokuCell *cell = s->grid_cells_[j][k];
|
|
|
|
if (!cell->readonly()) {
|
|
solved = false;
|
|
int v = cell->value();
|
|
cell->value(0);
|
|
cell->color(FL_LIGHT3);
|
|
if (v && s->sound_) s->sound_->play('A' + v - 1);
|
|
}
|
|
}
|
|
|
|
if (solved) s->new_game(s->seed_);
|
|
}
|
|
|
|
|
|
// Save the current game state...
|
|
void
|
|
Sudoku::save_game() {
|
|
// Save the current values and state of each grid...
|
|
for (int j = 0; j < 9; j ++)
|
|
for (int k = 0; k < 9; k ++) {
|
|
char name[255];
|
|
SudokuCell *cell = grid_cells_[j][k];
|
|
|
|
sprintf(name, "value%d.%d", j, k);
|
|
prefs_.set(name, grid_values_[j][k]);
|
|
|
|
sprintf(name, "state%d.%d", j, k);
|
|
prefs_.set(name, cell->value());
|
|
|
|
sprintf(name, "readonly%d.%d", j, k);
|
|
prefs_.set(name, cell->readonly());
|
|
|
|
for (int m = 0; m < 8; m ++) {
|
|
sprintf(name, "test%d%d.%d", m, j, k);
|
|
prefs_.set(name, cell->test_value(m));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Set title of window...
|
|
void
|
|
Sudoku::set_title() {
|
|
static const char * const titles[] = {
|
|
"Sudoku - Easy",
|
|
"Sudoku - Medium",
|
|
"Sudoku - Hard",
|
|
"Sudoku - Impossible"
|
|
};
|
|
|
|
label(titles[difficulty_]);
|
|
}
|
|
|
|
|
|
// Solve the puzzle...
|
|
void
|
|
Sudoku::solve_cb(Fl_Widget *widget, void *) {
|
|
((Sudoku *)(widget->window()))->solve_game();
|
|
}
|
|
|
|
|
|
// Solve the puzzle...
|
|
void
|
|
Sudoku::solve_game() {
|
|
int j, k;
|
|
|
|
for (j = 0; j < 9; j ++) {
|
|
for (k = 0; k < 9; k ++) {
|
|
SudokuCell *cell = grid_cells_[j][k];
|
|
|
|
cell->value(grid_values_[j][k]);
|
|
cell->readonly(1);
|
|
cell->color(FL_GRAY);
|
|
}
|
|
|
|
if (sound_) sound_->play('A' + grid_cells_[j][8]->value() - 1);
|
|
}
|
|
}
|
|
|
|
|
|
// Main entry for game...
|
|
int
|
|
main(int argc, char *argv[]) {
|
|
Sudoku s;
|
|
|
|
// Show the game...
|
|
s.show(argc, argv);
|
|
|
|
// Load the previous game...
|
|
s.load_game();
|
|
|
|
// Run until the user quits...
|
|
return (Fl::run());
|
|
}
|