Blackjack!

dpuu on 2008-12-24T20:37:42

The final challenge was blackjack. The instructions said that we could choose to implement that aces always equal 11, but that seems insufficiently interesting: I wanted to use junctions!

To start with, a simple definition of a deck of cards. The challenge required humanly named cards: so I started with a list of pairs, and then crossed this list against the list of suites. Finally, "@cards = @deck.pick( @deck.elems )" results in a random shuffle.

my @values = ( ace => 1|11, two => 2, three => 3, four => 4, five => 5, six => 6, seven => 7, eight => 8, nine => 9, ten => 10, jack => 10, queen => 10, king => 10, );

my @suites = < spades clubs diamonds hearts >;

my @deck = ( @values X @suites ).map: { my ($name, $value) = $^a.kv; "$name of $^b" => $value };

my @cards = @deck.pick( @deck.elems );


So that's my deck of cards. I first deal the initial hands:

my @dealer; my @player;

@dealer.push( @cards.shift ); @player.push( @cards.shift ); @dealer.push( @cards.shift );

say "DEALER:"; say @dealer[0].key; #only display the first card of dealer's hand say "";

say "PLAYER:"; .key.say for @player;


Now we come to the main player loop. Card values are junctions, so I don't need to worry about calculating all possible values of hands that include aces. However, I do need to remember to not test for "bust" using a "greater-than" test. Because of the logic of "any" junctions, I need to check only for "less than 21" is not bust (and == 21 is WIN):



my $choice = "hit";

my $player_value = [+] @player.map: { .value };

while ($choice ~~ /h/) {

my $card = @cards.shift;

@player.push( $card ); say $card.key;

$player_value += $card.value;

say "current value is { $player_value.perl }";

if $player_value == 21 { say "congradulations, you win!"; $choice = "s"; } elsif $player_value < 21 { say "hit (h) or stay (s)"; $choice = "stay" unless $player_value < 16; say $choice;

#TODO: read STDIN } else { say "Sorry, you bust!"; $choice = "s"; } }


Note that my version of Rakudo doesn't implement "last" properly yet, so I arranged my code to not need it.

Now the dealer gets to play (but only if the game is still live). A problem here was to figure out what "player value" to use. If a player hand is (ace, ace, eight), then possible values are 10, 20, and 30. I need to make sure that "20" is used for all tests against the dealers value. I couldn't figure out an analytic way to achieve this, so I brute forced it: "max (4 .. 21).grep: { $_ == $player_value }". (I start the list at 4 because that's the lowest possible blackjack hand).

Checking for a dealer win has the same problem: I can't use "less-than" or "greater-than" tests because of possible junctional values (e.g. "ace, six, ten" is both not-bust, and greater than any possible play hand).



say "";

if $player_value < 21 && not $player_value == 21 {

$player_value = max (4 .. 21).grep: { $_ == $player_value };

say "DEALER:"; .key.say for @dealer;

my $dealer_value = [+] @dealer.map: { .value }; my $done = 0;

while ! $done { say "dealer value: {$dealer_value.perl}";

if $dealer_value == any( $player_value ^.. 21) { say "you loose!"; $done = 1; } elsif $dealer_value < 21 { my $card = @cards.shift; @dealer.push( $card ); say $card.key; $dealer_value += $card.value; } else { say "dealer bust: you win!"; $done = 1; } }

}


And so that's it. Junctions work, but I can't help feeling that we need some more language features to help with the "collapsing" of a junction to its appropriate value -- and that includes a "partial collapse" that, for example, excludes all values greater than 21 after I've checked that there'd be at least one value left over. Hmm, perhaps that could be done with a subset type...