五子棋项目总结
之前一直没有发总结,总是感觉人机做的还不是太成熟,后来培训结束后又花了一些时间重写了AI的方法,还加上了预测。感觉比以前是要强一些,至少不会出现所谓的BUG,但是自我感觉也不算真的很NB,这里就与大家简单分享一下吧。
功能需求:
1、实现人人五子棋对战
2、实现人机五子棋对战
3、实现重来
4、实现悔棋按钮
5、实现进度的保存与读取
下面我们来一一分析:
1、人人对战是最基本的功能,在做这个之前,我们先做了一个界面:
要注意的一点就是类继承JPanel,然后重写paint方法,这样才会使我们下过的棋子不会随着窗体的移动以及最大化最小化而消失。主界面类:
public class ChessTable extends JPanel implements Config {
public static void main(String[] args) {
ChessTable ct = new ChessTable();
ct.initUI();
}
private String mode = "war";// 默认是人人对战
/**
* 得到游戏模式的方法
*
* @return 游戏模式
*/
public String getMode() {
return mode;
}
/**
* 设置游戏模式
*
* @param mode游戏模式
*/
public void setMode(String mode) {
this.mode = mode;
}
/**
* 初始化界面
*/
private void initUI() {
final JFrame jf = new JFrame("五子棋"); // 创建一个窗体
jf.setSize(new Dimension(630, 600)); // 改变窗体的大小
jf.setDefaultCloseOperation(3); // 设置窗体的关闭
jf.setLocationRelativeTo(null); // 设置窗体局中
jf.setResizable(false); // 设置窗体不可调节大小
JMenuBar jmb = new JMenuBar();// 添加菜单栏
JMenu jm = new JMenu("File");// 新建菜单对象
ButtonListener bl = new ButtonListener(this);
FileOperation fo = new FileOperation(this);// 新建一个监听器监听菜单
JMenuItem save = new JMenuItem("Save");// 新建保存菜单项
save.addActionListener(fo);// 添加到按钮监听器
JMenuItem open = new JMenuItem("Open");// 新建打开菜单项
open.addActionListener(fo);// 添加到按钮监听器
JMenuItem exit = new JMenuItem("Exit");// 新建关闭菜单项
exit.addActionListener(fo);// 添加到按钮监听器
jm.add(open);// 将打开菜单项添加到菜单
jm.add(save);// 将保存菜单项添加到菜单
jm.add(exit);// 将关闭菜单项添加到菜单
jmb.add(jm);// 将菜单添加到菜单栏
jf.add(jmb, BorderLayout.NORTH);// 将菜单栏添加到北部面板
state[0] = true;
JPanel eastPanel = new JPanel(); // 创建东边面板
eastPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 50)); // 设置东边面板流式布局
eastPanel.setPreferredSize(new Dimension(80, 0)); // 设置东边面板的宽度
JButton jbuAI = new JButton("人机"); // 添加人机按钮
jbuAI.setPreferredSize(new Dimension(78, 30)); // 设置按钮大小
jbuAI.addActionListener(bl);
JButton jbuwar = new JButton("人人"); // 设置人人按钮
jbuwar.setPreferredSize(new Dimension(78, 30)); // 设置按钮大小
jbuwar.addActionListener(bl);
JButton jbuwithdraw = new JButton("悔棋");// 设置悔棋按钮
jbuwithdraw.setPreferredSize(new Dimension(78, 30));// 设置按钮大小
jbuwithdraw.addActionListener(bl);
JButton jburestart = new JButton("重来"); // 设置重来按钮
jburestart.setPreferredSize(new Dimension(78, 30)); // 设置按钮大小
eastPanel.add(jbuAI); // 将按钮添加到面板
eastPanel.add(jbuwar); // 将按钮添加到面板
eastPanel.add(jbuwithdraw);// 将按钮添加到面板
eastPanel.add(jburestart); // 将按钮添加到面板
eastPanel.setOpaque(false); // 设置面板透明
this.setOpaque(false); // 设置中间面板透明
ImageIcon gobangimage = new ImageIcon("gobangbackground.jpg");// 实例化一个图片对象
JLabel jla = new JLabel(gobangimage);// 将图片添加到标签上
jla.setBounds(0, 0, gobangimage.getIconWidth(),
gobangimage.getIconHeight());// 设置图片的绝对位置
jf.getLayeredPane().add(jla, new Integer(Integer.MIN_VALUE));// 将图片添加到下层面板
JPanel contentPane = (JPanel) jf.getContentPane();// 实例化上层面板
contentPane.setOpaque(false);// 设置上层面板透明
jf.add(eastPanel, BorderLayout.EAST); // 将东边面板添加到窗体
jf.add(this, BorderLayout.CENTER); // 将中间面板添加到窗体
jf.setVisible(true);// 设置窗体可见
Graphics g = this.getGraphics();// 创建图形对象
final ChessListener cl = new ChessListener(g, this);// 创建监听器对象
this.addMouseListener(cl);// 将监听器添加到棋盘面板上
jburestart.addActionListener(bl);
}
// 重写paint方法
public void paint(Graphics g) {
super.paint(g); // 调用paint方法
drawTable(g); // 画棋盘
repaint(g); // 重绘棋子
}
/**
* 画棋盘,分别画横线和竖线
*
* @param g图形对象
*/
public void drawTable(Graphics g) {
for (int i = 0; i < Config.NUM; i++) {
g.drawLine(Config.X + i * Config.SIZE, Config.Y, Config.X + i
* Config.SIZE, Config.Y + Config.SIZE * (Config.NUM - 1));
g.drawLine(Config.X, Config.Y + i * Config.SIZE, Config.X
+ Config.SIZE * (Config.NUM - 1), Config.Y + i
* Config.SIZE);
}
}
/**
* 重绘棋子,将数组中存储的棋子重绘
*
* @param g图形对象
*/
private void repaint(Graphics g) {
for (int i = 0; i < Config.NUM; i++) {
for (int j = 0; j < Config.NUM; j++) {
if (array[i][j] == 1) {
g.setColor(Color.BLACK);
g.fillOval(Config.X + i * Config.SIZE - Config.CHESS_SIZE,
Config.Y + j * Config.SIZE - Config.CHESS_SIZE,
2 * Config.CHESS_SIZE, 2 * Config.CHESS_SIZE);
}
if (array[i][j] == -1) {
g.setColor(Color.WHITE);
g.fillOval(Config.X + i * Config.SIZE - Config.CHESS_SIZE,
Config.Y + j * Config.SIZE - Config.CHESS_SIZE,
2 * Config.CHESS_SIZE, 2 * Config.CHESS_SIZE);
}
}
}
}
}
然后我们做一个常量接口,相当于一个全局变量,我们只要在类中实现这个接口,就可以调用该全局变量。
public interface Config {
int X = 20;//距离左上角的距离
int Y = 20;//距离左上角的距离
int NUM = 15;//存储棋盘格子数
int SIZE = 36;//存储棋盘小方格大小
int CHESS_SIZE = 17;//存储棋子大小
int[][] array = new int[NUM][NUM];//棋盘矩阵
int[][] arraytemp = new int[NUM][NUM];//临时矩阵用作预测
GobangList xlist = new GobangList();//存储走的步子
GobangList ylist = new GobangList();//存储走的步子
boolean[] state = new boolean[1];//状态全局变量
int[][][] whitefound = new int[NUM][NUM][4];// 找上下左右左斜和右斜的矩阵
int[][][] blackfound = new int[NUM][NUM][4];// 找上下左右左斜和右斜的矩阵
/**
* 0表示横着的;1表示竖着的;2表示左斜,即从左下到右上;3表示右斜,即从左上到右下
*/
}
下面我们来创建按钮监听器,里面包含人人,人机,悔棋,重来四个按钮。
人人和人机就是改变主类中的mode。
重来就要将棋盘矩阵归零,然后刷新一下棋盘,这些不多说。
悔棋我们这里使用队列,因为队列长度会随着你添加的数据个数的改变而改变,那么我们就定义两个队列分别存储x和y。
/**
* 定义自定义的对象实现类,实现接口List
*
* @author TTH
*
* @param <E>数据类型
*/
public class GobangList {
// 初始化数组大小
private int size = 0;
// 定义数组对象
private int[] array;
/**
* 构造方法
*/
public GobangList() {
array = new int[0];
}
/**
* 构造方法
*
* @param length数组长度
*/
public GobangList(int length) {
array = new int[length];
}
/**
* 将指定的元素添加到此列表的尾部
*/
public void add(int e) {
// 建立临时的数组,长度为原数组加一
int[] arraytemp = new int[array.length + 1];
// 对数组进行循环
for (int i = 0; i < array.length; i++) {
// 将原数组的值赋给临时数组
arraytemp[i] = array[i];
}
// 把新增的值赋给临时数组
arraytemp[array.length] = e;
// 将临时数组的地址给原数组
array = arraytemp;
// 数组大小加一
size++;
}
/**
* 将指定的元素插入此列表中的指定位置
*/
public void inner(int index, int e) {
// 定义临时数组,长度为原数组加一
int[] arraytemp = new int[array.length + 1];
// 对要插入位置前进行循环
for (int i = 0; i < index; i++) {
// 将要插入位置前的数据给临时数组
arraytemp[i] = array[i];
}
// 将要插入的数据赋给临时数组
arraytemp[index] = e;
// 对插入位置后面进行循环
for (int i = index; i < array.length; i++) {
// 将要插入后的数据给临时数组
arraytemp[i + 1] = array[i];
}
// 将临时数组的地址给原数组
array = arraytemp;
// 数组大小加一
size++;
}
/**
* 返回此列表中指定位置上的元素
*
* @param index指定的位置
*/
public int get(int index) {
// 判断指定位置是否不在数组范围内
if (index < 0 || index > size)
// 如果不在范围,返回空
return 0;
// 否则返回该位置的数据类型
return array[index];
}
/**
* 用指定的元素替代此列表中指定位置上的元素。
*/
public void set(int index, int e) {
array[index] = e;
}
/**
* 移除此列表中指定位置上的元素
*/
public void remove(int index) {
int[] arraytemp = new int[size - 1];
for (int i = 0; i < index; i++) {
arraytemp[i] = array[i];
}
for (int i = index; i < array.length - 1; i++) {
arraytemp[i] = array[i + 1];
}
array = arraytemp;
// 数组大小减一
size--;
}
/**
* 返回此列表中的元素数
*/
public int size() {
return size;
}
/**
* 清空队列
*/
public void clear() {
// 长度为0
size = 0;
// 队列重置
array = new int[0];
}
}
每下一步棋子,就将x和y存储进去,当时人人下棋的时候,我们每次悔一步棋,当是人机下棋的时候,我们每次悔两步棋,就是在队列的末尾删除一个或者两个就可以了。
public class ButtonListener implements ActionListener, Config {
ChessTable ct;
public ButtonListener(ChessTable ct) {
this.ct = ct;
}
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("人机")) {
ct.setMode("AI");
}
else if (e.getActionCommand().equals("人人")) {
ct.setMode("war");
}
else if (e.getActionCommand().equals("悔棋")) {
if (ct.getMode().equals("war")) {
if (xlist.size() >= 1 && ylist.size() >= 1) // 如果长度不为0
{
array[xlist.get(xlist.size() - 1)][ylist
.get(ylist.size() - 1)] = 0;// 把当前棋子设为0
xlist.remove(xlist.size() - 1);// 移除最后一个x坐标
ylist.remove(ylist.size() - 1);// 移除最后一个y坐标
}
} else if (ct.getMode().equals("AI")) {
if (xlist.size() >= 2 && ylist.size() >= 2) // 如果长度不为0
{
for (int i = 0; i < 2; i++) // 移除两张牌
{
array[xlist.get(xlist.size() - 1)][ylist.get(ylist
.size() - 1)] = 0;// 把当前棋子设为0
xlist.remove(xlist.size() - 1);// 移除最后一个x坐标
ylist.remove(ylist.size() - 1);// 移除最后一个y坐标
}
}
}
ct.repaint();// 重绘窗体
}
else if (e.getActionCommand().equals("重来")) {
for (int i = 0; i < Config.NUM; i++) {
for (int j = 0; j < Config.NUM; j++)
array[i][j] = 0;// 通过循环初始化数组
}
ct.repaint();// 调用repaint方法重绘
state[0] = true;// 设置state状态为可以下棋
}
}
}
下面就是棋盘监听器,总体思路是这样,我们鼠标在棋盘上点一个点,我们要进行循环查找这个点在棋盘的什么位置,如果离某个交叉点的距离小于一个我们给定的值,那么我们就在这个点下子,然后我们会判断是该白子下还是黑子下,这里我是通过对棋盘进行遍历,得出矩阵的和,如果是0,那么是黑子下,如果是1,那么是白子下。最后就是判断每当你下一个棋子的时候,是否胜利,我们只需要在你下的这个子的横竖左右查找是否有五个的情况就可以了。
那么首先我们要建立一个数棋子的类:
public class Count implements Config {
/**
* 在行上数棋子
*
* @param x输入点的横坐标
* @param y输入点的纵坐标
* @return 行上左右棋子的个数
*/
public int countrow(int x, int y) {
int count = 1;
for (int i = x + 1; i < Config.NUM; i++) {
if (array[i][y] == array[x][y]) {
count++;
} else {
break;
}
}
for (int i = x - 1; i >= 0; i--) {
if (array[i][y] == array[x][y]) {
count++;
} else {
break;
}
}
return count;
}
/**
* 在列上数棋子
*
* @param x输入点的横坐标
* @param y输入点的纵坐标
* @return 列上左右棋子的个数
*/
public int countcolumn(int x, int y) {
int count = 1;
for (int j = y + 1; j < Config.NUM; j++) {
if (array[x][j] == array[x][y]) {
count++;
} else {
break;
}
}
for (int j = y - 1; j >= 0; j--) {
if (array[x][j] == array[x][y]) {
count++;
} else {
break;
}
}
return count;
}
/**
* 在左斜上数棋子
*
* @param x输入点的横坐标
* @param y输入点的纵坐标
* @return 左斜棋子的个数
*/
public int countleftlean(int x, int y) {
int count = 1;
// 左斜下遍历
for (int k = 1; k < Math.min(Math.abs(Config.NUM - x),
Math.abs(Config.NUM - y)); k++) {
int i = x + k;
int j = y + k;
if (array[i][j] == array[x][y]) {
count++;
} else {
break;
}
}
// 左斜上遍历
for (int k = 1; k < Math.min(x, y); k++) {
int i = x - k;
int j = y - k;
if (array[i][j] == array[x][y]) {
count++;
} else {
break;
}
}
return count;
}
/**
* 右斜数棋子
*
* @param x输入点的横坐标
* @param y输入点的纵坐标
* @return 右斜棋子的个数
*/
public int countrightlean(int x, int y) {
int count = 1;
// 右斜下遍历
for (int k = 1; k < Math.min(x, Math.abs(Config.NUM - y)); k++) {
int i = x - k;
int j = y + k;
if (array[i][j] == array[x][y]) {
count++;
} else {
break;
}
}
// 右斜上遍历
for (int k = 1; k < Math.min(Math.abs(Config.NUM - x), y); k++) {
int i = x + k;
int j = y - k;
if (array[i][j] == array[x][y]) {
count++;
} else {
break;
}
}
return count;
}
}
然后就是监听器:
public class ChessListener extends MouseAdapter implements Config {
private Graphics g;// 图形对象
private int x, y;// 下棋点的横纵坐标
private ChessTable ct;// 棋盘对象
private Count c = new Count();// 数子对象
/**
* 构造方法
*
* @param g图形对象
* @param ct面板对象
*/
public ChessListener(Graphics g, ChessTable ct) {
this.g = g;
this.ct = ct;
}
/**
* 构造方法
*
* @param g图形对象
*/
public ChessListener(Graphics g) {
this.g = g;
}
// 构造方法
public ChessListener() {
}
/**
* 鼠标按下事件
*/
public void mousePressed(MouseEvent e) {
x = e.getX();// 得到x点的坐标
y = e.getY();// 得到y点的坐标
if (state[0]) {// 如果在可以下棋的状态,进行循环
for (int i = 0; i < Config.NUM; i++) {
for (int j = 0; j < Config.NUM; j++) {
int tx = Config.X + i * Config.SIZE;// tx为循环到棋盘第i行的坐标
int ty = Config.Y + j * Config.SIZE;// ty为循环到棋盘第j列的坐标
if (Math.abs(x - tx) < Config.CHESS_SIZE
&& Math.abs(y - ty) < Config.CHESS_SIZE
&& array[i][j] == 0) {// 如果离循环到的该点距离小于棋子半径
Config.xlist.add(i);
Config.ylist.add(j);
int sum = 0;// 初始化计数器
for (int m = 0; m < Config.NUM; m++) {
for (int n = 0; n < Config.NUM; n++) {
sum += array[m][n];// 将棋盘数组每一个数相加
}
}
if (sum == 0) {// 如果总数为0,该黑棋走
g.setColor(Color.BLACK);// 设置黑色
array[i][j] = 1;// 在该点设置1
} else {// 否则,该白棋走
g.setColor(Color.WHITE);// 设置白色
array[i][j] = -1;// 在该点设置-1
}
g.fillOval(tx - Config.CHESS_SIZE, ty
- Config.CHESS_SIZE, 2 * Config.CHESS_SIZE,
2 * Config.CHESS_SIZE);// 根据上面设出的颜色画出棋子
if (ChessJudge(i, j))// 判断是否胜利
if (sum == 0) {// 如果是黑棋走
JOptionPane.showMessageDialog(null,
"black win!", "Game Over", 1);// 黑棋胜利
} else {// 如果是白棋走
JOptionPane.showMessageDialog(null,
"white win!", "Game Over", 1);// 白棋胜利
}
if (ct.getMode().equals("AI") && state[0]) {// 如果设置的是电脑下棋
ChessAI cai = new ChessAI(g);// 创建电脑AI对象
cai.underpawn();// 电脑下棋
}
if (ct.getMode().equals("war"))
;// 如果是人人对战,不执行命令
}
}
}
}
}
/**
* 判断输赢
*
* @param x该点的横坐标
* @param y该点的纵坐标
* @return 是否胜利
*/
public boolean ChessJudge(int x, int y) {
if (c.countrow(x, y) >= 5 || c.countcolumn(x, y) >= 5
|| c.countleftlean(x, y) >= 5 || c.countrightlean(x, y) >= 5) {// 如果超过5个
state[0] = false;// 设置状态为不可下
return true;// 返回游戏结束
}
return false;// 否则返回没有结束
}
}
2、这样,我们做的五子棋就可以实现人人对战了,那么我们接下来就要实现人机对战!
要实现让电脑下子,我们就要用计算机的思维来确定如何让电脑下子,如何让电脑更准确地选取一个点使其可以更有效地进攻和防守,我们这里采取权值的方法。遍历棋盘上每一个没有落子的点,计算其权值,这样我们就能依据权值来判定电脑应该下在哪一个点上。
那么怎么来确定每一个点的权值呢,我们这里引入了两个矩阵,分别计算每一个不为0的点,对应的横竖左右以及四个斜着的共八个方向的棋子个数,然后通过判断有几个来给出对应的权值。
比如现在轮到电脑下白棋,我们向右数白棋,如果数到一个,我们令countx加2,接着再往右,如果遇到白棋,countx再加2,直至不是白棋,但是如果被堵住了,就另countx减1,我们说被堵死为半,这样我们就可以通过数的个数对该点右方的白子进行一个判断:
-1为0半,0为0,1为1半,2为1,3为2半,4为2,5为3半,6为3,7为4半,8为4,一共就这么几种情况。
然后我们再向左数,跟向右的方法一样。这样我们就能得到水平上该空格左边有几个白棋以及右边有几个白棋,这样就可以进入综合考虑,
当左边和右边均被堵死,而且当前子下入后也不足5个的时候,这步棋就无关紧要,所以就设其权值为0;
当两边的棋子个数按上述方法统计加起来大于等于7的时候,显然这步棋可以一步绝杀,我们设其权值为10000;
当两边的棋子个数按上述方法统计加起来等于6的时候,我们设其权值为2000;
当两边的棋子个数按上述方法统计加起来大于等于4的时候,我们设其权值为300;
当两边的棋子个数按上述方法统计加起来等于3的时候,我们设其权值为50;
当两边的棋子个数按上述方法统计加起来等于2的时候,我们设其权值为10;
当两边的棋子个数按上述方法统计加起来等于1的时候,我们设其权值为1;
其余情况,我们设权值为0。
这样我们就对水平线上给出了具体的权值,同理我们要在竖直、左斜和右斜方向上进行数子,黑棋也同样数,这样就得到两个数组。我们走棋的时候遍历两个数组,得到最大值,再比较两个最大值,根据情况判断到底应该是走自己的还是堵对面的。这样,我们的简单的人机就实现了~
/**
* 数棋子的方法
*
* @author TTH
*
*/
public class AICount implements Config {
// in=1为白字,in=-1为黑子
public int countmax(int i, int j, int in) {
for (int m = 0; m < NUM; m++) {
for (int n = 0; n < NUM; n++) {
// 如果这一点没有子,调用四种方法
if (array[m][n] == 0) {
line(m, n, in);
row(m, n, in);
left(m, n, in);
right(m, n, in);
}
}
}
int sum = 0;
// 循环得到四种方法加起来的和
for (int k = 0; k < 4; k++) {
if (in == -1) {
sum += whitefound[i][j][k];
}
if (in == 1) {
sum += blackfound[i][j][k];
}
}
return sum;
}
/**
* 行
*
* @param i
* @param j
* @param in
*/
public void line(int i, int j, int in) {
int countx = 0, county = 0;
for (int k = i + 1; k < NUM; k++) {
if (array[k][j] == in) {
countx += 2;
} else if (array[k][j] == -in) {
countx -= 1;
break;
} else if (array[k][j] == 0) {
break;
}
}
for (int k = i - 1; k >= 0; k--) {
if (array[k][j] == in) {
county += 2;
} else if (array[k][j] == -in) {
county -= 1;
break;
} else if (array[k][j] == 0) {
break;
}
}
if (i == NUM - 1) {
countx -= 1;
}
if (i == 0) {
county -= 1;
}
if (in == -1)
whitefound[i][j][0] = cal(countx, county);
if (in == 1)
blackfound[i][j][0] = cal(countx, county);
}
/**
* 列
*
* @param i
* @param j
*/
public void row(int i, int j, int in) {
int countx = 0, county = 0;
for (int k = j + 1; k < NUM; k++) {
if (array[i][k] == in) {
countx += 2;
} else if (array[i][k] == -in) {
countx -= 1;
break;
} else if (array[i][k] == 0) {
break;
}
}
for (int k = j - 1; k >= 0; k--) {
if (array[i][k] == in) {
county += 2;
} else if (array[i][k] == -in) {
county -= 1;
break;
} else if (array[i][k] == 0) {
break;
}
}
if (i == NUM - 1) {
countx -= 1;
}
if (i == 0) {
county -= 1;
}
if (in == -1)
whitefound[i][j][1] = cal(countx, county);
if (in == 1)
blackfound[i][j][1] = cal(countx, county);
}
/**
* 左斜
*
* @param i
* @param j
*/
public void left(int i, int j, int in) {
int countx = 0, county = 0;
for (int k = 1; i + k < NUM && j >= k; k++) {
if (array[i + k][j - k] == in) {
countx += 2;
} else if (array[i + k][j - k] == -in) {
countx -= 1;
break;
} else if (array[i + k][j - k] == 0) {
break;
}
}
for (int k = 1; i >= k && j + k < NUM; k++) {
if (array[i - k][j + k] == in) {
county += 2;
} else if (array[i - k][j + k] == -in) {
county -= 1;
break;
} else if (array[i - k][j + k] == 0) {
break;
}
}
if (i == NUM - 1 || j == 0) {
countx -= 1;
}
if (i == 0 || j == NUM - 1) {
county -= 1;
}
if (in == -1)
whitefound[i][j][2] = cal(countx, county);
if (in == 1)
blackfound[i][j][2] = cal(countx, county);
}
/**
* 右斜
*
* @param i
* @param j
*/
public void right(int i, int j, int in) {
int countx = 0, county = 0;
for (int k = 1; i >= k && j >= k; k++) {
if (array[i - k][j - k] == in) {
countx += 2;
} else if (array[i - k][j - k] == -in) {
countx -= 1;
break;
} else if (array[i - k][j - k] == 0) {
break;
}
}
for (int k = 1; i + k < NUM && j + k < NUM; k++) {
if (array[i + k][j + k] == in) {
county += 2;
} else if (array[i + k][j + k] == -in) {
county -= 1;
break;
} else if (array[i + k][j + k] == 0) {
break;
}
}
if (i == 0 || j == 0) {
countx -= 1;
}
if (i == NUM - 1 || j == NUM - 1) {
county -= 1;
}
if (in == -1)
whitefound[i][j][3] = cal(countx, county);
if (in == 1)
blackfound[i][j][3] = cal(countx, county);
}
/**
* 计算权值的方法
*
* @param countx
* @param county
* @return 权值
*/
private int cal(int countx, int county) {
if (countx == 3 && county == 1 || countx == 1 && county == 3
|| countx == 5 && county == -1 || countx == -1 && county == 5
|| countx == -1 && county == 3 || countx == 3 && county == -1) {
return 0;
} else if (countx + county >= 7 || countx == 3 && county == 3
|| countx == 1 && county == 5 || countx == 5 && county == 1) {
return 10000;
} else if (countx + county == 6) {
return 2000;
} else if (countx + county >= 4) {
return 300;
} else if (countx + county == 3) {
return 50;
} else if (countx + county == 2) {
return 10;
} else if (countx + county == 1) {
return 1;
}
return 0;
}
}
3、简单人机实现后,我们要是想让电脑变的更加智能,就要给他加上一个预测。
当我们下了一步棋后,电脑会在全盘进行一次尝试,尝试时假如电脑下这个点,那么电脑会重新对全盘分析,并进行猜测我会下在权值最大的点,
然后电脑再分析下载他认为权值最大的点,这样依此类推预测几步。我们的目标是几步后电脑所下的点权值将会最大,所以当电脑下在这个点时,权值最大,那么我们会储存这个点作为下一步棋的基本点。
public class ChessAI implements Config {
private Graphics g;
private int putx, puty;// 电脑下棋的坐标
private int white[][] = new int[NUM][NUM];// 白棋的权值矩阵
private int black[][] = new int[NUM][NUM];// 黑棋的权值矩阵
private AICount ac = new AICount();// 实例化数子对象
private Count c = new Count();// 实例化数牌对象
private int in = 0;// 保存该下黑子还是白字
private int summax = 0;// 存储最大权值
AIThread ait;
// 建立构造方法传入g
public ChessAI(Graphics g) {
this.g = g;
}
// 下棋子的方法
public void underpawn() {
if (state[0]) {// 判断状态时候是正确的
statistic(); // 调用统计方法
xlist.add(putx);
ylist.add(puty);
g.fillOval(X + putx * SIZE - CHESS_SIZE, Y + puty * SIZE
- CHESS_SIZE, 2 * CHESS_SIZE, 2 * CHESS_SIZE);// 画棋子
array[putx][puty] = in;// 设置棋子矩阵
if (ChessJudge(putx, puty) && in == 1) {// 判断是否胜利
JOptionPane.showMessageDialog(null, "black win!", "Game Over",
1);// 如果胜利弹出对话框
state[0] = false;
}
if (ChessJudge(putx, puty) && in == -1) {// 判断是否胜利
JOptionPane.showMessageDialog(null, "white win!", "Game Over",
1);// 如果胜利弹出对话框
state[0] = false;
}
}
}
/**
* 统计方法
*/
public void statistic() {
int sum = 0;
for (int m = 0; m < NUM; m++) {
for (int n = 0; n < NUM; n++) {
sum += array[m][n];
}
}
if (sum == 0) // 电脑走黑子
{
g.setColor(Color.BLACK);
in = 1;
} else // 电脑走白子
{
g.setColor(Color.WHITE);// 设置棋子是白色的
in = -1;
}
// 遍历每一点算权值
for (int i = 0; i < NUM; i++) {
for (int j = 0; j < NUM; j++) {
if (arraytemp[i][j] == 0) {
white[i][j] = ac.countmax(i, j, -1);
black[i][j] = ac.countmax(i, j, 1);
}
}
}
int whitetemp = 0, blacktemp = 0, wtx = 0, wty = 0, btx = 0, bty = 0;
for (int i = 0; i < NUM; i++) {
for (int j = 0; j < NUM; j++) {
if (array[i][j] == 0) {
if (white[i][j] > whitetemp) {
wtx = i;
wty = j;
whitetemp = white[i][j];
}
if (black[i][j] > blacktemp) {
btx = i;
bty = j;
blacktemp = black[i][j];
}
}
}
}
if (blacktemp > 2000 || whitetemp > 2000 || blacktemp < 11
&& whitetemp < 11) {
if (blacktemp > whitetemp) {
putx = btx;
puty = bty;
} else {
putx = wtx;
puty = wty;
}
} else {
int temp = 0;// 存储几步后最大值
for (int i = Math.max(0, xlist.get(xlist.size() - 1) - 5); i < xlist
.get(xlist.size() - 1) + 5 && i < NUM; i++) {
for (int j = Math.max(0, ylist.get(ylist.size() - 1) - 5); j < ylist
.get(ylist.size() - 1) + 5 && j < NUM; j++) {
if (array[i][j] == 0) {
equals();
arraytemp[i][j] = in;
summax = 0;
predict(arraytemp, in, 5);// 得到几步后最大值
if (summax > temp) // 如果比以前还大,就获得现在这个点
{
temp = summax;
putx = i;
puty = j;
}
}
}
}
}
}
/**
* 预测算法
*
* @param arraytemp临时棋盘
* @param tin棋子颜色
* @param count数量
*/
public void predict(int[][] arraytemp, int tin, int count) {
count--;
if (count < 0) {
return;
}
// 遍历每一点算权值
for (int i = 0; i < NUM; i++) {
for (int j = 0; j < NUM; j++) {
if (arraytemp[i][j] == 0) {
white[i][j] = ac.countmax(i, j, -1);
black[i][j] = ac.countmax(i, j, 1);
}
}
}
// 遍历找到最大值点
for (int i = 0; i < NUM; i++) {
for (int j = 0; j < NUM; j++) {
if (arraytemp[i][j] == 0) {
if (summax < white[i][j]) {
summax = white[i][j];
putx = i;
puty = j;
}
if (summax < black[i][j]) {
summax = black[i][j];
putx = i;
puty = j;
}
}
}
}
arraytemp[putx][puty] = -tin;// 在这一点下一个子
for (int i = 0; i < NUM; i++) {
for (int j = 0; j < NUM; j++) {
if (arraytemp[i][j] == 0) {
white[i][j] = ac.countmax(i, j, -1);
black[i][j] = ac.countmax(i, j, 1);
}
}
}
// 遍历找到最大值点
for (int i = 0; i < NUM; i++) {
for (int j = 0; j < NUM; j++) {
if (arraytemp[i][j] == 0) {
if (summax < white[i][j]) {
summax = white[i][j];
putx = i;
puty = j;
}
if (summax < black[i][j]) {
summax = black[i][j];
putx = i;
puty = j;
}
}
}
}
arraytemp[putx][puty] = tin;// 在这一点下一个子
if (summax > 10000)
return;
predict(arraytemp, tin, count);// 预测另一个子下一步的情况
}
/**
* 判断棋的输赢
*
* @param x下棋点的横坐标
* @param y下棋点的纵坐标
* @return
*/
public boolean ChessJudge(int x, int y) {
int countrow = c.countrow(x, y);
int countcolumn = c.countcolumn(x, y);
int countleftlean = c.countleftlean(x, y);
int countrightlean = c.countrightlean(x, y);
if (countrow >= 5 || countcolumn >= 5 || countleftlean >= 5
|| countrightlean >= 5) {
state[0] = false;
return true;
}
return false;
}
}
4、文件存储
我们这里要向文件中存储三个内容:
1、棋盘矩阵
2、走棋队列,这样我们就可以在读取进度后仍能进行悔棋
3、游戏模式,我们以0代表人人,1代表人机
public class FileOperation implements ActionListener, Config {
private ChessTable ct;// 创建棋盘对象
private String path = "savefile.ctf";// 创建路径字符串
private File file = new File(path);// 创建文件对象
/**
* 构造函数
*
* @param ct棋盘面板
*/
public FileOperation(ChessTable ct) {
this.ct = ct;
}
/**
* 监听器
*/
public void actionPerformed(ActionEvent e) {
JMenuItem jmi = (JMenuItem) e.getSource();// 得到菜单按钮
if (jmi.getText().equals("Exit")) {
System.exit(0);// 如果是关闭,就关闭窗体
}
if (jmi.getText().equals("Save")) {
filesave(file);// 如果是保存,就调用保存方法
}
if (jmi.getText().equals("Open")) {
fileopen(file);// 如果是打开,就调用打开方法
}
}
/**
* 文件保存方法
*
* @param file文件对象
*/
public void filesave(File file) {
if (!file.exists()) // 如果文件不存在
{
try {
file.createNewFile();// 创建一个文件
} catch (IOException e) {
e.printStackTrace();
}
}
try {
OutputStream os = new FileOutputStream(file);// 新建文件输入流
DataOutputStream dos = new DataOutputStream(os);// 新建数据输入流
for (int i = 0; i < NUM; i++) {
for (int j = 0; j < NUM; j++) {
dos.writeInt(array[i][j]);// 通过遍历把数据给存入
}
}
dos.writeInt(xlist.size());// 写入x坐标的大小
for (int i = 0; i < xlist.size(); i++) {
dos.writeInt(xlist.get(i));// 写入x坐标
}
dos.writeInt(ylist.size());// 写入y坐标大小
for (int i = 0; i < ylist.size(); i++) {
dos.writeInt(ylist.get(i));// 写入y坐标
}
if (ct.getMode().equals("AI")) // 如果是人机模式
{
dos.writeInt(1);// 写入1
} else if (ct.getMode().equals("war")) // 如果是人人模式
{
dos.writeInt(0);// 写入0
}
dos.flush();// 清空此数据输入流,注意不是关闭!
os.close();// 关闭输入流
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 文件打开方法
*
* @param file文件对象
*/
public void fileopen(File file) {
if (file.exists())// 如果文件存在
try {
InputStream is = new FileInputStream(file);// 创建文件输入流
DataInputStream dis = new DataInputStream(is);// 创建数据输入流
for (int i = 0; i < NUM; i++) {
for (int j = 0; j < NUM; j++) {
array[i][j] = dis.readInt();// 循环读取数据
}
}
xlist.clear();// 清空队列
ylist.clear();// 清空队列
int xsize = dis.readInt();// 得到x坐标的大小
for (int i = 0; i < xsize; i++) {
xlist.add(dis.readInt());// 读取x坐标
}
int ysize = dis.readInt();// 得到y坐标的大小
for (int i = 0; i < ysize; i++) {
ylist.add(dis.readInt());// 读取y坐标
}
int mode = dis.readInt();// 得到模式的数据
if (mode == 0) // 如果是0
{
ct.setMode("war");// 设置模式为人人模式
} else if (mode == 1) // 如果是1
{
ct.setMode("AI");// 设置模式为人机模式
}
ct.repaint();// 重绘面板
dis.close();// 关闭数据输入流
is.close();// 关闭输入流
} catch (IOException e) {
e.printStackTrace();
}
}
}
这样我们的五子棋就完成了!