• 中国象棋程序的设计与实现(十二)--棋盘绘制算法(尽管注释非常详细,完全理解仍有难度)


    上几篇中,我们详细介绍了,棋盘类的定义和关键属性,简要介绍了棋盘绘制算法的骨架。

    本篇,我们将详细解读棋盘绘制算法的每一个细节。

    强烈建议,大家结合文章末尾的“棋盘截图”来思考绘制算法细节,不然,很可能会遇到问题。

    有些绘制细节,很难懂,不好描述,不再详细叙述。

    1.绘制算法骨架

     

    /**
         * 绘制棋盘
         * <P>
         * 绘制棋盘背景
         * </P>
         * <P>
         * 10条横线
         * <P>
         * 9条纵线
         * </P>
         * <P>
         * 炮兵卒14个标记
         * </P>
         * <P>
         * 九宫格
         * </P>
         * <P>
         * 楚河漢界
         * </P>
         * <P>
         * 如果有棋子移动,画出2个提示框,每个提示框由8条线组成
         * </P>
         * <P>
         * 绘制可选走法的提示框
         * </P>
         * <P>
         * 绘制竖线标记
         * </P>
         *
         *
         * 根据需要还绘制棋子移动的标记
         */
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            // 绘制棋盘背景
            drawBackgroundImage(g);
    
            Graphics2D g2 = (Graphics2D) g;
            // 兵、卒、炮标记笔画
            BasicStroke bsFlag = new BasicStroke(2);
            // 楚河汉界、棋盘边框笔画
            BasicStroke bsLine = new BasicStroke(2);
    
            // 棋盘线笔画
            BasicStroke bs1 = new BasicStroke(1);
    
            // 绘制直线
            drawLines(g2, bsLine, bs1);
            // 绘制九宫格
            drawJiuGongLines(g2, bs1);
            // 绘制楚河漢界
            drawChuheHanjieString(g2);
            // 绘制炮和兵标记
            drawPaoBingFlag(g2, bsFlag);
    
            // 如果有棋子移动,画出2个提示框,每个提示框由8条线组成
            drawMoveFlag(g2);
            // 绘制可选走法的提示框
            drawWillMoveFlag(g2);
    
            // 设置字体和线宽,为画坐标做准备
            BasicStroke bsOld = new BasicStroke(1);
            g2.setStroke(bsOld);
            g2.setFont(new Font("宋体", Font.PLAIN, 14));
            g2.setColor(new Color(0, 0, 0));
            // 绘制竖线标记
            drawShuXianFlag(g2);
        }
    
    


    2.绘制算法细节

    2.1绘制棋盘背景

      

     /**
         * 绘制棋盘背景
         */
        private void drawBackgroundImage(Graphics g) {
            //获得棋盘背景
            Image image = getBackgroundImage();
            if (image != null) {
                Dimension size = new Dimension(super.getWidth(), super.getHeight());
                //在指定的矩形区域绘制image图像
                g.drawImage(image, 0, 0, size.width, size.height, null);
            }
        }
    
        // 默认不绘制背景图片
        protected Image getBackgroundImage() {
            return null;
        }


    需要特别说明的是,这里用到了“模版方法模式”。

       具体的子类可以重载getBackgroundImage方法,从而可以自定义背景图像。

    2.2绘制直线(10条横线和9条竖线)

      

    private void drawLines(Graphics2D g2, BasicStroke bsLine, BasicStroke bs1) {
            // 10条横线
            for (int j = 1; j <= Y; j++) {
                // 4条需要加粗的横线
                if (j == 1 || j == 5 || j == 6 || j == 10) {
                    g2.setStroke(bsLine);
                    // TODO 重复代码可以提取出来
                    g2.drawLine(chessPoints[1][j].getX(), chessPoints[1][j].getY(),
                            chessPoints[X][j].getX(), chessPoints[X][j].getY());
                }
                // 6条不需要加粗的横线
                else {
                    g2.setStroke(bs1);
                    g2.drawLine(chessPoints[1][j].getX(), chessPoints[1][j].getY(),
                            chessPoints[X][j].getX(), chessPoints[X][j].getY());
                }
            }
    
            // 9条纵线
            for (int i = 1; i <= X; i++) {
                // 中间的纵线
                if (i != 1 && i != X) {
                    g2.setStroke(bs1);
                    //上半区的纵线
                    g2.drawLine(chessPoints[i][1].getX(), chessPoints[i][1].getY(),
                            chessPoints[i][Y - 5].getX(),
                            chessPoints[i][Y - 5].getY());
                    //下半区的纵线
                    g2.drawLine(chessPoints[i][Y - 4].getX(),
                            chessPoints[i][Y - 4].getY(), chessPoints[i][Y].getX(),
                            chessPoints[i][Y].getY());
                }
                // 两边的加粗的纵线
                else {
                    g2.setStroke(bsLine);
                    g2.drawLine(chessPoints[i][1].getX(), chessPoints[i][1].getY(),
                            chessPoints[i][Y].getX(), chessPoints[i][Y].getY());
                }
            }
        }
    
    


    2.3绘制九宫格

      

    private void drawJiuGongLines(Graphics2D g2, BasicStroke bs1) {
            // 红黑双方将帅的九宫格,4条斜线
            g2.setStroke(bs1);
            g2.drawLine(chessPoints[4][1].getX(), chessPoints[4][1].getY(),
                    chessPoints[6][3].getX(), chessPoints[6][3].getY());
            g2.drawLine(chessPoints[6][1].getX(), chessPoints[6][1].getY(),
                    chessPoints[4][3].getX(), chessPoints[4][3].getY());
            g2.drawLine(chessPoints[4][8].getX(), chessPoints[4][8].getY(),
                    chessPoints[6][Y].getX(), chessPoints[6][Y].getY());
            g2.drawLine(chessPoints[4][Y].getX(), chessPoints[4][Y].getY(),
                    chessPoints[6][8].getX(), chessPoints[6][8].getY());
        }
    


    2.4绘制楚河漢界4个汉字

      

    private void drawChuheHanjieString(Graphics2D g2) {
            // 楚河、汉界
            g2.setFont(new Font("宋体", Font.PLAIN, 32));
            g2.drawString("漢 界", chessPoints[2][5].getX(), chessPoints[2][5].getY()
                    + 2 * UNIT_HEIGHT / 3 + 2);
            g2.drawString("楚 河", chessPoints[6][5].getX(), chessPoints[2][5].getY()
                    + 2 * UNIT_HEIGHT / 3 + 2);
        }
    


    2.5绘制炮和兵的位置标记

      

    private void drawPaoBingFlag(Graphics2D g2, BasicStroke bsFlag) {
            // 画炮和兵的位置的标记
            int size = sidePoints.size();
            // 棋子中心点到标记直角边交点的水平距离
            double x = PIECE_WIDTH / 9;
            // 标记的长度
            double side = PIECE_WIDTH / 6;
            for (int i = 0; i < size; i++) {
                double a = sidePoints.get(i).getX();
                double b = sidePoints.get(i).getY();
                g2.setStroke(bsFlag);
                if (i >= 0 && i <= 9) {
                    //绘制中间的炮兵10个棋子
                    drawPBMiddle(g2, x, side, a, b);
                } else if (i == 10 || i == 11) {
                    //右边的1个卒和1个兵 TODO 方法名称不够合理
                    drawPBRight(g2, x, side, a, b);
                } else if (i == 12 || i == 13) {
                    //左边的1个卒和1个兵 TODO 方法名称不够合理
                    drawPBLeft(g2, x, side, a, b);
                }
    
            }
        }
    
    
    //绘制中间的炮兵10个棋子,1个完整的标记由8条线构成
        private void drawPBMiddle(Graphics2D g2, double x, double side, double a,
                double b) {
            // 左上角
            g2.drawLine((int) (a - x), (int) (b - x), (int) (a - x),
                    (int) (b - x - side));
            g2.drawLine((int) (a - x), (int) (b - x), (int) (a - x - side),
                    (int) (b - x));
            // 左下角
            g2.drawLine((int) (a - x), (int) (b + x), (int) (a - x),
                    (int) (b + x + side));
            g2.drawLine((int) (a - x), (int) (b + x), (int) (a - x - side),
                    (int) (b + x));
    
            // 右上角
            g2.drawLine((int) (a + x), (int) (b - x), (int) (a + x),
                    (int) (b - x - side));
            g2.drawLine((int) (a + x), (int) (b - x), (int) (a + x + side),
                    (int) (b - x));
            // 右下角
            g2.drawLine((int) (a + x), (int) (b + x), (int) (a + x),
                    (int) (b + x + side));
            g2.drawLine((int) (a + x), (int) (b + x), (int) (a + x + side),
                    (int) (b + x));
        }
    
    


    2.6如果有棋子移动,画出2个提示框,每个提示框由8条线组成

       先计算出,棋子起始位置的坐标,然后绘制提示框。

       类似于“绘制炮和兵的位置标记”。

    2.7绘制可选走法的提示框

       先计算出,所有可选走法位置的坐标,然后绘制提示框。
       类似于“绘制炮和兵的位置标记”。

    2.8绘制竖线标记

      

     // 默认,竖线标记1到9,一到九,是按照红方在下,黑方在上绘制的。如果子类不应该这样话,应该重载此方法,重新绘制。
        //方便棋手走棋,“馬八进七”。
        protected void drawShuXianFlag(Graphics2D g2) {
            // 绘制上方的1到9
            for (int i = 1; i <= X; i++) {
                g2.drawString("" + i, i * UNIT_WIDTH - 4, UNIT_HEIGHT / 2 - 4);
            }
            // 绘制下方的一到九
            for (int i = 1; i <= X; i++) {
                g2.drawString("" + ChessUtils.numToZi(10 - i), i * UNIT_WIDTH - 4,
                        10 * UNIT_HEIGHT + 34);
            }
        }
    


     

    3.棋盘效果

      绘制棋盘

    4.总结

     棋盘绘制算法的核心思路就是,定制算法骨架,分别实现每一个子算法。

     主要用到的是Java图形界面和绘图类库,包括Swing GUI, Graphics、Graphics2D、BasicStroke。

     如果有疑问,建议,读者多参考Java API文档。

     今后,如有可能,我们再详细介绍这些类库的用法。

    5.痛点

      绘制棋盘是中国象棋程序非常有难度的一个问题。

      限于时间、耐心、表达能力有限,本文仅仅是较为详细地介绍了一部分算法细节。

      如果想要更好的介绍,1种方式是“当面交流”,或者是“图文并茂”。

      可惜,后2种方式,不够现实,太麻烦。

      今后如有可能,我会尝试后面2种方式的。

    相关阅读

    中国象棋程序的设计与实现(零)--原始版源码

    中国象棋程序的设计与实现(一)--项目截图

    中国象棋程序的设计与实现(二)--源码

    中国象棋程序的设计与实现(三)--2012本科毕业论文等重要文档资料

    中国象棋程序的设计与实现(四)-- 一次“流产”的写书计划

    中国象棋程序的设计与实现(五)--回答CSDN读者的一些问题

    中国象棋程序的设计与实现(六)--N皇后问题的算法设计与实现(源码+注释+截图)

    中国象棋程序的设计与实现(七)--心得体会和开发日志

    中国象棋程序的设计与实现(八)-如何构造一个棋子(車馬炮等)

    中国象棋程序的设计与实现(九)–棋子点,棋子的小窝

    中国象棋程序的设计与实现(十)--棋盘的定义和绘制

    原文参见http://FansUnion.cn/articles/2919

  • 相关阅读:
    自己动手写RTP服务器——传输所有格式的视频
    自己动手写RTP服务器——用RTP协议传输TS流
    自己动手写RTP服务器——关于RTP协议
    P2P直播、点播技术学习经验
    开源网络通信库参考
    小明历险记:规则引擎drools教程一
    Drools规则引擎入门指南(一)
    drools规则引擎初探
    《高性能MySQL》笔记-BLOB与TEXT
    业务系统上下游数据一致性检测系统(类似对账系统)
  • 原文地址:https://www.cnblogs.com/qitian1/p/6463508.html
Copyright © 2020-2023  润新知