一个简单的Mastermind Game

简要说明一下 EG1222 这个 semester project 是要做一个简单的 MasterMind Game ,这个游戏的算法是一个数学主题演绎博弈的经典例子,当然我们要做的是这个游戏而不是研究参与这个游戏的过程。

通过这个例子我们可以了解一下面向对象编程的主要思想和方法。这也是我研究这个 project 的主要目的。

首先我们从需求上来分析一下这个程序的结构。

这是一个简单的程序,不需要多个 components ,只要一个 class ,或者说只要一个 namespace C# )。

这个 namespace 由几个模块构成: 1 、生成随机数; 2 、用户输入数据; 3 、用户数据和生成随机数比较; 4 、成绩显示。

在面向对象编程中,对象是可以多态和继承的,这就意味着代码的复用效率需要相当高。

当然在本例中,虽然使用的是面向对象的语言,但是实际结构和逻辑还是面向过程的,无论在面向对象编程还是在面向过程编程中,方法都是模块化编程的有力工具。复杂的算法中甚至需要方法中对自身的调用实现递归。

 

先说一下方法这个概念。

从大致上来说,方法类似于数学中的函数。注意,是仅仅类似。

方法,顾名思义,是可以用来执行的一个代码的集合,用来实现某种功能或者进行某种操作的。

方法在声明的时候是这样的格式:

修饰符 返回值类型 方法名 [ :类 ][ :接口 ][ :父方法 ] (形参)

{

    ……

}

方法在调用的时候的格式:

若有返回值,则

返回值 = 方法名(实际参数);

若无返回值,则:

方法名(实际参数);

 

这里,生成随机数在事件 BtnStart_Click 中可以实现

private void btnStart_Click(object sender, EventArgs e)

        {

            clearall();

            for (int i = 0; i <= 3; i++)

{

                int j = ro.Next(0, 6);

                intRanNum[i] = j;

            }

        }

intRanNum 是一个数组,用来储存随机生成的数字,范围是 0~5 ,对应 6 种颜色。

对应颜色的方法如下:

string [] strColor = new string [6] { "Red" , "Orange" , "Yellow" , "Green" , "Blue" , "Indigo" };

这样构造了一个 string 类型的数组用来存储颜色,这样数字能和这些颜色对应 string 在这个数组中的 Index 对应起来,从而把数和颜色对应起来。同样这个数组也可以用来被用于用户猜测数据显示颜色的调用过程。

 

Clearall() 方法是用来清除所有控件的。在程序一开始这里执行一下用来恢复程序到初始状态。

给出代码如下:

public void clearall()

        {

            counter1 = -1; counter2 = -1; counter3 = -1; counter4 = -1;

            counterGo = -1;

            counterAns = 0;

            for (int i = 0; i < 40; i++)

            {

                String strLabelName = "lbltry" + Convert .ToString(i + 1);

                Label _label = Try.Controls[strLabelName] as Label ;

                string strLabelName1 = "lblChk" + Convert .ToString(i + 1);

                Label __label = Check.Controls[strLabelName1] as Label ;

                __label.Text = "" ;

                _label.BackColor = Color .Empty;

            }

        }

这个方法没有返回值,所以不需要 return 语句。

 

用户输入数据是可以很容易通过 label_Click 事件完成的,可以使用计数器来完成计数。要注意,需要用 % 来完成颜色的转换,否则会出现数组的溢出。

以第一位为例 :

private void lblguess1_Click(object sender, EventArgs e)

        {

            counter1++;

            lblguess1.BackColor = System.Drawing.Color .FromName(strColor[counter1 %6]);

        }

还是那个方法,显示的时候直接 Color.FromName() ,直接从那个 Color 数组中读取颜色然后显示。一行代码解决问题。

 

关键是比较。

先讲算法。

这里有三个概念。一是 O ,表示位置和颜色都对;一是 P ,表示颜色对,但位置不对;一是 X ,表示位置和颜色都不对。

从算法上来讲,比较的过程是要有优先级的。即,先比较 O ,再比较 P ,最后一种剩余的情况就是 X

