1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 from __future__ import print_function 4 from __future__ import unicode_literals 5 from __future__ import division 6 from __future__ import absolute_import 7 try: 8 str = unicode 9 except NameError: 10 pass 11 12 import random, sys, sip 13 try: 14 sip.setapi("QString" ,2) 15 except ValueError: 16 pass 17 18 from PyQt4 import QtCore, QtGui 19 20 21 NoShape, ZShape, SShape, LineShape, TShape, SquareShape, LShape, MirroredLShape = range(8) 22 23 random.seed(None) 24 25 26 class TetrixWindow(QtGui.QWidget): 27 def __init__(self, parent = None): 28 QtGui.QWidget.__init__(self, parent, QtCore.Qt.Window) 29 30 self.board = TetrixBoard() 31 self.indictor = TetrixIndictor() 32 33 nextPieceLabel = QtGui.QLabel(self) 34 nextPieceLabel.setFrameStyle(QtGui.QFrame.Box | QtGui.QFrame.Raised) 35 nextPieceLabel.setAlignment(QtCore.Qt.AlignCenter) 36 self.board.setNextPieceLabel(nextPieceLabel) 37 38 scoreLcd = QtGui.QLCDNumber(6) 39 scoreLcd.setSegmentStyle(QtGui.QLCDNumber.Filled) 40 levelLcd = QtGui.QLCDNumber(2) 41 levelLcd.setSegmentStyle(QtGui.QLCDNumber.Filled) 42 linesLcd = QtGui.QLCDNumber(6) 43 linesLcd.setSegmentStyle(QtGui.QLCDNumber.Filled) 44 45 startButton = QtGui.QPushButton(self.trUtf8("开始(&S)")) 46 startButton.setFocusPolicy(QtCore.Qt.NoFocus) 47 quitButton = QtGui.QPushButton(self.trUtf8("退出(&X)")) 48 quitButton.setFocusPolicy(QtCore.Qt.NoFocus) 49 pauseButton = QtGui.QPushButton(self.trUtf8("暂停(&P)")) 50 pauseButton.setFocusPolicy(QtCore.Qt.NoFocus) 51 52 startButton.clicked.connect(self.board.start) 53 pauseButton.clicked.connect(self.board.pause) 54 quitButton.clicked.connect(self.close) 55 self.board.scoreChanged.connect(scoreLcd.display) 56 self.board.levelChanged.connect(levelLcd.display) 57 self.board.linesRemovedChanged.connect(linesLcd.display) 58 self.board.act.connect(self.indictor.showIndictor) 59 60 layout1 = QtGui.QHBoxLayout() 61 layout3 = QtGui.QVBoxLayout() 62 layout3.addWidget(self.board) 63 layout3.addWidget(self.indictor) 64 layout3.setSpacing(0) 65 layout1.addLayout(layout3) 66 layout2 = QtGui.QVBoxLayout() 67 layout2.addWidget(self.createLabel(self.trUtf8("下一个方块"))) 68 layout2.addWidget(nextPieceLabel) 69 layout2.addWidget(self.createLabel(self.trUtf8("级别"))) 70 layout2.addWidget(levelLcd) 71 layout2.addWidget(self.createLabel(self.trUtf8("成绩")),) 72 layout2.addWidget(scoreLcd) 73 layout2.addWidget(self.createLabel(self.trUtf8("总共消去行数"))) 74 layout2.addWidget(linesLcd) 75 layout2.addWidget(startButton) 76 layout2.addWidget(quitButton) 77 layout2.addWidget(pauseButton) 78 layout1.addLayout(layout2) 79 layout1.setStretch(0, 75) 80 layout1.setStretch(1, 25) 81 self.setLayout(layout1) 82 83 self.setWindowTitle(self.trUtf8("俄罗斯方块(Tetrix)")) 84 self.resize(self.logicalDpiX() / 96 * 275, self.logicalDpiY() / 96 * 380) 85 86 r = self.geometry() 87 r.moveCenter(QtGui.qApp.desktop().screenGeometry().center()) 88 self.setGeometry(r) 89 90 def createLabel(self, text): 91 lbl = QtGui.QLabel(text) 92 lbl.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignBottom) 93 return lbl 94 95 96 class TetrixIndictor(QtGui.QWidget): 97 """位于主游戏区下方的一个扁小的控件,用于显示当前位置落下时的位置。 98 现在主要的问题是游戏区的大小超出了人类的眼睛的焦点区。 99 或许可以让整个游戏界面更小一些。""" 100 101 def __init__(self, parent = None): 102 QtGui.QWidget.__init__(self, parent) 103 self.begin = self.end = None 104 self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) 105 106 def showIndictor(self, curX, piece): 107 self.begin = curX + piece.minX() 108 self.end = curX + piece.maxX() 109 self.update() 110 111 def paintEvent(self, event): 112 QtGui.QWidget.paintEvent(self, event) 113 if self.begin is None: 114 return 115 board = self.parent().board 116 pieceWidth = board.contentsRect().width() // TetrixBoard.BoardWidth 117 brush = QtGui.QBrush(QtCore.Qt.yellow) 118 painter = QtGui.QPainter(self) 119 painter.setBrush(brush) 120 painter.drawRect(board.contentsRect().left() + self.begin * pieceWidth, 0, \ 121 (self.end - self.begin + 1) * pieceWidth, self.height() - 1 ) 122 123 def sizeHint(self): 124 return QtCore.QSize(self.parent().board.width(), 8) 125 126 127 class TetrixBoard(QtGui.QFrame): 128 BoardWidth = 11 129 BoardHeight = 22 130 131 scoreChanged = QtCore.pyqtSignal(int) 132 levelChanged = QtCore.pyqtSignal(int) 133 linesRemovedChanged = QtCore.pyqtSignal(int) 134 act = QtCore.pyqtSignal(int, "PyQt_PyObject") 135 136 def __init__(self, parent = None): 137 super(TetrixBoard, self).__init__(parent) 138 self.setStyleSheet("background-color:black;border:2px solid darkGreen;") 139 140 self.timer = QtCore.QBasicTimer() 141 self.nextPieceLabel = None 142 self.isWaitingAfterLine = False 143 self.curPiece = TetrixPiece() 144 self.nextPiece = TetrixPiece() 145 self.curX = 0 146 self.curY = 0 147 self.numLinesRemoved = 0 148 self.numPiecesDropped = 0 149 self.score = 0 150 self.level = 0 151 self.board = None 152 153 #self.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Sunken) 154 self.setFrameStyle(QtGui.QFrame.Box) 155 self.setFocusPolicy(QtCore.Qt.StrongFocus) 156 self.isStarted = False 157 self.isPaused = False 158 self.clearBoard() 159 160 self.nextPiece.setRandomShape() 161 162 def focusOutEvent(self, event): 163 if self.isStarted and not self.isPaused: 164 self.pause() 165 QtGui.QFrame.focusOutEvent(self, event) 166 167 def shapeAt(self, x, y): 168 return self.board[(y * TetrixBoard.BoardWidth) + x] 169 170 def setShapeAt(self, x, y, shape): 171 self.board[(y * TetrixBoard.BoardWidth) + x] = shape 172 173 def timeoutTime(self): 174 return 1000 // (1 + self.level) 175 176 def squareWidth(self): 177 return self.contentsRect().width() // TetrixBoard.BoardWidth 178 179 def squareHeight(self): 180 return self.contentsRect().height() // TetrixBoard.BoardHeight 181 182 def setNextPieceLabel(self, label): 183 self.nextPieceLabel = label 184 #label.setScaledContents(True) 185 label.setMinimumSize(label.width(), label.width()) 186 187 def sizeHint(self): 188 return QtCore.QSize(TetrixBoard.BoardWidth * 15 + self.frameWidth() * 2, 189 TetrixBoard.BoardHeight * 15 + self.frameWidth() * 2) 190 191 def minimumSizeHint(self): 192 return QtCore.QSize(TetrixBoard.BoardWidth * 15 + self.frameWidth() * 2, 193 TetrixBoard.BoardHeight * 15 + self.frameWidth() * 2) 194 195 def start(self): 196 if self.isPaused: 197 return 198 199 self.isStarted = True 200 self.isWaitingAfterLine = False 201 self.numLinesRemoved = 0 202 self.numPiecesDropped = 0 203 self.score = 0 204 self.level = 1 205 self.clearBoard() 206 207 self.linesRemovedChanged.emit(self.numLinesRemoved) 208 self.scoreChanged.emit(self.score) 209 self.levelChanged.emit(self.level) 210 211 self.newPiece() 212 self.timer.start(self.timeoutTime(), self) 213 214 def pause(self): 215 if not self.isStarted: 216 return 217 218 self.isPaused = not self.isPaused 219 if self.isPaused: 220 self.timer.stop() 221 else: 222 self.timer.start(self.timeoutTime(), self) 223 224 self.update() 225 226 def paintEvent(self, event): 227 super(TetrixBoard, self).paintEvent(event) 228 229 painter = QtGui.QPainter(self) 230 rect = self.contentsRect() 231 232 if self.isPaused: 233 painter.drawText(rect, QtCore.Qt.AlignCenter, self.trUtf8("暂停")) 234 return 235 236 boardTop = rect.bottom() - TetrixBoard.BoardHeight * self.squareHeight() 237 238 for i in range(TetrixBoard.BoardHeight): 239 for j in range(TetrixBoard.BoardWidth): 240 shape = self.shapeAt(j, TetrixBoard.BoardHeight - i - 1) 241 if shape != NoShape: 242 self.drawSquare(painter, 243 rect.left() + j * self.squareWidth(), 244 boardTop + i * self.squareHeight(), shape) 245 246 if self.curPiece.shape() != NoShape: 247 for i in range(4): 248 x = self.curX + self.curPiece.x(i) 249 y = self.curY - self.curPiece.y(i) 250 self.drawSquare(painter, rect.left() + x * self.squareWidth(), 251 boardTop + (TetrixBoard.BoardHeight - y - 1) * self.squareHeight(), 252 self.curPiece.shape()) 253 254 def keyPressEvent(self, event): 255 if not self.isStarted or self.isPaused or self.curPiece.shape() == NoShape: 256 super(TetrixBoard, self).keyPressEvent(event) 257 return 258 259 key = event.key() 260 if key == QtCore.Qt.Key_Left: 261 self.tryMove(self.curPiece, self.curX - 1, self.curY) 262 elif key == QtCore.Qt.Key_Right: 263 self.tryMove(self.curPiece, self.curX + 1, self.curY) 264 elif key == QtCore.Qt.Key_Down: 265 self.tryMove(self.curPiece.rotatedRight(), self.curX, self.curY) 266 elif key == QtCore.Qt.Key_Up: 267 self.tryMove(self.curPiece.rotatedLeft(), self.curX, self.curY) 268 elif key == QtCore.Qt.Key_Space: 269 self.dropDown() 270 elif key == QtCore.Qt.Key_D: 271 self.oneLineDown() 272 else: 273 super(TetrixBoard, self).keyPressEvent(event) 274 275 def timerEvent(self, event): 276 if event.timerId() == self.timer.timerId(): 277 if self.isWaitingAfterLine: 278 self.isWaitingAfterLine = False 279 self.newPiece() 280 self.timer.start(self.timeoutTime(), self) 281 else: 282 self.oneLineDown() 283 else: 284 super(TetrixBoard, self).timerEvent(event) 285 286 def clearBoard(self): 287 self.board = [NoShape for i in range(TetrixBoard.BoardHeight * TetrixBoard.BoardWidth)] 288 289 def dropDown(self): 290 dropHeight = 0 291 newY = self.curY 292 while newY > 0: 293 if not self.tryMove(self.curPiece, self.curX, newY - 1): 294 break 295 newY -= 1 296 dropHeight += 1 297 298 self.pieceDropped(dropHeight) 299 300 def oneLineDown(self): 301 if not self.tryMove(self.curPiece, self.curX, self.curY - 1): 302 self.pieceDropped(0) 303 304 def pieceDropped(self, dropHeight): 305 for i in range(4): 306 x = self.curX + self.curPiece.x(i) 307 y = self.curY - self.curPiece.y(i) 308 self.setShapeAt(x, y, self.curPiece.shape()) 309 310 self.numPiecesDropped += 1 311 if self.numPiecesDropped % 25 == 0: 312 self.level += 1 313 self.timer.start(self.timeoutTime(), self) 314 self.levelChanged.emit(self.level) 315 316 self.score += dropHeight + 7 317 self.scoreChanged.emit(self.score) 318 self.removeFullLines() 319 320 if not self.isWaitingAfterLine: 321 self.newPiece() 322 323 def removeFullLines(self): 324 numFullLines = 0 325 326 for i in range(TetrixBoard.BoardHeight - 1, -1, -1): 327 lineIsFull = True 328 329 for j in range(TetrixBoard.BoardWidth): 330 if self.shapeAt(j, i) == NoShape: 331 lineIsFull = False 332 break 333 334 if lineIsFull: 335 numFullLines += 1 336 for k in range(i, TetrixBoard.BoardHeight - 1): 337 for j in range(TetrixBoard.BoardWidth): 338 self.setShapeAt(j, k, self.shapeAt(j, k + 1)) 339 340 for j in range(TetrixBoard.BoardWidth): 341 self.setShapeAt(j, TetrixBoard.BoardHeight - 1, NoShape) 342 343 if numFullLines > 0: 344 self.numLinesRemoved += numFullLines 345 self.score += 10 * numFullLines 346 self.linesRemovedChanged.emit(self.numLinesRemoved) 347 self.scoreChanged.emit(self.score) 348 349 self.timer.start(200, self) 350 self.isWaitingAfterLine = True 351 self.curPiece.setShape(NoShape) 352 self.update() 353 354 def newPiece(self): 355 self.curPiece = self.nextPiece 356 self.nextPiece = TetrixPiece() 357 self.nextPiece.setRandomShape() 358 self.showNextPiece() 359 self.curX = TetrixBoard.BoardWidth // 2 360 self.curY = TetrixBoard.BoardHeight - 1 + self.curPiece.minY() 361 self.act.emit(self.curX, self.curPiece) 362 363 if not self.tryMove(self.curPiece, self.curX, self.curY): 364 self.curPiece.setShape(NoShape) 365 self.timer.stop() 366 self.isStarted = False 367 368 def showNextPiece(self): 369 if self.nextPieceLabel is None: 370 return 371 372 dx = self.nextPiece.maxX() - self.nextPiece.minX() + 1 373 dy = self.nextPiece.maxY() - self.nextPiece.minY() + 1 374 375 self.pixmapNextPiece = QtGui.QPixmap(dx * self.squareWidth(), dy * self.squareHeight()) 376 painter = QtGui.QPainter(self.pixmapNextPiece) 377 painter.fillRect(self.pixmapNextPiece.rect(), self.nextPieceLabel.palette().background()) 378 379 for i in range(4): 380 x = self.nextPiece.x(i) - self.nextPiece.minX() 381 y = self.nextPiece.y(i) - self.nextPiece.minY() 382 self.drawSquare(painter, x * self.squareWidth(), 383 y * self.squareHeight(), self.nextPiece.shape()) 384 385 self.nextPieceLabel.setPixmap(self.pixmapNextPiece) 386 387 def tryMove(self, newPiece, newX, newY): 388 for i in range(4): 389 x = newX + newPiece.x(i) 390 y = newY - newPiece.y(i) 391 if x < 0 or x >= TetrixBoard.BoardWidth or y < 0 or y >= TetrixBoard.BoardHeight: 392 return False 393 if self.shapeAt(x, y) != NoShape: 394 return False 395 396 self.curPiece = newPiece 397 self.curX = newX 398 self.curY = newY 399 self.update() 400 self.act.emit(self.curX, self.curPiece) 401 return True 402 403 def drawSquare(self, painter, x, y, shape): 404 colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC, 405 0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00] 406 407 color = QtGui.QColor(colorTable[shape]) 408 painter.fillRect(x + 1, y + 1, self.squareWidth() - 2, 409 self.squareHeight() - 2, color) 410 411 painter.setPen(color.light()) 412 painter.drawLine(x, y + self.squareHeight() - 1, x, y) 413 painter.drawLine(x, y, x + self.squareWidth() - 1, y) 414 415 painter.setPen(color.dark()) 416 painter.drawLine(x + 1, y + self.squareHeight() - 1, 417 x + self.squareWidth() - 1, y + self.squareHeight() - 1) 418 painter.drawLine(x + self.squareWidth() - 1, 419 y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1) 420 421 422 class TetrixPiece(object): 423 coordsTable = ( 424 ((0, 0), (0, 0), (0, 0), (0, 0)), 425 ((0, -1), (0, 0), ( - 1, 0), ( - 1, 1)), 426 ((0, -1), (0, 0), (1, 0), (1, 1)), 427 ((0, -1), (0, 0), (0, 1), (0, 2)), 428 (( - 1, 0), (0, 0), (1, 0), (0, 1)), 429 ((0, 0), (1, 0), (0, 1), (1, 1)), 430 (( - 1, -1), (0, -1), (0, 0), (0, 1)), 431 ((1, -1), (0, -1), (0, 0), (0, 1)) 432 ) 433 434 def __init__(self): 435 self.coords = [[0,0] for _ in range(4)] 436 self.pieceShape = NoShape 437 438 self.setShape(NoShape) 439 440 def shape(self): 441 return self.pieceShape 442 443 def setShape(self, shape): 444 table = TetrixPiece.coordsTable[shape] 445 for i in range(4): 446 for j in range(2): 447 self.coords[i][j] = table[i][j] 448 449 self.pieceShape = shape 450 451 def setRandomShape(self): 452 self.setShape(random.randint(1, 7)) 453 454 def x(self, index): 455 return self.coords[index][0] 456 457 def y(self, index): 458 return self.coords[index][1] 459 460 def setX(self, index, x): 461 self.coords[index][0] = x 462 463 def setY(self, index, y): 464 self.coords[index][1] = y 465 466 def minX(self): 467 m = self.coords[0][0] 468 for i in range(4): 469 m = min(m, self.coords[i][0]) 470 471 return m 472 473 def maxX(self): 474 m = self.coords[0][0] 475 for i in range(4): 476 m = max(m, self.coords[i][0]) 477 478 return m 479 480 def minY(self): 481 m = self.coords[0][1] 482 for i in range(4): 483 m = min(m, self.coords[i][1]) 484 485 return m 486 487 def maxY(self): 488 m = self.coords[0][1] 489 for i in range(4): 490 m = max(m, self.coords[i][1]) 491 492 return m 493 494 def rotatedLeft(self): 495 if self.pieceShape == SquareShape: 496 return self 497 498 result = TetrixPiece() 499 result.pieceShape = self.pieceShape 500 for i in range(4): 501 result.setX(i, self.y(i)) 502 result.setY(i, -self.x(i)) 503 504 return result 505 506 def rotatedRight(self): 507 if self.pieceShape == SquareShape: 508 return self 509 510 result = TetrixPiece() 511 result.pieceShape = self.pieceShape 512 for i in range(4): 513 result.setX(i, -self.y(i)) 514 result.setY(i, self.x(i)) 515 516 return result 517 518 if __name__ == '__main__': 519 app = QtGui.QApplication(sys.argv) 520 window = TetrixWindow() 521 window.show() 522 if hasattr(app, "exec"): 523 result = getattr(app, "exec")() 524 else: 525 result = getattr(app, "exec_")() 526 sys.exit(result)