用Deepseek写扫雷uniapp小游戏

发布于:2025-03-27 ⋅ 阅读:(24) ⋅ 点赞:(0)

扫雷作为Windows系统自带的经典小游戏,承载了许多人的童年回忆。本文将详细介绍如何使用Uniapp框架从零开始实现一个完整的扫雷游戏,包含核心算法、交互设计和状态管理。无论你是Uniapp初学者还是有一定经验的开发者,都能从本文中获得启发。

一、游戏设计思路

1.1 游戏规则回顾

扫雷游戏的基本规则非常简单:

  • 游戏区域由方格组成,部分方格下藏有地雷

  • 玩家点击方格可以揭开它

  • 如果揭开的是地雷,游戏结束

  • 如果揭开的是空白格子,会显示周围8格中的地雷数量

  • 玩家可以通过标记来标识可能的地雷位置

  • 当所有非地雷方格都被揭开时,玩家获胜

1.2 技术实现要点

基于上述规则,我们需要实现以下核心功能:

  1. 游戏棋盘的数据结构

  2. 随机布置地雷的算法

  3. 计算每个格子周围地雷数量的方法

  4. 点击和长按的交互处理

  5. 游戏状态管理(进行中、胜利、失败)

  6. 计时和剩余地雷数的显示

二、Uniapp实现详解

2.1 项目结构

我们创建一个单独的页面minesweeper/minesweeper.vue来实现游戏,主要包含三个部分:

  • 模板部分:游戏界面布局

  • 脚本部分:游戏逻辑实现

  • 样式部分:游戏视觉效果

2.2 核心代码解析

2.2.1 游戏数据初始化
data() {
  return {
    rows: 10,       // 行数
    cols: 10,       // 列数
    mines: 15,      // 地雷数
    board: [],      // 游戏棋盘数据
    remainingMines: 0, // 剩余地雷数
    time: 0,        // 游戏时间
    timer: null,    // 计时器
    gameOver: false, // 游戏是否结束
    gameOverMessage: '', // 结束消息
    firstClick: true // 是否是第一次点击
  }
}
2.2.2 游戏初始化方法
startGame(rows, cols, mines) {
  this.rows = rows;
  this.cols = cols;
  this.mines = mines;
  this.remainingMines = mines;
  this.time = 0;
  this.gameOver = false;
  this.firstClick = true;
  
  // 初始化棋盘数据结构
  this.board = Array(rows).fill().map(() => 
    Array(cols).fill().map(() => ({
      mine: false,          // 是否是地雷
      revealed: false,      // 是否已揭开
      flagged: false,       // 是否已标记
      neighborMines: 0,     // 周围地雷数
      exploded: false       // 是否爆炸(踩中地雷)
    }))
  );
}
2.2.3 地雷布置算法
placeMines(firstRow, firstCol) {
  let minesPlaced = 0;
  
  // 随机布置地雷,但避开第一次点击位置及周围
  while (minesPlaced < this.mines) {
    const row = Math.floor(Math.random() * this.rows);
    const col = Math.floor(Math.random() * this.cols);
    
    if (!this.board[row][col].mine && 
        Math.abs(row - firstRow) > 1 && 
        Math.abs(col - firstCol) > 1) {
      this.board[row][col].mine = true;
      minesPlaced++;
    }
  }
  
  // 计算每个格子周围的地雷数
  for (let row = 0; row < this.rows; row++) {
    for (let col = 0; col < this.cols; col++) {
      if (!this.board[row][col].mine) {
        let count = 0;
        // 检查周围8个格子
        for (let r = Math.max(0, row - 1); r <= Math.min(this.rows - 1, row + 1); r++) {
          for (let c = Math.max(0, col - 1); c <= Math.min(this.cols - 1, col + 1); c++) {
            if (this.board[r][c].mine) count++;
          }
        }
        this.board[row][col].neighborMines = count;
      }
    }
  }
}

 2.2.4 格子揭示逻辑

revealCell(row, col) {
  // 第一次点击时布置地雷
  if (this.firstClick) {
    this.placeMines(row, col);
    this.startTimer();
    this.firstClick = false;
  }
  
  // 点击到地雷
  if (this.board[row][col].mine) {
    this.board[row][col].exploded = true;
    this.gameOver = true;
    this.gameOverMessage = '游戏结束!你踩到地雷了!';
    this.revealAllMines();
    return;
  }
  
  // 递归揭示空白区域
  this.revealEmptyCells(row, col);
  
  // 检查是否获胜
  if (this.checkWin()) {
    this.gameOver = true;
    this.gameOverMessage = '恭喜你赢了!';
  }
}
2.2.5 递归揭示空白区域
revealEmptyCells(row, col) {
  // 边界检查
  if (row < 0 || row >= this.rows || col < 0 || col >= this.cols || 
      this.board[row][col].revealed || this.board[row][col].flagged) {
    return;
  }
  
  this.board[row][col].revealed = true;
  
  // 如果是空白格子,递归揭示周围的格子
  if (this.board[row][col].neighborMines === 0) {
    for (let r = Math.max(0, row - 1); r <= Math.min(this.rows - 1, row + 1); r++) {
      for (let c = Math.max(0, col - 1); c <= Math.min(this.cols - 1, col + 1); c++) {
        if (r !== row || c !== col) {
          this.revealEmptyCells(r, c);
        }
      }
    }
  }
}

