前言:

Life is short ,you need python.

--Bruce Eckel

我與2048的緣,不是緣於一個玩家。而是一次,一次,重新的ACM比賽.四月份校賽初賽,第一次碰到2048,兩週後決賽再次遇到2048,後來五月份的廣東省賽,又出現了2048。

在這三次比賽過程中,我一次2048都沒玩過..全靠隊友的解釋,直到昨天。我突然想起寫個2048吧,於是下了個2048玩了幾盤,之後就開始用python來寫了,心想就不寫介面了,為了簡潔。

我對python並不熟悉,可是我在之前的博文就提過我希望成為一個python的玩家,所以我選擇了python,同一時候,也希望大家也能夠用很easy的程式碼取實現2048。

首先,假設你沒有玩過2048,沒關係,你僅僅要隨便在網上或電子市場一搜索肯定會有各種版本號,之後玩幾把就會了。

來到這裡,我如果大家都玩過2048了,於是我們先分析一下怎麼來寫一個簡單的2048?回憶一下。遊戲的過程是非常easy的:

開始===>(  進行操作(上下左右)===>矩陣移動===>數字合併===>計分  )===>退出

括號內就是遊戲的進行過程了。我們退出大概就是兩個:1.出現2048,勝利退出 2.無法合併同一時候無法出現新數(是且的關係)

懂得這個執行過程後,我們就一步步來寫,整個過程也不會難。哪怕你跟我一樣基本是新手

先看看簡略圖,看起來儘管真的非常樸素,可是愛美的你。能夠在看完我的程式後自己美化。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTA0NDc1OQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" height="474" width="804" alt="">

儲存結構:列表,當中包含二維矩陣列表,即所謂的mtr[4][4],以及棧,棧是用於退回。假設大家不想要這個功能能夠直接忽略。

我們的變數定義:

declare = "←:a/h  ↓: s/j ↑: w/k →: d/l ,q(uit),b(ack)"
illegal = "Illegal operation!"
noefficient = "This move has no efficient"
score = 0
step = 0
mtr = init() # init the matrix
mtr_stk = [] # use step for back
scr_stk = []

略微解釋一下: declare是解釋操作提示,自己喜歡怎麼寫就怎麼寫~

illegal是我們輸入非正常操作提示,。能夠省略

noefficient是我們往這個方向沒效果。也能夠省略~

score是得分,step是走了多少步,

mtr是我們的二維方陣,

mtr_stk,scr_stk是儲存棧,用於後退功能,假設不要後退。還是能夠省略~

演算法實現:

·「注:事實上沒有演算法可言....更準確叫函式實現。」

(1).在我們這個沒有介面的2048中。我們僅僅能用製表符啦。所以我們須要一個display函式。

這是一個很easy的事情。跟C語言的printf基本一致, 像例如以下程式碼。基本就能繪製出一個固定數字的表格了。

        print "┌"+("─"*5+"┬")*3+"─"*5+"┐"
print "│%4d │%4d │%4d │%4d │"%(1,2048,25,4)
print "├"+("─"*5+"┼")*3+"─"*5+"┤"
print "│%4d │%4d │%4d │%4d │"%(1,2048,25,4)
print "├"+("─"*5+"┼")*3+"─"*5+"┤"
print "│%4d │%4d │%4d │%4d │"%(1,2048,25,4)
print "├"+("─"*5+"┼")*3+"─"*5+"┤"
print "│%4d │%4d │%4d │%4d │"%(1,2048,25,4)
print "└"+("─"*5+"┴")*3+"─"*5+"┘"

可是我們須要的是將固定的數字變成我們矩陣mtr的數字。

另外能夠。注意到一點,數字輸出的形式是固定的,表格邊線。僅僅有交叉點是不同。其它是0。而交叉點有五個,我們能夠走迴圈(認為麻煩能夠直接忽略)

為了美觀,我們不將0輸出,所以我們輸出時候選擇的是字串即%s,這樣。我們遇到0,就能夠輸出' ',其它直接輸出。

我們能夠用python的三目運算子號

print "%4s " %(mtr[i][j] if mtr[i][j] else ' ')

所以我們的繪製表格寫成這樣子:最直接的方法!

