Last active
June 6, 2020 18:22
-
-
Save gorkaio/b8188dd0a0d2528ad757589f2a9f46ad to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-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