道山神連的博客
0%

配置Anaconda和Jupyter Notebook

在清华tuna镜像上下载anaconda的linux安装包,上传到服务器上,使用命令:

1
bash Anaconda3-2019.10-Linux-x86_64.sh

安装。(直接用./xxx.sh运行会提示权限不够,但并不需要sudo)安装过程中可以指定安装到的目录,我这里指定了~/data/

安装完成后还不能找到conda指令,需要运行命令:

1
2
echo 'export PATH="~/data/anaconda3/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

添加环境变量。现在就可以使用conda命令了。

接下来需要配置Jupyter,生成并修改配置文件,其方法和在Docker上部署Jupyter Notebook的第二节一样(不必用pip安装jupyter,anaconda已经含了)。

在本地浏览器访问

配置好环境之后,让jupyter服务器端在后台运行:

1
nohup jupyter notebook --allow-root > ~/data/jupyter.log

如果不输出日志,可以设置为:
1
nohup jupyter notebook >/dev/null 2>&1

nohup表示不挂起,在退出ssh终端后也会继续运行。jupyter的终端输出会记录在~/data/jupyter.log中。

在本地运行:
ssh -N -f -L localhost:8888:localhost:8888 -p 22 remote_user@remote_host
把远程主机的8888端口映射到本地的8888端口。在浏览器中输入localhost:8888即可访问jupyter服务。

(也可以使用Xshell,连接上远程主机后在文件-属性中选择类别:连接-SSH-隧道,如下添加:

之后在浏览器中输入localhost:8888访问。

由于要用到只能在Linux上运行的python包,又懒得装双系统,就了解了一下Docker,配置了一个能在Windows上用的Jupyter Notebook环境。

Docker上Ubuntu的基础配置

去官网上下载一个Docker Desktop,然后pull一个ubuntu镜像。刚pull下来的ubuntu镜像里什么必要的工具也没有,需要自己apt-get安装。(改软件源的配置文件时需要注意按照对应的系统版本去搜索配置文件的写法,否则apt-get会报错)

安装python, python3, pip, pip3, vim等必要软件。

配置Jupyter Notebook

参考了这篇博客

首先安装Jupyter(我的电脑上pip3 config似乎有问题):

1
pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple jupyter

以及其他必要的第三方库。
然后生成Jupyter配置文件:
1
jupyter notebook --generate-config

修改配置文件~/.jupyter/jupyter_notebook_config.py中的如下几项:
1
2
3
c.NotebookApp.open_browser = False
c.NotebookApp.ip = '*'
c.NotebookApp.port = 8888

设置密码:
1
jupyter notebook password

然后把当前容器commit到notebook镜像。

需要启动Jupyter Notebook时,用如下命令启动notebook容器:

1
docker run -d --name jupyter_pycbc -p 8888:8888 -v //D/DockerShare/:/home/FromHost notebook jupyter notebook --allow-root /home

各参数的含义为:

  • -d:在后台运行
  • -p:将容器的8888端口映射到宿主机的8888端口
  • -v:将容器的/home/FromHost目录挂载到宿主机的D:DockerShare目录下。注意Windows中D盘DockerShare文件夹的表示方式为//D/DockerShare

在宿主机访问localhost:8888即可使用Jupyter Notebook。

配置多个python

参考了这篇博客

之前配置的Jupyter Notebook只能使用python3,现在要同时使用python2和python3,需要进一步配置。

这只需添加python2的ipython kernel:

1
2
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple ipykernel
python -m ipykernel install --user --name python27 --display-name "python27"

再次启动Jupyter Notebook,即可看到两个python。

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