2008 Winter Scripting Games

/\/\o\/\/'s Solution to Advanced Windows PowerShell Event 10: Blackjack!

Event 1 Solution


Marc van Orsouw, better know to the world as /\/\o\/\/, is a Microsoft MVP and developer of the PowerShell add-in PowerTab. /\/\o\/\/ also writes the incredibly-popular blog The PowerShell Guy.

*

Blackjack!

For this year’s Scripting Games I was asked to complete one of the events and then write up a short article explaining how I went about solving the problem. The Scripting Guys handed out the events by drawing from a hat.

When /\/\o\/\/ put the hat on his head, the hat called out “Blackjack!” immediately. Meanwhile, /\/\o\/\/ drifted off into memories of his youth, back to when he was just 18. “Welcome to the House of Fun” said the sign, so, after studying tables and training on his own blackjack program in Atari Basic, /\/\o\/\/ went up to the casino (in T-shirt and sports shoes, of course). After being refused, he went to a secondhand shop around the corner and bought shoes, and a suit jacket to put over his T-shirt. And then it was back into the Casino again. (I’m so happy there was no Flickr yet, but we were in.)

Anyway, I was very happy with the event that was assigned to me. First, I remembered that I had dealt with cards before, and thus might have a virtual deck of cards handy ready to give me a head start. However, I could only find a deck of colored cards from the 2007 Scripting Games Advanced PowerShell Event 10. Referring back to this event was useful, as we’ll see later in this article, but the deck was not useful. Hence I started out creating a deck of normal playing cards.

Note. For this article I chose an “Interactive walk through” format. In this format, I build up the script following the Admin Development model, making up the result from interactively-tested pieces of code. The best way to follow this walk-through is to paste in the code samples as you go.

The code is not shown as a complete script until the end of the article, but all the code pieces can be pasted into the PowerShell console to follow the article interactively, and combined later.

Create a Deck

I made a hash table with the different suites in a deck of playing cards, and also created an array to hold all the values found within a suit:

# Generate a new deck of Playing cards

$suites = @{S='Spades';H='Hearts';D='Diamonds';C='Clubs'}
$suite = 2,3,4,5,6,7,8,9,0,'J','Q','K','A'
$Newdeck = $suites.getEnumerator() |% {$s=$_;$suite |% {"$($s.name)$_"}}

# Generate a Shoe ( for casino rules )

$Shoe = $NewDeck * 6

The result can be seen here:

Event 10 Solution


Also, I added an example to make a “Shoe,” a combination of multiple decks as used in a casino (not worked out). I started out from "Normal" blackjack rules, and will adapt the script where the event rules differ; that enables us to use this code as a suitable base for a more full-blown Blackjack script later on.

Ok, we have a deck, what to we do next? Ah, yes: shuffle.

Shuffle the Deck

My deck of colored cards was not useful for this event, but I remembered that my Windows PowerShell card shuffle machine from Scripting Games 2007 Advanced PowerShell Event 10 could be of use for this event. Therefore, the search for the deck still helped a lot. This might have been a challenge for competitors that did not attend last year’s Scripting Games, or did find my blog entry. But because I had the Shuffle machine from last year hanging around, making a function for this script was easy:

# Create a function to Shuffle cards

Function Shuffle-Cards ($cards){
  $r = new-object random
  $Deck = [System.Collections.Queue]($cards | % { $in = $_; 1 | select @{n='Card';e={$in}},@{n='val';e={$r.Next()}} } | sort val)
  ,$deck
}

# Now we can Shuffle a new deck (or Shoe )

$deck = Shuffle-Cards $NewDeck

Dealing the Cards

After we’ve shuffled the deck, we can start dealing the cards:

# And start dealing some cards to test 

$myCards = @()
$dealerCards = @()

$myCards += $deck.Dequeue()
$dealerCards += $deck.Dequeue()
$myCards += $deck.Dequeue()
Event 10 Solution


OK, so far so good. I have a shuffled deck of cards now, and I dealt the starting 2 hands. For casino rules, the dealer’s second card is sometimes held until all players have had their turn. For testing, I kept this same approach, dealing the second card facedown.

Checking Values of Cards

Next I had to check the value of the cards in a hand. Because this is needed on every turn I made a small function for checking the value of a hand:

