Pygame用200行代码实现俄罗斯方块
作者:微小冷
逻辑设计
俄罗斯方块的逻辑很简单,就是几个方块组合在一起,然后下落,当其碰到四周的墙壁后便无法移动。若某行被方块所填满,那么就删除这一行,然后此行上面的所有方块下降一行。
为了将这个逻辑代码化,可以用布尔矩阵来表示具体的方块类型,比如长条形方块可用如下矩阵表示。
BLOCK_I = [[0,1,0,0], [0,1,0,0], [0,1,0,0], [0,1,0,0]]
在实际操作时,会经常遇到旋转操作,其基本逻辑是,将第i ii行变成第− i -i−i列,从而旋转逻辑如下
def rotateBlock(block): newBlock = [[[] for _ in block] for b in block[0]] for i,row in enumerate(block, 1): for j,r in enumerate(row, 0): newBlock[j][-i] = r return newBlock
定义好所有的方块,并将其封入列表BLOCK
中,
BLOCK_S = [[0,1,1], [1,1,0], [0,0,0]] BLOCK_Z = [[1,1,0], [0,1,1], [0,0,0]] # I型方块 BLOCK_I = [[0,1,0,0], [0,1,0,0], [0,1,0,0], [0,1,0,0]], # O型方块 BLOCK_O = [[1,1], [1,1]] # J型方块 BLOCK_J = [[1,0,0], [1,1,1], [0,0,0]] # L型方块 BLOCK_L = [[0,0,1], [1,1,1], [0,0,0]] # T型方块 BLOCK_T = [[0,1,0], [1,1,1], [0,0,0]] BLOCKS = [BLOCK_S, BLOCK_I, BLOCK_J, BLOCK_L, BLOCK_O, BLOCK_T, BLOCK_Z]
有了这个,就可以随机生成其中某个方块了,这个过程需要引入一点随机性
import random # 创建一个图形 def newBlock(): block = random.choice(BLOCKS) for _ in range(random.randint(0,4)): block = rotateBlock(block) return block
挪动逻辑
原理十分简单,但想要写出一个可以玩的游戏,却还需要注意一些细节问题。比如俄罗斯方块在碰到墙壁时的行为;旋转方块的方法。在实现这些具体问题之前,先设置几个常量
SCREEN_WIDTH, SCREEN_HEIGHT = 450, 750 BLOCK_COL_NUM = 10 # 每行的方格数 BLOCK_ROW_NUM = 25 # 每列的方个数 SIZE = 30 # 每个小方格大小 BG_COLOR = (40, 40, 60) # 背景色 RED = (200, 30, 30) # 红色,GAME OVER 的字体颜色 WHITE = (255,255,255) # 白色 BLACK = (0,0,0) # 黑色 GREEN = (60, 128, 80) # 绿色 STOP_LST = [[0 for _ in range(BLOCK_COL_NUM)] for _ in range(BLOCK_ROW_NUM)]
接下来逐个实现俄罗斯方块的细节,当左右移动方块时,若方块碰到墙壁,则需要一个判定函数,代码如下。其中isR
为True时表示向右移动,否则向左移动。其判断逻辑非常直观,只需查看是否超出边界。
# 判断是否可以向左右移动 # isR为True,判断能否向右移动;isR为False,判断能否向左移动 def judgeMoveLR(block, stCol, isR): nCol = len(block[0]) cols = range(nCol-1, -1, -1) if isR else range(nCol) for col in cols: lstCol = [line[col] for line in block] if 1 not in lstCol: continue if not 0 <= stCol + col < BLOCK_COL_NUM: return False return True
如果判定成功,则需要移动方块,那么根据其输入左移或者右移,可以构造一个函数
# 相左或者向右移动 def moveLR(block, stCol, key): isR = key == pygame.K_RIGHT stCol = stCol + 1 if isR else stCol - 1 if judgeMoveLR(block ,stCol, isR): return 1 if isR else -1 return 0
方块在下落过程中若碰壁,则稍微有些麻烦,因为下方可能已经堆积了一些方块,为此不仅需要知道当前下落方块的信息,还要知道已经堆积的方块的信息。
# 判断是否可以继续下落 def judgeMoveDown(block, stRow, stCol, stopLst): # 堆积方块的位置 stopPosition = list() for row, line in enumerate(stopLst): for col, b in enumerate(line): if b: stopPosition.append((row, col)) # 判断碰撞 for row, line in enumerate(block): if 1 in line and stRow + row >= BLOCK_ROW_NUM: return False for col, b in enumerate(line): if b and (stRow + row, stCol + col) in stopPosition: return False return True
最后将三者合在一起,用于判断方块是否可以继续移动
def canMove(block, stRow, stCol, stopLst): flag = judgeMoveLR(block, stCol, False) flag &= judgeMoveLR(block, stCol, True) flag &= judgeMoveDown(block, stRow, stCol, stopLst) return flag
消除和堆积
其消除逻辑是,当某行全为1的时候,就把这行删掉,然后在顶端补充全0的列表。
def judgeLines(stopLst): # 记录刚刚消除的行数 i, N, count = 0, len(stopLst), 0 for i in range(N): if 0 not in stopLst[i]: line = [0]*len(stopLst.pop(i)) stopLst.insert(0, line) count += 10 return count
堆积逻辑则相对简单,只需将堆积部位置为1即可。
# 将停止移动的block添加到堆积方块 def addToStopLst(stopLst, block, stRow, stCol): for row, line in enumerate(block): for col, b in enumerate(line): if b: stopLst[stRow + row][stCol + col] = 1
至此,俄罗斯方块的操作逻辑便设计完成,接下来是绘图。
参数逻辑
俄罗斯方块的下落速度可以区分不同关卡,下面是一种用分数决定下落速度的方案,其速度值表示方块下落的延时时间。
def changeSpeed(score): scores = [20, 50, 100, 200] infos = "12345" speeds = [0.5, 0.4, 0.3, 0.2, 0.1] ind = bisect(scores, score) return infos[ind], speeds[ind]
绘图逻辑
首先是背景绘制,主要包括背景颜色和网格的绘制。
def drawBackground(screen): screen.fill(BG_COLOR) width = SIZE * BLOCK_COL_NUM pygame.draw.line(screen, BLACK, (width, 0), (width, SCREEN_HEIGHT), 4) for x in range(BLOCK_COL_NUM): pygame.draw.line(screen, BLACK, (x * SIZE, 0), (x * SIZE, SCREEN_HEIGHT), 1) for y in range(BLOCK_ROW_NUM): pygame.draw.line(screen, BLACK, (0, y * SIZE), (width, y * SIZE), 1)
这里仅采使用了左侧窗口,其右侧将用于存放提示信息,主要包括得分、速度等级以及接下来落下的方块。
def drawRight(screen, score, speedInfo): fontPath = r'C:\Windows\Fonts\msyh.ttc' font = pygame.font.Font(fontPath, 24) # 黑体24 posDct = { '得分: ':10, str(score): 50, '速度: ':100, speedInfo: 150, '下一个:':200 } for k,v in posDct.items(): msg = font.render(k, True, WHITE) screen.blit(msg, (BLOCK_COL_NUM * SIZE + 10, v))
然后是方块绘制函数,顾名思义,drawBlock绘制一个方块,drawBlocks绘制一组方块。
def drawBlock(screen, row, col, stRow, stCol): stRow += row * SIZE stCol += SIZE * col rect = pygame.Rect(stCol, stRow, SIZE, SIZE) pygame.draw.rect(screen, GREEN, rect, 0) pygame.draw.rect(screen, BLACK, rect, 1) def drawBlocks(screen, block, stRow, stCol): for row, line in enumerate(block): for col, b in enumerate(line): if not b: continue drawBlock(screen, row, col, stRow, stCol)
最后,若游戏失败,则弹出失败信息
def drawGamerOver(screen): fontPath = r'C:\Windows\Fonts\msyh.ttc' font = pygame.font.Font(fontPath, 72) w, h = font.size('GAME OVER') msg = font.render('GAME OVER', True, RED) screen.blit(msg, ((SCREEN_WIDTH - w) // 2, (SCREEN_HEIGHT - h) // 2)) # 显示"鼠标点击任意位置,再来一局" font = pygame.font.Font(fontPath, 24) w, h = font.size('点击任意位置,再来一局') msg = font.render('点击任意位置,再来一局', True, RED) screen.blit(msg, ((SCREEN_WIDTH - w) // 2, (SCREEN_HEIGHT - h) // 2 + 80))
主程序
主程序内容如下,其中initBlock用于初始化方块。由于大家对俄罗斯方块都很熟悉,且所有调用的子函数均已说明,所以就不再细说其流程了。
def initBlock(nextBlock=None): stRow, stCol = -2, 4 nowBlock = nextBlock if nextBlock else newBlock() nextBlock = newBlock() return stRow, stCol, nowBlock, nextBlock def main(): pygame.init() screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption('俄罗斯方块') # 初始化当前图形的位置,当前图形,以及下一个图形 stRow, stCol, nowBlock, nextBlock = initBlock() last_time = time.time() speed, speedInfo = 0.5, '1' score, gameOver = 0, False stopLst = deepcopy(STOP_LST) clock = pygame.time.Clock() while True: for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() elif event.type == pygame.KEYDOWN: if event.key in [pygame.K_LEFT, pygame.K_RIGHT]: stCol += moveLR(nowBlock, stCol, event.key) elif event.key == pygame.K_UP: rotBlock = rotateBlock(nowBlock) if canMove(rotBlock, stRow, stCol, stopLst): nowBlock = rotBlock elif event.key == pygame.K_DOWN: # 判断是否可以向下移动,如果碰到底部或者其它的图形就不能移动了 while judgeMoveDown(nowBlock, stRow + 1, stCol, stopLst): stRow += 1 if gameOver: stRow, stCol, nowBlock, nextBlock = initBlock() stopLst = deepcopy(STOP_LST) score = 0 gameOver = False # 判断是否修改当前图形显示的起始行 if not gameOver and time.time() - last_time > speed: last_time = time.time() # 可以向下移动的情形 if judgeMoveDown(nowBlock, stRow + 1, stCol, stopLst): stRow += 1 else: # 不可向下移动,则需新建一个block然后下落 addToStopLst(stopLst, nowBlock, stRow, stCol) score += judgeLines(stopLst) gameOver = 1 in stopLst[0] # 第一行中间有1则游戏结束 speedInfo, speed = changeSpeed(score) # 调整速度 stRow, stCol, nowBlock, nextBlock = initBlock(nextBlock) drawBackground(screen) drawRight(screen, score, speedInfo) drawBlocks(screen, nowBlock, stRow*SIZE, stCol*SIZE) # 当前方块 drawBlocks(screen, stopLst, 0, 0) # 堆积方块 drawBlocks(screen, nextBlock, 300, 320) # 下一个方块 # 显示游戏结束画面 if gameOver: drawGamerOver(screen) pygame.display.update() clock.tick(60) # 通过一定的延时,实现1秒钟能够循环60次
最终游戏效果如下
以上就是Pygame用200行代码实现俄罗斯方块的详细内容,更多关于Pygame俄罗斯方块的资料请关注脚本之家其它相关文章!