具有基本的數據合併以及分數統計,不一樣數字的色塊不一樣python
產生隨機數, 數據沒法合併斷定以及從新開始選項算法
同時能夠斷定遊戲失敗條件數據結構
建立一個基本的數據結構地圖數據來保存各位置的數值app
_map_data = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
]
利用 tkinter 經過遍歷地圖數據來循環建立標籤dom
建立時利用 tkinter 設置樣式以及顏色函數
一樣維護一個列表來填入每行的標籤oop
map_labels = [] for r in range(4): row = [] for c in range(len(_map_data[0])): value = _map_data[r][c] text = str(value) if value else '' label = Label(frame, text=text, width=4, height=2, font=("黑體", 30, "bold")) label.grid(row=r, column=c, padx=5, pady=5, sticky=N + E + W + S) row.append(label) map_labels.append(row)
不一樣數值的色塊以不一樣的顏色標識佈局
# 設置遊戲中每一個數據對應色塊的顏色 mapcolor = { 0: ("#cdc1b4", "#776e65"), 2: ("#eee4da", "#776e65"), 4: ("#ede0c8", "#f9f6f2"), 8: ("#f2b179", "#f9f6f2"), 16: ("#f59563", "#f9f6f2"), 32: ("#f67c5f", "#f9f6f2"), 64: ("#f65e3b", "#f9f6f2"), 128: ("#edcf72", "#f9f6f2"), 256: ("#edcc61", "#f9f6f2"), 512: ("#e4c02a", "#f9f6f2"), 1024: ("#e2ba13", "#f9f6f2"), 2048: ("#ecc400", "#f9f6f2"), 4096: ("#ae84a8", "#f9f6f2"), 8192: ("#b06ca8", "#f9f6f2"), # ----其它顏色都與8192相同--------- 2 ** 14: ("#b06ca8", "#f9f6f2"), 2 ** 15: ("#b06ca8", "#f9f6f2"), 2 ** 16: ("#b06ca8", "#f9f6f2"), 2 ** 17: ("#b06ca8", "#f9f6f2"), 2 ** 18: ("#b06ca8", "#f9f6f2"), 2 ** 19: ("#b06ca8", "#f9f6f2"), 2 ** 20: ("#b06ca8", "#f9f6f2"), }
建立兩個標籤分別標識分數 , 以及數字ui
label = Label(frame, text='分數', font=("黑體", 30, "bold"), bg="#bbada0", fg="#eee4da") label.grid(row=4, column=0, padx=5, pady=5) label_score = Label(frame, text='0', font=("黑體", 30, "bold"), bg="#bbada0", fg="#ffffff") label_score.grid(row=4, columnspan=2, column=1, padx=5, pady=5)
重置按鈕須要作到將遊戲重置spa
即地圖數據還原以及分數重置
此部分須要設計相關 函數來負責重置以及刷新界面
def reset(): '''從新設置遊戲數據,將地圖恢復爲初始狀態,並加入兩個數據 2 做用初始狀態''' _map_data[:] = [] # _map_data.clear() _map_data.append([0, 0, 0, 0]) _map_data.append([0, 0, 0, 0]) _map_data.append([0, 0, 0, 0]) _map_data.append([0, 0, 0, 0]) # 在空白地圖上填充兩個2 fill2() fill2()
def reset_game(): reset() update_ui() restart_button = Button(frame, text='從新開始', font=("黑體", 16, "bold"), bg="#8f7a66", fg="#f9f6f2", command=reset_game) restart_button.grid(row=4, column=3, padx=5, pady=5)
移動的邏輯分爲兩步
移動數字和合並數字
可是合併數字後又會發現存在空格, 所以須要第三步
def _left_move_number(line): '''左移一行數字,若是有數據移動則返回True,不然返回False: 如: line = [0, 2, 0, 8] 即表達以下一行: +---+---+---+---+ | 0 | 2 | 0 | 8 | <----向左移動 +---+---+---+---+ 此行數據須要左移三次: 第一次左移結果: +---+---+---+---+ | 2 | 0 | 8 | 0 | +---+---+---+---+ 第二次左移結果: +---+---+---+---+ | 2 | 8 | 0 | 0 | +---+---+---+---+ 第三次左移結果: +---+---+---+---+ | 2 | 8 | 0 | 0 | # 由於最左則爲2,因此8不動 +---+---+---+---+ 最終結果: line = [4, 8, 0, 0] ''' moveflag = False # 是否移動的標識,先假設沒有移動 for _ in range(3): # 重複執行下面算法三次 for i in range(3): # i爲索引 if 0 == line[i]: # 此處有空位,右側相鄰數字向左側移動,右側填空白 moveflag = True line[i] = line[i + 1] line[i + 1] = 0 return moveflag def _left_marge_number(line): '''向左側進行相同單元格合併,合併結果放在左側,右側補零 如: line = [2, 2, 4, 4] 即表達以下一行: +---+---+---+---+ | 2 | 2 | 4 | 4 | +---+---+---+---+ 全並後的結果爲: +---+---+---+---+ | 4 | 0 | 8 | 0 | +---+---+---+---+ 最終結果: line = [4, 8, 8, 0] ''' for i in range(3): if line[i] == line[i + 1]: moveflag = True line[i] *= 2 # 左側翻倍 line[i + 1] = 0 # 右側歸零 def _left_move_aline(line): '''左移一行數據,若是有數據移動則返回True,不然返回False: 如: line = [2, 0, 2, 8] 即表達以下一行: +---+---+---+---+ | 2 | | 2 | 8 | <----向左移動 +---+---+---+---+ 左移算法分爲三步: 1. 將全部數字向左移動來填補左側空格,即: +---+---+---+---+ | 2 | 2 | 8 | | +---+---+---+---+ 2. 判斷是否發生碰幢,若是兩個相臨且相等的數值則說明有碰撞須要合併, 合併結果靠左,右則填充空格 +---+---+---+---+ | 4 | | 8 | | +---+---+---+---+ 3. 再重複第一步,將全部數字向左移動來填補左側空格,即: +---+---+---+---+ | 4 | 8 | | | +---+---+---+---+ 最終結果: line = [4, 8, 0, 0] ''' moveflag = False if _left_move_number(line): moveflag = True if _left_marge_number(line): moveflag = True if _left_move_number(line): moveflag = True return moveflag
基本實現了一個就能夠所有實現了
本質自己就是列表 , 翻轉方向的就翻轉列表便可. 可是記得要再轉回來
上下的列表就是4個列表的同索引位置從新拼接列表.
一樣翻轉後在翻轉
def left(): """遊戲左鍵按下時或向左滑動屏幕時的算法""" moveflag = False # moveflag 是否成功移動數字標誌位,若是有移動則爲真值,原地圖不變則爲假值 # 將第一行都向左移動.若是有移動就返回True for line in _map_data: if _left_move_aline(line): moveflag = True return moveflag def right(): """遊戲右鍵按下時或向右滑動屏幕時的算法 選將屏幕進行左右對調,對調後,原來的向右滑動即爲如今的向左滑動 滑動完畢後,再次左右對調回來 """ # 左右對調 for r in _map_data: r.reverse() moveflag = left() # 向左滑動 # 再次左右對調 for r in _map_data: r.reverse() return moveflag def up(): """遊戲上鍵按下時或向上滑動屏幕時的算法 先把每一列都自上而下放入一個列表中line中,而後執行向滑動, 滑動完成後再將新位置擺回到原來的一列中 """ moveflag = False line = [0, 0, 0, 0] # 先初始化一行,準備放入數據 for col in range(4): # 先取出每一列 # 把一列中的每一行數入放入到line中 for row in range(4): line[row] = _map_data[row][col] # 將當前列進行上移,即line 左移 if (_left_move_aline(line)): moveflag = True # 把左移後的 line中的數據填充回原來的一列 for row in range(4): _map_data[row][col] = line[row] return moveflag def down(): """遊戲下鍵按下時或向下滑動屏幕時的算法 選將屏幕進行上下對調,對調後,原來的向下滑動即爲如今的向上滑動 滑動完畢後,再次上下對調回來 """ _map_data.reverse() moveflag = up() # 上滑 _map_data.reverse() return moveflag
def is_gameover(): """判斷遊戲是否結束,若是結束返回True,否是返回False """ for r in _map_data: # 若是水平方向還有0,則遊戲沒有結束 if r.count(0): return False # 水平方向若是有兩個相鄰的元素相同,應當是能夠合併的,則遊戲沒有結束 for i in range(3): if r[i] == r[i + 1]: return False for c in range(4): # 豎直方向若是有兩個相鄰的元素相同,應當能夠合併的,則遊戲沒有結束 for r in range(3): if _map_data[r][c] == _map_data[r + 1][c]: return False # 以上都沒有,則遊戲結束 return True
def get_score(): '''獲取遊戲的分數,得分規則是每次有兩個數加在一塊兒則生成相應的分數。 如 2 和 2 合併後得4分, 8 和 8 分並後得 16分. 根據一個大於2的數字就能夠知道他共合併了多少次,能夠直接算出分數: 如: 4 必定由兩個2合併,得4分 8 必定由兩個4合併,則計:8 + 4 + 4 得32分 ... 以此類推 ''' score = 0 for r in _map_data: for c in r: score += 0 if c < 4 else c * int((math.log(c, 2) - 1.0)) return score # 導入數學模塊
隨機數的添加形式爲 添加一個 2 到任意一個爲 0 的位置
先進行一個 0 位置的數量統計
def get_space_count(): """獲取沒有數字的方格的數量,若是數量爲0則說有沒法填充新數據,遊戲即將結束 """ count = 0 for r in _map_data: count += r.count(0) return count
利用定義偏移量來添加, 隨機 0~0位置統計個數, 而後選一個後循環+1偏移量到被選到數字進行復製爲 2
def fill2(): '''填充2到空位置,若是填度成功返回True,若是已滿,則返回False''' blank_count = get_space_count() # 獲得地圖上空白位置的個數 if 0 == blank_count: return False # 生成隨機位置, 如,當只有四個空時,則生成0~3的數,表明自左至右,自上而下的空位置 pos = random.randrange(0, blank_count) offset = 0 for row in _map_data: # row爲行row for col in range(4): # col 爲列,column if 0 == row[col]: if offset == pos: # 把2填充到第row行,第col列的位置,返回True row[col] = 2 return True offset += 1
keymap = { 'a': left, 'd': right, 'w': up, 's': down, 'Left': left, 'Right': right, 'Up': up, 'Down': down, 'q': root.quit, }
def on_key_down(event): '鍵盤按下處理函數' keysym = event.keysym if keysym in keymap: if keymap[keysym](): # 若是有數字移動 fill2() # 填充一個新的2 update_ui() if is_gameover(): mb = messagebox.askyesno( title="gameover", message="遊戲結束!\n是否退出遊戲!") if mb: root.quit() else: reset() update_ui()
# 設置焦點能接收按鍵事件 frame.focus_set() frame.bind("<Key>", on_key_down)
def update_ui(): '''刷新界面函數 根據計算出的f地圖數據,更新各個Label的設置 ''' for r in range(4): for c in range(len(_map_data[0])): number = _map_data[r][c] # 設置數字 label = map_labels[r][c] # 選中Lable控件 label['text'] = str(number) if number else '' label['bg'] = mapcolor[number][0] label['foreground'] = mapcolor[number][1] label_score['text'] = str(get_score()) # 重設置分數
"""2048遊戲 本模塊已完整實現2048遊戲的算法及分數的計算算法 本遊戲的界面採用python 標準庫 tkinter 來實現 此界面的佈局採用tkinter中的grid佈局 """ import random import math import sys _map_data = [ [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ] # -------------------------如下爲2048遊戲的基本算法--------------------------- # 重置 def reset(): '''從新設置遊戲數據,將地圖恢復爲初始狀態,並加入兩個數據 2 做用初始狀態''' _map_data[:] = [] # _map_data.clear() _map_data.append([0, 0, 0, 0]) _map_data.append([0, 0, 0, 0]) _map_data.append([0, 0, 0, 0]) _map_data.append([0, 0, 0, 0]) # 在空白地圖上填充兩個2 fill2() fill2() # 獲取 0 個數 def get_space_count(): """獲取沒有數字的方格的數量,若是數量爲0則說有沒法填充新數據,遊戲即將結束 """ count = 0 for r in _map_data: count += r.count(0) return count # 計算分數 def get_score(): '''獲取遊戲的分數,得分規則是每次有兩個數加在一塊兒則生成相應的分數。 如 2 和 2 合併後得4分, 8 和 8 分並後得 16分. 根據一個大於2的數字就能夠知道他共合併了多少次,能夠直接算出分數: 如: 4 必定由兩個2合併,得4分 8 必定由兩個4合併,則計:8 + 4 + 4 得32分 ... 以此類推 ''' score = 0 for r in _map_data: for c in r: score += 0 if c < 4 else c * int((math.log(c, 2) - 1.0)) return score # 導入數學模塊 # 隨機數生成 def fill2(): '''填充2到空位置,若是填度成功返回True,若是已滿,則返回False''' blank_count = get_space_count() # 獲得地圖上空白位置的個數 if 0 == blank_count: return False # 生成隨機位置, 如,當只有四個空時,則生成0~3的數,表明自左至右,自上而下的空位置 pos = random.randrange(0, blank_count) offset = 0 for row in _map_data: # row爲行row for col in range(4): # col 爲列,column if 0 == row[col]: if offset == pos: # 把2填充到第row行,第col列的位置,返回True row[col] = 2 return True offset += 1 # 結束斷定 def is_gameover(): """判斷遊戲是否結束,若是結束返回True,否是返回False """ for r in _map_data: # 若是水平方向還有0,則遊戲沒有結束 if r.count(0): return False # 水平方向若是有兩個相鄰的元素相同,應當是能夠合併的,則遊戲沒有結束 for i in range(3): if r[i] == r[i + 1]: return False for c in range(4): # 豎直方向若是有兩個相鄰的元素相同,應當能夠合併的,則遊戲沒有結束 for r in range(3): if _map_data[r][c] == _map_data[r + 1][c]: return False # 以上都沒有,則遊戲結束 return True # 移動合併分數 def _left_move_number(line): '''左移一行數字,若是有數據移動則返回True,不然返回False: 如: line = [0, 2, 0, 8] 即表達以下一行: +---+---+---+---+ | 0 | 2 | 0 | 8 | <----向左移動 +---+---+---+---+ 此行數據須要左移三次: 第一次左移結果: +---+---+---+---+ | 2 | 0 | 8 | 0 | +---+---+---+---+ 第二次左移結果: +---+---+---+---+ | 2 | 8 | 0 | 0 | +---+---+---+---+ 第三次左移結果: +---+---+---+---+ | 2 | 8 | 0 | 0 | # 由於最左則爲2,因此8不動 +---+---+---+---+ 最終結果: line = [4, 8, 0, 0] ''' moveflag = False # 是否移動的標識,先假設沒有移動 for _ in range(3): # 重複執行下面算法三次 for i in range(3): # i爲索引 if 0 == line[i]: # 此處有空位,右側相鄰數字向左側移動,右側填空白 moveflag = True line[i] = line[i + 1] line[i + 1] = 0 return moveflag # 移動位置 def _left_marge_number(line): '''向左側進行相同單元格合併,合併結果放在左側,右側補零 如: line = [2, 2, 4, 4] 即表達以下一行: +---+---+---+---+ | 2 | 2 | 4 | 4 | +---+---+---+---+ 全並後的結果爲: +---+---+---+---+ | 4 | 0 | 8 | 0 | +---+---+---+---+ 最終結果: line = [4, 8, 8, 0] ''' for i in range(3): if line[i] == line[i + 1]: moveflag = True line[i] *= 2 # 左側翻倍 line[i + 1] = 0 # 右側歸零 # 移動邏輯 def _left_move_aline(line): '''左移一行數據,若是有數據移動則返回True,不然返回False: 如: line = [2, 0, 2, 8] 即表達以下一行: +---+---+---+---+ | 2 | | 2 | 8 | <----向左移動 +---+---+---+---+ 左移算法分爲三步: 1. 將全部數字向左移動來填補左側空格,即: +---+---+---+---+ | 2 | 2 | 8 | | +---+---+---+---+ 2. 判斷是否發生碰幢,若是兩個相臨且相等的數值則說明有碰撞須要合併, 合併結果靠左,右則填充空格 +---+---+---+---+ | 4 | | 8 | | +---+---+---+---+ 3. 再重複第一步,將全部數字向左移動來填補左側空格,即: +---+---+---+---+ | 4 | 8 | | | +---+---+---+---+ 最終結果: line = [4, 8, 0, 0] ''' moveflag = False if _left_move_number(line): moveflag = True if _left_marge_number(line): moveflag = True if _left_move_number(line): moveflag = True return moveflag def left(): """遊戲左鍵按下時或向左滑動屏幕時的算法""" moveflag = False # moveflag 是否成功移動數字標誌位,若是有移動則爲真值,原地圖不變則爲假值 # 將第一行都向左移動.若是有移動就返回True for line in _map_data: if _left_move_aline(line): moveflag = True return moveflag def right(): """遊戲右鍵按下時或向右滑動屏幕時的算法 選將屏幕進行左右對調,對調後,原來的向右滑動即爲如今的向左滑動 滑動完畢後,再次左右對調回來 """ # 左右對調 for r in _map_data: r.reverse() moveflag = left() # 向左滑動 # 再次左右對調 for r in _map_data: r.reverse() return moveflag def up(): """遊戲上鍵按下時或向上滑動屏幕時的算法 先把每一列都自上而下放入一個列表中line中,而後執行向滑動, 滑動完成後再將新位置擺回到原來的一列中 """ moveflag = False line = [0, 0, 0, 0] # 先初始化一行,準備放入數據 for col in range(4): # 先取出每一列 # 把一列中的每一行數入放入到line中 for row in range(4): line[row] = _map_data[row][col] # 將當前列進行上移,即line 左移 if (_left_move_aline(line)): moveflag = True # 把左移後的 line中的數據填充回原來的一列 for row in range(4): _map_data[row][col] = line[row] return moveflag def down(): """遊戲下鍵按下時或向下滑動屏幕時的算法 選將屏幕進行上下對調,對調後,原來的向下滑動即爲如今的向上滑動 滑動完畢後,再次上下對調回來 """ _map_data.reverse() moveflag = up() # 上滑 _map_data.reverse() return moveflag # -------------------------如下爲2048遊戲的操做界面--------------------------- if (sys.version_info > (3, 0)): from tkinter import * from tkinter import messagebox else: from Tkinter import * def main(): reset() # 先從新設置遊戲數據 root = Tk() # 建立tkinter窗口 root.title('2048遊戲') # 設置標題文字 root.resizable(width=False, height=False) # 固定寬和高 # 如下是鍵盤映射 keymap = { 'a': left, 'd': right, 'w': up, 's': down, 'Left': left, 'Right': right, 'Up': up, 'Down': down, 'q': root.quit, } game_bg_color = "#bbada0" # 設置背景顏色 # 設置遊戲中每一個數據對應色塊的顏色 mapcolor = { 0: ("#cdc1b4", "#776e65"), 2: ("#eee4da", "#776e65"), 4: ("#ede0c8", "#f9f6f2"), 8: ("#f2b179", "#f9f6f2"), 16: ("#f59563", "#f9f6f2"), 32: ("#f67c5f", "#f9f6f2"), 64: ("#f65e3b", "#f9f6f2"), 128: ("#edcf72", "#f9f6f2"), 256: ("#edcc61", "#f9f6f2"), 512: ("#e4c02a", "#f9f6f2"), 1024: ("#e2ba13", "#f9f6f2"), 2048: ("#ecc400", "#f9f6f2"), 4096: ("#ae84a8", "#f9f6f2"), 8192: ("#b06ca8", "#f9f6f2"), # ----其它顏色都與8192相同--------- 2 ** 14: ("#b06ca8", "#f9f6f2"), 2 ** 15: ("#b06ca8", "#f9f6f2"), 2 ** 16: ("#b06ca8", "#f9f6f2"), 2 ** 17: ("#b06ca8", "#f9f6f2"), 2 ** 18: ("#b06ca8", "#f9f6f2"), 2 ** 19: ("#b06ca8", "#f9f6f2"), 2 ** 20: ("#b06ca8", "#f9f6f2"), } def on_key_down(event): '鍵盤按下處理函數' keysym = event.keysym if keysym in keymap: if keymap[keysym](): # 若是有數字移動 fill2() # 填充一個新的2 update_ui() if is_gameover(): mb = messagebox.askyesno( title="gameover", message="遊戲結束!\n是否退出遊戲!") if mb: root.quit() else: reset() update_ui() def update_ui(): '''刷新界面函數 根據計算出的f地圖數據,更新各個Label的設置 ''' for r in range(4): for c in range(len(_map_data[0])): number = _map_data[r][c] # 設置數字 label = map_labels[r][c] # 選中Lable控件 label['text'] = str(number) if number else '' label['bg'] = mapcolor[number][0] label['foreground'] = mapcolor[number][1] label_score['text'] = str(get_score()) # 重設置分數 # 建立一個frame窗口,此建立將容納所有的widget 部件 frame = Frame(root, bg=game_bg_color) frame.grid(sticky=N + E + W + S) # 設置焦點能接收按鍵事件 frame.focus_set() frame.bind("<Key>", on_key_down) # 初始化圖形界面 map_labels = [] for r in range(4): row = [] for c in range(len(_map_data[0])): value = _map_data[r][c] text = str(value) if value else '' label = Label(frame, text=text, width=4, height=2, font=("黑體", 30, "bold")) label.grid(row=r, column=c, padx=5, pady=5, sticky=N + E + W + S) row.append(label) map_labels.append(row) # 設置顯示分數的Lable label = Label(frame, text='分數', font=("黑體", 30, "bold"), bg="#bbada0", fg="#eee4da") label.grid(row=4, column=0, padx=5, pady=5) label_score = Label(frame, text='0', font=("黑體", 30, "bold"), bg="#bbada0", fg="#ffffff") label_score.grid(row=4, columnspan=2, column=1, padx=5, pady=5) # 如下設置從新開始按鈕 def reset_game(): reset() update_ui() restart_button = Button(frame, text='從新開始', font=("黑體", 16, "bold"), bg="#8f7a66", fg="#f9f6f2", command=reset_game) restart_button.grid(row=4, column=3, padx=5, pady=5) update_ui() # 更新界面 root.mainloop() # 進入tkinter主事件循環 main() # 啓動遊戲