在开始编写程序之前,我们应该先要对五子棋游戏要做的事进行剖析,明确设计任务,功能要求等等。通过程序要实现的功能,设定具体的每个模块所完成的每一个功能,然后连接每一个模块来实现所需要的功能设计。首先从整体入手,本次设计从简,设计为游戏棋盘界面与各个功能区按照一点的比例均放置在同一主界面上。设计为左边为游戏区,右边为功能区。
代码实现:
一.首先创建一个GoBangTest类,里面实现main方法,main方法用GoBangFrame创建一个对象,然后调用该对象的start方法,开始程序:
public class GoBangTest {
public static void main(String[] args) {
GoBangFrame win = new GoBangFrame();
win.start();
}
}
二.创建一个GoBangFrame类。
使用JFrame类实现游戏界面。
- 设置title;
- 游戏界面尺寸为900*700;
- 设置初始化位置;
- 关闭游戏界面缩放;
- 设置默认开关操作;
- 设置可见为true;
import java.awt.BorderLayout;
import javax.swing.JFrame;
public class GoBangFrame extends JFrame{
GoBangPanel goBangPanel;
public void start() {
goBangPanel = new GoBangPanel();
this.add(goBangPanel, BorderLayout.WEST); // 界面左侧棋盘
this.setTitle("五子棋"); // 标题
this.setSize(GoBangUtil.GAME_WIDTH, GoBangUtil.GAME_HEIGHT); // 大小
this.setLocation(450, 150); // 初始化位置
this.setResizable(false); // 不可伸缩
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //默认关闭操作
this.setVisible(true); //设置可见
}
}
三.创建GoBangPanel 类,绘制左侧棋盘。
使用JPanel的来实现。
- 棋盘的尺寸为650, 700;
- 背景颜色是Color.ORANGE;(颜色自己随意hhh
- 把棋盘的这个JPanel添加到JFrame的左(西)侧:this.add(new GoBangPanel(), BorderLayout.WEST);
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.JPanel;
public class GoBangPanel extends JPanel{
public GoBangPanel() { // 构造方法,初始化
this.setPreferredSize(new Dimension(GoBangUtil.PANEL_WIDTH, GoBangUtil.PANEL_HEIGHT));
this.setBackground(Color.ORANGE); // 设置背景色new Color(200, 100, 50)(设置背景色另一种方法
}
}
四.为了以后维护方便,创建一个工具类:GoBangUtil类,存放一些常量
如:游戏界面高度,宽度等等
public class GoBangUtil {
public static final int GAME_WIDTH = 900; // 界面宽度
public static final int GAME_HEIGHT = 700; // 界面高度
public static final int PANEL_WIDTH = 650; // 棋盘宽度
public static final int PANEL_HEIGHT = 700; // 棋盘高度
}
?,至现在已大功告成 正式开始了。
接下来在左边游戏区绘制棋盘线。
五子棋棋盘为十五路(15×15)棋盘,形状近于正方形,平面上画横竖各15条平行线,线路为黑色,构成225个交叉点。这里设置邻近两个交点的距离纵线为40,横线为40,棋盘距离界面边缘距离为40。
注意到距离纵线,横线,边界距离等常量以后会用到,因此暂且放入GoBangUtil类中
到现在GoBangUtil类中成员:
public class GoBangUtil {
public static final int GAME_WIDTH = 900; // 界面宽度
public static final int GAME_HEIGHT = 700; // 界面高度
public static final int PANEL_WIDTH = 650; // 棋盘宽度
public static final int PANEL_HEIGHT = 700; // 棋盘高度
public static final int CELL_WIDTH = 40; // 线间距离
public static final int LINE_COUNT = 15; // 线的数量
public static final int OFFSET = 40; // 偏移量
}
绘制以上的内容。需要重写JPanel的paintComponent(Graphics g)进行绘制。把g转换为Graphics2D g2d对象,以Graphics2D 对象来绘制的,进行2d画图。
因此,在GoBangPanel重写JPanel 的paintComponent方法,并且创建drawLine方法绘制棋盘线,并在paintComponent方法中调用。
棋盘的左侧和上、下侧边缘应该与留出一定的空间,以便于绘制顺序编号。预留宽度为GoBangUtil.OFFSET(即40)。
public void paintComponent(Graphics g) { // 绘制
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g; //把g转换为Graphics2D g2d对象
g2d.setStroke(new BasicStroke(2)); // 加粗线
drawLine(g2d); // 画棋盘
}
private void drawLine(Graphics2D g2d) {
for (int i = 0; i < GoBangUtil.LINE_COUNT; i++) { // 横线
g2d.drawLine(GoBangUtil.OFFSET, GoBangUtil.OFFSET + i * GoBangUtil.CELL_WIDTH,
GoBangUtil.OFFSET + (GoBangUtil.LINE_COUNT - 1) * GoBangUtil.CELL_WIDTH,
GoBangUtil.OFFSET + i * GoBangUtil.CELL_WIDTH);
}
for (int i = 0; i < GoBangUtil.LINE_COUNT; i++) { // 竖线
g2d.drawLine(GoBangUtil.OFFSET + i * GoBangUtil.CELL_WIDTH, GoBangUtil.OFFSET,
GoBangUtil.OFFSET + i * GoBangUtil.CELL_WIDTH,
GoBangUtil.OFFSET + (GoBangUtil.LINE_COUNT - 1) * GoBangUtil.CELL_WIDTH);
}
}
结果截图
根据正规五子棋棋盘要求,棋盘正中有一点,称为“天元”。棋盘两端的横线称端线,棋盘左右最外边的两条纵线称边线。从两条端线和两条边线向正中发展而纵横交叉在第四条线形成的四个点称为“星”。天元和星应在棋盘上用直径约为10的实心小圆点标出。
为了便于维护,还是将天元和星的大小规格放进GoBangUtil类里。
至现在GoBangUtil类中的成员:
public class GoBangUtil {
public static final int GAME_WIDTH = 900; // 界面宽度
public static final int GAME_HEIGHT = 700; // 界面高度
public static final int PANEL_WIDTH = 650; // 棋盘宽度
public static final int PANEL_HEIGHT = 700; // 棋盘高度
public static final int CELL_WIDTH = 40; // 线间距离
public static final int LINE_COUNT = 15; // 线的数量
public static final int OFFSET = 40; // 偏移量
public static final int STAR_WIDTH = 10; // 天元宽度
}
和绘制棋盘线相似,在GoBangPanel类中创建 drawStar方法,然后再paintComponent方法中调用。
绘制天元和星时注意,使用fillOval方法绘制,
public abstract void fillOval(int x,int y,int width,int height)
使用当前颜色填充外接指定矩形框的椭圆。
参数:
x - 要填充椭圆的左上角的 x 坐标。
y - 要填充椭圆的左上角的 y 坐标。
width - 要填充椭圆的宽度。
height - 要填充椭圆的高度。
(这里的左上角坐标是指椭圆的外接矩形的左上角坐标)
特别要调节x和y,让天元和星的圆心落到棋盘线的交叉点上,所以 x(和y)要 减 GoBangUtil.STAR_WIDTH / 2。
否则会出错:
paintComponent方法和drawStar方法完整代码:
public void paintComponent(Graphics g) { // 绘制
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setStroke(new BasicStroke(2)); // 加粗线
drawLine(g2d); // 画棋盘
drawStar(g2d); // 画天元和星
}
//绘制天元和星
private void drawStar(Graphics2D g2d) {
// 天元
g2d.fillOval(GoBangUtil.LINE_COUNT / 2 * GoBangUtil.CELL_WIDTH + GoBangUtil.OFFSET - GoBangUtil.STAR_WIDTH / 2,
GoBangUtil.LINE_COUNT / 2 * GoBangUtil.CELL_WIDTH + GoBangUtil.OFFSET - GoBangUtil.STAR_WIDTH / 2,
GoBangUtil.STAR_WIDTH, GoBangUtil.STAR_WIDTH);
// 左上角的星
g2d.fillOval(GoBangUtil.LINE_COUNT / 4 * GoBangUtil.CELL_WIDTH + GoBangUtil.OFFSET - GoBangUtil.STAR_WIDTH / 2,
GoBangUtil.LINE_COUNT / 4 * GoBangUtil.CELL_WIDTH + GoBangUtil.OFFSET - GoBangUtil.STAR_WIDTH / 2,
GoBangUtil.STAR_WIDTH, GoBangUtil.STAR_WIDTH);
// 左下角的星
g2d.fillOval(GoBangUtil.LINE_COUNT / 4 * GoBangUtil.CELL_WIDTH + GoBangUtil.OFFSET - GoBangUtil.STAR_WIDTH / 2,
(GoBangUtil.LINE_COUNT - GoBangUtil.LINE_COUNT / 4) * GoBangUtil.CELL_WIDTH - GoBangUtil.STAR_WIDTH / 2,
GoBangUtil.STAR_WIDTH, GoBangUtil.STAR_WIDTH);
// 右上角的星
g2d.fillOval(
(GoBangUtil.LINE_COUNT - GoBangUtil.LINE_COUNT / 4) * GoBangUtil.CELL_WIDTH - GoBangUtil.STAR_WIDTH / 2,
GoBangUtil.LINE_COUNT / 4 * GoBangUtil.CELL_WIDTH + GoBangUtil.OFFSET - GoBangUtil.STAR_WIDTH / 2,
GoBangUtil.STAR_WIDTH, GoBangUtil.STAR_WIDTH);
// 右下角的星
g2d.fillOval(
(GoBangUtil.LINE_COUNT - GoBangUtil.LINE_COUNT / 4) * GoBangUtil.CELL_WIDTH - GoBangUtil.STAR_WIDTH / 2,
(GoBangUtil.LINE_COUNT - GoBangUtil.LINE_COUNT / 4) * GoBangUtil.CELL_WIDTH - GoBangUtil.STAR_WIDTH / 2,
GoBangUtil.STAR_WIDTH, GoBangUtil.STAR_WIDTH);
}
结果截图如下:
接着就是绘制棋盘旁边的序号了。
以对局开始时的黑方为准,棋盘上的纵行线从近到远用阿拉伯数字1-15标记,横行线从左到右用英文字母A-O按字母顺序标记。
绘制数字列的时候,x保持不变,计算y的坐标值。获取数字的行高
FontMetrics fm = g2d.getFontMetrics();
//获取文本的高度
int height = fm.getAscent();
绘制字母的时候,可以通过ASCII码进行计算。获取字符的宽度
int width = fm.stringWidth(str);
和绘制棋盘线相似,在GoBangPanel类中创建 drawNumber方法,然后再paintComponent方法中调用。
paintComponent方法中添加
drawNumber(g2d); // 棋盘旁的坐标
GoBangPanel类中创建方法:
// 绘制棋盘旁的坐标
private void drawNumber(Graphics2D g2d) {
for (int i = GoBangUtil.LINE_COUNT; i > 0; i--) {
FontMetrics fn = g2d.getFontMetrics();
int height = fn.getAscent();
g2d.drawString(16 - i + "", 10, i * GoBangUtil.CELL_WIDTH + height / 2); // 左边数字
int width = fn.stringWidth(((char) (64 + i)) + "");// 下面的字母
g2d.drawString(((char) (64 + i)) + "", GoBangUtil.CELL_WIDTH * i - width / 2,
GoBangUtil.OFFSET + GoBangUtil.LINE_COUNT * GoBangUtil.CELL_WIDTH);
}
}
结果截图:
以上步骤进行的还算顺利。
接下来就要绘制预选框了。为了更清楚鼠标指向哪一个交点,专门设计一个落棋预选框。先做试验,首先画一个静态的,就先放在棋盘中间吧。
预选框坐标偏移量是单元格一半,线长为单元格1/4,在某一个点绘制预选框。
绘制规则:在(x,y)这个点向外(上下左右)移出一半的距离,然后绘制1/4。
重点就是线坐标和实际坐标之间的转换。
为了醒目,设置预选框的颜色为红色 g2d.setColor(Color.RED);
在GoBangPanel类中创建绘制提示框方法drawTrips方法。
// 绘制提示框
private void drawTrips(Graphics2D g2d) {
int i = GoBangUtil.LINE_COUNT/2;
int j = GoBangUtil.LINE_COUNT/2;
g2d.setColor(Color.red); //预选框为红色,
// 左上角向右
g2d.drawLine(i - GoBangUtil.OFFSET / 2, j - GoBangUtil.OFFSET / 2, i - GoBangUtil.OFFSET / 4,
j - GoBangUtil.OFFSET / 2);
// 左上角向下
g2d.drawLine(i - GoBangUtil.OFFSET / 2, j - GoBangUtil.OFFSET / 2, i - GoBangUtil.OFFSET / 2,
j - GoBangUtil.OFFSET / 4);
// 左下角向右
g2d.drawLine(i - GoBangUtil.OFFSET / 2, j + GoBangUtil.OFFSET / 2, i - GoBangUtil.OFFSET / 4,
j + GoBangUtil.OFFSET / 2);
// 左下角向上
g2d.drawLine(i - GoBangUtil.OFFSET / 2, j + GoBangUtil.OFFSET / 2, i - GoBangUtil.OFFSET / 2,
j + GoBangUtil.OFFSET / 4);
// 右上角向左
g2d.drawLine(i + GoBangUtil.OFFSET / 2, j - GoBangUtil.OFFSET / 2, i + GoBangUtil.OFFSET / 4,
j - GoBangUtil.OFFSET / 2);
// 右上角向下
g2d.drawLine(i + GoBangUtil.OFFSET / 2, j - GoBangUtil.OFFSET / 2, i + GoBangUtil.OFFSET / 2,
j - GoBangUtil.OFFSET / 4);
// 右上角向左
g2d.drawLine(i + GoBangUtil.OFFSET / 2, j + GoBangUtil.OFFSET / 2, i + GoBangUtil.OFFSET / 4,
j + GoBangUtil.OFFSET / 2);
// 右上角向下
g2d.drawLine(i + GoBangUtil.OFFSET / 2, j + GoBangUtil.OFFSET / 2, i + GoBangUtil.OFFSET / 2,
j + GoBangUtil.OFFSET / 4);
g2d.setColor(Color.black); //绘制完预选框,将画笔设置为黑色
}
在paintComponent中调用drawTrips方法drawTrips(g2d);
运行结果:
静态预选框搞定。
当然,实际运用中,肯定是通过获取鼠标当前位置,从而实时更新预选框的位置。
给下棋的对象(GoBangPanel)添加一个鼠标移动的事件。通过事件对象可以获取鼠标的x和y值。可以计算出线坐标。
注意:判断鼠标是否已经出了有效区域。
在GoBangPanel的构造器中进行添加。this.addMouseMotionListener(l);创建一个MouseMotionAdapter实例 。
实现public void mouseMoved(MouseEvent e)方法。
MouseEvent e就是事件对象。
调用repaint();方法把画图重新进行绘制,会自动调用paintComponent(Graphics g)方法
这个时候就应该在GoBangPanel类的构造方法即GoBangPanel方法中添加鼠标监听器。
this.addMouseMotionListener(mouseMotionListener);
在GoBangPanel类中添加两个成员变量。记录当前鼠标所在的线交点的坐标。
为了是的开局预选框在棋盘中心。给这两个成员变量赋初值。
private int x = GoBangUtil.LINE_COUNT / 2; // 记录线交点的横坐标
private int y = GoBangUtil.LINE_COUNT / 2; //记录线交点的纵坐标
然后在GoBangPanel类中添加
// 取得提示框的坐标
private MouseMotionListener mouseMotionListener = new MouseMotionListener() {
@Override
public void mouseMoved(MouseEvent e) {
int cx = e.getX(); // 获取鼠标当前坐标
int cy = e.getY();
if (cx >= GoBangUtil.OFFSET - GoBangUtil.OFFSET / 2
&& cx <= GoBangUtil.OFFSET + (GoBangUtil.LINE_COUNT - 1) * GoBangUtil.CELL_WIDTH
+ GoBangUtil.OFFSET / 2-1
&& cy >= GoBangUtil.OFFSET - GoBangUtil.OFFSET / 2 && cy <= GoBangUtil.OFFSET
+ (GoBangUtil.LINE_COUNT - 1) * GoBangUtil.CELL_WIDTH + GoBangUtil.OFFSET / 2-1) { // 判断是否在棋盘内
x = (cx - GoBangUtil.OFFSET / 2) / GoBangUtil.CELL_WIDTH; // 将座标的转换成棋盘内线的交点
y = (cy - GoBangUtil.OFFSET / 2) / GoBangUtil.CELL_WIDTH;
repaint();
}
}
@Override
public void mouseDragged(MouseEvent e) {
}
};
再将GoBangPanel类中的drawTrips方法添加两个参数,用以传递获取的当前鼠标所在的线交点的坐标。
// 绘制提示框
private void drawTrips(Graphics2D g2d, int i, int j) {
i = i * GoBangUtil.CELL_WIDTH + GoBangUtil.OFFSET;
j = j * GoBangUtil.CELL_WIDTH + GoBangUtil.OFFSET;
g2d.setColor(Color.red);
// 左上角向右
g2d.drawLine(i - GoBangUtil.OFFSET / 2, j - GoBangUtil.OFFSET / 2, i - GoBangUtil.OFFSET / 4,
j - GoBangUtil.OFFSET / 2);
// 左上角向下
g2d.drawLine(i - GoBangUtil.OFFSET / 2, j - GoBangUtil.OFFSET / 2, i - GoBangUtil.OFFSET / 2,
j - GoBangUtil.OFFSET / 4);
// 左下角向右
g2d.drawLine(i - GoBangUtil.OFFSET / 2, j + GoBangUtil.OFFSET / 2, i - GoBangUtil.OFFSET / 4,
j + GoBangUtil.OFFSET / 2);
// 左下角向上
g2d.drawLine(i - GoBangUtil.OFFSET / 2, j + GoBangUtil.OFFSET / 2, i - GoBangUtil.OFFSET / 2,
j + GoBangUtil.OFFSET / 4);
// 右上角向左
g2d.drawLine(i + GoBangUtil.OFFSET / 2, j - GoBangUtil.OFFSET / 2, i + GoBangUtil.OFFSET / 4,
j - GoBangUtil.OFFSET / 2);
// 右上角向下
g2d.drawLine(i + GoBangUtil.OFFSET / 2, j - GoBangUtil.OFFSET / 2, i + GoBangUtil.OFFSET / 2,
j - GoBangUtil.OFFSET / 4);
// 右上角向左
g2d.drawLine(i + GoBangUtil.OFFSET / 2, j + GoBangUtil.OFFSET / 2, i + GoBangUtil.OFFSET / 4,
j + GoBangUtil.OFFSET / 2);
// 右上角向下
g2d.drawLine(i + GoBangUtil.OFFSET / 2, j + GoBangUtil.OFFSET / 2, i + GoBangUtil.OFFSET / 2,
j + GoBangUtil.OFFSET / 4);
g2d.setColor(Color.black);
}
还要将paintComponent方法中调用drawTrips方法的形式更改为drawTrips(g2d, x, y); // 提醒框
效果如下:
那么接下来开始绘制右边的功能区了。
功能区包括一个显示棋盘上某点的攻击和防守的总价值的文本区。新游戏按钮,人人对战和人机对战模式。使用的计算棋盘上某点的总价值的方法。悔棋按钮,显示落棋顺序,人类先手和电脑先手按钮。
- 使用一个JPanel(rightPanel),使用BoxLayout布局,BoxLayout.Y_AXIS。
- 模式-创建JPanel(panel2)。
- 人人-使用JRadioButton
- 估值函数-使用JRadioButton
- 搜索深度 new JLabel(“搜索深度:”)和new JComboBox(new Integer[] {1,3,5})
- 显示落子顺序-JCheckBox
在GoBangFrame类中创建对象:
JButton nudo; //悔棋
JButton newGame; //新游戏
JCheckBox showNumber; //显示数字
JRadioButton renren; //人人对战
JRadioButton renji; //人机对战
JRadioButton guZhi; //估值函数
JRadioButton souSuoShu; //搜索树
JPanel jPanel1;
TextArea textArea; //右侧多行文本
JRadioButton human; //人类先手
JRadioButton robot; //机器先手
JComboBox<Integer> jComboBox1; //搜索深度
JComboBox<Integer> jComboBox2; //搜索节点
在GoBangFrame类中start方法中添加:
JPanel rightPanel = new JPanel(); // 界面右侧的功能区
rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));
jPanel1 = new JPanel(new BorderLayout()); // 多行文本框
jPanel1.setBorder(new TitledBorder("在键盘上单击鼠标右键,查看各个估值"));
textArea = new TextArea();
textArea.setEditable(false);
jPanel1.add(textArea);
rightPanel.add(jPanel1);
// 模式
JPanel jPanel2 = new JPanel();
jPanel2.setBorder(new TitledBorder("模式"));
ButtonGroup bg = new ButtonGroup(); // 两个选框互斥
renren = new JRadioButton("人人对战");
renji = new JRadioButton("人机对战");
renren.setSelected(true); // 默认人人对战
bg.add(renren);
bg.add(renji);
jPanel2.add(renren);
jPanel2.add(renji);
rightPanel.add(jPanel2);
// 智能
JPanel jPanel3 = new JPanel();
jPanel3.setBorder(new TitledBorder("智能"));
ButtonGroup bg1 = new ButtonGroup(); // 互斥
guZhi = new JRadioButton("估值函数");
souSuoShu = new JRadioButton("估值函數+搜索樹");
guZhi.setSelected(true);
bg1.add(guZhi);
bg1.add(souSuoShu);
jPanel3.add(guZhi);
jPanel3.add(souSuoShu);
rightPanel.add(jPanel3);
// 搜索樹
JPanel jPanel4 = new JPanel();
jPanel4.setBorder(new TitledBorder("搜索树"));
JLabel jLabel1 = new JLabel("搜索深度:");
jComboBox1 = new JComboBox<Integer>(new Integer[] { 1, 2, 3 });
JLabel jLabel2 = new JLabel("每层节点:");
jComboBox2 = new JComboBox<Integer>(new Integer[] { 3, 5, 10 });
jPanel4.add(jLabel1);
jPanel4.add(jComboBox1);
jPanel4.add(jLabel2);
jPanel4.add(jComboBox2);
rightPanel.add(jPanel4);
// 其他
JPanel jPanel5 = new JPanel();
nudo = new JButton("悔棋");
jPanel5.setBorder(new TitledBorder("其他"));
JLabel jLabel3 = new JLabel("显示落子顺序:");
showNumber = new JCheckBox();
jPanel5.add(jLabel3);
jPanel5.add(showNumber);
jPanel5.add(nudo);
rightPanel.add(jPanel5);
newGame = new JButton("新游戲");
rightPanel.add(newGame);
this.add(rightPanel);
// 人机模式
JPanel jPanel6 = new JPanel();
jPanel6.setBorder(new TitledBorder("人机模式"));
ButtonGroup bg2 = new ButtonGroup();
human = new JRadioButton("人类先手");
robot = new JRadioButton("机器先手");
robot.setSelected(true);
bg2.add(human);
bg2.add(robot);
jPanel6.add(human);
jPanel6.add(robot);
rightPanel.add(jPanel6);
结果如下:
ok,接下来再来绘制颗棋子。
棋子的规格是半径为单元格宽度的3/4的圆棋子。注意要在像素坐标的基础上,向左上方偏移棋子宽度的一半,目的是让棋子落到线相交点的中间。
棋子分成两种,一种黑色,一种白色。
此处使用1代表白棋,2代表黑棋。
再创建一个ChessBean类,用以储存棋子信息。其中包括
- 棋子的坐标
- 棋子的顺序(棋子是第几步下的)
- 棋子的颜色
public class ChessBean {
private int x; // 棋子横坐标
private int y;// 棋子纵坐标
private int player; //棋子颜色,1是黑,二是白,0是无棋子
private int orderNumber; // 记录棋子是第几部的
public ChessBean(int x, int y, int player, int orderNumber ){
super();
this.x = x;
this.y = y;
this.orderNumber = orderNumber;
this.player = player;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getPlayer() {
return player;
}
public void setPlayer(int player) {
this.player = player;
}
public int getOrderNumber() {
return orderNumber;
}
public void setOrderNumber(int orderNumber) {
this.orderNumber = orderNumber;
}
}
先在GoBangUtil类中添加表示棋子颜色的常量
public static final int EMPUTY = 0; //没有棋子
public static final int BLACK = 1; //白棋
public static final int WHILE = 2; //黑棋
在GoBangPanel类中添加以下成员变量,用来记录棋子的信息。
// 当前下棋,一为黑二为白,开始为黑
private int currentPlayer = GoBangUtil.BLACK; 开始为黑子
private final int CENTUEN = GoBangUtil.LINE_COUNT / 2; // 棋盘中心坐标
private ChessBean[][] chessBeans = new ChessBean[GoBangUtil.LINE_COUNT][GoBangUtil.LINE_COUNT];
private int count = 0;//表示棋子的顺序
在GoBangPanel类的构造方法中添加初始化chessBeans,还有鼠标监听器
this.addMouseListener(mouseListener); //
for (int i = 0; i < GoBangUtil.LINE_COUNT; i++) { // 初始化
for (int j = 0; j < GoBangUtil.LINE_COUNT; j++) {
ChessBean chessBean = new ChessBean(i, j, GoBangUtil.EMPUTY, 0);
chessBeans[i][j] = chessBean;
}
}
在GoBangPanel类中创建绘制棋子的方法drawChess。
- 下棋前应该判断该点是否有棋子
- 下棋应该黑白棋交替进行,本实验采用currentPlayer记录这一步棋子的颜色,而3-currentPlayer为下一步棋子的颜色。
- 在最后下的一颗棋子中间加一个红色的小正方形。小正方形的边长为8,即为GoBangUtil.CELL_WIDTH / 5
- 以上还没涉及到左边的功能区
// 绘制棋子
private void drawChess(Graphics2D g2d) {
int width = GoBangUtil.OFFSET / 4 * 3; // 棋子的宽度
for (ChessBean[] chessBeans2 : chessBeans) {
for (ChessBean chessBean : chessBeans2) {
if (chessBean.getPlayer() != 0) {
if (chessBean.getPlayer() == GoBangUtil.BLACK) { // 黑棋玩家
g2d.setColor(Color.BLACK);
} else if (chessBean.getPlayer() == GoBangUtil.WHILE) { // 白棋玩家
g2d.setColor(Color.WHITE);
}
int a = chessBean.getX() * GoBangUtil.CELL_WIDTH + GoBangUtil.OFFSET;
int b = chessBean.getY() * GoBangUtil.CELL_WIDTH + GoBangUtil.OFFSET;
g2d.fillOval(a - width / 2, b - width / 2, width, width);
}
}
}
g2d.setColor(Color.RED); // 最后一个棋子中间有小红色矩阵
int a = getMaxNum().getX() * GoBangUtil.CELL_WIDTH + GoBangUtil.OFFSET - GoBangUtil.CELL_WIDTH / 10;
int b = getMaxNum().getY() * GoBangUtil.CELL_WIDTH + GoBangUtil.OFFSET - GoBangUtil.CELL_WIDTH / 10;
g2d.fillRect(a, b, GoBangUtil.CELL_WIDTH / 5, GoBangUtil.CELL_WIDTH / 5);
g2d.setColor(Color.black);
}
// 获得最后下的一刻棋子
private ChessBean getMaxNum() {
ChessBean tempBean = null;
for (ChessBean[] chssBean2 : chessBeans) {
for (ChessBean chssBean : chssBean2) {
if (chessBeans != null && chssBean.getOrderNumber() == count) {
return chssBean;
}
}
}
return tempBean;
}
private MouseListener mouseListener = new MouseAdapter() {
public void mouseClicked(MouseEvent e) { //鼠标点击事件
int cx = e.getX(); // 获取鼠标当前坐标
int cy = e.getY();
int a = (cx - GoBangUtil.OFFSET / 2) / GoBangUtil.CELL_WIDTH; // 将座标的转换成棋盘内线的交点
int b = (cy - GoBangUtil.OFFSET / 2) / GoBangUtil.CELL_WIDTH;
if (a >= 0 && a < GoBangUtil.LINE_COUNT && b >= 0 && b < GoBangUtil.LINE_COUNT) { // 判断是否在棋盘内
if (chessBeans[a][b].getPlayer() == 0) { // 判断该点是否有棋
count++;
ChessBean chessBean = new ChessBean(a, b, currentPlayer, count);
currentPlayer = 3 - currentPlayer; // 黑棋下一步为白旗
chessBeans[a][b] = chessBean; // 储存当前玩家棋子信息
if (chessBeans[a][b].getPlayer() != 0)
System.out.println(chessBeans[x][y].getPlayer() == GoBangUtil.BLACK ? "黑子下棋完毕" : "白子下棋完毕");
repaint();
}
}
}
};
然后在paintComponent方法中调用drawChess方法:drawChess(g2d);// 绘制棋子
结果如下:
接下来的是判断是否出现五子连线的情况,即判断输赢。
每走一步,都以该棋子为原点,往八个方向出发,检查是否出现有五子相连
// 检查是否有五子相连
protected boolean checkWin(int x2, int y2, int player) {
boolean win = false;
if (check(x2, y2, 1, 0, player) + check(x2, y2, -1, 0, player) >= 4) {// 检查横是否有五子相连
win = true;
} else if (check(x2, y2, 0, 1, player) + check(x2, y2, 0, -1, player) >= 4) {// 检查竖是否有五子相连
win = true;
} else if (check(x2, y2, 1, 1, player) + check(x2, y2, -1, -1, player) >= 4) {// 检查横是否有五子相连
win = true;
} else if (check(x2, y2, -1, 1, player) + check(x2, y2, 1, -1, player) >= 4) {// 检查横是否有五子相连
win = true;
}
if (win) {
JOptionPane.showMessageDialog(GoBangPanel.this, "游戏已经结束");
return true;
}
return false;
}
private int check(int x2, int y2, int i, int j, int player) {
// 向某个方向(i, j)检查4个棋子。
int sum = 0;
for (int k = 0; k < 4; k++) {
x2 += i;
y2 += j;
if (x2 >= 0 && x2 < GoBangUtil.LINE_COUNT && y2 >= 0 && y2 < GoBangUtil.LINE_COUNT) {
if (chessBeans[x2][y2].getPlayer() == player) {
sum++;
} else {
break;
}
}
}
return sum;
}
在public void mouseClicked(MouseEvent e) 方法中,下完一颗棋子,就判断一次,因此在repaint();后面添加
checkWin(a, b, chessBeans[a][b].getPlayer());
效果如下:
人难免会出错,就像上图,那肯定要处理不小心下错的棋啦,那就是要悔棋。
添加一个鼠标点击事件。
把chessBeans二维数组中,orderNum最大的那个棋子的player设置为EMPTY,把orderNum设置为0。把count计数器减一。把currentPlayer设置为被删除的这个点的player
先在GoBangPanel类中创建悔棋方法
//悔棋
public void huiQi() {
// TODO 自动生成的方法存根
if (count > 0) {
ChessBean tempBean = getMaxNum();
currentPlayer = tempBean.getPlayer();
chessBeans[tempBean.getX()][tempBean.getY()].setPlayer(GoBangUtil.EMPUTY); //
chessBeans[tempBean.getX()][tempBean.getY()].setOrderNumber(0);
count--;
repaint();
} else {
JOptionPane.showMessageDialog(GoBangPanel.this, "已经无棋子可悔了!");
}
}
在GoBangFrame类的start方法中添加nudo.addMouseListener(mouseListener); // 添加事件
然后在GoBangFrame类中创建MouseListener的实例,在mouseClicked判断是否点击悔棋按钮,然后再调用huiqi方法
private MouseListener mouseListener = new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
// TODO 自动生成的方法存根
Object object = e.getSource();
if (object == nudo) {
if (renren.isSelected()) {
goBangPanel.huiQi();
}
}
}
@Override
public void mousePressed(MouseEvent e) {
// TODO 自动生成的方法存根
}
@Override
public void mouseReleased(MouseEvent e) {
// TODO 自动生成的方法存根
}
@Override
public void mouseEntered(MouseEvent e) {
// TODO 自动生成的方法存根
}
@Override
public void mouseExited(MouseEvent e) {
// TODO 自动生成的方法存根
}
};
接下来就是显示棋子的顺序。
- 在棋子上中间用红色的数字显示棋子的步数。
-------------------------------------分隔符---------------------------------
本来想着按照实现的过程然后把代码都贴上来的,但是由于各种原因,只能将这先放一放。所以先把所有的源码都放上来把。
qq: 758168660@qq.com
ps这五子棋最基本的设计理念来自GitHub,侵删