Learn Courses My Dashboard

Module 2: Challenge 2

Because, as I mentioned earlier:

randomElement() returns an Optional element, so it will need to be unwrapped at some point before it can be used.

So something like:

var card = deck.randomElement()
print("My card is \(card?.suit ?? "no card")")

By the way, this:

card = (String) (rndNum)

Is not the usual way to convert an Int to a String in Swift. Either of these would be better:

card = String(rndNum)
card = "\(rndNum)"

The code you have technically turns rndNum not into a String but into a (String), or a single-member tuple consisting of a String. Although those get treated by the language as the equivalent of the single element’s type, they aren’t actually the same thing, as you can see if you check the type on your card variable.

I corrected the code but am now receiving a warning message.

String interpolation produces a debug description for an optional value; did you mean to make this explicit?

//
//  ContentView.swift
//  Shared
//
//  Created by Eric Beecroft on 10/4/21.
//

import SwiftUI

struct Card: Equatable{
    var cnumb:Int
    var suit:String
}

struct ContentView: View {
    var suit = ["Spades", "Clubs", "Hearts", "Diamonds"]
    @State var deck = [Card]()
    var body: some View {
        VStack{
            Spacer()
            //Label("UI Card Program")
            Text("Cards in Deck: \(deck.count)")
            Spacer()
            HStack{
                Button("Build Deck"){
                    let rndNum = Int.random(in: 1...13)
                    let rndSuit = suit[Int.random(in: 0..<suit.count)]
                    let myCard = Card(cnumb: rndNum, suit: rndSuit)
                    if(!deck.contains(myCard)){
                        deck.append(myCard)
                        var card = ""
                        if(rndNum == 1){
                            card = "Ace"
                        }
                        else if(rndNum == 11){
                            if(rndSuit.contains("Hearts") || rndSuit.contains("Clubs")){
                                card = "One-eyed Jack"
                            }
                            else{
                                card = "Two-eyed Jack"
                            }
                        }
                        else if(rndNum == 12){
                            card = "Queen"
                        }
                        else if(rndNum == 13){
                            card = "King"
                        }
                        else{
                            card = String (rndNum)
                        }
                        print("Generated a \(card) of \(rndSuit)")
                    }
                    else{
                        print("Generated duplicate card!")
                        print("Number of cards in deck: \(deck.count)")
                    }
                }
                Button("Draw Card"){
                    if(deck.count > 0){
                        let card = deck.randomElement()
                        print("I drew a \(card?.cnumb) of \(card?.suit)")
                        
                    }
                    else{
                        print("There are no cards in the deck!")
                    }
                }
            }
            Spacer()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Because you are giving it an Optional value to print out.

This isn’t really a problem, unless you just hate seeing warnings in Xcode; warnings don’t prevent your code from running and usually won’t crash your app. If you keep it an Optional, the output will look something like:

I drew a nil of nil
I drew a Optional(9) of Optional("Hearts")

(depending on if the Optional is nil or not)

If, however, you supply a default value via nil coalescing:

print("I drew a \(card?.cnumb ?? 0) of \(card?.suit ?? "no suit")")

Then you will see output like:

I drew a 0 of no suit
I drew a 9 of Hearts

(depending on if the Optional is nil or not)

Okay. I just decided to look at the solution and I was wondering why it was so different than what I had written. Also when I typed up the solution it had decided to throw a couple of cannot find variable in scope for each of the functions. Deck, message, suits, and generatedLog are throwing scope errors in addCard and drawCard functions.

//
//  ContentView.swift
//  Module2Challenge2
//
//  Created by Eric Beecroft on 10/13/21.
//

import SwiftUI

struct Card{
    //Initialize card number and suit to defaults
    var num = 1
    var suit = 0
}

struct ContentView: View {
    //Important state variables
    @State var generatedLog = [String]()
    @State var message = ""
    
    //Sets up values for the cards
    @State var deck = [Card]()
    let suits = ["Clubs", "Spades", "Hearts", "Diamonds"]
    
    //The main program
    var body: some View {
        VStack(spacing: 10.0){
            Text(message)
            HStack(spacing: 10.0){
                Button("Add Card"){
                    addCard()
                }
                Button("Draw Card"){
                    
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

func addCard(){
    //Draws Random cards
    let randNumber = Int.random(in: 1...13)
    let randSuit = Int.random(in: 0...3)
    let newCard = Card(num: randNumber, suit: randSuit)
    let numberSuitString = String(newCard.num) + "/" + String(newCard.suit)
    
    if generatedLog.contains(numberSuitString){
        message = "Generated duplicate card."
    }
    else{
        deck.append(newCard)
        generatedLog.append(numberSuitString)
        let cardName = getCardName(newCard.num)
        let suitName = suits[newCard.suit]
        
        message = "Generated a \(cardName) of \(suitName)"
    }
}

func drawCard(){
    if(deck.count == 0){
        message = "No cards in the deck."
    }
    else{
        let randIndex = Int.random(in: 0...deck.count-1)
        let randomCard = deck[randIndex]
        let cardName = getCardName(randomCard.number)
        let suitName = suits[randomCard.suit]
        messsage = "Drew a \(cardName) of \(suitName)"
    }
}

func getCardName(_ cardNumber:Int) -> String{
    if(cardNumber == 1){
        return "Ace"
    }
    else if(cardNumber == 11){
        return "Jack"
    }
    else if(cardNumber == 12){
        return "Queen"
    }
    else if(cardNumber == 13){
        return "King"
    }
    else{
        return String(cardNumber)
    }
}

The generatedLog, deck and message variables were defined inside your ContentView struct.

The addCard and drawCard functions are defined outside your ContentView struct.

That’s why you are getting scope errors.

Okay I just rearranged the functions to be inside the Content view structures and I am still getting the scoping errors. I thought moving the preview portion to be at the end should have fixed it as that is how it shows in the solution.

//
//  ContentView.swift
//  Module2Challenge2
//
//  Created by Eric Beecroft on 10/13/21.
//

import SwiftUI

struct Card{
    //Initialize card number and suit to defaults
    var num = 1
    var suit = 0
}

struct ContentView: View {
    //Important state variables
    @State var generatedLog = [String]()
    @State var message = ""
    
    //Sets up values for the cards
    @State var deck = [Card]()
    let suits = ["Clubs", "Spades", "Hearts", "Diamonds"]
    
    //The main program
    var body: some View {
        VStack(spacing: 10.0){
            Text(message)
            HStack(spacing: 10.0){
                Button("Add Card"){
                    addCard()
                }
                Button("Draw Card"){
                    
                }
            }
        }
    }
}

func addCard(){
    //Draws Random cards
    let randNumber = Int.random(in: 1...13)
    let randSuit = Int.random(in: 0...3)
    let newCard = Card(num: randNumber, suit: randSuit)
    let numberSuitString = String(newCard.num) + "/" + String(newCard.suit)
    
    if generatedLog.contains(numberSuitString){
        message = "Generated duplicate card."
    }
    else{
        deck.append(newCard)
        generatedLog.append(numberSuitString)
        let cardName = getCardName(newCard.num)
        let suitName = suits[newCard.suit]
        
        message = "Generated a \(cardName) of \(suitName)"
    }
}

func drawCard(){
    if(deck.count == 0){
        message = "No cards in the deck."
    }
    else{
        let randIndex = Int.random(in: 0...deck.count-1)
        let randomCard = deck[randIndex]
        let cardName = getCardName(randomCard.number)
        let suitName = suits[randomCard.suit]
        messsage = "Drew a \(cardName) of \(suitName)"
    }
}

func getCardName(_ cardNumber:Int) -> String{
    if(cardNumber == 1){
        return "Ace"
    }
    else if(cardNumber == 11){
        return "Jack"
    }
    else if(cardNumber == 12){
        return "Queen"
    }
    else if(cardNumber == 13){
        return "King"
    }
    else{
        return String(cardNumber)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Your functions are still outside your ContentView.

You need to pull them inside the brackets that delineate ContentView. So, after the body property but before the } that closes out ContentView.

You ContentView should look something like this:

struct ContentView: View {  //opening brace of ContentView
    //properties

    var body: some View {
        //body stuff
    }

    func addCard() {
        //addCard code
    }

    func drawCard() {
        //drawCard code
    }

    func getCardName(_ cardNumber: Int) -> String {
        //getCardName code
    }
} //closing brace of ContentView
1 Like