Skip to content

Commit

Permalink
Week 3:
Browse files Browse the repository at this point in the history
* Finished part 1 of table class including tests
* Add .eunit to gitignore
* Changed deckPid to ?MODULE in deck
  • Loading branch information
erija952 committed Feb 26, 2012
1 parent e63d5de commit a7b39df
Show file tree
Hide file tree
Showing 5 changed files with 338 additions and 13 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Original file line Diff line number Diff line change
@@ -1,3 +1,4 @@
*~ *~
*.beam *.beam
/ebin/ /ebin/
/.eunit/
19 changes: 6 additions & 13 deletions src/deck.erl
Original file line number Original file line Diff line number Diff line change
@@ -1,4 +1,4 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% @doc %%% @doc
%%% The task for week 1 is to implement this module, deck. %%% The task for week 1 is to implement this module, deck.
%%% @end %%% @end
Expand Down Expand Up @@ -31,32 +31,25 @@
%%% API %%% API
%%%=================================================================== %%%===================================================================


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


-spec stop() -> ok. -spec stop() -> ok.
stop() -> stop() ->
gen_server:call(deckPid, terminate). gen_server:call(?MODULE, terminate).


-spec is_time_to_shuffle() -> boolean(). -spec is_time_to_shuffle() -> boolean().
is_time_to_shuffle() -> is_time_to_shuffle() ->
gen_server:call(deckPid, is_it_time_to_shuffle). gen_server:call(?MODULE, is_it_time_to_shuffle).


-spec shuffle() -> {ok}. -spec shuffle() -> {ok}.
shuffle() -> shuffle() ->
gen_server:call(deckPid, shuffle). gen_server:call(?MODULE, shuffle).


-spec get_card() -> card(). -spec get_card() -> card().
get_card() -> get_card() ->
gen_server:call(deckPid, get_card). gen_server:call(?MODULE, get_card).


%%%=================================================================== %%%===================================================================
%%% gen_server callbacks %%% gen_server callbacks
Expand Down
228 changes: 228 additions & 0 deletions src/table.erl
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,228 @@
%%%-------------------------------------------------------------------
%%% @doc
%%% Task3:The table
%%% @end
%%%-------------------------------------------------------------------
-module(table).
-behaviour(gen_server).

%% API
-export([start/0]).
-export([stop/0]).
-export([enter_table/1]).
-export([enter_table/2]).
-export([leave_position/1]).
-export([leave_table/0]).
-export([get_vacant_positions/0]).

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

-define(NRPOSITIONSATTABLE, 4).

-record(player, {name=[], from=0}).
-record(state, {players=[]}).

-type position() :: integer().
-type name() :: string().
-type id() :: integer().
-type enter_table_error_reason() :: {position_taken, position()} | {banned, name()} | {invalid_position, position()}.
-type leave_table_error_reason() :: {position_not_taken, position()}.

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

-spec start() -> {ok, pid()}.
start() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

-spec stop() -> ok.
stop() ->
gen_server:call(?MODULE, terminate).

-spec enter_table(name()) -> {ok, id()} | {error, enter_table_error_reason()}.
enter_table(Name) ->
gen_server:call(?MODULE, {enter_table, Name}).

-spec enter_table(name(), position()) -> {ok, id()} | {error, enter_table_error_reason()}.
enter_table(Name, Position) ->
gen_server:call(?MODULE, {enter_table, Name, Position}).

-spec leave_position(position()) -> ok | {error, leave_table_error_reason()}.
leave_position(Position) ->
gen_server:call(?MODULE, {leave_position, Position}).

-spec leave_table() -> ok.
leave_table() ->
gen_server:call(?MODULE, {leave_table}).

