JAVA五子棋的实现(二)

完整代码已上传到github上,地址:https://github.com/Alexlingl/GoBang。有需要的可以自取。

前面我们已经实现了一个初步的五子棋,已完成的功能如下:

1.一个15*15的五子棋界面;

2.能够在界面上下黑白棋子;

3.需要把棋子下在交叉点上;

4.实现棋子重绘;——作用:当界面大小被改变时能够保持棋盘和棋盘上面的棋子消失;

5.相同位置不能下多个棋子;

6.只有当“开始新游戏”的按钮被点击时,才能开始下棋;

今天我们继续完善这个五子棋,同样的在开始写代码之前我们还是要做一些准备工作

一、要实现哪些功能:

1.判断输赢

2.实现悔棋操作

3.实现认输操作

二、实现这些功能是否需要新增API类——这里的话不需要,我们前面用的那些API类就已经够了

三、各个功能实现的思路

1.判断输赢——出现了五颗连在一起的同颜色的棋子

A.判断的时间:刚落完棋子的时候

B.判断的范围:

一开始我想要遍历整个棋盘,但仔细想想这个想法是不切实际的。整个棋盘太大了,而且相连的棋子情况不好细分。于是我就换了一种思路:既然我们是在落子的时候开始判断,那么显然我们是要围绕我们刚落的这个棋子来进行判断。

如图,每一个点一共有四个方向可能出现五个相连的棋子。与这个棋子是否能决定输赢相关联的棋子每一个方向上有八个,往前四个往后四个。也就是每一个方向我们都要遍历九个棋子的颜色,判断是否有五个相同颜色的棋子,有就说明输赢已经出现。假设我们落下的棋子对应的数组位置为isAvail[i][j]。

同一行上:遍历isAvail[i-4][j]...isAvail[i+4][j];

同一列上:遍历isAvail[i][j-4]...isAvail[i][j+4];

同一斜线上(左上到右下):遍历isAvail[i-4][j-4]...isAvail[i+4][j+4]

同一斜线上(左下到右上):令sum=i+j;遍历isAvail[i-4][sum-(i-4)]...isAvail[i+4][sum-(i+4)];

C.注意事项:

要注意防止数组越界,iaAvail[i][j]中的i,j均不能小于0,大于14;

要注意五个棋子必须是相连的,在我们一次遍历的时候一旦出现了没有棋子或者是其他棋子,必须把前面的计数值清空,重新开始计数;

2.实现悔棋操作

设置一个ChessPosition对象,该对象包含listi,listj两个数据,分别表示我们与棋子对应的数组位置中的i和j。定义一个动态的ChessPosition对象数组ChessPositonList,每当我们落下一颗棋子时,就把这个棋子的数组位置信息添加到对象数组中。每当我们点击悔棋时,我们就把turn相应的数值转换到另一方,并且用remove方法,取出对象数组中最后一步所对应的棋子信息,并且删除这个信息。然后根据这个信息把isAvail数组中对应的位置重新置为0,并且调用GoBangframe对象的重绘方法。

3.实现认输操作

一旦“认输”按钮被点击,先通过turn的数值判断当前是轮到哪一方。如果是白方,就输出“黑方赢”的信息;如果是黑方,就输出“白方赢”的信息。

四、代码部分

//构建五子棋界面GoBangframe类

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import java.awt.Dimension;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionListener;
import java.awt.event.MouseListener;
import java.util.ArrayList;

public class GoBangframe extends JPanel implements GoBangconfig{
	public Graphics g;//定义一支画笔
	public int[][] isAvail=new int [15][15];//定义一个二维数组来储存棋盘的落子情况
	public ArrayList<ChessPosition>ChessPositonList=new ArrayList<ChessPosition>();//保存每一步的落子情况
	public int turn=1;
	
	//主函数入口
	public static void main(String args[]) {
		GoBangframe gf=new GoBangframe();//初始化一个五子棋界面的对象
		gf.initUI();//调用方法进行界面的初始化
	}
	
