导语
提到《俄罗斯方块》(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其它相关文章!
黄山市民网:https://www.huangshanshimin.com/