JS 中国象棋程序(0):界面设计

“JavaScript中国象棋程序” 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序。希望通过这个系列,我们对博弈程序的算法有一定的了解。同时,我们也将构建出一个不错的中国象棋程序。

程序的最终效果点击这里查看

在进入正题之前,本节是一些闲聊。如果你急切想进入正题,请跳过本节。

我学习中国象棋程序的历程

最初是买了本书《C/C++中国象棋程序入门与提高》。这是本好书,写得细致、透彻,我很愉快地读完了前6章,毫无压力。这6章讲解了局面表示、走法生成、局面评估、基本搜索算法等内容。遗憾的是,第7章我读不下去了。第7章是在讲解,如何使用VC6.0设计图形用户界面。天啊,我是一个web程序员,我只想了解一下象棋程序的设计思想以及算法,我实在不想去使用陈旧的VC6.0来学习windows GUI编程啊。
幸运的是,我在这本书最后一页的参考文献里,发现了象棋百科全书网。在这家网站的github仓库,发现了一个JavaScript版本的中国象棋软件,而且性能还不错。使用HTML + JavaScript来设计界面,自然是简单了很多啊,这样就能集中精力去学习象棋程序的算法了。

为什么选择JavaScript

本教程之所以选择JavaScript,讲解JavaScript版本的中国象棋程序,我有以下几个理由吧:
1、这个JavaScript版本的中国象棋程序,性能还不错。
2、界面设计简单,可以把主要精力用在对算法的学习上。
3、不用搭建环境。只要有文本编辑器(比如notepad++)和浏览器(最好是chrome吧),就足够了。
4、程序用到的都是很基础的JavaScript语法,应该没有语法方面的障碍。

项目初衷

曾经读到过一个教程手把手教你构建 C 语言编译器,我觉得很有意思。于是我就仿照这它的格式,写下了这个中国象棋程序教程。本教程的绝大部分思想都来自《C/C++中国象棋程序入门与提高》和象棋百科全书网象棋百科全书网上面还有很多不错的文章,让我受益匪浅。
如果你想了解原版的程序,请前往象棋百科全书网的github下载,这里面有很多个版本,我们使用的是JavaScript版。

这个教程难学吗?

1、至少前4节是不难的吧,都是一些基本的东西。
2、第5节介绍了Alpha-Beta搜素,这个算法很重要,是后面几节教程的基础。
3、如果搞明白了Alpha-Beta搜索算法,随后的3节应该也不算难吧,都是在Alpha-Beta算法的基础上进行优化。

最后,非常感谢象棋百科全书网的前辈以及《C/C++中国象棋程序入门与提高》的作者。
祝你学得愉快。

这一节我们设计图形界面,显示初始化棋局。当点击某棋子时,弹窗提示所点击的具体棋子。效果如下:

934105-20170221151846929-1961232006

 

1.1、棋盘表示

中国象棋有10行9列,很自然地想到可以用10×9矩阵表示棋盘。事实上,我们使用16×16矩阵来表示一个扩充了的虚拟棋盘。

934105-20170221152542195-1722498827

如上图所示,灰色部分为真实棋盘,置于虚拟棋盘之中。这么做可以快速判断棋子是否走出边界。例如象沿田字走,如果走到真实棋盘之外的虚拟棋盘中,说明走法不合法。

容易想到使用二维数组表示16×16矩阵,这样棋盘上的一个位置需要两个变量表示。一个走法包括起点和终点,就需要四个变量。如果使用长度为256的一维数组表示,一个位置只需一个变量,这就可以减少计算量。因此用一维数组表示16×16矩阵。

一维矩阵和二维矩阵之间的转换也很简单:

其中,sq & 15是通过位运算取余,与sq % 16结果相同(可参考篇文章)。

再使用一个辅助数组,标识虚拟棋盘中,哪些位置属于真实棋盘:

要判断某位置是否在真实棋盘,可使用函数:

1.2、棋子表示

使用整数表示棋子:

红方

8

9

10

11

12

13

14

黑方

16

17

18

19

20

21

22

棋子这样表示,可以快速判断某棋子属于红方还是黑方,如下表所示:

红方棋子

黑方棋子

十进制

二进制

十进制

二进制

8

0000 1000

16

0001 0000

9

0000 1001

17

0001 0001

10

0000 1010

18

0001 0010

11

0000 1011

19

0001 0011

12

0000 1100

20

0001 0100

13

0000 1101

21

0001 0101

14

0000 1110

22

0001 0110

可以看出:

红方棋子 & 8 = 1

黑方棋子 & 16 = 1

1.3、字符串表示局面