	public void initUI() {
		//初始化一个界面,并设置标题大小等属性
		JFrame jf=new JFrame();
		jf.setTitle("五子棋");
		jf.setSize(800,650);
		jf.setLocationRelativeTo(null);
		jf.setDefaultCloseOperation(3);
		
		jf.setLayout(new BorderLayout());//设置顶级容器JFrame为框架布局
		
		Dimension dim1=new Dimension(150,0);//设置右半部分的大小
		Dimension dim3=new Dimension(550,0);//设置左半部分的大小
		Dimension dim2=new Dimension(140,40);//设置右边按钮组件的大小
		
		//实现左边的界面,把GoBangframe的对象添加到框架布局的中间部分
		//已经有一个GoBangframe对象了,表示当前类的对象是this
		this.setPreferredSize(dim3);//设置下棋界面的大小
		this.setBackground(Color.LIGHT_GRAY);//设置下棋界面的颜色
		//这里的话直接把左边的画板添加上去,指明是在框架布局的中间版块
		//若放在其他版块会有一些小问题
		jf.add(this,BorderLayout.CENTER);//添加到框架布局的中间部分
		
		//实现右边的JPanel容器界面
		JPanel jp=new JPanel();
		jp.setPreferredSize(dim1);//设置JPanel的大小
		jp.setBackground(Color.white);//设置右边的界面颜色为白色
		jf.add(jp,BorderLayout.EAST);//添加到框架布局的东边部分
		jp.setLayout(new FlowLayout());//设置JPanel为流式布局
		
		//接下来我们需要把按钮等组件依次加到那个JPanel上面
		//设置按钮数组
		String[] butname= {"开始新游戏","悔棋","认输"};
		JButton[] button=new JButton[3];
		
		//依次把三个按钮组件加上去
		for(int i=0;i<butname.length;i++) {
			button[i]=new JButton(butname[i]);
			button[i].setPreferredSize(dim2);
			jp.add(button[i]);
		}
		
		//按钮监控类
		ButtonListener butListen=new ButtonListener(this);
		//对每一个按钮都添加状态事件的监听处理机制
		for(int i=0;i<butname.length;i++) {
			button[i].addActionListener(butListen);//添加发生操作的监听方法
		}
		
		//设置选项按钮
		String[] boxname= {"人人对战","人机对战"};
		JComboBox box=new JComboBox(boxname);
		jp.add(box);
				
		jf.setVisible(true);
	}
	
	
	//重写重绘方法,这里重写的是第一个大的JPanel的方法
	public void paint(Graphics g) {
		super.paint(g);//画出白框
		
		//重绘出棋盘
		g.setColor(Color.black);
		for(int i=0;i<row;i++) {
			g.drawLine(x, y+size*i, x+size*(column-1), y+size*i);
		}
		for(int j=0;j<column;j++) {
			g.drawLine(x+size*j, y, x+size*j, y+size*(row-1));
		}
		
		//重绘出棋子
		for(int i=0;i<row;i++) {
			for(int j=0;j<column;j++) {
				if(isAvail[i][j]==1) {
					int countx=size*i+20;
					int county=size*j+20;
					g.setColor(Color.black);
					g.fillOval(countx-size/2, county-size/2, size, size);
				}
				else if(isAvail[i][j]==2) {
					int countx=size*i+20;
					int county=size*j+20;
					g.setColor(Color.white);
					g.fillOval(countx-size/2, county-size/2, size, size);
				}
			}
		}
	}
	
}
import java.awt.event.ActionListener;
import java.awt.event.MouseListener;
import java.awt.event.ActionEvent;
import java.awt.Graphics;
import java.awt.Color;
import java.util.ArrayList;

//实现对GoBangframe下棋界面的监听接口处理
public class frameListener implements GoBangconfig,MouseListener{
	public GoBangframe gf;
	//public int turn;//判断当前轮到谁了,1表示黑方,2表示白方
	//动态数组对象的实例化
	//public ArrayList<ChessPosition>ChessPositonList=new ArrayList<ChessPosition>();
	
