搞一个例子,而没有实际的目标,做起来真是烦人。几次三番都想放弃。

后来想想,即使最后完成不了完整的服务器,把需要的知识点搞搞,摸熟悉也是好的。

这里没有完整的项目目录,主要是对需要的指点进行整理。要完整写个教材的话,话费我太多时间,恐怕我继续不下去。

先搞个建模吧。玩家数据目前如下

  1. defmodule Player do
  2. @behavior Access
  3. defdelegate [fetch(t, key), get_and_update(t, key, list)], to: Map
  4. defstruct [:base_info]
  5. def new(id) do
  6. %Player{
  7. base_info: BaseInfo.new(id)
  8. }
  9. end
  10. end
  1. defmodule BaseInfo do
  2. @behavior Access
  3. defdelegate [fetch(t, key), get_and_update(t, key, list)], to: Map
  4. defstruct [:id, :name, :avarta_id, :gem, :gold, :last_login, :last_logout]
  5. def new(id) do
  6. %BaseInfo{
  7. id: id,
  8. name: "",
  9. avarta_id: 0,
  10. gem: 0,
  11. gold: 500,
  12. last_login: 0,
  13. last_logout: 0
  14. }
  15. end
  16.  
  17. def add_gem(base_info, num) when is_integer(num) and num > 0 do
  18. {:ok, update_in(base_info, [:gem], &(&1 + num))}
  19. end
  20.  
  21. def cost_gem(base_info, num) when is_integer(num) and num > 0 do
  22. if base_info.gem >= num do
  23. {:ok, update_in(base_info, [:gem], &(&1 - num))}
  24. else
  25. ErrorMsg.gem_not_enough
  26. end
  27. end
  28.  
  29. def add_gold(base_info, num) when is_integer(num) and num > 0 do
  30. {:ok, update_in(base_info, [:gold], &(&1 + num))}
  31. end
  32.  
  33. def cost_gold(base_info, num) when is_integer(num) and num > 0 do
  34. if base_info.gold >= num do
  35. {:ok, update_in(base_info, [:gold], &(&1 - num))}
  36. else
  37. ErrorMsg.gold_not_enough
  38. end
  39. end
  40.  
  41. def set_name(base_info, name), do: %BaseInfo{base_info| name: name}
  42. def set_last_login(base_info, last_login), do: %BaseInfo{base_info| last_login: last_login}
  43. def set_last_logout(base_info, last_logout), do: %BaseInfo{base_info| last_logout: last_logout}
  44. def set_avarta_id(base_info, avarta_id), do: %BaseInfo{base_info| avarta_id: avarta_id}
  45.  
  46. end
  1. defmodule ErrorMsg do
  2. for line <- File.stream!(Path.join([__DIR__, "error_msg.txt"]), [], :line) do
  3. if line |> String.strip |> String.starts_with?("%") do
  4. else
  5. [str_error_atom, str_error_msg] = line |> String.split(",") |> Enum.map(&String.strip(&1))
  6. fun_name = String.to_atom(str_error_atom)
  7. def unquote(fun_name)(), do: {:error, unquote(fun_name), unquote(str_error_msg)}
  8. end
  9. end
  10.  
  11. end

在BaseInfo模块里有用的接口,需要区分操作成功和失败,所以用{:ok, value}和 {:error, :gem_not_enough, "钻石不足"} 等来表示,一开始是

使用模块属性如@gem_not_found {:error, :gem_not_enough}来表示的。这种对于纯erlang或者elixir已经够用了。考虑到需要客户端接入的话,

那么还是需要中文提示之类的,又考虑到有可能错误需要在系统范围内使用。即是以前erlang服务器里定了全局的宏用法。可是elixir没有erlang宏这种东西,模块属性又是编译期用的,没法在别的模块用。所以我构建了ErrorMsg模块,动态从如下外部数据源生成函数供其他模块用。

  1. %% 通用提示
  2. gem_not_enough, 钻石不足
  3. gold_not_enough, 金币不足

另外需要提到的一点,原来使用了update_in 用法,而Access Protocol在我的elixir 版本1.2.3已经废弃了,没法用@derive Access,

