Permalink
Browse files

Week 3:

* Finished part 1 of table class including tests
* Add .eunit to gitignore
* Changed deckPid to ?MODULE in deck
  • Loading branch information...
1 parent e63d5de commit a7b39df831ba91be9802ef1ae00a63e2e1613827 @erija952 erija952 committed Feb 24, 2012
Showing with 338 additions and 13 deletions.
  1. +1 −0 .gitignore
  2. +6 −13 src/deck.erl
  3. +228 −0 src/table.erl
  4. +76 −0 test/table_tests.erl
  5. +27 −0 todo
View
@@ -1,3 +1,4 @@
*~
*.beam
/ebin/
+/.eunit/
View
@@ -1,4 +1,4 @@
- %%%-------------------------------------------------------------------
+%%%-------------------------------------------------------------------
%%% @doc
%%% The task for week 1 is to implement this module, deck.
%%% @end
@@ -31,32 +31,25 @@
%%% API
%%%===================================================================
-%%--------------------------------------------------------------------
-%% @doc
-%% Starts the server
-%%
-%% @spec start() -> {ok, Pid} | ignore | {error, Error}
-%% @end
-%%--------------------------------------------------------------------
-spec start() -> {ok, pid()}.
start() ->
- gen_server:start_link({local,deckPid},?MODULE, [], []).
+ gen_server:start_link({local,?MODULE},?MODULE, [], []).
-spec stop() -> ok.
stop() ->
- gen_server:call(deckPid, terminate).
+ gen_server:call(?MODULE, terminate).
-spec is_time_to_shuffle() -> boolean().
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}.
shuffle() ->
- gen_server:call(deckPid, shuffle).
+ gen_server:call(?MODULE, shuffle).
-spec get_card() -> card().
get_card() ->
- gen_server:call(deckPid, get_card).
+ gen_server:call(?MODULE, get_card).
%%%===================================================================
%%% gen_server callbacks
View
@@ -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))].
+
View
@@ -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().
View
27 todo
@@ -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.