Trouble with my first non-tutorial project (solved)


#1

Hey all! I was playing cards with my son, and thought a good first project might be the card game war. For any not familiar, you take a standard 52 card deck and distribute it evenly between two players. Each turn, each player flips a card from his/her deck, and the higher ranked card takes both into a discard pile for that player. If the cards are tied, you have a war, and place 3 cards facedown, then a 4th faceup as a tie breaker. Once all of your deck has been exhausted, you shuffle your discard pile into it.

When I was first learning Java in college, we did poker, so I had an idea of how to program this:

  • make an enumeration of the Ranks (two through ace)
  • make an enumeration of the Suits
  • make a card type/struct/class which is made up of a rank and a suit, and knows how to print itself (“Two of Spades”)
  • make a deck (vector/array) of these cards, and add behavior like shuffling or dealing a hand (hand can be a special case of deck)

However, I’ve run into some difficulties. When I go to create the deck, I keep running into errors. The first error was that my enums aren’t enumerable/iterable? I looked it up on Stack Overflow, but that answer is lengthy and I don’t understand how it works. I’d like to start out with things I understand, without throwing some magic code in.

To get an iterable object, I made arrays of each of the variants of each enum. However, now my constructor? for the card struct is saying it’s getting references to the enums when it’s expecting the actual variants:

 error[E0308]: mismatched types
  --> src\main.rs:40:28
   |
40 |             deck.push(Card{rank, suit});
   |                            ^^^^ expected enum `main::Rank`, found &main::Rank
   |
   = note: expected type `main::Rank`
              found type `&main::Rank`

The full code is here. How can I get a deck of cards constructed, so I can start looking at shuffling them?


#2

Issue is that Card contains “objects”, but in the loop you have pointers to suits and ranks. The easiest solution will be to make your enums and struct Copyable and use pattern matching (which will do dereferencing and copy) to acquire owned rank and suit. Here is the fixed variant.


#3

Keep in mind .iter() returns references, if you want owned objects (not pointers) you could use .into_iter() which consumes the data, so it won’t be available after the loop.


#4

@newpavlov Thank you so much! Can you (or anyone) explain why I get warnings about fields being unused if I don’t add the derive onto my struct, but they go away as soon as I do?

@vishusandy I don’t think I want any given card to own a particular suit or rank, because wouldn’t that make it impossible to make any more with that suit/rank? Thank you for the tip on iter() vs into_iter().


#5

Ownership applies to owning a particular copy, and you can have as many copies as you like. It’s still possible to use an owned object — by borrowing it.

However, since suit is just a small enumeration, it’s best to make it a Copy type, so that every user of the type will own its copy. Otherwise, if the ownership was truly shared (e.g. Suit was a reference to a theme you use to draw cards), you could use an Rc/Arc type.


#6

I don’t know what the etiquette is here, and I apologize if this should be a new post (please let me know!).

I’ve continued working on this project, including modularizing it somewhat. I’m running into issues with adding a deal function that gives out a hand of cards from a deck.

The code I’m using is here, and I’m getting a cannot assign borrowed value error from this function:

// removes the first number_of_cards from deck and returns them as a new Hand
pub fn deal(&mut self, number_of_cards: usize) -> Hand { // Hand is a type alias for Deck
    let (first, rest) = self.cards.split_at(number_of_cards);
    self.cards = rest.to_vec();
    Hand{cards: first.to_vec()}
}

I attempted to add .clone() after the first use of self.cards, but then I get a different error (borrowed value does not live long enough)


#7

split_at returns references to the slice (or Vec) you’re splitting from; as such, while those references are live, you cannot mutate the source Vec. So you could to_vec() the returned slices inside a block, which will create owned values and drop the borrows, and then use the result of that in the rest of the method.

However, I think deal can be better written using drain:

pub fn deal(&mut self, number_of_cards: usize) -> Hand {
        let drain = self.cards.drain(..number_of_cards);
        Hand {
            cards: drain.collect(),
        }
    }

#8

Drain was perfect!

My next challenge has been trying to add the Display trait on everything. I got it successfully implemented on Rank, Suit, and Card. However, I can’t figure out how to display my vector of cards. Debug works great, but I’d ideally like them to be the joined representation of each. In Python:

', '.join(deck.cards)

However, in rust, the join method of vector isn’t implemented for arbitrary types. I tried mapping each card with .to_string() and then joining, but it gave me an error after the map (map doesn’t return a new vector with the return type of each iteration?).

I then tried looping through the cards and appending each one’s format to a String, but I must be doing something wrong, because I’m still getting errors.

Here is the code I have


#9

You don’t need the temp String - you can write directly to the Formatter:

fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut sep = false;
        for card in self.cards.iter() {
            if sep {
                write!(f, ", ")?;
            }
            write!(f, "{}", card)?;
            sep = true;
        }
        Ok(())
    }

You should look into the itertools crate, however - it’ll have additional methods, such as the join you’re looking for.