-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Finished part 1 of table class including tests * Add .eunit to gitignore * Changed deckPid to ?MODULE in deck
- Loading branch information
Showing
5 changed files
with
338 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -1,3 +1,4 @@ | |||
*~ | *~ | ||
*.beam | *.beam | ||
/ebin/ | /ebin/ | ||
/.eunit/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))]. | |||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | |||
% |