前面在实现完一个JAVA的面板之后,就想着继续实现一个相较于面板更复杂一点的程序——五子棋。但是我在在搜索资料的时候,发现网上很多关于五子棋实现的博文都是一步到位,直接给个源代码,不是很适合新手学习。所以我这里打算记录一下自己实现五子棋的整个过程,大致会分为三四个阶段。今天我们先来实现第一个阶段的五子棋。同样的我们在着手写代码之前我们需要先做一些准备。
完整代码已上传到github上,地址:https://github.com/Alexlingl/GoBang。有需要的可以自取。
五子棋系列博客(总共三篇,从简单功能简单界面到人机对战,以及较美观的登录及对战界面。第三篇博客中有最终实现的界面效果):
一、我们需要实现哪些东西
- 一个15*15的五子棋界面;
- 能够在界面上下黑白棋子;
- 需要把棋子下在交叉点上;
- 实现棋子重绘;——作用:当界面大小被改变时能够保持棋盘和棋盘上面的棋子不会消失;
- 相同位置不能下多个棋子;
- 只有当“开始新游戏”的按钮被点击时,才能开始下棋;
(判断输赢等其他部分我们放到后面实现)
二、根据我们要实现的功能确定我们所需要的API类
五子棋的大致界面如下:
2.1 实现五子棋界面需要的API类
JFrame 顶级容器类
BorderLayout 框架布局类——把容器分为上北下南左西右东中间五个部分,每个部分只能添加一个组件。在这里我们需要把整个界面分为左右两部分。选择在框架布局的中间和右边两个部分。
这里我们还需要另一个容器类——JPanel 面板容器类。主要用来实现右边的部分,因为原本框架布局的右边部分只能添加一个组件,但是我们需要添加的组件不只一个,因此就先这个部分加上一个JPanel,然后再在JPanel上加我们需要的组件,包括按钮等等。
JFrame和JPanel的区别:JFrame是最底层的容器,JPanel放在它上面,同一个界面只有一个JFrame,但是一个JFrame上面可以有多个JPanel。相当于JFrame是一个大餐桌,JPanel是盘子,如果我们要对餐桌JFrame上的东西进行管理分类等,我们就需要使用这些JPanel盘子。
FlowLayout 流式布局类。因为我们右边的JPanel部分需要将按钮等组件由上到下进行排列。
JButton 按钮组件类
JComboBox 下拉列表框类——实现那个人人对战和人机对战的选择
Dimension 封装组件宽度和高度的类——组件类不能直接用setSize方法来设置
2.2 事件监听机制的类
ActionListener 状态监听处理类
ActionEvent 状态监听事件,监听某个组件的状态是否有改变
MouseListener 鼠标监听处理类
MouseEvent 鼠标监听事件,里面包含鼠标被点击等事件的处理方法。当界面被点击时就放下一个棋子。
2.3 绘画所需要的类
Graphics|Graphics2D 画笔类
Color 颜色类,需要设置画笔的颜色,实现黑白棋子
三、各个功能实现的思路
1.一个15*15的五子棋界面
需要重写GoBangframe的重绘方法,使其在界面刚生成时就产生一个15*15的棋盘。(重绘方法会在每次页面大小被改变时执行,因此当你改变大小时,棋盘不会消失)。这里我们可以通过两个for循环画出15条行线和15条列线。
2.能够在界面上下黑白棋子
实现这个功能需要定义一个变量turn表名当前轮到哪方下棋。如果turn=1表明轮到黑方,turn=2表明轮到白方。每次黑方下完棋之后turn+1,白方下完棋之后turn-1。
3.需要把棋子下在交叉点上
如果鼠标点击在交叉点处的某个范围内,我们都把棋子下在这个交叉点上。在这个由于各自大小是40,我们定义的范围时以交叉点为中心的上下20的正方形。这样我们就能确保每一次点击都会落到某一个交叉点处,不会覆盖也不会出现没有落子的情况。
4.实现棋子重绘
作用:当界面大小被改变时能够保持棋盘上面的棋子不消失;
前面的棋盘重绘,虽然15*15的棋盘不会消失,但是棋子会消失。因此我们在重绘出棋盘以后还要把棋子也重新画上去。这里用了一个二维数组isAvail[15][15]来保存棋子信息,如果isArray[i][j]=0,表明这个位置没有棋子,等于1表明是一个黑棋子,等于2表明是一个白棋子。我们根据这些信息,遍历isArray数组,把棋子重新绘制上去。
5.相同位置不能下多个棋子
判断isArray[i][j]是不是等于0,如果是0就可以下,如果不是则提示玩家下在其它地方。
6.只有当“开始新游戏”的按钮被点击时,才能开始下棋
下棋是通过监听棋盘界面来实现的,因此我们只需要监听“开始新游戏”这个按钮,如果这个按钮被点击了,我们在给棋盘界面添加监听方法,否则不添加。
四、代码部分
//构建五子棋界面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;
public class GoBangframe extends JPanel implements GoBangconfig{
public Graphics g;//定义一支画笔
public int[][] isAvail=new int [15][15];//定义一个二维数组来储存棋盘的落子情况
//主函数入口
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的对象添加到框架布局的中间部分
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);
}
}
}
}
}
//设置按钮监听方法ButttonLitener类
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
//实现对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);
}
}
}
//定义GoBangconfig接口
//定义与棋盘数据相关的接口,保存棋盘的起点,格子大小,行数列数等信息
public interface GoBangconfig {
int x=20,y=20,size=40,row=15,column=15;
}
import java.awt.event.ActionListener;
import java.awt.event.MouseListener;
import java.awt.event.ActionEvent;
import java.awt.Graphics;
import java.awt.Color;
//实现对GoBangframe下棋界面的监听接口处理
public class frameListener implements GoBangconfig,MouseListener{
public GoBangframe gf;
public int turn=1;//判断当前轮到谁了,1表示黑方,2表示白方
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(turn==1) {
//先设置颜色
g.setColor(Color.black);
//落子
g.fillOval(countx-size/2, county-size/2, size, size);
//设置当前位置已经有棋子了,棋子为黑子
gf.isAvail[colu][ro]=1;
turn++;
}
else {
g.setColor(Color.white);
g.fillOval(countx-size/2, county-size/2, size, size);
//设置当前位置已经有棋子了,棋子为白子
gf.isAvail[colu][ro]=2;
turn--;
}
}
}
// 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) {
}
}
五、实现的界面
至此,我们已经初步实现了五子棋的界面和落子,后面我们会继续完善这个五子棋,包括输赢的判断和悔棋等操作。