Tetris 俄羅斯方塊遊戲

Jason990420發表於2020-03-21

檔案建立日期: 2020/03/21
最後修訂日期: None
相關軟體資訊:

win10 Python 3.7.6 Numpy 1.18.1 PySimpleGUI 4.16.0

說明: 本文請隨意引用或更改, 只須標示出處及作者, 作者不保證內容絶對正確無誤, 如造成任何後果, 請自行負責.

標題: Tetris 俄羅斯方塊遊戲

寫個遊戲玩玩….

目標

  1. 不同幾何形狀的碎片從頂部下降
  2. 在下降過程中,玩家可以橫向移動碎片並旋轉它們, 直到它們接觸到底部或降落在之前放置的棋子上
  3. 掉落的碎片可以加速, 一步一步, 或一次到底.
  4. 遊戲的目標是使用棋子水平連成完整的一條線, 當一條線完成時,它會消失了,放置在上方的方塊下降了一級.
  5. 完成線會授予積分, 並且累積一定數量的分數會使玩家上移一個級別.
  6. 碎片的速度會隨著每個級別的增加而增加.
  7. 一次清除越多行積分會越高.
  8. 四個小方塊組成的所有連線在一起的不同形狀碎片, 共有七種.

遊戲畫面

Tetris 俄羅斯方塊遊戲

  1. 匯入的庫

    import numpy as np
    import PySimpleGUI as sg
    import random
  1. 方塊的類建立
  • 方塊圖形: 七種方塊以各小塊相對座標來定義.

    class Game():
    
        def __init__(self):
            self.pixel  = np.array(
            [[[[0,0], [0,1], [0,2], [0,3]], [[0,0], [1,0], [2,0], [3,0]],  # ____
              [[0,0], [0,1], [0,2], [0,3]], [[0,0], [1,0], [2,0], [3,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]], [[0,0], [0,1], [1,0], [1,1]]],
             [[[0,1], [1,1], [2,1], [2,0]], [[0,0], [1,0], [1,1], [1,2]],  # ▁▁│
              [[0,1], [0,0], [1,0], [2,0]], [[0,0], [0,1], [0,2], [1,2]]],
             [[[0,0], [0,1], [1,1], [2,1]], [[1,0], [0,0], [0,1], [0,2]],  # │▁▁
              [[0,0], [1,0], [2,0], [2,1]], [[1,0], [1,1], [1,2], [0,2]]],
             [[[0,1], [1,1], [2,1], [1,0]], [[0,0], [0,1], [0,2], [1,1]],  # ▁│▁
              [[0,0], [1,0], [2,0], [1,1]], [[1,0], [1,1], [1,2], [0,1]]],
             [[[0,0], [1,0], [1,1], [2,1]], [[1,0], [1,1], [0,1], [0,2]], # ▔│▁
              [[0,0], [1,0], [1,1], [2,1]], [[1,0], [1,1], [0,1], [0,2]]],
             [[[0,1], [1,1], [1,0], [2,0]], [[0,0], [0,1], [1,1], [1,2]],  # ▁│▔
              [[0,1], [1,1], [1,0], [2,0]], [[0,0], [0,1], [1,1], [1,2]]]])
  • 遊戲旗標及引數

    包含格數的度及高度, 方塊的種類數目, 遊戲起始速度, 方塊起始位置, 方塊消去分數, 遊戲狀態(遊戲結束, 暫停), 方塊是否存在等等旗標及引數.

    其中字型大小主要用來定義方塊的大小, 因為每個小方塊都是文字框, 只是背景色不一樣.

            self.width, self.height = 10, 20
            self.start_x, self.start_y = 4, 0
            self.kind,self.axis, self.timer = 7, 0, 100
    
            self.font, self.background = '微軟正黑體 24', 'gray'
            self.pause, self.stop, self.no_blcok = False, True, True
    
            self.block = []
    
            self.lines, self.score, self.level, self.count = 0, 0, 0, 0
            self.plus = [0, 100, 200, 400, 800]
  • 遊戲記錄引數

    記錄每個位置是否被佔用及其顏色, 定義每種方塊的頻色, 每種方塊出現的機率, 以及按鍵和按鈕的對應函式.

            self.area = np.zeros((self.height, self.width))
            self.cont = np.full((self.height, self.width), 7)
            self.rate = [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 5, 6, 6, 6]
            self.colors = ['gold', 'blue', 'tomato', 'red', 'green', 'purple',
                           'brown', self.background]
            self.func = {'New'      :self.new,   'Pause'  :self.wait,
                         'Left:37'  :self.left,  'Up:38'  :self.rotate,
                         'Right:39' :self.right, 'Down:40':self.down,
                         'Escape:27':self.wait,  ' '      :self.space}
  • 遊戲類的方法及函式

    • 遊戲起始的展示畫面, 七種方塊旋轉動態展示.
        def blocks(self):
            self.count += 1
            if self.count == 50:
                self.count = 0
                old_axis = self.axis-1 if self.axis>0 else 3
                for kind in range(self.kind):
                    self.block = [kind, old_axis, (kind%2)*5+1, (kind//2)*5+1]
                    self.draw(show=False)
                    self.block = [kind, self.axis, (kind%2)*5+1, (kind//2)*5+1]
                    self.draw()
                self.axis = self.axis+1 if self.axis<3 else 0
    • 檢查方塊連線

      如果每一列的佔用總數等於遊戲畫面的寛度, 代表一連線, 每一連線刪除並下移上方所有的畫面.

      按消去的列總數, 取得分數, 更新總分及遊戲等級, 並調整方塊自動下落的速度.

        def check(self):
            count = 0
            if not self.no_block: self.draw(show=False)
            for y in range(self.height-1, -1, -1):
                if np.sum(self.area[y]) == self.width:
                    count += 1
                    self.area[1:y+1], self.area[0] = self.area[0:y], 0
                    self.cont[1:y+1], self.cont[0] = self.cont[0:y], 7
                    for z in range(0, y+1):
                        for x in range(self.width):
                            window.find_element(str(x+10*z)).Update(
                                background_color=self.colors[self.cont[z, x]])
            if not self.no_block: self.draw()
            self.score += min(self.plus[count], 999999)
            self.lines += count
            self.level = int(self.lines//2)
            self.timer = int(100-self.level) if self.level<100 else 1
            window.find_element('Score').Update(value='{:0>6d}'.format(self.score))
            window.find_element('Level').Update(value='{:0>2d}'.format(self.level))
    • 清除畫面

      更改所有格子的被佔用記錄為0, 設定所有格子的顏色為遊戲背景色

        def clear(self):
            self.area = np.zeros((self.height, self.width))
            for i in range(self.width*self.height):
                window.find_element(str(i)).Update(
                    background_color=self.background)
    • 方塊下降一格

      先取得下降一格的位置, 再認該位置是否沒被佔用或出界, 如果被佔用或出界, 不可下移, 而且設定該方塊不再存在, 並設定該方塊所有的格子位置被佔用.

        def down(self):
            kind, axis, x, y = self.block
            new_block = [kind, axis, x, y+1]
            if self.ok(new_block):
                self.block = new_block
                return True
            else:
                self.no_block = True
                data = self.get_font(self.block)+self.block[2:]
                xi, yi = data[:,0], data[:,1]
                self.area[yi, xi] = 1
                return False
    • 顯示方塊或不顯示方塊

      以改變文字框的方式來達成

        def draw(self, show=True):
            color = self.colors[self.block[0]] if show else self.background
            for x, y in self.get_font(self.block)+self.block[2:]:
                window.find_element(str(x+10*y)).Update(background_color=color)
                self.cont[y, x] = self.colors.index(color)
    • 取得方塊的圖形資料
        def get_font(self, block):
            return self.pixel[block[0], block[1]]
    • 左鍵左移處理

      先取得左移一格的位置, 再認該位置是否沒被佔用或出界, 如果被佔用或出界, 不可左移

        def left(self):
            kind, axis, x, y = self.block
            new_block = [kind, axis, x-1 if x>0 else x, y]
            if self.ok(new_block):
                self.block = new_block
                return True
            return False
    • 建立新的方塊

      新的方塊如果被佔用, 遊戲結束

        def new_block(self):
            self.block = [self.random(), 0, int(self.width/2-1), 0]
            self.draw(self.block)
            if self.ok(self.block):
                self.no_block = False
                self.count = 0
            else:
                self.stop = True
                sg.popup("Game Over", no_titlebar=True, font=self.font)
    • 遊戲開始
        def new(self):
            self.pause = False
            self.no_block = True
            self.stop = False
            self.clear()
    • 調整方塊的位置計算函式
        def offset(self, x_limit, x_max, x_min):
            return x_max-x_limit+1 if x_max>x_limit-1 else x_min if x_min<0 else 0
    • 檢查方塊的位置是否出界或被佔用
        def ok(self, block):
            if block[3] >= self.height:
                return False
            data = self.get_font(block)+block[2:]
            if not(np.max(data[:,0])<self.width  and np.min(data[:,0])>-1 and
                   np.max(data[:,1])<self.height and np.min(data[:,1])>-1):
                return False
            x, y = data[:,0], data[:,1]
            if np.sum(self.area[y, x]) != 0:
                return False
            return True
    • 按提供的樣品空間, 隨機選擇方塊的類別

      該空間類別出現的次數越多, 在遊戲中出現的機率越高

        def random(self):
            return random.choice(self.rate)
    • 右鍵右移處理

      先取得右移一格的位置, 再認該位置是否沒被佔用或出界, 如果被佔用或出界, 不可右移

        def right(self):
            kind, axis, x, y = self.block
            new_block = [kind, axis, x+1 if x<self.width-1 else x, y]
            if self.ok(new_block):
                self.block = new_block
                return True
            return False
    • 上鍵旋轉處理

      旋轉時, 必須考慮方塊是否會出界或被佔用, 所以必須以旋轉的方塊先調整位置, 再確認是否可移到該位置.

        def rotate(self):
            kind, axis, x, y = self.block
            new_block = [kind, (axis+1)%4, x, y]
            data=self.get_font(new_block)+[x, y]
            x_max, x_min = np.max(data[:,0]), np.min(data[:,0])
            y_max, y_min = np.max(data[:,1]), np.min(data[:,1])
            x_offset = self.offset(self.width, x_max, x_min)
            y_offset = self.offset(self.height, y_max, y_min)
            new_block[2:]= [x-x_offset, y-y_offset]
            if self.ok(new_block):
                self.block = new_block
                return True
            return False
    • 空格鍵快速下落

      連續一步一步的下移, 直到會出界或被佔用.

        def space(self):
            while True:
                self.draw(show=False)
                stop = not self.down()
                self.draw()
                if stop:
                    break
    • 動作前後更新方塊
        def update(self, func):
            self.draw(show=False)
            func()
            self.draw()
    • 暫停按鈕或按鍵處理
        def wait(self):
            self.pause = not self.pause
  1. GUI 介面
  • 方塊文字框

    def T(key, text=None, color='white', size=(None, None)):
      if text == None:
          text = ' '*5
      return sg.Text(text, key=key, pad=(1,1), size=size, justification='center',
          font=G.font, background_color=G.background, text_color=color)
  • 一般文字框

    def M(text, bg='green'):
      return sg.Text(text, font=G.font, size=(10, 1), justification='center',
          background_color=bg, text_color='white')
  • 按鈕物件

    def B(text, key):
      return sg.Button(text, font=G.font, size=(10, 1), key=key,
          bind_return_key=False, focus=False)
  • 遊戲類例項化

    G = Game()
  • 遊戲區配置

    layout1 = [[T(str(i+j*G.width), color=G.background) for i in range(G.width)]
                for j in range(G.height)]
  • 訊息區配置(分數, 等級, 遊戲說明, 按鍵)

    layout2 = [[M('分數')], [T('Score', text='000000', size=(10, 1))],
             [M('', bg=G.background)],
             [M('等級')], [T('Level', text='00', size=(10, 1))],
             [M('', bg=G.background)],
             [M('左鍵:左移')], [M('右鍵:右移')], [M('下鍵:下移')],
             [M('上鍵:旋轉')], [M('空格鍵:落下')], [M('ESC鍵:暫停')],
             [M('', bg=G.background)], [M(' ', bg=G.background)],
             [B('遊戲開始', 'New')], [B('遊戲暫停', 'Pause')],
             [B('遊戲結束', 'Over')]]
  • 整體遊戲 GUI 配置

    frame1 = sg.Frame('', layout=layout1, background_color=G.background,
                    border_width=5)
    frame2 = sg.Frame('', layout=layout2, background_color=G.background,
                   border_width=5, size=(None, G.height*32))
    layout = [[frame1, frame2]]
    window = sg.Window('Tetris 俄羅斯方塊', layout=layout, finalize=True,
                     use_default_focus=False, return_keyboard_events=True,
                     background_color=G.background)
  • 事件處理及遊戲迴圈處理

    blocks = True
    while True:
    
      event, values = window.read(timeout=10)
    
      if event in [None, 'Over']:
          break
      elif event in ['New', 'Pause', 'Escape:27']:
          blocks = False
          G.func[event]()
          continue
      if blocks:
          G.blocks()
          continue
      if G.pause or G.stop:
          continue
      if G.no_block:
          G.new_block()
      else:
          if event in G.func:
              G.update(G.func[event])
          G.count += 1
          if G.count == G.timer:
              G.count = 0
              G.update(G.down)
      G.check()
    
    window.close()
本作品採用《CC 協議》,轉載必須註明作者和本文連結
Jason Yang

相關文章