俄罗斯方块

记得很小的时候,从邻居家的小朋友那里拿到一个游戏机(按现在的眼光已经算不上游戏机了),开始了人生中第一个电子游戏-俄罗斯方块,我不知道多少人是从那古老的游戏机而知道俄罗斯方块的,但俄罗斯方块的大名想必都听说过。

一个小小的俄罗斯方块就就能为我们增加无限的乐趣。当然了,如果能自己开发出来,岂不是很有成就感。

UI

最近在写Frida可视化相关的项目。在选择UI框架的时候使用了PythonQt5。本文通过编写俄罗斯方块来熟悉PythonQt5的基本操作。

为什么选择PythonQt5?

  1. 跨平台。这一点是C#、MFC所不具备的。笔者用MFC写过游戏改键程序,有兴趣可以下载相应的代码: https://download.csdn.net/download/helloworlddm/11712844
  2. 简单。Qt是基于C++的,学过C++的想必都被那一眼望不到边的语法规则给折磨过,而python语言相比C++就简单多了,没有让人迷糊的指针、引用、模板。

手写俄罗斯方块

方块形状

俄罗斯方块称为积木拼图游戏。在这个游戏中,有七种不同形状叫方块: 如下图所示。这些形状的形成有四个方格。俄罗斯方块游戏的对象是移动和旋转的形状使他们适合尽可能多。如果我们设法形成一个行,该行摧毁我们得分。

游戏思想

我们使用QtCore.QBasicTimer()来创建一个游戏循环。
俄罗斯方块是绘制的。
图形是一个方块一个方块移动的(不是像素)
图形其实是一个简单的数字列表。

简化版

游戏简化一点,让它更容易理解。在比赛开始后立即启动。我们可以通过按p键暂停游戏。空格键将立即把俄罗斯方块块放到底部。游戏是在恒定速度,实现没有加速度。分数是我们已经删除的行数。

代码主体

代码包括四类: Tetris, Board, Tetrominoe 和Shape。Tetris 类用来存放游戏。Board是编写游戏逻辑的地方。Tetrominoe类包含所有俄罗斯方块的名称,Shape类包含一个俄罗斯方块的代码。

main函数
if __name__ == '__main__':app = QApplication([])tetris = Tetris()sys.exit(app.exec_())
Tetris类
class Tetris(QMainWindow):def __init__(self):super().__init__()self.initUI()def initUI(self):    '''initiates application UI'''self.tboard = Board(self)# 居中self.setCentralWidget(self.tboard)#创建一个状态栏将显示消息。我们将显示三种可能的消息:已删除的行数,停顿了一下消息,或游戏结束的消息self.statusbar = self.statusBar()        self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)# 启动游戏self.tboard.start()self.resize(180, 380)self.center()self.setWindowTitle('Tetris')        self.show()def center(self):'''centers the window on the screen'''#  窗口居中screen = QDesktopWidget().screenGeometry()size = self.geometry()self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2)

QMainWindow: 主窗口为构建应用程序的用户界面提供了一个框架。
A main window provides a framework for building an application’s user
interface. Qt has QMainWindow and its related classes for main window
management. QMainWindow has its own layout to which you can add
QToolBars, QDockWidgets, a QMenuBar, and a QStatusBar. The layout has
a center area that can be occupied by any kind of widget. You can see
an image of the layout below.

void QMainWindow::setCentralWidget(QWidget *widget)
Sets the given widget to be the main window’s central widget.
Note: QMainWindow takes ownership of the widget pointer and deletes it at the appropriate time.

这里涉及到信号机制。在后面会专门讲解。

