Flutter 框架跨平台鸿蒙开发 - 打造经典连连看游戏

Flutter实战:打造经典连连看游戏

前言

连连看是一款经典的益智消除游戏,考验玩家的观察力和反应速度。本文将带你从零开始,使用Flutter开发一个功能完整的连连看游戏,包含路径查找、计时挑战、提示和重排等功能。

应用特色

  • 🎮 经典玩法:完整还原经典连连看规则
  • 🔗 路径算法:直线、一转角、两转角连接
  • ⏱️ 计时挑战:限时完成,增加紧张感
  • 💡 提示功能:3次提示机会
  • 🔄 重排功能:2次重排机会
  • 🎯 三种难度:简单、中等、困难
  • 📊 得分系统:基础分+时间奖励+路径奖励
  • 🏆 最高分:本地保存最高分记录
  • ⏸️ 暂停功能:随时暂停/继续游戏
  • 🎨 18种图案:丰富的图标和颜色

效果展示




连连看
游戏规则
选择两个相同图案
路径不超过2个转角
路径上无障碍物
消除所有方块
连接类型
直线连接 0转角
一转角连接 1转角
两转角连接 2转角
难度设置
简单 6×8 180秒
中等 8×10 240秒
困难 10×12 300秒
得分规则
基础分 100
时间奖励 剩余秒数
路径奖励 转角越少越高

数据模型设计

1. 方块模型

dart 复制代码
class Tile {
  int row;
  int col;
  int type;        // 图案类型
  bool isMatched = false;

  Tile({
    required this.row,
    required this.col,
    required this.type,
  });
}

2. 路径点

dart 复制代码
class PathPoint {
  int row;
  int col;

  PathPoint(this.row, this.col);
}

3. 难度等级

dart 复制代码
enum Difficulty {
  easy(6, 8, 180, '简单'),
  medium(8, 10, 240, '中等'),
  hard(10, 12, 300, '困难');

  final int rows;
  final int cols;
  final int timeLimit;
  final String label;

  const Difficulty(this.rows, this.cols, this.timeLimit, this.label);
}

4. 游戏状态

dart 复制代码
enum GameState {
  ready,     // 准备中
  playing,   // 游戏中
  paused,    // 暂停
  won,       // 胜利
  lost,      // 失败
}

核心算法实现

1. 路径查找算法

连连看的核心是路径查找,需要判断两个方块是否可以连接:

dart 复制代码
List<PathPoint>? _findPath(Tile start, Tile end) {
  // 直线连接(0个转角)
  if (_canConnectStraight(start, end)) {
    return [
      PathPoint(start.row, start.col),
      PathPoint(end.row, end.col),
    ];
  }

  // 一个转角
  final path1 = _canConnectOneCorner(start, end);
  if (path1 != null) return path1;

  // 两个转角
  final path2 = _canConnectTwoCorners(start, end);
  if (path2 != null) return path2;

  return null;
}

2. 直线连接检测

dart 复制代码
bool _canConnectStraight(Tile start, Tile end) {
  if (start.row == end.row) {
    // 同一行
    final minCol = min(start.col, end.col);
    final maxCol = max(start.col, end.col);
    for (int col = minCol + 1; col < maxCol; col++) {
      if (_board[start.row][col] != null && 
          !_board[start.row][col]!.isMatched) {
        return false;
      }
    }
    return true;
  } else if (start.col == end.col) {
    // 同一列
    final minRow = min(start.row, end.row);
    final maxRow = max(start.row, end.row);
    for (int row = minRow + 1; row < maxRow; row++) {
      if (_board[row][start.col] != null && 
          !_board[row][start.col]!.isMatched) {
        return false;
      }
    }
    return true;
  }
  return false;
}

算法说明

  • 检查两个方块是否在同一行或同一列
  • 遍历中间的所有格子,确保没有障碍物

3. 一转角连接检测

dart 复制代码
List<PathPoint>? _canConnectOneCorner(Tile start, Tile end) {
  // 尝试转角点1: (start.row, end.col)
  final corner1Row = start.row;
  final corner1Col = end.col;
  if (_isEmptyOrMatched(corner1Row, corner1Col)) {
    final corner1 = Tile(row: corner1Row, col: corner1Col, type: 0);
    if (_canConnectStraight(start, corner1) && 
        _canConnectStraight(corner1, end)) {
      return [
        PathPoint(start.row, start.col),
        PathPoint(corner1Row, corner1Col),
        PathPoint(end.row, end.col),
      ];
    }
  }

  // 尝试转角点2: (end.row, start.col)
  final corner2Row = end.row;
  final corner2Col = start.col;
  if (_isEmptyOrMatched(corner2Row, corner2Col)) {
    final corner2 = Tile(row: corner2Row, col: corner2Col, type: 0);
    if (_canConnectStraight(start, corner2) && 
        _canConnectStraight(corner2, end)) {
      return [
        PathPoint(start.row, start.col),
        PathPoint(corner2Row, corner2Col),
        PathPoint(end.row, end.col),
      ];
    }
  }

  return null;
}

