前阵子不是给博客搬家了么,导致好多链接无法访问,我就想着弄一个404页面但不想太单调了,就把52破解的404搬过来改了下玩法提高了难度还挺好玩😂
优化后玩法
点击格子放围墙,把小丑围住即胜利;小丑跑到棋盘边缘则失败。 紫色为陷阱格子,小丑踩到会被禁行动一回合;金色为幸运格子,放置围墙后本回合小丑直接不能移动。
每8个玩家回合触发一次微风或迷雾
🌬微风:随机拆掉场上一堵普通围墙(不会拆陷阱、幸运格子)。
🌀迷雾:下一回合小丑不会走最优逃跑路线,会在可行格子里随机乱走。
代码是用豆包帮我完善的还挺不错。
完整代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>围堵小丑</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #f8f9fa;
color: #333;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
padding: 20px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.container {
max-width: 480px;
width: 100%;
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
h1.error-code {
font-size: 5.5rem;
color: #e53935;
margin-bottom: 10px;
font-weight: 700;
line-height: 1;
}
h2.sub-title {
font-size: 1.25rem;
color: #444;
margin-bottom: 12px;
font-weight: 500;
}
.desc {
font-size: 0.95rem;
color: #666;
line-height: 1.6;
margin-bottom: 8px;
}
.score-info{
font-size:0.85rem;
color:#666;
margin-bottom:12px;
}
#status {
font-size: 0.9rem;
color: #444;
margin-bottom: 14px;
min-height: 20px;
font-weight: 500;
transition: color 0.25s ease;
}
#status.win { color: #28a745; }
#status.lose { color: #e53935; }
#status.warn { color: #ff7700; }
#status.trap { color: #9c27b0; }
#status.event { color:#d48806; }
.game-wrap {
margin-bottom: 18px;
display: inline-block;
border: 1px solid #dee2e6;
border-radius: 10px;
padding: 12px;
background: #ffffff;
}
canvas {
display: block;
cursor: pointer;
margin: 0 auto;
-webkit-tap-highlight-color: transparent;
border-radius: 6px;
max-height: 52vh;
width: auto;
}
.reset-row {
margin-bottom: 22px;
}
.btn-reset {
font-size: 0.85rem;
color: #0d6efd;
background: none;
border: none;
cursor: pointer;
padding: 4px 10px;
border-radius: 6px;
transition: background 0.2s ease;
}
.btn-reset:hover {
background: #e7f1ff;
}
.btn-group {
display: flex;
gap: 14px;
justify-content: center;
}
.btn {
flex: 1;
max-width: 160px;
padding: 10px 20px;
border-radius: 8px;
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
transition: all 0.2s ease;
border: none;
cursor: pointer;
}
.btn-primary {
background: #0d6efd;
color: #fff;
}
.btn-primary:hover {
background: #0b5ed7;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.25);
}
.btn-secondary {
background: #ffffff;
color: #495057;
border: 1px solid #dee2e6;
}
.btn-secondary:hover {
background: #f8f9fa;
transform: translateY(-1px);
}
</style>
</head>
<body>
<div class="container">
<h1 class="error-code">404</h1>
<h2 class="sub-title">Not Found · 页面未找到</h2>
<p class="desc">
您所访问的页面不存在或者已更新,请跳转博客或者返回书签
</p>
<div class="score-info" id="scoreInfo"></div>
<div id="status">点击小圆点,围住小丑</div>
<div class="game-wrap">
<canvas id="game"></canvas>
</div>
<div class="reset-row">
<button class="btn-reset" id="resetBtn">重新开始</button>
</div>
<div class="btn-group">
<a href="https://blog.el9.cn" class="btn btn-primary">返回博客</a>
<a href="https://el9.cn" class="btn btn-secondary">返回书签</a>
</div>
</div>
<script>
(function() {
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const statusEl = document.getElementById('status');
const resetBtn = document.getElementById('resetBtn');
const scoreInfoEl = document.getElementById('scoreInfo');
const ROW = 11;
const COL = 11;
const R = 14;
const GAP = 5;
const STEP_TIME = 150;
const DOUBLE_MOVE_CHANCE = 0.3;
const EVENT_INTERVAL = 8;
const dpr = window.devicePixelRatio || 1;
//小丑出生点周围6个格子
const CLOWN_START_NEIGHBORS = [[4,5],[5,4],[5,6],[6,5],[4,4],[6,6]];
let map = [];
let trapMap = [];
let luckyCell = null;
let clown = { r:5, c:5 };
let clownDraw = { x:0, y:0 };
let gameOver = false;
let moving = false;
let loopId = null;
let moveAniId = null;
let nextStepCount = 1;
let justDoubleMove = false;
let turnCount = 0;
let eventCounter = 0;
let winCount = 0;
let failCount = 0;
let skipTurn = false;
let eventConfuse = false;
//本地存储
function loadStats(){
winCount = Number(localStorage.getItem("clownWinCount"))||0;
failCount = Number(localStorage.getItem("clownFailCount"))||0;
renderScoreText();
}
function saveWin(){
winCount +=1;
localStorage.setItem("clownWinCount", String(winCount));
renderScoreText();
}
function addFail(){
failCount +=1;
localStorage.setItem("clownFailCount", String(failCount));
renderScoreText();
}
function renderScoreText(){
scoreInfoEl.textContent = `本局回合:${turnCount}|累计胜利:${winCount}局|累计失败:${failCount}局`;
}
//获取画布坐标
function getPos(r, c) {
const x = R + 5 + c * (R * 2 + GAP) + (r % 2 === 1 ? R + GAP / 2 : 0);
const y = R + 5 + r * (R * 2 + GAP);
return { x, y };
}
//获取六边形相邻格子
function getNeighbors(r, c) {
const neighbors = [[r, c-1],[r, c+1]];
if(r%2===0){
neighbors.push([r-1,c-1],[r-1,c],[r+1,c-1],[r+1,c]);
}else{
neighbors.push([r-1,c],[r-1,c+1],[r+1,c],[r+1,c+1]);
}
return neighbors.filter(([nr,nc])=> nr>=0&&nr<ROW&&nc>=0&&nc<COL);
}
function isNearActiveTrap(r,c){
return getNeighbors(r,c).some(([nr,nc])=> trapMap[nr][nc]===1);
}
//BFS逃跑距离
function calcDistMap() {
const dist = Array.from({length:ROW}, ()=>Array(COL).fill(Infinity));
const queue = [];
for(let r=0;r<ROW;r++){
for(let c=0;c<COL;c++){
if((r===0||r===ROW-1||c===0||c===COL-1) && map[r][c]===0){
dist[r][c]=1;
queue.push([r,c]);
}
}
}
while(queue.length>0){
const [cr,cc] = queue.shift();
for(const [nr,nc] of getNeighbors(cr,cc)){
if(map[nr][nc]===0 && dist[nr][nc]===Infinity){
dist[nr][nc] = dist[cr][cc]+1;
queue.push([nr,nc]);
}
}
}
return dist;
}
//随机事件(移除警觉,仅微风、迷雾)
function triggerRandomEvent(){
const events = ["wind","confuse"];
const e = events[Math.floor(Math.random()*events.length)];
switch(e){
case "wind":{
const wallList = [];
for(let r=0;r<ROW;r++){
for(let c=0;c<COL;c++){
if(map[r][c]===1 && !(r===5&&c===5)) wallList.push({r,c});
}
}
if(wallList.length>2){
const t = wallList[Math.floor(Math.random()*wallList.length)];
map[t.r][t.c]=0;
}
statusEl.textContent = "🌬事件:微风袭来,一面围墙被吹倒!";
statusEl.className = "event";
break;
}
case "confuse":{
eventConfuse = true;
statusEl.textContent = "🌀事件:迷雾迷惑,小丑下一回合会乱走!";
statusEl.className = "event";
break;
}
}
}
function drawClown(x,y){
ctx.lineWidth = 1.6;
ctx.strokeStyle = '#d17a00';
ctx.fillStyle = 'rgba(209,122,0,0.08)';
ctx.beginPath(); ctx.arc(x,y+2,R-3,0,Math.PI*2); ctx.fill();
ctx.fillStyle = '#ff9f43';
ctx.beginPath(); ctx.arc(x,y,R-3.5,0,Math.PI*2); ctx.fill(); ctx.stroke();
ctx.fillStyle = '#ff5252';
ctx.beginPath();
ctx.moveTo(x-5,y-7); ctx.lineTo(x,y-15); ctx.lineTo(x+5,y-7); ctx.closePath();
ctx.fill(); ctx.stroke();
ctx.fillStyle = '#ffeb3b';
ctx.beginPath(); ctx.arc(x,y-16,1.8,0,Math.PI*2); ctx.fill();
ctx.fillStyle='#fff';
ctx.beginPath(); ctx.arc(x-3,y-2,2.4,0,Math.PI*2); ctx.arc(x+3,y-2,2.4,0,Math.PI*2); ctx.fill();
ctx.fillStyle='#111';
ctx.beginPath(); ctx.arc(x-3,y-2,1.3,0,Math.PI*2); ctx.arc(x+3,y-2,1.3,0,Math.PI*2); ctx.fill();
ctx.fillStyle='#fff';
ctx.beginPath(); ctx.arc(x-3.5,y-2.8,0.7,0,Math.PI*2); ctx.arc(x+2.5,y-2.8,0.7,0,Math.PI*2); ctx.fill();
ctx.fillStyle='#e53935';
ctx.beginPath(); ctx.arc(x,y+2.5,1.8,0,Math.PI*2); ctx.fill();
ctx.strokeStyle='#d17a00'; ctx.lineWidth=1.2;
ctx.beginPath(); ctx.arc(x,y+3.5,4,0.15*Math.PI,0.85*Math.PI); ctx.stroke();
}
function render(){
ctx.clearRect(0,0,canvas.width,canvas.height);
for(let r=0;r<ROW;r++){
for(let c=0;c<COL;c++){
const {x,y}=getPos(r,c);
ctx.beginPath(); ctx.arc(x,y,R,0,Math.PI*2);
if(luckyCell && luckyCell.r===r && luckyCell.c===c){
ctx.fillStyle="#ffd700";
}else if(map[r][c]===1){
ctx.fillStyle="#34495e";
}else if(trapMap[r][c]===1){
ctx.fillStyle="#ce93d8";
}else{
ctx.fillStyle="#b3d9ff";
}
ctx.fill();
}
}
drawClown(clownDraw.x, clownDraw.y);
loopId = requestAnimationFrame(render);
}
function rollNextStep(){
if(skipTurn){
skipTurn = false;
nextStepCount = 0;
statusEl.textContent = '⚠ 小丑踩到陷阱,本回合无法移动!';
statusEl.className = 'trap';
return;
}
if(justDoubleMove){
nextStepCount = 1;
justDoubleMove = false;
}else{
nextStepCount = isNearActiveTrap(clown.r,clown.c) ? 1 : (Math.random() < DOUBLE_MOVE_CHANCE ? 2 : 1);
}
if(nextStepCount === 2){
statusEl.textContent = '⚠ 下回合小丑将走 2 步!';
statusEl.className = 'warn';
}else{
statusEl.textContent = '点击小圆点,围住小丑';
statusEl.className = '';
}
}
function clownMove(){
if(gameOver || moving) return;
if(nextStepCount === 0){
moving = true;
setTimeout(()=>{ moving=false; rollNextStep(); },300);
return;
}
let stepsCompleted = 0;
moving = true;
if(nextStepCount === 2) justDoubleMove = true;
function doOneStep(){
const distMap = calcDistMap();
if(distMap[clown.r][clown.c] === Infinity){
gameOver = true;
statusEl.textContent = '你赢了!小丑被围住了';
statusEl.className = 'win';
moving = false;
saveWin();
return;
}
const neighbors = getNeighbors(clown.r,clown.c).filter(([nr,nc])=>map[nr][nc]===0);
if(neighbors.length === 0){
gameOver = true;
statusEl.textContent = '你赢了!小丑被围住了';
statusEl.className = 'win';
moving = false;
saveWin();
return;
}
let pick;
if(eventConfuse){
const idx = Math.floor(Math.random()*neighbors.length);
pick = {r:neighbors[idx][0], c:neighbors[idx][1]};
eventConfuse = false;
}else{
const candidates = [];
for(const [nr,nc] of neighbors){
const d = distMap[nr][nc];
if(d===Infinity) continue;
candidates.push({r:nr,c:nc, dist:d, isTrap: trapMap[nr][nc]===1});
}
candidates.sort((a,b)=>{
if(a.dist !== b.dist) return a.dist - b.dist;
if(a.isTrap !== b.isTrap) return a.isTrap ? 1 : -1;
return 0;
});
const bestDist = candidates[0].dist;
const filtered = candidates.filter(it=>it.dist===bestDist);
pick = filtered[Math.floor(Math.random()*filtered.length)];
}
const next = {r:pick.r, c:pick.c};
const start = {...clownDraw};
const end = getPos(next.r, next.c);
const startTime = performance.now();
function animateStep(now){
const p = Math.min((now-startTime)/STEP_TIME,1);
clownDraw.x = start.x + (end.x-start.x)*p;
clownDraw.y = start.y + (end.y-start.y)*p;
if(p<1){
moveAniId = requestAnimationFrame(animateStep);
}else{
clown = next;
if(trapMap[clown.r][clown.c]===1){
trapMap[clown.r][clown.c]=2;
skipTurn = true;
}
stepsCompleted++;
if(clown.r===0||clown.r===ROW-1||clown.c===0||clown.c===COL-1){
gameOver = true;
statusEl.textContent = '你输了!小丑跑掉了';
statusEl.className = 'lose';
moving = false;
addFail();
return;
}
if(stepsCompleted < nextStepCount){
doOneStep();
}else{
moving = false;
rollNextStep();
}
}
}
moveAniId = requestAnimationFrame(animateStep);
}
doOneStep();
}
function initCanvasSize(){
const maxX = Math.max(getPos(0,COL-1).x, getPos(1,COL-1).x)+R+5;
const cssHeight = getPos(ROW-1,0).y + R +5;
canvas.width = maxX * dpr;
canvas.height = cssHeight * dpr;
canvas.style.width = maxX + 'px';
canvas.style.height = cssHeight + 'px';
ctx.scale(dpr, dpr);
}
function initGame(){
if(loopId) cancelAnimationFrame(loopId);
if(moveAniId) cancelAnimationFrame(moveAniId);
gameOver = false;
moving = false;
turnCount = 0;
eventCounter = 0;
justDoubleMove = false;
skipTurn = false;
eventConfuse = false;
luckyCell = null;
map = Array.from({length:ROW}, ()=>Array(COL).fill(0));
trapMap = Array.from({length:ROW}, ()=>Array(COL).fill(0));
//【强制恰好4堵出生围墙】对6个周边格子洗牌,取前4个设墙
const shuffled = [...CLOWN_START_NEIGHBORS].sort(()=>Math.random()-0.5);
for(let i=0;i<4;i++){
const [r,c] = shuffled[i];
map[r][c]=1;
}
//生成其余随机障碍
let obstacleNum = 8 + Math.floor(Math.random()*4);
while(obstacleNum>0){
const r = Math.floor(Math.random()*ROW);
const c = Math.floor(Math.random()*COL);
const dist = Math.abs(r-5)+Math.abs(c-5);
if(map[r][c]===0 && !(r===5&&c===5) && dist>1){
map[r][c]=1;
obstacleNum--;
}
}
//生成幸运格
while(true){
const lr = Math.floor(Math.random()*ROW);
const lc = Math.floor(Math.random()*COL);
const dist = Math.abs(lr-5)+Math.abs(lc-5);
if(map[lr][lc]===0 && dist>=2){
luckyCell = {r:lr,c:lc};
break;
}
}
//生成陷阱
const trapCount = 2 + Math.floor(Math.random()*2);
let tc = 0;
while(tc < trapCount){
const tr = Math.floor(Math.random()*ROW);
const tc_ = Math.floor(Math.random()*COL);
const dist = Math.abs(tr-5)+Math.abs(tc_-5);
if(map[tr][tc_]===0 && trapMap[tr][tc_]===0 && dist>=2 && !(luckyCell && luckyCell.r===tr && luckyCell.c===tc_)){
trapMap[tr][tc_]=1;
tc++;
}
}
//校验地图,小丑无路可逃就重生成
const distMap = calcDistMap();
if(distMap[5][5] === Infinity){
return initGame();
}
clown = {r:5,c:5};
clownDraw = getPos(clown.r, clown.c);
rollNextStep();
renderScoreText();
render();
}
canvas.addEventListener('click', (e)=>{
if(gameOver){
initGame();
return;
}
if(moving) return;
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
const scaleX = canvas.width / dpr / rect.width;
const scaleY = canvas.height / dpr / rect.height;
const realX = mx * scaleX;
const realY = my * scaleY;
for(let r=0;r<ROW;r++){
for(let c=0;c<COL;c++){
const {x,y}=getPos(r,c);
const dist = Math.hypot(realX-x, realY-y);
if(dist < R){
if(map[r][c]===0 && !(r===clown.r && c===clown.c)){
let luckyTriggered = false;
if(luckyCell && luckyCell.r===r && luckyCell.c===c){
luckyTriggered = true;
luckyCell = null;
}
map[r][c]=1;
turnCount++;
eventCounter++;
renderScoreText();
if(luckyTriggered){
statusEl.textContent="✨幸运格生效!小丑本次无法移动!";
statusEl.className="trap";
return;
}
if(eventCounter >= EVENT_INTERVAL){
eventCounter = 0;
triggerRandomEvent();
return;
}
clownMove();
}
return;
}
}
}
});
resetBtn.addEventListener('click', initGame);
loadStats();
initCanvasSize();
initGame();
})();
</script>
</body>
</html>
ayiya