python 實現(xiàn) 2048 游戲 (二)

上一篇文章中挠轴,我們梳理了實現(xiàn)簡易版 2048 游戲的基本知識势篡,這篇文章將介紹如何實現(xiàn)各個模塊翩肌。換句話說,上一次我們確定了旅行的目的地禁悠,這一次就讓我們自由暢行在山間田野念祭。


流程一.png

游戲主程序,即 game 函數(shù)按部就班地向下執(zhí)行碍侦,該判斷就判斷粱坤,然后執(zhí)行相應函數(shù)隶糕。

首先讀取用戶輸入,第一個判斷:是否移動數(shù)字站玄,顯然要移動數(shù)字要滿足以下條件:

  • 用戶輸入小寫的 w s a d 對應上下左右
  • 該移動方向上允許移動

具體來說枚驻,移動方向最前面有空間或者有連續(xù)相同的數(shù)字≈昕酰可以移動則執(zhí)行 move 函數(shù)再登,并在棋盤上生成隨機數(shù)字,否則原樣輸出晾剖。

其次判斷:棋盤是否被填滿锉矢。被填滿時執(zhí)行 fail 函數(shù)。

最后判斷:是否勝利齿尽。如果獲勝沽损,打印獲勝提示。

def game(board, stdscr, rscore):
    global score
    global change
    
    # curses.noecho()
    # 屏幕不顯示用戶輸入的字符
    curses.noecho()
    while 1:
       
        # stdscr.getch()
        # 讀取用戶輸入的字符
        order = stdscr.getch()

        # move()對用戶移動的響應
        current_board, change = move(order, board)
        # change 為 1 隨機產(chǎn)生 2 或 4
        if change:
            current_board = choice(board)

        # 打印棋盤
        print_board(stdscr, current_board, rscore)

        # 當棋盤被填滿循头,判斷是否游戲結(jié)束
        if (current_board != 0).all():
            fail(current_board)

        # win 為 1 打印獲勝提示
        if win:
            stdscr.addstr('You win')

以上便是游戲主程序的基本邏輯绵估,接下來我們看具體的函數(shù)模塊。

首先是移動模塊:

basic 函數(shù)用來執(zhí)行移動與碰撞的操作卡骂。move_{up,down,right,left} 函數(shù)用來實現(xiàn)各個方向上的 basic 函數(shù)操作壹士。move 函數(shù)用來響應用戶指令,實現(xiàn)各個方向上的移動偿警。

棋盤由 4\times4 矩陣組成,0 代表該位置上沒有數(shù)字唯笙。basic 函數(shù)就是基于矩陣的運算螟蒸,且以右移為基礎(chǔ)移動。

4 \times 4 矩陣
\left[ \begin{matrix} &2&2&0&4&\\ &2&4&2&0&\\ &4&8&0&2&\\ &8&4&0&4&\\ \end{matrix} \right]

向右滑動:

每一周期分為 4 輪崩掘,每一輪操作一行(共 4 行)七嫌,從最左面的元素開始執(zhí)行。設(shè)置 flag 用于提示這一輪是否發(fā)生了改變苞慢,如果發(fā)生了改變诵原,這一輪就再進行一次循環(huán),直到 flag 保持為 0 不變挽放。對于循環(huán)的每一個元素绍赛,如果該元素不為 0 ,若下個元素為 0辑畦,就交換當前值與下個元素的值吗蚌。若下個元素與當前元素相同,則當前元素置 0 纯出,且下一個元素增加一倍蚯妇,分數(shù)還要增加 100 分敷燎。

舉個例子:對于第一行 [2 2 0 4]

第一輪:

  • 4 與 0 不交換 [2 2 0 4]
  • 0 與 2 交換 [2 0 2 4]
  • 0 與 2 交換 [0 2 2 4]
  • flag = 1 且 score + = 0

第二輪:

  • 4 與 2 不交換 [0 2 2 4]
  • a_{13} 雙倍 a_{12} 置 0 [0 0 4 4]
  • 0 不變 [0 0 4 4]
  • flag = 1 且 score += 100

