Results 1 to 3 of 3

Thread: [.NET 2.0+] Rolling Dice

  1. #1

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    [.NET 2.0+] Rolling Dice

    VB version here.

    Quite a few people have asked how to "roll dice" for games. Here's the "proper" way. First you define a Die class to represent a die.
    CSharp Code:
    1. public class Die
    2. {
    3.     private static Random faceSelector = new Random();
    4.  
    5.     private int _faceCount;
    6.     private int _value;
    7.  
    8.     public int FaceCount
    9.     {
    10.         get
    11.         {
    12.             return this._faceCount;
    13.         }
    14.         set
    15.         {
    16.             this._faceCount = value;
    17.         }
    18.     }
    19.  
    20.     public int Value
    21.     {
    22.         get
    23.         {
    24.             return this._value;
    25.         }
    26.     }
    27.  
    28.     public Die(int faceCount)
    29.     {
    30.         if (faceCount <= 0)
    31.         {
    32.             throw new ArgumentOutOfRangeException("faceCount", "Dice must have one or more faces.");
    33.         }
    34.  
    35.         this._faceCount = faceCount;
    36.     }
    37.  
    38.     public int Roll()
    39.     {
    40.         this._value = faceSelector.Next(1, this.FaceCount + 1);
    41.         return this.Value;
    42.     }
    43. }
    Note that the Die class has a static variable of type Random. This means that there is one and only one Random object for the class. This same object is used to generate random sides for each and every instance of the class.

    The Roll method generates the random number based on the number of faces the current die has. It returns this value and it is also available via the Value property thereafter.

    Many games use multiple dice though, so it's appropriate to define a class that represents multiple Die objects.

    (Note that this code snippet is labelled as VB.NET code but it is obvioulsy C#. It's just that the C# and CSharp code parsers seem to butcher opening square brackets, which is a problem when indexing in C# code.)
    CSharp Code:
    1. public class DieCollection : System.Collections.ObjectModel.Collection<Die>
    2. {
    3.     public int[] RollAll()
    4.     {
    5.         int[] values = new int[this.Count];
    6.  
    7.         for (int index = 0; index < values.Length; index++)
    8.         {
    9.             values[index] = this.Items[index].Roll();
    10.         }
    11.  
    12.         return values;
    13.     }
    14. }
    There's very little code to write for this class because it inherits all the standard collection functionality for the generic Collection<Die> class. Inherited functionality includes an indexer of type Die and an Add method that takes a Die parameter.

    Here's an example of using these classes. As is supposed to be the case in OOP, using the types is dead simple because all the work is done inside the classes themselves. You simply create a DieCollection, create and add as many Die objects as you need and call RollAll:
    CSharp Code:
    1. // Create the collection to store the dice.
    2. DieCollection dice = new DieCollection();
    3.  
    4. // Add two six-sided dice.
    5. dice.Add(new Die(6));
    6. dice.Add(new Die(6));
    7.  
    8. // Roll all the dice.
    9. int[] rolls = dice.RollAll();
    10.  
    11. // Display the values rolled.
    12. foreach (int roll in rolls)
    13. {
    14.     MessageBox.Show(roll.ToString(), "You rolled a ...");
    15. }
    Obviously for a game you would assign your DieCollection to a member variable and then call it's RollAll method each time a player had to roll the dice. You would not create a new DieCollection and Die objects each time you wanted to roll.

  2. #2

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: [.NET 2.0+] Rolling Dice

    Actually, having thought about it seems more appropriate to me for the RollAll method to return the total of all the rolls.
    CSharp Code:
    1. public class DieCollection : System.Collections.ObjectModel.Collection<Die>
    2. {
    3.     public int RollAll()
    4.     {
    5.         int total = 0;
    6.  
    7.         foreach (Die die in this.Items)
    8.         {
    9.             total += die.Roll();
    10.         }
    11.  
    12.         return total;
    13.     }
    14. }
    If you want the individual values you could loop through the dice and get their Value properties:
    CSharp Code:
    1. // Create the collection to store the dice.
    2. DieCollection dice = new DieCollection();
    3.  
    4. // Add two six-sided dice.
    5. dice.Add(new Die(6));
    6. dice.Add(new Die(6));
    7.  
    8. // Roll all the dice.
    9. int total = dice.RollAll();
    10.  
    11. // Display the total rolled.
    12. MessageBox.Show(total.ToString(), "You rolled...");
    13.  
    14. // Display the values rolled.
    15. foreach (Die die in dice)
    16. {
    17.     MessageBox.Show(die.Value.ToString(), "You rolled a...");
    18. }
    You could even add a property to the DieCollection that returned all the Values:
    vb.net Code:
    1. public int Values
    2. {
    3.     get
    4.     {
    5.         int[] faces = new int[this.Count];
    6.  
    7.         for (int index = 0; index < this.Count; index++)
    8.         {
    9.             faces[index] = this.Items[index].Value;
    10.         }
    11.  
    12.         return faces;
    13.     }
    14. }

  3. #3

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: [.NET 2.0+] Rolling Dice

    Having read this thread, I like the idea of using Dependency Injection as suggested by Evil_Giraffe. As a result, I have improved the code for my dice and added that functionality, along with some other additions.
    CSharp Code:
    1. /// <summary>
    2. /// Represents a single die.
    3. /// </summary>
    4. public class Die
    5. {
    6. #region Fields
    7.  
    8.     /// <summary>
    9.     /// The number of faces a die will have by default, if not specified by the user.
    10.     /// </summary>
    11.     public static readonly int DefaultFaceCount = 6;
    12.  
    13.     /// <summary>
    14.     /// The object used to select a die face.
    15.     /// </summary>
    16.     private IRandomNumberGenerator faceSelector;
    17.  
    18. #endregion Fields
    19.  
    20. #region Properties
    21.  
    22.     /// <summary>
    23.     /// Gets or sets the number of faces the die has.
    24.     /// </summary>
    25.     /// <value>
    26.     /// An <b>Int32</b> containing the number of faces.
    27.     /// </value>
    28.     /// <remarks>
    29.     /// The default number of faces is defined by <see cref="DefaultFaceCount" />
    30.     /// </remarks>
    31.     public int FaceCount { get; set; }
    32.  
    33.     /// <summary>
    34.     /// Gets the current face value of the die.
    35.     /// </summary>
    36.     /// <value>
    37.     /// An <b>Int32</b> containing the current face value.
    38.     /// </value>
    39.     /// <remarks>
    40.     /// This would be the side facing upwards on a physical die.
    41.     /// </remarks>
    42.     public int Value { get; private set; }
    43.  
    44. #endregion Properties
    45.  
    46. #region Constructors
    47.  
    48.     /// <summary>
    49.     /// Creates a new instance of the <see cref="Die" /> class with the default number of faces and using the default random number generator.
    50.     /// </summary>
    51.     /// <remarks>
    52.     /// The default number of faces is defined by <see cref="DefaultFaceCount" />
    53.     /// </remarks>
    54.     public Die()
    55.         : this(DefaultFaceCount)
    56.     {
    57.     }
    58.  
    59.     /// <summary>
    60.     /// Creates a new instance of the <see cref="Die" /> class using the default random number generator.
    61.     /// </summary>
    62.     /// <param name="faceCount">
    63.     /// The number of faces the die has.
    64.     /// </param>
    65.     public Die(int faceCount)
    66.         : this(faceCount, new RandomNumberGenerator())
    67.     {
    68.     }
    69.  
    70.     /// <summary>
    71.     /// Creates a new instance of the <see cref="Die" /> class with the default number of faces.
    72.     /// </summary>
    73.     /// <param name="randomNumberGenerator">
    74.     /// The object used to select a die face.
    75.     /// </param>
    76.     /// <remarks>
    77.     /// The default number of faces is defined by <see cref="DefaultFaceCount" />
    78.     /// </remarks>
    79.     public Die(IRandomNumberGenerator randomNumberGenerator)
    80.         : this(DefaultFaceCount, randomNumberGenerator)
    81.     {
    82.     }
    83.  
    84.     /// <summary>
    85.     /// Creates a new instance of the <see cref="Die" /> class.
    86.     /// </summary>
    87.     /// <param name="faceCount">
    88.     /// The number of faces the die has.
    89.     /// </param>
    90.     /// <param name="randomNumberGenerator">
    91.     /// The object used to select a die face.
    92.     /// </param>
    93.     public Die(int faceCount, IRandomNumberGenerator randomNumberGenerator)
    94.     {
    95.         if (faceCount <= 0)
    96.         {
    97.             throw new ArgumentOutOfRangeException("faceCount", "Dice must have one or more faces.");
    98.         }
    99.  
    100.         this.FaceCount = faceCount;
    101.  
    102.         if (randomNumberGenerator == null)
    103.         {
    104.             randomNumberGenerator = new RandomNumberGenerator();
    105.         }
    106.  
    107.         faceSelector = randomNumberGenerator;
    108.     }
    109.  
    110. #endregion Constructors
    111.  
    112. #region Methods
    113.  
    114.     /// <summary>
    115.     /// Rolls the die.
    116.     /// </summary>
    117.     /// <returns>
    118.     /// An <b>Int32</b> that is the new value of the <see cref="Value"/> property.
    119.     /// </returns>
    120.     public int Roll()
    121.     {
    122.         Value = faceSelector.GenerateRandomNumber(FaceCount);
    123.  
    124.         return Value;
    125.     }
    126.  
    127. #endregion Methods
    128. }
    CSharp Code:
    1. /// <summary>
    2. /// A collection to store multiple <see cref="Die" /> objects.
    3. /// </summary>
    4. public class DieCollection : System.Collections.ObjectModel.Collection<Die>
    5. {
    6. #region Constructors
    7.  
    8.     /// <summary>
    9.     /// Creates a new, empty instance of the <see cref="DieCollection" /> class.
    10.     /// </summary>
    11.     public DieCollection()
    12.     {
    13.     }
    14.  
    15.     /// <summary>
    16.     /// Create a new instance of the <see cref="DieCollection" /> with the specified number of items.
    17.     /// </summary>
    18.     /// <param name="dieCount">
    19.     /// The initial number of items in the collection.
    20.     /// </param>
    21.     /// <remarks>
    22.     /// The initial items all have the default number of faces, as defined by the <see cref="Die.DefaultFaceCount" /> field, and use the default random number generator.
    23.     /// </remarks>
    24.     public DieCollection(int dieCount)
    25.     {
    26.         for (var i = 0; i < dieCount; i++)
    27.         {
    28.             Items.Add(new Die());
    29.         }
    30.     }
    31.  
    32.     /// <summary>
    33.     /// Create a new instance of the <see cref="DieCollection" /> with the specified number of items.
    34.     /// </summary>
    35.     /// <param name="dieCount">
    36.     /// The initial number of items in the collection.
    37.     /// </param>
    38.     /// <param name="faceCount">
    39.     /// The number of faces each die has.
    40.     /// </param>
    41.     /// <remarks>
    42.     /// The initial items all use the default random number generator.
    43.     /// </remarks>
    44.     public DieCollection(int dieCount, int faceCount)
    45.     {
    46.         for (var i = 0; i < dieCount; i++)
    47.         {
    48.             Items.Add(new Die(faceCount));
    49.         }
    50.     }
    51.  
    52.     /// <summary>
    53.     /// Create a new instance of the <see cref="DieCollection" /> with the specified number of items.
    54.     /// </summary>
    55.     /// <param name="dieCount">
    56.     /// The initial number of items in the collection.
    57.     /// </param>
    58.     /// <param name="randomNumberGenerator">
    59.     /// The object used to select a die face.
    60.     /// </param>
    61.     /// <remarks>
    62.     /// The initial items all have the default number of faces, as defined by the <see cref="Die.DefaultFaceCount" /> field.
    63.     /// </remarks>
    64.     public DieCollection(int dieCount, IRandomNumberGenerator randomNumberGenerator)
    65.     {
    66.         for (var i = 0; i < dieCount; i++)
    67.         {
    68.             Items.Add(new Die(randomNumberGenerator));
    69.         }
    70.     }
    71.  
    72.     /// <summary>
    73.     /// Create a new instance of the <see cref="DieCollection" /> with the specified number of items.
    74.     /// </summary>
    75.     /// <param name="dieCount">
    76.     /// The initial number of items in the collection.
    77.     /// </param>
    78.     /// <param name="faceCount">
    79.     /// The number of faces each die has.
    80.     /// </param>
    81.     /// <param name="randomNumberGenerator">
    82.     /// The object used to select a die face.
    83.     /// </param>
    84.     public DieCollection(int dieCount, int faceCount, IRandomNumberGenerator randomNumberGenerator)
    85.     {
    86.         for (var i = 0; i < dieCount; i++)
    87.         {
    88.             Items.Add(new Die(faceCount, randomNumberGenerator));
    89.         }
    90.     }
    91.  
    92. #endregion Constructors
    93.  
    94. #region Methods
    95.  
    96.     /// <summary>
    97.     /// Rolls all the dice in the collection and returns the sum of the results.
    98.     /// </summary>
    99.     /// <returns>
    100.     /// An <b>Int32</b> containing the summed values of all dice.
    101.     /// </returns>
    102.     public int RollAll()
    103.     {
    104.         return Items.Sum(die => die.Roll());
    105.     }
    106.  
    107. #endregion Methods
    108. }
    Here is the interface and default implementation for dependency injection:
    CSharp Code:
    1. /// <summary>
    2. /// Represents an object that can generate a random integer.
    3. /// </summary>
    4. public interface IRandomNumberGenerator
    5. {
    6.     /// <summary>
    7.     /// Generates a random integer.
    8.     /// </summary>
    9.     /// <param name="max">
    10.     /// The maximum value (inclusive) that should be generated.
    11.     /// </param>
    12.     /// <returns>
    13.     /// An <b>Int32</b> containing a random number.
    14.     /// </returns>
    15.     /// <remarks>
    16.     /// The value returned should never be less than 1.
    17.     /// </remarks>
    18.     int GenerateRandomNumber(int max);
    19. }
    20.  
    21.  
    22. /// <summary>
    23. /// Represents an object that can generate a random integer.
    24. /// </summary>
    25. /// <remarks>
    26. /// This class is the default random number generator for the <see cref="Die" /> class.
    27. /// </remarks>
    28. public class RandomNumberGenerator : IRandomNumberGenerator
    29. {
    30.     /// <summary>
    31.     /// The underlying random number generator.
    32.     /// </summary>
    33.     /// <remarks>
    34.     /// A single <b>Random</b> object is used by all instances to avoid duplicate results when multiple values are generated quickly.
    35.     /// </remarks>
    36.     private static Random rng = new Random();
    37.  
    38.     /// <summary>
    39.     /// Generates a random number.
    40.     /// </summary>
    41.     /// <param name="max">
    42.     /// The maximum value (inclusive) that can be generated.
    43.     /// </param>
    44.     /// <returns>
    45.     /// An <b>Int32</b> containing a random number.
    46.     /// </returns>
    47.     /// <remarks>
    48.     /// The value returned will never be less than 1.
    49.     /// </remarks>
    50.     public int GenerateRandomNumber(int max)
    51.     {
    52.         if (max < 1)
    53.         {
    54.             throw new ArgumentOutOfRangeException("max", max, "Value cannot be less than 1.");
    55.         }
    56.  
    57.         return rng.Next(1, max + 1);
    58.     }
    59. }
    If you want to create dice that behave randomly then you use one of the constructors that doesn't have a parameter of type IRandomNumberGenerator. In that case, an instance of the RandomNumberGenerator class will be used, which uses a shared Random object internally.

    If you wanted your dice to produce known results for testing purposes then you could create your own implementation of the IRandomNumberGenerator interface and then pass an instance of that to one of the Die constructors that has a parameter of that type. Your dice will then produce the results you expect and you can test that your "game" behaves as expected under those circumstances.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width