10. 俄罗斯方块游戏制作

10.1. 设计思路

  • 背景棋盘
    • 俄罗斯方块屏幕有两个区域,一个是游戏区域,一个是方块预览区域
    • 游戏区域用于下落方块进行堆积
    • 预览区域用于显示下一个要下落的方块类型
tetris
  • 界面
    • 将界面拆分成若干个的网格
    • 每个格是10*10的大小
    • 将预览窗口也同样拆分成网格
    • 游戏就是控制在不同的时机渲染不同的网格
tetris
  • 机制
    • 消除机制:当某行没有空的方块时,会消除这行,同时对这行以上的所有行进行移动,向下移动一行。
    • 失败条件:当第0行不为空时,则游戏结束。
tetris

10.2. 代码分析

  • 画背景网格
class Grid(object):
    def __init__(self, master=None, x=10, y=10, w=193, h=303):
        self.x = x
        self.y = y
        self.w = w
        self.h = h
        self.rows = h // 10
        self.cols = w // 10
        self.bg = 0x000000;
        print(self.rows, self.cols)

        # 画背景
        for i in range(320):
            screen.drawline(0, i, 239, i, 1, self.bg);

        # 画边界
        screen.drawline(x, y, x + w - 1, y, 1, 0xFFFFFF);
        screen.drawline(x + w - 1, y, x + w - 1, y + h, 1, 0xFFFFFF);
        screen.drawline(x, y + h, x + w - 1, y + h, 1, 0xFFFFFF);
        screen.drawline(x, y, x, y + h, 1, 0xFFFFFF);

        # 画提示框边界
        screen.drawline(204, 10, 204 + 32 - 1, 10, 1, 0xFFFFFF);
        screen.drawline(204 + 32 - 1, 10, 204 + 32 - 1, 10 + 32, 1, 0xFFFFFF);
        screen.drawline(204, 10 + 32, 204 + 32 - 1, 10 + 32, 1, 0xFFFFFF);
        screen.drawline(204, 10, 204, 10 + 32, 1, 0xFFFFFF);

    def drawgrid(self, pos, color):
        x = pos[1] * 10 + self.x + 2
        y = pos[0] * 10 + self.y + 2
        for i in range(9):
            screen.drawline(x, y + i, x + 9 - 1, y + i, 1, color);

    def drawpre(self, pos, color):
        x = pos[1] * 10 + 204 + 2
        y = pos[0] * 10 + 10 + 2
        for i in range(9):
            screen.drawline(x, y + i, x + 9 - 1, y + i, 1, color);
  • 方块种类
brick = [
    [
        [
            [1, 1, 1],
            [0, 0, 1],
            [0, 0, 0]
        ],
        [
            [0, 0, 1],
            [0, 0, 1],
            [0, 1, 1]
        ],
        [
            [0, 0, 0],
            [1, 0, 0],
            [1, 1, 1]
        ],
        [
            [1, 1, 0],
            [1, 0, 0],
            [1, 0, 0]
        ]
    ],
    [
        [
            [0, 0, 0],
            [0, 1, 1],
            [0, 1, 1]
        ],
        [
            [0, 0, 0],
            [0, 1, 1],
            [0, 1, 1]
        ],
        [
            [0, 0, 0],
            [0, 1, 1],
            [0, 1, 1]
        ],
        [
            [0, 0, 0],
            [0, 1, 1],
            [0, 1, 1]
        ]
    ],
    [
        [
            [1, 1, 1],
            [0, 1, 0],
            [0, 1, 0]
        ],
        [
            [0, 0, 1],
            [1, 1, 1],
            [0, 0, 1]
        ],
        [
            [0, 1, 0],
            [0, 1, 0],
            [1, 1, 1]
        ],
        [
            [1, 0, 0],
            [1, 1, 1],
            [1, 0, 0]
        ]
    ],
    [
        [
            [0, 1, 0],
            [0, 1, 0],
            [0, 1, 0]
        ],
        [
            [0, 0, 0],
            [1, 1, 1],
            [0, 0, 0]
        ],
        [
            [0, 1, 0],
            [0, 1, 0],
            [0, 1, 0]
        ],
        [
            [0, 0, 0],
            [1, 1, 1],
            [0, 0, 0]
        ]
    ]
]
  • 游戏类