# Function to get the value of a hand

Function get-Value ($cards){
   $aces = $val = 0
   $cards |% {
       if ($_.card[1] -match '[0JQK]') {$val += 10}
       if ($_.card[1] -match '\d') {$val += [int]$_.card[1].toString()}
       if ($_.card[1] -match '[A]') {$val += 11;$aces += 1}
   }
   while ($aces -gt 0 ) {if ( $val -gt 21) {$val -= 10};$aces--}
   $val  
}

Now we can simply check the value of a hand like this:

Event 10 Solution


In the rules we were told to specify the cards by name. For the card suits I already had a hash table to look up the full names. I did not have anything like this for the card values, so I created another hash table and a function WriteCard:

# Write a card by Friendly name

$CardNames = @{'2'='Two';'3'='Three';'4'='Four';'5'='Five';
               '6'='Six';'7'='Seven';'8'='Eight';'9'='Nine';'0'='Ten';
               J='Jack';Q='Queen';K='King';A='Ace'}

Function WriteCard ($Card,$x,$y){
  $v = $cardNames."$($card.card[1])"
  $s = $suites."$($card.card[0])" 
  "$v of $s"
}
Event 10 Solution


Painting the Cards

Because I did not find this “PowerShell-y” enough, I made another function, PaintCard, making use of the WriteLine and ColorLine functions I had from last year, and my good old Throw-Dice function (/\/\o\/\/ PowerShelled: Throw Dice in MSH):

# Helper functions for Painting Cards 

Function ColorLine ($x,$y,$l,[system.consolecolor]$bgc) {
  $pos = $host.ui.RawUI.WindowPosition
  $pos.x += $x
  $pos.y += $y
  $row = $host.ui.rawui.NewBufferCellArray((' ' * $l),$bgc,$bgc) 
  $host.ui.rawui.SetBufferContents($pos,$row) 
} 

Function WriteLine ($x,$y,$Text,[system.consolecolor]$fgc,[system.consolecolor]$bgc) {
  $pos = $host.ui.RawUI.WindowPosition
  $pos.x += $x
  $pos.y += $y
  $row = $host.ui.rawui.NewBufferCellArray($text,$fgc,$bgc) 
  $host.ui.rawui.SetBufferContents($pos,$row) 
} 

Function PaintCard ($Card,$x,$y){
  $v = $card.card[1] 
  switch ($card.card[0]) {
    'S' {$s =  [char]9824;$fc = 'Black'}
    'H' {$s =  [char]9829;$fc = 'Red'}
    'D' {$s =  [char]9830;$fc = 'Red'}
    'C' {$s =  [char]9827;$fc = 'Black'}
  }

  WriteLine $x $y "$v    $v" $fc 'white'
  WriteLine $x ($y + 1) "$s    $s" $fc 'white'
  ($y + 2)..($y + 4) |% {ColorLine $x $_ 6 'White'}
  WriteLine $x ($y + 5) "$s    $s" $fc 'white'
  WriteLine $x ($y + 6) "$v    $v" $fc 'white'

  if ($v -eq '0') {
    WriteLine $x $y "10  10" $fc 'white'
    WriteLine $x ($y + 6) "10  10" $fc 'white'
  }

}

Function PaintFaceDownCard ($x,$y){
  ($y)..($y + 6) |% {ColorLine $x $_ 6 'Blue'}
  WriteLine $x ($y + 2) " /\/\ " 'white' 'Blue'
  WriteLine $x ($y + 3) "  ()  " 'white' 'Blue'
  WriteLine $x ($y + 4) " \/\/ " 'white' 'Blue'
}

# and we are ready to show the cards

$DealerCards |% {$i = 0} {PaintCard $_ $i 0 ;$i+=7}
$myCards |% {$i = 0} {PaintCard $_ $i 15 ;$i+=7}
PaintFaceDownCard 7 0

The end result looks like this:

Event 10 Solution


Player Loop

We’ve dealt with the shuffling, the dealing of the first hands, and displaying the cards. Now we need to implement the player loop:

