注释很详细,直接上代码
新增内容:
1. 五子棋基本制作
2.五子棋判断输赢算法
3. 使用径向渐变增加棋子立体感
4. 使用阴影增加棋子立体感
项目结构:
源码:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
*{
margin: 0;
padding: 0;
}
canvas{
display: block;
margin: 0 auto;
background-color:#d7b495;
margin-top: 3vh;
}
</style>
<body>
</body>
<script src="./js/canvas.js"></script>
</html>
canvas.js
let canvasSize = 1400; //画布大小
let canvasInteriorCellSize = 50; //画布内部单元大小
let canvasPadding = canvasInteriorCellSize; //画布内边距
let canvasInterior = canvasSize - 2 * canvasPadding; //画布内部大小
let canvasInteriorCellCount = canvasInterior / canvasInteriorCellSize; //画布内部单元格数量
let canvas; //画布
const blackHolder = 0, //黑方
whiteHolder = 1; //黑方与白方
const blackChessPieces = 1, //黑子
whiteChessPieces = 2; //白子
let holder = blackHolder; //当前下子方
let map = []; //棋盘数组
let isWinSign = false; //是否赢
/**
* 初始化棋盘数组
* @returns {void} - 无返回值
*/
function initMap() {
for (let i = 0; i <= canvasInteriorCellCount; i++) {
map[i] = [];
for (let j = 0; j <= canvasInteriorCellCount; j++) {
map[i][j] = 0;
}
}
}
/**
* 创建画布
* @param {number} width - 宽度
* @param {number} height - 高度
* @returns {HTMLCanvasElement} -返回画布
*/
function createCanvas(width, height) {
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
document.body.append(canvas);
return canvas;
}
/**
* 绘制棋盘
* @returns {void} - 无返回值
*/
function drawChessboard() {
const ctx = canvas.getContext("2d");
for (let i = 1; i <= canvasInteriorCellCount + 1; i++) {
let dist = i * canvasInteriorCellSize;
//绘制竖线
ctx.beginPath();
ctx.moveTo(dist, canvasPadding);
ctx.lineTo(dist, canvasSize - canvasPadding);
ctx.stroke();
//绘制横线
ctx.beginPath();
ctx.moveTo(canvasPadding, dist);
ctx.lineTo(canvasSize - canvasPadding, dist);
ctx.stroke();
}
}
/**
* 落子点击事件
* @returns {void} - 无返回值
*/
function dropEvent() {
canvas.addEventListener("click", function (e) {
let { offsetX, offsetY } = e; //获取点击位置
let size = canvasInteriorCellSize; //棋盘单元格大小
let padding = canvasPadding; //画布内边距
//最近棋盘交叉点x坐标
let x =
canvasPadding + Math.floor((offsetX - padding + size / 2) / size) * size;
//最近棋盘交叉点y坐标
let y =
canvasPadding + Math.floor((offsetY - padding + size / 2) / size) * size;
//如果点击在棋盘内(不包含边距)
if (
x >= padding &&
x <= canvasSize - padding &&
y >= padding &&
y <= canvasSize - padding
) {
drawChess(x, y);
}
});
}
/**
* 绘制棋子
* @param {number} x - 棋子x坐标
* @param {number} y - 棋子y坐标
* @returns {void} - 无返回值
*/
function drawChess(x, y) {
let r = (canvasInteriorCellSize / 2) * (6 / 7); //棋子半径
let ctx = canvas.getContext("2d");
let numX = x / canvasInteriorCellSize - 1; //棋子x坐标对应的棋盘交叉点序号
let numY = y / canvasInteriorCellSize - 1; //棋子y坐标对应的棋盘交叉点序号
//创建径向渐变
let g = ctx.createRadialGradient(
x - r / 2,
y - r / 2,
0,
x - r / 2,
y - r / 2,
r
);
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI); //绘制圆
//如果该位置已有棋子则直接返回
if (map[numX][numY]) {
return;
}
//根据下子方设置棋子颜色,标记与切换当前执子
if (holder == blackHolder) {
map[numX][numY] = blackChessPieces; //标记棋子
//设置渐变色标
g.addColorStop(0, "#ccc");
g.addColorStop(1, "#000");
holder = whiteHolder; //切换当前执子为白方
} else {
map[numX][numY] = whiteChessPieces;
g.addColorStop(0, "#ccc");
g.addColorStop(1, "#fff");
holder = blackHolder;
}
//设置渐变填充
ctx.fillStyle = g;
setChessShadow();
//判断是否赢并标记
isWinSign = isWin(numX, numY) ? true : false;
//填充
ctx.fill();
ctx.closePath();
//判断是否赢并弹出提示
if (isWinSign) {
setTimeout(function () {
alert((holder == !blackHolder ? "黑棋" : "白棋") + "赢了!");
restart(); //重新开始
}, 100); //延迟弹出提示给最后一个棋子绘制时间
}
}
/**
* 设置棋子阴影
* @returns {void} - 无返回值
*/
function setChessShadow() {
let ctx = canvas.getContext("2d");
ctx.shadowColor = "#333";
ctx.shadowBlur = 5;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
}
/**
* 重新开始
* @returns {void} - 无返回值
*/
function restart() {
//移除画布
document.getElementsByTagName("canvas")[0].remove();
start(); //开始
holder = blackHolder; //重新设置当前执子为黑方
isWinSign = false; //重新设置赢标记
}
/**
* 判断是否赢
* @param {number} x - 棋子的x下标
* @param {number} y - 棋子的y下标
* @returns {boolean} - 返回是否赢
*/
function isWin(x, y) {
//方向数组
const directions = [
[-1, 0], // 垂直方向
[0, 1], // 水平方向
[-1, 1], // 右上到左下斜向
[1, 1], // 左上到右下斜向
];
const sign = map[x][y]; //当前棋子类型标记
for (let dir of directions) {
let count = 1;
// 分别向两个方向延伸
for (let i = 1; i < 5; i++) {
//对应方向下一个子的下标
const nx = x + dir[0] * i;
const ny = y + dir[1] * i;
// 如果超出棋盘边界或者棋子类型不同,则跳出循环
if (
nx < 0 ||
nx > canvasInteriorCellCount ||
ny < 0 ||
ny > canvasInteriorCellCount ||
map[nx][ny] !== sign
)
break;
count++;
}
// 反方向延伸
for (let i = 1; i < 5; i++) {
const nx = x - dir[0] * i;
const ny = y - dir[1] * i;
if (
nx < 0 ||
nx > canvasInteriorCellCount ||
ny < 0 ||
ny > canvasInteriorCellCount ||
map[nx][ny] !== sign
)
break;
count++;
}
// 如果任何一个方向的连续相同棋子数量达到了5个,则返回 true
if (count >= 5) return true;
}
return false;
}
/**
* 开始
* @returns {void} - 无返回值
*/
function start() {
initMap();
canvas = createCanvas(canvasSize, canvasSize);
drawChessboard();
dropEvent();
}
start();
效果演示:
本系列到这暂且告一段落了,以后有啥有趣的再续更