通过布鲁霍·贝纳维德斯
免责声明 自我支撑……漫长的帖子即将到来。
我花了很长时间写这篇博客文章,主要原因是它涵盖了许多不同的主题。 这将是关于的博客文章
黑客马拉松 TDD 游戏开发 功能语言中的数据类型抽象 Erlang 位串语法 RESTful API 多种工具,例如牛仔,混合器,步道,大摇大摆,拉塞等。 历史 很久以前,当我还是个孩子的时候,我就开始用QBasic编程,而且,众所周知,QBasic附带了两个小游戏。 其中之一就是半字节
快进了25年……您会在Erlang Solutions找到我,试图在Roberto Romero和HernánRivas Acosta的帮助下为我们的下一次黑客马拉松 提出主题。 我们想要一个可以在单个屏幕上观看的多人游戏。 当然 我们想出了蛇 !
黑客马拉松 每年在Erlang Solutions,我们都会从项目中休假一天,我们将所有的时间花在庆祝我们最爱的项目上: 编程 。 2014年,我们举办了“一日构建您的应用” 竞赛,并取得了不错的成绩。 对于2015年的版本,我们决定推出一款多人游戏,由组织者设置服务器,然后由团队开发客户。 我将在本博文的其余部分中显示的代码全部来自Hernán,Roberto和我自己开发的游戏服务器。
目标 蛇 的主要目标当然是用于Hackathon。 但是我也有一个隐藏的议程:由于该项目将在github上开源,因此我想以此为例来说明我们的工作方式。 我希望能够使人们了解IRC,erlang问题,reddit,stackoverflow等问题,并提出诸如“嘿! 您如何在Erlang中建立网络服务器?” 告诉他们“看看这里。 这就是您的做法!”。 考虑到这一点,我们面对这个项目就好像它是一个客户项目一样,事实证明,我们的客户 (我们的开发人员)实际上是我们遇到过的最艰难的客户之一。
建筑 那么,我们如何为蛇游戏构建多人服务器? 我们首先将其分为3个主要部分:
一个显示游戏进度的网页。 提供网页并提供RESTful API以便实时 更新的Web服务器。 一个游戏服务器,它为客户端提供了连接方法。 对于网页,我们使用纯HTML和一些Javascript(至少可以告诉我,这对我来说不是很有趣:P)。 有趣的是 实时 的一部分。 我们在SSE上实现了该功能(通常如此)。 在游戏中,网站看起来像这样:
从服务器的角度来看,我们使用Cowboy on Trails实现了RESTful API,并使用swagger对其进行了文档记录。 最终看起来像这样:
另一方面,我们决定为客户开发人员提供2个选择。 他们可以只使用相同的RESTful API和SSE连接与服务器进行通信,也可以使用我们的HDP协议。
HDP 是运行在UDP上的快速协议。 它由Hernán开发,并以kafka风格 记录下来,供所有人使用。 它专门用于蛇,但可以轻松地在其他项目中复制。
内部架构 在内部,正如您在github上看到的那样,代码分为多个文件夹和多个模块,我们可以分别对其进行测试。
一方面,我们拥有游戏的基本构建块:规则(在spts_core
)被实现为每个游戏及其实体(表示为Data Types 或Models ,每个都有其自己的模块和不透明类型)的gen_fsm
。 您可以在spts_serpents
看到我们的方法:
%%% @doc Serpent model -module(spts_serpents). -author('elbrujohalcon@inaka.net'). -type status() :: alive | dead. -type name() :: binary(). -opaque serpent() :: #{ name => name() , numeric_id => pos_integer() , token => binary() , body => [spts_games:position()] , direction => spts_games:direction() , food => pos_integer() , status => status() }. -export_type([serpent/0, status/0, name/0]). -export([new/6]). -export([ name/1 , numeric_id/1 , direction/1 , direction/2 , to_json/1 , to_json/2 , to_binary/2 % ... ]). -spec name(serpent()) -> name(). name(#{name := Name}) -> Name. -spec direction( serpent(), spts_games:direction()) -> serpent(). direction(Serpent, Direction) -> Serpent#{direction := Direction}. % ...
我们还为API的不同端点提供了多个牛仔处理程序。 所有这些都是使用混合器 构建的,以“继承”来自spts_base_handler
功能。
%%% @doc /games/:game_id/serpents handler -module(spts_serpents_handler). -author('elbrujohalcon@inaka.net'). -include_lib("mixer/include/mixer.hrl"). -mixin([{ spts_base_handler , [ init/3 , rest_init/2 , allowed_methods/2 , content_types_accepted/2 , content_types_provided/2 , resource_exists/2 ] }]). -export([ handle_post/2 , trails/0 ]). -type state() :: spts_base_handler:state(). -behaviour(trails_handler). -spec trails() -> trails:trails(). trails() -> Metadata = #{ post => #{ tags => ["serpents"] , description => "Adds a serpent to a game" , consumes => ["application/json"] , produces => ["application/json"] , parameters => [ spts_web:param(request_body) , spts_web:param(game_id) ] } }, Path = "/api/games/:game_id/serpents", Opts = #{path => Path}, [trails:trail(Path, ?MODULE, Opts, Metadata)]. % ...
为了将事件从核心报告给SSE或HDP客户端,我们使用gen_event
但是为了简化我们的生活,我们实现了一个简单的gen_event
处理程序,该处理程序反过来只需要在您的客户端中实现一个功能: notify(pid(), event())
。 我们称它为spts_gen_event_handler
。 我们使用相同的处理程序为核心编写测试,而无需依赖任何类型的客户端。
对于HDP,我们编写了一个异步UDP侦听器,该侦听器为每个连接的 客户端启动gen_server
(UDP中没有实际的连接)。 之所以简单,是因为我们将所有协议解析/验证逻辑抽象到了自己的模块: spts_hdp
。 该模块本身就是Erlang 位语法的强大示例。 它展示了列表综合,结合了二进制综合和许多惊人的二进制模式匹配表达式。 我的最爱:
%% &#64;doc Parses a list of serpent diffs. %% Each one includes the serpent id and the %% length of its body, followed by each of %% the body cells. parse_diff_serpents(0, Next, Acc) -> {lists:reverse(Acc), Next}; parse_diff_serpents( N, < , BodyLength:?USHORT , Body1:BodyLength/binary , Body2:BodyLength/binary , Next/binary >>, Acc) -> Body &#61; <>, Serpent &#61; #{ id &#61;> SerpentId , body &#61;> [ {Row, Col} || <> <&#61; Body] }, parse_diff_serpents(N-1, Next, [Serpent|Acc]).
开发方法论 并且&#xff0c;为了创建所有这些&#xff0c;我们使用了通常的方法&#xff1a; TDD 。 为此&#xff0c;我们使用common_test
进行了工作&#xff0c;您可以在此处看到一些冗长的测试套件列表。 我们已经写了许多有关TDD&#xff0c;代码覆盖率甚至是元测试的博客文章&#xff0c;因此在这里我不再赘述。 请记住&#xff0c;如果您需要这些示例&#xff0c;那么这里就足够了。
客户 如果您问自己&#xff0c;那里有没有客户的例子&#xff1f; 是的&#xff0c;他们在这里 。 我自己开发了所有这些程序&#xff0c;您可以从头开始或使用spts_cli创建自己的程序 。
结果 结论 我们最终实现了我的两个目标。 一方面&#xff0c;我可以说我们举办了一场非同寻常的Hackathon&#xff0c;很多人在编码时开心地玩耍。 另一方面&#xff0c;现在我们有了一个很好的例子&#xff0c;说明了如何在Erlang Solutions上构建东西以向全世界展示。 您也可以享受它&#xff01; 做就是了…
$ git clone https://github.com/inaka/serpents.git $ cd serpents $ make $ _rel/serpents/bin/serpents start
…并在浏览器中打开http&#xff1a;// localhost&#xff1a;8585开始播放&#xff01;
而且&#xff0c;当然&#xff0c;就像我小时候对Nibbles所做的那样&#xff0c;最有趣的部分是当您开始修改代码以使毒蛇吃了毒果 后变得更胖&#xff0c;更瘦&#xff0c;随意喝酒时……
天啊&#xff01; 我必须 执行&#xff01; 再见&#xff0c;我9岁&#xff0c;我有重要的事情要做...
最初于 2018 年4月26日 发布在 www.erlang-solutions.com 上。
该博客文章最初于2015年11月13日为inaka.net撰写。
From: https://hackernoon.com/erlang-serpents-3eca35bf7c6d