根据稀有度创建“卡片组”

I have an array of images. Each image has a "rarity" key that tells me whether it's "common", "uncommon", or "rare". So for instance, the array might look something like this:

Array
(
    [0] => Array
        (
            [image] => photo1.jpg
            [rarity] => common
        )

    [1] => Array
        (
            [image] => photo2.jpg
            [rarity] => uncommon
        )
    .
    .
    .

    [x] => Array
        (
            [image] => photo(x).jpg
            [rarity] => rare
        )
)

I want to select 'y' number of images from the list, almost like creating a deck of cards, but with images instead. The rarity, of course, defines how likely that card will be selected. How do I go about doing this? I figure I start with array_rand(), but am stuck as to where to go with it.

EDIT FOR CLARIFICATION: Each image in the array can only occur in the final deck once.

Here's my attempt. In $chances you have to define the chances that a common, uncommon or rare card will be chosen to appear in a deck as an int between 0-100. Also you have to provide the deck size.

Presenting an answer where the cards are unique and cannot appear multiple times in the same deck:

$cards = array(/* your array from above */);
$deck = array();
$deck_size = 52;

$chances = array('common' => 90, 'uncommon' => 30, 'rare' => 5);
$max_attempts = 3;
$tmp_cards = $cards; // copied

while(count($deck) < $deck_size) {
  $roll = rand(0, 100);

  for($a = 0; $a < $max_attempts; $a++) {
    $index = array_rand($tmp_cards);
    $card = $tmp_cards[$index];
    $rarity = $card['rarity'];
    $image = $card['image'];

    if(isset($deck[$image])) {
      continue;
    }

    if($roll <= $chances[$rarity]) {
      // if the roll is lower than the required probability
      // we can put this card in
      $deck[$image] = $card;
      unset($tmp_cards[$index]); // delete the card so it's not picked again
      break;
    }
  }
}

$deck = array_values($deck);

Update

Improved the code and provided definite exits to all branches.

Let's say that the common category is 5 times more common than the rare, and the uncommon occurs 2 times more than the rare.

For each image, it is a change of 5 out of 8 that it is common, 2 out of 8 that it is uncommon, and 1 out of eight that it is rare.

Thus:

if ((rand() % 8) < 5)
{
    $key = array_rand($common);
    return $common[$key];
} else {
    etc.

I would do this with three different arrays. One for each rarity type.

Lets say the probabilities are: common = 70%, uncommon = 25%, rare = 5%.

Your three arrays are $commonArray, $uncommonArray and $rareArray.

Your target deck is $deck.

Then you generate a random number from 1 to 100 and choose one of the three arrays:

<?php
$rand = rand(1,100);
if ($rand <= 70) {
    $cardArray = &$commonArray;
} else if ($rand <= 95) {
    $cardArray = &$uncommonArray;
} else {
    $cardArray = &$rareArray;
}

Now choose one card from the selected array:

$chosen = array_rand($cardArray);
$deck[] = $cardArray[$chosen];
unset($cardArray[$chosen]); //remove the chosen card from the array to prevent duplicates

Repeat this until your $deck is the size you want it to be.

I would change the system of storing rarity from text to numbers instead, with low numbers being more rare. This way, you can expand out the system next time with just numbers, instead of inventing more categories like "ultra rare" or "beyond ultra rare". The number can be a weight-age. The higher the numbers, the more common it is. If all the frequency of the categories add up to 100%, you can easily write a generic version of Sjoerd's answer.

Also, rather than storing all the cards in one array, like what Sjoerd posted in his answer above, is to have each category being its own array. You could store all the cards in a DB and have function to group them by their rarity.

I'm going to assume for the sake of the discussion that your rarities are weighted in nice integers. (It doesn't need to be like that for the math to work. In fact, you could use actual probabilities 0 ≤ p ≤ 1 if you wanted. But it makes the explanation easier.) That is if you have a single common, uncommon, and rare card each in a deck and draw with replacement, after x+y+z draws you are expected to have drawn x common, y uncommon, and z rare cards.

You could also get the same expected draw if all cards were equally likely and your deck had x common, y uncommon, and z rare ones in it. So let's use this to find the true probability of drawing a card from each category.

Let's say that your deck has C common, U uncommon, and R rare cards in it. The equivalent deck of uniform-probability cards would have C·x common cards, U·y uncommon cards, and R·z rare cards, for a total of T = C·x + U·y + R·z cards. So the probability of selecting a common card is Pc = C·x / T, etc.

Once you've determined the category, you just have to select a card in it. They're all equally likely, so just pick one.

You can directly translate this into a program (in pseudo-PHP):

$Cx = count($common_card_array) * odds of common x
$Uy = ...
$Rz = ...
$Total = $Cx + $Uy + $Rz
$rnd = rand(1, $Total);
if ($rnd <= $Cx) {
    $rnd2 = rand(0, count($common_card_array) - 1)
    return $common_card_array[$rnd2];
}
$rnd = $rnd - $Cx;
if ($rnd <= $Uy) {
    ...
}
...