├── doc ├── UML-080209.png ├── eindverslag.pdf ├── images │ ├── UML_1.png │ ├── UML_2.png │ ├── UML_3.png │ ├── UML_4.png │ ├── SudokuHUD.png │ ├── demo_linux.png │ ├── normal_mac.png │ ├── opengl_mac.png │ ├── Crossplatform.png │ ├── demo_mac_osx.png │ ├── demo_windows.png │ ├── GUI_evolution_1.png │ ├── GUI_evolution_2.png │ ├── GUI_evolution_3.png │ ├── GUI_evolution_4.jpg │ ├── GUI_evolution_5.png │ ├── GUI_evolution_6.png │ ├── Dimensions_example.png │ ├── Schermafdruk-linux.png │ └── GUI_class_architecture.png ├── analyseverslag.pdf ├── GUI_class_architecture.graffle ├── demo.sud ├── Doxyfile └── analyseverslag.lyx ├── src ├── .gitignore ├── resources │ ├── win32 │ │ ├── icon.rc │ │ └── icon.ico │ ├── macx │ │ └── icon.icns │ ├── images │ │ └── icon.png │ └── Sudoku.qrc ├── translations │ ├── sudoku_fr.qm │ ├── sudoku_nl.qm │ ├── sudoku_fr.ts │ └── sudoku_nl.ts ├── Sudoku_debug.pro ├── Sudoku_release.pro ├── Exception.h ├── InternalException.h ├── FileIOException.h ├── Qt │ ├── PauseOverlay.h │ ├── SudokuView.h │ ├── Dimensions.h │ ├── NewGameDialog.h │ ├── PauseOverlayEventFilter.h │ ├── SudokuScene.h │ ├── PauseOverlay.cpp │ ├── SudokuHUD.h │ ├── SudokuView.cpp │ ├── MainWindow.h │ ├── NewGameDialog.cpp │ ├── SudokuElement.h │ ├── SudokuGame.h │ ├── TODO │ ├── SudokuHUD.cpp │ ├── SudokuElement.cpp │ ├── MainWindow.cpp │ ├── SudokuScene.cpp │ └── SudokuGame.cpp ├── BoardGenerator.h ├── SudokuApp.h ├── PositionElement.h ├── main.cpp ├── Sudoku.h ├── BoardGenerator.cpp ├── FileIO.h ├── Board.h ├── SudokuApp.cpp ├── Sudoku_shared.pri ├── FileIO.cpp ├── Board.cpp └── Sudoku.cpp └── UNLICENSE /doc/UML-080209.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/UML-080209.png -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | .moc 2 | .obj 3 | .rcc 4 | Makefile 5 | *.app 6 | *.pro* 7 | *.patch 8 | -------------------------------------------------------------------------------- /src/resources/win32/icon.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "icon.ico" -------------------------------------------------------------------------------- /doc/eindverslag.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/eindverslag.pdf -------------------------------------------------------------------------------- /doc/images/UML_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/UML_1.png -------------------------------------------------------------------------------- /doc/images/UML_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/UML_2.png -------------------------------------------------------------------------------- /doc/images/UML_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/UML_3.png -------------------------------------------------------------------------------- /doc/images/UML_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/UML_4.png -------------------------------------------------------------------------------- /doc/analyseverslag.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/analyseverslag.pdf -------------------------------------------------------------------------------- /doc/images/SudokuHUD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/SudokuHUD.png -------------------------------------------------------------------------------- /doc/images/demo_linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/demo_linux.png -------------------------------------------------------------------------------- /doc/images/normal_mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/normal_mac.png -------------------------------------------------------------------------------- /doc/images/opengl_mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/opengl_mac.png -------------------------------------------------------------------------------- /doc/images/Crossplatform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/Crossplatform.png -------------------------------------------------------------------------------- /doc/images/demo_mac_osx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/demo_mac_osx.png -------------------------------------------------------------------------------- /doc/images/demo_windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/demo_windows.png -------------------------------------------------------------------------------- /src/resources/macx/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/src/resources/macx/icon.icns -------------------------------------------------------------------------------- /src/resources/win32/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/src/resources/win32/icon.ico -------------------------------------------------------------------------------- /doc/images/GUI_evolution_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/GUI_evolution_1.png -------------------------------------------------------------------------------- /doc/images/GUI_evolution_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/GUI_evolution_2.png -------------------------------------------------------------------------------- /doc/images/GUI_evolution_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/GUI_evolution_3.png -------------------------------------------------------------------------------- /doc/images/GUI_evolution_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/GUI_evolution_4.jpg -------------------------------------------------------------------------------- /doc/images/GUI_evolution_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/GUI_evolution_5.png -------------------------------------------------------------------------------- /doc/images/GUI_evolution_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/GUI_evolution_6.png -------------------------------------------------------------------------------- /src/resources/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/src/resources/images/icon.png -------------------------------------------------------------------------------- /src/translations/sudoku_fr.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/src/translations/sudoku_fr.qm -------------------------------------------------------------------------------- /src/translations/sudoku_nl.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/src/translations/sudoku_nl.qm -------------------------------------------------------------------------------- /doc/images/Dimensions_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/Dimensions_example.png -------------------------------------------------------------------------------- /doc/images/Schermafdruk-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/Schermafdruk-linux.png -------------------------------------------------------------------------------- /doc/GUI_class_architecture.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/GUI_class_architecture.graffle -------------------------------------------------------------------------------- /doc/images/GUI_class_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wimleers/sudoku/HEAD/doc/images/GUI_class_architecture.png -------------------------------------------------------------------------------- /src/resources/Sudoku.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | images/icon.png 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Sudoku_debug.pro: -------------------------------------------------------------------------------- 1 | # $Id: Sudoku_debug.pro 297 2008-05-30 20:16:20Z wimleers $ 2 | 3 | CONFIG += debug 4 | 5 | !include(Sudoku_shared.pri) { 6 | warning("Could not include Sudoku_shared.pri, which is a necessity!") 7 | } 8 | -------------------------------------------------------------------------------- /src/Sudoku_release.pro: -------------------------------------------------------------------------------- 1 | # $Id: Sudoku_release.pro 297 2008-05-30 20:16:20Z wimleers $ 2 | 3 | CONFIG += release 4 | 5 | !include(Sudoku_shared.pri) { 6 | warning("Could not include Sudoku_shared.pri, which is a necessity!") 7 | } 8 | -------------------------------------------------------------------------------- /src/Exception.h: -------------------------------------------------------------------------------- 1 | // $Id: Exception.h 297 2008-05-30 20:16:20Z wimleers $ 2 | 3 | 4 | /** 5 | * Exception class definition. 6 | * 7 | * @file Exception.h 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #ifndef EXCEPTION_H 13 | #define EXCEPTION_H 14 | 15 | #include 16 | using namespace std; 17 | 18 | class Exception { 19 | public: 20 | Exception(const string & description) { m_description = description; } 21 | string GetDescription(void) { return m_description; } 22 | protected: 23 | string m_description; 24 | }; 25 | 26 | #endif // EXCEPTION_H 27 | -------------------------------------------------------------------------------- /src/InternalException.h: -------------------------------------------------------------------------------- 1 | // $Id: InternalException.h 297 2008-05-30 20:16:20Z wimleers $ 2 | 3 | 4 | /** 5 | * Internal Exception header file 6 | * 7 | * @file InternalException.h 8 | * @author Bram Bonne 9 | */ 10 | 11 | 12 | #ifndef INTERNALEXCEPTION_H 13 | #define INTERNALEXCEPTION_H 14 | 15 | #include 16 | using namespace std; 17 | #include "Exception.h" 18 | 19 | class InternalException : public Exception { 20 | public: 21 | InternalException(string method, string description) : Exception("InternalException was thrown:\nIn " + method + " :\t" + description) {} 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/FileIOException.h: -------------------------------------------------------------------------------- 1 | // $Id: FileIOException.h 345 2008-06-07 19:28:19Z wimleers $ 2 | 3 | 4 | /** 5 | * FileIOException class definition. 6 | * 7 | * @file FileIOException.h 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #ifndef FILEIOEXCEPTION_H 13 | #define FILEIOEXCEPTION_H 14 | 15 | #include 16 | using namespace std; 17 | #include "Exception.h" 18 | 19 | class FileIOException : public Exception { 20 | public: 21 | FileIOException(const string & method, const string & description) : Exception("FileIOException was thrown:\nIn " + method + " :\t" + description) {} 22 | }; 23 | 24 | #endif // FILEIOEXCEPTION_H 25 | -------------------------------------------------------------------------------- /src/Qt/PauseOverlay.h: -------------------------------------------------------------------------------- 1 | // $Id: PauseOverlay.h 297 2008-05-30 20:16:20Z wimleers $ 2 | 3 | 4 | /** 5 | * PauseOverlay definition. 6 | * 7 | * @file PauseOverlay.h 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #ifndef PAUSEOVERLAY_H 13 | #define PAUSEOVERLAY_H 14 | 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "Dimensions.h" 21 | #include "SudokuElement.h" 22 | 23 | 24 | class PauseOverlay : public QGraphicsItem { 25 | 26 | Q_DECLARE_TR_FUNCTIONS(PauseOverlay) 27 | 28 | public: 29 | PauseOverlay(void) { } 30 | ~PauseOverlay(void) { } 31 | 32 | QRectF boundingRect(void) const; 33 | void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget); 34 | }; 35 | 36 | #endif // PAUSEOVERLAY_H 37 | -------------------------------------------------------------------------------- /src/BoardGenerator.h: -------------------------------------------------------------------------------- 1 | // $Id: BoardGenerator.h 297 2008-05-30 20:16:20Z wimleers $ 2 | 3 | 4 | /** 5 | * Board Generator class definition. 6 | * 7 | * @file BoardGenerator.h 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #ifndef BOARDGENERATOR_H 13 | #define BOARDGENERATOR_H 14 | 15 | 16 | #include 17 | #include 18 | #include "Sudoku.h" 19 | 20 | 21 | class BoardGenerator : public QThread { 22 | 23 | Q_OBJECT 24 | 25 | public: 26 | BoardGenerator(QObject * parent = NULL) : QThread(parent) { } 27 | virtual ~BoardGenerator(void) { } 28 | 29 | void QueueBoardGeneration(Board * board, int level = 1); 30 | 31 | signals: 32 | void boardGenerated(int generationTime); 33 | 34 | protected: 35 | void run(); 36 | 37 | private: 38 | Board * m_board; 39 | int m_level; 40 | }; 41 | 42 | #endif // BOARDGENERATOR_H 43 | -------------------------------------------------------------------------------- /src/SudokuApp.h: -------------------------------------------------------------------------------- 1 | // $Id: SudokuApp.h 298M 2010-10-29 18:45:34Z (local) $ 2 | 3 | 4 | /** 5 | * SudokuApp definition. 6 | * 7 | * The sole reason for this class' existence is that we need the ability to 8 | * detect the ApplicationDeactivate event. (When this event occurs, we want 9 | * the game to be paused.) 10 | * 11 | * @file SudokuApp.h 12 | * @author Wim Leers 13 | */ 14 | 15 | 16 | #ifndef SUDOKUAPP_H 17 | #define SUDOKUAPP_H 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "Qt/MainWindow.h" 25 | 26 | 27 | class SudokuApp : public QApplication 28 | { 29 | public: 30 | SudokuApp(int & argc, char ** argv); 31 | 32 | protected: 33 | bool event(QEvent * event); 34 | 35 | private: 36 | MainWindow * m_mainWindow; 37 | }; 38 | 39 | #endif // SUDOKUAPP_H 40 | -------------------------------------------------------------------------------- /src/PositionElement.h: -------------------------------------------------------------------------------- 1 | // $Id: PositionElement.h 297 2008-05-30 20:16:20Z wimleers $ 2 | 3 | 4 | /** 5 | * PositionElement Class definition. 6 | * This class holds a position on the board and an element associated with it. 7 | * 8 | * @file PositionElement.h 9 | * @author Bram Bonne 10 | */ 11 | 12 | 13 | #ifndef POSITIONELEMENT_H 14 | #define POSITIONELEMENT_H 15 | 16 | class PositionElement { 17 | public: 18 | PositionElement(int x, int y, char e) : m_x(x), m_y(y), m_e(e) { } 19 | PositionElement(const PositionElement& other) { 20 | m_x = other.m_x; 21 | m_y = other.m_y; 22 | m_e = other.m_e; 23 | } 24 | int GetX(void) { return m_x; } 25 | int GetY(void) { return m_y; } 26 | char GetE(void) { return m_e; } 27 | 28 | private: 29 | int m_x; 30 | int m_y; 31 | char m_e; 32 | }; 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/Qt/SudokuView.h: -------------------------------------------------------------------------------- 1 | #ifndef SUDOKUVIEW_H 2 | #define SUDOKUVIEW_H 3 | 4 | #include 5 | #include 6 | #include "SudokuScene.h" 7 | #include "SudokuElement.h" 8 | 9 | 10 | // See SudokuView.cpp for details. 11 | #if defined(Q_OS_MACX) 12 | // We don't use OpenGL for rendering on Mac OS X. 13 | #elif defined(Q_OS_LINUX) 14 | // We don't use OpenGL for rendering on Linux. 15 | #elif defined(Q_OS_WIN32) 16 | // We do use OpenGL for rendering on Windows. 17 | #include 18 | #include 19 | #endif 20 | 21 | 22 | class SudokuView : public QGraphicsView { 23 | 24 | Q_OBJECT 25 | 26 | public: 27 | SudokuView(SudokuScene * scene, QWidget * parent = 0); 28 | 29 | private: 30 | virtual void resizeEvent(QResizeEvent * event); 31 | 32 | SudokuScene * m_scene; 33 | }; 34 | 35 | #endif // SUDOKUVIEW_H 36 | -------------------------------------------------------------------------------- /src/Qt/Dimensions.h: -------------------------------------------------------------------------------- 1 | // $Id: Dimensions.h 297 2008-05-30 20:16:20Z wimleers $ 2 | 3 | #ifndef DIMENSIONS_H 4 | #define DIMENSIONS_H 5 | 6 | 7 | #include 8 | 9 | 10 | class Dimensions { 11 | public: 12 | static const int margin = 10; 13 | 14 | static const int elementChoiceSize = 12; 15 | static const int elementSize = 50; 16 | 17 | static const int boardSize = elementSize * 9 + 4 * margin; 18 | 19 | static const int pauseOverlaySize = 5 * elementSize + 2 * margin; 20 | 21 | static const int HUDWidth = 150; 22 | static const int HUDHeight = boardSize - 2 * margin; 23 | 24 | static const int sceneWidth = boardSize + 2 * margin + HUDWidth + margin; 25 | static const int sceneHeight = boardSize; 26 | inline static double sceneRatio(void) { return (double) sceneWidth / sceneHeight; } 27 | 28 | static double scaleWithTextLength(QString text, QString translatedText) { return (double) text.size() / translatedText.size(); } 29 | }; 30 | 31 | 32 | #endif // DIMENSIONS_H 33 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // $Id: main.cpp 345 2008-06-07 19:28:19Z wimleers $ 2 | 3 | 4 | /** 5 | * Main. 6 | * 7 | * @file main.cpp 8 | * @author Bram Bonné 9 | * @author Wim Leers 10 | */ 11 | 12 | 13 | #include 14 | #include 15 | using namespace std; 16 | #include "Exception.h" 17 | #include "SudokuApp.h" 18 | 19 | 20 | int main(int argc, char * argv[]) { 21 | QT_REQUIRE_VERSION(argc, argv, "4.3.0") 22 | 23 | try { 24 | SudokuApp app(argc, argv); 25 | return app.exec(); 26 | } 27 | catch (Exception e) { 28 | cout << e.GetDescription() << endl; 29 | return 1; 30 | } 31 | } 32 | 33 | 34 | /** 35 | * @mainpage Trimesteroverschrijdend Project: Sudoku 36 | * 37 | * @section welcome Welkom bij de Doxygen documentatie voor het trimesteroverschrijdend project van Bram Bonné en Wim Leers 38 | * 39 | * Deze documentatie werd automatisch gegenereerd door middel van Doxygen. Het is handig om de structuur beter te begrijpen. 40 | * 41 | * @section authors Auteurs 42 | * @li Bram Bonné (0623825) 43 | * @li Wim Leers (0623800) 44 | */ 45 | -------------------------------------------------------------------------------- /src/Qt/NewGameDialog.h: -------------------------------------------------------------------------------- 1 | // $Id: NewGameDialog.h 333 2008-06-07 13:22:48Z wimleers $ 2 | 3 | 4 | /** 5 | * New Game Dialog definition. 6 | * 7 | * @file NewGameDialog.h 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #ifndef NEWGAMEDIALOG_H 13 | #define NEWGAMEDIALOG_H 14 | 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "../Sudoku.h" 27 | 28 | 29 | class NewGameDialog : public QDialog { 30 | 31 | Q_OBJECT 32 | 33 | public: 34 | NewGameDialog(QWidget * parent = NULL) : QDialog(parent, Qt::Sheet) { setupUI(); } 35 | ~NewGameDialog(void); 36 | 37 | signals: 38 | void newGame(int difficulty); 39 | 40 | private slots: 41 | void accept(void); 42 | 43 | private: 44 | QVBoxLayout * m_mainLayout; 45 | QHBoxLayout * m_firstSetting; 46 | QLabel * m_label1; 47 | QSpinBox * m_difficultyPicker; 48 | QPushButton * m_cancel, * m_start; 49 | int m_difficulty; 50 | 51 | void setupUI(void); 52 | }; 53 | 54 | 55 | #endif // NEWGAMEDIALOG_H 56 | -------------------------------------------------------------------------------- /src/Sudoku.h: -------------------------------------------------------------------------------- 1 | // $Id: Sudoku.h 297 2008-05-30 20:16:20Z wimleers $ 2 | 3 | 4 | /** 5 | * Sudoku class definition. 6 | * 7 | * @file Sudoku.h 8 | * @author Bram Bonne 9 | */ 10 | 11 | 12 | #ifndef SUDOKU_H 13 | #define SUDOKU_H 14 | 15 | 16 | #include "InternalException.h" 17 | #include "Board.h" 18 | #include "PositionElement.h" 19 | #include 20 | #include 21 | #include 22 | #include 23 | using namespace std; 24 | 25 | 26 | class Sudoku { 27 | public: 28 | Sudoku(void) { } 29 | virtual ~Sudoku(void) { } 30 | 31 | static void GenerateBoard(Board * board, int level = 1); 32 | static bool SolveBoard(Board * board); 33 | static bool BoardIsSolvable(Board board, bool scanSolveOnly = true); 34 | static int NumLevels(void) { return 5; } 35 | 36 | private: 37 | static bool ScanSolve(Board* board); 38 | static bool BackTrackSolve(Board* board, int startx = 0, int starty = 0); 39 | static void GenerateRandomSolution(Board* board); 40 | static bool SolvableWithRemoval(Board* board, int x, int y); 41 | static int LevelToNumMoves(int level); 42 | static bool BoardHasFilledParts(Board* board); 43 | }; 44 | 45 | #endif //SUDOKU_H 46 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /doc/demo.sud: -------------------------------------------------------------------------------- 1 |       A -------------------------------------------------------------------------------- /src/BoardGenerator.cpp: -------------------------------------------------------------------------------- 1 | // $Id: BoardGenerator.cpp 297M 2010-05-07 00:21:20Z (local) $ 2 | 3 | 4 | /** 5 | * BoardGenerator implementation. 6 | * 7 | * @file BoardGenerator.cpp 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #include "BoardGenerator.h" 13 | 14 | 15 | //---------------------------------------------------------------------------- 16 | // Public methods. 17 | 18 | /** 19 | * Generates a new board. Notifies the calling thread via the boardGenerated() 20 | * signal when finished. 21 | * 22 | * @see BoardGenerator::run() 23 | */ 24 | void BoardGenerator::QueueBoardGeneration(Board * board, int level) { 25 | m_board = board; 26 | m_level = level; 27 | 28 | start(QThread::NormalPriority); 29 | } 30 | 31 | void BoardGenerator::run() { 32 | Board * board = m_board; 33 | int level = m_level; 34 | 35 | // Start measuring time. 36 | QTime m_generationTime; 37 | m_generationTime.start(); 38 | 39 | Sudoku::GenerateBoard(board, level); 40 | 41 | // Make sure the generation took at least 0.5 seconds, to make sure the 42 | // animation doesn't just flash. 43 | unsigned long actualGenerationTime = (unsigned long) m_generationTime.elapsed(); 44 | if (actualGenerationTime < 500) 45 | msleep((unsigned long) 500 - actualGenerationTime); 46 | 47 | emit boardGenerated(actualGenerationTime); 48 | } 49 | -------------------------------------------------------------------------------- /src/FileIO.h: -------------------------------------------------------------------------------- 1 | // $Id: FileIO.h 297 2008-05-30 20:16:20Z wimleers $ 2 | 3 | 4 | /** 5 | * FileIO template class definition. 6 | * 7 | * @file FileIO.h 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #ifndef FILEIO_H 13 | #define FILEIO_H 14 | 15 | #include 16 | #include 17 | #include 18 | using namespace std; 19 | #include "FileIOException.h" 20 | 21 | enum Format { FORMAT_CSV }; 22 | 23 | #define FILEIO_ROW deque 24 | #define FILEIO_TABLE deque< FILEIO_ROW > 25 | 26 | class FileIO { 27 | public: 28 | FileIO(const string & fileName = "db", Format f = FORMAT_CSV) { m_fileName = fileName; m_format = f; } 29 | ~FileIO(void) {} 30 | Format GetFormat(void) const { return m_format; } 31 | void SetFormat(Format f) { m_format = f; } 32 | const string & GetFileName(void) const { return m_fileName; } 33 | void SetFileName(const string & fileName) { m_fileName = fileName; } 34 | FILEIO_TABLE GetTable(void) const { return m_table; } 35 | void SetTable(FILEIO_TABLE table ) { m_table = table; } 36 | void Open(void); 37 | bool Save(void); 38 | void Erase(void); 39 | 40 | private: 41 | Format m_format; 42 | string m_fileName; 43 | FILEIO_TABLE m_table; 44 | FILEIO_ROW csvReadRow(ifstream & fs); 45 | void cvsWriteRow(ofstream & fs, FILEIO_ROW row); 46 | }; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/Board.h: -------------------------------------------------------------------------------- 1 | // $Id: Board.h 297 2008-05-30 20:16:20Z wimleers $ 2 | 3 | 4 | /** 5 | * Board class definition. 6 | * 7 | * @file Board.h 8 | * @author Bram Bonne 9 | */ 10 | 11 | 12 | #ifndef BOARD_H 13 | #define BOARD_H 14 | 15 | #include 16 | #include "FileIO.h" 17 | #include "InternalException.h" 18 | #include 19 | #ifdef Q_OS_LINUX 20 | #include 21 | #endif 22 | #include 23 | using namespace std; 24 | 25 | class Board { 26 | public: 27 | Board(void); 28 | Board(const Board & other); 29 | 30 | int Get(int x, int y) const { return m_board[x][y]; } 31 | void Set(int x, int y, int e) { m_board[x][y] = e; } 32 | void Remove(int x, int y) { m_board[x][y] = 0; } 33 | void Clear(void); 34 | bool IsValidMove(int x, int y, int e) const; 35 | bool IsValid(void) const; 36 | bool IsFull(void) const; 37 | int NumFilledIn(void) const; 38 | bool* GetPossibleMoves(int x, int y) const; 39 | 40 | bool Import(const string & fname); 41 | bool Export(const string & fname) const; 42 | 43 | Board & operator=(const Board & other); 44 | const int* operator[](unsigned int i) const; 45 | int* operator[](unsigned int i); 46 | 47 | friend QDataStream& operator<<(QDataStream& out, const Board& board); 48 | friend QDataStream& operator>>(QDataStream& in, Board& board); 49 | 50 | private: 51 | bool CheckHorizontal(int x, int y, int e) const; 52 | bool CheckVertical(int x, int y, int e) const; 53 | bool CheckBlock(int x, int y, int e) const; 54 | bool IsValidHorizontal() const; 55 | bool IsValidVertical() const; 56 | bool IsValidBlocks() const; 57 | 58 | int m_board[9][9]; 59 | }; 60 | 61 | Q_DECLARE_METATYPE(Board) 62 | 63 | #endif //BOARD_H 64 | -------------------------------------------------------------------------------- /src/Qt/PauseOverlayEventFilter.h: -------------------------------------------------------------------------------- 1 | // $Id: PauseOverlayEventFilter.h 333 2008-06-07 13:22:48Z wimleers $ 2 | 3 | 4 | /** 5 | * PauseOverlayEventFilter class definition and implementation. 6 | * 7 | * @file PauseOverlayEventFilter.h 8 | * 9 | * @author Wim Leers 10 | */ 11 | 12 | 13 | #ifndef PAUSEOVERLAYEVENTFILTER_H 14 | #define PAUSEOVERLAYEVENTFILTER_H 15 | 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | 23 | class PauseOverlayEventFilter : public QObject { 24 | 25 | Q_OBJECT 26 | 27 | public: 28 | PauseOverlayEventFilter(QObject * parent, QAction * pauseAction) 29 | : QObject(parent) { m_pauseAction = pauseAction; } 30 | 31 | protected: 32 | bool eventFilter(QObject * obj, QEvent * event) { 33 | Q_UNUSED(obj); 34 | 35 | if (event->type() == QEvent::GraphicsSceneMouseDoubleClick) { 36 | m_pauseAction->setChecked(false); 37 | return true; 38 | } 39 | else if ( 40 | event->type() == QEvent::KeyPress 41 | || event->type() == QEvent::GraphicsSceneMouseMove 42 | || event->type() == QEvent::GraphicsSceneMousePress 43 | || event->type() == QEvent::GraphicsSceneMouseRelease 44 | || event->type() == QEvent::GraphicsSceneContextMenu 45 | || event->type() == QEvent::GraphicsSceneHoverEnter 46 | || event->type() == QEvent::GraphicsSceneHoverLeave 47 | || event->type() == QEvent::GraphicsSceneHoverMove 48 | || event->type() == QEvent::FocusIn 49 | || event->type() == QEvent::FocusOut 50 | ) { 51 | return true; 52 | } 53 | 54 | return false; 55 | } 56 | 57 | private: 58 | QAction * m_pauseAction; 59 | }; 60 | 61 | #endif // PAUSEOVERLAYEVENTFILTER_H 62 | -------------------------------------------------------------------------------- /src/Qt/SudokuScene.h: -------------------------------------------------------------------------------- 1 | // $Id: SudokuScene.h 333 2008-06-07 13:22:48Z wimleers $ 2 | 3 | 4 | /** 5 | * SudokuScene class definition. 6 | * 7 | * @file SudokuScene.h 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #ifndef SUDOKUSCENE_H 13 | #define SUDOKUSCENE_H 14 | 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "Dimensions.h" 21 | #include "SudokuGame.h" 22 | #include "SudokuElement.h" 23 | #include "SudokuHUD.h" 24 | #include "PauseOverlay.h" 25 | #include "PauseOverlayEventFilter.h" 26 | 27 | 28 | class SudokuScene : public QGraphicsScene { 29 | 30 | Q_OBJECT 31 | 32 | public: 33 | SudokuScene(QAction * mainWindowPauseAction); 34 | virtual ~SudokuScene(void); 35 | 36 | void setGame(SudokuGame * game); 37 | 38 | void renderBoard(QPainter * painter, const QRectF & target = QRectF()); 39 | 40 | // Qt-related methods. 41 | bool isWorking(void) const; 42 | void resizeScene(int width, int height); 43 | 44 | public slots: 45 | void updateSudoku(void); 46 | void updateSudokuElement(int x, int y); 47 | 48 | // HUD-related slots. 49 | void validityEnabled(bool enabled); 50 | void solvabilityEnabled(bool enabled); 51 | void statsEnabled(bool enabled); 52 | 53 | private slots: 54 | void pause(bool paused); 55 | void animationStep(void); 56 | void moveFocus(int fromX, int fromY, int toX, int toY); 57 | void loadHints(int x, int y); 58 | 59 | // Necessary when a new board is being generated in a separate thread. 60 | void resyncGeneratedElements(void); 61 | 62 | signals: 63 | void moveFinished(void); 64 | void gameIsActive(bool active); 65 | 66 | private: 67 | void setNewGameHelper(SudokuGame * game); 68 | void updateSudokuElementHelper(int x, int y); 69 | 70 | SudokuGame * m_game; // Not owned. 71 | SudokuGame * m_pendingNewGame; // Not owned. 72 | 73 | bool m_paused; // Used to check if the pause overlay is already added to the scene or not. 74 | 75 | // Qt-related variables. 76 | QTimer * m_animationTimer; 77 | double m_currentScale; 78 | SudokuElement ** m_elements; 79 | QGraphicsRectItem * m_boxes; 80 | SudokuHUD * m_hud; 81 | PauseOverlay * m_pauseOverlay; 82 | PauseOverlayEventFilter * m_filter; 83 | }; 84 | 85 | #endif // SUDOKUSCENE_H 86 | -------------------------------------------------------------------------------- /src/SudokuApp.cpp: -------------------------------------------------------------------------------- 1 | // $Id: SudokuApp.cpp 298M 2010-10-29 18:42:52Z (local) $ 2 | 3 | 4 | /** 5 | * SudokuApp implementation. 6 | * 7 | * @file SudokuApp.cpp 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #include "SudokuApp.h" 13 | 14 | 15 | //---------------------------------------------------------------------------- 16 | // Constructor. 17 | 18 | SudokuApp::SudokuApp(int & argc, char ** argv) : QApplication(argc, argv) { 19 | qDebug() << "[general]" << "Last update to source code was on $LastChangedDate: 2010-10-29 20:42:52 +0200 (Fri, 29 Oct 2010) $"; 20 | qDebug() << "[general]" << "This application was compiled using Qt" << qVersion(); 21 | 22 | // Set up translations. 23 | QTranslator * qtTranslator = new QTranslator(); 24 | qtTranslator->load("qt_" + QLocale::system().name()); 25 | installTranslator(qtTranslator); 26 | QTranslator * sudokuTranslator = new QTranslator(); 27 | sudokuTranslator->load("translations/sudoku_" + QLocale::system().name()); 28 | // For testing. 29 | //sudokuTranslator->load("translations/sudoku_nl"); 30 | installTranslator(sudokuTranslator); 31 | 32 | // This simplifies the retrieval of application settings using QSettings. 33 | QCoreApplication::setOrganizationName("BW Games"); 34 | QCoreApplication::setOrganizationDomain("bwgames.com"); 35 | QCoreApplication::setApplicationName("Sudoku"); 36 | 37 | // Force initialization of resources in case of a static build. 38 | //Q_INIT_RESOURCE(Sudoku); 39 | 40 | // Without this line, the application won't quit on Linux. 41 | connect(this, SIGNAL(lastWindowClosed()), this, SLOT(quit())); 42 | 43 | m_mainWindow = new MainWindow(); 44 | m_mainWindow->show(); 45 | } 46 | 47 | 48 | //---------------------------------------------------------------------------- 49 | // Protected methods. 50 | 51 | bool SudokuApp::event(QEvent * event) { 52 | #ifndef Q_OS_LINUX 53 | if (event->type() == QEvent::ApplicationDeactivate) { 54 | event->accept(); 55 | m_mainWindow->deactivated(); 56 | qDebug() << "[!LINUX]" << "Application was deactivated, game (if any) was paused."; 57 | return true; 58 | } 59 | #else 60 | Q_UNUSED(event) 61 | // QTBUG: The game won't be paused automatically in Linux, because the 62 | // QEvent::ApplicationDeactivate is triggered when 63 | // QEvent::WindowDeactivate should be triggered. 64 | #endif 65 | 66 | return false; 67 | } 68 | -------------------------------------------------------------------------------- /src/Qt/PauseOverlay.cpp: -------------------------------------------------------------------------------- 1 | // $Id: PauseOverlay.cpp 297 2008-05-30 20:16:20Z wimleers $ 2 | 3 | 4 | /** 5 | * PauseOverlay implementation. 6 | * 7 | * @file PauseOverlay.cpp 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #include "PauseOverlay.h" 13 | 14 | 15 | //---------------------------------------------------------------------------- 16 | // Public methods. 17 | 18 | /** 19 | * Implementation of the pure virtual boundingRect() method. 20 | */ 21 | QRectF PauseOverlay::boundingRect(void) const { 22 | return QRectF(0, 0, Dimensions::boardSize, Dimensions::boardSize); 23 | } 24 | 25 | /** 26 | * Implementation of the pure virtual paint() method. 27 | */ 28 | void PauseOverlay::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) { 29 | Q_UNUSED(widget); 30 | 31 | // Only draw the part that is exposed to the user. From: 32 | // http://thesmithfam.org/blog/2007/02/03/qt-improving-qgraphicsview-performance/. 33 | painter->setClipRect(option->exposedRect); 34 | 35 | // Draw the transparency box. 36 | painter->setPen(QPen(Qt::black, 10)); 37 | painter->setBrush(QColor(1, 1, 1, 200)); 38 | painter->drawRect(0, 0, Dimensions::boardSize, Dimensions::boardSize); 39 | 40 | // Draw the overlaybox. 41 | painter->setPen(QPen(Qt::black, 5)); 42 | painter->setBrush(Qt::white); 43 | int offset = Dimensions::margin + 2 * Dimensions::elementSize; 44 | painter->drawRect(offset, offset, Dimensions::pauseOverlaySize, Dimensions::pauseOverlaySize); 45 | 46 | // Draw the text. 47 | QFont font = QFont(painter->font()); 48 | QString text = tr("Paused"); 49 | font.setPixelSize(Dimensions::pauseOverlaySize / 4 * Dimensions::scaleWithTextLength("Paused", text)); 50 | painter->setFont(font); 51 | painter->setPen(QPen(Qt::gray, 5)); 52 | painter->drawText( 53 | QRectF(offset, offset, Dimensions::pauseOverlaySize, Dimensions::pauseOverlaySize), 54 | Qt::AlignCenter | Qt::AlignVCenter, 55 | text 56 | ); 57 | 58 | // Draw the help text. 59 | font = QFont(painter->font()); 60 | text = tr("Press %1 to continue!").arg("P"); 61 | font.setPixelSize(20 * Dimensions::scaleWithTextLength(QString("Press %1 to continue").arg("P"), text)); 62 | painter->setFont(font); 63 | painter->setPen(QPen(Qt::gray, 2)); 64 | painter->drawText( 65 | QRectF(offset, offset + Dimensions::pauseOverlaySize * 3 / 4, Dimensions::pauseOverlaySize, Dimensions::pauseOverlaySize / 4), 66 | Qt::AlignCenter | Qt::AlignVCenter, 67 | text 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /src/Qt/SudokuHUD.h: -------------------------------------------------------------------------------- 1 | // $Id: SudokuHUD.h 350 2008-06-07 20:52:53Z wimleers $ 2 | 3 | 4 | /** 5 | * Qt SudokuHUD widget definition. 6 | * 7 | * The heads-up display for a Sudoku game. 8 | * 9 | * @file SudokuHUD.h 10 | * @author Wim Leers 11 | */ 12 | 13 | 14 | #ifndef SUDOKUHUD_H 15 | #define SUDOKUHUD_H 16 | 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "Dimensions.h" 29 | 30 | 31 | class SudokuHUD : public QObject, public QGraphicsItem { 32 | 33 | Q_OBJECT 34 | Q_INTERFACES(QGraphicsItem) 35 | 36 | public: 37 | SudokuHUD(void); 38 | ~SudokuHUD(void); 39 | 40 | QRectF boundingRect() const; 41 | void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget); 42 | 43 | void validityEnabled(bool enabled) { m_validityEnabled = enabled; } 44 | void solvabilityEnabled(bool enabled) { m_solvabilityEnabled = enabled; } 45 | void statsEnabled(bool enabled) { m_statsEnabled = enabled; } 46 | 47 | // Public constants. 48 | static const int width = 150; 49 | 50 | signals: 51 | void pauseGame(void); 52 | 53 | public slots: 54 | void duration(unsigned int duration); 55 | void validity(bool validity); 56 | void solvability(bool solvability); 57 | void stats(int generated, int completed, int remaining); 58 | 59 | void gameLoaded(bool gameLoaded) { m_gameLoaded = gameLoaded; } 60 | 61 | void calculating(bool calculating); 62 | 63 | private slots: 64 | void repaintSpinner(void); 65 | 66 | private: 67 | QRect getBoundingRectForTimer(void) const; 68 | QRect getBoundingRectForValidity(void) const; 69 | QRect getBoundingRectForSolvability(void) const; 70 | QRect getBoundingRectForStats(void) const; 71 | QRect getBoundingRectForSpinner(void) const; 72 | QRect getBoundingRectForSpinnerText(void) const; 73 | 74 | QString secondsToString(unsigned int numSeconds) const; 75 | 76 | static QLinearGradient getBackgroundGradient(void); 77 | 78 | QTimer * m_animTimer; 79 | 80 | // The 8 variables. 81 | bool m_gameLoaded; 82 | unsigned int m_duration; 83 | bool m_validity; 84 | bool m_solvability; 85 | int m_generated; 86 | int m_completed; 87 | int m_remaining; 88 | bool m_calculating; 89 | 90 | // Settings. 91 | bool m_validityEnabled; 92 | bool m_solvabilityEnabled; 93 | bool m_statsEnabled; 94 | }; 95 | 96 | #endif // SUDOKUHUD_H 97 | -------------------------------------------------------------------------------- /src/Qt/SudokuView.cpp: -------------------------------------------------------------------------------- 1 | // $Id: SudokuView.cpp 312 2008-05-31 17:06:00Z wimleers $ 2 | 3 | 4 | /** 5 | * Qt SudokuView implementation. 6 | * 7 | * @file SudokuView.cpp 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #include "SudokuView.h" 13 | 14 | 15 | //---------------------------------------------------------------------------- 16 | // Constructor. 17 | 18 | SudokuView::SudokuView(SudokuScene * scene, QWidget * parent) : QGraphicsView(scene, parent), m_scene(scene) { 19 | // Performance. 20 | setRenderHints( 21 | QPainter::Antialiasing 22 | | QPainter::TextAntialiasing 23 | | QPainter::SmoothPixmapTransform 24 | | QPainter::HighQualityAntialiasing 25 | ); 26 | setOptimizationFlags( 27 | QGraphicsView::DontClipPainter 28 | | QGraphicsView::DontAdjustForAntialiasing 29 | | QGraphicsView::DontSavePainterState 30 | ); 31 | setCacheMode(QGraphicsView::CacheBackground); 32 | setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); 33 | 34 | 35 | // Use OpenGL for rendering when viable! 36 | #if defined(Q_OS_MACX) 37 | // The rendering is not 100% correct: borders are drawn thinner, the 38 | // board doesn't fit entirely in the view anymore and most annoyingly, 39 | // the text is seriously flawed. 40 | // When hovering like a maniac: 41 | // - QWidget uses 15-20% CPU time (maximum quality) 42 | // - QGLWidget uses 20-25% CPU time (maximum quality) 43 | // - QGLWidget uses 20-25% CPU time (default quality) 44 | // Obviously, QWidget is a better choice on Mac OS X. 45 | qDebug() << "[MACOSX] " << "Not using OpenGL for rendering. Qt's OpenGL bridge doesn't work well, not even in Leopard."; 46 | #elif defined(Q_OS_LINUX) 47 | // The rendering is not 100% correct: borders are drawn thinner, the 48 | // board doesn't fit entirely in the view anymore and most annoyingly, 49 | // the text is seriously flawed. 50 | qDebug() << "[LINUX] " << "Not using OpenGL for rendering. Qt's OpenGL bridge doesn't work well, at least not in Ubuntu 8.04."; 51 | #elif defined(Q_OS_WIN_32) 52 | // Needs testing. Assume QGLWidget. 53 | qDebug() << "[WIN32] " << "Using OpenGL for rendering. Needs testing."; 54 | setViewport(new QGLWidget()); 55 | #endif 56 | 57 | 58 | // Appearance. 59 | setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 60 | setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 61 | setFrameStyle(QFrame::NoFrame); 62 | 63 | // Background. 64 | setBackgroundBrush(QColor(170, 218, 238)); 65 | } 66 | 67 | 68 | //---------------------------------------------------------------------------- 69 | // Private methods. 70 | 71 | void SudokuView::resizeEvent(QResizeEvent * event) { 72 | m_scene->resizeScene(event->size().width(), event->size().height()); 73 | } 74 | -------------------------------------------------------------------------------- /src/Qt/MainWindow.h: -------------------------------------------------------------------------------- 1 | // $Id: MainWindow.h 333 2008-06-07 13:22:48Z wimleers $ 2 | 3 | 4 | /** 5 | * MainWindow class definition. 6 | * 7 | * @file MainWindow.h 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #ifndef MAINWINDOW_H 13 | #define MAINWINDOW_H 14 | 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "NewGameDialog.h" 31 | #include "SudokuGame.h" 32 | #include "SudokuView.h" 33 | #include "SudokuScene.h" 34 | #include "SudokuElement.h" 35 | #include "SudokuHUD.h" 36 | 37 | 38 | class MainWindow : public QMainWindow { 39 | 40 | Q_OBJECT 41 | 42 | public: 43 | MainWindow(QWidget * parent = NULL, Qt::WindowFlags flags = NULL); 44 | ~MainWindow(void) { } 45 | 46 | void deactivated(void); 47 | 48 | QWidget * m_central; 49 | QHBoxLayout * m_mainLayout; 50 | QVBoxLayout * m_gameInfoLayout; 51 | 52 | private slots: 53 | void spawnNewGameDialog(void); 54 | 55 | void newGame(int difficulty); 56 | void newGame(SudokuGame * game = NULL, bool isLoaded = false); 57 | void loadGame(void); 58 | void saveGame(void); 59 | void solveGame(void); 60 | 61 | void importBoardFromCSV(void); 62 | void exportBoardToCSV(void); 63 | 64 | void fullScreen(bool enabled); 65 | void print(void); 66 | void screenshot(void); 67 | 68 | void gameFinished(unsigned int duration); 69 | 70 | void enableGameActions(bool enable); 71 | 72 | private: 73 | void setupSudokuMenu(void); 74 | void setupSettingsMenu(void); 75 | void setupWindow(void); 76 | void setupOther(void); 77 | 78 | void pauseGameHelper(void); 79 | 80 | QString secondsToString(unsigned int numSeconds) const; 81 | 82 | // 'Sudoku' menu. 83 | QMenu * mSudoku; 84 | QAction * aNew; 85 | QAction * aOpen; 86 | QAction * aSave; 87 | QAction * aPause; 88 | QAction * aReset; 89 | QAction * aSolve; 90 | QAction * aImportFromCSV; 91 | QAction * aExportToCSV; 92 | QAction * aPrint; 93 | QAction * aExportToPNG; 94 | QAction * aFullScreen; 95 | QAction * aQuit; 96 | 97 | // 'Settings' menu. 98 | QMenu * mSettings; 99 | QAction * aValidity; 100 | QAction * aSolvability; 101 | QAction * aStats; 102 | 103 | // Sudoku game (model), scene and view (controller/view mix). 104 | SudokuGame * m_game; 105 | SudokuScene * m_scene; 106 | SudokuView * m_view; 107 | 108 | // New game dialog. 109 | NewGameDialog * m_newGameDialog; 110 | }; 111 | 112 | #endif // MAINWINDOW_H 113 | -------------------------------------------------------------------------------- /src/Qt/NewGameDialog.cpp: -------------------------------------------------------------------------------- 1 | // $Id: NewGameDialog.cpp 306 2008-05-31 14:51:10Z wimleers $ 2 | 3 | 4 | /** 5 | * New Game Dialog implementation. 6 | * 7 | * @file NewGameDialog.cpp 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #include "NewGameDialog.h" 13 | 14 | 15 | //---------------------------------------------------------------------------- 16 | // Constructor & destructor. 17 | 18 | NewGameDialog::~NewGameDialog(void) { 19 | // Is automatically deleted, because it's a child of of the main widget. 20 | //delete m_mainLayout; 21 | delete m_firstSetting; 22 | delete m_label1; 23 | delete m_difficultyPicker; 24 | delete m_cancel; 25 | delete m_start; 26 | } 27 | 28 | 29 | //---------------------------------------------------------------------------- 30 | // Private slots. 31 | 32 | /** 33 | * The dialog was accepted. Emit the newGame() signal. 34 | */ 35 | void NewGameDialog::accept(void) { 36 | m_difficulty = m_difficultyPicker->value(); 37 | 38 | QSettings settings; 39 | settings.setValue("difficulty", m_difficulty); 40 | 41 | close(); 42 | 43 | emit newGame(m_difficulty); 44 | } 45 | 46 | 47 | //---------------------------------------------------------------------------- 48 | // Private methods. 49 | 50 | /** 51 | * Create the UI. 52 | */ 53 | void NewGameDialog::setupUI(void) { 54 | setContextMenuPolicy(Qt::NoContextMenu); 55 | setModal(true); 56 | 57 | QSettings settings; 58 | 59 | // Configure layouts. 60 | m_mainLayout = new QVBoxLayout(this); 61 | m_firstSetting = new QHBoxLayout(); 62 | m_mainLayout->addLayout(m_firstSetting); 63 | 64 | // Set up the buttons. 65 | QDialogButtonBox * buttonBox = new QDialogButtonBox(this); 66 | buttonBox->setOrientation(Qt::Horizontal); 67 | m_cancel = new QPushButton(tr("&Cancel")); 68 | buttonBox->addButton(m_cancel, QDialogButtonBox::RejectRole); 69 | m_start = new QPushButton(tr("&Start!")); 70 | m_start->setDefault(true); 71 | buttonBox->addButton(m_start, QDialogButtonBox::AcceptRole); 72 | m_mainLayout->addWidget(buttonBox); 73 | 74 | // Difficulty label. 75 | m_label1 = new QLabel(this); 76 | m_label1->setText(tr("Difficulty level")); 77 | m_firstSetting->addWidget(m_label1); 78 | m_firstSetting->setAlignment(m_label1, Qt::AlignLeft); 79 | 80 | // Difficulty spinbox. 81 | m_difficultyPicker = new QSpinBox(this); 82 | m_difficultyPicker->setMinimum(1); 83 | m_difficultyPicker->setMaximum(Sudoku::NumLevels()); 84 | m_difficultyPicker->setSingleStep(1); 85 | m_difficultyPicker->setValue(settings.value("difficulty", 1).toInt()); 86 | m_firstSetting->addWidget(m_difficultyPicker); 87 | m_firstSetting->setAlignment(m_difficultyPicker, Qt::AlignRight); 88 | 89 | // Connect the signals from the buttonbox to the NewGameDialog. 90 | connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); 91 | connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); 92 | } 93 | -------------------------------------------------------------------------------- /src/Qt/SudokuElement.h: -------------------------------------------------------------------------------- 1 | // $Id: SudokuElement.h 350 2008-06-07 20:52:53Z wimleers $ 2 | 3 | 4 | /** 5 | * Qt SudokuElement widget definition. 6 | * 7 | * This class is derived from QGraphicsItem, because it will be used as an 8 | * item in a QGraphicsScene. However, it's derived first from QObject, because 9 | * we want to be able to emit signals. 10 | * 11 | * @file SudokuElement.h 12 | * @author Wim Leers 13 | */ 14 | 15 | 16 | #ifndef SUDOKUELEMENT_H 17 | #define SUDOKUELEMENT_H 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "Dimensions.h" 32 | 33 | 34 | class SudokuElement : public QObject, public QGraphicsItem { 35 | 36 | Q_OBJECT 37 | Q_INTERFACES(QGraphicsItem) 38 | 39 | public: 40 | SudokuElement(); 41 | 42 | QRectF boundingRect() const; 43 | void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget); 44 | 45 | void setChoices(bool const * choices); 46 | bool getGenerated(void) const; 47 | void setGenerated(bool generated); 48 | int getX(void) const { return m_x; } 49 | int getY(void) const { return m_y; } 50 | void setX(int x) { m_x = x; } 51 | void setY(int y) { m_y = y; } 52 | 53 | signals: 54 | void enableChoice(int x, int y, int number); 55 | void disableChoice(int x, int y, int number); 56 | void setFinalChoice(int x, int y, int number); 57 | void unsetFinalChoice(int x, int y); 58 | void loadHints(int x, int y); 59 | void selectOtherSudokuElement(int fromX, int fromY, int toX, int toY); 60 | 61 | public slots: 62 | void enableChoice(int number); 63 | void disableChoice(int number); 64 | void setFinalChoice(int number); 65 | void unsetFinalChoice(void); 66 | 67 | protected: 68 | void hoverEnterEvent(QGraphicsSceneHoverEvent * event); 69 | void hoverLeaveEvent(QGraphicsSceneHoverEvent * event); 70 | void mousePressEvent(QGraphicsSceneMouseEvent * event); 71 | void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event); 72 | void contextMenuEvent(QGraphicsSceneContextMenuEvent * event); 73 | 74 | void focusInEvent(QFocusEvent * event); 75 | void focusOutEvent(QFocusEvent * event); 76 | 77 | void keyPressEvent(QKeyEvent * event); 78 | 79 | private: 80 | int getChoiceByMousePos(const QPointF & pos) const; 81 | QRect getBoundingRectForChoiceByCoords(int x, int y) const; 82 | QRect getBoundingRectForChoice(int number) const; 83 | QRect getBoundingRectForFinalChoice(void) const; 84 | 85 | static QLinearGradient getBackgroundGradient(void); 86 | 87 | // Values. 88 | int m_x, m_y; 89 | bool * m_choices; 90 | int m_finalChoice; 91 | 92 | // States. 93 | bool m_focus; 94 | bool m_generated; 95 | }; 96 | 97 | #endif // SUDOKUELEMENT_H 98 | -------------------------------------------------------------------------------- /src/Sudoku_shared.pri: -------------------------------------------------------------------------------- 1 | # $Id: Sudoku_shared.pri 312M 2010-10-29 18:45:26Z (local) $ 2 | 3 | 4 | message(Qt version: $$[QT_VERSION]) 5 | CONFIG(release, debug|release) { 6 | message("--- Sudoku project file generation (release mode) ---") 7 | } else { 8 | message("--- Sudoku project file generation (debug mode) ---") 9 | } 10 | 11 | 12 | # Basic settings. 13 | message("Basic settings...") 14 | TEMPLATE = app 15 | CONFIG += qt thread resources warn_on 16 | OBJECTS_DIR = .obj 17 | MOC_DIR = .moc 18 | RCC_DIR = .rcc 19 | TARGET = Sudoku 20 | 21 | 22 | # Input files. 23 | message("Input files...") 24 | HEADERS += Board.h \ 25 | Exception.h \ 26 | FileIO.h \ 27 | FileIOException.h \ 28 | InternalException.h \ 29 | Sudoku.h \ 30 | BoardGenerator.h \ 31 | SudokuApp.h \ 32 | Qt/Dimensions.h \ 33 | Qt/MainWindow.h \ 34 | Qt/NewGameDialog.h \ 35 | Qt/PauseOverlay.h \ 36 | Qt/PauseOverlayEventFilter.h \ 37 | Qt/SudokuGame.h \ 38 | Qt/SudokuView.h \ 39 | Qt/SudokuScene.h \ 40 | Qt/SudokuElement.h \ 41 | Qt/SudokuHUD.h 42 | SOURCES += Board.cpp \ 43 | FileIO.cpp \ 44 | main.cpp \ 45 | Sudoku.cpp \ 46 | BoardGenerator.cpp \ 47 | SudokuApp.cpp \ 48 | Qt/MainWindow.cpp \ 49 | Qt/NewGameDialog.cpp \ 50 | Qt/PauseOverlay.cpp \ 51 | Qt/SudokuGame.cpp \ 52 | Qt/SudokuView.cpp \ 53 | Qt/SudokuScene.cpp \ 54 | Qt/SudokuElement.cpp \ 55 | Qt/SudokuHUD.cpp 56 | RESOURCES += resources/Sudoku.qrc 57 | TRANSLATIONS += translations/sudoku_nl.ts \ 58 | translations/sudoku_fr.ts 59 | 60 | # Linux platform-specific settings. 61 | linux-g++ { 62 | message("Building for Linux (makefile) ...") 63 | } 64 | 65 | 66 | # Mac OS X platform-specific settings. 67 | macx { 68 | message("Building for Mac OS X (Xcode project file) ...") 69 | 70 | # Application icon. 71 | ICON = resources/macx/icon.icns 72 | 73 | # Always create an .app bundle. 74 | CONFIG += app_bundle 75 | 76 | # We want a universal binaries as releases. 77 | CONFIG(release, debug|release) { 78 | message("Configured to build a universal binary") 79 | CONFIG += x86 x86_64 80 | } 81 | } 82 | 83 | 84 | # Windows platform-specific settings. 85 | win32 { 86 | message("Building for Windows (Visual Studio project file) ...") 87 | 88 | # Application icon. 89 | RC_FILE = resources/win32/icon.rc 90 | 91 | # Enable manifest embedding (because we're not generating DLLs). Enabling 92 | # this when the windows subystem is used, results in linker errors. 93 | #CONFIG += embed_manifest_exe 94 | 95 | # Using OpenGL on Windows works fine, as long as you don't mind a console 96 | # window (subsystem = WINDOWS in linker settings results in linker errors). 97 | QT += opengl 98 | 99 | CONFIG(release, debug|release) { 100 | message("Configured to be a windowed application") 101 | CONFIG += windows 102 | } 103 | 104 | # This does not have *any* effect! You still have to set the linker's 105 | # subsystem to console manually. 106 | CONFIG(debug, debug|release) { 107 | message("Configured to be a console application") 108 | CONFIG += console 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /src/FileIO.cpp: -------------------------------------------------------------------------------- 1 | // $Id: FileIO.cpp 297 2008-05-30 20:16:20Z wimleers $ 2 | 3 | 4 | // @TODO: Add more exceptions. 5 | 6 | 7 | /** 8 | * FileIO class definition. 9 | * 10 | * @file FileIO.cpp 11 | * @author Wim Leers 12 | */ 13 | 14 | 15 | #include "FileIO.h" 16 | 17 | 18 | //---------------------------------------------------------------------------- 19 | // Public methods. 20 | 21 | /** 22 | * Open (and read from) a file. 23 | */ 24 | void FileIO::Open(void) { 25 | ifstream fs(m_fileName.c_str(), ios::in); 26 | FILEIO_ROW row; 27 | 28 | // Make sure the table is empty. 29 | m_table.erase(m_table.begin(), m_table.end()); 30 | 31 | if (fs.is_open()) { 32 | while (!fs.eof()) { 33 | switch (m_format) { 34 | case FORMAT_CSV: 35 | row = csvReadRow(fs); 36 | if (row.size()) 37 | m_table.push_back(row); 38 | break; 39 | } 40 | } 41 | fs.close(); 42 | } 43 | } 44 | 45 | /** 46 | * Save a file. 47 | */ 48 | bool FileIO::Save(void) { 49 | ofstream fs(m_fileName.c_str(), ios::out); 50 | FILEIO_ROW row; 51 | FILEIO_TABLE::iterator it; 52 | 53 | if (fs.is_open()) { 54 | switch (m_format) { 55 | case FORMAT_CSV: 56 | for (it = m_table.begin(); it != m_table.end(); it++) 57 | cvsWriteRow(fs, *it); 58 | break; 59 | } 60 | fs.close(); 61 | return true; 62 | } 63 | else 64 | throw(FileIOException( 65 | "FileIO::Open()", 66 | "The file " + m_fileName + " could not be opened for writing." 67 | )); 68 | 69 | 70 | } 71 | 72 | /** 73 | * Erase the file. 74 | */ 75 | void FileIO::Erase(void) { 76 | // Erase the tabular representation of the file in the memory. 77 | m_table.erase(m_table.begin(), m_table.end()); 78 | 79 | // Erase the file itself. 80 | ofstream f(m_fileName.c_str(), ios::trunc); 81 | f.close(); 82 | } 83 | 84 | 85 | //---------------------------------------------------------------------------- 86 | // Private methods. 87 | 88 | /** 89 | * Reads a row of strings from a filestream. 90 | * 91 | * @param fs 92 | * Filestream that is opened in reading mode. 93 | * @return 94 | * A row of strings. 95 | */ 96 | FILEIO_ROW FileIO::csvReadRow(ifstream & fs) { 97 | string line; 98 | unsigned int pos = 0, prevPos = 0; 99 | FILEIO_ROW row; 100 | 101 | // Read the next row (one line) of the file. 102 | getline(fs, line); 103 | 104 | // Put all fields in this row in a queue. 105 | while (line.length() > 0 && pos < line.npos) { 106 | // Ignore the comma. 107 | if (pos > 0) 108 | prevPos = pos + 1; 109 | 110 | pos = (unsigned int) line.find(',', prevPos); 111 | row.push_back(line.substr(prevPos, pos - prevPos)); 112 | } 113 | return row; 114 | } 115 | 116 | /** 117 | * Writes a row of strings to a filestream. 118 | * 119 | * @param fs 120 | * Filestream that is opened in writing mode. 121 | * @param row 122 | * A row of strings. 123 | */ 124 | void FileIO::cvsWriteRow(ofstream & fs, FILEIO_ROW row) { 125 | FILEIO_ROW::iterator it, test; 126 | 127 | for (it = row.begin(); it != row.end(); it++) { 128 | fs << *it; 129 | 130 | // Test if we haven't reached the last column yet, and if so, print a 131 | // comma. 132 | test = it; 133 | test++; 134 | if (test != row.end()) 135 | fs << ','; 136 | } 137 | fs << endl; 138 | } 139 | -------------------------------------------------------------------------------- /src/Qt/SudokuGame.h: -------------------------------------------------------------------------------- 1 | // $Id: SudokuGame.h 370 2008-06-08 21:50:31Z wimleers $ 2 | 3 | 4 | /** 5 | * SudokuGame class definition. 6 | * 7 | * SudokuGame is the Model in the MVC pattern. In this case, that means 8 | * it contains all game data and logic. 9 | * 10 | * @file SudokuGame.h 11 | * @author Wim Leers 12 | */ 13 | 14 | 15 | #ifndef SUDOKUGAME_H 16 | #define SUDOKUGAME_H 17 | 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "../Board.h" 26 | #include "../Sudoku.h" 27 | #include "../BoardGenerator.h" 28 | 29 | 30 | class SudokuGame : public QObject { 31 | 32 | Q_OBJECT 33 | 34 | public: 35 | SudokuGame(int difficulty = 1); 36 | SudokuGame(Board * board); 37 | ~SudokuGame(void); 38 | 39 | bool load(const QString fileName); 40 | bool save(const QString fileName); 41 | 42 | Board const * getBoard(void) const; 43 | Board const * getOriginalBoard(void) const; 44 | bool const * getChoices(int x, int y) const; 45 | void setChoices(int x, int y, bool const * choices); 46 | 47 | bool solve(void); 48 | 49 | bool isWorking(void) const; 50 | bool isValid(void) const; 51 | bool isFinished(void) const; 52 | 53 | // Game info calculations methods. 54 | bool validityCalculation(void) { return m_validityCalculationEnabled; } 55 | bool solvabilityCalculation(void) { return m_solvabilityCalculationEnabled; } 56 | bool statsCalculation(void) { return m_statsCalculationEnabled; } 57 | void enableValidityCalculation(bool enable) { m_validityCalculationEnabled = enable; } 58 | void enableSolvabilityCalculation(bool enable) { m_solvabilityCalculationEnabled = enable; } 59 | void enableStatsCalculation(bool enable) { m_statsCalculationEnabled = enable; } 60 | void performAllCalculations(bool firstTime = false); 61 | 62 | int getFinalChoice(int x, int y) const; 63 | 64 | public slots: 65 | void start(void); 66 | void pause(bool pause); 67 | void reset(void); 68 | 69 | void enableChoice(int x, int y, int number); 70 | void disableChoice(int x, int y, int number); 71 | void setFinalChoice(int x, int y, int number); 72 | void unsetFinalChoice(int x, int y); 73 | 74 | private slots: 75 | void updateDuration(void); 76 | void boardGenerated(int generationTime); 77 | 78 | signals: 79 | void changed(int x, int y); 80 | void paused(bool paused); 81 | void finished(unsigned int duration); 82 | void validityChanged(bool valid, int lastFinalChoiceX, int lastFinalChoiceY); 83 | void solvabilityChanged(bool solvable, int lastFinalChoiceX, int lastFinalChoiceY); 84 | void statsChanged(int generated, int completed, int remaining); 85 | 86 | void ready(bool); 87 | 88 | void durationUpdated(unsigned int); 89 | 90 | void working(bool working); 91 | 92 | private: 93 | // Game object construction helper methods. 94 | void initialize(int difficulty); 95 | void generateBoard(void); 96 | void storeOriginalBoard(void); 97 | void finishConstruction(void); 98 | 99 | // Calculation methods. 100 | void startWorking(void); 101 | void stopWorking(void); 102 | void calculateValidity(bool firstTime = false); 103 | void calculateSolvability(bool firstTime = false); 104 | void calculateStats(bool firstTime = false); 105 | 106 | Board * m_originalBoard; 107 | Board * m_board; 108 | bool m_choices[9][9][9]; 109 | unsigned int m_duration; 110 | bool m_mustGenerateBoard; 111 | 112 | int m_lastFinalChoiceX; 113 | int m_lastFinalChoiceY; 114 | 115 | // States. 116 | bool m_ready; 117 | bool m_isLoadedGame; 118 | int m_difficulty; 119 | bool m_finished; 120 | bool m_working; 121 | bool m_valid; // This is a state, but there still is a special game info calculation setting for it, which can prevent unnecessary signals. 122 | 123 | // Game info calculation settings. 124 | bool m_validityCalculationEnabled; 125 | bool m_solvabilityCalculationEnabled; 126 | bool m_statsCalculationEnabled; 127 | 128 | // Timers. 129 | QTimer * m_durationTimer; 130 | 131 | // Sudoku board generation thread. 132 | BoardGenerator * m_thread; 133 | }; 134 | 135 | #endif // SUDOKUGAME_H 136 | -------------------------------------------------------------------------------- /src/Qt/TODO: -------------------------------------------------------------------------------- 1 | # 2 | # First stage: basic, well functioning, nicely looking GUI 3 | # 4 | 5 | # General (only necessary if threads are used) 6 | ✓ (no threads used) snappy UI: use QTimer with timeout of zero. See last code sample of http://doc.trolltech.com/4.3/qtimer.html#details. Won't be necessary, says Bram. 7 | 8 | # SudokuView, SudokuScene, SudokuElement 9 | ✓ grok and start from example: elasticnodes 10 | ✓ group together 9 ElementBoxes with a QGraphicsItemGroup, and group together 9 of these groups as well with a QGraphicsItemGroup (implemented differently) 11 | ✓ ensure the proper spacing exists (i.e. 9 "boxes" of 9 elements) 12 | ✓ check if QPainterPath can help improve performance -> not necessary: overkill, is used for *shapes*, whereas we use a lot of text 13 | ✓ disable the generated elements (i.e. prevent any changes) and display them in another color 14 | ✓ The process doesn't end on Windows (solved by upgrading to Qt 4.4) 15 | 16 | 17 | # Overall progress indicator 18 | ✓ duration timer 19 | ✓ validity of the Sudoku 20 | ✓ solvability of the Sudoku 21 | ✓ when game is finished: create QMessageBox::information and view should be disabled 22 | 23 | 24 | 25 | # Icons 26 | ✓ Mac OS X 27 | ✓ Windows 28 | ✓ window icon (cross-platform) 29 | 30 | 31 | # Preferences 32 | ✓ toggle stats display: numbers of elements that are: given, completed, remaining 33 | 34 | 35 | # In-game preferences 36 | ✓ toggle Sudoku validity display 37 | ✓ toggle Sudoku solvability display 38 | 39 | 40 | # Loading/saving/importing/exporting/printing 41 | ✓ open saved games (SudokuGame object deserialization) 42 | ✓ save games (SudokuGame object serialization) 43 | ✓ import from CSV 44 | ✓ export to CSV 45 | ✓ export to PNG (screenshot) 46 | ✓ print 47 | 48 | 49 | # Usability 50 | ✓ keyboard control 51 | ✓ keyboard control: use backspace to unset a final choice 52 | ✓ keyboard control: use arrows for navigation, default focus on the middle item of the puzzle 53 | ✓ Mac OS X integration (use sheets where possible, dock, icon) 54 | ✓ Windows integration (icon) 55 | ✓ Do not generate a board when the game starts, but just display an empty board and present the user with a NewGameDialog, in which he can choose the level. 56 | ✓ Hints on a per-element basis (hotkey and context menu) 57 | ✓ ability to pause the game (shortcut: P, also possible through the Game menu) 58 | ✓ ability to reset the game (shortcut: R, also possible through the Game menu) 59 | ✓ merge the Game menu into the Sudoku menu 60 | 61 | 62 | 63 | 64 | # 65 | # Second stage: fancy GUI 66 | # 67 | 68 | # Allow resize event, and scale everything along. 69 | ✓ Example: http://websvn.kde.org/trunk/KDE/kdegames/kreversi/kreversiview.cpp?view=markup 70 | ✓ Resizing with keeping the aspect ratio in mind. 71 | 72 | # Tweaks 73 | ✓ transparent, dark-grey overlay over the rest of the board for the PauseOverlay 74 | 75 | # HUD 76 | ✓ Über fancy HUD! 77 | 78 | # Use OpenGL for rendering 79 | ✓ Use QGLWidget: http://doc.trolltech.com/4.3/qabstractscrollarea.html#setViewport 80 | ✓ OpenGL w/ antialiasing: QGLFormat::sampleBuffers() 81 | 82 | # Advanced usability 83 | ✓ Remember last chosen generation level and the validity/solvability/stats info settings. Use QSettings. 84 | ✓ Display a 'Calculating...' progress thing in the HUD while generating a board, or validating/solving a board, instead of just 'hanging' the app. 85 | ✓ Context-sensitive menu items (i.e. only allow game-related actions when a game is active) 86 | 87 | # Animations 88 | - Example: concentric circles 89 | ✓ animations: pave the way by adding support for it throughout the code base 90 | - animations: QTOpenGL, example: http://daniel.molkentin.de/blog/archives/95-Leveraging-Qt-For-Smooth-Transition-Effects.html 91 | - animation easing: QTimeLine::setCurveShape() 92 | - animate through QGraphicsView::matrix() 93 | 94 | - ROTATE THE FUCKING THING WHEN THE GAME ENDS!! THIS IS THE MOST KICKASS FEATURE EVER! 95 | 96 | 97 | # Final things. 98 | ✓ Inlezen van csv die niet geldig is geeft kanjer van een error (tested on windows) 99 | - ergens laten zien dat H voor hints is, evenzeer toetsenbordnavigatie enzo (dus laatd at ergens rechts zien, ik zorg wel voor strings) 100 | ✓ Paused + new game = paused (all platforms) 101 | ✓ new game = soms VANZELF paused (onder linux) 102 | ✓ Paused in windows => BLIJFT paused (geraakt er niet uit) (tested on windows) 103 | ✓ Game saven -> settings uitzetten -> game inladen => settings staan uit in menu, maar wel rechts geactiveerd (niet zo erg, welke homo buiten kristof gaat dat doen? :p) (tested on linux) 104 | ✓ Teller geeft 2 seconden lang een GIGANTISCH getal voor hij vanaf 0 begint (tested on linux) 105 | ✓ completed = 0 en remaining = 50-53 ofzo wanneer computer heeft gesolved (imo een bug) 106 | ✓ door bord opgeloste zetten kunnen ge-unset worden door de gebruiker (waardoor ge dus een bord in 3s kunt oplossen) 107 | ✓ fix translation in QGraphicsItems 108 | -------------------------------------------------------------------------------- /doc/Doxyfile: -------------------------------------------------------------------------------- 1 | # Doxyfile 1.5.4 2 | 3 | #--------------------------------------------------------------------------- 4 | # Project related configuration options 5 | #--------------------------------------------------------------------------- 6 | DOXYFILE_ENCODING = UTF-8 7 | PROJECT_NAME = Sudoku 8 | PROJECT_NUMBER = 9 | OUTPUT_DIRECTORY = /Users/wimleers/School/TRPR/svn/doc 10 | CREATE_SUBDIRS = NO 11 | OUTPUT_LANGUAGE = English 12 | BRIEF_MEMBER_DESC = YES 13 | REPEAT_BRIEF = YES 14 | ABBREVIATE_BRIEF = "The $name class" \ 15 | "The $name widget" \ 16 | "The $name file" \ 17 | is \ 18 | provides \ 19 | specifies \ 20 | contains \ 21 | represents \ 22 | a \ 23 | an \ 24 | the 25 | ALWAYS_DETAILED_SEC = NO 26 | INLINE_INHERITED_MEMB = NO 27 | FULL_PATH_NAMES = YES 28 | STRIP_FROM_PATH = /Applications/Development/ 29 | STRIP_FROM_INC_PATH = 30 | SHORT_NAMES = NO 31 | JAVADOC_AUTOBRIEF = NO 32 | QT_AUTOBRIEF = NO 33 | MULTILINE_CPP_IS_BRIEF = NO 34 | DETAILS_AT_TOP = NO 35 | INHERIT_DOCS = YES 36 | SEPARATE_MEMBER_PAGES = NO 37 | TAB_SIZE = 8 38 | ALIASES = 39 | OPTIMIZE_OUTPUT_FOR_C = NO 40 | OPTIMIZE_OUTPUT_JAVA = NO 41 | BUILTIN_STL_SUPPORT = NO 42 | CPP_CLI_SUPPORT = NO 43 | SIP_SUPPORT = NO 44 | DISTRIBUTE_GROUP_DOC = NO 45 | SUBGROUPING = YES 46 | TYPEDEF_HIDES_STRUCT = NO 47 | #--------------------------------------------------------------------------- 48 | # Build related configuration options 49 | #--------------------------------------------------------------------------- 50 | EXTRACT_ALL = YES 51 | EXTRACT_PRIVATE = YES 52 | EXTRACT_STATIC = YES 53 | EXTRACT_LOCAL_CLASSES = YES 54 | EXTRACT_LOCAL_METHODS = NO 55 | EXTRACT_ANON_NSPACES = NO 56 | HIDE_UNDOC_MEMBERS = NO 57 | HIDE_UNDOC_CLASSES = NO 58 | HIDE_FRIEND_COMPOUNDS = NO 59 | HIDE_IN_BODY_DOCS = NO 60 | INTERNAL_DOCS = NO 61 | CASE_SENSE_NAMES = NO 62 | HIDE_SCOPE_NAMES = NO 63 | SHOW_INCLUDE_FILES = YES 64 | INLINE_INFO = YES 65 | SORT_MEMBER_DOCS = YES 66 | SORT_BRIEF_DOCS = NO 67 | SORT_BY_SCOPE_NAME = NO 68 | GENERATE_TODOLIST = YES 69 | GENERATE_TESTLIST = YES 70 | GENERATE_BUGLIST = YES 71 | GENERATE_DEPRECATEDLIST= YES 72 | ENABLED_SECTIONS = 73 | MAX_INITIALIZER_LINES = 30 74 | SHOW_USED_FILES = YES 75 | SHOW_DIRECTORIES = NO 76 | FILE_VERSION_FILTER = 77 | #--------------------------------------------------------------------------- 78 | # configuration options related to warning and progress messages 79 | #--------------------------------------------------------------------------- 80 | QUIET = NO 81 | WARNINGS = YES 82 | WARN_IF_UNDOCUMENTED = YES 83 | WARN_IF_DOC_ERROR = YES 84 | WARN_NO_PARAMDOC = NO 85 | WARN_FORMAT = "$file:$line: $text" 86 | WARN_LOGFILE = 87 | #--------------------------------------------------------------------------- 88 | # configuration options related to the input files 89 | #--------------------------------------------------------------------------- 90 | INPUT = /Users/wimleers/School/TRPR/svn/src 91 | INPUT_ENCODING = UTF-8 92 | FILE_PATTERNS = *.c \ 93 | *.cc \ 94 | *.cxx \ 95 | *.cpp \ 96 | *.c++ \ 97 | *.d \ 98 | *.java \ 99 | *.ii \ 100 | *.ixx \ 101 | *.ipp \ 102 | *.i++ \ 103 | *.inl \ 104 | *.h \ 105 | *.hh \ 106 | *.hxx \ 107 | *.hpp \ 108 | *.h++ \ 109 | *.idl \ 110 | *.odl \ 111 | *.cs \ 112 | *.php \ 113 | *.php3 \ 114 | *.inc \ 115 | *.m \ 116 | *.mm \ 117 | *.dox \ 118 | *.py \ 119 | *.f90 120 | RECURSIVE = YES 121 | EXCLUDE = 122 | EXCLUDE_SYMLINKS = NO 123 | EXCLUDE_PATTERNS = */.moc/* \ 124 | */.svn/* \ 125 | */.rcc/* 126 | EXCLUDE_SYMBOLS = 127 | EXAMPLE_PATH = 128 | EXAMPLE_PATTERNS = * 129 | EXAMPLE_RECURSIVE = NO 130 | IMAGE_PATH = 131 | INPUT_FILTER = 132 | FILTER_PATTERNS = 133 | FILTER_SOURCE_FILES = NO 134 | #--------------------------------------------------------------------------- 135 | # configuration options related to source browsing 136 | #--------------------------------------------------------------------------- 137 | SOURCE_BROWSER = YES 138 | INLINE_SOURCES = NO 139 | STRIP_CODE_COMMENTS = YES 140 | REFERENCED_BY_RELATION = YES 141 | REFERENCES_RELATION = YES 142 | REFERENCES_LINK_SOURCE = YES 143 | USE_HTAGS = NO 144 | VERBATIM_HEADERS = YES 145 | #--------------------------------------------------------------------------- 146 | # configuration options related to the alphabetical class index 147 | #--------------------------------------------------------------------------- 148 | ALPHABETICAL_INDEX = NO 149 | COLS_IN_ALPHA_INDEX = 5 150 | IGNORE_PREFIX = 151 | #--------------------------------------------------------------------------- 152 | # configuration options related to the HTML output 153 | #--------------------------------------------------------------------------- 154 | GENERATE_HTML = YES 155 | HTML_OUTPUT = html 156 | HTML_FILE_EXTENSION = .html 157 | HTML_HEADER = 158 | HTML_FOOTER = 159 | HTML_STYLESHEET = 160 | HTML_ALIGN_MEMBERS = YES 161 | GENERATE_HTMLHELP = NO 162 | HTML_DYNAMIC_SECTIONS = NO 163 | CHM_FILE = 164 | HHC_LOCATION = 165 | GENERATE_CHI = NO 166 | BINARY_TOC = NO 167 | TOC_EXPAND = NO 168 | DISABLE_INDEX = NO 169 | ENUM_VALUES_PER_LINE = 4 170 | GENERATE_TREEVIEW = NO 171 | TREEVIEW_WIDTH = 250 172 | #--------------------------------------------------------------------------- 173 | # configuration options related to the LaTeX output 174 | #--------------------------------------------------------------------------- 175 | GENERATE_LATEX = NO 176 | LATEX_OUTPUT = latex 177 | LATEX_CMD_NAME = latex 178 | MAKEINDEX_CMD_NAME = makeindex 179 | COMPACT_LATEX = NO 180 | PAPER_TYPE = a4wide 181 | EXTRA_PACKAGES = 182 | LATEX_HEADER = 183 | PDF_HYPERLINKS = NO 184 | USE_PDFLATEX = NO 185 | LATEX_BATCHMODE = NO 186 | LATEX_HIDE_INDICES = NO 187 | #--------------------------------------------------------------------------- 188 | # configuration options related to the RTF output 189 | #--------------------------------------------------------------------------- 190 | GENERATE_RTF = NO 191 | RTF_OUTPUT = rtf 192 | COMPACT_RTF = NO 193 | RTF_HYPERLINKS = NO 194 | RTF_STYLESHEET_FILE = 195 | RTF_EXTENSIONS_FILE = 196 | #--------------------------------------------------------------------------- 197 | # configuration options related to the man page output 198 | #--------------------------------------------------------------------------- 199 | GENERATE_MAN = NO 200 | MAN_OUTPUT = man 201 | MAN_EXTENSION = .3 202 | MAN_LINKS = NO 203 | #--------------------------------------------------------------------------- 204 | # configuration options related to the XML output 205 | #--------------------------------------------------------------------------- 206 | GENERATE_XML = NO 207 | XML_OUTPUT = xml 208 | XML_SCHEMA = 209 | XML_DTD = 210 | XML_PROGRAMLISTING = YES 211 | #--------------------------------------------------------------------------- 212 | # configuration options for the AutoGen Definitions output 213 | #--------------------------------------------------------------------------- 214 | GENERATE_AUTOGEN_DEF = NO 215 | #--------------------------------------------------------------------------- 216 | # configuration options related to the Perl module output 217 | #--------------------------------------------------------------------------- 218 | GENERATE_PERLMOD = NO 219 | PERLMOD_LATEX = NO 220 | PERLMOD_PRETTY = YES 221 | PERLMOD_MAKEVAR_PREFIX = 222 | #--------------------------------------------------------------------------- 223 | # Configuration options related to the preprocessor 224 | #--------------------------------------------------------------------------- 225 | ENABLE_PREPROCESSING = YES 226 | MACRO_EXPANSION = NO 227 | EXPAND_ONLY_PREDEF = NO 228 | SEARCH_INCLUDES = YES 229 | INCLUDE_PATH = 230 | INCLUDE_FILE_PATTERNS = 231 | PREDEFINED = 232 | EXPAND_AS_DEFINED = 233 | SKIP_FUNCTION_MACROS = YES 234 | #--------------------------------------------------------------------------- 235 | # Configuration::additions related to external references 236 | #--------------------------------------------------------------------------- 237 | TAGFILES = 238 | GENERATE_TAGFILE = 239 | ALLEXTERNALS = NO 240 | EXTERNAL_GROUPS = YES 241 | PERL_PATH = /usr/bin/perl 242 | #--------------------------------------------------------------------------- 243 | # Configuration options related to the dot tool 244 | #--------------------------------------------------------------------------- 245 | CLASS_DIAGRAMS = NO 246 | MSCGEN_PATH = /Applications/Development/Doxygen.app/Contents/Resources/ 247 | HIDE_UNDOC_RELATIONS = YES 248 | HAVE_DOT = YES 249 | CLASS_GRAPH = YES 250 | COLLABORATION_GRAPH = YES 251 | GROUP_GRAPHS = YES 252 | UML_LOOK = NO 253 | TEMPLATE_RELATIONS = NO 254 | INCLUDE_GRAPH = YES 255 | INCLUDED_BY_GRAPH = YES 256 | CALL_GRAPH = NO 257 | CALLER_GRAPH = NO 258 | GRAPHICAL_HIERARCHY = YES 259 | DIRECTORY_GRAPH = YES 260 | DOT_IMAGE_FORMAT = png 261 | DOT_PATH = /Applications/Development/Doxygen.app/Contents/Resources/ 262 | DOTFILE_DIRS = 263 | DOT_GRAPH_MAX_NODES = 50 264 | MAX_DOT_GRAPH_DEPTH = 1000 265 | DOT_TRANSPARENT = YES 266 | DOT_MULTI_TARGETS = NO 267 | GENERATE_LEGEND = YES 268 | DOT_CLEANUP = YES 269 | #--------------------------------------------------------------------------- 270 | # Configuration::additions related to the search engine 271 | #--------------------------------------------------------------------------- 272 | SEARCHENGINE = NO 273 | -------------------------------------------------------------------------------- /src/Board.cpp: -------------------------------------------------------------------------------- 1 | // $Id: Board.cpp 345 2008-06-07 19:28:19Z wimleers $ 2 | 3 | #include "Board.h" 4 | 5 | 6 | //---------------------------------------------------------------------------- 7 | // Public methods. 8 | 9 | /** 10 | * Creates a new instance of Board 11 | */ 12 | Board::Board(void) { 13 | for (int i = 0; i < 9; ++i) 14 | for (int j = 0; j < 9; ++j) 15 | m_board[i][j] = 0; 16 | } 17 | 18 | /** 19 | * Creates a new instance of Board 20 | * 21 | * @param other 22 | * Board that needs to be copied 23 | */ 24 | Board::Board(const Board & other) { 25 | operator=(other); 26 | } 27 | 28 | /** 29 | * Clears all values of the board. 30 | */ 31 | void Board::Clear(void) { 32 | for (int y = 0; y < 9; ++y) 33 | for (int x = 0; x < 9; ++x) 34 | Remove(x, y); 35 | } 36 | 37 | /** 38 | * Checks if a move is valid. 39 | * 40 | * @param x 41 | * x-pos 42 | * @param y 43 | * x-pos 44 | * @param e 45 | * number we want to add 46 | * @return 47 | * Valid or invalid (already there) 48 | * @warning 49 | * Method assumes that the board is valid before the move 50 | */ 51 | bool Board::IsValidMove(int x, int y, int e) const { 52 | if (CheckHorizontal(x, y, e) && CheckVertical(x, y, e) && CheckBlock(x, y, e)) // Lazy evaluation guarantees efficient handling 53 | return true; 54 | else 55 | return false; 56 | } 57 | 58 | /** 59 | * Checks if the board itself is valid. 60 | * 61 | * @return 62 | * Valid or invalid 63 | */ 64 | bool Board::IsValid(void) const { 65 | return (IsValidHorizontal() && IsValidVertical() && IsValidBlocks()); // Lazy evaluation guarantees efficient handling 66 | } 67 | 68 | /** 69 | * Checks if the board is full. 70 | * 71 | * @return 72 | * Valid or invalid 73 | */ 74 | bool Board::IsFull(void) const { 75 | bool full = true; 76 | 77 | for (int i = 0; full && i < 9; ++i) 78 | for (int j = 0; full && j < 9; ++j) 79 | full = (m_board[i][j] != 0); 80 | 81 | return full; 82 | } 83 | 84 | /** 85 | * Counts the number of filled in elements. 86 | * 87 | * @return 88 | * Quantity 89 | */ 90 | int Board::NumFilledIn(void) const { 91 | int num = 0; 92 | 93 | for (int i = 0; i < 9; ++i) 94 | for (int j = 0; j < 9; ++j) 95 | num += (m_board[i][j] != 0) ? 1 : 0; 96 | 97 | return num; 98 | } 99 | 100 | /** 101 | * Gives the possible moves for a given position. 102 | * 103 | * @return 104 | * The possible moves, in a boolean array 105 | */ 106 | bool* Board::GetPossibleMoves(int x, int y) const { 107 | bool* output = new bool[10]; 108 | 109 | output[0] = 0; 110 | for (int i = 1; i < 10; i++) 111 | output[i] = IsValidMove(x, y, i); 112 | 113 | return output; 114 | } 115 | 116 | /** 117 | * Loads a saved board from a file 118 | * 119 | * @param fname 120 | * Name of the file to load 121 | * @return 122 | * True if saved board is found and successfully loaded 123 | */ 124 | bool Board::Import(const string & fname) { 125 | FileIO file; 126 | 127 | file.SetFileName(fname); 128 | file.Open(); 129 | FILEIO_TABLE table = file.GetTable(); 130 | 131 | if (table.size() == 9) { 132 | FILEIO_TABLE::iterator tableIt = table.begin(); 133 | FILEIO_ROW::iterator rowIt = tableIt->begin(); 134 | 135 | for (int i = 0; i < 9; ++i) { 136 | for (int j = 0; j < 9; ++j) { // Read a row from the table 137 | m_board[j][i] = atoi(rowIt->c_str()); 138 | rowIt++; 139 | } 140 | tableIt++; 141 | rowIt = tableIt->begin(); 142 | } 143 | return true; 144 | } 145 | else 146 | return false; 147 | } 148 | 149 | /** 150 | * Saves the board to a file 151 | * 152 | * @param fname 153 | * Name of file to save to 154 | * @return 155 | * True if board is successfully saved 156 | */ 157 | bool Board::Export(const string & fname) const { 158 | FileIO file; 159 | 160 | FILEIO_TABLE table; 161 | for (int i = 0; i < 9; ++i) { 162 | FILEIO_ROW row; 163 | for (int j = 0; j < 9; ++j) { 164 | char s[2]; // Because it needs to be added as a char* 165 | s[0] = m_board[j][i] + '0'; 166 | s[1] = '\0'; 167 | row.push_back(s); // Fill a row 168 | } 169 | table.push_back(row); // Add the row to the table w're going to save 170 | } 171 | 172 | file.SetFileName(fname); // Prepare FileIO object for saving. 173 | file.SetTable(table); 174 | 175 | return file.Save(); // Save returns true if all is successful. 176 | } 177 | 178 | 179 | /** 180 | * Copies the other board to this board. 181 | * 182 | * @param other 183 | * The board which we are going to copy 184 | * @return 185 | * The copied board 186 | */ 187 | Board & Board::operator=(const Board & other) { 188 | for (int i = 0; i < 9; ++i) 189 | for (int j = 0; j < 9; ++j) 190 | m_board[i][j] = other.Get(i, j); 191 | 192 | return *this; 193 | } 194 | 195 | /** 196 | * Returns a row of the board, is constant. 197 | * 198 | * @param i 199 | * Position in the array 200 | * @return 201 | * The demanded row 202 | */ 203 | const int* Board::operator[](unsigned int i) const { 204 | return m_board[i]; 205 | } 206 | 207 | /** 208 | * Returns a row of the board, is not constant. 209 | * 210 | * @param i 211 | * Position in the array 212 | * @return 213 | * The demanded row 214 | */ 215 | int* Board::operator[](unsigned int i) { 216 | return m_board[i]; 217 | } 218 | 219 | 220 | //---------------------------------------------------------------------------- 221 | // Private methods. 222 | 223 | /** 224 | * Checks a vertical row for the given element. 225 | * 226 | * @param x 227 | * x-pos 228 | * @param y 229 | * x-pos 230 | * @param e 231 | * Element we want to add 232 | * @return 233 | * Valid or invalid (already there) 234 | */ 235 | bool Board::CheckHorizontal(int x, int y, int e) const { 236 | bool valid = (m_board[x][y] == 0); // Position can't already be filled in 237 | 238 | for (int i = 0; valid && i < 9; ++i) 239 | valid = (m_board[i][y] != e); 240 | 241 | return valid; 242 | } 243 | 244 | /** 245 | * Checks a horizontal row for the given element. 246 | * 247 | * @param x 248 | * x-pos 249 | * @param y 250 | * x-pos 251 | * @param e 252 | * Element we want to add 253 | * @return 254 | * Valid or invalid (already there) 255 | */ 256 | bool Board::CheckVertical(int x, int y, int e) const { 257 | bool valid = (m_board[x][y] == 0); // Position can't already be filled in 258 | 259 | for (int i = 0; i < 9 && valid; ++i) 260 | valid = (m_board[x][i] != e); 261 | 262 | return valid; 263 | } 264 | 265 | /** 266 | * Checks a block for the given element. 267 | * 268 | * @param x 269 | * x-pos 270 | * @param y 271 | * x-pos 272 | * @param e 273 | * Element we want to add 274 | * @return 275 | * Valid or invalid (already there) 276 | */ 277 | bool Board::CheckBlock(int x, int y, int e) const { 278 | bool valid = (m_board[x][y] == 0); 279 | int startx, starty; // Position of the first element in a block 280 | 281 | startx = x - (x % 3); // Highest product of 3 282 | starty = y - (y % 3); 283 | 284 | for (int i = starty; valid && (i % 3 != 0 || i == starty); ++i) // While not in the next block, and not at end of the board 285 | for (int j = startx; valid && (j % 3 != 0 || j == startx); ++j) 286 | valid = (m_board[j][i] != e); 287 | 288 | return valid; 289 | } 290 | 291 | /** 292 | * Checks if the board is valid in horizontal direction (helper function for IsValid() ). 293 | * 294 | * @return 295 | * Valid or invalid (two or more of the same element) 296 | */ 297 | bool Board::IsValidHorizontal(void) const { 298 | bool elementOccured[10]; 299 | bool valid = true; 300 | 301 | for (int i = 0; valid && i < 9; ++i) { // Iterate over the different rows of the board 302 | for (int j = 0; j < 10; ++j) // Initialize the boolean array 303 | elementOccured[j] = false; 304 | for (int j = 0; valid && j < 9; ++j) {// Iterate over the current row 305 | int curElem = m_board[j][i]; 306 | valid = !elementOccured[curElem]; // Board is still valid if element hasn't occured yet 307 | if (curElem != 0) 308 | elementOccured[curElem] = true; 309 | } 310 | } 311 | 312 | return valid; 313 | } 314 | 315 | /** 316 | * Checks if the board is valid in vertical direction (helper function for IsValid() ). 317 | * 318 | * @return 319 | * Valid or invalid (two or more of the same element) 320 | */ 321 | bool Board::IsValidVertical(void) const { 322 | bool elementOccured[10]; 323 | bool valid = true; 324 | 325 | for (int i = 0; valid && i < 9; ++i) { // Iterate over the different columns of the board 326 | for (int j = 0; j < 10; j++) // Initialize the boolean array 327 | elementOccured[j] = false; 328 | for (int j = 0; valid && j < 9; ++j) {// Iterate over the current column 329 | int curElem = m_board[i][j]; 330 | valid = !elementOccured[curElem]; // Board is still valid if element hasn't occured yet 331 | if (curElem != 0) 332 | elementOccured[curElem] = true; 333 | } 334 | } 335 | 336 | return valid; 337 | } 338 | 339 | /** 340 | * Checks if the blocks in the board are valid (helper function for IsValid() ). 341 | * 342 | * @return 343 | * Valid or invalid (two or more of the same element) 344 | */ 345 | bool Board::IsValidBlocks(void) const { 346 | bool elementOccured[10]; 347 | bool valid = true; 348 | 349 | for (int startX = 0; valid && startX < 9; startX += 3) // Iterate over the blocks, horizontally 350 | for (int startY = 0; valid && startY < 9; startY += 3) { // Iterate over the blocks, vertically 351 | for (int j = 0; j < 10; ++j) // Initialize the boolean array 352 | elementOccured[j] = false; 353 | for (int i = startX; valid && (i % 3 != 0 || i == startX); ++i) // Iterate over the current block 354 | for (int j = startY; valid && (j % 3 != 0 || j == startY); ++j) { 355 | int curElem = m_board[j][i]; 356 | valid = !elementOccured[curElem]; // Board is still valid if element hasn't occured yet 357 | if (curElem != 0) 358 | elementOccured[curElem] = true; 359 | } 360 | } 361 | 362 | return valid; 363 | } 364 | 365 | /** 366 | * Writes the board to a QDataStream. 367 | * 368 | * @param out 369 | * The output stream 370 | * @param board 371 | * The board we want to write out 372 | * @return 373 | * The output stream (to be able to do << again) 374 | */ 375 | QDataStream& operator<<(QDataStream& out, const Board& board) { 376 | for (int i = 0; i < 9; ++i) 377 | for (int j = 0; j < 9; ++j) 378 | out << board[j][i]; 379 | 380 | return out; 381 | } 382 | 383 | /** 384 | * Reads the board from a QDataStream 385 | * 386 | * @param in 387 | * The input stream 388 | * @param board 389 | * The board we want to write out 390 | * @return 391 | * The input stream (to be able to do >> again) 392 | */ 393 | QDataStream& operator>>(QDataStream& in, Board& board) { 394 | for (int i = 0; i < 9; ++i) 395 | for (int j = 0; j < 9; ++j) 396 | in >> board[j][i]; 397 | 398 | return in; 399 | } 400 | -------------------------------------------------------------------------------- /src/Qt/SudokuHUD.cpp: -------------------------------------------------------------------------------- 1 | // $Id: SudokuHUD.cpp 350 2008-06-07 20:52:53Z wimleers $ 2 | 3 | 4 | /** 5 | * Qt SudokuHUD widget implementation. 6 | * 7 | * @file SudokuHUD.cpp 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #include "SudokuHUD.h" 13 | 14 | 15 | //---------------------------------------------------------------------------- 16 | // Constructor & destructor. 17 | 18 | SudokuHUD::SudokuHUD(void) { 19 | m_gameLoaded = false; 20 | 21 | m_duration = 0; 22 | m_validityEnabled = false; 23 | m_solvabilityEnabled = false; 24 | m_statsEnabled = false; 25 | 26 | m_calculating = false; 27 | 28 | m_animTimer = new QTimer(this); 29 | connect(m_animTimer, SIGNAL(timeout()), this, SLOT(repaintSpinner())); 30 | } 31 | 32 | SudokuHUD::~SudokuHUD(void) { 33 | m_animTimer->stop(); 34 | disconnect(m_animTimer, SIGNAL(timeout()), this, SLOT(repaintSpinner())); 35 | delete m_animTimer; 36 | } 37 | 38 | 39 | //---------------------------------------------------------------------------- 40 | // Public methods. 41 | 42 | /** 43 | * Implementation of the pure virtual boundingRect() method. 44 | */ 45 | QRectF SudokuHUD::boundingRect(void) const { 46 | return QRectF(0, 0, Dimensions::HUDWidth, Dimensions::HUDHeight); 47 | } 48 | 49 | /** 50 | * Implementation of the pure virtual paint() method. 51 | */ 52 | void SudokuHUD::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) { 53 | Q_UNUSED(widget); 54 | 55 | // Only draw the part that is exposed to the user. From: 56 | // http://thesmithfam.org/blog/2007/02/03/qt-improving-qgraphicsview-performance/. 57 | painter->setClipRect(option->exposedRect); 58 | 59 | int m = Dimensions::margin; // Marge. 60 | int c = 2 * m; // Corner size. 61 | int o = 2; // Offset. 62 | int width = Dimensions::HUDWidth - o * 2; 63 | int height = Dimensions::HUDHeight - o * 2; 64 | 65 | // Create the painter path. 66 | QPainterPath roundRectPath; 67 | roundRectPath.moveTo(o + width, o + c); 68 | roundRectPath.arcTo(o + width - c, o, c, c, 0.0, 90.0); // Top right arc. 69 | roundRectPath.lineTo(o + c, o); // Line to top left arc. 70 | roundRectPath.arcTo(o, o, c, c, 90.0, 90.0); // Top left arc. 71 | roundRectPath.lineTo(o, o + height - c); // Line to bottom left arc. 72 | roundRectPath.arcTo(o, o + height - c, c, c, 180.0, 90.0); // Bottom left arc. 73 | roundRectPath.lineTo(o + width - c, o + height); // Line to bottom right arc. 74 | roundRectPath.arcTo(o + width - c , o + height - c, c, c, 270.0, 90.0); // Bottom right arc 75 | roundRectPath.lineTo(o + width, o + c); 76 | roundRectPath.closeSubpath(); 77 | 78 | // Use a gradient for the brush. 79 | painter->setBrush(getBackgroundGradient()); 80 | 81 | // Actually draw the box! 82 | painter->setPen(QPen(QColor(56, 165, 211), 2)); 83 | painter->drawPath(roundRectPath); 84 | 85 | // Smaller pen from now on. 86 | painter->setPen(QPen(QColor(56, 165, 211), 1)); 87 | 88 | 89 | // Debug: show the bounding rects. 90 | /* 91 | painter->setPen(QPen(QColor(56, 165, 211), 1)); 92 | painter->drawRect(getBoundingRectForTimer()); 93 | painter->drawRect(getBoundingRectForValidity()); 94 | painter->drawRect(getBoundingRectForSpinner()); 95 | painter->drawRect(getBoundingRectForSpinnerText()); 96 | painter->drawRect(getBoundingRectForSolvability()); 97 | painter->drawRect(getBoundingRectForStats()); 98 | */ 99 | 100 | // Timer. 101 | if (m_gameLoaded) { 102 | QFont font = QFont(painter->font()); 103 | font.setPixelSize(ceil(0.8 * 4 * m)); 104 | painter->setFont(font); 105 | painter->drawText( 106 | getBoundingRectForTimer(), 107 | Qt::AlignCenter | Qt::AlignVCenter, 108 | QString(secondsToString(m_duration)) 109 | ); 110 | } 111 | 112 | // Validity. 113 | if (m_validityEnabled && m_gameLoaded) { 114 | double size = 0.55; 115 | size += (m_validity) ? 0.15 : 0.0; 116 | QFont font = QFont(painter->font()); 117 | font.setPixelSize(ceil(size * 4 * m)); 118 | font.setBold(true); 119 | painter->setFont(font); 120 | painter->setPen((m_validity) ? QColor(0, 202, 73) : QColor(226, 6, 30)); 121 | painter->drawText( 122 | getBoundingRectForValidity(), 123 | Qt::AlignCenter | Qt::AlignVCenter, 124 | (m_validity) ? tr("Valid") : tr("Invalid") 125 | ); 126 | } 127 | 128 | // Spinner and spinner text. 129 | if (m_calculating) { 130 | painter->setPen(QPen(QColor(56, 165, 211), 1)); 131 | painter->setBrush(QColor(56, 165, 211)); 132 | 133 | // Spinner. 134 | static int offset = 0; 135 | offset += 5; 136 | QRect r = getBoundingRectForSpinner(); 137 | for (int i = 0; i < 8; i++) 138 | painter->drawPie(r, (offset + i * 45) * 16, 30 * 16); 139 | 140 | // Spinner text. 141 | QFont font = QFont(painter->font()); 142 | font.setPixelSize(ceil(1.5 * m)); 143 | font.setBold(false); 144 | painter->setFont(font); 145 | painter->drawText( 146 | getBoundingRectForSpinnerText(), 147 | Qt::AlignCenter | Qt::AlignTop, 148 | tr("Generating ...") 149 | ); 150 | } 151 | 152 | 153 | // Solvability. 154 | if (m_solvabilityEnabled && m_gameLoaded) { 155 | double size = 0.4; 156 | size += (m_solvability) ? 0.15 : 0.0; 157 | QFont font = QFont(painter->font()); 158 | font.setPixelSize(ceil(size * 4 * m)); 159 | font.setBold(true); 160 | painter->setFont(font); 161 | painter->setPen((m_solvability) ? QColor(0, 202, 73) : QColor(226, 6, 30)); 162 | painter->drawText( 163 | getBoundingRectForSolvability(), 164 | Qt::AlignCenter | Qt::AlignVCenter, 165 | (m_solvability) ? tr("Solvable") : tr("Unsolvable") 166 | ); 167 | } 168 | 169 | // Stats. 170 | if (m_statsEnabled && m_gameLoaded) { 171 | QFont font = QFont(painter->font()); 172 | font.setPixelSize(ceil(0.24 * 4 * m)); 173 | font.setBold(false); 174 | painter->setFont(font); 175 | painter->setPen(QColor(80, 80, 80)); 176 | painter->drawText( 177 | getBoundingRectForStats(), 178 | Qt::AlignLeft | Qt::AlignVCenter, 179 | tr("Generated: %1\n\nCompleted: %2\n\nRemaining: %3").arg(m_generated).arg(m_completed).arg(m_remaining) 180 | ); 181 | } 182 | } 183 | 184 | 185 | //---------------------------------------------------------------------------- 186 | // Public slots. 187 | 188 | void SudokuHUD::duration(unsigned int duration) { 189 | m_duration = duration; 190 | update(getBoundingRectForTimer()); 191 | } 192 | 193 | void SudokuHUD::validity(bool validity) { 194 | m_validity = validity; 195 | update(getBoundingRectForValidity()); 196 | } 197 | 198 | void SudokuHUD::solvability(bool solvability) { 199 | m_solvability = solvability; 200 | update(getBoundingRectForSolvability()); 201 | } 202 | 203 | void SudokuHUD::stats(int generated, int completed, int remaining) { 204 | m_generated = generated; 205 | m_completed = completed; 206 | m_remaining = remaining; 207 | update(getBoundingRectForStats()); 208 | } 209 | 210 | void SudokuHUD::calculating(bool calculating) { 211 | if (calculating) 212 | m_animTimer->start(50); 213 | else { 214 | m_animTimer->stop(); 215 | // QT BUG: For some odd reason, an artifact is shown after the spinner 216 | // stops. Simply repainting the entire HUD fixes that. 217 | update(QRect(0, 0, Dimensions::HUDWidth, Dimensions::HUDHeight)); 218 | } 219 | m_calculating = calculating; 220 | update(getBoundingRectForSpinner().united(getBoundingRectForSpinnerText())); 221 | } 222 | 223 | 224 | //---------------------------------------------------------------------------- 225 | // Private slots. 226 | 227 | void SudokuHUD::repaintSpinner(void) { 228 | // QT BUG: in theory, getBoundingRectForSpinner() should be sufficient, 229 | // since the text doesn't have to be redrawn. However, when you do that, 230 | // there's a glitch: it seems that, due to rounding errors, Qt sometimes 231 | // renders a bit outside of the given bounding rect, so there'll be some 232 | // artifacts. So we slightly increase the size of the bounding rectangle. 233 | QRect r = getBoundingRectForSpinner(); 234 | r.adjust(-5, -5, 10, 10); 235 | update(r); 236 | } 237 | 238 | 239 | //---------------------------------------------------------------------------- 240 | // Private methods. 241 | 242 | QRect SudokuHUD::getBoundingRectForTimer(void) const { 243 | int m = Dimensions::margin; 244 | int w = Dimensions::HUDWidth - 2 * m; 245 | int h = 4 * m; 246 | return QRect(m, m, w, h); 247 | } 248 | 249 | QRect SudokuHUD::getBoundingRectForValidity(void) const { 250 | int m = Dimensions::margin; 251 | int y = 2.5 * Dimensions::elementSize + m / 2; 252 | int h = Dimensions::elementSize; 253 | int w = Dimensions::HUDWidth - 2 * m; 254 | 255 | return QRect(m, y, w, h); 256 | } 257 | 258 | QRect SudokuHUD::getBoundingRectForSolvability(void) const { 259 | int m = Dimensions::margin; 260 | int y = 5.5 * Dimensions::elementSize + m + m / 2; 261 | int h = Dimensions::elementSize; 262 | int w = Dimensions::HUDWidth - 2 * m; 263 | 264 | return QRect(m, y, w, h); 265 | } 266 | 267 | QRect SudokuHUD::getBoundingRectForStats(void) const { 268 | int m = Dimensions::margin; 269 | int h = 1.5 * Dimensions::elementSize; 270 | int w = Dimensions::HUDWidth - 5 * m; 271 | 272 | return QRect(2.5 * m, Dimensions::HUDHeight - m - h, w, h); 273 | } 274 | 275 | QRect SudokuHUD::getBoundingRectForSpinner(void) const { 276 | int m = Dimensions::margin; 277 | int y = 3.5 * Dimensions::elementSize + 2.5 * m; 278 | int s = Dimensions::HUDWidth - 10 * m; 279 | 280 | return QRect(5 * m, y, s, s); 281 | } 282 | 283 | QRect SudokuHUD::getBoundingRectForSpinnerText(void) const { 284 | QRect spinner = getBoundingRectForSpinner(); 285 | int m = Dimensions::margin; 286 | int spinnerSize = Dimensions::HUDWidth - 10 * m; 287 | int y = spinner.y() + spinnerSize + m; 288 | int w = Dimensions::HUDWidth - 4 * m; 289 | 290 | return QRect(2 * m, y, w, m * 2); 291 | } 292 | 293 | 294 | /** 295 | * Convert a number of seconds into a human readable string, e.g. if you pass 296 | * in 73 as the number of seconds, you will receive "01:13". 297 | * 298 | * @param numSeconds 299 | * A number of seconds. 300 | * @return 301 | * A human readable string. 302 | */ 303 | QString SudokuHUD::secondsToString(unsigned int numSeconds) const { 304 | unsigned int minutes = numSeconds / 60; 305 | unsigned int seconds = numSeconds % 60; 306 | 307 | QString secondsString = QString::number(seconds); 308 | if (secondsString.length() == 1) 309 | secondsString = "0" + secondsString; 310 | 311 | return QString::number(minutes) + ":" + secondsString; 312 | } 313 | 314 | /** 315 | * Get the background gradient. This allows us to generate the gradient only 316 | * *once*! 317 | * 318 | * @return 319 | * A gradient object. 320 | */ 321 | QLinearGradient SudokuHUD::getBackgroundGradient(void) { 322 | static bool generated = false; 323 | static QLinearGradient gradient; 324 | 325 | if (!generated) { 326 | // These 3 variables are essentially duplicates from SudokuHUD::paint(). 327 | int o = 2; // Offset. 328 | int width = Dimensions::HUDWidth - o * 2; 329 | int height = Dimensions::HUDHeight - o * 2; 330 | 331 | gradient = QLinearGradient (QPointF(width / 2, 0), QPointF(width / 2, height)); 332 | gradient.setColorAt(0, QColor(223, 243, 252)); // Light blue at the top. 333 | gradient.setColorAt(0.4, Qt::white); // White just above the center. 334 | gradient.setColorAt(1, QColor(223, 243, 252)); // Light blue at the bottom. 335 | 336 | generated = true; 337 | } 338 | 339 | return gradient; 340 | } 341 | -------------------------------------------------------------------------------- /src/translations/sudoku_fr.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MainWindow 5 | 6 | 7 | Open a Sudoku Game 8 | 9 | 10 | 11 | 12 | Sudoku saved game (*.sud) 13 | 14 | 15 | 16 | 17 | Failed opening saved game 18 | 19 | 20 | 21 | 22 | The saved game could not be opened. Perhaps the file is corrupt? 23 | 24 | 25 | 26 | 27 | Save a Sudoku Game 28 | 29 | 30 | 31 | 32 | Failed saving the game 33 | 34 | 35 | 36 | 37 | The game could not be saved. Perhaps you don't have sufficient permissions? 38 | 39 | 40 | 41 | 42 | Import from CSV 43 | 44 | 45 | 46 | 47 | CSV file (*.csv) 48 | 49 | 50 | 51 | 52 | Invalid board 53 | 54 | 55 | 56 | 57 | The board could not be imported, the format is invalid. 58 | 59 | 60 | 61 | 62 | Export to CSV 63 | 64 | 65 | 66 | 67 | Print Sudoku Puzzle 68 | 69 | 70 | 71 | 72 | Save Screenshot 73 | 74 | 75 | 76 | 77 | PNG image file (*.png) 78 | 79 | 80 | 81 | 82 | Sudoku 83 | 84 | 85 | 86 | 87 | Quit 88 | 89 | 90 | 91 | 92 | Ctrl+Q 93 | 94 | 95 | 96 | 97 | Game 98 | 99 | 100 | 101 | 102 | New... 103 | 104 | 105 | 106 | 107 | Ctrl+N 108 | 109 | 110 | 111 | 112 | Open saved game... 113 | 114 | 115 | 116 | 117 | Ctrl+O 118 | 119 | 120 | 121 | 122 | Save... 123 | 124 | 125 | 126 | 127 | Ctrl+S 128 | 129 | 130 | 131 | 132 | Import from CSV... 133 | 134 | 135 | 136 | 137 | Export to CSV... 138 | 139 | 140 | 141 | 142 | Print... 143 | 144 | 145 | 146 | 147 | Ctrl+R 148 | 149 | 150 | 151 | 152 | Solve game 153 | 154 | 155 | 156 | 157 | Settings 158 | 159 | 160 | 161 | 162 | Show board validity 163 | 164 | 165 | 166 | 167 | Alt+V 168 | 169 | 170 | 171 | 172 | Show board solvability 173 | 174 | 175 | 176 | 177 | Alt+S 178 | 179 | 180 | 181 | 182 | Show stats 183 | 184 | 185 | 186 | 187 | Alt+Shift+S 188 | 189 | 190 | 191 | 192 | Congratulations! 193 | 194 | 195 | 196 | 197 | Congratulations, you finished this Sudoku in %1! 198 | 199 | 200 | 201 | 202 | Pause game 203 | 204 | 205 | 206 | 207 | Reset game 208 | 209 | 210 | 211 | 212 | Full screen 213 | 214 | 215 | 216 | 217 | Ctrl+F 218 | 219 | 220 | 221 | 222 | P 223 | 224 | 225 | 226 | 227 | R 228 | 229 | 230 | 231 | 232 | S 233 | 234 | 235 | 236 | 237 | Ctrl+I 238 | 239 | 240 | 241 | 242 | Ctrl+E 243 | 244 | 245 | 246 | 247 | Export to PNG... 248 | 249 | 250 | 251 | 252 | NewGameDialog 253 | 254 | 255 | &Cancel 256 | 257 | 258 | 259 | 260 | &Start! 261 | 262 | 263 | 264 | 265 | Difficulty level 266 | 267 | 268 | 269 | 270 | PauseOverlay 271 | 272 | 273 | Paused 274 | 275 | 276 | 277 | 278 | Press %1 to continue! 279 | 280 | 281 | 282 | 283 | QApplication 284 | 285 | 286 | SudokuElement 287 | 288 | 289 | Get hints 290 | 291 | 292 | 293 | 294 | SudokuHUD 295 | 296 | 297 | Valid 298 | 299 | 300 | 301 | 302 | Solvable 303 | 304 | 305 | 306 | 307 | Unsolvable 308 | 309 | 310 | 311 | 312 | Generated: %1 313 | 314 | Completed: %2 315 | 316 | Remaining: %3 317 | 318 | 319 | 320 | 321 | Invalid 322 | 323 | 324 | 325 | 326 | Generating ... 327 | 328 | 329 | 330 | 331 | -------------------------------------------------------------------------------- /src/Sudoku.cpp: -------------------------------------------------------------------------------- 1 | // $Id: Sudoku.cpp 328 2008-06-05 20:40:51Z gooz $ 2 | 3 | #include "Sudoku.h" 4 | #include 5 | 6 | 7 | //---------------------------------------------------------------------------- 8 | // Public methods. 9 | 10 | /** 11 | * Generates a new board. 12 | * 13 | * @param level 14 | * Difficulty at which we want the board to be solvable 15 | * @param board 16 | * Pointer to memory location where we can store the board 17 | */ 18 | void Sudoku::GenerateBoard(Board * board, int level) { 19 | bool possibilities; 20 | list< PositionElement > undoList; 21 | 22 | // Generate random solved board 23 | GenerateRandomSolution(board); 24 | // Remove elements and push them on the stack until the board is not solvable anymore 25 | do { 26 | possibilities = false; // We keep track of any removed elements in the last iteration 27 | int yoffset = rand() % 9; // So we don't always start erasing the first few elements 28 | int xoffset = rand() % 9; 29 | for (int y = 0; y < 9; ++y) 30 | for (int x = 0; x < 9; ++x) { 31 | int realx = (x + xoffset) % 9; // The real x and y positions 32 | int realy = (y + yoffset) % 9; 33 | if (board->Get(realx, realy) != 0 && SolvableWithRemoval(board, realx, realy)) { // We want the board to be solvable by ScanSolve() only, so it has only 1 solution 34 | PositionElement pe(realx, realy, board->Get(realx, realy)); 35 | undoList.push_back(pe); 36 | board->Remove(realx, realy); 37 | possibilities = true; 38 | } 39 | } 40 | } while (possibilities); 41 | 42 | // Now reinsert/remove as many elements as the current level demands 43 | int reinsert = LevelToNumMoves(level); 44 | 45 | bool stillFoundOne = true; 46 | stack redoStack; 47 | while (reinsert > 0 && stillFoundOne) { 48 | stillFoundOne = false; 49 | 50 | int yoffset = rand() % 9; // So we don't always start erasing the first few elements 51 | int xoffset = rand() % 9; 52 | 53 | for (int y = 0; y < 9 && reinsert > 0 && !undoList.empty(); ++y) 54 | for (int x = 0; x < 9 && reinsert > 0 && !undoList.empty(); ++x) { 55 | int realx = (x + xoffset) % 9; // The real x and y positions 56 | int realy = (y + yoffset) % 9; 57 | 58 | PositionElement pe(realx, realy, board->Get(realx, realy)); 59 | redoStack.push(pe); 60 | board->Remove(realx, realy); // Remove the element from the board and try to solve it again 61 | 62 | bool foundOne = false; 63 | for (list::iterator it = undoList.begin(); it != undoList.end() && !foundOne;) { 64 | board->Set(it->GetX(), it->GetY(), it->GetE()); 65 | if (BoardIsSolvable(*board, true)) { // Board is solvable by ScanSolve() 66 | PositionElement addableElement(it->GetX(), it->GetY(), it->GetE()); 67 | redoStack.push(addableElement); 68 | 69 | it = undoList.erase(it); // Remove from the undoList and add to the redoStack 70 | 71 | --reinsert; 72 | foundOne = true; 73 | stillFoundOne = true; 74 | } 75 | else 76 | it++; 77 | 78 | board->Remove(realx, realy); // Removed in BOTH CASES! 79 | 80 | if (!foundOne && !redoStack.empty()) { 81 | board->Set(redoStack.top().GetX(), redoStack.top().GetY(), redoStack.top().GetE()); 82 | redoStack.pop(); 83 | } 84 | } 85 | } 86 | } 87 | 88 | if (reinsert > 0 || BoardHasFilledParts(board)) // We don't want the board to contain filled in blocks/rows/columns 89 | GenerateBoard(board, level); // So start all over again if it happens 90 | // This is much faster than checking and removing to keep the current level! 91 | else { 92 | while (!redoStack.empty()) { 93 | board->Set(redoStack.top().GetX(), redoStack.top().GetY(), redoStack.top().GetE()); 94 | redoStack.pop(); 95 | } 96 | 97 | // To remove extra elements if level > 5, THIS IS NOT BEING USED AT THIS MOMENT! 98 | for (int i = 0; i > reinsert; --i) { 99 | int randx = rand() % 9; 100 | int randy = rand() % 9; 101 | if (board->Get(randx, randy) != 0) 102 | board->Remove(randx, randy); 103 | else 104 | ++i; 105 | } 106 | } 107 | } 108 | 109 | /** 110 | * Solves the board. 111 | * 112 | * @param board 113 | * The board we are going to solve 114 | * @return 115 | * Solvable 116 | */ 117 | bool Sudoku::SolveBoard(Board * board) { 118 | bool solved = false; 119 | 120 | if (board->IsValid()) { 121 | solved = ScanSolve(board); 122 | if (!solved) 123 | solved = BackTrackSolve(board); 124 | // This algorithm will work on a board where the boxes ScanSolve() could solve are already filled in, to reduce overhead 125 | } 126 | 127 | return solved; 128 | } 129 | 130 | /** 131 | * Checks if the board can be solved (standard only with ScanSolve() ) 132 | * 133 | * @param board 134 | * The board we want to check for solvability 135 | * @param scanSolveOnly 136 | * Optional parameter, when set to false it will also try to BackTrackSolve() the board 137 | * @return 138 | * Solvable 139 | */ 140 | bool Sudoku::BoardIsSolvable(Board board, bool scanSolveOnly) { 141 | if (board.IsValid()) { 142 | bool solved = ScanSolve(&board); // Works on a copy of the original board 143 | 144 | if (!scanSolveOnly && !solved) 145 | solved = BackTrackSolve(&board); // Will make use of things already done by ScanSolve() 146 | 147 | return solved; 148 | } 149 | else 150 | return false; 151 | } 152 | 153 | 154 | //---------------------------------------------------------------------------- 155 | // Private methods. 156 | 157 | /** 158 | * Tries to solve the board in the most natural way, by eliminating possibilities (and keeping track of which numbers are possible per position). 159 | * This is the 'easiest' method, and requires the least CPU cycles. 160 | * 161 | * @param board 162 | * The board we are going to solve 163 | * @return 164 | * Solvable 165 | */ 166 | bool Sudoku::ScanSolve(Board* board) { 167 | bool found = board->IsValid(); //Only enter the filling loop if the board is valid 168 | int numPossible; 169 | bool possibleSolutions[9][9][10]; // 10 to avoid confusion (element 0 is always false) 170 | for (int y = 0; y < 9; ++y) // Preparation; set all element 0's to false, and all the rest to true 171 | for (int x = 0; x < 9; ++x) { 172 | possibleSolutions[x][y][0] = false; 173 | for (int e = 1; e < 10; ++e) 174 | possibleSolutions[x][y][e] = true; 175 | } 176 | 177 | while (!board->IsFull() && found) { // While we still found possible moves 178 | found = false; // In found we keep if we have filled in an element 179 | for (int y = 0; y < 9; ++y) 180 | for (int x = 0; x < 9; ++x) { 181 | numPossible = 0; // We keep the number of possibilities 182 | for (int e = 1; e < 10; ++e) { // Looking for possible solutions per box 183 | if (possibleSolutions[x][y][e]) { // If it hasn't already been marked as impossible (we're working incrementally) 184 | possibleSolutions[x][y][e] = board->IsValidMove(x, y, e); 185 | if (possibleSolutions[x][y][e]) // If it is still a valid move 186 | ++numPossible; 187 | } 188 | } 189 | if (numPossible == 1) { 190 | for (int e = 1; e < 10; ++e) 191 | if (possibleSolutions[x][y][e]) 192 | board->Set(x, y, e); 193 | found = true; 194 | } 195 | } 196 | } 197 | 198 | return board->IsFull(); 199 | } 200 | 201 | /** 202 | * Tries to solve the board by backtracking. 203 | * This is the 'hard' method, and requires a lot of CPU cycles. 204 | * The overhead is reduced if we always run the ScanSolve() algorithm first. 205 | * 206 | * @param board 207 | * The board we are going to solve 208 | * @param startx, starty 209 | * Startpositions (because we are working recursively) 210 | * @return 211 | * Solvable 212 | */ 213 | bool Sudoku::BackTrackSolve(Board* board, int startx, int starty) { 214 | bool solved = board->IsFull(); // If we are at a leaf we have to return true, because the board is filled 215 | bool foundzero = false; 216 | int x = startx, y; 217 | 218 | for (y = starty; !solved && !foundzero && y < 9; ++y) { // Find the first element that is not filled in (we are working recursively) 219 | for (x = startx; !foundzero && x < 9; ++x) 220 | foundzero = (board->Get(x, y) == 0); 221 | startx = 0; 222 | } 223 | x--; 224 | y--; 225 | 226 | if (!solved && foundzero) { // Only if there is a 0 found we can still backtrack, else: end this recursion 227 | for (int e = 1; e < 10 && !solved; ++e) 228 | if (board->IsValidMove(x, y, e)) { 229 | board->Set(x, y, e); 230 | if (BackTrackSolve(board, x + 1, y)) 231 | solved = true; 232 | else 233 | board->Remove(x, y); 234 | } 235 | } 236 | 237 | return solved; 238 | } 239 | 240 | /** 241 | * Generates a random solved board 242 | * 243 | * @param board 244 | * Pointer to the memory location of the board 245 | */ 246 | void Sudoku::GenerateRandomSolution(Board* board) { 247 | int x, y, numberOfRandoms; 248 | int e; 249 | 250 | srand((unsigned int) time(NULL)); // Set random number seed 251 | do { 252 | board->Clear(); 253 | numberOfRandoms = 25; // After lots of testing, 25 seemed like the right number of random items 254 | for (int i = 0; i < numberOfRandoms; ++i) { 255 | x = rand() % 9; 256 | y = rand() % 9; 257 | e = rand() % 9 + 1; 258 | if (board->IsValidMove(x, y, e)) 259 | board->Set(x, y, e); 260 | else 261 | --i; 262 | } 263 | } while (!SolveBoard(board)); 264 | } 265 | 266 | /** 267 | * Checks if the board can be solved by ScanSolve() when we remove a particular element from it 268 | * 269 | * @param board 270 | * The board we are going to solve 271 | * @param x, y 272 | * Coordinates of the element we want to remove 273 | * 274 | * @return 275 | * Solvable 276 | */ 277 | bool Sudoku::SolvableWithRemoval(Board* board, int x, int y) { 278 | bool output = false; 279 | 280 | if (board->Get(x, y) == 0) 281 | return false; 282 | 283 | int value = board->Get(x, y); // So we don't have to copy the board each time 284 | board->Remove(x, y); 285 | output = (BoardIsSolvable(*board, true)); // ScanSolve() only 286 | board->Set(x, y, value); 287 | return output; 288 | } 289 | 290 | /** 291 | * Calculates how many elements should be reinserted/removed from the board 292 | * according to the level. Helper function of GenerateBoard 293 | * 294 | * @param level 295 | * The difficulty of the board 296 | * @return 297 | * Number of moves to undo/remove (removal is negative) 298 | */ 299 | int Sudoku::LevelToNumMoves(int level) { 300 | int reinsert; 301 | 302 | switch (level) { 303 | case 1: 304 | reinsert = 4; 305 | break; 306 | case 2: 307 | reinsert = 3; 308 | break; 309 | case 3: 310 | reinsert = 2; 311 | break; 312 | case 4: 313 | reinsert = 1; 314 | break; 315 | case 5: 316 | reinsert = 0; 317 | break; 318 | case 6: 319 | reinsert = -2; 320 | break; 321 | case 7: 322 | reinsert = -5; 323 | break; 324 | default: 325 | reinsert = -5; 326 | } 327 | 328 | return reinsert; 329 | } 330 | 331 | /** 332 | * Checks if the board contains any blocks/rows/columns that are 333 | * completely filled in. 334 | * This is a helper function for GenerateBoard() 335 | * 336 | * @param board 337 | * The board we want to check 338 | * @return 339 | * Number of moves to undo/remove (removal is negative) 340 | */ 341 | bool Sudoku::BoardHasFilledParts(Board* board) { 342 | bool filledFound = false; 343 | 344 | // Rows 345 | for (int y = 0; y < 9 && !filledFound; ++y) { 346 | bool containsUnfilledElem = false; 347 | for (int x = 0; x < 9 && !containsUnfilledElem; ++x) 348 | containsUnfilledElem = (board->Get(x, y) == 0); 349 | filledFound = !containsUnfilledElem; 350 | } 351 | 352 | // Columns 353 | for (int x = 0; x < 9 && !filledFound; ++x) { 354 | bool containsUnfilledElem = false; 355 | for (int y = 0; y < 9 && !containsUnfilledElem; ++y) 356 | containsUnfilledElem = (board->Get(x, y) == 0); 357 | filledFound = !containsUnfilledElem; 358 | } 359 | 360 | //Blocks 361 | for (int startX = 0; !filledFound && startX < 9; startX += 3) // Iterate over the blocks, horizontally 362 | for (int startY = 0; !filledFound && startY < 9; startY += 3) { // Iterate over the blocks, vertically 363 | bool containsUnfilledElem = false; 364 | for (int i = startX; !containsUnfilledElem && (i % 3 != 0 || i == startX); ++i) // Iterate over the current block 365 | for (int j = startY; !containsUnfilledElem && (j % 3 != 0 || j == startY); ++j) 366 | containsUnfilledElem = (board->Get(j, i) == 0); 367 | filledFound = !containsUnfilledElem; 368 | } 369 | 370 | return filledFound; 371 | } 372 | -------------------------------------------------------------------------------- /src/Qt/SudokuElement.cpp: -------------------------------------------------------------------------------- 1 | // $Id: SudokuElement.cpp 350M 2010-05-07 00:20:33Z (local) $ 2 | 3 | 4 | /** 5 | * Qt SudokuElement widget implementation. 6 | * 7 | * The choices' coordinates are (0, 0) at the left top and (2, 2) at the 8 | * bottom right. 9 | * 10 | * @file SudokuElement.cpp 11 | * @author Wim Leers 12 | */ 13 | 14 | 15 | #include "SudokuElement.h" 16 | 17 | 18 | //---------------------------------------------------------------------------- 19 | // Constructor & destructor. 20 | 21 | SudokuElement::SudokuElement(void) { 22 | m_x = -1; 23 | m_y = -1; 24 | m_finalChoice = -1; 25 | m_choices = new bool[9]; 26 | m_focus = false; 27 | m_generated = false; 28 | 29 | for (int i = 0; i < 9; i++) 30 | m_choices[i] = false; 31 | 32 | setAcceptsHoverEvents(true); 33 | setFlag(QGraphicsItem::ItemIsFocusable); 34 | } 35 | 36 | 37 | //---------------------------------------------------------------------------- 38 | // Public methods. 39 | 40 | /** 41 | * Implementation of the pure virtual boundingRect() method. 42 | */ 43 | QRectF SudokuElement::boundingRect(void) const { 44 | return QRectF(0, 0, Dimensions::elementSize, Dimensions::elementSize); 45 | } 46 | 47 | /** 48 | * Implementation of the pure virtual paint() method. 49 | */ 50 | void SudokuElement::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) { 51 | Q_UNUSED(widget); 52 | 53 | // Use a gradient for the brush. 54 | QLinearGradient gradient = SudokuElement::getBackgroundGradient(); 55 | gradient.setColorAt(0, (m_focus) ? QColor(170, 218, 238) : QColor(223, 243, 252)); 56 | gradient.setColorAt(1, (m_focus) ? QColor(223, 243, 252) : Qt::white ); 57 | painter->setBrush(gradient); 58 | 59 | // Draw the box. 60 | painter->setPen(QPen(QColor(56, 165, 211), 1)); 61 | painter->drawRect(0, 0, Dimensions::elementSize, Dimensions::elementSize); 62 | 63 | // If no final choice is made, then draw the 9 choices inside the box. 64 | // Otherwise, we draw the final choice at an item-filling size. 65 | if (m_finalChoice == -1) { 66 | // Increase the foint point size temporarily. 67 | QFont font = QFont(painter->font()); 68 | font.setPixelSize(Dimensions::elementChoiceSize); 69 | painter->setFont(font); 70 | 71 | for (int number = 1; number <= 9; number++) { 72 | bool chosen = m_choices[number - 1]; 73 | if (chosen || m_focus) { // We draw the number if it's either chosen or if the user is hovering over this item. 74 | 75 | // When chosen: dark blue pen, light blue-ish otherwise. 76 | painter->setPen(QPen((chosen) ? QColor(3, 73, 104) : QColor(87, 106, 114), 0)); 77 | 78 | // When chosen: bold font. 79 | QFont font = QFont(painter->font()); 80 | font.setBold(chosen); 81 | painter->setFont(font); 82 | 83 | // Draw! 84 | painter->drawText( 85 | getBoundingRectForChoice(number), 86 | Qt::AlignCenter | Qt::AlignVCenter, 87 | QString::number(number) 88 | ); 89 | } 90 | } 91 | } 92 | else { 93 | // Font. 94 | QFont font = QFont(painter->font()); 95 | // Increase the font point size temporarily. 96 | font.setPixelSize(0.6 * Dimensions::elementSize); 97 | // Make the font bold if it's not a generated element. 98 | font.setBold(!m_generated); 99 | painter->setFont(font); 100 | 101 | // Pen. 102 | painter->setPen(QPen((m_generated) ? QColor(56, 165, 211) : Qt::black, 1)); 103 | 104 | painter->drawText( 105 | getBoundingRectForFinalChoice(), 106 | Qt::AlignCenter | Qt::AlignVCenter, 107 | QString::number(m_finalChoice) 108 | ); 109 | } 110 | } 111 | 112 | /** 113 | * Set all choices and schedule a repaint. 114 | * 115 | * @param choices 116 | * An array of 9 bools, with false meaning that the choice is not set. 117 | */ 118 | void SudokuElement::setChoices(bool const * choices) { 119 | for (int i = 0; i < 9; i++) 120 | m_choices[i] = choices[i]; 121 | 122 | update(); 123 | } 124 | 125 | /** 126 | * Get the SudokuElement's generated state. 127 | */ 128 | bool SudokuElement::getGenerated(void) const { 129 | return m_generated; 130 | } 131 | 132 | /** 133 | * Set the SudokuElement's generated state. 134 | * 135 | * @param generated 136 | * Whether this SudokuElement should be rendered as if it were generated. 137 | */ 138 | void SudokuElement::setGenerated(bool generated) { 139 | m_generated = generated; 140 | 141 | // TRICKY: we cannot just disable a generated element, because we could 142 | // then longer focus on it. And that's necessary for keyboard navigation. 143 | // So instead, we disregard all events except for keyboard navigation 144 | // events by checking if m_generated is true. 145 | //setEnabled(!generated); 146 | } 147 | 148 | /** 149 | * Enable a choice. 150 | * 151 | * @param number 152 | * A number (1-9). 153 | */ 154 | void SudokuElement::enableChoice(int number) { 155 | if (number < 1 || number > 9) 156 | qFatal("SudokuElement::setChoice(): number was not in the valid range (1-9). number: %d.", number); 157 | 158 | m_choices[number - 1] = true; 159 | } 160 | 161 | /** 162 | * Disable a choice. 163 | * 164 | * @param number 165 | * A number (1-9). 166 | */ 167 | void SudokuElement::disableChoice(int number) { 168 | if (number < 1 || number > 9) 169 | qFatal("SudokuElement::unsetChoice(): number was not in the valid range (1-9). number: %d.", number); 170 | 171 | m_choices[number - 1] = false; 172 | } 173 | 174 | /** 175 | * Set the final choice. 176 | * 177 | * @param number 178 | * A number (1-9). 179 | */ 180 | void SudokuElement::setFinalChoice(int number) { 181 | if (number < 1 || number > 9) 182 | qFatal("SudokuElement::setFinalChoice(): number was not in the valid range (1-9). number: %d.", number); 183 | 184 | m_finalChoice = number; 185 | } 186 | 187 | /** 188 | * Unset the final choice. 189 | */ 190 | void SudokuElement::unsetFinalChoice(void) { 191 | m_finalChoice = -1; 192 | } 193 | 194 | 195 | //---------------------------------------------------------------------------- 196 | // Protected methods. 197 | 198 | /** 199 | * Override of hoverEnterEvent(). 200 | */ 201 | void SudokuElement::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { 202 | Q_UNUSED(event); 203 | setFocus(Qt::OtherFocusReason); 204 | } 205 | 206 | /** 207 | * Override of hoverLeaveEvent(). 208 | */ 209 | void SudokuElement::hoverLeaveEvent(QGraphicsSceneHoverEvent * event) { 210 | Q_UNUSED(event); 211 | clearFocus(); 212 | } 213 | 214 | /** 215 | * Override of mousePressEvent(). 216 | */ 217 | void SudokuElement::mousePressEvent(QGraphicsSceneMouseEvent * event) { 218 | if (m_generated) // This is a generated element, so ignore this event. 219 | return; 220 | 221 | if (m_finalChoice != -1) 222 | return; 223 | 224 | int number = getChoiceByMousePos(event->lastScenePos()); 225 | if (number > 0) { 226 | // Send out signals to allow for responding events. 227 | if (!m_choices[number - 1]) 228 | emit enableChoice(m_x, m_y, number); 229 | else 230 | emit disableChoice(m_x, m_y, number); 231 | } 232 | } 233 | 234 | /** 235 | * Override of mouseDoubleClickEvent(). 236 | */ 237 | void SudokuElement::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event) { 238 | if (m_generated) // This is a generated element, so ignore this event. 239 | return; 240 | 241 | // If there's no final choice set yet, allow this double click to set it. 242 | if (m_finalChoice == -1) { 243 | int number = getChoiceByMousePos(event->lastScenePos()); 244 | if (number > 0) 245 | emit setFinalChoice(m_x, m_y, number); 246 | } 247 | // Otherwise, if a final choice is set, allow this double click to unset 248 | // it. 249 | else if (QRectF(getBoundingRectForFinalChoice()).contains(mapFromScene(event->lastScenePos()))) 250 | emit unsetFinalChoice(m_x, m_y); 251 | } 252 | 253 | /** 254 | * Override of contextMenuEvent(). 255 | */ 256 | void SudokuElement::contextMenuEvent(QGraphicsSceneContextMenuEvent * event) { 257 | Q_UNUSED(event); 258 | 259 | // FIXME: This does not work in Mac OS X, due to a bug in Qt: this method 260 | // is never called! 261 | 262 | /* 263 | QMenu menu; 264 | QAction * hint = menu.addAction(tr("Get hints")); 265 | menu.show(); 266 | */ 267 | } 268 | 269 | /** 270 | * Override of focusInEvent(). 271 | */ 272 | void SudokuElement::focusInEvent(QFocusEvent * event) { 273 | Q_UNUSED(event); 274 | 275 | m_focus = true; 276 | update(); 277 | } 278 | 279 | /** 280 | * Override of focusOutEvent(). 281 | */ 282 | void SudokuElement::focusOutEvent(QFocusEvent * event) { 283 | Q_UNUSED(event); 284 | 285 | m_focus = false; 286 | update(); 287 | } 288 | 289 | /** 290 | * Override of keyPressEvent(). 291 | */ 292 | void SudokuElement::keyPressEvent(QKeyEvent * event) { 293 | // Selection movement. 294 | switch (event->key()) { 295 | case Qt::Key_Left: // Left arrow. 296 | emit selectOtherSudokuElement(m_x, m_y, m_x - 1, m_y); 297 | break; 298 | case Qt::Key_Up: // Top arrow. 299 | emit selectOtherSudokuElement(m_x, m_y, m_x, m_y - 1); 300 | break; 301 | case Qt::Key_Right: // Right arrow. 302 | emit selectOtherSudokuElement(m_x, m_y, m_x + 1, m_y); 303 | break; 304 | case Qt::Key_Down: // Bottom arrow. 305 | emit selectOtherSudokuElement(m_x, m_y, m_x, m_y + 1); 306 | break; 307 | } 308 | 309 | 310 | 311 | // This is a generated element, so only allow keyboard navigation. 312 | if (m_generated) 313 | return; 314 | 315 | 316 | 317 | // Final choices and choices. 318 | if (event->key() >= Qt::Key_1 && event->key() <= Qt::Key_9) { 319 | int number = event->key() - Qt::Key_0; 320 | 321 | #ifdef Q_OS_MACX 322 | #define CHOICE_MODIFIER Qt::AltModifier 323 | #else 324 | #define CHOICE_MODIFIER Qt::ControlModifier 325 | #endif 326 | 327 | if (event->modifiers() != CHOICE_MODIFIER) // Final choice. 328 | if (number != m_finalChoice) 329 | emit setFinalChoice(m_x, m_y, number); 330 | else 331 | emit unsetFinalChoice(m_x, m_y); 332 | else // Choice. 333 | if (!m_choices[number - 1]) 334 | emit enableChoice(m_x, m_y, number); 335 | else 336 | emit disableChoice(m_x, m_y, number); 337 | } 338 | 339 | // Unset a final choice. 340 | if (event->key() == Qt::Key_Backspace) 341 | emit unsetFinalChoice(m_x, m_y); 342 | 343 | if (event->key() == Qt::Key_H) 344 | emit loadHints(m_x, m_y); 345 | } 346 | 347 | 348 | //---------------------------------------------------------------------------- 349 | // Private methods. 350 | 351 | /** 352 | * Get the number by passing in a mouse position (from the scene). 353 | * 354 | * @param scenePos 355 | * The mouse position on the scene. 356 | * @return 357 | * An integer between 0 and 9: 358 | * - 0 if the position does not map to a number 359 | * - 1-9 if it does map 360 | */ 361 | int SudokuElement::getChoiceByMousePos(const QPointF & scenePos) const { 362 | QPointF itemPos = mapFromScene(scenePos); 363 | 364 | for (int number = 1; number <= 9; number++) 365 | // NOTE: if it turns out to be hard for the user to click the number, 366 | // we can always upscale the bounding rectangle for the number, 367 | // allowing him to click just beside it. 368 | if (QRectF(getBoundingRectForChoice(number)).contains(itemPos)) 369 | return number; 370 | 371 | return 0; 372 | } 373 | 374 | /** 375 | * Get the bounding rectangle for a number, and determine the number by its 376 | * position in the SudokuElement ((x, y) coordinates, where x, y = {0, 1, 2}). 377 | * 378 | * @param x 379 | * x coordinate of a number in the SudokuElement 380 | * @param y 381 | * y coordinate of a number in the SudokuElement 382 | * @return 383 | * A bounding rectangle 384 | */ 385 | QRect SudokuElement::getBoundingRectForChoiceByCoords(int x, int y) const { 386 | if (x < 0 || x > 2 || y < 0 || y > 2) 387 | qFatal("SudokuElement::getBoundingRectForChoiceByCoords(): x or y was not in the valid range (0-2). x: %d, y: %d.", x, y); 388 | 389 | int offset = (Dimensions::elementSize - (3 * Dimensions::elementChoiceSize)) / 4; 390 | int nextNumberOffset = offset + Dimensions::elementChoiceSize; 391 | 392 | return QRect( 393 | offset + x * nextNumberOffset, 394 | offset + y * nextNumberOffset, 395 | Dimensions::elementChoiceSize, 396 | Dimensions::elementChoiceSize 397 | ); 398 | } 399 | 400 | /** 401 | * Get the bounding rectangle for a number. 402 | * 403 | * @param number 404 | * A number (1-9). 405 | * @return 406 | * A bounding rectangle. 407 | */ 408 | QRect SudokuElement::getBoundingRectForChoice(int number) const { 409 | if (number < 1 || number > 9) 410 | qFatal("SudokuElement::getboundingRectForChoice(): number was not in the valid range (1-9). number: %d.", number); 411 | 412 | for (int y = 0; y < 3; y++) 413 | for (int x = 0; x < 3; x++) 414 | if (--number == 0) 415 | return getBoundingRectForChoiceByCoords(x, y); 416 | 417 | // This should never be reached! 418 | qFatal("The number %d could not be found!", number); 419 | return QRect(); 420 | } 421 | 422 | /** 423 | * Get the bounding rectangle for the final choice. 424 | * 425 | * @return 426 | * A bounding rectangle. 427 | */ 428 | QRect SudokuElement::getBoundingRectForFinalChoice(void) const { 429 | return QRect(ceil(0.1 * Dimensions::elementSize), ceil(0.1 * Dimensions::elementSize), ceil(0.8 * Dimensions::elementSize), ceil(0.8 * Dimensions::elementSize)); 430 | } 431 | 432 | /** 433 | * Get the background gradient, without the colors set. This allows us to 434 | * generate the gradient only *once*! 435 | * 436 | * @return 437 | * A gradient object for which only the colors still have to be set. 438 | */ 439 | QLinearGradient SudokuElement::getBackgroundGradient(void) { 440 | static bool generated = false; 441 | static QLinearGradient gradient; 442 | 443 | if (!generated) { 444 | double start = (double) Dimensions::elementSize / 6; 445 | double end = (double) Dimensions::elementSize * 5 / 6; 446 | gradient = QLinearGradient(QPointF(start, start), QPointF(end, end)); 447 | 448 | generated = true; 449 | } 450 | 451 | return gradient; 452 | } 453 | -------------------------------------------------------------------------------- /src/translations/sudoku_nl.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MainWindow 6 | 7 | 8 | Open a Sudoku Game 9 | Open een Sudoku Spel 10 | 11 | 12 | 13 | Sudoku saved game (*.sud) 14 | Opgeslagen Sudoku spel (*.sud) 15 | 16 | 17 | 18 | Failed opening saved game 19 | Kon opgeslagen spel niet openen 20 | 21 | 22 | 23 | The saved game could not be opened. Perhaps the file is corrupt? 24 | Het opgeslagen spel kon niet geopend worden. Misschien is dit bestand corrupt? 25 | 26 | 27 | 28 | Save a Sudoku Game 29 | Sla een Sudoku spel op 30 | 31 | 32 | 33 | Failed saving the game 34 | Kon spel niet opslaan 35 | 36 | 37 | 38 | The game could not be saved. Perhaps you don't have sufficient permissions? 39 | Het spel kon niet opgeslagen worden. Misschien heeft u niet voldoende rechten? 40 | 41 | 42 | 43 | Import from CSV 44 | Importeer van CSV 45 | 46 | 47 | 48 | CSV file (*.csv) 49 | CSV bestand (*.csv) 50 | 51 | 52 | 53 | Invalid board 54 | Ongeldig bord 55 | 56 | 57 | 58 | The board could not be imported, the format is invalid. 59 | Dit bord kon niet ingelezen worden, het formaat is ongeldig. 60 | 61 | 62 | 63 | Export to CSV 64 | Exporteer naar CSV 65 | 66 | 67 | 68 | Print Sudoku Puzzle 69 | Print Sudoku Puzzel 70 | 71 | 72 | 73 | Save Screenshot 74 | Sla Screenshot op 75 | 76 | 77 | 78 | PNG image file (*.png) 79 | PNG afbeelding (*.png) 80 | 81 | 82 | 83 | Preferences 84 | Voorkeuren 85 | 86 | 87 | 88 | Sudoku 89 | Sudoku 90 | 91 | 92 | 93 | Preferences... 94 | Voorkeuren... 95 | 96 | 97 | 98 | Ctrl+P 99 | Ctrl+P 100 | 101 | 102 | 103 | Quit 104 | Afsluiten 105 | 106 | 107 | 108 | Ctrl+Q 109 | Ctrl+Q 110 | 111 | 112 | 113 | Game 114 | Spel 115 | 116 | 117 | 118 | New... 119 | Nieuw... 120 | 121 | 122 | 123 | Ctrl+N 124 | Ctrl+n 125 | 126 | 127 | 128 | Open saved game... 129 | Open opgeslagen spel... 130 | 131 | 132 | 133 | Ctrl+O 134 | Ctrl+O 135 | 136 | 137 | 138 | Save... 139 | Opslaan... 140 | 141 | 142 | 143 | Ctrl+S 144 | Ctrl+S 145 | 146 | 147 | 148 | Import from CSV... 149 | Importeer van CSV... 150 | 151 | 152 | 153 | Ctrl+Shift+I 154 | Ctrl+Shift+I 155 | 156 | 157 | 158 | Export to CSV... 159 | Exporteer naar CSV... 160 | 161 | 162 | 163 | Ctrl+Shift+E 164 | Ctrl+Shift+E 165 | 166 | 167 | 168 | Print... 169 | Afdrukken... 170 | 171 | 172 | 173 | Make Screenshot... 174 | Maak Screenshot... 175 | 176 | 177 | 178 | Ctrl+R 179 | Ctrl+R 180 | 181 | 182 | 183 | Solve game 184 | Geef oplossing 185 | 186 | 187 | 188 | Ctrl+Shift+S 189 | Ctrl+Shift+S 190 | 191 | 192 | 193 | Settings 194 | Instellingen 195 | 196 | 197 | 198 | Show board validity 199 | Laat geldigheid van het bord zien 200 | 201 | 202 | 203 | Alt+V 204 | Alt+V 205 | 206 | 207 | 208 | Show board solvability 209 | Laat bord oplosbaarheid zien 210 | 211 | 212 | 213 | Alt+S 214 | Alt+S 215 | 216 | 217 | 218 | Show stats 219 | Laat statistieken zien 220 | 221 | 222 | 223 | Alt+Shift+S 224 | Alt+Shift+S 225 | 226 | 227 | 228 | Congratulations! 229 | Proficiat! 230 | 231 | 232 | 233 | Congratulations, you finished this Sudoku in %1! 234 | Proficiat, u heeft deze Suoku in %1 opgelost! 235 | 236 | 237 | 238 | Pause game 239 | Pauzeer spel 240 | 241 | 242 | 243 | Ctrl+Shift+P 244 | Ctrl+Shift+P 245 | 246 | 247 | 248 | Reset game 249 | Reset spel 250 | 251 | 252 | 253 | Ctrl+Shift+R 254 | Ctrl+Shift+R 255 | 256 | 257 | 258 | Full screen 259 | Volledig scherm 260 | 261 | 262 | 263 | Ctrl+F 264 | Ctrl+F 265 | 266 | 267 | 268 | P 269 | P 270 | 271 | 272 | 273 | R 274 | R 275 | 276 | 277 | 278 | S 279 | S 280 | 281 | 282 | 283 | Ctrl+I 284 | Ctrl+I 285 | 286 | 287 | 288 | Ctrl+E 289 | Ctrl+E 290 | 291 | 292 | 293 | Export to PNG... 294 | Exporteer naar PNG... 295 | 296 | 297 | 298 | NewGameDialog 299 | 300 | 301 | &Cancel 302 | &Annuleren 303 | 304 | 305 | 306 | &Start! 307 | &Start! 308 | 309 | 310 | 311 | Difficulty level 312 | Moeilijkheidsgraad 313 | 314 | 315 | 316 | PauseOverlay 317 | 318 | 319 | Paused 320 | Gepauzeerd 321 | 322 | 323 | 324 | Press %1 to continue! 325 | Druk op %1 om verder te gaan! 326 | 327 | 328 | 329 | QApplication 330 | 331 | 332 | Paused 333 | Gepauzeerd 334 | 335 | 336 | 337 | SudokuElement 338 | 339 | 340 | Get hints 341 | Laat hints zien 342 | 343 | 344 | 345 | SudokuHUD 346 | 347 | 348 | Valid 349 | Geldig 350 | 351 | 352 | 353 | Invalid 354 | Ongeldig 355 | 356 | 357 | 358 | Solvable 359 | Oplosbaar 360 | 361 | 362 | 363 | Unsolvable 364 | Niet oplosbaar 365 | 366 | 367 | 368 | Generated: %1 369 | 370 | Completed: %2 371 | 372 | Remaining: %3 373 | Gegenereerd: %1 374 | 375 | Vervolledigd: %2 376 | 377 | Overblijvend: %3 378 | 379 | 380 | 381 | Calculating ... 382 | Berekenen... 383 | 384 | 385 | 386 | Generating ... 387 | Genereren... 388 | 389 | 390 | 391 | -------------------------------------------------------------------------------- /src/Qt/MainWindow.cpp: -------------------------------------------------------------------------------- 1 | // $Id: MainWindow.cpp 373 2008-06-09 00:44:58Z wimleers $ 2 | 3 | 4 | /** 5 | * MainWindow class implementation. 6 | * 7 | * @file MainWindow.cpp 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #include "MainWindow.h" 13 | 14 | 15 | //---------------------------------------------------------------------------- 16 | // Constructor. 17 | 18 | MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) { 19 | aPause = NULL; // to prevent crashes when deactivating the app when aPause hasn't been loaded yet. 20 | 21 | setupSudokuMenu(); 22 | setupSettingsMenu(); 23 | 24 | m_game = NULL; 25 | m_scene = new SudokuScene(aPause); 26 | enableGameActions(false); 27 | m_view = new SudokuView(m_scene); 28 | m_view->setEnabled(false); 29 | setCentralWidget(m_view); 30 | 31 | #ifdef Q_OS_MACX 32 | extern void qt_mac_set_dock_menu(QMenu *); 33 | qt_mac_set_dock_menu(mSudoku); 34 | qDebug() << "[MACOSX] " << "Set the 'Game' menu as a Dock menu."; 35 | #endif 36 | 37 | setupWindow(); 38 | setupOther(); 39 | 40 | setAttribute(Qt::WA_DeleteOnClose, true); 41 | 42 | // The MainWindow has to be displayed first, or no Sheet-style window will 43 | // be used on Mac OS X! 44 | show(); 45 | 46 | // Start a new game by default. 47 | spawnNewGameDialog(); 48 | } 49 | 50 | 51 | //---------------------------------------------------------------------------- 52 | // Public methods. 53 | 54 | /** 55 | * The application was deactivated: pause the game! 56 | */ 57 | void MainWindow::deactivated(void) { 58 | pauseGameHelper(); 59 | } 60 | 61 | 62 | //---------------------------------------------------------------------------- 63 | // Private slots. 64 | 65 | /** 66 | * Spawn a NewGameDialog. This is a modal dialog, so the main window will 67 | * block automatically. 68 | */ 69 | void MainWindow::spawnNewGameDialog(void) { 70 | pauseGameHelper(); 71 | 72 | m_newGameDialog = new NewGameDialog(this); 73 | m_newGameDialog->show(); 74 | connect(m_newGameDialog, SIGNAL(newGame(int)), this, SLOT(newGame(int))); 75 | } 76 | 77 | /** 78 | * Create a new game with the given difficulty. 79 | */ 80 | void MainWindow::newGame(int difficulty) { 81 | if (m_newGameDialog != NULL) { 82 | disconnect(m_newGameDialog, SIGNAL(newGame(int)), this, SLOT(newGame(int))); 83 | delete m_newGameDialog; 84 | } 85 | newGame(new SudokuGame(difficulty), false); 86 | } 87 | 88 | /** 89 | * Create or load a game, update the scene and all signals. 90 | */ 91 | void MainWindow::newGame(SudokuGame * game, bool isLoaded) { 92 | m_view->setEnabled(true); 93 | 94 | // Disconnect old signals if this isn't the first game. 95 | if (m_game != NULL) { 96 | // Make sure the current game isn't paused when the new game is loaded! 97 | aPause->setChecked(false); 98 | 99 | disconnect(m_game, SIGNAL(finished(unsigned int)), this, SLOT(gameFinished(unsigned int))); 100 | disconnect(aPause, SIGNAL(toggled(bool)), m_game, SLOT(pause(bool))); 101 | disconnect(aReset, SIGNAL(triggered()), m_game, SLOT(reset())); 102 | } 103 | 104 | // If no pointer to a SudokuGame object was passed, create one. 105 | if (game == NULL) 106 | game = new SudokuGame(1); 107 | 108 | m_game = game; 109 | m_scene->setGame(m_game); 110 | 111 | // Connect all signals and trigger an update of all states, causing the 112 | // corresponding signals to be sent. 113 | connect(m_game, SIGNAL(finished(unsigned int)), this, SLOT(gameFinished(unsigned int))); 114 | connect(aPause, SIGNAL(toggled(bool)), m_game, SLOT(pause(bool))); 115 | connect(aReset, SIGNAL(triggered()), m_game, SLOT(reset())); 116 | 117 | // If this game is being loaded, then restore its settings. 118 | if (isLoaded) { 119 | aValidity->setChecked(m_game->validityCalculation()); 120 | aSolvability->setChecked(m_game->solvabilityCalculation()); 121 | aStats->setChecked(m_game->statsCalculation()); 122 | } 123 | 124 | // Pass through the HUD settings. 125 | m_scene->validityEnabled(aValidity->isChecked()); 126 | m_scene->solvabilityEnabled(aSolvability->isChecked()); 127 | m_scene->statsEnabled(aStats->isChecked()); 128 | } 129 | 130 | /** 131 | * Load a game. 132 | */ 133 | void MainWindow::loadGame(void) { 134 | pauseGameHelper(); 135 | 136 | const QString fileName = QFileDialog::getOpenFileName( 137 | this, 138 | tr("Open a Sudoku Game"), 139 | "~/Documents", 140 | tr("Sudoku saved game (*.sud)") 141 | ); 142 | 143 | if (!fileName.isEmpty()) { 144 | SudokuGame * game = new SudokuGame(); 145 | if (game->load(fileName)) 146 | newGame(game, true); 147 | else 148 | QMessageBox::warning(this, tr("Failed opening saved game"), tr("The saved game could not be opened. Perhaps the file is corrupt?")); 149 | } 150 | } 151 | 152 | /** 153 | * Save a game. 154 | */ 155 | void MainWindow::saveGame(void) { 156 | pauseGameHelper(); 157 | 158 | const QString fileName = QFileDialog::getSaveFileName( 159 | this, 160 | tr("Save a Sudoku Game"), 161 | "~/Documents/untitled.sud", 162 | tr("Sudoku saved game (*.sud)") 163 | ); 164 | 165 | if (!fileName.isEmpty() && !m_game->save(fileName)) 166 | QMessageBox::warning(this, tr("Failed saving the game"), tr("The game could not be saved. Perhaps you don't have sufficient permissions?")); 167 | } 168 | 169 | /** 170 | * Solve a game. 171 | */ 172 | void MainWindow::solveGame(void) { 173 | m_game->reset(); 174 | m_game->solve(); 175 | } 176 | 177 | /** 178 | * Import a board from a CSV file. 179 | */ 180 | void MainWindow::importBoardFromCSV(void) { 181 | pauseGameHelper(); 182 | 183 | const QString fileName = QFileDialog::getOpenFileName( 184 | this, 185 | tr("Import from CSV"), 186 | "~/Documents", 187 | tr("CSV file (*.csv)") 188 | ); 189 | 190 | if (!fileName.isEmpty()) { 191 | Board * board = new Board(); 192 | if (board->Import(fileName.toStdString())) { 193 | SudokuGame * game = new SudokuGame(board); 194 | newGame(game); 195 | } 196 | else 197 | QMessageBox::warning(this, tr("Invalid board"), tr("The board could not be imported, the format is invalid.")); 198 | } 199 | } 200 | 201 | /** 202 | * Export a board to a CSV file. 203 | */ 204 | void MainWindow::exportBoardToCSV(void) { 205 | pauseGameHelper(); 206 | 207 | const QString fileName = QFileDialog::getSaveFileName( 208 | this, 209 | tr("Export to CSV"), 210 | "~/Documents/untitled.csv", 211 | tr("CSV file (*.csv)") 212 | ); 213 | 214 | if (!fileName.isEmpty()) 215 | m_game->getBoard()->Export(fileName.toStdString()); 216 | } 217 | 218 | /** 219 | * Show the game in full screen, or go back to the normal view. 220 | */ 221 | void MainWindow::fullScreen(bool enabled) { 222 | if (enabled) 223 | showFullScreen(); 224 | else 225 | showNormal(); 226 | } 227 | 228 | /** 229 | * Print the current Sudoku puzzle. 230 | */ 231 | void MainWindow::print(void) { 232 | pauseGameHelper(); 233 | 234 | // Set up the printer. 235 | QPrinter printer(QPrinter::HighResolution); 236 | printer.setPageSize(QPrinter::A4); 237 | 238 | // Configure a dialog, execute it, and on accept, print the Sudoku! 239 | QPrintDialog * dialog = new QPrintDialog(&printer, this); 240 | dialog->setWindowTitle(tr("Print Sudoku Puzzle")); 241 | if (dialog->exec() == QDialog::Accepted) { 242 | QPainter painter(&printer); 243 | m_scene->renderBoard(&painter); 244 | } 245 | delete dialog; 246 | } 247 | 248 | /** 249 | * Make a screenshot of the current Sudoku puzzle. 250 | */ 251 | void MainWindow::screenshot(void) { 252 | pauseGameHelper(); 253 | 254 | const QString fileName = QFileDialog::getSaveFileName( 255 | this, 256 | tr("Save Screenshot"), 257 | "~/Desktop/Sudoku.png", 258 | tr("PNG image file (*.png)") 259 | ); 260 | 261 | if (!fileName.isEmpty()) { 262 | QImage image(Dimensions::boardSize, Dimensions::boardSize, QImage::Format_ARGB32); 263 | 264 | QPainter painter(&image); 265 | m_scene->renderBoard(&painter); 266 | 267 | image.save(fileName, "PNG"); 268 | } 269 | } 270 | 271 | /** 272 | * The user finished the game! 273 | */ 274 | void MainWindow::gameFinished(unsigned int duration) { 275 | m_view->setEnabled(false); 276 | enableGameActions(false); 277 | QMessageBox::information(this, tr("Congratulations!"), tr("Congratulations, you finished this Sudoku in %1!").arg(secondsToString(duration))); 278 | // TODO: animations? 279 | } 280 | 281 | void MainWindow::enableGameActions(bool enable) { 282 | aSave->setEnabled(enable); 283 | 284 | aPause->setEnabled(enable); 285 | aReset->setEnabled(enable); 286 | aSolve->setEnabled(enable); 287 | 288 | aExportToCSV->setEnabled(enable); 289 | aPrint->setEnabled(enable); 290 | aExportToPNG->setEnabled(enable); 291 | 292 | aValidity->setEnabled(enable); 293 | aSolvability->setEnabled(enable); 294 | aStats->setEnabled(enable); 295 | } 296 | 297 | 298 | //---------------------------------------------------------------------------- 299 | // Private methods. 300 | 301 | /** 302 | * Set up the 'Sudoku' menu. 303 | */ 304 | void MainWindow::setupSudokuMenu(void) { 305 | #ifdef Q_OS_MACX 306 | mSudoku = menuBar()->addMenu(tr("Game")); 307 | qDebug() << "[MACOSX] " << "Renamed the 'Sudoku' menu to 'Game' to prevent duplicate menu names."; 308 | #else 309 | mSudoku = menuBar()->addMenu(tr("Sudoku")); 310 | #endif 311 | 312 | aNew = mSudoku->addAction(tr("New...")); 313 | aNew->setShortcut(tr("Ctrl+N")); 314 | connect(aNew, SIGNAL(triggered()), this, SLOT(spawnNewGameDialog())); 315 | 316 | mSudoku->addSeparator(); 317 | 318 | aOpen = mSudoku->addAction(tr("Open saved game...")); 319 | aOpen->setShortcut(tr("Ctrl+O")); 320 | connect(aOpen, SIGNAL(triggered()), this, SLOT(loadGame())); 321 | aSave = mSudoku->addAction(tr("Save...")); 322 | aSave->setShortcut(tr("Ctrl+S")); 323 | connect(aSave, SIGNAL(triggered()), this, SLOT(saveGame())); 324 | 325 | mSudoku->addSeparator(); 326 | 327 | aPause = mSudoku->addAction(tr("Pause game")); 328 | aPause->setShortcut(tr("P")); 329 | aPause->setCheckable(true); 330 | aPause->setChecked(false); 331 | aReset = mSudoku->addAction(tr("Reset game")); 332 | aReset->setShortcut(tr("R")); 333 | aSolve = mSudoku->addAction(tr("Solve game")); 334 | aSolve->setShortcut(tr("S")); 335 | connect(aSolve, SIGNAL(triggered()), this, SLOT(solveGame())); 336 | 337 | mSudoku->addSeparator(); 338 | 339 | aImportFromCSV = mSudoku->addAction(tr("Import from CSV...")); 340 | aImportFromCSV->setShortcut(tr("Ctrl+I")); 341 | connect(aImportFromCSV, SIGNAL(triggered()), this, SLOT(importBoardFromCSV())); 342 | aExportToCSV = mSudoku->addAction(tr("Export to CSV...")); 343 | aExportToCSV->setShortcut(tr("Ctrl+E")); 344 | connect(aExportToCSV, SIGNAL(triggered()), this, SLOT(exportBoardToCSV())); 345 | aExportToPNG = mSudoku->addAction(tr("Export to PNG...")); 346 | aExportToPNG->setShortcut(tr("Ctrl+R")); 347 | connect(aExportToPNG, SIGNAL(triggered()), this, SLOT(screenshot())); 348 | aPrint = mSudoku->addAction(tr("Print...")); 349 | aPrint->setShortcut(QKeySequence::Print); 350 | connect(aPrint, SIGNAL(triggered()), this, SLOT(print())); 351 | 352 | mSudoku->addSeparator(); 353 | 354 | aFullScreen = mSudoku->addAction(tr("Full screen")); 355 | aFullScreen->setShortcut(tr("Ctrl+F")); 356 | aFullScreen->setCheckable(true); 357 | aFullScreen->setChecked(false); 358 | connect(aFullScreen, SIGNAL(toggled(bool)), this, SLOT(fullScreen(bool))); 359 | 360 | #ifndef Q_OS_MACX 361 | mSudoku->addSeparator(); 362 | 363 | aQuit = mSudoku->addAction(tr("Quit")); 364 | aQuit->setShortcut(tr("Ctrl+Q")); 365 | connect(aQuit, SIGNAL(triggered()), this, SLOT(close())); 366 | #endif 367 | } 368 | 369 | /** 370 | * Set up the 'Settings' menu. 371 | */ 372 | void MainWindow::setupSettingsMenu(void) { 373 | QSettings settings; 374 | 375 | mSettings = menuBar()->addMenu(tr("Settings")); 376 | 377 | aValidity = mSettings->addAction(tr("Show board validity")); 378 | aValidity->setShortcut(tr("Alt+V")); 379 | aValidity->setCheckable(true); 380 | aValidity->setChecked(settings.value("hud/validity", true).toBool()); 381 | 382 | aSolvability = mSettings->addAction(tr("Show board solvability")); 383 | aSolvability->setShortcut(tr("Alt+S")); 384 | aSolvability->setCheckable(true); 385 | aSolvability->setChecked(settings.value("hud/solvability", false).toBool()); 386 | 387 | mSettings->addSeparator(); 388 | 389 | aStats = mSettings->addAction(tr("Show stats")); 390 | aStats->setShortcut(tr("Alt+Shift+S")); 391 | aStats->setCheckable(true); 392 | aStats->setChecked(settings.value("hud/stats", true).toBool()); 393 | } 394 | 395 | /** 396 | * Set up the window. 397 | */ 398 | void MainWindow::setupWindow(void) { 399 | // Window title and icon. 400 | setWindowTitle(tr("Sudoku")); 401 | setWindowIcon(QIcon(":/resources/images/icon.png")); 402 | 403 | 404 | // Set the Window size and center it. 405 | int height = Dimensions::sceneHeight; 406 | int width = Dimensions::sceneWidth; 407 | QDesktopWidget * d = QApplication::desktop(); 408 | int x = (d->width() - width) / 2; 409 | int y = (d->height() - height) / 2; 410 | setGeometry(x, y, width, height); 411 | setMinimumSize(width, height); 412 | 413 | qDebug() << "[general]" << "Detected desktop resolution:" << d->width() << "x" << d->height(); 414 | qDebug() << "[general]" << "Window size:" << width << "x" << height; 415 | qDebug() << "[general]" << "Window position: (" << x << "," << y << ")"; 416 | 417 | #ifdef Q_OS_MACX 418 | // Use the unified title and toolbar look on Mac OS X 10.4 and later. 419 | setUnifiedTitleAndToolBarOnMac(true); 420 | qDebug() << "[MACOSX]" << "Use the unified title and toolbar."; 421 | #endif 422 | } 423 | 424 | /** 425 | * Set up other stuff. 426 | */ 427 | void MainWindow::setupOther(void) { 428 | // SudokuView repaints relatively slow, prevent performance issues. 429 | setAnimated(false); 430 | 431 | connect(m_scene, SIGNAL(gameIsActive(bool)), this, SLOT(enableGameActions(bool))); 432 | // Connect HUD settings signals. 433 | connect(aValidity, SIGNAL(toggled(bool)), m_scene, SLOT(validityEnabled(bool))); 434 | connect(aSolvability, SIGNAL(toggled(bool)), m_scene, SLOT(solvabilityEnabled(bool))); 435 | connect(aStats, SIGNAL(toggled(bool)), m_scene, SLOT(statsEnabled(bool))); 436 | } 437 | 438 | void MainWindow::pauseGameHelper(void) { 439 | if (aPause != NULL && m_game != NULL && !m_game->isFinished()) 440 | aPause->setChecked(true); 441 | } 442 | 443 | /** 444 | * Convert a number of seconds into a human readable string, e.g. if you pass 445 | * in 73 as the number of seconds, you will receive "01:13". 446 | * 447 | * @param numSeconds 448 | * A number of seconds. 449 | * @return 450 | * A human readable string. 451 | */ 452 | QString MainWindow::secondsToString(unsigned int numSeconds) const { 453 | unsigned int minutes = numSeconds / 60; 454 | unsigned int seconds = numSeconds % 60; 455 | 456 | QString secondsString = QString::number(seconds); 457 | if (secondsString.length() == 1) 458 | secondsString = "0" + secondsString; 459 | 460 | return QString::number(minutes) + ":" + secondsString; 461 | } 462 | -------------------------------------------------------------------------------- /src/Qt/SudokuScene.cpp: -------------------------------------------------------------------------------- 1 | // $Id: SudokuScene.cpp 358 2008-06-08 13:13:41Z wimleers $ 2 | 3 | 4 | /** 5 | * Qt SudokuScene implementation. 6 | * 7 | * @file SudokuScene.cpp 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #include "SudokuScene.h" 13 | 14 | 15 | //---------------------------------------------------------------------------- 16 | // Constructor & destructor. 17 | 18 | SudokuScene::SudokuScene(QAction * mainWindowPauseAction) { 19 | m_game = NULL; 20 | m_paused = false; 21 | m_currentScale = 1.0; 22 | m_pauseOverlay = new PauseOverlay(); 23 | m_pauseOverlay->setZValue(1); 24 | 25 | // Create the event filter that will be used when the pause overlay is 26 | // active. 27 | m_filter = new PauseOverlayEventFilter(this, mainWindowPauseAction); 28 | 29 | setSceneRect(0, 0, Dimensions::sceneWidth, Dimensions::sceneHeight); 30 | 31 | // Allocate all 81 SudokuElement widgets and all 9 QGraphicsRectItem 32 | // widgets that will be used as boxes. 33 | m_elements = new SudokuElement * [9]; 34 | m_boxes = new QGraphicsRectItem [9]; 35 | for (int i = 0; i < 9; i++) 36 | m_elements[i] = new SudokuElement [9]; 37 | 38 | // Now add them to the scene. 39 | int xOffset = 0, yOffset = 0; 40 | for (int i = 0; i < 9; i++) { 41 | xOffset += (i == 3 || i == 6) ? Dimensions::margin : 0; 42 | yOffset = 0; 43 | for (int j = 0; j < 9; j++) { 44 | yOffset += (j == 3 || j == 6) ? Dimensions::margin : 0; 45 | SudokuElement * e = (&m_elements[i][j]); 46 | e->setPos(xOffset + Dimensions::margin + Dimensions::elementSize * i, yOffset + Dimensions::margin + Dimensions::elementSize * j); 47 | e->setX(i); 48 | e->setY(j); 49 | addItem(e); 50 | 51 | if ((i + 1) % 3 == 0 && (j + 1) % 3 == 0) { 52 | // Accentuate the boxes with thicker lines. 53 | static int box_id = -1; 54 | box_id++; 55 | m_boxes[box_id].setRect(QRectF(xOffset + Dimensions::margin + Dimensions::elementSize * (i - 2), yOffset + Dimensions::margin + Dimensions::elementSize * (j - 2), Dimensions::elementSize * 3, Dimensions::elementSize * 3)); 56 | m_boxes[box_id].setPen(QPen(QColor(36, 156, 206), 2)); 57 | addItem(&m_boxes[box_id]); 58 | } 59 | } 60 | } 61 | 62 | // Allocate the HUD and add it to the scene. 63 | m_hud = new SudokuHUD(); 64 | m_hud->setPos(Dimensions::boardSize + Dimensions::margin, Dimensions::margin); 65 | addItem(m_hud); 66 | 67 | // Set up the animation timer. 68 | // TODO: animations! 69 | m_animationTimer = new QTimer(this); 70 | connect(m_animationTimer, SIGNAL(timeout()), SLOT(animationStep())); 71 | } 72 | 73 | SudokuScene::~SudokuScene(void) { 74 | for (int i = 0; i < 9; i++) 75 | delete m_elements[i]; 76 | delete m_elements; 77 | 78 | m_animationTimer->stop(); 79 | delete m_animationTimer; 80 | delete m_pauseOverlay; 81 | delete m_filter; 82 | } 83 | 84 | 85 | //---------------------------------------------------------------------------- 86 | // Public methods. 87 | 88 | /** 89 | * Set a new game, but if an animation is still in progress, then wait until 90 | * it has finished. 91 | * 92 | * @param game 93 | * A SudokuGame object. 94 | */ 95 | void SudokuScene::setGame(SudokuGame * game) { 96 | // If an animation is still executing, then we must wait until the 97 | // animation has finished before we can set the new game. If we don't do 98 | // this, animationStep() might try to act on the already deleted object, 99 | // causing a crash. 100 | if (this->isWorking() || (m_game && m_game->isWorking())) 101 | m_pendingNewGame = game; 102 | else { 103 | pause(false); // Remove the pause overlay immediately because we're setting a new game! 104 | setNewGameHelper(game); 105 | } 106 | } 107 | 108 | /** 109 | * When a board is being generated in a separate thread, it's necessary to 110 | * resync the generated elements to show them in the scene. 111 | */ 112 | void SudokuScene::resyncGeneratedElements(void) { 113 | Board const * originalBoard = m_game->getOriginalBoard(); 114 | for (int i = 0; i < 9; i++) 115 | for (int j = 0; j < 9; j++) { 116 | m_elements[i][j].setGenerated(originalBoard->Get(i, j) > 0); 117 | m_elements[i][j].setEnabled(true); 118 | } 119 | 120 | m_game->performAllCalculations(true); 121 | updateSudoku(); 122 | } 123 | 124 | /** 125 | * Wrapper to make it easy to render only the board, and without a possible 126 | * pause overlay. Typically used for printing the board or exporting to a 127 | * image. 128 | * 129 | * @see QGraphicsScene::render() 130 | */ 131 | void SudokuScene::renderBoard(QPainter * painter, const QRectF & target) { 132 | bool wasPaused = m_paused; 133 | if (wasPaused) 134 | pause(false); 135 | 136 | render(painter, target, QRect(0, 0, Dimensions::boardSize, Dimensions::boardSize)); 137 | 138 | if (wasPaused) 139 | pause(true); 140 | } 141 | 142 | /** 143 | * Check if the scene is working (i.e. if animations are in progress). 144 | */ 145 | bool SudokuScene::isWorking(void) const { 146 | return m_animationTimer->isActive(); 147 | } 148 | 149 | /** 150 | * Resize the scene, including all SudokuElements. 151 | * 152 | * @param width 153 | * The new width of the scene (in pixels). 154 | * @param height 155 | * The new height of the scene (in pixels). 156 | */ 157 | void SudokuScene::resizeScene(int width, int height) { 158 | setSceneRect(0, 0, width, height); 159 | 160 | // If the new aspect ratio (width to height) is greater than the initial 161 | // one, we must scale using width, otherwise using height, to maintain 162 | // the aspect ratio of the scene (and as a result to keep all items of the 163 | // scene visible). 164 | double scale; 165 | if ((double) width / height > Dimensions::sceneRatio()) 166 | scale = (double) height / Dimensions::sceneHeight; 167 | else 168 | scale = (double) width / Dimensions::sceneWidth; 169 | 170 | // Store in CPU registry because this will be used to scale *every* item 171 | // in the scene! 172 | register double scaledScale = scale / m_currentScale; 173 | 174 | QGraphicsItem * item; 175 | foreach (item, this->items()) { 176 | item->setPos(item->pos() * scaledScale); 177 | item->scale(scaledScale, scaledScale); 178 | } 179 | 180 | m_currentScale = scale; 181 | } 182 | 183 | 184 | //---------------------------------------------------------------------------- 185 | // Public slots. 186 | 187 | void SudokuScene::updateSudoku(void) { 188 | for (int x = 0; x < 9; x++) 189 | for (int y = 0; y < 9; y++) 190 | updateSudokuElementHelper(x, y); 191 | 192 | update(QRect(Dimensions::margin, Dimensions::margin, Dimensions::boardSize, Dimensions::boardSize)); 193 | } 194 | 195 | void SudokuScene::updateSudokuElement(int x, int y) { 196 | updateSudokuElementHelper(x, y); 197 | 198 | // TODO: add a tighter bounding rectangle to improve speed. 199 | update(QRect(Dimensions::margin, Dimensions::margin, Dimensions::boardSize, Dimensions::boardSize)); 200 | } 201 | 202 | void SudokuScene::validityEnabled(bool enabled) { 203 | m_game->enableValidityCalculation(enabled); 204 | m_hud->validityEnabled(enabled); 205 | 206 | QSettings settings; 207 | settings.setValue("hud/validity", enabled); 208 | 209 | if (enabled) 210 | m_game->performAllCalculations(true); 211 | 212 | update(QRect(Dimensions::boardSize + Dimensions::margin, Dimensions::margin, Dimensions::HUDWidth, Dimensions::HUDHeight)); 213 | } 214 | 215 | void SudokuScene::solvabilityEnabled(bool enabled) { 216 | m_game->enableSolvabilityCalculation(enabled); 217 | m_hud->solvabilityEnabled(enabled); 218 | 219 | QSettings settings; 220 | settings.setValue("hud/solvability", enabled); 221 | 222 | if (enabled) 223 | m_game->performAllCalculations(true); 224 | 225 | update(QRect(Dimensions::boardSize + Dimensions::margin, Dimensions::margin, Dimensions::HUDWidth, Dimensions::HUDHeight)); 226 | } 227 | 228 | void SudokuScene::statsEnabled(bool enabled) { 229 | m_game->enableStatsCalculation(enabled); 230 | m_hud->statsEnabled(enabled); 231 | 232 | QSettings settings; 233 | settings.setValue("hud/stats", enabled); 234 | 235 | if (enabled) 236 | m_game->performAllCalculations(true); 237 | 238 | update(QRect(Dimensions::boardSize + Dimensions::margin, Dimensions::margin, Dimensions::HUDWidth, Dimensions::HUDHeight)); 239 | } 240 | 241 | 242 | //---------------------------------------------------------------------------- 243 | // Private slots. 244 | 245 | /** 246 | * Handle all animations in the scene. 247 | */ 248 | void SudokuScene::animationStep(void) { 249 | if (m_pendingNewGame != NULL) 250 | setNewGameHelper(m_pendingNewGame); 251 | else { 252 | // TODO: animations. 253 | 254 | emit moveFinished(); 255 | } 256 | } 257 | 258 | /** 259 | * Show a PauseOverlay when the game is paused. 260 | * 261 | * @param paused 262 | * true when the game is paused, false otherwise. 263 | */ 264 | void SudokuScene::pause(bool paused) { 265 | if (!m_paused && paused) { 266 | m_pauseOverlay->scale(m_currentScale, m_currentScale); 267 | addItem(m_pauseOverlay); 268 | installEventFilter(m_filter); 269 | update(QRectF(0, 0, Dimensions::boardSize, Dimensions::boardSize)); 270 | } 271 | else if (m_paused && !paused) { 272 | removeItem(m_pauseOverlay); 273 | double scaleBack = 1.0 / m_currentScale; 274 | m_pauseOverlay->scale(scaleBack, scaleBack); 275 | removeEventFilter(m_filter); 276 | update(QRectF(0, 0, Dimensions::boardSize, Dimensions::boardSize)); 277 | } 278 | m_paused = paused; 279 | } 280 | 281 | /** 282 | * Move the focus from one element to another. Used for keyboard navigation. 283 | * 284 | * @param fromX 285 | * The x-coordinate of the element that has focus. 286 | * @param fromY 287 | * The y-coordinate of the element that has focus. 288 | * @param toX 289 | * The x-coordinate of the element that the focus will move to. 290 | * @param toY 291 | * The y-coordinate of the element that the focus will move to. 292 | */ 293 | void SudokuScene::moveFocus(int fromX, int fromY, int toX, int toY) { 294 | Q_UNUSED(fromX); 295 | Q_UNUSED(fromY); 296 | 297 | // TODO: animation? 298 | 299 | toX = (toX < 0) ? 8 : toX % 9; 300 | toY = (toY < 0) ? 8 : toY % 9; 301 | setFocusItem(&m_elements[toX][toY], Qt::PopupFocusReason); 302 | } 303 | 304 | /** 305 | * Load the hints for an element. 306 | * 307 | * @param x 308 | * The x-coordinate of the element for which the hints will be loaded. 309 | * @param y 310 | * The y-coordinate of the element for which the hints will be loaded. 311 | */ 312 | void SudokuScene::loadHints(int x, int y) { 313 | bool * hints = m_game->getBoard()->GetPossibleMoves(x, y); 314 | 315 | // We received an array of 10 bools, with the first array element being 316 | // meaningless. Convert to an array of 9 bools. 317 | bool * preparedHints = new bool[9]; 318 | for (int i = 0; i < 9; i++) 319 | preparedHints[i] = hints[i + 1]; 320 | 321 | m_game->setChoices(x, y, preparedHints); 322 | 323 | delete hints; 324 | delete preparedHints; 325 | } 326 | 327 | 328 | //---------------------------------------------------------------------------- 329 | // Private methods. 330 | 331 | /** 332 | * Set a new game (assume that no animation is in progress), make the 333 | * necessary connections, etc. 334 | * 335 | * @param game 336 | * A SudokuGame object. 337 | */ 338 | void SudokuScene::setNewGameHelper(SudokuGame * game) { 339 | m_animationTimer->stop(); 340 | 341 | // If an old game exists (i.e. if this isn't the first game of this 342 | // session), disconnect all signals and delete it from memory. 343 | if (m_game != NULL) { 344 | // The current game ended, make the HUD stop displaying info. 345 | m_hud->gameLoaded(false); 346 | 347 | for (int i = 0; i < 9; i++) { 348 | for (int j = 0; j < 9; j++) { 349 | disconnect(&m_elements[i][j], SIGNAL(enableChoice(int, int, int)), m_game, SLOT(enableChoice(int, int, int))); 350 | disconnect(&m_elements[i][j], SIGNAL(disableChoice(int, int, int)), m_game, SLOT(disableChoice(int, int, int))); 351 | disconnect(&m_elements[i][j], SIGNAL(setFinalChoice(int, int, int)), m_game, SLOT(setFinalChoice(int, int, int))); 352 | disconnect(&m_elements[i][j], SIGNAL(unsetFinalChoice(int, int)), m_game, SLOT(unsetFinalChoice(int, int))); 353 | } 354 | } 355 | disconnect(m_game, SIGNAL(changed(int, int)), this, SLOT(updateSudokuElement(int, int))); 356 | disconnect(m_game, SIGNAL(paused(bool)), this, SLOT(pause(bool))); 357 | disconnect(m_game, SIGNAL(ready(bool)), this, SLOT(resyncGeneratedElements())); 358 | 359 | // HUD connections. 360 | disconnect(m_game, SIGNAL(validityChanged(bool, int, int)), m_hud, SLOT(validity(bool))); 361 | disconnect(m_game, SIGNAL(solvabilityChanged(bool, int, int)), m_hud, SLOT(solvability(bool))); 362 | disconnect(m_game, SIGNAL(statsChanged(int, int, int)), m_hud, SLOT(stats(int, int, int))); 363 | disconnect(m_game, SIGNAL(durationUpdated(unsigned int)), m_hud, SLOT(duration(unsigned int))); 364 | disconnect(m_game, SIGNAL(working(bool)), m_hud, SLOT(calculating(bool))); 365 | disconnect(m_game, SIGNAL(ready(bool)), m_hud, SLOT(gameLoaded(bool))); 366 | 367 | delete m_game; 368 | } 369 | 370 | m_game = game; 371 | m_pendingNewGame = NULL; // If this was a pending game, it's now active! 372 | 373 | // Connect signals. 374 | Board const * originalBoard = m_game->getOriginalBoard(); 375 | for (int i = 0; i < 9; i++) { 376 | for (int j = 0; j < 9; j++) { 377 | if (originalBoard != NULL) 378 | m_elements[i][j].setGenerated(originalBoard->Get(i, j) > 0); 379 | connect(&m_elements[i][j], SIGNAL(enableChoice(int, int, int)), m_game, SLOT(enableChoice(int, int, int))); 380 | connect(&m_elements[i][j], SIGNAL(disableChoice(int, int, int)), m_game, SLOT(disableChoice(int, int, int))); 381 | connect(&m_elements[i][j], SIGNAL(setFinalChoice(int, int, int)), m_game, SLOT(setFinalChoice(int, int, int))); 382 | connect(&m_elements[i][j], SIGNAL(unsetFinalChoice(int, int)), m_game, SLOT(unsetFinalChoice(int, int))); 383 | 384 | connect(&m_elements[i][j], SIGNAL(loadHints(int, int)), this, SLOT(loadHints(int, int))); 385 | connect(&m_elements[i][j], SIGNAL(selectOtherSudokuElement(int, int, int, int)), this, SLOT(moveFocus(int, int, int, int))); 386 | } 387 | } 388 | connect(m_game, SIGNAL(changed(int, int)), this, SLOT(updateSudokuElement(int, int))); 389 | connect(m_game, SIGNAL(paused(bool)), this, SLOT(pause(bool))); 390 | connect(m_game, SIGNAL(ready(bool)), this, SLOT(resyncGeneratedElements())); 391 | 392 | // HUD connections. 393 | connect(m_game, SIGNAL(validityChanged(bool, int, int)), m_hud, SLOT(validity(bool))); 394 | connect(m_game, SIGNAL(solvabilityChanged(bool, int, int)), m_hud, SLOT(solvability(bool))); 395 | connect(m_game, SIGNAL(statsChanged(int, int, int)), m_hud, SLOT(stats(int, int, int))); 396 | connect(m_game, SIGNAL(durationUpdated(unsigned int)), m_hud, SLOT(duration(unsigned int))); 397 | connect(m_game, SIGNAL(working(bool)), m_hud, SLOT(calculating(bool))); 398 | connect(m_game, SIGNAL(ready(bool)), m_hud, SLOT(gameLoaded(bool))); 399 | 400 | // Give focus to the SudokuElement in the middle! 401 | setFocusItem(&m_elements[4][4], Qt::PopupFocusReason); 402 | 403 | updateSudoku(); 404 | 405 | // Start the game: 406 | // - when this is a saved game or an import: starts the game immediately. 407 | // - when this is a new game for which a board has to be generated: start 408 | // generating the board and after that finished, actually start the game. 409 | for (int i = 0; i < 9; i++) 410 | for (int j = 0; j < 9; j++) 411 | m_elements[i][j].setEnabled(false); 412 | m_game->start(); 413 | 414 | emit gameIsActive(true); 415 | } 416 | 417 | 418 | void SudokuScene::updateSudokuElementHelper(int x, int y) { 419 | // Final choice. 420 | int value = m_game->getFinalChoice(x, y); 421 | if (value == -1) 422 | m_elements[x][y].unsetFinalChoice(); 423 | else 424 | m_elements[x][y].setFinalChoice(value); 425 | 426 | // Choices. 427 | m_elements[x][y].setChoices(m_game->getChoices(x, y)); 428 | } 429 | -------------------------------------------------------------------------------- /doc/analyseverslag.lyx: -------------------------------------------------------------------------------- 1 | #LyX 1.5.3 created this file. For more info see http://www.lyx.org/ 2 | \lyxformat 276 3 | \begin_document 4 | \begin_header 5 | \textclass article 6 | \language dutch 7 | \inputencoding auto 8 | \font_roman default 9 | \font_sans default 10 | \font_typewriter default 11 | \font_default_family default 12 | \font_sc false 13 | \font_osf false 14 | \font_sf_scale 100 15 | \font_tt_scale 100 16 | \graphics default 17 | \paperfontsize default 18 | \spacing single 19 | \papersize a4paper 20 | \use_geometry false 21 | \use_amsmath 1 22 | \use_esint 1 23 | \cite_engine basic 24 | \use_bibtopic false 25 | \paperorientation portrait 26 | \secnumdepth 3 27 | \tocdepth 3 28 | \paragraph_separation indent 29 | \defskip medskip 30 | \quotes_language english 31 | \papercolumns 1 32 | \papersides 1 33 | \paperpagestyle default 34 | \tracking_changes false 35 | \output_changes false 36 | \author "" 37 | \author "" 38 | \end_header 39 | 40 | \begin_body 41 | 42 | \begin_layout Title 43 | Project Sudoku: Analyseverslag 44 | \end_layout 45 | 46 | \begin_layout Author 47 | Wim Leers (0623800) en Bram Bonné (0623825) 48 | \end_layout 49 | 50 | \begin_layout Section 51 | Beschrijving van het onderwerp 52 | \end_layout 53 | 54 | \begin_layout Subsection 55 | Doel en interpretatie 56 | \end_layout 57 | 58 | \begin_layout Standard 59 | Het doel is om een Sudoku-programma in Qt te ontwikkelen dat zowel gebruikt 60 | kan worden om het spel Sudoku te spelen (op gegenereerde spelborden) als 61 | om Sudoku's zelf op te lossen (deze kunnen dan door de gebruiker ingevoerd 62 | worden). 63 | \end_layout 64 | 65 | \begin_layout Standard 66 | De spelregels die we zullen toepassen zijn deze die op Wikipedia 67 | \begin_inset Foot 68 | status open 69 | 70 | \begin_layout Standard 71 | http://en.wikipedia.org/wiki/Sudoku 72 | \end_layout 73 | 74 | \end_inset 75 | 76 | gevonden kunnen worden. 77 | Eventueel (als we tijd genoeg hebben) zouden we ook de spelvariant Hypersudoku 78 | \begin_inset Foot 79 | status open 80 | 81 | \begin_layout Standard 82 | http://en.wikipedia.org/wiki/Hypersudoku 83 | \end_layout 84 | 85 | \end_inset 86 | 87 | kunnen implementeren, gezien dan enkel het 'check'-algoritme aangepast 88 | zou moeten worden. 89 | \end_layout 90 | 91 | \begin_layout Subsection 92 | Extra's 93 | \end_layout 94 | 95 | \begin_layout Standard 96 | De extra's waar we momenteel aan denken zijn de volgende: 97 | \end_layout 98 | 99 | \begin_layout Itemize 100 | Opslaan en inlezen van de huidige sudoku, zodat een spel later verdergezet 101 | kan worden. 102 | \end_layout 103 | 104 | \begin_layout Itemize 105 | Verschillende moeilijkheidsgraden, gebaseerd op het aantal mogelijke manieren 106 | om de sudoku op te lossen. 107 | \end_layout 108 | 109 | \begin_layout Itemize 110 | Hints (mogelijke zetten, zowel voor 1 geselecteerd vakje als voor het hele 111 | bord). 112 | \end_layout 113 | 114 | \begin_layout Itemize 115 | Een volledig vertaalbaar programma (door middel van translation files). 116 | \end_layout 117 | 118 | \begin_layout Itemize 119 | Doxygen documentatie (en dus ook Doxygen tags in de code). 120 | \end_layout 121 | 122 | \begin_layout Itemize 123 | Platformonafhankelijkheid. 124 | \end_layout 125 | 126 | \begin_layout Itemize 127 | Hypersudoku (als de tijd het toelaat). 128 | \end_layout 129 | 130 | \begin_layout Section 131 | Analyse 132 | \end_layout 133 | 134 | \begin_layout Subsection 135 | Klassediagram 136 | \end_layout 137 | 138 | \begin_layout Standard 139 | \begin_inset Graphics 140 | filename UML-080209.png 141 | scale 50 142 | 143 | \end_inset 144 | 145 | 146 | \end_layout 147 | 148 | \begin_layout Subsection 149 | ADT's 150 | \end_layout 151 | 152 | \begin_layout Description 153 | Board Dit is een klasse die een array van enums (met waarden 154 | \family typewriter 155 | EMPTY 156 | \family default 157 | of 0-9) en de operaties hierop voorziet, alsook enkele kleine checkfuncties 158 | die betrekking hebben tot het bord zelf. 159 | Deze functies zijn 160 | \family typewriter 161 | isValidMove() 162 | \family default 163 | , 164 | \family typewriter 165 | isValid() 166 | \family default 167 | en 168 | \family typewriter 169 | isSolvable() 170 | \family default 171 | . 172 | Ook kan hij een array inlezen uit, of wegschrijven naar een bestand (met 173 | behulp van de 174 | \family typewriter 175 | FileIO 176 | \family default 177 | klasse). 178 | \end_layout 179 | 180 | \begin_layout Description 181 | Sudoku Deze klasse voorziet de algoritmes voor het spel. 182 | De oplosser voor het bord ( 183 | \family typewriter 184 | SolveBoard() 185 | \family default 186 | ) zetten we hierin, alsook de 187 | \family typewriter 188 | GenerateBoard() 189 | \family default 190 | functie. 191 | Beiden krijgen een pointer naar een 192 | \family typewriter 193 | Board 194 | \family default 195 | element mee waarop ze moeten werken. 196 | \end_layout 197 | 198 | \begin_layout Description 199 | QtGame Dit is de klasse waarin alle interactie met de gebruiker (het Qt-gedeelte 200 | ) alsook het spelverloop geregeld wordt. 201 | Deze klasse heeft dus de nodige signals en slots om het spelverloop in 202 | goede banen te leiden. 203 | In de slots (maw: wanneer de gebruiker een bepaalde actie uitvoert) wordt 204 | natuurlijk gebruik gemaakt van de methods van de Sudoku klasse. 205 | Deze klasse bevat ook het spelbord. 206 | \end_layout 207 | 208 | \begin_layout Description 209 | MainWindow Deze klasse is – zoals de naam al aangeeft – de klasse voor het 210 | venster dat de andere Qt widgets bevat. 211 | Voor het belangrijkste element, het spelbord, gaan we hoogstwaarschijnlijk 212 | een 213 | \family typewriter 214 | QGraphicsView 215 | \family default 216 | gebruiken. 217 | Op deze manier kunnen we op een zeer flexibele manier het spelbord vormgeven 218 | en kunnen we geavanceerde interacties voorzien. 219 | Deze bespreking is slechts een 220 | \emph on 221 | inschatting 222 | \emph default 223 | van hoe de klassestructuur van het Qt-gedeelte er zal gaan uitzien: het 224 | is onmogelijk om nu al te zeggen hoe het er precies zal gaan uitzien. 225 | \end_layout 226 | 227 | \begin_layout Description 228 | FileIO: De klasse die gebruikt wordt voor de FileIO zelf, dus niet voor 229 | het schrijven naar een bord. 230 | Gegevens die uit een bestand komen of in een bestand gaan worden via queues 231 | behandeld. 232 | We hergebruiken deze klasse uit ons vorig project (Reversi).s 233 | \end_layout 234 | 235 | \begin_layout Description 236 | Exception: De standaard exception klasse. 237 | Deze wordt nooit op zichzelf geïnstantieerd, maar kent de afgeleide klassen 238 | 239 | \family typewriter 240 | QtException 241 | \family default 242 | , 243 | \family typewriter 244 | InternalException 245 | \family default 246 | en 247 | \family typewriter 248 | FileException 249 | \family default 250 | , die respectievelijk fouten bij de interface, het interne gedeelte en het 251 | lezen/schrijven naar bestanden opvangen. 252 | \end_layout 253 | 254 | \begin_layout Subsection 255 | Algoritmes 256 | \end_layout 257 | 258 | \begin_layout Standard 259 | De moeilijke algoritmes bevinden zich voornamelijk in de 260 | \family typewriter 261 | Sudoku 262 | \family default 263 | en de 264 | \family typewriter 265 | Board 266 | \family default 267 | klassen. 268 | Deze bespreken we dan ook hier. 269 | \end_layout 270 | 271 | \begin_layout Standard 272 | De belangrijkste van deze functies is de 273 | \family typewriter 274 | isValidMove() 275 | \family default 276 | functie, gezien deze gaat kijken of het zetten van een element op een bepaalde 277 | plaats nog een geldig bord oplevert. 278 | Deze functie zal ervan uit gaan dat het bord dat wordt meegegeven reeds 279 | een geldig bord is, om op die manier zo efficiënt mogelijk de huidige zet 280 | te kunnen controleren. 281 | Hij gaat dan voor de meegegeven zet in de parameters kijken of het vakje 282 | al ingevuld is en daarna onderzoeken of hetzelfde getal al horizontaal, 283 | verticaal of in het 3x3 blokje voorkomt. 284 | De functie wordt gebruikt door zowat alle functies die iets te maken hebben 285 | met het controleren, het oplossen, of het genereren van het bord. 286 | \end_layout 287 | 288 | \begin_layout Standard 289 | \begin_inset listings 290 | lstparams "language=C" 291 | inline false 292 | status open 293 | 294 | \begin_layout Standard 295 | 296 | if (veld leeg) 297 | \end_layout 298 | 299 | \begin_layout Standard 300 | 301 | return (CheckHorizontal() && CheckVertical() && CheckBlock()); 302 | \end_layout 303 | 304 | \begin_layout Standard 305 | 306 | return false; 307 | \end_layout 308 | 309 | \end_inset 310 | 311 | 312 | \end_layout 313 | 314 | \begin_layout Standard 315 | Waarbij we gebruik maken van het feit dat de compiler aan 'lazy evaluation' 316 | zal doen en als dusdanig alleen 317 | \family typewriter 318 | checkVertical() 319 | \family default 320 | zal oproepen als 321 | \family typewriter 322 | checkHorizontal() 323 | \family default 324 | 325 | \family typewriter 326 | true 327 | \family default 328 | teruggaf. 329 | 330 | \family typewriter 331 | checkHorizontal() 332 | \family default 333 | ziet er dan ongeveer zo uit: 334 | \end_layout 335 | 336 | \begin_layout Standard 337 | \begin_inset listings 338 | lstparams "language={C++}" 339 | inline false 340 | status open 341 | 342 | \begin_layout Standard 343 | 344 | for (i = 0; !foutGevonden && i < 9; i++) 345 | \end_layout 346 | 347 | \begin_layout Standard 348 | 349 | foutGevonden = (bord[rij][i] == toeTeVoegenElement); 350 | \end_layout 351 | 352 | \begin_layout Standard 353 | 354 | return !foutGevonden; 355 | \end_layout 356 | 357 | \end_inset 358 | 359 | 360 | \end_layout 361 | 362 | \begin_layout Standard 363 | De functie 364 | \family typewriter 365 | isValid() 366 | \family default 367 | doet wat hij zegt. 368 | Hij krijgt gewoon een bord mee en gaat hierop kijken of er geen dubbele 369 | elementen voorkomen op plaatsen waar het niet mag (door gebruik te maken 370 | van de 371 | \family typewriter 372 | isValidMove() 373 | \family default 374 | functie op elke plaats van het bord). 375 | \end_layout 376 | 377 | \begin_layout Standard 378 | 379 | \family typewriter 380 | solveBoard() 381 | \family default 382 | zal een bord volledig invullen. 383 | Hij werkt door het gebruik van backtracking om zo de mogelijkheden af te 384 | gaan. 385 | Een stukje pseudocode: 386 | \end_layout 387 | 388 | \begin_layout Standard 389 | \begin_inset listings 390 | lstparams "language={C++}" 391 | inline false 392 | status open 393 | 394 | \begin_layout Standard 395 | 396 | if (!board.IsFull()) { 397 | \end_layout 398 | 399 | \begin_layout Standard 400 | 401 | for (alle mogelijke zetten) { 402 | \end_layout 403 | 404 | \begin_layout Standard 405 | 406 | if (board.IsValidMove( te proberen element )) { 407 | \end_layout 408 | 409 | \begin_layout Standard 410 | 411 | board.Set( dit element ); 412 | \end_layout 413 | 414 | \begin_layout Standard 415 | 416 | if (SolveBoard()) 417 | \end_layout 418 | 419 | \begin_layout Standard 420 | 421 | return true; 422 | \end_layout 423 | 424 | \begin_layout Standard 425 | 426 | else 427 | \end_layout 428 | 429 | \begin_layout Standard 430 | 431 | undoZet(); 432 | \end_layout 433 | 434 | \begin_layout Standard 435 | 436 | } 437 | \end_layout 438 | 439 | \begin_layout Standard 440 | 441 | } 442 | \end_layout 443 | 444 | \begin_layout Standard 445 | 446 | if ( geen van de zetten gelukt ) 447 | \end_layout 448 | 449 | \begin_layout Standard 450 | 451 | return false; 452 | \end_layout 453 | 454 | \begin_layout Standard 455 | 456 | } 457 | \end_layout 458 | 459 | \begin_layout Standard 460 | 461 | else 462 | \end_layout 463 | 464 | \begin_layout Standard 465 | 466 | return true; 467 | \end_layout 468 | 469 | \end_inset 470 | 471 | 472 | \end_layout 473 | 474 | \begin_layout Standard 475 | De functie 476 | \family typewriter 477 | generateBoard() 478 | \family default 479 | zal gebruik maken van 480 | \family typewriter 481 | solveBoard() 482 | \family default 483 | om een bord te genereren. 484 | Eerst wordt (door gebruik te maken van willekeurige nummers en 485 | \family typewriter 486 | solveBoard() 487 | \family default 488 | ) een uitgespeeld spel gegenereerd. 489 | Hierna gaat 490 | \family typewriter 491 | generateBoard() 492 | \family default 493 | elementen wegnemen tot op een moment dat er nog maar een aantal (dat afhangt 494 | van de moeilijkheidsgraad) mogelijke zetten zijn. 495 | \end_layout 496 | 497 | \begin_layout Standard 498 | \begin_inset listings 499 | lstparams "language={C++}" 500 | inline false 501 | status open 502 | 503 | \begin_layout Standard 504 | 505 | /* Elementen in random volgorde/met random waarde 506 | \end_layout 507 | 508 | \begin_layout Standard 509 | 510 | * proberen te plaatsen, zodat bord random genoeg is */ 511 | \end_layout 512 | 513 | \begin_layout Standard 514 | 515 | solveBoard(tempboard); 516 | \end_layout 517 | 518 | \begin_layout Standard 519 | 520 | // triedElems[][] houdt bij welke elementen geprobeerd zijn 521 | \end_layout 522 | 523 | \begin_layout Standard 524 | 525 | while (tempBoard.isValid() && !isFull(triedElems)) { 526 | \end_layout 527 | 528 | \begin_layout Standard 529 | 530 | do { 531 | \end_layout 532 | 533 | \begin_layout Standard 534 | 535 | randomX = rand() % 9 + 1; 536 | \end_layout 537 | 538 | \begin_layout Standard 539 | 540 | randomY = rand() % 9 + 1; 541 | \end_layout 542 | 543 | \begin_layout Standard 544 | 545 | } while (triedElems[randomX][randomY]); 546 | \end_layout 547 | 548 | \begin_layout Standard 549 | 550 | triedElems[randomX][randomY] = true; 551 | \end_layout 552 | 553 | \begin_layout Standard 554 | 555 | tempboard.RemoveElem(randomX, randomY); 556 | \end_layout 557 | 558 | \begin_layout Standard 559 | 560 | if (!tempboard.IsSolvable()) 561 | \end_layout 562 | 563 | \begin_layout Standard 564 | 565 | undoLast(); // Werkt met stack 566 | \end_layout 567 | 568 | \begin_layout Standard 569 | 570 | } 571 | \end_layout 572 | 573 | \begin_layout Standard 574 | 575 | /* MAXNIVEAU is de hoogste moeilijkheidsgraad 576 | \end_layout 577 | 578 | \begin_layout Standard 579 | 580 | * We willen het aantal mogelijkheden van de gekozen 581 | \end_layout 582 | 583 | \begin_layout Standard 584 | 585 | * moeilijkheidsgraad hebben, undoLast() zet dan een element 586 | \end_layout 587 | 588 | \begin_layout Standard 589 | 590 | * terug, zodat het gemakkelijker wordt. 591 | */ 592 | \end_layout 593 | 594 | \begin_layout Standard 595 | 596 | for (int i = 0; i < MAXNIVEAU - moeilijkheidsgraad; i++) 597 | \end_layout 598 | 599 | \begin_layout Standard 600 | 601 | UndoLast(); 602 | \end_layout 603 | 604 | \end_inset 605 | 606 | 607 | \end_layout 608 | 609 | \begin_layout Standard 610 | 611 | \end_layout 612 | 613 | \begin_layout Subsection 614 | Bestandsstructuren 615 | \end_layout 616 | 617 | \begin_layout Standard 618 | Het enige dat moet opgeslagen/ingelezen kunnen worden is de huidige spelsituatie. 619 | Deze zullen we wegschrijven in het 620 | \emph on 621 | csv 622 | \emph default 623 | formaat (9 rijen, 9 kolommen), zodat het overal gemakkelijk geopend kan 624 | worden. 625 | Lege vakjes worden weergegeven door een spatie zodat het bestand er ook 626 | 'natuurlijk' uitziet in een tekst-editor (doordat een 627 | \emph on 628 | csv 629 | \emph default 630 | bestand gewoon een aantal rijen zijn met waarden die gescheiden worden 631 | door komma's). 632 | \end_layout 633 | 634 | \begin_layout Section 635 | Taakverdeling en Planning 636 | \end_layout 637 | 638 | \begin_layout Standard 639 | Tot nu toe hebben we enkel de (voorlopige) klassedefinities (samen) gemaakt. 640 | Dit betekent dat de 641 | \family typewriter 642 | Exception 643 | \family default 644 | klassen klaar zijn. 645 | Aangezien we de 646 | \family typewriter 647 | FileIO 648 | \family default 649 | klasse uit ons vorige project zullen hergebruiken, is deze ook af. 650 | De taakverdeling voor de rest van het project ziet er als volgt uit, maar 651 | is onderhevig aan veranderingen: 652 | \end_layout 653 | 654 | \begin_layout Standard 655 | Wim zal zowel het grafische gedeelte als het spelverloop voor zijn rekening 656 | nemen. 657 | Dit houdt in dat hij ervoor zal zorgen dat het spel van begin tot eind 658 | gespeeld kan worden. 659 | \end_layout 660 | 661 | \begin_layout Standard 662 | Bram zorgt er dan voor dat het interne gedeelte van het spel werkt, dit 663 | wil zeggen: de volledige 664 | \family typewriter 665 | Board 666 | \family default 667 | en 668 | \family typewriter 669 | Sudoku 670 | \family default 671 | klassen. 672 | Hij houdt zich dus bezig met de algoritmen om een bord te genereren of 673 | op te lossen en met deze die kijken of het bord nog oplosbaar is op een 674 | bepaald moment in het spel. 675 | Ook zal hij zorgen voor de documentatie. 676 | \end_layout 677 | 678 | \begin_layout Standard 679 | De planning ziet er (voorlopig) ongeveer zo uit: 680 | \end_layout 681 | 682 | \begin_layout Description 683 | einde\InsetSpace ~ 684 | paasvakantie Basisstructuur af, dat wil zeggen: zorgen dat de klassen 685 | er uitzien zoals ze zullen zijn aan het einde van het project. 686 | \end_layout 687 | 688 | \begin_layout Description 689 | week\InsetSpace ~ 690 | 2\InsetSpace ~ 691 | trimester\InsetSpace ~ 692 | 3 Ervoor zorgen dat de algoritmes werken, zodat er een spel 693 | gespeeld kan worden (eventueel enkel via test-cases door de ontwikkelaars). 694 | \end_layout 695 | 696 | \begin_layout Description 697 | week\InsetSpace ~ 698 | 5\InsetSpace ~ 699 | trimester\InsetSpace ~ 700 | 3 Spel moet speelbaar zijn door iedereen. 701 | Mogelijk is nog niet alle functionaliteit beschikbaar, maar de belangrijkste 702 | functies zouden af moeten zijn. 703 | \end_layout 704 | 705 | \begin_layout Description 706 | week\InsetSpace ~ 707 | 7\InsetSpace ~ 708 | trimester\InsetSpace ~ 709 | 3 Volledige implementatie af, zodat we kunnen beginnen met 710 | het testen van speciale gevallen om de laatste bugs eruit te werken en 711 | zodat we aan het eindverslag en de presentatie kunnen beginnen. 712 | \end_layout 713 | 714 | \end_body 715 | \end_document 716 | -------------------------------------------------------------------------------- /src/Qt/SudokuGame.cpp: -------------------------------------------------------------------------------- 1 | // $Id: SudokuGame.cpp 370 2008-06-08 21:50:31Z wimleers $ 2 | 3 | 4 | /** 5 | * Qt SudokuGame implementation. 6 | * 7 | * @file SudokuGame.cpp 8 | * @author Wim Leers 9 | */ 10 | 11 | 12 | #include "SudokuGame.h" 13 | 14 | 15 | //---------------------------------------------------------------------------- 16 | // Constructors & destructor. 17 | 18 | SudokuGame::SudokuGame(int difficulty) { 19 | m_mustGenerateBoard = true; 20 | initialize(difficulty); 21 | m_board = new Board(); 22 | m_originalBoard = NULL; 23 | m_duration = 0; 24 | m_durationTimer = NULL; 25 | m_thread = NULL; 26 | 27 | // Actual board generation is done in a separate method (generateBoard()), 28 | // to allow for precise timing of this CPU-intensive process. 29 | } 30 | 31 | SudokuGame::SudokuGame(Board * board) { 32 | m_mustGenerateBoard = false; 33 | initialize(-1); 34 | m_board = board; 35 | storeOriginalBoard(); 36 | } 37 | 38 | SudokuGame::~SudokuGame(void) { 39 | delete m_board; 40 | delete m_originalBoard; 41 | 42 | if (m_durationTimer != NULL) { 43 | m_durationTimer->stop(); 44 | disconnect(m_durationTimer, SIGNAL(timeout()), this, SLOT(updateDuration())); 45 | delete m_durationTimer; 46 | } 47 | } 48 | 49 | 50 | //---------------------------------------------------------------------------- 51 | // Public methods. 52 | 53 | /** 54 | * Load a SudokuGame (unserialize from file). 55 | * 56 | * @author 57 | * Bram Bonne 58 | * @param fileName 59 | * A filename. 60 | * @return 61 | * true in case of a succesful load, false otherwise. 62 | */ 63 | bool SudokuGame::load(const QString fileName) { 64 | QFile file(fileName); 65 | if (file.open(QIODevice::ReadOnly)) { 66 | // This is a game that's being loaded, so we definitely don't need to 67 | // generate a new board. We do need to mark this game as a loaded game 68 | // because otherwise the duration would be reset! 69 | m_mustGenerateBoard = false; 70 | m_isLoadedGame = true; 71 | 72 | QDataStream stream(&file); 73 | stream >> *m_board; 74 | // m_originalBoard is initialized to NULL, so we have to stream it 75 | // into a temporary variable first. 76 | Board *ob = new Board(); 77 | stream >> *ob; 78 | m_originalBoard = ob; 79 | 80 | for (int i = 0; i < 9; i++) 81 | for (int j = 0; j < 9; j++) 82 | for (int k = 0; k < 9; k++) 83 | stream >> m_choices[k][j][i]; 84 | 85 | stream >> m_lastFinalChoiceX >> m_lastFinalChoiceY; 86 | stream >> m_difficulty >> m_finished >> m_working >> m_valid; 87 | stream >> m_duration; 88 | stream >> m_validityCalculationEnabled >> m_solvabilityCalculationEnabled >> m_statsCalculationEnabled; 89 | 90 | return true; 91 | } 92 | else 93 | return false; 94 | } 95 | 96 | /** 97 | * Save a SudokuGame object (serialize to file). 98 | * 99 | * @author 100 | * Bram Bonne 101 | * @param fileName 102 | * A filename. 103 | * @return 104 | * true in case of a succesful save, false otherwise. 105 | */ 106 | bool SudokuGame::save(const QString fileName) { 107 | QFile file(fileName); 108 | if (file.open(QIODevice::WriteOnly)) { 109 | QDataStream stream(&file); 110 | stream << *m_board << *m_originalBoard; 111 | 112 | for (int i = 0; i < 9; i++) 113 | for (int j = 0; j < 9; j++) 114 | for (int k = 0; k < 9; k++) 115 | stream << m_choices[k][j][i]; 116 | 117 | stream << m_lastFinalChoiceX << m_lastFinalChoiceY; 118 | stream << m_difficulty << m_finished << m_working << m_valid; 119 | stream << m_duration; 120 | stream << m_validityCalculationEnabled << m_solvabilityCalculationEnabled << m_statsCalculationEnabled; 121 | 122 | return true; 123 | } 124 | else 125 | return false; 126 | } 127 | 128 | /** 129 | * Retrieve the current board for read-only access. 130 | * 131 | * @return 132 | * The current board. 133 | */ 134 | Board const * SudokuGame::getBoard(void) const { 135 | return m_board; 136 | } 137 | 138 | /** 139 | * Retrieve the original board for read-only access. 140 | * 141 | * @return 142 | * The original board. 143 | */ 144 | Board const * SudokuGame::getOriginalBoard(void) const { 145 | return m_originalBoard; 146 | } 147 | 148 | /** 149 | * Retrieve the choices for an element for read-only access. 150 | * 151 | * @param x 152 | * A valid x-coordinate (0-8). 153 | * @param y 154 | * A valid y-coordinate (0-8). 155 | * @return 156 | * The choices for the element at the given x- and y-coordinates. 157 | */ 158 | bool const * SudokuGame::getChoices(int x, int y) const { 159 | return m_choices[x][y]; 160 | } 161 | 162 | /** 163 | * Set all choices for an element. 164 | * 165 | * @param x 166 | * A valid x-coordinate (0-8). 167 | * @param y 168 | * A valid y-coordinate (0-8). 169 | 170 | * @param choices 171 | * An array of 9 bools, with false meaning that the choice is not set. 172 | */ 173 | void SudokuGame::setChoices(int x, int y, bool const * choices) { 174 | for (int i = 0; i < 9; i++) 175 | m_choices[x][y][i] = choices[i]; 176 | 177 | emit changed(x, y); 178 | } 179 | 180 | 181 | /** 182 | * Solve the game. 183 | * 184 | * @return 185 | * true if the game could be solved, false otherwise. 186 | */ 187 | bool SudokuGame::solve(void) { 188 | bool status; 189 | 190 | startWorking(); 191 | status = Sudoku::SolveBoard(m_board); 192 | stopWorking(); 193 | 194 | // TODO: animations? 195 | for (int x = 0; x < 9; x++) 196 | for (int y = 0; y < 9; y++) 197 | emit changed(x, y); 198 | 199 | // Recalculate only the stats, since validity and solvability will remain 200 | // positive by definition. 201 | calculateStats(); 202 | 203 | // Stop the timer and reset the duration to 0, to indicate that the user 204 | // didn't solve the Sudoku. 205 | m_durationTimer->stop(); 206 | m_duration = 0; 207 | emit durationUpdated(m_duration); 208 | 209 | return status; 210 | } 211 | 212 | /** 213 | * Check if the game is working (i.e. if calculations are being made). 214 | * 215 | * @return 216 | * true if the game is working, false otherwise. 217 | */ 218 | bool SudokuGame::isWorking(void) const { 219 | return m_working; 220 | } 221 | 222 | /** 223 | * Check if board is valid. 224 | * 225 | * @return 226 | * true if the board is valid, false otherwise. 227 | */ 228 | bool SudokuGame::isValid(void) const { 229 | return m_valid; 230 | } 231 | 232 | bool SudokuGame::isFinished(void) const { 233 | return m_finished; 234 | } 235 | 236 | /** 237 | * Get the final choice for a certain Sudoku element. 238 | * 239 | * @param x 240 | * A valid x-coordinate (0-8). 241 | * @param y 242 | * A valid y-coordinate (0-8). 243 | * @return 244 | * The final choice (1-9) for the given Sudoku element, -1 if not set. 245 | */ 246 | int SudokuGame::getFinalChoice(int x, int y) const { 247 | int finalChoice = (int) m_board->Get(x, y); 248 | 249 | return (finalChoice >= 1 && finalChoice <= 9) ? finalChoice : -1; 250 | } 251 | 252 | /** 253 | * Trigger all state checking functions, causing the corresponding signals to 254 | * be sent. 255 | * 256 | * @param firstTime 257 | * If true, a signal will always be sent. 258 | */ 259 | void SudokuGame::performAllCalculations(bool firstTime) { 260 | emit durationUpdated(m_duration); 261 | 262 | calculateValidity(firstTime); 263 | 264 | if (m_solvabilityCalculationEnabled) 265 | calculateSolvability(firstTime); 266 | 267 | if (m_statsCalculationEnabled) 268 | calculateStats(firstTime); 269 | } 270 | 271 | 272 | //---------------------------------------------------------------------------- 273 | // Public slots. 274 | 275 | /** 276 | * Pause/unpause the game. 277 | * 278 | * @param pause 279 | * true if the game should be paused, false if it should be paused. 280 | */ 281 | void SudokuGame::pause(bool pause) { 282 | if ( (!m_ready) || (m_finished) ) 283 | return; 284 | 285 | if (pause) { 286 | m_durationTimer->stop(); 287 | } 288 | else 289 | m_durationTimer->start(); 290 | 291 | emit paused(pause); 292 | } 293 | 294 | /** 295 | * Reset the game. 296 | */ 297 | void SudokuGame::reset(void) { 298 | startWorking(); 299 | m_duration = 0; 300 | delete m_board; 301 | m_board = new Board(*m_originalBoard); 302 | performAllCalculations(true); 303 | for (int x = 0; x < 9; x++) 304 | for (int y = 0; y < 9; y++) { 305 | for (int i = 0; i < 9; i++) 306 | m_choices[x][y][i] = false; 307 | emit changed(x, y); 308 | } 309 | stopWorking(); 310 | } 311 | 312 | 313 | /** 314 | * Enable a choice. 315 | * 316 | * @param x 317 | * A valid x-coordinate (0-8). 318 | * @param y 319 | * A valid y-coordinate (0-8). 320 | * @param number 321 | * A number (1-9). 322 | */ 323 | void SudokuGame::enableChoice(int x, int y, int number) { 324 | qDebug() << "SudokuGame::enableChoice() signal received, x =" << x << ", y =" << y << ", number =" << number; 325 | 326 | m_choices[x][y][number - 1] = true; 327 | 328 | emit changed(x, y); 329 | } 330 | 331 | /** 332 | * Disable a choice. 333 | * 334 | * @param x 335 | * A valid x-coordinate (0-8). 336 | * @param y 337 | * A valid y-coordinate (0-8). 338 | * @param number 339 | * A number (1-9). 340 | */ 341 | void SudokuGame::disableChoice(int x, int y, int number) { 342 | qDebug() << "SudokuGame::disableChoice() signal received, x =" << x << ", y =" << y << ", number =" << number; 343 | 344 | m_choices[x][y][number - 1] = false; 345 | 346 | emit changed(x, y); 347 | } 348 | 349 | /** 350 | * Set the final choice. 351 | * 352 | * @param x 353 | * A valid x-coordinate (0-8). 354 | * @param y 355 | * A valid y-coordinate (0-8). 356 | * @param number 357 | * A number (1-9). 358 | */ 359 | void SudokuGame::setFinalChoice(int x, int y, int number) { 360 | m_board->Set(x, y, (char) number); 361 | m_lastFinalChoiceX = x; 362 | m_lastFinalChoiceY = y; 363 | performAllCalculations(); 364 | 365 | #ifndef QT_NO_DEBUG_OUTPUT 366 | qDebug() << "SudokuGame::setFinalChoice()" << "Set final choice (" << number << ") for (" << x << "," << y << ")."; 367 | #endif 368 | 369 | emit changed(x, y); 370 | 371 | if (m_board->IsFull() && m_board->IsValid()) { 372 | m_finished = true; 373 | m_durationTimer->stop(); 374 | emit finished(m_duration); 375 | } 376 | } 377 | 378 | /** 379 | * Unset the final choice. 380 | * 381 | * @param x 382 | * A valid x-coordinate (0-8). 383 | * @param y 384 | * A valid y-coordinate (0-8). 385 | */ 386 | void SudokuGame::unsetFinalChoice(int x, int y) { 387 | m_board->Remove(x, y); 388 | performAllCalculations(); 389 | 390 | #ifndef QT_NO_DEBUG_OUTPUT 391 | qDebug() << "SudokuGame::unsetFinalChoice()" << "Unset final choice for (" << x << "," << y << ")."; 392 | #endif 393 | 394 | emit changed(x, y); 395 | } 396 | 397 | 398 | //---------------------------------------------------------------------------- 399 | // Private slots. 400 | 401 | /** 402 | * Slot that's connected to a timer that fires every second. This allows us 403 | * to store the time and send a notification to the main window. 404 | */ 405 | void SudokuGame::updateDuration(void) { 406 | m_duration++; 407 | emit durationUpdated(m_duration); 408 | } 409 | 410 | /** 411 | * This slot is called when a new board has been generated. It finishes the 412 | * construction of a SudokuGame object. 413 | */ 414 | void SudokuGame::boardGenerated(int generationTime) { 415 | qDebug() << "Finished generating new board in worker thread in" << generationTime << "ms."; 416 | 417 | stopWorking(); 418 | 419 | disconnect(m_thread, SIGNAL(boardGenerated(int)), this, SLOT(boardGenerated(int))); 420 | 421 | storeOriginalBoard(); 422 | finishConstruction(); 423 | } 424 | 425 | 426 | //---------------------------------------------------------------------------- 427 | // Private methods. 428 | 429 | /** 430 | * Convenience method that contains code shared by all constructors. 431 | */ 432 | void SudokuGame::initialize(int difficulty) { 433 | m_ready = false; 434 | m_isLoadedGame = false; 435 | m_difficulty = difficulty; 436 | m_finished = false; 437 | m_working = false; 438 | m_valid = true; 439 | 440 | m_validityCalculationEnabled = true; 441 | m_solvabilityCalculationEnabled = true; 442 | m_statsCalculationEnabled = true; 443 | 444 | 445 | // Set all all 729 choice booleans (9 per SudokuElement, of which there 446 | // are 81) to false. 447 | for (int x = 0; x < 9; x++) 448 | for (int y = 0; y < 9; y++) 449 | for (int z = 0; z < 9; z++) 450 | m_choices[x][y][z] = false; 451 | } 452 | 453 | void SudokuGame::generateBoard(void) { 454 | qDebug() << "Start generating new board..."; 455 | startWorking(); 456 | qRegisterMetaType("Board"); 457 | m_thread = new BoardGenerator(this); 458 | connect(m_thread, SIGNAL(boardGenerated(int)), this, SLOT(boardGenerated(int))); 459 | m_thread->QueueBoardGeneration(m_board, m_difficulty); 460 | } 461 | 462 | void SudokuGame::storeOriginalBoard(void) { 463 | // Store the original board separately. This allows us to reset the Sudoku 464 | // and mark some elements as generated elements, even after interrupting 465 | // the application. 466 | m_originalBoard = new Board(); 467 | *m_originalBoard = *m_board; 468 | } 469 | 470 | void SudokuGame::start(void) { 471 | if (!m_mustGenerateBoard) 472 | finishConstruction(); 473 | else 474 | generateBoard(); 475 | } 476 | 477 | void SudokuGame::finishConstruction(void) { 478 | m_ready = true; 479 | emit ready(true); 480 | qDebug() << "Game is ready!"; 481 | 482 | performAllCalculations(true); 483 | 484 | // Start the duration timer. 485 | if (!m_isLoadedGame) 486 | m_duration = 0; 487 | m_durationTimer = new QTimer(this); 488 | connect(m_durationTimer, SIGNAL(timeout()), this, SLOT(updateDuration())); 489 | m_durationTimer->start(1000); 490 | } 491 | 492 | /** 493 | * Convenience method for managing the m_working property: sets it to true. 494 | */ 495 | inline void SudokuGame::startWorking(void) { 496 | m_working = true; 497 | emit working(true); 498 | } 499 | 500 | /** 501 | * Convenience method for managing the m_working property: sets it to false. 502 | */ 503 | inline void SudokuGame::stopWorking(void) { 504 | m_working = false; 505 | emit working(false); 506 | } 507 | 508 | /** 509 | * Calculates the validity of the current board and sends a signal when the 510 | * validity changes. 511 | * This method is different form calculateSolvability() and calculateStats() 512 | * in that it uses (and updates!) the m_valid state instead of using a static 513 | * variable to detect changes. The m_valid state is used for the game's flow 514 | * as well so this method is also called for internal use. That's why the 515 | * check for m_validityCalculationEnabled is *inside* the method, unlike the 516 | * other calculation methods. 517 | * 518 | * @param firstTime 519 | * If true, a signal will always be sent. 520 | */ 521 | void SudokuGame::calculateValidity(bool firstTime) { 522 | if (!m_ready) 523 | return; 524 | 525 | bool previously = m_valid; 526 | 527 | startWorking(); 528 | m_valid = m_board->IsValid(); 529 | stopWorking(); 530 | 531 | if (m_validityCalculationEnabled) { 532 | if (firstTime) 533 | emit validityChanged(m_valid, m_lastFinalChoiceX, m_lastFinalChoiceY); 534 | else if (previously && !m_valid) 535 | emit validityChanged(false, m_lastFinalChoiceX, m_lastFinalChoiceY); 536 | else if (!previously && m_valid) 537 | emit validityChanged(true, m_lastFinalChoiceX, m_lastFinalChoiceY); 538 | } 539 | } 540 | 541 | /** 542 | * Calculates the solvability of the current board and sends a signal when 543 | * the solvability changes. 544 | * 545 | * @param firstTime 546 | * If true, a signal will always be sent. 547 | */ 548 | void SudokuGame::calculateSolvability(bool firstTime) { 549 | if (!m_ready) 550 | return; 551 | 552 | static bool previously = true; 553 | 554 | startWorking(); 555 | bool solvable = Sudoku::BoardIsSolvable(*m_board, false); 556 | stopWorking(); 557 | 558 | if (firstTime) 559 | emit solvabilityChanged(solvable, m_lastFinalChoiceX, m_lastFinalChoiceY); 560 | else if (previously && !solvable) 561 | emit solvabilityChanged(false, m_lastFinalChoiceX, m_lastFinalChoiceY); 562 | else if (!previously && solvable) 563 | emit solvabilityChanged(true, m_lastFinalChoiceX, m_lastFinalChoiceY); 564 | 565 | previously = solvable; 566 | } 567 | 568 | /** 569 | * Calculates the statistics of the current board and sends a signal when 570 | * the statistics change. 571 | * 572 | * @param firstTime 573 | * If true, a signal will always be sent. 574 | */ 575 | void SudokuGame::calculateStats(bool firstTime) { 576 | if (!m_ready) 577 | return; 578 | 579 | static int prevGenerated = 0, prevCompleted = 0, prevRemaining = 0; 580 | int generated, completed, remaining; 581 | 582 | startWorking(); 583 | // Calculate the generated and completed amounts. 584 | generated = m_originalBoard->NumFilledIn(); 585 | completed = m_board->NumFilledIn(); 586 | 587 | // The completed amount counted too many: we have to substract the number 588 | // of generated elements. 589 | completed -= generated; 590 | // The remaining amount is 81 minus the other two amounts. 591 | remaining = 81 - generated - completed; 592 | stopWorking(); 593 | 594 | if (firstTime) 595 | emit statsChanged(generated, completed, remaining); 596 | else if (prevCompleted != completed || prevGenerated != generated) 597 | emit statsChanged(generated, completed, remaining); 598 | 599 | prevGenerated = generated; 600 | prevCompleted = completed; 601 | prevRemaining = remaining; 602 | } 603 | --------------------------------------------------------------------------------