# Player loop 

  $stand = $False

  while ($stand -eq $False ) {
    WriteLine 2 28 "Dealer : $(get-Value $dealerCards)" 'White' 'Black'
    WriteLine 2 29 "You    : $(get-Value $myCards)" 'White' 'Black'
    if ((get-Value $myCards) -eq 21 ) {"BlackJack, You win !";return}
    WriteLine 2 30 '[H]it [S]tand [Q]uit ?' 'White' 'Black'

    $key = $host.ui.RawUI.ReadKey('NoEcho,IncludeKeyUp')
    $Choice = [string]$key.Character
  
    Switch ($Choice) {
        'h' {
              $myCards += $deck.Dequeue()
              $myCards |% {$i = 0} {PaintCard $_ $i 15 ;$i+=7}
              if ((get-Value $myCards) -eq 21) {"BlackJack";return}
              if ((get-Value $myCards) -gt 21) {"Busted";return}
            }
        's' {
              $stand = $true
            }
        'q' {
              write-host "Quit"
              return
            }
         Default {write-host -no "`a"}
    }
  }

I used my own prompting to make sure the console did not “move” when I combined the code pieces. I also used the WriteLine function used for painting the cards to write the Status at a fixed place on the screen:

Event 10 Solution


Dealer loop

After the user’s turn we need a dealer loop; that was the next thing to implement:

# Dealer Loop
  
  $rule = 17 # Dealer stands on all 17s ( most common )
  $rule = (get-Value $myCards) # Dealer stands on win ( Scripting games )

  while ($True) {
     $dealerCards += $deck.Dequeue()
     $DealerCards |% {$i = 0} {PaintCard $_ $i 0 ;$i+=7}
     WriteLine 2 28 "Dealer : $(get-Value $dealerCards)" 'White' 'Black'

     sleep 1
     if ((get-Value $dealerCards) -eq 21 ) {"Dealer BlackJack, Dealer wins";return}
     if ((get-Value $dealerCards) -gt 21 ) {"Dealer busted, you win";return}
     if ((get-Value $dealerCards) -ge $rule ) {"Dealer Stands at $(get-Value $dealerCards)"
       if((get-Value $dealerCards) -ge (get-Value $myCards)){"you Lose"}else{"You win"};return
     }
  }

Combine

The last step was to combine everything into one function to get the following result:

Event 10 Solution


The final function looks like this:

