neumachen
3/16/2017 - 7:55 PM

rps.erl

%%% assignment 3.12 Functional Programming in Erlang, 2017

-module(rps).
-export([play/1,echo/1,play_two/3,rock/1,no_repeat/1,const/1,enum/1,cycle/1,rand/1,val/1,tournament/2]).

-export([type_count/1, least_freq_played/1,most_freq_played/1,rand_strategy/1]).

-created_by("Marcelo Ruiz Camauër").
-include_lib("eunit/include/eunit.hrl").


%
% interactively play against a strategy, provided as argument.
% example:
% rps:play(fun rps:rand/1).
%

play(Strategy) ->
    % play interactively until user enters "stop"
    io:format("Rock - paper - scissors~n"),
    io:format("Play one of rock (r), paper (p), scissors (s), or 'stop'. ~n"),
    play(Strategy,[]).

% tail recursive loop for play/1:
play(Strategy,Moves) ->
    {ok,P} = io:read("Your play: "),
    Play = expand(P),
    case Play of
        stop ->
            io:format("Stopped~n");
        _    ->         % should have something here to catch illegal input...
            OponentPlay = Strategy(Moves),
            Result = result(Play,OponentPlay),
            io:format("Result: ~p vs ~p, you ~p~n",[Play,OponentPlay,Result]),
            play(Strategy,[Play|Moves])
    end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% auxiliary functions
%

% transform shorthand atoms to expanded form
    
expand(r) -> rock;
expand(p) -> paper;		    
expand(s) -> scissors;
expand(X) -> X.

% result of one set of plays

result(rock,rock) -> draw;
result(rock,paper) -> lose;
result(rock,scissors) -> win;
result(paper,rock) -> win;
result(paper,paper) -> draw;
result(paper,scissors) -> lose;
result(scissors,rock) -> lose;
result(scissors,paper) -> win;
result(scissors,scissors) -> draw.

% result of a tournament

tournament(PlaysL,PlaysR) ->
    lists:sum(
      lists:map(fun outcome/1,
		lists:zipwith(fun result/2,PlaysL,PlaysR))).

outcome(win)  ->  1;
outcome(lose) -> -1;
outcome(draw) ->  0.

% transform 0, 1, 2 to rock, paper, scissors and vice versa.

enum(0) ->
    rock;
enum(1) ->
    paper;
enum(2) ->
    scissors.

val(rock) ->
    0;
val(paper) ->
    1;
val(scissors) ->
    2.

% give the play which the argument beats.

beats(rock) ->
    scissors;
beats(paper) ->
    rock;
beats(scissors) ->
    paper.

type_count(L) ->  % by Callous M.
    FilterFn = fun(Type) -> fun(X) when X =:= Type -> true; (_) -> false end end,
    R = {_, rock} = {length(lists:filter(FilterFn(rock), L)), rock},
    P = {_, paper} = {length(lists:filter(FilterFn(paper), L)), paper},
    S = {_, scissors} = {length(lists:filter(FilterFn(scissors), L)), scissors},
    {R, P, S}.



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% STRATEGIES:
% Add file strategies that when passed a list of previous moves by the opponent, will determine what to play:
% 
% * assume that your opponent never repeats herself: if you know this you can make a choice that will never lose;
% * make a random choice each time; you may want to use the random:uniform/1 function so that 
%   random:uniform(N) returns a random choice of 1,2, … N with equal probability each time;
% * cycles through the three choices in some order;
% * apply an analysis to the previous plays and choose the least frequent, assuming that in the long run your 
%   opponent will play each choice equally;
% * apply an analysis to the previous plays and choose the most frequent, assuming that in the long run your 
%   opponent is going to play that choice more often than the others.
% 
% We can also define functions that combine strategies and return strategies:
% * Define a strategy that takes a list of strategies and each play chooses a random one to apply.
% * Define a strategy that takes a list of strategies and each play chooses from the list the strategy 
%   which gets the best result when played against the list of plays made so far.
%
echo([]) ->
     paper;
echo([Last|_]) ->
    % play whatever the opponent played last. 
    Last.

