• Advertisement

C# Understanding probabilities of getting certain items?

Recommended Posts

I am currently designing and coding up a general card game. I have developed enough server and database code to have a complete register/login system working and also have everything set up the database for modular building of cards. This is all great and working, and now I come to the point where I have to create "packs" of cards.

I don't understand how to implement the "percentage of getting a card" type of system. I was originally thinking that I can tag each card in the database with a percentage of being acquired, so that I can fine tune each and every single card if the need ever arises. So, let's say that card1 has a 1% chance of being pulled and card2 has a 100% chance of being pulled.

With this setup, if I use the normal random methods

Random _random = new Random(); //this is class level declaration
	//create a pack here of 5 cards
	double percentageToPull = _random.NextDouble() * 100;

Now, let's assume percentageToPull becomes 55%, we should never pull a card below that 55%. But, if percentageToPull becomes .9, we now have a chance at pulling that 1% card. However, in my mind there is also that 100% card sitting there. So I can't assume the lower percentage pulls are guaranteed (.9 != 1), and if I look at cards >= .9, it's now another rng on top of rng.

However, on the other side is how most games are set up now. They set up "rarity levels" of their cards. Cards 1-3 are "common", card 4 is "uncommon", etc... So when creating a pack we can guarantee pulling any card within any rarity level.

Both systems create a random aspect of creating packs of cards to be pulled. However each system works different. I like the idea of having control over each individual cards "chance" of being pulled, but if creates this much difficulty, i'll go with the more standardized rarity level system.

Any thoughts/opinions?

Edited by detlion1643

Share this post

Link to post
Share on other sites

Suppose you have a list of cards and choose from it randomly. Now, for common cards, duplicate them. For rarer cards, duplicate them less. This would exactly mirror how it works with real cards.

So suppose you have cards A, B, C, D, E, each with increasing rarity. So you might have 50 "A" cards, 30 "B" cards, 10 "C" cards, 5 "D" cards, and one "E" card. So the way you would define a card's rarity is as commonality, or redundancy. And to predict the chance of getting a particular rarity, you check its commonality against the total size of the expanded list.

If you want to ensure that the number of unique common or rare cards has no effect on probability, you could instead make those into categories. You choose a rarity category in this way, and then you choose the exact card randomly from a list. Simple.

Share this post

Link to post
Share on other sites

So I really was overthinking it and trying to solve a problem that doesn't exist? I was thinking since it's coded, we could make it more defined with card having its own separate chance of being pulled. But now that it's explained, that wouldn't work as the number of cards in the entire pool keeps changing.

It is simple to create the rarity category system and tag cards with their category. Then, I'd roll the random, see what category I need, and pull a random card from that category. Thanks for the input.

Share this post

Link to post
Share on other sites

When you say "100%", you have to define what that means. 100% of what?

Obviously if you're choosing 1 card out of 2, it's simply not possible to have "100% chance of card 1, 5% chance of card 2". Probability doesn't work like that.

If you were implementing a booster pack system, e.g. of 7 cards, then I suppose you could say there is a 100% chance of getting a certain card... unless, for example, you had 8 cards each "100%", which means that again it's impossible.

A more workable concept is to think in terms of relative frequency. You might decide that a common card is twice as likely to be selected as an uncommon card is, and an uncommon card is twice as likely to be selected as a rare card. But even here, there are 2 interpretations:

  1. For every card selected, it's twice as likely that it is common than it is uncommon.
  2. For any given common card, you're twice as likely to select that than you are to select any given uncommon card.

It's easy to see how these 2 definitions can work differently. Examples: (ignoring rare cards for now)

Pick one at random from: "Common1", "Common2", "Common3", "Common4", "Uncommon1", "Uncommon2" - this fits definition 1. You're twice as likely to pick a Common card. But you're just as likely to pick Common1 as you are to pick Uncommon1. Common3 is as frequent as Uncommon2. So although this fits one definition, it probably isn't intuitively what you expect.

