canvas五子棋小游戏
第一篇文章,我们一起开发一个五子棋游戏。
在开始教程之前如果你想预览一下这个游戏会是什么样子的话可以点击试玩 。 如果里面的代码你现在一点也看不懂,很多语法都不熟悉也不需要着急,接下来教程会一步一步教你编写出这个小游戏所有的代码。
先新建一个index.html,在body里添加一个canvas标签。
<canvas id="mycanvas" width="600" height="600" style="border:1px solid #c3c3c3;" />
然后在创建一个index.js文件,在html添加一个script标签
<script src="index.js"></script>
在index.js里先定义一些游戏的全局变量。
let c = document.getElementById("mycanvas");
let ctx = c.getContext("2d");
//棋子的半径
let Radius = 30;
//棋盘大小
let Length = 10;
//当前玩家1代表黑2代表白
let Player = 1;
//棋盘用一个二维数组表示
let Checkerboard = [[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, 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]];
游戏大致思路就是获取点击的坐标算出棋子的坐标,然后替换数组对应位置的值。再在画布上渲染出来。
//canvas渲染函数
function show() {
//先清除画布
ctx.clearRect(0, 0, c.width, c.height);
for (var i = 0; i < Length; i++) {
for (var j = 0; j < Length; j++) {
//画棋盘棋盘的每个点上两条直线
ctx.fillStyle = '#000000';
//画横线
if (i == 0) {
ctx.beginPath();
ctx.lineWidth = 1;
ctx.moveTo((i * 2 + 1) * Radius, j * (Radius * 2) + Radius);
ctx.lineTo((i * 2 + 2) * Radius, j * (Radius * 2) + Radius);
ctx.stroke();
} else if (i == Length - 1) {
ctx.beginPath();
ctx.lineWidth = 1;
ctx.moveTo((i * 2) * Radius, j * (Radius * 2) + Radius);
ctx.lineTo((i * 2 + 1) * Radius, j * (Radius * 2) + Radius);
ctx.stroke();
} else {
ctx.beginPath();
ctx.lineWidth = 1;
ctx.moveTo((i * 2) * Radius, j * (Radius * 2) + Radius);
ctx.lineTo((i * 2 + 2) * Radius, j * (Radius * 2) + Radius);
ctx.stroke();
}
//画竖线
if (j == 0) {
ctx.beginPath();
ctx.lineWidth = 1;
ctx.moveTo(i * (Radius * 2) + Radius, (j * 2 + 1) * Radius);
ctx.lineTo(i * (Radius * 2) + Radius, (j * 2 + 2) * Radius);
ctx.stroke();
} else if (j == Length - 1) {
ctx.beginPath();
ctx.lineWidth = 1;
ctx.moveTo(i * (Radius * 2) + Radius, (j * 2) * Radius);
ctx.lineTo(i * (Radius * 2) + Radius, (j * 2 + 1) * Radius);
ctx.stroke();
} else {
ctx.beginPath();
ctx.lineWidth = 1;
ctx.moveTo(i * (Radius * 2) + Radius, (j * 2) * Radius);
ctx.lineTo(i * (Radius * 2) + Radius, (j * 2 + 2) * Radius);
ctx.stroke();
}
//画白棋
if (Checkerboard[i][j] == 2) {
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc((i * 2 + 1) * Radius, (j * 2 + 1) * Radius, Radius, 0, 2 * Math.PI);
ctx.stroke();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc((i * 2 + 1) * Radius, (j * 2 + 1) * Radius, 29, 0, 2 * Math.PI);
ctx.fill();
}
//画黑棋
if (Checkerboard[i][j] == 1) {
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc((i * 2 + 1) * Radius, (j * 2 + 1) * Radius, Radius, 0, 2 * Math.PI);
ctx.fill();
}
}
}
}
//调用渲染函数
show();
浏览器打开index.html,显示效果如下 完美,达到了想要的效果,接下来处理点击事件
c.addEventListener('click', function (e) {
//获取点击的坐标,因为我们的棋盘是从(30,30)开始的,所以要先减去30,再除60,然后四舍五入,就是在棋盘上的坐标。
let x = Math.round((e.layerX - Radius) / (Radius * 2));
let y = Math.round((e.layerY - Radius) / (Radius * 2));
//将当前玩家的值赋给棋盘上的点。这里注意如果当前点已经不是0了直接退出函数。
if(Checkerboard[x][y]!=0){
return;
}
Checkerboard[x][y] = Player;
//再改变当前玩家。
Player = Player == 1 ? 2 : 1;
//调用渲染函数
show();
}, false);
保存,运行index.html,现在已经可以愉快的玩耍了,但是现在并没有判断输赢。想要获得胜利得横着竖着和对角线有五个相连得通色棋子。
我们判断落下的点的4个方向是否有相连的五个同色棋子,如果有就获胜。先判断竖着是否有,竖着就要判断点的上方和下方加起来是否有五个。
let vertical = function (x, y) {
if (Checkerboard[x][y] == Player) {
return vertical(x, y + 1) + vertical(x, y - 1);
} else {
return 0;
}
}
在canvas的点击事件里调用一下判断函数
c.addEventListener('click', function (e) {
//获取点击的坐标,因为我们的棋盘是从(30,30)开始的,所以要先减去30,再除60,然后四舍五入,就是在棋盘上的坐标。
let x = Math.round((e.layerX - Radius) / (Radius * 2));
let y = Math.round((e.layerY - Radius) / (Radius * 2));
//将当前玩家的值赋给棋盘上的点。这里注意如果当前点已经不是0了直接退出函数。
if(Checkerboard[x][y]!=0){
return;
}
Checkerboard[x][y] = Player;
//再改变当前玩家。
Player = Player == 1 ? 2 : 1;
//调用渲染函数
show();
if(vertical(x,y)>=5){
alert(Player);
}
}, false);
保存,运行,你会发现报错。因为这样有2个点以上就会出现无限的递归调用死循环。是我想的太简单还想一句话搞定,想了想,想要得到结果,上方和下方必须得拆分开。
//当前点上方的数量
let verticalUp = function (x, y) {
if (x >= 0 && y < 10 && x < 10 && y >= 0 && Checkerboard[x][y] == Player) {
return 1 + verticalUp(x, y - 1);
} else {
return 0;
}
}
//当前点下方的数量
let verticalDown = function (x, y) {
if (x >= 0 && y < 10 && x < 10 && y >= 0 && Checkerboard[x][y] == Player) {
return 1 + verticalDown(x, y + 1);
} else {
return 0;
}
}
然后把点击事件也改一下
//因为在计算上方和下方的时候都计算了当前点所以这里-1。
let 垂直 = verticalDown(x, y) + verticalUp(x, y) - 1;
if(垂直>=5){
alert(Player == 1 ? "黑方胜" : "白方胜");
}
保存运行,当你在垂直方向有4个点相连时会弹出胜利的提示。圆满完成任务。再参考垂直方向完成其他4个方向的判断
let horizontalLeft = function (x, y) { //水平方向左
if (x >= 0 && y < 10 && x < 10 && y >= 0 && Checkerboard[x][y] == Player) {
return 1 + horizontalLeft(x - 1, y);
} else {
return 0;
}
}
let horizontalRight = function (x, y) { //水平方向右
if (x >= 0 && y < 10 && x < 10 && y >= 0 && Checkerboard[x][y] == Player) {
return 1 + horizontalRight(x + 1, y);
} else {
return 0;
}
}
let verticalUp = function (x, y) {
if (x >= 0 && y < 10 && x < 10 && y >= 0 && Checkerboard[x][y] == Player) {
return 1 + verticalUp(x, y - 1);
} else {
return 0;
}
}
let verticalDown = function (x, y) {
if (x >= 0 && y < 10 && x < 10 && y >= 0 && Checkerboard[x][y] == Player) {
return 1 + verticalDown(x, y + 1);
} else {
return 0;
}
}
let leftUp = function (x, y) {
if (x >= 0 && y < 10 && x < 10 && y >= 0 && Checkerboard[x][y] == Player) {
return 1 + leftUp(x - 1, y - 1);
} else {
return 0;
}
}
let rightDown = function (x, y) {
if (x >= 0 && y < 10 && x < 10 && y >= 0 && Checkerboard[x][y] == Player) {
return 1 + rightDown(x + 1, y + 1);
} else {
return 0;
}
}
let leftDown = function (x, y) {
if (x >= 0 && y < 10 && x < 10 && y >= 0 && Checkerboard[x][y] == Player) {
return 1 + leftDown(x + 1, y - 1);
} else {
return 0;
}
}
let rightUp = function (x, y) {
if (x >= 0 && y < 10 && x < 10 && y >= 0 && Checkerboard[x][y] == Player) {
return 1 + rightUp(x - 1, y + 1);
} else {
return 0;
}
}
在点击事件里判断是否有玩家胜利。
c.addEventListener('click', function (e) {
let x = Math.round((e.layerX - Radius) / (Radius * 2));
let y = Math.round((e.layerY - Radius) / (Radius * 2));
if(Checkerboard[x][y]!=0){
return;
}
Checkerboard[x][y] = Player;
let 水平 = horizontalLeft(x, y) + horizontalRight(x, y) - 1;
let 垂直 = verticalDown(x, y) + verticalUp(x, y) - 1;
let 左上 = leftUp(x, y) + rightDown(x, y) - 1;
let 左下 = leftDown(x, y) + rightUp(x, y) - 1;
console.log(x, y);
console.log('水平', 水平);
console.log('垂直', 垂直);
console.log('左上', 左上);
console.log('左下', 左下);
show();
if (水平 >= 5 || 垂直 >= 5 || 左上 >= 5 || 左下 >= 5) {
alert(Player == 1 ? "黑方胜" : "白方胜");
init();
}
Player = Player == 1 ? 2 : 1;
}, false);
再新增一个init函数当胜利后初始化游戏数据。
function init() {
Checkerboard = [[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, 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]];
Player = 1;
show();
}
好了,一个可以玩耍的五子棋小游戏就完成了,虽然功能有点简陋。后续准备增加一些界面美化,一些设置项,增加联机对战功能。 源码地址