	public void setGraphics(GoBangframe gf) {
		this.gf=gf;
	}
	
	
	  public void mouseClicked(java.awt.event.MouseEvent e) {
		  int x=e.getX();
		  int y=e.getY();
		  //计算棋子要落在棋盘的哪个交叉点上
		  int countx=(x/40)*40+20;
		  int county=(y/40)*40+20;
		  Graphics g=gf.getGraphics();
		  
		  if(gf.isAvail[(countx-20)/40][(county-20)/40]!=0) {
			  System.out.println("此处已经有棋子了,请下在其它地方");
		  }
		  else {
			  //计算棋盘上棋子在数组中相应的位置
			  int colu=(countx-20)/40;
			  int ro=(county-20)/40;
			  
			  if(gf.turn==1) {
				  //先获取要落的地方
				  g.setColor(Color.black);
				  //落子
				  g.fillOval(countx-size/2, county-size/2, size, size);
				  //设置当前位置已经有棋子了,棋子为黑子
				  gf.isAvail[colu][ro]=1;
				  //把当前所下的棋子位置保存在动态数组中
				  gf.ChessPositonList.add(new ChessPosition(colu,ro));
				  gf.turn++;
				  
				  //判断是否已经出现五科棋子了
				  //列判断
				  //首先界定数组范围,防止越界
				  int imin=colu-4,imax=colu+4;
				  if(imin<0) imin=0;
				  if(imax>14) imax=14;
				  int count1=0;//判断相连的棋子数
				  for(int i=imin;i<=imax;i++) {
					  if(gf.isAvail[i][ro]==1) count1++;
					  //如果出现了其他棋子,或者是没有棋子时,就重新开始计数
					  else count1=0;
					  if(count1==5) { 
						  System.out.println("黑方赢");
						  return;
					  }
				  }
				  //行判断
				  //首先界定数组范围,防止越界
				  int jmin=ro-4,jmax=ro+4;
				  if(jmin<0) jmin=0;
				  if(jmax>14) jmax=14;
				  int count2=0;//判断相连的棋子数
				  for(int j=jmin;j<=jmax;j++) {
					  if(gf.isAvail[colu][j]==1) count2++;
					  else count2=0;
					  if(count2==5) {
						  System.out.println("黑方赢");
						  return;
					  }
					//如果出现了其他棋子,或者是没有棋子时,就重新开始计数
					  
				  }
				  //135度判断
				  //首先界定数组范围,防止越界
				  int count3=0;//判断相连的棋子数
				  for(int i=-4;i<=4;i++) {
					  if((colu+i>=0)&&(ro+i>=0)&&(colu+i<=14)&&(ro+i<=14)) {
						  if(gf.isAvail[colu+i][ro+i]==1) count3++;
							//如果出现了其他棋子,或者是没有棋子时,就重新开始计数
						  else count3=0;
						  if(count3==5) {
							  System.out.println("黑方赢");
							  return;
						  }
					  }
				  }
				  int count4=0;//判断相连的棋子数
				  for(int i=-4;i<=4;i++) {
					  if((colu+i>=0)&&(ro-i>=0)&&(colu+i<=14)&&(ro-i<=14)) {
						  //System.out.print("count4:"+count4);
						  if(gf.isAvail[colu+i][ro-i]==1) count4++;
							//如果出现了其他棋子,或者是没有棋子时,就重新开始计数
						  else count4=0;
						  if(count4==5) {
							  System.out.println("黑方赢");
							  return;
						  }
					  }
				  }
			  }
			  else {
				  g.setColor(Color.white);
				  g.fillOval(countx-size/2, county-size/2, size, size);
				  //设置当前位置已经有棋子了,棋子为白子
				  gf.ChessPositonList.add(new ChessPosition(colu,ro));
				  gf.isAvail[colu][ro]=2;
				  gf.turn--;
				 
				  //列判断
				  //首先界定数组范围,防止越界
				  int imin=colu-4,imax=colu+4;
				  if(imin<0) imin=0;
				  if(imax>14) imax=14;
				  int count1=0;//判断相连的棋子数
				  for(int i=imin;i<=imax;i++) {
					  if(gf.isAvail[i][ro]==2) count1++;

					//如果出现了其他棋子,或者是没有棋子时,就重新开始计数
					  else count1=0;
					  if(count1==5) {
						  System.out.println("白方赢");
						  return;
					  }
				  }
				  //行判断
				  //首先界定数组范围,防止越界
				  int jmin=ro-4,jmax=ro+4;
				  if(jmin<0) jmin=0;
				  if(jmax>14) jmax=14;
				  int count2=0;//判断相连的棋子数
				  for(int j=jmin;j<=jmax;j++) {
					  if(gf.isAvail[colu][j]==2) count2++;
					  //如果出现了其他棋子,或者是没有棋子时,就重新开始计数
					  else count2=0;
					  if(count2==5) {
						  System.out.println("白方赢");
						  return;
					  }
					  
				  }
				  //135度判断
				  //首先界定数组范围,防止越界
				  int count3=0;//判断相连的棋子数
				  for(int i=-4;i<=4;i++) {
					  if((colu+i>=0)&&(ro+i>=0)&&(colu+i<=14)&&(ro+i<=14)) {
						  if(gf.isAvail[colu+i][ro+i]==2) count3++;
						  //如果出现了其他棋子,或者是没有棋子时,就重新开始计数
						  else count3=0;
						  if(count3==5) {
							  System.out.println("白方赢");
							  return;
						  }
					  }
				  }
				  int count4=0;//判断相连的棋子数
				  for(int i=-4;i<=4;i++) {
					  if((colu+i>=0)&&(ro-i>=0)&&(colu+i<=14)&&(ro-i<=14)) {
						  if(gf.isAvail[colu+i][ro-i]==2) count4++;
						  //如果出现了其他棋子,或者是没有棋子时,就重新开始计数
						  else count4=0;
						  if(count4==5) {
							  System.out.println("白方赢");
							  return;
						  }
					  }
				  }
			  }
		  }
	  }
	  
	  
	  // Method descriptor #8 (Ljava/awt/event/MouseEvent;)V
	  public void mousePressed(java.awt.event.MouseEvent e) {
		  
	  }
	  
