代码分析
状态机:
共分为四个状态:init,game, not game(win, gameover), exit(退出程序)
Init:
game_field.reset()
返回game状态
Game:
game_field.draw(stdscr)
action = get_user_action(stdscr) 获取输入
根据action选择返回对应的状态
如果是restart就返回init
exit则返回exit
根据action移动,再判断是赢是输返回对应状态
都不是,则返回game状态
Notgame:
game_field.draw(stdscr)
action = get_user_action(stdscr)
根据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:
保留了最高分
将分数重置为0
field数组重置为0
随机生成了2个位置的2或4
Draw:
清屏&输出当前分数&(如果最高分不为0,输出最高分)
根据field数组画图(draw_hor_separator(), draw_row(row))
判断是赢还是输,输出对应语句(is_win(),is_gameover())
Move:
定义了一个move[direction]的函数字典(move_row_left(row))
Move_row_left(row): 定义了两个函数tighten()和merge()。tighten将非零元素删除,merge将相同元素相加。最后返回tighten(merge(tighten(row)))
判断是否可以移动,可以的话就移动,修改field并且返回True,否则返回False。(is_move_possible())
is_win&is_gameover:判断是否达到win_value|任意方向都不可移动
Spawn():在0的位置随机生成2或4
随机生成2或4
遍历field找到0的位置
随机选择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