2048小游戲

前言:
最近在學(xué)習(xí)Python的過程中厂汗,在github上看見別人實(shí)現(xiàn)的一個(gè)小游戲2048,感覺很有趣野芒,這里記錄下實(shí)現(xiàn)過程蓄愁。

界面效果圖:


image.png

下面我們具體來實(shí)現(xiàn),首先導(dǎo)入我們需要的庫狞悲,這里呢導(dǎo)入兩個(gè)庫撮抓,一個(gè)用來產(chǎn)生隨機(jī)數(shù),另一個(gè)用來做數(shù)學(xué)運(yùn)算

import random
import math

然后這里聲明下使用新式類,至于什么是新式類這里就不提了摇锋,但是現(xiàn)在python3下的類都是新式類丹拯,也就是說都繼承至object,所以下面這行代碼可要可不要

__mataclass__ = type  # 使用新式類

接下來定義一個(gè)類用來封裝我們的游戲地圖

class map2048():

在這個(gè)類里,我們需要定義許多方法來使用乱投,首先是重新設(shè)置游戲數(shù)據(jù)對應(yīng)的方法

 # 重新設(shè)置游戲數(shù)據(jù)
    def reset(self):
        self.__row = 4  # 行數(shù)
        self.__col = 4  # 列數(shù)
        self.data = [
            [0 for x in range(self.__col)]
            for y in range(self.__row)
        ]
        # self.data = [[x + 4 * y for x in range(self.__col)]
        #              for y in range(self.__row)]
        # self.data = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
        self.fill2()
        self.fill2()

然后在類初始化時(shí)調(diào)用上面的方法來保證游戲打開時(shí)咽笼,數(shù)據(jù)被重新設(shè)置

 def __init__(self):
        self.reset()

然后是獲取沒有數(shù)字的位置的個(gè)數(shù),這樣當(dāng)我們按下按鍵時(shí)戚炫,我們可以生成對應(yīng)的隨機(jī)數(shù)來填充對應(yīng)的空值


    # 獲取沒有數(shù)字的位置的個(gè)數(shù)
    def get_space_count(self):
        """
        獲取沒有數(shù)字的方格的數(shù)量
        """
        count = 0
        for r in self.data:
            count += r.count(0)
        return count

然后獲取游戲的分?jǐn)?shù)剑刑,這里需要使用到math庫里的log函數(shù)來生成分?jǐn)?shù)

 # 獲取游戲的分?jǐn)?shù)。
    def get_score(self):
        s = 0
        for r in self.data:
            for c in r:
                s += 0 if c < 4 else c * int((math.log(c, 2) - 1.0))
        return s

接下來便是填充2到空位置,如果填度成功返回True,如果已滿施掏,則返回False钮惠。這里我們首先獲取有多少空位置,然后產(chǎn)生隨機(jī)數(shù)表示該空值所在位置七芭,再填充2到對應(yīng)的位置上

# 填充2到空位置素挽,如果填度成功返回True,如果已滿,則返回False
    def fill2(self):
        blank_count = self.get_space_count()
        if 0 == blank_count:
            return False
        # 生成隨機(jī)位置
        pos = random.randrange(0, blank_count)
        offset = 0
        for r in self.data:
            for ci in range(self.__col):
                if 0 == r[ci]:
                    if offset == pos:
                        r[ci] = 2
                        return True
                    offset += 1

然后判斷游戲是否結(jié)束,這里有3種情況游戲沒有結(jié)束狸驳,水平方向還有0预明,水平方向有兩個(gè)相鄰元素相同,豎直方向有兩個(gè)相鄰元素相同耙箍。

# 判斷游戲是否結(jié)束
    def is_gameover(self):
        for r in self.data:
            # 如果水平方向還有0,則游戲沒有結(jié)束
            if r.count(0):
                return False
            # 水平方向如果有兩個(gè)相鄰的元素相同撰糠,則沒有游戲結(jié)束
            for i in range(self.__col - 1):
                if r[i] == r[i + 1]:
                    return False
        for c in range(self.__col):
            # 豎直方向如果有兩個(gè)相鄰的元素相同,則沒有游戲結(jié)束
            for r in range(self.__row - 1):
                if self.data[r][c] == self.data[r + 1][c]:
                    return False
        # 以上都沒有辩昆,則游戲結(jié)束
        return True

然后是游戲的左移動,主要分為3步阅酪,首先所有數(shù)字左移填補(bǔ)空格,然后檢測是否發(fā)生碰撞汁针,也就是相同數(shù)字合并术辐,這時(shí)由于可能的碰撞會產(chǎn)生新的空格,所以需要再將所有數(shù)字左移來填補(bǔ)空格施无。