所以我搜了网上抄到上述改法(现在不知道会有什么问题)。

最后上简单的测试代码

  1. defmodule BaseInfoTest do
  2. use ExUnit.Case
  3. setup do
  4. base_info = %BaseInfo{ gem: 10, gold: 10}
  5. {:ok, base_info: base_info}
  6. end
  7.  
  8. test "add valid gem", %{base_info: base_info} do
  9. assert {:ok, new_base_info} = base_info |> BaseInfo.add_gem(10)
  10. assert new_base_info.gem == base_info.gem + 10
  11. end
  12.  
  13. test "add invalid gem", %{base_info: base_info} do
  14. assert catch_error(
  15. {:ok, _} = base_info |> BaseInfo.add_gem(-10)
  16. )
  17. end
  18.  
  19. test "add float gem", %{base_info: base_info} do
  20. assert catch_error(
  21. {:ok, _} = base_info |> BaseInfo.add_gem(10.5)
  22. )
  23. end
  24.  
  25. test "cost valid gem", %{base_info: base_info} do
  26. assert {:ok, new_base_info} = base_info |> BaseInfo.cost_gem(10)
  27. assert new_base_info.gem == base_info.gem - 10
  28. end
  29.  
  30. test "cost invalid gem", %{base_info: base_info} do
  31. assert catch_error base_info |> BaseInfo.cost_gem(-10)
  32. end
  33.  
  34. test "cost float gem", %{base_info: base_info} do
  35. assert catch_error base_info |> BaseInfo.cost_gem(10.5)
  36. end
  37.  
  38. test "cost gem_not_enough", %{base_info: base_info} do
  39. assert ErrorMsg.gem_not_enough == base_info |> BaseInfo.cost_gem(20)
  40. end
  41.  
  42. test "add valid gold", %{base_info: base_info} do
  43. assert {:ok, new_base_info} = base_info |> BaseInfo.add_gold(10)
  44. assert new_base_info.gold == base_info.gold + 10
  45. end
  46.  
  47. test "add invalid gold", %{base_info: base_info} do
  48. assert catch_error base_info |> BaseInfo.add_gold(-10)
  49. end
  50.  
  51. test "add float gold", %{base_info: base_info} do
  52. assert catch_error base_info |> BaseInfo.add_gold(10.5)
  53. end
  54.  
  55. test "cost valid gold", %{base_info: base_info} do
  56. assert {:ok, new_base_info} = base_info |> BaseInfo.cost_gold(10)
  57. assert new_base_info.gold == base_info.gold - 10
  58. end
  59.  
  60. test "cost invalid gold", %{base_info: base_info} do
  61. assert catch_error base_info |> BaseInfo.cost_gold(-10)
  62. end
  63.  
  64. test "cost float gold", %{base_info: base_info} do
  65. assert catch_error base_info |> BaseInfo.cost_gold(10.5)
  66. end
  67.  
  68. test "cost gold_not_enough", %{base_info: base_info} do
  69. assert ErrorMsg.gold_not_enough == base_info |> BaseInfo.cost_gold(20)
  70. end
  71.  
  72. test "set_avarta_id", %{base_info: base_info} do
  73. new_base_info = base_info |> BaseInfo.set_avarta_id(10001)
  74. assert new_base_info.avarta_id == 10001
  75. end
  76.  
  77. end

其他不测了,正式项目,当然还是仔细都测了好。

留下的疑问,在ErrorMsg模块里,不知道如何把生成函数挪到for外面,我试了下没成功。知道的同学留个解决方案

今天到此结束。明天正式建模玩家进程吧。

