Introduction
This is a simple version of Yahtzee that includes a high score table. Enjoy.
Background
This is my second article that I have written for CodeProject, so I hope some of you find it useful or at least fun.
One day, my daughter asked me if we could get Yahtzee for the computer. I thought instead of buying something, why not write it. She has beta tested it quite well, so hopefully most of the bugs have been ironed out in the process. This has took me a while to write, and if you want to see the history of how I got to where the program is now, you can see this on my website.
The Rules
The full rules for the Yahtzee game can be downloaded from Hasbro's website.
This version of the game will only allow one Yahtzee bonus, and does not include the Joker rules.
The Game
The program is made up of four forms and two rather useful classes that do most of the work. One of these classes does all of the work for the dice that you see at the top of the form, whilst the other does all of the work checking scores.
The Dice
The dice
class inherits from System.Windows.Forms.UserControl
so that it is easier to do the drawing of the object. The control knows how to draw itself and also what the next number to draw is. The latter is done using the Random
class, which originally posed some problems, like how do you make sure that two or more dice are not initialized with the same seed. I achieved this by using the following method:
public void InitializeRandomRoll( int nSeed )
{
DateTime aTime = new DateTime(1000);
aTime = DateTime.Now;
nSeed += (int)(aTime.Millisecond);
RandomPick = new Random(nSeed);
}
And since the initial seed is always different, the main form has its own random number generator, and seeds the method above with the next number.
Another problem in Yahtzee is, each turn you roll the dice three times. The first time, you roll all five dice. You then select some of the dice and keep them to one side. The others you put back in the cup and roll again. From the second roll, you may or may not keep some of these dice to one side, and roll whatever dice are left a third time.
We need to be able to hold a dice, so that it does not roll when the roll button is pressed. So, we add a variable to hold the state of the dice.
private bool m_bHoldState = false;
public bool HoldState
{
get { return m_bHoldState; }
set { m_bHoldState = value; }
}
Now, we need to make sure that if the dice is held, it does not get rolled. This is simply a matter of adding an if
statement to the Roll()
method.
public void Roll()
{
if( !HoldState )
{
RollNumber = RandomPick.Next(1,7);
this.Invalidate();
}
}
Finally, the dice is drawn in black if the dice is not held, and in red if it is. The following method draws a single dot at a given point:
public void DrawDot( Graphics g, Point p )
{
SolidBrush myBrush;
if( HoldState )
{
myBrush = new SolidBrush( Color.Red );
}
else
{
myBrush = new SolidBrush( Color.Black );
}
g.FillEllipse( myBrush, p.X, p.Y, dotWidth, dotWidth );
myBrush.Dispose();
}
The rest of the dice code is pretty easy to follow.
Calculating The Score
To calculate the scores, a class was created. The class has many functions that calculate the individual scores, it also keeps a tally of the total, upper and lower scores.
The algorithm used to add up the total scores of five dice when the same number is needed is shown below. The total of all of the dice that have the same number is returned to the form, or zero if none of the dice are the same as that needed.
public int AddUpDice( int DieNumber, Dice[] myDice )
{
int Sum = 0;
for( int i = 0; i < 5; i++ )
{
if( myDice[i].RollNumber == DieNumber )
{
Sum += DieNumber;
}
}
return Sum;
}
The algorithms for Three of a Kind and Four of a Kind are very similar. They are based on the above algorithm, but do not know which number that the three or four of a kind are meant to be. So, two loops are used to determine if indeed three or four of the dice are the same.
public int CalculateThreeOfAKind( Dice[] myDice )
{
int Sum = 0;
bool ThreeOfAKind = false;
for( int i = 1; i <= 6; i++ )
{
int Count = 0;
for( int j = 0; j < 5; j++ )
{
if( myDice[j].RollNumber == i )
Count++;
if( Count > 2 )
ThreeOfAKind = true;
}
}
if( ThreeOfAKind )
{
for( int k = 0; k < 5; k++ )
{
Sum += myDice[k].RollNumber;
}
}
return Sum;
}
The algorithm for calculating a Full House is slightly more complicated than the previous one. The basis relies on the fact that if we sort the dice numbers into ascending order, then the first two dice will be the same and the last three dice will be the same, but the second and third dice must be different. Alternatively, the first three dice will be the same and the last two dice will be the same, but the third and fourth dice must be different. This can be done in a simple if
statement.
Instead of returning the sum of the dice, we return a fixed score, which is 25.
public int CalculateFullHouse( Dice[] myDice )
{
int Sum = 0;
int[] i = new int[5];
i[0] = myDice[0].RollNumber;
i[1] = myDice[1].RollNumber;
i[2] = myDice[2].RollNumber;
i[3] = myDice[3].RollNumber;
i[4] = myDice[4].RollNumber;
Array.Sort(i);
if( (((i[0] == i[1]) && (i[1] == i[2])) &&
(i[3] == i[4]) &&
(i[2] != i[3])) ||
((i[0] == i[1]) &&
((i[2] == i[3]) && (i[3] == i[4])) &&
(i[1] != i[2])) )
{
Sum = 25;
}
return Sum;
}
The algorithm for calculating a Large Straight is similar to that of calculating a Full House, but much simpler. The basis for the if
statement is that when sorted, the five dice will contain the numbers 1, 2, 3, 4 and 5, or the numbers 2, 3, 4, 5 and 6. No other combination is acceptable.
Again, instead of returning the sum of the dice, we return a fixed score, which is 40.
public int CalculateLargeStraight( Dice[] myDice )
{
int Sum = 0;
int[] i = new int[5];
i[0] = myDice[0].RollNumber;
i[1] = myDice[1].RollNumber;
i[2] = myDice[2].RollNumber;
i[3] = myDice[3].RollNumber;
i[4] = myDice[4].RollNumber;
Array.Sort(i);
if( ((i[0] == 1) &&
(i[1] == 2) &&
(i[2] == 3) &&
(i[3] == 4) &&
(i[4] == 5)) ||
((i[0] == 2) &&
(i[1] == 3) &&
(i[2] == 4) &&
(i[3] == 5) &&
(i[4] == 6)) )
{
Sum = 40;
}
return Sum;
}
The algorithm for calculating a Small Straight is more complicated than the Large Straight algorithm. The basis for the if
statement is that four dice must contain numbers that are in a numerical sequence, with no gaps. A problem arises when two of the numbers are the same. This is solved, by moving the number that is the same to the end of the array and ignoring it.
Again, instead of returning the sum of the dice, we return a fixed score, which is 30.
public int CalculateSmallStraight( Dice[] myDice )
{
int Sum = 0;
int[] i = new int[5];
i[0] = myDice[0].RollNumber;
i[1] = myDice[1].RollNumber;
i[2] = myDice[2].RollNumber;
i[3] = myDice[3].RollNumber;
i[4] = myDice[4].RollNumber;
Array.Sort(i);
for( int j = 0; j < 4; j++ )
{
int temp = 0;
if( i[j] == i[j+1] )
{
temp = i[j];
for( int k = j; k < 4; k++ )
{
i[k] = i[k+1];
}
i[4] = temp;
}
}
if( ((i[0] == 1) && (i[1] == 2) && (i[2] == 3) && (i[3] == 4)) ||
((i[0] == 2) && (i[1] == 3) && (i[2] == 4) && (i[3] == 5)) ||
((i[0] == 3) && (i[1] == 4) && (i[2] == 5) && (i[3] == 6)) ||
((i[1] == 1) && (i[2] == 2) && (i[3] == 3) && (i[4] == 4)) ||
((i[1] == 2) && (i[2] == 3) && (i[3] == 4) && (i[4] == 5)) ||
((i[1] == 3) && (i[2] == 4) && (i[3] == 5) && (i[4] == 6)) )
{
Sum = 30;
}
return Sum;
}
The algorithm for Yahtzee is the same as for Three of a Kind and Four of a Kind. Again, instead of returning the sum of the dice, we return a fixed score, which is 50.
public int CalculateYahtzee( Dice[] myDice )
{
int Sum = 0;
for( int i = 1; i <= 6; i++ )
{
int Count = 0;
for( int j = 0; j < 5; j++ )
{
if( myDice[j].RollNumber == i )
Count++;
if( Count > 4 )
Sum = 50;
}
}
return Sum;
}
We do not really need to do any checking when calculating the Chance score, we simply add up the dice and return it as a number.
public int AddUpChance( Dice[] myDice )
{
int Sum = 0;
for( int i = 0; i < 5; i++ )
{
Sum += myDice[i].RollNumber;
}
return Sum;
}
Counting the scores
So that we know when the game is over, we keep count of how many scores have been allocated. As there are only fourteen possible scores, we know when to stop. The score count is kept in the following variable:
private int ScoreCount = 0;
After each score is allocated, the Reset
method checks to see if the game is over.
if( ScoreCount == 14 )
{
DialogResult result;
result = MessageBox.Show( "Your Score is " + TotalScore.Text + ".
Would You like to play again?",
"End Of Game",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question );
if( result == DialogResult.Yes )
{
. . .
}
else
{
this.Close();
}
}
Enjoy the game!