Colin MacDonald
%% beginning
func(Input) ->
Output = [],
func(Input, Output).
%% beginning
func(Input) ->
Output = [],
func(Input, Output).
%% end
func([], Output) -> lists:reverse(Output).
%% beginning
func(Input) ->
Output = [],
func(Input, Output).
%% end
func([], Output) -> lists:reverse(Output).
%% middle
func([First | Rest], Output) ->
NewFirst = munge(First),
func(Rest, [NewFirst | Output]);
Foo = [cat, dog].
Foo | cat - dog
Foo = [cat, dog]. Bar = [monkey | Foo].
Bar Foo | | monkey - cat - dog
Foo = [cat, dog]. Bar = [monkey | Foo]. Baz = [elephant, tiger | Foo].
Baz Bar Foo | | | | monkey - cat - dog | / elephant - tiger /
%% Beginning: score/1 -> score/3
score(Rolls) ->
Frame = 1,
Score = 0,
score(Rolls, Frame, Score).
%% Beginning
score(Rolls) ->
score(Rolls, 1, 0).
%% End
score(_Rolls, 11, Score) -> Score.
%% Beginning
score(Rolls) ->
score(Rolls, 1, 0).
%% End
score(_Rolls, 11, Score) -> Score;
%% Middle
score([Roll1, Roll2 | Rest], Frame, Score) ->
NewScore = Score + Roll1 + Roll2,
score(Rest, Frame + 1, NewScore).
%% Beginning
score(Rolls) ->
score(Rolls, 1, 0).
%% End
score(_Rolls, 11, Score) -> Score;
%% Strike
score([10 | Rest], Frame, Score) ->
...
%% Spare
score([Roll1, Roll2 | Rest], Frame, Score) when Roll1 + Roll2 == 10 ->
...
%% Normal
score([Roll1, Roll2 | Rest], Frame, Score) ->
NewScore = Score + Roll1 + Roll2,
score(Rest, Frame + 1, NewScore).
%% Beginning
score(Rolls) ->
score(Rolls, 1, 0).
%% End
score(_Rolls, 11, Score) -> Score;
%% Strike
score([10 | Rest], Frame, Score) ->
[Bonus1, Bonus2 | _] = Rest,
NewScore = Score + 10 + Bonus1 + Bonus2,
score(Rest, Frame + 1, NewScore);
%% Spare
score([Roll1, Roll2 | Rest], Frame, Score) when Roll1 + Roll2 == 10 ->
[Bonus1 | _] = Rest,
NewScore = Score + 10 + Bonus1,
score(Rest, Frame + 1, NewScore);
%% Normal
score([Roll1, Roll2 | Rest], Frame, Score) ->
NewScore = Score + Roll1 + Roll2,
score(Rest, Frame + 1, NewScore).
What about incomplete games?
score(Rolls) -> score(Rolls, 1, 0).
score(_Rolls, 11, Score) -> Score;
score([10 | Rest], Frame, Score) ->
score(Rest, Frame + 1, Score + 10 + strike_bonus(Rest));
score([Roll1, Roll2 | Rest], Frame, Score) when (Roll1 + Roll2 == 10) ->
score(Rest, Frame + 1, Score + 10 + spare_bonus(Rest));
score([Roll1, Roll2 | Rest], Frame, Score) ->
score(Rest, Frame + 1, Score + Roll1 + Roll2);
score([Roll1], _Frame, Score) -> Score + Roll1;
score([], _Frame, Score) -> Score.
spare_bonus([]) -> 0;
spare_bonus([Bonus1 | _Rest]) -> Bonus1.
strike_bonus([]) -> 0;
strike_bonus([Only]) -> Only;
strike_bonus([Bonus1, Bonus2 | _Rest]) -> Bonus1 + Bonus2.
Eshell V5.8.4 (abort with ^G)
1> Line = io:get_line("Next> ").
Next>
Eshell V5.8.4 (abort with ^G)
1> Line = io:get_line("Next> ").
Next> colin 4
"colin 4\n"
Eshell V5.8.4 (abort with ^G)
1> Line = io:get_line("Next> ").
Next> colin 4
"colin 4\n"
2> [Player, RollText] = string:tokens(Line, " \t\n").
["colin","4"]
Eshell V5.8.4 (abort with ^G)
1> Line = io:get_line("Next> ").
Next> colin 4
"colin 4\n"
2> [Player, RollText] = string:tokens(Line, " \t\n").
["colin","4"]
3> {Roll, _} = string:to_integer(RollText).
{4,[]}
Eshell V5.8.4 (abort with ^G)
1> Line = io:get_line("Next> ").
Next> colin 4
"colin 4\n"
2> [Player, RollText] = string:tokens(Line, " \t\n").
["colin","4"]
3> {Roll, _} = string:to_integer(RollText).
{4,[]}
4> GameData = dict:new().
{dict,0,...
Eshell V5.8.4 (abort with ^G)
1> Line = io:get_line("Next> ").
Next> colin 4
"colin 4\n"
2> [Player, RollText] = string:tokens(Line, " \t\n").
["colin","4"]
3> {Roll, _} = string:to_integer(RollText).
{4,[]}
4> GameData = dict:new().
{dict,0,...
5> NewGameData = dict:append(Player, Roll, GameData).
{dict,1,...
Eshell V5.8.4 (abort with ^G)
1> Line = io:get_line("Next> ").
Next> colin 4
"colin 4\n"
2> [Player, RollText] = string:tokens(Line, " \t\n").
["colin","4"]
3> {Roll, _} = string:to_integer(RollText).
{4,[]}
4> GameData = dict:new().
{dict,0,...
5> NewGameData = dict:append(Player, Roll, GameData).
{dict,1,...
6> {ok, Rolls} = dict:find(Player, NewGameData).
{ok,[4]}
Eshell V5.8.4 (abort with ^G)
1> Line = io:get_line("Next> ").
Next> colin 4
"colin 4\n"
2> [Player, RollText] = string:tokens(Line, " \t\n").
["colin","4"]
3> {Roll, _} = string:to_integer(RollText).
{4,[]}
4> GameData = dict:new().
{dict,0,...
5> NewGameData = dict:append(Player, Roll, GameData).
{dict,1,...
6> {ok, Rolls} = dict:find(Player, NewGameData).
{ok,[4]}
7> Score = bowling_game:score(Rolls).
4
loop(GameData) ->
Line = io:get_line("Next> "),
[Player, RollText] = string:tokens(Line, " \t\n"),
{Roll, _} = string:to_integer(RollText),
NewGameData = dict:append(Player, Roll, GameData),
{ok, Rolls} = dict:find(Player, NewGameData).
Score = bowling_game:score(Rolls).
io:format("New score for ~s: ~p~n", [Player, Score]),
loop(NewGameData).
main([]) -> loop(dict:new()).
loop(GameData) ->
...
#!/usr/local/bin/escript
#
# scorekeeper.erl
-import(bowling_game).
main([]) -> loop(dict:new()).
loop(GameData) ->
...
$ ./scorekeeper.erl Next>
$ ./scorekeeper.erl Next> colin 3 New score for colin: 3 Next>
$ ./scorekeeper.erl Next> colin 3 New score for colin: 3 Next> colin 4 New score for colin: 7 Next>
$ ./scorekeeper.erl Next> colin 3 New score for colin: 3 Next> colin 4 New score for colin: 7 Next> colin 10 New score for colin: 17 Next>
$ ./scorekeeper.erl Next> colin 3 New score for colin: 3 Next> colin 4 New score for colin: 7 Next> colin 10 New score for colin: 17 Next> colin 3 New score for colin: 23 Next>
Remember the command-line loop?
loop(GameData) ->
Line = io:get_line("Next> "),
[Player, RollText] = string:tokens(Line, " \t\n"),
{Roll, _} = string:to_integer(RollText),
NewGameData = dict:append(Player, Roll, GameData),
{ok, Rolls} = dict:find(Player, NewGameData).
Score = bowling_game:score(Rolls).
io:format("New score for ~s: ~p~n", [Player, Score]),
loop(NewGameData).
Here’s the message-handling loop.
loop(GameData) ->
receive {From, {append, Player, RollText}} ->
%% This is the same
{Roll, _} = string:to_integer(RollText),
NewGameData = dict:append(Player, Roll, GameData),
{ok, Rolls} = dict:find(Player, NewGameData),
Score = bowling_game:score(Rolls),
From ! Score,
loop(NewGameData)
end.
loop(GameData) ->
receive {From, {append, Player, RollText}} ->
{Roll, _} = string:to_integer(RollText),
NewGameData = dict:append(Player, Roll, GameData),
{ok, Rolls} = dict:find(Player, NewGameData),
Score = bowling_game:score(Rolls),
From ! Score,
loop(NewGameData)
end.
init() ->
Data = dict:new(),
Start = fun() -> loop(Data) end,
spawn(Start). % returns pid
loop(GameData) ->
receive {From, {append, Player, RollText}} ->
{Roll, _} = string:to_integer(RollText),
NewGameData = dict:append(Player, Roll, GameData),
{ok, Rolls} = dict:find(Player, NewGameData),
Score = bowling_game:score(Rolls),
From ! Score,
loop(NewGameData)
end.
init() ->
Data = dict:new(),
Start = fun() -> loop(Data) end,
spawn(Start). % returns pid
append(Player, RollText, Pid) ->
Pid ! {self(), {append, Player, RollText}},
receive Resp -> Resp end.
http://localhost:8000/add/Player/Roll
e.g.
http://localhost:8000/add/colin/4
-module(bowling_web). -behaviour(spooky). -export([init/1, get/2]). % Spooky API
-module(bowling_web).
-behaviour(spooky).
-export([init/1, get/2]). % Spooky API
-import(bowling_service).
init([])->
Pid = bowling_service:init(),
register(bowl_svc, Pid),
[{port, 8000}].
-module(bowling_web).
-behaviour(spooky).
-export([init/1, get/2]). % Spooky API
-import(bowling_service).
init([])->
register(bowl_svc, bowling_service:init()),
[{port, 8000}].
%% REST handler
get(_Req, ["add", Player, RollText])->
...
-module(bowling_web).
-behaviour(spooky).
-export([init/1, get/2]). % Spooky API
-import(bowling_service).
init([])->
register(bowl_svc, bowling_service:init()),
[{port, 8000}].
%% REST handler
get(_Req, ["add", Player, RollText])->
Score = bowling_service:append(Player, RollText, bowl_svc),
{200, io_lib:format("~p", [Score])};
-module(bowling_web).
-behaviour(spooky).
-export([init/1, get/2]). % Spooky API
-import(bowling_service).
init([])->
register(bowl_svc, bowling_service:init()),
[{port, 8000}].
%% REST handler
get(_Req, ["add", Player, RollText])->
Score = bowling_service:append(Player, RollText, bowl_svc),
{200, io_lib:format("~p", [Score])};
%% static page handlers
get(Req, [])-> get(Req, ["form.html"]); % main page
get(_Req, Path)-> % other static resources
Filename = filename:join(Path),
case file:read_file(Filename) of
{ok, PageBytes} -> {200, binary_to_list(PageBytes)};
{error, Reason} -> {404, Reason}
end.
$ erl -pa $SPOOKY/ebin -pa $SPOOKY/deps/*/ebin
...
Eshell V5.8.4 (abort with ^G)
1> spooky:start_link(bowling_web).
{ok,<0.35.0>}
2>
#!/usr/local/bin/escript
main([]) ->
SpookyDir = os:getenv("SPOOKY_DIR"),
%% Add spooky and its dependencies to the code path.
true = code:add_path(SpookyDir ++ "/ebin"),
Deps = filelib:wildcard(SpookyDir ++ "/deps/*/ebin"),
ok = code:add_paths(Deps),
%% Compile our modules, just to be safe.
c:c(bowling_game),
c:c(bowling_service),
c:c(bowling_web),
spooky:start_link(bowling_web),
io:format("Started spooky~n"),
io:get_line("Return to exit... "),
spooky:stop().