def left(self):
        # moveflag 是否成功移動數(shù)字標(biāo)志位,如果有移動則為真值,原地圖不變則為假值
        moveflag = False
        # 將所有數(shù)字向左移動來填補(bǔ)左側(cè)空格
        for times in range(self.__col - 1):
            for r in self.data:
                for c in range(self.__col - 1):
                    if 0 == r[c]:
                        moveflag = True
                        r[c] = r[c + 1]
                        r[c + 1] = 0
        # 判斷是否發(fā)生碰幢辉词,如果有碰撞則合并,合并結(jié)果靠左,右側(cè)填充空格
        for r in self.data:
            for c in range(self.__col - 1):
                if r[c] == r[c + 1]:
                    moveflag = True
                    r[c] *= 2
                    r[c + 1] = 0
        # 再將所有數(shù)字向左移動來填補(bǔ)左側(cè)空格
        for times in range(self.__col - 1):
            for r in self.data:
                for c in range(self.__col - 1):
                    if 0 == r[c]:
                        moveflag = True
                        r[c] = r[c + 1]
                        r[c + 1] = 0
        return moveflag

游戲右移操作,與左移剛好相反帆精,所以可以直接使用數(shù)組對應(yīng)的reverse()方法较屿,注意這里是水平取反隧魄,所以取數(shù)組data的每一行來執(zhí)行reverse()方法

 # 游戲右移操作
    def right(self):
        for r in self.data:
            r.reverse()
        moveflag = self.left()
        for r in self.data:
            r.reverse()
        return moveflag

游戲上移操作,與左移相對應(yīng)卓练,只是這里變化的是橫坐標(biāo)。

 # 游戲上移操作
    def up(self):
        # moveflag 是否成功移動數(shù)字標(biāo)志位,如果有移動則為真值,原地圖不變則為假值
        moveflag = False
        # 將所有數(shù)字向上移動來填補(bǔ)上面空格
        for times in range(self.__row - 1):
            for c in range(self.__col):
                for r in range(self.__row - 1):
                    if 0 == self.data[r][c]:
                        moveflag = True
                        self.data[r][c] = self.data[r + 1][c]
                        self.data[r + 1][c] = 0
        # 判斷是否發(fā)生碰幢购啄,如果有碰撞則合并,合并結(jié)果靠上襟企,下面填充空格
        for c in range(self.__col):
            for r in range(self.__row - 1):
                if self.data[r][c] == self.data[r + 1][c]:
                    moveflag = True
                    self.data[r][c] *= 2
                    self.data[r + 1][c] = 0
        # 再將所有數(shù)字向上移動來填補(bǔ)上面空格
        for times in range(self.__row - 1):
            for c in range(self.__col):
                for r in range(self.__row - 1):
                    if 0 == self.data[r][c]:
                        moveflag = True
                        self.data[r][c] = self.data[r + 1][c]
                        self.data[r + 1][c] = 0
        return moveflag

游戲下移操作,同樣是取反,只是這里是數(shù)組的列取反狮含。

# 游戲下移操作
    def down(self):
        self.data.reverse()
        moveflag = self.up()
        self.data.reverse()
        return moveflag

以上便是我們封裝的地圖類,接下來便是生成窗體顽悼,綁定事件,設(shè)置字體大小和顏色了
首先導(dǎo)入窗體需要的tkinter庫几迄,這里先判斷下python是否為3.0以上(包括3.0)

import sys
 
if (sys.version_info > (3, 0)):
    from tkinter import *
    from tkinter import messagebox
else:
    from Tkinter import *

然后生成剛剛實(shí)現(xiàn)的地圖類對象蔚龙,并設(shè)置鍵盤按鍵與對象里的方法一一對應(yīng)的鍵值對

game = map2048()
keymap = {
    'a': game.left,
    'd': game.right,
    'w': game.up,
    's': game.down,
    'Left': game.left,
    'Right': game.right,
    'Up': game.up,
    'Down': game.down,
    'q': exit,
}

設(shè)置游戲背景色和數(shù)字的背景和前景色,這里數(shù)字還可以繼續(xù)擴(kuò)大2倍,想實(shí)現(xiàn)可以自己添加

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"),
}

游戲各方塊的lable數(shù)據(jù)

# 游戲各方塊的lable數(shù)據(jù)
map_labels = []

鼠標(biāo)按下處理函數(shù)和鍵盤按下處理函數(shù)映胁,這里由于是2048游戲木羹,所以不支持鼠標(biāo)點(diǎn)擊,在對應(yīng)的方法里便不需要實(shí)現(xiàn)

