导语

提到《俄罗斯方块》(Tetris),那真是几乎无人不知无人不晓。

其历史之悠久,可玩性之持久,能手轻轻一挥,吊打一大波游戏。

对于绝大多数小友而言,《俄罗斯方块》的规则根本无需多言——将形状不一的方块填满一行消除

即可。这款火了30几年的《俄罗斯方块》游戏之前就已经写过的哈,往期的Pygame合集里面可以

找找看!但今天木木子介绍的是《俄罗斯方块》的新作——实现AI自动玩儿游戏。

估计会让你三观尽毁,下巴掉落,惊呼:我玩了假游戏吧!

正文

移动、掉落、填充、消除!

木木子·你我的童年回忆《俄罗斯方块AI版本》已正式上线!

代码由三部分组成 Tetris.py、tetris_model.py 和 tetris_ai.py游戏的主要逻辑由 Tetis 控制,model 定义了方块的样式,AI 顾名思义实现了主要的 AI 算法。

1)Tetris.py

class Tetris(QMainWindow):def __init__(self):super().__init__()self.isStarted = Falseself.isPaused = Falseself.nextMove = Noneself.lastShape = Shape.shapeNoneself.initUI()def initUI(self):self.gridSize = 22self.speed = 10self.timer = QBasicTimer()self.setFocusPolicy(Qt.StrongFocus)hLayout = QHBoxLayout()self.tboard = Board(self, self.gridSize)hLayout.addWidget(self.tboard)self.sidePanel = SidePanel(self, self.gridSize)hLayout.addWidget(self.sidePanel)self.statusbar = self.statusBar()self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)self.start()self.center()self.setWindowTitle('AI俄罗斯方块儿')self.show()self.setFixedSize(self.tboard.width() + self.sidePanel.width(),self.sidePanel.height() + self.statusbar.height())def center(self):screen = QDesktopWidget().screenGeometry()size = self.geometry()self.move((screen.width() - size.width()) // 2, (screen.height() - size.height()) // 2)def start(self):if self.isPaused:returnself.isStarted = Trueself.tboard.score = 0BOARD_DATA.clear()self.tboard.msg2Statusbar.emit(str(self.tboard.score))BOARD_DATA.createNewPiece()self.timer.start(self.speed, self)def pause(self):if not self.isStarted:returnself.isPaused = not self.isPausedif self.isPaused:self.timer.stop()self.tboard.msg2Statusbar.emit("paused")else:self.timer.start(self.speed, self)self.updateWindow()def updateWindow(self):self.tboard.updateData()self.sidePanel.updateData()self.update()def timerEvent(self, event):if event.timerId() == self.timer.timerId():if TETRIS_AI and not self.nextMove:self.nextMove = TETRIS_AI.nextMove()if self.nextMove:k = 0while BOARD_DATA.currentDirection != self.nextMove[0] and k < 4:BOARD_DATA.rotateRight()k += 1k = 0while BOARD_DATA.currentX != self.nextMove[1] and k < 5:if BOARD_DATA.currentX > self.nextMove[1]:BOARD_DATA.moveLeft()elif BOARD_DATA.currentX < self.nextMove[1]:BOARD_DATA.moveRight()k += 1# lines = BOARD_DATA.dropDown()lines = BOARD_DATA.moveDown()self.tboard.score += linesif self.lastShape != BOARD_DATA.currentShape:self.nextMove = Noneself.lastShape = BOARD_DATA.currentShapeself.updateWindow()else:super(Tetris, self).timerEvent(event)def keyPressEvent(self, event):if not self.isStarted or BOARD_DATA.currentShape == Shape.shapeNone:super(Tetris, self).keyPressEvent(event)returnkey = event.key()if key == Qt.Key_P:self.pause()returnif self.isPaused:returnelif key == Qt.Key_Left:BOARD_DATA.moveLeft()elif key == Qt.Key_Right:BOARD_DATA.moveRight()elif key == Qt.Key_Up:BOARD_DATA.rotateLeft()elif key == Qt.Key_Space:self.tboard.score += BOARD_DATA.dropDown()else:super(Tetris, self).keyPressEvent(event)self.updateWindow()def drawSquare(painter, x, y, val, s):colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC,0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]if val == 0:returncolor = QColor(colorTable[val])painter.fillRect(x + 1, y + 1, s - 2, s - 2, color)painter.setPen(color.lighter())painter.drawLine(x, y + s - 1, x, y)painter.drawLine(x, y, x + s - 1, y)painter.setPen(color.darker())painter.drawLine(x + 1, y + s - 1, x + s - 1, y + s - 1)painter.drawLine(x + s - 1, y + s - 1, x + s - 1, y + 1)class SidePanel(QFrame):def __init__(self, parent, gridSize):super().__init__(parent)self.setFixedSize(gridSize * 5, gridSize * BOARD_DATA.height)self.move(gridSize * BOARD_DATA.width, 0)self.gridSize = gridSizedef updateData(self):self.update()def paintEvent(self, event):painter = QPainter(self)minX, maxX, minY, maxY = BOARD_DATA.nextShape.getBoundingOffsets(0)dy = 3 * self.gridSizedx = (self.width() - (maxX - minX) * self.gridSize) / 2val = BOARD_DATA.nextShape.shapefor x, y in BOARD_DATA.nextShape.getCoords(0, 0, -minY):drawSquare(painter, x * self.gridSize + dx, y * self.gridSize + dy, val, self.gridSize)class Board(QFrame):msg2Statusbar = pyqtSignal(str)speed = 10def __init__(self, parent, gridSize):super().__init__(parent)self.setFixedSize(gridSize * BOARD_DATA.width, gridSize * BOARD_DATA.height)self.gridSize = gridSizeself.initBoard()def initBoard(self):self.score = 0BOARD_DATA.clear()def paintEvent(self, event):painter = QPainter(self)# Draw backboardfor x in range(BOARD_DATA.width):for y in range(BOARD_DATA.height):val = BOARD_DATA.getValue(x, y)drawSquare(painter, x * self.gridSize, y * self.gridSize, val, self.gridSize)# Draw current shapefor x, y in BOARD_DATA.getCurrentShapeCoord():val = BOARD_DATA.currentShape.shapedrawSquare(painter, x * self.gridSize, y * self.gridSize, val, self.gridSize)# Draw a borderpainter.setPen(QColor(0x777777))painter.drawLine(self.width()-1, 0, self.width()-1, self.height())painter.setPen(QColor(0xCCCCCC))painter.drawLine(self.width(), 0, self.width(), self.height())def updateData(self):self.msg2Statusbar.emit(str(self.score))self.update()if __name__ == '__main__':# random.seed(32)app = QApplication([])tetris = Tetris()sys.exit(app.exec_())

2)Tetris_model.py

