自己动手实现俄罗斯方块
俄罗斯方块
记得很小的时候,从邻居家的小朋友那里拿到一个游戏机(按现在的眼光已经算不上游戏机了),开始了人生中第一个电子游戏-俄罗斯方块,我不知道多少人是从那古老的游戏机而知道俄罗斯方块的,但俄罗斯方块的大名想必都听说过。
一个小小的俄罗斯方块就就能为我们增加无限的乐趣。当然了,如果能自己开发出来,岂不是很有成就感。
UI
最近在写Frida可视化相关的项目。在选择UI框架的时候使用了PythonQt5。本文通过编写俄罗斯方块来熟悉PythonQt5的基本操作。
为什么选择PythonQt5?
- 跨平台。这一点是C#、MFC所不具备的。笔者用MFC写过游戏改键程序,有兴趣可以下载相应的代码: https://download.csdn.net/download/helloworlddm/11712844
- 简单。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
俄罗斯方块的坐标系:
写在最后
界面编程是一件很有成就感的事情。快过年了,可以用这个俄罗斯方块来炫以下技能。
公众号
更多内容,欢迎关注我的微信公众号:无情剑客
自己动手实现俄罗斯方块相关推荐
- 动手使用ABAP Channel开发一些小工具,提升日常工作效率
今天的故事要从ABAP小游戏说起. 中国的ABAP从业者们手头或多或少都搜集了一些ABAP小游戏,比如下面这些. 消灭星星: 扫雷: 来自我的朋友刘梦,公众号"SAP干货铺"里的俄 ...
- 动手使用ABAP Channel开发一些小工具,提升日常工作效率 1
今天的故事要从ABAP小游戏说起. 中国的ABAP从业者们手头或多或少都搜集了一些ABAP小游戏,比如下面这些. 消灭星星: 扫雷: 来自我的朋友刘梦,公众号"SAP干货铺"里的俄 ...
- 预售┃没有标题,配得上这款“俄罗斯方块”
▲ 数据汪特别推荐 点击上图进入玩酷屋 在之前的文章时,马斯提到数学存在一种现象叫"梯次掉队",原因在于孩子的数学思维地基没有打牢.(传送门) 提到初中孩子需要空间想象能力时,很多 ...
- 没有标题,配得上这款“俄罗斯方块”
在之前的文章时,马斯提到数学存在一种现象叫"梯次掉队",原因在于孩子的数学思维地基没有打牢.(传送门) 提到初中孩子需要空间想象能力时,很多父母疑惑为何需要?关于这点,小木给大家说 ...
- 无聊玩玩俄罗斯方块,用python自己做不带广告
今天给大家带来一个有童年趣味的小游戏,俄罗斯方块,这也是老袁在上班摸鱼的游戏之一哟嘻嘻,但是建议大家还是要保持一个热爱工作的心,不要愧对老板对你的栽培和录用啊,咳咳咳!!言归正传,给大家 ...
- 俄罗斯方块的那些事:1.概要
前言 前几天偶然在youtube上看到一个关于俄罗斯方块游戏的视频 视频按时间顺序讲述了这个游戏的诞生,发展,演化,穿插着冷战背景,版权纠纷等等内容,还配有丰富的插画,图片.60时分钟的视频节目(中间 ...
- 俄罗斯方块Tetris(C基础,Linux终端)
文章目录 俄罗斯方块Tetris(C基础,Linux终端) 前言 游戏说明 游戏效果展示 游戏程序实现步骤 一.准备工作 1.非阻塞型输入 2.在屏幕上打印一个方块 二.头文件.宏定义.全局变量.声明 ...
- 基于java的俄罗斯方块游戏系统设计与实现(项目报告+答辩PPT+源代码+数据库+截图+部署视频)
基于Java的俄罗斯方块游戏的设计与实现 俄罗斯方块是一款风靡全球,从一开始到现在都一直经久不衰的电脑.手机.掌上游戏机产品,是一款游戏规则简单,但又不缺乏乐趣的简单经典小游戏,上手容易,适用范围广泛 ...
- c语言经典案例 俄罗斯方块,C语言实现俄罗斯方块经典游戏课程设计
C语言实现俄罗斯方块经典游戏课程设计 计算机实习报告 一.功能说明 1.1总体功能说明 本工程用C++语言实现了俄罗斯方块经典游戏. 俄罗斯方块游戏特点:俄罗斯方块的基本规则是通过键盘控制移动.旋转和 ...
最新文章
- javascript迭代器_JavaScript符号,迭代器,生成器,异步/等待和异步迭代器-全部简单解释...
- android studio 自动try,Catch Try让我在Android Studio调试中感到困惑
- 第5章 Python 数字图像处理(DIP) - 图像复原与重建10 - 空间滤波 - 统计排序滤波器 - 中值、最大值、最小值、中点、修正阿尔法均值滤波器
- .NET 应用程序支持直接调用 WebAssembly 模块
- 隐藏在数学中的哲理,令人回味无穷
- 前端学习(3014):vue+element今日头条管理--表单验证基本使用2
- java try的用法_Java中try、catch的使用方法
- 马斯克发全员信 呼吁员工6月底全力以赴
- ps怎么把图片背景变透明_ps怎么添加背景?ps怎么添加背景图?
- dos远程登录oracle,DOS批处理下 操作telnet实现自动远程登录操作
- 这样让你的采集内容变原创seo出来的伪原创
- php网页文件在,php是网页文件吗
- 电气工程学计算机有用吗,我是学计算机的,因为一直喜欢电气,所以想考个注..._电气工程师_帮考网...
- 国外苹果id_爆料者称苹果仍在继续研发iPhone屏下Touch ID
- 安装 PS2017CC
- 少年不惧岁月长,彼方尚有荣光在
- Python网络爬虫(一):爬虫基础
- 2020-08-21
- 《周志明的软件架构课》学习笔记 Day15
- 自媒体人想要写好实时热点文章,一定要掌握好这三点