1.游戏设计思路
最初期的时候刚刚学完画图板的制作,老师让我们做一个五子棋游戏,是人人对战的那种,点击界面,实现绘棋。这很简单,给界面添加监听,点击鼠标,绘制棋子。后来,做到人机对战,就需要给计算机添加一定的算法,让计算机知道在什么地方下棋最有利。我的算法是,遍历整个棋盘,在每一个空位上,进行横、竖、左斜、右斜方向上黑棋和白棋的判断,再根据情况给出一定的权值,权值最高处就是电脑要下棋的交叉点。附件里面有完整的工程,有兴趣的朋友可以下载参考,欢迎交流讨论。
2.游戏主界面
3.具体实现
主界面类
package wuziqi; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; public class mainUI extends JFrame { public Listener d = new Listener(); private int array[][] = new int[Config.ROWS][Config.COLUMNS];//用来存储棋盘上上所有交叉点的情况,0代表没有棋子,1代表该交叉点上是黑棋,-1代表该交叉点上是白棋 public static void main(String[] args) { mainUI wzq = new mainUI();//主界面对象 wzq.ininUI();//初始化 } public void ininUI() { this.setLayout(null);//设为绝对布局 ImageIcon im = new ImageIcon(this.getClass().getResource("img/bg2.jpg"));//右上角的标志图片 JLabel jla = new JLabel(im); jla.setBounds(610, 4, 182, 380);//位置,大小 this.add(jla); Color c = new Color(238, 238, 238); JPanel jp = new JPanel(); jp.setBackground(c);//背景颜色 jp.setBounds(595, 376, 200, 240); this.add(jp); //添加按钮区 String[] str = { "开始游戏", "重新开始", "我要悔棋", "保存游戏", "继续游戏" }; for (int i = 0; i < str.length; i++) { Color b = new Color(220, 220, 220); JButton jb = new JButton(str[i]); jb.addActionListener(d); jb.setBackground(b); jb.setFont(new Font("宋体", Font.BOLD, 18)); jb.setBounds(16, 0 + i * 44, 181, 34); jp.setLayout(null); jp.add(jb); } //标题,大小,位置,关闭时退出 this.setTitle("五子棋人机对战v1.0 ----DESIGNED BY CrazyZhang"); this.setSize(820, 655); this.setDefaultCloseOperation(3); this.setLocationRelativeTo(null); this.setVisible(true); Graphics g = this.getGraphics();//获取画笔对象 this.addMouseListener(d);//添加监听器 d.setArray(array);//set方法传值 d.setG(g); d.setJp(this); } /** * 重写父类的paint方法,在每一次界面重新出现的时候都要刷新一次,也就是线条,棋子重绘一次 */ public void paint(Graphics g) { super.paint(g); for (int i = 0; i < Config.ROWS; i++)//绘制横线 g.drawLine(Config.X0, Config.Y0 + Config.SIZE * i, Config.X0 + 560,Config.Y0 + Config.SIZE * i); for (int j = 0; j < Config.COLUMNS; j++)//绘制纵线 g.drawLine(Config.X0 + Config.SIZE * j, Config.Y0, Config.X0+ Config.SIZE * j, Config.Y0 + 560); //根据array记录的棋盘上的情况,重新绘制棋子 for (int i = 0; i < Config.ROWS; i++) { for (int j = 0; j < Config.COLUMNS; j++) { if (array[i][j] == 1) { g.setColor(Color.BLACK); g.fillOval(Config.SIZE * i + Config.X0 - Config.CHESS_SIZE/ 2, Config.SIZE * j + Config.Y0- Config.CHESS_SIZE / 2, Config.CHESS_SIZE,Config.CHESS_SIZE); } else if (array[i][j] == -1) { g.setColor(Color.WHITE); g.fillOval(Config.SIZE * i + Config.X0 - Config.CHESS_SIZE/ 2, Config.SIZE * j + Config.Y0- Config.CHESS_SIZE / 2, Config.CHESS_SIZE,Config.CHESS_SIZE); } } } } }
监听器类
package wuziqi; import java.awt.Color; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import javax.swing.JOptionPane; public class Listener implements MouseListener, ActionListener { public int flag; private int t[][] = new int[Config.ROWS][Config.COLUMNS]; private int p[][] = new int[Config.ROWS][Config.COLUMNS]; private int HX[] = new int[Config.ROWS * Config.COLUMNS]; private int HY[] = new int[Config.ROWS * Config.COLUMNS]; public Chess e; public mainUI jp; private int array[][]; private Graphics g; private int q = 0, w = 0, d = 0, f = 0, v = 0, h = 0, x1, y1, x2, y2, c, r, count = 0; public void setChess(Chess e) { this.e = e; } public void setG(Graphics g) { this.g = g; } public void setArray(int[][] array) { this.array = array; } public void setJp(mainUI jp) { this.jp = jp; } @Override public void mouseClicked(MouseEvent e) { } /** * 记录该空格左右两边共有多少颗连续的棋子颜色一样,判断输赢时调用 * * @param c * @param r * @return */ public int win1(int c, int r) { int flag = 0; for (int i = r + 1; i < array.length; i++) { if (array[c][r] == array[c][i]) { flag++; } else break; } for (int j = r; j >= 0; j--) { if (array[c][r] == array[c][j]) { flag++; } else break; } return flag; } /** * 记录改空格上下两边共有多少颗连续的棋子颜色一样,判断输赢时调用 * * @param c * @param r * @return */ public int win2(int c, int r) { int flag = 0; for (int i = c + 1; i < array.length; i++) { if (array[c][r] == array[i][r]) { flag++; } else break; } for (int j = c; j >= 0; j--) { if (array[c][r] == array[j][r]) { flag++; } else break; } return flag; } /** * 记录改空格左斜方向上两边共有多少颗连续的棋子颜色一样,判断输赢时调用 * * @param c * @param r * @return */ public int win3(int c, int r) { int flag = 0; for (int i = c + 1, j = r + 1; i < array.length && j < array.length; i++, j++) { if (array[c][r] == array[i][j]) { flag++; } else break; } for (int i = c, j = r; j >= 0 && i >= 0; i--, j--) { if (array[c][r] == array[i][j]) { flag++; } else break; } return flag; } /** * 记录改空格右斜方向上左右两边共有多少颗连续的棋子颜色一样,判断输赢时调用 * * @param c * @param r * @return */ public int win4(int c, int r) { int flag = 0; for (int i = c, j = r; i >= 0 && j < array.length; i--, j++) { if (array[c][r] == array[i][j]) { flag++; } else break; } for (int i = c, j = r; i < array.length && j >= 0; i++, j--) { if (array[c][r] == array[i][j]) { flag++; } else break; } return flag; } public void mousePressed(MouseEvent e) { } /** * 核心算法区,玩家点击鼠标,获取玩家点击的坐标,计算出交叉点所在,然后再该交叉点绘制黑棋,array记录情况 * 轮到白子下棋,计算机遍历棋盘,找到最有利的地方下棋,array记录情况 */ public void mouseReleased(MouseEvent e) { x1 = e.getX();// 获取当前点击的坐标 y1 = e.getY(); int c = (x1 - Config.X0 + Config.SIZE / 2) / Config.SIZE;// 得到行数 int r = (y1 - Config.Y0 + Config.SIZE / 2) / Config.SIZE;// 列数 x1 = Config.X0 + Config.SIZE * c - Config.CHESS_SIZE / 2;// 得到交叉点坐标 y1 = Config.Y0 + Config.SIZE * r - Config.CHESS_SIZE / 2; if (flag == 1 || flag == -1) { if (array[c][r] == 0) { flag = 1;// flag初始化 HX[count] = c; HY[count] = r; count++; g.setColor(Color.BLACK);// 绘制黑棋 g.fillOval(x1, y1, Config.CHESS_SIZE, Config.CHESS_SIZE); array[c][r] = flag;// array记录情况 flag = -1;// flag变换值,现在轮到白子下棋 bestchoose(); } } if (win1(c, r) >= 5 || win2(c, r) >= 5 || win3(c, r) >= 5 || win4(c, r) >= 6) { if (array[c][r] == 1) { JOptionPane.showMessageDialog(null, "黑子赢"); } // 游戏结束之后,遍历棋盘,没下棋的交叉点全部另为2,不能再下棋 for (int i = 0; i < Config.ROWS; i++) for (int j = 0; j < Config.COLUMNS; j++) { if (array[i][j] == 0) array[i][j] = 2; } } } /** * 记录该空格左右两边共有多少颗连续的黑棋 * @param x * @param y * @return 根据情况左右黑棋的情况和空格的情况,返回一个权值 */ public int bCountRow(int x, int y) { int cnt = 0; int flag = 0; for (int i = x + 1; i < array.length; i++) { if (array[i][y] == 1) cnt++; else if (array[i][y] == 0) { flag++; break; } else break; } for (int i = x - 1; i >= 0; i--) { if (array[i][y] == 1) cnt++; else if (array[i][y] == 0) { flag++; break; } else break; } return bCheseeScore(cnt, flag); } /** * 记录该空格上下两边共有多少颗连续的黑棋 * @param x * @param y * @return 根据情况左右黑棋的情况和空格的情况,返回一个权值 */ public int bCountColumn(int x, int y) { int cnt = 0; int flag = 0; for (int j = y + 1; j < array.length; j++) { if (array[x][j] == 1) cnt++; else if (array[x][j] == 0) { flag++; break; } else break; } for (int j = y - 1; j >= 0; j--) { if (array[x][j] == 1) cnt++; else if (array[x][j] == 0) { flag++; break; } else break; } return bCheseeScore(cnt, flag); } /** * 记录该空格右斜两边共有多少颗连续的黑棋 * @param x * @param y * @return 根据情况左右黑棋的情况和空格的情况,返回一个权值 */ public int bCountRU(int x, int y) { int cnt = 0; int flag = 0; for (int i = x + 1, j = y - 1; i < array.length && j >= 0; i++, j--) { if (array[i][j] == 1) cnt++; else if (array[i][j] == 0) { flag++; break; } else break; } for (int i = x - 1, j = y + 1; i >= 0 && j < array.length; i--, j++) { if (array[i][j] == 1) cnt++; else if (array[i][j] == 0) { flag++; break; } else break; } return bCheseeScore(cnt, flag); } /** * 记录该空左斜方向两边共有多少颗连续的黑棋 * @param x * @param y * @return 根据情况左右黑棋的情况和空格的情况,返回一个权值 */ public int bCountLU(int x, int y) { int cnt = 0; int flag = 0; for (int i = x + 1, j = y + 1; i < array.length && j < array.length; i++, j++) { if (array[i][j] == 1) cnt++; else if (array[i][j] == 0) { flag++; break; } else break; } for (int i = x - 1, j = y - 1; i >= 0 && j >= 0; i--, j--) { if (array[i][j] == 1) cnt++; else if (array[i][j] == 0) { flag++; break; } else break; } return bCheseeScore(cnt, flag); } /** * 记录该空格左右两边共有多少颗连续的白棋 * @param x * @param y * @return 根据情况左右白棋的情况和空格的情况,返回一个权值 */ public int wCountRow(int x, int y) { int flag = 0; int cnt = 0; for (int i = x + 1; i < array.length; i++) { if (array[i][y] == -1) cnt++; else if (array[i][y] == 0) { flag++; break; } else break; } for (int i = x - 1; i >= 0; i--) { if (array[i][y] == -1) cnt++; else if (array[i][y] == 0) { flag++; break; } else break; } return wCheseeScore(cnt, flag); } /** * 记录该空格上下两边共有多少颗连续的黑棋 * @param x * @param y * @return 根据情况左右白棋的情况和空格的情况,返回一个权值 */ public int wCountColumn(int x, int y) { int cnt = 0; int flag = 0; for (int j = y + 1; j < array.length; j++) { if (array[x][j] == -1) cnt++; else if (array[x][j] == 0) { flag++; break; } else break; } for (int j = y - 1; j >= 0; j--) { if (array[x][j] == -1) cnt++; else if (array[x][j] == 0) { flag++; break; } else break; } return wCheseeScore(cnt, flag); } /** * 记录该空格右斜两边共有多少颗连续的黑棋 * @param x * @param y * @return 根据情况左右白棋的情况和空格的情况,返回一个权值 */ public int wCountRU(int x, int y) { int cnt = 0; int flag = 0; for (int i = x + 1, j = y - 1; i < array.length && j >= 0; i++, j--) { if (array[i][j] == -1) cnt++; else if (array[i][j] == 0) { flag++; break; } else break; } for (int i = x - 1, j = y + 1; i >= 0 && j < array.length; i--, j++) { if (array[i][j] == -1) cnt++; else if (array[i][j] == 0) { flag++; break; } else break; } return wCheseeScore(cnt, flag); } /** * 记录该空格左斜两边共有多少颗连续的黑棋 * @param x * @param y * @return 根据情况左右白棋的情况和空格的情况,返回一个权值 */ public int wCountLU(int x, int y) { int cnt = 0; int flag = 0; for (int i = x + 1, j = y + 1; i < array.length && j < array.length; i++, j++) { if (array[i][j] == -1) cnt++; else if (array[i][j] == 0) { flag++; break; } else break; } for (int i = x - 1, j = y - 1; i >= 0 && j >= 0; i--, j--) { if (array[i][j] == -1) cnt++; else if (array[i][j] == 0) { flag++; break; } else break; } return wCheseeScore(cnt, flag); } /** * 根据黑棋相连的情况和空格数量,判断权值 * @param cnt * @param flag * @return */ public int bCheseeScore(int cnt, int flag) { int score = 0; if (cnt == 4 || cnt == 5 || cnt == 6 || cnt == 7 || cnt == 8) {// 第1,2,3级 score = 900000; } else if (cnt == 3) { if (flag == 2) { score = 200000; System.out.println(flag); } else if (flag == 1) { score = 50000; } else { score = 500; } } else if (cnt == 2) { if (flag == 2) { score = 50000; } else if (flag == 1) { score = 20000; } else { score = 400; } } else if (cnt == 1) { if (flag == 2) { score = 15000; } else if (flag == 1) { score = 10000; } else { score = 300; } } else { score = 100; } return score; } /** * 根据白棋相连的情况和空格数量,判断权值 * @param cnt * @param flag * @return */ public int wCheseeScore(int cnt, int flag) { int score = 0; if (cnt == 4 || cnt == 5 || cnt == 6 || cnt == 7 || cnt == 8) {// 第1,2,3级 score = 1000000; } else if (cnt == 3) { if (flag == 2) { score = 210000; } else if (flag == 1) { score = 50000; } else { score = 500; } } else if (cnt == 2) { if (flag == 2) { score = 55000; } else if (flag == 1) { score = 20000; } else { score = 400; } } else if (cnt == 1) { if (flag == 2) { score = 15000; } else if (flag == 1) { score = 10000; } else { score = 300; } } else { score = 100; } return score; } /** * 根据权值,选择攻和守的一方作为侧重点, * 以攻、守中,较大者作为该交叉点的最终权值 * 在取得最高权值的交叉点下棋 */ public void bestchoose() { t = new int[Config.ROWS][Config.COLUMNS]; p = new int[Config.ROWS][Config.COLUMNS]; q = 0; w = 0; d = 0; f = 0; v = 0; h = 0; for (int i = 0; i < Config.ROWS; i++) for (int j = 0; j < Config.COLUMNS; j++) { if (array[i][j] == 0) { t[i][j] = bCountRow(i, j) + bCountColumn(i, j) + bCountRU(i, j) + bCountLU(i, j); } } for (int i = 0; i < Config.ROWS; i++) for (int j = 0; j < Config.COLUMNS; j++) { if (array[i][j] == 0) { p[i][j] = wCountRow(i, j) + wCountColumn(i, j) + wCountRU(i, j) + wCountLU(i, j); } } for (int i = 0; i < Config.ROWS; i++) for (int j = 0; j < Config.COLUMNS; j++) { if (t[i][j] > q) { q = t[i][j]; d = i; f = j; } if (p[i][j] > w) { w = p[i][j]; v = i; h = j; } } if (q > w) { c = d; r = f; } else { c = v; r = h; } array[c][r] = flag; x1 = Config.X0 + Config.SIZE * c - Config.CHESS_SIZE / 2; y1 = Config.Y0 + Config.SIZE * r - Config.CHESS_SIZE / 2; g.setColor(Color.WHITE); g.fillOval(x1, y1, Config.CHESS_SIZE, Config.CHESS_SIZE); HX[count] = c; HY[count] = r; count++; flag = 1; if (win1(c, r) >= 5 || win2(c, r) >= 5 || win3(c, r) >= 5 || win4(c, r) >= 6) { JOptionPane.showMessageDialog(null, "白子赢"); for (int i = 0; i < Config.ROWS; i++) for (int j = 0; j < Config.COLUMNS; j++) { if (array[i][j] == 0) array[i][j] = 2; } } } /** * 按钮区 */ public void actionPerformed(ActionEvent e) { String s = e.getActionCommand(); if (s.equals("开始游戏")) { flag = 1; for (int i = 0; i < 15; i++) { for (int j = 0; j < 15; j++) { array[i][j] = 0; } } } else if (s.equals("重新开始")) { for (int i = 0; i < 15; i++) { for (int j = 0; j < 15; j++) { array[i][j] = 0; } } jp.repaint(); } else if (s.equals("我要悔棋")) { this.huiqi(); } else if (s.equals("保存游戏")) { try { this.save(); } catch (IOException e1) { e1.printStackTrace(); } } else if (s.equals("继续游戏")) { this.goon(); } } public void huiqi() { for (int i = 0; i < Config.ROWS; i++) for (int j = 0; j < Config.COLUMNS; j++) { if (array[i][j] == 2) array[i][j] = 0; } if (count >= 0) { array[HX[count - 1]][HY[count - 1]] = 0; jp.repaint(); } count--; } /** * 游戏的保存,点击"保存游戏"时调用,将数组信息存到玩家电脑C盘目录下的text.text文件中 * @throws IOException */ public void save() throws IOException { int count = 0; int[] socket = new int[675]; for (int i = 0; i < array.length; i++) { for (int j = 0; j < array[i].length; j++) { if (array[i][j] != 0) { int newflag = array[i][j]; e = new Chess(i, j, newflag); socket[count] = e.x; count++; socket[count] = e.y; count++; socket[count] = e.newflag; count++; } } } String aryStr = aryToString(socket);// 转 BufferedWriter bw = new BufferedWriter(new FileWriter("C:\\test.txt"));// 存 try { bw.write(aryStr); bw.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 继续游戏,将C盘目录下text.text文件读取出来,转换成array的值,根据array绘制棋子 */ public void goon() { int i = 0; int j = 0; int k = 0; try { BufferedReader br = new BufferedReader(new FileReader( "C:\\test.txt"));// 取 String content = br.readLine(); int[] newAry = strToArray(content.trim()); for (int t = 0; t < newAry.length;) { i = newAry[t]; t++; j = newAry[t]; t++; k = newAry[t]; t++; array[i][j] = k; rpaint(i, j, k); flag = 1; jp.repaint(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public void rpaint(int x, int y, int newflag) { int xm = Config.X0 + Config.SIZE * x - Config.CHESS_SIZE / 2; int ym = Config.Y0 + Config.SIZE * y - Config.CHESS_SIZE / 2; if (newflag == 1) { g.setColor(Color.BLACK); g.fillOval(xm, ym, Config.CHESS_SIZE, Config.CHESS_SIZE); HX[count] = x; HY[count] = y; count++; } else if (newflag == -1) { g.setColor(Color.white); g.fillOval(xm, ym, Config.CHESS_SIZE, Config.CHESS_SIZE); HX[count] = x; HY[count] = y; count++; } } private static int[] strToArray(String str) {// 字符串转数组 String[] strAry = str.split(","); int[] ary = new int[strAry.length]; for (int i = 0; i < strAry.length; i++) { ary[i] = Integer.parseInt(strAry[i]); } return ary; } private static String aryToString(int[] ary) {// 数组转字符串 StringBuilder sb = new StringBuilder(); for (int i = 0; i < ary.length; i++) { sb.append(ary[i]).append(","); } sb.deleteCharAt(sb.length() - 1); return sb.toString(); } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } }
配置类
package wuziqi; public interface Config { public static final int X0 = 34;// 表格左上角起点的x值 public static final int Y0 = 56;// 表格左上角起点的y值 public static final int ROWS = 15;// 棋盘的行数 public static final int COLUMNS = 15;// 棋盘的列数 public static int CHESS_SIZE = 34;// 棋子大小 public static int SIZE = 40;// 单元格大小 }
棋子类
package wuziqi; public class Chess { public int x; public int y; public int newflag; public Chess(int x, int y, int newflag) { super(); this.x = x; this.y = y; this.newflag = newflag; } }
4.总结与期待
这个游戏很简单,适合初学者学习,帮助加深对面向对象编程的理解和方法的调用,类与类之间参数的传递的理解。附件里面有完整的工程,有兴趣的朋友可以下载参考,欢迎交流讨论。