利用Python编写2048游戏小项目 - Go语言中文社区

利用Python编写2048游戏小项目


2048游戏介绍

 《2048》作为一款风靡全球的益智类数字小游戏,应该大部分朋友都有玩过。《2048》是20岁的Gabriele Cirulli开发的一款数字游戏。初衷就是觉得好玩,在将其开源版本放到Github上后,意外走红。这款游戏的玩法很简单,每次可以选择上下左右滑动,每滑动一次,所有的数字方块都会往滑动的方向靠拢,系统也会在空白的地方乱数出现一个数字方块,相同数字的方块在靠拢、相撞时会相加。不断的叠加最终拼凑出2048这个数字就算成功。

官方2048网址:http://2048game.com/

我们将要实现的2048操作基本和原版类似,只不过我们会用上下左右键替换原版的上下左右滑动。 

游戏规则:

1.一开始方格内会出现2或者4这两个小数字;玩家只需要上下左右其中一个方向来移动出现的数字,所有的数字就会向对应的方向靠拢,而剩余的空白方块就会随机出现一个数字(2或4);

2.相同的数字相撞时会叠加靠拢,然后一直这样,不断的叠加最终拼凑出2048这个数字就算成功。

3.如果向上向下向左向右均不可移动,那么Game Over!否则游戏将继续执行!

 

接下来就开始分开多个步骤模块进行游戏的编写。

1.游戏方格的绘制 

import random

# 1.生成4*4的方格数据
# 方格宽度的变量值
width = 4
# 循环width(4)次, 生成列表, 列表元素为width(4)个
data = [[0 for j in range(width)] for i in range(width)]

# 2. 开始游戏时,随机生成2或者4, 2出现的几率比较大
def random_create():
    while True:
        # 获取随机修改信息的行数;
        row = random.randint(0, 3)
        # 获取随机修改信息的列数
        column = random.randint(0, 3)
        # 生成随机填充的值;2出现的几率比较大
        value = random.choice([2, 2, 2, 2, 2, 4])

        # 判断方格指定位置原先是否有数据, 如果有, 则继续随机获取行或者列; 如果没有, 填充信息;
        if data[row][column] == 0:
            data[row][column] = value
            break


# 3. 绘制方格的分隔符
def draw_sep():
    """+----+----+----+----+"""
    print("+----" * 4 + "+")


# 4. 绘制每一行的数据
def draw_one_row(row):
    # [0, 0, 2, 2], 如果有值, 内容填充
    # |     |     |     2   |   2   |
    for item in row:
        if item == 0:
            print('|    ', end='')
        else:
            print('|' + str(item).center(4), end='')
    print('|')

if __name__ == '__main__':
    random_create()
    random_create()
    print("开始游戏时, 方格的数据显示:")
    draw_sep()
    for row in data:
        draw_one_row(row)
        draw_sep()

运行后,可以看到游戏的方格已经画好了,如下:

2.判断方格的数字是否可以向左移动

在移动的时候,我们可能会想到下面几个问题:

问题一:如何判断方格中数字是否可以向左移动?
               只要方格的任意一行可以数字可以向左移动, 就返回True;

问题二:如何判断方格的一行数字是否可以向左移动?
               只要这一行的任意两个元素可以向左移动, 则返回True;

问题三: 如何判断两个元素可以向左移动?
                - 如果第一个数值为0, 第二个数值不为0, 则说明可以向左移动; eg:0  2,0  4 ,0  8
                - 如果第一个数值不为0, 第二个数值与第一个元素相等, 则说明可以向左移动;eg: 4  4,2  2

def is_row_left(row: list) -> bool:
    """用户传入一行数据, 判断是否可以向左移动, 返回Bool"""
    # 任意两个元素是否可以向左移动?
    def is_item_left(index):  # index指的是索引值
        """判断当前索引值和下一个索引值, 是否可以向左移动"""
        # - 如果第一个数值为0, 第二个数值不为0, 则说明可以向左移动; eg: 0 2  , 0 4 , 0 8
        if row[index] == 0 and row[index + 1] != 0:
            return True
        # - 如果第一个数值不为0, 第二个数值与第一个元素相等, 则说明可以向左移动;eg: 4 4, 2 2,
        if row[index] != 0 and row[index] == row[index+1]:
            return  True
        return  False

    # 只要这一行的任意两个元素可以向左移动, 认为这一行可以向左移动, 返回True;
    # any(): 传递可迭代对象, 如果有任意一个为True, 则返回True;
    # all(): 传递可迭代对象, 如果有任意一个为False, 则返回False;
    return  any([is_item_left(index) for index in range(3)])

