【Canvas】使用Vue3+TS+Canvas实现五子棋
2022-11-25 本文已影响0人
Ringo_
1.效果预览
动画.gif2.实现思路
- 创建画布
<canvas id="chess_canvas" ref="chess_canvas"></canvas>
- 创建绘制对象和棋盘DOM
let ctx: CanvasRenderingContext2D;
const chess_canvas = ref<HTMLCanvasElement>();
- 使用二维数组记录棋盘格的信息
let record: number[][] = [];
- 记录当前要下的棋子是黑还是白
let isBlack = true;
- 初始化画布,将画布的大小设置为600x600,边缘留出20,棋盘规模为14x14,每个格子的大小为40
ctx = chess_canvas.value?.getContext("2d") as CanvasRenderingContext2D;
chess_canvas.value!.width = 600;
chess_canvas.value!.height = 600;
chess_canvas.value!.style.background = "#e3cdb0";
isBlack = true;
- 绘制棋盘的横线和竖线
for (let i = 0; i < 15; i++) {
ctx.beginPath();
ctx.moveTo(20, 20 + 40 * i);
ctx.lineTo(580, 20 + 40 * i);
ctx.stroke();
ctx.closePath();
}
for (let i = 0; i < 15; i++) {
ctx.beginPath();
ctx.moveTo(20 + 40 * i, 20);
ctx.lineTo(20 + 40 * i, 580);
ctx.stroke();
ctx.closePath();
}
- 初始化棋盘格数组,0代表未下,1代表黑子,2代表白子
for (let i = 0; i < 15; i++) {
record[i] = new Array(15).fill(0);
}
- 给棋盘添加点击事件,棋子只能落到格子的交叉点上,所以要对x的位置和y的位置进行取整,选取离点击点位置最近的格子,绘制棋子,之后更换棋子颜色
chess_canvas.value!.onclick = e => {
let x = e.offsetX - (e.offsetX % 40) + 20;
let y = e.offsetY - (e.offsetY % 40) + 20;
let pX = (x - 20) / 40;
let pY = (y - 20) / 40;
// 棋盘的X和Y和二维数组的X和Y是相反的
if (record[pY][pX] != 0) {
return;
}
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.fillStyle = isBlack ? "black" : "white";
ctx.fill();
ctx.closePath();
record[pY][pX] = isBlack ? 1 : 2;
isBlack = !isBlack;
};
-
在下过一个棋子之后,判断这颗棋子是否能和棋盘中的其他棋子连成5颗。
我的做法是这样的(自己想的,代码量不多,有不足之处请指教):每下过一颗棋子,以该棋子为中心,读取【上4颗+该棋子+下4颗】,【左4颗+该棋子+右4颗】,【左上对角4颗+该棋子+左下对角4颗】,【右上对角4颗+该棋子+右下对角4颗】,存储为4个数组:
image.png
然后依次判断这4个数组中,是否有连续5个相同的数字(数字的值等于刚刚下过的棋子的值),判断算法是这样设计的:一个数组最大长度为9,若该数组中含有5个相同的数字,那么该数组的中间数字必定为这5个数字中的一个,然后以该中间数为中心,若左边的数和中间数相等,则一直向左寻找边界,若右边的数和中间的数相等,则一直向右寻找边界,如果找到左右边界,判断左右边界的差值,如果差值为4,那么已经连成了5子。
const isWin = (x: number, y: number) => {
let res: number[][] = [];
let xadd = [];
let yadd = [];
let zadd = [];
let wadd = [];
let flag = false;
// 上 下 左 右 +4
for (let i = 4; i >= -4; i--) {
if (x - i >= 0 && x - i <= 14) {
xadd.push(record[x - i][y]);
}
if (y - i >= 0 && y - i <= 14) {
yadd.push(record[x][y - i]);
}
if (x - i >= 0 && x - i <= 14 && y - i >= 0 && y - i <= 14) {
zadd.push(record[x - i][y - i]);
}
if (x + i >= 0 && x + i <= 14 && y - i >= 0 && y - i <= 14) {
wadd.push(record[x + i][y - i]);
}
}
res.push(xadd);
res.push(yadd);
res.push(zadd);
res.push(wadd);
let target = record[x][y];
res.forEach(arr => {
let mid = Math.floor(arr.length / 2);
let left = mid;
let right = mid;
while (arr[left] == target) {
left--;
}
while (arr[right] == target) {
right++;
}
if (right - 1 - (left + 1) == 4) {
flag = true;
}
});
return flag;
};
- 连成5子后,显示胜利弹窗,可以选择重玩
setTimeout(() => {
if (isWin(pY, pX)) {
const con = confirm(`${!isBlack ? "黑棋" : "白棋"}赢了!是否重新开局?`);
ctx.clearRect(0, 0, 600, 600);
con && initCanvas();
}
}, 10);
3.全部代码
<div class="box">
<canvas id="chess_canvas" ref="chess_canvas"></canvas>
</div>
import { onBeforeUnmount, onMounted, ref } from "vue";
/** 绘制对象 */
let ctx: CanvasRenderingContext2D;
/** 棋盘DOM */
const chess_canvas = ref<HTMLCanvasElement>();
/** 记录棋盘格信息的数组 */
let record: number[][] = [];
/** 当前是否要下黑棋 */
let isBlack = true;
/** 初始化 */
const initCanvas = () => {
ctx = chess_canvas.value?.getContext("2d") as CanvasRenderingContext2D;
chess_canvas.value!.width = 600;
chess_canvas.value!.height = 600;
chess_canvas.value!.style.background = "#e3cdb0";
isBlack = true;
drawCheckerboard();
};
/** 绘制棋盘:每个棋格大小40*40 */
const drawCheckerboard = () => {
for (let i = 0; i < 15; i++) {
ctx.beginPath();
ctx.moveTo(20, 20 + 40 * i);
ctx.lineTo(580, 20 + 40 * i);
ctx.stroke();
ctx.closePath();
}
for (let i = 0; i < 15; i++) {
ctx.beginPath();
ctx.moveTo(20 + 40 * i, 20);
ctx.lineTo(20 + 40 * i, 580);
ctx.stroke();
ctx.closePath();
}
/** 初始化棋盘格数组,0代表未下,1代表黑子,2代表白子 */
for (let i = 0; i < 15; i++) {
record[i] = new Array(15).fill(0);
}
/** 给棋盘添加点击事件 */
chess_canvas.value!.onclick = e => {
// 进行取整,确保棋子落在最近的棋盘格交叉点上
let x = e.offsetX - (e.offsetX % 40) + 20;
let y = e.offsetY - (e.offsetY % 40) + 20;
let pX = (x - 20) / 40;
let pY = (y - 20) / 40;
if (record[pY][pX] != 0) {
return;
}
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.fillStyle = isBlack ? "black" : "white";
ctx.fill();
ctx.closePath();
record[pY][pX] = isBlack ? 1 : 2;
isBlack = !isBlack;
setTimeout(() => {
if (isWin(pY, pX)) {
const con = confirm(`${!isBlack ? "黑棋" : "白棋"}赢了!是否重新开局?`);
ctx.clearRect(0, 0, 600, 600);
con && initCanvas();
}
}, 10);
};
};
const isWin = (x: number, y: number) => {
let res: number[][] = [];
let xadd = [];
let yadd = [];
let zadd = [];
let wadd = [];
let flag = false;
// 上 下 左 右 +4
for (let i = 4; i >= -4; i--) {
if (x - i >= 0 && x - i <= 14) {
xadd.push(record[x - i][y]);
}
if (y - i >= 0 && y - i <= 14) {
yadd.push(record[x][y - i]);
}
if (x - i >= 0 && x - i <= 14 && y - i >= 0 && y - i <= 14) {
zadd.push(record[x - i][y - i]);
}
if (x + i >= 0 && x + i <= 14 && y - i >= 0 && y - i <= 14) {
wadd.push(record[x + i][y - i]);
}
}
res.push(xadd);
res.push(yadd);
res.push(zadd);
res.push(wadd);
let target = record[x][y];
res.forEach(arr => {
let mid = Math.floor(arr.length / 2);
let left = mid;
let right = mid;
while (arr[left] == target) {
left--;
}
while (arr[right] == target) {
right++;
}
if (right - 1 - (left + 1) == 4) {
flag = true;
}
});
return flag;
};
onMounted(() => {
initCanvas();
});
onBeforeUnmount(() => {
ctx.clearRect(0, 0, 600, 600);
});