def T(a):
return a if a else ' '
def display(mtr):
print "┌"+("─"*5+"┬")*3+"─"*5+"┐"
print "│%4s │%4s │%4s │%4s │"%(T(mtr[0][0]),T(mtr[0][1]),T(mtr[0][2]),T(mtr[0][3]))
print "├"+("─"*5+"┼")*3+"─"*5+"┤"
print "│%4s │%4s │%4s │%4s │"%(T(mtr[1][0]),T(mtr[1][1]),T(mtr[1][2]),T(mtr[1][3]))
print "├"+("─"*5+"┼")*3+"─"*5+"┤"
print "│%4s │%4s │%4s │%4s │"%(T(mtr[2][0]),T(mtr[2][1]),T(mtr[2][2]),T(mtr[2][3]))
print "├"+("─"*5+"┼")*3+"─"*5+"┤"
print "│%4s │%4s │%4s │%4s │"%(T(mtr[3][0]),T(mtr[3][1]),T(mtr[3][2]),T(mtr[3][3]))
print "└"+("─"*5+"┴")*3+"─"*5+"┘"

假設走迴圈能夠這樣寫:就是列舉交叉點情況。

def display(mtrx): #output function
a = ("┌", "├", "├", "├", "└")
b = ("┬", "┼", "┼", "┼", "┴")
c = ("┐", "┤", "┤", "┤", "┘")
for i in range(4):
print a[i] + ("─" * 5 + b[i]) * 3 + ("─" * 5 + c[i])
for j in range(4):
print "│%4s" % (mtrx[i][j] if mtrx[i][j] else ' '),
print "│"
print a[4] + ("─" * 5 + b[4]) * 3 + ("─" * 5 + c[4])

(2)有了表格雛形,我們須要對矩陣進行初始化。then,我們就能夠看到一個像模像樣的2048開始啦!

初始化矩陣是很easy的,我們在矩陣中選兩個不同位置,設定為2其它為0就可以。

先設定為0(沒有memset,╮(╯▽╰)╭)

可是這樣就能夠啦:(看不慣?沒關係。很easy理解,慢慢習慣吧!)

mtr = [[0 for i in range(4)] for j in range(4)]  

我們選的位置是採取隨機的,要保證不同,方法非常多,可是我們python的random.sample就非常棒了!

我們用簡單的一句

ran_pos = random.sample(range(16), 2)

便能夠得到一個由兩個不同組成的片段

之後我們通過 4*i+j 進行二維到一維的對映,相同也用( num/4 。num%4)進行一維到二維的對映

所以我們初始話的過程也是十分簡單。(要是偷懶。也能夠直接忽略隨機過程,自己指定吧!哈哈)

def init(): #initial of matrix
mtr = [[0 for i in range(4)] for j in range(4)]
ran_pos = random.sample(range(16), 2)
mtr[ran_pos[0]/4][ran_pos[0]%4] = mtr[ran_pos[1]/4][ran_pos[1]%4] = 2
return mtr

怎麼樣?2048有2個2啦!!!

O(∩_∩)O哈哈~

(3)動起來!。讓我們的2048動起來!

老實說。這個過程是全個程式最核心的部分。也是最難過的~只是理解了也就不難了。

我們先看向左移動吧。

記得我們玩的時候,合併過的格子是不會再在這次合併的,比方 某一行為 2 2 2 2 最後移動到的結果是4 4 0 0 而不是 8 0 0 0。

我們要理解這個過程:

我們對於每一行的操作是一樣的,即行間無關,所以我們如今要討論第i行,當我們向左動起來的時候。我們從最左開始確定。我們一開始合併前兩個,再合併前三個,再合併前4個。

比方

2 2 4 4   => 4 0 4 4 => 4 4 0 4 =>4 4 4 0 => 4 8 0 0

注意兩點: 1.合併過的不再參與合併,所以我們須要一個visit列表,用到前面所講的對映

2.合併的情況=>相鄰的相等,移動的情況=>當前元素為0

python程式碼

visit=[]
score=0
for i in range(4):
for j in range(1, 4):
for k in range(j,0,-1):
if mtr[i][k - 1] == 0 :
mtr[i][k - 1] = mtr[i][k]
mtr[i][k] = 0
elif mtr[i][k - 1] == mtr[i][k] and 4 * i + k - 1 not in visit and 4 * i + k not in visit:
mtr[i][k - 1] *= 2
mtr[i][k] = 0
score += mtr[i][k - 1]
visit.append(4 * i + k)
visit.append(4 * i + k - 1)

