-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathmainwindow.cpp
More file actions
329 lines (317 loc) · 11.5 KB
/
Copy pathmainwindow.cpp
File metadata and controls
329 lines (317 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) {
window = new QWidget();
menu = new QMenuBar(window);
fileMenu = new QMenu(tr("&File"), window);
helpMenu = new QMenu(tr("&Help"), window);
menu->addMenu(fileMenu);
menu->addMenu(helpMenu);
exitAction = new QAction(tr("E&xit"), fileMenu);
connect(exitAction, SIGNAL(triggered()), this, SLOT(quit()));
helpAction = new QAction(tr("&Help"), helpMenu);
connect(helpAction, SIGNAL(triggered()), this, SLOT(help()));
aboutAction = new QAction(tr("&About"), helpMenu);
connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
fileMenu->addAction(exitAction);
helpMenu->addAction(helpAction);
helpMenu->addAction(aboutAction);
layout = new QVBoxLayout(window);
top = new QHBoxLayout(window);
top->setAlignment(Qt::AlignLeft);
grid = new QGridLayout(window);
grid->setSpacing(0);
grid->setAlignment(Qt::AlignTop | Qt::AlignLeft);
style = new QCommonStyle();
widthBox = new QSpinBox(window);
heightBox = new QSpinBox(window);
widthBox->setRange(MIN_PUZZLE_SIZE, MAX_PUZZLE_SIZE);
heightBox->setRange(MIN_PUZZLE_SIZE, MAX_PUZZLE_SIZE);
widthBox->setValue(DEFAULT_PUZZLE_SIZE);
heightBox->setValue(DEFAULT_PUZZLE_SIZE);
widthLabel = new QLabel(tr("Columns:"), window);
heightLabel = new QLabel(tr("Rows:"), window);
generate = new QPushButton(tr("Generate puzzle"), this);
connect(generate, SIGNAL(clicked()), this, SLOT(generatePuzzle()));
surrender = new QPushButton(tr("Give up"), this);
connect(surrender, SIGNAL(clicked()), this, SLOT(giveUp()));
surrender->setEnabled(false);
ngram = NULL;
lockAction = false;
window->setLayout(layout);
setCentralWidget(window);
top->addWidget(heightLabel);
top->addWidget(heightBox);
top->addWidget(widthLabel);
top->addWidget(widthBox);
top->addWidget(generate);
top->addWidget(surrender);
layout->addSpacing(20);
layout->addLayout(top);
layout->addLayout(grid);
}
MainWindow::~MainWindow() {
if (ngram) {
cleanUp();
}
delete style;
style = NULL;
delete window;
window = NULL;
}
// Generates, verifies and displays the puzzle and its graphical components.
void MainWindow::generatePuzzle() {
int pos, spacer_x, spacer_y;
// Start by disabling the buttons, so the user can't mess things up, as
// generating the puzzle can take some time. (Especially the big ones.)
widthBox->setEnabled(false);
heightBox->setEnabled(false);
generate->setEnabled(false);
// If this isn't the first puzzle generated, we need to clean out the garbage.
// ngram will be a NULL pointer the first time, but defined on subsequent calls.
if (ngram) {
cleanUp();
}
width = widthBox->value();
height = heightBox->value();
// We need to map the clicks and right clicks to the respective methods.
// Using mappers allows us to easily identify the button that sent the signal.
mapperLeftButton = new QSignalMapper(this);
mapperRightButton = new QSignalMapper(this);
ngram = new Nonogram(width, height);
xAxisClue = ngram->getXAxis();
yAxisClue = ngram->getYAxis();
Solver *solv = new Solver(width, height, xAxisClue, yAxisClue);
// Retry puzzle creation until we get a solvable one.
while (!solv->solve()) {
delete solv;
delete ngram;
ngram = new Nonogram(width, height);
xAxisClue = ngram->getXAxis();
yAxisClue = ngram->getYAxis();
solv = new Solver(width, height, xAxisClue, yAxisClue);
}
delete solv;
// Create and add the clue labels
spacer_x = 0;
spacer_y = 0;
for (int i = 0; i < width; ++i) {
QString str = "";
QString num = "";
for (int j = 0; j < xAxisClue[i]->size(); ++j) {
num.setNum(xAxisClue[i]->at(j), 10);
str.append(num);
if (j < xAxisClue[i]->size() - 1) {
str.append("\n");
}
}
xAxis.push_back(new QLabel(str));
xAxis.at(i)->setAlignment(Qt::AlignCenter | Qt::AlignBottom);
// We want to separate the UI buttons in 5 x 5 chunks, so that counting
// blocks becomes easier for the user.
if (i > 0 && i % 5 == 0) {
++spacer_x;
grid->setColumnMinimumWidth(i + spacer_x, 2);
}
grid->addWidget(xAxis.at(i), 0, i + spacer_x + 1);
}
for (int i = 0; i < height; ++i) {
QString str = "";
QString num = "";
for (int j = 0; j < yAxisClue[i]->size(); ++j) {
num.setNum(yAxisClue[i]->at(j), 10);
str.append(num);
str.append(" ");
}
yAxis.push_back(new QLabel(str));
yAxis.at(i)->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
if (i > 0 && i % 5 == 0) {
++spacer_y;
grid->setRowMinimumHeight(i + spacer_y, 2);
}
grid->addWidget(yAxis.at(i), i + spacer_y + 1, 0);
}
// Create the playing field itself.
spacer_y = 0;
for (int i = 0; i < height; ++i) {
if (i > 0 && i % 5 == 0) {
++spacer_y;
}
spacer_x = 0;
for (int j = 0; j < width; ++j) {
if (j > 0 && j % 5 == 0) {
++spacer_x;
}
pos = i * width + j;
status.push_back(UNKNOWN);
puzzle.push_back(new PushButton(&mouseButton, &firstClick));
puzzle.at(pos)->setStyle(style);
connect(puzzle.at(pos), SIGNAL(solid()), mapperLeftButton, SLOT(map()));
connect(puzzle.at(pos), SIGNAL(dot()), mapperRightButton, SLOT(map()));
connect(puzzle.at(pos), SIGNAL(released()), this, SLOT(checkSolution()));
mapperLeftButton->setMapping(puzzle.at(pos), pos);
mapperRightButton->setMapping(puzzle.at(pos), pos);
grid->addWidget(puzzle.at(pos), i + spacer_y + 1, j + spacer_x + 1);
}
}
connect(mapperLeftButton, SIGNAL(mapped(int)), this, SLOT(solidClicked(int)));
connect(mapperRightButton, SIGNAL(mapped(int)), this, SLOT(dotClicked(int)));
// Enable button that lets user see the solution without solving
surrender->setEnabled(true);
}
// Cleans up stuff before a new game is started
void MainWindow::cleanUp() {
delete ngram;
ngram = NULL;
for (int i = 0; i < xAxis.size(); ++i) {
delete xAxis.at(i);
}
xAxis.clear();
for (int i = 0; i < yAxis.size(); ++i) {
delete yAxis.at(i);
}
yAxis.clear();
for (int i = 0; i < width * height; ++i) {
delete puzzle.at(i);
}
puzzle.clear();
delete mapperLeftButton;
delete mapperRightButton;
status.clear();
}
// Display all solids that the user has missed, in grey and all
// dots he has mistakenly identified as solids in red.
void MainWindow::giveUp() {
surrender->setEnabled(false);
int pos;
for (int i = 0; i < height; ++i) {
size_t mask = 1 << width;
for (int j = 0; j < width; ++j) {
mask >>= 1;
pos = i * width + j;
if (status.at(pos) == SOLID) {
if (!(ngram->getField()[i] & mask)) {
puzzle.at(pos)->setStyleSheet("background-color: rgb(255, 0, 0)");
}
}
else if (ngram->getField()[i] & mask) {
puzzle.at(pos)->setStyleSheet("background-color: rgb(150, 150, 150)");
}
}
}
widthBox->setEnabled(true);
heightBox->setEnabled(true);
generate->setEnabled(true);
}
// Called when a button is clicked or dragged over with button depressed.
// The firstClick & lockAction variables are used to determine which call to
// the function represents the first click, then lock the behaviour of the
// function to the behaviour expected from that first click.
void MainWindow::solidClicked(int position) {
puzzle.at(position)->setText("");
if (status.at(position) == SOLID) {
if (firstClick || lockAction) {
status.at(position) = UNKNOWN;
puzzle.at(position)->setStyleSheet("background-color: rgb(215, 215, 215)");
lockAction = true;
}
}
else {
status.at(position) = SOLID;
puzzle.at(position)->setStyleSheet("background-color: rgb(50, 50, 50)");
}
firstClick = false;
}
// Same as above, but for the right mouse button.
void MainWindow::dotClicked(int position) {
if (status.at(position) == DOT) {
if (firstClick || lockAction) {
status.at(position) = UNKNOWN;
puzzle.at(position)->setText("");
puzzle.at(position)->setStyleSheet("background-color: rgb(215, 215, 215)");
lockAction = true;
}
}
else {
status.at(position) = DOT;
puzzle.at(position)->setText("X");
puzzle.at(position)->setStyleSheet("background-color: rgb(215, 215, 215)");
}
firstClick = false;
}
// Check whether the puzzle is solved or not. Display a message if it is.
void MainWindow::checkSolution() {
// Since this function is called when the mouse button is released,
// we use it to unset the lockAction variable.
lockAction = false;
for (int i = 0; i < height; ++i) {
size_t test = 0;
size_t mask = 1 << width;
for (int j = 0; j < width; ++j) {
mask >>= 1;
if (status.at(i * width + j) == SOLID) {
test |= mask;
}
}
if (test != ngram->getField()[i]) {
return;
}
}
for (int i = 0; i < width * height; ++i) {
puzzle.at(i)->setEnabled(false);
}
QMessageBox mb;
mb.setWindowTitle(tr("Well done"));
mb.setText(tr("You have solved the puzzle!"));
mb.exec();
widthBox->setEnabled(true);
heightBox->setEnabled(true);
generate->setEnabled(true);
surrender->setEnabled(false);
}
void MainWindow::about() {
QMessageBox mb;
mb.setWindowTitle(tr("About"));
mb.setText(tr("<p><h3>Nonogram-qt 1.1.1</h3></p><p>Copyright: Daniel Suni, 2012, 2013, 2018</p><p>Distributed under the GNU GPL v3</p>"));
mb.exec();
}
void MainWindow::help() {
QWidget *helpWindow = new QWidget();
helpWindow->setWindowTitle(tr("Help"));
QHBoxLayout *bl = new QHBoxLayout(helpWindow);
QLabel *text = new QLabel(tr("<h2>What are nonograms?</h2>\
<p>Nonograms are logic puzzles consisting of a rectangular grid divided into cells. These cells can be either<br>\
colored (solids) or blank (dots). At the start of the game all cells are blank, and the purpose of the game is<br>\
to figure out which ones should be colored.</p>\
<p>Each row and column in the grid is fitted with a clue consisting of a series of numbers. These numbers reveal<br>\
how many solids there are on the row/column as well as something about their grouping. If e.g. the clue is<br>\
(3 1 2) we know that from left to right there is first a series of 3 consequtive solids, then X number of blanks,<br>\
where X >= 1, then a single solid, then another unspecified number of blanks, and finally 2 consequtive solids.<br>\
If the row was 10 cells long, one possible arrangement would be (-###-#--##), another one would be (###-#-##--).<br>\
Since there are rules for both rows and columns, only one arrangement is actually correct, though. The puzzle is<br>\
to find the arrangement that conforms to all the given clues.</p>\
<h2>How to play</h2>\
<p>In order to start a new game, please select the size of the desired playing field, and click the "Generate puzzle"<br>\
button. Generating the puzzle can take a few seconds - especially if it's a big one, so please be patient.</p>\
<p>When starting all cells are blank. You can mark a cell as a solid by clicking on it. You can also mark larger areas<br>\
by dragging the mouse with the button pressed. If you've made a mistake, just click the cell again to revert it to a<br>\
dot. You can also mark known dots by right clicking (and dragging). This will be shown by an 'X' appearing in that<br>\
cell. Notice that you don't need to explicitly mark the dots in order to solve the puzzle. That functionality only<br>\
exists for your own convenience.</p>\
<p>Every time you make a move the computer will automatically check whether you've successfully solved the puzzle<br>\
or not. Once the puzzle is solved, you will immediately be informed. If the puzzle turns out to be too hard, you can<br>\
end it, and look at the solution by pressing the "Give up" button. Remaining solids will be displayed in<br>\
grey, and possible mistakes (i.e. dots marked as solids) will be displayed in red.</p>"), helpWindow);
helpWindow->setLayout(bl);
bl->addWidget(text);
helpWindow->show();
}
void MainWindow::quit() {
if (ngram) {
cleanUp();
}
delete style;
style = NULL;
delete window;
window = NULL;
exit(0);
}