def is_move_left(data):
    """判断方格数字是否可以向左移动"""
    return  any([is_row_left(row) for row in data])

 3.实现矩阵的反转和转置

def invert(data):
    """
    :param data: 方格中的数据
    :return: 反转后的方格数据
    """
    return  [row[::-1] for row in data]


def transpose(data):
    """
    实现矩阵的转置
    """
    return  [list(row) for row in  zip(*data)]

加入这两个操作可以大大节省我们的代码量,减少重复劳动。我们只需要编写数字向左移动的代码,在需要数字向右移动时,先将方格中的数字反转,再将其向左移动就会实现数字向右移动了;在需要数字向上移动时,先将方格中的数字进行转置,再将其向左移动就会实现数字向上移动了。

4.判断方格中数字是否可以向右向上向下方移动 

def is_move_right(data):
    """
    判断2048能否向右移动
    """
    invertData = invert(data)
    return  is_move_left(invertData)



def is_move_up(data):
    """
    判断2048能否向上移动
    """
    transposeData = transpose(data)
    return  is_move_left(transposeData)



def is_move_down(data):
    """
    判断2048能否向下移动
    源数据:
        [2, 0, 4, 2],
        [4, 0, 2, 2],
        [0, 0, 4, 2],
        [0, 0, 4, 2],


    处理之后: 判断是否可以向右移动, 也就是判断源数据是否可以向下移动.
        [2, 4, 0, 0],
        [0, 0, 0, 0],
        [4, 2, 4, 4],
        [2, 2, 2, 2]
    """
    transposeData = transpose(data)
    return  is_move_right(transposeData)

5.方格中的数字上下左右移动

def tight(row):
    """将这一行所有的非0数字向前放, 0向后放."""
    return sorted(row, key=lambda x: 1 if x == 0 else 0)

def merge(row):
    """
    依次循环判断两个数是否相等, 如果相等, 第一个元素*2, 第二个元素归0;   [4 0 4 0]
    """
    # 比较的次数
    for index in range(3):
        # 如果相等, 第一个元素*2, 第二个元素归0;
        if row[index] == row[index + 1]:
            # row[index] = row[index] * 2
            row[index] *= 2
            row[index + 1] = 0
    return row


def row_left(row):
    """将某一行数据向左移动"""
    # 三部曲
    return tight(merge(tight(row)))


def move_left(data):
    """2048整体向左移动"""
    return [row_left(row) for row in data]


def move_right(data):  # 反转====反转回来
    """
    2048整体向右移动
    源数据:
        [2 0 2 0]
        [0 0 0 0]
        [0 0 0 0]
        [0 0 0 0]


    反转
        [0 2 0 2]
        [0 0 0 0]
        [0 0 0 0]
        [0 0 0 0]

    向左移动
        [4 0 0 0]
        [0 0 0 0]
        [0 0 0 0]
        [0 0 0 0]


    反转:
        [0 0 0 4]
        [0 0 0 0]
        [0 0 0 0]
        [0 0 0 0]

    """
    invertData = invert(data)
    return invert(move_left(invertData))


def move_up(data):
    """2048整体向上移动"""
    transposeData = transpose(data)
    return transpose(move_left(transposeData))


def move_down(data):
    """2048整体向下移动"""
    transposeData = transpose(data)
    return transpose(move_right(transposeData))

6.游戏结束的判断

 问题一:何时用户游戏胜利?

                当棋盘中出现num=2048时, 则代表用户胜利。

问题二:何时game over?

               当用户在任何方向都不能移动时, 则代表游戏结束, 用户失败。               

from itertools import chain

def is_win(data):
    """
    2048里面有一个数字大于或者等于2048, 游戏胜利
    """
    #   将嵌套的元素连成一个列表
    # print(*data)  # 解包
    # print(list(chain(*data)))
    # maxNum = max(chain(*data))
    # print(maxNum)
    return max(chain(*data)) >= 2048

# 只要有任意一个方向可以移动, 那就没有结束
def is_gameover(data):
    return  not any([is_move_left(data), is_move_right(data),
                     is_move_up(data), is_move_down(data)])

7.获取键盘的输入

利用cursor模块中的getch()方法实现键盘输入的获取。

def get_user_action(stdscr):
        """获取用户输入的函数封装"""
        action = stdscr.getch()
        if action == curses.KEY_UP:
            return 'Up'
        elif action == curses.KEY_DOWN:
            return 'Down'
        elif action == curses.KEY_LEFT:
            return 'Left'
        elif action == curses.KEY_RIGHT:
            return 'Right'
        # ord方法查看字母对应的ACII
        elif action == ord('r'):
            return 'Restart'
        elif action == ord('e'):
            return 'Exit'

 2048完整代码

