专栏首页Python高效编程python 实现 2048 游戏 (二)

python 实现 2048 游戏 (二)

上一篇文章中,我们梳理了实现简易版 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 游戏的完结版,如果想获取源代码,点击阅读原文即可。

阅读原文:https://github.com/xflywind/Python-Application

本文分享自微信公众号 - Python高效编程(gh_53af3cd256b6),作者:flywind

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-12-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 快来算一算你写了多少行代码

    前面我们一直在介绍图形界面,这次换个口味,在命令行实现代码行数统计程序。本程序共 135 行,其中 18 行空行、110 行有效代码、7行注释,大小为 7.71...

    用户2870857
  • Python 实现简易版成绩管理系统

    上一次,公众号介绍了如何使用 Python 实现单链表,下面让我们一探单链表的简单应用:在命令行,实现简易版成绩管理系统。

    用户2870857
  • 动画演示广度优先算法寻找最短路径

    上一节,我们刚刚介绍了使用深度优先算法(DFS)解决迷宫问题,这一节我们来介绍广度优先算法(BFS)。BFS 算法与 DFS 十分相似,唯一的区别就是 DFS ...

    用户2870857
  • leetcode419. Battleships in a Board

    假设有一个2D板,在板上用X表示战舰,已知板上任意两个战舰体之间一定会用.隔开,因此不会出现两个X相邻的情况。现在要求用O(N)的时间复杂度和O(1)的空间复杂...

    眯眯眼的猫头鹰
  • leetcode: 37. Sudoku Solver

    …and its solution numbers marked in red.

    JNingWei
  • Swift 有效的数独 - LeetCode

    判断一个数独是否有效,根据:Sudoku Puzzles - The Rules。 (数独规则: 每一行不能有重复的数字;每一列不能有重复的数字;将数独框划分...

    韦弦zhy
  • LeetCode 999. 车的可用捕获量

    在一个 8 x 8 的棋盘上,有一个白色车(rook)。也可能有空方块,白色的象(bishop)和黑色的卒(pawn)。它们分别以字符 “R”,“.”,“B” ...

    Michael阿明
  • leetcode: 79. Word Search

    JNingWei
  • LeetCode 130. 被围绕的区域(图的BFS/DFS)

    解释: 任何边界上的 ‘O’ 都不会被填充为 ‘X’。 任何不在边界上,或不与边界上的 ‘O’ 相连的 ‘O’ 最终都会被填充为 ‘X’。如果两个元素在水平或...

    Michael阿明
  • 2018最新区块链白皮书大全

    用户1408045

扫码关注云+社区

领取腾讯云代金券