Python實(shí)現(xiàn)AI自動(dòng)玩俄羅斯方塊游戲
導(dǎo)語(yǔ)
提到《俄羅斯方塊》(Tetris),那真是幾乎無人不知無人不曉。?
其歷史之悠久,可玩性之持久,能手輕輕一揮,吊打一大波游戲。
對(duì)于絕大多數(shù)小友而言,《俄羅斯方塊》的規(guī)則根本無需多言——將形狀不一的方塊填滿一行消除即可。
這款火了30幾年的《俄羅斯方塊》游戲之前就已經(jīng)寫過的哈,往期的Pygame合集里面可以找找看!
但今天木木子介紹的是《俄羅斯方塊》的新作——實(shí)現(xiàn)AI自動(dòng)玩兒游戲。
估計(jì)會(huì)讓你三觀盡毀,下巴掉落,驚呼:我玩了假游戲吧!
正文
移動(dòng)、掉落、填充、消除!
木木子·你我的童年回憶《俄羅斯方塊AI版本》已正式上線!
代碼由三部分組成 Tetris.py、tetris_model.py 和 tetris_ai.py游戲的主要邏輯由 Tetis 控制,model 定義了方塊的樣式,AI 顧名思義實(shí)現(xiàn)了主要的 AI 算法。
1)Tetris.py
class Tetris(QMainWindow): def __init__(self): super().__init__() self.isStarted = False self.isPaused = False self.nextMove = None self.lastShape = Shape.shapeNone self.initUI() def initUI(self): self.gridSize = 22 self.speed = 10 self.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: return self.isStarted = True self.tboard.score = 0 BOARD_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: return self.isPaused = not self.isPaused if 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 = 0 while BOARD_DATA.currentDirection != self.nextMove[0] and k < 4: BOARD_DATA.rotateRight() k += 1 k = 0 while 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 += lines if self.lastShape != BOARD_DATA.currentShape: self.nextMove = None self.lastShape = BOARD_DATA.currentShape self.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) return key = event.key() if key == Qt.Key_P: self.pause() return if self.isPaused: return elif 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: return color = 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 = gridSize def updateData(self): self.update() def paintEvent(self, event): painter = QPainter(self) minX, maxX, minY, maxY = BOARD_DATA.nextShape.getBoundingOffsets(0) dy = 3 * self.gridSize dx = (self.width() - (maxX - minX) * self.gridSize) / 2 val = BOARD_DATA.nextShape.shape for 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 = 10 def __init__(self, parent, gridSize): super().__init__(parent) self.setFixedSize(gridSize * BOARD_DATA.width, gridSize * BOARD_DATA.height) self.gridSize = gridSize self.initBoard() def initBoard(self): self.score = 0 BOARD_DATA.clear() def paintEvent(self, event): painter = QPainter(self) # Draw backboard for 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 shape for x, y in BOARD_DATA.getCurrentShapeCoord(): val = BOARD_DATA.currentShape.shape drawSquare(painter, x * self.gridSize, y * self.gridSize, val, self.gridSize) # Draw a border painter.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 random class Shape(object): shapeNone = 0 shapeI = 1 shapeL = 2 shapeJ = 3 shapeT = 4 shapeO = 5 shapeS = 6 shapeZ = 7 shapeCoord = ( ((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 = shape def 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, 0 for x, y in tmpCoords: if minX > x: minX = x if maxX < x: maxX = x if minY > y: minY = y if maxY < y: maxY = y return (minX, maxX, minY, maxY) class BoardData(object): width = 10 height = 22 def __init__(self): self.backBoard = [0] * BoardData.width * BoardData.height self.currentX = -1 self.currentY = -1 self.currentDirection = 0 self.currentShape = Shape() self.nextShape = Shape(random.randint(1, 7)) self.shapeStat = [0] * 8 def 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 = False if self.tryMoveCurrent(0, 5, -minY): self.currentX = 5 self.currentY = -minY self.currentDirection = 0 self.currentShape = self.nextShape self.nextShape = Shape(random.randint(1, 7)) result = True else: self.currentShape = Shape() self.currentX = -1 self.currentY = -1 self.currentDirection = 0 result = False self.shapeStat[self.currentShape.shape] += 1 return result def 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 False if self.backBoard[x + y * BoardData.width] > 0: return False return True def moveDown(self): lines = 0 if self.tryMoveCurrent(self.currentDirection, self.currentX, self.currentY + 1): self.currentY += 1 else: self.mergePiece() lines = self.removeFullLines() self.createNewPiece() return lines def dropDown(self): while self.tryMoveCurrent(self.currentDirection, self.currentX, self.currentY + 1): self.currentY += 1 self.mergePiece() lines = self.removeFullLines() self.createNewPiece() return lines def moveLeft(self): if self.tryMoveCurrent(self.currentDirection, self.currentX - 1, self.currentY): self.currentX -= 1 def moveRight(self): if self.tryMoveCurrent(self.currentDirection, self.currentX + 1, self.currentY): self.currentX += 1 def rotateRight(self): if self.tryMoveCurrent((self.currentDirection + 1) % 4, self.currentX, self.currentY): self.currentDirection += 1 self.currentDirection %= 4 def rotateLeft(self): if self.tryMoveCurrent((self.currentDirection - 1) % 4, self.currentX, self.currentY): self.currentDirection -= 1 self.currentDirection %= 4 def removeFullLines(self): newBackBoard = [0] * BoardData.width * BoardData.height newY = BoardData.height - 1 lines = 0 for 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 -= 1 else: lines += 1 if lines > 0: self.backBoard = newBackBoard return lines def mergePiece(self): for x, y in self.currentShape.getCoords(self.currentDirection, self.currentX, self.currentY): self.backBoard[x + y * BoardData.width] = self.currentShape.shape self.currentX = -1 self.currentY = -1 self.currentDirection = 0 self.currentShape = Shape() def clear(self): self.currentX = -1 self.currentY = -1 self.currentDirection = 0 self.currentShape = Shape() self.backBoard = [0] * BoardData.width * BoardData.height BOARD_DATA = BoardData()
3)Tetris_ai.py??
from tetris_model import BOARD_DATA, Shape import math from datetime import datetime import numpy as np class TetrisAI(object): def nextMove(self): t1 = datetime.now() if BOARD_DATA.currentShape == Shape.shapeNone: return None currentDirection = BOARD_DATA.currentDirection currentY = BOARD_DATA.currentY _, _, minY, _ = BOARD_DATA.nextShape.getBoundingOffsets(0) nextY = -minY # print("=======") strategy = None if 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 strategy def calcNextDropDist(self, data, d0, xRange): res = {} for x0 in xRange: if x0 not in res: res[x0] = BOARD_DATA.height - 1 for x, y in BOARD_DATA.nextShape.getCoords(d0, x0, 0): yy = 0 while yy + y < BOARD_DATA.height and (yy + y < 0 or data[(y + yy), x] == Shape.shapeNone): yy += 1 yy -= 1 if yy < res[x0]: res[x0] = yy return res def 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 board def dropDown(self, data, shape, direction, x0): dy = BOARD_DATA.height - 1 for x, y in shape.getCoords(direction, x0, 0): yy = 0 while yy + y < BOARD_DATA.height and (yy + y < 0 or data[(y + yy), x] == Shape.shapeNone): yy += 1 yy -= 1 if 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.shape def calculateScore(self, step1Board, d1, x1, dropDist): # print("calculateScore") t1 = datetime.now() width = BOARD_DATA.width height = BOARD_DATA.height self.dropDownByDist(step1Board, BOARD_DATA.nextShape, d1, x1, dropDist[x1]) # print(datetime.now() - t1) # Term 1: lines to be removed fullLines, nearFullLines = 0, 0 roofY = [0] * width holeCandidates = [0] * width holeConfirm = [0] * width vHoles, vBlocks = 0, 0 for y in range(height - 1, -1, -1): hasHole = False hasBlock = False for x in range(width): if step1Board[y, x] == Shape.shapeNone: hasHole = True holeCandidates[x] += 1 else: hasBlock = True roofY[x] = height - y if holeCandidates[x] > 0: holeConfirm[x] += holeCandidates[x] holeCandidates[x] = 0 if holeConfirm[x] > 0: vBlocks += 1 if not hasBlock: break if not hasHole and hasBlock: fullLines += 1 vHoles = 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 = 0 else: stdY = math.sqrt(sum([y ** 2 for y in roofY]) / len(roofY) - (sum(roofY) / len(roofY)) ** 2) if len(roofDy) <= 0: stdDY = 0 else: 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 score TETRIS_AI = TetrisAI()
效果展示
1)視頻展示——
【普通玩家VS高手玩家】一帶傳奇游戲《俄羅斯方塊兒》AI版!
2)截圖展示——
以上就是Python實(shí)現(xiàn)AI自動(dòng)玩俄羅斯方塊游戲的詳細(xì)內(nèi)容,更多關(guān)于Python俄羅斯方塊的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C站最全Python標(biāo)準(zhǔn)庫(kù)總結(jié),你想要的都在這里
這篇文章主要介紹了C站最全的Python標(biāo)準(zhǔn)庫(kù),總共包含10個(gè)類型,希望能對(duì)大家有幫助,看完不虧系列2021-07-07解決Ubuntu pip 安裝 mysql-python包出錯(cuò)的問題
今天小編就為大家分享一篇解決Ubuntu pip 安裝 mysql-python包出錯(cuò)的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-06-06關(guān)于Tensorflow中的tf.train.batch函數(shù)的使用
本篇文章主要介紹了關(guān)于Tensorflow中的tf.train.batch函數(shù)的使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04從CentOS安裝完成到生成詞云python的實(shí)例
下面小編就為大家分享一篇從CentOS安裝完成到生成詞云python的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助2017-12-12如何在Django中設(shè)置定時(shí)任務(wù)的方法示例
這篇文章主要介紹了如何在Django中設(shè)置定時(shí)任務(wù)的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01通過Python收集匯聚MySQL 表信息的實(shí)例詳解
這篇文章主要介紹了通過Python收集匯聚MySQL 表信息的實(shí)例代碼,核心代碼是創(chuàng)建保存數(shù)據(jù)的腳本,收集的功能腳本,代碼簡(jiǎn)單明了,需要的朋友可以參考下2021-10-10python簡(jiǎn)單獲取數(shù)組元素個(gè)數(shù)的方法
這篇文章主要介紹了python簡(jiǎn)單獲取數(shù)組元素個(gè)數(shù)的方法,實(shí)例分析了Python中l(wèi)en方法的相關(guān)使用技巧,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2015-07-07淺析python參數(shù)的知識(shí)點(diǎn)
在本文里小編給大家分享的是關(guān)于python參數(shù)的知識(shí)點(diǎn)內(nèi)容,正在學(xué)習(xí)的讀者們跟著思考下吧。2018-12-12