算法图解

复制代码
一转角连接示例:

转角点1:              转角点2:
A ----→ C            A
        ↓            ↓
        B            C ----→ B

A: 起点 (start.row, start.col)
B: 终点 (end.row, end.col)
C: 转角点

4. 两转角连接检测

dart 复制代码
List<PathPoint>? _canConnectTwoCorners(Tile start, Tile end) {
  // 尝试水平延伸
  for (int col = 0; col < _difficulty.cols; col++) {
    if (col == start.col || col == end.col) continue;
    if (_isEmptyOrMatched(start.row, col) && 
        _isEmptyOrMatched(end.row, col)) {
      final corner1 = Tile(row: start.row, col: col, type: 0);
      final corner2 = Tile(row: end.row, col: col, type: 0);
      if (_canConnectStraight(start, corner1) &&
          _canConnectStraight(corner1, corner2) &&
          _canConnectStraight(corner2, end)) {
        return [
          PathPoint(start.row, start.col),
          PathPoint(start.row, col),
          PathPoint(end.row, col),
          PathPoint(end.row, end.col),
        ];
      }
    }
  }

  // 尝试垂直延伸
  for (int row = 0; row < _difficulty.rows; row++) {
    if (row == start.row || row == end.row) continue;
    if (_isEmptyOrMatched(row, start.col) && 
        _isEmptyOrMatched(row, end.col)) {
      final corner1 = Tile(row: row, col: start.col, type: 0);
      final corner2 = Tile(row: row, col: end.col, type: 0);
      if (_canConnectStraight(start, corner1) &&
          _canConnectStraight(corner1, corner2) &&
          _canConnectStraight(corner2, end)) {
        return [
          PathPoint(start.row, start.col),
          PathPoint(row, start.col),
          PathPoint(row, end.col),
          PathPoint(end.row, end.col),
        ];
      }
    }
  }

  return null;
}

算法图解

复制代码
两转角连接示例:

水平延伸:            垂直延伸:
A ----→ C1          A
        ↓           ↓
        C2 ----→ B  C1 ----→ C2
                            ↓
                            B

5. 转角数计算

dart 复制代码
int _getTurns(List<PathPoint> path) {
  if (path.length <= 2) return 0;

  int turns = 0;
  for (int i = 1; i < path.length - 1; i++) {
    final prev = path[i - 1];
    final curr = path[i];
    final next = path[i + 1];

    final dir1 = _getDirection(prev, curr);
    final dir2 = _getDirection(curr, next);

    if (dir1 != dir2) {
      turns++;
    }
  }
  return turns;
}

String _getDirection(PathPoint from, PathPoint to) {
  if (from.row == to.row) return 'horizontal';
  if (from.col == to.col) return 'vertical';
  return 'none';
}

得分系统

得分公式

dart 复制代码
final baseScore = 100;                    // 基础分
final timeBonus = _timeRemaining;         // 时间奖励
final pathBonus = (4 - _getTurns(path)) * 50;  // 路径奖励
_score += baseScore + timeBonus + pathBonus;

得分详解

项目 计算方式 说明
基础分 100 每次消除固定得分
时间奖励 剩余秒数 剩余时间越多奖励越高
路径奖励 (4 - 转角数) × 50 转角越少奖励越高

路径奖励示例

  • 直线连接(0转角):200分
  • 一转角连接(1转角):150分
  • 两转角连接(2转角):100分

游戏功能实现

1. 提示功能

dart 复制代码
void _useHint() {
  if (_hintsRemaining <= 0) return;

  // 查找可以连接的一对方块
  for (int i = 0; i < _difficulty.rows; i++) {
    for (int j = 0; j < _difficulty.cols; j++) {
      final tile1 = _board[i][j];
      if (tile1 == null || tile1.isMatched) continue;

      for (int m = 0; m < _difficulty.rows; m++) {
        for (int n = 0; n < _difficulty.cols; n++) {
          if (i == m && j == n) continue;
          final tile2 = _board[m][n];
          if (tile2 == null || tile2.isMatched) continue;

          if (tile1.type == tile2.type) {
            final path = _findPath(tile1, tile2);
            if (path != null) {
              setState(() {
                _selectedTile = tile1;
                _currentPath = path;
                _hintsRemaining--;
              });

              // 3秒后清除提示
              Future.delayed(const Duration(seconds: 3), () {
                if (_selectedTile == tile1) {
                  setState(() {
                    _selectedTile = null;
                    _currentPath = [];
                  });
                }
              });
              return;
            }
          }
        }
      }
    }
  }
}

