65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的井字棋游戏(带电脑玩家)。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.22/5 (16投票s)

2010 年 8 月 10 日

CPOL

4分钟阅读

viewsIcon

163150

downloadIcon

9797

一款简单的 C# 游戏,可在 Windows、Linux (mono) 和 Mac (mono) 上运行

引言

Capture.JPG Screenshot.jpg
Windows 上的游戏 (.NET)
Linux 上的游戏 (Mono)

这是一个用 C# 编写的井字棋游戏程序。它内置了电脑玩家模块,甚至可以进行双人游戏。只要安装了 .NET 或 mono 运行时,就可以在任何地方运行。源代码兼容 Visual Studio 和 #Develop。

背景

在我探索 Java 和 C# 的过程中,我发现了一些有趣的相似之处和不同之处,并最终了解到学习其中一种语言,也能学会另一种。这个程序是我尝试向编程初学者展示这一点。

Using the Code

为了方便学习程序,它被划分成模块,在程序中表示为函数。使用简单的标签显示九个方格,并通过点击事件触发相应的代码执行。首先,下面解释使用的变量:

  • pos:一个二维数组,用于表示九个方格,因为在数组上进行操作比较容易。
  • cnt:一个计数器,用于跟踪已进行的回合数。
  • val:一个与字母对应的数值。1 代表 X4 代表 O。在数组中,使用数值而不是字母。
  • let:用于保存字母,XO
  • abcd:整数,用于保存倒数第二步和最后一步的坐标,电脑玩家逻辑会用到。
  • diffvs:用于识别用户选择的难度级别和游戏模式。
  • rndturn:用于生成随机数和切换电脑玩家模式。
  • pl1pl2:用于在状态栏中显示玩家姓名。

各个函数及其用途解释如下:

  • reset():此函数用于随时重新开始游戏,或者在声明获胜或平局后重新开始,同时也用于初始化某些组件。
  • play():此函数负责开始一回合,更新相应的标签和数组位置,并调用其他函数 flip()checkwin()。它使用函数 link() 来将坐标与相应的标签关联起来。
  • flip():在回合中负责在 XO 之间切换,最终对变量 letval 同样切换数值 14
  • checkwin():此函数在程序中下完一回合后检查 获胜平局 条件。它还使用 declare() 来声明结果,并根据规则管理对手的切换:如果第一玩家获胜或平局,他继续下一次,否则第二次由第二玩家先下。
  • compplay():这是电脑玩家模块,它根据玩家选择的难度级别进行电脑回合,并相应地调用 winorstop()doany() 函数。
  • winorstop():此函数用于在存在获胜机会时下出获胜回合,并在存在失败机会时阻止对手获胜。

标签和数组位置的排列如下:

label1 label2 label3 0,0 0,1 0,2
label6 label5 label4 1,0 1,1 1,2
label9 label8 label7 2,0 2,1 2,2

变量以及不同函数的代码如下:

int[,] pos=new int[3,3];
int cnt,val,a,b,c=1,d=1,diff=1,vs=1;
char let;
String pl1="You",pl2="Computer";
Random rnd=new Random();
bool turn=true; 
void reset()
	    {
	        for (int i=0;i<3 ;i++ )
	        {
	            for (int j=0;j<3 ;j++ ){pos[i,j]=0;}  //Fill array with zeros
	        }
	        foreach(Control ctrl in this.Controls)
			{
				if (ctrl is Label) 
				{
					ctrl.ResetText();  //Clear text for 
							//all labels
				}
			}
	        cnt=0;
	        val=1;  // X->1 and O->4
	        let='X';
	        label10.Text=pl1+" to Play NOW.";  //Setting status label.
	    }
