文档章节

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

FansUnion
 FansUnion
发布于 2015/05/03 01:31
字数 1958
阅读 42
收藏 0

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

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

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

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

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

© 著作权归作者所有

FansUnion
粉丝 60
博文 858
码字总数 825464
作品 0
丰台
高级程序员
私信 提问
科普:alphago是什么

鉴于大部分人类对alphago的认识: 1:Alphago有什么了不起的?不就是算得快吗,ibm早在20年前就通过象棋战胜人类了,又是Google的一次营销。 2:alphago 实现人工智能了,电脑马上要超过人类占...

cuibo1123
2016/03/15
0
0
人工智能:博弈--人机中国象棋

为了应付某人的毕设,研究过一段时间的人机象棋,现来谈谈详细的算法思路和流程。 注:本文没有任何干货源码,写过二层遍历、基本评价函数与所谓“深度学习”算法下的人机象棋,棋力之弱小,...

执契
2018/11/10
0
0
不只是围棋!AlphaGo Zero之后DeepMind推出泛化强化学习算法AlphaZero

  选自arXiv   作者:David Silver等   机器之心编译      在 DeepMind 发表 Nature 论文介绍 AlphaGo Zero 之后,这家公司一直在寻求将这种强大算法泛化到其他任务中的可能性。昨...

机器之心
2017/12/07
0
0
[译] AlphaGo 的确是一个大事件

[译] AlphaGo 的确是一个大事件 转自:http://www.jianshu.com/p/157a15de47df 字数3797 阅读696 评论0 喜欢4 作者:Michael Nielsen,源地址:https://www.quantamagazine.org/20160329-why......

wangxiaocvpr
2016/08/01
0
0
Java游戏开发——中国象棋联机版

游戏介绍: 中国象棋是起源于中国的一种棋戏,属于二人对抗性游戏的一种,在中国有着悠久的历史。由于规则简单,趣味性强,成为流行极为广泛的棋类游戏。 中国象棋使用方形格状棋盘及红黑二色...

墨染秦月
01/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

springboot 整合redis

springboot整合redis官方是有文档的: 英文看不懂可以翻译,代码应该看得懂, 这个是自动注入的。当然也可以xml注入,手动配置。 整合步骤: pom文件: <!-- spring boot web --> ...

jason_kiss
10分钟前
0
0
手机耗电问题,大部分是没有正确使用这个“锁”

当安卓设备闲置时,设备很快会进入休眠状态,以达到省电和减少CPU占用的目的。但有些应用在手机灭屏甚至系统休眠时,依然频繁占用CPU处理事件或唤醒屏幕提示用户消息,这类行为会导致手机耗电...

安卓绿色联盟
11分钟前
0
0
UI 设计中的渐变

>**简评:**渐变是通过两种或多种不同的色彩来绘制一个元素,同时在颜色的交界处进行衰减变化的一种设计。从拟物到扁平再到渐变,人们慢慢发现它能创造出从未有过的一种色彩感觉 —— 独特、...

极光推送
17分钟前
0
0
powerdesigner name 转注释vb脚本

Option Explicit ValidationMode = True InteractiveMode = im_BatchDim mdl ' the current model' get the current active model Set mdl = ......

zhu97
21分钟前
0
0
V2Ray的安装与使用

1 1. bash <(curl -s -L https://git.io/v2ray.sh)yum update -y && yum install curl -y安装好 curl 之后就能安装脚本了输入快捷管理命令v2ray后,开始进行v2ray服务端配置...

吕湘颖
24分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部