我以前用js寫過一個60行代碼的俄羅斯方塊,現在python比較流行,決定再寫一個python版本的。
遊戲算法與之前的基本一致,也是用二進制作爲點陣圖數據,用位運算的“與”來判斷方塊與場地是否重疊,用位運算的“或”來把方塊與場地合併。
這次重新優化了程序,並改良了之前不那麼優雅的語法。
本想用python編寫會超過60行,結果最終優化後的程序不算註釋和空行只有45行。
遊戲操作:上鍵是旋轉,左右下鍵是移動,空格鍵是暫停,遊戲得分顯示在標題欄。
由於是精簡版,沒有下一個方塊的提示,也沒有級別,方塊下落速度隨分數加快。
#-*- coding:utf-8 -*-
import random, tkinter, tkinter.messagebox
# 7種方塊圖案(以4*4的16位二進制點陣存儲)
tetris = (0x66,),(0x2222,0xf0),(0xc60,0x264),(0x6c0,0x462),(0x446,0x2e,0x622,0x74),(0x226,0xe2,0x644,0x470),(0x262,0x72,0x232,0x270)
# 遊戲場地數據 (以每行12位二進制點陣存儲,底線設置兩行是爲了防止計算時列表索引越界)
fie = [0x801]*25+[0x7fe]*2
# 遊戲運行需要的數據
p = {"pause":True,"lines":0}
# 將012字符映射成空格,實心方塊與空心方塊的轉換表
mak = str.maketrans("012","\u3000\u25a0\u25a1")
# 初始化新方塊函數
def init():
# 爲了方便在標題欄顯示分數
win.title("【分數:%d】俄羅斯方塊 作者:jslang" % (p["lines"]*100))
# 初始化新方塊的數據 dia是隨機選擇出的一種方塊圖案,r是方塊圖案旋轉的次數,x和y是方塊位置座標
p.update(dia=random.choice(tetris),r=random.randrange(4),y=0,x=8,pause=False)
# 動作函數 e是操作的屬性名,v是對屬性增減的數值
def act(e, v):
# 如果是暫停狀態則退出函數
if p["pause"]: return
# 對操作的屬性增減
p[e] += v
# 取得當前旋轉的方塊圖案
h = p['dia'][p['r']%len(p['dia'])]
# 將4*4的16位二進制點陣轉換成4行每行12位二進制點陣
f = {i:(h<<i*4&0xf000)>>p['x'] for i in range(4)}
# 用位運算的“與”來判斷方塊與場地是否重疊
if all(map(lambda i: f[i]&fie[p['y']+i]==0, f)):
# 將二進制數據轉換成方塊字符並顯示
box['text'] = '\n'.join(str(int(bin(fie[i])[2:])+int(bin(f.get(i-p['y'],0))[2:])*2)[1:-1] for i in range(4,25)).translate(mak)
return True
# 解決方塊處於左右邊緣無法旋轉的問題
if e!='r' or not(act('x',-1) or act('x',1) or act('x',2)):
# 對之前的屬性增減反向操作,等於撤銷之前的操作
p[e] -= v
# 判斷方塊已到底部
if e=='y':
for i in f:
# 用位運算的“或”來把方塊與場地合併
fie[p['y']+i] |= f[i]
# 如果一行已填滿,則刪除這一行並在列表頭添加新行
if fie[p['y']+i]==0xfff:
del fie[p['y']+i]
fie.insert(0,0x801)
p["lines"] += 1
# 判斷遊戲結束
if fie[3]!=0x801:
p.update(pause=True,lines=0)
fie[:25] = [0x801]*25
if not tkinter.messagebox.askyesno("遊戲結束","方塊到頂了,要重新再來一局嗎"):
win.quit()
init()
# 定時器函數
def timeout():
# 方塊下落一行
act('y',1)
# 間隔一定的時間再次調用本函數
win.after(300-p["lines"]*3,timeout)
# 創建遊戲窗口和組件
win = tkinter.Tk()
box = tkinter.Label(win,font=("\u5B8B\u4F53",28),fg="#fc9",bg="#024")
box.pack()
# 綁定鍵盤事件,上鍵是旋轉,左右下鍵是移動,空格鍵是暫停
win.bind('<Up>', lambda e: act('r',1))
win.bind('<Down>', lambda e: act('y',1))
win.bind('<Left>', lambda e: act('x',-1))
win.bind('<Right>', lambda e: act('x',1))
win.bind('<space>', lambda e: p.update(pause=not p["pause"]))
init()
timeout()
# 進入窗口消息循環
win.mainloop()