中秋连连看小游戏开发完整教程

目录

最终效果


前言

中秋佳节,月圆人团圆。月饼、玉兔、明月、灯笼------这些传统元素构成了中秋节独特的文化符号。为了让更多人在数字时代也能感受到传统节日的魅力,我设计并实现了这款"中秋连连看"小游戏。

连连看作为一款经典的益智游戏,规则简单却不失趣味性,特别适合在节日期间与家人朋友共同娱乐。本项目将传统的连连看玩法与中秋元素相结合,通过纯前端技术(HTML + CSS + JavaScript)实现了一个功能完整、可实际游玩的小游戏。

游戏特色

  • 完全可玩:不是Demo,是真正可以玩的游戏
  • 中秋主题:月饼、玉兔等8种中秋元素图案
  • 多种难度:简单(8×8)、普通(10×10)、困难(12×12)三种模式
  • 智能提示:卡住时可使用提示功能
  • 重排功能:当无法继续时自动或手动重排
  • 计时系统:限时挑战增加紧张感
  • 关卡系统:无限关卡,挑战你的极限
  • 精美界面:渐变背景、动画效果、响应式设计

游戏介绍

基本规则:

  1. 点击两个相同的图案进行配对
  2. 两个图案之间的连接路径不能超过2个转折点
  3. 连接路径上不能有其他图案阻挡
  4. 成功配对后图案消除,获得分数
  5. 在规定时间内消除所有图案即可过关

得分规则:

  • 基础分:每对10分
  • 连击奖励:连续消除有额外加分(1.5倍递增)
  • 时间奖励:剩余时间×2作为额外奖励

道具系统:

  • 💡 提示:显示一对可消除的图案(3次)
  • 🔄 重排:重新打乱剩余图案位置(3次)

游戏元素

本游戏使用8种中秋主题的Emoji图案:

  • 🥮 月饼
  • 🐰 玉兔
  • 🌕 满月
  • 🏮 灯笼
  • 🌙 月牙
  • 🎋 竹子
  • 🌾 稻穗
  • 🍂 枫叶

技术架构

技术栈

  • HTML5:结构化标记语言
  • CSS3:样式、动画、渐变、响应式设计
  • JavaScript ES6+:游戏逻辑、算法实现
  • Canvas API:绘制连接线

项目结构

复制代码
zhongqiu/
├── lianliankan.html          # 主HTML文件
├── lianliankan.css           # 样式文件
├── lianliankan.js            # 游戏逻辑
└── 连连看游戏技术文章.md      # 技术文档

核心模块划分

复制代码
游戏系统
├── 游戏初始化模块
│   ├── 配置管理
│   ├── 状态初始化
│   └── 事件绑定
│
├── 棋盘管理模块
│   ├── 棋盘生成
│   ├── 图案分配
│   ├── 渲染更新
│   └── 重排功能
│
├── 交互处理模块
│   ├── 点击事件
│   ├── 选择逻辑
│   ├── 匹配判定
│   └── 消除动画
│
├── 算法核心模块
│   ├── 路径查找
│   ├── 直线判定
│   ├── 一次转折
│   ├── 两次转折
│   └── 有效移动检测
│
├── 游戏控制模块
│   ├── 计时系统
│   ├── 计分系统
│   ├── 关卡管理
│   ├── 暂停/继续
│   └── 结束判定
│
└── UI展示模块
    ├── 界面更新
    ├── 消息提示
    ├── 弹窗管理
    ├── 进度显示
    └── Canvas绘图

HTML结构设计

整体布局

HTML采用语义化标签,清晰地划分了游戏的各个功能区域:

html 复制代码
<body>
  <!-- 背景装饰层 -->
  <div class="bg-decoration">
    月亮、星星、灯笼等装饰元素
  </div>
  
  <!-- 主游戏容器 -->
  <div class="game-container">
    <!-- 标题区 -->
    <header class="game-header"></header>
    
    <!-- 信息栏 -->
    <div class="game-info"></div>
    
    <!-- 控制按钮 -->
    <div class="game-controls"></div>
    
    <!-- 游戏棋盘 -->
    <div class="game-board-container">
      <div class="game-board"></div>
      <canvas class="line-canvas"></canvas>
    </div>
    
    <!-- 进度条 -->
    <div class="progress-container"></div>
    
    <!-- 消息提示 -->
    <div class="game-message"></div>
  </div>
  
  <!-- 各种弹窗 -->
  <div class="modal" id="startModal"></div>
  <div class="modal" id="pauseModal"></div>
  <div class="modal" id="endModal"></div>