function start-BlackJack {

  # load helper functions

    Function Shuffle-Cards ($cards){
    $r = new-object random
    $Deck = [System.Collections.Queue]($cards | % { $in = $_; 1 | select @{n='Card';e={$in}},@{n='val';e={$r.Next()}} } | sort val)
    ,$deck
    }
    
    # Function to get the value of a hand
    
    Function get-Value ($cards){
    $aces = $val = 0
    $cards |% {
        if ($_.card[1] -match '[0JQK]') {$val += 10}
        if ($_.card[1] -match '\d') {$val += [int]$_.card[1].toString()}
        if ($_.card[1] -match '[A]') {$val += 11;$aces += 1}
   }
    while ($aces -gt 0 ) {if ( $val -gt 21) {$val -= 10};$aces--}
    $val  
}    
    # Helper functions for Painting Cards 
    
    Function ColorLine ($x,$y,$l,[system.consolecolor]$bgc) {
    $pos = $host.ui.RawUI.WindowPosition
    $pos.x += $x
    $pos.y += $y
    $row = $host.ui.rawui.NewBufferCellArray((' ' * $l),$bgc,$bgc) 
    $host.ui.rawui.SetBufferContents($pos,$row) 
    } 
    
    Function WriteLine ($x,$y,$Text,[system.consolecolor]$fgc,[system.consolecolor]$bgc) {
    $pos = $host.ui.RawUI.WindowPosition
    $pos.x += $x
    $pos.y += $y
    $row = $host.ui.rawui.NewBufferCellArray($text,$fgc,$bgc) 
    $host.ui.rawui.SetBufferContents($pos,$row) 
    } 
    
    Function PaintCard ($Card,$x,$y){
    $v = $card.card[1] 
    switch ($card.card[0]) {
        'S' {$s =  [char]9824;$fc = 'Black'}
        'H' {$s =  [char]9829;$fc = 'Red'}
        'D' {$s =  [char]9830;$fc = 'Red'}
        'C' {$s =  [char]9827;$fc = 'Black'}
    }
    
    WriteLine $x $y "$v    $v" $fc 'white'
    WriteLine $x ($y + 1) "$s    $s" $fc 'white'
    ($y + 2)..($y + 4) |% {ColorLine $x $_ 6 'White'}
    WriteLine $x ($y + 5) "$s    $s" $fc 'white'
    WriteLine $x ($y + 6) "$v    $v" $fc 'white'
    
    if ($v -eq '0') {
        WriteLine $x $y "10  10" $fc 'white'
        WriteLine $x ($y + 6) "10  10" $fc 'white'
    }
    }

    Function PaintFaceDownCard ($x,$y){
    ($y)..($y + 6) |% {ColorLine $x $_ 6 'Blue'}
    WriteLine $x ($y + 2) " /\/\ " 'white' 'Blue'
    WriteLine $x ($y + 3) "  ()  " 'white' 'Blue'
    WriteLine $x ($y + 4) " \/\/ " 'white' 'Blue'
    }


  # Set up new Game 

  cls 
  "`n" * 15

  # Generate a new deck of Playing cards

  $suites = @{S='Spades';H='Hearts';D='Diamonds';C='Clubs'}
  $suite = 2,3,4,5,6,7,8,9,0,'J','Q','K','A'
  $Newdeck = $suites.getEnumerator() |% {$s=$_;$suite |% {"$($s.name)$_"}}

  $deck = Shuffle-Cards $NewDeck

  $myCards = @()
  $dealerCards = @()

  $myCards += $deck.Dequeue()
  $dealerCards += $deck.Dequeue()
  $myCards += $deck.Dequeue()

  $DealerCards |% {$i = 0} {PaintCard $_ $i 0 ;$i+=7}
  $myCards |% {$i = 0} {PaintCard $_ $i 15 ;$i+=7}
  PaintFaceDownCard 7 0

  # Player loop 

  $stand = $False

  while ($stand -eq $False ) {
    WriteLine 2 28 "Dealer : $(get-Value $dealerCards)" 'White' 'Black'
    WriteLine 2 29 "You    : $(get-Value $myCards)" 'White' 'Black'
    if ((get-Value $myCards) -eq 21 ) {"BlackJack, You win !";return}
    WriteLine 2 30 '[H]it [S]tand [Q]uit ?' 'White' 'Black'

    $key = $host.ui.RawUI.ReadKey('NoEcho,IncludeKeyUp')
    $Choice = [string]$key.Character
  
    Switch ($Choice) {
        'h' {
              $myCards += $deck.Dequeue()
              $myCards |% {$i = 0} {PaintCard $_ $i 15 ;$i+=7}
              if ((get-Value $myCards) -eq 21) {"BlackJack";return}
              if ((get-Value $myCards) -gt 21) {"Busted";return}
            }
        's' {
              $stand = $true
            }
        'q' {
              write-host "Quit"
              return
            }
         Default {write-host -no "`a"}
    }
  }

  # Dealer Loop
  
  $rule = 17 # Dealer stands on all 17s ( most common )
  $rule = (get-Value $myCards) # Dealer stands on win ( Scripting games )

  while ($True) {
     $dealerCards += $deck.Dequeue()
     $DealerCards |% {$i = 0} {PaintCard $_ $i 0 ;$i+=7}
     WriteLine 2 28 "Dealer : $(get-Value $dealerCards)" 'White' 'Black'

     sleep 1
     if ((get-Value $dealerCards) -eq 21 ) {"Dealer BlackJack, Dealer wins";return}
     if ((get-Value $dealerCards) -gt 21 ) {"Dealer busted, you win";return}
     if ((get-Value $dealerCards) -ge $rule ) {"Dealer Stands at $(get-Value $dealerCards)"
       if((get-Value $dealerCards) -ge (get-Value $myCards)){"you Lose"}else{"You win"};return
     }
  }
}
start-BlackJack

I enjoyed writing this article about how I came to my solution. Event 10 is the last event of the Scripting Games. That means that you will read this after the Games have ended, and I hope you had a good time.

By the way, I will post my solutions to the other events – although not with a complete article like this ;-) – on my blog http://thePowerShellGuy.com.

Enjoy.

P.S. All the code formatting was done in PowerShell Analyzer.


Top of pageTop of page