使用数组表示局面,程序处理起来很方便,但是再网上传递棋局很不方便。我们可以用一行字符串表示一个局面,这就是FEN格式串,一种使用ASCII码字符描述国际象棋局面的标准,当然也可应用于中国象棋。中国象棋的初始局面可表示为:

rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w – – 0 1

(1)、红色区域,表示棋盘布局,小写表示黑方,大写表示红方。一个字母表示一个棋子,对应关系如下。

红方

字母

黑方

字母

对应单词

K

k

king

A

a

advisor

B

b

bishop

N

n

knight

R

r

rook

C

c

cannon

P

p

pawn

至于为什么马不用H(horse),象不用E(elephant),这是为了与国际象棋相对应。如果没有棋子,则用数字表示出相邻连续的空位数。中国象棋共有十行,每行都用一个字符串表示,行间使用正斜杠分割。例如:

rnbakabnr表示:934105-20170221153730351-345818983

9表示:第二行都是空格。

1c5c1表示:934105-20170221153824913-1710346427

(2)、绿色区域,表示轮到哪一方走子,“w”表示红方,“b”表示黑方。(没有用r表示红方,我想也是为了与国际象棋对应吧,毕竟国际象棋是黑白两色。)

(3)、深紫色区域,在中国象棋中没有意义,始终用“-”表示。

(4)、紫红色区域,在中国象棋中没有意义,始终用“-”表示。

(5)、蓝色区域,表示双方没有吃子的走棋步数(半回合数),通常该值达到120就要判和(六十回合自然限着),一旦形成局面的上一步是吃子,这里就标记“0”。

(6)、棕色区域,表示当前的回合数。

我们的程序就是使用FEN串初始化棋局的,这就涉及到了将FEN串转化为一维棋局数组。暂时不考虑哪方走子,只解析红色部分,伪代码如下:

1.4、棋盘前端设计思路

由于棋盘有90个交叉点,我们把棋盘划分为的90个小正方形区域,交叉点是小正方形的中心。每个区域都会定义一个img标签。

934105-20170221154619898-632109959

上图使用红色方框,标识出了4个小正方形区域。

这些img标签有两个作用:

(1)、显示棋子图片

如果某个区域存在棋子,就会显示相应的棋子图片;否则,显示一张透明图片(也就是oo.gif)。

(2)、响应点击事件

每个img标签都会绑定onmousedown事件。点击不同的img标签时,会传递不同的参数给响应函数,这样就知道点击的具体是哪个区域了。

1.5、核心代码说明

本节的代码可以在 Github 下载,也可以直接clone

git clone -b step-1 https://github.com/Royhoo/write-a-chinesechess-program

程序中定义了两个对象:Board和Position。Board表示一个棋盘,主要功能是初始化棋局,显示棋盘、棋子,响应棋盘上的点击事件。Position存储了一维棋局数组,并定义了很多对该数组进行操作的方法。

Board对象实例化的代码位于index.html中。

通过prototype属性,我们为这两个对象添加了很多的属性和方法。

Board的主要属性和方法:

(1)、pos

这是Position对象的一个实例。

(2)、flushBoard()

刷新棋盘,也就是重新显示棋盘上的棋子。

(3)、drawSquare(sq)

显示sq位置的棋子图片。如果该位置没棋子,则显示一张透明的图片。

(4)、clickSquare(sq_)

点击棋盘的响应函数。点击棋盘(棋子或者空位置),就会调用该函数。sq_是点击的位置。

Position的主要属性和方法:

(1)、squares

这就是一维棋局数组。

(2)、fromFen(fen)

通过FEN串初始化棋局,也就是将参数fen表示的棋局,转化为一维棋局数组squares表示的棋局。

(3)、addPiece(sq, pc)

将棋子pc添加进棋局中的sp位置。

1 8 收藏 3 评论

关于作者:未济

关注C,PHP,MySQL,JAVA,数学,美剧(尤其是权利的游戏)。目前主要关注nlp。 个人主页 · 我的文章 · 12 ·   

可能感兴趣的话题



直接登录
最新评论
  • 王念一 高一学生 02/24

    所以 16*16 有什么特别含义么。。。

    虽然看着舒服点但是一个长度为 90 的一维数组也没啥不可啊

    • 未济 软件工程师 02/24

      // 根据一维矩阵,获取二维矩阵行数
      function RANK_Y(sq) {
        return sq >> 4;
      }
      // 根据一维矩阵,获取二维矩阵列数
      function FILE_X(sq) {
        return sq & 15;
      }
      你看这两个函数,如果是16*16,求二维数组的行数和列数,都是按位运算,速度比较快。

      另外,16*16的话,判断马、和象是否走出了棋盘,也比较简单,这可以参考下一节。

跳到底部
返回顶部