2. 重排功能

dart 复制代码
void _shuffle() {
  if (_shufflesRemaining <= 0) return;

  setState(() {
    // 收集所有未匹配的方块类型
    List<int> types = [];
    for (var row in _board) {
      for (var tile in row) {
        if (tile != null && !tile.isMatched) {
          types.add(tile.type);
        }
      }
    }

    // 打乱
    types.shuffle();

    // 重新分配
    int index = 0;
    for (var row in _board) {
      for (var tile in row) {
        if (tile != null && !tile.isMatched) {
          tile.type = types[index++];
        }
      }
    }

    _shufflesRemaining--;
    _selectedTile = null;
    _currentPath = [];
  });
}

3. 棋盘初始化

dart 复制代码
void _initBoard() {
  final rows = _difficulty.rows;
  final cols = _difficulty.cols;
  final totalTiles = rows * cols;
  final pairCount = totalTiles ~/ 2;

  // 生成配对的图案
  List<int> types = [];
  for (int i = 0; i < pairCount; i++) {
    final type = i % _icons.length;
    types.add(type);
    types.add(type);  // 每种图案两个
  }

  // 打乱
  types.shuffle();

  // 创建棋盘
  _board = List.generate(
    rows,
    (i) => List.generate(
      cols,
      (j) => Tile(
        row: i,
        col: j,
        type: types[i * cols + j],
      ),
    ),
  );
}

UI组件设计

1. 方块渲染

dart 复制代码
Widget _buildTile(int row, int col) {
  final tile = _board[row][col];
  if (tile == null) return const SizedBox.shrink();

  final isSelected = _selectedTile == tile;
  final isInPath = _currentPath.any((p) => p.row == row && p.col == col);

  return GestureDetector(
    onTap: () => _onTileTap(tile),
    child: AnimatedOpacity(
      opacity: tile.isMatched ? 0.0 : 1.0,
      duration: const Duration(milliseconds: 300),
      child: Container(
        width: 60,
        height: 60,
        margin: const EdgeInsets.all(2),
        decoration: BoxDecoration(
          color: isSelected || isInPath
              ? Colors.yellow.withOpacity(0.5)
              : _colors[tile.type % _colors.length].withOpacity(0.2),
          borderRadius: BorderRadius.circular(8),
          border: Border.all(
            color: isSelected
                ? Colors.yellow
                : _colors[tile.type % _colors.length],
            width: isSelected ? 3 : 2,
          ),
          boxShadow: isSelected
              ? [
                  BoxShadow(
                    color: Colors.yellow.withOpacity(0.5),
                    blurRadius: 10,
                    spreadRadius: 2,
                  ),
                ]
              : null,
        ),
        child: Icon(
          _icons[tile.type % _icons.length],
          color: _colors[tile.type % _colors.length],
          size: 32,
        ),
      ),
    ),
  );
}

2. 路径绘制

使用CustomPainter绘制连接路径:

dart 复制代码
class PathPainter extends CustomPainter {
  final List<PathPoint> path;

  PathPainter(this.path);

  @override
  void paint(Canvas canvas, Size size) {
    if (path.length < 2) return;

    final paint = Paint()
      ..color = Colors.yellow
      ..strokeWidth = 4
      ..style = PaintingStyle.stroke;

    for (int i = 0; i < path.length - 1; i++) {
      final start = path[i];
      final end = path[i + 1];

      final startOffset = Offset(
        start.col * 64 + 32,
        start.row * 64 + 32,
      );
      final endOffset = Offset(
        end.col * 64 + 32,
        end.row * 64 + 32,
      );

      canvas.drawLine(startOffset, endOffset, paint);
    }
  }

  @override
  bool shouldRepaint(PathPainter oldDelegate) {
    return oldDelegate.path != path;
  }
}

3. 状态栏

