/* * This code is released under the GNU General Public License. See COPYING for * details. Copyright 2005 P. Jordan, C. Lewis, J. Spray * jcspray@icculus.org */ /* Contributions included by: * * Christopher Simon * Grahame White * Dr Balwinder S Dheeman * * See ChangeLog for details */ // changed case to reflect C++-ness #include "GNUDoku.H" #include "sudoku-solve.H" #include #include #include #include #include #include #include #include // Internationalize one day! #define _( x ) x using std::string; int main(int argc, char *argv[]) { Gtk::Main kit(argc, argv); GNUDoku doku; Gtk::Window mainwin; doku.guiSetup(mainwin); if (argc > 1) { //FIXME: if this fails, no dialog appears doku.openPuzzle (argv[1]); } Gtk::Main::run(mainwin); return 0; } GNUDoku::GNUDoku() { difficultyadj_ = new Gtk::Adjustment(0.5, 0, 1, 0.01, 0.1, 0); editupdateblock_ = false; orig_ratio_ = 0; } void GNUDoku::generatePuzzleUI() { int seed = time(NULL); std::ostringstream seedstr; int diff = (int)(difficultyadj_->get_value() * 10.0); // I don't know how to do formatting in C++ - jcs if (diff < 10) seedstr << "0"; seedstr << diff << seed; updateWindowTitle (seedstr.str ()); generatePuzzle ((float)diff / 10.0, seed); } void GNUDoku::generatePuzzle(float const difficulty, int const seed) { srand (seed); int top = 0; Sudoku::attempt stack[81]; Sudoku::flags flag_data; char visited[81] = { 0 }; for (int i = 0; i < 81; ++i) stack[i].index = -1; memset(&flag_data, 0, sizeof(flag_data)); Sudoku::solve(stack, top, &flag_data, visited); int const blanks = static_cast( difficulty * 45.0) + 18; Sudoku::blank(visited, blanks); fillBoxes(stack, visited); } void GNUDoku::solvePuzzle() { int top = 0; Sudoku::attempt stack[81]; Sudoku::flags flag_data; char visited[81] = { 0 }; for (int i = 0; i < 81; ++i) stack[i].index = -1; memset(&flag_data, 0, sizeof(flag_data)); top = getFromBoxes(stack, &flag_data, visited); if (top == -1) return; // unroll the old stack structure into the new Board object, solve, then // convert back (!) sudoku::Board board; for (int i = 0; i < top; ++i) { sudoku::move fixed(stack[i], sudoku::MOVE_FIXED); fixed.Do(board); } std::vector moves; bool solved = sudoku::Solve(board, moves); if (solved) { //board.Print(); // convert moves necessary for solution into old-style attempts for (std::vector::const_iterator cur = moves.begin(); cur != moves.end(); ++cur) { stack[top] = Sudoku::attempt(*cur); visited[cur->index] = 1; ++top; } assert(top == 81); fillBoxes(stack, visited); } else { std::cout << "NOT SOLVEABLE!" << std::endl; board.Print(); Gtk::MessageDialog *dialog = new Gtk::MessageDialog ( _("Solving failed!\n\nThe problem could not be " "solved. This is probably because it is insolvable. If you are " "absolutely certain that the initial grid is valid, please send it " "to the GNUDoku developers."), true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dialog->run(); delete dialog; } } int GNUDoku::getFromBoxes(Sudoku::attempt *top, Sudoku::flags *flags, char visited[]) { // int res = 0; int count = 0; // Sudoku::attempt* const stackbase = top; bool errors_found = false; Sudoku::flags firstoccu; for (int i = 0; i < 9; ++i) { for (int j = 0; j < 9; ++j) { Glib::ustring boxtext = box_[j][i]->get_text(); if (boxtext.empty()) { // Set background to default mark_box_erroneous (box_[j][i], false); continue; } top->col = j; top->row = i; top->value = atoi(boxtext.c_str()) - 1; top->index = top->row * 9 + top->col; top->box = (top->row / 3) * 3 + (top->col / 3); top->tries = 0; if (flags->col[top->col][top->value] || flags->row[top->row][top->value] || flags->box[top->box][top->value]) { errors_found = true; mark_box_erroneous (box_[j][i], true); if (flags->col[top->col][top->value]) { int index = firstoccu.col[top->col][top->value]; mark_box_erroneous (box_[index % 9][index / 9], true); } if (flags->row[top->row][top->value]) { int index = firstoccu.row[top->row][top->value]; mark_box_erroneous (box_[index % 9][index / 9], true); } if (flags->box[top->box][top->value]) { int index = firstoccu.box[top->box][top->value]; mark_box_erroneous (box_[index % 9][index / 9], true); } } else { mark_box_erroneous (box_[j][i], false); } flags->col[top->col][top->value] = 1; flags->row[top->row][top->value] = 1; flags->box[top->box][top->value] = 1; firstoccu.col[top->col][top->value] = top->index; firstoccu.row[top->row][top->value] = top->index; firstoccu.box[top->box][top->value] = top->index; visited[top->index] = 1; ++top; ++count; } } if (errors_found) return -1; else return count; } void GNUDoku::onBoxEdit() { if(editupdateblock_) return; for (int i = 0; i < 9; ++i) { for (int j = 0; j < 9; ++j) { char digit = box_[i][j]->get_text()[0]; if(!isdigit(digit) || digit == '0') box_[i][j]->set_text(""); } } int top = 0; Sudoku::attempt stack[81]; Sudoku::flags flag_data; char visited[81] = { 0 }; for (int i = 0; i < 81; ++i) stack[i].index = -1; memset(&flag_data, 0, sizeof(flag_data)); top = getFromBoxes(stack, &flag_data, visited); } void GNUDoku::fillBoxes(const Sudoku::attempt stack[], const char visited[]) { editupdateblock_ = true; char grid[81] = { 0 }; memset(grid, ' ', sizeof(grid)); for(int i = 0; i < 81; ++i) { if(stack[i].index >= 0 && visited[stack[i].index]) grid[stack[i].index] = '1' + stack[i].value; } for(int i = 0; i < 9; ++i) { for(int j = 0; j < 9; ++j) { char twocharstring[2]; twocharstring[1] = 0; twocharstring[0] = grid[i * 9 + j]; if (twocharstring[0] == ' ') { box_[j][i]->set_text(""); box_[j][i]->set_editable(true); mark_box_editable (box_[j][i], true); } else { box_[j][i]->set_text(twocharstring); box_[j][i]->set_editable(false); mark_box_editable (box_[j][i], false); } mark_box_erroneous (box_[j][i], false); } } editupdateblock_ = false; } void GNUDoku::clearBoxes() { editupdateblock_ = true; for(int i = 0; i < 9; ++i) { for(int j = 0; j < 9; ++j) { box_[j][i]->set_text(""); box_[j][i]->set_editable(true); mark_box_editable (box_[j][i], true); // Set background to default mark_box_erroneous (box_[j][i], false); } } editupdateblock_ = false; updateWindowTitle (""); } void GNUDoku::mark_box_erroneous (Gtk::Entry *box, bool haserror) { #ifdef MARK_ERRORS_BY_COLOR if (haserror) box->modify_base (Gtk::STATE_NORMAL, error_color_); else box->unset_base (Gtk::STATE_NORMAL); #else box->modify_font(haserror ? bold_desc_ : plain_desc_); #endif } void GNUDoku::mark_box_editable (Gtk::Entry *box, bool editable) { box->set_has_frame (editable); // box->set_sensitive (editable); } void onBoxFocus(Gtk::Entry *box) { box->select_region(0, -1); } // Event handler for table resize void GNUDoku::onHBoxResize(Gdk::Rectangle &rect) { int width = box_[0][0]->get_width(); // If the original ratio was not setup, return. if (orig_ratio_ == 0) return; // Calculation the new fontsize. int fontsize = static_cast(orig_ratio_ * width); // If there is a change in font size, adjust the fontsize for each box. if ((fontsize / Pango::SCALE) != (box_[0][0]->get_style()->get_font().get_size() / Pango::SCALE)) { for (int x = 0; x < 9; x++) { for (int y = 0; y < 9; y++) { Pango::FontDescription fdesc = box_[x][y]->get_modifier_style()->get_font(); fdesc.set_size(fontsize); box_[x][y]->modify_font(fdesc); } } } } void GNUDoku::guiSetup(Gtk::Window &window) { Gtk::Table &gridtable = *Gtk::manage(new Gtk::Table(9, 10, false)); /* Create base font style description */ Pango::FontDescription desc; for (int x = 0; x < 9; ++x) { for (int y = 0; y < 9; ++y) { Gtk::Entry *entry = Gtk::manage(new Gtk::Entry); entry->signal_changed().connect( sigc::mem_fun(*this, &GNUDoku::onBoxEdit)); /*entry->signal_focus().connect( sigc::mem_fun(*this, &GNUDoku::onBoxFocus), entry);*/ entry->set_width_chars(2); entry->set_alignment(0.5f); entry->set_max_length(1); // set base font so that later calls to 'get_modifier_style' will work entry->modify_font(desc); box_[x][y] = entry; // Gtk::EXPAND | Gtk::FILL ensures expansion in x direction is possible. // Expansion in y direction will be set by font size adjustment. gridtable.attach(*entry, x, x + 1, y, y + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 0, 0 ); } } gridtable.set_col_spacing(2, 8); gridtable.set_col_spacing(5, 8); gridtable.set_row_spacing(2, 8); gridtable.set_row_spacing(5, 8); Gtk::Button &generatebutton = *Gtk::manage(new Gtk::Button); generatebutton.set_label(_("_Generate")); generatebutton.set_use_underline(); #if !defined GNUDOKU_GTKMMVER || GNUDOKU_GTKMMVER > 26 generatebutton.set_image(*Gtk::manage( new Gtk::Image(Gtk::Stock::EXECUTE, Gtk::ICON_SIZE_BUTTON))); #endif generatebutton.signal_clicked().connect( sigc::mem_fun(*this, &GNUDoku::generatePuzzleUI)); Gtk::Button &generatefromnumberbutton = *Gtk::manage(new Gtk::Button); generatefromnumberbutton.set_label(_("_Load Seed")); generatefromnumberbutton.set_use_underline(); #if !defined GNUDOKU_GTKMMVER || GNUDOKU_GTKMMVER > 26 generatefromnumberbutton.set_image(*Gtk::manage( new Gtk::Image(Gtk::Stock::OPEN, Gtk::ICON_SIZE_BUTTON))); #endif generatefromnumberbutton.signal_clicked().connect( sigc::mem_fun(*this, &GNUDoku::generatePuzzleFromNumberUI)); Gtk::Button &solvebutton = *Gtk::manage(new Gtk::Button); solvebutton.set_label(_("_Solve")); solvebutton.set_use_underline(); #if !defined GNUDOKU_GTKMMVER || GNUDOKU_GTKMMVER > 26 solvebutton.set_image(*Gtk::manage( new Gtk::Image(Gtk::Stock::APPLY, Gtk::ICON_SIZE_BUTTON))); #endif solvebutton.signal_clicked().connect( sigc::mem_fun(*this, &GNUDoku::solvePuzzle)); Gtk::Button &clearbutton = *Gtk::manage(new Gtk::Button); clearbutton.set_label(_("_Clear")); clearbutton.set_use_underline(); #if !defined GNUDOKU_GTKMMVER || GNUDOKU_GTKMMVER > 26 clearbutton.set_image(*Gtk::manage( new Gtk::Image(Gtk::Stock::CLEAR, Gtk::ICON_SIZE_BUTTON))); #endif clearbutton.signal_clicked().connect( sigc::mem_fun(*this, &GNUDoku::clearBoxes)); Gtk::Button &openbutton = *Gtk::manage(new Gtk::Button(Gtk::Stock::OPEN)); openbutton.signal_clicked().connect( sigc::mem_fun(*this, &GNUDoku::openPuzzleUI)); Gtk::Button &savebutton = *Gtk::manage(new Gtk::Button(Gtk::Stock::SAVE)); savebutton.signal_clicked().connect( sigc::mem_fun(*this, &GNUDoku::savePuzzleUI)); Gtk::VBox &diffbox = *Gtk::manage(new Gtk::VBox); Gtk::HScale &diffscale = *Gtk::manage(new Gtk::HScale); diffscale.set_draw_value(false); diffscale.set_adjustment(*difficultyadj_); diffbox.pack_start(diffscale); Gtk::HBox &difflabelbox = *Gtk::manage(new Gtk::HBox); diffbox.pack_start(difflabelbox); Gtk::Label &easylabel = *Gtk::manage(new Gtk::Label); easylabel.set_label(_("Easy")); easylabel.set_use_markup(); difflabelbox.pack_start(easylabel, false, false, 0); Gtk::Label &hardlabel = *Gtk::manage(new Gtk::Label); hardlabel.set_label(_("Hard")); hardlabel.set_use_markup(); difflabelbox.pack_end(hardlabel, false, false, 0); #if !defined GNUDOKU_GTKMMVER || GNUDOKU_GTKMMVER > 26 Gtk::Button &aboutbutton = *Gtk::manage(new Gtk::Button(Gtk::Stock::ABOUT)); aboutbutton.signal_clicked().connect( sigc::mem_fun(*this, &GNUDoku::showAbout)); #endif Gtk::Button &quitbutton = *Gtk::manage(new Gtk::Button(Gtk::Stock::QUIT)); quitbutton.signal_clicked().connect( sigc::mem_fun(*this, &GNUDoku::doQuit)); Gtk::Table &controlstable = *Gtk::manage(new Gtk::Table(6, 2, false)); controlstable.set_row_spacings (12); controlstable.set_col_spacings (6); controlstable.attach(generatebutton, 0, 2, 0, 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 0, 0 ); controlstable.attach(diffbox, 0, 2, 1, 2, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 0, 0 ); controlstable.attach(solvebutton, 0, 2, 2, 3, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 0, 0 ); controlstable.attach(clearbutton, 1, 2, 3, 4, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 0, 0 ); controlstable.attach(generatefromnumberbutton, 0, 1, 3, 4, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 0, 0 ); controlstable.attach(openbutton, 1, 2, 4, 5, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 0, 0 ); controlstable.attach(savebutton, 0, 1, 4, 5, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 0, 0 ); #if !defined GNUDOKU_GTKMMVER || GNUDOKU_GTKMMVER > 26 controlstable.attach(aboutbutton, 0, 1, 5, 6, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 0, 0 ); #endif controlstable.attach(quitbutton, 1, 2, 5, 6, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 0, 0 ); Gtk::HBox &hbox = *Gtk::manage(new Gtk::HBox); hbox.pack_start(gridtable, true, true, 0); hbox.pack_start(controlstable, false, false, 0); hbox.set_border_width(12); hbox.set_spacing(12); // Allocation signal to handle resize events. hbox.signal_size_allocate().connect( sigc::mem_fun(*this, &GNUDoku::onHBoxResize)); window.add(hbox); window.set_resizable(true); window.show_all(); mainwindow_ = &window; /* Setup color definitions.*/ #ifdef MARK_ERRORS_BY_COLOR error_color_ = Gdk::Color("red"); #else bold_desc_ = plain_desc_ = box_[0][0]->get_modifier_style()->get_font(); bold_desc_.set_weight(Pango::WEIGHT_BOLD); plain_desc_.set_weight(Pango::WEIGHT_NORMAL); #endif // determine the original ratio of fontsize to box width, after drawing the window. orig_ratio_ = static_cast (box_[0][0]->get_style()->get_font().get_size()) / box_[0][0]->get_width(); } void GNUDoku::showAbout() { #if !defined GNUDOKU_GTKMMVER || GNUDOKU_GTKMMVER > 26 Gtk::AboutDialog dialog; dialog.set_version(VERSION); dialog.set_name("GNUDoku"); dialog.set_comments("A free Su Doku game"); dialog.set_copyright("Copyright © 2005 Phillip Jordan, Colin Lewis, John Spray"); dialog.run(); #endif } void GNUDoku::doQuit() { Gtk::Main::quit(); } int GNUDoku::setFromString (string puzzle) { char tmp[2]; if (puzzle.length() < 81) { std::cerr << "setFromString: puzzle string is too short, at " << puzzle.length() << "characters. Aborting.\n"; return -1; } editupdateblock_ = true; for(int i = 0; i < 9; ++i) { for(int j = 0; j < 9; ++j) { box_[j][i]->set_editable(true); mark_box_editable (box_[j][i], true); mark_box_erroneous (box_[j][i], false); } } tmp[1] = 0; for (int i = 0; i < 81; ++i) { if (puzzle[i] < 48 || puzzle[i] > 57) { std::cerr << "setFromString: invalid character '" << puzzle[i] << "' " "encountered in puzzle string. Aborting.\n"; editupdateblock_ = false; return -1; } tmp[0] = puzzle[i]; if (puzzle [i] == '0') box_[i % 9][i / 9]->set_text (""); else box_[i % 9][i / 9]->set_text (tmp); } return 0; editupdateblock_ = false; } string GNUDoku::dumpToString (void) { char puzzlestring[82]; puzzlestring[81] = 0; for (int i = 0; i < 81; ++i) { Glib::ustring text = box_[i % 9][i / 9]->get_text (); if (text.empty ()) puzzlestring[i] = '0'; else puzzlestring[i] = text[0]; } return string (puzzlestring); } int GNUDoku::openPuzzle (std::string filename) { FILE *puzzle = fopen (filename.c_str(), "r"); if (!puzzle) { std::cerr << "openPuzzle: open failed!\n"; return -1; } char puzzlestring[82]; puzzlestring[81] = 0; fread (puzzlestring, 1, 81, puzzle); fclose (puzzle); working_folder = filename; return setFromString (puzzlestring); } int GNUDoku::savePuzzle (std::string filename) { string puzzlestring = dumpToString (); FILE *puzzle = fopen (filename.c_str(), "w"); if (!puzzle) { std::cerr << "savePuzzle: open failed!\n"; return -1; } // TODO: check for out of space, etc? fwrite (puzzlestring.c_str (), 1, 81, puzzle); fclose (puzzle); working_folder = filename; return 0; } void GNUDoku::generatePuzzleFromNumberUI () { Gtk::Dialog promptdialog ( "Enter puzzle number", TRUE, FALSE); promptdialog.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); promptdialog.add_button (Gtk::Stock::OK, Gtk::RESPONSE_OK); Gtk::VBox *box = promptdialog.get_vbox (); box->set_spacing (12); Gtk::Label questionlabel ("Enter puzzle number:"); questionlabel.set_use_markup (TRUE); box->pack_start (questionlabel, FALSE, FALSE); Gtk::Entry puzzlenumber; box->pack_start (puzzlenumber, FALSE, FALSE); promptdialog.show_all (); box->set_border_width (12); if (promptdialog.run () == Gtk::RESPONSE_OK) { // First two characters are difficulty Glib::ustring diffstr = puzzlenumber.get_text().substr(0, 2); Glib::ustring seedstr = puzzlenumber.get_text(); seedstr.erase (0, 2); int diffint; sscanf (diffstr.c_str (), "%d", &diffint); float diff = (float)diffint / 10.0f; if (diff > 1.0f) diff = 1.0f; else if (diff < 0.0f) diff = 0.0f; int seed; sscanf (seedstr.c_str (), "%d", &seed); updateWindowTitle (puzzlenumber.get_text ()); generatePuzzle (diff, seed); } return; } void GNUDoku::openPuzzleUI () { Gtk::FileChooserDialog chooser (_("Open Puzzle"), Gtk::FILE_CHOOSER_ACTION_OPEN); chooser.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); chooser.add_button (Gtk::Stock::OPEN, Gtk::RESPONSE_OK); chooser.set_default_response (Gtk::RESPONSE_OK); if (!working_folder.empty ()) chooser.set_filename (working_folder); Gtk::FileFilter sudokufilter; sudokufilter.add_pattern ("*.sudoku"); sudokufilter.set_name (_("Sudoku files (*.sudoku)")); chooser.add_filter (sudokufilter); Gtk::FileFilter allfilesfilter; allfilesfilter.add_pattern ("*"); allfilesfilter.set_name (_("All files")); chooser.add_filter (allfilesfilter); if (chooser.run () == Gtk::RESPONSE_OK) { if (openPuzzle (chooser.get_filename ())) { // There was an error opening the puzzle. // TODO: give more informative errors string complaint = string ("") + _("Opening failed!") + "\n\n" + _("The file '") + Glib::filename_display_basename (chooser.get_filename ()) + _("' could not be opened."); Gtk::MessageDialog errordialog ( complaint, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); errordialog.run(); } } } void GNUDoku::savePuzzleUI () { Gtk::FileChooserDialog chooser (_("Save Puzzle"), Gtk::FILE_CHOOSER_ACTION_SAVE); chooser.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); chooser.add_button (Gtk::Stock::SAVE, Gtk::RESPONSE_OK); chooser.set_default_response (Gtk::RESPONSE_OK); if (!working_folder.empty ()) chooser.set_filename (working_folder); if (chooser.run () == Gtk::RESPONSE_OK) { string filename = chooser.get_filename (); if (filename.find (".") == string::npos) filename += ".sudoku"; // TODO: test + ask if file already exists if (savePuzzle (filename)) { // There was an error opening the puzzle. // TODO: give more informative errors string complaint = string ("") + _("Saving failed!") + "\n\n" + _("The file '") + Glib::filename_display_basename (filename) + _("' could not be saved."); Gtk::MessageDialog errordialog ( complaint, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); errordialog.run(); } } } void GNUDoku::updateWindowTitle (Glib::ustring seed) { std::ostringstream title; if (!seed.empty ()) title << "GNUDoku (" << seed << ")"; else title << "GNUDoku"; mainwindow_->set_title (title.str ()); }