1 2############################################################################# 3## 4## Copyright (C) 2013 Riverbank Computing Limited. 5## Copyright (C) 2016 The Qt Company Ltd. 6## Contact: http://www.qt.io/licensing/ 7## 8## This file is part of the Qt for Python examples of the Qt Toolkit. 9## 10## $QT_BEGIN_LICENSE:BSD$ 11## You may use this file under the terms of the BSD license as follows: 12## 13## "Redistribution and use in source and binary forms, with or without 14## modification, are permitted provided that the following conditions are 15## met: 16## * Redistributions of source code must retain the above copyright 17## notice, this list of conditions and the following disclaimer. 18## * Redistributions in binary form must reproduce the above copyright 19## notice, this list of conditions and the following disclaimer in 20## the documentation and/or other materials provided with the 21## distribution. 22## * Neither the name of The Qt Company Ltd nor the names of its 23## contributors may be used to endorse or promote products derived 24## from this software without specific prior written permission. 25## 26## 27## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 28## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 29## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 30## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 31## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 32## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 33## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 34## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 35## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 36## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 37## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 38## 39## $QT_END_LICENSE$ 40## 41############################################################################# 42 43"""PySide2 port of the widgets/widgets/tetrix example from Qt v5.x""" 44 45import random 46 47from PySide2 import QtCore, QtGui, QtWidgets 48 49 50NoShape, ZShape, SShape, LineShape, TShape, SquareShape, LShape, MirroredLShape = range(8) 51 52 53class TetrixWindow(QtWidgets.QWidget): 54 def __init__(self): 55 super(TetrixWindow, self).__init__() 56 57 self.board = TetrixBoard() 58 59 nextPieceLabel = QtWidgets.QLabel() 60 nextPieceLabel.setFrameStyle(QtWidgets.QFrame.Box | QtWidgets.QFrame.Raised) 61 nextPieceLabel.setAlignment(QtCore.Qt.AlignCenter) 62 self.board.setNextPieceLabel(nextPieceLabel) 63 64 scoreLcd = QtWidgets.QLCDNumber(5) 65 scoreLcd.setSegmentStyle(QtWidgets.QLCDNumber.Filled) 66 levelLcd = QtWidgets.QLCDNumber(2) 67 levelLcd.setSegmentStyle(QtWidgets.QLCDNumber.Filled) 68 linesLcd = QtWidgets.QLCDNumber(5) 69 linesLcd.setSegmentStyle(QtWidgets.QLCDNumber.Filled) 70 71 startButton = QtWidgets.QPushButton("&Start") 72 startButton.setFocusPolicy(QtCore.Qt.NoFocus) 73 quitButton = QtWidgets.QPushButton("&Quit") 74 quitButton.setFocusPolicy(QtCore.Qt.NoFocus) 75 pauseButton = QtWidgets.QPushButton("&Pause") 76 pauseButton.setFocusPolicy(QtCore.Qt.NoFocus) 77 78 startButton.clicked.connect(self.board.start) 79 pauseButton.clicked.connect(self.board.pause) 80 quitButton.clicked.connect(qApp.quit) 81 self.board.scoreChanged.connect(scoreLcd.display) 82 self.board.levelChanged.connect(levelLcd.display) 83 self.board.linesRemovedChanged.connect(linesLcd.display) 84 85 layout = QtWidgets.QGridLayout() 86 layout.addWidget(self.createLabel("NEXT"), 0, 0) 87 layout.addWidget(nextPieceLabel, 1, 0) 88 layout.addWidget(self.createLabel("LEVEL"), 2, 0) 89 layout.addWidget(levelLcd, 3, 0) 90 layout.addWidget(startButton, 4, 0) 91 layout.addWidget(self.board, 0, 1, 6, 1) 92 layout.addWidget(self.createLabel("SCORE"), 0, 2) 93 layout.addWidget(scoreLcd, 1, 2) 94 layout.addWidget(self.createLabel("LINES REMOVED"), 2, 2) 95 layout.addWidget(linesLcd, 3, 2) 96 layout.addWidget(quitButton, 4, 2) 97 layout.addWidget(pauseButton, 5, 2) 98 self.setLayout(layout) 99 100 self.setWindowTitle("Tetrix") 101 self.resize(550, 370) 102 103 def createLabel(self, text): 104 lbl = QtWidgets.QLabel(text) 105 lbl.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignBottom) 106 return lbl 107 108 109class TetrixBoard(QtWidgets.QFrame): 110 BoardWidth = 10 111 BoardHeight = 22 112 113 scoreChanged = QtCore.Signal(int) 114 115 levelChanged = QtCore.Signal(int) 116 117 linesRemovedChanged = QtCore.Signal(int) 118 119 def __init__(self, parent=None): 120 super(TetrixBoard, self).__init__(parent) 121 122 self.timer = QtCore.QBasicTimer() 123 self.nextPieceLabel = None 124 self.isWaitingAfterLine = False 125 self.curPiece = TetrixPiece() 126 self.nextPiece = TetrixPiece() 127 self.curX = 0 128 self.curY = 0 129 self.numLinesRemoved = 0 130 self.numPiecesDropped = 0 131 self.score = 0 132 self.level = 0 133 self.board = None 134 135 self.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Sunken) 136 self.setFocusPolicy(QtCore.Qt.StrongFocus) 137 self.isStarted = False 138 self.isPaused = False 139 self.clearBoard() 140 141 self.nextPiece.setRandomShape() 142 143 def shapeAt(self, x, y): 144 return self.board[(y * TetrixBoard.BoardWidth) + x] 145 146 def setShapeAt(self, x, y, shape): 147 self.board[(y * TetrixBoard.BoardWidth) + x] = shape 148 149 def timeoutTime(self): 150 return 1000 / (1 + self.level) 151 152 def squareWidth(self): 153 return self.contentsRect().width() / TetrixBoard.BoardWidth 154 155 def squareHeight(self): 156 return self.contentsRect().height() / TetrixBoard.BoardHeight 157 158 def setNextPieceLabel(self, label): 159 self.nextPieceLabel = label 160 161 def sizeHint(self): 162 return QtCore.QSize(TetrixBoard.BoardWidth * 15 + self.frameWidth() * 2, 163 TetrixBoard.BoardHeight * 15 + self.frameWidth() * 2) 164 165 def minimumSizeHint(self): 166 return QtCore.QSize(TetrixBoard.BoardWidth * 5 + self.frameWidth() * 2, 167 TetrixBoard.BoardHeight * 5 + self.frameWidth() * 2) 168 169 def start(self): 170 if self.isPaused: 171 return 172 173 self.isStarted = True 174 self.isWaitingAfterLine = False 175 self.numLinesRemoved = 0 176 self.numPiecesDropped = 0 177 self.score = 0 178 self.level = 1 179 self.clearBoard() 180 181 self.linesRemovedChanged.emit(self.numLinesRemoved) 182 self.scoreChanged.emit(self.score) 183 self.levelChanged.emit(self.level) 184 185 self.newPiece() 186 self.timer.start(self.timeoutTime(), self) 187 188 def pause(self): 189 if not self.isStarted: 190 return 191 192 self.isPaused = not self.isPaused 193 if self.isPaused: 194 self.timer.stop() 195 else: 196 self.timer.start(self.timeoutTime(), self) 197 198 self.update() 199 200 def paintEvent(self, event): 201 super(TetrixBoard, self).paintEvent(event) 202 203 painter = QtGui.QPainter(self) 204 rect = self.contentsRect() 205 206 if self.isPaused: 207 painter.drawText(rect, QtCore.Qt.AlignCenter, "Pause") 208 return 209 210 boardTop = rect.bottom() - TetrixBoard.BoardHeight * self.squareHeight() 211 212 for i in range(TetrixBoard.BoardHeight): 213 for j in range(TetrixBoard.BoardWidth): 214 shape = self.shapeAt(j, TetrixBoard.BoardHeight - i - 1) 215 if shape != NoShape: 216 self.drawSquare(painter, 217 rect.left() + j * self.squareWidth(), 218 boardTop + i * self.squareHeight(), shape) 219 220 if self.curPiece.shape() != NoShape: 221 for i in range(4): 222 x = self.curX + self.curPiece.x(i) 223 y = self.curY - self.curPiece.y(i) 224 self.drawSquare(painter, rect.left() + x * self.squareWidth(), 225 boardTop + (TetrixBoard.BoardHeight - y - 1) * self.squareHeight(), 226 self.curPiece.shape()) 227 228 def keyPressEvent(self, event): 229 if not self.isStarted or self.isPaused or self.curPiece.shape() == NoShape: 230 super(TetrixBoard, self).keyPressEvent(event) 231 return 232 233 key = event.key() 234 if key == QtCore.Qt.Key_Left: 235 self.tryMove(self.curPiece, self.curX - 1, self.curY) 236 elif key == QtCore.Qt.Key_Right: 237 self.tryMove(self.curPiece, self.curX + 1, self.curY) 238 elif key == QtCore.Qt.Key_Down: 239 self.tryMove(self.curPiece.rotatedRight(), self.curX, self.curY) 240 elif key == QtCore.Qt.Key_Up: 241 self.tryMove(self.curPiece.rotatedLeft(), self.curX, self.curY) 242 elif key == QtCore.Qt.Key_Space: 243 self.dropDown() 244 elif key == QtCore.Qt.Key_D: 245 self.oneLineDown() 246 else: 247 super(TetrixBoard, self).keyPressEvent(event) 248 249 def timerEvent(self, event): 250 if event.timerId() == self.timer.timerId(): 251 if self.isWaitingAfterLine: 252 self.isWaitingAfterLine = False 253 self.newPiece() 254 self.timer.start(self.timeoutTime(), self) 255 else: 256 self.oneLineDown() 257 else: 258 super(TetrixBoard, self).timerEvent(event) 259 260 def clearBoard(self): 261 self.board = [NoShape for i in range(TetrixBoard.BoardHeight * TetrixBoard.BoardWidth)] 262 263 def dropDown(self): 264 dropHeight = 0 265 newY = self.curY 266 while newY > 0: 267 if not self.tryMove(self.curPiece, self.curX, newY - 1): 268 break 269 newY -= 1 270 dropHeight += 1 271 272 self.pieceDropped(dropHeight) 273 274 def oneLineDown(self): 275 if not self.tryMove(self.curPiece, self.curX, self.curY - 1): 276 self.pieceDropped(0) 277 278 def pieceDropped(self, dropHeight): 279 for i in range(4): 280 x = self.curX + self.curPiece.x(i) 281 y = self.curY - self.curPiece.y(i) 282 self.setShapeAt(x, y, self.curPiece.shape()) 283 284 self.numPiecesDropped += 1 285 if self.numPiecesDropped % 25 == 0: 286 self.level += 1 287 self.timer.start(self.timeoutTime(), self) 288 self.levelChanged.emit(self.level) 289 290 self.score += dropHeight + 7 291 self.scoreChanged.emit(self.score) 292 self.removeFullLines() 293 294 if not self.isWaitingAfterLine: 295 self.newPiece() 296 297 def removeFullLines(self): 298 numFullLines = 0 299 300 for i in range(TetrixBoard.BoardHeight - 1, -1, -1): 301 lineIsFull = True 302 303 for j in range(TetrixBoard.BoardWidth): 304 if self.shapeAt(j, i) == NoShape: 305 lineIsFull = False 306 break 307 308 if lineIsFull: 309 numFullLines += 1 310 for k in range(TetrixBoard.BoardHeight - 1): 311 for j in range(TetrixBoard.BoardWidth): 312 self.setShapeAt(j, k, self.shapeAt(j, k + 1)) 313 314 for j in range(TetrixBoard.BoardWidth): 315 self.setShapeAt(j, TetrixBoard.BoardHeight - 1, NoShape) 316 317 if numFullLines > 0: 318 self.numLinesRemoved += numFullLines 319 self.score += 10 * numFullLines 320 self.linesRemovedChanged.emit(self.numLinesRemoved) 321 self.scoreChanged.emit(self.score) 322 323 self.timer.start(500, self) 324 self.isWaitingAfterLine = True 325 self.curPiece.setShape(NoShape) 326 self.update() 327 328 def newPiece(self): 329 self.curPiece = self.nextPiece 330 self.nextPiece.setRandomShape() 331 self.showNextPiece() 332 self.curX = TetrixBoard.BoardWidth // 2 + 1 333 self.curY = TetrixBoard.BoardHeight - 1 + self.curPiece.minY() 334 335 if not self.tryMove(self.curPiece, self.curX, self.curY): 336 self.curPiece.setShape(NoShape) 337 self.timer.stop() 338 self.isStarted = False 339 340 def showNextPiece(self): 341 if self.nextPieceLabel is not None: 342 return 343 344 dx = self.nextPiece.maxX() - self.nextPiece.minX() + 1 345 dy = self.nextPiece.maxY() - self.nextPiece.minY() + 1 346 347 pixmap = QtGui.QPixmap(dx * self.squareWidth(), dy * self.squareHeight()) 348 painter = QtGui.QPainter(pixmap) 349 painter.fillRect(pixmap.rect(), self.nextPieceLabel.palette().background()) 350 351 for int in range(4): 352 x = self.nextPiece.x(i) - self.nextPiece.minX() 353 y = self.nextPiece.y(i) - self.nextPiece.minY() 354 self.drawSquare(painter, x * self.squareWidth(), 355 y * self.squareHeight(), self.nextPiece.shape()) 356 357 self.nextPieceLabel.setPixmap(pixmap) 358 359 def tryMove(self, newPiece, newX, newY): 360 for i in range(4): 361 x = newX + newPiece.x(i) 362 y = newY - newPiece.y(i) 363 if x < 0 or x >= TetrixBoard.BoardWidth or y < 0 or y >= TetrixBoard.BoardHeight: 364 return False 365 if self.shapeAt(x, y) != NoShape: 366 return False 367 368 self.curPiece = newPiece 369 self.curX = newX 370 self.curY = newY 371 self.update() 372 return True 373 374 def drawSquare(self, painter, x, y, shape): 375 colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC, 376 0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00] 377 378 color = QtGui.QColor(colorTable[shape]) 379 painter.fillRect(x + 1, y + 1, self.squareWidth() - 2, 380 self.squareHeight() - 2, color) 381 382 painter.setPen(color.lighter()) 383 painter.drawLine(x, y + self.squareHeight() - 1, x, y) 384 painter.drawLine(x, y, x + self.squareWidth() - 1, y) 385 386 painter.setPen(color.darker()) 387 painter.drawLine(x + 1, y + self.squareHeight() - 1, 388 x + self.squareWidth() - 1, y + self.squareHeight() - 1) 389 painter.drawLine(x + self.squareWidth() - 1, 390 y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1) 391 392 393class TetrixPiece(object): 394 coordsTable = ( 395 ((0, 0), (0, 0), (0, 0), (0, 0)), 396 ((0, -1), (0, 0), (-1, 0), (-1, 1)), 397 ((0, -1), (0, 0), (1, 0), (1, 1)), 398 ((0, -1), (0, 0), (0, 1), (0, 2)), 399 ((-1, 0), (0, 0), (1, 0), (0, 1)), 400 ((0, 0), (1, 0), (0, 1), (1, 1)), 401 ((-1, -1), (0, -1), (0, 0), (0, 1)), 402 ((1, -1), (0, -1), (0, 0), (0, 1)) 403 ) 404 405 def __init__(self): 406 self.coords = [[0,0] for _ in range(4)] 407 self.pieceShape = NoShape 408 409 self.setShape(NoShape) 410 411 def shape(self): 412 return self.pieceShape 413 414 def setShape(self, shape): 415 table = TetrixPiece.coordsTable[shape] 416 for i in range(4): 417 for j in range(2): 418 self.coords[i][j] = table[i][j] 419 420 self.pieceShape = shape 421 422 def setRandomShape(self): 423 self.setShape(random.randint(1, 7)) 424 425 def x(self, index): 426 return self.coords[index][0] 427 428 def y(self, index): 429 return self.coords[index][1] 430 431 def setX(self, index, x): 432 self.coords[index][0] = x 433 434 def setY(self, index, y): 435 self.coords[index][1] = y 436 437 def minX(self): 438 m = self.coords[0][0] 439 for i in range(4): 440 m = min(m, self.coords[i][0]) 441 442 return m 443 444 def maxX(self): 445 m = self.coords[0][0] 446 for i in range(4): 447 m = max(m, self.coords[i][0]) 448 449 return m 450 451 def minY(self): 452 m = self.coords[0][1] 453 for i in range(4): 454 m = min(m, self.coords[i][1]) 455 456 return m 457 458 def maxY(self): 459 m = self.coords[0][1] 460 for i in range(4): 461 m = max(m, self.coords[i][1]) 462 463 return m 464 465 def rotatedLeft(self): 466 if self.pieceShape == SquareShape: 467 return self 468 469 result = TetrixPiece() 470 result.pieceShape = self.pieceShape 471 for i in range(4): 472 result.setX(i, self.y(i)) 473 result.setY(i, -self.x(i)) 474 475 return result 476 477 def rotatedRight(self): 478 if self.pieceShape == SquareShape: 479 return self 480 481 result = TetrixPiece() 482 result.pieceShape = self.pieceShape 483 for i in range(4): 484 result.setX(i, -self.y(i)) 485 result.setY(i, self.x(i)) 486 487 return result 488 489 490if __name__ == '__main__': 491 492 import sys 493 494 app = QtWidgets.QApplication(sys.argv) 495 window = TetrixWindow() 496 window.show() 497 random.seed(None) 498 sys.exit(app.exec_()) 499