第三輪:

  • a_{14} 雙倍 a_{13} 置 0 [0 0 0 8]
  • 不變 [0 0 0 8]
  • 不變 [0 0 0 8]
  • flag = 1 且 score += 100

第四輪:

  • 不變
  • 不變
  • 不變
  • flag = 0 且 score += 0

即第一輪最后輸出結(jié)果 [0 0 0 8]。

以上就是向右移動的操作箩言,而對于其他方向上的移動其實就是在此基礎(chǔ)上進行矩陣的轉(zhuǎn)置與逆置操作硬贯。

# A 為 4*4 的矩陣
# 轉(zhuǎn)置操作
A.T
# 逆置操作
A[::-1,::-1]

下圖為原矩陣:
\left[ \begin{matrix} &a_{11}&a_{12}&a_{13}&a_{14}& \\ &a_{21}&a_{22}&a_{23}&a_{24}& \\ &a_{31}&a_{32}&a_{33}&a_{34}& \\ &a_{41}&a_{42}&a_{43}&a_{44}& \\ \end{matrix} \right]

向下滑動:

將原矩陣轉(zhuǎn)置得到新矩陣,新矩陣向右滑動陨收,相當于原矩陣向下滑動饭豹,再轉(zhuǎn)置變回原矩陣。

\left[ \begin{matrix} &a_{11}&a_{21}&a_{31}&a_{41}& \\ &a_{12}&a_{22}&a_{32}&a_{42}& \\ &a_{13}&a_{23}&a_{33}&a_{43}& \\ &a_{14}&a_{24}&a_{34}&a_{44}& \\ \end{matrix} \right]

向左滑動:

將原矩陣逆置得到新矩陣畏吓,新矩陣向右滑動墨状,相當于原矩陣向左滑動,再逆置變回原矩陣菲饼。

\left[ \begin{matrix} &a_{44}&a_{43}&a_{42}&a_{41}& \\ &a_{34}&a_{33}&a_{32}&a_{31}& \\ &a_{24}&a_{23}&a_{22}&a_{21}& \\ &a_{14}&a_{13}&a_{12}&a_{11}& \\ \end{matrix} \right]

向上滑動:

將原矩陣轉(zhuǎn)置加逆置得到新矩陣肾砂,新矩陣向右滑動,相當于原矩陣向上滑動宏悦,再通過轉(zhuǎn)置加逆置變回原矩陣镐确。

\left[ \begin{matrix} &a_{44}&a_{34}&a_{24}&a_{14}& \\ &a_{43}&a_{33}&a_{23}&a_{13}& \\ &a_{42}&a_{32}&a_{22}&a_{12}& \\ &a_{41}&a_{31}&a_{21}&a_{11}& \\ \end{matrix} \right]

# 基礎(chǔ)移動
def basic(board):
    global score
    global win
    # 以右移為基礎(chǔ)移動

    for i in range(4):
        flag = 1
        while flag:
            flag = 0
            j = 2
            while j >= 0:
                if board[i, j] != 0:
                    if board[i, j + 1] == board[i, j]:
                        board[i, j + 1] = 2 * board[i, j]
                        if board[i, j + 1] == 2048:
                            win = 1
                        board[i, j] = 0
                        score += 100
                        flag = 1

                    elif board[i, j + 1] == 0:
                        temp = board[i, j]
                        board[i, j] = board[i, j + 1]
                        board[i, j + 1] = temp
                        flag = 1

                j -= 1
    return board


# 右移
def move_right(board):
    return basic(board)


# 上移
def move_up(board):
    # 逆置 + 轉(zhuǎn)置
    board = board[::-1, ::-1].T
    board = basic(board)
    board = board[::-1, ::-1].T
    return board


# 左移
def move_left(board):
    # 逆置
    board = board[::-1, ::-1]
    board = basic(board)
    board = board[::-1, ::-1]
    return board


# 下移
def move_down(board):
    # 轉(zhuǎn)置
    board = board.T
    board = basic(board)
    board = board.T
    return board


