anycoder-c54106b0 / index.html
tyzhaoqi's picture
Upload folder using huggingface_hub
08e3ce6 verified
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>植物大战僵尸 Web版</title>
<!-- 引入 FontAwesome 用于图标 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-green: #4CAF50;
--dark-green: #2E7D32;
--lawn-light: #81C784;
--lawn-dark: #66BB6A;
--sun-yellow: #FFEB3B;
--text-color: #333;
--card-bg: #fff;
--danger-red: #e53935;
}
* {
box-sizing: border-box;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
}
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #222;
color: var(--text-color);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
/* 游戏主容器 */
#game-container {
width: 100%;
max-width: 960px;
height: 100%;
max-height: 600px;
background-color: var(--lawn-light);
position: relative;
display: flex;
flex-direction: column;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
border-radius: 8px;
overflow: hidden;
}
/* 顶部栏 */
header {
background-color: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 100;
}
.brand {
font-weight: bold;
font-size: 1.2rem;
display: flex;
align-items: center;
gap: 10px;
}
.brand a {
color: var(--sun-yellow);
text-decoration: none;
font-size: 0.9rem;
background: rgba(255,255,255,0.1);
padding: 4px 8px;
border-radius: 4px;
transition: background 0.3s;
}
.brand a:hover {
background: rgba(255,255,255,0.2);
}
/* 游戏区域布局 */
#game-area {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
}
/* 顶部控制面板 (阳光 & 卡牌) */
#hud {
background: rgba(255, 255, 255, 0.9);
padding: 10px;
display: flex;
align-items: center;
gap: 15px;
border-bottom: 4px solid var(--dark-green);
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.sun-counter {
display: flex;
align-items: center;
background: #fff;
border: 2px solid var(--sun-yellow);
border-radius: 20px;
padding: 5px 15px;
font-weight: bold;
font-size: 1.2rem;
color: #F57F17;
box-shadow: inset 0 0 5px rgba(0,0,0,0.1);
}
.sun-icon {
margin-right: 8px;
color: var(--sun-yellow);
text-shadow: 0 0 2px #F9A825;
}
.plant-cards {
display: flex;
gap: 10px;
}
.card {
width: 70px;
height: 90px;
background: var(--card-bg);
border: 2px solid #ccc;
border-radius: 6px;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: transform 0.1s, border-color 0.2s;
position: relative;
}
.card:hover {
transform: translateY(-2px);
}
.card.selected {
border-color: var(--primary-green);
background-color: #E8F5E9;
box-shadow: 0 0 8px var(--primary-green);
}
.card.disabled {
filter: grayscale(100%);
opacity: 0.6;
cursor: not-allowed;
}
.card-emoji {
font-size: 2rem;
margin-bottom: 5px;
}
.card-cost {
font-size: 0.8rem;
font-weight: bold;
color: #555;
}
/* 草坪网格 */
#lawn-grid {
flex: 1;
display: grid;
grid-template-rows: repeat(5, 1fr);
grid-template-columns: repeat(9, 1fr);
position: relative;
}
.cell {
border: 1px solid rgba(0,0,0,0.05);
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
/* 棋盘格效果 */
.cell:nth-child(odd) {
background-color: var(--lawn-light);
}
.cell:nth-child(even) {
background-color: var(--lawn-dark);
}
/* 每一行错位颜色调整 */
.cell:nth-child(18n+1), .cell:nth-child(18n+3), .cell:nth-child(18n+5), .cell:nth-child(18n+7), .cell:nth-child(18n+9),
.cell:nth-child(18n+10), .cell:nth-child(18n+12), .cell:nth-child(18n+14), .cell:nth-child(18n+16), .cell:nth-child(18n+18) {
background-color: var(--lawn-light);
}
.cell:nth-child(18n+2), .cell:nth-child(18n+4), .cell:nth-child(18n+6), .cell:nth-child(18n+8),
.cell:nth-child(18n+11), .cell:nth-child(18n+13), .cell:nth-child(18n+15), .cell:nth-child(18n+17) {
background-color: var(--lawn-dark);
}
.cell:hover {
filter: brightness(1.1);
cursor: pointer;
}
/* 游戏实体 (植物、僵尸、子弹) */
.entity {
position: absolute;
pointer-events: none; /* 让点击穿透到网格 */
z-index: 10;
}
.plant {
font-size: 3rem;
z-index: 5;
transition: transform 0.1s;
}
.plant.hit {
transform: scale(0.9);
filter: brightness(0.8);
}
.zombie {
font-size: 3.5rem;
z-index: 20;
transition: left 0.1s linear; /* 平滑移动 */
}
.zombie.eating {
animation: shake 0.5s infinite;
}
.projectile {
width: 20px;
height: 20px;
background-color: #66BB6A;
border-radius: 50%;
border: 2px solid #2E7D32;
z-index: 15;
box-shadow: 0 0 5px #fff;
}
.sun-token {
position: absolute;
width: 50px;
height: 50px;
font-size: 2.5rem;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
z-index: 100;
filter: drop-shadow(0 0 5px orange);
animation: sun-glow 2s infinite alternate;
pointer-events: auto; /* 允许点击 */
transition: top 5s linear, opacity 0.3s;
}
/* 模态框 */
.modal {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.85);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 200;
color: white;
text-align: center;
}
.hidden {
display: none !important;
}
h1 {
font-size: 3rem;
color: var(--primary-green);
text-shadow: 2px 2px 0 #000;
margin-bottom: 10px;
}
.btn {
padding: 15px 40px;
font-size: 1.5rem;
background: var(--primary-green);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
margin-top: 20px;
box-shadow: 0 4px 0 var(--dark-green);
transition: transform 0.1s, box-shadow 0.1s;
}
.btn:active {
transform: translateY(4px);
box-shadow: 0 0 0 var(--dark-green);
}
.instructions {
margin-top: 20px;
color: #ddd;
max-width: 600px;
line-height: 1.6;
}
/* 动画定义 */
@keyframes sun-glow {
from { transform: scale(1); filter: drop-shadow(0 0 5px orange); }
to { transform: scale(1.1); filter: drop-shadow(0 0 15px yellow); }
}
@keyframes shake {
0% { transform: rotate(0deg); }
25% { transform: rotate(5deg); }
75% { transform: rotate(-5deg); }
100% { transform: rotate(0deg); }
}
@media (max-width: 600px) {
.card { width: 50px; height: 70px; }
.card-emoji { font-size: 1.5rem; }
.card-cost { font-size: 0.7rem; }
.plant, .zombie { font-size: 2rem; }
h1 { font-size: 2rem; }
}
</style>
</head>
<body>
<div id="game-container">
<header>
<div class="brand">
<i class="fas fa-seedling"></i> PvZ Web版
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a>
</div>
<div id="score-display">波数: 1</div>
</header>
<div id="game-area">
<div id="hud">
<div class="sun-counter">
<i class="fas fa-sun sun-icon"></i>
<span id="sun-amount">150</span>
</div>
<div class="plant-cards">
<!-- 向日葵卡片 -->
<div class="card" id="card-sunflower" onclick="selectPlant('sunflower', 50)">
<div class="card-emoji">🌻</div>
<div class="card-cost">50</div>
</div>
<!-- 豌豆射手卡片 -->
<div class="card" id="card-peashooter" onclick="selectPlant('peashooter', 100)">
<div class="card-emoji">🌱</div>
<div class="card-cost">100</div>
</div>
<!-- 坚果卡片 -->
<div class="card" id="card-wallnut" onclick="selectPlant('wallnut', 50)">
<div class="card-emoji">🌰</div>
<div class="card-cost">50</div>
</div>
</div>
</div>
<div id="lawn-grid">
<!-- 网格由 JS 生成 -->
</div>
</div>
<!-- 开始界面 -->
<div id="start-screen" class="modal">
<h1>植物大战僵尸</h1>
<div class="instructions">
<p>收集阳光,种植植物,抵御僵尸的进攻!</p>
<p>🌻 向日葵: 生产阳光<br>🌱 豌豆射手: 发射子弹攻击<br>🌰 坚果墙: 阻挡僵尸</p>
</div>
<button class="btn" onclick="startGame()">开始游戏</button>
</div>
<!-- 游戏结束界面 -->
<div id="game-over-screen" class="modal hidden">
<h1 style="color: var(--danger-red)">僵尸吃掉了你的脑子!</h1>
<p>存活波数: <span id="final-wave">1</span></p>
<button class="btn" onclick="resetGame()">重新开始</button>
</div>
</div>
<script>
// --- 游戏配置与状态 ---
const config = {
rows: 5,
cols: 9,
sunValue: 25,
sunSpawnRate: 8000, // 自然阳光生成间隔
zombieSpawnRateInitial: 10000, // 初始僵尸生成间隔
bulletSpeed: 5, // 子弹每帧移动像素
zombieSpeed: 0.3, // 僵尸每帧移动像素
};
let state = {
sun: 150,
isPlaying: false,
selectedPlant: null,
plants: [],
zombies: [],
projectiles: [],
suns: [],
wave: 1,
lastTime: 0,
timers: {
sun: 0,
zombie: 0
}
};
// --- DOM 元素引用 ---
const gridEl = document.getElementById('lawn-grid');
const sunEl = document.getElementById('sun-amount');
const cards = {
sunflower: document.getElementById('card-sunflower'),
peashooter: document.getElementById('card-peashooter'),
wallnut: document.getElementById('card-wallnut')
};
// --- 初始化网格 ---
function initGrid() {
gridEl.innerHTML = '';
for (let r = 0; r < config.rows; r++) {
for (let c = 0; c < config.cols; c++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.dataset.row = r;
cell.dataset.col = c;
cell.addEventListener('click', () => handleCellClick(r, c));
gridEl.appendChild(cell);
}
}
}
// --- 游戏流程控制 ---
function startGame() {
document.getElementById('start-screen').classList.add('hidden');
document.getElementById('game-over-screen').classList.add('hidden');
resetState();
state.isPlaying = true;
state.lastTime = performance.now();
requestAnimationFrame(gameLoop);
updateUI();
}
function resetGame() {
startGame();
}
function resetState() {
state.sun = 150;
state.plants = [];
state.zombies = [];
state.projectiles = [];
state.suns = [];
state.wave = 1;
state.timers.sun = 0;
state.timers.zombie = 0;
// 清理界面上的实体
document.querySelectorAll('.entity, .sun-token').forEach(el => el.remove());
updateUI();
}
function gameOver() {
state.isPlaying = false;
document.getElementById('final-wave').innerText = state.wave;
document.getElementById('game-over-screen').classList.remove('hidden');
}
// --- 核心游戏循环 ---
function gameLoop(timestamp) {
if (!state.isPlaying) return;
const deltaTime = timestamp - state.lastTime;
state.lastTime = timestamp;
updateLogic(deltaTime);
render(); // 虽然主要由DOM事件驱动,但我们需要在这里处理移动逻辑的视觉更新
requestAnimationFrame(gameLoop);
}
function updateLogic(deltaTime) {
// 1. 生成自然阳光
state.timers.sun += deltaTime;
if (state.timers.sun > config.sunSpawnRate) {
spawnSun();
state.timers.sun = 0;
}
// 2. 生成僵尸 (随着时间推移,生成速度变快)
state.timers.zombie += deltaTime;
let currentSpawnRate = Math.max(2000, config.zombieSpawnRateInitial - (state.wave * 500));
if (state.timers.zombie > currentSpawnRate) {
spawnZombie();
state.timers.zombie = 0;
// 每生成5个僵尸增加一波难度
if (state.zombies.length % 5 === 0) {
state.wave++;
document.getElementById('score-display').innerText = `波数: ${state.wave}`;
}
}
// 3. 更新植物 (攻击/生产)
state.plants.forEach(plant => {
plant.timer += deltaTime;
// 向日葵生产阳光
if (plant.type === 'sunflower' && plant.timer > 10000) { // 10秒生产一次
spawnSun(plant.row, plant.col, true);
plant.timer = 0;
}
// 豌豆射手攻击
if (plant.type === 'peashooter' && plant.timer > 1500) { // 1.5秒射击一次
// 检测该行是否有僵尸
const hasZombie = state.zombies.some(z => z.row === plant.row && z.x > plant.col * (100/config.cols));
if (hasZombie) {
spawnProjectile(plant.row, plant.col);
plant.timer = 0;
}
}
});
// 4. 更新子弹位置与碰撞
for (let i = state.projectiles.length - 1; i >= 0; i--) {
let p = state.projectiles[i];
p.x += config.bulletSpeed * (deltaTime / 16); // 归一化速度
// 移除超出屏幕的子弹
if (p.x > gridEl.clientWidth) {
p.el.remove();
state.projectiles.splice(i, 1);
continue;
}
// 子弹碰撞检测
let hit = false;
for (let j = 0; j < state.zombies.length; j++) {
let z = state.zombies[j];
if (p.row === z.row && Math.abs(p.x - z.x) < 30 && z.health > 0) {
// 击中僵尸
z.health -= 20;
z.el.style.filter = "brightness(2) sepia(1) hue-rotate(-50deg) saturate(5)"; // 受伤闪红
setTimeout(() => { if(z.el) z.el.style.filter = ""; }, 100);
if (z.health <= 0) {
killZombie(j);
}
// 移除子弹
p.el.remove();
state.projectiles.splice(i, 1);
hit = true;
break;
}
}
if (!hit) {
p.el.style.left = p.x + 'px';
}
}
// 5. 更新僵尸位置与啃食
for (let i = state.zombies.length - 1; i >= 0; i--) {
let z = state.zombies[i];
// 检测是否在啃食植物
let eating = false;
const cellWidth = gridEl.clientWidth / config.cols;
// 简单的网格碰撞:僵尸中心点所在的列
let currentCol = Math.floor((z.x + 20) / cellWidth);
// 查找该位置是否有植物
let plant = state.plants.find(p => p.row === z.row && p.col === currentCol);
if (plant) {
eating = true;
z.el.classList.add('eating');
plant.health -= 0.5; // 啃食伤害
plant.el.classList.add('hit');
if (plant.health <= 0) {
// 植物死亡
plant.el.remove();
state.plants = state.plants.filter(p => p !== plant);
z.el.classList.remove('eating');
}
} else {
z.el.classList.remove('eating');
}
if (!eating) {
z.x -= config.zombieSpeed * (deltaTime / 16);
z.el.style.left = z.x + 'px';
}
// 游戏结束判定
if (z.x < -20) {
gameOver();
}
}
}
function render() {
// 大部分渲染在逻辑中直接操作 DOM 样式了,这里主要用于清理无效引用
updateUI();
}
// --- 实体操作 ---
function selectPlant(type, cost) {
if (state.sun >= cost) {
state.selectedPlant = { type, cost };
// 高亮选中卡片
Object.values(cards).forEach(c => c.classList.remove('selected'));
cards[type].classList.add('selected');
}
}
function handleCellClick(row, col) {
if (!state.isPlaying || !state.selectedPlant) return;
// 检查该格子是否已有植物
if (state.plants.some(p => p.row === row && p.col === col)) return;
// 扣除阳光
state.sun -= state.selectedPlant.cost;
// 创建植物
createPlant(row, col, state.selectedPlant.type);
// 重置选择
state.selectedPlant = null;
Object.values(cards).forEach(c => c.classList.remove('selected'));
updateUI();
}
function createPlant(row, col, type) {
const cell = document.querySelector(`.cell[data-row='${row}'][data-col='${col}']`);
const el = document.createElement('div');
el.classList.add('entity', 'plant');
let emoji = '';
let health = 100;
if (type === 'sunflower') {
emoji = '🌻';
health = 80;
} else if (type === 'peashooter') {
emoji = '🌱';
health = 100;
} else if (type === 'wallnut') {
emoji = '🌰';
health = 400; // 高血量
}
el.innerText = emoji;
// 居中显示在格子里
el.style.top = '50%';
el.style.left = '50%';
el.style.transform = 'translate(-50%, -50%)';
cell.appendChild(el);
state.plants.push({
row, col, type, health, el, timer: 0
});
}
function spawnZombie() {
const row = Math.floor(Math.random() * config.rows);
const el = document.createElement('div');
el.classList.add('entity', 'zombie');
el.innerText = '🧟';
const startX = gridEl.clientWidth;
const rowHeight = gridEl.clientHeight / config.rows;
el.style.top = (row * rowHeight + rowHeight/2 - 20) + 'px'; // 垂直居中微调
el.style.left = startX + 'px';
gridEl.appendChild(el);
state.zombies.push({
row, x: startX, health: 100, el
});
}
function killZombie(index) {
const z = state.zombies[index];
z.el.innerText = '💥'; // 死亡特效
setTimeout(() => {
if(z.el) z.el.remove();
}, 500);
state.zombies.splice(index, 1);
}
function spawnProjectile(row, col) {
const el = document.createElement('div');
el.classList.add('entity', 'projectile');
const cellWidth = gridEl.clientWidth / config.cols;
const rowHeight = gridEl.clientHeight / config.rows;
// 初始位置
const startX = col * cellWidth + 40;
const startY = row * rowHeight + rowHeight/2 - 10;
el.style.left = startX + 'px';
el.style.top = startY + 'px';
gridEl.appendChild(el);
state.projectiles.push({
row, x: startX, el
});
}
function spawnSun(row = null, col = null, fromPlant = false) {
const el = document.createElement('div');
el.classList.add('sun-token');
el.innerText = '☀️';
let targetTop, targetLeft;
if (fromPlant && row !== null && col !== null) {
// 从植物产生
const cell = document.querySelector(`.cell[data-row='${row}'][data-col='${col}']`);
const rect = cell.getBoundingClientRect();
const gridRect = gridEl.getBoundingClientRect();
// 相对于 grid 的坐标
targetLeft = (rect.left - gridRect.left) + 10;
targetTop = (rect.top - gridRect.top) + 10;
el.style.left = targetLeft + 'px';
el.style.top = targetTop + 'px';
} else {
// 从天空掉落
targetLeft = Math.random() * (gridEl.clientWidth - 50);
targetTop = gridEl.clientHeight - Math.random() * 200; // 随机高度
el.style.left = targetLeft + 'px';
el.style.top = '-50px';
// 触发下落动画
setTimeout(() => {
el.style.top = targetTop + 'px';
}, 50);
}
el.onclick = (e) => {
e.stopPropagation(); // 防止点穿
collectSun(el);
};
// 8秒后自动消失
setTimeout(() => {
if(el.parentElement) {
el.style.opacity = 0;
setTimeout(() => el.remove(), 300);
}
}, 8000);
gridEl.appendChild(el);
}
function collectSun(el) {
state.sun += config.sunValue;
// 飞向左上角动画
const hudRect = document.getElementById('hud').getBoundingClientRect();
const gridRect = gridEl.getBoundingClientRect();
// 计算相对位置
const targetX = hudRect.left - gridRect.left;
const targetY = hudRect.top - gridRect.top;
el.style.transition = 'all 0.5s ease-in';
el.style.left = targetX + 'px';
el.style.top = targetY + 'px';
el.style.opacity = 0;
setTimeout(() => {
el.remove();
updateUI();
}, 500);
}
function updateUI() {
sunEl.innerText = state.sun;
// 更新卡片状态 (变灰如果阳光不足)
if (state.sun < 50) {
cards.sunflower.classList.add('disabled');
cards.wallnut.classList.add('disabled');
} else {
cards.sunflower.classList.remove('disabled');
cards.wallnut.classList.remove('disabled');
}
if (state.sun < 100) {
cards.peashooter.classList.add('disabled');
} else {
cards.peashooter.classList.remove('disabled');
}
}
// --- 启动 ---
initGrid();
</script>
</body>
</html>