一、背景

近一个月没写 Javascript 代码,有点生疏。正好浏览网页时弹出五子棋的游戏广告,于是想通过编写这个小游戏练练手。

二、简单介绍

2.1 效果展示

2.2 实现思路

  1. 棋盘:通过图片(chessboard.png)和 div 标签渲染出棋盘。

  2. 棋子:通过图片(black_flag.png、white_flag.png等)渲染出黑白棋子。落子前,鼠标出会出现一个可以随鼠标移动的棋子。我们创建一个浮动的 div,动态设置其 top 和 left 。

  3. 落子:给容器(class="container")添加 click 事件,给其添加对应的 classname。即被点击的单元格设置棋子背景图片。此外,需要判断落子点是否存在棋子。

  4. 输赢:使用二维数组保存棋盘(棋子)状态,通过横向、纵向、左上到右下和右上到左下四个方向进行判断是否有 5 个以上连续同颜色(样式)的棋子。

2.3 涉及技术

DOM操作、面向对象、事件操作和间隔函数 setInterval

2.4 项目结构

三、实现步骤

3.1 绘制棋盘

style.css 内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
html,body {
padding: 0;
margin: 0;
}

.container {
position: relative;
width: 540px;
height: 540px;
margin: 10px auto;
padding-top: 7px;
padding-left: 7px;
background: url("../images/chessboard.png") no-repeat;
cursor: pointer;
}

.none {
position: absolute;
width: 36px;
height: 36px;
box-sizing: border-box;
/*border: 1px solid #fff;*/
}

.black_flag {
position: absolute;
width: 36px;
height: 36px;
background: url("../images/black_flag.png") no-repeat;
}

.black_flag_cur {
position: absolute;
background: url("../images/black_flag_cur.png") no-repeat;
/*设置点击无效*/
pointer-events: none;
}

.white_flag {
position: absolute;
width: 36px;
height: 36px;
background: url("../images/white_flag.png") no-repeat;
}

.white_flag_cur {
position: absolute;
background: url("../images/white_flag_cur.png") no-repeat;
/*设置点击无效*/
pointer-events: none;
}

chessboard.js 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var Chessboard = function() {
// 保存棋盘棋子状态
this.flagArr = [];
this.size = 36;
}

// 初始化棋盘
Chessboard.prototype.init = function() {
var container = document.getElementById("container");

for (var i = 0; i < 15; i++) {
var arr = [];
for (var j = 0; j < 15; j++) {
var div = document.createElement("div");
div.className = "none";
div.style.top = (i * this.size) + "px";
div.style.left = (j * this.size) + "px";
container.appendChild(div);
arr.push(div);
}
this.flagArr.push(arr);
}

}

game.js 代码:

1
2
3
4
5
6
7
8
var Game = function() {

}

Game.prototype.start = function() {
var chessboard = new Chessboard();
chessboard.init();
}

最终效果如下:

为了方便查看 div 与棋盘图片中格子之间的对应关系,笔者将 div 边框设置成白色。

从图中我们可以看到,div 大小正好对应棋盘的落子点。我们将 div 背景设置成棋子图片就实现了落子操作。

3.2 绘制棋子

chessboard.js 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
var Chessboard = function() {
// 保存棋盘棋子状态
this.flagArr = [];
this.size = 36;

// 默认黑色为先手
this.currentFlag = true;

// 保存落子前的样式映射
this.flagCurMap = [];
// 黑子
this.flagCurMap[true] = "black_flag_cur";
// 白子
this.flagCurMap[false] = "white_flag_cur";
}

// 初始化棋盘
Chessboard.prototype.init = function() {
var container = document.getElementById("container");

for (var i = 0; i < 15; i++) {
var arr = [];
for (var j = 0; j < 15; j++) {
var div = document.createElement("div");
div.className = "none";
div.style.top = (i * this.size) + "px";
div.style.left = (j * this.size) + "px";
container.appendChild(div);
arr.push(div);
}
this.flagArr.push(arr);
}

// 添加事件监听器
this.addListener(container);
}

// 落子事件监听器
Chessboard.prototype.addListener = function() {
var that = this;

// 设置落子前的鼠标样式
var mouse = document.createElement("div");
mouse.id = "mouse";
mouse.style.width = mouse.style.height = 36 + "px";
document.body.appendChild(mouse);
document.body.onmousemove = function(event) {
mouse.className = that.flagCurMap[that.currentFlag];
var x = event.clientX - 16;
var y = event.clientY - 16;
mouse.style.top = y + "px";
mouse.style.left = x + "px";
}
}

结果如下图:

3.3 落子