當你想明確這個過程後,整個move過程就自然清晰起來了。這個是本文第一個,最後一個比較長的函數了!

def move(mtr, dirct):#the core code!move by the four direction
score = 0
visit = []
if dirct == 0: # left
for i in range(4):
for j in range(1, 4):
for k in range(j,0,-1):
if mtr[i][k - 1] == 0 :
mtr[i][k - 1] = mtr[i][k]
mtr[i][k] = 0
elif mtr[i][k - 1] == mtr[i][k] and 4 * i + k - 1 not in visit and 4 * i + k not in visit:
mtr[i][k - 1] *= 2
mtr[i][k] = 0
score += mtr[i][k - 1]
visit.append(4 * i + k)
visit.append(4 * i + k - 1)
elif dirct == 1: # down
for j in range(4):
for i in range(3, 0, -1):
for k in range(0,i):
if mtr[k+1][j] == 0:
mtr[k+1][j] = mtr[k][j]
mtr[k][j]=0
elif mtr[k+1][j]==mtr[k][j] and (4 *(k+1)+j) not in visit and (4*k+j) not in visit:
mtr[k+1][j]*=2
mtr[k][j]=0
score=mtr[k+1][j]
visit.append(4*(k)+j)
visit.append(4*(k+1)+j)
elif dirct == 2: # up
for j in range(4):
for i in range(1,4):
for k in range(i,0,-1):
if mtr[k-1][j]==0:
mtr[k-1][j]=mtr[k][j]
mtr[k][j]=0
elif mtr[k-1][j]==mtr[k][j] and (4 *(k-1)+j) not in visit and (4*k+j) not in visit:
mtr[k-1][j]*=2
mtr[k][j]=0
score += mtr[k-1][j]
visit.append(4*(k)+j)
visit.append(4*(k-1)+j)
elif dirct == 3: # right
for i in range(4):
for j in range(3, 0, -1):
for k in range(j):
if mtr[i][k+1] == 0:
mtr[i][k+1] = mtr[i][k]
mtr[i][k]=0
elif mtr[i][k] ==mtr[i][k+1] and 4 * i + k + 1 not in visit and 4 * i + k not in visit:
mtr[i][k+1]*=2
mtr[i][k]=0
score+=mtr[i][k+1]
visit.append(4*i+k+1)
visit.append(4*i+k)
return score

(4)當你來到這步,已經進入輕鬆的狀態了,讓控制檯讀入你的操作。短短兩行,當中一行是轉成小寫

        dirct = raw_input("Step :%d Score :%d (%s):" % (step, score, declare))
dirct = dirct.lower()

(5)推斷讀入操作並處理

        if dirct == "q":
break
elif dirct == "a" or dirct == "h":
dirct = 0
elif dirct == "s" or dirct == "j":
dirct = 1
elif dirct == "w" or dirct == "k":
dirct = 2
elif dirct == "d" or dirct == "l":
dirct = 3
elif dirct == "b":
if len(mtr_stk) == 1:
print "Can't Back.."
else:
mtr_stk.pop()
scr_stk.pop()
mtr = copy.deepcopy(mtr_stk[-1])
score = scr_stk[-1]
step -= 1
continue
else:
print illegal
continue

我是vim 的腦殘粉,所以還支援了hjkl。假設你沒有習慣vim什麼的。直接asdw就能夠了。

上面最麻煩最值得注意的就是要用深複製.這個bug我調了非常久啊親,用"="傷不起啊。

上面的處理意思就是: 對映方向。和b,q的處理(有continue是由於我們將巢狀於while中)。

(6)移動的處理

在上面處理後。篩選出移動的操作,之後我們呼叫移動函式,進行加分。和比較。

        tmp = copy.deepcopy(mtr)
op_scr = move(mtr, dirct)
if tmp != mtr:
score = score + op_scr
update(mtr) #更新
display(mtr)
tmp = copy.deepcopy(mtr)
mtr_stk.append(tmp) # 插入後退佇列
scr_stk.append(int(score))
step = step + 1 # 步數加1
else:
print noefficient

