导语

提到《俄罗斯方块》(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
                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实现ai自动玩俄罗斯方块游戏的详细内容,更多关于python俄罗斯方块的资料请关注www.887551.com其它相关文章!