目录

1. 简介

2. 从列到全场

2.1 遍历一列

2.2 重复多列

3. 蛇形走位

3.1 改变移动路径

3.2 改变种植判断依据

3.3 小挑战

4. 收割全场

5. 思考


1. 简介

在Python编程农场里提供生产木头的植物有两种:Bush(灌木)和Tree(树)

Tree 能提供更多的木头,但是每个 Tree 的东南西北(上下左右)四个方向上不能有其它 Tree ,每多一个 Tree ,这个 Tree 的生长时间就会翻一倍(x2):

很明显,这个植物主要是考察间隔种植,奇偶数判断,for 与 if 的结合使用。

想要全场种植生产木头,我们可以混合种植 Tree 和 Bush ,确保所有 Tree 的生长时间都没有被减慢。

也就是每个Tree的东南西北四个方向都没有其它 Tree 

2. 从列到全场

我们可以先在一列中实现行与行之间间隔种植 Tree 和 Bush ,然后将一列的算法重复到全场。

2.1 遍历一列

我们可以先种一列 Bush:

clear()
for i in range(get_world_size()):
	plant(Entities.Bush)
	move(North)

clear() 可以重置农场,确保从头开始

get_world_size() 可以获取(正方形)农场的边长

接下来我们可以利用无人机在农场中的坐标位置来判断种 Tree 还是 Bush

在同一列中,只有y坐标会发生变化,农场左下角是(0,0),向上(y)、向右(x)为正方向

我们让偶数编号的行种Bush:

如果一个数能被2整除(求余结果为0)那么他一定是偶数(包括0)

如果一个数对2求余结果为1,那么他一定是奇数

加上,让奇数编号的行种 Tree:

clear()
for i in range(get_world_size()):
	y = get_pos_y()
	if y % 2 == 0: 
		plant(Entities.Bush)
	if y % 2 == 1:
		plant(Entities.Tree)
	move(North)

为了让程序在第一个 if 成立的情况不用再次判断第2个 if ,一个数不可能同时是奇数和偶数,我们可以使用否则如果elif(else if在Python里的语法单词)

更进一步,由于我们的坐标只有整数,而一个整数肯定是奇数和偶数中的一种,所以我们可以使用否则else,进一步优化程序效率:

clear()
for i in range(get_world_size()):
	y = get_pos_y()
	if y % 2 == 0: 
		plant(Entities.Bush)
	else:
		plant(Entities.Tree)
	move(North)

2.2 重复多列

我们可以把这一列的算法重复应用到每一列,每次应用之前需要把无人机移动到目标列:

我们会发现每个 Tree 的东西方向会有其它Tree

因此我们在切换列之后还有对列进行一个判断,奇数列和偶数列执行的种植方案反过来,由于我们要判断列,所以要获取无人机的x坐标

大家自己手打练习一下 if-else 逻辑,我就不给你们复制粘贴啦🤪

3. 简洁、优化代码

在功能不变的情况下,如何减少代码的行数和字符数呢?

我们可以分析一下代码,这个程序的核心逻辑是对无人机的位置进行判断,根据不同位置种植不同类型的植物

可是获取位置,一定要用get_pos_x(),get_pos_y()吗?

Python的for循环,循环固定次数时用到的range函数,底层逻辑是range函数产生一个整数序列,如果只填一个参数A的话,这个整数数列是从0到A-1(即不包括A),也就是说在我们编写循环的时候,其实这两个循环变量和我们的x、y坐标值是重叠的。

也就是说如果我们从(0,0)出发,按照下面的程序移动:

clear()
for x in range(get_world_size()):
	for y in range(get_world_size()):
		move(North)
	move(East)

此时的循环迭代变量x,y和我们在最内层循环用get_pos_x(),get_pos_y()获得的值是一一对应的

这是一种符合逻辑的“巧合”,正好可以利用这一特性来简化我们的程序:

更进一步,我们的get_world_size() 函数使用了两次,且这个函数名字较长,函数值不会在程序执行时变化,所以我们可以把这个函数的结果保存到一个自定义的变量size里:

clear()
size = get_world_size()
for x in range(size):
	for y in range(size):
		if y % 2 == 0: 
			if x % 2 == 0:
				plant(Entities.Bush)
			else:
				plant(Entities.Tree)
		else:
			if x % 2 == 0:
				plant(Entities.Tree)
			else:
				plant(Entities.Bush)
		move(North)
	move(East)

3. 蛇形走位

我们前面的算法虽然简单,但是其实并没有充分利用这个无人机,每次无人机种植完一列以后,他都会从这一列的末尾飞回这一列的开头(因为我们在农场边界执行了move(North) ),在他飞行的过程中,什么也没做,这浪费了时间

很明显,种植完第一列之后,走红色路线效率更高。

3.1 改变移动路径

所以我们第一步是控制无人机的move在列的末尾时不执行,换句话说,如果没到列的末尾就move

并且在切换列后前进的方向也要改变(在North和South之间切换),可以利用列的位置信息判断。

偶数列move(North),奇数列move(South)

clear()
size = get_world_size()
for x in range(size):
	for y in range(size):
		if y % 2 == 0: 
			if x % 2 == 0:
				plant(Entities.Bush)
			else:
				plant(Entities.Tree)
		else:
			if x % 2 == 0:
				plant(Entities.Tree)
			else:
				plant(Entities.Bush)
		if y < size-1:
			if x % 2 == 0:
				move(North)
			else:
				move(South)
	move(East)

3.2 改变种植判断依据

但是这个时候虽然成功优化了移动效率,但是我们发现又出现了Tree的东西方向有其它Tree

这是为什么呢?