2.3 界面实现

游戏界面主要分为三个部分:

  1. 游戏信息区:显示标题、剩余地雷数和用时

  2. 游戏棋盘:由方格组成的扫雷区域

  3. 控制区:难度选择按钮和游戏结束提示

<view class="game-board">
  <view v-for="(row, rowIndex) in board" :key="rowIndex" class="row">
    <view 
      v-for="(cell, colIndex) in row" 
      :key="colIndex" 
      class="cell"
      :class="{
        'revealed': cell.revealed,
        'flagged': cell.flagged,
        'mine': cell.revealed && cell.mine,
        'exploded': cell.exploded
      }"
      @click="revealCell(rowIndex, colIndex)"
      @longpress="toggleFlag(rowIndex, colIndex)"
    >
      <!-- 显示格子内容 -->
      <text v-if="cell.revealed && !cell.mine && cell.neighborMines > 0">
        {{ cell.neighborMines }}
      </text>
      <text v-else-if="cell.flagged">🚩</text>
      <text v-else-if="cell.revealed && cell.mine">💣</text>
    </view>
  </view>
</view>

三、关键技术与优化点

3.1 性能优化

  1. 延迟布置地雷:只在第一次点击后才布置地雷,确保第一次点击不会踩雷,提升用户体验

  2. 递归算法优化:在揭示空白区域时使用递归算法,但要注意边界条件,避免无限递归

3.2 交互设计

  1. 长按标记:使用@longpress事件实现标记功能,符合移动端操作习惯

  2. 视觉反馈:为不同类型的格子(普通、已揭示、标记、地雷、爆炸)设置不同的样式

3.3 状态管理

  1. 游戏状态:使用gameOvergameOverMessage管理游戏结束状态

  2. 计时器:使用setInterval实现游戏计时功能,注意在组件销毁时清除计时器

四、扩展思路

这个基础实现还可以进一步扩展:

  1. 本地存储:使用uni.setStorage保存最佳成绩

  2. 音效增强:添加点击、标记、爆炸等音效

  3. 动画效果:为格子添加翻转动画,增强视觉效果

  4. 自定义难度:允许玩家自定义棋盘大小和地雷数量

  5. 多平台适配:优化在不同平台(H5、小程序、App)上的显示效果

五、总结

通过本文的介绍,我们完整实现了一个基于Uniapp的扫雷游戏,涵盖了从数据结构设计、核心算法实现到用户交互处理的全部流程。这个项目不仅可以帮助理解Uniapp的开发模式,也是学习游戏逻辑开发的好例子。读者可以根据自己的需求进一步扩展和完善这个游戏。

完整代码

