Programming Fundamentals - Lab 5 - Solution

EXERCISE
Build a deck of cards. Given the arrays of card data, use a loop to write out the cards to the screen.

Sample output:
2 of Clubs
2 of Diamonds
2 of Hearts
2 of Spades
3 of Clubs

This lab includes 4 arrays (1 for each suit) of ranks.

clubs = [2, 3, 4, 5, 6, 7, 8, 9, 10, "Jack", "Queen", "King", "Ace"]
diamonds = [2, 3, 4, 5, 6, 7, 8, 9, 10, "Jack", "Queen", "King", "Ace"]
hearts = [2, 3, 4, 5, 6, 7, 8, 9, 10, "Jack", "Queen", "King", "Ace"]
spades = [2, 3, 4, 5, 6, 7, 8, 9, 10, "Jack", "Queen", "King", "Ace"]

To display the "cards", we can loop through each array and output the rank with its suit.  Let's start by just looping through the clubs.  We can use either an incrementing index to find each element of the array by position.  Or, we can use the for ___ in ___ loop which does that for us.  Let's go with the latter.

for ____ in clubs
  # do something
end

Sometimes it's nice just to start with the loop structure.  We get to make up a variable that will represent each element in the array as we iterate through it.  We could call it zebra or x or anyting else, but we'll use something descriptive like rank.  And on each iteration of the loop, we'll display a string with the rank in it and a placeholder for the suit.

for rank in clubs
  puts "#{rank} of Clubs"
end

Great.  Now, we just repeat for each other suit:

for rank in clubs
  puts "#{rank} of Clubs"
end

for rank in diamonds
  puts "#{rank} of Diamonds"
end

for rank in hearts
  puts "#{rank} of Hearts"
end

for rank in spades
  puts "#{rank} of Spades"
end

This will output all 52 cards organized by suit, just as instructed.  Problem solved.

However, there's some duplication in this code that could be improved upon.  Instead of 4 loops, since the data in each array is the same, we can loop through it once and display the rank for each suit:

for rank in clubs
  puts "#{rank} of Clubs"
  puts "#{rank} of Diamonds"
  puts "#{rank} of Hearts"
  puts "#{rank} of Spades"
end

We'd probably want to rename the clubs array to something more generic, like ranks.  And that will bring us to the first challenge.

Challenge #1

Challenge #1
The arrays are identical for each suit and can be simplified by using a ranks array as seen below. Try to complete the exercise again by combining these arrays.

This challenge includes 2 arrays with ranks and suits that we'll use:

ranks = [2, 3, 4, 5, 6, 7, 8, 9, 10, "Jack", "Queen", "King", "Ace"]
suits = ["Clubs", "Diamonds", "Hearts", "Spades"]

Looking at the previous solution, you can see that we're outputing the rank 4 times, once for each suit: puts "#{rank} of Clubs" (then "Diamonds", then "Hearts", then "Spades").  Instead of repeating that code 4 times, we can combine the rank with each suit using the suits array.  

For example, when rank is 2, we want to combine it with "Clubs", then "Diamonds", then "Hearts", then "Spades".  To go through each of those elements in the suits array, it makes sense to use another loop:

for rank in ranks
  for ___ in suits
    puts "#{rank} of ___"
  end
end

Once again, we need to name a variable for the suits loop.  We can name it anything, but suit is a nice descriptive variable.  You may notice a pattern that often the array name is pluralized and then we use the singular of it for the loop.  That's a good pattern to remember!

Now that we have a variable name for the suits loop, we can replace the placeholder in our string with the suit.

for rank in ranks
  for suit in suits
    puts "#{rank} of #{suit}"
  end
end

Our code now loops through the ranks and, for each rank, it then loops through the suits and combines that rank with each suit.  The combination of rank and suit is a "card".  Once the suits loop is complete for the rank, the code moves on to the next rank and repeats until all the ranks have been combined with all the suits.

Challenge #2

Deal a poker hand. Shuffle the deck and "deal" (i.e. display) a 5 card hand (i.e. 5 cards from the deck).
You will want to look at the documentation for Arrays: https://ruby-doc.org/core-2.7.0/Array.html

For the challenge, we don't just want to see all the combinations of ranks and suits, but we want to store them as cards in a new array that we can then shuffle and deal a hand from.  So instead of just using puts, we'll add each "card" to a new array.  First let's create that array:

deck = []

To start, the deck array is empty.  We'll fill it as we build each code in our loop code.

for rank in ranks
  for suit in suits
    card = "#{rank} of #{suit}"
    deck.push(card)
  end
end
puts deck

Here, instead of just using puts with the card, we're "pushing" (i.e. adding it) to the end of the deck.  So "2 of Clubs" becomes the first element in the deck array.  Then "2 of Diamonds" becomes the second element. And "2 of Hearts", and then "2 of Spades", and "3 of Clubs", etc, until we have all the cards (combinations of ranks and suits) in the deck array.

The deck is currently in perfect order, so we want to shuffle it to get a random order.  If we look back to the ruby Array documentation, we'll find a method .shuffle (here) which appears to do exactly what we want - randomize the order.

shuffled_deck = deck.shuffle
puts shuffled_deck

Now we want to get a 5-card hand from the shuffled deck.  We could just do this:

hand = [
  shuffled_deck[0],
  shuffled_deck[1],
  shuffled_deck[2],
  shuffled_deck[3],
  shuffled_deck[4]
]

But hopefully you're starting to guess that there are more efficient ways to perform common tasks.  In this case, looking again at the documentation shows the use of the square brackes ([]) to read n elements from the array starting at any position (here).  We use a starting index and the number of elements like this:

hand = shuffled_deck[0,5]
puts hand

This will read 5 elements from the shuffled_deck array beginning at index 0.

And the final step is to display the cards in the hand.  Again we could hardcode each index, but a loop seems appropriate here.  That way, if the size of the hand ever changes (e.g. 7-code poker), the loop will still just work.

for card in hand
  puts card
end

All the code together:

ranks = [2, 3, 4, 5, 6, 7, 8, 9, 10, "Jack", "Queen", "King", "Ace"]
suits = ["Clubs", "Diamonds", "Hearts", "Spades"]

deck = []
for rank in ranks
  for suit in suits
    card = "#{rank} of #{suit}"
    deck.push(card)
  end
end

shuffled_deck = deck.shuffle
hand = shuffled_deck[0,5]

for card in hand
  puts card
end