各位安安,我是AC。
最近暑假到了,有些大佬在各个地方发了不同的教学文,于是小弟决定跟上这股风潮,发表一个常见问题的解决方案:
如果今天你要根据玩家的计分板给他们等量的物品,该怎么写?
举例:要如何根据玩家在diamond计分板上的分数,给予玩家钻石?
1. 递归法
递归就是调用自己,首先令所有玩家运行function:
execute as @a[scores={diamond=1..}] run function exchange:diamond
在function exchange:diamond中写下:
give @s diamond
scoreboard players remove @s diamond 1
execute if score @s diamond matches 1.. run function exchange:diamond
有没有效?当然有效,但是这会出现一个很大的问题,
那就是只要玩家的分数是多少,这个function就会运行多少次,所以运行次数会 = 分数 * 4 – 1 次。
更别说每个玩家都会有他自己的分数,假设服务器里有10个玩家,每个人都有100分,这样整体的运行次数就会 = (100 * 4 – 1 ) * 10 = 3990,全部都要在1 tick内完成。
如果玩家多起来、分数高起来、运行次数多起来,势必会对服务器造成不小的负担,要是再多一点,还有可能会触发maxCommandChainLength的上限-如果你不去调整它的话。
2.二分搜
不知道各位有没有发现过一件事情,当你将十进位自然数转换为二进位时,存在一些规律:
1的二进位:1
2的二进位:10
4的二进位:100
8的二进位:1000
16的二进位:10000
32的二进位:100000
64的二进位:1000000
128的二进位:10000000
没错,那就是2的0与自然数幂,在二进位时会呈现开头为1,后面为「次方个0」的情形。因此可以由此推断出:2的0与自然数幂可以组合出所有自然数。
随便以一个数字-1378为例,其二进位为10101100010,我们可以得知:
10101100010 = 10000000000 + 100000000 + 1000000 + 100000 + 10
换算成十进位就是:
1378 = 1024 + 256 + 64 + 32 + 2
于是乎,透过这道原理「2的0与自然数幂可以组合出所有自然数」,我们可以修改我们的指令为,在有限次数内运行结束的算法。
方法是,每次都对分数运行「减少最靠近的2的幂」,并给予相同数量的物品。
要注意的是,1.17后give设置了上限为100组,对64个堆栈成1组的物品而言,代表give最多只能给予6400个物品,这代表在我们的二分搜系统中,最多只能做到give @s diamond 4096。但没关系,只要多重复几次即可。
于是我们可以改写function exchange:diamond为下:
give @s[scores={diamond=4096..}] diamond 4096
scoreboard players remove @s[scores={diamond=4096..}] diamond 4096
give @s[scores={diamond=2048..}] diamond 2048
scoreboard players remove @s[scores={diamond=2048..}] diamond 2048
give @s[scores={diamond=1024..}] diamond 1024
scoreboard players remove @s[scores={diamond=1024..}] diamond 1024
give @s[scores={diamond=512..}] diamond 512
scoreboard players remove @s[scores={diamond=512..}] diamond 512
give @s[scores={diamond=256..}] diamond 256
scoreboard players remove @s[scores={diamond=256..}] diamond 256
give @s[scores={diamond=128..}] diamond 128
scoreboard players remove @s[scores={diamond=128..}] diamond 128
give @s[scores={diamond=64..}] diamond 64
scoreboard players remove @s[scores={diamond=64..}] diamond 64
give @s[scores={diamond=32..}] diamond 32
scoreboard players remove @s[scores={diamond=32..}] diamond 32
give @s[scores={diamond=16..}] diamond 16
scoreboard players remove @s[scores={diamond=16..}] diamond 16
give @s[scores={diamond=8..}] diamond 8
scoreboard players remove @s[scores={diamond=8..}] diamond 8
give @s[scores={diamond=4..}] diamond 4
scoreboard players remove @s[scores={diamond=4..}] diamond 4
give @s[scores={diamond=2..}] diamond 2
scoreboard players remove @s[scores={diamond=2..}] diamond 2
give @s[scores={diamond=1..}] diamond 1
scoreboard players remove @s[scores={diamond=1..}] diamond 1
这样所有指令就会在26步内走完。
回到我们服务器里有10个玩家,每个人都有100分的情况。由于运行次数一定是26次,整体运行次数就会变成:
26 * 10 = 260 ,跟递归的3990比起来差的多了。
当然你可以视情况增减指令,例如增加4096的次数,以处理分数超过8192的情形。或者是当确定分数不可能到达512时,将512以上的指令尽数删除等。
而如果你不嫌麻烦的话,也可以将每两行并成一个函数,例如前两行可改写成:
execute if score @s diamond matches 4096.. run function exchange:4096
而function exchange:4096内则写着:
give @s diamond 4096
scoreboard players remove @s diamond 4096
也是可以的。
3.比较
递归法撰写起来简单,而二分搜法除非用程序辅助,否则慢慢打是很累人的。
不过在数字高的时候,二分搜法由于运行次数固定不受分数高低影响,因此可以大大降低时间复杂度。但是在数字低的时候呢?
如果你确定递归法的运行次数一定会比二分搜法少(也就是每一人皆不会运行超过二分搜的次数),那么递归法其实是比较好的。以我们的例子而言,如果玩家的分数会被控制在6分以内,那么递归胜;否则,二分搜胜。
方法没有绝对,端看适用时机以及使用技巧。
小小文章,献丑了,若有需勘误之处,还请不吝指教。
谢谢大家。
附录
以下C语言代码范例说明指令系统的逻辑:
#include <time.h>
#include <stdlib.h>
int main(int argc, const char* argv[])
{
srand(clock());
int scoreboard = rand(); //diamond 计分板
int inventory = 0; //身上的钻石
int binary;
for (binary = 16384; binary; binary >>= 1) //16384 == (RAND_MAX >> 1) + 1
{
if (scoreboard >= binary)
{
inventory += binary;
scoreboard -= binary;
}
}
return 0;
}