html5的canvas实现中国象棋
最近有了一个写中国象棋程序的想法,就根据canvas实现了一下。下面是最终效果:
首先,分析页面布局。主要的是有一个显示图像的区域,左下角是显示当前选中的棋子的<p>标签,右下角是两个按钮。
对于图像区域的布局,首先为了让棋子的移动,红圈的移动,和棋盘之间不会互相影响。那我们就要创建三个图层,也就是用三个<canvas>根据相对布局放在同一个位置。
在css设定z-index的值来确定图层的上下。棋盘图层应该放到最下面,棋子图层由于要对点击事件进行相应,于是放在最上层。其余就是JavaScript功能的实现。
注意几点:
- 棋子的图片应该是一个透明背景图,这样呈现的效果才会真实(为了更加真实,棋子图片是我的3D渲染图);
- 在网页刚打开时会调用init()方法,但是图片可能会没有加载出来,会造成棋盘上棋子显示不全的情况。这时只需刷新几次网页就好了。
- 在之后的棋子移动过程中可能也会有棋子显示不出来的情况(但是,目前还没有遇到),如果遇到这种情况就需要点击“刷新”按钮。
- 悔棋目前不支持多步,只能悔棋一步。若想改成多步,可能需要用几个数组来存储棋子走过的位置等一些信息。
- 没有写对于胜利的判断,因为我觉得象棋的胜利已经很明显了。若想要添加,可以写一个win()方法,在每一次Remove()方法调用后都判断一次。
所以整个的代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>中国象棋</title>
<style type="text/css">
* {
padding: 0;
margin: 0;
}
#id1 {
position: relative;
}
#canvasp {
position: absolute;
top: 0px;
left: 0px;
z-index: 2;
}
#btn1 {
position: absolute;
width: 50px;
height: 30px;
left: 450px;
}
#btn3 {
position: absolute;
width: 50px;
height: 30px;
left: 380px;
}
#id3 {
position: absolute;
top: 0px;
left: 0px;
z-index: 1;
}
</style>
</head>
<body>
<div id="id1">
<div id="canvasp">
<canvas id="myCanvas" width="500" height="470" style="border:1px solid #d3d3d3;" onmousedown="show_coords(event)">
Your browser does not support the HTML5 canvas tag.
</canvas>
</div>
<div id="id2"><canvas id="canvas" width="500" height="470"></canvas></div>
<div id="id3"><canvas id="canvas3" width="500" height="470"></canvas></div>
</div>
<div id="id2">
<button id="btn1">刷新</button>
<button id="btn3">悔棋</button>
<p id="checkedItem">选中了啥</p>
</div>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
//图片
var c2 = document.getElementById("canvas");
var canvas = c2.getContext("2d");
//提示方框
var c3 = document.getElementById("canvas3");
var canvas3 = c3.getContext("2d");
//选中
var checkedItem = document.getElementById("checkedItem");
var img = new Image();
img.src = "img/chess/象棋棋盘2.png";
img.onload = imgfn; //图片加载完在执行
var img2 = new Image();
var qizi = ["红車", "红马", "红相", "红仕", "红帅",
"红炮", "红兵", "黑車", "黑马", "黑象", "黑士",
"黑将", "黑炮", "黑卒"
]
init();
function changImgPath(size) {
var imgapath = "img/chess/象棋" + qizi[size] + ".gif";
img2.src = imgapath;
}
function imgfn() {
canvas.drawImage(img, 0, 0, 500, 480)
}
function imgfn2(x, y) {
ctx.drawImage(img2, x, y, 50, 50)
}
function imgfn3(x, y) {
canvas3.drawImage(img, x, y, 70, 70)
}
var pre_x = 0;
var pre_y = 0;
var victory = false;
var Pre_isChecked = false;
var Pre_checkedItem = 0;
var Now_checkedItem = -2; //默认选择空
var Now_team = true; //true表示黑方
var Pre_team = true; //true表示黑方
var Red_runed = false; //红方走过了没有
var Black_runed = false; //黑方走过了没有
var qizi_remember = -2; //默认为没有
var Remove_successed = false; //上一步走了没
var pre_pre_index = [0, 0]; //记录悔棋
var index = new Array(
[7, 8, 9, 10, 11, 10, 9, 8, 7],
[-2, -2, -2, -2, -2, -2, -2, -2, -2],
[-2, 12, -2, -2, -2, -2, -2, 12, -2],
[13, -2, 13, -2, 13, -2, 13, -2, 13],
[-2, -2, -2, -2, -2, -2, -2, -2, -2],
[-2, -2, -2, -2, -2, -2, -2, -2, -2],
[6, -2, 6, -2, 6, -2, 6, -2, 6],
[-2, 5, -2, -2, -2, -2, -2, 5, -2],
[-2, -2, -2, -2, -2, -2, -2, -2, -2],
[0, 1, 2, 3, 4, 3, 2, 1, 0]
)
function repeat(x, y) {
return index[y][x] >= 0;
}
function show_coords(event) {
if(victory == false) {
x = event.clientX
y = event.clientY
var qp_x = Math.round((x - 25) / 55);
var qp_y = Math.round((y - 25) / 48);
Now_checkedItem = index[qp_y][qp_x];
Now_team = Now_checkedItem > 6;
changImgPath(Pre_checkedItem)
if(Pre_isChecked && (Now_team != Pre_team || Now_checkedItem == -2)) {
// alert(Now_team+"xxx"+Pre_team)
pre_pre_index[0] = pre_x;
pre_pre_index[1] = pre_y;
Remove(qp_x, qp_y, Pre_checkedItem, Now_checkedItem);
}
canvas3.clearRect(0, 0, 500, 500);
pre_x = qp_x;
pre_y = qp_y;
x = qp_x * 50 + 25;
y = qp_y * 46 + 9;
canvas3.strokeStyle = 'red';
canvas3.font = "50px Georgia";
canvas3.strokeText("⚪", x - 7, y + 42);
checkedItem.innerHTML = qizi[index[qp_y][qp_x]]
Pre_isChecked = repeat(qp_x, qp_y)
Pre_checkedItem = index[qp_y][qp_x];
Pre_team = Pre_checkedItem > 6;
} else {
alert("请重新开局。");
}
}
var btn1 = document.getElementById('btn1');
var btn3 = document.getElementById("btn3");
function clear(x, y) {
x = x * 50 + 25;
y = y * 46 + 9;
ctx.clearRect(x, y + 3, 50, 47);
}
btn1.onclick = function() {
imgfn2(pre_x * 50 + 25, pre_y * 46 + 9)
}
btn3.onclick = function() {
if(!Remove_successed)
return
x = pre_pre_index[0];
y = pre_pre_index[1];
clear(pre_x, pre_y);
imgfn2(x * 50 + 25, y * 46 + 9);
if(qizi_remember != -2) {
changImgPath(qizi_remember);
setTimeout("imgfn2(" + pre_x + "*50+25," + pre_y + "*46+9)", 30)
}
index[pre_y][pre_x] = qizi_remember;
index[y][x] = Pre_checkedItem;
Pre_checkedItem = -2;
if(Red_runed) {
Red_runed = false;
Black_runed = true;
// alert("调用了"+Red_runed)
} else if(Black_runed) {
Black_runed = false;
Red_runed = true;
}
}
function Remove(x, y, Pre_checkedItem, Now_checkedItem) {
if(Red_runed) {
if(Pre_checkedItem <= 6) {
Remove_successed = false;
return;
}
}
if(Black_runed) {
if(Pre_checkedItem > 6) {
Remove_successed = false;
return;
}
}
if(!rule(x, y, Pre_checkedItem)) {
Remove_successed = false;
return;
}
Remove_successed = true;
clear(pre_x, pre_y);
if(Now_checkedItem != -2) {
clear(x, y);
}
imgfn2(x * 50 + 25, y * 46 + 9)
setTimeout("imgfn2(" + x + "*50+25," + y + "*46+9)", 15)
qizi_remember = index[y][x]; //记录被吃掉的棋子或空白
index[pre_y][pre_x] = -2;
index[y][x] = Pre_checkedItem;
if(Pre_checkedItem > 6) {
Black_runed = true;
Red_runed = false;
}
if(Pre_checkedItem <= 6) {
Red_runed = true;
Black_runed = false;
}
}
function rule(x, y, Pre_checkedItem) {
switch(Pre_checkedItem) {
case 0:
case 7:
if(x == pre_x || y == pre_y) {
if(x == pre_x) {
var max_y = (y > pre_y) ? y : pre_y;
var min_y = (y > pre_y) ? pre_y : y;
for(var i = min_y + 1; i < max_y; i++) {
if(index[i][x] != -2) {
return false;
}
}
}
if(y == pre_y) {
var max_x = (x > pre_x) ? x : pre_x;
var min_x = (x > pre_x) ? pre_x : x;
for(var i = min_x + 1; i < max_x; i++) {
if(index[y][i] != -2) {
return false;
}
}
}
return true;
} else {
return false;
}
break;
case 1:
case 8:
if((Math.abs(x - pre_x) == 1 && Math.abs(y - pre_y) == 2) ||
(Math.abs(x - pre_x) == 2 && Math.abs(y - pre_y) == 1)) {
if((y - pre_y == 2 && index[pre_y + 1][pre_x] != -2) ||
(y - pre_y == -2 && index[pre_y - 1][pre_x] != -2) ||
(x - pre_x == 2 && index[pre_y][pre_x + 1] != -2) ||
(x - pre_x == -2 && index[pre_y][pre_x - 1] != -2)) {
return false;
}
return true;
} else {
return false;
}
case 2:
case 9:
if((Math.abs(x - pre_x) == 2 && Math.abs(y - pre_y) == 2)) {
if((Pre_checkedItem > 6 && y <= 4) || (Pre_checkedItem <= 6 && y > 4)) {
if((y > pre_y && x > pre_x && index[pre_y + 1][pre_x + 1] != -2) ||
(y < pre_y && x > pre_x && index[pre_y - 1][pre_x + 1] != -2) ||
(y > pre_y && x < pre_x && index[pre_y + 1][pre_x - 1] != -2) ||
(y < pre_y && x < pre_x && index[pre_y - 1][pre_x - 1] != -2)) {
return false;
}
return true;
}
return false;
} else {
return false;
}
case 3:
case 10:
if((Math.abs(x - pre_x) == 1 && Math.abs(y - pre_y) == 1)) {
if((x >= 3 && x <= 5) && (y < 3 || y > 6)) {
return true;
}
return false;
} else {
return false;
}
case 4:
case 11:
if((Math.abs(x - pre_x) == 1 && y == pre_y) ||
(Math.abs(y - pre_y) == 1 && x == pre_x)) {
if((x >= 3 && x <= 5) && (y < 3 || y > 6)) {
return true;
}
return false;
} else {
return false;
}
case 5:
case 12:
if((x == pre_x || y == pre_y)) {
var count2 = 0;
if(x == pre_x) {
var max_y = (y > pre_y) ? y : pre_y;
var min_y = (y > pre_y) ? pre_y : y;
for(var i = min_y + 1; i < max_y; i++) {
if(index[i][x] != -2) {
count2++;
if(count2 > 1)
return false;
}
}
}
if(y == pre_y) {
var max_x = (x > pre_x) ? x : pre_x;
var min_x = (x > pre_x) ? pre_x : x;
for(var i = min_x + 1; i < max_x; i++) {
if(index[y][i] != -2) {
count2++;
if(count2 > 1)
return false;
}
}
}
if((count2 == 0 && Now_checkedItem == -2) ||
(count2 == 1 && Now_checkedItem >= 0)) {
return true;
} else {
return false;
}
return true;
} else {
return false;
}
break;
case 6:
case 13:
var passRaiver = false;
if(Pre_checkedItem > 6) { //黑色棋子
passRaiver = pre_y > 4
if(passRaiver) {
if((Math.abs(x - pre_x) == 1 && y == pre_y) ||
(Math.abs(y - pre_y) == 1 && x == pre_x)) {
if(y - pre_y < 0) {
return false;
}
return true;
} else {
return false;
}
} else {
if(y - pre_y == 1 && x == pre_x) {
return true;
} else {
return false;
}
}
return true;
} else {
passRaiver = pre_y < 5
if(passRaiver) {
if((Math.abs(x - pre_x) == 1 && y == pre_y) ||
(Math.abs(y - pre_y) == 1 && x == pre_x)) {
if(y - pre_y > 0) {
return false;
}
return true;
} else {
return false;
}
} else {
if(y - pre_y == -1 && x == pre_x) {
return true;
} else {
return false;
}
}
return false;
}
default:
alert("default了");
return true;
break;
}
}
function init() {
changImgPath(0)
setTimeout("imgfn2(0*50+25,9*46+9)", 30)
setTimeout("imgfn2(8*50+25,9*46+9)", 30)
setTimeout("changImgPath(1)", 50)
setTimeout("imgfn2(1*50+25,9*46+9)", 70)
setTimeout("imgfn2(7*50+25,9*46+9)", 70)
setTimeout("changImgPath(2)", 90)
setTimeout("imgfn2(2*50+25,9*46+9)", 110)
setTimeout("imgfn2(6*50+25,9*46+9)", 110)
setTimeout("changImgPath(3)", 130)
setTimeout("imgfn2(3*50+25,9*46+9)", 150)
setTimeout("imgfn2(5*50+25,9*46+9)", 150)
setTimeout("changImgPath(4)", 170)
setTimeout("imgfn2(4*50+25,9*46+9)", 190)
setTimeout("changImgPath(5)", 210)
setTimeout("imgfn2(1*50+25,7*46+9)", 230)
setTimeout("imgfn2(7*50+25,7*46+9)", 230)
setTimeout("changImgPath(6)", 250)
setTimeout("imgfn2(0*50+25,6*46+9)", 270)
setTimeout("imgfn2(2*50+25,6*46+9)", 270)
setTimeout("imgfn2(4*50+25,6*46+9)", 270)
setTimeout("imgfn2(6*50+25,6*46+9)", 270)
setTimeout("imgfn2(8*50+25,6*46+9)", 270)
setTimeout("changImgPath(13)", 290)
setTimeout("imgfn2(0*50+25,3*46+9)", 310)
setTimeout("imgfn2(2*50+25,3*46+9)", 310)
setTimeout("imgfn2(4*50+25,3*46+9)", 310)
setTimeout("imgfn2(6*50+25,3*46+9)", 320)
setTimeout("imgfn2(8*50+25,3*46+9)", 320)
setTimeout("changImgPath(12)", 330)
setTimeout("imgfn2(1*50+25,2*46+9)", 350)
setTimeout("imgfn2(7*50+25,2*46+9)", 350)
setTimeout("changImgPath(11)", 370)
setTimeout("imgfn2(4*50+25,0*46+9)", 390)
setTimeout("changImgPath(10)", 410)
setTimeout("imgfn2(3*50+25,0*46+9)", 430)
setTimeout("imgfn2(5*50+25,0*46+9)", 430)
setTimeout("changImgPath(9)", 450)
setTimeout("imgfn2(2*50+25,0*46+9)", 470)
setTimeout("imgfn2(6*50+25,0*46+9)", 470)
setTimeout("changImgPath(8)", 490)
setTimeout("imgfn2(1*50+25,0*46+9)", 510)
setTimeout("imgfn2(7*50+25,0*46+9)", 510)
setTimeout("changImgPath(7)", 530)
setTimeout("imgfn2(0*50+25,0*46+9)", 550)
setTimeout("imgfn2(8*50+25,0*46+9)", 550)
}
</script>
</body>
</html>
棋子图片和棋盘的图片已经上传到了我的百度网盘上了,链接如下:
链接:https://pan.baidu.com/s/1HwurAERgQiBt82WlH7L4IA
提取码:bddb
复制这段内容后打开百度网盘手机App,操作更方便哦
====================================================
2023年6月14日更新:
已更新链接;已将链接时长改为永久。
链接:https://pan.baidu.com/s/1EqKFkUxbvMhH5f4hYpwakw
提取码:wng6