self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)
class Board(QFrame):# 创建一个signalmsg2Statusbar = pyqtSignal(str)BoardWidth = 10BoardHeight = 22Speed = 300def __init__(self, parent):super().__init__(parent)self.initBoard()def initBoard(self):self.timer = QBasicTimer()self.isWaitingAfterLine = Falseself.curX = 0self.curY = 0self.numLinesRemoved = 0self.board = []self.setFocusPolicy(Qt.StrongFocus)self.isStarted = Falseself.isPaused = Falseself.clearBoard()def shapeAt(self, x, y):return self.board[(y * Board.BoardWidth) + x]def setShapeAt(self, x, y, shape):self.board[(y * Board.BoardWidth) + x] = shapedef squareWidth(self):return self.contentsRect().width() // Board.BoardWidthdef squareHeight(self):return self.contentsRect().height() // Board.BoardHeightdef start(self):if self.isPaused:returnself.isStarted = Trueself.isWaitingAfterLine = Falseself.numLinesRemoved = 0self.clearBoard()self.msg2Statusbar.emit(str(self.numLinesRemoved))self.newPiece()self.timer.start(Board.Speed, self)def pause(self):if not self.isStarted:returnself.isPaused = not self.isPausedif self.isPaused:self.timer.stop()self.msg2Statusbar.emit("paused")else:self.timer.start(Board.Speed, self)self.msg2Statusbar.emit(str(self.numLinesRemoved))self.update()def paintEvent(self, event):painter = QPainter(self)rect = self.contentsRect()boardTop = rect.bottom() - Board.BoardHeight * self.squareHeight()for i in range(Board.BoardHeight):for j in range(Board.BoardWidth):shape = self.shapeAt(j, Board.BoardHeight - i - 1)if shape != Tetrominoe.NoShape:self.drawSquare(painter,rect.left() + j * self.squareWidth(),boardTop + i * self.squareHeight(), shape)if self.curPiece.shape() != Tetrominoe.NoShape:for i in range(4):x = self.curX + self.curPiece.x(i)y = self.curY - self.curPiece.y(i)self.drawSquare(painter, rect.left() + x * self.squareWidth(),boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),self.curPiece.shape())def keyPressEvent(self, event):if not self.isStarted or self.curPiece.shape() == Tetrominoe.NoShape:super(Board, self).keyPressEvent(event)returnkey = event.key()if key == Qt.Key_P:self.pause()returnif self.isPaused:returnelif key == Qt.Key_Left:self.tryMove(self.curPiece, self.curX - 1, self.curY)elif key == Qt.Key_Right:self.tryMove(self.curPiece, self.curX + 1, self.curY)elif key == Qt.Key_Down:self.tryMove(self.curPiece.rotateRight(), self.curX, self.curY)elif key == Qt.Key_Up:self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)elif key == Qt.Key_Space:self.dropDown()elif key == Qt.Key_D:self.oneLineDown()else:super(Board, self).keyPressEvent(event)def timerEvent(self, event):if event.timerId() == self.timer.timerId():if self.isWaitingAfterLine:self.isWaitingAfterLine = Falseself.newPiece()else:self.oneLineDown()else:super(Board, self).timerEvent(event)def clearBoard(self):for i in range(Board.BoardHeight * Board.BoardWidth):self.board.append(Tetrominoe.NoShape)def dropDown(self):newY = self.curYwhile newY > 0:if not self.tryMove(self.curPiece, self.curX, newY - 1):breaknewY -= 1self.pieceDropped()def oneLineDown(self):if not self.tryMove(self.curPiece, self.curX, self.curY - 1):self.pieceDropped()def pieceDropped(self):for i in range(4):x = self.curX + self.curPiece.x(i)y = self.curY - self.curPiece.y(i)self.setShapeAt(x, y, self.curPiece.shape())self.removeFullLines()if not self.isWaitingAfterLine:self.newPiece()def removeFullLines(self):numFullLines = 0rowsToRemove = []for i in range(Board.BoardHeight):n = 0for j in range(Board.BoardWidth):if not self.shapeAt(j, i) == Tetrominoe.NoShape:n = n + 1if n == 10:rowsToRemove.append(i)rowsToRemove.reverse()for m in rowsToRemove:for k in range(m, Board.BoardHeight):for l in range(Board.BoardWidth):self.setShapeAt(l, k, self.shapeAt(l, k + 1))numFullLines = numFullLines + len(rowsToRemove)if numFullLines > 0:self.numLinesRemoved = self.numLinesRemoved + numFullLinesself.msg2Statusbar.emit(str(self.numLinesRemoved))self.isWaitingAfterLine = Trueself.curPiece.setShape(Tetrominoe.NoShape)self.update()def newPiece(self):self.curPiece = Shape()self.curPiece.setRandomShape()self.curX = Board.BoardWidth // 2 + 1self.curY = Board.BoardHeight - 1 + self.curPiece.minY()if not self.tryMove(self.curPiece, self.curX, self.curY):self.curPiece.setShape(Tetrominoe.NoShape)self.timer.stop()self.isStarted = Falseself.msg2Statusbar.emit("Game over")def tryMove(self, newPiece, newX, newY):for i in range(4):x = newX + newPiece.x(i)y = newY - newPiece.y(i)if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:return Falseif self.shapeAt(x, y) != Tetrominoe.NoShape:return Falseself.curPiece = newPieceself.curX = newXself.curY = newYself.update()return Truedef drawSquare(self, painter, x, y, shape):colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC,0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]color = QColor(colorTable[shape])painter.fillRect(x + 1, y + 1, self.squareWidth() - 2,self.squareHeight() - 2, color)painter.setPen(color.lighter())painter.drawLine(x, y + self.squareHeight() - 1, x, y)painter.drawLine(x, y, x + self.squareWidth() - 1, y)painter.setPen(color.darker())painter.drawLine(x + 1, y + self.squareHeight() - 1,x + self.squareWidth() - 1, y + self.squareHeight() - 1)painter.drawLine(x + self.squareWidth() - 1,y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1)