还记得我们之前利用for循环的迭代变量优化了程序吗?

我们之前说,这只是一种符合逻辑的“巧合”,事实上,这两个循环变量并不等同与无人机的坐标

也就是说,当我们采用S形走位时,如果无人机在奇数列,从上往下种植植物时,y迭代变量依然是从0开始依次被赋值的

当无人机从南向北飞行时:迭代y(0,1,2....,size-1)坐标y(0,1,2...,size-1)

当无人机从北向南飞行时:迭代y(0,1,2....,size-1)坐标y(size-1, size-2, size-3, ..., 0)

所以在S形走位的情况下,我们不能用迭代变量来作为种植植物类型的判断,得用真正的坐标:

注意:

虽然种植植物时是用坐标x,y来判断,但是我们之前设计移动路径时的逻辑是没到这一列的末尾时move,由于每一列我们move的方向不同,但是迭代的次数是相同的,所以相当于迭代次数没到最后一次时(j < size-1 或者 j != size-1)我们才move

clear()
size = get_world_size()
for i in range(size):
	for j in range(size):
		x = get_pos_x()
		y = get_pos_y()
		if y % 2 == 0: 
			if x % 2 == 0:
				plant(Entities.Bush)
			else:
				plant(Entities.Tree)
		else:
			if x % 2 == 0:
				plant(Entities.Tree)
			else:
				plant(Entities.Bush)
		if j < size-1:
			if x % 2 == 0:
				move(North)
			else:
				move(South)
	move(East)

3.3 小挑战

大家可以想一想怎么样在之前代码的基础上,只修改一点点(不添加代码),实现下面这个S路线

4. 收割全场

收割全场很简单,我们只需要在之前程序的基础上,把种植部分改成如果可以收割,那么收割、种植对应植物:

现在我们这个程序逻辑把种植和收割结合在一起,只要这个地上有植物可以收割我们就能无限循环下去,所以我们在clear()之后做一个空翻等待一会do_a_flip()

这样子所有Grass都生长好之后我们就可以无限循环收割并种植木头的程序了:

clear()
do_a_flip()
size = get_world_size()
while True:
	for i in range(size):
		for j in range(size):
			x = get_pos_x()
			y = get_pos_y()
			if can_harvest():
				harvest()
				# 种植部分:
				##########################
				if y % 2 == 0: 
					if x % 2 == 0:
						plant(Entities.Bush)
					else:
						plant(Entities.Tree)
				else:
					if x % 2 == 0:
						plant(Entities.Tree)
					else:
						plant(Entities.Bush)
				###########################
			if j < size-1:
				if x % 2 == 0:
					move(North)
				else:
					move(South)
		move(East)

最后,这个程序逻辑还有一个Bug,就是只有才农场边长为偶数时才能成功,边长为奇数时不能成功,这是因为我们开启了无限循环while True,当我们的程序启动无限循环迭代后,就要求无论农场边长为多少,在每次迭代循环开始时无人机都应该在(0,0)这个位置。

奇数边长的农场的最后一列一定是向上种植的,这就导致最后一个move(East)无法让无人机回到(0,0)

所以我们要添加一段逻辑,在全部for循环结束以后,即每次while迭代循环的最后,依据农场边长的奇偶性,如果是奇数那么向北移动一次(move(North) ),这样无人机由于超过边界,会自动飞回起点,可以衔接下一次循环。

最终代码:

clear()
do_a_flip()
size = get_world_size()
while True:
	for i in range(size):
		for j in range(size):
			x = get_pos_x()
			y = get_pos_y()
			if can_harvest():
				harvest()
				# 种植部分:
				##########################
				if y % 2 == 0: 
					if x % 2 == 0:
						plant(Entities.Bush)
					else:
						plant(Entities.Tree)
				else:
					if x % 2 == 0:
						plant(Entities.Tree)
					else:
						plant(Entities.Bush)
				###########################
			if j < size-1:
				if x % 2 == 0:
					move(North)
				else:
					move(South)
		move(East)
		
	if size % 2 == 1:
		move(North)

		

5. 巧算

种植部分,还有一种非常巧妙的方法(我学生告诉我的)

就是利用x,y坐标之和,我们来看一下坐标之和的规律:

大家有没有发现,所有奇数和偶数刚好是错开的,刚刚好符合Tree的种植规律

从任意格子出发,往东南西北这四个方向中的任意一个,移动一步,xy坐标的奇偶性一定会发生一次切换(因为这四个方向x或y坐标+1或-1),那么坐标之和只会是在原来的基础上±1

也许这就是游戏设计者设计时的想法?

也就是说如果x,y坐标之和奇数种Tree,偶数种Bush(或者反过来),可以让所有Tree生长速度都不被减慢

最终代码:

clear()
do_a_flip()
size = get_world_size()
while True:
	for i in range(size):
		for j in range(size):
			x = get_pos_x()
			y = get_pos_y()
			if can_harvest():
				harvest()
				# 种植部分:
				##########################
				if (x + y) % 2 == 0: 
					plant(Entities.Tree)
				else:
					plant(Entities.Bush)
				###########################
			if j < size-1:
				if x % 2 == 0:
					move(North)
				else:
					move(South)
		move(East)

大家可以对比一下:

6. 思考

其实现在这个算法还是有缺陷,因为每次迭代循环以后,无人机飞回起点的部分是什么也没做的,浪费了时间,理论上无人机的路线应该是一个动态旋转的S形,确保无人机每一步都会去下一个相邻的格子上判断植物生长状态并种植。

回到起点,形成闭环,也就是说4条路线构成一个完成的循环,大家可以思考一下有什么比较简洁的写法可以实现这个复杂的路线。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