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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.59/5 (15投票s)

2010年7月21日

CPOL

4分钟阅读

viewsIcon

124303

downloadIcon

10530

平台独立的游戏,可在 Windows、Mac 和 Linux 上运行

Capture2.JPG Screenshot-1.jpg

引言

这是一个用 C# 编写的井字棋游戏程序。它内置了电脑玩家模块,还可以进行双人游戏。只要安装了 JRE 即可运行。源代码与 NetBeans 兼容。

背景

在探索 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 的值 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.
    }

操作流程如下:

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

关注点

在 C# 中应用透明度等特效,并在 Java 中实现它们,既有趣又富有教育意义。通过查看 C# 等效代码,开发者可以学习 Java,反之亦然。

历史

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

© . All rights reserved.