以上就是所有的2048游戏基本的步骤模块了,接下来,再通过主程序的编写和所有模块的连接,就可以实现最终的2048游戏了,拼接后的完整代码如下:

import random
import curses
from itertools import chain

class GameField(object):
    """游戏类"""
    # 初始化信息
    def __init__(self, width=4, height=4, win_value=2048):
        self.width = width  # 方格的宽度
        self.height = height
        self.win_value = win_value
        self.score = 0  # 当前得分, 默认为0
        self.highscore = 0  # 最高分
        self.is_moves = {
            'Up': self.is_move_up,
            'Down': self.is_move_down,
            'Left': self.is_move_left,
            'Right': self.is_move_right
        }

        self.moves = {
            'Up': self.move_up,
            'Down': self.move_down,
            'Left': self.move_left,
            'Right': self.move_right
        }

    def random_create(self):
        """在随机位置生成2或者4"""
        while True:
            # 获取随机修改信息的行数;
            row = random.randint(0, self.height - 1)
            # 获取随机修改信息的列数
            column = random.randint(0, self.width - 1)
            # 生成随机填充的值;2出现的几率比较大
            value = random.choice([2, 2, 2, 2, 2, 4])

            # 判断方格指定位置原先是否有数据, 如果有, 则继续随机获取行或者列; 如果没有, 填充信息;
            if self.data[row][column] == 0:
                self.data[row][column] = value
                break

    def reset(self):  # 重新开始2048游戏
        # 更新最高分
        if self.score > self.highscore:
            self.highscore = self.score
        self.score = 0
        self.data = [[0 for j in range(self.width)] for i in range(self.height)]
        self.random_create()
        self.random_create()

    def draw(self, stdscr):
        """接收一个参数stdscr, 标准屏幕, 将来在该屏幕上绘制数据"""
        # . 绘制棋盘的分隔符
        def draw_sep():
            """+----+----+----+----+"""
            stdscr.addstr("+----" * 4 + "+" + 'n')

        # . 绘制每一行的数据
        def draw_one_row(row):
            # [0, 0, 2, 2], 如果有值, 内容填充
            # |     |     |     2   |   2   |
            for item in row:
                if item == 0:
                    stdscr.addstr('|    ')
                else:
                    stdscr.addstr('|' + str(item).center(4))
            stdscr.addstr('|n')

        # *** 清空窗口绘制的图形信息
        stdscr.clear()
        stdscr.addstr("2048游戏".center(40, '*') + 'n')

        # 绘制方格
        draw_sep()
        for row in self.data:
            draw_one_row(row)
            draw_sep()

        # 游戏的相关信息
        stdscr.addstr("n当前分数: %s" % (self.score))
        stdscr.addstr("n当前最高分数: %s" % (self.highscore))
        stdscr.addstr("n游戏帮助:  上下左右键  E(xit)  R(estart)")

    def is_win(self):
        """
        2048里面有一个数字大于或者等于2048, 游戏胜利
        """
        return max(chain(*self.data)) >= 2048

    def is_gameover(self):
        return not any([self.is_move_left(self.data), self.is_move_right(self.data),
                        self.is_move_up(self.data), self.is_move_down(self.data)])

    # [0, 2, 0, 2]  ==>01 12 23
    @staticmethod
    def is_row_left(row: list) -> bool:
        """用户传入一行数据, 判断是否可以向左移动, 返回Bool"""

        # 任意两个元素是否可以向左移动?
        def is_item_left(index):  # index指的是索引值
            """判断当前索引值和下一个索引值, 是否可以向左移动"""
            # - 如果第一个数值为0, 第二个数值不为0, 则说明可以向左移动; eg: 0 2  , 0 4 , 0 8
            if row[index] == 0 and row[index + 1] != 0:
                return True
            # - 如果第一个数值不为0, 第二个数值与第一个元素相等, 则说明可以向左移动;eg: 4 4, 2 2,
            if row[index] != 0 and row[index] == row[index + 1]:
                return True
            return False
        # 只要这一行的任意两个元素可以向左移动, 认为这一行可以向左移动, 返回True;
        # any(): 传递可迭代对象, 如果有任意一个为True, 则返回True;
        # all(): 传递可迭代对象, 如果有任意一个为False, 则返回False;
        return any([is_item_left(index) for index in range(3)])

    def is_move_left(self, data):
        """判断方格数字是否可以向左移动"""
        return any([self.is_row_left(row) for row in data])

    @staticmethod
    def invert(data):
        """
        :param data: 方格的数据
        :return: 反转后的方格数据
        """
        return [row[::-1] for row in data]

    @staticmethod
    def transpose(data):
        """
        实现矩阵的转置
        """
        return [list(row) for row in zip(*data)]

    def is_move_right(self, data):
        """
        判断2048能否向右移动
        """
        invertData = self.invert(data)
        return self.is_move_left(invertData)

    def is_move_up(self, data):
        """
        判断2048能否向上移动
        """
        transposeData = self.transpose(data)
        return self.is_move_left(transposeData)

    def is_move_down(self, data):
        """
        判断2048能否向下移动
        """
        transposeData = self.transpose(data)
        return self.is_move_right(transposeData)

    def move_left(self, data):
        # 判断是否可以移动
        def tight(row):
            """将这一行所有的非0数字向前放, 0向后放."""
            return sorted(row, key=lambda x: 1 if x == 0 else 0)

        def merge(row):
            """
             依次循环判断两个数是否相等, 如果相等, 第一个元素*2, 第二个元素归0;   [4 0 4 0]

            """
            # 比较的次数
            for index in range(3):
                # 如果相等, 第一个元素*2, 第二个元素归0;
                if row[index] == row[index + 1]:
                    # row[index] = row[index] * 2
                    row[index] *= 2
                    row[index + 1] = 0
                    self.score += row[index]  #
            return row

        def row_left(row):
            """将某一行数据向左移动"""
            # 三部曲
            return tight(merge(tight(row)))

        return [row_left(row) for row in data]

    def move_right(self, data):  # 反转====反转回来
        invertData = self.invert(data)
        return self.invert(self.move_left(invertData))

    def move_up(self, data):
        """2048整体向上移动"""
        transposeData = self.transpose(data)
        return self.transpose(self.move_left(transposeData))

    def move_down(self, data):
        """2048整体向下移动"""
        transposeData = self.transpose(data)
        return self.transpose(self.move_right(transposeData))

    def move(self, direction):
        """
        1. 判断这个方向是否可以移动?
        2. 执行移动操作;
        3. 再随机生成一个2或者4
        :param direction:
        :return:
        """
        if direction in self.moves:
            if self.is_moves[direction](self.data):
                self.data = self.moves[direction](self.data)
                self.random_create()


