summaryrefslogtreecommitdiff
path: root/test/sudoku.cxx
diff options
context:
space:
mode:
authorMatthias Melcher <github@matthiasm.com>2023-08-15 22:59:07 +0200
committerMatthias Melcher <github@matthiasm.com>2023-08-15 22:59:07 +0200
commite8b378302cdb582a09dd5a577541ecf32029e93f (patch)
treec48995b09151eaf9b12be691d58cc6163183b984 /test/sudoku.cxx
parentcfe5b2d6e109a8b0bedb4f2ad83a0803f888cd5b (diff)
Undoing previous changes
Diffstat (limited to 'test/sudoku.cxx')
-rw-r--r--test/sudoku.cxx788
1 files changed, 638 insertions, 150 deletions
diff --git a/test/sudoku.cxx b/test/sudoku.cxx
index fc2003dc3..97766ac2f 100644
--- a/test/sudoku.cxx
+++ b/test/sudoku.cxx
@@ -15,43 +15,6 @@
// https://www.fltk.org/bugs.php
//
-// A Sudoku (i.e. the puzzle) is a partially completed grid. A grid has 9 rows,
-// 9 columns and 9 boxes, each having 9 cells (81 total). Boxes can also be
-// called blocks or regions.[1] Three horizontally adjacent blocks are a band,
-// and three vertically adjacent blocks are a stack.[2] The initially defined
-// values are clues or givens. An ordinary Sudoku (i.e. a proper Sudoku) has
-// one solution. Rows, columns and regions can be collectively referred to as
-// groups, of which the grid has 27. The One Rule encapsulates the three prime
-// rules, i.e. each digit (or number) can occur only once in each row, column,
-// and box; and can be compactly stated as: "Each digit appears once in each
-// group."
-// - wikipedia.org
-
-// Wishlist:
-// - [ ] new easy to new hard, etc.
-// - [ ] store current puzzle in preferences
-// - [ ] undo, redo
-// - [ ] update hints now
-// - [ ] always update hints
-// - [ ] highlight row, column, and box
-// - [ ] highlight other cells with same value
-// - [ ] verify current solution
-// - [ ] hint/flag/note vs. solve mode and button 1..9, erase
-// - [ ] gift one field
-// - [ ] bg is white and bright blue
-// - [ ] selected field bg is green, boxed
-// - [ ] same number is yellow
-// - [ ] conflicts can use arrows and crosses
-// - [ ] fixed numbers are bold, user values are not
-// - [ ] timer
-// - [ ] game hamburge menu
-
-#include "sudoku.h"
-#include "sudoku_puzzle.h"
-#include "sudoku_cell.h"
-#include "sudoku_sound.h"
-#include "sudoku_generator.h"
-
#include <FL/Fl.H>
#include <FL/Enumerations.H>
#include <FL/Fl_Double_Window.H>
@@ -73,11 +36,29 @@
#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
@@ -87,6 +68,560 @@
# 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() FL_OVERRIDE;
+ int handle(int event) FL_OVERRIDE;
+ 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) FL_OVERRIDE;
+ 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(&params);
+ 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);
+
+#elif defined(FLTK_USE_X11)
+# 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;
@@ -95,9 +630,7 @@ Fl_Preferences Sudoku::prefs_(Fl_Preferences::USER_L, "fltk.org", "sudoku");
// Create a Sudoku game window...
Sudoku::Sudoku()
- : Fl_Double_Window(kPuzzleSize + 2*kPadding,
- kPuzzleSize + 2*kPadding + kMenuOffset,
- "FLTK Sudoku")
+ : Fl_Double_Window(GROUP_SIZE * 3, GROUP_SIZE * 3 + MENU_OFFSET, "Sudoku")
{
int j, k;
Fl_Group *g;
@@ -108,7 +641,7 @@ Sudoku::Sudoku()
{ "&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", FL_COMMAND | 'u', update_helpers_cb, 0, 0 },
+ { "&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 },
@@ -143,38 +676,28 @@ Sudoku::Sudoku()
menubar_->menu(items);
// Create the grids...
- grid_ = new SudokuPuzzle(kPadding,
- kPadding + kMenuOffset,
- kPuzzleSize,
- kPuzzleSize);
- Fl_Group *rsgrid = new Fl_Group(grid_->x()+1, grid_->y()+1,
- grid_->w()-2, grid_->h()-2);
- grid_->resizable(rsgrid);
+ 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 ++) {
- Fl_Group *group = new Fl_Group(grid_->x() + 1 + k*kGroupSize,
- grid_->y() + 1 + j*kGroupSize,
- kGroupSize,
- kGroupSize);
- group->box(FL_BORDER_BOX);
- Fl_Group *rsgroup = new Fl_Group(group->x()+1, group->y()+1,
- group->w()-2, group->h()-2);
- rsgroup->box(FL_NO_BOX);
- new Fl_Box(FL_BORDER_BOX, group->x()+1, group->y()+1, kCellSize, kCellSize, NULL);
- new Fl_Box(FL_BORDER_BOX, group->x()+1+kCellSize, group->y()+1, kCellSize, kCellSize, NULL);
- new Fl_Box(FL_BORDER_BOX, group->x()+1+2*kCellSize, group->y()+1, kCellSize, kCellSize, NULL);
- group->resizable(rsgroup);
- group->end();
+ 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(grid_->x() + 2 + k*kCellSize + (k/3),
- grid_->y() + 2 + j*kCellSize + (j/3),
- kCellSize, kCellSize);
+ 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);
- cell->hide();
grid_cells_[j][k] = cell;
}
@@ -362,12 +885,12 @@ Sudoku::update_helpers() {
int j, k, m;
// First we delete any entries that the user may have made
- // TODO: set all marks if none were set
- // TODO: clear marks if value is used, don't set individual marks
for (j = 0; j < 9; j ++) {
for (k = 0; k < 9; k ++) {
SudokuCell *cell = grid_cells_[j][k];
- cell->clear_marks();
+ for (m = 0; m < 8; m ++) {
+ cell->test_value(0, m);
+ }
}
}
@@ -402,11 +925,10 @@ Sudoku::update_helpers() {
}
}
// transfer our findings to the markers
- for (m = 1; m <= 9; m ++) {
+ for (m = 1, k = 0; m <= 9; m ++) {
if (!taken[m])
- dst_cell->mark(m, true);
+ dst_cell->test_value(m, k ++);
}
- dst_cell->redraw();
}
}
@@ -472,44 +994,47 @@ Sudoku::help_cb(Fl_Widget *, void *) {
// 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;
- bool empty = false;
- for (int j = 0; j < 9; j ++) {
- Fl_Preferences row(prefs_, Fl_Preferences::Name("Row%d", j));
+ for (int j = 0; j < 9; j ++)
for (int k = 0; k < 9; k ++) {
- Fl_Preferences p(row, Fl_Preferences::Name("Col%d", k));
- int v;
+ char name[255];
+ int val;
SudokuCell *cell = grid_cells_[j][k];
- p.get("value", v, 0);
- grid_values_[j][k] = v;
- if (v) empty = false;
+ snprintf(name, sizeof(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;
- p.get("state", v, 0);
- cell->value(v);
+ snprintf(name, sizeof(name), "state%d.%d", j, k);
+ prefs_.get(name, val, 0);
+ cell->value(val);
- p.set("readonly", cell->readonly());
- cell->readonly(v != 0);
- if (v) {
- cell->color(FL_GRAY);
- } else {
+ snprintf(name, sizeof(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 = 1; m <= 9; m ++) {
- p.get(Fl_Preferences::Name("m%d", m), v, 0);
- cell->mark(m, v);
+ for (int m = 0; m < 8; m ++) {
+ snprintf(name, sizeof(name), "test%d%d.%d", m, j, k);
+ prefs_.get(name, val, 0);
+ cell->test_value(val, m);
}
- cell->redraw();
}
- }
// If we didn't load any values or the last game was solved, then
// create a new game automatically...
@@ -539,47 +1064,19 @@ 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;
-// }
+ 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 grid_data[81];
- int *g = grid_data;
- generate_sudoku(grid_data, 22, 31);
- SudokuCell *cell;
- for (int j = 0; j < 9; j ++) {
- for (int k = 0; k < 9; k ++) {
- int v = *g++;
- int vv = v; if (vv<0) vv = -vv;
- grid_values_[j][k] = vv;
- cell = grid_cells_[j][k];
- if (v<0) {
- cell->value(0);
- cell->readonly(0);
- cell->color(FL_LIGHT3);
- } else {
- cell->value(vv);
- cell->readonly(1);
- cell->color(FL_GRAY);
- }
- }
- }
- return;
- }
-
-
-
-
int j, k, m, n, t, count;
@@ -759,23 +1256,25 @@ Sudoku::restart_cb(Fl_Widget *widget, void *) {
void
Sudoku::save_game() {
// Save the current values and state of each grid...
- for (int j = 0; j < 9; j ++) {
- Fl_Preferences row(prefs_, Fl_Preferences::Name("Row%d", j));
+ for (int j = 0; j < 9; j ++)
for (int k = 0; k < 9; k ++) {
- Fl_Preferences p(row, Fl_Preferences::Name("Col%d", k));
char name[255];
SudokuCell *cell = grid_cells_[j][k];
- p.set("value", grid_values_[j][k]);
- p.set("state", cell->value());
- p.set("readonly", cell->readonly());
- for (int m = 1; m <= 9; m ++) {
- if (cell->mark(m))
- p.set(Fl_Preferences::Name("m%d", m), 1);
- else
- p.deleteEntry(Fl_Preferences::Name("m%d", m));
+
+ snprintf(name, sizeof(name), "value%d.%d", j, k);
+ prefs_.set(name, grid_values_[j][k]);
+
+ snprintf(name, sizeof(name), "state%d.%d", j, k);
+ prefs_.set(name, cell->value());
+
+ snprintf(name, sizeof(name), "readonly%d.%d", j, k);
+ prefs_.set(name, cell->readonly());
+
+ for (int m = 0; m < 8; m ++) {
+ snprintf(name, sizeof(name), "test%d%d.%d", m, j, k);
+ prefs_.set(name, cell->test_value(m));
}
}
- }
}
@@ -820,17 +1319,6 @@ Sudoku::solve_game() {
// Main entry for game...
-// Note 21-17 (proven minimum) clues can be set
-// easy: 30-36
-// expert: 25-30
-// algo: 22 (rare) to 25
-
-// extremely easy: 46+
-// easy: 36-46
-// medium: 32-35
-// difficult: 28-31
-// evil: 17-27
-
int
main(int argc, char *argv[]) {
Sudoku s;