Add Sudoku undo and redo.
This commit is contained in:
parent
fad1a67734
commit
44a8508ffe
143
test/sudoku.cxx
143
test/sudoku.cxx
@ -130,6 +130,8 @@ class SudokuSound {
|
|||||||
void play(char note);
|
void play(char note);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef unsigned int State;
|
||||||
|
typedef State GameState[81];
|
||||||
|
|
||||||
// Sudoku cell class...
|
// Sudoku cell class...
|
||||||
class SudokuCell : public Fl_Widget {
|
class SudokuCell : public Fl_Widget {
|
||||||
@ -137,6 +139,7 @@ class SudokuCell : public Fl_Widget {
|
|||||||
int col_;
|
int col_;
|
||||||
bool readonly_;
|
bool readonly_;
|
||||||
int value_;
|
int value_;
|
||||||
|
int solution_;
|
||||||
int hint_map_;
|
int hint_map_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -156,6 +159,14 @@ class SudokuCell : public Fl_Widget {
|
|||||||
bool hint_set(int n) const { return ((hint_map_&(1<<n))!=0); }
|
bool hint_set(int n) const { return ((hint_map_&(1<<n))!=0); }
|
||||||
void value(int v) { value_ = v; redraw(); }
|
void value(int v) { value_ = v; redraw(); }
|
||||||
int value() const { return value_; }
|
int value() const { return value_; }
|
||||||
|
State state() { return (hint_map_>>1) | (value_<<12) | (readonly_<<9); }
|
||||||
|
void state(State s) {
|
||||||
|
hint_map_ = (s & 0x000001ff) << 1;
|
||||||
|
readonly_ = (s & 0x00000200) >> 9;
|
||||||
|
value_ = (s & 0x0000f000) >> 12;
|
||||||
|
if (readonly_) color(FL_GRAY); else color(FL_LIGHT3);
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -169,6 +180,8 @@ class Sudoku : public Fl_Double_Window {
|
|||||||
Fl_Group *grid_groups_[3][3];
|
Fl_Group *grid_groups_[3][3];
|
||||||
int difficulty_;
|
int difficulty_;
|
||||||
SudokuSound *sound_;
|
SudokuSound *sound_;
|
||||||
|
GameState undo_stack[64];
|
||||||
|
int undo_head_, undo_tail_, redo_head_;
|
||||||
|
|
||||||
static void check_cb(Fl_Widget *widget, void *);
|
static void check_cb(Fl_Widget *widget, void *);
|
||||||
static void close_cb(Fl_Widget *widget, void *);
|
static void close_cb(Fl_Widget *widget, void *);
|
||||||
@ -198,6 +211,12 @@ class Sudoku : public Fl_Double_Window {
|
|||||||
void solve_game();
|
void solve_game();
|
||||||
void update_helpers();
|
void update_helpers();
|
||||||
void clear_hints_for(int row, int col, int val);
|
void clear_hints_for(int row, int col, int val);
|
||||||
|
void save_state(GameState &s);
|
||||||
|
void load_state(GameState &s);
|
||||||
|
void undo();
|
||||||
|
void redo();
|
||||||
|
void clear_undo();
|
||||||
|
void undo_checkpoint();
|
||||||
};
|
};
|
||||||
|
|
||||||
Sudoku *sudoku = NULL;
|
Sudoku *sudoku = NULL;
|
||||||
@ -217,7 +236,6 @@ int SudokuSound::frequencies[9] = {
|
|||||||
short *SudokuSound::sample_data[9] = { 0 };
|
short *SudokuSound::sample_data[9] = { 0 };
|
||||||
int SudokuSound::sample_size = 0;
|
int SudokuSound::sample_size = 0;
|
||||||
|
|
||||||
|
|
||||||
// Initialize the SudokuSound class
|
// Initialize the SudokuSound class
|
||||||
SudokuSound::SudokuSound() {
|
SudokuSound::SudokuSound() {
|
||||||
sample_size = 0;
|
sample_size = 0;
|
||||||
@ -566,7 +584,7 @@ SudokuCell::handle(int event) {
|
|||||||
else value(1);
|
else value(1);
|
||||||
} else value(sudoku->next_value(this));
|
} else value(sudoku->next_value(this));
|
||||||
}
|
}
|
||||||
|
// TODO: add this to the undo process
|
||||||
Fl::focus(this);
|
Fl::focus(this);
|
||||||
redraw();
|
redraw();
|
||||||
return 1;
|
return 1;
|
||||||
@ -589,10 +607,12 @@ SudokuCell::handle(int event) {
|
|||||||
} else {
|
} else {
|
||||||
set_hint(key);
|
set_hint(key);
|
||||||
}
|
}
|
||||||
|
sudoku->undo_checkpoint();
|
||||||
redraw();
|
redraw();
|
||||||
} else {
|
} else {
|
||||||
value(key);
|
value(key);
|
||||||
sudoku->clear_hints_for(row(), col(), key);
|
sudoku->clear_hints_for(row(), col(), key);
|
||||||
|
sudoku->undo_checkpoint();
|
||||||
do_callback();
|
do_callback();
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
@ -604,9 +624,11 @@ SudokuCell::handle(int event) {
|
|||||||
}
|
}
|
||||||
if (Fl::event_state() & (FL_SHIFT | FL_CAPS_LOCK)) {
|
if (Fl::event_state() & (FL_SHIFT | FL_CAPS_LOCK)) {
|
||||||
clear_hints();
|
clear_hints();
|
||||||
|
sudoku->undo_checkpoint();
|
||||||
} else {
|
} else {
|
||||||
value(0);
|
value(0);
|
||||||
do_callback();
|
do_callback();
|
||||||
|
sudoku->undo_checkpoint();
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -621,35 +643,49 @@ SudokuCell::handle(int event) {
|
|||||||
Fl_Help_Dialog *Sudoku::help_dialog_ = (Fl_Help_Dialog *)0;
|
Fl_Help_Dialog *Sudoku::help_dialog_ = (Fl_Help_Dialog *)0;
|
||||||
Fl_Preferences Sudoku::prefs_(Fl_Preferences::USER_L, "fltk.org", "sudoku");
|
Fl_Preferences Sudoku::prefs_(Fl_Preferences::USER_L, "fltk.org", "sudoku");
|
||||||
|
|
||||||
|
static void undo_cb(Fl_Widget*, void*) {
|
||||||
|
sudoku->undo();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redo_cb(Fl_Widget*, void*) {
|
||||||
|
sudoku->redo();
|
||||||
|
}
|
||||||
|
|
||||||
// Create a Sudoku game window...
|
// Create a Sudoku game window...
|
||||||
Sudoku::Sudoku()
|
Sudoku::Sudoku()
|
||||||
: Fl_Double_Window(GROUP_SIZE * 3, GROUP_SIZE * 3 + MENU_OFFSET, "Sudoku")
|
: Fl_Double_Window(GROUP_SIZE * 3, GROUP_SIZE * 3 + MENU_OFFSET, "Sudoku"),
|
||||||
|
undo_head_(0), undo_tail_(0), redo_head_(0)
|
||||||
{
|
{
|
||||||
int j, k;
|
int j, k;
|
||||||
Fl_Group *g;
|
Fl_Group *g;
|
||||||
SudokuCell *cell;
|
SudokuCell *cell;
|
||||||
static Fl_Menu_Item items[] = {
|
static Fl_Menu_Item items[] = {
|
||||||
{ "&Game", 0, 0, 0, FL_SUBMENU },
|
{ "&Game", 0, 0, 0, FL_SUBMENU },
|
||||||
{ "&New Game", FL_COMMAND | 'n', new_cb, 0, FL_MENU_DIVIDER },
|
{ "&New Game", FL_COMMAND | 'n', new_cb, 0, FL_MENU_DIVIDER },
|
||||||
{ "&Check Game", FL_COMMAND | 'c', check_cb, 0, 0 },
|
{ "&Check Game", FL_COMMAND | 'c', check_cb, 0, 0 },
|
||||||
{ "&Restart Game", FL_COMMAND | 'r', restart_cb, 0, 0 },
|
{ "&Restart Game", FL_COMMAND | 'r', restart_cb, 0, 0 },
|
||||||
{ "&Solve Game", FL_COMMAND | 's', solve_cb, 0, FL_MENU_DIVIDER },
|
{ "&Solve Game", FL_COMMAND | 's', solve_cb, 0, FL_MENU_DIVIDER },
|
||||||
{ "&Update Helpers", 0, update_helpers_cb, 0, 0 },
|
{ "&Update Helpers", 0, update_helpers_cb, 0, 0 },
|
||||||
{ "&Mute Sound", FL_COMMAND | 'm', mute_cb, 0, FL_MENU_TOGGLE | FL_MENU_DIVIDER },
|
{ "&Mute Sound", FL_COMMAND | 'm', mute_cb, 0, FL_MENU_TOGGLE | FL_MENU_DIVIDER },
|
||||||
#ifndef USE_MACOS
|
#ifndef USE_MACOS
|
||||||
{ "&Quit", FL_COMMAND | 'q', close_cb, 0, 0 },
|
{ "&Quit", FL_COMMAND | 'q', close_cb, 0, 0 },
|
||||||
#endif
|
#endif
|
||||||
{ 0 },
|
{ 0 },
|
||||||
|
{ "&Edit", 0, 0, 0, FL_SUBMENU },
|
||||||
|
{ "&Undo", FL_COMMAND | 'z', undo_cb },
|
||||||
|
{ "&Redo", FL_COMMAND | 'Z', redo_cb },
|
||||||
|
{ 0 },
|
||||||
{ "&Difficulty", 0, 0, 0, FL_SUBMENU },
|
{ "&Difficulty", 0, 0, 0, FL_SUBMENU },
|
||||||
{ "&Easy", 0, diff_cb, (void *)"0", FL_MENU_RADIO },
|
{ "&Easy", 0, diff_cb, (void *)"0", FL_MENU_RADIO },
|
||||||
{ "&Medium", 0, diff_cb, (void *)"1", FL_MENU_RADIO },
|
{ "&Medium", 0, diff_cb, (void *)"1", FL_MENU_RADIO },
|
||||||
{ "&Hard", 0, diff_cb, (void *)"2", FL_MENU_RADIO },
|
{ "&Hard", 0, diff_cb, (void *)"2", FL_MENU_RADIO },
|
||||||
{ "&Impossible", 0, diff_cb, (void *)"3", FL_MENU_RADIO },
|
{ "&Impossible", 0, diff_cb, (void *)"3", FL_MENU_RADIO },
|
||||||
{ 0 },
|
{ 0 },
|
||||||
|
#ifndef USE_MACOS
|
||||||
{ "&Help", 0, 0, 0, FL_SUBMENU },
|
{ "&Help", 0, 0, 0, FL_SUBMENU },
|
||||||
{ "&About Sudoku", FL_F + 1, help_cb, 0, 0 },
|
{ "&About Sudoku", FL_F + 1, help_cb, 0, 0 },
|
||||||
{ 0 },
|
{ 0 },
|
||||||
|
#endif
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -670,6 +706,9 @@ Sudoku::Sudoku()
|
|||||||
|
|
||||||
menubar_ = new Fl_Sys_Menu_Bar(0, 0, 3 * GROUP_SIZE, 25);
|
menubar_ = new Fl_Sys_Menu_Bar(0, 0, 3 * GROUP_SIZE, 25);
|
||||||
menubar_->menu(items);
|
menubar_->menu(items);
|
||||||
|
#ifdef USE_MACOS
|
||||||
|
menubar_->about(help_cb, NULL);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Create the grids...
|
// Create the grids...
|
||||||
grid_ = new Fl_Group(0, MENU_OFFSET, 3 * GROUP_SIZE, 3 * GROUP_SIZE);
|
grid_ = new Fl_Group(0, MENU_OFFSET, 3 * GROUP_SIZE, 3 * GROUP_SIZE);
|
||||||
@ -728,6 +767,7 @@ Sudoku::Sudoku()
|
|||||||
}
|
}
|
||||||
|
|
||||||
set_title();
|
set_title();
|
||||||
|
clear_undo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -843,7 +883,7 @@ Sudoku::diff_cb(Fl_Widget *widget, void *d) {
|
|||||||
|
|
||||||
if (diff != sudoku->difficulty_) {
|
if (diff != sudoku->difficulty_) {
|
||||||
sudoku->difficulty_ = diff;
|
sudoku->difficulty_ = diff;
|
||||||
sudoku->new_game(sudoku->seed_);
|
sudoku->new_cb(widget, NULL);
|
||||||
sudoku->set_title();
|
sudoku->set_title();
|
||||||
|
|
||||||
if (diff > 1)
|
if (diff > 1)
|
||||||
@ -921,6 +961,7 @@ Sudoku::update_helpers() {
|
|||||||
dst_cell->set_hint(m);
|
dst_cell->set_hint(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
undo_checkpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sudoku::clear_hints_for(int row, int col, int val) {
|
void Sudoku::clear_hints_for(int row, int col, int val) {
|
||||||
@ -1044,6 +1085,7 @@ Sudoku::load_game() {
|
|||||||
// create a new game automatically...
|
// create a new game automatically...
|
||||||
if (solved || !grid_values_[0][0]) new_game(time(NULL));
|
if (solved || !grid_values_[0][0]) new_game(time(NULL));
|
||||||
else check_game(false);
|
else check_game(false);
|
||||||
|
clear_undo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1127,10 +1169,10 @@ Sudoku::new_game(time_t seed) {
|
|||||||
for (j = 0; j < 9; j ++)
|
for (j = 0; j < 9; j ++)
|
||||||
for (k = 0; k < 9; k ++) {
|
for (k = 0; k < 9; k ++) {
|
||||||
cell = grid_cells_[j][k];
|
cell = grid_cells_[j][k];
|
||||||
|
|
||||||
cell->value(0);
|
cell->value(0);
|
||||||
cell->readonly(0);
|
cell->readonly(0);
|
||||||
cell->color(FL_LIGHT3);
|
cell->color(FL_LIGHT3);
|
||||||
|
cell->clear_hints();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show N cells...
|
// Show N cells...
|
||||||
@ -1166,6 +1208,7 @@ Sudoku::new_game(time_t seed) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clear_undo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1236,7 +1279,7 @@ Sudoku::restart_cb(Fl_Widget *widget, void *) {
|
|||||||
for (int j = 0; j < 9; j ++)
|
for (int j = 0; j < 9; j ++)
|
||||||
for (int k = 0; k < 9; k ++) {
|
for (int k = 0; k < 9; k ++) {
|
||||||
SudokuCell *cell = sudoku->grid_cells_[j][k];
|
SudokuCell *cell = sudoku->grid_cells_[j][k];
|
||||||
|
cell->clear_hints();
|
||||||
if (!cell->readonly()) {
|
if (!cell->readonly()) {
|
||||||
solved = false;
|
solved = false;
|
||||||
int v = cell->value();
|
int v = cell->value();
|
||||||
@ -1246,7 +1289,10 @@ Sudoku::restart_cb(Fl_Widget *widget, void *) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (solved) sudoku->new_game(sudoku->seed_);
|
if (solved)
|
||||||
|
sudoku->new_game(sudoku->seed_);
|
||||||
|
else
|
||||||
|
sudoku->clear_undo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1275,6 +1321,58 @@ Sudoku::save_game() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Sudoku::save_state(GameState &s) {
|
||||||
|
for (int j = 0; j < 9; j ++) {
|
||||||
|
for (int k = 0; k < 9; k ++) {
|
||||||
|
s[j*9+k] = grid_cells_[j][k]->state();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sudoku::load_state(GameState &s) {
|
||||||
|
for (int j = 0; j < 9; j ++) {
|
||||||
|
for (int k = 0; k < 9; k ++) {
|
||||||
|
grid_cells_[j][k]->state(s[j*9+k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sudoku::undo() {
|
||||||
|
if (undo_head_ != undo_tail_) {
|
||||||
|
undo_head_ = ((undo_head_-1) & 63);
|
||||||
|
load_state(undo_stack[undo_head_]);
|
||||||
|
redraw();
|
||||||
|
} else {
|
||||||
|
fl_beep(FL_BEEP_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sudoku::redo() {
|
||||||
|
if (undo_head_ != redo_head_) {
|
||||||
|
undo_head_ = ((undo_head_+1) & 63);
|
||||||
|
load_state(undo_stack[undo_head_]);
|
||||||
|
redraw();
|
||||||
|
} else {
|
||||||
|
fl_beep(FL_BEEP_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sudoku::clear_undo() {
|
||||||
|
undo_head_ = undo_tail_ = redo_head_ = 0;
|
||||||
|
save_state(undo_stack[undo_head_]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sudoku::undo_checkpoint() {
|
||||||
|
if (undo_head_ == redo_head_) {
|
||||||
|
redo_head_ = ((redo_head_+1) & 63);
|
||||||
|
}
|
||||||
|
undo_head_ = ((undo_head_+1) & 63);
|
||||||
|
if (undo_head_ == undo_tail_) {
|
||||||
|
undo_tail_ = ((undo_tail_+1) & 63);
|
||||||
|
}
|
||||||
|
save_state(undo_stack[undo_head_]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Set title of window...
|
// Set title of window...
|
||||||
void
|
void
|
||||||
@ -1313,6 +1411,7 @@ Sudoku::solve_game() {
|
|||||||
|
|
||||||
if (sound_) sound_->play('A' + grid_cells_[j][8]->value() - 1);
|
if (sound_) sound_->play('A' + grid_cells_[j][8]->value() - 1);
|
||||||
}
|
}
|
||||||
|
undo_checkpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user