在 chessboard.js 的监听器方法中添加落子的点击事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
var Chessboard = function() {
// 保存棋盘棋子状态
this.flagArr = [];
this.size = 36;

// 默认黑色为先手
this.currentFlag = true;

// 保存落子前的样式映射
this.flagCurMap = [];
// 黑子
this.flagCurMap[true] = "black_flag_cur";
// 白子
this.flagCurMap[false] = "white_flag_cur";

// 保存落子后的样式映射
this.flagMap = [];
// 黑子
this.flagMap[true] = "black_flag";
// 白子
this.flagMap[false] = "white_flag";

// 保存结果映射关系
this.resultMap = [];
this.resultMap[true] = "黑子胜利";
this.resultMap[false] = "白子胜利";
}

// 初始化棋盘
Chessboard.prototype.init = function() {
var container = document.getElementById("container");

for (var i = 0; i < 15; i++) {
var arr = [];
for (var j = 0; j < 15; j++) {
var div = document.createElement("div");
div.className = "none";
div.style.top = (i * this.size) + "px";
div.style.left = (j * this.size) + "px";
container.appendChild(div);
arr.push(div);
}
this.flagArr.push(arr);
}

// 添加事件监听器
this.addListener(container);
}

// 落子事件监听器
Chessboard.prototype.addListener = function(container) {
var that = this;

// 设置落子前的鼠标样式
var mouse = document.createElement("div");
mouse.id = "mouse";
mouse.style.width = mouse.style.height = 36 + "px";
document.body.appendChild(mouse);
document.body.onmousemove = function(event) {
mouse.className = that.flagCurMap[that.currentFlag];
var x = event.clientX - 16;
var y = event.clientY - 16;
mouse.style.top = y + "px";
mouse.style.left = x + "px";
}

// 落子监听
container.onclick = function(event) {
// 判断落子点是否存在棋子
if (event.target.className != "none") {
alert("此处不能落子!");
return;
}

// 落子,设置棋子图片
event.target.className = that.flagMap[that.currentFlag];

// 换棋手
that.currentFlag = !that.currentFlag;
}
}

运行结果如下:

3.4 判断输赢

在 chessboard.js 的落子监听实践代码中,判断是否五连子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// 落子事件监听器
Chessboard.prototype.addListener = function(container) {
var that = this;

// 设置落子前的鼠标样式
var mouse = document.createElement("div");
mouse.id = "mouse";
mouse.style.width = mouse.style.height = 36 + "px";
document.body.appendChild(mouse);
document.body.onmousemove = function(event) {
mouse.className = that.flagCurMap[that.currentFlag];
var x = event.clientX - 16;
var y = event.clientY - 16;
mouse.style.top = y + "px";
mouse.style.left = x + "px";
}

// 落子监听
container.onclick = function(event) {
// 判断落子点是否存在棋子
if (event.target.className != "none") {
alert("此处不能落子!");
return;
}

// 落子,设置棋子图片
event.target.className = that.flagMap[that.currentFlag];

// 当前落子坐标
var x = Math.floor(event.target.offsetLeft / that.size);
var y = Math.floor(event.target.offsetTop / that.size);

// 判断是否胜利
if (that._checkSuccess(x, y)) {
document.getElementById("mouse").style.display = "none";
container.onclick = null;
document.body.onmousemove = null;
alert(that.resultMap[that.currentFlag]);
return;
}

// 换棋手
that.currentFlag = !that.currentFlag;
}
}

// 判断棋局
Chessboard.prototype._checkSuccess = function(x, y) {
var result = false;
// 当前落子的样式/颜色
var className = this.flagArr[y][x].className;

// 横向判断
var count = 0;
for (var i = 0; i < 15; i++) {
if (className == this.flagArr[y][i].className) {
count++;
if (count >= 5) {
return true;
}
} else {
count = 0;
}
}

// 纵向判断
for (var j = 0; j < 15; j++) {
if (className == this.flagArr[j][x].className) {
count++;
if (count >= 5) {
return true;
}
} else {
count = 0;
}
}

// 左上到右下判断
var a = y - x;
var index = 0;
if (a > 0) {
for (a; a < 15; a++) {
if (className == this.flagArr[a][index++].className) {
count++;
if (count >= 5) {
return true;
}
} else {
count = 0;
}
}
} else {
a = Math.abs(a);
for (a; a < 15; a++) {
if (className == this.flagArr[index++][a].className) {
count++;
if (count >= 5) {
return true;
}
} else {
count = 0;
}
}
}

// 右上到左下判断
var b = 14 - y -x;
var index2 = 14;
if (b > 0) {
b = 14 - b;
index2 = 0;
for (b; b >= 0; b--) {
if (className == this.flagArr[index2++][b].className) {
count++;
if (count >= 5) {
return true;
}
} else {
count = 0;
}
}
} else {
b = Math.abs(b);
for (b; b < 15; b++) {
if (className == this.flagArr[index2--][b].className) {
count++;
if (count >= 5) {
return true;
}
} else {
count = 0;
}
}
}

if (count >= 5) {
result = true;
}

return result;
}

演示结果:

剩余的一些文本提示,倒计时就不在此处介绍。具体代码可以在下边提供的链接中下载。

四、源码下载