cribbage.erl

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 here.