From e8b378302cdb582a09dd5a577541ecf32029e93f Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Tue, 15 Aug 2023 22:59:07 +0200 Subject: Undoing previous changes --- test/CMakeLists.txt | 2 +- test/Makefile | 9 +- test/sudoku.cxx | 788 ++++++++++++++++++++++++++++++++++-------- test/sudoku.h | 80 ----- test/sudoku_cell.cxx | 176 ---------- test/sudoku_cell.h | 49 --- test/sudoku_generator.cxx | 599 -------------------------------- test/sudoku_generator.h | 60 ---- test/sudoku_puzzle.cxx | 846 ---------------------------------------------- test/sudoku_puzzle.h | 76 ----- test/sudoku_sound.cxx | 308 ----------------- test/sudoku_sound.h | 97 ------ 12 files changed, 643 insertions(+), 2447 deletions(-) delete mode 100644 test/sudoku.h delete mode 100644 test/sudoku_cell.cxx delete mode 100644 test/sudoku_cell.h delete mode 100644 test/sudoku_generator.cxx delete mode 100644 test/sudoku_generator.h delete mode 100644 test/sudoku_puzzle.cxx delete mode 100644 test/sudoku_puzzle.h delete mode 100644 test/sudoku_sound.cxx delete mode 100644 test/sudoku_sound.h diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 30b9ff542..6fb3b5105 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -133,7 +133,7 @@ CREATE_EXAMPLE (resize-example5c "resize-example5c.cxx;resize-arrows.cxx" fltk) CREATE_EXAMPLE (rotated_text rotated_text.cxx fltk) CREATE_EXAMPLE (scroll scroll.cxx fltk) CREATE_EXAMPLE (subwindow subwindow.cxx fltk) -CREATE_EXAMPLE (sudoku "sudoku.cxx;sudoku_puzzle.cxx;sudoku_cell.cxx;sudoku_sound.cxx;sudoku_generator.cxx;sudoku.plist;sudoku.icns;sudoku.rc" "fltk_images;fltk;${AUDIOLIBS}") +CREATE_EXAMPLE (sudoku "sudoku.cxx;sudoku.plist;sudoku.icns;sudoku.rc" "fltk_images;fltk;${AUDIOLIBS}") CREATE_EXAMPLE (symbols symbols.cxx fltk) CREATE_EXAMPLE (tabs tabs.fl fltk) CREATE_EXAMPLE (table table.cxx fltk) diff --git a/test/Makefile b/test/Makefile index 51af5bbcb..f1187caf5 100644 --- a/test/Makefile +++ b/test/Makefile @@ -131,7 +131,6 @@ CPPFILES =\ shape.cxx \ subwindow.cxx \ sudoku.cxx \ - sudoku_generator.cxx \ symbols.cxx \ table.cxx \ tabs.cxx \ @@ -597,19 +596,19 @@ scroll$(EXEEXT): scroll.o subwindow$(EXEEXT): subwindow.o -sudoku: sudoku.o sudoku_generator.o +sudoku: sudoku.o echo Linking $@... - $(CXX) $(ARCHFLAGS) $(CXXFLAGS) $(LDFLAGS) sudoku.o sudoku_generator.o -o $@ $(AUDIOLIBS) $(LINKFLTKIMG) $(LDLIBS) + $(CXX) $(ARCHFLAGS) $(CXXFLAGS) $(LDFLAGS) sudoku.o -o $@ $(AUDIOLIBS) $(LINKFLTKIMG) $(LDLIBS) $(OSX_ONLY) $(RM) -f -r sudoku.app $(OSX_ONLY) mkdir -p sudoku.app/Contents/MacOS sudoku.app/Contents/Resources $(OSX_ONLY) $(INSTALL_BIN) sudoku$(EXEEXT) sudoku.app/Contents/MacOS $(OSX_ONLY) $(INSTALL_BIN) mac-resources/sudoku.icns sudoku.app/Contents/Resources/ $(OSX_ONLY) $(INSTALL_BIN) mac-resources/sudoku.plist sudoku.app/Contents/Info.plist -sudoku.exe: sudoku.o sudoku_generator.o sudoku.rc +sudoku.exe: sudoku.o sudoku.rc echo Linking $@... $(RC) sudoku.rc sudokures.o - $(CXX) $(ARCHFLAGS) $(CXXFLAGS) $(LDFLAGS) sudoku.o sudoku_generator.o sudokures.o -o $@ $(AUDIOLIBS) $(LINKFLTKIMG) $(LDLIBS) + $(CXX) $(ARCHFLAGS) $(CXXFLAGS) $(LDFLAGS) sudoku.o sudokures.o -o $@ $(AUDIOLIBS) $(LINKFLTKIMG) $(LDLIBS) symbols$(EXEEXT): symbols.o 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 #include #include @@ -73,11 +36,29 @@ #include "pixmaps/sudoku.xbm" +// Audio headers... +#include + +#ifndef _WIN32 +# include +#endif // !_WIN32 + +#ifdef HAVE_ALSA_ASOUNDLIB_H +# define ALSA_PCM_NEW_HW_PARAMS_API +# include +#endif // HAVE_ALSA_ASOUNDLIB_H +#ifdef __APPLE__ +# include +#endif // __APPLE__ +#ifdef _WIN32 +# include +#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(¶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 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; diff --git a/test/sudoku.h b/test/sudoku.h deleted file mode 100644 index 5b3cebe24..000000000 --- a/test/sudoku.h +++ /dev/null @@ -1,80 +0,0 @@ -// -// Sudoku game using the Fast Light Tool Kit (FLTK). -// -// Copyright 2005-2018 by Michael Sweet. -// Copyright 2019-2021 by Bill Spitzak and others. -// -// 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 -// - -#ifndef _SUDOKU_H_ -#define _SUDOKU_H_ - -#include -#include - -class SudokuCell; -class SudokuSound; -class Fl_Sys_Menu_Bar; -class Fl_Help_Dialog; - -#ifdef __APPLE__ -const int kMenuOffset = 0; -#else -const int kMenuOffset = 25; -#endif // __APPLE__ -const int kPadding = 5; -const int kCellSize = 50; -const int kGroupSize = 3*kCellSize + 2; -const int kPuzzleSize = 3*kGroupSize + 2; - -// 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(); -}; - - -#endif // _SUDOKU_H_ diff --git a/test/sudoku_cell.cxx b/test/sudoku_cell.cxx deleted file mode 100644 index a718ecf5c..000000000 --- a/test/sudoku_cell.cxx +++ /dev/null @@ -1,176 +0,0 @@ -// -// Sudoku game using the Fast Light Tool Kit (FLTK). -// -// Copyright 2005-2018 by Michael Sweet. -// Copyright 2019-2021 by Bill Spitzak and others. -// -// 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 "sudoku_cell.h" -#include "sudoku.h" - -#include -#include - -// Create a cell widget -SudokuCell::SudokuCell(int X, int Y, int W, int H) -: Fl_Widget(X, Y, W, H, 0), - marks_(0) -{ - value(0); -} - - -// Draw cell -void -SudokuCell::draw() { - static Fl_Align align[10] = { - 0, - FL_ALIGN_TOP_LEFT, - FL_ALIGN_TOP, - FL_ALIGN_TOP_RIGHT, - FL_ALIGN_LEFT, - 0, - FL_ALIGN_RIGHT, - FL_ALIGN_BOTTOM_LEFT, - FL_ALIGN_BOTTOM, - FL_ALIGN_BOTTOM_RIGHT, - }; - - - // Draw the cell box... - fl_draw_box(FL_BORDER_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'; - - if (readonly()) - fl_font(FL_HELVETICA_BOLD, h() - 10); - else - fl_font(FL_HELVETICA, h() - 10); - fl_draw(s, x(), y(), w(), h(), FL_ALIGN_CENTER); - } - - fl_font(FL_HELVETICA_BOLD, h()*2/9); - - for (int i = 1; i <= 9; i ++) { - if (mark(i)) { - s[0] = 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)) { - toggle_mark(key); - value_ = 0; - 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); -} - -void SudokuCell::mark(int n, bool set) { - if (n<1 || n>9) return; - if (set) { - marks_ |= (1<9) return; - marks_ ^= (1<9) return 0; - return (marks_>>n) & 1; -} - -void SudokuCell::clear_marks() { - marks_ = 0; -} - - - diff --git a/test/sudoku_cell.h b/test/sudoku_cell.h deleted file mode 100644 index 2fc2b9cf7..000000000 --- a/test/sudoku_cell.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// Sudoku game cell using the Fast Light Tool Kit (FLTK). -// -// Copyright 2005-2018 by Michael Sweet. -// Copyright 2019-2021 by Bill Spitzak and others. -// -// 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 -// - -#ifndef _SUDOKU_CELL_H_ -#define _SUDOKU_CELL_H_ - -#include - -// Sudoku cell class... -class SudokuCell : public Fl_Widget { - bool readonly_; - int value_; - int marks_; - - 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 mark(int n, bool set); - void toggle_mark(int n); - bool mark(int n); - void clear_marks(); - void value(int v) { - value_ = v; - clear_marks(); - redraw(); - } - int value() const { return value_; } -}; - - -#endif // _SUDOKU_CELL_H_ diff --git a/test/sudoku_generator.cxx b/test/sudoku_generator.cxx deleted file mode 100644 index 36a74850f..000000000 --- a/test/sudoku_generator.cxx +++ /dev/null @@ -1,599 +0,0 @@ -// -// Sudoku game generator using the Fast Light Tool Kit (FLTK). -// -// Copyright (c) 2018 Vaibhav Thakkar. -// Copyright 2023 by Vaibhav Thakkar and Matthias Melcher. -// -// 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 -// - -// -// This solver is based on the work of Vaibhav Thakkar -// from https://github.com/vaithak/Sudoku-Generator -// vaithak/Sudoku-Generator is licensed under the MIT License -// Copyright (c) 2018 Vaibhav Thakkar -// -// The solver was modified to fit FLTKs requirements of using minimal C++ -// and adapted to the FLTK naming scheme. -// - -#include "sudoku_generator.h" - -#include -#include -#include -#include - -#define UNASSIGNED 0 - -void Sudoku_Generator::restoreWorkGrid() -{ - for(int i=0;i<9;i++) - for(int j=0;j<9;j++) - this->grid[i][j] = this->solnGrid[i][j]; -} - -// Generate a random number between 0 and maxLimit-1 -int genRandNum(int maxLimit) -{ - return rand()%maxLimit; -} - -// We take an integer array of the length n and swap the content around randomly. -// The function was part of the c++11 standard, but was removed in C++17 because -// it was regarded as flawed. -// This implementation is a minimal hack without care about random dstribution. -// For additional randomness, we do that three times. -void random_shuffle(int *data, int n) -{ - for (int z = 3; z>0; --z) { - for (int i = n-1; i > 0; --i) { - int j = rand() % (i+1); - int tmp = data[i]; - data[i] = data[j]; - data[j] = tmp; - } - } -} - -// Helper functions for solving grid -bool FindUnassignedLocation(const GridData &grid, int &row, int &col) -{ - for (row = 0; row < 9; row++) - { - for (col = 0; col < 9; col++) - { - if (grid[row][col] == UNASSIGNED) - return true; - } - } - return false; -} - -// Return true if num exists in row of grid. -bool UsedInRow(const GridData &grid, int row, int num) -{ - for (int col = 0; col < 9; col++) - { - if (grid[row][col] == num) - return true; - } - return false; -} - -// Return true if num exists in col of grid. -bool UsedInCol(const GridData &grid, int col, int num) -{ - for (int row = 0; row < 9; row++) - { - if (grid[row][col] == num) - return true; - } - return false; -} - -// Return true if num exists in box at row, col of grid. -bool UsedInBox(const GridData grid, int boxStartRow, int boxStartCol, int num) -{ - for (int row = 0; row < 3; row++) - { - for (int col = 0; col < 3; col++) - { - if (grid[row+boxStartRow][col+boxStartCol] == num) - return true; - } - } - return false; -} - -// Return true if a number can be used at row, col and does not appear -// yet in the rom, column, or box -bool isSafe(GridData &grid, int row, int col, int num) -{ - return !UsedInRow(grid, row, num) && !UsedInCol(grid, col, num) && !UsedInBox(grid, row - row%3 , col - col%3, num); -} - -// Fill the box at idx*3, idx*3 with random values -void Sudoku_Generator::fillEmptyDiagonalBox(int idx) -{ - int start = idx*3; - random_shuffle(guessNum, 9); - for (int i = 0; i < 3; ++i) - { - for (int j = 0; j < 3; ++j) - { - this->grid[start+i][start+j] = guessNum[i*3+j]; - } - } -} - -// Create a solved Sudoku puzzle -void Sudoku_Generator::createSeed() -{ - /* Fill diagonal boxes to form: - x | . | . - . | x | . - . | . | x - */ - this->fillEmptyDiagonalBox(0); - this->fillEmptyDiagonalBox(1); - this->fillEmptyDiagonalBox(2); - - /* Fill the remaining blocks: - x | x | x - x | x | x - x | x | x - */ - this->solveGrid(); // Not truly random, but still good enough because we generate random diagonals. - - // Saving the solution grid - for(int i=0;i<9;i++) - { - for(int j=0;j<9;j++) - { - this->solnGrid[i][j] = this->grid[i][j]; - } - } -} - -// Initialize the egenrator -Sudoku_Generator::Sudoku_Generator() -{ - // initialize difficulty level - this->difficultyLevel = 0; - - // Randomly shuffling the array of removing grid positions - for(int i=0;i<81;i++) - { - this->gridPos[i] = i; - } - - random_shuffle(gridPos, 81); - - // Randomly shuffling the guessing number array - for(int i=0;i<9;i++) - { - this->guessNum[i]=i+1; - } - - random_shuffle(guessNum, 9); - - // Initialising the grid - for(int i=0;i<9;i++) - { - for(int j=0;j<9;j++) - { - this->grid[i][j]=0; - } - } - - grid_status = true; -} - -// Custom Initialising with grid passed as argument -Sudoku_Generator::Sudoku_Generator(int grid_data[81], bool row_major) -{ - // First pass: Check if all cells are valid - for(int i=0; i<81; ++i) - { - int curr_num = grid_data[i]; - if(!((curr_num == UNASSIGNED) || (curr_num > 0 && curr_num < 10))) - { - grid_status=false; - return; - } - - if(row_major) grid[i/9][i%9] = curr_num; - else grid[i%9][i/9] = curr_num; - } - - // Second pass: Check if all columns are valid - for (int col_num=0; col_num<9; ++col_num) - { - bool nums[10]={false}; - for (int row_num=0; row_num<9; ++row_num) - { - int curr_num = grid[row_num][col_num]; - if(curr_num!=UNASSIGNED && nums[curr_num]==true) - { - grid_status=false; - return; - } - nums[curr_num] = true; - } - } - - // Third pass: Check if all rows are valid - for (int row_num=0; row_num<9; ++row_num) - { - bool nums[10]={false}; - for (int col_num=0; col_num<9; ++col_num) - { - int curr_num = grid[row_num][col_num]; - if(curr_num!=UNASSIGNED && nums[curr_num]==true) - { - grid_status=false; - return; - } - nums[curr_num] = true; - } - } - - // Fourth pass: Check if all blocks are valid - for (int block_num=0; block_num<9; ++block_num) - { - bool nums[10]={false}; - for (int cell_num=0; cell_num<9; ++cell_num) - { - int curr_num = grid[((int)(block_num/3))*3 + (cell_num/3)][((int)(block_num%3))*3 + (cell_num%3)]; - if(curr_num!=UNASSIGNED && nums[curr_num]==true) - { - grid_status=false; - return; - } - nums[curr_num] = true; - } - } - - // Randomly shuffling the guessing number array - for(int i=0;i<9;i++) - { - this->guessNum[i]=i+1; - } - - random_shuffle(guessNum, 9); - - grid_status = true; -} - -// Return status of the custom grid passed. -bool Sudoku_Generator::gridStatus() -{ - return grid_status; -} - -// Printing the grid -void Sudoku_Generator::printGrid() -{ - for(int i=0;i<9;i++) - { - for(int j=0;j<9;j++) - { - if(grid[i][j] == 0) - printf(" ."); - else - printf(" %d", grid[i][j]); - if (((j%3) == 2) && (j < 8)) - printf(" |"); - } - printf("\n"); - if (((i%3) == 2) && (i < 8)) - printf("-------+-------+-------\n"); - } - printf("\nDifficulty of current sudoku(0 being easiest): %d\n", difficultyLevel); -} - -// Modified Sudoku solver -bool Sudoku_Generator::solveGrid() -{ - int row, col; - - // If there is no unassigned location, we are done - if (!FindUnassignedLocation(this->grid, row, col)) - return true; // success! - - // Consider digits 1 to 9 - for (int num = 0; num < 9; num++) - { - // if looks promising - if (isSafe(this->grid, row, col, this->guessNum[num])) - { - // make tentative assignment - this->grid[row][col] = this->guessNum[num]; - - // return, if success, yay! - if (solveGrid()) - return true; - - // failure, unmake & try again - this->grid[row][col] = UNASSIGNED; - } - } - - return false; // this triggers backtracking -} - -// Check if the grid is uniquely solvable -void Sudoku_Generator::countSoln(int &number) -{ - int row, col; - - if(!FindUnassignedLocation(this->grid, row, col)) - { - number++; - return ; - } - - - for(int i=0;i<9 && number<2;i++) - { - if( isSafe(this->grid, row, col, this->guessNum[i]) ) - { - this->grid[row][col] = this->guessNum[i]; - countSoln(number); - } - - this->grid[row][col] = UNASSIGNED; - } - -} -// END: Check if the grid is uniquely solvable - - -// START: Generate puzzle -void Sudoku_Generator::genPuzzle(int minHints) -{ - int numHints = 81; - for(int i=0;i<81;i++) - { - int x = (this->gridPos[i])/9; - int y = (this->gridPos[i])%9; - int temp = this->grid[x][y]; - this->grid[x][y] = UNASSIGNED; - - // If now more than 1 solution , replace the removed cell back. - int check=0; - countSoln(check); - if(check!=1) - { - this->grid[x][y] = temp; -// printf("Can't remove cell %d\n", i); - } else { - numHints--; - if (numHints <= minHints) break; - } - } - printf("Found %d hints\n", numHints); -} -// END: Generate puzzle - - -// START: Calculate branch difficulty score -int Sudoku_Generator::branchDifficultyScore() -{ - int emptyPositions = -1; - GridData tempGrid; - int sum=0; - - for(int i=0;i<9;i++) - { - for(int j=0;j<9;j++) - { - tempGrid[i][j] = this->grid[i][j]; - } - } - - while(emptyPositions!=0) - { - Fl_Int_Vector empty[81]; - int empty_n = 0; - - for(int i=0;i<81;i++) - { - if(tempGrid[(int)(i/9)][(int)(i%9)] == 0) - { - Fl_Int_Vector temp; - temp.push_back(i); - - for(int num=1;num<=9;num++) - { - if(isSafe(tempGrid,i/9,i%9,num)) - { - temp.push_back(num); - } - } - - empty[empty_n++] = temp; - } - - } - - if(empty_n == 0) - { - return sum; - } - - int minIndex = 0; - - int check = empty_n; - for(int i=0;isolnGrid[rowIndex][colIndex]; - sum = sum + ((branchFactor-2) * (branchFactor-2)) ; - - emptyPositions = empty_n - 1; - } - - return sum; - -} -// END: Finish branch difficulty score - - -// START: Calculate difficulty level of current grid -void Sudoku_Generator::calculateDifficulty() -{ - int B = branchDifficultyScore(); - int emptyCells = 0; - - for(int i=0;i<9;i++) - { - for(int j=0;j<9;j++) - { - if(this->grid[i][j] == 0) - emptyCells++; - } - } - - this->difficultyLevel = B*100 + emptyCells; -} -// END: calculating difficulty level - - -// START: The main function -int generate_sudoku(int grid_data[81], int minHints, int maxHints) -{ -#if 0 - int i, j; - FILE *f = fopen("/Users/matt/dev/su.cxx", "wb"); - fprintf(f, "// all horizontal chains\n"); - for (i=0; i<9; i++) { - fprintf(f, "{ "); - for (j=0; j<9; j++) { - fprintf(f, "%2d, ", i*9+j); - } - fprintf(f, "},\n"); - } - fprintf(f, "// all vertical chains\n"); - for (i=0; i<9; i++) { - fprintf(f, "{ "); - for (j=0; j<9; j++) { - fprintf(f, "%2d, ", j*9+i); - } - fprintf(f, "},\n"); - } - fprintf(f, "// all squares\n"); - for (i=0; i<9; i++) { - fprintf(f, "{ "); - for (j=0; j<9; j++) { - fprintf(f, "%2d, ", ((i%3)*3) + ((i/3)*3*9) + (j%3) + (j/3)*9); - } - fprintf(f, "},\n"); - } - fprintf(f, "// every field is part of 3 chains\n"); - for (i=0; i<81; i++) { - fprintf(f, "{ "); - int col = i % 9; - int row = i / 9; - fprintf(f, " %2d, %2d, %2d ", - i/9, i%9, (col/3) + (row/3)*3 - ); - fprintf(f, "},\n"); - } - fclose(f); -#endif - - - // Initialising seed for random number generation - srand((unsigned int)time(NULL)); - - // Creating an instance of Sudoku - Sudoku_Generator *puzzle = new Sudoku_Generator(); - - // Creating a seed for puzzle generation - puzzle->createSeed(); - - // Generating the puzzle - puzzle->genPuzzle(minHints); -// int minDiff = 100, maxDiff = 0; -// for (int zz=0; zz<100; zz++) { -// time_t start; time(&start); -// random_shuffle(puzzle->gridPos, 81); -// puzzle->restoreWorkGrid(); -// puzzle->genPuzzle(minHints); -// time_t end; time(&end); -// puzzle->calculateDifficulty(); -// printf("--- in %ld, difficulty is %d\n", end-start, puzzle->difficultyLevel); -// if (puzzle->difficultyLevel < minDiff) minDiff = puzzle->difficultyLevel; -// if (puzzle->difficultyLevel > maxDiff) maxDiff = puzzle->difficultyLevel; -// } -// printf("Difficulty range is %d to %d\n", minDiff, maxDiff); - // 22: 55 to 1658 - // 25: 56 to 1456 - // 28: 53 to 953, 53 to 1153, 53 to 1253 - // 31: 50 to 850, 50 to 950, 50 to 850 - // 40: 41 to 141 - // 45: 36 to 136 - - - // Calculating difficulty of puzzle - puzzle->calculateDifficulty(); - - // testing by printing the grid - puzzle->printGrid(); - -// // Printing the grid into SVG file -// string rem = "sudokuGen"; -// string path = argv[0]; -// path = path.substr(0,path.size() - rem.size()); -// puzzle->printSVG(path); -// cout<<"The above sudoku puzzle has been stored in puzzles.svg in current folder\n"; -// // freeing the memory - -// puzzle->printGrid(); - printf("Difficulty: %d\n", puzzle->difficultyLevel); -// for (int d = 0; d<9; d++) { -// int x = 0, y = 0; -// for (;;) { -// x = genRandNum(9); -// y = genRandNum(9); -// if (puzzle->grid[x][y] == 0) break; -// } -// puzzle->grid[x][y] = puzzle->solnGrid[x][y]; -// printf(" %d %d\n", x, y); -// puzzle->calculateDifficulty(); -// printf("Difficulty: %d\n", puzzle->difficultyLevel); -// } - - int *g = grid_data; - for(int i=0;i<9;i++) { - for(int j=0;j<9;j++) { - if (puzzle->grid[i][j] == UNASSIGNED) { - *g++ = -puzzle->solnGrid[i][j]; - } else { - *g++ = puzzle->solnGrid[i][j]; - } - } - } - - delete puzzle; - - return 0; -} -// END: The main function diff --git a/test/sudoku_generator.h b/test/sudoku_generator.h deleted file mode 100644 index 4510adc67..000000000 --- a/test/sudoku_generator.h +++ /dev/null @@ -1,60 +0,0 @@ -// -// Sudoku game generator using the Fast Light Tool Kit (FLTK). -// -// Copyright (c) 2018 Vaibhav Thakkar. -// Copyright 2023 by Vaibhav Thakkar and Matthias Melcher. -// -// 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 -// - -// -// This solver is based on the work of Vaibhav Thakkar -// from https://github.com/vaithak/Sudoku-Generator -// vaithak/Sudoku-Generator is licensed under the MIT License -// Copyright (c) 2018 Vaibhav Thakkar -// -// The solver was modified to fit FLTKs requirements of using minimal C++ -// and adapted to the FLTK naming scheme. -// - -#ifndef _SUDOKU_GENERATOR_H_ -#define _SUDOKU_GENERATOR_H_ - -typedef int GridData[9][9]; - -extern int generate_sudoku(int grid_data[81], int minHints, int maxHints); - -class Sudoku_Generator { -private: -public: - GridData grid; - GridData solnGrid; - int guessNum[9]; - int gridPos[81]; - int difficultyLevel; - bool grid_status; - -public: - Sudoku_Generator (); - Sudoku_Generator(int[81], bool row_major=true); - void fillEmptyDiagonalBox(int); - void createSeed(); - void printGrid(); - bool solveGrid(); - void countSoln(int &number); - void genPuzzle(int minHints); - bool gridStatus(); - void calculateDifficulty(); - void restoreWorkGrid(); - int branchDifficultyScore(); -}; - -#endif // _SUDOKU_GENERATOR_H_ diff --git a/test/sudoku_puzzle.cxx b/test/sudoku_puzzle.cxx deleted file mode 100644 index cc693b583..000000000 --- a/test/sudoku_puzzle.cxx +++ /dev/null @@ -1,846 +0,0 @@ -// -// Sudoku game puzzle using the Fast Light Tool Kit (FLTK). -// -// Copyright 2005-2018 by Michael Sweet. -// Copyright 2019-2021 by Bill Spitzak and others. -// -// 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 "sudoku_puzzle.h" -#include "sudoku.h" - -#include - -SudokuPuzzle::SudokuPuzzle(int x, int y, int w, int h, const char *label) -: Fl_Group(x, y, w, h, label) -{ - box(FL_BORDER_BOX); -} - -#if 0 -// 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_cell.h" -#include "sudoku_sound.h" -#include "sudoku_generator.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "pixmaps/sudoku.xbm" - -// -// 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__ - - -// Sudoku class globals... -Fl_Help_Dialog *Sudoku::help_dialog_ = (Fl_Help_Dialog *)0; -Fl_Preferences Sudoku::prefs_(Fl_Preferences::USER_L, "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", FL_COMMAND | 'u', 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 - // 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(); - } - } - - // 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; m <= 9; m ++) { - if (!taken[m]) - dst_cell->mark(m, true); - } - dst_cell->redraw(); - } -} - - -// Show the on-line help... -void -Sudoku::help_cb(Fl_Widget *, void *) { - if (!help_dialog_) { - help_dialog_ = new Fl_Help_Dialog(); - - help_dialog_->value( - "\n" - "\n" - "Sudoku Help\n" - "\n" - "\n" - - "

