简单的Yahtzee游戏





3.00/5 (6投票s)
2004 年 10 月 26 日
5分钟阅读

169242

3038
一篇关于用 C# 创建一个简单的骰子游戏的文章。
引言
这是 Yahtzee 的一个简化版本,包含一个高分榜。希望您喜欢。
背景
这是我为 CodeProject 写的第二篇文章,希望对大家有所帮助,或者至少能带来一些乐趣。
有一天,我女儿问我能不能为电脑买个 Yahtzee。我想,与其买,为什么不自己写一个呢。她已经对它进行了相当充分的测试,所以希望在这个过程中大部分的 bug 都已经被修复了。我花了不少时间来写这个,如果你想了解这个程序是如何发展到现在的历史,可以访问我的网站。
规则
Yahtzee 游戏的完整规则可以从Hasbro 的网站下载。
此版本的游戏只允许一个 Yahtzee 加分项,不包含 Joker 规则。
游戏
该程序由四个窗体和两个相当有用的类组成,它们完成了大部分工作。其中一个类负责处理窗体顶部显示的骰子,而另一个类则负责检查分数。
骰子
dice
类继承自 System.Windows.Forms.UserControl
,这样可以更轻松地绘制对象。该控件知道如何绘制自身以及下一个要绘制的数字。后者是通过 Random
类实现的,这最初带来了一些问题,例如如何确保两个或多个骰子不会用相同的种子初始化。我是通过以下方法实现的:
public void InitializeRandomRoll( int nSeed )
{
DateTime aTime = new DateTime(1000);
aTime = DateTime.Now;
nSeed += (int)(aTime.Millisecond);
RandomPick = new Random(nSeed);
}
由于初始种子始终不同,主窗体有自己的随机数生成器,并用下一个数字来播种上述方法。
Yahtzee 的另一个问题是,每轮有三次掷骰子。第一次,你掷所有五个骰子。然后,你选择一些骰子并把它们放在一边。其他的你放回骰盅,然后再次掷骰子。从第二次掷骰子开始,你可以选择保留其中的一些骰子,也可以不保留,然后掷剩下的骰子进行第三次掷骰。
我们需要能够锁定一个骰子,这样当按下掷骰按钮时它就不会被掷出。所以,我们添加一个变量来保存骰子的状态。
private bool m_bHoldState = false;
public bool HoldState
{
get { return m_bHoldState; }
set { m_bHoldState = value; }
}
现在,我们需要确保如果骰子被锁定,它就不会被掷出。这只需在 Roll()
方法中添加一个 if
语句即可。
public void Roll()
{
// If the dice is not held, roll it
if( !HoldState )
{
RollNumber = RandomPick.Next(1,7);
this.Invalidate();
}
}
最后,如果骰子未被锁定,则以黑色绘制;如果被锁定,则以红色绘制。以下方法在给定点绘制一个点
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();
}
其余的骰子代码都很容易理解。
计算分数
为了计算分数,创建了一个类。该类有许多计算单独分数的函数,它还保存总分、上半部分分数和下半部分分数的总和。
下面显示了在需要相同数字时,将五个骰子的总分数相加所使用的算法。所有相同数字的骰子的总和将返回给窗体,如果没有骰子与所需的数字相同,则返回零。
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;
}
三条相同(Three of a Kind)和四条相同(Four of a Kind)的算法非常相似。它们都基于上述算法,但不知道三条或四条相同是指哪个数字。因此,使用两个循环来确定是否有三个或四个骰子是相同的。
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;
}
计算满堂彩(Full House)的算法比前一个稍微复杂一些。其基础是,如果我们按升序对骰子数字进行排序,那么前两个骰子将是相同的,后三个骰子将是相同的,但第二个和第三个骰子必须不同。或者,前三个骰子是相同的,后两个骰子是相同的,但第三个和第四个骰子必须不同。这可以通过一个简单的 if
语句来实现。
我们不返回骰子的总和,而是返回一个固定的分数,即 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])) && // Three of a Kind
(i[3] == i[4]) && // Two of a Kind
(i[2] != i[3])) ||
((i[0] == i[1]) && // Two of a Kind
((i[2] == i[3]) && (i[3] == i[4])) && // Three of a Kind
(i[1] != i[2])) )
{
Sum = 25;
}
return Sum;
}
计算大顺子(Large Straight)的算法与计算满堂彩(Full House)的算法类似,但简单得多。 if
语句的基础是,当排序后,五个骰子包含数字 1、2、3、4 和 5,或者数字 2、3、4、5 和 6。其他任何组合都是不接受的。
同样,我们不返回骰子的总和,而是返回一个固定的分数,即 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;
}
计算小顺子(Small Straight)的算法比大顺子(Large Straight)算法更复杂。 if
语句的基础是,四个骰子必须包含数字序列中的数字,没有间隔。当有两个数字相同时,会出现问题。通过将相同的数字移动到数组的末尾并忽略它来解决这个问题。
同样,我们不返回骰子的总和,而是返回一个固定的分数,即 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);
// Problem can arise hear, if there is more than one of the same number, so
// we must move any doubles to the end
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;
}
Yahtzee 的算法与三条相同(Three of a Kind)和四条相同(Four of a Kind)的算法相同。同样,我们不返回骰子的总和,而是返回一个固定的分数,即 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;
}
在计算机会分数(Chance)时,我们实际上不需要做任何检查,只需将骰子相加并将其作为数字返回即可。
public int AddUpChance( Dice[] myDice )
{
int Sum = 0;
for( int i = 0; i < 5; i++ )
{
Sum += myDice[i].RollNumber;
}
return Sum;
}
计算总分
为了知道游戏何时结束,我们计算已经分配了多少分数。由于只有十四种可能的分数,我们知道何时停止。分数计数保存在以下变量中
private int ScoreCount = 0;
每次分配分数后,Reset
方法会检查游戏是否结束。
if( ScoreCount == 14 )
{
DialogResult result;
// Displays the MessageBox.
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 )
{
// Reset Everything
. . .
}
else
{
this.Close();
}
}
祝您游戏愉快!