Lot's of changes, must rethink UI.

This commit is contained in:
Matthias Melcher 2023-08-15 10:49:05 +02:00
parent af22676abf
commit 3b093e895b
6 changed files with 986 additions and 19 deletions

View File

@ -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)

View File

@ -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;
}

View File

@ -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_;

View File

@ -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);
}

846
test/sudoku_puzzle.cxx Normal file
View File

@ -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

76
test/sudoku_puzzle.h Normal file
View File

@ -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_