patterncsharpModerate
WinForms dice roller
Viewed 0 times
rollerdicewinforms
Problem
I am writing a dice roller winforms application using C# 2012 VS. Dice roller is set up for playing Shadowrun tabletop. I feel that there might be too much code going into the GUI, but I am unsure how it should be formatted. I am also very open to general advice. This is my first program I am trying to construct on my own after graduating. I have never attempted a GUI application before.
GitHub
User Control Code:
```
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace DiceRollerWinForms
{
public partial class DiceRollerUserControl : Form
{
private RollDice diceRoll = new RollDice();
private Roll currentRoll = new Roll();
private Int32 rollNumber = 1;
private Int32 currentNumDice = 1;
private Int32 lastNumHit = 0;
private Int32 lastNumDiceRolled = 0;
private string numDiceText = "Number of Dice";
public DiceRollerUserControl()
{
InitializeComponent();
}
private void newToolStripMenuItem_Click(object sender, EventArgs e)
{
}
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
{
}
private void splitContainer1_Panel1_Paint(object sender, PaintEventArgs e)
{
}
private void mainSplitContainer_SplitterMoved(object sender, SplitterEventArgs e)
{
}
private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
{
}
private void diceToRollBox_TextChanged(object sender, EventArgs e)
{
if (Int32.TryParse(diceToRollBox.Text, out currentNumDice))
{
currentNumDice = Int32.Parse(diceToRollBox.Text);
}
else
{
currentNumDice = 1;
}
GitHub
User Control Code:
```
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace DiceRollerWinForms
{
public partial class DiceRollerUserControl : Form
{
private RollDice diceRoll = new RollDice();
private Roll currentRoll = new Roll();
private Int32 rollNumber = 1;
private Int32 currentNumDice = 1;
private Int32 lastNumHit = 0;
private Int32 lastNumDiceRolled = 0;
private string numDiceText = "Number of Dice";
public DiceRollerUserControl()
{
InitializeComponent();
}
private void newToolStripMenuItem_Click(object sender, EventArgs e)
{
}
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
{
}
private void splitContainer1_Panel1_Paint(object sender, PaintEventArgs e)
{
}
private void mainSplitContainer_SplitterMoved(object sender, SplitterEventArgs e)
{
}
private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
{
}
private void diceToRollBox_TextChanged(object sender, EventArgs e)
{
if (Int32.TryParse(diceToRollBox.Text, out currentNumDice))
{
currentNumDice = Int32.Parse(diceToRollBox.Text);
}
else
{
currentNumDice = 1;
}
Solution
As an addendum to @retailcoder epic answer I offer...
Object Oriented Dice
Lots of your existing code will simply melt away when we take a more object oriented approach. The key is making classes for the basic things in dice game world and have each class responsible for doing what it is supposed to.
Die Class
A single Dice, that is.
notes
Dice class
We have the concept of "rolling dice." OK then let's make some Dice.
notes
DiceGame
Just a sketch. This could be a craps, yahtzee, lier's dice...
notes
Inversion of Control
We still need a class to put it all together. But first IOC...
The knee-jerk way is to instantiate the
Inversion of Control allows for dependency injection. By inverting the composition of objects we can inject different objects. It limits where we make changes for these different things. This is very powerful when doing unit testing.
IOC Makes Change Easier
So we're rolling 6-faced dice. What if you wanted to modify your code to also support 8, 12, or 20-faced dice? (I don't know what ShadowRun is, but maybe rules could be configurable?) And who says all dice must have the same number of faces? That makes quite a bunch of hard-coded 6's to fix!
Add a new
Drumroll, please
Object Oriented Dice
Lots of your existing code will simply melt away when we take a more object oriented approach. The key is making classes for the basic things in dice game world and have each class responsible for doing what it is supposed to.
Die Class
A single Dice, that is.
public class Die {
protected int sides = 6;
protected Random generate = new Random();
public int Roll() { return generate.Next(1,(sides+1)); }
}notes
- This is one 6 sided thing. It can be rolled.
- Random class - See OP's concerns on this
Dice class
We have the concept of "rolling dice." OK then let's make some Dice.
public class Dice {
List dice;
public int Count { get { return dice.Count; }
public Dice (params Die[] theDice) { // see notes.
foreach (Die die in theDice) { dice.Add(die); }
}
// this is an indexer. see notes.
public Die this[int i] { get { return dice[i]; } }
public int Roll() {
int total = 0;
foreach (Die die in dice) { total =+ die.Roll(); }
return total;
}
}notes
- Now rolling a single die is nicely abstracted. The code reads like what it does.
paramsallows you to have a variable number of parameters. So we can pass in 1, 2, 10 dice if we want
- An indexer is cool. Read this.
DiceGame
Just a sketch. This could be a craps, yahtzee, lier's dice...
public class DiceGame {
protected Dice dice;
public DiceGame(Dice theDice) { dice = theDice; }
public void Play() { // TBD
int total = dice.Roll(); rolling all the dice at once
// indexer allows individual Rolls.
int firstDieRoll = dice[0].Roll();
int secondDieRoll = dice[1].Roll();
// when I don't care how many there are
for (int i=0; i<dice.Count; i++) {
dice[i].Roll();
}
}
}notes
- Everything is in terms of
Dice, and that sound logical.DiceGameis nicely abstracted as far as handling dice is concerned.
- The Game deals with
Diceonly, not individualDies.
- It does not know how to roll the dice. The dice know, we just tell the dice to do it.
Dice, in turn, does not know how to Roll individualDies, we just tell the die to roll.
Inversion of Control
We still need a class to put it all together. But first IOC...
The knee-jerk way is to instantiate the
DiceGame, inside of which we "new-up" some Dice, inside of which we make some Die objects. The better way is to invert this construction hierarchy. Make the smallest bits first, then pass them into the constructor of thing it belongs in and so on...public DiceGameBuilder {
protected Die die1 = new Die();
protected Die die2 = new Die();
protected Dice dice = new Dice(die1, die2); //params doing it's thing
// DiceGameBuilder actually does not need die1, die2 objects
// so the constructor call would look like this
protected Dice dice = new Dice (new Die(), new Die());
protected DiceGame craps = new DiceGame(dice);
craps.Play();
}Inversion of Control allows for dependency injection. By inverting the composition of objects we can inject different objects. It limits where we make changes for these different things. This is very powerful when doing unit testing.
IOC Makes Change Easier
So we're rolling 6-faced dice. What if you wanted to modify your code to also support 8, 12, or 20-faced dice? (I don't know what ShadowRun is, but maybe rules could be configurable?) And who says all dice must have the same number of faces? That makes quite a bunch of hard-coded 6's to fix!
Add a new
Die constructorpublic Die (int sides = 6) { this.sides = sides; }Drumroll, please
- Optional constructor parameters :
- Existing calls with no parameters does not need to change
- Be sure to document the default behavior! XML comments would be just fine.
- ONLY the
Dieclass is touched.
- Each
Diecan have a different number of sides
- Without a
Dieclass this kind of modification would be a nightmare
- I did not made a
Dieclass because I knew I was going to make this change. TheDieis a logical, meaningful object in the dice game world - that's why.
Diceneeds no modification
DiceGameneeds no modification
- The change is very small because only a
Dieneeds to know how many sides it has.
- The classes tend to be small because each class is responsible for doing it's own thing.
- Methods tend to be small - this is a symptom of well designed classes.
Dice.Roll()is 3 lines of code!!
- We built all of this business layer code with zero regard for user interface. That's good.
- You should be able to "drive" the dice game without UI.
- The above points means there will be loose coupling with the UI when you write it.
- By building, driving, testing the business layer first (at least some minimally functioning part) the UI will be that much easie
Code Snippets
public class Die {
protected int sides = 6;
protected Random generate = new Random();
public int Roll() { return generate.Next(1,(sides+1)); }
}public class Dice {
List<Die> dice;
public int Count { get { return dice.Count; }
public Dice (params Die[] theDice) { // see notes.
foreach (Die die in theDice) { dice.Add(die); }
}
// this is an indexer. see notes.
public Die this[int i] { get { return dice[i]; } }
public int Roll() {
int total = 0;
foreach (Die die in dice) { total =+ die.Roll(); }
return total;
}
}public class DiceGame {
protected Dice dice;
public DiceGame(Dice theDice) { dice = theDice; }
public void Play() { // TBD
int total = dice.Roll(); rolling all the dice at once
// indexer allows individual Rolls.
int firstDieRoll = dice[0].Roll();
int secondDieRoll = dice[1].Roll();
// when I don't care how many there are
for (int i=0; i<dice.Count; i++) {
dice[i].Roll();
}
}
}public DiceGameBuilder {
protected Die die1 = new Die();
protected Die die2 = new Die();
protected Dice dice = new Dice(die1, die2); //params doing it's thing
// DiceGameBuilder actually does not need die1, die2 objects
// so the constructor call would look like this
protected Dice dice = new Dice (new Die(), new Die());
protected DiceGame craps = new DiceGame(dice);
craps.Play();
}public Die (int sides = 6) { this.sides = sides; }Context
StackExchange Code Review Q#33076, answer score: 15
Revisions (0)
No revisions yet.