完整代码已上传到github上,地址:https://github.com/Alexlingl/GoBang。有需要的可以自取。
五子棋系列博客(总共三篇,从简单功能简单界面到人机对战,以及较美观的登录及对战界面。第三篇博客中有最终实现的界面效果):
JAVA五子棋的实现(一)
JAVA五子棋的实现(三)——人机对战(权值法)
前面我们已经实现了一个初步的五子棋,已完成的功能如下:
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 ArrayListChessPositonList=new ArrayList();//保存每一步的落子情况
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
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 ArrayListChessPositonList=new ArrayList();
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):有画笔参数
六、实现界面
至此,我们已经初步实现了五子棋项目判断输赢、悔棋和认输的功能。