# 移動
def move(order, board):
    # ord 求碼值
    global score
    global win
    change = 1
    tempboard = copy.deepcopy(board)

    # 退出游戲
    if order == ord('q'):
        save_score(score)
        exit()
    # 重置游戲
    elif order == ord('r'):
        win = 0
        save_score(score)
        score = 0
        stdscr.clear()
        wrapper(main)
    # 勝利后,只有退出和重置游戲
    elif win:
        change = 0
        newboard = tempboard
        return newboard, change
    # 上下左右移動
    elif order == ord('w'):
        newboard = move_up(board)
    elif order == ord('s'):
        newboard = move_down(board)
    elif order == ord('a'):
        newboard = move_left(board)
    elif order == ord('d'):
        newboard = move_right(board)

    # 按其他鍵程序不響應
    else:
        newboard = board

    if (newboard == tempboard).all():
        change = 0

    return newboard, change

接下來饼煞,我們講 choice 模塊:首先獲取值為 0 的矩陣元素的位置源葫,并儲存在字典里,以序號( 最大值為 count ) 為索引砖瞧。其次產(chǎn)生 [0,count) 范圍內(nèi)的隨機數(shù)(隨機抽取值為 0 的元素)息堂,并且產(chǎn)生隨機數(shù) 2 或 4 (概率為 75% 與 25%)。最后將隨機抽取的元素更改為生成的隨機數(shù)(2 或 4)块促。

# 隨機產(chǎn)生 2 或 4
def choice(board):
    udict = {}
    # 統(tǒng)計0的個數(shù)
    count = 0
    for i in range(4):
        for j in range(4):
            # board[i,j] 為 0
            # eg. {0:(1,3),1:(2,1),3:(3,2)}
            # 根據(jù) key 可以獲得元素 0 在棋盤上的位置
            if not board[i, j]:
                udict[count] = (i, j)
                count += 1
    # np.random.randint(0, count)
    # 產(chǎn)生 [0,count) 范圍內(nèi)的隨機數(shù)
    random_number = np.random.randint(0, count)
    # np.random.choice([2,2,2,4])
    # 隨機選取列表 [2,2,2,4] 中的元素
    two_or_four = np.random.choice([2, 2, 2, 4])
    # 更改棋盤上 0 元素為隨機數(shù)
    board[udict[random_number]] = two_or_four
    return board

然后是生成分數(shù):

首先游戲開始時加載一次分數(shù)(歷史最高分)荣堰,游戲結(jié)束時保存最高分。每次打印棋盤前竭翠,都比較當前分數(shù)與當前最高分振坚,并更改當前最高分數(shù)。

# 加載最高分
def load_score():
    rank_score = np.load(FILENAME)
    return rank_score


# 保存最高分
def save_score(score):
    rscore = load_score()
    if score > rscore:
        np.save(FILENAME, score)

# 比較當前分數(shù)與當前最高分
def compare_score(score, rscore):
    if score > rscore:
        rscore = score
    return rscore

其次是打印模塊:

只打印非零值斋扰。

# 打印棋盤
def print_board(stdscr, board, rscore):
    global score
    rscore = compare_score(score, rscore)
    
# stdscr.clear()
# 清除屏幕
# stdsscr.addstr()
# 打印字符串
    stdscr.clear()
    stdscr.addstr('得分:' + str(score) + '\n')
    stdscr.addstr('歷史最高:' + str(rscore) + '\n')
    for i in range(4):
        stdscr.addstr('-' * 22 + '\n')
        for j in range(4):
            stdscr.addstr('|')
            if board[i, j]:
                stdscr.addstr('{:^4d}'.format(board[i, j]))
            else:
                stdscr.addstr('    '.format())
        stdscr.addstr('|')
        stdscr.addstr('\n')
    stdscr.addstr('-' * 22 + '\n')

最后是一些零碎的知識點:

