python 实现 2048 游戏 (二)

上一篇文章中,我们梳理了实现简易版 2048 游戏的基本知识,这篇文章将介绍如何实现各个模块。换句话说,上一次我们确定了旅行的目的地,这一次就让我们自由畅行在山间田野。


流程一.png

游戏主程序,即 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 函数用来响应用户指令,实现各个方向上的移动。

棋盘由 4\times4 矩阵组成,0 代表该位置上没有数字。basic 函数就是基于矩阵的运算,且以右移为基础移动。

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 行),从最左面的元素开始执行。设置 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]
  • 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

即第一轮最后输出结果 [0 0 0 8]。

以上就是向右移动的操作,而对于其他方向上的移动其实就是在此基础上进行矩阵的转置与逆置操作。

# A 为 4*4 的矩阵
# 转置操作
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]

向下滑动:

将原矩阵转置得到新矩阵,新矩阵向右滑动,相当于原矩阵向下滑动,再转置变回原矩阵。

\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]

向上滑动:

将原矩阵转置加逆置得到新矩阵,新矩阵向右滑动,相当于原矩阵向上滑动,再通过转置加逆置变回原矩阵。

\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]

# 基础移动
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 游戏的完结版,如果想获取源代码,在微信后台回复 2048 。

想了解更多请关注:

qrcode_for_gh_53af3cd256b6_258.jpg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,105评论 1 32
  • 模块考试,期中考试,期终考试,中考,高考…… 研究生考试、公务员考试、事业编考试、笔试、面试…… 提到考试,我相信...
    五月荷阅读 234评论 0 1
  • 每天吃晚饭时,电视里面传出来的声音是,“千年等一回,等一回啊,千年等一回,我无悔啊,是谁在耳边说,爱我永不变,只为...
    顾澄曦阅读 1,103评论 0 3
  • 因为两天前的那次大讨论,全家最近都笼罩在一种不开心的氛围之下,长辈们都在担心如果我回家之后我一个人该怎么做?我...
    沐子蝉阅读 171评论 0 0
  • 我不喜欢过夏天 晕沉沉的总觉得头脑不清醒 但又喜欢水中美丽的荷花 恬静端庄 看也看不够静静地美 雨季总让我上不来气...
    泥巴团团阅读 370评论 0 2