最近在搞一些我的世界指令。
其中有这么一个指令,需要编号系统,也就是 uid 。
本指令需要引用的模块:
意义
通常的编号系统中,当玩家进入或退出时,编号会从头重新分配。然而这样会导致编号不是很稳定。
比如编号的一个用途是用来传送特定玩家。按照通常的编号分配方法,如果你现在选中了一个玩家,马上要传送了。这个时候突然一个新玩家进来了,导致重新分配编号,有没有可能在你决定传送的下一秒钟,你选中玩家的编号就因为重新分配而改变,然后你就传送错人了呢?
对于这一类问题,最根本的解决方法,就是搞一个“稳定”的编号系统。当新玩家加入的时候,只为新玩家分配编号;而当有玩家退出时,也只尽量少地对玩家编号重新分配。
实现方法简述
我想到了一些稳定编号系统的实现,这里搭配图例说明:
稳定时的情况
比如现在有这么一个多人游戏,游戏里有 \(4\) 个人。
对于这个状况,用名为 ID
的计分板存储编号,每个人都被分配了从 \(0\) 开始数的编号,用 C-counter
的 ID
存储当前玩家数。
玩家加入
首先,我们考虑来了一个人。
鉴于这个人可能曾经进入过游戏,身上可能带着曾经被排的号,我们就用 \(n\) 来表示。
我们不难发现,在其他人编号不改变的情况下给他分号,他只能是 \(4\) 号。于是我们把 C-counter
的 ID
值赋给他的 ID
。
这样,一共就有 \(5\) 个玩家了。我们把 C-counter
的 ID
递增一次。
不论是什么命令,总是会有两次运行之间的延迟。那么对于有多个新玩家由于运行延迟而“同时”加入的情况怎么办呢?出于指令简单考虑,我们可以采用 @r
随机抽取,然后单独对抽到的每一个人都进行上边的操作。
玩家退出
玩家还有可能退出。我们先考虑普通玩家退出的情况。假如我们的 \(1\) 号玩家退出了。
那么我们发现,让编号最大的人的编号 \(3\) 变为退出的人的编号 \(1\) ,是改变编号最少的重排方法。而编号最大的人的编号就是 C-counter
的 ID
递减一次。
从 \(0\) 开始一个一个递增检测是否有对应编号的人,直到找到检测不到对应人的编号,那个编号就是退出的人的编号。
同时,除了普通玩家退出,还有编号最大的人退出的情况。这里假如我们的 \(3\) 号玩家退出了。
我们不难发现,这种情况只需要给 C-counter
的 ID
值递减一次就可以了。
那么对于有多个新玩家“同时”退出的情况怎么办呢?我们可以使用计分板存储退出的人数,而每次我们执行一遍上述操作,就给计分板递减一次,直到计分板为 \(0\) 时说明我们已经处理完了所有人的退出。
错误处理
考虑到这个系统比较的复杂且容错性较低,这里提供一套处理编号错误的方法。
首先,显而易见的是对于这个编号系统,可能出现的错误有三种:
C-counter
的ID
值出错,与房间实际玩家数量不相等。- 有两个人同时使用同一个编号。
- 有一个应该被实用的编号空着,没有人使用。
简单的错误处理
最简单的错误处理方法就是重新排。我们只需要把所有人都踢了,重设 C-counter
的 ID
值,再让他们回来,就可以处理一切错误了。
不过实际上不需要把所有人都踢了。如果是通过标签标记哪些人需要被分配编号的话,只需要把所有人的标签都取消,再重新打上标签,就能得到一样的效果。
显而易见的是这种错误处理方法比较的暴力,相当于从头重新分配一次编号,破坏了稳定性。
不过,当你认为房间编号系统基本不会受到干扰而出现错误的话,这确实是一个很容易实施的方法。
稳定的错误处理
考虑到房间里无形的各种干扰,定时处理一次错误是比较完善的做法,于是我们需要更稳定的错误处理方法来保证每次错误处理不会有太大的稳定性开销。
这里,我想到一种最稳定的做法。就是首先把 C-counter
的 ID
变成正确的玩家数量,再把需要重新分配编号的人一个一个挑出来插入到空编号里。
需要重新分配的编号的人可以分为两种。
-
和其他人使用同一个编号的人,也就是占用他人编号的人。
给编号为 \(n\) 的人全部打个标签,再随机从其中找一个人去掉标签。由此可通过标签获得占用了编号 \(n\) 的人。从 \(0\) 开始一个一个找,就能把这种人全找到。
-
编号大于等于
C-counter
的ID
值的人给所有人编号减去
C-counter
的ID
值,编号依然大于等于 \(0\) 的人就是这种人。
然后我们可以一个一个从这些人中抽人。抽着人给他加个标签,让编号为 \(n\) 的人给他去掉标签。如果标签没有被去掉,就把他的编号变成 \(n\) ,赋 C-lastVoid
的 ID
值为 \(n\) 来把他找到的空编号存起来。就这样让这个人从 \(0\) 开始一个一个寻找空编号。分配完他后,再从需要重新分配的编号的人中抽一个人,接着 C-lastVoid
的 ID
值继续寻找空编号。