About the Game

\n" - - "

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.

\n" - - "

This version of the puzzle is copyright 2005-2010 by Michael R\n" - "Sweet.

\n" - - "

Note: 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'.

\n" - - "

How to Play the Game

\n" - - "

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.

\n" - - "

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.

\n" - "\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; - bool empty = false; - - for (int j = 0; j < 9; j ++) { - Fl_Preferences row(prefs_, Fl_Preferences::Name("Row%d", j)); - for (int k = 0; k < 9; k ++) { - Fl_Preferences p(row, Fl_Preferences::Name("Col%d", k)); - int v; - - SudokuCell *cell = grid_cells_[j][k]; - - p.get("value", v, 0); - grid_values_[j][k] = v; - if (v) empty = false; - - p.get("state", v, 0); - cell->value(v); - - p.set("readonly", cell->readonly()); - cell->readonly(v != 0); - if (v) { - 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); - } - cell->redraw(); - } - } - - // 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 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; - - - // 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 ++) { - Fl_Preferences row(prefs_, Fl_Preferences::Name("Row%d", 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)); - } - } - } -} - - -// 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... -// 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; - - // Show the game... - s.show(argc, argv); - - // Load the previous game... - s.load_game(); - - // Run until the user quits... - return (Fl::run()); -} - -#endif diff --git a/test/sudoku_puzzle.h b/test/sudoku_puzzle.h deleted file mode 100644 index b1e5609c3..000000000 --- a/test/sudoku_puzzle.h +++ /dev/null @@ -1,76 +0,0 @@ -// -// Sudoku game puzzle using the Fast Light Tool Kit (FLTK). -// -// Copyright 2005-2018 by Michael Sweet. -// Copyright 2019-2021 by Bill Spitzak and others. -// -// 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 -// - -#ifndef _SUDOKU_PUZZLE_H_ -#define _SUDOKU_PUZZLE_H_ - -#include - -class SudokuPuzzle : public Fl_Group { -public: - SudokuPuzzle(int x, int y, int w, int h, const char *label=NULL); -}; - -#if 0 -class SudokuCell; -class SudokuSound; -class Fl_Sys_Menu_Bar; -class Fl_Help_Dialog; - -// 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(); -}; - -#endif - -#endif // _SUDOKU_PUZZLE_H_ diff --git a/test/sudoku_sound.cxx b/test/sudoku_sound.cxx deleted file mode 100644 index 887313298..000000000 --- a/test/sudoku_sound.cxx +++ /dev/null @@ -1,308 +0,0 @@ -// -// Sudoku game using the Fast Light Tool Kit (FLTK). -// -// Copyright 2005-2018 by Michael Sweet. -// Copyright 2019-2021 by Bill Spitzak and others. -// -// 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 "sudoku_sound.h" - -#include -#include - -#include - -// 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 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__ -} - diff --git a/test/sudoku_sound.h b/test/sudoku_sound.h deleted file mode 100644 index dd7f6fe3c..000000000 --- a/test/sudoku_sound.h +++ /dev/null @@ -1,97 +0,0 @@ -// -// Sudoku game sound using the Fast Light Tool Kit (FLTK). -// -// Copyright 2005-2018 by Michael Sweet. -// Copyright 2019-2023 by Bill Spitzak and others. -// -// 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 -// - -#ifndef _SUDOKU_SOUND_H_ -#define _SUDOKU_SOUND_H_ - -// Audio headers... -#include - -#ifdef HAVE_ALSA_ASOUNDLIB_H -# define ALSA_PCM_NEW_HW_PARAMS_API -# include -#endif // HAVE_ALSA_ASOUNDLIB_H -#ifdef __APPLE__ -# include -#endif // __APPLE__ -#ifdef _WIN32 -# include -#endif // _WIN32 - -// 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); -}; - -#endif // _SUDOKU_SOUND_H_ -- cgit v1.2.3