# Entropy | 熵 Rust Game. 一个Rust小游戏。 --- --- --- # 入门 欢迎游玩Entropy! 目前游戏以HTTP API的格式提供服务,Python SDK以及更多客户端正在开发中。 首先,你需要连接到游戏服务器,并注册一个账号。 ## 注册 ### API端点 `POST /player` ### 请求内容 请求不需要鉴权;请求需附带json格式的参数表,详情如下: | 参数名 | 类型 | 描述 | | -------- | ------ | ------ | | name | String | 用户名 | | password | String | 密码 | ### 返回内容 不出意外的话,服务器返回状态码200,附带内容为一个玩家对象的所有信息,格式为json: | 返回值 | 类型 | 描述 | | -------- | ------ | -------------------------- | | id | int | 身份号,用以登陆的唯一编号 | | name | String | 用户名,对外展示的名字 | | password | String | 密码 | ### 例子 使用`helloworld`作为用户名,`123456`作为密码(仅仅演示用,非常不推荐简单的密码)注册一个账号,则:对`/player`端点`POST`方法发起请求,并附带信息Body: ```json { "name": "helloworld", "password": "helloworld" } ``` 服务器返回状态码200,并返回内容: ```json { "id": 449, "name": "helloworld", "password": "helloworld" } ``` 服务器返回的ID和密码请一定记录好,这就是登入的唯一凭证。 ## 获取玩家信息🔒 ## API端点 `GET /player` 是的没错,和注册是同一个端口,但是请求方法变为了`GET`。 这是一个需要鉴权的接口,在请求的同时需要提供用户ID和密码,否则服务器将返回400错误 ### 鉴权相关 服务器采用HTTP协议的Basic基础鉴权方案,在每一次请求中附带用户ID和密码,如果访问需要鉴权的接口但未提供相应的信息时,服务器会返回400错误。 关于更多鉴权请参阅[MDN的文档](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Authentication) ### 请求内容 GET请求无需附带请求体,鉴权内容中已附带玩家账号信息 ### 返回内容 如果鉴权成功,则返回玩家的所有信息 | 返回值 | 类型 | 描述 | | -------- | ------ | -------------------------- | | id | int | 身份号,用以登陆的唯一编号 | | name | String | 用户名,对外展示的名字 | | password | String | 密码 | 返回内容与同URI的POST方法接口完全一致,这都是数据库玩家表包含的字段 ## 创建第一个Guest🔒 人生旅程,皆为过客。一个玩家可以控制多个实体角色,在Entropy游戏中,这些实体被称作**Guest**,这个特殊称谓取代了Character,一定程度上减小了代码量,同时也是参考了虚拟机系统中的Server-Client-Guest的三级名称。 一个玩家注册时是没有控制Guest的,常识地讲,玩家需要一个Guest来进行游玩,为此需要访问`/player/guest/spawn`端口,这个端口也是需要使用ID和密码鉴权的。 ### API端点 `GET /player/guest/spawn` 我们从`/player`深入,来到了`/player/guest`,这代表着玩家的Guest操作,创建操作被命名为spawn,似乎很常规 ### 鉴权相关 该端点需要鉴权,方式同上 ### 请求内容 GET请求无需附带请求体,鉴权内容中已附带玩家账号信息 ### 返回内容 如果鉴权成功,则立即创建一个Guest,并将这个Guest的主人设置为请求的玩家,完成这一切后返回新创建的Guest的信息。 每个玩家通过这个端点创生的第一个Guest是**免费的**,没有任何开销。但这样的spawn也只有一次机会。 如果玩家在已有guest的情况下再次调用这个端点,则会报错。 稍后会介绍其他创建更多Guest的方法。 | 返回值 | 类型 | 描述 | | ----------- | --------- | ----------------------------------------- | | id | int | Guest的身份号,用以查询、控制等的唯一编号 | | energy | int | 能量,一种收集物 | | pos | (int,int) | Guest的位置,一个二维坐标 | | temperature | int | Guest的温度,一种状态 | | master_id | int | Guest的拥有者的ID,对应了玩家ID | ## 查看玩家控制的Guest🔒 ### API端点 `GET /player/guest` ### 鉴权相关 该接口需要鉴权,方式同上 ### 请求内容 玩家信息已包含在鉴权信息内,无需提交额外的请求内容 ### 返回内容 将会返回一个json的列表对象,其中每一个元素就是一个Guest的所有属性,详细字段如下: | 元素属性 | 类型 | 描述 | | ----------- | --------- | ----------------------------------------- | | id | int | Guest的身份号,用以查询、控制等的唯一编号 | | energy | int | 能量,一种收集物 | | pos | (int,int) | Guest的位置,一个二维坐标 | | temperature | int | Guest的温度,一种状态 | | master_id | int | Guest的拥有者的ID,对应了玩家ID | 例如: ```json [ { "id": 1, "energy": 19, "pos": [ 0, 0 ], "temperature": 19, "master_id": 1 } ] ``` --- --- --- # 游戏的坐标系统,GRID GRID这个名字灵感来源于《创:战纪》中的*电子世界网络*,类似的用法还有*国家电网 State Grid*。 而实际上这个名字的意思就是一个坐标系统——平面直角坐标系。 Entropy的世界是二维的,拥有*x*和*y*两个维度,但是在数据库中的存储并不是两个字段(主要原因是Sea-ORM对pgsql的二维索引并不友好),所以干脆直接 将两个维度压缩为一个扁平维度进行存储,在对网络服务的时候由定制的序列化策略,在对坐标序列化时将低维度的索引转换为二维的坐标(这样做的缺点是不支持数据库空间索引,但Sea-ORM本来就不支持二维索引)。 虽然上面转换的过程看起来很复杂,但是不要紧,坐标系统本身其实很简单。 ## 世界地图与节点 首先,整个世界是一个二维的梯度平面,世界由很多节点*Node*构成。每一个Node有两个属性。一个是坐标索引,是二维数组(i16,i16);另一个是节点值,是一个不定长的数组Vec,最长不超过1024,长度全随机。代码就像这样: ```rust struct NodeID(i16, i16); // 二维空间坐标 struct Node{ // 这是一个Node id: NodeID, data: Vec, // 节点包含许多Cell } struct FlatID(i32); // 压缩后的扁平索引,用以数据库存储 ``` 为了交流方便,就将节点中数据数组的一个特定数值称之为单元Cell。 Cell既是一个字节,也是一个1位宽的无符号整数,Entropy游戏中一个Cell的值就被认为是这个Cell的温度。由此引出主题: **玩家通过Guest和Cell的温度差进行发电,收集能量;消耗能量,不断增殖** - 玩家控制Guest只能访问与其同节点上的Cell进行发电 - 发电的效率为卡诺热机的理论效率[^1],关于卡诺循环详见[知乎专栏](https://zhuanlan.zhihu.com/p/514761818) - 经过效率折算后,收集能量与温度转换的比例是1:1,也就是1点温度差等价于1点温度 - 小数部分在对玩家收获能量时向下取整,另一部分向上取整,总和不变 [^1]: 虽然Entropy的概念设计中不涉及内能与体积,但是效率依然选择了经典的卡诺循环效率。 访问一个节点也很简单,甚至不需要鉴权 ### API端点 `GET /node/:x/:y` `GET /node/bytes/:x/:y` 其中x是节点的横坐标,y是纵坐标;第一个端点将返回json格式数据,而第二个端点将直接返回Node的data属性对应的字节串(由于每个Cell宽度仅为1,所以这里不存在大小端序问题) ### 请求内容 不需要任何参数,坐标信息直接在API端点的URI中提供 ### 返回内容 返回一个Node的信息,详情如下: | 返回值 | 类型 | 描述 | | ------ | ---------- | -------------------------------------- | | id | (int, int) | Node的坐标 | | data | [int] | Node的data部分,即由Cell构成的数字列表 | 值得注意的是,json返回结果中的Cell部分的温度为1位宽有符号整数,范围从-128到127; 而bytes返回的则是字节串,需要对每一位强制转换为int8才是Cell的温度。 ### 例子 例如`GET`请求`/node/1/1`,返回: ```json { "id": [ 1, 1 ], "data": [ -49, -28, -26, 102, 44 ] } ``` 这里只是个简单的例子。事实上,Node的data部分的长度从0到1024不等,这么短的data出现概率很小,只是为了演示用的。 ## 坐标与移动系统 世界是非连续的,无论Guest还是Node都有一个二维的整数坐标。 玩家只能与当前节点上的单元交互,但是玩家可以主动移动,每次只能向周围8个方向移动一个格子的距离。移动没有除了现实世界时间以外的任何消耗。 玩家移动的速度(单位时间内途径的节点数)取决于客户端请求频率和服务器算力。 #### 开始移动 默认给予的Guest总是在(0, 0)位置上,我们定义第一个维度正方向为右,第二个维度正方向为上(和平面直角坐标系一致)。 玩家可以控制Guest向上下左右4个位置移动,每次移动1个格子、消耗1点能量,具体如下表: | 方向 | 参数 | | ---- | ------ | | 上 | [0,1] | | 左 | [-1,0] | | 右 | [1,0] | | 下 | [0,-1] | 现在我们开始正式移动,移动在Entropy中称作Walk,移动是Guest的一个方法。首先,玩家需要确定移动的是哪个Guest以及移动的方向。 ### API端点 `POST /guest/walk/:id` 其中id为玩家控制的Guest的编号,如果不清楚ID可以调用`/player/guest`接口查询全部Guest ### 请求内容 例如,我要将我控制的编号为449的Guest向右边移动一个单位,那么位移参数就是[1,0],控制ID就是449。在请求中需要提供 1. 玩家鉴权信息,即Authentication Header 2. 玩家控制的Guest的编号,如果该编号的Guest不存在或非玩家控制都会返回400错误 3. 在请求体中写明移动的方向,必须为9个方向之一,超出限制也会返回400错误 请求体的参数如下: | 参数名 | 类型 | 描述 | | ------ | --------- | ----------------------------------------- | | to | (int,int) | 将要位移的方向,只能在上文4个方向中选一个 | ### 响应内容 如果移动成功,则会返回Guest的最新状态,即全部属性,具体如下(再次和前文重复) | 返回值 | 类型 | 描述 | | ----------- | --------- | ----------------------------------------- | | id | int | Guest的身份号,用以查询、控制等的唯一编号 | | energy | int | 能量,一种收集物 | | pos | (int,int) | Guest的位置,一个二维坐标 | | temperature | int | Guest的温度,一种状态 | | master_id | int | Guest的拥有者的ID,对应了玩家ID | ## 收获能量 Entropy的设计中,能量是从通过Guest的温差发电机产生的。 ### 温度系统 Entropy的温度由一个字节表示,即在开尔文温标下,最低温0K度(绝对零度),最高温255K度。 热力差发电的步骤为: 1. 获取高低热源温度差$\Delta H$ 2. 通过卡诺公式计算效率$\eta = 1 - \frac{T_C}{T_H}$[^2] 3. 计算实际可利用的温度差$\hat{\Delta T} = {\Delta H} * \eta$ 4. 对可利用温度差向下取整,得到实际温度变化$\Delta T = \lfloor\hat{\Delta T}\rfloor$ 5. 对高、低热源分别减去、加上温度变化,使温度变得“平均” $$ T'_C = T_C + \Delta T\\T'_H = T_H - \Delta T $$ [^2]: 其中,$T_C$是冷端绝对温度,$T_H$是热端绝对温度。 WIP ## 增殖更多Guest WIP ## 尾声 恭喜你看到这里,你已经学会了全部的API了!