Lot's of changes, must rethink UI.
This commit is contained in:
parent
af22676abf
commit
3b093e895b
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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_;
|
||||
|
||||
@ -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
846
test/sudoku_puzzle.cxx
Normal 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
76
test/sudoku_puzzle.h
Normal 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_
|
||||
Loading…
Reference in New Issue
Block a user