一、需求分析
1、画一个15x15的棋盘版面
2、功能按钮:开始游戏,悔棋,认输
3、单选按钮:人人对战、人机对战
4、要求:在棋盘上下棋子,棋子必须要在交叉点上;同一个位置上不能有再下棋子;棋子不能消失;判断输赢。
二、设计思路
1、图形界面(棋盘版面)
通过JFrame与JPanel 窗体实现,将JFrame分为2个部分,一部分用于绘制棋盘,另一部分用于放置功能按钮等。 并且添加监听。
(1)创建窗体
public void showUI(){JFrame frame=new JFrame(); //创建窗体 frame frame.setTitle("五子棋 "); //设置窗体的标题 frame.setSize(750,650); //设置大小 frame.setResizable(false); //大小不可变 frame.setLocationRelativeTo(null); //窗体居中 frame.setDefaultCloseOperation(3); //退出时关闭进程 cl=new ChessListener(this); // 实例化事件处理类的对象,将棋盘面板作为参数传递过去 centerPanel(frame); //在窗体frame上添加中间面板 ---棋盘 eastPanel(frame); //窗体frame上添加东边面板 ---功能按钮frame.setVisible(true); //设置窗体可见 cl.setExist(exist); //将棋子数组传入到事件监听类中}
(2)创建两个面板---中间面板(绘制棋盘)、东边面板(放置功能按钮)
/** * 往窗体上添加中间面板的方法 * @param frame 窗体 */public void centerPanel(JFrame frame){this.setBackground(Color.ORANGE);frame.add(this);}/** * 往窗体上添加东边面板的方法 ---用于放置功能按钮 * @param frame 窗体 */public void eastPanel(JFrame frame){JPanel epanel=new JPanel(); //创建一个面板对象 epanel.setBackground(Color.GRAY); //背景颜色设置为gray epanel.setPreferredSize(new Dimension(150,600)); //设置大小 epanel.setLayout(new FlowLayout());//默认也是流式布局 String[] buttonArray={"开始游戏","悔棋","认输"}; //数组存储 功能按钮命令 for(int i=0;i<buttonArray.length;i++){ //使用循环实例化按钮对象 JButton button=new JButton(buttonArray[i]); //实例化按钮对象 button.setPreferredSize(new Dimension(100,50)); //设置大小 epanel.add(button); //在面板上添加按钮 button.addActionListener(cl); //为每一个按钮添加监听 }String[] radioArray={"人人对战","人机对战"}; //数组存储 单选按钮命令 ButtonGroup bg=new ButtonGroup(); //实例化一个按钮组的对象 for(int i=0;i<radioArray.length;i++){ //使用循环创建单选按钮对象 JRadioButton radioButton=new JRadioButton(radioArray[i]); //实例化单选按钮对象 bg.add(radioButton); //将每个创建的单选按钮添加到同一组中 radioButton.setPreferredSize(new Dimension(100,50)); //设置单选按钮大小 radioButton.setOpaque(false); //设置不透明 radioButton.setForeground(Color.WHITE); //前景色为白色 if(i==0){ //默认选中第一个单选按钮 radioButton.setSelected(true);}epanel.add(radioButton); //在面板上添加单选按钮 radioButton.addActionListener(cl); //加监听器 }frame.add(epanel,BorderLayout.EAST); //为窗体(边框布局)添加面板---放置在东侧 }
(3)新建一个接口,用于保存棋盘的各种参数(常量)
public interface Config {public static final int X0=20;//棋盘起点坐标x0 public static final int Y0=30; //棋盘起点坐标y0 public static final int ROWS=15; //行数 public static final int COLUMNS =15; //列数 public static final int CHESS_SIZE=40; //棋子的大小 public static final int SIZE=40; //棋盘行与行 列与列之间的距离}
(4)绘制棋盘
/** * 绘制棋盘的方法 * @param g 传入画笔 */public void drawChessTable(Graphics g){for(int r=0;r<Config.ROWS;r++){ //行 x 不变 y变 g.drawLine(Config.X0, Config.Y0+r*Config.SIZE, Config.X0+(Config.COLUMNS-1)*Config.SIZE, Config.Y0+r*Config.SIZE);}for(int c=0;c<Config.COLUMNS;c++){ //列 x变 y不变 g.drawLine(Config.X0+Config.SIZE*c,Config.Y0, Config.X0+Config.SIZE*c, Config.Y0+(Config.ROWS-1)*Config.SIZE);}}
(5)棋子重绘
我们下棋的时候并不希望因为最大化或最小化窗体而导致棋子消失,因为组件具有重绘的方法,不需要我们重绘,但是对于棋子来说,我们必须将棋子重新画出。
这里我们考虑用一个exist[][] 二维数组存储棋子的颜色与位置,值为0时表示没有棋子,值为1时表示黑色棋子,值为-1 时表示该位置有白色的棋子。
/** * 棋子重绘的方法 * @param g */public void reDrawChess(Graphics g){Graphics2D g2d=(Graphics2D) g; //转为Graphics2D 后面要为画笔设置颜色 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);for(int r=0;r<Config.ROWS;r++){ //外循环控制行 for(int c=0;c<Config.COLUMNS;c++){ //内循环控制列 if(exist[r][c]!=0){ //如果该位置不为空 if(exist[r][c]==1){ //该位置是黑子 g2d.setColor(Color.BLACK);g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);}else if(exist[r][c]==-1){ //该位置是白子 g2d.setColor(Color.WHITE);g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);}}}}}
(5)重写面板的paint() 方法,完成重绘
因为我们的主类(fiveChess) 继承了JPanel类,在中间面板的方法里,直接向窗体添加的面板就是主类面板。即:
/** * 往窗体上添加中间面板的方法 * @param frame 窗体 */public void centerPanel(JFrame frame){this.setBackground(Color.ORANGE);frame.add(this);}
所以在主类里直接从写JPanel的paint()方法即可:
public void paint(Graphics g){super.paint(g);drawChessTable(g);reDrawChess(g);}
注:主类的完整代码
package com.xs.chessAI;import java.awt.BasicStroke;import java.awt.BorderLayout;import java.awt.Color;import java.awt.Dimension;import java.awt.FlowLayout;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.RenderingHints;import javax.swing.ButtonGroup;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JPanel;import javax.swing.JRadioButton;/** * 五子棋 * @author Administrator * */public class FiveChess extends JPanel{ //继承JPanel类 使FiveChess作为一个面板 private int[][] exist=new int[Config.ROWS][Config.COLUMNS]; //创建一个棋子数组 用于保存棋盘上哪个位置有哪个颜色的棋子 private ChessListener cl; //声明事件处理类型的 变量 /** * 图像界面的显示方法 */public void showUI(){JFrame frame=new JFrame(); //创建窗体 frame frame.setTitle("五子棋 "); //设置窗体的标题 frame.setSize(750,650); //设置大小 frame.setResizable(false); //大小不可变 frame.setLocationRelativeTo(null); //窗体居中 frame.setDefaultCloseOperation(3); //退出时关闭进程 cl=new ChessListener(this); // 实例化事件处理类的对象,将棋盘面板作为参数传递过去 centerPanel(frame); //在窗体frame上添加中间面板 ---棋盘 eastPanel(frame); //窗体frame上添加东边面板 ---功能按钮frame.setVisible(true); //设置窗体可见 cl.setExist(exist); //将棋子数组传入到事件监听类中}/** * 重写面板重绘的方法 */public void paint(Graphics g){super.paint(g);drawChessTable(g);reDrawChess(g);}/** * 往窗体上添加中间面板 * @param frame 窗体 */public void centerPanel(JFrame frame){this.setBackground(Color.ORANGE);frame.add(this);}/** * 往窗体上添加东边面板 ---用于放置功能按钮 * @param frame 窗体 */public void eastPanel(JFrame frame){JPanel epanel=new JPanel(); //创建一个面板对象 epanel.setBackground(Color.GRAY); //背景颜色设置为gray epanel.setPreferredSize(new Dimension(150,600)); //设置大小 epanel.setLayout(new FlowLayout());//默认也是流式布局 String[] buttonArray={"开始游戏","悔棋","认输"}; //数组存储 功能按钮命令 for(int i=0;i<buttonArray.length;i++){ //使用循环实例化按钮对象 JButton button=new JButton(buttonArray[i]); //实例化按钮对象 button.setPreferredSize(new Dimension(100,50)); //设置大小 epanel.add(button); //在面板上添加按钮 button.addActionListener(cl); //为每一个按钮添加监听 }String[] radioArray={"人人对战","人机对战"}; //数组存储 单选按钮命令 ButtonGroup bg=new ButtonGroup(); //实例化一个按钮组的对象 for(int i=0;i<radioArray.length;i++){ //使用循环创建单选按钮对象 JRadioButton radioButton=new JRadioButton(radioArray[i]); //实例化单选按钮对象 bg.add(radioButton); //将每个创建的单选按钮添加到同一组中 radioButton.setPreferredSize(new Dimension(100,50)); //设置单选按钮大小 radioButton.setOpaque(false); //设置不透明 radioButton.setForeground(Color.WHITE); //前景色为白色 if(i==0){ //默认选中第一个单选按钮 radioButton.setSelected(true);}epanel.add(radioButton); //在面板上添加单选按钮 radioButton.addActionListener(cl); //加监听器 }frame.add(epanel,BorderLayout.EAST); //为窗体(边框布局)添加面板---放置在东侧 }/** * 绘制棋盘的方法 * @param g 传入画笔 */public void drawChessTable(Graphics g){for(int r=0;r<Config.ROWS;r++){ //行 x 不变 y变 g.drawLine(Config.X0, Config.Y0+r*Config.SIZE, Config.X0+(Config.COLUMNS-1)*Config.SIZE, Config.Y0+r*Config.SIZE);}for(int c=0;c<Config.COLUMNS;c++){ //列 x变 y不变 g.drawLine(Config.X0+Config.SIZE*c,Config.Y0, Config.X0+Config.SIZE*c, Config.Y0+(Config.ROWS-1)*Config.SIZE);}}/** * 棋子重绘的方法 * @param g */public void reDrawChess(Graphics g){Graphics2D g2d=(Graphics2D) g; //转为Graphics2D 后面要为画笔设置颜色 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);for(int r=0;r<Config.ROWS;r++){ //外循环控制行 for(int c=0;c<Config.COLUMNS;c++){ //内循环控制列 if(exist[r][c]!=0){ //如果该位置不为空 if(exist[r][c]==1){ //该位置是黑子 g2d.setColor(Color.BLACK);g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);}else if(exist[r][c]==-1){ //该位置是白子 g2d.setColor(Color.WHITE);g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);}}}}}public static void main(String[] args) {FiveChess chess=new FiveChess();chess.showUI();}}
二、监听类
功能:根据按钮命令---采取相应的操作
下棋(将棋子放到棋盘上) 人人对战 人机对战(AI算法) 开始游戏(清空棋盘)
悔棋 认输
(1) 下棋功能
鼠标在棋盘上点击时,棋子落在相应的位置
public void mouseClicked(MouseEvent e){ //鼠标点击事件的处理方法 x=e.getX(); //获取点击位置的x坐标 y=e.getY(); //获取点击位置的y坐标 if(mode.equals("人人对战")){ //调用人人对战方法 pvp(x,y);}else if(mode.equals("人机对战")){ //调用人机对战的方法 pvai(x,y);}}
这里mode 是根据按钮命令获取的对战模式,默认为人人对战,mode 的获取命令在在actionPerformed 方法中,后面会提到。
(2)人人对战的方法
/** * 人人对战的方法 * @param x * @param y */public void pvp(int x,int y){flag++; //步数+1 g=fiveChess.getGraphics(); //从棋盘面板类中获取画布 g2d=(Graphics2D) g;g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);for(int r=0;r<Config.ROWS;r++){ //行 for(int c=0;c<Config.COLUMNS;c++){ //列 if(exist[r][c]==0){ //判断该位置上是否有棋子 if((Math.abs(x-Config.X0-c*Config.SIZE)<Config.SIZE/3.0)&&(Math.abs(y-Config.Y0-r*Config.SIZE)<Config.SIZE/3.0)){//将棋子放到交叉点上,误差为1/3 if(count==1){ //count 为1放置黑子 g2d.setColor(Color.BLACK);exist[r][c]=1; //记录下了黑色棋子r与c的位置 g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);count=-1; //下一次点击时 下白色的棋子 list.add(new Chess(r,c)); //将棋子对象存到数组队列中,保存了棋子的属性 r,c if(win.checkWin()==1){ //判断黑色棋子是否赢了 JOptionPane.showMessageDialog(fiveChess, "黑色棋子获得胜利");fiveChess.removeMouseListener(this); //获胜之后,不允许再在棋盘上下棋子,所以移除鼠标监听 return;}}else if(count==-1){ //放置白子 g2d.setColor(Color.WHITE);exist[r][c]=-1;g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);count=1; //下一步要下黑色棋子 list.add(new Chess(r,c)); //将棋子对象存到数组队列中,保存了棋子的属性 r,c if(win.checkWin()==-1){ //判断白色棋子是否赢了 JOptionPane.showMessageDialog(fiveChess, "白色棋子获得胜利");fiveChess.removeMouseListener(this);return;}}}}}}}
根据获取的坐标,先判断是否在棋盘交点1/3处,根据count 值判断下一步下白棋还是黑棋(count = 1 黑棋 count = -1 白棋),并把位置与颜色传入二维数组中。
(3)人机对战的方法
/** * 人机对战的方法 * @param x 人所下棋子的横坐标 * @param y 人所下棋子的纵坐标 */public void pvai(int x,int y){flag++; //步数+1 g=fiveChess.getGraphics();g2d=(Graphics2D) g;g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);for(int r=0;r<Config.ROWS;r++){for(int c=0;c<Config.COLUMNS;c++){if(exist[r][c]==0){ //判断该位置上是否有棋子 if((Math.abs(x-Config.X0-c*Config.SIZE)<Config.SIZE/3.0)&&(Math.abs(y-Config.Y0-r*Config.SIZE)<Config.SIZE/3.0)){//将棋子放到交叉点上,误差为1/3 g2d.setColor(Color.BLACK);exist[r][c]=1; //记录下了黑色棋子的位置 g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);list.add(new Chess(r,c)); //队列中添加棋子数组对象 存 r c if(win.checkWin()==1){ //判断黑色棋子是否胜利 JOptionPane.showMessageDialog(fiveChess, "黑色棋子获得胜利");fiveChess.removeMouseListener(this);return;}ai(g2d); //调用ai下棋的方法 if(win.checkWin()==-1){ //ai下的是白色棋子,每下完一步,都要判断一次是否获胜 JOptionPane.showMessageDialog(fiveChess, "白色棋子获得胜利");fiveChess.removeMouseListener(this);return;}}}}}
人下棋时,算法与人人对战时一样,ai下棋,调用ai下棋的方法。每次人下棋或者ai下棋后,都要进行判断输赢。
ai下棋的方法:
/** * ai 下棋的方法 * @param g2d */public void ai(Graphics2D g2d){g2d.setColor(Color.WHITE); //设置ai所下棋子的颜色为白色 int[][] weightArray=new int[Config.ROWS][Config.COLUMNS];//创建一个存储权值的二维数组 /** * 设置每种棋子相连情况下的权值 */map.put("010", 1);map.put("0110", 20);map.put("01110", 50);map.put("011110", 500);map.put("0-10", 10);map.put("0-1-10", 30);map.put("0-1-1-10", 70);map.put("0-1-1-1-10", 500);map.put("-110", 1);map.put("-1110", 10);map.put("-11110", 30);map.put("-111110", 500);map.put("1-10", 1);map.put("1-1-10", 10);map.put("1-1-1-10", 30);map.put("1-1-1-1-10", 500);for(int r=0;r<exist.length;r++){ //求出权值 将权值存到数组中 for(int c=0;c<exist[r].length;c++){if(exist[r][c]==0){ //判断是否是空位 String code=countHL(r,c);Integer weight = map.get(code);//获取棋子相连情况对应的权值 if(null != weight){//判断权值不为null weightArray[r][c] += weight;//累加权值 }code=countVU(r,c);weight = map.get(code);//获取棋子相连情况对应的权值 if(null != weight){//判断权值不为null weightArray[r][c] += weight;//累加权值 }code=countLLU(r,c);weight = map.get(code);//获取棋子相连情况对应的权值 if(null != weight){//判断权值不为null weightArray[r][c] += weight;//累加权值 }code=countLRU(r,c);weight = map.get(code);//获取棋子相连情况对应的权值 if(null != weight){//判断权值不为null weightArray[r][c] += weight;//累加权值 }}}}int max=weightArray[0][0]; //找出最大的权值 for(int r=0;r<weightArray.length;r++){for(int c=0;c<weightArray[r].length;c++){if(weightArray[r][c]>max){max=weightArray[r][c];}}}for(int r=0;r<weightArray.length;r++){ //随机取最大权值处所在的点 for(int c=0;c<weightArray[r].length;c++){if(weightArray[r][c]==max&&exist[r][c]==0){ //权值最大且是空位 exist[r][c]=-1;g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);list.add(new Chess(r,c));return;}}}}
AI算法有很多种,这里采用了权值算法,即根据棋子相连的情况,统计每一个空位的权值大小,选出权值最大点 在这个位置下棋。存储棋子相连情况与权值使用HashMap 。
统计权值方法:4种(水平向左统计、垂直向上统计、向上正斜统计( \ ) 、向下反斜统计( / ))
/** * 水平向左方向统计棋子相连的情况 * @param r 行 * @param c 列 * @return*/private String countHL(int r,int c){String code = "0"; //默认记录r,c位置的情况 int chess = 0; //记录第一次出现的棋子 for(int c1=c-1;c1>=0;c1--){ //向左统计 if(exist[r][c1]==0){ //表示没有棋子的位置 if(c1+1==c){ //相邻的空位 break;}else{ //空位不相连 code = exist[r][c1] + code;//记录棋子相连的情况 break;}}else{ //表示有棋子 if(chess==0){ //判断是否是第一次出现棋子 code = exist[r][c1] + code; //记录棋子相连的情况 chess = exist[r][c1]; //记录第一次的棋子的颜色 }else if(chess==exist[r][c1]){ //表示之后的棋子和第一次的棋子颜色一致 code = exist[r][c1] + code; //记录棋子相连的情况 }else{ //表示之后的棋子和第一次的棋子颜色不同 code = exist[r][c1] + code; //记录棋子相连的情况 break;}}}return code;}/** * 垂直向上统计棋子相连的情况 * @param r 行 * @param c 列 * @return */private String countVU(int r,int c){String code = "0"; //默认记录r,c位置的情况 int chess = 0; //记录第一次出现的棋子 for(int r1=r-1;r1>=0;r1--){ //向上统计 if(exist[r1][c]==0){ //表示没有棋子 if(r1+1==r){ //相邻的空位 break;}else{ //不相邻的空位 code = exist[r1][c] + code; //记录棋子相连的情况 break;}}else{ //表示有棋子 if(chess==0){ //判断是否是第一次出现棋子 code = exist[r1][c] + code; //记录棋子相连的情况 chess = exist[r1][c]; //记录第一次的棋子的颜色 }else if(chess==exist[r1][c]){ //表示之后的棋子和第一次的棋子颜色一致 code = exist[r1][c] + code; //记录棋子相连的情况 }else{ //表示之后的棋子和第一次的棋子颜色不同 code = exist[r1][c] + code; //记录棋子相连的情况 break;}}}return code;}/*** 正斜(\) 棋子相连统计 * @param r * @param c * @return */private String countLLU(int r,int c){String code = "0"; //默认记录r,c位置的情况 int chess = 0; //记录第一次出现的棋子 for(int r1=r-1,c1=c-1;r1>=0&&c1>0;r1--,c1--){//向上统计 if(exist[r1][c1]==0){ //表示没有棋子 if(r1+1==r&&c1+1==c){ //相邻的空位 break;}else{ //不相邻的空位 code = exist[r1][c1] + code; //记录棋子相连的情况 break;}}else{ //表示有棋子 if(chess==0){ //判断是否是第一次出现棋子 code = exist[r1][c1] + code; //记录棋子相连的情况 chess = exist[r1][c1]; //记录第一次的棋子的颜色 }else if(chess==exist[r1][c1]){ //表示之后的棋子和第一次的棋子颜色一致 code = exist[r1][c1] + code; //记录棋子相连的情况 }else{ //表示之后的棋子和第一次的棋子颜色不同 code = exist[r1][c1] + code; //记录棋子相连的情况 break;}}}return code;}/** * 反斜(/) 棋子相连的统计 * @param r* @param c * @return */private String countLRU(int r,int c){String code = "0"; //默认记录r,c位置的情况 int chess = 0; //记录第一次出现的棋子 for(int r1=r-1,c1=c+1;r1>=0&&c1<Config.COLUMNS;r1--,c1++){if(exist[r1][c1]==0){ //表示没有棋子 if(r1+1==r&&c1-1==c){ //相邻的空位 break;}else{code = exist[r1][c1] + code; //记录棋子相连的情况 break;}}else{ //表示有棋子 if(chess==0){//判断是否是第一次出现棋子 code = exist[r1][c1] + code; //记录棋子相连的情况 chess = exist[r1][c1]; //记录第一次的棋子的颜色 }else if(chess==exist[r1][c1]){ //表示之后的棋子和第一次的棋子颜色一致 code = exist[r1][c1] + code; //记录棋子相连的情况 }else{ //表示之后的棋子和第一次的棋子颜色不同 code = exist[r1][c1] + code; //记录棋子相连的情况 break;}}}return code;}
(4)获取命令的方法
这里根据点击的功能按钮 采取不同的功能操作
/** * 点击事件处理方法 */public void actionPerformed(ActionEvent e){if(e.getSource() instanceof JRadioButton){ //点击单选按钮 mode=e.getActionCommand();}else if(e.getSource() instanceof JButton){ //点击其他功能按钮 if(e.getActionCommand().equals("开始游戏")){MouseListener[] mls=fiveChess.getMouseListeners();if(mls.length>0){ //如果还有其他监听 ---移除 fiveChess.removeMouseListener(this);}reset(); //调用棋盘回到初始状态的方法 fiveChess.addMouseListener(this); // 为棋盘添加鼠标监听 }else if(e.getActionCommand().equals("悔棋")){ //悔棋的算法 if(mode.equals("人人对战")){if(list.size()>1){ //存储棋子的数组队列长度大于1 Chess chess=list.get(list.size()-1); //获取最后一个被存入数组队列的棋子对象 int r=chess.getR(); //分别获取棋子的x与y坐标 int c=chess.getC();exist[r][c]=0; //设置最后一步所下棋子的位置为空 list.remove(list.size()-1); //移除队列中最后一个棋子对象 fiveChess.repaint(); //调用面板重绘的方法 if(count==1){ //如果悔棋的是黑色方,下一步下棋的还是黑色方 count=-1; //如果悔棋的是白色方,下一步下棋的还是白色方 }else{count=1;}}}else if(mode.equals("人机对战")){ //人机对战,悔一次棋要退两步,1、电脑所下的棋 2、人所下的棋子 if(list.size()>2){ //至少下了两步 Chess chess=list.get(list.size()-2); //获取倒数第二步的棋子对象 int r=chess.getR();int c=chess.getC();exist[r][c]=0; //令其为空 Chess chessAI=list.get(list.size()-1); //获取最后一步棋子对象 r=chessAI.getR();c=chessAI.getC();exist[r][c]=0; //令其为空 list.remove(list.size()-1); //删除数组队列中后两个对象 list.remove(list.size()-1);fiveChess.repaint(); //调用棋子面板的重绘方法 }}}else if(e.getActionCommand().equals("认输")){ //认输的算法 if(flag<10){JOptionPane.showMessageDialog(fiveChess, "总步数小于10,不能认输");}else{if(mode.equals("人人对战")){if(count==1){ //本来应该下黑色棋子了 但是点击了认输,所以白色棋子获胜 JOptionPane.showMessageDialog(fiveChess, "白色棋子获得胜利");}else if(count==-1){ //本来应该下白色棋子了 但是点击了认输,所以黑色棋子获胜 JOptionPane.showMessageDialog(fiveChess, "黑色棋子获得胜利");}}else if(mode.equals("人机对战")){ //电脑不会认输的 JOptionPane.showMessageDialog(fiveChess, "白色棋子AI获得胜利");}fiveChess.removeMouseListener(this); //认输后不能在棋盘上上下棋了 所以要移除棋盘上的鼠标监听 }}}}
棋盘重置的方法:
/** * 设置棋盘回到初始状态的方法 */public void reset(){count=1; //默认黑色棋子先下棋 flag=0; //下棋步数重置为0 for(int r=0;r<exist.length;r++){ //遍历二维数组,将所有位置清空 for(int c=0;c<exist[r].length;c++){exist[r][c]=0;}}fiveChess.repaint(); //调用棋盘重绘的方法 }
即让存储棋子的二维数组每一个点的值都为0;然后重绘棋子面板。
注:监听类完整代码
package com.xs.chessAI;import java.awt.Color;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.RenderingHints;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.awt.event.MouseAdapter;import java.awt.event.MouseEvent;import java.awt.event.MouseListener;import java.util.ArrayList;import java.util.HashMap;import javax.swing.JButton;import javax.swing.JOptionPane;import javax.swing.JRadioButton;/** * 事件处理类* @author Administrator * */public class ChessListener extends MouseAdapter implements ActionListener{private int x,y; //记录点击坐标 private Graphics g; //存储画笔 private int count=1; //判别人人对战时下一步下黑子还是白子 private int[][] exist; //生命棋子数组 private Graphics2D g2d; //画笔对象 private HashMap<String, Integer> map=new HashMap<String,Integer>();//创建集合对象,用途是存储每一种棋子相连对应的权值 private FiveChess fiveChess; //生命面板类型的变量 private WhoWin win; //生命判断输赢类的对象 private String mode="人人对战"; //默认的对战模式为人人对战 private int flag=0; //记录下棋的步数 private ArrayList<Chess> list=new ArrayList<Chess>(); //数组队列 存储的Chess类型的对象 /** * 构造方法 * @param fiveChess */public ChessListener(FiveChess fiveChess) {this.fiveChess=fiveChess;}/*** 设置方法,接收数组 * @param exist 存储棋盘上棋子的位置 */public void setExist(int[][] exist){this.exist=exist;win=new WhoWin(exist);}/** * 点击事件处理方法 */public void actionPerformed(ActionEvent e){if(e.getSource() instanceof JRadioButton){ //点击单选按钮 mode=e.getActionCommand();}else if(e.getSource() instanceof JButton){ //点击其他功能按钮 if(e.getActionCommand().equals("开始游戏")){MouseListener[] mls=fiveChess.getMouseListeners();if(mls.length>0){ //如果还有其他监听 ---移除 fiveChess.removeMouseListener(this);}reset(); //调用棋盘回到初始状态的方法 fiveChess.addMouseListener(this); // 为棋盘添加鼠标监听 }else if(e.getActionCommand().equals("悔棋")){ //悔棋的算法 if(mode.equals("人人对战")){if(list.size()>1){ //存储棋子的数组队列长度大于1 Chess chess=list.get(list.size()-1); //获取最后一个被存入数组队列的棋子对象 int r=chess.getR(); //分别获取棋子的x与y坐标 int c=chess.getC();exist[r][c]=0; //设置最后一步所下棋子的位置为空 list.remove(list.size()-1); //移除队列中最后一个棋子对象 fiveChess.repaint(); //调用面板重绘的方法 if(count==1){ //如果悔棋的是黑色方,下一步下棋的还是黑色方 count=-1; //如果悔棋的是白色方,下一步下棋的还是白色方 }else{count=1;}}}else if(mode.equals("人机对战")){ //人机对战,悔一次棋要退两步,1、电脑所下的棋 2、人所下的棋子 if(list.size()>2){ //至少下了两步 Chess chess=list.get(list.size()-2); //获取倒数第二步的棋子对象 int r=chess.getR();int c=chess.getC();exist[r][c]=0; //令其为空 Chess chessAI=list.get(list.size()-1); //获取最后一步棋子对象 r=chessAI.getR();c=chessAI.getC();exist[r][c]=0; //令其为空 list.remove(list.size()-1); //删除数组队列中后两个对象 list.remove(list.size()-1);fiveChess.repaint(); //调用棋子面板的重绘方法 }}}else if(e.getActionCommand().equals("认输")){ //认输的算法 if(flag<10){JOptionPane.showMessageDialog(fiveChess, "总步数小于10,不能认输");}else{if(mode.equals("人人对战")){if(count==1){ //本来应该下黑色棋子了 但是点击了认输,所以白色棋子获胜 JOptionPane.showMessageDialog(fiveChess, "白色棋子获得胜利");}else if(count==-1){ //本来应该下白色棋子了 但是点击了认输,所以黑色棋子获胜 JOptionPane.showMessageDialog(fiveChess, "黑色棋子获得胜利");}}else if(mode.equals("人机对战")){ //电脑不会认输的 JOptionPane.showMessageDialog(fiveChess, "白色棋子AI获得胜利");}fiveChess.removeMouseListener(this); //认输后不能在棋盘上上下棋了 所以要移除棋盘上的鼠标监听 }}}}public void mouseClicked(MouseEvent e){ //鼠标点击事件的处理方法 x=e.getX(); //获取点击位置的x坐标 y=e.getY(); //获取点击位置的y坐标 if(mode.equals("人人对战")){ //调用人人对战方法 pvp(x,y);}else if(mode.equals("人机对战")){ //调用人机对战的方法 pvai(x,y);}}/** * 设置棋盘回到初始状态的方法 */public void reset(){count=1; //默认黑色棋子先下棋 flag=0; //下棋步数重置为0 for(int r=0;r<exist.length;r++){ //遍历二维数组,将所有位置清空 for(int c=0;c<exist[r].length;c++){exist[r][c]=0;}}fiveChess.repaint(); //调用棋盘重绘的方法 }/** * 人人对战的方法 * @param x * @param y */public void pvp(int x,int y){flag++; //步数+1 g=fiveChess.getGraphics(); //从棋盘面板类中获取画布 g2d=(Graphics2D) g;g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);for(int r=0;r<Config.ROWS;r++){ //行 for(int c=0;c<Config.COLUMNS;c++){ //列 if(exist[r][c]==0){ //判断该位置上是否有棋子 if((Math.abs(x-Config.X0-c*Config.SIZE)<Config.SIZE/3.0)&&(Math.abs(y-Config.Y0-r*Config.SIZE)<Config.SIZE/3.0)){//将棋子放到交叉点上,误差为1/3 if(count==1){ //count 为1放置黑子 g2d.setColor(Color.BLACK);exist[r][c]=1; //记录下了黑色棋子r与c的位置 g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);count=-1; //下一次点击时 下白色的棋子 list.add(new Chess(r,c)); //将棋子对象存到数组队列中,保存了棋子的属性 r,c if(win.checkWin()==1){ //判断黑色棋子是否赢了 JOptionPane.showMessageDialog(fiveChess, "黑色棋子获得胜利");fiveChess.removeMouseListener(this); //获胜之后,不允许再在棋盘上下棋子,所以移除鼠标监听 return;}}else if(count==-1){ //放置白子 g2d.setColor(Color.WHITE);exist[r][c]=-1;g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);count=1; //下一步要下黑色棋子 list.add(new Chess(r,c)); //将棋子对象存到数组队列中,保存了棋子的属性 r,c if(win.checkWin()==-1){ //判断白色棋子是否赢了 JOptionPane.showMessageDialog(fiveChess, "白色棋子获得胜利");fiveChess.removeMouseListener(this);return;}}}}}}}/** * 人机对战的方法 * @param x 人所下棋子的横坐标 * @param y 人所下棋子的纵坐标 */public void pvai(int x,int y){flag++; //步数+1 g=fiveChess.getGraphics();g2d=(Graphics2D) g;g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);for(int r=0;r<Config.ROWS;r++){for(int c=0;c<Config.COLUMNS;c++){if(exist[r][c]==0){ //判断该位置上是否有棋子 if((Math.abs(x-Config.X0-c*Config.SIZE)<Config.SIZE/3.0)&&(Math.abs(y-Config.Y0-r*Config.SIZE)<Config.SIZE/3.0)){//将棋子放到交叉点上,误差为1/3 g2d.setColor(Color.BLACK);exist[r][c]=1; //记录下了黑色棋子的位置 g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);list.add(new Chess(r,c)); //队列中添加棋子数组对象 存 r c if(win.checkWin()==1){ //判断黑色棋子是否胜利 JOptionPane.showMessageDialog(fiveChess, "黑色棋子获得胜利");fiveChess.removeMouseListener(this);return;}ai(g2d); //调用ai下棋的方法 if(win.checkWin()==-1){ //ai下的是白色棋子,每下完一步,都要判断一次是否获胜 JOptionPane.showMessageDialog(fiveChess, "白色棋子获得胜利");fiveChess.removeMouseListener(this);return;}}}}}}/** * ai 下棋的方法 * @param g2d */public void ai(Graphics2D g2d){g2d.setColor(Color.WHITE); //设置ai所下棋子的颜色为白色 int[][] weightArray=new int[Config.ROWS][Config.COLUMNS];//创建一个存储权值的二维数组 /** * 设置每种棋子相连情况下的权值 */map.put("010", 1);map.put("0110", 20);map.put("01110", 50);map.put("011110", 500);map.put("0-10", 10);map.put("0-1-10", 30);map.put("0-1-1-10", 70);map.put("0-1-1-1-10", 500);map.put("-110", 1);map.put("-1110", 10);map.put("-11110", 30);map.put("-111110", 500);map.put("1-10", 1);map.put("1-1-10", 10);map.put("1-1-1-10", 30);map.put("1-1-1-1-10", 500);for(int r=0;r<exist.length;r++){ //求出权值 将权值存到数组中 for(int c=0;c<exist[r].length;c++){if(exist[r][c]==0){ //判断是否是空位 String code=countHL(r,c);Integer weight = map.get(code);//获取棋子相连情况对应的权值 if(null != weight){//判断权值不为null weightArray[r][c] += weight;//累加权值 }code=countVU(r,c);weight = map.get(code);//获取棋子相连情况对应的权值 if(null != weight){//判断权值不为null weightArray[r][c] += weight;//累加权值 }code=countLLU(r,c);weight = map.get(code);//获取棋子相连情况对应的权值 if(null != weight){//判断权值不为null weightArray[r][c] += weight;//累加权值 }code=countLRU(r,c);weight = map.get(code);//获取棋子相连情况对应的权值 if(null != weight){//判断权值不为null weightArray[r][c] += weight;//累加权值 }}}}int max=weightArray[0][0]; //找出最大的权值 for(int r=0;r<weightArray.length;r++){for(int c=0;c<weightArray[r].length;c++){if(weightArray[r][c]>max){max=weightArray[r][c];}}}for(int r=0;r<weightArray.length;r++){ //随机取最大权值处所在的点 for(int c=0;c<weightArray[r].length;c++){if(weightArray[r][c]==max&&exist[r][c]==0){ //权值最大且是空位 exist[r][c]=-1;g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);list.add(new Chess(r,c));return;}}}}/** * 水平向左方向统计棋子相连的情况* @param r 行 * @param c 列 * @return */private String countHL(int r,int c){String code = "0"; //默认记录r,c位置的情况 int chess = 0; //记录第一次出现的棋子 for(int c1=c-1;c1>=0;c1--){ //向左统计 if(exist[r][c1]==0){ //表示没有棋子的位置 if(c1+1==c){ //相邻的空位 break;}else{ //空位不相连 code = exist[r][c1] + code;//记录棋子相连的情况 break;}}else{ //表示有棋子 if(chess==0){ //判断是否是第一次出现棋子 code = exist[r][c1] + code; //记录棋子相连的情况 chess = exist[r][c1]; //记录第一次的棋子的颜色 }else if(chess==exist[r][c1]){ //表示之后的棋子和第一次的棋子颜色一致 code = exist[r][c1] + code; //记录棋子相连的情况 }else{ //表示之后的棋子和第一次的棋子颜色不同 code = exist[r][c1] + code; //记录棋子相连的情况 break;}}}return code;}/** * 垂直向上统计棋子相连的情况 * @param r 行 * @param c 列 * @return */private String countVU(int r,int c){String code = "0"; //默认记录r,c位置的情况 int chess = 0; //记录第一次出现的棋子 for(int r1=r-1;r1>=0;r1--){ //向上统计 if(exist[r1][c]==0){ //表示没有棋子 if(r1+1==r){ //相邻的空位 break;}else{ //不相邻的空位 code = exist[r1][c] + code; //记录棋子相连的情况 break;}}else{ //表示有棋子 if(chess==0){ //判断是否是第一次出现棋子 code = exist[r1][c] + code; //记录棋子相连的情况 chess = exist[r1][c]; //记录第一次的棋子的颜色 }else if(chess==exist[r1][c]){ //表示之后的棋子和第一次的棋子颜色一致 code = exist[r1][c] + code; //记录棋子相连的情况 }else{ //表示之后的棋子和第一次的棋子颜色不同 code = exist[r1][c] + code; //记录棋子相连的情况 break;}}}return code;}/** * 正斜(\) 棋子相连统计 * @param r * @param c * @return */private String countLLU(int r,int c){String code = "0"; //默认记录r,c位置的情况 int chess = 0; //记录第一次出现的棋子 for(int r1=r-1,c1=c-1;r1>=0&&c1>0;r1--,c1--){//向上统计 if(exist[r1][c1]==0){ //表示没有棋子 if(r1+1==r&&c1+1==c){ //相邻的空位 break;}else{ //不相邻的空位 code = exist[r1][c1] + code; //记录棋子相连的情况 break;}}else{ //表示有棋子 if(chess==0){ //判断是否是第一次出现棋子 code = exist[r1][c1] + code; //记录棋子相连的情况 chess = exist[r1][c1]; //记录第一次的棋子的颜色 }else if(chess==exist[r1][c1]){ //表示之后的棋子和第一次的棋子颜色一致 code = exist[r1][c1] + code; //记录棋子相连的情况 }else{ //表示之后的棋子和第一次的棋子颜色不同 code = exist[r1][c1] + code; //记录棋子相连的情况 break;}}}return code;}/*** 反斜(/) 棋子相连的统计 * @param r * @param c * @return */private String countLRU(int r,int c){String code = "0"; //默认记录r,c位置的情况 int chess = 0; //记录第一次出现的棋子 for(int r1=r-1,c1=c+1;r1>=0&&c1<Config.COLUMNS;r1--,c1++){if(exist[r1][c1]==0){ //表示没有棋子 if(r1+1==r&&c1-1==c){ //相邻的空位 break;}else{code = exist[r1][c1] + code; //记录棋子相连的情况 break;}}else{ //表示有棋子 if(chess==0){//判断是否是第一次出现棋子 code = exist[r1][c1] + code; //记录棋子相连的情况 chess = exist[r1][c1]; //记录第一次的棋子的颜色 }else if(chess==exist[r1][c1]){ //表示之后的棋子和第一次的棋子颜色一致 code = exist[r1][c1] + code; //记录棋子相连的情况 }else{ //表示之后的棋子和第一次的棋子颜色不同 code = exist[r1][c1] + code; //记录棋子相连的情况 break;}}}return code;}}
三、判断输赢类:
我们注意到,人人对战与人机对战时,每下一步棋都要判断一次输赢,所以这里我把判断输赢的方法单独写到一个类里。
输赢判断规则: 同一颜色棋子5子相连 (共4个方向 水平 垂直 正斜(\) 反斜(/))
package com.xs.chessAI;/*** 判断输赢类* @author Administrator**/public class WhoWin {private int[][] exist;public WhoWin(int exist[][]){this.exist=exist;}/*** 判断输赢的方法*/public int checkWin(){if((rowWin()==1)||(columnWin()==1)||(rightSideWin()==1)||(leftSideWin()==1)){return 1;}else if((rowWin()==-1)||(columnWin()==-1)||(rightSideWin()==-1)||(leftSideWin()==-1)){return -1;}return 0;}/*** 以行的方式赢*/public int rowWin(){for(int i=0;i<Config.ROWS;i++){for(int j=0;j<Config.COLUMNS-4;j++){if(exist[i][j]==-1){if(exist[i][j+1]==-1&&exist[i][j+2]==-1&&exist[i][j+3]==-1&&exist[i][j+4]==-1){return -1;}}if(exist[i][j]==1){if(exist[i][j+1]==1&&exist[i][j+2]==1&&exist[i][j+3]==1&&exist[i][j+4]==1){return 1;}}}}return 0;}/*** 以列的方式赢*/public int columnWin(){for(int i=0;i<Config.ROWS-4;i++){for(int j=0;j<Config.COLUMNS;j++){if(exist[i][j]==-1){if(exist[i+1][j]==-1&&exist[i+2][j]==-1&&exist[i+3][j]==-1&&exist[i+4][j]==-1){return -1;}}if(exist[i][j]==1){if(exist[i+1][j]==1&&exist[i+2][j]==1&&exist[i+3][j]==1&&exist[i+4][j]==1){return 1;}}}}return 0;}/*** 斜的方式赢*/public int rightSideWin(){ //正斜for(int i=0;i<Config.ROWS-4;i++){for(int j=0;j<Config.COLUMNS-4;j++){if(exist[i][j]==-1){if(exist[i+1][j+1]==-1&&exist[i+2][j+2]==-1&&exist[i+3][j+3]==-1&&exist[i+4][j+4]==-1){return -1;}}if(exist[i][j]==1){if(exist[i+1][j+1]==1&&exist[i+2][j+2]==1&&exist[i+3][j+3]==1&&exist[i+4][j+4]==1){return 1;}}}}return 0;}public int leftSideWin(){ //反斜for(int i=4;i<Config.ROWS;i++){for(int j=0;j<Config.COLUMNS-4;j++){if(exist[i][j]==-1){if(exist[i-1][j+1]==-1&&exist[i-2][j+2]==-1&&exist[i-3][j+3]==-1&&exist[i-4][j+4]==-1){return -1;}}if(exist[i][j]==1){if(exist[i-1][j+1]==1&&exist[i-2][j+2]==1&&exist[i-3][j+3]==1&&exist[i-4][j+4]==1){return 1;}}}}return 0;}}
四、棋子类
在悔棋的过程,我们使用队列来实现的,即每下一步棋,我们都要记录这一棋子的位置,添加到队列中,悔棋时,只需删掉这一位置的棋子即可。
故在每次下棋后,应当为队列添加一个棋子对象(记录了行和列),记录这一次下棋的位置,所以这里创建一个棋子类。
package com.xs.chessAI;/*** 棋子类---具有棋子行与列的属性* @author Administrator**/public class Chess {private int r; //行private int c; //列public Chess(int r,int c){this.r=r;this.setC(c);}public int getR() {return r;}public void setR(int r) {this.r = r;}public int getC() {return c;}public void setC(int c) {this.c = c;}}
这样,一个简单的五子棋小游戏制作就完成啦。
暂无评论数据