The problem
From the Practice section of the web-apps README:
Cards. Make a page at
/cardsthat deals a five-card hand from a shuffled deck, a new hand every time the page loads. Build the deck from these two arrays:ranks = [2, 3, 4, 5, 6, 7, 8, 9, 10, "Jack", "Queen", "King", "Ace"] suits = ["Clubs", "Diamonds", "Hearts", "Spades"]Show each card's name ("Queen of Hearts"). Then show its picture:
public/images/cards/has one per card, named likequeen_of_hearts.svg, and the page can show it with<img src="/images/cards/queen_of_hearts.svg">. Add a link that deals again.
You've dealt this hand before, in lab 5 of ruby-intro. That time it went to the terminal with puts; this time it goes on a page.
1. The route and the controller
get("/cards", { :controller => "cards", :action => "index" })
bin/rails generate controller cards
class CardsController < ApplicationController
def index
render({ :template => "cards/index" })
end
end
2. The deck
A loop inside a loop: for every rank, every suit. That's 13 times 4, 52 cards. Start with an empty array and push each card onto it:
<% 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 = { "name" => "#{rank} of #{suit}", "image" => "#{rank}_of_#{suit}".downcase } %>
<% deck.push(card) %>
<% end %>
<% end %>
Each card needs two things: a name to show ("Queen of Hearts") and the name of its picture (queen_of_hearts). They differ in two ways, the spaces and the capital letters, so a hash holds both, the way a row from the database holds its columns. card["name"] and card["image"] read them the way company["name"] did.
"#{rank}_of_#{suit}" is "Queen_of_Hearts"; .downcase makes it "queen_of_hearts", the file's name. A number works too: "#{10}_of_Clubs".downcase is "10_of_clubs".
3. Shuffle, then deal five
<% hand = deck.shuffle[0, 5] %>
shuffle gives back the deck in a random order. [0, 5] takes five cards, starting from the first. Every refresh is a new request, so the view runs again and the deck is shuffled again.
This is why the hand comes from a shuffled deck, instead of picking five cards with rand the way the dice did: a deck has one Queen of Hearts. Five rand picks could pick the same card twice; five cards off the top of a shuffled deck can't. Two dice can match. Two cards in one hand can't.
4. The hand on the page
<h1>Cards</h1>
<p>
<% for card in hand %>
<img src="/images/cards/<%= card["image"] %>.svg" width="100" alt="<%= card["name"] %>">
<% end %>
</p>
<ul>
<% for card in hand %>
<li><%= card["name"] %></li>
<% end %>
</ul>
<a href="/cards">Deal again</a>
The alt text is what a screen reader says for the picture, and what the browser shows if the picture can't load. The card's name is exactly right for it.
The whole view
<% 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 = { "name" => "#{rank} of #{suit}", "image" => "#{rank}_of_#{suit}".downcase } %>
<% deck.push(card) %>
<% end %>
<% end %>
<% hand = deck.shuffle[0, 5] %>
<h1>Cards</h1>
<p>
<% for card in hand %>
<img src="/images/cards/<%= card["image"] %>.svg" width="100" alt="<%= card["name"] %>">
<% end %>
</p>
<ul>
<% for card in hand %>
<li><%= card["name"] %></li>
<% end %>
</ul>
<a href="/cards">Deal again</a>
That's a lot of Ruby for a view. It works, and for one page it's fine. Next week, Ruby that works something out moves into the controller, and the view goes back to mostly showing it.
When it went wrong
- Broken images. The file name: lowercase, underscores,
.svgon the end. View the page source and compare onesrcwith a file name inpublic/images/cards/. - The same card twice in a hand. Five
randpicks instead of one shuffled deck. - 52 cards on the page. The loop over
deckinstead ofhand. - "Queen of Hearts" in the image name. The name used where the image belongs: spaces and capitals, so no file matches.