fltk/test/sudoku.cxx

720 lines
15 KiB
C++
Raw Normal View History

//
// "$Id$"
//
// Sudoku game using the Fast Light Tool Kit (FLTK).
//
// Copyright 2005 by Michael Sweet.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA.
//
// Please report all bugs and problems on the following page:
//
// http://www.fltk.org/str.php
//
#include <FL/Fl.H>
#include <FL/Enumerations.H>
#include <FL/Fl_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_Preferences.H>
#include <FL/Fl_Sys_Menu_Bar.H>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
//
// 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 cell class...
class SudokuCell : public Fl_Widget {
bool readonly_;
int value_;
int test_value_[8];
public:
SudokuCell(int X, int Y, int W, int H);
void draw();
int handle(int event);
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_; }
};
// 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 && !readonly()) {
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 :
if (!readonly()) {
Fl::focus(this);
redraw();
return 1;
}
break;
case FL_UNFOCUS :
redraw();
return 1;
break;
case FL_PUSH :
if (!readonly() && Fl::event_inside(this)) {
Fl::focus(this);
redraw();
return 1;
}
break;
case FL_KEYDOWN :
int key = Fl::event_key() - '0';
if (key > 0 && key <= 9) {
if (Fl::event_state() & FL_SHIFT) {
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) {
value(0);
do_callback();
return 1;
}
break;
}
return Fl_Widget::handle(event);
}
// Sudoku window class...
class Sudoku : public Fl_Window {
Fl_Sys_Menu_Bar *menubar_;
Fl_Group *grid_;
char grid_values_[9][9];
SudokuCell *grid_cells_[9][9];
Fl_Group *grid_groups_[3][3];
int difficulty_;
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 new_cb(Fl_Widget *widget, void *);
static void reset_cb(Fl_Widget *widget, void *);
void set_title();
static void solve_cb(Fl_Widget *widget, void *);
static Fl_Preferences prefs_;
public:
Sudoku();
void check_game(bool highlight = true);
void load_game();
void new_game();
void resize(int X, int Y, int W, int H);
void save_game();
void solve_game();
};
// Preferences/saved state for game...
Fl_Preferences Sudoku::prefs_(Fl_Preferences::USER, "fltk.org", "sudoku");
// Create a Sudoku game window...
Sudoku::Sudoku()
: Fl_Window(GROUP_SIZE * 3, GROUP_SIZE * 3 + MENU_OFFSET, "Sudoku")
{
int i, j, k, m;
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 },
{ "&Solve Game", FL_COMMAND | 's', solve_cb, 0, FL_MENU_DIVIDER },
{ "&Quit", FL_COMMAND | 'q', close_cb, 0, 0 },
{ 0 },
{ "&Difficulty", 0, 0, 0, FL_SUBMENU },
{ "&Easy", FL_COMMAND | '1', diff_cb, (void *)"0", FL_MENU_RADIO },
{ "&Medium", FL_COMMAND | '2', diff_cb, (void *)"1", FL_MENU_RADIO },
{ "&Hard", FL_COMMAND | '3', diff_cb, (void *)"2", FL_MENU_RADIO },
{ "&Impossible", FL_COMMAND | '4', diff_cb, (void *)"3", FL_MENU_RADIO },
{ 0 },
{ 0 }
};
// Menubar...
prefs_.get("difficulty", difficulty_, 0);
if (difficulty_ < 0 || difficulty_ > 3) difficulty_ = 0;
items[7 + 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 (i = 0; i < 3; i ++)
for (j = 0; j < 3; j ++) {
g = new Fl_Group(j * GROUP_SIZE, i * GROUP_SIZE + MENU_OFFSET,
GROUP_SIZE, GROUP_SIZE);
g->box(FL_BORDER_BOX);
g->color(FL_DARK3);
grid_groups_[i][j] = g;
for (k = 0; k < 3; k ++)
for (m = 0; m < 3; m ++) {
cell = new SudokuCell(j * GROUP_SIZE + CELL_OFFSET + m * CELL_SIZE,
i * GROUP_SIZE + CELL_OFFSET + k * CELL_SIZE +
MENU_OFFSET,
CELL_SIZE, CELL_SIZE);
cell->callback(reset_cb);
grid_cells_[i * 3 + k][j * 3 + m] = cell;
}
g->end();
}
callback(close_cb);
resizable(grid_);
// 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, X, W, H);
}
// Load the previous game...
load_game();
set_title();
}
// 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 i, j;
// Check the game for right/wrong answers...
for (i = 0; i < 9; i ++)
for (j = 0; j < 9; j ++) {
SudokuCell *cell = grid_cells_[i][j];
int val = cell->value();
if (!val) empty = true;
if (val && grid_values_[i][j] != val) {
if (highlight) {
cell->color(FL_YELLOW);
cell->redraw();
}
correct = false;
}
}
if (!empty && correct) {
// Success!
solve_game();
fl_message("Congratulations, you solved the game!");
} else {
int k, m;
for (i = 0; i < 9; i += 3)
for (j = 0; j < 9; j += 3) {
correct = true;
for (k = 0; correct && k < 3; k ++)
for (m = 0; m < 3; m ++)
if (grid_cells_[i + k][j + m]->value() !=
grid_values_[i + k][j + m]) {
correct = false;
break;
}
if (correct) {
for (k = 0; k < 3; k ++)
for (m = 0; m < 3; m ++) {
SudokuCell *cell = grid_cells_[i + k][j + m];
cell->readonly(1);
cell->color(fl_color_average(FL_GRAY, FL_GREEN, 0.5f));
}
}
}
}
}
// 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();
}
// Set the level of difficulty...
void
Sudoku::diff_cb(Fl_Widget *widget, void *d) {
Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget);
s->difficulty_ = atoi((char *)d);
s->new_game();
s->set_title();
prefs_.set("difficulty", s->difficulty_);
}
// 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;
for (int i = 0; i < 9; i ++)
for (int j = 0; j < 9; j ++) {
char name[255];
int val;
SudokuCell *cell = grid_cells_[i][j];
sprintf(name, "value%d.%d", i, j);
if (!prefs_.get(name, val, 0)) {
i = 9;
grid_values_[0][0] = 0;
break;
}
grid_values_[i][j] = val;
sprintf(name, "state%d.%d", i, j);
prefs_.get(name, val, 0);
cell->value(val);
sprintf(name, "readonly%d.%d", i, j);
prefs_.get(name, val, 0);
cell->readonly(val);
if (val) cell->color(FL_GRAY);
else {
cell->color(FL_LIGHT3);
solved = false;
}
for (int k = 0; k < 8; k ++) {
sprintf(name, "test%d%d.%d", k, i, j);
prefs_.get(name, val, 0);
cell->test_value(val, k);
}
}
// 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();
}
// Create a new game...
void
Sudoku::new_cb(Fl_Widget *widget, void *) {
((Sudoku *)(widget->window()))->new_game();
}
// Create a new game...
void
Sudoku::new_game() {
int i, j, k, m, t, count;
// Generate a new (valid) Sudoku grid...
srand(time(NULL));
memset(grid_values_, 0, sizeof(grid_values_));
for (i = 0; i < 9; i += 3) {
for (j = 0; j < 9; j += 3) {
for (t = 1; t <= 9; t ++) {
for (count = 0; count < 20; count ++) {
k = i + (rand() % 3);
m = j + (rand() % 3);
if (!grid_values_[k][m]) {
int kk;
for (kk = 0; kk < k; kk ++)
if (grid_values_[kk][m] == t) break;
if (kk < k) continue;
int mm;
for (mm = 0; mm < m; mm ++)
if (grid_values_[k][mm] == t) break;
if (mm < m) continue;
grid_values_[k][m] = t;
break;
}
}
if (count == 20) {
// Unable to find a valid puzzle so far, so start over...
j = 9;
i = -3;
memset(grid_values_, 0, sizeof(grid_values_));
}
}
}
}
// Start by making all cells editable
SudokuCell *cell;
for (i = 0; i < 9; i ++)
for (j = 0; j < 9; j ++) {
cell = grid_cells_[i][j];
cell->value(0);
cell->readonly(0);
cell->color(FL_LIGHT3);
}
// Show N cells...
count = 5 * (5 - difficulty_);
int numbers[9];
for (i = 0; i < 9; i ++) numbers[i] = i + 1;
while (count > 0) {
for (i = 0; i < 20; i ++) {
k = rand() % 9;
m = rand() % 9;
t = numbers[k];
numbers[k] = numbers[m];
numbers[m] = t;
}
for (i = 0; count > 0 && i < 9; i ++) {
t = numbers[i];
for (j = 0; count > 0 && j < 9; j ++) {
cell = grid_cells_[i][j];
if (grid_values_[i][j] == t && !cell->readonly()) {
cell->value(grid_values_[i][j]);
cell->readonly(1);
cell->color(FL_GRAY);
count --;
break;
}
}
}
}
// Show additional cells as needed to avoid ambiguous solutions.
// The basic premise is to find all possible numbers for each hidden
// cell and show the cell if we have more than two possible solutions.
int possible;
count = 5 * (5 - difficulty_);
while (count > 0) {
i = rand() % 9;
j = rand() % 9;
cell = grid_cells_[i][j];
if (cell->readonly()) continue;
possible = 9;
memset(numbers, 0, sizeof(numbers));
// Check vertical cells
for (k = 0; k < 9; k ++) {
cell = grid_cells_[k][j];
t = grid_values_[k][j] - 1;
if (i != k && !numbers[t] && cell->readonly()) {
possible --;
numbers[t] = 1;
}
}
// Check horizontal cells
for (m = 0; m < 9; m ++) {
cell = grid_cells_[i][m];
t = grid_values_[i][m] - 1;
if (j != m && !numbers[t] && cell->readonly()) {
possible --;
numbers[t] = 1;
}
}
// Now, if the count > 2, show this cell...
if (possible > 2) {
cell = grid_cells_[i][j];
cell->value(grid_values_[i][j]);
cell->readonly(1);
cell->color(FL_GRAY);
count --;
}
}
}
// 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) {
// Force resizes to keep the proper aspect ratio...
int M = H - MENU_OFFSET;
if (M > W) M = W;
if (W != M || H != (M + MENU_OFFSET)) {
W = M;
H = M + MENU_OFFSET;
}
// Resize the window...
Fl_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);
}
// Save the current game state...
void
Sudoku::save_game() {
// Save the current values and state of each grid...
for (int i = 0; i < 9; i ++)
for (int j = 0; j < 9; j ++) {
char name[255];
SudokuCell *cell = grid_cells_[i][j];
sprintf(name, "value%d.%d", i, j);
prefs_.set(name, grid_values_[i][j]);
sprintf(name, "state%d.%d", i, j);
prefs_.set(name, cell->value());
sprintf(name, "readonly%d.%d", i, j);
prefs_.set(name, cell->readonly());
for (int k = 0; k < 8; k ++) {
sprintf(name, "test%d%d.%d", k, i, j);
prefs_.set(name, cell->test_value(k));
}
}
}
// 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 i, j;
for (i = 0; i < 9; i ++)
for (j = 0; j < 9; j ++) {
SudokuCell *cell = grid_cells_[i][j];
cell->value(grid_values_[i][j]);
cell->readonly(1);
cell->color(fl_color_average(FL_GRAY, FL_GREEN, 0.5f));
}
}
// Main entry for game...
int
main(int argc, char *argv[]) {
Sudoku s;
s.show(argc, argv);
return (Fl::run());
}
//
// End of "$Id$".
//