Module 2: Challenge 2

Not exactly sure since you didn’t specify what the errors you’re now getting are, but it’s probably because ContentView() in your preview needs to take a parameter. You declare @State var deck: Card but never initialize it within ContentView. Normally, that means you would pass in a value from a parent View or whatever, but this being an @State variable means you shouldn’t do that; typically a passed in state value would be @Binding instead of @State.

For me I know how to initialize something that is an int, but I don’t know how to initialize something that is a Card or a Deck. That is kind of where I am having difficulty.

Your struct that you have that defines the properties of the card is great.

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

Now that you have that the idea is to create a deck which is an array of that struct so the code to do that is:

@State private var deck = [Card]()

So this is the error message I am getting from the random number generator. ‘RandomNumberGenerator’ cannot be constructed because it has no accessible initializers. I am making a bit of progress at least.

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

import SwiftUI

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

struct ContentView: View {
    var suit = ["Spades", "Clubs", "Hearts", "Diamonds"]
    @State var deck = [Card]()
    var body: some View {
        HStack{
            Button("One"){
                deck[0].cnumb = RandomNumberGenerator(1...13)
                deck[0].suit = RandomNumberGenerator(0...suit.count - 1)
                
            }
            Button("Two"){
                
            }
        }
    }
}

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

The RandomNumberGenerator is a protocol and should not be used in this manner

The correct method to generate a random Integer is:

let randomNumber = Int.random(in: 1...13)

For your random suit selection you would do something like this:

let index = Int.random(in: 0..<suit.count)
let randomSuit = suit[index]

OR you can use this method which is available on arrays

let randomSuit = suit.randomElement()

Having created a randomNumber and a randomSuit you now need to create a new card with those values assigned to the new card properties and append that to the deck making sure that it is not a duplicate.

As you read through the challenge description you will notice that Chris Ching refers to a label that you use to advise the user what card has been generated (or if it is a duplicate) when you press the first button and what card has been drawn when you press the second button. So in your View you need to add a Text() object to give feedback to the user.

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

1 Like

Huh I thought RandomNumber generator was the same as .random. Well I am progress at least little bit little. So now I am getting this error message: No exact matches in call to instance method ‘append’ when trying to append data.

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

import SwiftUI

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

struct ContentView: View {
    var suit = ["Spades", "Clubs", "Hearts", "Diamonds"]
    @State var deck = [Card]()
    var body: some View {
        HStack{
            Button("One"){
                let rndNum = Int.random(in: 1...13)
                let rndSuit = suit[Int.random(in: 0..<suit.count)]
                if(!indeck)
                {
                   deck.append(cnumb: rndNum, suit: rndSuit)
                    print("Generated a \rndNum of \rndSuit")
                }
                else
                {
                    print("Generated duplicate card!")
                }
            }
            Button("Two"){
                
            }
        }
    }
}

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

I must say the challenge is pretty challenging. I didn’t know that a randomElement() command actually existed.

What you need to do is create an instance of a Card and populate the properties with the randomNumber and random suit that you selected.

For example

let newCard = Card(cnumb: rndNum, suit: rndSuit)

then you need to check if that newCard already exists in the deck

Arrays have a .contains method in which you pass a parameter that you want to test for

For example:

deck.contains(newCard) will return true if it exists or false if it does not.

What were you going to do with indeck?

I am using indeck as a placeholder. If the card 8 is not in the deck of cards already then add it, otherwise print out that the card created is a duplicate card.

It is currently throwing the error message “Referencing instance method ‘contains’ on ‘Sequence’ requires that 'Card conform to ‘Equatable’”

I disabled the append command to cut down on error messages. The code looks similar to the C++ code, but a bit different. I didn’t know that contains actually existed on arrays.

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

import SwiftUI

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

struct ContentView: View {
    var suit = ["Spades", "Clubs", "Hearts", "Diamonds"]
    @State var deck = [Card]()
    var body: some View {
        HStack{
            Button("One"){
                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(cnumb: rndNum, suit: rndSuit)
                    print("Generated a \rndNum of \rndSuit")
                }
                else
                {
                    print("Generated duplicate card!")
                }
            }
            Button("Two"){
                
            }
        }
    }
}

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

The object that you append to deck must be of type Card which is the struct that you defined. Since you now have a myCard declared which you have used to test if it exists in the array deck, if the test is false you can append that card like this:

deck.append(myCard)

I just added myCard to the deck but the contains method appears to be not functioning. Its giving me the error message: Referencing instance method ‘contains’ on ‘Sequence’ requires that ‘Card’ conform to ‘Equatable’

Any Advice?

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

import SwiftUI

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

struct ContentView: View {
    var suit = ["Spades", "Clubs", "Hearts", "Diamonds"]
    @State var deck = [Card]()
    var body: some View {
        HStack{
            Button("One"){
                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)
                    print("Generated a \rndNum of \rndSuit")
                }
                else
                {
                    print("Generated duplicate card!")
                }
            }
            Button("Two"){
                
            }
        }
    }
}

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

As the error message says, you need to make your Card struct conform to the Equatable protocol. Equatable is what lets you do == between two items.

To conform to Equatable, you would need to write a == function to tell the compiler how to test equality between two Cards.

HOWEVER, since Card's two properties are Int and String, which are already Equatable, you don’t need to do anything to make Card itself Equatable except this:

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

The compiler is able to synthesize Equatable conformance for you if all the properties are themselves Equatable once you tell it to do so.

Thanks Rooster. I was kind of confused why it was asking that. I also didn’t know there was a protocol for this already. Now the errors are gone but the deck doesn’t appear to build and I am not getting any print statements displaying in the console.

//
//  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 {
        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)
                    print("Generated a \rndNum of \rndSuit")
                }
                else
                {
                    print("Generated duplicate card!")
                }
            }
            Button("Two"){
                
            }
        }
    }
}

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

If you are just running the code in your Preview you will not get anything printed in the Console. It doesn’t work like that.

To see any output in the Console you must build and run the code in a Simulator.

Also, you need to change this line:

print("Generated a \rndNum of \rndSuit")

to this:

print("Generated a \(rndNum) of \(rndSuit)")

inn order to get the output you want. Otherwise it will look like this:

Generated a 
ndNum of 
ndSuit

That’s because \r indicates a line break.

1 Like

I ran it in the simulator and now the print statements function. I didn’t know that they don’t work in the preview mode.

@Chris_Parker

I changed the print statement to include parens. I can’t get the label to work, so I commented it out.

I am now getting the following error message: Value of optional type ‘Card?’ must be unwrapped to refer to member ‘suit’ of wrapped base type ‘Card’

//
//  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){
                        var card = deck.randomElement()
                        
                    }
                    else{
                        print("There are no cards in the deck!")
                    }
                    var card = deck.randomElement()
                    print("My card is: \(card.suit)")
                }
            }
            Spacer()
        }
    }
}

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

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)