高阶Erlang:超大只的问答房间

使用gen_server实现的更为复杂的。。。山寨kahoot!!!!!!

然鹅这次我们换名字了,现在它叫阿童木TA(robota)!

-module(robota).
-behaviour(gen_server).

%% Test helper function
-export([get_module_grader/1, get_concurrent_grader/1, valid/3,
        get_mgrader/1, run_all_module_grader/1, troels_eval/2, check/2, eval/3]).


%% API
-export([get_the_show_started/0, new/2,
         add_part/3, del_part/2, available/1, unavailable/1, status/1,
         grade/4]).

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

-define(SERVER, ?MODULE).

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

get_the_show_started() ->
    try 
        gen_server:start_link({local, ?SERVER}, ?MODULE, [], [])
    catch 
        _ : _ -> {error,"Failed to crate a RoboTA!/n"}
    end.

new(RoboTA, Name) ->
   gen_server:call(RoboTA, {new, Name}).

add_part(AssHandler, Label, Grader) ->
    gen_server:call(?MODULE, {add_part, AssHandler, Label, Grader}).

del_part(AssHandler, Label) ->
    gen_server:cast(?MODULE, {del_part, AssHandler, Label}).

available(AssHandler) ->
    gen_server:call(?MODULE, {available, AssHandler}).

unavailable(AssHandler) ->
    gen_server:call(?MODULE, {unavailable, AssHandler}).

status(AssHandler) ->
     gen_server:call(?MODULE, {status, AssHandler}).

grade(RoboTA, Name, Submission, Pid) ->
    gen_server:call(RoboTA, {grade, Name, Submission, Pid}).