dart 复制代码
Widget _buildStatusBar() {
  return Container(
    padding: const EdgeInsets.all(16),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: [
        _buildStatusItem(
          Icons.timer,
          '$_timeRemaining秒',
          _timeRemaining <= 30 ? Colors.red : Colors.blue,
        ),
        _buildStatusItem(
          Icons.star,
          '$_score',
          Colors.amber,
        ),
        _buildStatusItem(
          Icons.emoji_events,
          '$_bestScore',
          Colors.green,
        ),
      ],
    ),
  );
}

技术要点详解

1. AnimatedOpacity动画

消除方块时的淡出效果:

dart 复制代码
AnimatedOpacity(
  opacity: tile.isMatched ? 0.0 : 1.0,
  duration: const Duration(milliseconds: 300),
  child: // 方块内容
)

2. CustomPainter绘制

自定义绘制连接路径:

dart 复制代码
CustomPaint(
  size: Size(
    _difficulty.cols * 64.0,
    _difficulty.rows * 64.0,
  ),
  painter: PathPainter(_currentPath),
)

3. 双向滚动

支持横向和纵向滚动:

dart 复制代码
SingleChildScrollView(
  child: SingleChildScrollView(
    scrollDirection: Axis.horizontal,
    child: _buildBoard(),
  ),
)

4. Future.delayed延迟执行

延迟清除路径和选择:

dart 复制代码
Future.delayed(const Duration(milliseconds: 300), () {
  setState(() {
    _selectedTile = null;
    _currentPath = [];
  });
});

游戏技巧

基本技巧

  1. 优先消除边缘:边缘方块更容易连接
  2. 记住位置:记住相同图案的位置
  3. 规划路径:选择转角少的路径得分更高
  4. 合理使用道具:在关键时刻使用提示和重排

高级技巧

  1. 预判死局:提前判断是否需要重排
  2. 时间管理:前期慢慢找,后期快速消除
  3. 路径优化:优先选择直线和一转角路径
  4. 道具策略:保留一次重排应对死局

功能扩展建议

1. 关卡模式

dart 复制代码
class Level {
  int number;
  Difficulty difficulty;
  int timeLimit;
  int targetScore;
  
  Level({
    required this.number,
    required this.difficulty,
    required this.timeLimit,
    required this.targetScore,
  });
}

List<Level> levels = [
  Level(number: 1, difficulty: Difficulty.easy, timeLimit: 180, targetScore: 5000),
  Level(number: 2, difficulty: Difficulty.easy, timeLimit: 150, targetScore: 6000),
  Level(number: 3, difficulty: Difficulty.medium, timeLimit: 240, targetScore: 8000),
];

2. 连击系统

dart 复制代码
int _combo = 0;
DateTime? _lastMatchTime;

void _onMatch() {
  final now = DateTime.now();
  if (_lastMatchTime != null && 
      now.difference(_lastMatchTime!).inSeconds < 3) {
    _combo++;
    _score += _combo * 50; // 连击奖励
  } else {
    _combo = 1;
  }
  _lastMatchTime = now;
}

3. 特殊道具

dart 复制代码
enum SpecialPowerUp {
  freeze,      // 冻结时间
  bomb,        // 炸弹消除
  rainbow,     // 万能匹配
}

void _useBomb(int row, int col) {
  // 消除周围3×3区域
  for (int i = -1; i <= 1; i++) {
    for (int j = -1; j <= 1; j++) {
      final r = row + i;
      final c = col + j;
      if (r >= 0 && r < _difficulty.rows && 
          c >= 0 && c < _difficulty.cols) {
        _board[r][c]?.isMatched = true;
      }
    }
  }
}

4. 成就系统

dart 复制代码
class Achievement {
  String id;
  String name;
  String description;
  bool unlocked;
  
  Achievement({
    required this.id,
    required this.name,
    required this.description,
    this.unlocked = false,
  });
}

List<Achievement> achievements = [
  Achievement(
    id: 'speed_master',
    name: '速度大师',
    description: '在60秒内完成一局',
  ),
  Achievement(
    id: 'perfect_path',
    name: '完美路径',
    description: '连续10次使用直线连接',
  ),
];

5. 多人对战

dart 复制代码
class MultiplayerGame {
  Player player1;
  Player player2;
  List<List<Tile?>> sharedBoard;
  
  void onPlayerMatch(Player player, Tile tile1, Tile tile2) {
    // 玩家消除方块
    player.score += 100;
    // 检查胜负
    if (_isAllMatched()) {
      _declareWinner();
    }
  }
}

6. 音效

dart 复制代码
import 'package:audioplayers/audioplayers.dart';

class AudioManager {
  final AudioPlayer _player = AudioPlayer();
  
  Future<void> playMatch() async {
    await _player.play(AssetSource('audio/match.mp3'));
  }
  
