五子棋系列博客(总共三篇,从简单功能简单界面到人机对战,以及较美观的登录及对战界面。第三篇博客中有最终实现的界面效果):
前面我们已经实现了一个初步的五子棋,已完成的功能如下:
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):有画笔参数
六、实现界面
至此,我们已经初步实现了五子棋项目判断输赢、悔棋和认输的功能。