Java 实现的井字游戏,带电脑玩家






4.59/5 (15投票s)
平台独立的游戏,可在 Windows、Mac 和 Linux 上运行


引言
这是一个用 C# 编写的井字棋游戏程序。它内置了电脑玩家模块,还可以进行双人游戏。只要安装了 JRE 即可运行。源代码与 NetBeans 兼容。
背景
在探索 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();
boolean turn=true;
void reset()
{
for (int i=0;i<3 ;i++ )
{
for (int j=0;j<3 ;j++ ){pos[i][j]=0;} //The array is filled with zeros.
}
Component[] cmpnt=this.getContentPane().getComponents();
for(int i=0;i<cmpnt.length;i++)
{
if(cmpnt[i] instanceof JLabel)
((JLabel) cmpnt[i]).setText("") ; //All the labels are cleared.
}
cnt=0;
val=1; //X->1 and O->4
let='X';
jLabel10.setText(pl1+" to Play NOW."); //Status label is set.
}
boolean play(int l,int m)
{
if(pos[l][m]==0) //Check for overplaying of a move.
{
a=c;b=d;c=l;d=m; // Store coordinates for 2nd last and the last move.
JLabel ctrl=link(l,m); //linking the label to corresponding coordinates.
ctrl.setText(String.valueOf(let)); //Reflecting the move in label.
pos[l][m]=val; // Reflecting the move in array.
flip(); //Toggling between X and O.
checkwin(l,m,pos[l][m]); //Checking for the win situation.
return true;
}
else
return false; // If the move fails(if already played)
}
JLabel link(int l,int m)//Linking is used for mainly computer player logic.
{
if(l==0)
{
if(m==0)
return jLabel1;
if(m==1)
return jLabel2;
if(m==2)
return jLabel3;
}
if(l==1)
{
if(m==0)
return jLabel6;
if(m==1)
return jLabel5;
if(m==2)
return jLabel4;
}
if(l==2)
{
if(m==0)
return jLabel9;
if(m==1)
return jLabel8;
if(m==2)
return jLabel7;
}
return null;
}
void flip() // Easiest part of code.
{
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)
{ //Checking only the corresponding row and column for win situation.
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 only the corresponding diagonals for win situation.
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)
{
cnt=0;
}
}
}
if(cnt==1||cnt==0)
{ //When either first player wins or a 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); //To be called if computer is to play first next.
}
else
turn=false;
}
else
if(cnt==4)
{ // When 2nd player wins.
declare(pl2+" (Playing O) Wins!");
String temp=pl1;
pl1=pl2;
pl2=temp;//Toggling between player one and two.
reset();
if(vs==1)
if(pl1=="Computer")
compplay(val); //To be called if computer is to play first next.
else
turn=false;
}
}
}
void declare(String stmt)
{
if(JOptionPane.showConfirmDialog(this,stmt+" Do you want to continue?","",0)!=0)
{
System.exit(0); // Exiting if user does not click yes.
}
}
void compplay(int n)
{
boolean carry=true; // Is used so that only one module is executed.
if(diff==3)
carry=winorstop(a,b,n); // Checking for 2/3 win situation.
if((diff==2||diff==3) && carry)
{
if(n==1)
carry=winorstop(c,d,4); //Checking for situation where loss may occur.
else
carry=winorstop(c,d,1);
}
if(carry)
doany();// To play random move.
}
boolean winorstop(int l,int m,int n)
{
if(pos[l][0]+pos[l][1]+pos[l][2]==n*2) //Checking corresponding row
// for 2/3 situation.
{
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) // Checking corresponding
// column for 2/3 situation.
{
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) // Checking diagonal for 2/3.
{
for(int i=0;i<3;i++)
{
if(play(i,i)) // Play the move.
return false;
}
}
else
if(pos[2][0]+pos[1][1]+pos[0][2]==n*2)// Checking other diagonal
// for 2/3.
{
for(int i=0,j=2;i<3;i++,j--)
{
if(play(i,j)) // Play the move.
return false;
}
}
return true;
}
void doany()
{
int l=2,m=0;
switch(cnt)
{
case 0: play(0,0);// Some certain steps are used.
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.nextInt(3);//Play random moves
m=rnd.nextInt(3);//Until at least one is successful.
}
break;
}
}
private void jLabel1MouseClicked(java.awt.event.MouseEvent evt) {
if(play(0,0)&&turn==true)
compplay(val); // Execute only if the move was successful
// and if the game is in Computer Player mode.
}
操作流程如下:
- 玩家点击标签,将控制权转交给
play()
。 play()
将检查相应的数组位置是否为空,这对于防止玩家和电脑玩家模块重叠移动是必需的。- 如果移动成功,则更新相应的标签和与标签对应的数组元素。随后将调用
flip()
和checkwin()
。 flip()
将更改let
和val
的值,供下次移动使用。checkwin()
将使用play()
传递的坐标,仅检查相应的行和列;如果失败,则检查两条对角线。该函数仅在至少进行了四次移动后才激活。- 控制权返回到点击事件函数,其中
turn
决定是否应激活电脑玩家模块;在双人模式下,它会等待第二位玩家。 - 如果选择了“对战电脑”,则调用
compplay()
。 compplay()
根据用户选择的难度级别调用其他函数:Easy
:只调用doany()
Medium
:先在停止模式下调用winorstop()
,然后调用doany()
Hard
:先在获胜模式下调用winorstop()
,然后是停止模式,最后调用doany()
- 当出现获胜或平局时,第一位玩家重新开始。如果是电脑玩家的回合,则直接从
checkwin()
内部调用compplay()
。 - 尽管
doany()
用于随机移动,但它会执行一些特定的移动,以使Easy
模式看起来不那么“疯狂”。
关注点
在 C# 中应用透明度等特效,并在 Java 中实现它们,既有趣又富有教育意义。通过查看 C# 等效代码,开发者可以学习 Java,反之亦然。
历史
这是对之前上传的双人游戏版本的替换,同时也是对其的改进。为了消除冗余,已替换之前的版本。 ;)