vue3 实现贪吃蛇 电脑版01

html 复制代码
<template>
  <div class="snake-game">
    <!-- 游戏标题和分数 -->
    <div class="game-header">
      <h1>贪吃蛇</h1>
      <div class="score-panel">
        <div class="current-score">分数: {{ currentScore }}</div>
        <div class="high-score">最高分: {{ highScore }}</div>
      </div>
    </div>

    <!-- 游戏区域 -->
    <div class="game-area">
      <div class="game-board" :style="{ width: `${boardSize}px`, height: `${boardSize}px` }">
        <!-- 蛇身(绝对定位) -->
        <div
            v-for="(segment, index) in snake"
            :key="index"
            class="snake-segment"
            :class="{ 'snake-head': index === 0 }"
            :style="{
              left: `${segment.x * cellSize}px`,
              top: `${segment.y * cellSize}px`,
              width: `${cellSize}px`,
              height: `${cellSize}px`
            }"
        ></div>

        <!-- 食物(绝对定位,固定尺寸) -->
        <div
            class="food"
            v-if="food"
            :style="{
              left: `${food.x * cellSize}px`,
              top: `${food.y * cellSize}px`,
              width: `${cellSize}px`,
              height: `${cellSize}px`
            }"
        ></div>

        <!-- 游戏开始界面 -->
        <div class="game-start" v-if="!isPlaying && !gameOver">
          <button @click="startGame" class="start-btn">开始游戏</button>
        </div>

        <!-- 游戏结束界面 -->
        <div class="game-over" v-if="gameOver">
          <h2>游戏结束</h2>
          <p>最终得分: {{ currentScore }}</p>
          <button @click="startGame" class="restart-btn">再来一局</button>
        </div>
      </div>
    </div>

    <!-- 虚拟方向按键 -->
    <div class="control-pad" v-if="isPlaying || gameOver">
      <div class="control-row top-row">
        <button
            class="control-btn up"
            @click="setDirection(0, -1)"
            @touchstart.prevent="setDirection(0, -1)"
        >
          ↑
        </button>
      </div>
      <div class="control-row middle-row">
        <button
            class="control-btn left"
            @click="setDirection(-1, 0)"
            @touchstart.prevent="setDirection(-1, 0)"
        >
          ←
        </button>
        <button
            class="control-btn down"
            @click="setDirection(0, 1)"
            @touchstart.prevent="setDirection(0, 1)"
        >
          ↓
        </button>
        <button
            class="control-btn right"
            @click="setDirection(1, 0)"
            @touchstart.prevent="setDirection(1, 0)"
        >
          →
        </button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue';

// 游戏配置
const gridSize = 15; // 游戏网格大小(15x15)
const initialSpeed = 250; // 初始速度(毫秒)
const speedIncrease = 5; // 每吃一个食物加速的毫秒数

// 响应式计算游戏板和单元格大小
const boardSize = computed(() => {
  return Math.min(window.innerWidth * 0.9, 400); // 游戏板大小
});
const cellSize = computed(() => {
  return boardSize.value / gridSize; // 每个单元格的像素大小(固定)
});

// 游戏状态
const snake = ref([{ x: 7, y: 7 }]); // 蛇的初始位置(网格坐标)
const food = ref(null); // 食物位置(网格坐标)
const direction = ref({ x: 1, y: 0 }); // 当前方向(初始向右)
const nextDirection = ref({ ...direction.value }); // 下一次移动的方向
const currentScore = ref(0); // 当前分数
const highScore = ref(0); // 最高分
const isPlaying = ref(false); // 是否正在游戏中
const gameOver = ref(false); // 游戏是否结束
const gameInterval = ref(null); // 游戏循环计时器

// 初始化游戏
const startGame = () => {
  // 重置游戏状态
  snake.value = [{ x: 7, y: 7 }];
  direction.value = { x: 1, y: 0 };
  nextDirection.value = { ...direction.value };
  currentScore.value = 0;
  gameOver.value = false;
  isPlaying.value = true;

  // 生成食物
  generateFood();

  // 开始游戏循环
  if (gameInterval.value) clearInterval(gameInterval.value);
  gameInterval.value = setInterval(moveSnake, initialSpeed);
};

