python2048游戏代码分析&知识点总结

代码分析

状态机:

共分为四个状态:init,game, not game(win, gameover), exit(退出程序)

  • Init:

    1. game_field.reset()
    2. 返回game状态
  • Game:

    1. game_field.draw(stdscr)

    2. action = get_user_action(stdscr) 获取输入

    3. 根据action选择返回对应的状态

    4. 如果是restart就返回init

    5. exit则返回exit

    6. 根据action移动,再判断是赢是输返回对应状态

    7. 都不是,则返回game状态

  • Notgame:

    1. game_field.draw(stdscr)
    2. action = get_user_action(stdscr)
    3. 根据action选择返回对应的状态

主要操作:

主要操作为draw,move,get_user_action,is_win,is_gameover,主要都集成在game_field类里面

game_field:
  • init:height, width, win_value, high_score, score, reset()(生成初始的field)
  • Reset:
    1. 保留了最高分
    2. 将分数重置为0
    3. field数组重置为0
    4. 随机生成了2个位置的2或4
  • Draw:
    1. 清屏&输出当前分数&(如果最高分不为0,输出最高分)
    2. 根据field数组画图(draw_hor_separator(), draw_row(row))
    3. 判断是赢还是输,输出对应语句(is_win(),is_gameover())
  • Move:
    1. 定义了一个move[direction]的函数字典(move_row_left(row))
    2. Move_row_left(row): 定义了两个函数tighten()和merge()。tighten将非零元素删除,merge将相同元素相加。最后返回tighten(merge(tighten(row)))
    3. 判断是否可以移动,可以的话就移动,修改field并且返回True,否则返回False。(is_move_possible())
  • is_win&is_gameover:判断是否达到win_value|任意方向都不可移动
  • Spawn():在0的位置随机生成2或4
    1. 随机生成2或4
    2. 遍历field找到0的位置
    3. 随机选择0位置赋值随机数
Get_user_action():

获取用户输入,转换成right、left、up、down、quit和restart操作。

自己代码与原始代码的对比:

  • 两个对数组做转秩操作的函数其中的左右倒序:

    def reverse(array):
        return [list(reversed(row)) for row in array] # reversed(row)返回迭代器list_reverseiterator object 
        # return [row[::-1] for row in array]

    利用reversed函数返回的是迭代器类型,后续对转秩数组操作如果采用下标索引会出错。并且迭代器在使用一次list(reversed(row))后会消耗掉,在第二次使用的时候会重新创建一个新的迭代器,再次调用为空。感觉在除一次遍历之外尽量避免使用。

  • 移动数组将两个相邻且相等的元素合并:

    #原始代码:
    def merge(row):
        pair = False
        new_row = []
        for i in range(len(row)):
            if pair:
                new_row.append(2 * row[i])
                self.score += 2 * row[i]
                pair = False
                else:
                    if i + 1 < len(row) and row[i] == row[i + 1]:
                        pair = True
                        new_row.append(0)
                        else:
                            new_row.append(row[i])
                            assert len(new_row) == len(row)
                            return new_row

    用了两层if判断,一是与前一个元素相同,新数组写进两者之和,计分;二是与前一元素不同,再判断。如果与下一元素相同,新数组写0,修改标签;否则新数组写入当前元素。

    #我的代码:
    def merge(row): 
        flag=False
        new_row=[]
        for i in range(len(row)):
            try:
                if flag:
                    new_row[i-1]=0
                    new_row.append(row[i]*2)
                    self.score+=row[i]*2
                    flag=False
                    else:
                        flag=True if row[i]==row[i+1] else False
                        new_row.append(row[i])
                        except:
                            new_row.append(row[i])
                            return new_row

    一层if判断,区别在于与不考虑是否与后一元素是否相等,先写入原始元素并且判断,等到遍历到下一元素时再修改前一元素。

  • 判断赢了没:

    def is_win(self):
        return any([self.field[i][j]>= self.win_value for i in range(self.height) for j in range(self.width)])
    	# return any(any(i >= self.win_value for i in row) for row in self.field)

    要习惯用迭代值来遍历list

  • 画分隔符():

    def draw_hor_separator():
        line = '+' + ('+------' * self.width + '+')[1:]
        separator = defaultdict(lambda: line)
        if not hasattr(draw_hor_separator, "counter"):
            draw_hor_separator.counter = 0
            cast(separator[draw_hor_separator.counter])
            draw_hor_separator.counter += 1

    这里使用了一种[函数名].[变量名]的格式声明并引用变量counter,这种格式它意味着函数本身也是对象,也可以像其他对象一样拥有属性;在这种情况下,可能的目的是拥有一些其他语言所称的"静态"变量——一个对函数本身存在全局性的变量,而不是一个单独的调用。可以在外部调用。

    def example():
        pass
    example.foo ="hello"
    print(example.foo) 

    作者的意思大概是生成一个默认字典defauldict,每次修改counter值作为默认字典的输入,然后输出一行分隔符。但是好像没啥必要?反正最后都是要在循环中调用n次,为什么不直接输出一行分隔符呢?我的代码直接输出目前没发现什么问题。

  • 判断当前方向是否可移动中的某一行是否可移动:

    #原始代码
    def row_is_left_movable(row):
        def change(i):  # true if there'll be change in i-th tile
            if row[i] == 0 and row[i + 1] != 0:  # Move
                return True
            if row[i] != 0 and row[i + 1] == row[i]:  # Merge
                return True
            return False
        return any(change(i) for i in range(len(row) - 1))

    判别单个元素是否具备可移动的特征,但是其实可以写成某一行是否具备可移动特征。

    #我的代码
    def is_row_movable(row):
        if 0 in row:
            return True
        for i in range(self.width-1):
            if row[i]==row[i+1]:
                return True
            return False

    不用反复调用我觉得还挺简单的23333

  • 原始代码中move操作有一个返回值,是否移动成功。加了这个返回值就可以省略主程序中调用is_move_possible函数进行判断。

  • 原始代码中not_game在判断是win还是gameover时使用了默认字典。默认是当前状态,如果没有行为就会一直在当前界面循环。我是用if…elif…else实现的。感觉默认字典看起来要厉害一些233333

学到的知识点:

  • Python_函数做字典的值 :当需要用到3个及以上的if…elif…else时就要考虑该方法进行简化,通过将函数名称当做字典的值,利用字典的关键字查询,可以快速定位函数,然后执行

  • 默认字典:

    访问字典中某个‘键’时,若键不存在则会报错。解决方法有两种:字典自带的setdefault函数和collections模块的defaultdict函数。

    • 其中setdefault能为某一个键设置默认值,再次赋值可以被修改。

    • defaultdict可以为所有不存在的key设置一个默认值。
      defaultdict的构造函数接受一个工厂函数作为参数。一个类型名可以作为一个工厂函数,例如collections.defaultdict(int)默认返回0;collections.defaultdict(tuple)默认返回(); collections.defaultdict(str)默认返回空字符串。

      该函数在生成默认值时进行无参调用。因此如果使用类型名作为工厂函数,该类型名一定要具有无参构造函数。

      class Test:
          def __init__(self): #无参构造函数
              print("testing")
      dd = collections.defaultdict(Test) #使用自定义类型Test作为工厂函数
      print(dd['a']) #运行到这里输出testing
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值