本帖最后由 switefaster 于 2019-8-15 16:44 编辑

各位好久不见,我又来水帖了
今天我们要解决一个老问题:如何将玩家 tp 到记分板指定的坐标
想必大部分人都会毫不犹豫地想到二分法(传统艺能)
然而二分 tp 法有一个相当明显的缺陷即玩家需要被多次 tp,如果正好途径大量未加载的区块就会卡到生活不能自理,这显然不是一个合格的 CBer 可以认可的结果,而如果想要使用实体作为 marker 传送,则又会遇上区块加载这个大坑。所以今天我们的目标是克服这个缺陷做到一次性 tp 到目标坐标。

算法设计

首先我们来考虑一个简单的解决方案:比如我们需要从数轴上的点 0 tp 到 100,我们可以设两点直接距离为 x 递减1并每次将坐标顺着 x 方向移动一格,当 x 达到 0 时则将玩家 tp 到达到的坐标。实现上我们可以使用 execute positioned 递归。


很显然这种算法的时间复杂度为 Θ(n),这似乎是个不错的解决方案,而事实上它确实是一个不错的解决方案。不过如果 x 过大,超过了 100000,这种算法可能就显得有些力不从心了。
作为合格的 CBer,我们怎能容忍问题出现在自己面前?
让我们思考这种算法的弊端出现在哪里?很显然,这种算法每一步的跨度实在是太小了。不管 x 是 1, 10 还是 1e9,每一步都只能位移 1 单位,慢到不能忍。
这时候我们会想:要是能每步能跨大一点,并且加起来准确地达到目标就好了!(ん?)
再详细一点,如果能表示成类似 a0*b0+a1*b1+...+an*bn 的形式就好了!
答案很明显,我们该使用进制模拟。在这里,我将使用二进制(通常主要是因为处理器对二进制的处理能力最强,然而对 mcfunc 而言这应该没太大影响,除非 mj 给记分板加个位运算)。
现在我们可以整理出我们的新算法了:
我们使用位运算每次取出表示距离的数字 d 的第 i 位(从左向右从0开始),若第 i 位为 1,则将坐标偏移 i^2 格,直到坐标达到目标位置为止。由此我们可以看出这种算法我们最多进行 d 的二进制表示中 1 的数量次位移,最多进行 log2(d)+1 次位运算,时间复杂度为Θ(log2(n)),比 Θ(n) 快不知道哪里去了。


至此,我们完成了对算法的初步设计。

MCFUNC 实现

事实上实现没有什么好讲的,只有几个需要注意的点:
1. 可以使用 (n / 2^i) % 2 来获取数字 n 第 i 位的内容(0 或 1)
2. 可以将以2为底的 n 次幂的指数和幂运算的结果存放于 as 中方便查找(然而实体多时拉低效率,不过可以少写个幂运算)
3. 需要预处理好的位移和幂的上界建议为 2^30,这主要是由于记分板最多只能存到 2^31-1
4. 获取距离之后要取绝对值,并且要记下符号,按着符号方向 tp
5. i 可以从大往小穷举,一达到目标就立即停止,这样通常会快一点
6. mcfunc 递归回溯的时候并不会帮你还原 scb 的值,所以请注意防止回溯时影响结果
嗯,其他也没什么好讲的,也就各种位移的 mcf 重复工作量比较大而已。由于文件过多我就不直接向这里贴代码了


关于本人提供的示例的说明:ftp:offset 内为各个 2^i 的位移
请设置 distance_x,y,z 为目标坐标随后执行 /function ftp:teleport

注意:我因为懒并没有实现上述注意点中的第 5 点




[groupid=546]Command Block Logic[/groupid]

fast_teleport.zip

43.87 KB, 下载次数: 8