上次的代码已经能够让我们画出一张五子棋的棋盘界面了,只是它还不能用来下棋。这一次的文章我们将让这个五子棋盘真正的可以用来下棋。
一、做之前的思考
1. 首先要弄明白的是,所谓的下棋,其实质是在GoBang类的对象gb的棋子数组isArrive[ ][ ]存储一些值,将0变为1(黑棋)或2(白棋),在根据1或者2让棋盘绘制出棋子在棋盘面板上。
2.再者,既然棋盘上有三个按钮和一个下拉菜单,就自然地想到要添加3+1个动作监听。ActionListener。用
if(e.getActionCommand().equals("xxx"))来应对不同的情况。不过,JComboBox的和JButton有点不同,需要如下操作
JComboBox<String> box = (JComboBox<String>) e.getSource();// 获取事件源对象
type = box.getSelectedItem().toString(); // 获取选择的对战模式
type = box.getSelectedItem().toString(); // 获取选择的对战模式
再用这type去做if判断。
3.下棋的动作是用鼠标在棋盘面板上点击才发生的,所以应该为其添加鼠标监听。并通过点击来获取该位置的横纵坐标x,y,计算出isArrive[][]的countx,county,才能将数据填入其中;再通过countx,county计算出arrivex, arrivey,也就是在棋盘上应该绘制棋子的位置。
4 .对于ActionListener应该想到:
当我们点击了“开始新游戏”,棋盘才能下棋,否则是不能的。也就是在发生了这个动作后才为棋盘面板添加鼠标监听。而且,所有的原来的棋子都应该清空,(棋子数组清零),棋盘清空(重绘),而且最好能让“人人对战”、“人机对战”的模式选择了就锁定(毕竟不可能一盘棋里下到一半突然换人),即将box锁定。
当我们点击了“悔棋”,我们希望做的是将上一步棋去掉,而且应该让棋子的颜色变回前一种颜色。为了记录每一步棋子的位置,很自然地想到用ArrayList<chess>list来储存,chess类是定义为了便于记录的。此时的具体操作在下面叙述。
当我们点击了”认输“,只需要根据此时轮到谁下棋来判断谁输谁赢就行了。同时认输后让box解除封印。
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("开始新游戏")) {
gf.addMouseListener(this);
for (int i = 0; i < gf.isArrive.length; i++)
for (int j = 0; j < gf.isArrive[i].length; j++)
gf.isArrive[i][j] = 0; // 初始化存储棋子的数组使其恢复到初始状态
box.setEnabled(false); // 让下拉可选框锁定
gf.repaint();
}
else if (e.getActionCommand().equals("悔棋")) {
if (list.size() > 0) {
// 从list列表中获取最后一颗棋子的位置
chess lastchess = list.remove(list.size() - 1); //得到上一步棋的位置
gf.isArrive[lastchess.r][lastchess.c] = 0; //让该位置的数组置零
if (turn == 1)
turn++;
else
turn--;
gf.repaint();
}
}
else if (e.getActionCommand().equals("认输")) {
if (turn == 1)
JOptionPane.showMessageDialog(gf, "黑棋认输,白棋获胜!");
else
JOptionPane.showMessageDialog(gf, "白棋认输,黑棋获胜!");
gf.removeMouseListener(this); //移除监听 ( 按道理来说不应该在分出胜负以后棋盘上还能落子,所以应该移除监听
box.setEnabled(true);
} else if (e.getSource() instanceof JComboBox) {
JComboBox<String> box = (JComboBox<String>) e.getSource();// 获取事件源对象
type = box.getSelectedItem().toString(); // 获取选择的对战模式
}
}
5.对于MouseListener应该想到:
在点击时获取坐标,然后根据box的文字来用if区分调用“人人对战”、“人机对战”的方法
public void mouseClicked(MouseEvent e) {
//取得横纵坐标int x = e.getX();
int y = e.getY();
if (type.equals("人人对战"))
{this.PERSONPLAY(x, y);}
else if (type.equals("人机对战"))
{this.AIPLAY(x, y);}
二、人人对战
人人对战的思路我不多加赘述了,用代码来边写别解释吧
public void PERSONPLAY(int x, int y) {
// 人下的方法
// 计算棋子要落的交叉点
int countx = (y - 20 + size / 2) / size;
int county = (x - 20 + size / 2) / size;
g = (Graphics2D) gf.getGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿,让棋子更加圆润
int arrivex, arrivey; // 棋盘上的落点
arrivex = 20 + county * size;
arrivey = 20 + countx * size;
if (gf.isArrive[countx][county] != 0) // 有棋子,不可下棋
{
JOptionPane.showMessageDialog(gf, "此处已有棋子,请换一个地方");
} else {// 当前位置可以下棋
if (turn == 1) {
// 设置颜色
g.setColor(Color.black);
// 下棋
g.fillOval(arrivex - size / 2, arrivey - size / 2, size, size);
gf.isArrive[countx][county] = 1;
turn++;
} else {
// 设置颜色
g.setColor(Color.white);
// 下棋
g.fillOval(arrivex - size / 2, arrivey - size / 2, size, size);
gf.isArrive[countx][county] = 2;
turn--;
}
//
list.add(new chess(countx, county));// 有序地存储每个棋子的行列,为悔棋做准备
// 判断输赢
if (Gobangwin.judge(gf.isArrive, countx, county)) {
if (turn == 2)
JOptionPane.showMessageDialog(gf, "黑棋胜利");
else
JOptionPane.showMessageDialog(gf, "白棋胜利");
gf.removeMouseListener(this); //移除监听 ( 按道理来说不应该在分出胜负以后棋盘上还能落子,所以应该移除监听
}
}
}
上面代码中缺少判断输赢的算法,我建了一个Gobangwin类来进行操作。在这个类里面就定义判断输赢的算法:
从水平,竖直,左斜,右斜的方向来分别判断是否五子相连。这个类比较简单,不多赘述
package GoBang;
public class Gobangwin {
public static boolean judge(int[][]isArrive, int r, int c){ //如果五子相连就返回true,否则就返回false
if(countx(isArrive,r,c)>=5||county(isArrive,r,c)>=5||countxy1(isArrive,r,c)>=5||countxy2(isArrive,r,c)>=5)
return true;
else return false;
}
//计算竖直方向是否五子相连
private static int countx(int[][] isArrive, int r, int c) {
int count = 1;
for (int r1 = r - 1; r1 >= 0; r1--)
if (isArrive[r][c] == isArrive[r1][c])
count++;
else
break;
for (int r1 = r + 1; r1 < isArrive.length; r1++)
if (isArrive[r][c] == isArrive[r1][c])
count++;
else
break;
return count;
}
//计算水平方向是否五子相连
private static int county(int[][] isArrive, int r, int c) {
int count = 1;
for (int c1 = c - 1; c1 >= 0; c1--)
if (isArrive[r][c] == isArrive[r][c1])
count++;
else
break;
for (int c1 = c + 1; c1 < isArrive[r].length; c1++)
if (isArrive[r][c] == isArrive[r][c1])
count++;
else
break;
return count;
}
//计算左上至右下斜的方向是否五子相连
private static int countxy1(int[][] isArrive, int r, int c) {
int count = 1; //往左上角走
for (int r1 = r - 1,c1=c-1; r1 >= 0&&c1>=0; r1--,c1--)
if (isArrive[r][c] == isArrive[r1][c1])
count++;
else
break;
//往右下角走
for (int r1 = r + 1,c1=c+1; r1 < isArrive.length&&c1<isArrive[r].length; r1++,c1++)
if (isArrive[r][c] == isArrive[r1][c1])
count++;
else
break;
return count;
}
//计算右上至左下斜的方向是否五子相连
private static int countxy2(int[][] isArrive, int r, int c) {
int count = 1; //往右上角走
for (int r1 = r - 1,c1=c+1; r1 >= 0&&c1<isArrive[r].length; r1--,c1++)
if (isArrive[r][c] == isArrive[r1][c1])
count++;
else
break;
//往左下角走
for (int r1 = r + 1,c1=c-1; r1 < isArrive.length&&c1>=0; r1++,c1--)
if (isArrive[r][c] == isArrive[r1][c1])
count++;
else
break;
return count;
}
}
上面的代码思路很简单,需要注意的是左斜右斜时r,c的变化是往下r增大,往右是c在增大。(这里有点不同,也可能只是我一个人觉得。。)
tip1.为了方便时刻改变棋子的大小和行列,我们把size和row,column封装在一个接口然后去继承它。
package GoBang;
public interface GoBangconfig {
public static int size=30,column=15,row=15,x=20,y=20;
}
tip2.这个程序还是蛮大的,建议写一部分就调试一部分,确认没错时再继续往下写。(血和泪的教训)
至此。这个棋盘已经可以用来和朋友下棋了。
下一部分将实现人机对战,也就是AI算法