class Game(Grid):
    def __init__(self):
        super().__init__()
        self.back = [[0 for i in range(0, self.cols)] for i in range(0, self.rows)]
        self.matrix_o = [[0 for i in range(0, self.cols)] for i in range(0, self.rows)]
        self.curRow = -10
        self.curCol = -10
        self.start = True
        self.shape = -1
        self.isDown = True
        self.oldrow = 0
        self.oldcol = 0
        # 当前有方块的开始行
        self.haverow = 29
        self.nextBrick = -1
        self.shape = 0
        self.arr = [[0 for i in range(0, 3)] for i in range(0, 3)]
        self.nextarr = [[0 for i in range(0, 3)] for i in range(0, 3)]
        # 使用一个字典将数字与其对应的颜色存放起来
        self.color = {0: 0x0000FF, 1: 0x00FF00, 2: 0xFF0000, 3: 0xFFFF00}
  • 绘制当前的下落方块
def drawBack(self, rownum):
    for i in range(self.haverow, rownum + 1):
        for j in range(0, self.cols):
            pos = (i, j)
            if self.back[i][j] == 0:
                self.drawgrid(pos, self.bg)
            else:
                self.drawgrid(pos, 0x00FFFF)
    self.haverow += 1
    if self.haverow >= self.rows:
        self.haverow = self.rows - 1

def drawRect(self):
    for i in range(0, len(self.nextarr)):
        for j in range(0, len(self.nextarr[i])):
            pos = (i, j)
            if self.nextarr[i][j] == 0:
                self.drawpre(pos, self.bg);
            elif self.nextarr[i][j] == 1:
                self.drawpre(pos, self.color[self.nextBrick])
    # print(self.oldrow, self.oldcol)
    # print(self.isDown)
    for i in range(0, 3):
        for j in range(0, 3):
            print("oldrow+i=", self.oldrow + i, self.oldcol + j)
            if ((self.oldrow + i) >= self.rows) or ((self.oldcol + j) >= self.cols) or ((self.oldcol + j) < -1):
                break
            if self.oldcol + j < 0:
                pos = (self.oldrow + i, 0)
            else:
                pos = (self.oldrow + i, self.oldcol + j)
            if self.back[self.oldrow + i][self.oldcol + j] == 0:
                self.drawgrid(pos, self.bg);
    # 绘制当前正在运动的方块
    # print(self.curRow,self.curCol)
    if (self.curRow != -10) and (self.curCol != -10):
        for i in range(0, len(self.arr)):
            for j in range(0, len(self.arr[i])):
                if self.arr[i][j] == 1:
                    pos = (self.curRow + i, self.curCol + j)
                    if self.isDown:
                        if i < self.haverow:
                            self.haverow = i
                        self.drawgrid(pos, 0x00FFFF)
                    else:
                        self.drawgrid(pos, self.color[self.curBrick])
    # 判断方块是否已经运动到达底部
    if self.isDown:
        for i in range(0, 3):
            for j in range(0, 3):
                if self.arr[i][j] != 0:
                    self.back[self.curRow + i][self.curCol + j] = self.arr[i][j]
        self.oldrow = 0
        self.oldcol = 0
        # 判断整行消除
        self.removeRow()

        self.isDead()
        # 获得下一个方块
        self.getCurBrick()
    else:
        self.oldrow = self.curRow
        self.oldcol = self.curCol
  • 行的消除
# 判断是否有整行需要消除
def removeRow(self):
    rownum = 0
    print("removeRow")
    for i in range(0, self.rows):
        tag1 = True
        for j in range(0, self.cols):
            if self.back[i][j] == 0:
                tag1 = False
                break
        if tag1 == True:
            print(i, j)
            rownum = i
            # 从上向下挪动
            for m in range(i - 1, 0, -1):
                for n in range(0, self.cols):
                    self.back[m + 1][n] = self.back[m][n]
    print(rownum)
    if rownum > 0:
        self.drawBack(rownum)
  • 已固定的方块的渲染
def drawBack(self, rownum):
    for i in range(self.haverow, rownum + 1):
        for j in range(0, self.cols):
            pos = (i, j)
            if self.back[i][j] == 0:
                self.drawgrid(pos, self.bg)
            else:
                self.drawgrid(pos, 0x00FFFF)
    self.haverow += 1
    if self.haverow >= self.rows:
        self.haverow = self.rows - 1