比如正确答案是 1112 ,用户猜测的数据是 1212 ,如果是平行比较的话,会得出结果是 OPOO ,但实际的结果应该是 OXOO ,因为猜测的第二个数字“ 2 ”在答案中已经没有可以匹配的数据了。因而应该是 X ,但是因为没有优先比较 O ,而是顺序比较每人个数字,就会出现 X 这样的情况。

而优先比较完 O 之后,需要把比较过的数据删除,但是在数组中删除数据会很不方便,因为后面所有数据的索引都要改变,还要为此写一个特别的方法而且索引变了循环变量也不好控制。所以用一个讨巧的方法,把比较过的数据变成“ -1 ”,这样在下一轮比较的时候加一个条件限制一下 -1 的情况就可以实现把比较过的数据删除了。

同样的情况也适用于 P 的比较。假设答案还是是 1012 ,我们猜测了 2204 ,那么如果不优先比较 P 的结果是,我们会得到 PPPX ,但实际是 PXPX ,因为第二个“ 2 ”,在答案中已经没有可以匹配位置的数据。换句话说,答案中只有一个“ 2 ”,而猜测中有 2 个“ 2 ”,就只能出现一次 P ,否则就说明算法有错误。

因而,整理一下。

先比较 O ,然后把得到 O 的数据对改成 -1

然后再比较 P ,条件是数据不 =-1 ,然后再把得到 P 的数据对改成 -1

最后剩下的情况就是 X ,在我们的程序中 X 是无用的数据,因而不计数。可以作为调试参数使用。

整个比较的过程就是:

for (int i = 0; i <= 3; i++)

            {

                if (intInput[i] == intRan[i] && intRan[i] != -1 && intInput[i] != -1 )

                {

                    counterO++;

                    intInput[i] = -1;

                    intRan[i] = -1;

                }

            }

for (int Ran = 0; Ran <= 3; Ran++)

                {

                    for (int input = 0; input <= 3; input++)

                    {

 

                        if ((intRan[Ran] != -1) && (intInput[input] == intRan[Ran]) && (Ran != input) && (intInput[input] != -1))

                        {

                            counterP++;

                            intRan[Ran] = -1;

                            intInput[input] = -1;

                        }

                        else if (intRan[Ran] != -1 && intInput[input] != -1 && intInput[input] != intRan[Ran])

                        {

                            counterX++;

                        }

                    }

}

比较的时候,其实是两个数组中的数的比较,一个是 intInput ,一个是 intRan 。比较 O 的时候,因为没有位置问题,所以只用一层循环就可以实现遍历。而当比较 P 的时候,由于有位置问题,所以需要两层循环,来实现 intInput 里的每一个数在 intRan 中的遍历。

 

最后是显示问题。先说一个东西。

做这个 project 最烦人的地方就在于那么多的 label ,都要显示颜色,都要显示结果,一个一个写显然很烦人,而且有些 label 要写很多遍。错一个就会出问题。

所以我们想, label1,label2,label3 ,这些名字里变的只是最后那一位数字,能不能通过这些数字来组合 label 的名字实现一次多个 label 的设置或者调用呢?

于是有了这样的代码:

for (int i = 0; i <= 3; i++)

            {

                String strLabelName = "lbltry" + Convert .ToString(i + 1 + Row * 4);

                Label _label = Try.Controls[strLabelName] as Label ;

                _label.BackColor  = Color .FromName(strColor [intInput[i]]);

            }

第一行用来生成对应的 label 名称,比如现在是 Row = 1 ,也就是在第一行显示,那么名字就是 lbltry1

第二行的代码是生成一个 System.WindowForms.Form1.Label 类的实例,叫 _label ,这个实例就叫对象,这里是真正的面向对象编程。这个对象的功能是在 Try 这个 GroupBox 里,找到名叫 strLabelName 的控件,然后把这个对象转化成这个控件。我这么干讲有可能还是不能理解,尤其是对象、实例这样的概念。

