1.效果預(yù)覽
動畫.gif
?? 項目地址?
2.實現(xiàn)思路
- 創(chuàng)建畫布
<canvas id="chess_canvas" ref="chess_canvas"></canvas>
- 創(chuàng)建繪制對象和棋盤DOM
let ctx: CanvasRenderingContext2D;
const chess_canvas = ref<HTMLCanvasElement>();
- 使用二維數(shù)組記錄棋盤格的信息
let record: number[][] = [];
- 記錄當(dāng)前要下的棋子是黑還是白
let isBlack = true;
- 初始化畫布妥曲,將畫布的大小設(shè)置為600x600业舍,邊緣留出20妓柜,棋盤規(guī)模為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();
}
- 初始化棋盤格數(shù)組客燕,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和二維數(shù)組的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個數(shù)組:
image.png
然后依次判斷這4個數(shù)組中鸯屿,是否有連續(xù)5個相同的數(shù)字(數(shù)字的值等于剛剛下過的棋子的值)澈吨,判斷算法是這樣設(shè)計的:一個數(shù)組最大長度為9,若該數(shù)組中含有5個相同的數(shù)字寄摆,那么該數(shù)組的中間數(shù)字必定為這5個數(shù)字中的一個谅辣,然后以該中間數(shù)為中心,若左邊的數(shù)和中間數(shù)相等婶恼,則一直向左尋找邊界桑阶,若右邊的數(shù)和中間的數(shù)相等,則一直向右尋找邊界勾邦,如果找到左右邊界蚣录,判斷左右邊界的差值,如果差值為4眷篇,那么已經(jīng)連成了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>();
/** 記錄棋盤格信息的數(shù)組 */
let record: number[][] = [];
/** 當(dāng)前是否要下黑棋 */
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();
}
/** 初始化棋盤格數(shù)組昧港,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);
});