Toby Kennedy
Toby Kennedy

Reputation: 19

Best way to choose two random ints to assign values to

I am creating a Dungeons and Dragons Character Creator. There is a randomize feature that is going to create a complete character sheet. There is a part that I have gotten to and I am not quite sure the best way to proceed.

The way I have the racial modifiers set up is with if statements. Here is an example.

if (raceInt == 0 || raceInt == 2 || raceInt == 10)
{
    raceStrMod = 2;
}

if (raceInt == 3 || raceInt == 4 || raceInt == 5 || raceInt == 11 || raceInt == 12)
{
    raceDexMod = 2;
}

However there are races that have modifiers that let you select two stats to add a modifier to, such as Strength or Dexterity. What would be the best way to select two random ints for just those races?

For example, the half-elf race which would get +2 to Dex and then +1 to two other random stats. So I need to find a way to randomly select two of the remaining ints to make the value = 1.

My race mod ints are initialized as

int raceStrMod = 0;
int raceDexMod = 0;
int raceConMod = 0;
int raceIntMod = 0;
int raceWisMod = 0;
int raceChaMod = 0;

Then the if statements assign a value dependent on which race was randomly selected.

Thank you all for the input! This is how I ended up coding it

        if (raceInt == 9)
        {
            int randomX = rnd.Next(1, 5);
            int randomY = rnd.Next(1, 5);

            int attempts = 0;
            while (randomX == randomY && attempts < 10)
            {
                randomY = rnd.Next(1, 5);
                attempts++;
            }

            //if they are still not unique after 10 attempts
            if (randomX == randomY)
            {
                if (randomX == 5)
                    randomY = 1;
                else
                    randomY = randomX + 1;
            }

            int[] randomNumbers = { randomX, randomY };

            foreach (int i in randomNumbers)
            {
                switch (i)
                {
                    case 1:
                        raceStrMod = 1;
                        break;
                    case 2:
                        raceDexMod = 1;
                        break;
                    case 3:
                        raceConMod = 1;
                        break;
                    case 4:
                        raceIntMod = 1;
                        break;
                    case 5:
                        raceWisMod = 1;
                        break;
                }
            }
        }

Upvotes: 0

Views: 213

Answers (3)

Eric Lippert
Eric Lippert

Reputation: 660159

I am creating a Dungeons and Dragons Character Creator.

That's a fun beginner project; I did the same when I was learning to program.

I need to find a way to randomly select two of the remaining...

You need to find two distinct values, call then x and y. The solution you've arrived at is:

  • Generate x
  • Try to generate y ten times
  • If no attempt succeeded to find a distinct y, hard-code a choice.

That works, and you almost never have to use the hard-coded choice. But I thought you might be interested to know that there is an easier way to generate two distinct numbers. Let's suppose we want two distinct numbers from 0, 1, 2, 3 or 4. (Obviously if you want a different range, say, 1 through 5, you can solve that problem by generating two distinct numbers 0->4 and then adding one to each.)

The improved algorithm is:

  • Choose x between 0 and 4 as usual.
  • Choose n between 1 and 4.
  • y = (x + n) % 5;

Think about it this way. Suppose we make a list like this:

0, 1, 2, 3, 4, 0, 1, 2, 3

We randomly choose x from the first five entries on the list, and then we choose y by stepping forwards between 1 and 4 steps. Since the list does not repeat in one to four steps, we know that we'll get two unique elements. The math does the equivalent of that.

You could similarly have used % in your program:

if (randomX == 5)
  randomY = 1;
else
  randomY = randomX + 1;

could be written

randomY = randomX % 5 + 1

If you're unfamiliar with %, it is the remainder operator. It is the complement of the / operator. The rule is:

int x = whatever;
int y = whatever;
int r = x % y;

is the same as:

int r = x - (x / y) * y;

That is, it is the remainder when x is divided by y. Keep in mind that the remainder can be negative!

Upvotes: 1

Peter Duniho
Peter Duniho

Reputation: 70671

Has your class introduced you to enum types yet? If not, is there any restriction on your final project with respect to using language features that weren't taught in the class?

Your question is arguably too broad, as there are many different ways to address this sort of thing even in real-world code, and the classroom context introduces potential roadblocks that while might constrain the question, being unknown they make it impossible to know what answer is actually going to work for you.

That said…

Ignoring the classroom aspect and focusing only on the problem itself, I would use enum types and dictionaries for this sort of thing. For example:

enum Attribute
{
    Strength,
    Dexterity,
    Constitution,
    Charisma,
    Intelligence,
    Wisdom,
    Count, // must always be last
}

Dictionary<Attribute, int> modifiers = new Dictionary<Attribute, int>();

Then you can pick a random attribute like (assuming you have a random variable referencing a Random object…don't make the classic newbie mistake of creating a new Random object every time you want to pick a new random number):

Attribute attributeToModify = (Attribute)random.Next((int)Attribute.Count);

And you can store that selection like:

modifiers[attributeToModify] = 1;

This can be used to store however many modifiers you like. You can encapsulate that in an object representing the character itself, or you could put it into a separate AttributeModifiers class. One advantage of doing the latter would be that if you have modifiers that come from different sources, you can track that in the character object as a list of AttributeModifier instances, each in turn keeping track of what the actual source of those modifiers are.

This just barely scratches the surface. As I noted, the question itself is fairly broad. But I strongly recommend using the available language features to ensure that your variables represent things in a type-specific way, rather than just using int values for things that aren't really integers, and to use collection classes that more correctly represent the semantics of what your code is intended to do.

Note that this also means you probably should have an enum type for the races. E.g.:

enum Race
{
    Dwarf,
    Elf,
    HalfElf,
    Halfling,
    HalfOrc,
    Human,
    // etc.
}

And your chain of if statements is probably better represented as a switch:

Attribute racialMod;

switch (race)
{
    case Human:
    case Halfling:
    // etc.
        racialMod = Attribute.Strength;
        break;
    case Elf:
    case HalfElf:
    // etc.
        racialMod = Attribute.Dexterity;
        break;
}

modifiers[racialMod] = 2;

Something like that. The point is to make sure the code reads more like what the original specification would say (if you actually had written one). This will make the code easier to understand, and it will be less likely for you to put bugs in the code (e.g. you accidentally type the wrong magic, unnamed integer).

Upvotes: 2

Ross Gurbutt
Ross Gurbutt

Reputation: 1029

(Disclaimer: I don't love this option, but couldn't think of another way other than reflection which is even nastier)
You could define a class that masks the fact that all of the mods are stored as an array and therefore can be indexed using a random number.
Something like the following:

    public class StatMods
    {
        public int RaceStrMod { get { return this.mods[0]; } set { this.mods[0] = value; } }
        public int RaceDexMod { get { return this.mods[1]; } set { this.mods[1] = value; } }
        public int RaceConMod { get { return this.mods[2]; } set { this.mods[2] = value; } }
        public int RaceIntMod { get { return this.mods[3]; } set { this.mods[3] = value; } }
        public int RaceWisMod { get { return this.mods[4]; } set { this.mods[4] = value; } }
        public int RaceChaMod { get { return this.mods[5]; } set { this.mods[5] = value; } }

        private readonly int[] mods;
        private static readonly Random rand = new Random();

        public StatMods()
        {
            this.mods = new int[6];
        }

        public void ApplyRandomMod(int modification)
        {
            this.mods[rand.Next(0, 6)] += modification;
        }
    }

Upvotes: 0

Related Questions