代码分析

状态机:

共分为四个状态: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