Skip to content

Commit

Permalink
Merge pull request #12 from inaka/elbrujohalcon.12.point_assignment
Browse files Browse the repository at this point in the history
Point Assignment
  • Loading branch information
Brujo Benavides authored Jul 2, 2016
2 parents 07e93b5 + edb6d5f commit 55b443e
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 29 deletions.
29 changes: 21 additions & 8 deletions src/bo_server.erl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ init(noargs) -> {ok, #{}}.
({task, bo_players:name()}, {pid(), term()}, state()) ->
{reply, {ok, bo_task:task()}
| {error, ended | forbidden | notfound}, state()};
({score, bo_players:name()}, {pid(), term()}, state()) ->
{reply, {ok, integer()} | {error, forbidden | notfound}, state()};
({submit, bo_players:name(), term()}, {pid(), term()}, state()) ->
{reply, {ok, bo_task:task()} | the_end
| {error, invalid | timeout | ended | forbidden | notfound}
Expand All @@ -51,19 +53,24 @@ handle_call({signup, PlayerName}, {From, _}, State) ->
_:conflict -> {reply, {error, conflict}, State}
end;
handle_call({task, PlayerName}, {From, _}, State) ->
case check_player(PlayerName, From) of
case check_player_and_task(PlayerName, From) of
{error, Error} -> {reply, {error, Error}, State};
Player -> {reply, task(Player), State}
end;
handle_call({submit, PlayerName, Solution}, {From, _}, State) ->
handle_call({score, PlayerName}, {From, _}, State) ->
case check_player(PlayerName, From) of
{error, Error} -> {reply, {error, Error}, State};
Player -> {reply, {ok, bo_players:score(Player)}, State}
end;
handle_call({submit, PlayerName, Solution}, {From, _}, State) ->
case check_player_and_task(PlayerName, From) of
{error, Error} -> {reply, {error, Error}, State};
Player -> {reply, test(Player, Solution), State}
end;
handle_call({skip, PlayerName}, {From, _}, State) ->
case check_player(PlayerName, From) of
case check_player_and_task(PlayerName, From) of
{error, Error} -> {reply, {error, Error}, State};
Player -> {reply, advance(Player), State}
Player -> {reply, advance(Player, skip), State}
end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand All @@ -81,13 +88,19 @@ handle_info(_, State) -> {noreply, State}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Internals
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
check_player_and_task(PlayerName, From) ->
case check_player(PlayerName, From) of
{error, Error} -> {error, Error};
Player -> check_task(Player)
end.

check_player(PlayerName, From) ->
Node = node(From),
case bo_players_repo:fetch(PlayerName) of
notfound -> {error, notfound};
Player ->
case bo_players:node(Player) of
Node -> check_task(Player);
Node -> Player;
NotNode ->
error_logger:warning_msg(
"~p trying to access from ~p but registered at ~p",
Expand All @@ -106,12 +119,12 @@ task(Player) -> {ok, bo_task:describe(bo_players:task(Player))}.

test(Player, Solution) ->
case bo_task:test(bo_players:task(Player), Solution) of
ok -> advance(Player);
ok -> advance(Player, solve);
NOK -> NOK
end.

advance(Player) ->
NewPlayer = bo_players_repo:advance(Player),
advance(Player, Action) ->
NewPlayer = bo_players_repo:advance(Player, Action),
case bo_players:task(NewPlayer) of
undefined -> the_end;
Task -> {ok, bo_task:describe(Task)}
Expand Down
38 changes: 28 additions & 10 deletions src/models/bo_players.erl
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@
-behaviour(sumo_doc).

-type name() :: binary().
-type action() :: skip | solve.

-type done_task() ::
#{ task := module()
, action := action()
}.

-opaque player() ::
#{ name := name()
, node := node()
, task := module() | undefined
, done := [module()]
, done := [done_task()]
, created_at => calendar:datetime()
}.

-export_type(
[ name/0
, action/0
, player/0
]).

Expand All @@ -29,8 +36,9 @@
, node/1
, task/1
, done/1
, finish/1
, task/2
, score/1
, finish/2
, task/3
]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand Down Expand Up @@ -73,14 +81,24 @@ node(#{node := Node}) -> Node.
task(#{task := Task}) -> Task.

-spec done(player()) -> [module()].
done(#{done := Done}) -> Done.
done(#{done := Done}) -> [Task || #{task := Task} <- Done].

-spec finish(player()) -> player().
finish(Player) ->
-spec score(player()) -> integer().
score(#{done := Done}) -> lists:sum(lists:map(fun do_score/1, Done)).

-spec finish(player(), action()) -> player().
finish(Player, Action) ->
#{task := Task, done := Done} = Player,
Player#{task := undefined, done := [Task|Done]}.
DoneTask = #{task => Task, action => Action},
Player#{task := undefined, done := [DoneTask|Done]}.

-spec task(player(), module()) -> player().
task(Player, NextTask) ->
-spec task(player(), action(), module()) -> player().
task(Player, Action, NextTask) ->
#{task := Task, done := Done} = Player,
Player#{task := NextTask, done := [Task|Done]}.
DoneTask = #{task => Task, action => Action},
Player#{task := NextTask, done := [DoneTask|Done]}.

do_score(#{task := Task, action := skip}) ->
round(-0.5 * bo_task:score(Task));
do_score(#{task := Task, action := solve}) ->
bo_task:score(Task).
10 changes: 5 additions & 5 deletions src/repos/bo_players_repo.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-module(bo_players_repo).

-export([signup/2, fetch/1, advance/1]).
-export([signup/2, fetch/1, advance/2]).

%% @throws conflict
-spec signup(bo_players:name(), node()) -> bo_players:player().
Expand All @@ -17,13 +17,13 @@ signup(PlayerName, Node) ->
-spec fetch(bo_players:name()) -> bo_players:player() | notfound.
fetch(PlayerName) -> sumo:find(bo_players, PlayerName).

-spec advance(bo_players:player()) -> bo_players:player().
advance(Player) ->
-spec advance(bo_players:player(), bo_players:action()) -> bo_players:player().
advance(Player, Action) ->
DoneTasks = [bo_players:task(Player) | bo_players:done(Player)],
case bo_tasks:all() -- DoneTasks of
[] -> sumo:persist(bo_players, bo_players:finish(Player));
[] -> sumo:persist(bo_players, bo_players:finish(Player, Action));
RemainingTasks ->
NextTask =
lists:nth(rand:uniform(length(RemainingTasks)), RemainingTasks),
sumo:persist(bo_players, bo_players:task(Player, NextTask))
sumo:persist(bo_players, bo_players:task(Player, Action, NextTask))
end.
5 changes: 4 additions & 1 deletion src/tasks/bo_first_task.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

-behaviour(bo_task).

-export([description/0, expected_arity/0, timeout/0, tests/0]).
-export([description/0, expected_arity/0, score/0, timeout/0, tests/0]).

-spec description() -> binary().
description() -> <<"Echo: Return whatever you receive">>.

-spec expected_arity() -> 1.
expected_arity() -> 1.

-spec score() -> 10.
score() -> 10.

-spec timeout() -> 1000.
timeout() -> 1000.

Expand Down
8 changes: 7 additions & 1 deletion src/tasks/bo_task.erl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

-type task() :: #{ name := module()
, desc := binary()
, score := pos_integer()
}.

-type test() :: fun((fun()) -> ok | {error, term()}).
Expand All @@ -14,15 +15,20 @@
-callback expected_arity() -> non_neg_integer().
-callback timeout() -> pos_integer().
-callback tests() -> [test()].
-callback score() -> pos_integer().

-export([describe/1, test/2]).
-export([describe/1, test/2, score/1]).

-spec describe(module()) -> bo_task:task().
describe(Task) ->
#{ name => Task
, desc => Task:description()
, score => Task:score()
}.

-spec score(module()) -> pos_integer().
score(Task) -> Task:score().

-spec test(module(), fun()) -> result().
test(Task, Fun) ->
Arity = Task:expected_arity(),
Expand Down
6 changes: 5 additions & 1 deletion test/bo_invalid_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,17 @@ invalid_user(Config) ->

ct:comment("Providing a wrong user fails"),
{error, notfound} = bo_test_client:submit(Client, <<"wrong">>, solution),
{error, notfound} = bo_test_client:score(Client, <<"wrong">>),
{error, notfound} = bo_test_client:skip(Client, <<"wrong">>),

ct:comment("Providing a wrong node is forbidden"),
{ok, Client2} = bo_test_client:start(invalid_node),

{error, forbidden} =
try
bo_test_client:submit(Client2, player(), solution)
{error, forbidden} = bo_test_client:submit(Client2, player(), solution),
{error, forbidden} = bo_test_client:score(Client2, player()),
bo_test_client:skip(Client2, player())
after
bo_test_client:stop(Client2)
end,
Expand Down
112 changes: 112 additions & 0 deletions test/bo_score_SUITE.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
-module(bo_score_SUITE).
-author('elbrujohalcon@inaka.net').

-export([all/0]).
-export([init_per_suite/1, end_per_suite/1]).
-export([ initial_score/1
, solving_tasks_adds/1
, skipping_tasks_removes/1
]).

-type config() :: proplists:proplist().

-spec all() -> [atom()].
all() -> [initial_score, solving_tasks_adds, skipping_tasks_removes].

-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
case net_kernel:start(['bo_test@127.0.0.1']) of
{ok, _} -> ok;
{error, {already_started, _}} -> ok;
{error, Error} -> throw(Error)
end,
_ = application:load(beam_olympics),
application:set_env(
beam_olympics, all_tasks, [bo_first_task, simple_task1, simple_task2]),
{ok, _} = bo:start(),
_ = sumo:delete_all(bo_players),
{ok, Client} = bo_test_client:start(skip_suite),
[{client, Client} | Config].

-spec end_per_suite(config()) -> config().
end_per_suite(Config) ->
{client, Client} = lists:keyfind(client, 1, Config),
ok = bo_test_client:stop(Client),
_ = sumo:delete_all(bo_players),
application:unset_env(beam_olympics, all_tasks),
ok = bo:stop(),
Config.

-spec initial_score(config()) -> {comment, string()}.
initial_score(Config) ->
{client, Client} = lists:keyfind(client, 1, Config),
{ok, _} = bo_test_client:signup(Client, <<"initial_score">>),

ct:comment("The initial score is 0"),
{ok, 0} = bo_test_client:score(Client, <<"initial_score">>),

{comment, ""}.

-spec solving_tasks_adds(config()) -> {comment, string()}.
solving_tasks_adds(Config) ->
{client, Client} = lists:keyfind(client, 1, Config),
{ok, #{name := FirstTask}} = bo_test_client:signup(Client, <<"sta">>),

ct:comment("After solving first task, the score increases"),
{ok, #{name := NextTask}} =
bo_test_client:submit(Client, <<"sta">>, fun id/1),
{ok, FirstScore} = bo_test_client:score(Client, <<"sta">>),
FirstScore = FirstTask:score(),

ct:comment("After solving next task, the score increases"),
{ok, #{name := FinalTask}} =
bo_test_client:submit(Client, <<"sta">>, fun NextTask:solution/1),
{ok, NextScore} = bo_test_client:score(Client, <<"sta">>),
NextScore = NextTask:score() + FirstScore,

ct:comment("Failed attempts don't alter the score"),
_ = bo_test_client:submit(Client, <<"sta">>, fun(_) -> wrong end),
_ = bo_test_client:submit(Client, <<"sta">>, wrong),
{ok, NextScore} = bo_test_client:score(Client, <<"sta">>),

ct:comment("After solving last task, the score increases"),
the_end = bo_test_client:submit(Client, <<"sta">>, fun FinalTask:solution/1),
{ok, FinalScore} = bo_test_client:score(Client, <<"sta">>),
FinalScore = FinalTask:score() + NextScore,

ct:comment("Failed attempts don't alter the score"),
_ = bo_test_client:submit(Client, <<"sta">>, fun(_) -> wrong end),
_ = bo_test_client:submit(Client, <<"sta">>, wrong),
{ok, FinalScore} = bo_test_client:score(Client, <<"sta">>),

{comment, ""}.

-spec skipping_tasks_removes(config()) -> {comment, string()}.
skipping_tasks_removes(Config) ->
{client, Client} = lists:keyfind(client, 1, Config),
{ok, #{name := FirstTask}} = bo_test_client:signup(Client, <<"str">>),

ct:comment("After solving first task, the score increases"),
{ok, #{name := NextTask}} = bo_test_client:skip(Client, <<"str">>),
{ok, FirstScore} = bo_test_client:score(Client, <<"str">>),
{FirstScore, FirstScore} = {FirstScore, 0 - round(0.5 * FirstTask:score())},

ct:comment("After solving next task, the score increases"),
{ok, #{name := FinalTask}} = bo_test_client:skip(Client, <<"str">>),
{ok, NextScore} = bo_test_client:score(Client, <<"str">>),
NextScore = FirstScore - round(0.5 * NextTask:score()),

ct:comment("After solving last task, the score increases"),
the_end = bo_test_client:skip(Client, <<"str">>),
{ok, FinalScore} = bo_test_client:score(Client, <<"str">>),
FinalScore = NextScore - round(0.5 * FinalTask:score()),

ct:comment("Failed attempts don't alter the score"),
_ = bo_test_client:skip(Client, <<"str">>),
{ok, FinalScore} = bo_test_client:score(Client, <<"str">>),

{comment, ""}.

id(X) ->
ct:log("id evaluated for ~p", [X]),
X.
5 changes: 4 additions & 1 deletion test/bo_test_client.erl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-module(bo_test_client).

-export([start/1, stop/1]).
-export([signup/2, task/2, submit/3, skip/2]).
-export([signup/2, task/2, submit/3, skip/2, score/2]).
-export([gen_call/2]).

-type task() :: bo_task:task().
Expand Down Expand Up @@ -36,6 +36,9 @@ submit(Node, Player, Solution) -> call(Node, {submit, Player, Solution}).
{ok, bo_task:task()} | the_end | {error, term()}.
skip(Node, Player) -> call(Node, {skip, Player}).

-spec score(node(), player_name()) -> {ok, integer()} | {error, term()}.
score(Node, Player) -> call(Node, {score, Player}).

call(Node, Msg) ->
Caller = self(),
Pid = proc_lib:spawn(Node, ?MODULE, gen_call, [Caller, Msg]),
Expand Down
11 changes: 10 additions & 1 deletion test/simple_task1.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@

-behaviour(bo_task).

-export([description/0, expected_arity/0, timeout/0, tests/0, solution/1]).
-export([ description/0
, expected_arity/0
, score/0
, timeout/0
, tests/0
, solution/1
]).

-spec description() -> binary().
description() -> <<"Echo: Always return 1">>.

-spec expected_arity() -> 1.
expected_arity() -> 1.

-spec score() -> 100.
score() -> 100.

-spec timeout() -> 1000.
timeout() -> 1000.

Expand Down
Loading

0 comments on commit 55b443e

Please sign in to comment.