# 获得当前的方块
def getCurBrick(self):
    self.shape = 0
    if self.nextBrick == -1:
        self.curBrick = randint(0, len(brick) - 1)
        self.nextBrick = randint(0, len(brick) - 1)
    elif self.isDown:
        self.curBrick = self.nextBrick
        self.nextBrick = randint(0, len(brick) - 1)
    self.nextarr = brick[self.nextBrick][self.shape]
    # self.curBrick = 3
    # 当前方块数组
    self.arr = brick[self.curBrick][self.shape]
    # self.nextarr = self.arr
    self.curRow = -1
    self.curCol = 8
    # 是否到底部为False
    self.isDown = False
  • 按键控制
def onKeyboardEvent(self, key):
    keymatch = ["Down", "Left", "Up", "Right"]
    # 未开始,不必监听键盘输入
    if self.start == False:
        return
    # 记录原来的值
    tempCurCol = self.curCol
    tempCurRow = self.curRow
    tempShape = self.shape
    tempArr = self.arr
    direction = -1
    print(keymatch[key])
    if keymatch[key] == "Left":
        # 左移
        self.curCol -= 1
        direction = 1
    elif keymatch[key] == "Up":
        # 变化方块的形状
        self.shape += 1
        direction = 2
        if self.shape >= 4:
            self.shape = 0
        self.arr = brick[self.curBrick][self.shape]
    elif keymatch[key] == "Right":
        direction = 3
        # 右移
        self.curCol += 1
    elif keymatch[key] == "Down":
        direction = 4
        # 下移
        self.curRow += 2
    if self.isEdge(direction) == False:
        self.curCol = tempCurCol
        self.curRow = tempCurRow
        self.shape = tempShape
        self.arr = tempArr
    # self.drawRect()
    return True
  • 边界检测
# 判断当前方块是否到达边界
def isEdge(self, direction):
    tag = True
    # print(direction)
    # 向左,判断边界
    if direction == 1:
        for i in range(0, 3):
            for j in range(0, 3):
                if (self.arr[j][i] != 0) and (
                        self.curCol + i < 0 or self.back[self.curRow + j][self.curCol + i] != 0):
                    tag = False
                    break
                # 向右,判断边界
    elif direction == 3:
        for i in range(0, 3):
            for j in range(0, 3):
                if (self.arr[j][i] != 0) and (
                        self.curCol + i >= self.cols or self.back[self.curRow + j][self.curCol + i] != 0):
                    tag = False
                    break
    # 向下,判断底部
    elif direction == 4:
        for i in range(0, 3):
            for j in range(0, 3):
                if (self.arr[i][j] != 0) and (
                        self.curRow + i >= self.rows or self.back[self.curRow + i][self.curCol + j] != 0):
                    tag = False
                    self.isDown = True
                    break
    # 进行变形,判断边界
    elif direction == 2:
        if self.curCol < 0:
            self.curCol = 0
        if self.curCol + 2 >= self.cols:
            self.curCol = self.cols - 3
        if self.curRow + 2 >= self.rows:
            self.curRow = self.curRow - 3
    return tag
  • 边界检测
def isDead(self):
    for j in range(0, len(self.back[0])):
        if self.back[0][j] != 0:
            print("GAME OVER")
            text.draw("GAME OVER", 34, 150, 0xFF0000, 0x000000)
            self.start = False;
            break;
  • 主循环
# 方块向开始下移动
def brickStart(self):
    while True:
        # 需要进行垃圾回收
        gc.collect()

        if self.start == False:
            print("exit thread")
            break
        if self.isDown:
            self.getCurBrick()
        i = 0
        j = -1
        for k in keys:
            if k.value() == 0:
                if i != j:
                    print("i=", i)
                    print("j=", j)
                    j = i
                    self.onKeyboardEvent(i)
            i = i + 1
            if i > 3:
                i = 0

        tempRow = self.curRow;
        self.curRow += 1
        if self.isEdge(4) == False:
            self.curRow = tempRow
        # 每一秒下降一格
        time.sleep_ms(120)
        self.drawRect()