开发者

Python实现AI自动玩俄罗斯方块游戏

目录
  • 导语
  • 正文
    • 1)Tetris.py
    • 2)Tetris_model.py​​
    • 3)Tetris_ai.py​​
  • 效果展示

    导语

    提到《俄罗斯方块》(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 = 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
            OnFLun        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.fillRectOnFLun(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.swww.cppcns.comhapeS):
                    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)
    
        dwww.cppcns.comef 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 has编程客栈Hole 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)视频展示——

    Python实现AI自动玩俄罗斯方块游戏

    【普通玩家VS高手玩家】一带传奇游戏《俄罗斯方块儿》AI版!

    2)截图展示——

    Python实现AI自动玩俄罗斯方块游戏

    以上就是python实现AI自动玩俄罗斯方块游戏的详细内容,更多关于Python俄罗斯方块的资料请关注我们其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