import randomclass Shape(object):shapeNone = 0shapeI = 1shapeL = 2shapeJ = 3shapeT = 4shapeO = 5shapeS = 6shapeZ = 7shapeCoord = (((0, 0), (0, 0), (0, 0), (0, 0)),((0, -1), (0, 0), (0, 1), (0, 2)),((0, -1), (0, 0), (0, 1), (1, 1)),((0, -1), (0, 0), (0, 1), (-1, 1)),((0, -1), (0, 0), (0, 1), (1, 0)),((0, 0), (0, -1), (1, 0), (1, -1)),((0, 0), (0, -1), (-1, 0), (1, -1)),((0, 0), (0, -1), (1, 0), (-1, -1)))def __init__(self, shape=0):self.shape = shapedef getRotatedOffsets(self, direction):tmpCoords = Shape.shapeCoord[self.shape]if direction == 0 or self.shape == Shape.shapeO:return ((x, y) for x, y in tmpCoords)if direction == 1:return ((-y, x) for x, y in tmpCoords)if direction == 2:if self.shape in (Shape.shapeI, Shape.shapeZ, Shape.shapeS):return ((x, y) for x, y in tmpCoords)else:return ((-x, -y) for x, y in tmpCoords)if direction == 3:if self.shape in (Shape.shapeI, Shape.shapeZ, Shape.shapeS):return ((-y, x) for x, y in tmpCoords)else:return ((y, -x) for x, y in tmpCoords)def getCoords(self, direction, x, y):return ((x + xx, y + yy) for xx, yy in self.getRotatedOffsets(direction))def getBoundingOffsets(self, direction):tmpCoords = self.getRotatedOffsets(direction)minX, maxX, minY, maxY = 0, 0, 0, 0for x, y in tmpCoords:if minX > x:minX = xif maxX < x:maxX = xif minY > y:minY = yif maxY < y:maxY = yreturn (minX, maxX, minY, maxY)class BoardData(object):width = 10height = 22def __init__(self):self.backBoard = [0] * BoardData.width * BoardData.heightself.currentX = -1self.currentY = -1self.currentDirection = 0self.currentShape = Shape()self.nextShape = Shape(random.randint(1, 7))self.shapeStat = [0] * 8def getData(self):return self.backBoard[:]def getValue(self, x, y):return self.backBoard[x + y * BoardData.width]def getCurrentShapeCoord(self):return self.currentShape.getCoords(self.currentDirection, self.currentX, self.currentY)def createNewPiece(self):minX, maxX, minY, maxY = self.nextShape.getBoundingOffsets(0)result = Falseif self.tryMoveCurrent(0, 5, -minY):self.currentX = 5self.currentY = -minYself.currentDirection = 0self.currentShape = self.nextShapeself.nextShape = Shape(random.randint(1, 7))result = Trueelse:self.currentShape = Shape()self.currentX = -1self.currentY = -1self.currentDirection = 0result = Falseself.shapeStat[self.currentShape.shape] += 1return resultdef tryMoveCurrent(self, direction, x, y):return self.tryMove(self.currentShape, direction, x, y)def tryMove(self, shape, direction, x, y):for x, y in shape.getCoords(direction, x, y):if x >= BoardData.width or x < 0 or y >= BoardData.height or y < 0:return Falseif self.backBoard[x + y * BoardData.width] > 0:return Falsereturn Truedef moveDown(self):lines = 0if self.tryMoveCurrent(self.currentDirection, self.currentX, self.currentY + 1):self.currentY += 1else:self.mergePiece()lines = self.removeFullLines()self.createNewPiece()return linesdef dropDown(self):while self.tryMoveCurrent(self.currentDirection, self.currentX, self.currentY + 1):self.currentY += 1self.mergePiece()lines = self.removeFullLines()self.createNewPiece()return linesdef moveLeft(self):if self.tryMoveCurrent(self.currentDirection, self.currentX - 1, self.currentY):self.currentX -= 1def moveRight(self):if self.tryMoveCurrent(self.currentDirection, self.currentX + 1, self.currentY):self.currentX += 1def rotateRight(self):if self.tryMoveCurrent((self.currentDirection + 1) % 4, self.currentX, self.currentY):self.currentDirection += 1self.currentDirection %= 4def rotateLeft(self):if self.tryMoveCurrent((self.currentDirection - 1) % 4, self.currentX, self.currentY):self.currentDirection -= 1self.currentDirection %= 4def removeFullLines(self):newBackBoard = [0] * BoardData.width * BoardData.heightnewY = BoardData.height - 1lines = 0for y in range(BoardData.height - 1, -1, -1):blockCount = sum([1 if self.backBoard[x + y * BoardData.width] > 0 else 0 for x in range(BoardData.width)])if blockCount < BoardData.width:for x in range(BoardData.width):newBackBoard[x + newY * BoardData.width] = self.backBoard[x + y * BoardData.width]newY -= 1else:lines += 1if lines > 0:self.backBoard = newBackBoardreturn linesdef mergePiece(self):for x, y in self.currentShape.getCoords(self.currentDirection, self.currentX, self.currentY):self.backBoard[x + y * BoardData.width] = self.currentShape.shapeself.currentX = -1self.currentY = -1self.currentDirection = 0self.currentShape = Shape()def clear(self):self.currentX = -1self.currentY = -1self.currentDirection = 0self.currentShape = Shape()self.backBoard = [0] * BoardData.width * BoardData.heightBOARD_DATA = BoardData()

