One way to do it is to generate an array of indices that represent your cards. Shuffle that array, and then remove the indices from that array as you draw a card.
// generate random list of indices from 0...12 four each
var cardIndices = (0...51).map {($0 % 13, arc4random())}.sort{$0.1 < $1.1}.map{$0.0}
// To get a card, remove last card from deck
let last = cardIndices.removeLast()
// use the index to look up the picture
let randomCard = picture[last]
// It's also easy to check how many cards you have left in your deck
let remaining = cardIndices.count
This works by first creating an array of tuples which contain a number from 0...12 and a some random integer. Then that array is sorted by the random integer element in the tuple, and then map
is used to separate out just the array of indices leaving you with a random array of Int
with values from 0...12 (four values of each).
Here it is in class form.
import UIKit
struct Card {
let image: UIImage
let text: String
}
class Deck {
private let cards:[Card] = [
Card(image: UIImage(named: "Card2")!, text: "Velg en som må drikke"),
Card(image: UIImage(named: "Card3")!, text: "Drikk selv"),
Card(image: UIImage(named: "Card4")!, text: "Alle jenter må drikke"),
Card(image: UIImage(named: "Card5")!, text: "Tommelen"),
Card(image: UIImage(named: "Card6")!, text: "Alle gutter må drikke"),
Card(image: UIImage(named: "Card7")!, text: "Pek på himmelen"),
Card(image: UIImage(named: "Card8")!, text: "Drikkepartner"),
Card(image: UIImage(named: "Card9")!, text: "Rim"),
Card(image: UIImage(named: "Card10")!, text: "Kategori"),
Card(image: UIImage(named: "CardJack")!, text: "Lag en regel"),
Card(image: UIImage(named: "CardQueen")!, text: "Spørsmålsrunde"),
Card(image: UIImage(named: "CardKing")!, text: "Hell drikke i koppen"),
Card(image: UIImage(named: "CardAce")!, text: "Fossefall")
]
private var cardIndices = [Int]()
var cardsInDeck: Int { return cardIndices.count }
func shuffleCards() {
cardIndices = (0...51).map{($0 % 13, arc4random())}.sort{$0.1 < $1.1}.map{$0.0}
}
func drawCard() -> Card {
if cardIndices.count == 0 {
shuffleCards()
}
let last = cardIndices.removeLast()
return cards[last]
}
}
Notes:
- The
cards
and cardIndices
have been made private
to hide those details from a user of Deck
.
- Thanks to @Paulw11's suggestion, this solution now uses a
struct
to represent a card. This keeps the data together and provides a nice value that can be returned from drawCard
.
- The user of a
Deck
can create a Deck
with Deck()
, they can call shuffleCards()
to randomize the deck, check the cardsInDeck
property to find out how many shuffled cards are available, and they can call drawCard()
to get the next card from the deck.
How to Use
For the viewController that controls the deck, add a property to the viewController:
class MyGame: UIViewController {
var deck = Deck()
// the rest of the code
}
Then when you need a card, for example inside of an @IBAction
for a button, just call deck.drawCard
:
@IBAction func turnOverNextCard(button: UIButton) {
let card = deck.drawCard()
// Use the image and text to update the UI
topCardImageView.image = card.image
topCardLabel.text = card.text
// I'm not going to wait for the deck to shuffle itself
if deck.cardsInDeck < 10 {
deck.shuffleCards()
}
}
Splitting Hairs: A Better Shuffle
My shuffle routine shuffles the deck by associating a random UInt32
with each card and then sorting the deck by those values. If the same random number is generated for two cards, then it is possible that earlier cards in the deck would be favored over later cards (or vice versa depending on the sort algorithm). This is really splitting hairs, but in the interest of providing the best shuffle possible, I provide the following alternative:
func shuffleCards() {
cardIndices = (0...51).map {$0 % 13}
for i in (1...51).reverse() {
let rand = Int(arc4random_uniform(UInt32(i + 1)))
(cardIndices[i], cardIndices[rand]) = (cardIndices[rand], cardIndices[i])
}
}
This algorithm is based upon the Fisher-Yates shuffle.