Remix of 20×20蛇对战 | 8食物3炸弹
by RapidBunny56229 lines6.5 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>20×20蛇对战 | 8食物3炸弹</title>
<style>
*{box-sizing:border-box;font-family:system-ui}
body{display:flex;flex-direction:column;align-items:center;background:#111;color:#fff;margin:4px}
#game{border:2px solid #aaa;background:#000}
.info{margin:6px 0;font-size:14px;text-align:center}
#restartBtn{
margin:8px 0;
padding:6px 16px;
font-size:16px;
background:#22aa44;
color:white;
border:none;
border-radius:4px;
cursor:pointer;
}
#restartBtn:hover{background:#2cc450}
</style>
</head>
<body>
<div class="info">
玩家1:WASD(绿)|玩家2:方向键(橙)<br>
吃到食物+1长度|撞炸弹-2长度|头撞对方身体直接淘汰<br>
蛇死亡遗留方块数量 = 生前吃掉的食物总数
</div>
<button id="restartBtn">🔄 重新开始游戏</button>
<canvas id="game" width="400" height="400" tabindex="0"></canvas>
<script>
// 等待DOM加载完毕再启动游戏,解决执行时机问题
document.addEventListener('DOMContentLoaded', function(){
const size = 20;
const cell = 20;
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
if(!ctx){
console.log("画布上下文获取失败");
return;
}
const restartBtn = document.getElementById("restartBtn");
//创建蛇
function createSnake(x,y,dx,dy,color){
return {
body:[{x,y},{x-dx,y-dy},{x-dx*2,y-dy*2}],
dx, dy,
color,
foodEaten:0,
alive:true
}
}
let s1, s2;
let foods = [];
let bombs = [];
//获取空坐标
function getEmptyPos(){
while(true){
let p = {x:Math.floor(Math.random()*size), y:Math.floor(Math.random()*size)};
let occupied = false;
s1.body.forEach(b=>{if(b.x===p.x&&b.y===p.y) occupied=true;})
s2.body.forEach(b=>{if(b.x===p.x&&b.y===p.y) occupied=true;})
foods.forEach(f=>{if(f.x===p.x&&f.y===p.y) occupied=true;})
bombs.forEach(b=>{if(b.x===p.x&&b.y===p.y) occupied=true;})
if(!occupied) return p;
}
}
//重置游戏
function resetGame(){
s1 = createSnake(5,10,1,0,'#39ff66');
s2 = createSnake(14,10,-1,0,'#ff9933');
foods = [];
bombs = [];
for(let i=0;i<8;i++) foods.push(getEmptyPos());
for(let i=0;i<3;i++) bombs.push(getEmptyPos());
canvas.focus();
}
resetGame();
//键盘
const keys = {};
document.addEventListener('keydown',e=>{
keys[e.key.toLowerCase()] = true;
keys[e.code] = true;
})
document.addEventListener('keyup',e=>{
keys[e.key.toLowerCase()]=false;
keys[e.code]=false;
})
restartBtn.onclick = resetGame;
//更新方向
function updateDir(){
//玩家1 WASD
if(keys['w'] && s1.dy!==1){s1.dx=0;s1.dy=-1}
if(keys['s'] && s1.dy!==-1){s1.dx=0;s1.dy=1}
if(keys['a'] && s1.dx!==1){s1.dx=-1;s1.dy=0}
if(keys['d'] && s1.dx!==-1){s1.dx=1;s1.dy=0}
//玩家2方向键
if(keys['ArrowUp'] && s2.dy!==1){s2.dx=0;s2.dy=-1}
if(keys['ArrowDown'] && s2.dy!==-1){s2.dx=0;s2.dy=1}
if(keys['ArrowLeft'] && s2.dx!==1){s2.dx=-1;s2.dy=0}
if(keys['ArrowRight'] && s2.dx!==-1){s2.dx=1;s2.dy=0}
}
//移动蛇
function moveSnake(snake){
if(!snake.alive) return;
const head = snake.body[0];
const newHead = {x:head.x + snake.dx, y:head.y + snake.dy};
//穿墙环绕
if(newHead.x<0) newHead.x = size-1;
if(newHead.x>=size) newHead.x = 0;
if(newHead.y<0) newHead.y = size-1;
if(newHead.y>=size) newHead.y = 0;
//头碰到对手身体 → 淘汰
const enemy = snake === s1 ? s2 : s1;
for(let seg of enemy.body){
if(newHead.x === seg.x && newHead.y === seg.y){
snake.alive = false;
return;
}
}
let ateFood = false;
for(let i=0;i<foods.length;i++){
const f = foods[i];
if(newHead.x === f.x && newHead.y === f.y){
foods.splice(i,1);
snake.foodEaten +=1;
ateFood = true;
foods.push(getEmptyPos());
break;
}
}
let hitBomb = false;
for(let i=0;i<bombs.length;i++){
const b = bombs[i];
if(newHead.x === b.x && newHead.y === b.y){
hitBomb = true;
bombs.splice(i,1);
bombs.push(getEmptyPos());
break;
}
}
snake.body.unshift(newHead);
if(ateFood){
//吃到食物,长度+1,不删尾巴
}else if(hitBomb){
//撞炸弹,删除2节,最少保留1节
for(let t=0;t<2 && snake.body.length>1;t++) snake.body.pop();
}else{
snake.body.pop();
}
}
//绘制画面
function draw(){
ctx.fillStyle="#000";
ctx.fillRect(0,0,canvas.width,canvas.height);
//网格线
ctx.strokeStyle="#222";
for(let i=0;i<=size;i++){
ctx.beginPath();ctx.moveTo(i*cell,0);ctx.lineTo(i*cell,size*cell);ctx.stroke();
ctx.beginPath();ctx.moveTo(0,i*cell);ctx.lineTo(size*cell,i*cell);ctx.stroke();
}
//食物黄色
ctx.fillStyle='#ffdd00';
foods.forEach(f=>ctx.fillRect(f.x*cell+1,f.y*cell+1,cell-2,cell-2));
//炸弹红色
ctx.fillStyle='#ff2222';
bombs.forEach(b=>ctx.fillRect(b.x*cell+1,b.y*cell+1,cell-2,cell-2));
//画蛇
function drawSnake(s){
ctx.fillStyle = s.color;
s.body.forEach(seg=>ctx.fillRect(seg.x*cell+1,seg.y*cell+1,cell-2,cell-2));
}
drawSnake(s1);
drawSnake(s2);
//死亡遗留方块,数量=吃掉食物总数
if(!s1.alive){
ctx.fillStyle="rgba(57,255,102,0.4)";
for(let i=0;i<s1.foodEaten;i++){
const seg = s1.body[i];
ctx.fillRect(seg.x*cell+1,seg.y*cell+1,cell-2,cell-2);
}
}
if(!s2.alive){
ctx.fillStyle="rgba(255,153,51,0.4)";
for(let i=0;i<s2.foodEaten;i++){
const seg = s2.body[i];
ctx.fillRect(seg.x*cell+1,seg.y*cell+1,cell-2,cell-2);
}
}
ctx.fillStyle="#fff";
ctx.font="14px sans-serif";
ctx.fillText(`绿蛇 存活:${s1.alive} 吃食物:${s1.foodEaten}`,5,395);
ctx.fillText(`橙蛇 存活:${s2.alive} 吃食物:${s2.foodEaten}`,200,395);
}
const gameTimer = setInterval(()=>{
updateDir();
moveSnake(s1);
moveSnake(s2);
draw();
},180);
setTimeout(draw,50); //延迟首帧渲染,解决iframe初始化黑屏
});
</script>
</body>
</html>Game Source: Remix of 20×20蛇对战 | 8食物3炸弹
Creator: RapidBunny56
Libraries: none
Complexity: complex (229 lines, 6.5 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: remix-of-20-20-8-3-rapidbunny56" to link back to the original. Then publish at arcadelab.ai/publish.