</body>

关键设计点

  1. 背景装饰层:使用固定定位,不影响游戏区域的交互
  2. 游戏棋盘:采用CSS Grid布局,灵活适应不同难度的网格大小
  3. Canvas画布:覆盖在棋盘上方,用于绘制连接线
  4. 弹窗系统:统一的modal样式,通过显隐控制不同场景

数据属性设计

每个棋子使用data-*属性存储坐标:

html 复制代码
<div class="tile" data-row="3" data-col="5">🥮</div>

这样便于在JavaScript中快速定位和操作。


CSS样式实现

1. 整体风格

采用现代化、扁平化的设计风格,配合中秋主题的配色方案:

主色调:

  • 背景:深紫色渐变(#1a237e → #4a148c)
  • 主要元素:白色半透明卡片(backdrop-filter毛玻璃效果)
  • 强调色:红色(#d32f2f)、金黄色(#ffd700)
css 复制代码
body {
    background: linear-gradient(135deg, 
        #1a237e 0%, 
        #311b92 50%, 
        #4a148c 100%);
}

.game-container {
    background: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(10px);
    border-radius: 20px;
}

2. 背景装饰动画

月亮呼吸效果:

css 复制代码
.moon-bg {
    animation: moonPulse 8s ease-in-out infinite;
}

@keyframes moonPulse {
    0%, 100% { 
        transform: scale(1); 
        opacity: 0.8; 
    }
    50% { 
        transform: scale(1.05); 
        opacity: 1; 
    }
}

星空移动效果:

使用径向渐变创建星星,配合background-position动画:

css 复制代码
.stars-bg {
    background-image: 
        radial-gradient(2px 2px at 20% 30%, white, transparent),
        radial-gradient(2px 2px at 60% 70%, white, transparent),
        /* ... 更多星星 */;
    animation: starsMove 120s linear infinite;
}

灯笼摆动效果:

css 复制代码
@keyframes lanternSwing {
    0%, 100% { transform: rotate(-5deg); }
    50% { transform: rotate(5deg); }
}

3. 棋盘与棋子样式

Grid布局:

css 复制代码
.game-board {
    display: grid;
    gap: 5px;
    /* 通过JavaScript动态设置行列数 */
}

棋子设计:

css 复制代码
.tile {
    width: 60px;
    height: 60px;
    background: white;
    border-radius: 10px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
    transition: all 0.3s ease;
}

/* 悬停效果 */
.tile:hover {
    transform: scale(1.05);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}

/* 选中状态 */
.tile.selected {
    background: linear-gradient(135deg, #ffd700, #ffed4e);
    transform: scale(1.1);
    box-shadow: 0 0 20px rgba(255, 215, 0, 0.6);
}

4. 消除动画

css 复制代码
@keyframes matchAnimation {
    0% { 
        transform: scale(1); 
        opacity: 1; 
    }
    50% { 
        transform: scale(1.3) rotate(10deg); 
        opacity: 0.8; 
    }
    100% { 
        transform: scale(0); 
        opacity: 0; 
    }
}

这个动画分三个阶段:

  1. 保持原状
  2. 放大并旋转
  3. 缩小消失

5. 提示闪烁效果

css 复制代码
@keyframes hintBlink {
    0%, 100% { 
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); 
    }
    50% { 
        box-shadow: 0 0 25px rgba(76, 175, 80, 0.8);
        transform: scale(1.1);
    }
}

6. 按钮渐变与悬停效果

css 复制代码
.btn-primary {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    transition: all 0.3s ease;
}

.btn-primary:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 15px rgba(0, 0, 0, 0.3);
}

7. 响应式设计

css 复制代码
@media (max-width: 768px) {
    .tile {
        width: 45px;
        height: 45px;
        font-size: 1.5rem;
    }
}

@media (max-width: 480px) {
    .tile {
        width: 35px;
        height: 35px;
        font-size: 1.2rem;
    }
}

JavaScript核心逻辑

1. 游戏配置与状态管理

配置对象:

javascript 复制代码
const GAME_CONFIG = {
    tiles: ['🥮', '🐰', '🌕', '🏮', '🌙', '🎋', '🌾', '🍂'],
    easy: { rows: 8, cols: 8, time: 240 },
    normal: { rows: 10, cols: 10, time: 180 },
    hard: { rows: 12, cols: 12, time: 120 },
    pointsPerMatch: 10,
    comboMultiplier: 1.5
};

状态对象:

javascript 复制代码
let gameState = {
    board: [],              // 二维数组存储棋盘
    rows: 0,                // 行数
    cols: 0,                // 列数
    level: 1,               // 当前关卡
    score: 0,               // 分数
    time: 180,              // 剩余时间
    hints: 3,               // 提示次数
    shuffles: 3,            // 重排次数
    selectedTile: null,     // 当前选中的棋子
    isPlaying: false,       // 是否正在游戏
    isPaused: false,        // 是否暂停
    timer: null,            // 计时器
    combo: 0,               // 连击数
    totalPairs: 0,          // 总对数
    matchedPairs: 0,        // 已匹配对数
    difficulty: 'normal'    // 难度
};

2. 棋盘初始化

生成算法:

javascript 复制代码
function initBoard() {
    const { rows, cols } = gameState;
    const totalCells = rows * cols;
    gameState.totalPairs = totalCells / 2;
    
    // 1. 创建图案对
    const tiles = [];
    const tilesPerType = totalCells / GAME_CONFIG.tiles.length;
    
    GAME_CONFIG.tiles.forEach(tile => {
        for (let i = 0; i < tilesPerType / 2; i++) {
            tiles.push(tile, tile);  // 成对添加
        }
    });
    
    // 2. 打乱图案
    shuffleArray(tiles);
    
    // 3. 填充二维数组
    gameState.board = [];
    for (let i = 0; i < rows; i++) {
        gameState.board[i] = [];
        for (let j = 0; j < cols; j++) {
            gameState.board[i][j] = tiles[i * cols + j];
        }
    }
    
    renderBoard();
}

关键点:

  • 确保每种图案有偶数个(成对)
  • 使用Fisher-Yates算法打乱数组
  • 按行列顺序填充二维数组

3. 渲染棋盘

javascript 复制代码
function renderBoard() {
    const { rows, cols, board } = gameState;
    
    // 设置Grid布局
    elements.gameBoard.style.gridTemplateRows = `repeat(${rows}, 1fr)`;
    elements.gameBoard.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
    
    // 清空并重新创建
    elements.gameBoard.innerHTML = '';
    
    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            const tile = document.createElement('div');
            tile.className = 'tile';
            tile.textContent = board[i][j];
            tile.dataset.row = i;
            tile.dataset.col = j;
            
            tile.addEventListener('click', () => handleTileClick(i, j));
            
            elements.gameBoard.appendChild(tile);
        }
    }
    
    // 设置Canvas大小
    const boardRect = elements.gameBoard.getBoundingClientRect();
    elements.lineCanvas.width = boardRect.width;
    elements.lineCanvas.height = boardRect.height;
}

