game.html 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>贪吃蛇小游戏</title>
  7. <style>
  8. * {
  9. margin: 0;
  10. padding: 0;
  11. box-sizing: border-box;
  12. font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  13. }
  14. body {
  15. display: flex;
  16. justify-content: center;
  17. align-items: center;
  18. min-height: 100vh;
  19. background: linear-gradient(135deg, #1a1a2e, #16213e);
  20. color: #fff;
  21. padding: 20px;
  22. }
  23. .game-container {
  24. max-width: 800px;
  25. width: 100%;
  26. background: rgba(255, 255, 255, 0.05);
  27. backdrop-filter: blur(10px);
  28. border-radius: 20px;
  29. box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  30. overflow: hidden;
  31. padding: 25px;
  32. }
  33. header {
  34. text-align: center;
  35. margin-bottom: 20px;
  36. }
  37. h1 {
  38. font-size: 2.8rem;
  39. color: #ffd700;
  40. text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
  41. margin-bottom: 5px;
  42. }
  43. .subtitle {
  44. font-size: 1.2rem;
  45. color: #e6e6e6;
  46. opacity: 0.8;
  47. }
  48. .game-info {
  49. display: flex;
  50. justify-content: space-between;
  51. align-items: center;
  52. background: rgba(0, 0, 0, 0.2);
  53. padding: 15px 25px;
  54. border-radius: 12px;
  55. margin-bottom: 20px;
  56. }
  57. .score-container {
  58. display: flex;
  59. align-items: center;
  60. gap: 10px;
  61. }
  62. .score-label {
  63. font-size: 1.2rem;
  64. font-weight: bold;
  65. }
  66. .score {
  67. font-size: 2rem;
  68. font-weight: bold;
  69. color: #ffd700;
  70. background: rgba(0, 0, 0, 0.3);
  71. padding: 5px 15px;
  72. border-radius: 10px;
  73. min-width: 80px;
  74. text-align: center;
  75. }
  76. .game-status {
  77. font-size: 1.2rem;
  78. font-weight: bold;
  79. padding: 8px 20px;
  80. border-radius: 50px;
  81. background: rgba(255, 255, 255, 0.1);
  82. }
  83. .game-status.playing {
  84. color: #4ade80;
  85. background: rgba(74, 222, 128, 0.15);
  86. }
  87. .game-status.paused {
  88. color: #fbbf24;
  89. background: rgba(251, 191, 36, 0.15);
  90. }
  91. .game-status.game-over {
  92. color: #f87171;
  93. background: rgba(248, 113, 113, 0.15);
  94. }
  95. .game-board {
  96. display: flex;
  97. justify-content: center;
  98. }
  99. canvas {
  100. background: rgba(0, 0, 0, 0.15);
  101. border-radius: 10px;
  102. box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
  103. }
  104. .controls {
  105. display: flex;
  106. justify-content: center;
  107. gap: 20px;
  108. margin: 25px 0;
  109. }
  110. button {
  111. padding: 12px 30px;
  112. font-size: 1.1rem;
  113. font-weight: bold;
  114. border: none;
  115. border-radius: 50px;
  116. cursor: pointer;
  117. transition: all 0.3s ease;
  118. outline: none;
  119. min-width: 120px;
  120. }
  121. #start-btn {
  122. background: linear-gradient(to right, #4ade80, #22c55e);
  123. color: white;
  124. box-shadow: 0 4px 10px rgba(74, 222, 128, 0.4);
  125. }
  126. #start-btn:hover {
  127. background: linear-gradient(to right, #22c55e, #16a34a);
  128. transform: translateY(-3px);
  129. box-shadow: 0 6px 15px rgba(74, 222, 128, 0.6);
  130. }
  131. #pause-btn {
  132. background: linear-gradient(to right, #fbbf24, #f59e0b);
  133. color: white;
  134. box-shadow: 0 4px 10px rgba(251, 191, 36, 0.4);
  135. }
  136. #pause-btn:hover {
  137. background: linear-gradient(to right, #f59e0b, #d97706);
  138. transform: translateY(-3px);
  139. box-shadow: 0 6px 15px rgba(251, 191, 36, 0.6);
  140. }
  141. #reset-btn {
  142. background: linear-gradient(to right, #60a5fa, #3b82f6);
  143. color: white;
  144. box-shadow: 0 4px 10px rgba(59, 130, 246, 0.4);
  145. }
  146. #reset-btn:hover {
  147. background: linear-gradient(to right, #3b82f6, #2563eb);
  148. transform: translateY(-3px);
  149. box-shadow: 0 6px 15px rgba(59, 130, 246, 0.6);
  150. }
  151. .instructions {
  152. background: rgba(255, 255, 255, 0.07);
  153. padding: 20px;
  154. border-radius: 12px;
  155. margin-top: 20px;
  156. }
  157. .instructions h2 {
  158. color: #ffd700;
  159. margin-bottom: 15px;
  160. font-size: 1.4rem;
  161. }
  162. .instructions ul {
  163. padding-left: 25px;
  164. }
  165. .instructions li {
  166. margin-bottom: 10px;
  167. line-height: 1.5;
  168. }
  169. .key {
  170. display: inline-block;
  171. background: rgba(255, 255, 255, 0.1);
  172. border: 1px solid rgba(255, 255, 255, 0.2);
  173. padding: 2px 8px;
  174. border-radius: 5px;
  175. font-family: monospace;
  176. }
  177. @media (max-width: 600px) {
  178. .game-info {
  179. flex-direction: column;
  180. gap: 15px;
  181. }
  182. .controls {
  183. flex-direction: column;
  184. align-items: center;
  185. }
  186. button {
  187. width: 100%;
  188. max-width: 300px;
  189. }
  190. canvas {
  191. width: 100%;
  192. max-width: 400px;
  193. height: auto;
  194. }
  195. }
  196. </style>
  197. </head>
  198. <body>
  199. <div class="game-container">
  200. <header>
  201. <h1>贪吃蛇小游戏</h1>
  202. <p class="subtitle">经典重现 - 控制小蛇吃食物,避免撞墙或撞到自己</p>
  203. </header>
  204. <div class="game-info">
  205. <div class="score-container">
  206. <span class="score-label">分数:</span>
  207. <div class="score">0</div>
  208. </div>
  209. <div class="game-status paused">游戏暂停</div>
  210. </div>
  211. <div class="game-board">
  212. <canvas id="gameCanvas" width="500" height="400"></canvas>
  213. </div>
  214. <div class="controls">
  215. <button id="start-btn">开始游戏</button>
  216. <button id="pause-btn">暂停游戏</button>
  217. <button id="reset-btn">重新开始</button>
  218. </div>
  219. <div class="instructions">
  220. <h2>游戏说明</h2>
  221. <ul>
  222. <li>使用 <span class="key">↑</span> <span class="key">←</span> <span class="key">↓</span> <span class="key">→</span> 方向键控制蛇的移动方向</li>
  223. <li>吃到红色食物可以增加分数并使蛇变长</li>
  224. <li>撞到墙壁或自己的身体会导致游戏结束</li>
  225. <li>游戏暂停时,按方向键可以继续游戏</li>
  226. <li>每吃一个食物得10分,吃得越多得分越高</li>
  227. </ul>
  228. </div>
  229. </div>
  230. <script>
  231. // 获取Canvas元素和上下文
  232. const canvas = document.getElementById('gameCanvas');
  233. const ctx = canvas.getContext('2d');
  234. // 游戏配置
  235. const gridSize = 20;
  236. const tileCount = canvas.width / gridSize;
  237. const fps = 8; // 游戏速度(帧率)
  238. // 游戏状态
  239. let snake = [];
  240. let food = {};
  241. let dx = gridSize; // x方向移动速度
  242. let dy = 0; // y方向移动速度
  243. let score = 0;
  244. let gameRunning = false;
  245. let gameInterval;
  246. // 获取DOM元素
  247. const scoreDisplay = document.querySelector('.score');
  248. const gameStatus = document.querySelector('.game-status');
  249. const startBtn = document.getElementById('start-btn');
  250. const pauseBtn = document.getElementById('pause-btn');
  251. const resetBtn = document.getElementById('reset-btn');
  252. // 初始化游戏
  253. function initGame() {
  254. // 初始化蛇(3个部分)
  255. snake = [
  256. {x: 7 * gridSize, y: 10 * gridSize},
  257. {x: 6 * gridSize, y: 10 * gridSize},
  258. {x: 5 * gridSize, y: 10 * gridSize}
  259. ];
  260. // 初始移动方向
  261. dx = gridSize;
  262. dy = 0;
  263. // 初始分数
  264. score = 0;
  265. updateScore();
  266. // 生成食物
  267. generateFood();
  268. // 绘制游戏
  269. drawGame();
  270. // 更新状态显示
  271. gameStatus.textContent = '游戏暂停';
  272. gameStatus.className = 'game-status paused';
  273. }
  274. // 生成食物
  275. function generateFood() {
  276. // 确保食物不会生成在蛇身上
  277. let foodOnSnake;
  278. do {
  279. foodOnSnake = false;
  280. food = {
  281. x: Math.floor(Math.random() * tileCount) * gridSize,
  282. y: Math.floor(Math.random() * tileCount) * gridSize
  283. };
  284. // 检查食物是否在蛇身上
  285. for (let segment of snake) {
  286. if (segment.x === food.x && segment.y === food.y) {
  287. foodOnSnake = true;
  288. break;
  289. }
  290. }
  291. } while (foodOnSnake);
  292. }
  293. // 绘制游戏
  294. function drawGame() {
  295. // 清除画布
  296. ctx.clearRect(0, 0, canvas.width, canvas.height);
  297. // 绘制网格背景
  298. ctx.fillStyle = 'rgba(30, 30, 46, 0.3)';
  299. for (let x = 0; x < canvas.width; x += gridSize) {
  300. for (let y = 0; y < canvas.height; y += gridSize) {
  301. ctx.fillRect(x, y, gridSize, gridSize);
  302. }
  303. }
  304. // 绘制蛇
  305. snake.forEach((segment, index) => {
  306. // 蛇头
  307. if (index === 0) {
  308. ctx.fillStyle = '#4ade80';
  309. ctx.fillRect(segment.x, segment.y, gridSize, gridSize);
  310. // 蛇头内部装饰
  311. ctx.fillStyle = '#166534';
  312. ctx.fillRect(segment.x + 4, segment.y + 4, gridSize - 8, gridSize - 8);
  313. // 眼睛
  314. ctx.fillStyle = '#000';
  315. ctx.fillRect(segment.x + 5, segment.y + 5, 4, 4);
  316. ctx.fillRect(segment.x + 11, segment.y + 5, 4, 4);
  317. }
  318. // 蛇身
  319. else {
  320. ctx.fillStyle = '#22c55e';
  321. ctx.fillRect(segment.x, segment.y, gridSize, gridSize);
  322. // 蛇身内部装饰
  323. ctx.fillStyle = '#14532d';
  324. ctx.fillRect(segment.x + 4, segment.y + 4, gridSize - 8, gridSize - 8);
  325. }
  326. // 边框
  327. ctx.strokeStyle = '#14532d';
  328. ctx.strokeRect(segment.x, segment.y, gridSize, gridSize);
  329. });
  330. // 绘制食物
  331. ctx.fillStyle = '#ef4444';
  332. ctx.beginPath();
  333. ctx.arc(food.x + gridSize/2, food.y + gridSize/2, gridSize/2, 0, Math.PI * 2);
  334. ctx.fill();
  335. // 食物内部装饰
  336. ctx.fillStyle = '#fecaca';
  337. ctx.beginPath();
  338. ctx.arc(food.x + gridSize/2, food.y + gridSize/2, gridSize/4, 0, Math.PI * 2);
  339. ctx.fill();
  340. }
  341. // 移动蛇
  342. function moveSnake() {
  343. // 创建新的蛇头
  344. const head = {x: snake[0].x + dx, y: snake[0].y + dy};
  345. // 检查是否撞墙
  346. if (head.x < 0 || head.x >= canvas.width || head.y < 0 || head.y >= canvas.height) {
  347. gameOver();
  348. return;
  349. }
  350. // 检查是否撞到自己
  351. for (let i = 0; i < snake.length; i++) {
  352. if (snake[i].x === head.x && snake[i].y === head.y) {
  353. gameOver();
  354. return;
  355. }
  356. }
  357. // 添加新的蛇头
  358. snake.unshift(head);
  359. // 检查是否吃到食物
  360. if (head.x === food.x && head.y === food.y) {
  361. // 增加分数
  362. score += 10;
  363. updateScore();
  364. // 生成新食物
  365. generateFood();
  366. } else {
  367. // 如果没吃到食物,移除蛇尾
  368. snake.pop();
  369. }
  370. // 重绘游戏
  371. drawGame();
  372. }
  373. // 更新分数显示
  374. function updateScore() {
  375. scoreDisplay.textContent = score;
  376. }
  377. // 游戏结束
  378. function gameOver() {
  379. clearInterval(gameInterval);
  380. gameRunning = false;
  381. gameStatus.textContent = '游戏结束!';
  382. gameStatus.className = 'game-status game-over';
  383. }
  384. // 开始游戏
  385. function startGame() {
  386. if (!gameRunning) {
  387. gameRunning = true;
  388. gameInterval = setInterval(moveSnake, 1000 / fps);
  389. gameStatus.textContent = '游戏中...';
  390. gameStatus.className = 'game-status playing';
  391. }
  392. }
  393. // 暂停游戏
  394. function pauseGame() {
  395. if (gameRunning) {
  396. clearInterval(gameInterval);
  397. gameRunning = false;
  398. gameStatus.textContent = '游戏暂停';
  399. gameStatus.className = 'game-status paused';
  400. }
  401. }
  402. // 重新开始游戏
  403. function resetGame() {
  404. pauseGame();
  405. initGame();
  406. }
  407. // 键盘控制
  408. document.addEventListener('keydown', (e) => {
  409. // 防止页面滚动
  410. if ([37, 38, 39, 40].includes(e.keyCode)) {
  411. e.preventDefault();
  412. }
  413. // 如果游戏暂停,按方向键开始游戏
  414. if (!gameRunning && [37, 38, 39, 40].includes(e.keyCode)) {
  415. startGame();
  416. }
  417. // 方向控制(不能直接反向)
  418. switch(e.keyCode) {
  419. case 37: // 左箭头
  420. if (dx === 0) {
  421. dx = -gridSize;
  422. dy = 0;
  423. }
  424. break;
  425. case 38: // 上箭头
  426. if (dy === 0) {
  427. dx = 0;
  428. dy = -gridSize;
  429. }
  430. break;
  431. case 39: // 右箭头
  432. if (dx === 0) {
  433. dx = gridSize;
  434. dy = 0;
  435. }
  436. break;
  437. case 40: // 下箭头
  438. if (dy === 0) {
  439. dx = 0;
  440. dy = gridSize;
  441. }
  442. break;
  443. }
  444. });
  445. // 按钮事件监听
  446. startBtn.addEventListener('click', startGame);
  447. pauseBtn.addEventListener('click', pauseGame);
  448. resetBtn.addEventListener('click', resetGame);
  449. // 初始化游戏
  450. initGame();
  451. </script>
  452. </body>
  453. </html>