3)Tetris_ai.py

from tetris_model import BOARD_DATA, Shape
import math
from datetime import datetime
import numpy as npclass TetrisAI(object):def nextMove(self):t1 = datetime.now()if BOARD_DATA.currentShape == Shape.shapeNone:return NonecurrentDirection = BOARD_DATA.currentDirectioncurrentY = BOARD_DATA.currentY_, _, minY, _ = BOARD_DATA.nextShape.getBoundingOffsets(0)nextY = -minY# print("=======")strategy = Noneif BOARD_DATA.currentShape.shape in (Shape.shapeI, Shape.shapeZ, Shape.shapeS):d0Range = (0, 1)elif BOARD_DATA.currentShape.shape == Shape.shapeO:d0Range = (0,)else:d0Range = (0, 1, 2, 3)if BOARD_DATA.nextShape.shape in (Shape.shapeI, Shape.shapeZ, Shape.shapeS):d1Range = (0, 1)elif BOARD_DATA.nextShape.shape == Shape.shapeO:d1Range = (0,)else:d1Range = (0, 1, 2, 3)for d0 in d0Range:minX, maxX, _, _ = BOARD_DATA.currentShape.getBoundingOffsets(d0)for x0 in range(-minX, BOARD_DATA.width - maxX):board = self.calcStep1Board(d0, x0)for d1 in d1Range:minX, maxX, _, _ = BOARD_DATA.nextShape.getBoundingOffsets(d1)dropDist = self.calcNextDropDist(board, d1, range(-minX, BOARD_DATA.width - maxX))for x1 in range(-minX, BOARD_DATA.width - maxX):score = self.calculateScore(np.copy(board), d1, x1, dropDist)if not strategy or strategy[2] < score:strategy = (d0, x0, score)print("===", datetime.now() - t1)return strategydef calcNextDropDist(self, data, d0, xRange):res = {}for x0 in xRange:if x0 not in res:res[x0] = BOARD_DATA.height - 1for x, y in BOARD_DATA.nextShape.getCoords(d0, x0, 0):yy = 0while yy + y < BOARD_DATA.height and (yy + y < 0 or data[(y + yy), x] == Shape.shapeNone):yy += 1yy -= 1if yy < res[x0]:res[x0] = yyreturn resdef calcStep1Board(self, d0, x0):board = np.array(BOARD_DATA.getData()).reshape((BOARD_DATA.height, BOARD_DATA.width))self.dropDown(board, BOARD_DATA.currentShape, d0, x0)return boarddef dropDown(self, data, shape, direction, x0):dy = BOARD_DATA.height - 1for x, y in shape.getCoords(direction, x0, 0):yy = 0while yy + y < BOARD_DATA.height and (yy + y < 0 or data[(y + yy), x] == Shape.shapeNone):yy += 1yy -= 1if yy < dy:dy = yy# print("dropDown: shape {0}, direction {1}, x0 {2}, dy {3}".format(shape.shape, direction, x0, dy))self.dropDownByDist(data, shape, direction, x0, dy)def dropDownByDist(self, data, shape, direction, x0, dist):for x, y in shape.getCoords(direction, x0, 0):data[y + dist, x] = shape.shapedef calculateScore(self, step1Board, d1, x1, dropDist):# print("calculateScore")t1 = datetime.now()width = BOARD_DATA.widthheight = BOARD_DATA.heightself.dropDownByDist(step1Board, BOARD_DATA.nextShape, d1, x1, dropDist[x1])# print(datetime.now() - t1)# Term 1: lines to be removedfullLines, nearFullLines = 0, 0roofY = [0] * widthholeCandidates = [0] * widthholeConfirm = [0] * widthvHoles, vBlocks = 0, 0for y in range(height - 1, -1, -1):hasHole = FalsehasBlock = Falsefor x in range(width):if step1Board[y, x] == Shape.shapeNone:hasHole = TrueholeCandidates[x] += 1else:hasBlock = TrueroofY[x] = height - yif holeCandidates[x] > 0:holeConfirm[x] += holeCandidates[x]holeCandidates[x] = 0if holeConfirm[x] > 0:vBlocks += 1if not hasBlock:breakif not hasHole and hasBlock:fullLines += 1vHoles = sum([x ** .7 for x in holeConfirm])maxHeight = max(roofY) - fullLines# print(datetime.now() - t1)roofDy = [roofY[i] - roofY[i+1] for i in range(len(roofY) - 1)]if len(roofY) <= 0:stdY = 0else:stdY = math.sqrt(sum([y ** 2 for y in roofY]) / len(roofY) - (sum(roofY) / len(roofY)) ** 2)if len(roofDy) <= 0:stdDY = 0else:stdDY = math.sqrt(sum([y ** 2 for y in roofDy]) / len(roofDy) - (sum(roofDy) / len(roofDy)) ** 2)absDy = sum([abs(x) for x in roofDy])maxDy = max(roofY) - min(roofY)# print(datetime.now() - t1)score = fullLines * 1.8 - vHoles * 1.0 - vBlocks * 0.5 - maxHeight ** 1.5 * 0.02 \- stdY * 0.0 - stdDY * 0.01 - absDy * 0.2 - maxDy * 0.3# print(score, fullLines, vHoles, vBlocks, maxHeight, stdY, stdDY, absDy, roofY, d0, x0, d1, x1)return scoreTETRIS_AI = TetrisAI()