QBasicTimer: 为对象提供timer(计时器)事件。
This is a fast, lightweight, and low-level class used by Qt internally. We recommend using the higher-level QTimer class rather than this class if you want to use timers in your applications. Note that this timer is a repeating timer that will send subsequent timer events unless the stop() function is called.
void QBasicTimer::start(int msec, QObject *object)
Starts (or restarts) the timer with a msec milliseconds timeout. The timer will be a Qt::CoarseTimer. See Qt::TimerType for information on the different timer types.

Tetrominoe(7种形状)

class Tetrominoe(object):NoShape = 0ZShape = 1SShape = 2LineShape = 3TShape = 4SquareShape = 5LShape = 6MirroredLShape = 7
class Shape(object):coordsTable = (((0, 0),     (0, 0),     (0, 0),     (0, 0)),((0, -1),    (0, 0),     (-1, 0),    (-1, 1)),((0, -1),    (0, 0),     (1, 0),     (1, 1)),((0, -1),    (0, 0),     (0, 1),     (0, 2)),((-1, 0),    (0, 0),     (1, 0),     (0, 1)),((0, 0),     (1, 0),     (0, 1),     (1, 1)),((-1, -1),   (0, -1),    (0, 0),     (0, 1)),((1, -1),    (0, -1),    (0, 0),     (0, 1)))def __init__(self):self.coords = [[0,0] for i in range(4)]self.pieceShape = Tetrominoe.NoShapeself.setShape(Tetrominoe.NoShape)def shape(self):return self.pieceShapedef setShape(self, shape):'''sets a shape'''table = Shape.coordsTable[shape]for i in range(4):for j in range(2):self.coords[i][j] = table[i][j]self.pieceShape = shapedef setRandomShape(self):'''chooses a random shape'''self.setShape(random.randint(1, 7))def x(self, index):'''returns x coordinate'''return self.coords[index][0]def y(self, index):'''returns y coordinate'''return self.coords[index][1]def setX(self, index, x):'''sets x coordinate'''self.coords[index][0] = xdef setY(self, index, y):'''sets y coordinate'''self.coords[index][1] = ydef minX(self):'''returns min x value'''m = self.coords[0][0]for i in range(4):m = min(m, self.coords[i][0])return mdef maxX(self):'''returns max x value'''m = self.coords[0][0]for i in range(4):m = max(m, self.coords[i][0])return mdef minY(self):'''returns min y value'''m = self.coords[0][1]for i in range(4):m = min(m, self.coords[i][1])return mdef maxY(self):'''returns max y value'''m = self.coords[0][1]for i in range(4):m = max(m, self.coords[i][1])return mdef rotateLeft(self):'''rotates shape to the left'''if self.pieceShape == Tetrominoe.SquareShape:return selfresult = Shape()result.pieceShape = self.pieceShapefor i in range(4):result.setX(i, self.y(i))result.setY(i, -self.x(i))return resultdef rotateRight(self):'''rotates shape to the right'''if self.pieceShape == Tetrominoe.SquareShape:return selfresult = Shape()result.pieceShape = self.pieceShapefor i in range(4):result.setX(i, -self.y(i))result.setY(i, self.x(i))return result

俄罗斯方块的坐标系:

写在最后

界面编程是一件很有成就感的事情。快过年了,可以用这个俄罗斯方块来炫以下技能。

公众号

更多内容,欢迎关注我的微信公众号:无情剑客