首先我們要初始化程序渡八,初次運行游戲會在當前目錄生成 ‘out.npy’ 文件,并且儲存 0 在文本中传货。其次初始化棋盤屎鳍,最后就可以愉快地開始游戲了。

import numpy as np
import curses
import copy
import os
from curses import wrapper

stdscr = curses.initscr()
# 分數(shù)
score = 0
# 判斷是否獲勝
win = 0
#
FILENAME = 'out.npy'


# 初始化
def init():
    # 初始化棋盤
    # 初始棋盤 2 或 4 的隨機數(shù)字
    if FILENAME not in os.listdir():
        np.save(FILENAME, 0)
    init_board = choice(np.zeros((4, 4), dtype=np.int))
    return init_board


# 主程序
def main(stdscr):
    # 初始化程序
    init_board = init()
    rscore = load_score()
    # 打印棋盤
    print_board(stdscr, init_board, rscore)
    # 游戲主進程
    game(init_board, stdscr, rscore)


if __name__ == "__main__":
    wrapper(main)

以上便是 python 實現(xiàn) 2048 游戲的完結(jié)版问裕,如果想獲取源代碼哥艇,在微信后臺回復 2048 。

想了解更多請關(guān)注:

qrcode_for_gh_53af3cd256b6_258.jpg

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末僻澎,一起剝皮案震驚了整個濱河市貌踏,隨后出現(xiàn)的幾起案子十饥,更是在濱河造成了極大的恐慌,老刑警劉巖祖乳,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逗堵,死亡現(xiàn)場離奇詭異,居然都是意外死亡眷昆,警方通過查閱死者的電腦和手機蜒秤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亚斋,“玉大人作媚,你說我怎么就攤上這事∷Э” “怎么了纸泡?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赖瞒。 經(jīng)常有香客問我女揭,道長,這世上最難降的妖魔是什么栏饮? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任吧兔,我火速辦了婚禮,結(jié)果婚禮上袍嬉,老公的妹妹穿的比我還像新娘境蔼。我一直安慰自己,他們只是感情好伺通,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布欧穴。 她就那樣靜靜地躺著,像睡著了一般泵殴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拼苍,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天笑诅,我揣著相機與錄音,去河邊找鬼疮鲫。 笑死吆你,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的俊犯。 我是一名探鬼主播妇多,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼燕侠!你這毒婦竟也來了者祖?” 一聲冷哼從身側(cè)響起立莉,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎七问,沒想到半個月后蜓耻,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡械巡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年刹淌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讥耗。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡有勾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出古程,到底是詐尸還是另有隱情蔼卡,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布籍琳,位于F島的核電站菲宴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏趋急。R本人自食惡果不足惜喝峦,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呜达。 院中可真熱鬧谣蠢,春花似錦、人聲如沸查近。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霜威。三九已至谈喳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間戈泼,已是汗流浹背婿禽。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留大猛,地道東北人扭倾。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像挽绩,于是被迫代替她去往敵國和親膛壹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

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

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,103評論 1 32
  • 模塊考試肩民,期中考試,期終考試撬槽,中考此改,高考…… 研究生考試、公務(wù)員考試侄柔、事業(yè)編考試共啃、筆試、面試…… 提到考試暂题,我相信...
    五月荷閱讀 232評論 0 1
  • 每天吃晚飯時移剪,電視里面?zhèn)鞒鰜淼穆曇羰牵扒甑纫换匦秸撸纫换匕∽菘粒甑纫换兀覠o悔啊言津,是誰在耳邊說攻人,愛我永不變,只為...
    顧澄曦閱讀 1,103評論 0 3
  • 因為兩天前的那次大討論悬槽,全家最近都籠罩在一種不開心的氛圍之下怀吻,長輩們都在擔心如果我回家之后我一個人該怎么做?我...
    沐子蟬閱讀 171評論 0 0
  • 我不喜歡過夏天 暈沉沉的總覺得頭腦不清醒 但又喜歡水中美麗的荷花 恬靜端莊 看也看不夠靜靜地美 雨季總讓我上不來氣...
    泥巴團團閱讀 370評論 0 2