bool play(int l,int m)
	    {
	        if(pos[l,m]==0)// Check to avoid overplays.
	        {
	            a=c;b=d;c=l;d=m;  //Hold coordinates of 2nd last and last moves.
	            Label ctrl=link(l,m);  //Link the coordinates to the label.
					// (used for computer player)
	            ctrl.Text=let.ToString();  //Reflecting move to screen.
	            pos[l,m]=val;  //Reflecting move in array
	            flip();  //Toggling between X and O
	            checkwin(l,m,pos[l,m]);  // Check for win or Draw situation.
	            return true;
	        }
	        else
	            return false;  //Useful if move is to be replayed.
	    }
Label link(int l,int m)  //Returning appropriate label for the passed coordinates.
	    {
	        if(l==0)
	        {
	            if(m==0)
	                    return label1;
	            if(m==1)
	                    return label2;
	            if(m==2)
	                    return label3;
	        }
	        if(l==1)
	        {
	            if(m==0)
	                    return label6;
	            if(m==1)
	                    return label5;
	            if(m==2)
	                    return label4;
	        }
	        if(l==2)
	        {
	            if(m==0)
	                    return label9;
	            if(m==1)
	                    return label8;
	            if(m==2)
	                    return label7;
	        }
	        return null;
	    }
void flip()   //Logic for toggle
	    {
	        if(let=='X')
	        {
	            let = 'O';
	            val=4;
	            cnt++;
	        }
	        else
	        {
	            let = 'X';
	            val=1;
	            cnt++;
	        }
	    } 
 void checkwin(int l,int m,int n)
	    {
	        if(cnt==1)
	            if(vs==1)
	                turn=true;
	        if(cnt>4)
	        {   // Check for corresponding row first.
	            if((pos[l,0]+pos[l,1]+pos[l,2]==n*3)||
				(pos[0,m]+pos[1,m]+pos[2,m]==n*3))
	            {
	                cnt=n;
	            }
	            else
	            {   //Checking for corresponding column.
	                if((pos[0,0]+pos[1,1]+pos[2,2]==n*3)||
				(pos[2,0]+pos[1,1]+pos[0,2]==n*3))
	                {
	                    cnt=n;
	                }
	                else
	                {
	                    if(cnt==9)
	                    {   //In a draw situation.
	                            cnt=0;
	                    }
	                }
	            }
	            if(cnt==1||cnt==0)
	            {  // If the first player wins or Draw occurs.
	                if(cnt==1)
	                    declare(pl1+" (Playing X) Wins!");
	                if(cnt==0)
	                    declare("The Game is a Draw!");
	                reset();
	                if(vs==1)
	                if(pl1=="Computer")
	                {
	                    turn=false;
	                    compplay(val);  	//If the First player happens 
					//to be computer we need to call it.
	                }
	                else
	                    turn=false;
	               
	            }
	            else
	            if(cnt==4)
	            {
	                declare(pl2+" (Playing O) Wins!");
	                String temp=pl1;
	                pl1=pl2;
	                pl2=temp;
	                reset();
	                if(vs==1)
	                if(pl1=="Computer")
	                    compplay(val);  	// If the first palyer is computer, 
					// we need to call this.
	                else
	                    turn=false;
	            }
	        }
	    }
	    
	    void declare(string stmt)
		{
			if(MessageBox.Show(stmt+" Do you want to continue?",
				"",MessageBoxButtons.YesNo,MessageBoxIcon.Question)
					!=DialogResult.Yes)
			{
				Application.Exit();  	//Exit if user does 
							//not click yes.
			}
		}
void compplay(int n)
	    {
	        bool carry=true;  	// Is used so that multiple moves are not played 
				// by computer.
	        if(diff==3)  	//Is called only if Hard difficulty is set.
	            carry=winorstop(a,b,n); // a & b are used so that check 
					// is performed only at last computers move.
	        if((diff==2||diff==3) && carry)  //Is called if Hard or Medium 
						// difficulty is set.
	        {// For stop require to check for opponents pieces using c & d
	            if(n==1)
	                carry=winorstop(c,d,4);
	            else
	                carry=winorstop(c,d,1);
	        }
	        if(carry)
	                doany();  // Executed in all three difficulty levels
	    }  
