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:
/// <summary> /// Represents a single die. /// </summary> public class Die { #region Fields /// <summary> /// The number of faces a die will have by default, if not specified by the user. /// </summary> public static readonly int DefaultFaceCount = 6; /// <summary> /// The object used to select a die face. /// </summary> private IRandomNumberGenerator faceSelector; #endregion Fields #region Properties /// <summary> /// Gets or sets the number of faces the die has. /// </summary> /// <value> /// An <b>Int32</b> containing the number of faces. /// </value> /// <remarks> /// The default number of faces is defined by <see cref="DefaultFaceCount" /> /// </remarks> public int FaceCount { get; set; } /// <summary> /// Gets the current face value of the die. /// </summary> /// <value> /// An <b>Int32</b> containing the current face value. /// </value> /// <remarks> /// This would be the side facing upwards on a physical die. /// </remarks> public int Value { get; private set; } #endregion Properties #region Constructors /// <summary> /// Creates a new instance of the <see cref="Die" /> class with the default number of faces and using the default random number generator. /// </summary> /// <remarks> /// The default number of faces is defined by <see cref="DefaultFaceCount" /> /// </remarks> public Die() : this(DefaultFaceCount) { } /// <summary> /// Creates a new instance of the <see cref="Die" /> class using the default random number generator. /// </summary> /// <param name="faceCount"> /// The number of faces the die has. /// </param> public Die(int faceCount) : this(faceCount, new RandomNumberGenerator()) { } /// <summary> /// Creates a new instance of the <see cref="Die" /> class with the default number of faces. /// </summary> /// <param name="randomNumberGenerator"> /// The object used to select a die face. /// </param> /// <remarks> /// The default number of faces is defined by <see cref="DefaultFaceCount" /> /// </remarks> public Die(IRandomNumberGenerator randomNumberGenerator) : this(DefaultFaceCount, randomNumberGenerator) { } /// <summary> /// Creates a new instance of the <see cref="Die" /> class. /// </summary> /// <param name="faceCount"> /// The number of faces the die has. /// </param> /// <param name="randomNumberGenerator"> /// The object used to select a die face. /// </param> public Die(int faceCount, IRandomNumberGenerator randomNumberGenerator) { if (faceCount <= 0) { throw new ArgumentOutOfRangeException("faceCount", "Dice must have one or more faces."); } this.FaceCount = faceCount; if (randomNumberGenerator == null) { randomNumberGenerator = new RandomNumberGenerator(); } faceSelector = randomNumberGenerator; } #endregion Constructors #region Methods /// <summary> /// Rolls the die. /// </summary> /// <returns> /// An <b>Int32</b> that is the new value of the <see cref="Value"/> property. /// </returns> public int Roll() { Value = faceSelector.GenerateRandomNumber(FaceCount); return Value; } #endregion Methods }Here is the interface and default implementation for dependency injection:CSharp Code:
/// <summary> /// A collection to store multiple <see cref="Die" /> objects. /// </summary> public class DieCollection : System.Collections.ObjectModel.Collection<Die> { #region Constructors /// <summary> /// Creates a new, empty instance of the <see cref="DieCollection" /> class. /// </summary> public DieCollection() { } /// <summary> /// Create a new instance of the <see cref="DieCollection" /> with the specified number of items. /// </summary> /// <param name="dieCount"> /// The initial number of items in the collection. /// </param> /// <remarks> /// 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. /// </remarks> public DieCollection(int dieCount) { for (var i = 0; i < dieCount; i++) { Items.Add(new Die()); } } /// <summary> /// Create a new instance of the <see cref="DieCollection" /> with the specified number of items. /// </summary> /// <param name="dieCount"> /// The initial number of items in the collection. /// </param> /// <param name="faceCount"> /// The number of faces each die has. /// </param> /// <remarks> /// The initial items all use the default random number generator. /// </remarks> public DieCollection(int dieCount, int faceCount) { for (var i = 0; i < dieCount; i++) { Items.Add(new Die(faceCount)); } } /// <summary> /// Create a new instance of the <see cref="DieCollection" /> with the specified number of items. /// </summary> /// <param name="dieCount"> /// The initial number of items in the collection. /// </param> /// <param name="randomNumberGenerator"> /// The object used to select a die face. /// </param> /// <remarks> /// The initial items all have the default number of faces, as defined by the <see cref="Die.DefaultFaceCount" /> field. /// </remarks> public DieCollection(int dieCount, IRandomNumberGenerator randomNumberGenerator) { for (var i = 0; i < dieCount; i++) { Items.Add(new Die(randomNumberGenerator)); } } /// <summary> /// Create a new instance of the <see cref="DieCollection" /> with the specified number of items. /// </summary> /// <param name="dieCount"> /// The initial number of items in the collection. /// </param> /// <param name="faceCount"> /// The number of faces each die has. /// </param> /// <param name="randomNumberGenerator"> /// The object used to select a die face. /// </param> public DieCollection(int dieCount, int faceCount, IRandomNumberGenerator randomNumberGenerator) { for (var i = 0; i < dieCount; i++) { Items.Add(new Die(faceCount, randomNumberGenerator)); } } #endregion Constructors #region Methods /// <summary> /// Rolls all the dice in the collection and returns the sum of the results. /// </summary> /// <returns> /// An <b>Int32</b> containing the summed values of all dice. /// </returns> public int RollAll() { return Items.Sum(die => die.Roll()); } #endregion Methods }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.CSharp Code:
/// <summary> /// Represents an object that can generate a random integer. /// </summary> public interface IRandomNumberGenerator { /// <summary> /// Generates a random integer. /// </summary> /// <param name="max"> /// The maximum value (inclusive) that should be generated. /// </param> /// <returns> /// An <b>Int32</b> containing a random number. /// </returns> /// <remarks> /// The value returned should never be less than 1. /// </remarks> int GenerateRandomNumber(int max); } /// <summary> /// Represents an object that can generate a random integer. /// </summary> /// <remarks> /// This class is the default random number generator for the <see cref="Die" /> class. /// </remarks> public class RandomNumberGenerator : IRandomNumberGenerator { /// <summary> /// The underlying random number generator. /// </summary> /// <remarks> /// A single <b>Random</b> object is used by all instances to avoid duplicate results when multiple values are generated quickly. /// </remarks> private static Random rng = new Random(); /// <summary> /// Generates a random number. /// </summary> /// <param name="max"> /// The maximum value (inclusive) that can be generated. /// </param> /// <returns> /// An <b>Int32</b> containing a random number. /// </returns> /// <remarks> /// The value returned will never be less than 1. /// </remarks> public int GenerateRandomNumber(int max) { if (max < 1) { throw new ArgumentOutOfRangeException("max", max, "Value cannot be less than 1."); } return rng.Next(1, max + 1); } }
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.




Reply With Quote