// 生成食物(固定在网格坐标上)
const generateFood = () => {
  let newFood;
  // 确保食物不会出现在蛇身上
  do {
    newFood = {
      x: Math.floor(Math.random() * gridSize), // 网格X坐标(0-14)
      y: Math.floor(Math.random() * gridSize)  // 网格Y坐标(0-14)
    };
  } while (snake.value.some(segment => segment.x === newFood.x && segment.y === newFood.y));

  food.value = newFood; // 食物坐标一旦生成,不会随蛇移动改变
};

// 移动蛇(基于网格坐标计算)
const moveSnake = () => {
  if (!isPlaying.value) return;

  // 更新方向
  direction.value = { ...nextDirection.value };

  // 创建新头部(基于网格坐标计算)
  const head = {
    x: snake.value[0].x + direction.value.x,
    y: snake.value[0].y + direction.value.y
  };

  // 检查是否碰撞
  if (checkCollision(head)) {
    endGame();
    return;
  }

  // 将新头部添加到蛇身
  snake.value.unshift(head);

  // 检查是否吃到食物(基于网格坐标匹配)
  if (head.x === food.value.x && head.y === food.value.y) {
    currentScore.value += 10;
    if (currentScore.value > highScore.value) {
      highScore.value = currentScore.value;
      localStorage.setItem('snakeHighScore', highScore.value);
    }
    generateFood(); // 生成新食物(新的网格坐标)
    adjustSpeed();
  } else {
    snake.value.pop(); // 没吃到食物则移除尾部
  }
};

// 检查碰撞
const checkCollision = (head) => {
  // 撞墙(超出网格范围)
  if (head.x < 0 || head.x >= gridSize || head.y < 0 || head.y >= gridSize) {
    return true;
  }
  // 撞到自己
  return snake.value.some((segment, index) => index !== 0 && segment.x === head.x && segment.y === head.y);
};

// 调整游戏速度
const adjustSpeed = () => {
  if (gameInterval.value) {
    clearInterval(gameInterval.value);
    const newSpeed = Math.max(initialSpeed - (currentScore.value / 10) * speedIncrease, 100);
    gameInterval.value = setInterval(moveSnake, newSpeed);
  }
};

// 结束游戏
const endGame = () => {
  isPlaying.value = false;
  gameOver.value = true;
  clearInterval(gameInterval.value);
};

// 设置方向
const setDirection = (x, y) => {
  if ((direction.value.x === -x && direction.value.y === -y) || !isPlaying.value) {
    return;
  }
  nextDirection.value = { x, y };
};

// 键盘控制(调试用)
const handleKeydown = (e) => {
  if (!isPlaying.value) return;
  switch(e.key) {
    case 'ArrowUp': setDirection(0, -1); break;
    case 'ArrowDown': setDirection(0, 1); break;
    case 'ArrowLeft': setDirection(-1, 0); break;
    case 'ArrowRight': setDirection(1, 0); break;
  }
};

// 加载最高分
const loadHighScore = () => {
  const saved = localStorage.getItem('snakeHighScore');
  if (saved) highScore.value = parseInt(saved);
};

// 生命周期
onMounted(() => {
  loadHighScore();
  window.addEventListener('keydown', handleKeydown);
});

onUnmounted(() => {
  window.removeEventListener('keydown', handleKeydown);
  if (gameInterval.value) clearInterval(gameInterval.value);
});
</script>

