├── 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 |
--------------------------------------------------------------------------------