It's summer, time to relax and have a little fun. So hows about a little game of cards? Or maybe I should have titled this: "Fun With Arrays." I actually just wanted an excuse to demonstrate a few useful techniques for working with arrays in Perl, but why not an array of playing cards?
To teach perl to deal a hand of cards is a blissfully succinct job, with only three steps: create the deck, shuffle the deck and deal the hands. I imagine this could be done in three lines of code (if not one) but that's not the rabbit I'm chasing today -- I'd like the program to be easily understandable when it's finished. So let's get started with the first task.
A deck of cards has four suits each with thirteen pip cards, numbered one (ace) to ten, and three royal cards (jack, queen, king). That's a total of fifty-two cards, two arrays, and a job for the list and range operator.
my @suits = qw(hearts diamonds clubs spades);
my @pips = ('ace', 2..10, 'jack', 'queen', 'king');The simplest way of proceeding further is to emulate a real deck, by turning those two arrays into one with each suit and pip paired up. This is really a "combination" problem: we want every possible combination of four suits and thirteen pips. You could accomplish that with two forloops, but Perl gives us mapand if you nest them you get a nice little one-liner.
my @deck = map{my $suit=$_; map{"$_ of $suit"}@pips}@suits;For the shuffle we interate over the entire deck from top to bottom, swapping each card with a randomly selected card from part of the deck we haven't yet processed. In order to keep the possibilities unbiased we are changing the number we give randeach time (a la the Fisher-Yates algorithm). The randfunction will return any real number up to, but not including, the given argument, so we need to add one to our argument if we want it included in the possible outcomes. Conveniently, intjust discards the decimal part of it's argument (in effect it always rounds down) so it compliments this technique.
foreach my $i (reverse 0..$#deck) {
my $r = int rand ($i+1);
@deck[$i, $r] = @deck[$r, $i] unless ($i == $r);
}If you're really lazy (and you should be -- this is Perl after all) you can use the List::Util module and its shufflesubroutine. This module is already included in recent standard distributions and it's implemented in compiled C.
Finally, to create the hands we'll need an array for each player. Pretending we have constants for the number of players and the required hand-size, that looks like this...
my @hands = map{[]}(1..PLAYERS);
for (1..HANDSIZE) {
foreach (@hands) { push @$_, pop @deck }
}Note the use of mapagain to initialize the empty arrays, and the use of popto ensure that we deal our cards from the top of the deck! The complete program is cited below.
#!/usr/bin/perl
use strict;
use warnings;
use constant PLAYERS=>4;
use constant HANDSIZE=>5;
# create a deck of cards
my @suits = qw(hearts diamonds clubs spades);
my @pips = ('ace', 2..10, 'jack', 'queen', 'king');
my @deck = map{my $suit=$_; map{"$_ of $suit"}@pips}@suits;
# shuffle the deck
foreach my $i (reverse 0..$#deck) {
my $r = int rand ($i+1);
@deck[$i, $r] = @deck[$r, $i] unless ($i == $r);
}
# deal the hands
my @hands = map{[]}(1..PLAYERS);
for (1..HANDSIZE) {
foreach (@hands) { push @$_, pop @deck }
}
# reveal
foreach (@hands) {
print join(', ', @$_), "\n\n";
}