注意:不能只用加分來推斷!

由於我們非常多時候都是沒有碰撞合併。等待下一個2,4出現

我們用深複製,將老的矩陣放在tmp上。再移動,將老新矩陣比較,沒改變的時候就輸出 noefficient,否則就加分,插入新數字。並顯示。最後將矩陣壓入棧中。由於要後退!

(7)插入新數字

我們遍歷0-16,並對映後。將相應0的位置放入random列表,我們用random.choice選出一個,同理我們生成一個ran_num=[2,4],選取加分

之後相應位置加分咯。可是注意,當我們沒有空位時候不要插數字。

def update(mtr):
ran_pos=[]
ran_num=[2,4] for i in range(4):
for j in range(4):
if mtr[i][j]==0:
ran_pos.append(4*i+j)
if len(ran_pos)>0:
k=random.choice(ran_pos)
n=random.choice(ran_num)
mtr[k/4][k%4]=n

(8)最後一個啦!

!遊戲的迴圈條件

這個還是非常easy的。遊戲進行。當前僅當:沒有2048且(有0或者能夠合併)。

所以我們僅僅須要推斷2048 in mtr,有就結束,沒有 == > 0 in mtr ?有就繼續。沒有==>能夠合併?有繼續,沒有==》,就返回結束啦

def go_on(mtr, score):
if 2048 in mtr:
print "Great!You win!Your score is ", score
raw_input("Press any key to continue...")
exit()
if 0 in mtr:
return True
for i in range(4):
for j in range(4):
if i < 3 and mtr[i][j] == mtr[i + 1][j]:
return True
if j < 3 and mtr[i][j] == mtr[i][j + 1]:
return True
print "Gameover!"
return False

(9)一些其它的東西

注意一開始要畫出開始的畫面(迴圈外),一開始要將初始矩陣,初始分數。壓入棧。

最後來個終於版本號的

