Skip to content

Instantly share code, notes, and snippets.

@dduugg
Created May 26, 2020 20:34
Show Gist options
  • Save dduugg/89457f3e7417fe3a895f96f21e9b29e3 to your computer and use it in GitHub Desktop.
Save dduugg/89457f3e7417fe3a895f96f21e9b29e3 to your computer and use it in GitHub Desktop.
Strategies exercises
-module(rps).
-export([const/1, cycle/1, echo/1, enum/1,
hindsight_strat/1, least_freq/1, most_freq/1,
no_repeat/1, play/1, play_two/3, rand/1, rand_strat/1,
rock/1, test/0, tournament/2, val/1]).
%
% play one strategy against another, for N moves.
%
play_two(StrategyL, StrategyR, N) ->
play_two(StrategyL, StrategyR, [], [], N).
% tail recursive loop for play_two/3
% 0 case computes the result of the tournament
% FOR YOU TO DEFINE
% REPLACE THE dummy DEFINITIONS
play_two(_, _, PlaysL, PlaysR, 0) ->
io:format("Result: ~p~n", [score(PlaysL, PlaysR)]);
play_two(StrategyL, StrategyR, PlaysL, PlaysR, N) ->
play_two(StrategyL, StrategyR,
[StrategyL(PlaysR) | PlaysL],
[StrategyR(PlaysL) | PlaysR], N - 1).
% helper function to provide aggregate score of two sequences of plays
score(PlaysL, PlaysR) -> score(PlaysL, PlaysR, 0).
score([], [], Score) -> Score;
score([PlayL | PlaysL], [PlayR | PlaysR], Score) ->
score(PlaysL, PlaysR,
outcome(result(PlayL, PlayR)) + Score).
score_test() ->
1 = score([paper, paper, paper, paper],
[rock, paper, scissors, rock]),
ok.
%
% interactively play against a strategy, provided as argument.
%
play(Strategy) ->
io:format("Rock - paper - scissors~n"),
io:format("Play one of rock, paper, scissors, ...~n"),
io:format("... r, p, s, stop, followed by '.'~n"),
play(Strategy, []).
% tail recursive loop for play/1
play(Strategy, Moves) ->
{ok, P} = io:read("Play: "),
Play = expand(P),
case Play of
stop -> io:format("Stopped~n");
_ ->
Result = result(Play, Strategy(Moves)),
io:format("Result: ~p~n", [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.
% give the play which beats the argument.
% this is used in frequency-based strategies.
beat_by(X) -> beats(beats(X)).
%
% strategies.
%
echo([]) -> paper;
echo([Last | _]) -> Last.
rock(_) -> rock.
% FOR YOU TO DEFINE
% REPLACE THE dummy DEFINITIONS
no_repeat([]) -> paper;
no_repeat([X | _]) ->
case X of
rock -> scissors;
paper -> rock;
scissors -> paper
end.
% ¯\_(ツ)_/¯ (no spec given)
const(Play) -> Play.
% A strategy which cycles through moves in the order of R, P, S
cycle(Xs) -> enum(length(Xs) rem 3).
% A strategy which chooses a move at random
rand(_) -> enum(rand:uniform(3) - 1).
% A strategy that expects to see the move used least by the opponent thus far.
least_freq(Xs) ->
{R, P, S} = tally(Xs),
Least = case {R, P, S} of
_ when R =< P andalso R =< S -> rock;
_ when P =< R andalso P =< S -> paper;
_ -> scissors
end,
beat_by(Least).
least_freq_test() ->
paper = least_freq([paper, scissors]), ok.
% A strategy that expects to see the move used most by the opponent thus far.
most_freq(Xs) ->
{R, P, S} = tally(Xs),
Most = case {R, P, S} of
_ when R >= P andalso R >= S -> rock;
_ when P >= R andalso P >= S -> paper;
_ -> scissors
end,
beat_by(Most).
most_freq_test() ->
scissors = most_freq([paper, scissors, paper, rock]),
ok.
% helper function to produce a tally of RPS choices from a list of moves
tally(Xs) -> tally(Xs, {0, 0, 0}).
tally_test() ->
{3, 0, 1} = tally([rock, rock, rock, scissors]), ok.
tally([], RPS) -> RPS;
tally([X | Xs], {R, P, S}) ->
RPS = case X of
rock -> {R + 1, P, S};
paper -> {R, P + 1, S};
scissors -> {R, P, S + 1}
end,
tally(Xs, RPS).
% A stategy that chooses randomly from a list of strategies
rand_strat(Strats) ->
fun (Xs) ->
(lists:nth(rand:uniform(length(Strats)), Strats))(Xs)
end.
% A strategy which chooses the best-performing strategy from
% a list, if used in hindsight
hindsight_strat(Strats) ->
fun (Xs) ->
StratScores = lists:zip(Strats,
hindsight_scores(Xs, Strats)),
{Strat, _} = best_strat(StratScores),
Strat(Xs)
end.
hindsight_strat_test() ->
Strat = hindsight_strat([fun rock/1, fun cycle/1]),
% this is rock when rock/1 and paper when cycle/1:
rock = Strat([scissors, scissors, scissors]),
ok.
% returns the best Strategy & Score tuple from a list
best_strat([{Strat, Score}]) -> {Strat, Score};
best_strat([{Strat, Score} | Xs]) ->
{Strat2, Score2} = best_strat(Xs),
case Score > Score2 of
true -> {Strat, Score};
false -> {Strat2, Score2}
end.
% helper function which returns a list of scores corresponding
% to applying each of a list of strategies against a sequence of moves
hindsight_scores(_, []) -> [];
hindsight_scores(Xs, [Strat | Strats]) ->
[hindsight_score(Xs, Strat) | hindsight_scores(Xs,
Strats)].
hindsight_scores_test() ->
[0, 2] = hindsight_scores([rock, paper, scissors,
scissors, scissors],
[fun cycle/1, fun rock/1]),
ok.
% returns the score of a strategy used for a list of Moves
hindsight_score(Xs, Strat) ->
hindsight_score(Xs, Strat, 0).
hindsight_score([], _, Outcome) -> Outcome;
hindsight_score([X | Xs], Strat, Outcome) ->
hindsight_score(Xs, Strat,
Outcome + outcome(result(Strat(Xs), X))).
hindsight_score_test() ->
1 = hindsight_score([rock, paper, scissors, scissors],
fun rock/1).
test() ->
score_test(),
least_freq_test(),
most_freq_test(),
tally_test(),
hindsight_strat_test(),
hindsight_scores_test(),
hindsight_score_test(),
ok.
@elbrujohalcon
Copy link

Great code!
One thing that might have simplified the code a bit would be for tally/1 to return a list, like [{3, rock}, {2, scissors}, {4, paper}]. That way instead of casing over a tuple on its return you could do {_, MostPlayed} = max(tally(Xs)) or {_, LeastPlayed} = min(tally(Xs)) in your strategies.

@dduugg
Copy link
Author

dduugg commented May 27, 2020

@elbrujohalcon that's a great suggestion, thanks for the thoughtful feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment