Skip to content

Instantly share code, notes, and snippets.

@paucus
Last active March 20, 2017 19:30
Show Gist options
  • Save paucus/58eed0168c028313445d83e5e71cee82 to your computer and use it in GitHub Desktop.
Save paucus/58eed0168c028313445d83e5e71cee82 to your computer and use it in GitHub Desktop.
%%% 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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment