b179cc108a
git-svn-id: file:///fltk/svn/fltk/branches/branch-1.1@5344 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
1227 lines
29 KiB
C++
1227 lines
29 KiB
C++
//
|
|
// "$Id$"
|
|
//
|
|
// Sudoku game using the Fast Light Tool Kit (FLTK).
|
|
//
|
|
// Copyright 2005-2006 by Michael Sweet.
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Library General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Library General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Library General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
// USA.
|
|
//
|
|
// Please report all bugs and problems on the following page:
|
|
//
|
|
// http://www.fltk.org/str.php
|
|
//
|
|
|
|
#include <FL/Fl.H>
|
|
#include <FL/Enumerations.H>
|
|
#include <FL/Fl_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/x.H>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <FL/math.h>
|
|
|
|
#ifdef WIN32
|
|
# include "sudokurc.h"
|
|
#elif !defined(__APPLE__)
|
|
# include "sudoku.xbm"
|
|
#endif // WIN32
|
|
|
|
// 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;
|
|
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_[8];
|
|
|
|
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_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 help_cb(Fl_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();
|
|
};
|
|
|
|
|
|
// 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
|
|
if (AudioDeviceAddIOProc(device, audio_cb, (void *)this) != noErr) return;
|
|
|
|
// Start the device...
|
|
AudioDeviceStart(device, audio_cb);
|
|
|
|
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) {
|
|
AudioDeviceStop(device, audio_cb);
|
|
AudioDeviceRemoveIOProc(device, audio_cb);
|
|
}
|
|
|
|
#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__
|
|
|
|
|
|
// Play a note for 250ms...
|
|
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(50000);
|
|
|
|
#elif defined(WIN32)
|
|
if (sample_size) {
|
|
memcpy(data_ptr, sample_data[note - 'A'], sample_size * 4);
|
|
|
|
waveOutWrite(device, header_ptr, sizeof(WAVEHDR));
|
|
|
|
Sleep(50);
|
|
} else Beep(frequencies[note - 'A'], 50);
|
|
|
|
#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(50000);
|
|
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 = 50;
|
|
|
|
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;
|
|
break;
|
|
|
|
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_Window(GROUP_SIZE * 3, GROUP_SIZE * 3 + MENU_OFFSET, "Sudoku")
|
|
{
|
|
int i, j;
|
|
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 },
|
|
{ "&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...
|
|
sound_ = new SudokuSound();
|
|
|
|
// Menubar...
|
|
prefs_.get("difficulty", difficulty_, 0);
|
|
if (difficulty_ < 0 || difficulty_ > 3) difficulty_ = 0;
|
|
|
|
items[8 + 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 (i = 0; i < 3; i ++)
|
|
for (j = 0; j < 3; j ++) {
|
|
g = new Fl_Group(j * GROUP_SIZE, i * GROUP_SIZE + MENU_OFFSET,
|
|
GROUP_SIZE, GROUP_SIZE);
|
|
g->box(FL_BORDER_BOX);
|
|
if ((i == 1) ^ (j == 1)) g->color(FL_DARK3);
|
|
else g->color(FL_DARK2);
|
|
g->end();
|
|
|
|
grid_groups_[i][j] = g;
|
|
}
|
|
|
|
for (i = 0; i < 9; i ++)
|
|
for (j = 0; j < 9; j ++) {
|
|
cell = new SudokuCell(j * CELL_SIZE + CELL_OFFSET +
|
|
(j / 3) * (GROUP_SIZE - 3 * CELL_SIZE),
|
|
i * CELL_SIZE + CELL_OFFSET + MENU_OFFSET +
|
|
(i / 3) * (GROUP_SIZE - 3 * CELL_SIZE),
|
|
CELL_SIZE, CELL_SIZE);
|
|
cell->callback(reset_cb);
|
|
grid_cells_[i][j] = cell;
|
|
}
|
|
|
|
// Set icon for window (MacOS uses app bundle for icon...)
|
|
#ifdef WIN32
|
|
icon((char *)LoadIcon(fl_display, MAKEINTRESOURCE(IDI_ICON)));
|
|
#elif !defined(__APPLE__)
|
|
fl_open_display();
|
|
icon((char *)XCreateBitmapFromData(fl_display, DefaultRootWindow(fl_display),
|
|
(char *)sudoku_bits, sudoku_width,
|
|
sudoku_height));
|
|
#endif // WIN32
|
|
|
|
// 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() {
|
|
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 i, j, k;
|
|
|
|
// Check the game for right/wrong answers...
|
|
for (i = 0; i < 9; i ++)
|
|
for (j = 0; j < 9; j ++) {
|
|
SudokuCell *cell = grid_cells_[i][j];
|
|
int val = cell->value();
|
|
|
|
if (cell->readonly()) continue;
|
|
|
|
if (!val) empty = true;
|
|
else {
|
|
for (k = 0; k < 9; k ++)
|
|
if ((i != k && grid_cells_[k][j]->value() == val) ||
|
|
(j != k && grid_cells_[i][k]->value() == val)) break;
|
|
|
|
if (k < 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 (i = 0; i < 9; i += 3)
|
|
for (j = 0; j < 9; j += 3)
|
|
for (int ii = 0; ii < 3; ii ++)
|
|
for (int jj = 0; jj < 3; jj ++) {
|
|
SudokuCell *cell = grid_cells_[i + ii][j + jj];
|
|
int val = cell->value();
|
|
|
|
if (cell->readonly() || !val) continue;
|
|
|
|
int iii;
|
|
|
|
for (iii = 0; iii < 3; iii ++) {
|
|
int jjj;
|
|
|
|
for (jjj = 0; jjj < 3; jjj ++)
|
|
if (ii != iii && jj != jjj &&
|
|
grid_cells_[i + iii][j + jjj]->value() == val) break;
|
|
|
|
if (jjj < 3) break;
|
|
}
|
|
|
|
if (iii < 3) {
|
|
if (highlight) {
|
|
cell->color(FL_YELLOW);
|
|
cell->redraw();
|
|
}
|
|
|
|
correct = false;
|
|
}
|
|
}
|
|
|
|
if (!empty && correct) {
|
|
// Success!
|
|
for (i = 0; i < 9; i ++) {
|
|
for (j = 0; j < 9; j ++) {
|
|
SudokuCell *cell = grid_cells_[i][j];
|
|
cell->color(FL_GREEN);
|
|
cell->readonly(1);
|
|
}
|
|
|
|
sound_->play('A' + grid_cells_[i][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();
|
|
|
|
prefs_.set("difficulty", s->difficulty_);
|
|
}
|
|
}
|
|
|
|
|
|
// 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 by Michael R Sweet</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 i = 0; i < 9; i ++)
|
|
for (int j = 0; j < 9; j ++) {
|
|
char name[255];
|
|
int val;
|
|
|
|
SudokuCell *cell = grid_cells_[i][j];
|
|
|
|
sprintf(name, "value%d.%d", i, j);
|
|
if (!prefs_.get(name, val, 0)) {
|
|
i = 9;
|
|
grid_values_[0][0] = 0;
|
|
break;
|
|
}
|
|
|
|
grid_values_[i][j] = val;
|
|
|
|
sprintf(name, "state%d.%d", i, j);
|
|
prefs_.get(name, val, 0);
|
|
cell->value(val);
|
|
|
|
sprintf(name, "readonly%d.%d", i, j);
|
|
prefs_.get(name, val, 0);
|
|
cell->readonly(val);
|
|
|
|
if (val) cell->color(FL_GRAY);
|
|
else {
|
|
cell->color(FL_LIGHT3);
|
|
solved = false;
|
|
}
|
|
|
|
for (int k = 0; k < 8; k ++) {
|
|
sprintf(name, "test%d%d.%d", k, i, j);
|
|
prefs_.get(name, val, 0);
|
|
cell->test_value(val, k);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
|
|
// 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 i, j, k, m, t, count;
|
|
|
|
|
|
// Generate a new (valid) Sudoku grid...
|
|
seed_ = seed;
|
|
srand(seed);
|
|
|
|
memset(grid_values_, 0, sizeof(grid_values_));
|
|
|
|
for (i = 0; i < 9; i += 3) {
|
|
for (j = 0; j < 9; j += 3) {
|
|
for (t = 1; t <= 9; t ++) {
|
|
for (count = 0; count < 20; count ++) {
|
|
k = i + (rand() % 3);
|
|
m = j + (rand() % 3);
|
|
if (!grid_values_[k][m]) {
|
|
int kk;
|
|
|
|
for (kk = 0; kk < k; kk ++)
|
|
if (grid_values_[kk][m] == t) break;
|
|
|
|
if (kk < k) continue;
|
|
|
|
int mm;
|
|
|
|
for (mm = 0; mm < m; mm ++)
|
|
if (grid_values_[k][mm] == t) break;
|
|
|
|
if (mm < m) continue;
|
|
|
|
grid_values_[k][m] = t;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (count == 20) {
|
|
// Unable to find a valid puzzle so far, so start over...
|
|
j = 9;
|
|
i = -3;
|
|
memset(grid_values_, 0, sizeof(grid_values_));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Start by making all cells editable
|
|
SudokuCell *cell;
|
|
|
|
for (i = 0; i < 9; i ++)
|
|
for (j = 0; j < 9; j ++) {
|
|
cell = grid_cells_[i][j];
|
|
|
|
cell->value(0);
|
|
cell->readonly(0);
|
|
cell->color(FL_LIGHT3);
|
|
}
|
|
|
|
// Show N cells...
|
|
count = 10 * (5 - difficulty_);
|
|
|
|
int numbers[9];
|
|
|
|
for (i = 0; i < 9; i ++) numbers[i] = i + 1;
|
|
|
|
while (count > 0) {
|
|
for (i = 0; i < 20; i ++) {
|
|
k = rand() % 9;
|
|
m = rand() % 9;
|
|
t = numbers[k];
|
|
numbers[k] = numbers[m];
|
|
numbers[m] = t;
|
|
}
|
|
|
|
for (i = 0; count > 0 && i < 9; i ++) {
|
|
t = numbers[i];
|
|
|
|
for (j = 0; count > 0 && j < 9; j ++) {
|
|
cell = grid_cells_[i][j];
|
|
|
|
if (grid_values_[i][j] == t && !cell->readonly()) {
|
|
cell->value(grid_values_[i][j]);
|
|
cell->readonly(1);
|
|
cell->color(FL_GRAY);
|
|
|
|
count --;
|
|
break;
|
|
}
|
|
}
|
|
|
|
sound_->play('A' + t - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Return the next available value for a cell...
|
|
int
|
|
Sudoku::next_value(SudokuCell *c) {
|
|
int i, j, k, m;
|
|
|
|
|
|
for (i = 0; i < 9; i ++) {
|
|
for (j = 0; j < 9; j ++)
|
|
if (grid_cells_[i][j] == c) break;
|
|
|
|
if (j < 9) break;
|
|
}
|
|
|
|
if (i == 9) return 1;
|
|
|
|
i -= i % 3;
|
|
j -= j % 3;
|
|
|
|
int numbers[9];
|
|
|
|
memset(numbers, 0, sizeof(numbers));
|
|
|
|
for (k = 0; k < 3; k ++)
|
|
for (m = 0; m < 3; m ++) {
|
|
c = grid_cells_[i + k][j + m];
|
|
if (c->value()) numbers[c->value() - 1] = 1;
|
|
}
|
|
|
|
for (i = 0; i < 9; i ++)
|
|
if (!numbers[i]) return i + 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_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 i = 0; i < 9; i ++)
|
|
for (int j = 0; j < 9; j ++) {
|
|
SudokuCell *cell = s->grid_cells_[i][j];
|
|
|
|
if (!cell->readonly()) {
|
|
solved = false;
|
|
int v = cell->value();
|
|
cell->value(0);
|
|
cell->color(FL_LIGHT3);
|
|
if (v) 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 i = 0; i < 9; i ++)
|
|
for (int j = 0; j < 9; j ++) {
|
|
char name[255];
|
|
SudokuCell *cell = grid_cells_[i][j];
|
|
|
|
sprintf(name, "value%d.%d", i, j);
|
|
prefs_.set(name, grid_values_[i][j]);
|
|
|
|
sprintf(name, "state%d.%d", i, j);
|
|
prefs_.set(name, cell->value());
|
|
|
|
sprintf(name, "readonly%d.%d", i, j);
|
|
prefs_.set(name, cell->readonly());
|
|
|
|
for (int k = 0; k < 8; k ++) {
|
|
sprintf(name, "test%d%d.%d", k, i, j);
|
|
prefs_.set(name, cell->test_value(k));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 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 i, j;
|
|
|
|
for (i = 0; i < 9; i ++) {
|
|
for (j = 0; j < 9; j ++) {
|
|
SudokuCell *cell = grid_cells_[i][j];
|
|
|
|
cell->value(grid_values_[i][j]);
|
|
cell->readonly(1);
|
|
cell->color(FL_GRAY);
|
|
}
|
|
|
|
sound_->play('A' + grid_cells_[i][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());
|
|
}
|
|
|
|
|
|
//
|
|
// End of "$Id$".
|
|
//
|