搞一个例子,而没有实际的目标,作起来真是烦人。几回三番都想放弃。服务器
后来想一想,即便最后完成不了完整的服务器,把须要的知识点搞搞,摸熟悉也是好的。函数
这里没有完整的项目目录,主要是对须要的指点进行整理。要完整写个教材的话,话费我太多时间,恐怕我继续不下去。测试
先搞个建模吧。玩家数据目前以下fetch
defmodule Player do @behavior Access defdelegate [fetch(t, key), get_and_update(t, key, list)], to: Map defstruct [:base_info] def new(id) do %Player{ base_info: BaseInfo.new(id) } end end
defmodule BaseInfo do @behavior Access defdelegate [fetch(t, key), get_and_update(t, key, list)], to: Map defstruct [:id, :name, :avarta_id, :gem, :gold, :last_login, :last_logout] def new(id) do %BaseInfo{ id: id, name: "", avarta_id: 0, gem: 0, gold: 500, last_login: 0, last_logout: 0 } end def add_gem(base_info, num) when is_integer(num) and num > 0 do {:ok, update_in(base_info, [:gem], &(&1 + num))} end def cost_gem(base_info, num) when is_integer(num) and num > 0 do if base_info.gem >= num do {:ok, update_in(base_info, [:gem], &(&1 - num))} else ErrorMsg.gem_not_enough end end def add_gold(base_info, num) when is_integer(num) and num > 0 do {:ok, update_in(base_info, [:gold], &(&1 + num))} end def cost_gold(base_info, num) when is_integer(num) and num > 0 do if base_info.gold >= num do {:ok, update_in(base_info, [:gold], &(&1 - num))} else ErrorMsg.gold_not_enough end end def set_name(base_info, name), do: %BaseInfo{base_info| name: name} def set_last_login(base_info, last_login), do: %BaseInfo{base_info| last_login: last_login} def set_last_logout(base_info, last_logout), do: %BaseInfo{base_info| last_logout: last_logout} def set_avarta_id(base_info, avarta_id), do: %BaseInfo{base_info| avarta_id: avarta_id} end
defmodule ErrorMsg do for line <- File.stream!(Path.join([__DIR__, "error_msg.txt"]), [], :line) do if line |> String.strip |> String.starts_with?("%") do else [str_error_atom, str_error_msg] = line |> String.split(",") |> Enum.map(&String.strip(&1)) fun_name = String.to_atom(str_error_atom) def unquote(fun_name)(), do: {:error, unquote(fun_name), unquote(str_error_msg)} end end end
在BaseInfo模块里有用的接口,须要区分操做成功和失败,因此用{:ok, value}和 {:error, :gem_not_enough, "钻石不足"} 等来表示,一开始是atom
使用模块属性如@gem_not_found {:error, :gem_not_enough}来表示的。这种对于纯erlang或者elixir已经够用了。考虑到须要客户端接入的话,spa
那么仍是须要中文提示之类的,又考虑到有可能错误须要在系统范围内使用。便是之前erlang服务器里定了全局的宏用法。但是elixir没有erlang宏这种东西,模块属性又是编译期用的,无法在别的模块用。因此我构建了ErrorMsg模块,动态从以下外部数据源生成函数供其余模块用。code
%% 通用提示
gem_not_enough, 钻石不足
gold_not_enough, 金币不足
另外须要提到的一点,原来使用了update_in 用法,而Access Protocol在个人elixir 版本1.2.3已经废弃了,无法用@derive Access, blog
因此我搜了网上抄到上述改法(如今不知道会有什么问题)。接口
最后上简单的测试代码进程
defmodule BaseInfoTest do use ExUnit.Case setup do base_info = %BaseInfo{ gem: 10, gold: 10} {:ok, base_info: base_info} end test "add valid gem", %{base_info: base_info} do assert {:ok, new_base_info} = base_info |> BaseInfo.add_gem(10) assert new_base_info.gem == base_info.gem + 10 end test "add invalid gem", %{base_info: base_info} do assert catch_error( {:ok, _} = base_info |> BaseInfo.add_gem(-10) ) end test "add float gem", %{base_info: base_info} do assert catch_error( {:ok, _} = base_info |> BaseInfo.add_gem(10.5) ) end test "cost valid gem", %{base_info: base_info} do assert {:ok, new_base_info} = base_info |> BaseInfo.cost_gem(10) assert new_base_info.gem == base_info.gem - 10 end test "cost invalid gem", %{base_info: base_info} do assert catch_error base_info |> BaseInfo.cost_gem(-10) end test "cost float gem", %{base_info: base_info} do assert catch_error base_info |> BaseInfo.cost_gem(10.5) end test "cost gem_not_enough", %{base_info: base_info} do assert ErrorMsg.gem_not_enough == base_info |> BaseInfo.cost_gem(20) end test "add valid gold", %{base_info: base_info} do assert {:ok, new_base_info} = base_info |> BaseInfo.add_gold(10) assert new_base_info.gold == base_info.gold + 10 end test "add invalid gold", %{base_info: base_info} do assert catch_error base_info |> BaseInfo.add_gold(-10) end test "add float gold", %{base_info: base_info} do assert catch_error base_info |> BaseInfo.add_gold(10.5) end test "cost valid gold", %{base_info: base_info} do assert {:ok, new_base_info} = base_info |> BaseInfo.cost_gold(10) assert new_base_info.gold == base_info.gold - 10 end test "cost invalid gold", %{base_info: base_info} do assert catch_error base_info |> BaseInfo.cost_gold(-10) end test "cost float gold", %{base_info: base_info} do assert catch_error base_info |> BaseInfo.cost_gold(10.5) end test "cost gold_not_enough", %{base_info: base_info} do assert ErrorMsg.gold_not_enough == base_info |> BaseInfo.cost_gold(20) end test "set_avarta_id", %{base_info: base_info} do new_base_info = base_info |> BaseInfo.set_avarta_id(10001) assert new_base_info.avarta_id == 10001 end end
其余不测了,正式项目,固然仍是仔细都测了好。
留下的疑问,在ErrorMsg模块里,不知道如何把生成函数挪到for外面,我试了下没成功。知道的同窗留个解决方案
今天到此结束。明天正式建模玩家进程吧。