​4)效果展示

1)视频展示——


2)截图展示——

总结

【普通玩家VS高手玩家】一带传奇游戏《俄罗斯方块儿》新作—实现AI自动玩游戏~相关推荐

  1. python创始人游戏_李总为AI湿身,而5118老板连夜教你Python写AI自动玩游戏,快搬凳...

    5118 Python公开课 昨天在百度AI开发者大会现场,正当李彦宏在现场讲解百度无人驾驶汽车自动泊车时,一名不明人士冲向演讲台并向他泼洒不名液体.李彦宏没有停止演讲,并在现场表示:"大家 ...

  2. tensorflow2.0 dqn 深度强化学习 AI自动玩游戏,有详细注解

    有人看文章后半部分代码是空的,其实是博客的字变黑了.我不知道这个怎么回事,没检查出语法问题.所以只要你把空白的部分一起复制就能看到所有的代码了!! 准备环境 安装python 游戏包 pip inst ...

  3. 如何设置计算机玩游戏不卡,win10电脑怎么设置玩游戏不卡?

    很多人说Win10系统游戏兼容性差,游戏体验差.其实这个说法很不正确.对于大多数游戏玩家来说,Win10是一个不错的游戏平台.不仅兼容性好,Xbox的原生加持也让玩家更享受.很多人不知道Win10中有 ...

  4. 新一代区块链游戏:超越炒作的NFT和玩游戏盈利

    为什么要用区块链和加密来制作游戏?区块链游戏无法吸引世界各地的游戏开发者和游戏玩家.许多人将区块链视为一个复杂的解决方案,以解决他们从未遇到过的问题,或者是一个扰乱他们商业模式的技术挫折.在本文中,我 ...

  5. 《有限与无限的游戏》第二章 没人能独自玩游戏:经典摘抄(1)

    没有人能独自一人玩游戏.一个人不成其为人,没有群体,便没有自我.我们并不是作为我们本身与他人有关系,相反,我们是与他人有关系的我们. 只有能够变化的,才能够持续下去,这是无限游戏的参与者所遵循的原则. ...

  6. 《有限与无限的游戏》第二章 没人能独自玩游戏:经典摘抄(3)

    将社会视为文化的一种,并不等同于要推翻甚至或改变社会,而只是去消除它感知的必要性.无限游戏的参与者有他们的规则,他们只是不忘记这一点:规则是对一致同意的一种表达,并不是对一致同意的要求. 社会由其边界 ...

  7. python自动玩游戏_超牛!用Python自动玩转2048游戏

    本篇作者:BlueDamage 近来在折腾selenium自动化, 感觉配合爬虫很有意思, 大多数以前难以模拟登录的网站都可以爬了,折腾了这么久, 于是想自动玩个2048游戏!嘿嘿, 我是一个不擅长玩 ...

  8. 计算机电脑怎么打开游戏,电脑玩游戏卡怎么办,详细教您电脑玩游戏卡怎么办...

    我们在玩游戏的时候要是遇到很卡的情况发生的话,那是真的很气的.因为正玩的好好的,就是因为电脑卡了,所以就造成游戏输了.这得多憋屈,所以小伙伴们,今天小编就告诉你们电脑玩游戏卡要怎么办. 其实小编也是一 ...

  9. 可以打游戏的计算机,还在用笔记本玩游戏?台式机才能给你极致享受

    [PConline 游戏爆测]随着笔记本的性能越来越好,玩家对于游戏本的需求也越来越高了,再加上购买游戏笔记本并不需要额外购买显示器,就能享受到高刷新率高色域的屏幕,让玩家对于游戏台式机就更加不感兴趣 ...