-spec get_vacant_positions() -> {ok, [position()]}.
get_vacant_positions() ->
gen_server:call(?MODULE, {get_vacant_positions}).

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

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% @end
%%--------------------------------------------------------------------
init([]) ->
{ok, #state{players=orddict:new()}}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @spec handle_call(Request, From, State) ->
%% {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_call({enter_table, Name}, From, State) ->
VacantPositions = get_vacant_position_list(State),
add_player(Name,hd(VacantPositions),From,State);

handle_call({enter_table, Name, Position}, From, State) ->
add_player(Name,Position,From,State);

handle_call({get_vacant_positions}, _From, State) ->
{reply, {ok, get_vacant_position_list(State)}, State};

handle_call({leave_position, Position}, _From, State) ->
{Result, NewState} = leave_position(Position, State),
{reply, Result, NewState};

handle_call({leave_table}, From, State) ->
NewState = leave_table(From,State,?NRPOSITIONSATTABLE),
{reply, ok, NewState};

handle_call(terminate, _From, State) ->
{stop, normal, ok, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @spec handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
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.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.

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

add_player(Name,Position,From,State) ->
{Result, NewState} = add_player_if_valid_position(Name,Position,From,State),
case Result of
ok -> {reply, {ok,Position}, NewState};
position_taken -> {reply, {position_taken, Position}, State};
invalid_position -> {reply, {invalid_position, Position}, State}
end.


add_player_if_valid_position(_Name, Position,_From, State) when Position > ?NRPOSITIONSATTABLE->
{invalid_position,State};
add_player_if_valid_position(Name,Position,{From,_Ref},State) ->
case orddict:is_key(Position,State#state.players) of
true ->
{position_taken,State};
false ->
NewOrddict = orddict:store(Position,#player{name=Name,from=From},State#state.players),
{ok,State#state{players=NewOrddict}}
end.


leave_position(Position, State) ->
case orddict:is_key(Position,State#state.players) of
true ->
NewOrddict = orddict:erase(Position,State#state.players),
{ok, State#state{players=NewOrddict}};
false ->
{position_not_taken,State}
end.


leave_table(From,State,Position) when Position > 0 ->
case orddict:is_key(Position,State#state.players) of
true ->
Player = orddict:fetch(Position,State#state.players),
NewState = verify_pid_of_player(From,Player,State,Position),
leave_table(From,NewState,Position-1);
false ->
leave_table(From,State,Position-1)
end;
leave_table(_From,State,0) ->
State.


verify_pid_of_player({From,_Pid},Player,State,Position) when Player#player.from == From ->
State#state{players=orddict:erase(Position,State#state.players)};
verify_pid_of_player({_From,_Pid},_Player,State,_Position) ->
State.


get_vacant_position_list(State) ->
OccupiedKeys = orddict:fetch_keys(State#state.players),
List = lists:seq(1,?NRPOSITIONSATTABLE),
%Non-Intersection of List and OccupiedKeys
[I || I <- List, not(lists:member(I,OccupiedKeys))].

76 changes: 76 additions & 0 deletions test/table_tests.erl
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,76 @@
-module(table_tests).
-include_lib("eunit/include/eunit.hrl").

-compile(export_all).

-define(NRPOSITIONSATTABLE, 4).

start_server_test() ->
?assertMatch({ok,_Pid}, table:start()),
?assertEqual(ok, table:stop()).

table_test_() ->
{foreach,
fun setup/0, % setup function
fun teardown/1, % teardown function
[fun ?MODULE:should_add_player/0, % tests
fun ?MODULE:should_add_player_when_entering_if_vacant_position/0,
fun ?MODULE:should_be_possible_to_have_multiple_positions/0,
fun ?MODULE:should_return_error_on_occupied_position/0,
fun ?MODULE:should_return_error_on_invalid_position/0,
fun ?MODULE:should_get_vacant_positions/0,
fun ?MODULE:should_free_up_position_if_player_leaves_position/0,
fun ?MODULE:should_return_error_if_leaving_non_taken_position/0,
fun ?MODULE:should_leave_all_positions_when_leaving_table/0]}.

should_add_player() ->
?assertMatch({ok,1},table:enter_table("Kalle")).

should_add_player_when_entering_if_vacant_position() ->
?assertMatch({ok,1},table:enter_table("Kalle",1)),
?assertMatch({ok,2},table:enter_table("Pelle",2)).

should_be_possible_to_have_multiple_positions() ->
?assertMatch({ok,1},table:enter_table("Pelle",1)),
?assertMatch({ok,2},table:enter_table("Pelle",2)).

should_return_error_on_invalid_position() ->
?assertMatch({invalid_position,?NRPOSITIONSATTABLE+1},table:enter_table("Kalle",?NRPOSITIONSATTABLE+1)).

should_return_error_on_occupied_position() ->
?assertMatch({ok,1},table:enter_table("Kalle",1)),
?assertMatch({position_taken,1},table:enter_table("Pelle",1)).

should_get_vacant_positions() ->
List = lists:seq(1,?NRPOSITIONSATTABLE),
?assertMatch({ok,List}, table:get_vacant_positions()),
table:enter_table("Pelle", 3),
List2 = lists:delete(3, List),
?assertMatch({ok,List2}, table:get_vacant_positions()).

should_free_up_position_if_player_leaves_position() ->
?assertMatch({ok,1}, table:enter_table("Kalle",1)),
?assertMatch(ok, table:leave_position(1)),
List = lists:seq(1,?NRPOSITIONSATTABLE),
?assertMatch({ok,List}, table:get_vacant_positions()).

%Definition here is uncertain... No need to return error if leaving nontaken?
should_return_error_if_leaving_non_taken_position() ->
?assertMatch(position_not_taken, table:leave_position(1)).

%Definition here is uncertain... What if player is not at table?
%How to spawn another session and check that those players isn't removed?
should_leave_all_positions_when_leaving_table() ->
?assertMatch({ok,1}, table:enter_table("Kalle",1)),
?assertMatch({ok,2}, table:enter_table("Kalle",2)),
?assertMatch({ok,3}, table:enter_table("Pelle",3)), %Pid identifies player, not name....
?assertMatch(ok, table:leave_table()),
List = lists:seq(1,?NRPOSITIONSATTABLE),
?assertMatch({ok,List}, table:get_vacant_positions()).

%% Internal functions
setup() ->
table:start().

teardown(_) ->
table:stop().
27 changes: 27 additions & 0 deletions todo
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,27 @@
%%%%%%%%%%%%%%%%%%%% TODO %%%%%%%%%%%%%%%%%%%%%%

% REMAINING QUESTIONS AND ISSUES TABLE:
% What if another player leaves the players position? How to test?
% Could add an -export([enter_table/1]), where player doesnt care abour position.

% Communication with main needs further testing.
% Should be possible to enter game with is ongoing, but should not make any "moves".
% Should notify main if a player has entered.
% Should notify main server if a player has left.
% Should communicate with main server using player id?


%%Questions:
% I HATE THIS CODE, WHY DOES IT RETURN OK AND NOT MY REVERSED LIST??? ITS TAKEN ONE HOUR OF MY LIFE
%check_vacant_positions(State) ->
% check_vacant_positions(1,[],State).
%check_vacant_positions(Position, VacantPositionList,_State) when Position > ?NRPOSITIONSATTABLE ->
% lists:reverse(VacantPositionList),
%check_vacant_positions(Position, VacantPositionList, State) ->
% case orddict:is_key(Position,State#state.players) of
% false -> check_vacant_positions(Position+1, [Position | VacantPositionList], State),
% io:fwrite("FALSE Position ~w, vacantList ~w\n", [Position,VacantPositionList]);
% true -> check_vacant_positions(Position+1, VacantPositionList, State),
% io:fwrite("TRUE Position ~w, vacantList ~w\n",[VacantPositionList, Position])
% end.
%

0 comments on commit a7b39df

Please sign in to comment.