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 Card
s.
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.
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.
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 anOptional
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
Hi, Chris:
I am trying exactly this idea and the Array.contains will not accept my newCard object. It just keeps throwing errors upon build.
Can you please help? Here is my code file contents:
//
// ContentView.swift
// Lesson 2 Challenge
//
// Created by John on 10/21/21.
//
import SwiftUI
struct ContentView: View {
@State private var cardDeck: [PlayingCard] = []
@State private var cardSuits: [String] = ["Hearts", "Spades", "Clubs", "Diamonds"]
@State private var message: String = ""
var body: some View {
VStack {
Text(message)
.padding()
HStack {
Button {
//Create playing card structure instance
let randNum = Int.random(in: 1...13)
let randSuit = cardSuits.randomElement() ?? "Diamonds"
var newCard = PlayingCard(cardValue: randNum, cardSuit: randSuit)
//Add newCard to cardDeck
if (cardDeck.contains(newCard)) {
// if cardDeck.isEmpty {
message = "Generate duplicate card."
} else {
cardDeck.append(newCard)
}
} label: {
Text("Create Playing Card")
}
}
}
}
struct PlayingCard {
var cardValue: Int = 0
var cardSuit: String = ""
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
It’s the same issue I explained earlier in the thread: https://codecrew.codewithchris.com/t/module-2-challenge-2/15165/21?u=roosterboy
You need to make your PlayingCard
struct conform to the Equatable
protocol.
Also, when posting code to these forums, place three backticks ```
on the line before your code and three backticks ```
on the line after your code so that it will be formatted properly. You can also highlight an entire code block and click the </>
button on the toolbar to wrap the block for you.
This makes it far easier to read and also makes it easier for other posters to copy/paste the code in order to test solutions and such.
The Struct has the Int and String, so doesn’t that make it Equatable? I saw that in the thread.
John