歡迎光臨
每天分享高質量文章

python 實現 2048 遊戲 (二)

來源:Python高效程式設計

作者:flywind

我們之前梳理了實現簡易版 2048 遊戲的基本知識,這篇文章將介紹如何實現各個模組。換句話說,上一次我們確定了旅行的目的地,這一次就讓我們自由暢行在山間田野。

主程式,即 game 函式按部就班地向下執行,該判斷就判斷,然後執行相應函式。首先讀取使用者輸入,第一個判斷:是否移動數字,顯然要移動數字要滿足以下條件:

  • 使用者輸入小寫的 w s a d 對應上下左右

  • 該移動方向上允許移動

具體來說,移動方向最前面有空間或者有連續相同的數字。可以移動則執行 move 函式,併在棋盤上生成隨機數字,否則原樣輸出。

其次判斷:棋盤是否被填滿。被填滿時執行 fail 函式。

最後判斷:是否勝利。如果獲勝,列印獲勝提示。

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 隨機產生 2 或 4
        if change:
            current_board = choice(board)

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

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

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

首先是移動模組:

basic 函式用來執行移動與碰撞的操作。move_{up,down,right,left} 函式用來實現各個方向上的 basic 函式操作。move 函式用來響應使用者指令,實現各個方向上的移動。

棋盤由矩陣組成,0 代表該位置上沒有數字。basic 函式就是基於矩陣的運算,且以右移為基礎移動。

矩陣

向右滑動:

每一週期分為 4 輪,每一輪操作一行(共 4 行),從最左面的元素開始執行。設定 flag 用於提示這一輪是否發生了改變,如果發生了改變,這一輪就再進行一次迴圈,直到 flag 保持為 0 不變。對於迴圈的每一個元素,如果該元素不為 0 ,若下個元素為 0,就交換當前值與下個元素的值。若下個元素與當前元素相同,則當前元素置 0 ,且下一個元素增加一倍,分數還要增加 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]

  • 雙倍 置 0      [0 0 4 4]

  • 0 不變                        [0 0 4 4]

  • flag = 1 且 score += 100

第三輪:

  • 雙倍 置 0     [0 0 0 8]

  • 不變                          [0 0 0 8]

  • 不變                          [0 0 0 8]

  • flag = 1 且 score += 100

第四輪:

  • 不變

  • 不變

  • 不變

  • flag = 0 且 score += 0

即第一輪最後輸出結果 [0 0 0 8]。

以上就是向右移動的操作,而對於其他方向上的移動其實就是在此基礎上進行矩陣的轉置與逆置操作。

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

下圖為原矩陣:

向下滑動:

將原矩陣轉置得到新矩陣,新矩陣向右滑動,相當於原矩陣向下滑動,再轉置變回原矩陣。

向左滑動:

將原矩陣逆置得到新矩陣,新矩陣向右滑動,相當於原矩陣向左滑動,再逆置變回原矩陣。

向上滑動:

將原矩陣轉置加逆置得到新矩陣,新矩陣向右滑動,相當於原矩陣向上滑動,再透過轉置加逆置變回原矩陣。

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

    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):
    # 逆置 + 轉置
    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):
    # 轉置
    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 ) 為索引。其次產生 [0,count) 範圍內的隨機數(隨機抽取值為 0 的元素),並且產生隨機數 2 或 4 (機率為 75% 與 25%)。最後將隨機抽取的元素更改為生成的隨機數(2 或 4)。

# 隨機產生 2 或 4
def choice(board):
    udict = {}
    # 統計0的個數
    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)}
            # 根據 key 可以獲得元素 0 在棋盤上的位置
            if not board[i, j]:
                udict[count] = (i, j)
                count += 1
    # np.random.randint(0, count)
    # 產生 [0,count) 範圍內的隨機數
    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 元素為隨機數
    board[udict[random_number]] = two_or_four
    return board

然後是生成分數:

首先遊戲開始時載入一次分數(歷史最高分),遊戲結束時儲存最高分。每次列印棋盤前,都比較當前分數與當前最高分,並更改當前最高分數。

# 載入最高分
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)

# 比較當前分數與當前最高分
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()
# 分數
score = 0
# 判斷是否獲勝
win = 0
#
FILENAME = 'out.npy'


# 初始化
def init():
    # 初始化棋盤
    # 初始棋盤 2 或 4 的隨機數字
    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 實現 2048 遊戲的完結版

贊(0)

分享創造快樂