1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the examples of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:BSD$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** BSD License Usage |
18 | ** Alternatively, you may use this file under the terms of the BSD license |
19 | ** as follows: |
20 | ** |
21 | ** "Redistribution and use in source and binary forms, with or without |
22 | ** modification, are permitted provided that the following conditions are |
23 | ** met: |
24 | ** * Redistributions of source code must retain the above copyright |
25 | ** notice, this list of conditions and the following disclaimer. |
26 | ** * Redistributions in binary form must reproduce the above copyright |
27 | ** notice, this list of conditions and the following disclaimer in |
28 | ** the documentation and/or other materials provided with the |
29 | ** distribution. |
30 | ** * Neither the name of The Qt Company Ltd nor the names of its |
31 | ** contributors may be used to endorse or promote products derived |
32 | ** from this software without specific prior written permission. |
33 | ** |
34 | ** |
35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
46 | ** |
47 | ** $QT_END_LICENSE$ |
48 | ** |
49 | ****************************************************************************/ |
50 | |
51 | #include "tetrixboard.h" |
52 | |
53 | #include <QKeyEvent> |
54 | #include <QLabel> |
55 | #include <QPainter> |
56 | |
57 | //! [0] |
58 | TetrixBoard::TetrixBoard(QWidget *parent) |
59 | : QFrame(parent), isStarted(false), isPaused(false) |
60 | { |
61 | setFrameStyle(QFrame::Panel | QFrame::Sunken); |
62 | setFocusPolicy(Qt::StrongFocus); |
63 | clearBoard(); |
64 | |
65 | nextPiece.setRandomShape(); |
66 | } |
67 | //! [0] |
68 | |
69 | //! [1] |
70 | void TetrixBoard::setNextPieceLabel(QLabel *label) |
71 | { |
72 | nextPieceLabel = label; |
73 | } |
74 | //! [1] |
75 | |
76 | //! [2] |
77 | QSize TetrixBoard::sizeHint() const |
78 | { |
79 | return QSize(BoardWidth * 15 + frameWidth() * 2, |
80 | BoardHeight * 15 + frameWidth() * 2); |
81 | } |
82 | |
83 | QSize TetrixBoard::minimumSizeHint() const |
84 | //! [2] //! [3] |
85 | { |
86 | return QSize(BoardWidth * 5 + frameWidth() * 2, |
87 | BoardHeight * 5 + frameWidth() * 2); |
88 | } |
89 | //! [3] |
90 | |
91 | //! [4] |
92 | void TetrixBoard::start() |
93 | { |
94 | if (isPaused) |
95 | return; |
96 | |
97 | isStarted = true; |
98 | isWaitingAfterLine = false; |
99 | numLinesRemoved = 0; |
100 | numPiecesDropped = 0; |
101 | score = 0; |
102 | level = 1; |
103 | clearBoard(); |
104 | |
105 | emit linesRemovedChanged(numLines: numLinesRemoved); |
106 | emit scoreChanged(score); |
107 | emit levelChanged(level); |
108 | |
109 | newPiece(); |
110 | timer.start(msec: timeoutTime(), obj: this); |
111 | } |
112 | //! [4] |
113 | |
114 | //! [5] |
115 | void TetrixBoard::pause() |
116 | { |
117 | if (!isStarted) |
118 | return; |
119 | |
120 | isPaused = !isPaused; |
121 | if (isPaused) { |
122 | timer.stop(); |
123 | } else { |
124 | timer.start(msec: timeoutTime(), obj: this); |
125 | } |
126 | update(); |
127 | //! [5] //! [6] |
128 | } |
129 | //! [6] |
130 | |
131 | //! [7] |
132 | void TetrixBoard::paintEvent(QPaintEvent *event) |
133 | { |
134 | QFrame::paintEvent(event); |
135 | |
136 | QPainter painter(this); |
137 | QRect rect = contentsRect(); |
138 | //! [7] |
139 | |
140 | if (isPaused) { |
141 | painter.drawText(r: rect, flags: Qt::AlignCenter, text: tr(s: "Pause" )); |
142 | return; |
143 | } |
144 | |
145 | //! [8] |
146 | int boardTop = rect.bottom() - BoardHeight*squareHeight(); |
147 | |
148 | for (int i = 0; i < BoardHeight; ++i) { |
149 | for (int j = 0; j < BoardWidth; ++j) { |
150 | TetrixShape shape = shapeAt(x: j, y: BoardHeight - i - 1); |
151 | if (shape != NoShape) |
152 | drawSquare(painter, x: rect.left() + j * squareWidth(), |
153 | y: boardTop + i * squareHeight(), shape); |
154 | } |
155 | //! [8] //! [9] |
156 | } |
157 | //! [9] |
158 | |
159 | //! [10] |
160 | if (curPiece.shape() != NoShape) { |
161 | for (int i = 0; i < 4; ++i) { |
162 | int x = curX + curPiece.x(index: i); |
163 | int y = curY - curPiece.y(index: i); |
164 | drawSquare(painter, x: rect.left() + x * squareWidth(), |
165 | y: boardTop + (BoardHeight - y - 1) * squareHeight(), |
166 | shape: curPiece.shape()); |
167 | } |
168 | //! [10] //! [11] |
169 | } |
170 | //! [11] //! [12] |
171 | } |
172 | //! [12] |
173 | |
174 | //! [13] |
175 | void TetrixBoard::keyPressEvent(QKeyEvent *event) |
176 | { |
177 | if (!isStarted || isPaused || curPiece.shape() == NoShape) { |
178 | QFrame::keyPressEvent(event); |
179 | return; |
180 | } |
181 | //! [13] |
182 | |
183 | //! [14] |
184 | switch (event->key()) { |
185 | case Qt::Key_Left: |
186 | tryMove(newPiece: curPiece, newX: curX - 1, newY: curY); |
187 | break; |
188 | case Qt::Key_Right: |
189 | tryMove(newPiece: curPiece, newX: curX + 1, newY: curY); |
190 | break; |
191 | case Qt::Key_Down: |
192 | tryMove(newPiece: curPiece.rotatedRight(), newX: curX, newY: curY); |
193 | break; |
194 | case Qt::Key_Up: |
195 | tryMove(newPiece: curPiece.rotatedLeft(), newX: curX, newY: curY); |
196 | break; |
197 | case Qt::Key_Space: |
198 | dropDown(); |
199 | break; |
200 | case Qt::Key_D: |
201 | oneLineDown(); |
202 | break; |
203 | default: |
204 | QFrame::keyPressEvent(event); |
205 | } |
206 | //! [14] |
207 | } |
208 | |
209 | //! [15] |
210 | void TetrixBoard::timerEvent(QTimerEvent *event) |
211 | { |
212 | if (event->timerId() == timer.timerId()) { |
213 | if (isWaitingAfterLine) { |
214 | isWaitingAfterLine = false; |
215 | newPiece(); |
216 | timer.start(msec: timeoutTime(), obj: this); |
217 | } else { |
218 | oneLineDown(); |
219 | } |
220 | } else { |
221 | QFrame::timerEvent(event); |
222 | //! [15] //! [16] |
223 | } |
224 | //! [16] //! [17] |
225 | } |
226 | //! [17] |
227 | |
228 | //! [18] |
229 | void TetrixBoard::clearBoard() |
230 | { |
231 | for (int i = 0; i < BoardHeight * BoardWidth; ++i) |
232 | board[i] = NoShape; |
233 | } |
234 | //! [18] |
235 | |
236 | //! [19] |
237 | void TetrixBoard::dropDown() |
238 | { |
239 | int dropHeight = 0; |
240 | int newY = curY; |
241 | while (newY > 0) { |
242 | if (!tryMove(newPiece: curPiece, newX: curX, newY: newY - 1)) |
243 | break; |
244 | --newY; |
245 | ++dropHeight; |
246 | } |
247 | pieceDropped(dropHeight); |
248 | //! [19] //! [20] |
249 | } |
250 | //! [20] |
251 | |
252 | //! [21] |
253 | void TetrixBoard::oneLineDown() |
254 | { |
255 | if (!tryMove(newPiece: curPiece, newX: curX, newY: curY - 1)) |
256 | pieceDropped(dropHeight: 0); |
257 | } |
258 | //! [21] |
259 | |
260 | //! [22] |
261 | void TetrixBoard::pieceDropped(int dropHeight) |
262 | { |
263 | for (int i = 0; i < 4; ++i) { |
264 | int x = curX + curPiece.x(index: i); |
265 | int y = curY - curPiece.y(index: i); |
266 | shapeAt(x, y) = curPiece.shape(); |
267 | } |
268 | |
269 | ++numPiecesDropped; |
270 | if (numPiecesDropped % 25 == 0) { |
271 | ++level; |
272 | timer.start(msec: timeoutTime(), obj: this); |
273 | emit levelChanged(level); |
274 | } |
275 | |
276 | score += dropHeight + 7; |
277 | emit scoreChanged(score); |
278 | removeFullLines(); |
279 | |
280 | if (!isWaitingAfterLine) |
281 | newPiece(); |
282 | //! [22] //! [23] |
283 | } |
284 | //! [23] |
285 | |
286 | //! [24] |
287 | void TetrixBoard::removeFullLines() |
288 | { |
289 | int numFullLines = 0; |
290 | |
291 | for (int i = BoardHeight - 1; i >= 0; --i) { |
292 | bool lineIsFull = true; |
293 | |
294 | for (int j = 0; j < BoardWidth; ++j) { |
295 | if (shapeAt(x: j, y: i) == NoShape) { |
296 | lineIsFull = false; |
297 | break; |
298 | } |
299 | } |
300 | |
301 | if (lineIsFull) { |
302 | //! [24] //! [25] |
303 | ++numFullLines; |
304 | for (int k = i; k < BoardHeight - 1; ++k) { |
305 | for (int j = 0; j < BoardWidth; ++j) |
306 | shapeAt(x: j, y: k) = shapeAt(x: j, y: k + 1); |
307 | } |
308 | //! [25] //! [26] |
309 | for (int j = 0; j < BoardWidth; ++j) |
310 | shapeAt(x: j, y: BoardHeight - 1) = NoShape; |
311 | } |
312 | //! [26] //! [27] |
313 | } |
314 | //! [27] |
315 | |
316 | //! [28] |
317 | if (numFullLines > 0) { |
318 | numLinesRemoved += numFullLines; |
319 | score += 10 * numFullLines; |
320 | emit linesRemovedChanged(numLines: numLinesRemoved); |
321 | emit scoreChanged(score); |
322 | |
323 | timer.start(msec: 500, obj: this); |
324 | isWaitingAfterLine = true; |
325 | curPiece.setShape(NoShape); |
326 | update(); |
327 | } |
328 | //! [28] //! [29] |
329 | } |
330 | //! [29] |
331 | |
332 | //! [30] |
333 | void TetrixBoard::newPiece() |
334 | { |
335 | curPiece = nextPiece; |
336 | nextPiece.setRandomShape(); |
337 | showNextPiece(); |
338 | curX = BoardWidth / 2 + 1; |
339 | curY = BoardHeight - 1 + curPiece.minY(); |
340 | |
341 | if (!tryMove(newPiece: curPiece, newX: curX, newY: curY)) { |
342 | curPiece.setShape(NoShape); |
343 | timer.stop(); |
344 | isStarted = false; |
345 | } |
346 | //! [30] //! [31] |
347 | } |
348 | //! [31] |
349 | |
350 | //! [32] |
351 | void TetrixBoard::showNextPiece() |
352 | { |
353 | if (!nextPieceLabel) |
354 | return; |
355 | |
356 | int dx = nextPiece.maxX() - nextPiece.minX() + 1; |
357 | int dy = nextPiece.maxY() - nextPiece.minY() + 1; |
358 | |
359 | QPixmap pixmap(dx * squareWidth(), dy * squareHeight()); |
360 | QPainter painter(&pixmap); |
361 | painter.fillRect(pixmap.rect(), nextPieceLabel->palette().window()); |
362 | |
363 | for (int i = 0; i < 4; ++i) { |
364 | int x = nextPiece.x(index: i) - nextPiece.minX(); |
365 | int y = nextPiece.y(index: i) - nextPiece.minY(); |
366 | drawSquare(painter, x: x * squareWidth(), y: y * squareHeight(), |
367 | shape: nextPiece.shape()); |
368 | } |
369 | nextPieceLabel->setPixmap(pixmap); |
370 | //! [32] //! [33] |
371 | } |
372 | //! [33] |
373 | |
374 | //! [34] |
375 | bool TetrixBoard::tryMove(const TetrixPiece &newPiece, int newX, int newY) |
376 | { |
377 | for (int i = 0; i < 4; ++i) { |
378 | int x = newX + newPiece.x(index: i); |
379 | int y = newY - newPiece.y(index: i); |
380 | if (x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight) |
381 | return false; |
382 | if (shapeAt(x, y) != NoShape) |
383 | return false; |
384 | } |
385 | //! [34] |
386 | |
387 | //! [35] |
388 | curPiece = newPiece; |
389 | curX = newX; |
390 | curY = newY; |
391 | update(); |
392 | return true; |
393 | } |
394 | //! [35] |
395 | |
396 | //! [36] |
397 | void TetrixBoard::drawSquare(QPainter &painter, int x, int y, TetrixShape shape) |
398 | { |
399 | static constexpr QRgb colorTable[8] = { |
400 | 0x000000, 0xCC6666, 0x66CC66, 0x6666CC, |
401 | 0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00 |
402 | }; |
403 | |
404 | QColor color = colorTable[int(shape)]; |
405 | painter.fillRect(x: x + 1, y: y + 1, w: squareWidth() - 2, h: squareHeight() - 2, |
406 | b: color); |
407 | |
408 | painter.setPen(color.lighter()); |
409 | painter.drawLine(x1: x, y1: y + squareHeight() - 1, x2: x, y2: y); |
410 | painter.drawLine(x1: x, y1: y, x2: x + squareWidth() - 1, y2: y); |
411 | |
412 | painter.setPen(color.darker()); |
413 | painter.drawLine(x1: x + 1, y1: y + squareHeight() - 1, |
414 | x2: x + squareWidth() - 1, y2: y + squareHeight() - 1); |
415 | painter.drawLine(x1: x + squareWidth() - 1, y1: y + squareHeight() - 1, |
416 | x2: x + squareWidth() - 1, y2: y + 1); |
417 | } |
418 | //! [36] |
419 | |