Coupling Cribbage and Erlang into a program
sounds like a fun little program to write to aid in learning Erlang
while writing a program that brings a game I like in life to the virtual
world. Is it the most efficient? Probably not, but you gotta start
somewhere. To the code! The first thing I wanted to do was create a
method to calculate points. An ace is a 1, 2-10 are face value, and
Jack, Queen, King are 11, 12, 13, respectively. Easy adjustments could
be made to allow characters (A, J, Q, K) but for now, I like keeping it
simple.
-module(cribbage).
-export([points/1]).
points([]) -> 0;
points(L) ->
Hand = lists:sort(L),
fifteens(Hand, 0) + runs(Hand, 0, 0) + pairs(Hand, 1, 0).
The above creates a module called cribbage and exports a function called
points/1 which takes one parameter, a list of cards. There are three
kinds of scoring in Cribbage: combinations of cards that equal 15, runs
of three or more, and pairs (or sets or four of a kind). There is one
other kind, but it's not part of this portion of the game.
cardval(C) when C > 9 -> 10;
cardval(C) -> C.
fifteens(_L, Total) when Total > 15 -> 0;
fifteens(_Hand, Total) when Total =:= 15 -> 2;
fifteens([], _Total) -> 0;
fifteens([H | T], Total) when Total < 15 ->
fifteens(T, Total) + fifteens(T, Total + cardval(H)).
cardval is a function that converts the value of face cards
(11-J,12-Q,13-K) to 10 and leaves other cards unchanged in value. This
is useful in finding all combinations of 15 in the hand. When a combo
equals 15, two points are added to the score. Runs were the trickiest of
the three to get right. First, I defined a simple function to determine
the points for a run of given length.
run(3) -> 3;
run(4) -> 6;
run(5) -> 12;
run(_Length) -> 0.
Some people may play with different values for runs of different
lengths, so this allows for easy editing. Runs come in two flavors: 1) A
normal run, and 2) A run where one or two of the cards are doubled. To
account for this, I have runs/3 and runs/4. runs/3 handles the first
case, and passes control to runs/4 when a run of the second case is
encountered. Another special case is when a run has two different cards
doubled (e.g. 3,4,4,5,5) where the run of three is doubled and doubled
again.
%% two cases for runs
%% 1. A straight run - 4,5,6,7
%% 2. A run with a double in the sequence - 4,4,5,6 or 4,5,5,6
runs([], _Curr, Len) -> run(Len);
runs([H | T], Curr, Len) when H =:= (Curr+1) -> runs(T, H, Len + 1);
runs([H | T], Curr, Len) when H =:= Curr -> runs(T, Curr, Len, {H, 2});
runs([H | T], _Curr, Len) -> run(Len) + runs(T, H, 1).
runs([], _Curr, Len, {_Card, Mult}) -> Mult * run(Len);
runs([H | T], Curr, Len, {Card, Mult}) when H =:= (Curr+1) ->
runs(T, H, (Len+1), {Card, Mult});
%% needed for special cases where multiple cards are doubled up
%% like 3,4,4,5,5
runs([H | T], Curr, Len, {Card, Mult}) when H =:= Curr, H > Card ->
runs(T, Curr, Len, {H, (Mult*2)});
%% handles a triple carding, like 2,2,2,3,4
runs([H | T], Curr, Len, {Card, Mult}) when H =:= Curr ->
runs(T, Curr, Len, {Card, (Mult+1)});
runs([H | T], _Curr, Len, {_Card, Mult}) ->
(Mult * run(Len)) + runs(T, H, 1).
For pairs, I do a similar thing: define a pair(Length) function that
returns the point value given a number of similar cards. But it's all
pretty straightforward.
pairs([], Pairs, _Curr) -> pair(Pairs);
pairs([H | T], Pairs, Curr) when H =:= Curr -> pairs(T, Pairs+1, Curr);
pairs([H | T], Pairs, _Curr) -> pair(Pairs) + pairs(T, 1, H).
pair(2) -> 2;
pair(3) -> 6;
pair(4) -> 12;
pair(_Length) -> 0.
That's it for now. Actual game play to come. You can get the code
.