【问题标题】:What RESTful API would you use for a turn-based game server?你会为回合制游戏服务器使用什么 RESTful API?
【发布时间】:2010-09-29 04:01:47
【问题描述】:

您如何将回合制游戏服务器建模为 RESTful API?例如,国际象棋服务器,您可以在其中与相同 API 的另一个客户端下棋。您需要某种方式来请求和与其他客户协商游戏,以及玩游戏的各个动作的某种方式。

这是 REST (RESTful) API 的理想选择吗?还是应该以不同的方式建模?

【问题讨论】:

    标签: rest chess


    【解决方案1】:

    我在想这样的事情:

    /game/<gameID>/move/<moveID>
    

    就基本资源而言。我不确定如何处理“其他玩家移动了吗?”想法,虽然。我曾考虑过在播放移动之前简单地阻止 GET 请求 - 即我的客户会将我的移动坐标放置到

    /game/13/move/1
    

    然后会得到

    /game/13/move/2
    

    服务器不会立即响应,但会保持连接打开,直到其他玩家移动(即 PUT 到该位置)。这就是中岛所说的“彗星式”吗?

    Charlie,我不太清楚你所说的“令牌”是什么意思——这是否可以在不需要轮询或阻塞连接的情况下解决相同的问题?

    对于玩家 ID,将其建模为 URL 中的资源是否有意义?我打算简单地使用 HTTP 用户身份验证(其中用户/通行证作为每个请求的一部分发送)。您仍然可以在没有身份验证的情况下获取大多数资源,但是如果您尝试这样做,例如,

    PUT /game/13/move/2
    

    如果您没有该游戏的正确凭据,它会给您一个权限被拒绝错误。

    【讨论】:

    • 很好,我可能会建议一个临时错误,甚至是 404,并让客户端在 x 秒后重新轮询,与此有关的问题是获取请求的缓存,以及来自服务器或客户端的超时 (浏览器?)。
    【解决方案2】:

    好的,REST 的基本思想是您正在转移状态;您希望服务器上很少或没有“会话状态”。所以你不会想要使用会话状态和keepalive,这就是Comet 所做的。但是想想一个邮件游戏的例子:你们都有一个棋盘副本,并且你们交换动作。邮局不知道这个游戏。

    现在,我承认,当我想到它时,我的脑海中就会出现这种情况——事实上,我可能会根据这个问题写一篇文章——但这是我的想法,就像一些故事一样:

    1. 你想在 行,所以你去一个已知的 URI 到 得到一个。你返回一个页面 显示谁(如果有人)正在等待 开始游戏。
    2. 您选择等待的人之一 播放,然后单击相应的 关联。你得到一个新的显示(ajax 如果你愿意,这里有魔法) 董事会成立。你们中的一个是白人, 白色先移动。
    3. 有权移动的人进入 一个动作,并提交(比如采取 在游戏中你的手离开棋子。) 董事会更新和权利 移动到另一个玩家。

    就服务器状态而言,您不需要太多东西 --- 尽管您可能希望通过跟踪移动等来扩展它,比如排名 --- 并且可以计算谁有权移动的问题完全来自棋盘页面:如果你有权限,你有一个输入动作的表格;当您发回表单返回时,响应会返回一个页面给您,没有用于输入移动的位置。

    我所说的“令牌”只是指“我的举动”/“你的举动”这一状态的任意表示。

    好像你需要的资源是

    • “寻找游戏”主页
    • 如果您正在跟踪统计信息,则为用户页面 之类的
    • 每个活动游戏的唯一 URI。

    【讨论】:

      【解决方案3】:

      您要建模的资源是什么?我似乎有四个:你,你的对手,特定的游戏(会话,实例)和游戏板状态。所以它会从类似的东西开始

      /game
      /game/gameID/gamer/gamerID
      /game/gameID/board
      

      我们对InfoQ 有一个很好的介绍/概述。

      【讨论】:

      • 同意查理,从资源的定义开始。除了已经提到的那些,我也会考虑添加一个 MOVE 资源。
      • 如果您定义的 URI 不仅仅是一个入口点,那么这不是一个 RESTful API。如果将资源的 URI 定义为 API 的一部分,则违反了 REST 的约束。
      【解决方案4】:

      我认为 REST 不是这样的应用程序的好选择。您需要执行的转换和操作(进行移动、查看移动历史记录、撤消、获取建议、转出通知)并不能巧妙地映射到 REST 的资源概念。 (如果考虑一下用于更复杂的回合制游戏(如 Scrabble 或 Monopoly)的 RESTful API 的外观,困难可能会更加明显。)

      我认为任何明智的 REST API 最终都可能成为非 RESTful 的包装器,例如来回发送 portable game notation 的有状态协议。

      【讨论】:

        【解决方案5】:

        谢谢,查理。我仍然不清楚你是如何在你的计划中得到对手行动的通知的。当然,谁有权移动的问题可以简单地计算出来——要么从棋盘资源中计算出来,要么通过使用明确说明轮到谁移动的单独资源来计算。但是客户怎么知道这个资源已经改变了呢?它是否必须简单地不断轮询,记住以前的状态,直到它注意到发生了变化?在 Post Office 模型中,Post Office 将消息“推送”到客户端(您的邮箱),这在 HTTP 中是不可能的。

        我想这是一个更普遍的问题的一部分:如果我想要监视 REST 资源的更改或修改,那么最好的方法是什么?服务器可以做些什么来使客户端更容易做到这一点?

        我认为我实际上会将其作为一个单独的问题发布,因为我认为它本身很有趣。

        编辑添加:What is a RESTful way of monitoring a REST resource for changes?

        【讨论】:

          【解决方案6】:

          planet.jabber 的一位开发人员参与了在线国际象棋社区Chesspark。他们广泛使用 Jabber/XMPP;如果我没记错的话,these are his posts on the subject

          XMPP 是一种即时消息协议,大致基于小型 XML 消息交换。大多数语言都有库,包括 Javascript。不过,我不确定它是否适合您的问题。

          【讨论】:

            【解决方案7】:

            我认为你可以建模 RESTful。 实施这将更加困难,因为您要么需要comet) 式的解决方案,要么必须通过 AJAX 以相对较短的间隔轮询服务器。

            就如何公开 RESTful 界面而言,我想说的是,您需要一个带有坐标的棋盘、可以占据这些坐标的棋子以及改变这些坐标的动作。

            当玩家移动时,将创建一个新动作。在验证以确保它被允许之后,您将更新游戏的状态,然后呈现更新 UI 所需的任何响应。

            所以我基本上就是这样建模的。不过,实施方面是我认为更大的困难。

            【讨论】:

            • 您的“comet”链接无效。 URL 的右括号不知何故掉到了文本中。
            【解决方案8】:

            我不认为这一切都那么复杂,中岛。你会传递数据,比如 JSON,用于棋盘位置、移动,以及谁有下一步移动的标记。这就像通过邮件玩一样。

            你首先去游戏并寻找合作伙伴,所以

            /game/
            

            为您提供等待的人员列表。当你进来时,如果没有人在等你,你会得到一个游戏ID;否则,您选择等待的人并获取他们拥有的游戏 ID。

            /game/gameID
            

            向您展示棋盘。你需要一些东西来决定谁玩白棋,我会把它留作练习。 GET 操作为您提供棋盘,因此 POST 发送移动;如果你没有移动,你会得到一个错误。您可以通过下一个 GET 找到结果。

            见鬼,在这个模型中我什至没有使用玩家 ID,虽然它可能很好,所以没有人可以作为一个 kibitzer 潜入游戏。

            【讨论】:

            • 这不是 RESTful。客户端不必组装 URI 或了解它们的结构。这违反了 REST 的约束。
            • 对不起,你误会了。维基百科的文章很好,或者你可以阅读罗伊菲尔丁的论文。
            • 菲尔丁同意维基百科的文章状况不佳。最好在响应中以超文本形式提供整个 URI - gzip 使额外使用的带宽可以忽略不计,同时增加了清晰度。无论如何,您可以在响应中包含 URI 模板,但如果您想调用 API RESTful,则不能将这些 URI 指定为 API 的一部分。所以客户端无法知道 /game/gameID,只是 /game/ 响应中有字段为不同的游戏提供 URI。
            【解决方案9】:

            所以你的想法是,不是将动作作为第一类对象,而是将每个动作都视为游戏本身的更新?这当然是一种不同的方法,尽管出于几个原因,我认为我更愿意将动作对象拆分为它自己的第一类实体。最大的原因是我相信它更具可测试性。移动是否有效可以存在于动作对象中,而无需担心棋盘始终处于有效状态。当然,我不知道这两种方法会带来什么,但这对我来说感觉更好。

            另一个你可以通过 YAGNI 完全反驳的原因是,一流的动作对象会提供移动历史。也许很有趣,但除非有要求,否则这是一个有争议的问题。

            【讨论】:

              【解决方案10】:

              对于象棋这样的简单游戏,实际上只是定义媒体类型。

              下面是一个示例,它可能是一个过度简化的媒体类型来模拟国际象棋游戏。

              我将跳过可能在同一服务器上运行的多个游戏的管理,而只是为已经运行的游戏建模。

              第一步通常是为应用程序定义一个索引。

              index

              游戏的入口点。获取此内容以发现有关游戏的信息。

              有效负载可能如下所示:

              {
                  "links": {
                      "self": "http://my-chess-game.host/games/123",
                      "player": "http://my-chess-game.host/players/1",
                      "player": "http://my-chess-game.host/players/2",
                      "me": "http://my-chess-game.host/players/1",
                       ...
                  }
                  "board": [
                      {
                         "x": 0,
                         "y": 1,
                         "piece": null,
                         "rel": "space",
                         "href": "http://my-chess-game/.../boards/123/0/1/something-random-to-discourage-uri-construction"
                      },
                      {
                         "x": 1,
                         "y": 2,
                         "rel": "space",
                         "href": "...",
                         "piece": {
                             "player": "http://my-chess-game/.../players/1",
                             "type": "http://my-chess-game/pieces/Bishop",
                             "rel": "piece",
                             "href": "http://my-chess-game/games/123/pieces/player1/Bishop/1",
                             "links": [
                                  { "rel": "move": "href": "http://my-chess-game/.../boards/123/..." },
                                  ...
                              ]
                          }
                      },
              
                      ...
                  ]
              }
              

              move

              将 JSON 有效负载发布到标有 relmove 的链接以移动片段。必须包含以下字段:

              • location:要移动到的空间的 URI

              成功的响应的状态码为 200,并且将包含一个与 index 有效负载相同的实体,并具有游戏的更新状态。

              如果不允许用户将他的棋子移到那里,或者轮不到他,则为 400。

              player

              获取玩家的描述。

              响应中必须包含以下字段:

              • username:玩家的用户名
              • href:标识此游戏玩家的 URI。

              piece

              片段嵌入在index 有效负载中,但可以单独存在。每个piece 必须具有以下字段:

              • type:标识片段类型的 URI。例如。主教,鲁克,国王。获取此 URI 可能会提供有关该棋子如何在国际象棋游戏中工作的信息。
              • href:标识此板上实际部件的 URI。对此 URI 的 GET 请求可能会提供有关此特定片段的信息。

              每件作品都必须有一个move 链接。


              我可以在这里做出的另一个设计决定是为每个有效动作提供一个单独的链接。这样做可能有充分的理由,但我想证明这不是必需的。您可能还希望包含一些其他资源来处理诸如帮助客户确定轮到谁以及诸如此类的事情。

              对于更复杂的游戏,例如文明、RTS、FPS 或 MMOG 等等,这可能不是那么实用的 IMO。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-06-26
                • 2011-04-20
                • 1970-01-01
                • 2011-03-22
                • 2010-11-02
                • 1970-01-01
                • 1970-01-01
                • 2014-03-07
                相关资源
                最近更新 更多