本节最后修改于 2022 / 11 / 12
上节中我们讲了带参数调用MP模块。有人立马会想到:可调用 + 带参数 = 能递归。
——递归是什么?其实就是自己调用自己。那不就成死循环了吗?其实不是,因为递归到了一定程度会停止继续递归,这个程度叫边界条件。之所以在某种程度上可以说:可调用 + 带参数 = 能递归,是因为“可调用”意味着能自己调用自己;“带参数”意味着能判断边界条件来防止变成死循环——
调用MP模块实际上就是满足某个条件,而MP模块会循环检查这个条件,根据其是否满足来决定是否执行命令。所以MP模块的递归其实就是在执行完一次命令后,条件仍然满足自己再执行一次。直到MP模块遇到边界条件才会修改实体使其不满足激活条件。
但是由于MP模块的参数是“全局”的,而基岩版我的世界中又没有二维的计分板没法实现计分板堆栈,MP模块调用自己会丢失现在的计分板参数,所以只能实现尾递归或者用一些奇技淫巧。
定量给东西可以说是最简单的递归形式了。它不需要返回计分板值,而且貌似很有用,可以说是非常经典了。
# 例子
# 实现一个定量给鸡蛋模块,入口计分板为GE_eggNeed
# 对于一个GE_eggNeed的值为n的人,在运行结束后要让他的背包里有n个鸡蛋
[+,X,-,0] give @a[scores={GE_eggNeed=1..}] egg
[+,L,-,0] scoreboard players remove @a[scores={GE_eggNeed=1..}] GE_eggNeed 1
[+,L,-,0] scoreboard players reset @a[scores={GE_eggNeed=0}] GE_eggNeed
定量给东西有二分法的实现,感兴趣的读者可以了解一下,在此我们就不对其深入研究了。
除了给鸡蛋这种简单的没有返回值的MP模块,修改计分板这种有返回值的MP模块更复杂一些。例如获取数列特定项的MP模块。
等差数列意思就是一个数列,任意相邻两项的差相同。例如
求数列第
一种是把
但是世界上有很多数列并没有通项公式,这时可以使用第二种方法叫递推公式。递推公式就是根据前面项得出后面项的公式。例如
这就是一种递归,我们在不断的调用递推公式,只不过每次的参数都不一样:第一次我们代入的
我们要练习MP模块的递归,于是我们就假装不知道等差数列的通项公式吧。
# 例子
# 实现一个等差数列模块,入口计分版为AP_n,出口计分板为AP_value
# 对于一个AP_n的值为n的人,要使其AP_value为数列5,4,3,2,1,...的第n项
[+,X,-,0] tag @a[scores={AP_nNow=1..}] add AP_inited //初始化部分开始
[+,L,-,0] scoreboard players set @a[scores={AP_n=1..},tag=!AP_inited] AP_value 5 //还没开始算的人的值为5
[+,L,-,0] scoreboard players set @a[scores={AP_n=1..},tag=!AP_inited] AP_nNow 1 //还没开始算的人现在算到了第1项
[+,L,-,0] tag @a[tag=AP_inited] remove AP_inited //初始化部分结束
[+,L,-,0] execute @a[scores={AP_n=1..}] ~~~ scoreboard players operation @s AP_diff = @s AP_n //边界处理部分开始
[+,L,-,0] execute @a[scores={AP_n=1..}] ~~~ scoreboard players operation @s AP_diff -= @s AP_nNow //如果AP_diff为0说明已经算到了该算到的项,就是算完了
[+,L,-,0] scoreboard players set @a[scores={AP_diff=0}] AP_n 0 //算完的人清空入口计分板
[+,L,-,0] scoreboard players reset @a[scores={AP_diff=0}] AP_nNow //算完的人重置临时计分板
[+,L,-,0] scoreboard players reset @a[scores={AP_diff=0..}] AP_diff //重置临时计分板,边界处理部分结束
[+,L,-,0] scoreboard players remove @a[scores={AP_n=1..}] AP_value 1 //正在算的人的值-1
[+,L,-,0] scoreboard players add @a[scores={AP_n=1..}] AP_nNow 1 //正在算的人多算了一项
这个模块的调用方法:给实体设置AP_n
分数。获取返回值方法:当实体AP_n=0
时,获取实体AP_value
的分数并重置AP_value
和AP_n
。
用点奇技淫巧也可以做出不是尾递归的递归。
众所周知,斐波那契数列
不难发现,斐波那契数列不能只通过上一项来求出这一项,所以无法简单地写成尾递归的形式。这里我们采用的奇技淫巧是使用两个计分板,一个存储上一项的值,一个存储上上一项的值。我们将这一项求出来后,将这一项的值覆盖到存储上上一项的计分板上。这样子对于下一次调用模块来说,这两个计分板仍然是一个存储上一项,一个存储上上一项。根据调用次数的奇偶,两个计分板,哪个是上一项哪个是上上一项也不同。这是使用有限的计分板把结果存储了起来。
# 例子
# 实现一个斐波那契模块,入口计分板为Fi_n,出口计分板为Fi_value
# 对于一个Fi_n的值为n的人,要使其Fi_value为斐波那契数列的第n项
# 玩家_2的计分板C_num的值为2,用以取模判断奇偶
[+,X,-,0] tag @a[scores={Fi_nNow=1..}] add Fi_inited //初始化部分开始
[+,L,-,0] scoreboard players set @a[scores={Fi_n=1..},tag=!Fi_inited] Fi_valueAno 1
[+,L,-,0] scoreboard players set @a[scores={Fi_n=1..},tag=!Fi_inited] Fi_value 1 //还没开始算的人的前面两项都为1
[+,L,-,0] scoreboard players set @a[scores={Fi_n=1..},tag=!Fi_inited] Fi_nNow 2 //还没开始算的人现在算到了第2项
[+,L,-,0] tag @a[tag=Fi_inited] remove Fi_inited //初始化部分结束
[+,L,-,0] execute @a[scores={Fi_n=1..}] ~~~ scoreboard players operation @s Fi_type = @s Fi_nNow //获取调用奇偶部分开始
[+,L,-,0] execute @a[scores={Fi_n=1..}] ~~~ scoreboard players operation @s Fi_type %= _2 C_num //获取调用奇偶部分结束
[+,L,-,0] execute @a[scores={Fi_n=1..}] ~~~ scoreboard players operation @s Fi_diff = @s Fi_n //边界处理部分开始
[+,L,-,0] execute @a[scores={Fi_n=1..}] ~~~ scoreboard players operation @s Fi_diff -= @s Fi_nNow //如果Fi_diff为0说明已经算到了该算到的项,就是算完了
[+,L,-,0] scoreboard players set @a[scores={Fi_diff=..0}] Fi_n 0 //算完的人清空入口计分板
[+,L,-,0] scoreboard players reset @a[scores={Fi_diff=..0}] Fi_nNow //算完的人重置临时计分板
[+,L,-,0] execute @a[scores={Fi_diff=..0,Fi_type=0}] ~~~ scoreboard players operation @s Fi_value = @s Fi_valueAno //偶数调用次数的人设置Fi_value为真正的上一项的值
[+,L,-,0] scoreboard players reset @a[scores={Fi_diff=..0}] Fi_valueAno //重置临时计分板
[+,L,-,0] scoreboard players reset @a[scores={Fi_diff=-1..}] Fi_diff //重置临时计分板,边界处理部分结束
[+,L,-,0] execute @a[scores={Fi_n=1..,Fi_type=0}] ~~~ scoreboard players operation @s Fi_value += @s Fi_valueAno //正在算的偶数调用次数的人覆盖Fi_value为这一项的值
[+,L,-,0] execute @a[scores={Fi_n=1..,Fi_type=1}] ~~~ scoreboard players operation @s Fi_valueAno += @s Fi_value //正在算的奇数调用次数的人覆盖Fi_valueAno为这一项的值
[+,L,-,0] scoreboard players add @a[scores={Fi_n=1..}] Fi_nNow 1 //正在算的人多算了一项
[+,L,-,0] scoreboard players reset @a[scores={Fi_type=0..}] Fi_type //重置临时计分板
这个模块的调用方法:给实体设置Fi_n
分数。获取返回值方法:当实体Fi_n=0
时,获取实体Fi_value
的分数并重置Fi_value
和Fi_n
。