檔案建立日期: 2020/03/21
最後修訂日期: None
相關軟體資訊:
win10 | Python 3.7.6 | Numpy 1.18.1 | PySimpleGUI 4.16.0 |
說明: 本文請隨意引用或更改, 只須標示出處及作者, 作者不保證內容絶對正確無誤, 如造成任何後果, 請自行負責.
標題: Tetris 俄羅斯方塊遊戲
寫個遊戲玩玩….
目標
- 不同幾何形狀的碎片從頂部下降
- 在下降過程中,玩家可以橫向移動碎片並旋轉它們, 直到它們接觸到底部或降落在之前放置的棋子上
- 掉落的碎片可以加速, 一步一步, 或一次到底.
- 遊戲的目標是使用棋子水平連成完整的一條線, 當一條線完成時,它會消失了,放置在上方的方塊下降了一級.
- 完成線會授予積分, 並且累積一定數量的分數會使玩家上移一個級別.
- 碎片的速度會隨著每個級別的增加而增加.
- 一次清除越多行積分會越高.
- 四個小方塊組成的所有連線在一起的不同形狀碎片, 共有七種.
遊戲畫面
匯入的庫
import numpy as np import PySimpleGUI as sg import random
- 方塊的類建立
方塊圖形: 七種方塊以各小塊相對座標來定義.
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
- 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 協議》,轉載必須註明作者和本文連結