五子棋项目总结
之前一直没有发总结,总是感觉人机做的还不是太成熟,后来培训结束后又花了一些时间重写了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(); } } }
这样我们的五子棋就完成了!
相关推荐
这个小程序是我1年多前自己写着玩的,当时不懂这么多,两个个文件就搞定了,写的也不规范。...当时参考了一个别人的程序,他在程序中设定只搜索8x8的空间,怕电脑承受不了,我自己实现后并没有发现这个问题。
我毕业设计自己做的基于java的五子棋游戏软件,获得优秀论文,功能在单纯的人机对战上算比较齐全,也比较好玩!希望为毕业设计做游戏的同学提供些查考! 软件的基本功能 软件的基本功能介绍如下: 1. 程序运行后...
1. **图形用户界面(GUI)**:JAVA五子棋人机对战版必然包含了GUI设计,这通常使用Java的Swing或JavaFX库来实现。GUI让玩家能够直观地看到棋盘,并通过鼠标点击选择落子位置。 2. **事件监听**:为了响应用户的点击...
【Java五子棋游戏人机对战】是一个充满挑战与趣味的编程项目,它结合了计算机科学中的多个重要知识点,包括图形用户界面设计、算法设计、人工智能以及游戏规则的实现。下面将详细阐述这些关键点。 首先,项目的名称...
游戏开始后,玩家在游戏设置中选择人机对战,则系统执黑棋,玩家自己执白棋。双方轮流下一棋,先将横、竖或斜线的5个或5个以上同色棋子连成不间断的一排者为胜。 【项目资源】:包含前端、后端、移动开发、操作系统...
《基于Java实现的五子棋人机对战游戏解析》 在编程领域,游戏开发是一项既具挑战性又富有乐趣的实践。本项目名为“java实现的五子棋人机对战游戏”,它是一个使用Java语言编写的后端游戏程序,适用于毕业设计、课程...
随着人工智能及计算机硬件的发展,计算机象棋程序的下棋水平也不断地得到提高。20世纪60年代初,麦卡锡提出了alpha-beta修剪算法,把为决定下一个走步而需对棋盘状态空间的搜索量从指数级减少为指数的平方根,大大地...
Java 五子棋小游戏不仅有着简洁、美观的界面设计和流畅的操作体验,还具备了人机对战、双人对战等多种模式,可以让玩家与朋友们一起享受五子棋的乐趣。无论你是棋迷还是初学者,都可以在这款游戏中找到自己的乐趣。 ...
【安卓五子棋源码 可实现人机对战】是一个专门为Android平台设计的五子棋应用程序,它包含了实现人机对战的核心算法和技术。在Android应用开发中,这样的项目通常涉及以下几个重要的知识点: 1. **Android Studio ...
本资源是Java版的中国象棋人机对战程序源代码,对于想要深入理解Java编程、人工智能(AI)算法以及游戏开发的程序员来说,这是一个极好的学习材料。在这个项目中,作者巧妙地运用了AI技术来模拟象棋的对弈过程,使...
该软件不仅支持人机对战,让玩家可以与智能AI一较高下,还实现了人人对战模式,方便朋友之间切磋技艺。更重要的是,它具备国际化功能,满足了全球玩家的需求,并且可以读取和设置残局,使研究棋局和学习策略变得更加...
总的来说,安卓五子棋人机对战游戏的开发涵盖了安卓应用的基础架构、AI算法的实现、用户界面的设计以及游戏逻辑的控制等多个方面,是一个综合性的编程实践项目。对于想要学习安卓开发和AI算法的初学者来说,这是一个...
五子棋 人机对战版 ,你能容易地赢,说明你是五子棋高手了 c# 编写,但vc和java用户也可以看,没有用到特殊语法。 只有五子棋算法源码,界面是别人的,这是一个比赛中写的五子棋策略接口,你也可以写自己的策略接口...
《Java实现的五子棋游戏:开启人机对战新体验》 在计算机科学与信息技术领域,游戏开发是一项集趣味性、技术性于一体的实践。Java作为一种广泛应用的编程语言,不仅适用于企业级应用,也能用于创建各种类型的游戏。...
【标题】"JAVA版GUI人机对战 五子棋"是使用Java编程语言和Swing库构建的一款桌面游戏,允许玩家与计算机进行五子棋的对弈。五子棋是一种双人对弈的策略游戏,目标是在棋盘上先形成连续的五个棋子(横向、纵向或对角...
【AI五子棋人机对战Java版】是一款基于Java编程语言实现的智能五子棋游戏,它允许玩家与计算机进行对弈。在这个项目中,AI(人工智能)扮演着关键角色,通过算法来模拟对手的策略,使得机器能够与人类玩家进行智力上...
游戏开始后,玩家在游戏设置中选择人机对战,则系统执黑棋,玩家自己执白棋。双方轮流下一棋,先将横、竖或斜线的5个或5个以上同色棋子连成不间断的一排者为胜。 【项目资源】:包含前端、后端、移动开发、操作系统...
Java GUI版五子棋是一款基于图形用户界面的棋类游戏,允许玩家与计算机进行对战。这个项目展示了如何使用Java的Swing库来构建一个功能完备的桌面应用程序,同时涉及了算法设计,尤其是人工智能(AI)的部分,用于...
在本项目中,"五子棋 人机对弈 JAVA实现" 是一个使用Java编程语言设计的五子棋游戏,特别之处在于它包含了人机对弈的功能,这意味着玩家可以与计算机进行对战。这个项目对于想要了解AI和图形用户界面(GUI)开发的...