diff options
| -rw-r--r-- | test/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | test/sudoku.cxx | 63 | ||||
| -rw-r--r-- | test/sudoku.h | 10 | ||||
| -rw-r--r-- | test/sudoku_cell.cxx | 8 | ||||
| -rw-r--r-- | test/sudoku_puzzle.cxx | 846 | ||||
| -rw-r--r-- | test/sudoku_puzzle.h | 76 |
6 files changed, 986 insertions, 19 deletions
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d1cda15cc..30b9ff542 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_cell.cxx;sudoku_sound.cxx;sudoku_generator.cxx;sudoku.plist;sudoku.icns;sudoku.rc" "fltk_images;fltk;${AUDIOLIBS}") +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 (symbols symbols.cxx fltk) CREATE_EXAMPLE (tabs tabs.fl fltk) CREATE_EXAMPLE (table table.cxx fltk) diff --git a/test/sudoku.cxx b/test/sudoku.cxx index 629dba3fb..fc2003dc3 100644 --- a/test/sudoku.cxx +++ b/test/sudoku.cxx @@ -27,7 +27,27 @@ // 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" @@ -57,6 +77,7 @@ // Default sizes... // + #define GROUP_SIZE 160 #define CELL_SIZE 50 #define CELL_OFFSET 5 @@ -74,7 +95,9 @@ 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") + : Fl_Double_Window(kPuzzleSize + 2*kPadding, + kPuzzleSize + 2*kPadding + kMenuOffset, + "FLTK Sudoku") { int j, k; Fl_Group *g; @@ -120,28 +143,38 @@ Sudoku::Sudoku() menubar_->menu(items); // Create the grids... - grid_ = new Fl_Group(0, MENU_OFFSET, 3 * GROUP_SIZE, 3 * GROUP_SIZE); + 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); 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; + 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(); } 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 = new SudokuCell(grid_->x() + 2 + k*kCellSize + (k/3), + grid_->y() + 2 + j*kCellSize + (j/3), + kCellSize, kCellSize); cell->callback(reset_cb); + cell->hide(); grid_cells_[j][k] = cell; } diff --git a/test/sudoku.h b/test/sudoku.h index 817a9ad5d..5b3cebe24 100644 --- a/test/sudoku.h +++ b/test/sudoku.h @@ -26,6 +26,16 @@ 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_; diff --git a/test/sudoku_cell.cxx b/test/sudoku_cell.cxx index 671bde4bd..a718ecf5c 100644 --- a/test/sudoku_cell.cxx +++ b/test/sudoku_cell.cxx @@ -48,8 +48,7 @@ SudokuCell::draw() { // 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()); + fl_draw_box(FL_BORDER_BOX, x(), y(), w(), h(), color()); // Draw the cell background... if (Fl::focus() == this) { @@ -67,7 +66,10 @@ SudokuCell::draw() { if (value_) { s[0] = value_ + '0'; - fl_font(FL_HELVETICA_BOLD, h() - 10); + 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); } diff --git a/test/sudoku_puzzle.cxx b/test/sudoku_puzzle.cxx new file mode 100644 index 000000000..cc693b583 --- /dev/null +++ b/test/sudoku_puzzle.cxx @@ -0,0 +1,846 @@ +// +// 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 <FL/fl_draw.H> + +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 <FL/Fl.H> +#include <FL/Enumerations.H> +#include <FL/Fl_Double_Window.H> +#include <FL/Fl_Button.H> +#include <FL/Fl_Group.H> +#include <FL/fl_ask.H> +#include <FL/fl_draw.H> +#include <FL/Fl_Help_Dialog.H> +#include <FL/Fl_Preferences.H> +#include <FL/Fl_Sys_Menu_Bar.H> +#include <FL/platform.H> +#include <FL/Fl_Image_Surface.H> +#include <FL/Fl_Bitmap.H> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <FL/math.h> + +#include "pixmaps/sudoku.xbm" + +// +// 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( + "<HTML>\n" + "<HEAD>\n" + "<TITLE>Sudoku Help</TITLE>\n" + "</HEAD>\n" + "<BODY BGCOLOR='#ffffff'>\n" + + "<H2>About the Game</H2>\n" + + "<P>Sudoku (pronounced soo-dough-coo with the emphasis on the\n" + "first syllable) is a simple number-based puzzle/game played on a\n" + "9x9 grid that is divided into 3x3 subgrids. The goal is to enter\n" + "a number from 1 to 9 in each cell so that each number appears\n" + "only once in each column and row. In addition, each 3x3 subgrid\n" + "may only contain one of each number.</P>\n" + + "<P>This version of the puzzle is copyright 2005-2010 by Michael R\n" + "Sweet.</P>\n" + + "<P><B>Note:</B> The 'Hard' and 'Impossible' difficulty\n" + "levels generate Sudoku puzzles with multiple possible solutions.\n" + "While some purists insist that these cannot be called 'Sudoku'\n" + "puzzles, the author (me) has personally solved many such puzzles\n" + "in published/printed Sudoku books and finds them far more\n" + "interesting than the simple single solution variety. If you don't\n" + "like it, don't play with the difficulty set to 'High' or\n" + "'Impossible'.</P>\n" + + "<H2>How to Play the Game</H2>\n" + + "<P>At the start of a new game, Sudoku fills in a random selection\n" + "of cells for you - the number of cells depends on the difficulty\n" + "level you use. Click in any of the empty cells or use the arrow\n" + "keys to highlight individual cells and press a number from 1 to 9\n" + "to fill in the cell. To clear a cell, press 0, Delete, or\n" + "Backspace. When you have successfully completed all subgrids, the\n" + "entire puzzle is highlighted in green until you start a new\n" + "game.</P>\n" + + "<P>As you work to complete the puzzle, you can display possible\n" + "solutions inside each cell by holding the Shift key and pressing\n" + "each number in turn. Repeat the process to remove individual\n" + "numbers, or press a number without the Shift key to replace them\n" + "with the actual number to use.</P>\n" + "</BODY>\n" + ); + } + + help_dialog_->show(); +} + + +// Load the game from saved preferences... +void +Sudoku::load_game() { + + // Load the current values and state of each grid... + memset(grid_values_, 0, sizeof(grid_values_)); + + bool solved = true; + 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 new file mode 100644 index 000000000..b1e5609c3 --- /dev/null +++ b/test/sudoku_puzzle.h @@ -0,0 +1,76 @@ +// +// 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 <FL/Fl_Group.H> + +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_ |
