Skip to content

Instantly share code, notes, and snippets.

@gorkaio
Last active June 6, 2020 18:22
Show Gist options
  • Save gorkaio/b8188dd0a0d2528ad757589f2a9f46ad to your computer and use it in GitHub Desktop.
Save gorkaio/b8188dd0a0d2528ad757589f2a9f46ad to your computer and use it in GitHub Desktop.
-module(rps).
-export([play/0, play/1, result/2, tournament/2, auto/3]).
-export([rock/1, echo/1, random/1, cycle/1, lfreq/1, mfreq/1, mad_hatter/1, best_of/1]). % Strategies
-export([test/0]).
-type gesture() :: rock | paper | scissors.
-type result() :: win | lose | draw.
-type strategy() :: fun(([gesture()]) -> gesture()).
-define(STRATEGIES, [fun rock/1, fun echo/1, fun random/1, fun cycle/1, fun lfreq/1, fun mfreq/1, fun mad_hatter/1, fun best_of/1]).
%% Interactive play
-spec play() -> [].
play() -> play(fun random/1).
-spec play(strategy()) -> [].
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, [], []).
-spec play(strategy(), [gesture()], [gesture()]) -> [].
play(Strategy, LeftMoves, RightMoves) ->
{ok, P} = io:read("Play: "),
Play = expand(P),
case Play of
stop ->
io:format("Stopped~n"),
io:format("Tournament score: ~p~n", [tournament(LeftMoves, RightMoves)]);
_ ->
Move = Strategy(LeftMoves),
Result = result(Play, Move),
io:format("Left chose: ~p~n", [Play]),
io:format("Right chose: ~p~n", [Move]),
io:format("Result: ~p~n", [Result]),
play(Strategy, [Play|LeftMoves], [Move|RightMoves])
end.
%% Auto tournament
-spec auto(strategy(), strategy(), integer()) -> [].
auto(StrategyLeft, StrategyRight, N) when N > 0 -> auto(StrategyLeft, StrategyRight, N, [], []).
-spec auto(strategy(), strategy(), integer(), [gesture()], [gesture()]) -> [].
auto(_, _, 0, LeftMoves, RightMoves) -> io:format("Tournament score: ~p~n", [tournament(LeftMoves, RightMoves)]);
auto(StrategyLeft, StrategyRight, N, LeftMoves, RightMoves) ->
LeftMove = StrategyLeft(RightMoves),
RightMove = StrategyRight(LeftMoves),
io:format("Left chose: ~p~n", [LeftMove]),
io:format("Right chose: ~p~n", [RightMove]),
io:format("Result: ~p~n", [result(LeftMove, RightMove)]),
auto(StrategyLeft, StrategyRight, N - 1, [LeftMove|LeftMoves], [RightMove|RightMoves]).
%% Helper functions
% gesture or 'stop' atom from read IO atom
-spec expand(atom()) -> gesture()|stop.
expand(r) -> rock;
expand(p) -> paper;
expand(s) -> scissors;
expand(stop) -> stop.
% Gesture from integer
-spec gesture(integer()) -> gesture().
gesture(1) -> rock;
gesture(2) -> paper;
gesture(3) -> scissors.
% Count amount of each gesture in a sequence of plays
-spec count_gestures([]) -> {integer(), integer(), integer()}.
count_gestures(Gestures) -> count_gestures(Gestures, {0,0,0}).
count_gestures([], Ac) -> Ac;
count_gestures([G|Gs], {R, P, S}) ->
NewCount = case G of
rock -> {R + 1, P, S};
paper -> {R, P + 1, S};
scissors -> {R, P, S + 1}
end,
count_gestures(Gs, NewCount).
% Least frequent gesture in a sequence of plays
-spec lfreq_gesture({integer(), integer(), integer()}|[gesture()]) -> gesture().
lfreq_gesture({R, P, S}) when R >= P, P >= S -> scissors;
lfreq_gesture({R, P, S}) when R >= S, S >= P -> paper;
lfreq_gesture({_, _, _}) -> rock;
lfreq_gesture(Gestures) ->
{R, P, S} = count_gestures(Gestures),
lfreq_gesture({R, P, S}).
lfreq_gesture_test() ->
scissors = lfreq_gesture([]),
rock = lfreq_gesture([paper]),
scissors = lfreq_gesture([rock, paper]),
rock = lfreq_gesture([scissors]),
paper = lfreq_gesture([scissors, rock, rock, rock, paper, scissors]),
passed.
% Most frequent gesture in a sequence of plays
-spec mfreq_gesture({integer(), integer(), integer()}|[gesture()]) -> gesture().
mfreq_gesture({R, P, S}) when R >= P, P >= S -> rock;
mfreq_gesture({R, P, S}) when P >= R, R >= S -> paper;
mfreq_gesture({_, _, _}) -> scissors;
mfreq_gesture(Gestures) ->
{R, P, S} = count_gestures(Gestures),
mfreq_gesture({R, P, S}).
mfreq_gesture_test() ->
rock = mfreq_gesture([]),
paper = mfreq_gesture([paper]),
rock = mfreq_gesture([rock, paper]),
scissors = mfreq_gesture([scissors]),
scissors = mfreq_gesture([scissors, paper]),
paper = mfreq_gesture([paper, scissors, paper, rock]),
passed.
%% Beat
-spec beat(gesture()) -> gesture().
beat(rock) -> paper;
beat(paper) -> scissors;
beat(scissors) -> rock.
test_beat() ->
paper = beat(rock),
scissors = beat(paper),
rock = beat(scissors),
passed.
%% Lose
-spec lose(gesture()) -> gesture().
lose(rock) -> scissors;
lose(scissors) -> paper;
lose(paper) -> rock.
test_lose() ->
scissors = lose(rock),
paper = lose(scissors),
rock = lose(paper),
passed.
%% Round
-spec result(gesture(), gesture()) -> result().
result(X, Y) when X == Y -> draw;
result(X, Y) ->
case beat(X) == Y of
true -> lose;
false -> win
end.
test_result() ->
lose = result(rock, paper),
lose = result(paper, scissors),
draw = result(rock, rock),
win = result(rock, scissors),
win = result(scissors, paper),
passed.
%% Tournament
-spec tournament([gesture()], [gesture()]) -> integer().
tournament(X, Y) ->
Results = lists:zipwith(fun result/2, X, Y),
lists:foldl(fun(A, Sum) -> Sum + score(A) end, 0, Results).
%% Result score as integer
-spec score(result()) -> integer().
score(win) -> 1;
score(lose) -> -1;
score(_) -> 0.
tournament_test() ->
0 = tournament([], []),
0 = tournament([rock], [rock]),
1 = tournament([rock], [scissors]),
-1 = tournament([scissors], [rock]),
2 = tournament([rock,paper], [scissors,rock]),
-2 = tournament([scissors,rock],[rock,paper]),
2 = tournament([rock,paper,scissors], [scissors,rock,scissors]),
passed.
%% Strategies
-spec rock([gesture()]) -> gesture().
rock(_) -> rock.
-spec echo([gesture()]) -> gesture().
echo([]) -> rock;
echo([G|_]) -> G.
-spec random([gesture()]) -> gesture().
random(_) -> gesture(rand:uniform(3)).
-spec cycle([gesture()]) -> gesture().
cycle([]) -> gesture(1);
cycle(Gs) ->
Value = (length(Gs) rem 3) + 1,
gesture(Value).
-spec lfreq([gesture()]) -> gesture().
lfreq(Gs) -> lfreq_gesture(Gs).
-spec mfreq([gesture()]) -> gesture().
mfreq(Gs) -> mfreq_gesture(Gs).
-spec mad_hatter([gesture()]) -> gesture().
mad_hatter(Gs) ->
Hats = ?STRATEGIES -- [fun mad_hatter/1],
Hat = lists:nth(rand:uniform(length(Hats)), Hats),
Hat(Gs).
-spec best_of([strategy()]) -> strategy().
best_of([]) -> best_of(?STRATEGIES -- [fun best_of/1]);
best_of(Strategies) ->
fun(Gs) ->
Strategy = choose_strategy(Gs, Strategies),
Strategy(Gs)
end.
%% Choose best strategy. Try to make left lose, then try to draw, then default to first given strategy
-spec choose_strategy([gesture()], [strategy()]) -> strategy().
choose_strategy(Gs, [S|_] = Strategies) ->
choose_strategy(Gs, Strategies, lose, fun() -> choose_strategy(Gs, Strategies, draw, fun() -> S end) end).
-spec choose_strategy([gesture()], [strategy()], result(), strategy()) -> strategy().
choose_strategy(Gs, Strategies, Result, Default) ->
case lists:search(fun(St) -> St(Gs) == Result end, Strategies) of
{value, Value} -> Value;
false -> Default()
end.
%% Run Tests
test() ->
test_beat(),
test_lose(),
test_result(),
tournament_test(),
lfreq_gesture_test(),
mfreq_gesture_test(),
passed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment