Elixir遊戲服設計二

搞一個例子,而沒有實際的目標,作起來真是煩人。幾回三番都想放棄。服務器

後來想一想,即便最後完成不了完整的服務器,把須要的知識點搞搞,摸熟悉也是好的。函數

這裏沒有完整的項目目錄,主要是對須要的指點進行整理。要完整寫個教材的話,話費我太多時間,恐怕我繼續不下去。測試

先搞個建模吧。玩家數據目前以下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外面,我試了下沒成功。知道的同窗留個解決方案

今天到此結束。明天正式建模玩家進程吧。

相關文章
相關標籤/搜索