【心得】【指令】根據計分板給予玩家物品:「二分搜」vs「遞歸」

各位安安,我是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;
}
本文來自網路,不代表3樓貓立場,轉載請註明出處:https://www.3loumao.org/14619.html?variant=zh-tw
返回頂部