Example 2: pick one at random from "Common1", "Common1", "Common1", "Common1", "Uncommon1", "Uncommon1", "Uncommon2", "Uncommon2". Here, you're twice as likely to select Common1 than you are Uncommon1. You're also twice as likely to select Common1 than you are Uncommon2. And yet, half of your selections will be Uncommon, just as many as your Sommon selections. Again, this is unintuitive. Because there are more types of Uncommon card, the individual infrequency is balanced out by the overall frequency.

So, what you will probably want is a system that tries to balance both of these concepts. You will probably want to attach a weighting to each card individually, but you also need to manage the size of the relative categories and ensure they are of similar proportion, so that both the rarity interpretations hold true. Then, selecting is a case of a standard weighted random sample. (e.g. https://medium.com/@peterkellyonline/weighted-random-selection-3ff222917eb6 or, use the cumulative frequency and a binary search.)

Share this post

Link to post
Share on other sites

I've worked on a few card games in the past and had to address a similar problem. We ultimately ended up with a solution based on a conditional probability model.

We gave each card a weight and used the aforementioned weighted random selection to find a card in the pool. The weight was based on rarity or other traits, and represented how much "opportunity" a card had relative to other cards to be selected for the deck. Once a card was found, we used a percentage chance to determine if the card should actually be added to the deck, and repeated this process until the deck was full.

This approach also allowed us to limit the number of occurrences of any given card in the deck by temporarily setting its weight to 0 when its limit was reached, a very important feature for most deck building games.

Edited by Zipster

Share this post

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Advertisement
  • Advertisement
  • Popular Tags

  • Advertisement
  • Popular Now

  • Similar Content

    • By Manuel Berger
      Hello fellow devs!
      Once again I started working on an 2D adventure game and right now I'm doing the character-movement/animation. I'm not a big math guy and I was happy about my solution, but soon I realized that it's flawed.
      My player has 5 walking-animations, mirrored for the left side: up, upright, right, downright, down. With the atan2 function I get the angle between player and destination. To get an index from 0 to 4, I divide PI by 5 and see how many times it goes into the player-destination angle.

      In Pseudo-Code:
      angle = atan2(destination.x - player.x, destination.y - player.y) //swapped y and x to get mirrored angle around the y axis
      index = (int) (angle / (PI / 5));
      PlayAnimation(index); //0 = up, 1 = up_right, 2 = right, 3 = down_right, 4 = down

      Besides the fact that when angle is equal to PI it produces an index of 5, this works like a charm. Or at least I thought so at first. When I tested it, I realized that the up and down animation is playing more often than the others, which is pretty logical, since they have double the angle.

      What I'm trying to achieve is something like this, but with equal angles, so that up and down has the same range as all other directions.

      I can't get my head around it. Any suggestions? Is the whole approach doomed?

      Thank you in advance for any input!
    • By Alexander Nazarov
      Hello. I'm newby in Unity and just start learning basics of this engine. I want to create a game like StackJump (links are below). And now I wondering what features do I have to use to create such my game. Should I use Physics engine or I can move objects changing transform manually in Update().
      If I should use Physics can you in several words direct me how can I implement and what I have to use. Just general info, no need for detailed description of developing process.
      Game in PlayMarket
      Video of the game
    • By Dave Haylett
      Hi all. My project is coming along wonderfully, and am starting to consider alpha deployment, and would like your advice.
      My project need access to 10,000 small PNG image files at runtime, each is only a few kilobytes each, which during development I used to load in directly from a fixed path on my HDD whenever one was needed (obviously not a solution for go-live), using something like this:
      img = new WriteableBitmap(new BitmapImage(new Uri(@screenshotsPath + filename)));
      The image would then be blitted onto a buffer screen, etc. etc. At a time, a few dozen would be being used.
      Now I'm thinking about deployment, and also when I produce an update to my app, there could be more images to add to the folders. So I'm considering the best way of a) deploying the images to the user as part of the project, and b) how to most easily handle updates to the app, whereby more images will be added.
      I have just experimented with adding them all as a Resource (!). This inflated the exe from 10mb to 100mb (not a major problem), increased the compile time from 3 secs to 30 secs (annoying), increased RAM usage from 500mb to 1.5gb (not a major problem either), but means that it solves my fixed directory issue, distribution issue, and update issue, simply by having the files all stuck into the executable. Here's the new code I'm using:
      img = BitmapFactory.FromResource("Shots/" + filename);
      The next thing I was going to try was to mark them as Content > Copy if Newer. This would resolve the executable size and RAM usage (and also the directory issue as well), however it seems that I'd need to highlight them all, and move them from Resource to Content. As an up-front job this isn't too bad, but as I add new images to the project, I'll need to go in and do this every time, which gets annoying, as the VS2015 default is Resource. Also, I'm not sure how this would work in terms of updates. Would something like ClickOnce deployment recognise new PNGs and install them to the users?
      I also have 3,000 ZIP files (~500kb each) which also need deploying and updating in the same way. These are currently read directly from my HDD until I can find a permanent solution for adding these to the project as well.
      Can anyone thing of a better way of doing what I'm trying to achieve?
      Thanks for any help folks.
    • By Felis Nigripes
      I'm doing a test quest.
      The player gets a quest from an NPC to bring him fish.

      Once the player picks up the fish, the original NPC gets replaced by a new one with a new conversation trigger. The NPC tells the Player "Well done" and should give 200xp.

      The script tells the xp counter to go up by making a reference to the gameobject that holds the text component
      But it throws this error:

      I'm aware that the error may hide in plain sight. I just have to sort this out, since I'm writing the AI at the same time, and the time it takes to resolve everyone of these errors is tremendous.
      Plus, I think I'll learn something. I've been having trouble with some basic functionalities recently. There might be something wrong with my understanding on how programming works.
      Glad if someone could help (:
      Edit: I'm fully aware that the update function requires an input. I call the function in the editor when the dialogue ends, it still doesn't work.
    • By Vu Chi Thien
      Hi fellow game devs,
      With the help of  @CombatWombat in my previous post about clutch modeling, I now have a solid direction for the modeling the clutch. The way I'm doing it is having 2 clutch states: locked and unlocked. EngineRPM and torque will be calculated separately in each state. My problem right now is the logic and code for specifying locking and unlocking.
      The condition for locking is when (engineSpeed - drivetrainSpeed) in previous update cross zero (different sign) with the current update (to determine if engineSpeed = drivetrainSpeed or not in-between updates) and engineTorque <= clutchTorque.
      The condition for unlocking is when engineTorque > clutchTorque.
      The diagram looks roughly like this (taken from matlab website with the similar implementation):

      However, the 2 conditions are triggers for switching states, not for determine the current state to be in, so in the end my clutch state just jumped around. I don't have a lot of experience in doing state machine, so can some one give me rough code of how to implement this? Below is my rough code:
      speedError = engineSpeed - drivetrainSpeed; if ((Math.Sign(speedError) != Math.Sign(deltaW) && currentTotalEngineTorque <= clutchReactTorque)) { clutchLocked = true; } else clutchLocked = false; deltaW = speedError; //end of update I think the main struggle is the cross zero. Because cross zero is the "trigger condition" to check if the clutch should lock when it is slipping, not the condition for continuous locking, while the code I have above is the "continuous condition" saying "this condition is true then it is locked/unlocked". Another word, if the clutch is slipping, the condition above would decide if it's locked or not, but once it is locked, the cross zero condition is not true anymore (since speedError and deltaW have same sign as engineSpeed == drivetrainSpeed when clutch is locked). I'm sorry that I cannot explain this better as English is not my first language.
  • Advertisement