rock(_) ->
    % play always 'rock', no matter previous opponents moves
    rock.

no_repeat([]) ->
    % play something that does not repeat the opponent's last play:
    paper;
no_repeat([X|_Xs]) ->
   case X of
        rock    -> scissors;
        paper   -> rock;
        scissors -> paper
    end.


const(_Xs) ->
    % play always the same choice
    paper.


cycle(Xs) ->            
    % cycles through the three choices in sequence
    enum(length(Xs) rem 3).


rand(_) ->
    % choice made at random
    enum(rand:uniform(3)-1).

least_freq_played(Xs) -> 
    {R,P,S} = type_count(Xs),
    {_, T} = min(R, min(P, S)),
    T.

rand_strategy(Xs)-> 
    Choice = rand:uniform(8),
    %io:format("strategy ~p ~n",[Choice]),
    case Choice of  
        1->least_freq_played(Xs);
        2->most_freq_played(Xs);
        3->rand(Xs);
        4->rock(Xs);
        5->cycle(Xs);
        6->no_repeat(Xs);
        7->echo(Xs);
        8->rock(Xs)
    end.
    
% most_freq
most_freq_played(Xs) -> 
    {R,P,S} = type_count(Xs),
    {_, T} = max(R, max(P, S)),
    T.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Strategy vs Strategy
% Define a function that takes three arguments: two strategies and a number N, and which plays 
% the strategies against each other for N turns. At each stage the function should output the result of the 
% round, and it should show the result of the tournament at the end.
%
% play one strategy against another, for N moves.
% example:
% rps:play_two(fun rps:rand/1,fun rps:rand/1,30).
% rps:play_two(fun rps:least_freq_played/1,fun rps:no_repeat/1,30).
% rps:play_two(fun rps:least_freq_played/1,fun rps:most_freq_played/1,30).
% rps:play_two(fun rps:rand_strategy/1,fun rps:rand_strategy/1,30).

play_two(StrategyL,StrategyR,N) ->
    % given a strategy function for each player, play them out for N moves
    play_two(StrategyL,StrategyR,[],[],N).

% tail recursive loop for play_two/3
% 0 case computes the result of the tournament


play_two(_,_,PlaysL,PlaysR,0) ->
    % if we played out all the moves, report the final result
   io:format("Final score for left player: ~p~n", [tournament(PlaysL, PlaysR)]);

play_two(StrategyL,StrategyR,PlaysL,PlaysR,N) ->
    PlayL = StrategyL(PlaysR),
    PlayR = StrategyR(PlaysL),
    Result = result(PlayL, PlayR),
    case Result of
        draw    -> io:format("turn ~p ~p vs ~p, ~p.~n", [N,PlayL, PlayR, Result]);
        _       -> io:format("turn ~p ~p vs ~p, ~p for left player.~n", [N,PlayL, PlayR, Result])
    end,
    case abs(tournament(PlaysL, PlaysR)) > 10 of  % if winning or losing by a lot, end the game
        true -> MovesLeft = 1 ;
        false-> MovesLeft = N
    end,
    play_two(StrategyL, StrategyR, [PlayL | PlaysL], [PlayR | PlaysR], MovesLeft - 1).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% tests
result_test() ->
	?assertEqual(result(rock,rock), draw),
	?assertEqual(result(rock,paper), lose),
	?assertEqual(result(rock,scissors), win),
	?assertEqual(result(paper,rock), win),
	?assertEqual(result(paper,paper), draw),
	?assertEqual(result(paper,scissors), lose),
	?assertEqual(result(scissors,rock), lose),
	?assertEqual(result(scissors,paper), win),
	?assertEqual(result(scissors,scissors), draw).

tournament_test() ->
	?assertEqual(tournament([rock],[rock]), 0),
	?assertEqual(tournament([rock,rock],[rock,paper]), -1),
	?assertEqual(tournament([rock,rock,paper],[rock,paper,scissors]), -2),
	?assertEqual(tournament([rock,rock,paper,paper],[rock,paper,scissors,rock]), -1).