4. 点击处理流程

javascript 复制代码
function handleTileClick(row, col) {
    // 1. 游戏状态检查
    if (!gameState.isPlaying || gameState.isPaused) return;
    
    const clickedTile = getTileElement(row, col);
    if (!clickedTile || clickedTile.classList.contains('empty')) return;
    
    // 2. 点击同一个棋子,取消选择
    if (gameState.selectedTile && 
        gameState.selectedTile.row === row && 
        gameState.selectedTile.col === col) {
        deselectTile();
        return;
    }
    
    // 3. 第一次点击,选中棋子
    if (!gameState.selectedTile) {
        selectTile(row, col);
        return;
    }
    
    // 4. 第二次点击,尝试匹配
    const firstTile = gameState.selectedTile;
    
    // 检查图案是否相同
    if (gameState.board[firstTile.row][firstTile.col] !== 
        gameState.board[row][col]) {
        deselectTile();
        selectTile(row, col);
        showMessage('图案不匹配', 'error');
        return;
    }
    
    // 检查是否可连接
    const path = findPath(firstTile.row, firstTile.col, row, col);
    
    if (path) {
        matchTiles(firstTile.row, firstTile.col, row, col, path);
    } else {
        deselectTile();
        selectTile(row, col);
        showMessage('无法连接', 'error');
    }
}

流程图:

复制代码
点击棋子
    ↓
检查游戏状态 → 暂停/结束 → 返回
    ↓
检查是否为空 → 是 → 返回
    ↓
