1. 程式人生 > 實用技巧 >寫的第一個俄羅斯方塊

寫的第一個俄羅斯方塊

#object: 用tkinter繪製俄羅斯方塊
#writer: mike
#time: 2020,09,04

import tkinter as tk
import random
#import pygame
from tkinter import messagebox
import time


window = tk.Tk()
window.geometry("500x600")
window.title("俄羅斯方塊")

#全域性變數
rows = 17
coloms = 10
cell_size = 30
FPS = 500
SHAPES = {

'o': [(-1,-1),(0,-1),(-1,0),(0
,0)], 's':[(-1,0),(0,0),(0,-1),(1,-1)], 't':[(-1,-1),(0,-1),(0,0,),(1,-1)], 'i':[(0,-2),(0,-1),(0,0),(0,1)], 'l':[(-1,-2),(0,0),(-1,-1),(-1,0)], 'j':[(-1,0),(0,0),(0,-1),(0,-2)], 'z':[(-1,-1),(0,-1),(0,0),(1,0)], } SHAPESCOLOR = { 'o': "blue", 's':"red", 't':"yellow", "i":"green", 'l':"purple", 'j':"orange", 'z':"Cyan", } CURRENT_BLOCK
= None SCORE = 0 #記錄已經生成的小方塊 #初始化為空的二維陣列 block_list = [] for i in range(rows): i_row = ["" for j in range(coloms)] block_list.append(i_row) #生成隨機的俄羅斯方塊 def generate_new_block(): kind = random.choice(list(SHAPES.keys())) new_block = { #注意這裡應該用地板除,否則產生的方塊座標就亂了 'cell_position
' : [(coloms//2), 0], 'cell_list' : SHAPES[kind], 'kind' : kind, } return new_block #在指定的行,指定的列,繪製指定顏色的一個方格 def draw_cell(canvas, x, y, color="#CCCCCC"): """ canvas: 畫布 r: 行數 c: 列數 color:顏色 """ #左上角座標 x_left = x*cell_size y_left = y*cell_size #右上角座標 x_right = x_left + cell_size y_right = y_left + cell_size #繪製矩形 canvas.create_rectangle(x_left, y_left, x_right, y_right, fill = color, outline = "white", width = 2) #繪製整個背景 def draw_whole_background(canvas,block): for i in range(rows): for j in range(coloms): #取出當前的陣列中的標記 cell_type = block[i][j] #如果當前陣列中有值,就按當前顏色來繪製,如果沒有值,就按背景色來繪製 if cell_type: draw_cell(canvas,j, i, SHAPESCOLOR[cell_type]) else: draw_cell(canvas, j, i ) #繪製特定的形狀 def draw_special_cell(canvas, r, c, cell_list, color = "#CCCCCC"): """ canvas: 畫布 r:行 c:列 cell_list: 關於(0,1)的具體的形狀 color: 具體的顏色 """ for cell in cell_list: #得到相加之後的小方格的行列 x, y = cell cell_x = r + x cell_y = c + y #依靠知覺,將colums與rows互換了位置 if cell_x < coloms and cell_y < rows and cell_x>= 0 and cell_y >= 0: draw_cell(canvas, cell_x, cell_y, color) #繪製移動的帶形狀對的方塊 #思路為:首先將原有方塊繪製成背景色,移動座標,繪製新顏色 #本函式position 的設定預設是不移動 def move_shape(canvas, block, position = [0,0]): """ canvas:畫布 block:包括了一個特定形狀的所有內容 position:移動的位置 """ shape_type = block['kind'] c, r = block['cell_position'] cell_list = block['cell_list'] #將原有形狀填充為背景色 draw_special_cell(canvas, c, r, cell_list) #預設就是背景色 #繪製新背景色 d_x, d_y = position new_x = c + d_x new_y = r + d_y #更改原有的字典 block['cell_position'] = [new_x, new_y] draw_special_cell(canvas, new_x, new_y, cell_list, SHAPESCOLOR[shape_type]) # 重新整理螢幕 def screen_loop(): #window.update() global CURRENT_BLOCK #如果當前沒有形狀,則生成一個形狀 if CURRENT_BLOCK is None: new_block = generate_new_block() move_shape(canvas, new_block) CURRENT_BLOCK = new_block #如果陣列中已經有標記了,也就是出生點已經被佔用了,那麼遊戲結束 if not check_move(CURRENT_BLOCK): messagebox.showinfo("GAME OVER ", "you final score is %d"%SCORE) window.destroy() return else: #如果小方格通過了邊界檢查,就向下移動 if check_move(CURRENT_BLOCK, [0,1]): #如果已經有一個形狀了,那麼就向下移動這個小方塊,直到這個小方塊落地。 move_shape(canvas, CURRENT_BLOCK, [0,1]) #如果小方塊超過了邊界,就停止移動,並且在最初的位置生成新的小方塊 else: #當小方塊無法移動時, 先將小方塊存進陣列,表示這幾個小方格已經被佔用了 save_block(CURRENT_BLOCK) CURRENT_BLOCK = None clear_row() #生成一個顯示分數的lable var = tk.StringVar() var.set("score is " + str(SCORE)) fen_label = tk.Label(window, textvariable = var, bg = '#FFBBBB', width = 20, height = 2) fen_label.place(x=330, y=150) #這條語句是不是有遞迴的意思,如果是,那就能解釋越來越慢的原因 print(len(block_list[3])) window.after(FPS, screen_loop) #檢查小方塊是否會超出邊界 #這裡position的預設值是0 def check_move(block, direction = [0,0]): #得到圓點的座標 x, y = block['cell_position'] #得到其他小格子的座標 qita = block['cell_list'] for cell in qita: cell_x, cell_y = cell #通過迴圈計算出各個格子的新座標 new_x = cell_x + x + direction[0] new_y = cell_y + y + direction[1] if new_x < 0 or new_x >= coloms or new_y >= rows: return False #如果小方格已經被標記了,那麼也不能移動了 #如果不見 new_y 大於0 , 那麼小方格會在上方不下來, 因為在列表中,如果行為負數,列表預設從導數第一個算起 #注意這裡如果是 new_y > 0, 依然會有已經滿行的,但是還在出生點生成小方格 if block_list[new_y][new_x] and new_y>=0: return False return True #記錄已經有的小方塊的函式 def save_block(block): block_type = block['kind'] hang, lie = block['cell_position'] c_list = block['cell_list'] #將每一個形狀的小方格紀錄進二維陣列 for cell in c_list: xx, yy = cell new_xx = hang + xx new_yy = lie + yy block_list[new_yy][new_xx] = block_type #左右移動小方塊 def left_right_move(event): direction = [0,0] #event 表示鍵盤上的事件 if event.keysym == "Left": direction = [-1,0] elif event.keysym == "Right": direction = [1,0] global CURRENT_BLOCK #如果當前block不為空,並且向下移動也沒越界,沒有標記,那麼向下移動 if CURRENT_BLOCK is not None and check_move(CURRENT_BLOCK, direction): move_shape(canvas, CURRENT_BLOCK, direction) #旋轉小方塊 def rotate_move(event): global CURRENT_BLOCK if CURRENT_BLOCK is None: return #計算旋轉的座標 yuan_list = CURRENT_BLOCK['cell_list'] rotate_list = [] for cell in yuan_list: cu_x, cu_y = cell #依據正餘旋函式公式,旋轉90度相當於,(x,y )變為(y,-x) rotate_xy = [cu_y, -cu_x] rotate_list.append(rotate_xy) #建立旋轉後的小方塊字典 after_rotate = { 'kind':CURRENT_BLOCK['kind'], 'cell_list':rotate_list, 'cell_position':CURRENT_BLOCK['cell_position'], } #如果可以變換,那麼清空原有的小方塊,並繪製旋轉後的小方塊 if check_move(after_rotate): #獲得當前的圓點座標 xx , yy = CURRENT_BLOCK['cell_position'] #清空當前的小方塊 draw_special_cell(canvas, xx, yy , CURRENT_BLOCK['cell_list']) #繪製旋轉後的小方塊 draw_special_cell(canvas, xx, yy, after_rotate['cell_list'], SHAPESCOLOR[CURRENT_BLOCK['kind']]) CURRENT_BLOCK = after_rotate #向下移動小方塊 def speed_down(event): global CURRENT_BLOCK if CURRENT_BLOCK is None: return #得到當前的小方塊的字典 cell_li = CURRENT_BLOCK['cell_list'] xxx, yyy = CURRENT_BLOCK['cell_position'] #初始化最小的深度 min_height = rows #得到每個形狀的各個小方格的座標 for cell in cell_li: cel_x, cel_y = cell new_x = xxx + cel_x new_y = yyy + cel_y #判斷當前的小方塊是否已經被佔用 if block_list[new_y][new_x]: return h = 0 #從上到下遍歷每一個小格子 for ri in range(new_y+1, rows): #如果下面的小格子已經被佔用了,就退出 if block_list[ri][new_x]: break else: #如果下面的小格子沒有衝突,那麼就加一 h += 1 #如果當前的可移動行數比當前的最小值小,就更新當前的最小值 if h < min_height: min_height = h down = [0,min_height] #直接定位到最下面的位置 if check_move(CURRENT_BLOCK, down): move_shape(canvas, CURRENT_BLOCK, down) #判斷指定行是否已經滿了 def check_row_complete(row): for cell in row: if cell == '': return False else: return True #清除已經完成的行 def clear_row(): #下面的變數用於檢查一行是否已經滿了 whether_complete = False for ri in range(len(block_list)): if check_row_complete(block_list[ri]): whether_complete = True #如果已經滿的行,大於第一行,那麼所有的當前行都等於上一行的內容 if ri > 0: #遍歷從當前行往上的所有行,當然除了第一行除外 for curr_r in range(ri,0,-1): block_list[curr_r] = block_list[curr_r-1] block_list[0] = ['' for j in range(coloms)] #對於第一行,直接賦值為空 else: block_list[ri] = ['' for j in range(coloms)] #如果發現有行已經被清除了,那麼重新繪製一遍所有的小方格 if whether_complete: draw_whole_background(canvas, block_list) global SCORE SCORE += 100 # 加入音樂 # pygame.mixer.init() # pygame.mixer.music.load(r"C:\users\mike1\desktop\123.mp3") # pygame.mixer.music.play(-1,0) #遊戲介面的寬與高 width = coloms*cell_size height = rows*cell_size #繪製遊戲介面 canvas = tk.Canvas(window, width = width, height = height) canvas.pack(side = 'left') #繪製基本的資訊 tk.Label(window, text = "writer: mike \ntime: 2020.09.04", bg = "#99FF33").place(x=350, y=50) draw_whole_background(canvas,block_list) #繫結鍵盤的左右鍵 canvas.focus_set() canvas.bind("<KeyPress-Left>", left_right_move) canvas.bind("<KeyPress-Right>", left_right_move) canvas.bind("<KeyPress-Up>", rotate_move) canvas.bind("<KeyPress-Down>", speed_down) #通過迴圈實現重新整理螢幕,不知上面的寫法是不是遞迴 # while 1: # screen_loop() # time.sleep(0.3) screen_loop() window.mainloop()

以上的俄羅斯方塊,執行到一定時間之後會變的很慢,難道是因為,用到了遞迴嗎,我並沒有用到遞迴啊, 並不是很清楚?