项目展示:地址
图片
HTML
<div id="app">
<canvas id="stage" width="500" height="500"></canvas>
<canvas id="line" width="500" height="500"></canvas>
<div class="btns">
<button class="restart">重新开始</button>
</div>
</div>
<script src="./Tween.min.js"></script>
CSS
body{
margin: 0;
}
#app{
position: relative;
margin: 30px auto;
padding: 0 60px;
}
#stage,
#line{
position: absolute;
}
.btns{
position: absolute;
right: 50px;
}
.btns button{
padding: 6px 11px;
color: #fff;
background-color: #38f;
border: none;
outline: none;
border-radius: 2px;
cursor: pointer;
}
第一步:定义变量
const stage = document.querySelector("#stage"), //舞台
s_ctx = stage.getContext("2d"), // 上下文环境
line = document.querySelector("#line"), // 线
l_ctx = line.getContext("2d"),
size = 50, // 格子宽高
r = Math.floor(size / 2), //半径
row = 12,
col = 12,
w = 600,
h = 600,
imgs = ["2c.png","2d.png","2h.png","2s.png","jh.png","3s.png","4c.png","3d.png","9c.png","kc.png","6d.png","8s.png"]; // 图片
stage.width = w;
stage.height = h;
line.width = w;
line.height = h;
let isClick = false;
let coors = new Array(row).fill(null).map(item => new Array(col).fill(null)), // 填充坐标
coorsImg = [],
dx = [0, 1, 0, -1], //X轴方位
dy = [-1, 0, 1, 0]; //Y轴方位
第二步:初始化项目
async function initImg(){ // 加载图片
let temp = [],
imgLen = imgs.length,
len = Math.floor(((row - 2) * (col - 2) ) / 2);
for(let i = 0; i < len; i++){
let type = rand(0,imgLen);
let src = imgs[type];
temp.push({img:await loadImg(src),type});
temp.push({img:await loadImg(src),type});
}
return temp.sort((a,b)=> (Math.random() - 0.5));
}
function initBorad(){ // 初始化格子和图片
for(let i = 0; i < col; i++){
for(let j = 0; j < row; j++){
drawRect(j,i,coors[i][j]);
}
}
}
function drawRect(x,y,temp,shadowBlur=0,shadowColor="rgba(0, 0, 0, 0)",strokeStyle="#000",lineWidth=1){ // 渲染格子和图片
s_ctx.beginPath();
temp && s_ctx.drawImage(temp.img,x * size, y * size, size, size);
s_ctx.lineWidth = lineWidth;
s_ctx.shadowBlur = shadowBlur;
s_ctx.shadowColor = shadowColor;
s_ctx.strokeStyle = strokeStyle;
s_ctx.strokeRect(x * size, y * size, size, size);
s_ctx.closePath();
}
function createCoors(){ // 初始化
initImg().then(imgs=>{
coorsImg = imgs;
render();
});
}
function render(){ // 渲染
let rows = row - 1,
cols = col - 1,
n = rows - 1;
for(let i = 1; i < rows; i++){
for(let j = 1; j < cols; j++){
coors[i][j] = coorsImg[(i - 1) * n + (j - 1)];
}
}
initBorad();
}
这步执行完,内容已经基本能渲染完毕,剩下的就是逻辑内容和算法计算。
第三步:绑定事件操作
function bindEvent(){
let prev = null;
// 因为线这个canvas层级高于舞台,所以事件绑定在线元素上
line.onclick = function(e){
if(isClick)return; // 判断是否可以点击
let {offsetX, offsetY} = e; // 获取点击坐标
// 转化成游戏坐标
const x = toCoors(offsetX);
const y = toCoors(offsetY);
// 判断坐标是否存在
if(!coors[y][x])return;
// 记录上一次的点击坐标
if(!prev){
prev = {x, y};
drawRect(x,y,null,3,"red","#38f",2);
return
}
const px = prev.x;
const py = prev.y;
const t1 = coors[y][x],
t2 = coors[py][px];
prev = null;
// 两种类型不匹配则回归初始状态
if(t1.type !== t2.type){
clearAndRender();
return;
}
drawRect(x,y,null,3,"red","#38f",2);
// 最短路径检测
isClick = BFS(px,py,x,y);
if(isClick){
coors[y].splice(x,1,null);
coors[py].splice(px,1,null);
return;
}
clearAndRender();
}
}
这一步事件就算是绑定完成,接下来就是最短路径计算。
第四步:最短路径计算
function BFS(x,y,tx,ty){
let records = {}; // 记录已走坐标
let steps = []; // 广度路径步骤
let paths = [{x,y}]; // 走过的路径
steps.push({x,y});
while(steps.length){
let temp = steps.shift();
x = temp.x;
y = temp.y;
for(let i = 0; i < 4; i++){
let cx = x + dx[i],
cy = y + dy[i];
if(cx < 0 || cy < 0 || cx > row - 1 || cy > col - 1)continue;
if(cx === tx && cy === ty){
paths.push({x:cx,y:cy});
// 计算路径和绘制线路
authDrawLine(processPath([...paths]));
return true
}
if(!records[cx+"_"+cy] && !coors[cy][cx]){
records[cx+"_"+cy] = true;
steps.push({x:cx,y:cy});
paths.push({x:cx,y:cy});
}
continue;
}
}
return false
}
function processPath(path){ // 计算路径坐标
let point = path.pop();
let i = path.length - 1;
let points = [point];
while(true){
if(i < 0)break;
let temp = path[i];
let x = Math.abs(temp.x - point.x);
let y = Math.abs(temp.y - point.y);
if(( (x == 1 && y == 0) || (x == 0 && y == 1)) ){
points.push(temp);
point = temp;
}
i--;
}
return points;
}
这一步之后,游戏基本算完成了,剩下就是线的动画,这个就不继续讲解代码了,可自己拷贝下来查看。
总的来说,这个游戏并不难,路径计算也不复杂,注意点就是不要进入死循环。
deny (作者)
不对,是提交的字段有限制,存进去就没有。
2023-04-19 16:51
deny (作者)
只能用 array 这个代替 string被限制方案。
2023-04-19 17:42
deny (作者)
array线上也不行,只能本地测试可以。
2023-04-19 20:08
deny (作者)
不行,最多只能存 1K多大小,但是直接在 unicloud控制台却可以存进去,这API不同步,不懂搞的什么。
2023-04-19 20:49