“JavaScript中国象棋程序” 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序。这是教程的第1节。

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

这一系列共有9个部分:

0、JavaScript中国象棋程序(0)- 前言

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

JavaScript中国象棋程序(1) - 界面设计-LMLPHP

1.1、棋盘表示

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

JavaScript中国象棋程序(1) - 界面设计-LMLPHP

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

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

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

// 将二维矩阵转换为一维矩阵
function COORD_XY(x, y) {
return x + (y << 4);
} // 根据一维矩阵,获取二维矩阵行数
function RANK_Y(sq) {
return sq >> 4;
} // 根据一维矩阵,获取二维矩阵列数
function FILE_X(sq) {
return sq & 15;
}

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

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

  var IN_BOARD_ = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];

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

function IN_BOARD(sq) {
return IN_BOARD_[sq] != 0;
}

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 - -

(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表示:JavaScript中国象棋程序(1) - 界面设计-LMLPHP

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

1c5c1表示:JavaScript中国象棋程序(1) - 界面设计-LMLPHP

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

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

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

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

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

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

// 将FEN串转为一维数组
行变量 y = 3
列变量 x = 3
var c = FEN串第一个字符;
while (c != " ") {
if (c == "/") { // 换行
x = 3;
y ++;
if (y > 12) {
break;
}
} else if (c >= "1" && c <= "9") { // 出现空位
列向量x增加c
} else if (c >= "A" && c <= "Z") { // 红方棋子
将字符表示的棋子转换为整数,并放入数组x + (y << 4)的位置
} else if (c >= "a" && c <= "z") {
将字符表示的棋子转换为整数,并放入数组x + (y << 4)的位置
} c = FEN串的下一个字符;
}

1.4、棋盘前端设计思路

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

JavaScript中国象棋程序(1) - 界面设计-LMLPHP

上图使用红色方框,标识出了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位置。

05-06 11:07