NoneBot
NoneBot是基于酷Q的一个QQ机器人框架,其基本介绍和使用参见其文档。本段旨在补充一些目前在文档中还未点出的有关NoneBot的一些性质,它们在NG词语游戏插件的编写中是不可或缺的。
当QQ机器人接收到一条消息时,它会首先对消息进行parsing,解析出当前消息对应的指令和参数。随后,它会根据上下文——即CoolQ HTTP API中事件上报的数据的字典,包含消息和发信人的信息——中的发信人信息来生成一个唯一的ctx_id。这个ctx_id的作用是标记仍然有效的命令会话(如果是开始运行收到的第一条信息,那么不会有可用的命令会话)。NoneBot指令是要通过命令会话和用户进行数据交互的,而这里的意义是让同一个命令会话只响应ctx_id相同的用户的输入,也就是说两个ctx_id不同的用户调用同一条指令的时候会产生两个命令会话分别根据他们输入的参数实现不同的操作,从而不会互相干扰。默认的ctx_id是对每个用户都唯一的,这显然不利于我们的实现——我们希望多个玩家参与到这个游戏中来,要让同一个命令会话响应所有人发出的消息。好在ctx_id的实现中允许我们给来自同一个群的所有消息赋予同样的ctx_id,这需要更改NoneBot的源码,将nonebot\command\__init__.py中525行:
1 | ctx_id = context_id(ctx) |
改为:
1 | ctx_id = context_id(ctx, mode='group') |
如果没有获取到可用的命令会话,并且解析出来了消息对应的指令的话,那么将会创建一个新的命令会话,然后进行处理。至此我们可以暂时离开消息的解析和响应的层面,在处理命令的层面考虑机器人的逻辑。对于一个已经被正确识别的命令,命令会话将首先进入参数解析器arg_parser(没有arg_parser也可以),然后进入命令处理器。命令会话类CommandSession有几个属性和方法是需要用到的:
_current_arg_text:字符串,消息的纯文本部分,剥离了命令部分并去除前部分空格的消息。_state:字典类型,用于存储希望被保存在会话中的局部变量。send():文档中也介绍过,向当前会话发送信息(群聊或私聊),传入参数at_sender=True可以在消息首部@发送者。pause():正常情况下一个命令会话会在响应完用户的一条消息后就过期,但是如果调用了pause(),那么命令会话会被暂停并保存,接收到下一条ctx_id相同的用户(考虑到上一段的修改,即同一个群里的下一条消息或来自同一个人的私聊)时会被重新运行一次。is_first_run:bool类型,如果会话是第一次创建则为True,如果会话是调用了pause()被重新调起则为False。
以上并非是CommandSession提供的全部接口,但是对于实现NG词语游戏的插件已足够。
NG词语游戏的实现——基于状态机
NG词语游戏出自漫画《辉夜大小姐想让我告白~天才们的恋爱头脑战~》,规则为参与游戏的每个玩家为下一个玩家设置一个“NG词”, 设置完成后展示之,使得除了自己的每个人都能知道自己的NG词。之后所有玩家进行自由谈话,说出自己的NG词的玩家判负,退场。直至只剩一人为止。
在命令会话中实现的操作是获取局部变量->根据不同的游戏进程对用户的输入做出不同的回应->写回局部变量,这很容易让人联想到状态机,而NoneBot提供的框架对于基于状态机的实现也是很方便的,因此我们选择基于状态机实现。
游戏状态有三个:玩家注册状态,设置NG词状态,监听状态;状态跳转也是很简单的注册->设置->监听。状态机类NGFSM存储所有玩家的列表,每次接收发送者的信息,然后把要向会话(也就是群聊)和要私聊发送的信息返回给命令处理器。最初我是把状态机直接写在了命令处理器里,相比之下这样的交互方式可能有些冗杂,但是我认为游戏的逻辑实现部分和更底层一些的通信部分不应该耦合在一起,因此花了一些功夫把状态机单独提出来一个类。下面介绍每个状态的实现:
注册状态
这个状态要接收每个希望参与游戏的玩家的注册,规定注册需要回复sign in,响应所有注册的用户的回复,根据上下文记录QQ号、称呼信息,加到玩家列表中。另外,响应超级用户发出的stop signing,结束注册,状态跳到设置状态。
设置状态
由于游戏性质,设置NG词不能在群聊中进行,而是要和机器人私聊设置。这为实现带来了一个难点——设置NG词的命令会话和游戏的会话不能是一个,那么它们之间该如何通信呢?我的解决方法是单独设置另外一个命令用于设置NG词,这个命令响应玩家的设置之后将NG词存储在磁盘上,当所有玩家设置完成之后,由超级用户手动在群聊中发出check指令通知游戏会话已设置完毕,然后从磁盘中读取设置的数据,从而实现IO的同步。当然,这一过程可以足够用户友好——只要在进入设置状态前把群聊的号码记录到磁盘上,就可以在所有玩家都设置完之后主动调用CQHTTP API,自动向群聊里发送一条提醒消息。check之后,读出所有玩家的NG词,进入监听状态。
监听状态
这一状态中只需要监听玩家发出的消息,只要有人的消息中包含自己的NG词就向会话发出提醒信息,判该玩家出局,然后继续监听,直至只剩一人。
项目代码已上传到GitHub仓库NGWordGame。