自己动手实现俄罗斯方块相关推荐

  1. 动手使用ABAP Channel开发一些小工具,提升日常工作效率

    今天的故事要从ABAP小游戏说起. 中国的ABAP从业者们手头或多或少都搜集了一些ABAP小游戏,比如下面这些. 消灭星星: 扫雷: 来自我的朋友刘梦,公众号"SAP干货铺"里的俄 ...

  2. 动手使用ABAP Channel开发一些小工具,提升日常工作效率 1

    今天的故事要从ABAP小游戏说起. 中国的ABAP从业者们手头或多或少都搜集了一些ABAP小游戏,比如下面这些. 消灭星星: 扫雷: 来自我的朋友刘梦,公众号"SAP干货铺"里的俄 ...

  3. 预售┃没有标题,配得上这款“俄罗斯方块”

    ▲ 数据汪特别推荐 点击上图进入玩酷屋 在之前的文章时,马斯提到数学存在一种现象叫"梯次掉队",原因在于孩子的数学思维地基没有打牢.(传送门) 提到初中孩子需要空间想象能力时,很多 ...

  4. 没有标题,配得上这款“俄罗斯方块”

    在之前的文章时,马斯提到数学存在一种现象叫"梯次掉队",原因在于孩子的数学思维地基没有打牢.(传送门) 提到初中孩子需要空间想象能力时,很多父母疑惑为何需要?关于这点,小木给大家说 ...

  5. 无聊玩玩俄罗斯方块,用python自己做不带广告

           今天给大家带来一个有童年趣味的小游戏,俄罗斯方块,这也是老袁在上班摸鱼的游戏之一哟嘻嘻,但是建议大家还是要保持一个热爱工作的心,不要愧对老板对你的栽培和录用啊,咳咳咳!!言归正传,给大家 ...

  6. 俄罗斯方块的那些事:1.概要

    前言 前几天偶然在youtube上看到一个关于俄罗斯方块游戏的视频 视频按时间顺序讲述了这个游戏的诞生,发展,演化,穿插着冷战背景,版权纠纷等等内容,还配有丰富的插画,图片.60时分钟的视频节目(中间 ...

  7. 俄罗斯方块Tetris(C基础,Linux终端)

    文章目录 俄罗斯方块Tetris(C基础,Linux终端) 前言 游戏说明 游戏效果展示 游戏程序实现步骤 一.准备工作 1.非阻塞型输入 2.在屏幕上打印一个方块 二.头文件.宏定义.全局变量.声明 ...

  8. 基于java的俄罗斯方块游戏系统设计与实现(项目报告+答辩PPT+源代码+数据库+截图+部署视频)

    基于Java的俄罗斯方块游戏的设计与实现 俄罗斯方块是一款风靡全球,从一开始到现在都一直经久不衰的电脑.手机.掌上游戏机产品,是一款游戏规则简单,但又不缺乏乐趣的简单经典小游戏,上手容易,适用范围广泛 ...

  9. c语言经典案例 俄罗斯方块,C语言实现俄罗斯方块经典游戏课程设计

    C语言实现俄罗斯方块经典游戏课程设计 计算机实习报告 一.功能说明 1.1总体功能说明 本工程用C++语言实现了俄罗斯方块经典游戏. 俄罗斯方块游戏特点:俄罗斯方块的基本规则是通过键盘控制移动.旋转和 ...

最新文章

  1. javascript迭代器_JavaScript符号,迭代器,生成器,异步/等待和异步迭代器-全部简单解释...
  2. android studio 自动try,Catch Try让我在Android Studio调试中感到困惑
  3. 第5章 Python 数字图像处理(DIP) - 图像复原与重建10 - 空间滤波 - 统计排序滤波器 - 中值、最大值、最小值、中点、修正阿尔法均值滤波器
  4. .NET 应用程序支持直接调用 WebAssembly 模块
  5. 隐藏在数学中的哲理,令人回味无穷
  6. 前端学习(3014):vue+element今日头条管理--表单验证基本使用2
  7. java try的用法_Java中try、catch的使用方法
  8. 马斯克发全员信 呼吁员工6月底全力以赴
  9. ps怎么把图片背景变透明_ps怎么添加背景?ps怎么添加背景图?
  10. dos远程登录oracle,DOS批处理下 操作telnet实现自动远程登录操作
  11. 这样让你的采集内容变原创seo出来的伪原创
  12. php网页文件在,php是网页文件吗
  13. 电气工程学计算机有用吗,我是学计算机的,因为一直喜欢电气,所以想考个注..._电气工程师_帮考网...
  14. 国外苹果id_爆料者称苹果仍在继续研发iPhone屏下Touch ID
  15. 安装 PS2017CC
  16. 少年不惧岁月长,彼方尚有荣光在
  17. Python网络爬虫(一):爬虫基础
  18. 2020-08-21
  19. 《周志明的软件架构课》学习笔记 Day15
  20. 自媒体人想要写好实时热点文章,一定要掌握好这三点

热门文章

  1. radio RDS功能简介
  2. ApiPost是什么?
  3. Mac电脑使用:您的安全性偏好设置仅允许安装来自App Store和被认可的开发者的应用(解决方法)
  4. 程序 数列求和 c语言,[编程入门]有规律的数列求和-题解(C语言代码)
  5. 推荐几个优秀的人像摄影师
  6. Echarts 双柱状图+折线图合并---实现效果详解(vue+Echarts实现)
  7. 不能同吃的食物组合(你知道吗?)
  8. 【图文教程】Shell基础知识
  9. UML交互图(时序图、顺序图、序列图是一样的、协作图)
  10. 如何在不重装系统的情况下换固态硬盘?