一、完成2048游戏的开发

1、游戏整体思路

1)、绘制棋盘

  • 初始化棋盘的宽度
  • 当前得分,默认为0
  • 最高分
  • 初始化随机生成两个数,对为0的空格进行填充
  • 窗口绘制图形信息

2)、游戏的相关信息

  • 当前分数、最高分数
  • 上下左右建对空格内的数字进行移动
  • 判断是否可以向右移动
  • 是否可以向左只需要将向右进行反转
  • 向左移动时,每一行的数字都左对齐,并且如果有相邻的两个相同则最终等于他们之和
  • 向右、向下、向上与向左类似

3)、游戏结果以及游戏结束

  • 每移动一次分数累加
  • 当分数大于等于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, 游戏胜利
        :param data:
        :return:
        """
        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):
        """
        实现矩阵的转置
        :param data:
        :return:
        """
        return [list(row) for row in zip(*data)]

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

    def is_move_up(self, data):
        """
        判断2048能否向上移动
        :param data:
        :return:

        """
        transposeData = self.transpose(data)
        return self.is_move_left(transposeData)

    def is_move_down(self, data):
        """
        判断2048能否向下移动
        :param data:
        :return:
        """
        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, 2, 0]
             2-2). 依次循环判断两个数是否相等, 如果相等, 第一个元素*2, 第二个元素归0;   [4 0 4 0]
            :param row:
            :return:
            """
            # 比较的次数
            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):
            """将某一行数据向左移动"""
            # 三部曲
            # print(tight(merge(tight([2, 2, 4, 0]))))
            # print(tight(merge(tight([4, 0, 2, 4]))))
            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)