	  // Method descriptor #8 (Ljava/awt/event/MouseEvent;)V
	  public void mouseReleased(java.awt.event.MouseEvent e) {
		  
	  }
	  
	  // Method descriptor #8 (Ljava/awt/event/MouseEvent;)V
	  public void mouseEntered(java.awt.event.MouseEvent e) {
		  
	  }
	  
	  // Method descriptor #8 (Ljava/awt/event/MouseEvent;)V
	  public void mouseExited(java.awt.event.MouseEvent e) {
		  
	  }
}
//设置按钮监听方法ButttonLitener类
import java.awt.event.ActionListener;
import java.awt.Color;
import java.awt.event.ActionEvent;
import javax.swing.JFrame;

//实现对JPanel的监听接口处理
public class ButtonListener implements GoBangconfig,ActionListener{
	public GoBangframe gf;
	
	public ButtonListener(GoBangframe gf) {
		this.gf=gf;//获取左半部分的画板
	}
	
	//当界面发生操作时进行处理
	public void actionPerformed(ActionEvent e) {
		//获取当前被点击按钮的内容,判断是不是"开始新游戏"这个按钮
		if(e.getActionCommand().equals("开始新游戏")) {
			//如果是开始新游戏的按钮,再为左半部分设置监听方法
			frameListener fl=new frameListener();
			fl.setGraphics(gf);//获取画笔对象
			gf.addMouseListener(fl);
		}
		//判断当前点击的按钮是不是悔棋
		else if(e.getActionCommand().equals("悔棋")) {
			if(gf.ChessPositonList.size()>1) {
				//把棋子数组相应的位置置为0;
				ChessPosition l=new ChessPosition();
				//获取最后一个棋子的对象信息
				l=gf.ChessPositonList.remove(gf.ChessPositonList.size()-1);
				//把相应的数组位置置为0
				gf.isAvail[l.Listi][l.Listj]=0;
				//把玩家还原为上一步的玩家
				if(gf.turn==1) gf.turn++;
				else gf.turn--;
				
				//直接调用gf的重绘方法,重绘方法的画笔应该是在棋盘页面还没生成的时候就要获取
				//调用repaint会自动调用paint方法,而且不用给参数
				gf.repaint();
				//gf.paint(gf.getGraphics());

			}
			else {
				System.out.println("不能悔棋!");
			}
		}
		else if(e.getActionCommand().equals("认输")) {
			if(gf.turn==1) System.out.println("白方赢");
			else System.out.println("黑方赢");
		}
	}
	
}
//定义与棋盘数据相关的接口,保存棋盘的起点,格子大小,行数列数等信息
public interface GoBangconfig {
	int x=20,y=20,size=40,row=15,column=15;
}
//新建一个棋子类ChessPosition保存每一步棋子所在的位置
public class ChessPosition {
	public int Listi,Listj;
	
	public ChessPosition() {
		
	}
	public ChessPosition(int Listi,int Listj) {
		this.Listi=Listi;
		this.Listj=Listj;
	}
}

五、总结

1.这里在判断是否已经出现五颗相连的同颜色棋子时,不要用else if 语句。因为if..else if语句的执行情况是:一旦if条件满足,它就会直接进入if里面,不会再去判断else if,这样子的话即使棋子已经五颗了它也不会输出“黑方赢”的信息

				  for(int i=imin;i<=imax;i++) {
					  if(gf.isAvail[i][ro]==1) count1++;
					  //如果出现了其他棋子,或者是没有棋子时,就重新开始计数
					  else count1=0;
					  if(count1==5) { 
						  System.out.println("黑方赢");
						  return;
					  }
				  }

2.不要过多地创建对象。如果在写代码的时候我们需要用到当前这个对象,就直接用this来指,不要再重新定义一个对象。

3.连个重绘方法。

repaint():没有参数,会自动调用paint

paint(Graphics):有画笔参数

六、实现界面


至此,我们已经初步实现了五子棋项目判断输赢、悔棋和认输的功能。



猜你喜欢

转载自blog.csdn.net/alexwym/article/details/80856795