页面效果:本项目采用
HTML
页面,结合了JavaScript
和CSS
来创建一个简单的迷宫探索游戏,游戏特色包括自动寻路功能和可进行迷宫路线的更新。页面整体采用“毒药水式”的色彩搭配,给人一种迷幻,科技之感
代码展示:代码中标明了详细的注释
<html>
<head>
<meta charset="UTF-8">
<style>
/* 设置背景色为渐变色 */
#stage {
background: linear-gradient(to right, #11e015 0, #d9c123 20%, rgba(155, 21, 227, 0.54) 90%);
}
/* 设置重新开始按钮的样式 */
.rebuild {
width:170px;
height:30px;
line-height: 30px;
text-align: center;
background-image: linear-gradient(to right, rgba(154, 89, 168, 0.67), rgba(30, 145, 199, 0.67), rgba(106, 255, 0, 0.67));
color: rgb(243, 18, 237);
font-size: 18px;
margin-bottom: 20px;
cursor: pointer;
}
/* 设置html和canvas的宽度,并使其居中 */
html,
canvas{
width: 530px;
margin: auto;
}
</style>
</head>
<body>
<table><tr>
<!-- 游戏界面的canvas元素 -->
<td><canvas id="stage"></canvas></td>
<td valign="top" style="padding:10px">
<!-- 重新开始按钮 -->
<div class="rebuild">重新开始</div>
<!-- 显示当前步数和位置 -->
当前步数: <span id="goNum"></span>
<br><br>当前位置 <span id="XY">(x: 1,y: 1)</span>
<br><br>
<!-- 自动寻路按钮 -->
<input id="runBtn" type="button" value="自动寻路">
<br><br>
<!-- 显示自动寻路结果的区域 -->
<div id="autoOut"></div>
</td>
</tr></table>
</body>
<script>
var si = 8; // 设置每个格子的大小
var mapSize = 40; // 设置地图的大小
window.onload = function () {
var stage = document.querySelector('#stage'),
ctx = stage.getContext('2d');
stage.width = si * (mapSize * 2 - 1); // 设置canvas的宽度
stage.height = si * (mapSize * 2 - 1); // 设置canvas的高度
// 取区域随机数x>=min && x<max
function randInt(min, max) {
max = max || 0;
min = min || 0;
var step = Math.abs(max - min);
var st = (arguments.length < 2) ? 0 : min;
var result;
result = st + (Math.ceil(Math.random() * step)) - 1;
return result;
}
// 使用普里姆算法生成连通图的二维数组
function primMaze(r, c) {
// 初始化数组
function init(r, c) {
var a = new Array(2 * r + 1);
for (let i = 0, len = a.length; i < len; i++) {
var cols = 2 * c + 1;
a[i] = new Array(cols);
for (let j = 0, len1 = a[i].length; j < len1; j++) {
a[i][j] = 1;
}
}
a[a.length - 1][a[0].length - 2] = 0;
for (let i = 0; i < r; i++)
for (let j = 0; j < c; j++) {
a[2 * i + 1][2 * j + 1] = 0;
}
return a;
}
// 处理数组,产生最终的数组
function process(arr) {
var acc = [],
noacc = [];
var r = arr.length >> 1,
c = arr[0].length >> 1;
var count = r * c;
for (var i = 0; i < count; i++) {
noacc[i] = 0;
}
var offs = [-c, c, -1, 1],
offR = [-1, 1, 0, 0],
offC = [0, 0, -1, 1];
var pos = randInt(count);
noacc[pos] = 1;
acc.push(pos);
while (acc.length < count) {
var ls = -1,
offPos = -1;
var pr = pos / c | 0,
pc = pos % c,
co = 0,
o = 0;
while (++co < 5) {
o = randInt(0, 5);
ls = offs[o] + pos;
var tpr = pr + offR[o];
var tpc = pc + offC[o];
if (tpr >= 0 && tpc >= 0 && tpr <= r - 1 && tpc <= c - 1 && noacc[ls] == 0) {
offPos = o;
break;
}
}
if (offPos < 0) {
pos = acc[randInt(acc.length)];
} else {
pr = 2 * pr + 1;
pc = 2 * pc + 1;
arr[pr + offR[offPos]][pc + offC[offPos]] = 0;
pos = ls;
noacc[pos] = 1;
acc.push(pos);
}
}
}
var a = init(r, c);
process(a);
return a;
}
// 栅格线条
function drawGrid(context, color, stepx, stepy) {
context.strokeStyle = color;
context.lineWidth = 0.5;
for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) {
context.beginPath();
context.moveTo(i, 0);
context.lineTo(i, context.canvas.height);
context.stroke();
}
for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) {
context.beginPath();
context.moveTo(0, i);
context.lineTo(context.canvas.width, i);
context.stroke();
}
}
function createRect(x, y, r, c) {
ctx.beginPath();
ctx.fillStyle = c;
ctx.rect(x, y, r, r);
ctx.fill();
}
var region = 0; // 可探索区域
function update() {
ctx.clearRect(0, 0, mapSize * si * 2, mapSize * si * 2);
drawGrid(ctx, 'lightgray', si, si);
var mapArr = primMaze(mapSize - 1, mapSize - 1);
region = 0;
for (var i = 0, len = mapArr.length; i < len; i++) {
for (var j = 0, len1 = mapArr[i].length; j < len1; j++) {
if (mapArr[i][j]) {
createRect(i * si, j * si, si, "#999999");
} else {
region++;
}
}
}
return mapArr;
}
// 定义一个函数,用于在界面上绘制矩形,参数x, y表示矩形左上角的坐标,si是矩形的大小,c是矩形的颜色
function draw(x, y, c) {
createRect(x * si + 2, y * si + 2, si - 4, c);
}
// 定义一个函数,用于在界面上绘制移动对象的当前位置,颜色为"#66aaff"
function drawMove(x, y) {
createRect(x * si + 1, y * si + 1, si - 2, "#66aaff");
md[x][y] = 2;
}
// 定义一个函数,用于在界面上绘制移动对象经过的路径,颜色为"#3333ff"
function drawMove1(x, y) {
createRect(x * si + 2, y * si + 2, si - 4, "#3333ff");
}
// 定义一个函数,用于清除界面上指定位置的矩形
function clearMove(x, y) {
ctx.clearRect(x * si, y * si, si, si);
}
// 初始化变量,包括地图md,当前位置currentXY,上一次位置onceXY,步数goNums等
var md;
var currentXY;
var onceXY;
var goNums = 0;
var timeout = 10;
var walk = 0;
var Stop = true;
// 初始化函数,用于重置游戏状态
function init() {
document.getElementById("autoOut").innerHTML = "";
md = update(); // 更新地图数据
currentXY = [1, 1];
document.getElementById("XY").innerHTML = "x: " + currentXY[0] + " y: " + currentXY[1];
onceXY = [1, 1];
goNums = 0;
drawMove(currentXY[0], currentXY[1]);
document.getElementById("goNum").innerHTML = goNums;
control = true;
walk = 0;
pathList = [];
Stop = true;
}
init();
// 定义moveIt函数,用于处理键盘事件,控制移动对象的移动
function moveIt() {
if (!control) return;
var oxy = [currentXY[0], currentXY[1]]; // 记录原来位置
var moveIs = true;
// 根据键盘按键改变移动对象的位置
switch (event.keyCode) {
case 37:
currentXY[0]--;
break; // left
case 38:
currentXY[1]--;
break; // up
case 39:
currentXY[0]++;
break; // right
case 40:
currentXY[1]++;
break; // down
}
// 如果移动到地图外,则重新开始
if (currentXY[0] >= md[0].length || currentXY[1] >= md.length) {
clearMove(currentXY[0], currentXY[1]);
init();
return;
}
// 如果新位置是墙,则不能移动
if (md[currentXY[0]][currentXY[1]] == 1) {
moveIs = false;
}
// 如果可以移动,则更新画面和状态
if (moveIs) {
clearMove(oxy[0], oxy[1]);
drawMove1(oxy[0], oxy[1]);
if (md[currentXY[0]][currentXY[1]] == 2) {
clearMove(onceXY[0], onceXY[1]);
md[onceXY[0]][onceXY[1]] = 0;
}
drawMove(currentXY[0], currentXY[1]);
goNums++;
} else {
currentXY = [oxy[0], oxy[1]]; // 回原位
}
onceXY = [currentXY[0], currentXY[1]];
document.getElementById("goNum").innerHTML = goNums;
document.getElementById("XY").innerHTML = "x: " + currentXY[0] + " y: " + currentXY[1];
}
var control = true;
document.onkeydown = moveIt;
document.querySelector('.rebuild').addEventListener('click', init);
// 定义一些用于自动寻路的变量
var pathList; // 当前路径
var pathOkList; // 成功的路径
var pathNode; // 分叉路口数
var LukouList;
// 定义endAuto函数,用于寻路成功后的回调
function endAuto() {
// 找到最短的路径并画出
var pathCount = pathOkList.length;
var pNum = region;
var zIndex = 0;
for (var i = 0; i < pathOkList.length; i++) {
if (pathOkList[i].length < pNum) {
pNum = pathOkList[i].length;
zIndex = i;
}
}
var zdPath = pathOkList[zIndex];
for (var j = 0; j < zdPath.length; j++) {
drawMove1(zdPath[j][0], zdPath[j][1]);
}
control = true;
}
// 定义distance函数,用于计算两点之间的距离
function distance(p1, p2) {
return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2));
};
var clearPath = true;
// 定义autoFx函数,用于自动寻路
function autoFx() {
if (Stop) return;
var x = currentXY[0];
var y = currentXY[1];
var oxy = [currentXY[0], currentXY[1]];
var wayout = 0;
// 检查四个方向是否可以走
if (md[x + 1][y] == 0) {
wayout++;
pathNode.push([x + 1, y]);
}
if (md[x - 1][y] == 0) {
wayout++;
pathNode.push([x - 1, y]);
}
if (md[x][y + 1] == 0) {
wayout++;
pathNode.push([x, y + 1]);
}
if (md[x][y - 1] == 0) {
wayout++;
pathNode.push([x, y - 1]);
}
var htmlStr = "已探索区域:" + Math.round(walk / region * 100) + "%";
htmlStr += "<br><br> 有效步数: " + pathList.length;
document.getElementById("autoOut").innerHTML = htmlStr;
document.getElementById("XY").innerHTML = "x: " + currentXY[0] + " y: " + currentXY[1];
document.getElementById("goNum").innerHTML = goNums;
var ts = function () {
// 如果没有分叉路口,则结束寻路
if (pathNode.length == 0) {
endAuto();
return;
}
var lastGo = pathNode.pop();
// 清除无效路径
while (distance(lastGo, pathList[pathList.length - 1]) > 1) {
var p = pathList.pop();
setTimeout(clearMove(p[0], p[1]), timeout);
draw(p[0], p[1], "#aaaaaa");
}
currentXY[0] = lastGo[0];
currentXY[1] = lastGo[1];
walk++;
setTimeout(function () {
autoFx()
}, timeout);
};
// 如果到达地图右下角,则认为寻路成功
if (x >= md[0].length - 2 && y >= md.length - 2) {
var ppi = pathOkList.length;
pathOkList[ppi] = [];
for (var i = 0; i < pathList.length; i++) {
pathOkList[ppi][i] = [];
pathOkList[ppi][i][0] = pathList[i][0];
pathOkList[ppi][i][1] = pathList[i][1];
}
endAuto();
}
// 如果有路走,则继续探索
if (wayout > 0) {
goNums++;
pathList.push([x, y]);
drawMove1(x, y);
var newPath = pathNode.pop();
currentXY[0] = newPath[0];
currentXY[1] = newPath[1];
md[oxy[0]][oxy[1]] = 2;
walk++;
setTimeout(function () {
autoFx()
}, timeout);
} else {
// 死路,尝试其他路径
if (pathNode.length > 0) {
ts();
} else {
endAuto();
}
}
}
// 绑定点击事件,开始自动寻路
// 获取页面中ID为"runBtn"的元素,并将其onclick事件处理函数设置为以下函数
document.getElementById("runBtn").onclick = function () {
// 检查变量control的值,如果为false,则不执行以下代码,直接返回
if (!control) return;
// 将ID为"autoOut"的元素的innerHTML设置为空字符串,即清空其内容
document.getElementById("autoOut").innerHTML = "";
// 将变量currentXY的值设置为[1,1]
currentXY = [1, 1];
// 将变量pathList的值设置为空数组,用于存储路径信息
pathList = [];
// 将变量pathOkList的值设置为空数组,用于存储经过验证的路径信息
pathOkList = [];
// 将变量pathNode的值设置为空数组,用于存储路径节点信息
pathNode = [];
// 将变量walk的值设置为0
walk = 0;
// 将变量control的值设置为false
control = false;
// 将变量Stop的值设置为false
Stop = false;
autoFx();
};
}
</script>
</html>