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






4.22/5 (16投票s)
一款简单的 C# 游戏,可在 Windows、Linux (mono) 和 Mac (mono) 上运行
引言
![]() |
![]() |
Windows 上的游戏 (.NET)
|
Linux 上的游戏 (Mono)
|
这是一个用 C# 编写的井字棋游戏程序。它内置了电脑玩家模块,甚至可以进行双人游戏。只要安装了 .NET 或 mono 运行时,就可以在任何地方运行。源代码兼容 Visual Studio 和 #Develop。
背景
在我探索 Java 和 C# 的过程中,我发现了一些有趣的相似之处和不同之处,并最终了解到学习其中一种语言,也能学会另一种。这个程序是我尝试向编程初学者展示这一点。
Using the Code
为了方便学习程序,它被划分成模块,在程序中表示为函数。使用简单的标签显示九个方格,并通过点击事件触发相应的代码执行。首先,下面解释使用的变量:
pos
:一个二维数组,用于表示九个方格,因为在数组上进行操作比较容易。cnt
:一个计数器,用于跟踪已进行的回合数。val
:一个与字母对应的数值。1
代表X
,4
代表O
。在数组中,使用数值而不是字母。let
:用于保存字母,X
或O
。a
、b
、c
、d
:整数,用于保存倒数第二步和最后一步的坐标,电脑玩家逻辑会用到。diff
和vs
:用于识别用户选择的难度级别和游戏模式。rnd
和turn
:用于生成随机数和切换电脑玩家模式。pl1
和pl2
:用于在状态栏中显示玩家姓名。
各个函数及其用途解释如下:
reset()
:此函数用于随时重新开始游戏,或者在声明获胜或平局后重新开始,同时也用于初始化某些组件。play()
:此函数负责开始一回合,更新相应的标签和数组位置,并调用其他函数flip()
和checkwin()
。它使用函数link()
来将坐标与相应的标签关联起来。flip()
:在回合中负责在X
和O
之间切换,最终对变量let
和val
同样切换数值1
和4
。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.
}
操作流程如下:
- 玩家点击标签,将控制权转移到
play()
。 play()
会检查相应的数组位置是否为空,这对于防止玩家和电脑玩家模块重叠下子是必要的。- 如果下子成功,相应的标签和数组元素将被更新。然后会调用
flip()
和checkwin()
。 flip()
会改变let
和val
的值,供下一回合使用。checkwin()
会使用play()
传递的坐标,仅检查相应的行和列。如果失败,则检查两条对角线。该函数仅在至少下了四回合后才激活。- 控制权返回到点击事件函数,其中
turn
决定是否激活电脑玩家模块;在双人模式下,它会等待第二位玩家。 - 如果选择了“对战电脑”,则调用
compplay()
。 compplay()
根据用户选择的难度级别调用其他函数。简单
:只调用doany()
。中等
:先调用winorstop()
进行阻止模式,然后调用doany()
。困难
:先调用winorstop()
进行获胜模式,然后进行阻止模式,最后调用doany()
。- 当遇到
获胜
或平局
时,第一玩家再次下子。如果是电脑玩家的回合,则从checkwin()
内部调用compplay()
。 - 虽然
doany()
用于随机下子,但会进行一些特定的下子,以使“简单”模式不至于显得太“疯狂”。
关注点
在 C# 中应用透明度等效果,并在 Java 中实现同样的功能,既有趣又富有启发性。通过查看等效的代码,开发者可以学习 Java,反之亦然。
历史
这是对之前上传的双人版本进行的替换,同时也是对其进行的改进。为了消除冗余,已删除之前的版本。;)