Elixir游戏服设计二的更多相关文章

  1. Elixir游戏服设计五

    在<Elixir游戏服设计一>里提到,按照系统功能划分成app要保证原子性很难, 现在想想也没那么难.保证原子性,无非就是需要某个单点去完成操作.那么选择玩家进程去做原子性工作就可以了. ...

  2. Elixir游戏服设计一

    在Erlang游戏服设计总结http://www.cnblogs.com/rubyist/p/5530575.html里, 我提到我想要的游戏服设计方法,希望以应用做为基础构建块.最近我在学习elix ...

  3. 简单Elixir游戏服设计- 游戏玩法介绍

    抄以前的,做了点修改. 到目前为止,我们完成了玩家的数据和进程建模,现在介绍游戏玩法. 为什么我们还不做客户端接入.协议指定呢?为什么还没有网关和数据存储呢.在我接手的游戏, 这些通常已经定下来了,我 ...

  4. Elixir游戏服设计三

    玩家进程用gen_server来建模,我不直接使用 use GenServer, 而是使用exactor,该库可以去掉反锁的接口定义. 我们新建一个 player_server_manager app ...

  5. 简单Elixir游戏服设计-玩家进程跑起来

    有了玩家模型,我们试试让玩家进程跑起来. 需要搞个PlayerSupervisor来负责启动和监控玩家进程. defmodule PlayerSupervisor do use Supervisor ...

  6. 关于Elixir游戏服设计系列

    写着写着就废球了,感觉空对空,实在没什么意思. 另外很快就要搞新项目,决定新项目就直接上elixir了.目前该做的准备工作已经探索了一些了. 以下的东西是写给同事参考的,感兴趣的可以看看,提建议更好. ...

  7. 简单Elixir游戏服设计-玩家进程注册

    上回说用Registry 做本地注册(跨服可以用syn,只是稍微麻烦点,需要模拟global注册机制,写个封装模块). 修改game_server 项目的mix.exs, 增加应用启动 def app ...

  8. Elixir游戏服设计四

    上章说到我们要引入syn https://github.com/ostinelli/syn/ 看过文档,它并没有直接提供{via, Module, Name} 相关的方法.我们需要封装一下. Name ...

  9. 简单Elixir游戏服设计-玩法simple_poker

    上回介绍了玩法,现在编写了玩法的简单建模. 做到现在感觉目前还没有使用umbrella的必要(也许以后会发现必要吧),model 应用完全可以合并到game_server. 代码还在https://g ...

随机推荐

  1. About the diffrence of wait timed_wait and block in java

    import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * * @au ...

  2. 微信iOS收款到账语音提醒开发总结

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/yYCaPMxHGT9LyRyAPewVWQ 作者:l ...

  3. springboot 入门一 hello world!

    微服务框架springboot,目的是用来简化新Spring应用的初始搭建以及开发过程.简化的代价,就是约定俗成很多规则,比如默认读取的配置文件名是application.properties 必需在 ...

  4. 个人作业3-(Alpha阶段)

    一. 总结自己的alpha 过程 1.团队的整体情况 Alpha阶段初期我们团队因分工以及项目具体实施一度茫然,好在在团队队长的带领下确认分工及制定具体计划,使任务有序的进行下去,中间过程虽然遇到一些 ...

  5. 201521123105 第七周Java学习总结

    1. 本周学习总结 2. 书面作业 1.ArrayList代码分析 1.1 解释ArrayList的contains源代码 以下是ArrayList的contains源代码: public boole ...

  6. Sublime Text编辑器如何隐藏顶部的菜单栏

    隐藏前: 解决办法: 1.按住Ctrl+Shifp+p,出现一个框,在框里输入“view:”,出现了如下界面 2.选择:“View:Toggle Menu”即可.结果为: 大功告成!!!!

  7. 201521123072《java程序设计》第十四周学习总结

    201521123072<java程序设计>第十四周学习总结 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库 ...

  8. 商城项目整理(四)JDBC+富文本编辑器实现商品增加,样式设置,和修改

    UEditor富文本编辑器:http://ueditor.baidu.com/website/ 相应页面展示: 商品添加: 商品修改: 前台商品展示: 商品表建表语句: create table TE ...

  9. 银河麒麟操作系统打开VMware报vmmon无法编译

    使用银河麒麟操作系统打开VMware可能会报vmmon无法编译 这个时候... 将/usr/src/linux-headers-xxx/include/miscdevice.h第71行void改为in ...

  10. csv文件读取

    from urllib.request import urlopen from io import StringIO import csv data = urlopen("http://py ...