  Future<void> playWin() async {
    await _player.play(AssetSource('audio/win.mp3'));
  }
  
  Future<void> playTick() async {
    await _player.play(AssetSource('audio/tick.mp3'));
  }
}

算法优化

1. 路径缓存

dart 复制代码
Map<String, List<PathPoint>?> _pathCache = {};

List<PathPoint>? _findPathCached(Tile start, Tile end) {
  final key = '${start.row},${start.col}-${end.row},${end.col}';
  if (_pathCache.containsKey(key)) {
    return _pathCache[key];
  }
  
  final path = _findPath(start, end);
  _pathCache[key] = path;
  return path;
}

2. 提前检测死局

dart 复制代码
bool _hasValidMoves() {
  for (int i = 0; i < _difficulty.rows; i++) {
    for (int j = 0; j < _difficulty.cols; j++) {
      final tile1 = _board[i][j];
      if (tile1 == null || tile1.isMatched) continue;

      for (int m = i; m < _difficulty.rows; m++) {
        for (int n = 0; n < _difficulty.cols; n++) {
          if (i == m && j >= n) continue;
          final tile2 = _board[m][n];
          if (tile2 == null || tile2.isMatched) continue;

          if (tile1.type == tile2.type && _findPath(tile1, tile2) != null) {
            return true;
          }
        }
      }
    }
  }
  return false;
}

常见问题解答

Q1: 如何确保棋盘一定有解?

A: 可以在初始化时检测是否有可连接的方块对,如果没有则重新生成。

Q2: 路径查找算法的时间复杂度是多少?

A: 最坏情况下是O(n²),其中n是棋盘大小。可以通过缓存优化。

Q3: 如何实现更流畅的动画?

A: 使用AnimatedContainer、Hero动画或自定义Tween动画。

项目结构

复制代码
lib/
├── main.dart                    # 主程序入口
├── models/
│   ├── tile.dart               # 方块模型
│   ├── path_point.dart         # 路径点
│   └── difficulty.dart         # 难度配置
├── screens/
│   ├── game_page.dart          # 游戏页面
│   └── menu_page.dart          # 菜单页面
├── widgets/
│   ├── game_board.dart         # 游戏棋盘
│   ├── tile_widget.dart        # 方块组件
│   └── path_painter.dart       # 路径绘制器
└── utils/
    ├── path_finder.dart        # 路径查找算法
    └── score_calculator.dart   # 得分计算

总结

本文实现了一个功能完整的连连看游戏,涵盖了以下核心技术:

  1. 路径查找算法:直线、一转角、两转角连接检测
  2. 得分系统:基础分+时间奖励+路径奖励
  3. 提示功能:自动查找可连接的方块对
  4. 重排功能:打乱未匹配的方块
  5. CustomPainter:自定义绘制连接路径
  6. 动画效果:淡出动画和选中高亮
  7. 数据持久化:保存最高分记录

通过本项目,你不仅学会了如何实现连连看游戏,还掌握了Flutter中路径查找、自定义绘制、动画效果的核心技术。这些知识可以应用到更多益智游戏的开发。

挑战你的观察力,享受连连看的乐趣!
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
弓.长.2 小时前
React Native 鸿蒙跨平台开发:实现一个模拟计算器
react native·react.js·harmonyos
南村群童欺我老无力.2 小时前
Flutter 框架跨平台鸿蒙开发 - 打造表情包制作器应用
开发语言·javascript·flutter·华为·harmonyos
IT陈图图2 小时前
漫游记:基于 Flutter × OpenHarmony 的旅行记录应用首页实现
flutter·华为·鸿蒙·openharmony
小白阿龙3 小时前
鸿蒙+flutter 跨平台开发——合成大西瓜游戏的实现
flutter·游戏·harmonyos·鸿蒙
WaWaJie_Ngen3 小时前
C++实现一笔画游戏
c++·算法·游戏·游戏程序·课程设计
小尧嵌入式3 小时前
【Linux开发一】类间相互使用|继承类和构造写法|虚函数实现多态|五子棋游戏|整数相除混合小数|括号使用|最长问题
开发语言·c++·算法·游戏
小白阿龙3 小时前
鸿蒙+flutter 跨平台开发——快捷记账应用的开发
flutter·华为·harmonyos·鸿蒙
向前V3 小时前
Flutter for OpenHarmony数独游戏App实战:胜利弹窗
java·flutter·游戏
Felven4 小时前
华为鲲鹏920s处理器在统信系统下接收外部GPIO中断问题
华为·统信·鲲鹏920s·gpio中断