I’m trying to create a function in rust to generate a card (or series of cards) from a deck of cards (represented by a vector of u16, via 0..52). The problem is, obviously in real life you can’t pull two of the same card from a deck of cards. My idea (being a JS dev by trade) was to have a top level array of all the possible cards and remove the chosen card when picking a new one. (Code as reference is provided below)
use rand::seq::SliceRandom;
mod cards;
fn get_random_i(deck: &mut Vec<u16>) -> u16 {
let mut rng = rand::thread_rng();
let i = deck.choose(&mut rng).unwrap().to_owned();
deck.swap_remove(i.into());
return i;
}
fn get_random_hand(deck: &mut Vec<u16>) -> Vec<u16> {
return (0..2).map(|_| get_random_i(deck)).collect();
}
fn get_random_board(deck: &mut Vec<u16>) -> Vec<u16> {
return (0..5).map(|_| get_random_i(deck)).collect();
}
fn main() {
let mut deck: Vec<u16> = (0..52).collect();
let hand: Vec<u16> = get_random_hand(&mut deck);
let board: Vec<u16> = get_random_board(&mut deck);
println!(
"{}, {}",
cards::CARDS[hand[0] as usize],
cards::CARDS[hand[1] as usize]
);
}
The issue with the code provided is that it will somewhat infrequently throw a fatal error when the subsequent calls select an index that is an invalid index (greater than length of vector) in the vector. The exact error is as follows thread 'main' panicked at 'swap_remove index (is 49) should be < len (is 47)', library/alloc/src/vec/mod.rs:1306:13
What I don’t understand is how this is possible… I presume it might have to do something with the references passed into the functions which might cause the functions to have references to old versions of the vector or something of the sort. As stated previously, I’m a JS dev primarily, so I’m still trying to navigate the nuances that the whole reference vs value difference causes.
I’ve looked at a bunch of other SO posts, a lot of which have helped me get to this point, however, a lot of the ones dealing with manipulating vectors either pass it down recursively or something of the sort, whereas I’m trying to keep it at a higher level and just manipulate it in the child functions, which may or may not be possible within Rust. Obviously a different way to write it would be to return the chosen card and the new and shortened deck with each call to the function, and then pass that new deck into subsequent calls. That being said, that version to write this would obviously make all of the functions and calls a bit more verbose, so I feel like maybe I’m just missing something obvious due to my ignorance of Rust.
>Solution :
You fill a vector with the numbers 0 to 51 inclusive. Each element is its own index until you start removing elements.
deck.swap_remove(i.into()); is removing by index not by value. So if this code randomly selects element 0 and removes it, then on the next invocation it selects element 0 again (which has value 51 because it was swapped into position 0 by swap_remove) your code will try to remove the element at index 51 (which is now beyond the end of the vector) when it should remove the element at position 0.
Instead, either:
- Remove elements by value, not by index, or
- Select elements by index, not by value, or
- Shuffle the vector once and then just
pop()values off the end.
I would go with the shuffling approach as it’s more intuitive and therefore much easier to get right.
You need to add a single line to main():
fn main() {
let mut deck: Vec<u16> = (0..52).collect();
deck.shuffle();
// ...
And then the whole of get_random_i() can be replaced with a single call to deck.pop().unwrap():
fn get_random_hand(deck: &mut Vec<u16>) -> Vec<u16> {
return (0..2).map(|_| deck.pop().unwrap()).collect();
}
Obviously, the unwrap() may be inappropriate if the deck can run out of cards at this point in the code. In that case you would want to handle the case where deck.pop() returns None appropriately instead of panicking, such as by returning a hand that is short some cards, or by shuffling the discard pile back into the deck, or something else depending on the game rules.