%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) -> {ok, #{}}.


handle_call({new, Name}, _From, State) -> 
    case maps:find(Name, State) of 
        {ok, _} -> {reply, {error,the_name_has_been_token}, State};
        _ -> Ref = make_ref(),
             AssHandler = {Name, Ref},
             NewState = maps:put(Name, {[], false, []}, State),
             {reply, {ok, AssHandler}, NewState}
    end;
   
handle_call({add_part, AssHandler, Label, Grader}, _From, State) -> 
    {Name, _Ref} = AssHandler,
    case maps:find(Name, State) of 
        % check assignment not active
        {ok, {AssignmentList,false, GraderList}} -> 
            % check label unique
            case lists:keymember(Label, 1, AssignmentList) of
                true -> {reply, {error, {Label, not_unique}}, State};
                false ->
                        NewAssignmentList = lists:append(AssignmentList,[{Label,Grader}]),
                        NewState = maps:put(Name, {NewAssignmentList, false, GraderList}, State),
                        {reply, ok, NewState}
            end;
        {ok, {_AssignmentList,true, _}} ->
            {reply, {error, {Name, is_available}}, State};
        _ -> {reply, {error, fake_asshandler}, State}
    end;


    
handle_call({available, AssHandler}, _From, State) -> 
    {Name, _Ref} = AssHandler,
    case maps:find(Name, State) of 
        {ok, {AssignmentList,false, GraderList}} -> 
            ModuleGrader = get_mgrader(AssignmentList),
            case ModuleGrader of
                [] -> NewState = maps:put(Name, {AssignmentList, true}, State),
                      {reply, ok, NewState};
                _ -> 
                    try 
                        NewAssignmentList = run_all_module_grader(AssignmentList),
                        NewState = maps:put(Name, {NewAssignmentList, true, GraderList}, State),
                        {reply, ok, NewState}
                    catch
                        Error -> Error
                    end                   
            end;           
        {ok, {_AssignmentList,true, _}} ->
            {reply, {error, already_available}, State};        
        _ -> {reply, {error, fake_asshandler}, State}     
    end;

handle_call({unavailable, AssHandler}, _From, State) -> 
    {Name, _Ref} = AssHandler,
    case maps:find(Name, State) of 
        {ok, {AssignmentList, true, GraderList}} -> 
            % check no submission under grading and all graders have been upload
            ModuleGrader = get_mgrader(AssignmentList),
            try
                unload_all(ModuleGrader),
                check_no_grading(GraderList),
                NewState = maps:put(Name, {AssignmentList, false, []}, State),
                {reply, ok, NewState}
            catch
                Reason -> {reply, {error, Reason}, State}
            end;           
        {ok, {_AssignmentList,false, _}} ->
            {reply, {error, already_unavailable}, State};        
        _ -> {reply, {error, fake_asshandler}, State}     
    end;

handle_call({status, AssHandler}, _From, State) -> 
    {Name, _Ref} = AssHandler,
    case maps:find(Name, State) of 
        {ok, {AssignmentList, false, _}} -> 
            {reply, {unavailable, AssignmentList}, State};
        {ok, {AssignmentList, true, _}} ->
            {reply, {available, AssignmentList}, State}     
    end;



handle_call({grade, Name, Submission, Pid}, _From, State) -> 
    case maps:find(Name, State) of 
        {ok, {_AssignmentList, false, _}} -> 
            {reply, {error, {Name, is_unavailable}}, State};
        {ok, {AssignmentList, true, GraderList}} ->
            GraderProcess = spawn(fun() -> graderloop([]) end),
            NewState = maps:put(Name, {AssignmentList, true, [GraderProcess | GraderList]}, State),
            case valid(Submission, [], AssignmentList) of
                false -> Pid ! {error, invalid_submission};
                ValidSub -> 
                            try
                                Feedback = eval(ValidSub, AssignmentList, GraderProcess),
                                Pid ! {ok, Feedback}
                            catch
                                _:_ -> totally_grader_failed
                            end
            end,
            {reply, finished, NewState}
    end.

handle_cast({del_part, AssHandler, Label}, State) -> 
    {Name, _Ref} = AssHandler,
    case maps:find(Name, State) of
        {ok, {AssignmentList, Active, GraderList}} -> 
            case lists:keyfind(Label, 1, AssignmentList) of
                false -> {noreply, State};
                Tuple -> NewAssignmentList = lists:delete(Tuple, AssignmentList),
                         NewState = maps:put(Name, {NewAssignmentList, Active, GraderList}, State),
                         {noreply, NewState}
            end;
        _ -> {noreply, State}
    end.
    

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

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

% helper functions
get_concurrent_grader([]) -> [];
get_concurrent_grader([X|Xs]) -> 
    case X of
        {_Label, {troels, Gs}} -> 
            lists:append(Gs, get_concurrent_grader(Xs));
        _ -> get_concurrent_grader(Xs)
    end.

get_module_grader([]) -> [];
get_module_grader([X|Xs]) -> 
    case X of
        {_Label, {andrzej, Callback, Init}} -> 
            lists:append([{andrzej, Callback, Init}], get_module_grader(Xs));
        {andrzej, Callback, Init} ->
            lists:append([{andrzej, Callback, Init}], get_module_grader(Xs));
        _ -> get_module_grader(Xs)
    end.

% combine get_concurrent_grader and get_module_grader
get_mgrader(AL) ->
    CL = get_concurrent_grader(AL),
    AAL = lists:append(CL,AL),
    get_module_grader(AAL).
    

% run all module grader
run_all_module_grader([]) -> [];
run_all_module_grader([X|Xs]) ->
    case X of
        {Label, {andrzej, Callback, Init}} -> 
             case Callback:setup(Init) of
                        {ok, Salt} -> lists:append([{Label, {andrzej, Callback, Salt}}], run_all_module_grader(Xs));   
                        % throw?                        
                        Error -> Error
             end;
        {Label, {troels, Gs}} ->
            try 
                NGs = run_list_module_grader(Gs),
                lists:append([{Label, {troels, NGs}}], run_all_module_grader(Xs))
            catch
                _ -> grader_failed
            end;
        Other -> lists:append([Other],run_all_module_grader(Xs))
    end.
run_list_module_grader([]) -> [];
run_list_module_grader([X|Xs]) ->
    case X of
        {andrzej, Callback, Init} ->
             case Callback:setup(Init) of
                        {ok, Salt} -> lists:append([{andrzej, Callback, Salt}], run_list_module_grader(Xs));   
                        % throw?                        
                        Error -> Error
             end;
        Other -> lists:append([Other],run_list_module_grader(Xs))
    end.

valid([],_,_) -> [];
valid([{Label, Answer} | Xs], Cont, AssignmentList) ->
    case lists:keymember(Label, 1, AssignmentList) of
        true -> 
            case lists:member(Label, Cont) of
                true -> valid(Xs, Cont, AssignmentList);
                false -> lists:append([{Label, Answer}], valid(Xs, [Label|Cont], AssignmentList))
            end;
        false -> false
    end.

% check submissions with label and Answers with label
eval(_ValidSub,[],_) -> [];
eval(ValidSub,[{Label,Grader} | AL],GraderProcess) ->
    case lists:keyfind(Label, 1, ValidSub) of
        false -> lists:append([{Label, missing}], eval(ValidSub, AL, GraderProcess));
        {Label,Answer} -> 
            try
                Me = self(),
                GraderProcess ! {Me,grading_start},
                spawn(fun() -> 
                    try
                        Result = check(Answer, Grader),
                        Me ! Result
                    catch
                        _:_ -> grader_failed
                    end
                end),
                receive
                    Result -> 
                        GraderProcess ! {Me, grading_finish},
                        lists:append([{Label,Result}], eval(ValidSub,AL, GraderProcess))
                after
                    3000 -> 
                        GraderProcess ! {Me, grading_finish},
                        lists:append([{Label,grader_failed}], eval(ValidSub,AL, GraderProcess))
                end
            catch
                _:_ -> lists:append([{Label,grader_failed}], eval(ValidSub,AL, GraderProcess))
            end
    end.
    
% check Gs result or total grader fail
troels_eval([],[]) -> [];
troels_eval([A|As],[G|Gs]) ->
    Me = self(),    
    try 
        spawn(fun() -> 
            try
                Res = check(A,G),
                Me ! Res
            catch
                _:_ -> grader_failed
            end
        end),
        Rs = troels_eval(As,Gs),
        receive
            Result -> lists:append(Rs, [Result])
        end
    catch
        _:_ -> throw(grader_failed)
    end;
troels_eval(_,_) -> throw(grader_failed). 

% check single Answer in Gs is correct or not
check(A,G) ->
    case G of
        abraham -> looks_good;
        niels -> failed;
        {mikkel, Expect} -> 
            case (A =:= Expect) of
                true -> looks_good;
                _ -> failed
            end;
        {simon, Arg, Expect} ->
            case (A(Arg) =:= Expect) of
                true -> looks_good;
                false -> failed
            end;
        {andrzej, Callback, Salt} ->
            {ok, Result} = Callback:grade(Salt, {fakelabel, A}),
            Result;
        {troels, Gs} ->
            try
                troels_eval(A, Gs)
            catch
                _:_ -> grader_failed
            end;
        _ -> throw(grader_failed)
    end.

unload_all([]) -> ok;
unload_all([{andrzej, Callback, Salt}|Xs]) ->
    try
        Callback:unload(Salt),
        unload_all(Xs)
    catch
        Error -> Error
    end.

graderloop(GraderList) ->
    receive
        {Pid, grading_start} -> 
            NewGraderList = [Pid | GraderList],
            graderloop(NewGraderList);
        {Pid, grading_finish} -> 
            graderloop(lists:delete(Pid, GraderList));
        {From, check_finished} ->
            case GraderList of
                [] -> From ! all_finished;
                _ -> graderloop(GraderList)
            end        
    end.
check_no_grading([]) -> ok;
check_no_grading([G|Gs]) ->
    From = self(),
    G ! {From, check_finished},
    receive
        all_finished -> check_no_grading(Gs)
    end.

  

原文地址:https://www.cnblogs.com/hanani/p/9981217.html