1763 字
9 分钟
--
编程农场:初见

#

今天,我尝试了编程农场这个有趣的游戏。

它是一个以类python语言为蓝本的自动化处理游戏,在游戏中,玩家需要收集资源,解锁科技树。

值得注意的是,在这个游戏中,性能也是很重要的一环,需要考虑游戏规则和无人机性能。

can_harvest()#

如果作物没有长好就直接选择收割,则什么也得不到,需要先进行判断:

if can_harvest():
harvest()

Tree#

种植树可以获得木头,但是如果树的十字格周围,则生长速度会降低:

树喜欢保持一定的空间,相邻的两棵树生长速度会减慢。每在其正北、正东、正西或正南方向地块上种植一棵树,都会使其生长时间翻倍。所以如果在每个地块上都种上树,它们的生长时间将是原来的 222*2 = 16 倍。

也就是,一棵树的周围,只能种别的,比如草。

看起来很复杂,其实很简单,只需要让 偶数行的奇数格奇数行的偶数格 种树就好了。

也就是:

#规定x,y是无人机的当前位置,可以使用get_post_xy获得,这里简化。
if x%2 == 0 and y %2 != 0:
if x%2 != 0 and y %2 == 0:

这么写不太优雅,我们可以优化成一条if

if (x%2) != (y%2):

原理很简单,奇数行的偶数格,和偶数行的奇数格,本质上是x和y的奇偶性相反。

种树和种胡萝卜,用key来进行解耦约束吧!#

因为我还要种胡萝卜,所以定义了一个key进行规范约束:

key = 3
while True:
# 如果在key以内,那么执行种树
if x <= key:
pass
# 否则,我们就执行种胡萝卜
else:
pass

pass有点类似godot,先跳过这里的执行,我们先不需要研究具体的细节。

种间种植:草与木#

在刚才的说明中,我们知道, Tree 的上下左右周围有别的树,效率会非常低,因此需要考虑种间种植。由于种植胡萝卜需要消耗草,权衡了一下,我打算种草。

种草非常简单,在土地上直接收割,会自动长出来。

# 没了!就这么多
if can_harvest():
harvest()

种树则相对复杂一些,由于我的无人机等级比较高,导致无人机一轮的时候,树木还没有成熟,会空转一轮(性能会被严重消耗),我们可以浇水让它长得更快:

if can_harvest():
harvest()
plant(Entities.Tree)
# 直接硬编码 0.8 进去,是比较不好的写法。后续维护会比较麻烦,我们可以定义一个water_key,但是这里就不多写了。
if get_water() < 0.8:
use_item(Items.Water)

初始化:让土地回归初始#

有时候,代码写错了,导致整个土地非常杂乱。

如果手动一个一个修改,就非常麻烦。我们需要编写一个初始化的代码,可以让所有地块回归到普通土地上。

首先,我们需要对整个农场进行遍历,这意味着未来农场大小更新了,这个代码依然是可以复用的(尽管效率不高!未来再优化吧!)

# 获得地图大小,比较有趣的是,在很多系统中的get size获取的值是一个二维数组或容器,例如[2,3],<2,3>
# 但是,编程农场的地图始终是正方形的(至少我目前玩到的部分是),因此get size获取的值,可以同时给x和y。
x_max = get_world_size()
y_max = get_world_size()

遍历:

for _ in range(y_max):
for _ in range(x_max):

for xxx in range 是一个python的语法,好吧,我在c++里可没见过。

它的完整写法是:for xxx in range(a,b,c) ,你或许注意到了 _ ,有点类似JavaScript的匿名函数,这是一个匿名变量,代表我们只使用它参与循坏,而不进行其他使用,非常方便。

这句代码的意思是,使一个变量(_)在遍历中,从a到b,每次+c

说白了,这不是就是 for( int x = a; x < b; x += c ) 吗?

其实我还是觉得c的语法比较优美,python系的语法看起来怪怪的(以及我感觉 ||or 帅多了!)

遍历完毕了!现在我们要进行初始化了,对于每一块土地,我们应该进行这样的判定:

  • 如果有作物->收获
  • 如果本格是耕作过的土地(Soil),进行耕作,恢复成原本的样子。

代码也非常简单:

x_max = get_world_size()
y_max = get_world_size()
while True:
for _ in range(y_max):
for _ in range(x_max):
x = get_pos_x()
y = get_pos_y()
if can_harvest():
harvest()
if get_ground_type() == Grounds.Soil:
till()
move(East)
if x >= x_max-1:
move(North)

不过,这段代码写的比较糟糕,维护性也比较差。

其实可以把固定的移动次数和地图大小完全解耦:

x_max = get_world_size()
y_max = get_world_size()
while True:
x = get_pos_x()
y = get_pos_y()
if can_harvest():
harvest()
if get_ground_type() == Grounds.Soil:
till()
move(East)
if x >= x_max-1:
move(North)

此外,AI给了另一种版本:

x_max = get_world_size()
y_max = get_world_size()
while True:
for y in range(y_max):
for x in range(x_max):
if can_harvest():
harvest()
if get_ground_type() == Grounds.Soil:
till()
if x < x_max-1:
move(East)
if y < y_max:
move(East)
move(North)

AI给的代码中,不仅减少了 get_world_size() 的调用频率(仅剩1次),而且对x和y的移动进行了解耦。

但是,这段代码就完全没有问题吗?

事实上,尽管性能更加出色,但我更喜欢上面的版本,因为每次都要获取x和y的实时位置,这意味着无论无人机在什么位置开始,位置都是正确的。

而第二个版本的代码,则严格要求必须在(0,0)开始才正确,否则会出错:

由于遍历是x_max次(即地图长度),在目前的地图中,固定为6次。但如果坐标是从(2,2)开始,那么在第一个循环中:

# 这里用了一种伪代码的写法:
# 在这个循环中,x需要位移6次
for y=0 in range(y_max):
for x=0 in range(x_max):
# 但实际上,2+6 = 2,进行完毕后x回到2
if x < x_max-1:
pass
# 但由于 move north嵌在x循环中,所以y的偏移也随着错了。
# 这意味着后续的所有x和y都会错位。
if y < y_max:
pass

而第一个版本的代码,每次循环的时候,都会立刻获取一次x和y的位置。并且使用匿名变量,不关心这个变量的变化,因此不受到影响。

而get size的性能开销,对于这个游戏来说可以完全忽略了。

附代码:自动化种间种植。#

# 先想办法获得地图的大小
x_max = get_world_size()
y_max = get_world_size()
key = 4
while True:
for y in range(y_max + 1):
for x in range(x_max + 1):
y = get_pos_y()
x = get_pos_x()
if x <= key:
if (x%2) != (y%2):
if can_harvest():
harvest()
plant(Entities.Tree)
if get_water() < 0.8:
use_item(Items.Water)
#elif y%2 == 0:
#if can_harvest():
#harvest()
#plant(Entities.Bush)
else:
if can_harvest():
harvest()
else:
if get_ground_type() != Grounds.Soil:
till()
if can_harvest():
harvest()
plant(Entities.Carrot)
if get_water() < 0.8:
use_item(Items.Water)
move(East)
if x >= x_max-1:
move(North)
编程农场:初见
https://vilstia.org/posts/技术文章/编程农场初见/
作者
琴泠 - Lumina Qin
发布于
2026-06-11
许可协议
CC BY-NC-SA 4.0