<style scoped>
.snake-game {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100vh;
  background-color: #f0f2f5;
  padding: 20px 10px;
  box-sizing: border-box;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

/* 游戏标题和分数 */
.game-header {
  width: 100%;
  max-width: 400px;
  margin-bottom: 20px;
  text-align: center;
}

.game-header h1 {
  color: #1a1a1a;
  margin: 0 0 15px 0;
  font-size: 28px;
}

.score-panel {
  display: flex;
  justify-content: space-around;
  background-color: white;
  padding: 12px;
  border-radius: 10px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.current-score, .high-score {
  font-size: 16px;
  font-weight: 500;
  color: #333;
}

/* 游戏区域 */
.game-area {
  position: relative;
  margin-bottom: 30px;
}

.game-board {
  position: relative; /* 作为蛇和食物的定位容器 */
  background-color: #e6f7ff;
  border: 2px solid #1890ff;
  border-radius: 8px;
  overflow: hidden;
}

/* 蛇身和食物使用绝对定位 */
.snake-segment, .food {
  position: absolute;
  box-sizing: border-box;
}

.snake-segment {
  background-color: #52c41a;
  border-radius: 4px;
  transition: all 0.1s ease;
}

.snake-head {
  background-color: #2e7d32;
  border: 2px solid #1b5e20;
}

.food {
  background-color: #ff4d4f;
  border-radius: 50%;
  box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.5) inset;
}

/* 游戏开始和结束界面 */
.game-start, .game-over {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.7);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  color: white;
}

.game-over h2 {
  margin: 0 0 15px 0;
  font-size: 24px;
}

.game-over p {
  margin: 0 0 20px 0;
  font-size: 18px;
}

.start-btn, .restart-btn {
  background-color: #1890ff;
  color: white;
  border: none;
  border-radius: 6px;
  padding: 12px 24px;
  font-size: 16px;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.2s;
}

.start-btn:hover, .restart-btn:hover {
  background-color: #096dd9;
}

.start-btn:active, .restart-btn:active {
  transform: scale(0.98);
}

/* 虚拟方向按键 */
.control-pad {
  width: 240px;
  height: 200px;
  position: relative;
  margin-top: auto;
  margin-bottom: 20px;
}

.control-row {
  display: flex;
  justify-content: center;
}

.top-row {
  margin-bottom: 10px;
}

.middle-row {
  gap: 10px;
}

.control-btn {
  width: 70px;
  height: 70px;
  border-radius: 10px;
  background-color: #1890ff;
  color: white;
  border: none;
  font-size: 20px;
  font-weight: bold;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.control-btn:active {
  transform: scale(0.95);
  background-color: #096dd9;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* 适配小屏幕 */
@media (max-width: 360px) {
  .control-pad {
    width: 200px;
    height: 170px;
  }

  .control-btn {
    width: 60px;
    height: 60px;
    font-size: 18px;
  }

  .game-header h1 {
    font-size: 24px;
  }
}
</style>
相关推荐
trsoliu3 小时前
2025年Web前端前沿技术动态:WebGL动画、CSS View Transitions与HTML隐藏API
前端·javascript·css
trsoliu3 小时前
2025年Web前端最新趋势:React基金会成立、AI编码助手崛起与Astro极速搜索
前端·javascript·react.js
一 乐3 小时前
商城推荐系统|基于SprinBoot+vue的商城推荐系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·商城推荐系统
鹏多多3 小时前
React瀑布流Masonry-Layout插件全方位指南:从基础到进阶实践
前端·javascript·react.js
fruge3 小时前
前端数据可视化实战:Chart.js vs ECharts 深度对比与实现指南
前端·javascript·信息可视化
卓码软件测评3 小时前
借助大语言模型实现高效测试迁移:Airbnb的大规模实践
开发语言·前端·javascript·人工智能·语言模型·自然语言处理
Dragon Wu3 小时前
Taro 自定义tab栏和自定义导航栏
前端·javascript·小程序·typescript·前端框架·taro
艾小码3 小时前
2025年前端菜鸟自救指南:从零搭建专业开发环境
前端·javascript
开发者小天9 小时前
调整为 dart-sass 支持的语法,将深度选择器/deep/调整为::v-deep
开发语言·前端·javascript·vue.js·uni-app·sass·1024程序员节