#!/usr/bin/env python
# coding=utf-8
#********************************************************
# > OS : Linux 3.2.0-60-generic #91-Ubuntu
# > Author : yaolong
# > Mail : [email protected]
# > Time : 2014年06月01日 星期日 13:13:39
#********************************************************
import random
import copy def T(a):
return a if a else ' '
def display(mtr):
print "┌"+("─"*5+"┬")*3+"─"*5+"┐"
print "│%4s │%4s │%4s │%4s │"%(T(mtr[0][0]),T(mtr[0][1]),T(mtr[0][2]),T(mtr[0][3]))
print "├"+("─"*5+"┼")*3+"─"*5+"┤"
print "│%4s │%4s │%4s │%4s │"%(T(mtr[1][0]),T(mtr[1][1]),T(mtr[1][2]),T(mtr[1][3]))
print "├"+("─"*5+"┼")*3+"─"*5+"┤"
print "│%4s │%4s │%4s │%4s │"%(T(mtr[2][0]),T(mtr[2][1]),T(mtr[2][2]),T(mtr[2][3]))
print "├"+("─"*5+"┼")*3+"─"*5+"┤"
print "│%4s │%4s │%4s │%4s │"%(T(mtr[3][0]),T(mtr[3][1]),T(mtr[3][2]),T(mtr[3][3]))
print "└"+("─"*5+"┴")*3+"─"*5+"┘" def init():
mtr = [[0 for i in range(4)] for j in range(4)] # 小小蛋疼..
ran_pos = random.sample(range(16), 2)
mtr[ran_pos[0]/4][ran_pos[0]%4] = mtr[ran_pos[1]/4][ran_pos[1]%4] = 2 return mtr def go_on(mtr, score):
if 2048 in mtr:
print "Great!You win!Your score is ", score
raw_input("Press any key to continue...")
exit()
if 0 in mtr:
return True
for i in range(4):
for j in range(4):
if i < 3 and mtr[i][j] == mtr[i + 1][j]:
return True
if j < 3 and mtr[i][j] == mtr[i][j + 1]:
return True
print "Gameover!"
return False def move(mtr, dirct):
score = 0
visit = []
if dirct == 0: # left
for i in range(4):
for j in range(1, 4):
for k in range(j,0,-1):
if mtr[i][k - 1] == 0 :
mtr[i][k - 1] = mtr[i][k]
mtr[i][k] = 0
elif mtr[i][k - 1] == mtr[i][k] and 4 * i + k - 1 not in visit and 4 * i + k not in visit:
mtr[i][k - 1] *= 2
mtr[i][k] = 0
score += mtr[i][k - 1]
visit.append(4 * i + k)
visit.append(4 * i + k - 1)
# for i in range(4):
# for j in range(3): elif dirct == 1: # down
for j in range(4):
for i in range(3, 0, -1):
for k in range(0,i):
if mtr[k+1][j] == 0:
mtr[k+1][j] = mtr[k][j]
mtr[k][j]=0
elif mtr[k+1][j]==mtr[k][j] and (4 *(k+1)+j) not in visit and (4*k+j) not in visit:
mtr[k+1][j]*=2
mtr[k][j]=0
score=mtr[k+1][j]
visit.append(4*(k)+j)
visit.append(4*(k+1)+j) elif dirct == 2: # up
for j in range(4):
for i in range(1,4):
for k in range(i,0,-1):
if mtr[k-1][j]==0:
mtr[k-1][j]=mtr[k][j]
mtr[k][j]=0
elif mtr[k-1][j]==mtr[k][j] and (4 *(k-1)+j) not in visit and (4*k+j) not in visit:
mtr[k-1][j]*=2
mtr[k][j]=0
score += mtr[k-1][j]
visit.append(4*(k)+j)
visit.append(4*(k-1)+j) elif dirct == 3: # right
for i in range(4):
for j in range(3, 0, -1):
for k in range(j):
if mtr[i][k+1] == 0:
mtr[i][k+1] = mtr[i][k]
mtr[i][k]=0
elif mtr[i][k] ==mtr[i][k+1] and 4 * i + k + 1 not in visit and 4 * i + k not in visit:
mtr[i][k+1]*=2
mtr[i][k]=0
score+=mtr[i][k+1]
visit.append(4*i+k+1)
visit.append(4*i+k) return score def update(mtr):
ran_pos=[]
ran_num=[2,4] for i in range(4):
for j in range(4):
if mtr[i][j]==0:
ran_pos.append(4*i+j)
if len(ran_pos)>0:
k=random.choice(ran_pos)
n=random.choice(ran_num)
mtr[k/4][k%4]=n # map 0 left,1 down,2 up ,3 right
# a,h=> left ,s,j=>down, w,k=>up, d,l=>right
declare = "←:a/h ↓: s/j ↑: w/k →: d/l ,q(uit),b(ack)"
illegal = "Illegal operation!"
noefficient = "This move has no efficient"
if __name__ == '__main__':
score = 0
step = 0
mtr = init()
mtr_stk = [] # for back
scr_stk = []
tmp = copy.deepcopy(mtr)
mtr_stk.append(tmp)
scr_stk.append(0)
display(mtr)
while go_on(mtr, score):
dirct = raw_input("Step :%d Score :%d (%s):" % (step, score, declare))
dirct = dirct.lower()
if dirct == "q":
break
elif dirct == "a" or dirct == "h":
dirct = 0
elif dirct == "s" or dirct == "j":
dirct = 1
elif dirct == "w" or dirct == "k":
dirct = 2
elif dirct == "d" or dirct == "l":
dirct = 3
elif dirct == "b":
if len(mtr_stk) == 1:
print "Can't Back.."
else:
mtr_stk.pop()
scr_stk.pop()
mtr = copy.deepcopy(mtr_stk[-1])
score = scr_stk[-1]
step -= 1
continue
else:
print illegal
continue
tmp = copy.deepcopy(mtr)
op_scr = move(mtr, dirct)
if tmp != mtr:
score = score + op_scr
update(mtr) #更新
display(mtr)
tmp = copy.deepcopy(mtr)
mtr_stk.append(tmp) # 插入後退佇列
scr_stk.append(int(score))
step = step + 1 # 步數加1
else:
print noefficient

還有我github的版本號,大致一樣,點這裡

假設我的演算法有什麼不正確。請提出。由於寫得匆忙。我未能非常完整第測試。可是我自己玩一些或OK該,沒有上場2048╮(╯▽╰)╭。。。

版權宣告:本文部落格原創文章,部落格,未經同意,不得轉載。