# PEP8
def main(stdscr):
    gameObj = GameField(win_value=32)

    def get_user_action(stdscr):
        """获取用户输入的函数封装"""
        action = stdscr.getch()
        if action == curses.KEY_UP:
            return 'Up'
        elif action == curses.KEY_DOWN:
            return 'Down'
        elif action == curses.KEY_LEFT:
            return 'Left'
        elif action == curses.KEY_RIGHT:
            return 'Right'
        # ord方法查看字母对应的ACII
        elif action == ord('r'):
            return 'Restart'
        elif action == ord('e'):
            return 'Exit'

    def init():
        """初始化函数, 初始化结束后, 游戏进入‘Game’状态"""
        gameObj.reset()
        gameObj.draw(stdscr)
        return 'Game'

    def game():
        gameObj.draw(stdscr)
        action = get_user_action(stdscr)
        if action == 'Restart':
            return 'Init'
        elif action == 'Exit':
            return 'Exit'
        else:
            if gameObj.move(action):
                # 判断win or gameover?
                if gameObj.is_win():
                    return 'Win'
                elif gameObj.is_gameover():
                    return 'GameOver'
            return 'Game'

    def not_game():
        """当游戏状态为Win或者GameOver时执行的逻辑"""
        action = get_user_action(stdscr)
        if action == 'Restart':
            return 'Init'
        elif action == 'Exit':
            return 'Exit'

    state_dict = {
        'Init': init,
        'Game': game,
        'Win': not_game,
        'GameOver': not_game,
        'Exit': exit
    }
    state = 'Init'  # 刚开始玩游戏, 游戏状态为Init
    while True:
        state = state_dict.get(state)()

curses.wrapper(main)

程序运行后操作示例

由于curses模块只能获取在命令窗口的键盘输入,所以程序最终需要在Terminal中运行:

[kiosk@foundation ~]$ cd /home/kiosk/Desktop
[kiosk@foundation Desktop]$ python3 2048.py 

运行之后,界面如下:

按上键,向上移动:

按下键 ,向下移动:

按 左键,向左移动:

按右键,向右移动:

按 r 键,重新开始游戏:

需要退出时,就按下 e 键,即可退出。

一个粗糙的2048游戏就完成了,但是基本功能很完整,若发现有什么需要改进的地方,欢迎大家在评论区评论。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/Mr_fengzi/article/details/95169971
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-22 12:51:08
  • 阅读 ( 502 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