Yoleo commited on
Commit
fa9d6ca
·
verified ·
1 Parent(s): cc78016

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +6 -4
  2. index.html +1287 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Snake Literario
3
- emoji: 🏢
4
  colorFrom: blue
5
- colorTo: yellow
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: snake-literario
3
+ emoji: 🐳
4
  colorFrom: blue
5
+ colorTo: green
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1287 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>炫酷道具贪吃蛇</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
11
+
12
+ body {
13
+ font-family: 'Press Start 2P', cursive;
14
+ background: linear-gradient(135deg, #1a1a2e, #16213e);
15
+ color: #fff;
16
+ overflow: hidden;
17
+ }
18
+
19
+ .game-container {
20
+ position: relative;
21
+ box-shadow: 0 0 50px rgba(0, 255, 255, 0.3);
22
+ border: 4px solid rgba(0, 255, 255, 0.5);
23
+ border-radius: 8px;
24
+ overflow: hidden;
25
+ }
26
+
27
+ #game-canvas {
28
+ background-color: #0f0f23;
29
+ display: block;
30
+ }
31
+
32
+ .snake-cell {
33
+ box-shadow: 0 0 10px currentColor;
34
+ }
35
+
36
+ .food-cell {
37
+ animation: pulse 1.5s infinite;
38
+ }
39
+
40
+ .powerup-cell {
41
+ animation: glow 2s infinite alternate, spin 4s infinite linear;
42
+ }
43
+
44
+ .portal-cell {
45
+ animation: portalPulse 2s infinite;
46
+ }
47
+
48
+ .bomb-cell {
49
+ animation: bombPulse 1s infinite;
50
+ }
51
+
52
+ @keyframes pulse {
53
+ 0% { transform: scale(1); }
54
+ 50% { transform: scale(1.2); }
55
+ 100% { transform: scale(1); }
56
+ }
57
+
58
+ @keyframes glow {
59
+ 0% { filter: drop-shadow(0 0 5px currentColor); }
60
+ 100% { filter: drop-shadow(0 0 15px currentColor); }
61
+ }
62
+
63
+ @keyframes spin {
64
+ 0% { transform: rotate(0deg); }
65
+ 100% { transform: rotate(360deg); }
66
+ }
67
+
68
+ @keyframes portalPulse {
69
+ 0% { box-shadow: 0 0 10px #9c27b0, inset 0 0 10px #9c27b0; }
70
+ 50% { box-shadow: 0 0 20px #9c27b0, inset 0 0 20px #9c27b0; }
71
+ 100% { box-shadow: 0 0 10px #9c27b0, inset 0 0 10px #9c27b0; }
72
+ }
73
+
74
+ @keyframes bombPulse {
75
+ 0% { opacity: 0.7; transform: scale(0.9); }
76
+ 50% { opacity: 1; transform: scale(1.1); }
77
+ 100% { opacity: 0.7; transform: scale(0.9); }
78
+ }
79
+
80
+ .score-display {
81
+ text-shadow: 0 0 10px #00ffaa;
82
+ font-size: 1.5rem;
83
+ letter-spacing: 2px;
84
+ }
85
+
86
+ .particle {
87
+ position: absolute;
88
+ width: 8px;
89
+ height: 8px;
90
+ border-radius: 50%;
91
+ pointer-events: none;
92
+ }
93
+
94
+ .game-over {
95
+ backdrop-filter: blur(5px);
96
+ background-color: rgba(0, 0, 0, 0.7);
97
+ }
98
+
99
+ .powerup-indicator {
100
+ animation: indicatorPulse 1s infinite alternate;
101
+ box-shadow: 0 0 10px currentColor;
102
+ }
103
+
104
+ @keyframes indicatorPulse {
105
+ 0% { opacity: 0.7; transform: scale(0.9); }
106
+ 100% { opacity: 1; transform: scale(1.1); }
107
+ }
108
+ </style>
109
+ </head>
110
+ <body class="min-h-screen flex flex-col items-center justify-center p-4">
111
+ <div class="text-center mb-6">
112
+ <h1 class="text-4xl md:text-5xl font-bold mb-2 text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 to-purple-500">
113
+ 炫酷道具贪吃蛇
114
+ </h1>
115
+ <p class="text-amber-400 text-sm md:text-base">AI自动模式已启用 - 按空格键切换手动/自动</p>
116
+ </div>
117
+
118
+ <div class="flex flex-col md:flex-row gap-6 items-center justify-center">
119
+ <div class="game-container relative">
120
+ <canvas id="game-canvas" width="600" height="600"></canvas>
121
+
122
+ <div id="game-over" class="game-over absolute inset-0 flex flex-col items-center justify-center hidden">
123
+ <h2 class="text-4xl text-red-500 mb-6">游戏结束!</h2>
124
+ <p class="text-xl text-white mb-8">最终分数: <span id="final-score" class="text-green-400">0</span></p>
125
+ <button id="restart-btn" class="px-6 py-3 bg-gradient-to-r from-cyan-500 to-blue-600 rounded-full font-bold uppercase tracking-wider hover:scale-105 transition-transform">
126
+ 重新开始
127
+ </button>
128
+ </div>
129
+ </div>
130
+
131
+ <div class="w-full md:w-64 flex flex-col gap-6">
132
+ <div class="bg-gray-900 bg-opacity-70 p-4 rounded-lg border border-cyan-500 border-opacity-30">
133
+ <h3 class="text-xl text-cyan-400 mb-3 flex items-center gap-2">
134
+ <i class="fas fa-tachometer-alt"></i> 游戏状态
135
+ </h3>
136
+ <div class="space-y-4">
137
+ <div>
138
+ <p class="text-gray-400 text-sm mb-1">得分</p>
139
+ <p id="score" class="score-display text-2xl text-green-400">0</p>
140
+ </div>
141
+ <div>
142
+ <p class="text-gray-400 text-sm mb-1">长度</p>
143
+ <p id="length" class="text-xl text-white">1</p>
144
+ </div>
145
+ <div>
146
+ <p class="text-gray-400 text-sm mb-1">速度</p>
147
+ <p id="speed" class="text-xl text-white">1x</p>
148
+ </div>
149
+ <div>
150
+ <p class="text-gray-400 text-sm mb-1">模式</p>
151
+ <p id="mode" class="text-xl text-amber-400">AI自动</p>
152
+ </div>
153
+ </div>
154
+ </div>
155
+
156
+ <div class="bg-gray-900 bg-opacity-70 p-4 rounded-lg border border-purple-500 border-opacity-30">
157
+ <h3 class="text-xl text-purple-400 mb-3 flex items-center gap-2">
158
+ <i class="fas fa-magic"></i> 道具效果
159
+ </h3>
160
+ <div class="space-y-3">
161
+ <div class="flex items-center gap-3">
162
+ <div class="w-6 h-6 rounded-full bg-yellow-400 powerup-indicator" id="speed-indicator"></div>
163
+ <p class="text-sm">速度提升 <span id="speed-timer" class="text-yellow-400">0s</span></p>
164
+ </div>
165
+ <div class="flex items-center gap-3">
166
+ <div class="w-6 h-6 rounded-full bg-green-500 powerup-indicator" id="growth-indicator"></div>
167
+ <p class="text-sm">快速生长 <span id="growth-timer" class="text-green-500">0s</span></p>
168
+ </div>
169
+ <div class="flex items-center gap-3">
170
+ <div class="w-6 h-6 rounded-full bg-red-500 powerup-indicator" id="invincible-indicator"></div>
171
+ <p class="text-sm">无敌状态 <span id="invincible-timer" class="text-red-500">0s</span></p>
172
+ </div>
173
+ </div>
174
+ </div>
175
+
176
+ <div class="bg-gray-900 bg-opacity-70 p-4 rounded-lg border border-amber-500 border-opacity-30">
177
+ <h3 class="text-xl text-amber-400 mb-3 flex items-center gap-2">
178
+ <i class="fas fa-info-circle"></i> 道具说明
179
+ </h3>
180
+ <div class="space-y-3 text-xs">
181
+ <div class="flex items-start gap-2">
182
+ <div class="w-4 h-4 rounded-full bg-red-500 mt-1 flex-shrink-0"></div>
183
+ <p>普通食物 - +1分, 长度+1</p>
184
+ </div>
185
+ <div class="flex items-start gap-2">
186
+ <div class="w-4 h-4 rounded-full bg-yellow-400 mt-1 flex-shrink-0"></div>
187
+ <p>速度道具 - 移动速度加快</p>
188
+ </div>
189
+ <div class="flex items-start gap-2">
190
+ <div class="w-4 h-4 rounded-full bg-green-500 mt-1 flex-shrink-0"></div>
191
+ <p>生长道具 - 每次移动长度增加</p>
192
+ </div>
193
+ <div class="flex items-start gap-2">
194
+ <div class="w-4 h-4 rounded-full bg-blue-400 mt-1 flex-shrink-0"></div>
195
+ <p>无敌道具 - 可以穿过墙壁和身体</p>
196
+ </div>
197
+ <div class="flex items-start gap-2">
198
+ <div class="w-4 h-4 rounded-full bg-purple-500 mt-1 flex-shrink-0"></div>
199
+ <p>传送门 - 传送到随机位置</p>
200
+ </div>
201
+ <div class="flex items-start gap-2">
202
+ <div class="w-4 h-4 rounded-full bg-orange-500 mt-1 flex-shrink-0"></div>
203
+ <p>炸弹 - 长度减半</p>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ </div>
208
+ </div>
209
+
210
+ <div class="mt-8 text-gray-400 text-xs">
211
+ <p>使用方向键或WASD控制 | 空格键切换手动/AI模式 | 躲避炸弹</p>
212
+ </div>
213
+
214
+ <script>
215
+ document.addEventListener('DOMContentLoaded', () => {
216
+ // 游戏常量
217
+ const CELL_SIZE = 20;
218
+ const CELLS_X = 30;
219
+ const CELLS_Y = 30;
220
+ const FPS = 60;
221
+ const BASE_SPEED = 5; // 每x帧移动一次
222
+ const SPEED_INCREASE = 0.95; // 每次吃到食物速度增加系数
223
+
224
+ // 道具类型
225
+ const ITEM_TYPES = {
226
+ FOOD: { color: '#f44336', score: 1, grow: 1, lifetime: 0 },
227
+ SPEED: { color: '#ffeb3b', score: 2, grow: 0, lifetime: 10, effect: 'speed' },
228
+ GROWTH: { color: '#4caf50', score: 2, grow: 0, lifetime: 10, effect: 'growth' },
229
+ INVINCIBLE: { color: '#2196f3', score: 3, grow: 0, lifetime: 8, effect: 'invincible' },
230
+ PORTAL: { color: '#9c27b0', score: 0, grow: -1, lifetime: 0 },
231
+ BOMB: { color: '#ff9800', score: 0, grow: -0.5, lifetime: 0 }
232
+ };
233
+
234
+ // 游戏变量
235
+ let canvas = document.getElementById('game-canvas');
236
+ let ctx = canvas.getContext('2d');
237
+ let snake = [];
238
+ let direction = 'right';
239
+ let nextDirection = 'right';
240
+ let gameLoopInterval;
241
+ let gameSpeed = BASE_SPEED;
242
+ let speedCounter = 0;
243
+ let score = 0;
244
+ let items = [];
245
+ let particles = [];
246
+ let activeEffects = {};
247
+ let isGameOver = false;
248
+ let isAutoMode = true;
249
+ let lastPortalTime = 0;
250
+ let portalCooldown = 3; // seconds
251
+
252
+ // DOM 元素
253
+ const scoreElement = document.getElementById('score');
254
+ const lengthElement = document.getElementById('length');
255
+ const speedElement = document.getElementById('speed');
256
+ const modeElement = document.getElementById('mode');
257
+ const gameOverElement = document.getElementById('game-over');
258
+ const finalScoreElement = document.getElementById('final-score');
259
+ const restartButton = document.getElementById('restart-btn');
260
+ const speedIndicator = document.getElementById('speed-indicator');
261
+ const growthIndicator = document.getElementById('growth-indicator');
262
+ const invincibleIndicator = document.getElementById('invincible-indicator');
263
+ const speedTimer = document.getElementById('speed-timer');
264
+ const growthTimer = document.getElementById('growth-timer');
265
+ const invincibleTimer = document.getElementById('invincible-timer');
266
+
267
+ // 初始化游戏
268
+ function initGame() {
269
+ // 重置游戏状态
270
+ snake = [
271
+ { x: 5, y: 15 },
272
+ { x: 4, y: 15 },
273
+ { x: 3, y: 15 }
274
+ ];
275
+ direction = 'right';
276
+ nextDirection = 'right';
277
+ gameSpeed = BASE_SPEED;
278
+ speedCounter = 0;
279
+ score = 0;
280
+ items = [];
281
+ particles = [];
282
+ activeEffects = {};
283
+ isGameOver = false;
284
+
285
+ // 更新UI
286
+ updateScore(0);
287
+ lengthElement.textContent = snake.length;
288
+ speedElement.textContent = '1x';
289
+ modeElement.textContent = isAutoMode ? 'AI自动' : '手动控制';
290
+ modeElement.className = isAutoMode ? 'text-xl text-amber-400' : 'text-xl text-white';
291
+
292
+ // 隐藏游戏结束画面
293
+ gameOverElement.classList.add('hidden');
294
+
295
+ // 创建初始食物
296
+ createRandomItem(ITEM_TYPES.FOOD);
297
+
298
+ // 开始游戏循环
299
+ if (gameLoopInterval) clearInterval(gameLoopInterval);
300
+ gameLoopInterval = setInterval(gameLoop, 1000 / FPS);
301
+ }
302
+
303
+ // 游戏主循环
304
+ function gameLoop() {
305
+ // 清除画布
306
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
307
+
308
+ // 更新计时器
309
+ speedCounter++;
310
+ updateEffects();
311
+
312
+ // AI控制
313
+ if (isAutoMode && speedCounter % 2 === 0) {
314
+ aiControl();
315
+ }
316
+
317
+ // 每gameSpeed帧移动一次
318
+ if (speedCounter >= gameSpeed) {
319
+ moveSnake();
320
+ speedCounter = 0;
321
+ }
322
+
323
+ // 绘制网格线
324
+ drawGrid();
325
+
326
+ // 绘制道具
327
+ drawItems();
328
+
329
+ // 绘制蛇
330
+ drawSnake();
331
+
332
+ // 绘制粒子效果
333
+ drawParticles();
334
+
335
+ // 更新粒子
336
+ updateParticles();
337
+
338
+ // 随机生成道具
339
+ if (Math.random() < 0.002) { // 0.2% 每帧的几率
340
+ const availableTypes = Object.values(ITEM_TYPES).filter(type =>
341
+ type !== ITEM_TYPES.FOOD &&
342
+ (!activeEffects[type.effect] || type.effect === undefined)
343
+ );
344
+
345
+ if (availableTypes.length > 0) {
346
+ const randomType = availableTypes[Math.floor(Math.random() * availableTypes.length)];
347
+ createRandomItem(randomType);
348
+ }
349
+ }
350
+ }
351
+
352
+ // 绘制网格线
353
+ function drawGrid() {
354
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
355
+ ctx.lineWidth = 1;
356
+
357
+ // 垂直线
358
+ for (let x = 0; x <= CELLS_X; x++) {
359
+ ctx.beginPath();
360
+ ctx.moveTo(x * CELL_SIZE, 0);
361
+ ctx.lineTo(x * CELL_SIZE, CELLS_Y * CELL_SIZE);
362
+ ctx.stroke();
363
+ }
364
+
365
+ // 水平线
366
+ for (let y = 0; y <= CELLS_Y; y++) {
367
+ ctx.beginPath();
368
+ ctx.moveTo(0, y * CELL_SIZE);
369
+ ctx.lineTo(CELLS_X * CELL_SIZE, y * CELL_SIZE);
370
+ ctx.stroke();
371
+ }
372
+ }
373
+
374
+ // 移动蛇
375
+ function moveSnake() {
376
+ direction = nextDirection;
377
+
378
+ // 创建新头部
379
+ const head = { ...snake[0] };
380
+
381
+ switch (direction) {
382
+ case 'up': head.y--; break;
383
+ case 'down': head.y++; break;
384
+ case 'left': head.x--; break;
385
+ case 'right': head.x++; break;
386
+ }
387
+
388
+ // 检查是否撞墙(无敌状态下可以穿过墙壁)
389
+ if (!activeEffects.invincible) {
390
+ if (head.x < 0 || head.x >= CELLS_X || head.y < 0 || head.y >= CELLS_Y) {
391
+ gameOver();
392
+ return;
393
+ }
394
+ } else {
395
+ // 穿墙处理
396
+ if (head.x < 0) head.x = CELLS_X - 1;
397
+ if (head.x >= CELLS_X) head.x = 0;
398
+ if (head.y < 0) head.y = CELLS_Y - 1;
399
+ if (head.y >= CELLS_Y) head.y = 0;
400
+ }
401
+
402
+ // 检查是否撞到自己(无敌状态下可以穿过自己)
403
+ if (!activeEffects.invincible) {
404
+ for (let i = 0; i < snake.length - 1; i++) {
405
+ if (head.x === snake[i].x && head.y === snake[i].y) {
406
+ gameOver();
407
+ return;
408
+ }
409
+ }
410
+ }
411
+
412
+ // 添加到头部
413
+ snake.unshift(head);
414
+
415
+ // 检查是否吃到道具
416
+ let itemConsumed = false;
417
+ let itemIndex = -1;
418
+
419
+ for (let i = 0; i < items.length; i++) {
420
+ const item = items[i];
421
+
422
+ if (head.x === item.x && head.y === item.y) {
423
+ itemIndex = i;
424
+
425
+ // 应用道具效果
426
+ if (item.type.effect) {
427
+ activateEffect(item.type.effect, item.type.lifetime);
428
+ }
429
+
430
+ // 处理特殊道具
431
+ if (item.type === ITEM_TYPES.PORTAL) {
432
+ if (Date.now() - lastPortalTime > portalCooldown * 1000) {
433
+ teleportSnake();
434
+ lastPortalTime = Date.now();
435
+ } else {
436
+ continue; // 跳过传送门,因为冷却中
437
+ }
438
+ }
439
+
440
+ if (item.type === ITEM_TYPES.BOMB) {
441
+ createExplosion(head.x, head.y);
442
+ }
443
+
444
+ // 更新分数和长度
445
+ updateScore(score + item.type.score);
446
+
447
+ // 生长处理 (正数=增加长度,负数=减少长度)
448
+ if (item.type.grow > 0) {
449
+ // 如果吃的生长道具,额外添加几节
450
+ if (activeEffects.growth) {
451
+ for (let j = 0; j < 2; j++) {
452
+ snake.push({ ...snake[snake.length - 1] });
453
+ }
454
+ }
455
+ } else if (item.type.grow < 0) {
456
+ // 炸弹效果
457
+ const newLength = Math.ceil(snake.length / (Math.abs(item.type.grow) + 1));
458
+ snake = snake.slice(0, newLength);
459
+
460
+ if (snake.length < 1) {
461
+ gameOver();
462
+ return;
463
+ }
464
+ }
465
+
466
+ // 粒子效果
467
+ createParticles(head.x, head.y, item.type.color);
468
+ itemConsumed = true;
469
+ break;
470
+ }
471
+ }
472
+
473
+ // 移除吃到的道具
474
+ if (itemConsumed && itemIndex !== -1) {
475
+ items.splice(itemIndex, 1);
476
+
477
+ // 如果吃的是普通食物,创建新的食物
478
+ if (items.filter(item => item.type === ITEM_TYPES.FOOD).length < 1) {
479
+ createRandomItem(ITEM_TYPES.FOOD);
480
+ }
481
+ } else {
482
+ // 如果没有吃到道具,移除尾部
483
+ snake.pop();
484
+ }
485
+
486
+ // 快速生长效果
487
+ if (activeEffects.growth) {
488
+ snake.push({ ...snake[snake.length - 1] });
489
+ }
490
+
491
+ // 更新长度显示
492
+ lengthElement.textContent = snake.length;
493
+ }
494
+
495
+ // 绘制蛇
496
+ function drawSnake() {
497
+ const head = snake[0];
498
+
499
+ // 绘制身体
500
+ for (let i = 0; i < snake.length; i++) {
501
+ const segment = snake[i];
502
+ const isHead = i === 0;
503
+
504
+ let color;
505
+ if (isHead) {
506
+ if (activeEffects.invincible) {
507
+ // 头部无敌效果
508
+ color = `hsl(${(Date.now() / 20) % 360}, 100%, 60%)`;
509
+ } else {
510
+ color = '#00ffaa';
511
+ }
512
+ } else {
513
+ // 彩虹身体效果
514
+ const hue = (240 + i * 1) % 360;
515
+ color = `hsl(${hue}, 100%, 60%)`;
516
+ }
517
+
518
+ // 绘制蛇节
519
+ ctx.fillStyle = color;
520
+ ctx.beginPath();
521
+ ctx.roundRect(
522
+ segment.x * CELL_SIZE + 1,
523
+ segment.y * CELL_SIZE + 1,
524
+ CELL_SIZE - 2,
525
+ CELL_SIZE - 2,
526
+ 4
527
+ );
528
+ ctx.fill();
529
+
530
+ // 添加视觉层次效果
531
+ if (i > 0) {
532
+ ctx.fillStyle = `rgba(255, 255, 255, 0.1)`;
533
+ ctx.beginPath();
534
+ ctx.roundRect(
535
+ segment.x * CELL_SIZE + 4,
536
+ segment.y * CELL_SIZE + 4,
537
+ CELL_SIZE - 8,
538
+ CELL_SIZE - 8,
539
+ 2
540
+ );
541
+ ctx.fill();
542
+ }
543
+
544
+ // 如果是头部,添加眼睛
545
+ if (isHead) {
546
+ const eyeSize = CELL_SIZE * 0.15;
547
+ const eyeOffset = CELL_SIZE * 0.2;
548
+
549
+ ctx.fillStyle = 'white';
550
+
551
+ // 根据方向绘制眼睛
552
+ if (direction === 'right' || direction === 'left') {
553
+ // 水平方向 - 上下两只眼睛
554
+ ctx.beginPath();
555
+ ctx.arc(
556
+ segment.x * CELL_SIZE + (direction === 'right' ? CELL_SIZE - eyeOffset : eyeOffset),
557
+ segment.y * CELL_SIZE + eyeOffset,
558
+ eyeSize, 0, Math.PI * 2
559
+ );
560
+ ctx.fill();
561
+
562
+ ctx.beginPath();
563
+ ctx.arc(
564
+ segment.x * CELL_SIZE + (direction === 'right' ? CELL_SIZE - eyeOffset : eyeOffset),
565
+ segment.y * CELL_SIZE + CELL_SIZE - eyeOffset,
566
+ eyeSize, 0, Math.PI * 2
567
+ );
568
+ ctx.fill();
569
+ } else {
570
+ // 垂直方向 - 左右两只眼睛
571
+ ctx.beginPath();
572
+ ctx.arc(
573
+ segment.x * CELL_SIZE + eyeOffset,
574
+ segment.y * CELL_SIZE + (direction === 'down' ? CELL_SIZE - eyeOffset : eyeOffset),
575
+ eyeSize, 0, Math.PI * 2
576
+ );
577
+ ctx.fill();
578
+
579
+ ctx.beginPath();
580
+ ctx.arc(
581
+ segment.x * CELL_SIZE + CELL_SIZE - eyeOffset,
582
+ segment.y * CELL_SIZE + (direction === 'down' ? CELL_SIZE - eyeOffset : eyeOffset),
583
+ eyeSize, 0, Math.PI * 2
584
+ );
585
+ ctx.fill();
586
+ }
587
+
588
+ // 瞳孔
589
+ const pupilSize = eyeSize * 0.6;
590
+ ctx.fillStyle = '#333';
591
+
592
+ if (direction === 'right' || direction === 'left') {
593
+ // 水平方向
594
+ ctx.beginPath();
595
+ ctx.arc(
596
+ segment.x * CELL_SIZE + (direction === 'right' ? CELL_SIZE - eyeOffset : eyeOffset),
597
+ segment.y * CELL_SIZE + eyeOffset,
598
+ pupilSize, 0, Math.PI * 2
599
+ );
600
+ ctx.fill();
601
+
602
+ ctx.beginPath();
603
+ ctx.arc(
604
+ segment.x * CELL_SIZE + (direction === 'right' ? CELL_SIZE - eyeOffset : eyeOffset),
605
+ segment.y * CELL_SIZE + CELL_SIZE - eyeOffset,
606
+ pupilSize, 0, Math.PI * 2
607
+ );
608
+ ctx.fill();
609
+ } else {
610
+ // 垂直方向
611
+ ctx.beginPath();
612
+ ctx.arc(
613
+ segment.x * CELL_SIZE + eyeOffset,
614
+ segment.y * CELL_SIZE + (direction === 'down' ? CELL_SIZE - eyeOffset : eyeOffset),
615
+ pupilSize, 0, Math.PI * 2
616
+ );
617
+ ctx.fill();
618
+
619
+ ctx.beginPath();
620
+ ctx.arc(
621
+ segment.x * CELL_SIZE + CELL_SIZE - eyeOffset,
622
+ segment.y * CELL_SIZE + (direction === 'down' ? CELL_SIZE - eyeOffset : eyeOffset),
623
+ pupilSize, 0, Math.PI * 2
624
+ );
625
+ ctx.fill();
626
+ }
627
+ }
628
+ }
629
+ }
630
+
631
+ // 绘制道具
632
+ function drawItems() {
633
+ for (const item of items) {
634
+ const itemX = item.x * CELL_SIZE + CELL_SIZE / 2;
635
+ const itemY = item.y * CELL_SIZE + CELL_SIZE / 2;
636
+ const itemSize = CELL_SIZE * 0.6;
637
+
638
+ ctx.save();
639
+ ctx.translate(itemX, itemY);
640
+
641
+ // 不同类型的绘制效果
642
+ switch (item.type) {
643
+ case ITEM_TYPES.FOOD:
644
+ // 普通食物 - 圆形
645
+ ctx.fillStyle = item.type.color;
646
+ ctx.beginPath();
647
+ ctx.arc(0, 0, itemSize / 2, 0, Math.PI * 2);
648
+ ctx.fill();
649
+
650
+ // 高光
651
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
652
+ ctx.beginPath();
653
+ ctx.arc(-itemSize / 4, -itemSize / 4, itemSize / 5, 0, Math.PI * 2);
654
+ ctx.fill();
655
+ break;
656
+
657
+ case ITEM_TYPES.SPEED:
658
+ // 速度道具 - 闪电形状
659
+ ctx.fillStyle = item.type.color;
660
+ ctx.beginPath();
661
+ ctx.moveTo(-itemSize / 2, -itemSize / 4);
662
+ ctx.lineTo(itemSize / 4, 0);
663
+ ctx.lineTo(-itemSize / 4, 0);
664
+ ctx.lineTo(itemSize / 2, itemSize / 4);
665
+ ctx.lineTo(-itemSize / 4, itemSize / 4);
666
+ ctx.lineTo(itemSize / 4, -itemSize / 4);
667
+ ctx.closePath();
668
+ ctx.fill();
669
+ break;
670
+
671
+ case ITEM_TYPES.GROWTH:
672
+ // 生长道具 - 加号形状
673
+ ctx.fillStyle = item.type.color;
674
+ ctx.fillRect(-itemSize / 8, -itemSize / 2, itemSize / 4, itemSize);
675
+ ctx.fillRect(-itemSize / 2, -itemSize / 8, itemSize, itemSize / 4);
676
+ break;
677
+
678
+ case ITEM_TYPES.INVINCIBLE:
679
+ // 无敌道具 - 星星形状
680
+ ctx.fillStyle = item.type.color;
681
+ ctx.beginPath();
682
+ for (let i = 0; i < 5; i++) {
683
+ ctx.lineTo(
684
+ 0,
685
+ -itemSize / 2
686
+ );
687
+ ctx.rotate(Math.PI / 5);
688
+ ctx.lineTo(
689
+ 0,
690
+ -itemSize / 5
691
+ );
692
+ ctx.rotate(Math.PI / 5);
693
+ }
694
+ ctx.closePath();
695
+ ctx.fill();
696
+ break;
697
+
698
+ case ITEM_TYPES.PORTAL:
699
+ // 传送门 - 环形
700
+ ctx.fillStyle = item.type.color;
701
+ ctx.beginPath();
702
+ ctx.arc(0, 0, itemSize / 2, 0, Math.PI * 2);
703
+ ctx.fill();
704
+ ctx.fillStyle = '#0f0f23';
705
+ ctx.beginPath();
706
+ ctx.arc(0, 0, itemSize / 3, 0, Math.PI * 2);
707
+ ctx.fill();
708
+
709
+ // 动态环
710
+ ctx.strokeStyle = item.type.color;
711
+ ctx.lineWidth = 2;
712
+ ctx.beginPath();
713
+ ctx.arc(0, 0, itemSize / 2 - 2, 0, Math.PI * (1 + performance.now() / 1000 % 1));
714
+ ctx.stroke();
715
+ break;
716
+
717
+ case ITEM_TYPES.BOMB:
718
+ // 炸弹 - 圆形带引线
719
+ ctx.fillStyle = item.type.color;
720
+ ctx.beginPath();
721
+ ctx.arc(0, 0, itemSize / 2, 0, Math.PI * 2);
722
+ ctx.fill();
723
+
724
+ // 引线
725
+ ctx.strokeStyle = '#333';
726
+ ctx.lineWidth = 2;
727
+ ctx.beginPath();
728
+ ctx.moveTo(itemSize / 2, -itemSize / 4);
729
+ ctx.lineTo(itemSize / 2 + 5, -itemSize / 2);
730
+ ctx.stroke();
731
+
732
+ // 火花
733
+ ctx.fillStyle = `hsl(${(Date.now() / 30) % 360}, 100%, 60%)`;
734
+ ctx.beginPath();
735
+ ctx.arc(itemSize / 2 + 5, -itemSize / 2, 2, 0, Math.PI * 2);
736
+ ctx.fill();
737
+ break;
738
+ }
739
+
740
+ ctx.restore();
741
+ }
742
+ }
743
+
744
+ // 创建随机道具
745
+ function createRandomItem(type) {
746
+ const item = {
747
+ x: Math.floor(Math.random() * CELLS_X),
748
+ y: Math.floor(Math.random() * CELLS_Y),
749
+ type: type,
750
+ createdAt: Date.now()
751
+ };
752
+
753
+ // 确保道具不会生成在蛇身上
754
+ let isValidPosition = true;
755
+ for (const segment of snake) {
756
+ if (segment.x === item.x && segment.y === item.y) {
757
+ isValidPosition = false;
758
+ break;
759
+ }
760
+ }
761
+
762
+ // 确保道具不会重叠
763
+ for (const existingItem of items) {
764
+ if (existingItem.x === item.x && existingItem.y === item.y) {
765
+ isValidPosition = false;
766
+ break;
767
+ }
768
+ }
769
+
770
+ if (isValidPosition) {
771
+ items.push(item);
772
+ return true;
773
+ } else {
774
+ // 如果位置无效,尝试递归创建(最多3次)
775
+ return createRandomItem(type);
776
+ }
777
+ }
778
+
779
+ // 更新分数
780
+ function updateScore(newScore) {
781
+ score = newScore;
782
+ scoreElement.textContent = score;
783
+
784
+ // 随着分数增加,速度会提升
785
+ const speedMultiplier = 1 + score * 0.005;
786
+ gameSpeed = Math.max(BASE_SPEED * SPEED_INCREASE ** (score / 5), 3);
787
+ speedElement.textContent = speedMultiplier.toFixed(1) + 'x';
788
+ }
789
+
790
+ // 游戏结束
791
+ function gameOver() {
792
+ isGameOver = true;
793
+ clearInterval(gameLoopInterval);
794
+
795
+ // 显示游戏结束画面
796
+ finalScoreElement.textContent = score;
797
+ gameOverElement.classList.remove('hidden');
798
+
799
+ // 创建爆炸效果
800
+ createExplosion(snake[0].x, snake[0].y);
801
+ }
802
+
803
+ // 传送蛇
804
+ function teleportSnake() {
805
+ // 找到有效位置
806
+ let newX, newY;
807
+ let attempts = 0;
808
+ const maxAttempts = 100;
809
+
810
+ do {
811
+ newX = Math.floor(Math.random() * (CELLS_X - 10)) + 5;
812
+ newY = Math.floor(Math.random() * (CELLS_Y - 10)) + 5;
813
+ attempts++;
814
+ } while (
815
+ (attempts < maxAttempts) &&
816
+ items.some(item => item.x === newX && item.y === newY)
817
+ );
818
+
819
+ if (attempts < maxAttempts) {
820
+ // 计算偏移量
821
+ const offsetX = newX - snake[0].x;
822
+ const offsetY = newY - snake[0].y;
823
+
824
+ // 重新定位整条蛇
825
+ for (let i = 0; i < snake.length; i++) {
826
+ snake[i].x += offsetX;
827
+ snake[i].y += offsetY;
828
+
829
+ // 确保蛇不会超出边界
830
+ if (snake[i].x < 0) snake[i].x = 0;
831
+ if (snake[i].x >= CELLS_X) snake[i].x = CELLS_X - 1;
832
+ if (snake[i].y < 0) snake[i].y = 0;
833
+ if (snake[i].y >= CELLS_Y) snake[i].y = CELLS_Y - 1;
834
+ }
835
+
836
+ // 传送门粒子效果
837
+ createPortalEffect(snake[0].x, snake[0].y);
838
+ }
839
+ }
840
+
841
+ // 激活效果
842
+ function activateEffect(effect, duration) {
843
+ // 重置或延长已有效果
844
+ if (activeEffects[effect]) {
845
+ clearTimeout(activeEffects[effect].timeout);
846
+ }
847
+
848
+ activeEffects[effect] = {
849
+ startTime: Date.now(),
850
+ duration: duration * 1000, // 转换为毫秒
851
+ timeout: setTimeout(() => {
852
+ delete activeEffects[effect];
853
+ updateEffectIndicators();
854
+ }, duration * 1000)
855
+ };
856
+
857
+ // 应用效果
858
+ switch (effect) {
859
+ case 'speed':
860
+ break;
861
+ case 'growth':
862
+ break;
863
+ case 'invincible':
864
+ break;
865
+ }
866
+
867
+ updateEffectIndicators();
868
+ }
869
+
870
+ // 更新效果计时器显示
871
+ function updateEffects() {
872
+ const now = Date.now();
873
+
874
+ for (const effect in activeEffects) {
875
+ const remaining = Math.max(0, (activeEffects[effect].startTime + activeEffects[effect].duration - now) / 1000);
876
+
877
+ switch (effect) {
878
+ case 'speed':
879
+ speedTimer.textContent = remaining.toFixed(1) + 's';
880
+ break;
881
+ case 'growth':
882
+ growthTimer.textContent = remaining.toFixed(1) + 's';
883
+ break;
884
+ case 'invincible':
885
+ invincibleTimer.textContent = remaining.toFixed(1) + 's';
886
+ break;
887
+ }
888
+ }
889
+ }
890
+
891
+ // 更新效果指示器
892
+ function updateEffectIndicators() {
893
+ speedIndicator.style.display = activeEffects.speed ? 'block' : 'none';
894
+ growthIndicator.style.display = activeEffects.growth ? 'block' : 'none';
895
+ invincibleIndicator.style.display = activeEffects.invincible ? 'block' : 'none';
896
+
897
+ speedTimer.textContent = '0s';
898
+ growthTimer.textContent = '0s';
899
+ invincibleTimer.textContent = '0s';
900
+ }
901
+
902
+ // 创建粒子效果
903
+ function createParticles(x, y, color) {
904
+ const particleCount = 20;
905
+ const centerX = x * CELL_SIZE + CELL_SIZE / 2;
906
+ const centerY = y * CELL_SIZE + CELL_SIZE / 2;
907
+
908
+ for (let i = 0; i < particleCount; i++) {
909
+ const angle = Math.random() * Math.PI * 2;
910
+ const speed = Math.random() * 3 + 1;
911
+ const size = Math.random() * 4 + 2;
912
+ const lifetime = Math.random() * 1000 + 500;
913
+
914
+ particles.push({
915
+ x: centerX,
916
+ y: centerY,
917
+ vx: Math.cos(angle) * speed,
918
+ vy: Math.sin(angle) * speed,
919
+ size: size,
920
+ color: color,
921
+ life: lifetime,
922
+ maxLife: lifetime,
923
+ alpha: 1
924
+ });
925
+ }
926
+ }
927
+
928
+ // 创建爆炸效果
929
+ function createExplosion(x, y) {
930
+ const particleCount = 50;
931
+ const centerX = x * CELL_SIZE + CELL_SIZE / 2;
932
+ const centerY = y * CELL_SIZE + CELL_SIZE / 2;
933
+
934
+ for (let i = 0; i < particleCount; i++) {
935
+ const angle = Math.random() * Math.PI * 2;
936
+ const speed = Math.random() * 5 + 2;
937
+ const size = Math.random() * 6 + 3;
938
+ const lifetime = Math.random() * 1500 + 500;
939
+ const hue = Math.random() * 60; // 橙红色调
940
+
941
+ particles.push({
942
+ x: centerX,
943
+ y: centerY,
944
+ vx: Math.cos(angle) * speed,
945
+ vy: Math.sin(angle) * speed,
946
+ size: size,
947
+ color: `hsl(${hue}, 100%, 50%)`,
948
+ life: lifetime,
949
+ maxLife: lifetime,
950
+ alpha: 1
951
+ });
952
+ }
953
+ }
954
+
955
+ // 创建传送门效果
956
+ function createPortalEffect(x, y) {
957
+ const particleCount = 40;
958
+ const centerX = x * CELL_SIZE + CELL_SIZE / 2;
959
+ const centerY = y * CELL_SIZE + CELL_SIZE / 2;
960
+
961
+ for (let i = 0; i < particleCount; i++) {
962
+ const angle = Math.random() * Math.PI * 2;
963
+ const radius = Math.random() * 20 + 10;
964
+ const startRadius = 0;
965
+ const endRadius = radius * 2;
966
+ const speed = Math.random() * 0.5 + 0.5;
967
+ const size = Math.random() * 3 + 2;
968
+ const lifetime = Math.random() * 2000 + 1000;
969
+ const hue = Math.random() * 40 + 280; // 紫色调
970
+
971
+ particles.push({
972
+ x: centerX + Math.cos(angle) * startRadius,
973
+ y: centerY + Math.sin(angle) * startRadius,
974
+ vx: Math.cos(angle) * speed,
975
+ vy: Math.sin(angle) * speed,
976
+ targetX: centerX + Math.cos(angle) * endRadius,
977
+ targetY: centerY + Math.sin(angle) * endRadius,
978
+ size: size,
979
+ color: `hsl(${hue}, 100%, 60%)`,
980
+ life: lifetime,
981
+ maxLife: lifetime,
982
+ alpha: 1,
983
+ isPortal: true
984
+ });
985
+ }
986
+ }
987
+
988
+ // 更新粒子
989
+ function updateParticles() {
990
+ for (let i = particles.length - 1; i >= 0; i--) {
991
+ const p = particles[i];
992
+
993
+ // 更新位置
994
+ p.x += p.vx;
995
+ p.y += p.vy;
996
+
997
+ // 如果是传送门粒子,移动到目标位置
998
+ if (p.isPortal && p.targetX && p.targetY) {
999
+ const dx = p.targetX - p.x;
1000
+ const dy = p.targetY - p.y;
1001
+ const dist = Math.sqrt(dx * dx + dy * dy);
1002
+ p.vx = dx / dist * p.vx * 2;
1003
+ p.vy = dy / dist * p.vy * 2;
1004
+
1005
+ if (dist < 5) {
1006
+ p.life = 0; // 到达目标后立即消失
1007
+ }
1008
+ } else {
1009
+ // 重力效果
1010
+ p.vy += 0.05;
1011
+
1012
+ // 摩擦力
1013
+ p.vx *= 0.98;
1014
+ p.vy *= 0.98;
1015
+ }
1016
+
1017
+ // 更新生命周期
1018
+ p.life -= 1000 / FPS;
1019
+ p.alpha = p.life / p.maxLife;
1020
+
1021
+ // 移除死亡的粒子
1022
+ if (p.life <= 0) {
1023
+ particles.splice(i, 1);
1024
+ }
1025
+ }
1026
+ }
1027
+
1028
+ // 绘制粒子
1029
+ function drawParticles() {
1030
+ for (const p of particles) {
1031
+ ctx.globalAlpha = p.alpha;
1032
+ ctx.fillStyle = p.color;
1033
+ ctx.beginPath();
1034
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
1035
+ ctx.fill();
1036
+ }
1037
+ ctx.globalAlpha = 1;
1038
+ }
1039
+
1040
+ // AI控制算法
1041
+ function aiControl() {
1042
+ const head = snake[0];
1043
+ const food = items.filter(item => item.type === ITEM_TYPES.FOOD)[0];
1044
+ const dangerousItems = items.filter(item => item.type === ITEM_TYPES.BOMB);
1045
+
1046
+ // 如果没有食物,保持移动
1047
+ if (!food) {
1048
+ return;
1049
+ }
1050
+
1051
+ // 计算到食物的方向
1052
+ const dx = food.x - head.x;
1053
+ const dy = food.y - head.y;
1054
+ const adx = Math.abs(dx);
1055
+ const ady = Math.abs(dy);
1056
+
1057
+ // 计算安全移动方向(避免撞墙和撞自己)
1058
+ const safeDirections = [];
1059
+
1060
+ // 检查上方是否安全
1061
+ if (!willCollide(head.x, head.y - 1) && direction !== 'down') {
1062
+ safeDirections.push('up');
1063
+ }
1064
+ // 检查下方是否安全
1065
+ if (!willCollide(head.x, head.y + 1) && direction !== 'up') {
1066
+ safeDirections.push('down');
1067
+ }
1068
+ // 检查左方是否安全
1069
+ if (!willCollide(head.x - 1, head.y) && direction !== 'right') {
1070
+ safeDirections.push('left');
1071
+ }
1072
+ // 检查右方是否安全
1073
+ if (!willCollide(head.x + 1, head.y) && direction !== 'left') {
1074
+ safeDirections.push('right');
1075
+ }
1076
+
1077
+ // 如果没有安全方向,保持当前方向(可能会撞上,但AI会尽量避障)
1078
+ if (safeDirections.length === 0) {
1079
+ nextDirection = direction;
1080
+ return;
1081
+ }
1082
+
1083
+ // 优先考虑靠近食物的方向
1084
+ let bestDirection;
1085
+
1086
+ if (adx > ady) {
1087
+ // 水平优先
1088
+ if (dx > 0 && safeDirections.includes('right')) {
1089
+ bestDirection = 'right';
1090
+ } else if (dx < 0 && safeDirections.includes('left')) {
1091
+ bestDirection = 'left';
1092
+ } else {
1093
+ // 水平不可行,尝试垂直
1094
+ if (dy > 0 && safeDirections.includes('down')) {
1095
+ bestDirection = 'down';
1096
+ } else if (dy < 0 && safeDirections.includes('up')) {
1097
+ bestDirection = 'up';
1098
+ } else {
1099
+ // 随机选择安全方向
1100
+ bestDirection = safeDirections[Math.floor(Math.random() * safeDirections.length)];
1101
+ }
1102
+ }
1103
+ } else {
1104
+ // 垂直优先
1105
+ if (dy > 0 && safeDirections.includes('down')) {
1106
+ bestDirection = 'down';
1107
+ } else if (dy < 0 && safeDirections.includes('up')) {
1108
+ bestDirection = 'up';
1109
+ } else {
1110
+ // 垂直不可行,尝试水平
1111
+ if (dx > 0 && safeDirections.includes('right')) {
1112
+ bestDirection = 'right';
1113
+ } else if (dx < 0 && safeDirections.includes('left')) {
1114
+ bestDirection = 'left';
1115
+ } else {
1116
+ // 随机选择安全方向
1117
+ bestDirection = safeDirections[Math.floor(Math.random() * safeDirections.length)];
1118
+ }
1119
+ }
1120
+ }
1121
+
1122
+ // 检查危险的炸弹并避开
1123
+ for (const bomb of dangerousItems) {
1124
+ const distToBomb = Math.abs(bomb.x - head.x) + Math.abs(bomb.y - head.y);
1125
+
1126
+ if (distToBomb < 5) { // 如果在炸弹附近
1127
+ // 计算远离炸弹的方向
1128
+ const dxBomb = bomb.x - head.x;
1129
+ const dyBomb = bomb.y - head.y;
1130
+
1131
+ // 尝试远离炸弹
1132
+ const moveAwayDirections = [];
1133
+
1134
+ if (dxBomb > 0 && safeDirections.includes('left')) {
1135
+ moveAwayDirections.push('left');
1136
+ }
1137
+ if (dxBomb < 0 && safeDirections.includes('right')) {
1138
+ moveAwayDirections.push('right');
1139
+ }
1140
+ if (dyBomb > 0 && safeDirections.includes('up')) {
1141
+ moveAwayDirections.push('up');
1142
+ }
1143
+ if (dyBomb < 0 && safeDirections.includes('down')) {
1144
+ moveAwayDirections.push('down');
1145
+ }
1146
+
1147
+ if (moveAwayDirections.length > 0) {
1148
+ // 选择最远离炸弹的方向(如果可能)
1149
+ let maxDist = 0;
1150
+ let bestAvoidDirection = moveAwayDirections[0];
1151
+
1152
+ for (const dir of moveAwayDirections) {
1153
+ let newX = head.x;
1154
+ let newY = head.y;
1155
+
1156
+ switch (dir) {
1157
+ case 'up': newY--; break;
1158
+ case 'down': newY++; break;
1159
+ case 'left': newX--; break;
1160
+ case 'right': newX++; break;
1161
+ }
1162
+
1163
+ const newDist = Math.abs(bomb.x - newX) + Math.abs(bomb.y - newY);
1164
+ if (newDist > maxDist) {
1165
+ maxDist = newDist;
1166
+ bestAvoidDirection = dir;
1167
+ }
1168
+ }
1169
+
1170
+ bestDirection = bestAvoidDirection;
1171
+ }
1172
+ }
1173
+ }
1174
+
1175
+ // 检查传送门,如果有无敌效果可以使用
1176
+ const portal = items.find(item => item.type === ITEM_TYPES.PORTAL);
1177
+ if (activeEffects.invincible && portal && Date.now() - lastPortalTime > portalCooldown * 1000) {
1178
+ const dxPortal = portal.x - head.x;
1179
+ const dyPortal = portal.y - head.y;
1180
+
1181
+ if (Math.abs(dxPortal) + Math.abs(dyPortal) < 3) { // 接近传送门
1182
+ if (dxPortal > 0 && safeDirections.includes('right')) {
1183
+ bestDirection = 'right';
1184
+ } else if (dxPortal < 0 && safeDirections.includes('left')) {
1185
+ bestDirection = 'left';
1186
+ } else if (dyPortal > 0 && safeDirections.includes('down')) {
1187
+ bestDirection = 'down';
1188
+ } else if (dyPortal < 0 && safeDirections.includes('up')) {
1189
+ bestDirection = 'up';
1190
+ }
1191
+ }
1192
+ }
1193
+
1194
+ // 检查速度道具,如果有无敌效果可以使用
1195
+ const speedItem = items.find(item => item.type === ITEM_TYPES.SPEED);
1196
+ if (speedItem && !activeEffects.speed) {
1197
+ const dxSpeed = speedItem.x - head.x;
1198
+ const dySpeed = speedItem.y - head.y;
1199
+
1200
+ if (Math.abs(dxSpeed) + Math.abs(dySpeed) < 8) { // 在速度道具附近
1201
+ if (dxSpeed > 0 && safeDirections.includes('right')) {
1202
+ bestDirection = 'right';
1203
+ } else if (dxSpeed < 0 && safeDirections.includes('left')) {
1204
+ bestDirection = 'left';
1205
+ } else if (dySpeed > 0 && safeDirections.includes('down')) {
1206
+ bestDirection = 'down';
1207
+ } else if (dySpeed < 0 && safeDirections.includes('up')) {
1208
+ bestDirection = 'up';
1209
+ }
1210
+ }
1211
+ }
1212
+
1213
+ nextDirection = bestDirection;
1214
+ }
1215
+
1216
+ // 检查移动是否会碰撞
1217
+ function willCollide(x, y) {
1218
+ // 检查墙壁(无敌状态下可以穿过)
1219
+ if (!activeEffects.invincible) {
1220
+ if (x < 0 || x >= CELLS_X || y < 0 || y >= CELLS_Y) {
1221
+ return true;
1222
+ }
1223
+ }
1224
+
1225
+ // 检查自己(无敌状态下可以穿过)
1226
+ if (!activeEffects.invincible) {
1227
+ for (let i = 0; i < snake.length; i++) {
1228
+ if (x === snake[i].x && y === snake[i].y) {
1229
+ return true;
1230
+ }
1231
+ }
1232
+ }
1233
+
1234
+ return false;
1235
+ }
1236
+
1237
+ // 键盘控制
1238
+ document.addEventListener('keydown', (e) => {
1239
+ if (isGameOver && e.key === 'r') {
1240
+ initGame();
1241
+ return;
1242
+ }
1243
+
1244
+ // 空格键切换自动/手动���式
1245
+ if (e.key === ' ') {
1246
+ isAutoMode = !isAutoMode;
1247
+ modeElement.textContent = isAutoMode ? 'AI自动' : '手动控制';
1248
+ modeElement.className = isAutoMode ? 'text-xl text-amber-400' : 'text-xl text-white';
1249
+ return;
1250
+ }
1251
+
1252
+ // 手动控制
1253
+ if (!isAutoMode) {
1254
+ switch (e.key) {
1255
+ case 'ArrowUp':
1256
+ case 'w':
1257
+ case 'W':
1258
+ if (direction !== 'down') nextDirection = 'up';
1259
+ break;
1260
+ case 'ArrowDown':
1261
+ case 's':
1262
+ case 'S':
1263
+ if (direction !== 'up') nextDirection = 'down';
1264
+ break;
1265
+ case 'ArrowLeft':
1266
+ case 'a':
1267
+ case 'A':
1268
+ if (direction !== 'right') nextDirection = 'left';
1269
+ break;
1270
+ case 'ArrowRight':
1271
+ case 'd':
1272
+ case 'D':
1273
+ if (direction !== 'left') nextDirection = 'right';
1274
+ break;
1275
+ }
1276
+ }
1277
+ });
1278
+
1279
+ // 重新开始按钮
1280
+ restartButton.addEventListener('click', initGame);
1281
+
1282
+ // 开始游戏
1283
+ initGame();
1284
+ });
1285
+ </script>
1286
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=yuliqing16/spider" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p><p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Yoleo/snake-literario" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1287
+ </html>