已选同一个 → 是 → 取消选择 → 返回
    ↓
第一次选择 → 是 → 标记选中 → 返回
    ↓
第二次选择
    ↓
图案相同? → 否 → 重新选择
    ↓ 是
可以连接? → 否 → 显示提示 + 重新选择
    ↓ 是
执行消除 → 更新状态 → 检查胜利条件

连连看算法详解

这是游戏的核心,也是最复杂的部分。连连看的规则是:两个相同图案之间的连接路径最多只能有2个转折点

算法分类

  1. 直线连接(0个转折)
  2. 一次转折连接(1个转折)
  3. 两次转折连接(2个转折)

1. 直线连接

两点在同一行或同一列,且之间无障碍物。

javascript 复制代码
function canConnectDirect(row1, col1, row2, col2) {
    // 同一行
    if (row1 === row2) {
        const minCol = Math.min(col1, col2);
        const maxCol = Math.max(col1, col2);
        for (let col = minCol + 1; col < maxCol; col++) {
            if (gameState.board[row1][col] !== null) {
                return false;  // 有障碍
            }
        }
        return true;
    }
    
    // 同一列
    if (col1 === col2) {
        const minRow = Math.min(row1, row2);
        const maxRow = Math.max(row1, row2);
        for (let row = minRow + 1; row < maxRow; row++) {
            if (gameState.board[row][col1] !== null) {
                return false;  // 有障碍
            }
        }
        return true;
    }
    
    return false;
}

示意图:

复制代码
横向直线:
🥮 . . . 🥮   ✓ 可连接

🥮 . 🐰 . 🥮   ✗ 中间有障碍

纵向直线:
🥮
.
.
🥮   ✓ 可连接

2. 一次转折连接

经过一个转折点连接两点。转折点有两种可能:

  • (row1, col2):先横后竖
  • (row2, col1):先竖后横
javascript 复制代码
function canConnectWithOneTurn(row1, col1, row2, col2) {
    // 转折点1: (row1, col2)
    if (isEmpty(row1, col2) || (row1 === row2 && col1 === col2)) {
        if (canConnectDirect(row1, col1, row1, col2) && 
            canConnectDirect(row1, col2, row2, col2)) {
            return [[row1, col1], [row1, col2], [row2, col2]];
        }
    }
    
    // 转折点2: (row2, col1)
    if (isEmpty(row2, col1) || (row1 === row2 && col1 === col2)) {
        if (canConnectDirect(row1, col1, row2, col1) && 
            canConnectDirect(row2, col1, row2, col2)) {
            return [[row1, col1], [row2, col1], [row2, col2]];
        }
    }
    
    return null;
}

示意图:

复制代码
转折点 (row1, col2):
🥮 → → ↓
      ↓
      🥮

转折点 (row2, col1):
🥮
↓
↓
→ → → 🥮

3. 两次转折连接

需要经过两个转折点。策略:

  • 尝试在每一行寻找可行的中转点
  • 尝试在每一列寻找可行的中转点
javascript 复制代码
function canConnectWithTwoTurns(row1, col1, row2, col2) {
    const { rows, cols } = gameState;
    
    // 通过行扩展 (寻找横向中转线)
    for (let col = 0; col < cols; col++) {
        if (col === col1 || col === col2) continue;
        
        if ((isEmpty(row1, col) || col === col2) && 
            (isEmpty(row2, col) || col === col1)) {
            
            if (canConnectDirect(row1, col1, row1, col) &&
                canConnectDirect(row1, col, row2, col) &&
                canConnectDirect(row2, col, row2, col2)) {
                return [[row1, col1], [row1, col], [row2, col], [row2, col2]];
            }
        }
    }
    
    // 通过列扩展 (寻找纵向中转线)
    for (let row = 0; row < rows; row++) {
        if (row === row1 || row === row2) continue;
        
        if ((isEmpty(row, col1) || row === row2) && 
            (isEmpty(row, col2) || row === row1)) {
            
            if (canConnectDirect(row1, col1, row, col1) &&
                canConnectDirect(row, col1, row, col2) &&
                canConnectDirect(row, col2, row2, col2)) {
                return [[row1, col1], [row, col1], [row, col2], [row2, col2]];
            }
        }
    }
    
    return null;
}

示意图:

复制代码
横向中转:
🥮 → → → col
         ↓
         ↓
      🥮 ← col

