一 主框架的创建及棋盘的绘制
①主框架函数的创建
public class ChessUI extends JFrame { public static void main(String[] args) { ChessUI ui= new ChessUI(); ui.initChessUI(); } public void initChessUI(){ this.setSize(600,600); this.setTitle("五子棋"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JPanel panel = new MyPanel(); //设置面板颜色 panel.setBackground(new Color(200,200,100)); //为面板添加鼠标监听器 ChessListener lis = new ChessListener(panel); panel.addMouseListener(lis); this.add(panel); this.setVisible(true); } }
②因为五子棋制作时一些数据比不可少 且每个类都要用到 所以定义一个接口 来存放数据
public interface Config { int X0 = 40, Y0 = 40;// 棋盘左上角的起始点的坐标 int ROWS = 15, COLS = 15;// 棋盘的行和列 int SIZE = 36;// 单元格大小 int CHESS_SIZE = 30;// 棋子大小 //定义一个数组用来保存棋盘的状态 byte[][] CHESSES = new byte[ROWS][COLS]; }
注 byte CHESSES数组 用来存放棋盘每个位置的状态 没有棋子时时0 ,黑棋子时是1,白棋子时是-1,数组中每个元素都对应棋盘上相应的位置
③panel面板上 用来绘制棋盘 棋子 所以必须重写panit方法 且要加上MouseListener监听器
定义一个MyPanel类 来继承 Panel
public class MyPanel extends JPanel implements Config{ @Override public void paint(Graphics g) { // TODO Auto-generated method stub super.paint(g); } }
④在Mypanel类中 paint函数里 画棋盘
for (int i = 0; i < ROWS; i++) { g.drawLine(X0, Y0 + i * SIZE, X0 + (COLS-1) * SIZE, Y0 + i * SIZE); } for (int i = 0; i < COLS; i++) { g.drawLine(X0 + SIZE * i, Y0, X0 + SIZE * i, Y0 + SIZE* (ROWS - 1)); }
二 鼠标监听器的添加
为panel面板添加鼠标监听器 当鼠标点击棋盘时 在棋盘上绘制 棋子
①
public class ChessListener implements MouseListener, Config { public JPanel MyPanel; public Graphics g; private byte flag = 1; // 记录棋子颜色 private boolean isdraw = true; //判断是否继续绘制 public ChessListener(JPanel MyPanel) { this.MyPanel = MyPanel; } @Override public void mousePressed(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mouseReleased(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mouseEntered(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mouseExited(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mouseClicked(MouseEvent e) { // TODO Auto-generated method stub } }
注 通过构造函数来获得panel面板 来获得Graphics 来绘制
②在ChessListener中添加函数 用来绘制棋子
public void putChess(int r, int c, byte flag) { // 用数组记录绘制的位置 颜色 CHESSES[r][c] = flag; // 当flag==1时 绘制黑子 if (flag == 1) { g.setColor(Color.black); flag = -1; } else { g.setColor(Color.white); flag = 1; } // 以该交叉点坐标为圆心放棋子 g.fillOval(c * SIZE + X0 - CHESS_SIZE / 2, r * SIZE + Y0 - CHESS_SIZE / 2, CHESS_SIZE, CHESS_SIZE); }
注:通过参数来传递要绘制棋子的位置 类型(颜色)再以该位置为圆心画圆
③在监听器中添加判断输赢的方法
通过扫描最后一次放子的位置 横向扫描 纵向扫描 左斜扫描 右斜扫描
来判断是否有5个相同颜色的棋子在同一条直线上
具体代码如下
// 判断获胜函数 public void Judge(int xx, int yy) { int fg = CHESSES[xx][yy]; String winner = null; if (fg == 1) winner = "黑子"; else if (fg == -1) winner = "白子"; // 设置计数器 int count = 0; // ********************扫描纵向棋子************************ // 扫描当前棋子下面4个棋子 for (int i = 1; i <= 4; i++) { if (xx + i >= 0 && xx + i < ROWS) { if (CHESSES[xx + i][yy] == fg) { count++; // 如果是相同棋子 count加1 } else break; // 否则停止扫描 } } // 扫描当前棋子上面4个棋子 for (int i = 1; i <= 4; i++) { if (xx - i >= 0 && xx - i < ROWS) { if (CHESSES[xx - i][yy] == fg) { count++; } else break; } } if (count == 4) { isdraw = false; JOptionPane.showMessageDialog(null, "1恭喜" + winner + "获胜"); return; } // ********************扫描横向棋子************************ count = 0; for (int i = 1; i <= 4; i++) { if (yy + i >= 0 && yy + i < COLS) { if (CHESSES[xx][yy + i] == fg) { count++; // 如果是相同棋子 count加1 } else // 否则停止扫描 break; } } for (int i = 1; i <= 4; i++) { if (yy - i >= 0 && yy - i < COLS) { if (CHESSES[xx][yy - i] == fg) { count++; } else break; } } if (count == 4) { isdraw = false; JOptionPane.showMessageDialog(null, "2恭喜" + winner + "获胜"); return; } // ********************扫描右斜向棋子************************ count = 0; // 扫描斜下 for (int i = 1; i <= 4; i++) { if (xx + i >= 0 && xx + i < ROWS && yy + i >= 0 && yy + i < COLS) { if (CHESSES[xx + i][yy + i] == fg) { count++; } else break; } } // 扫描斜上 for (int i = 1; i <= 4; i++) { if (xx - i >= 0 && xx - i < ROWS && yy - i >= 0 && yy - i < COLS) { if (CHESSES[xx - i][yy - i] == fg) { count++; } else break; } } if (count == 4) { isdraw = false; JOptionPane.showMessageDialog(null, "3恭喜" + winner + "获胜"); return; } // ********************扫描左斜向棋子************************ count = 0; // 扫描斜下 for (int i = 1; i <= 4; i++) { if (xx + i >= 0 && xx + i < ROWS && yy - i >= 0 && yy - i < COLS) { if (CHESSES[xx + i][yy - i] == fg) { count++; } else break; } } // 扫描斜上 for (int i = 1; i <= 4; i++) { if (xx - i >= 0 && xx - i < ROWS && yy + i >= 0 && yy + i < COLS) { if (CHESSES[xx - i][yy + i] == fg) { count++; } else break; } } if (count == 4) { isdraw = false; JOptionPane.showMessageDialog(null, "4恭喜" + winner + "获胜"); return; } }
注:①参数是刚放下棋子的位置 flag为棋子类型(颜色)
②每次扫描右分为两个阶段
④实现监听器中mousePressed函数 当鼠标点击下后 要在棋盘上绘制棋子
public void mousePressed(MouseEvent e) { // 获得鼠标的位置 int x = e.getX(); int y = e.getY(); // 获得和该位置最近的交叉点的坐标 int r = (y - Y0) / SIZE; int c = (x - X0) / SIZE; if ((y - Y0) % SIZE > SIZE / 2) { r++; } if ((x - X0) % SIZE > SIZE / 2) { c++; } if (!isOver && r >= 0 && r < ROWS && c >= 0 && c < COLS && CHESSES[r][c] == 0) { // 计算交叉点坐标 int x1 = X0 + SIZE * c; int y1 = Y0 + SIZE * r; // 将棋子状态保存起来 CHESSES[r][c] = flag; if (flag == 1) { g.setColor(Color.BLACK); flag = -1; } else { g.setColor(Color.WHITE); flag = 1; } putChess(r, c, flag); } }
注:①要在putChess函数中 添加判断输赢函数Judge
②通过一系列运算 将点击时的坐标 转换为要绘制在棋盘上的坐标 再通过putChess函数来绘制棋子
以上只基本实现了人人对战
我自己做了一个简易版的人机对战
三 添加机器算法
①添加机器人 Robot类 用来计算出机器要下棋子的坐标
public class Robot implements Config { Point p;// 机器放棋子的位置 // 如何根据棋盘的局势计算放子位置 public Point checkPoint() { } }
②要实现checkPoint函数 我又创建了一个类 CheckChess类
该类中有四个函数 分别扫描指定位置4个方向 并返回相同颜色棋子个数
具体代码如下:
public class CheckChess implements Config { public int flag = -1; public CheckChess(int flag) { this.flag = flag; } public void Setflag(int flag){ this.flag = flag; } // 判断当前位置横向相同的棋子个数 public int HengCheck(int r, int c) { int count = 0; for (int i = c + 1; i < COLS; i++) { if (CHESSES[r][i] == flag) { count++; // 如果是相同棋子 count加1 } else // 否则停止扫描 break; } for (int i = c - 1; i >= 0; i--) { if (CHESSES[r][i] == flag) { count++; } else break; } return count; } // 判断当前位置竖向的棋子个数 public int ShuCheck(int r, int c) { // 扫描当前棋子下面棋子 int count = 0; for (int i = r + 1; i < ROWS; i++) { if (CHESSES[i][c] == flag) { count++; // 如果是相同棋子 count加1 } else break; // 否则停止扫描 } // 扫描当前棋子上面棋子 for (int i = r - 1; i >= 0; i--) { if (CHESSES[i][c] == flag) { count++; } else break; } return count; } // 判断当前位置左斜的棋子个数 public int LCheck45(int r, int c) { int count = 0; for (int i = r - 1, j = c + 1; i >= 0 && j < COLS; i--, j++) { if (CHESSES[i][j] == flag) { count++; } else { break; } } for (int i = r + 1, j = c - 1; i < ROWS && j >= 0; i++, j--) { if (CHESSES[i][j] == flag) { count++; } else { break; } } return count; } // 判断当前位置左斜的棋子个数 public int RCheck45(int r, int c) { int count = 0; for (int i = r - 1, j = c - 1; i >= 0 && j >=0; i--, j--) { if (CHESSES[i][j] == flag) { count++; } else { break; } } for (int i = r + 1, j = c + 1; i < ROWS && j <COLS; i++, j++) { if (CHESSES[i][j] == flag) { count++; } else { break; } } return count; } }
注; flag为要扫描棋子的颜色
③实现Robot类中 checkPoint函数
// 如何根据棋盘的局势计算放子位置 public Point checkPoint() { CheckChess check = new CheckChess(-1); // 扫描棋盘上没放子的地方 周围有4个一样的棋子 for (int r = 0; r < ROWS; r++) { for (int c = 0; c < COLS; c++) { if (CHESSES[r][c] == 0) { // 寻找横纵斜4个方向是否有4个相同的白子 check.Setflag(-1); if (check.HengCheck(r, c) == 4 || check.ShuCheck(r, c) == 4 || check.RCheck45(r, c) == 4 || check.LCheck45(r, c) == 4) return new Point(r, c); check.Setflag(1); if (check.HengCheck(r, c) == 4 || check.ShuCheck(r, c) == 4 || check.RCheck45(r, c) == 4 || check.LCheck45(r, c) == 4) return new Point(r, c); } } } // 扫描棋盘上没放子的地方 周围有3个一样的棋子 check.Setflag(-1); for (int r = 0; r < ROWS; r++) { for (int c = 0; c < COLS; c++) { if (CHESSES[r][c] == 0) { // 寻找横纵斜4个方向是否有3个相同的白子 check.Setflag(-1); if (check.HengCheck(r, c) == 3 || check.ShuCheck(r, c) == 3 || check.RCheck45(r, c) == 3 || check.LCheck45(r, c) == 3) return new Point(r, c); check.Setflag(1); if (check.HengCheck(r, c) == 3 || check.ShuCheck(r, c) == 3 || check.RCheck45(r, c) == 3 || check.LCheck45(r, c) == 3) return new Point(r, c); } } } // 扫描棋盘上没放子的地方 周围有2个一样的棋子 for (int r = 0; r < ROWS; r++) { for (int c = 0; c < COLS; c++) { if (CHESSES[r][c] == 0) { // 寻找横纵斜4个方向是否有2个相同的白子 check.Setflag(-1); if (check.HengCheck(r, c) == 2 || check.ShuCheck(r, c) == 2 || check.RCheck45(r, c) == 2 || check.LCheck45(r, c) == 2) return new Point(r, c); } } } // 扫描棋盘上没放子的地方 周围有1个一样的棋子 for (int r = 0; r < ROWS; r++) { for (int c = 0; c < COLS; c++) { if (CHESSES[r][c] == 0) { // 寻找横纵斜4个方向是否有1个相同的白子 check.Setflag(-1); if (check.HengCheck(r, c) == 1 || check.ShuCheck(r, c) == 1 || check.RCheck45(r, c) == 1 || check.LCheck45(r, c) == 1) return new Point(r, c); } } } return new Point(7, 7); }
注:① 函数返回的时机器要下棋子在棋盘上的位置
② 该函数只是最简易的算法 先逐个扫描棋盘 当扫描到没有放棋子的位置时 判断这个位置4个方向有没有4相同白色的棋子 如果有则在返回该位置 如果没有就判断该位置有没有4个相同的黑色棋子 如果有则返回该位置 没有就继续扫描。当扫描完棋盘后还是没有返回位置,就再重新扫描棋盘 这次判断的是 是否具有3个相同的棋子 以此类推
③最后返回的是白子的起始位置
④修改监听器中mousePressed函数
public void mousePressed(MouseEvent e) { // TODO Auto-generated method stub g = MyPanel.getGraphics(); int x, y; // 按下鼠标时坐标 x = e.getX(); y = e.getY(); int r = (y - Y0) / SIZE; // 行数 int c = (x - X0) / SIZE; // 列数 if ((x - X0) % SIZE > SIZE / 2) { c++; } if ((y - Y0) % SIZE > SIZE / 2) { r++; } if (r >= 0 && r < ROWS && c >= 0 && c < COLS && CHESSES[r][c] == 0 && isdraw) { // 绘制玩家下的棋子 putChess(r, c, (byte) 1); // 产生机器要下的棋子坐标 并绘制棋子 Point p = robot.checkPoint(); putChess(p.x, p.y, (byte) -1); } }
四 重写MyPanel中panit方法
public void paint(Graphics g) { // TODO Auto-generated method stub super.paint(g); for (int i = 0; i < ROWS; i++) { g.drawLine(X0, Y0 + i * SIZE, X0 + (COLS-1) * SIZE, Y0 + i * SIZE); } for (int i = 0; i < COLS; i++) { g.drawLine(X0 + SIZE * i, Y0, X0 + SIZE * i, Y0 + SIZE* (ROWS - 1)); } for(int i=0;i<ROWS;i++){ for(int j=0;j<ROWS;j++){ if(CHESSES[i][j]!=0){ switch ((int)CHESSES[i][j]){ case 1: g.setColor(Color.black); break; case -1: g.setColor(Color.white); break; } g.fillOval(j * SIZE + X0 - CHESS_SIZE / 2, i * SIZE + Y0- CHESS_SIZE / 2, CHESS_SIZE, CHESS_SIZE); } } } }
上面过程基本实现了五子棋方法 添加了简单的机器人 但还存在许多缺陷 比如机器算法没有算出放棋子的最优位置 机器算法起始位置都是一样 的 等 这些bug还有待我去完善