bool winorstop(int l,int m,int n)
	    {
	        if(pos[l,0]+pos[l,1]+pos[l,2]==n*2) //check for row, 
						// if two of three are filled.
	        {
	            for(int i=0;i<3;i++)
	            {
	                if(play(l,i))
	                    return false;
	            }
	        }
	        else
	            if(pos[0,m]+pos[1,m]+pos[2,m]==n*2) //Check for column for 2/3
	            {
	                for(int i=0;i<3;i++)
	                {
	                    if(play(i,m))
	                        return false;
	                }
	            }
	            else
	                if(pos[0,0]+pos[1,1]+pos[2,2]==n*2) //Check for diagonal 
							//for 2/3 situation.
	                {
	                        for(int i=0;i<3;i++)
	                        {
	                                if(play(i,i))
	                                        return false;
	                        }
	                }
	                else
	                    if(pos[2,0]+pos[1,1]+pos[0,2]==n*2) //Check for 
						//other diagonal for 2/3 situation.

	                    {
	                            for(int i=0,j=2;i<3;i++,j--)
	                            {
	                                    if(play(i,j))
	                                            return false;
	                            }
	                    }
	
	        return true;
	    } 
void doany()
	    {
	        int l=2,m=0;
	        switch(cnt)
	        {
	            case 0: play(0,0);   //First two moves are certainly played.
	                    break;       
	            case 1: if(!(play(1,1)))
	                        play(0,0);
	                    break;
	            case 2: if(!(play(2,2)))
	                        play(0,2);
	                    break;
	            case 3: if((pos[0,1]+pos[1,1]+pos[2,1])==val)
	                        play(0,1);
	                    else
	                        if((pos[1,0]+pos[1,1]+pos[1,2])==val)
	                            play(1,0);
	                        else
	                            if(pos[0,1]!=0)
	                                play(0,2);
	                            else
	                                play(2,0);
	
	                    break;
	            default : while(!(play(l,m)))
	                      {
	                        l=rnd.Next(3);  // Random moves are played
	                        m=rnd.Next(3);  //Until at least one is successful
	                      }
	                    break;
	        }
	    } 
void Label1Click(object sender, EventArgs e)
		{
			if(play(0,0)&&turn==true)
                        compplay(val); // Is executed only if Players move 
				//was successful and the game is in Vs computer mod.
		}     

操作流程如下:

  1. 玩家点击标签,将控制权转移到 play()
  2. play() 会检查相应的数组位置是否为空,这对于防止玩家和电脑玩家模块重叠下子是必要的。
  3. 如果下子成功,相应的标签和数组元素将被更新。然后会调用 flip()checkwin()
  4. flip() 会改变 letval 的值,供下一回合使用。
  5. checkwin() 会使用 play() 传递的坐标,仅检查相应的行和列。如果失败,则检查两条对角线。该函数仅在至少下了四回合后才激活。
  6. 控制权返回到点击事件函数,其中 turn 决定是否激活电脑玩家模块;在双人模式下,它会等待第二位玩家。
  7. 如果选择了“对战电脑”,则调用 compplay()
  8. compplay() 根据用户选择的难度级别调用其他函数。
    1. 简单:只调用 doany()
    2. 中等:先调用 winorstop() 进行阻止模式,然后调用 doany()
    3. 困难:先调用 winorstop() 进行获胜模式,然后进行阻止模式,最后调用 doany()
  9. 当遇到 获胜平局 时,第一玩家再次下子。如果是电脑玩家的回合,则从 checkwin() 内部调用 compplay()
  10. 虽然 doany() 用于随机下子,但会进行一些特定的下子,以使“简单”模式不至于显得太“疯狂”。

关注点

在 C# 中应用透明度等效果,并在 Java 中实现同样的功能,既有趣又富有启发性。通过查看等效的代码,开发者可以学习 Java,反之亦然。

历史

这是对之前上传的双人版本进行的替换,同时也是对其进行的改进。为了消除冗余,已删除之前的版本。;)

© . All rights reserved.