纵向中转:
🥮     
↓      
↓      
row → → → 🥮

路径查找总控函数

javascript 复制代码
function findPath(row1, col1, row2, col2) {
    // 1. 尝试直线连接
    if (canConnectDirect(row1, col1, row2, col2)) {
        return [[row1, col1], [row2, col2]];
    }
    
    // 2. 尝试一次转折
    const oneTurn = canConnectWithOneTurn(row1, col1, row2, col2);
    if (oneTurn) return oneTurn;
    
    // 3. 尝试两次转折
    const twoTurns = canConnectWithTwoTurns(row1, col1, row2, col2);
    if (twoTurns) return twoTurns;
    
    // 4. 无法连接
    return null;
}

算法复杂度分析

  • 直线连接:O(n),n为行数或列数
  • 一次转折:O(n),最多检查2个转折点
  • 两次转折:O(n²),需要遍历所有可能的中转线
  • 总体:O(n²),可接受的时间复杂度

游戏状态管理

计时系统

javascript 复制代码
function startTimer() {
    if (gameState.timer) clearInterval(gameState.timer);
    
    gameState.timer = setInterval(() => {
        if (gameState.isPaused) return;
        
        gameState.time--;
        updateUI();
        
        // 时间警告
        if (gameState.time === 30) {
            showMessage('⚠️ 只剩30秒了!', 'error');
        }
        
        // 时间到
        if (gameState.time <= 0) {
            loseGame();
        }
    }, 1000);
}

设计考虑:

  • 暂停时不计时(通过检查isPaused标志)
  • 最后30秒警告提醒
  • 时间归零触发失败

计分系统

基础分数:

javascript 复制代码
const basePoints = GAME_CONFIG.pointsPerMatch;  // 10分

连击加成:

javascript 复制代码
gameState.combo++;  // 每次成功匹配增加连击数

let points = basePoints;
if (gameState.combo > 1) {
    // 1.5倍递增:10, 15, 22, 33, 49, ...
    points = Math.floor(
        basePoints * Math.pow(GAME_CONFIG.comboMultiplier, gameState.combo - 1)
    );
}

时间奖励:

javascript 复制代码
const timeBonus = gameState.time * 2;  // 剩余每秒2分
gameState.score += timeBonus;

匹配逻辑

javascript 复制代码
function matchTiles(row1, col1, row2, col2, path) {
    // 1. 绘制连接线
    drawPath(path);
    
    // 2. 增加连击
    gameState.combo++;
    
    // 3. 计算分数
    let points = GAME_CONFIG.pointsPerMatch;
    if (gameState.combo > 1) {
        points = Math.floor(
            points * Math.pow(GAME_CONFIG.comboMultiplier, gameState.combo - 1)
        );
        showMessage(`连击 x${gameState.combo}! +${points}分`, 'combo');
    } else {
        showMessage(`匹配成功! +${points}分`, 'success');
    }
    
    gameState.score += points;
    gameState.matchedPairs++;
    
    // 4. 延迟后移除棋子(显示动画)
    setTimeout(() => {
        removeTiles(row1, col1, row2, col2);
        updateUI();
        
        // 5. 检查胜利/失败条件
        checkGameEnd();
    }, 500);
}

胜利与失败判定

javascript 复制代码
function checkGameEnd() {
    // 全部消除 → 胜利
    if (gameState.matchedPairs >= gameState.totalPairs) {
        setTimeout(() => winGame(), 500);
        return;
    }
    
    // 无有效移动 → 自动重排
    if (!hasValidMoves()) {
        showMessage('没有可消除的图案了!自动重排...', 'error');
        setTimeout(() => shuffleBoard(), 1500);
    }
}

// 时间到 → 失败
if (gameState.time <= 0) {
    loseGame();
}

UI交互设计

Canvas绘制连接线

javascript 复制代码
function drawPath(path) {
    const canvas = elements.lineCanvas;
    const ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    const tileSize = 60 + 5;  // 棋子大小 + 间距
    const offset = tileSize / 2 + 20;  // 棋盘padding
    
    ctx.strokeStyle = '#4caf50';  // 绿色
    ctx.lineWidth = 3;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    
    ctx.beginPath();
    
    // 连接路径上的所有点
    for (let i = 0; i < path.length; i++) {
        const [row, col] = path[i];
        const x = col * tileSize + offset;
        const y = row * tileSize + offset;
        
        if (i === 0) {
            ctx.moveTo(x, y);
        } else {
            ctx.lineTo(x, y);
        }
    }
    
    ctx.stroke();
    
    // 500ms后清除
    setTimeout(() => clearCanvas(), 500);
}

提示功能

javascript 复制代码
function useHint() {
    if (gameState.hints <= 0) {
        showMessage('没有提示次数了!', 'error');
        return;
    }
    
    // 查找一对可消除的图案
    const validMove = findValidMove();
    
    if (validMove) {
        gameState.hints--;
        const [row1, col1, row2, col2] = validMove;
        
        // 添加闪烁效果
        const tile1 = getTileElement(row1, col1);
        const tile2 = getTileElement(row2, col2);
        
        if (tile1) tile1.classList.add('hint');
        if (tile2) tile2.classList.add('hint');
        
        // 3秒后移除效果
        setTimeout(() => {
            if (tile1) tile1.classList.remove('hint');
            if (tile2) tile2.classList.remove('hint');
        }, 3000);
        
        updateUI();
        showMessage('💡 已显示提示', 'success');
    } else {
        showMessage('没有可消除的图案!', 'error');
    }
}

查找有效移动:

javascript 复制代码
function findValidMove() {
    const { rows, cols, board } = gameState;
    
    // 双重循环遍历所有可能的配对
    for (let i1 = 0; i1 < rows; i1++) {
        for (let j1 = 0; j1 < cols; j1++) {
            if (board[i1][j1] === null) continue;
            
            for (let i2 = 0; i2 < rows; i2++) {
                for (let j2 = 0; j2 < cols; j2++) {
                    if (board[i2][j2] === null) continue;
                    if (i1 === i2 && j1 === j2) continue;
                    
                    // 图案相同且可连接
                    if (board[i1][j1] === board[i2][j2]) {
                        if (findPath(i1, j1, i2, j2)) {
                            return [i1, j1, i2, j2];
                        }
                    }
                }
            }
        }
    }
    
    return null;
}

重排功能

javascript 复制代码
function shuffleBoard() {
    // 1. 收集所有非空棋子
    const tiles = [];
    const { rows, cols, board } = gameState;
    
    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            if (board[i][j] !== null) {
                tiles.push(board[i][j]);
            }
        }
    }
    
    // 2. 打乱
    shuffleArray(tiles);
    
    // 3. 重新分配到非空位置
    let index = 0;
    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            if (board[i][j] !== null) {
                board[i][j] = tiles[index++];
            }
        }
    }
    
    // 4. 重新渲染
    renderBoard();
    updateUI();
    showMessage('🔄 棋盘已重排!', 'success');
}

弹窗管理

javascript 复制代码
function showModal(modalId) {
    document.getElementById(modalId).classList.remove('hidden');
}

function hideModal(modalId) {
    document.getElementById(modalId).classList.add('hidden');
}

三种弹窗:

  1. 开始弹窗:游戏介绍、难度选择
  2. 暂停弹窗:暂停时显示
  3. 结束弹窗:胜利/失败统计

消息提示系统

javascript 复制代码
function showMessage(message, type = '') {
    elements.gameMessage.textContent = message;
    elements.gameMessage.className = 'game-message ' + type;
    
    // 2秒后自动清除
    setTimeout(() => {
        elements.gameMessage.textContent = '';
        elements.gameMessage.className = 'game-message';
    }, 2000);
}

消息类型:

  • success:绿色,成功操作
  • error:红色,错误提示
  • combo:橙色,连击提示

最终实现场景

游戏启动流程

  1. 打开页面

    • 显示精美的背景(月亮、星星、灯笼)
    • 弹出开始弹窗
  2. 选择难度

    • 简单:8×8,240秒
    • 普通:10×10,180秒
    • 困难:12×12,120秒
  3. 点击开始

    • 生成随机棋盘
    • 启动倒计时
    • 按钮变为可用状态

游戏进行中

正常游玩:

复制代码
[查看棋盘] → [点击第一个棋子] → [高亮显示]
    ↓
[点击第二个棋子]
    ↓
[判断是否匹配]
    ├─ 匹配且可连接 → [绘制路径] → [播放动画] → [消除] → [加分]
    └─ 不匹配/无法连接 → [提示错误] → [重新选择]