<template>
  <view class="minesweeper-container">
    <view class="game-header">
      <text class="title">扫雷游戏</text>
      <view class="game-info">
        <text>剩余: {{ remainingMines }}</text>
        <text>时间: {{ time }}</text>
      </view>
    </view>
    
    <view class="game-board">
      <view 
        v-for="(row, rowIndex) in board" 
        :key="rowIndex" 
        class="row"
      >
        <view 
          v-for="(cell, colIndex) in row" 
          :key="colIndex" 
          class="cell"
          :class="{
            'revealed': cell.revealed,
            'flagged': cell.flagged,
            'mine': cell.revealed && cell.mine,
            'exploded': cell.exploded
          }"
          @click="revealCell(rowIndex, colIndex)"
          @longpress="toggleFlag(rowIndex, colIndex)"
        >
          <text v-if="cell.revealed && !cell.mine && cell.neighborMines > 0">
            {{ cell.neighborMines }}
          </text>
          <text v-else-if="cell.flagged">🚩</text>
          <text v-else-if="cell.revealed && cell.mine">💣</text>
        </view>
      </view>
    </view>
    
    <view class="game-controls">
      <button @click="startGame(10, 10, 15)">初级 (10×10, 15雷)</button>
      <button @click="startGame(15, 15, 40)">中级 (15×15, 40雷)</button>
      <button @click="startGame(20, 20, 99)">高级 (20×20, 99雷)</button>
    </view>
    
    <view v-if="gameOver" class="game-over">
      <text>{{ gameOverMessage }}</text>
      <button @click="startGame(rows, cols, mines)">再玩一次</button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      rows: 10,
      cols: 10,
      mines: 15,
      board: [],
      remainingMines: 0,
      time: 0,
      timer: null,
      gameOver: false,
      gameOverMessage: '',
      firstClick: true
    }
  },
  created() {
    this.startGame(10, 10, 15);
  },
  methods: {
    startGame(rows, cols, mines) {
      this.rows = rows;
      this.cols = cols;
      this.mines = mines;
      this.remainingMines = mines;
      this.time = 0;
      this.gameOver = false;
      this.firstClick = true;
      
      clearInterval(this.timer);
      
      // 初始化棋盘
      this.board = Array(rows).fill().map(() => 
        Array(cols).fill().map(() => ({
          mine: false,
          revealed: false,
          flagged: false,
          neighborMines: 0,
          exploded: false
        }))
      );
    },
    
    placeMines(firstRow, firstCol) {
      let minesPlaced = 0;
      
      while (minesPlaced < this.mines) {
        const row = Math.floor(Math.random() * this.rows);
        const col = Math.floor(Math.random() * this.cols);
        
        // 确保第一次点击的位置和周围没有地雷
        if (
          !this.board[row][col].mine && 
          Math.abs(row - firstRow) > 1 && 
          Math.abs(col - firstCol) > 1
        ) {
          this.board[row][col].mine = true;
          minesPlaced++;
        }
      }
      
      // 计算每个格子周围的地雷数
      for (let row = 0; row < this.rows; row++) {
        for (let col = 0; col < this.cols; col++) {
          if (!this.board[row][col].mine) {
            let count = 0;
            for (let r = Math.max(0, row - 1); r <= Math.min(this.rows - 1, row + 1); r++) {
              for (let c = Math.max(0, col - 1); c <= Math.min(this.cols - 1, col + 1); c++) {
                if (this.board[r][c].mine) count++;
              }
            }
            this.board[row][col].neighborMines = count;
          }
        }
      }
    },
    
    revealCell(row, col) {
      if (this.gameOver || this.board[row][col].revealed || this.board[row][col].flagged) {
        return;
      }
      
      // 第一次点击时放置地雷并开始计时
      if (this.firstClick) {
        this.placeMines(row, col);
        this.startTimer();
        this.firstClick = false;
      }
      
      // 点击到地雷
      if (this.board[row][col].mine) {
        this.board[row][col].exploded = true;
        this.gameOver = true;
        this.gameOverMessage = '游戏结束!你踩到地雷了!';
        this.revealAllMines();
        clearInterval(this.timer);
        return;
      }
      
      // 递归揭示空白区域
      this.revealEmptyCells(row, col);
      
      // 检查是否获胜
      if (this.checkWin()) {
        this.gameOver = true;
        this.gameOverMessage = '恭喜你赢了!';
        clearInterval(this.timer);
      }
    },
    
    revealEmptyCells(row, col) {
      if (
        row < 0 || row >= this.rows || 
        col < 0 || col >= this.cols || 
        this.board[row][col].revealed || 
        this.board[row][col].flagged
      ) {
        return;
      }
      
      this.board[row][col].revealed = true;
      
      if (this.board[row][col].neighborMines === 0) {
        // 如果是空白格子,递归揭示周围的格子
        for (let r = Math.max(0, row - 1); r <= Math.min(this.rows - 1, row + 1); r++) {
          for (let c = Math.max(0, col - 1); c <= Math.min(this.cols - 1, col + 1); c++) {
            if (r !== row || c !== col) {
              this.revealEmptyCells(r, c);
            }
          }
        }
      }
    },
    
    toggleFlag(row, col) {
      if (this.gameOver || this.board[row][col].revealed) {
        return;
      }
      
      if (this.board[row][col].flagged) {
        this.board[row][col].flagged = false;
        this.remainingMines++;
      } else if (this.remainingMines > 0) {
        this.board[row][col].flagged = true;
        this.remainingMines--;
      }
    },
    
    startTimer() {
      clearInterval(this.timer);
      this.timer = setInterval(() => {
        this.time++;
      }, 1000);
    },
    
    revealAllMines() {
      for (let row = 0; row < this.rows; row++) {
        for (let col = 0; col < this.cols; col++) {
          if (this.board[row][col].mine) {
            this.board[row][col].revealed = true;
          }
        }
      }
    },
    
    checkWin() {
      for (let row = 0; row < this.rows; row++) {
        for (let col = 0; col < this.cols; col++) {
          if (!this.board[row][col].mine && !this.board[row][col].revealed) {
            return false;
          }
        }
      }
      return true;
    }
  },
  beforeDestroy() {
    clearInterval(this.timer);
  }
}
</script>

<style>
.minesweeper-container {
  padding: 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.game-header {
  margin-bottom: 20px;
  text-align: center;
}

.game-header .title {
  font-size: 24px;
  font-weight: bold;
  margin-bottom: 10px;
}

.game-info {
  display: flex;
  justify-content: space-around;
  width: 100%;
}

.game-board {
  border: 2px solid #333;
  margin-bottom: 20px;
}

.row {
  display: flex;
}

.cell {
  width: 30px;
  height: 30px;
  border: 1px solid #ccc;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #ddd;
  font-weight: bold;
}

.cell.revealed {
  background-color: #fff;
}

.cell.flagged {
  background-color: #ffeb3b;
}

.cell.mine {
  background-color: #f44336;
}

.cell.exploded {
  background-color: #d32f2f;
}

.game-controls {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  max-width: 300px;
}

.game-over {
  margin-top: 20px;
  text-align: center;
  font-size: 18px;
  font-weight: bold;
}

button {
  margin-top: 10px;
  padding: 10px;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

button:active {
  background-color: #3e8e41;
}
</style>

 


网站公告

今日签到

点亮在社区的每一天
去签到