Home > English, Erlang > Ideas for a gather spot in Erlang

Ideas for a gather spot in Erlang


The game my flatmates and I play the most is for sure Monster Hunter Tri for WII. In this game you control an hunter which goes around, kills/captures monsters and so on. In the world of Monster Hunter is quite common to find (as in any other good game) some sort of “gather spot”, which are nothing more than places where you can find herbs to be picked up or rocks to be mined. A couple of days ago, while my friends where playing I started emacs and I started implementing a super simple gather spot.

I decided that my gather spot should respect these rules:

  • gather spot should be a non registered process
  • gather spot should be a gen_server process
  • gather spot can handle messages from players asking for a drop (if you don’t know what a drop is check out this)
  • gather spot should have different drops according to its type (e.g. rock/herb)
  • gather spot should have a random number of drops
  • gather spot should have different probabilities of drop (a friend of mine lost his mind trying to get “Eternal strife” rust shards)
  • gather spot should stop when it has nothing more to drop
In this post I will neither tell you how I managed the location of the gather spot in the XY-plane, nor how I configured a supervision tree for them; I want only to show you how simple is to implement something working in Erlang. You may notice that in my code I didn’t register with a name the process, this is due to the fact that I may have a huge number of this kind of process in a single Erlang node. Here follow resource.erl and helper.hrl…I will not give you explanation about these two files since they are quite simple. 
%%%-------------------------------------------------------------------
%%% @author Paolo <>
%%% @copyright (C) 2012, Paolo
%%% @doc
%%%
%%% @end
%%% Created : 13 Mar 2012 by Paolo
%%%-------------------------------------------------------------------
-module(resource).

-behaviour(gen_server).

%% API
-export([start_link/0]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
 terminate/2, code_change/3]).

-define(SERVER, ?MODULE).
-include("helper.hrl").

%%%===================================================================
%%% API
%%%===================================================================

%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
%% @end
%%--------------------------------------------------------------------
start_link() ->
 gen_server:start_link(?MODULE, [], []).

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% @end
%%--------------------------------------------------------------------
init([]) ->
 <<A:32, B:32, C:32>> = crypto:rand_bytes(12),
 random:seed({A,B,C}),
 Rs = resources(),
 Rt = lists:nth(random:uniform(length(resources())), Rs),
 PossibleDrops = possible_drops(Rt),
 Drops = [generate_drops(PossibleDrops, random:uniform(100)) ||
           _  <- lists:seq(1, number_of_drops(resource_max_drops))],
 {ok, #state{drops = Drops}}.

%% {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_call(drop, _From, #state_resource{drops = [LastDrop | []]} = State) ->
 {stop, normal, LastDrop, State};

handle_call(drop, _From, #state_resource{drops = [Drop | Drops]} = State) ->
 {reply, Drop, State#state_resource{drops = Drops}};

handle_call(_Request, _From, State) ->
 Reply = ok,
 {reply, Reply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @spec handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_cast(stop, State) ->
 {stop, normal, State};

handle_cast(_Msg, State) ->
 {noreply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
 {noreply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
 ok.

code_change(_OldVsn, State, _Extra) ->
 {ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================

As you can see, the code above is fairly simple and respect all the rules I introduced above. You may notice that resource type, drops number and actual drop are random. The important thing here is that the gen_server which represents the resource will exit in normal way when no more drops are present in its state.  Let’s see helper.hrl, which will give you some better insight:

-record(state_resource, {pid,
			 drops = []}).

resources() ->
    [rock, plant].

possible_drops(rock) ->
    ["Rust Shard",
     "Charm",
     "Iron",
     "Whetstone",
     "Stone"
    ];

possible_drops(herb) ->
    ["Some rare stuff",
     "Some uncommon stuff",
     "Ivy",
     "Spider Web",
     "Herb"
    ];

generate_drops(Drops, Number) when Number < 3 ->
    lists:nth(1, Drops);

generate_drops(Drops,Number) when Number < 14 ->
    lists:nth(2, Drops);

generate_drops(Drops, Number) when Number < 30->
    lists:nth(3, Drops);

generate_drops(Drops, Number) when Number < 50 ->
    lists:nth(4, Drops);

generate_drops(Drops, _Number) ->
    lists:nth(5, Drops).

number_of_drops(resource_max_drops) ->
    {ok, MaxDrops} = application:get_env(resource_max_drops),
    number_of_drops(MaxDrops);

number_of_drops(MaxDrops) when is_integer(MaxDrops)->
    <<A:32, B:32, C:32>> = crypto:rand_bytes(12),
    random:seed({A,B,C}),
    random:uniform(MaxDrops).

Now, some may argue that this code is far from perfect…well I agree. In fact this is just a starting point…a lot of stuff can be improved, therefore why don’t you start from this code and do something on you own? How can you improve this code? Here some ideas:

  • how would you place in a random way a resource spot in a given x-y or x-y-x point? 
  • how would you handle supersion tree? 
  • how would you handle resource respawning?
  • how would you abstract helper.hrl?
Categories: English, Erlang Tags: , ,
  1. May 1, 2013 at 5:13 am

    Hi, I check your new stuff regularly. Your writing style is awesome,
    keep up the good work!

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: