1. 引言 2048 這段時間火的不行啊,大家都紛紛仿造,“百家爭鳴”,於是出現了各種技術版本:除了手機版本,還有C語言版、Qt版、Web版、java版、C#版等,剛好我接觸Python不久,於是弄了個Python版——控制台的2048,正好熟悉下Python文法,程式運行效果如下:
圖 1 Python版控制台2048運行
程式碼加上注釋大概150行左右,利用了一些Python內建資料類型的操作節省了不少代碼量。下面說說我的編寫思路,最後會給出原始碼。
2. 2048 實現思路
2.1 遊戲規則 這個遊戲可玩性很好,簡單的移動方向鍵讓數字疊加,並且獲得這些數字每次疊加後的得分,當出現2048這個數字時遊戲勝利。同時每次移動方向鍵時,都會在這個4*4的方格矩陣的空白地區隨機產生一個數字2或者4,如果方格被數字填滿了,那麼就GameOver了。
2.2 實現思路 這個遊戲的全部操作都是圍繞著一個4*4的矩陣進行,每次從使用者介面擷取使用者的操作(即移動方向),然後重新計算這個4*4矩陣的狀態,最後重新整理使用者介面顯示4*4矩陣的最新狀態,不斷的迴圈這個過程,直到出現2048或沒有空白方塊了,下面是一個處理流程:
我寫的是控制台程式,沒有UI介面,因此用字元(W/S/A/D)代表方向鍵的輸入,以數字0代表空白方格。接下來是計算部分,以向左移動為例,4*4矩陣在接收到向左移動的指令後,應該將每行的數字向左疊加, 將一行的疊加操作定義為函數 handle(list, direction),其第一個參數用來儲存4*4矩陣中的某一行(列),第二個參數表示移動的方向(上下左右)。
這樣當左右移動方向鍵時,可以這樣來計算矩陣:遍曆矩陣的每行,並將每行的數字沿左或右進行疊加操作。for row in matrix: handle(row, direction)
對於上下移動方向鍵時,由於矩陣是按行儲存的,不能直接處理矩陣中的列,可以通過變通採用上面的函數handle()。對於矩陣中每一列,先將其拷貝到一個列表中,然後調用handle()函數對該列表進行疊加處理,最後再將疊加後的新列表拷貝回原始矩陣中其所在的列,其邏輯上等同於下面的代碼操作。for col in matrix: handle(col, direction)
handle(row, direction)函數的作用是沿指定方向疊加一行中的數字,請看下面幾個例子:
| 移動方向 |
移動前 |
移動後 |
| handle(x, 'left') |
x = [0, 2, 2, 2] |
x = [4, 2, 0, 0] |
| handle(x, 'left') |
x = [2, 4, 2, 2] |
x = [2, 8, 0, 0] |
| handle(x, 'right') |
x = [2, 4, 2, 2] |
x = [0, 0, 2, 8] |
實現 handle(row, direction) 函數
根據上面的介紹,實現handle函數是關鍵。仔細觀察疊加的過程,其都是由兩個子過程組成的:(1)
align(row, direction) 沿direction方向對齊列表row中的數字,例如:x = [0, 4, 0, 2]align(x, 'left') 後 x = [4, 2, 0, 0]在 align(x, 'right') 後 x = [0, 0, 4, 2]
(2)
addSame(row, direction) 尋找
相同且相鄰的數字。如果找到,將其中一個翻倍,另一個置0(如果direction是'left'將左側翻倍,右側置0,如果direction為'right',將右側翻倍,左側置0),並返回True;否則,返回False。例如:x = [2, 2, 2, 2]addSame(x, 'left') 後 x = [4, 0, 2, 2] 返回 True再 addSame(x, 'left') 後 x = [4, 0, 4, 0] 返回 True再 addSame(x, 'left') 後 x = [4, 0, 4, 0] 返回 False
有了上面兩個子函數,應該不難實現。有了這兩個子函數,函數handle()就很好實現了,如下:handle(row, direction): align(row, direction) result = addSame(row, direction) while result == True: align(row, direction) result = addSame(row, direction)
下面結合一個實際例子再來看看handle函數的處理過程:x = [2, 4, 2, 2]調用 handle(x, 'right'),變數 x 變化過程:align(x, 'right') -> [2, 4, 2, 2]addSame(x, 'right') -> [2, 4, 0, 4] -> return Truealign(x, 'right') -> [0, 2, 4, 4] addSame(x, 'right') -> [0, 2, 0, 8] -> return Truealign(x, 'right') -> [0, 0, 2, 8] addSame(x, 'right') -> [0, 0, 2, 8] -> return False最終得到的 x = [0, 0, 2, 8] 最主要的部分已經說完了,下面就貼出代碼吧,由於代碼不多就直接貼出來吧。
3. 代碼
這些是全部代碼,儲存在單一檔案中即可運行,運行環境 Python3.0+
# -*- coding:UTF-8 -*-#! /usr/bin/python3import randomv = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]def display(v, score): '''顯示介面 ''' print('{0:4} {1:4} {2:4} {3:4}'.format(v[0][0], v[0][1], v[0][2], v[0][3])) print('{0:4} {1:4} {2:4} {3:4}'.format(v[1][0], v[1][1], v[1][2], v[1][3])) print('{0:4} {1:4} {2:4} {3:4}'.format(v[2][0], v[2][1], v[2][2], v[2][3])) print('{0:4} {1:4} {2:4} {3:4}'.format(v[3][0], v[3][1], v[3][2], v[3][3]), ' Total score: ', score)def init(v): '''隨機分布網格值 ''' for i in range(4): v[i] = [random.choice([0, 0, 0, 2, 2, 4]) for x in v[i]]def align(vList, direction): '''對齊非零的數字 direction == 'left':向靠左對齊,例如[8,0,0,2]靠左對齊後[8,2,0,0] direction == 'right':向靠右對齊,例如[8,0,0,2]靠右對齊後[0,0,8,2] ''' # 移除列表中的0 for i in range(vList.count(0)): vList.remove(0) # 被移除的0 zeros = [0 for x in range(4 - len(vList))] # 在非0數位一側補充0 if direction == 'left': vList.extend(zeros) else: vList[:0] = zeros def addSame(vList, direction): '''在列表尋找相同且相鄰的數字相加, 找到合格返回True,否則返回False,同時還返回增加的分數 direction == 'left':從右向左尋找,找到相同且相鄰的兩個數字,左側數字翻倍,右側數字置0 direction == 'right':從左向右尋找,找到相同且相鄰的兩個數字,右側數字翻倍,左側數字置0 ''' score = 0 if direction == 'left': for i in [0, 1, 2]: if vList[i] == vList[i+1] != 0: vList[i] *= 2 vList[i+1] = 0 score += vList[i] return {'bool':True, 'score':score} else: for i in [3, 2, 1]: if vList[i] == vList[i-1] != 0: vList[i-1] *= 2 vList[i] = 0 score += vList[i-1] return {'bool':True, 'score':score} return {'bool':False, 'score':score}def handle(vList, direction): '''處理一行(列)中的資料,得到最終的該行(列)的數字狀態值, 返回得分 vList: 列表結構,儲存了一行(列)中的資料 direction: 移動方向,向上和向左都使用方向'left',向右和向下都使用'right' ''' totalScore = 0 align(vList, direction) result = addSame(vList, direction) while result['bool'] == True: totalScore += result['score'] align(vList, direction) result = addSame(vList, direction) return totalScore def operation(v): '''根據移動方向重新計算矩陣狀態值,並記錄得分 ''' totalScore = 0 gameOver = False direction = 'left' op = input('operator:') if op in ['a', 'A']: # 向左移動 direction = 'left' for row in range(4): totalScore += handle(v[row], direction) elif op in ['d', 'D']: # 向右移動 direction = 'right' for row in range(4): totalScore += handle(v[row], direction) elif op in ['w', 'W']: # 向上移動 direction = 'left' for col in range(4): # 將矩陣中一列複製到一個列表中然後處理 vList = [v[row][col] for row in range(4)] totalScore += handle(vList, direction) # 從處理後的列表中的數字覆蓋原來矩陣中的值 for row in range(4): v[row][col] = vList[row] elif op in ['s', 'S']: # 向下移動 direction = 'right' for col in range(4): # 同上 vList = [v[row][col] for row in range(4)] totalScore += handle(vList, direction) for row in range(4): v[row][col] = vList[row] else: print('Invalid input, please enter a charactor in [W, S, A, D] or the lower') return {'gameOver':gameOver, 'score':totalScore} # 統計空白地區數目 N N = 0 for q in v: N += q.count(0) # 不存在剩餘的空白地區時,遊戲結束 if N == 0: gameOver = True return {'gameOver':gameOver, 'score':totalScore} # 按2和4出現的幾率為3/1來產生隨機數2和4 num = random.choice([2, 2, 2, 4]) # 產生隨機數k,上一步產生的2或4將被填到第k個空白地區 k = random.randrange(1, N+1) n = 0 for i in range(4): for j in range(4): if v[i][j] == 0: n += 1 if n == k: v[i][j] = num break return {'gameOver':gameOver, 'score':totalScore}init(v)score = 0print('Input:W(Up) S(Down) A(Left) D(Right), press <CR>.')while True: display(v, score) result = operation(v) if result['gameOver'] == True: print('Game Over, You failed!') print('Your total score:', score) else: score += result['score'] if score >= 2048: print('Game Over, You Win!!!') print('Your total score:', score)