最新文章

  1. sqlite3之基本操作(二)
  2. UA OPTI570 量子力学29 摄动理论简介
  3. linux——第三方软件仓库的搭建
  4. ie网络集合代理无法启动_网络故障诊断70例!经典老文了!
  5. AI入门:不用任何公式把循环神经网络讲清楚
  6. SkinMagic使用后按钮加自定义图标或菜单GetMneu返回NULL的解决方法
  7. SpringBoot2.x整合Redis 分布式集群_02
  8. CentOS5.5下SVN部署文档
  9. iPhone手机投屏小米盒子
  10. IT职场人生系列之十六:入职(新手篇)
  11. python文件目录无权限_python检查目录文件权限并修改目录文件权限的操作
  12. nginx日志采集 mysql_shell + go + mysql nginx日志统计 (三) :数据的展示
  13. 瑞友天翼服务器ip地址怎么修改,怎么修改这个IP地址
  14. linux启动和grub修复
  15. web项目防御mysql注入_WEB安全 php+mysql5注入防御(二)
  16. word文档怎么到下一页去写_word文档怎么插入下一页
  17. vue中时间格式2021-11-21T12:30:00.000+00:00转换yyyy-MM-dd HH:mm:ss
  18. 跑步适合戴哪种耳机不掉?专业跑步耳机推荐
  19. 01Java并发编程的艺术之并发编程的挑战
  20. 【LeetCode】476. 数字的补数 Number Complement

热门文章

  1. HTML表单:<form>标签
  2. 基于android的快递管理系统app
  3. Python 获取进程PID
  4. 【为自己写的】如何提升自信
  5. 这反人类的限制,终于被破解掉了!
  6. 苹果删除照片不释放内存_新买的手机变得很卡?试试删除这3个文件夹,释放大量内存...
  7. 质量英文词汇-医疗器械
  8. gcc编译python代码_GCC编译流程(一)
  9. 不要制定“太伟大”的计划
  10. [转载]《觉醒的中医》(二)