这么说,假如人,是一个类,那么每一个人,具体的人,就是人这个类的一个实例,也就是一个对象。那么人出生的过程就是这个对象实例化的过程,然后现在要找到一个人,进行一些操作,就需要把这个人的名字转化成具体这个人这个对象,然后才能找到这个人。这样说稍微有点能理解了吧?

之后就是更改背景颜色的过程啦,不多讲。

有了这样的一个东西之后,显示的部分就非常容易写啦。

 

下面给出比较这个方法的代码:

public int CheckColor(int [] intInput, int [] intRan,int Row)

        {

            string [] strResult = new string [4];

            int counterO = 0, counterP = 0, counterX = 0;

            int intAns = 0;

            for (int i = 0; i <= 3; i++)

            {

                String strLabelName = "lbltry" + Convert .ToString(i + 1 + Row * 4);

                Label _label = Try.Controls[strLabelName] as Label ;

                _label.BackColor  = Color .FromName(strColor [intInput[i]]);

            }

            for (int i = 0; i <= 3; i++)

            {

                if (intInput[i] == intRan[i] && intRan[i] != -1 && intInput[i] != -1 )

                {

                    counterO++;

                    intInput[i] = -1;

                    intRan[i] = -1;

                }

            }

                for (int Ran = 0; Ran <= 3; Ran++)

                {

                    for (int input = 0; input <= 3; input++)

                    {

 

                        if ((intRan[Ran] != -1) && (intInput[input] == intRan[Ran]) && (Ran != input) && (intInput[input] != -1))

                        {

                            counterP++;

                            intRan[Ran] = -1;

                            intInput[input] = -1;

                        }

                        else if (intRan[Ran] != -1 && intInput[input] != -1 && intInput[input] != intRan[Ran])

                        {

                            counterX++;

                        }

                    }

                }

            for (int i = 0; i < counterO + counterP; i++)

            {

                if (i < counterO) { strResult[i] = "O" ; }

                else { strResult[i] = "P" ; }

            }

            for (int i = 0; i < counterO + counterP; i++)           

{

                String strLabelName = "lblChk" + Convert .ToString(i + 1+Row * 4 );

                Label _label = Check.Controls[strLabelName] as Label ;

                _label.Text = strResult[i];

            }

            for (int i = 0; i < 4;i++ )

            {

                if (strResult[i] == "O" )

                {

                    intAns++;

                }

            }

            return intAns;

        }

intAns 用来计数,记录“ O ”的个数,当 intAns=4 的时候,就可以显示“ congratulations ”啦。

 

最后是 buttonGo 的代码,这里是程序的执行部分。但是由于前面有 clearall() CheckColor(int[] intInput, int[] intRan, int Row) 这两个方法,代码完全可以复用,所以很简单的几行就可以:代码加注释,不解释了。

private void btnGo_Click(object sender, EventArgs e)

        {

            int [] copy = new int [4];

            intRanNum.CopyTo(copy, 0);// 将原始数据复制到另一个数组中,比较完一次就恢复一次,否则比较过程中变成 -1 会影响原始数据。

            intInputNum[0] = counter1 %6;

            intInputNum[1] = counter2 %6;

            intInputNum[2] = counter3 %6;

            intInputNum[3] = counter4 %6;// 将用户猜测的数据读入 intInputNum 数组。

            counterGo ++;

            counterAns = CheckColor( intInputNum,copy,counterGo);// 这里就是调用比较的方法进行比较

            if (counterAns == 4)

            {

                MessageBox .Show("Congratulations!" );

            }

            for (int i = 0; i <= 3; i++)

            {

                String strLabelName = "lblguess" + Convert .ToString(i + 1);

                Label _label = groupBox1.Controls[strLabelName] as Label ;

                _label.BackColor = Color .Empty;

            }

            counter1 = -1; counter2 = -1; counter3 = -1; counter4 = -1;

        }

 

总体来说,完成基本的 MasterMind Game 的功能就需要这么多。大概 160 行左右的代码,加上必要的定义变量的语句的话。所以这样看来这个游戏还是一个初级,当然,可以加入的元素很多,比如自定义难度,计时,比如 UI 等等。

算法只有更好,没有最好。如有误,请指正。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值