使用道具:

  • 点击"提示"按钮 → 两个可消除的棋子闪烁
  • 点击"重排"按钮 → 剩余棋子重新打乱
  • 点击"暂停"按钮 → 弹出暂停窗口,计时暂停

连击系统:

复制代码
第1对:+10分
第2对:+15分(连击x2)
第3对:+22分(连击x3)
第4对:+33分(连击x4)
...

游戏结束

胜利条件:

  • 在规定时间内消除所有棋子

胜利界面:

复制代码
🎉 恭喜过关!

最终得分:1250
剩余时间:45秒
时间奖励:+90分
当前关卡:3

[下一关] [返回主菜单]

失败条件:

  • 时间归零

失败界面:

复制代码
😢 时间到!

最终得分:680
已消除:28/50对
完成度:56%

[重新挑战] [返回主菜单]

关卡递进

每通过一关:

  • 关卡数 +1
  • 保持之前的总分
  • 时间重置
  • 道具次数重置

实际游玩体验

第1关(普通难度):

  • 10×10的棋盘,100个棋子(50对)
  • 8种图案,每种12-13个
  • 180秒倒计时
  • 新手较容易,图案分布均匀

第5关:

  • 同样10×10,但因为熟练度提升,玩得更快
  • 连击次数增多,高分段出现
  • 策略性增强(先消边缘还是中间?)

高难度挑战:

  • 12×12的棋盘,144个棋子(72对)
  • 只有120秒
  • 极度考验观察力和反应速度
  • 提示和重排要谨慎使用

技术总结

关键技术点

  1. 数据结构

    • 二维数组存储棋盘状态
    • 对象管理游戏状态
    • 数组存储路径信息
  2. 算法实现

    • Fisher-Yates洗牌算法
    • 路径搜索算法(DFS思想)
    • 有效移动检测
  3. DOM操作

    • 动态创建元素
    • Grid布局应用
    • 事件监听与处理
  4. Canvas绘图

    • 2D上下文操作
    • 路径绘制
    • 动画清除
  5. CSS动画

    • Keyframes定义
    • Transform变换
    • Transition过渡
  6. 游戏设计

    • 状态机管理
    • 计时系统
    • 计分机制
    • UI反馈

使用说明

快速开始

  1. 下载文件

    bash 复制代码
    # 确保以下三个文件在同一目录
    - lianliankan.html
    - lianliankan.css
    - lianliankan.js
  2. 打开游戏

    • 直接在浏览器中打开 lianliankan.html

    • 或使用本地服务器:

      bash 复制代码
      python -m http.server 8000
      # 访问 http://localhost:8000/lianliankan.html
  3. 开始游玩

    • 阅读游戏说明
    • 选择难度
    • 点击"开始游戏"

操作指南

鼠标操作:

  • 点击棋子选择
  • 再次点击可消除的图案配对
  • 点击控制按钮使用功能

按钮说明:

  • 🎮 开始游戏:开始新游戏
  • ⏸️ 暂停:暂停当前游戏
  • 💡 提示:显示一对可消除的图案
  • 🔄 重排:重新打乱剩余图案
  • 🔄 重新开始:返回主菜单

结语

用代码绘制节日,用技术传承文化。愿每一位开发者都能在创作中感受到编程的乐趣,在分享中传递技术的温度。

最好祝大家中秋快乐!Happy Mid-Autumn Festival! 🌕✨


参考资源


相关推荐
itslife3 小时前
vite 源码 - 创建服务
前端·javascript
我的写法有点潮5 小时前
彻底理解 JavaScript 的深浅拷贝
前端·javascript·vue.js
Never_Satisfied6 小时前
在JavaScript / HTML中,转移字符导致js生成的html出错
开发语言·javascript·html
知识分享小能手6 小时前
微信小程序入门学习教程,从入门到精通,WXS语法详解(10)
前端·javascript·学习·微信小程序·小程序·vue·团队开发
csgo打的菜又爱玩11 小时前
Vue 基础(实战模板与命名指南)
前端·javascript·vue.js
gerrgwg13 小时前
Vue-library-start,一个基于Vite的vue组件库开发模板
前端·javascript·vue.js
开心不就得了15 小时前
自定义脚手架
前端·javascript
没事多睡觉66617 小时前
Vue 虚拟列表实现方案详解:三种方法的完整对比与实践
前端·javascript·vue.js
excel17 小时前
Vue3 EffectScope 源码解析与理解
前端·javascript·面试