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

本文详细分析了Python实现的2048游戏代码,涉及状态机的设计,包括初始化、游戏进行、游戏结束和退出状态。重点介绍了game_field类中的draw、move、is_win、is_gameover等核心方法。同时,对比了原代码与个人实现的差异,如数组操作、合并逻辑和赢局判断,并分享了在Python中使用函数作为字典值和defaultdict的知识点。
摘要由CSDN通过智能技术生成

代码分析

状态机:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值