博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java五子棋项目
阅读量:7136 次
发布时间:2019-06-28

本文共 10881 字,大约阅读时间需要 36 分钟。

java基本入门之后,迎来第一个挑战——五子棋设计

寒假的时候,靠着看java手册,实现了双人对战并判断输赢的功能。但是一直卡在了人机对战上面。

之后随着学习的深入,终于实现。

以下详细的叙述一下整体的设计过程:

首先是五子棋窗口界面的设计,画窗体,加按钮,这些都比较基础,主要是要重写重绘的方法,否则每次改变窗体都会使其变化。

package wuziqi;import java.awt.BorderLayout;import java.awt.Color;import java.awt.Dimension;import java.awt.FlowLayout;import java.awt.Graphics;import java.awt.event.ActionListener;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.JPanel;public class FiveChessUI extends JPanel implements Config {    /**     *五子棋界面     */    private static final long serialVersionUID = 1L;    private static int[][] chesses=new int[ROWS][COLUMNS];        public static void main(String[] args) {        FiveChessUI fcUI = new FiveChessUI();        fcUI.initUI();    }    public void initUI () {        JFrame jf = new JFrame();        jf.setTitle("五子棋v0.5 by xzw");        jf.setSize(700,650);        jf.setLocationRelativeTo(null);                jf.add(this,BorderLayout.CENTER);        this.setBackground(Color.ORANGE);                jf.setDefaultCloseOperation(3);        this.setLayout(new FlowLayout());                JPanel jp2 = new JPanel();        jp2.setPreferredSize(new Dimension(100, 0));        jp2.setBackground(Color.CYAN);        //        JButton jbuStart = new JButton("开始");//        jbuStart.setPreferredSize(new Dimension(70, 70));                JButton jbuReg = new JButton("悔棋");        jbuReg.setPreferredSize(new Dimension(70, 50));                JButton jbuR = new JButton("重来");        jbuR.setPreferredSize(new Dimension(70, 50));                JButton jbup2p = new JButton("双人");        jbup2p.setPreferredSize(new Dimension(70, 70));                JButton jbup2c = new JButton("人机");        jbup2c.setPreferredSize(new Dimension(70, 70));                JLabel la2 = new JLabel("当前执子:black");        jp2.add(jbup2p);        jp2.add(jbup2c);        jp2.add(jbuReg);        jp2.add(jbuR);                jp2.add(la2);                jf.add(jp2,BorderLayout.EAST);        jf.setVisible(true);                Graphics g = this.getGraphics();                ChessListener e = new ChessListener(g,chesses,this);        jbup2p.addActionListener(e);        jbup2c.addActionListener(e);        jbuReg.addActionListener(e);        jbuR.addActionListener(e);        }    /**     * 重写重绘方法     */    public void paint(Graphics g){        //调用父类的重绘窗体        super.paint(g);        //重绘窗体的同时绘制棋盘和棋子        drawChessTable(g);        drawChesses(g);    }    //画棋盘    public void drawChessTable (Graphics g){        g.setColor(Color.BLACK);        for (int i=0;i

其次是界面鼠标监听器的设计,窗体被分为2个界面,左边的界面作为棋盘,右边的界面用作放按钮,模式,悔棋,重来等按钮都放在上面。

在做这里的时候意识到自己还不会传参,因此要在不同类里对同一个对象进行修改就会很困难。于是我向请教学会了传参的方法,即构造函数传参法。在一个类的构造函数的参数里添加需要传递的参数,并声明this.var=var(var即为参数)然后在另一个类里调用这个类的方法时,声明对象时加入要传的参数,即实现了参数传递。

通过继承mouseAdapter类,重写该类的mouseReleased的方法,实现鼠标点击后画一个棋子。(重写方法时要求名称相同,参数相同,返回值相同)同时,要想做到在鼠标点击的附近的交叉点上画棋子,需要先计算出鼠标点击处的行列坐标。

定义一与棋盘相同大小的二维数组,用来存放棋子,每在一个交叉点下一颗棋子,就在数组相应位置将其置为1或2,便于之后的判断输赢以及人机算法的实现。另加一个变量判断所下棋子的黑白。

public ChessListener(Graphics g, int[][] chesses,JPanel jp) {        this.g = g;        this.chesses = chesses;        this.jp=jp;        a = new Check(chesses);    }    int flag=0;    int r=0,c=0;    public void mouseReleased(MouseEvent e) {        // 得到鼠标事件发生的时候光标的位置        int x = e.getX();        int y = e.getY();        r = (x - X0 + CHESS_SIZE / 2) / CHESS_SIZE;        c = (y - Y0 + CHESS_SIZE / 2) / CHESS_SIZE;        if (chesses[r][c] == 0) {            if (count == 0) {                chesses[r][c] = 1;                g.setColor(Color.BLACK);                g.fillOval(X0 + r * CHESS_SIZE - CHESS_SIZE / 2, Y0 + c * CHESS_SIZE - CHESS_SIZE / 2,                        CHESS_SIZE, CHESS_SIZE);                count++;            } else if (count == 1) {                chesses[r][c] = 2;                g.setColor(Color.WHITE);                g.fillOval(X0 + r * CHESS_SIZE - CHESS_SIZE / 2, Y0 + c * CHESS_SIZE - CHESS_SIZE / 2,                        CHESS_SIZE, CHESS_SIZE);                count--;            }                        }        if (a.validateChess(r, c)) {            if (count == 0) {                JOptionPane.showMessageDialog(null, "WhiteWin");                jp.removeMouseListener(this);                            } else {                JOptionPane.showMessageDialog(null, "BlackWin");                jp.removeMouseListener(this);            }        }    }

然后就是五子棋判断输赢的方法了。原理很简单,只要对每次下的子的四个方向判断是否有5个子连在一起即可。只要中间有一个颜色不同,立即break。


几天后发现,这里的判断输赢的方法有些不合理,斜向的判断方法有问题。有时会出现斜向4个棋子就被判赢了,经过

观察发现,斜向的两个方向我把合在了一起。所以就会出现4+1=5,判断赢的情况。
改进后代码如下:

package wuziqi;public class Check {    private int[][] chesses;    public Check(int[][] chesses) {        this.chesses = chesses;    }    public boolean validateChess(int x, int y) {        boolean state = false;        if (checkRow(x, y) == 5||checkColumn(x,y)==5||checkDiagonal1(x,y)==5||checkDiagonal2(x,y)==5)            state = true;//        System.out.print(chesses.length);        return state;    }        public int checkRow(int x,int y) {        int count=0;        for (int i=x+1;i
=0;i--) {//go left if (chesses[i][y]==chesses[x][y]) { count++; } else break; } return count; } public int checkColumn(int x,int y) { int count=0; for (int i=y+1;i
=0;i--) {//go up if (chesses[x][i]==chesses[x][y]) { count++; } else break; } return count; } public int checkDiagonal1(int x,int y) { int count=1;for(inti=1;x+i
=0&&y-i>=0;i++) {//go left up if (chesses[x-i][y-i]==chesses[x][y]) { count++; } else break; } return count; } public int checkDiagonal2(int x,int y) { int count=1; for (int i=1;x+i
=0;i++) {//go right up if (chesses[x+i][y-i]==chesses[x][y]) { count++; } else break; } for (int i=1;x-i>=0&&y+i

接下来,就是花费我时间最长的人机算法了。首先要让电脑实现下棋,而且是在相对正常的位置落子,即要通过判断棋盘上棋子的形势。由于二维数组中1代表黑棋,2代表白棋,1,2的数字集合即可代表棋盘上的棋子情况,可以对整个棋盘数组进行遍历,得到每一个可以下的位置的八个方向的棋子情况。因此可以让每一种情况对应一种权值,权值越大,说明该位置越危险,最后只需找到权值最大的那个位置,落子,即可。

string来代表棋子情况,数组存权值,建立一个同时存string和int的的hashmap。并预存好每个情况对应的权值。


权值修改:(使电脑更智能)

public ChessAI(int chesses[][], int chessValue[][]) {        this.chesses = chesses;        this.chessValue = chessValue;        //black        hm.put("1", 11);        hm.put("11", 110);        hm.put("111", 1200);        hm.put("1111", 11000);                hm.put("12", 11);        hm.put("112", 110);        hm.put("1112", 1100);        hm.put("11112", 11000);                hm.put("21", 11);        hm.put("211", 110);        hm.put("2111", 1100);        hm.put("21111", 11000);                //white        hm.put("2", 10);        hm.put("22", 100);        hm.put("222", 1100);        hm.put("2222", 10000);                hm.put("221", 100);        hm.put("2221", 1000);        hm.put("22221", 10000);        hm.put("122", 100);        hm.put("1222", 1000);        hm.put("12222", 10000);    }``每个位置,八个方向遍历,存入权值数组。
public void AI() {        for (int i = 0; i < ROWS; i++) {            for (int j = 0; j < COLUMNS; j++) {                if (chesses[i][j] == 0) {                    String code = "";                    color = 0;                    // 向东                    for (int k = i + 1; k < ROWS; k++) {                        if (chesses[k][j] == 0) {// 为空跳出循环                            break;                        } else {                            if (color == 0) {// 用color记住开始的地方棋子的颜色                                color = chesses[k][j];                                code += chesses[k][j];                            } else if (chesses[k][j] == color) {// 颜色相同                                code += chesses[k][j];                            } else {// 颜色不同                                code += chesses[k][j];                                break;                            }                        }                    }//                     System.out.print(code);                    Integer value = hm.get(code);                    if (value != null) {                        chessValue[i][j] += value;                    }                    // 向西                    code = "";                    color = 0
// south-east                    code = "";                    color = 0;                    for (int p = 1; i + p < ROWS && j + p < COLUMNS; p++) {                        if (chesses[i + p][j + p] == 0) {                            break;                        } else {                            if (color == 0) {// 开始的地方棋子的颜色                                color = chesses[i + p][j + p];                                code += chesses[i + p][j + p];                            } else if (chesses[i + p][j + p] == color) {                                code += chesses[i + p][j + p];                            } else {                                code += chesses[i + p][j + p];                                break;                            }                        }                    }                    value = hm.get(code);                    if (value != null) {                        chessValue[i][j] += value;                    }

以及接下来ai监听器的设计,先由玩家自己下黑子之后,判断输赢。再调用ai算法,获取权值最大的点,然后在该点画白子,下完之后立即清空权值数组。以便于下次下子时,重新统计。

public void mouseReleased(MouseEvent e) {        int x = e.getX();        int y = e.getY();        r = (x - X0 + CHESS_SIZE / 2) / CHESS_SIZE;        c = (y - Y0 + CHESS_SIZE / 2) / CHESS_SIZE;        if (chesses[r][c] == 0 && count == 0) {            chesses[r][c] = 1;            g.setColor(Color.BLACK);            g.fillOval(X0 + r * CHESS_SIZE - CHESS_SIZE / 2, Y0 + c * CHESS_SIZE - CHESS_SIZE / 2, CHESS_SIZE,                    CHESS_SIZE);            count++;        }        if (a.validateChess(r, c)) {            JOptionPane.showMessageDialog(null, "BlackWin");            jp.removeMouseListener(this);        }        else {        ai.AI();        r1 = ai.getr(chessValue);        c1 = ai.getc(chessValue);        if (chesses[r1][c1] == 0 && count == 1) {            chesses[r1][c1] = 2;            g.setColor(Color.WHITE);            g.fillOval(X0 + r1 * CHESS_SIZE - CHESS_SIZE / 2, Y0 + c1 * CHESS_SIZE - CHESS_SIZE / 2, CHESS_SIZE,                    CHESS_SIZE);            count--;        }        if (a.validateChess(r1, c1)) {            JOptionPane.showMessageDialog(null, "WhiteWin");            jp.removeMouseListener(this);        }        }        // 电脑下完后清空        for (int i1 = 0; i1 < ROWS; i1++) {            for (int j1 = 0; j1 < COLUMNS; j1++) {                chessValue[i1][j1] = 0;            }        }    }

游戏效果图

至此,五子棋项目设计完成。但实际下的结果电脑有时候下的子还是不太科学,希望之后还能改善让难度更高些。

你可能感兴趣的文章
第四章:Redis 慢查询日志系统
查看>>
【真实案例分享】面对BAT大厂的竞争对手时,小公司Java工程师是如何败北的?【石杉的架构笔记】...
查看>>
Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例
查看>>
Gradle传给虚拟机的参数
查看>>
聊一聊RPC
查看>>
Redis 概念以及底层数据结构
查看>>
Docker容器中运行.Net Core应用程序
查看>>
通过mysql工具能连接上docker下的mysql,部署的工程连接就报错。
查看>>
兄dei,是时候给你的项目做一波优化了~
查看>>
数据库事务的方方面面
查看>>
Python爬虫实战之爬取链家广州房价_01简单的单页爬虫
查看>>
Chrome 性能监测
查看>>
LocalDateTime和Date互相转换
查看>>
基于Serverless架构最新应用场景详解
查看>>
[BTCC] 要“工程师”“工程师”“工程师”
查看>>
阻塞IO
查看>>
信用算力实现金融级数据服务的实践
查看>>
Xcode配置测试环境和线上环境
查看>>
三大主流软件负载均衡器对比(LVS 、 Nginx 、Haproxy)
查看>>
学习技能总结:
查看>>