# 鼠標(biāo)按下處理函數(shù)
def on_mouse_down(event):
    print("clicked at", event.x, event.y)
 
 
# 鍵盤按下處理函數(shù)
def on_key_down(event):
    keysym = event.keysym
    if keysym in keymap:
        if keymap[keysym]():
            game.fill2()
    update_ui()
    if game.is_gameover():
        mb = messagebox.askyesno(title="gameover", message="游戲結(jié)束!\n是否退出游戲!")
        if mb:
            exit()
        else:
            game.reset()
            update_ui()
 
 
# 刷新界面函數(shù)
def update_ui():
    # 更改各個(gè)Label的設(shè)置
    for r in range(len(game.data)):
        for c in range(len(game.data[0])):
            number = game.data[r][c]
            label = map_labels[r][c]
            label['text'] = str(number) if number else ''
            label['bg'] = mapcolor[number][0]
            label['foreground'] = mapcolor[number][1]
    label_score['text'] = str(game.get_score())

以下為2048的界面

root = Tk()
root.title('2048')

生成對應(yīng)的窗體,并指定寬高和背景顏色,grid方法表示對齊方式坑填,這里就是填滿整個(gè)窗體

frame = Frame(root, width=300, height=300, bg=game_bg_color)
frame.grid(sticky=N+E+W+S)

設(shè)置焦點(diǎn)能接收按鍵事件抛人,綁定鍵盤點(diǎn)擊事件

frame.focus_set()
frame.bind("<Key>", on_key_down)
# 以下綁定鼠標(biāo)抬起事件
frame.bind("<ButtonRelease-1>", on_mouse_down)

初始化圖形界面

# 初始化圖形界面
for r in range(len(game.data)):
    row = []
    for c in range(len(game.data[0])):
        value = game.data[r][c]
        text = '' if 0 == value else str(value)
        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)
bottom_row = len(game.data)
print("button", str(bottom_row))
label = Label(frame, text='分?jǐn)?shù)', font=("黑體", 30, "bold"),
              bg="#bbada0", fg="#eee4da")
label.grid(row=bottom_row, column=0, padx=5, pady=5)
label_score = Label(frame, text='0', font=("黑體", 30, "bold"),
                    bg="#bbada0", fg="#ffffff")
label_score.grid(row=bottom_row, columnspan=2, column=1, padx=5, pady=5)

游戲重新開始,設(shè)置對應(yīng)的重新開始按鍵和布局

#游戲重新開始
def reset_game():
    game.reset()
    update_ui()
restart_button = Button(frame, text='重新開始', font=("黑體", 16, "bold"),
                        # width=4, height=2,
                        bg="#8f7a66", fg="#f9f6f2", command=reset_game)
restart_button.grid(row=bottom_row, column=3, padx=5, pady=5)

啟動窗體

update_ui() 
root.mainloop()

總結(jié):在本次學(xué)習(xí)中,收獲很多脐瑰,特別是對python的語法知識更加熟悉妖枚,這其中也遇到了不少問題,例如self關(guān)鍵字不能在類外部使用苍在,寫完整個(gè)程序绝页,還是特別有意思的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寂恬,一起剝皮案震驚了整個(gè)濱河市抒寂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌掠剑,老刑警劉巖屈芜,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異朴译,居然都是意外死亡井佑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門眠寿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來躬翁,“玉大人,你說我怎么就攤上這事盯拱『蟹ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵狡逢,是天一觀的道長宁舰。 經(jīng)常有香客問我,道長奢浑,這世上最難降的妖魔是什么蛮艰? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮雀彼,結(jié)果婚禮上壤蚜,老公的妹妹穿的比我還像新娘。我一直安慰自己徊哑,他們只是感情好袜刷,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著莺丑,像睡著了一般著蟹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天草则,我揣著相機(jī)與錄音钢拧,去河邊找鬼。 笑死炕横,一個(gè)胖子當(dāng)著我的面吹牛源内,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播份殿,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼膜钓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了卿嘲?” 一聲冷哼從身側(cè)響起颂斜,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拾枣,沒想到半個(gè)月后沃疮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梅肤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年司蔬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姨蝴。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡俊啼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出左医,到底是詐尸還是另有隱情授帕,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布浮梢,位于F島的核電站跛十,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏黔寇。R本人自食惡果不足惜偶器,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缝裤。 院中可真熱鬧,春花似錦颊郎、人聲如沸憋飞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽榛做。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間检眯,已是汗流浹背厘擂。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锰瘸,地道東北人刽严。 一個(gè)月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像避凝,于是被迫代替她去往敵國和親舞萄。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容