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 .