Flutter 实战:wordle_clone 五字母猜词游戏的棋盘状态、键盘反馈与鸿蒙适配解析

前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

wordle_clone 是一个用 Flutter 实现的五字母猜词游戏。它用 6 行 5 列棋盘、虚拟键盘、字母颜色反馈和胜负判定规则,模拟了 Wordle 的核心玩法。虽然项目体量不大,但它把二维状态管理键盘输入结果回显局部重置跨端 UI 适配都串了起来。

本文基于项目真实源码展开,重点分析 棋盘状态结构绿色/橙色/灰色反馈规则虚拟键盘实现重置逻辑胜负判定鸿蒙适配关注点。文章内容可直接发布到 CSDN,不包含面向作者的检查说明。

猜词游戏最容易被写成“看起来像游戏的输入框”,但真正有价值的点,是把字母位置、格子状态和键盘反馈组织成一套稳定的数据流。wordle_clone 正好能把这条链路讲清楚。

在这里插入图片描述

图示说明:本文围绕 Flutter 猜词游戏的棋盘、键盘、状态矩阵和跨端交互展开,适合用于鸿蒙、Android、iOS 等多端小型游戏应用开发复盘。

一、项目定位与功能概览

1.1 应用主题

wordle_clone 的定位是一个 五字母猜词游戏。系统会从预设单词列表中随机选择一个 5 字母目标词,玩家通过虚拟键盘输入猜测,提交后根据字母位置反馈颜色:

颜色 含义 状态值
绿色 字母正确且位置正确 2
橙色 字母存在但位置错误 1
灰色 字母不存在 0

1.2 核心玩法

页面中的核心交互闭环如下:

  1. 从键盘输入 5 个字母。
  2. 点击 ENTER 提交。
  3. 系统对每个字母进行比对。
  4. 棋盘格子和虚拟键盘同步变色。
  5. 猜中后显示 Congratulations。
  6. 6 轮都未猜中则显示 Game Over。

1.3 适合学习的点

这个项目适合学习以下 Flutter 实战能力:

  • 如何维护 6x5 的二维棋盘。
  • 如何让虚拟键盘和棋盘状态联动。
  • 如何用 setState() 驱动局部更新。
  • 如何做游戏类胜负判定。
  • 如何面向鸿蒙检查触摸、布局和重置交互。

二、工程结构与运行方式

2.1 工程结构

项目保持标准 Flutter 工程结构,核心代码集中在 lib/main.dart

文件或目录 作用 说明
lib/main.dart 应用入口与页面实现 包含棋盘、键盘和判定逻辑
pubspec.yaml 依赖声明 使用 Flutter SDK 与 Material 图标
test/widget_test.dart Widget 测试入口 可扩展为棋盘和键盘测试
ohos/ 鸿蒙平台工程目录 用于跨端构建和适配

2.2 依赖声明

项目没有引入复杂第三方插件:

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.8

这说明猜词逻辑、棋盘渲染和键盘交互都由 Flutter 与 Dart 完成,没有依赖网络、数据库或原生输入法插件。

2.3 常用命令

开发和验证时可以使用以下命令:

flutter pub get
flutter analyze
flutter test
flutter run
命令 作用 使用场景
flutter pub get 获取依赖 首次运行或依赖变化
flutter analyze 静态分析 检查语法和 lint
flutter test 执行测试 验证 Widget 行为
flutter run 启动应用 本地调试界面

三、应用入口与主题配置

3.1 main 函数

应用入口保持 Flutter 标准写法:

void main() {
  runApp(const MyApp());
}

这个入口没有额外初始化,适合当前这种纯前端状态的小游戏。

3.2 MyApp 根组件

根组件负责创建 MaterialApp

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Wordle Clone',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
      ),
      home: const MyHomePage(title: 'Wordle Clone'),
    );
  }
}

这里的关键信息有三个:

  • 应用标题是 Wordle Clone
  • 主题种子色是 Colors.green
  • 首页是 MyHomePage

3.3 主题色与游戏语义

绿色在这个项目中很有辨识度,和正确字母的反馈颜色一致,也让整个界面有更强的游戏感。顶部 AppBar、按钮和状态提示都能和主题保持一定统一。

当前源码没有显式设置 useMaterial3: true,因此文章以真实代码为准。如果后续需要统一 Material 3 表现,可以在 ThemeData 中补充该配置。

四、状态字段设计

4.1 核心状态总览

页面状态集中在 _MyHomePageState 中:

final List<String> _words = ['APPLE', 'BRAIN', 'CRANE', 'DREAM', 'EAGER', 'FLAME', 'GRAPE', 'HOUSE'];
late String _targetWord;
int _currentRow = 0;
int _currentCol = 0;
final List<List<String>> _board = List.generate(6, (_) => List.filled(5, ''));
final List<List<int>> _states = List.generate(6, (_) => List.filled(5, 0));
bool _gameOver = false;
String _message = 'Guess the 5-letter word';
final Map<String, Color> _letterStates = {};

这些字段分别描述目标词、当前输入位置、棋盘内容、状态矩阵、游戏结束状态和键盘颜色状态。

4.2 状态分层

可以把这些状态分成三类:

状态类别 字段 作用
目标状态 _words_targetWord 决定玩家要猜什么
输入状态 _currentRow_currentCol_board 决定当前输入位置
反馈状态 _states_letterStates_message_gameOver 决定界面反馈

4.3 二维棋盘结构

棋盘采用 6 行 5 列:

维度 数量 含义
行数 6 最多 6 次猜测
列数 5 每次输入 5 个字母

这种结构很适合棋盘类、填字类和回合类小游戏,因为它能天然表达“第几行、第几个字母”的位置关系。

五、目标词与初始化逻辑

5.1 预设单词列表

游戏使用一个内置单词列表:

final List<String> _words = ['APPLE', 'BRAIN', 'CRANE', 'DREAM', 'EAGER', 'FLAME', 'GRAPE', 'HOUSE'];

这意味着当前项目不依赖外部词库,能直接运行。

5.2 initState 随机选词

初始化时按当前秒数选取目标词:


void initState() {
  super.initState();
  _targetWord = _words[DateTime.now().second % _words.length];
}

5.3 选词策略

这个策略简单、可运行,但并不是真随机。它的优点是:

  • 不需要额外依赖。
  • 每次启动结果可能不同。
  • 逻辑容易理解。

它的局限也很明显:

问题 说明
可预测性 由当前秒数决定
不够均匀 启动时间接近时可能重复
不适合竞技 不适合严肃游戏场景

5.4 可扩展的选词方式

如果后续要增强随机性,可以换成更明确的随机对象:

final random = Random();
_targetWord = _words[random.nextInt(_words.length)];

当前项目没有这样做,但这已经是很自然的扩展方向。

六、输入控制与按键处理

6.1 onKeyPressed 主入口

所有键盘交互都走 _onKeyPressed()

void _onKeyPressed(String key) {
  if (_gameOver) return;

  if (key == 'ENTER') {
    _submitGuess();
  } else if (key == 'DEL') {
    if (_currentCol > 0) {
      setState(() {
        _currentCol--;
        _board[_currentRow][_currentCol] = '';
      });
    }
  } else if (_currentCol < 5) {
    setState(() {
      _board[_currentRow][_currentCol] = key;
      _currentCol++;
    });
  }
}

6.2 输入规则

按键逻辑可以拆成三类:

按键类型 行为
字母键 写入当前格子
DEL 删除上一个字母
ENTER 提交当前猜测

6.3 当前位置控制

_currentRow_currentCol 决定当前正在输入的格子。普通字母输入时只要 _currentCol < 5,就会写入棋盘并把列号加一。

6.4 删除行为

DEL 只会删除当前行中已输入的上一个字母:

_currentCol--;
_board[_currentRow][_currentCol] = '';

这让输入过程更符合用户对 Wordle 类游戏的预期。

七、猜测提交与判定规则

7.1 submitGuess 主逻辑

提交逻辑是整个游戏的核心:

void _submitGuess() {
  if (_currentCol != 5) {
    setState(() {
      _message = 'Not enough letters';
    });
    return;
  }

  final guess = _board[_currentRow].join();

  setState(() {
    for (int i = 0; i < 5; i++) {
      if (guess[i] == _targetWord[i]) {
        _states[_currentRow][i] = 2;
        _letterStates[guess[i]] = Colors.green;
      } else if (_targetWord.contains(guess[i])) {
        _states[_currentRow][i] = 1;
        if (_letterStates[guess[i]] != Colors.green) {
          _letterStates[guess[i]] = Colors.orange;
        }
      } else {
        _states[_currentRow][i] = 0;
        _letterStates[guess[i]] = Colors.grey;
      }
    }

    if (guess == _targetWord) {
      _gameOver = true;
      _message = 'Congratulations!';
    } else if (_currentRow == 5) {
      _gameOver = true;
      _message = 'Game Over! Word: $_targetWord';
    } else {
      _currentRow++;
      _currentCol = 0;
      _message = 'Try again';
    }
  });
}

7.2 提交前校验

如果当前行还没输满 5 个字母,系统会提示:

_message = 'Not enough letters';

这一步能避免半成品输入被提交。

7.3 比对规则

判定逻辑是典型 Wordle 风格:

条件 结果 状态值
字母和位置都对 绿色 2
字母存在但位置错 橙色 1
字母不存在 灰色 0

7.4 胜负判定

提交后会判断是否结束游戏:

情况 结果文案
猜中目标词 Congratulations!
6 次都没猜中 Game Over! Word: ...
还没结束 Try again

7.5 复杂点:重复字母

当前实现使用 contains() 判断字母是否存在:

else if (_targetWord.contains(guess[i]))

这对简单场景足够,但在重复字母较多时,反馈可能不完全符合标准 Wordle 的精确规则。这个项目更像是一个易懂的 Flutter 猜词教学样例,而不是完整复刻版。

八、棋盘渲染与格子状态

8.1 格子颜色

格子颜色由 _states 决定:

Color _getTileColor(int row, int col) {
  final state = _states[row][col];
  if (state == 2) return Colors.green;
  if (state == 1) return Colors.orange;
  return Colors.grey.shade300;
}

8.2 棋盘布局

棋盘区域通过嵌套 List.generate() 绘制:

Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: List.generate(6, (row) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: List.generate(5, (col) {
        return Container(...);
      }),
    );
  }),
)

8.3 当前行高亮

当前行未填满的格子会显示蓝色边框和浅蓝底色:

color: _states[row][col] > 0
    ? _getTileColor(row, col)
    : isCurrentRow
        ? Colors.blue.shade100
        : Colors.grey.shade200,

这能帮助玩家快速定位当前输入位置。

8.4 棋盘视觉表

状态 背景色 说明
未填写 灰色或浅蓝 等待输入
正确位置 绿色 字母与位置都正确
位置错误 橙色 字母存在但位置不同

九、虚拟键盘实现

9.1 键盘布局

虚拟键盘分成三行:

final rows = [
  ['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'],
  ['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L'],
  ['ENTER', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', 'DEL'],
];

9.2 宽键处理

ENTERDEL 使用更宽的按钮:

final isWide = key == 'ENTER' || key == 'DEL';
width: isWide ? 56 : 36,

9.3 键盘颜色联动

键盘颜色来自 _letterStates

final bgColor = _letterStates[key] ?? Colors.grey.shade300;

这意味着字母一旦在棋盘里被判定,键盘按键也会同步变色。

9.4 键盘颜色策略

键盘颜色 含义
绿色 该字母正确且位置正确
橙色 该字母存在但位置错误
灰色 该字母不存在

9.5 UI 交互细节

键盘使用 MaterialInkWell

Material(
  color: bgColor,
  borderRadius: BorderRadius.circular(8),
  child: InkWell(
    onTap: () => _onKeyPressed(key),
    borderRadius: BorderRadius.circular(8),
    child: Container(...),
  ),
)

这让按钮点击反馈更自然,也符合 Flutter 的 Material 风格。

十、消息栏与结果反馈

10.1 顶部消息栏

页面顶部用 Card 显示状态消息:

Card(
  color: _gameOver
      ? (_board[_currentRow].join() == _targetWord ? Colors.green.shade50 : Colors.red.shade50)
      : Colors.blue.shade50,
  child: Padding(
    padding: const EdgeInsets.all(16),
    child: Text(_message),
  ),
)

10.2 消息颜色

消息颜色也会随状态变化:

状态 背景色 前景色
游戏进行中 蓝色浅色 蓝色
猜中 绿色浅色 绿色
失败 红色浅色 红色

10.3 结果展示的意义

结果栏的作用不仅是提示输赢,还能帮助玩家理解当前输入是否完整、是否需要继续尝试。

10.4 可扩展消息

后续可以增加:

  • 输入不足提示。
  • 猜对后的总结文案。
  • 游戏结束后的答案解释。
  • 新一局提示。

十一、重置按钮与新一局逻辑

11.1 AppBar 重置按钮

页面右上角有刷新按钮:

IconButton(
  icon: const Icon(Icons.refresh),
  onPressed: () {
    setState(() {
      _targetWord = _words[DateTime.now().second % _words.length];
      _currentRow = 0;
      _currentCol = 0;
      _board.fillRange(0, _board.length, List.filled(5, ''));
      _states.fillRange(0, _states.length, List.filled(5, 0));
      _gameOver = false;
      _message = 'Guess the 5-letter word';
      _letterStates.clear();
    });
  },
)

11.2 重置动作

重置会做这些事情:

  1. 重新选目标词。
  2. 清空棋盘。
  3. 清空状态矩阵。
  4. 回到第 0 行第 0 列。
  5. 清空键盘颜色。
  6. 恢复初始提示文案。

11.3 重置策略的意义

这种重置方式能让玩家快速开始新一局,而不用退出页面再进来。

11.4 需要注意的地方

当前重置时重新选词仍然依赖当前秒数,所以严格来说它并不是完全随机。若未来要做更正式的游戏,可以换成 Random

十二、游戏状态机视角

12.1 状态机划分

wordle_clone 的核心状态可以粗略分成三种:

状态 特征
输入中 还能输入字母,尚未提交
检查中 点击 ENTER 后进行判定
结束 猜中或 6 次失败

12.2 状态迁移

输入中 -> 提交 -> 检查结果 -> 下一行或结束

12.3 为什么适合状态机思维

猜词游戏本质上就是一个非常标准的有限状态流程:

  • 每一行就是一轮。
  • 每次提交都会推进轮次。
  • 达到成功条件或失败条件时停止。

12.4 状态迁移表

当前状态 条件 下一状态
输入中 输入 5 个字母 可提交
可提交 猜中 结束成功
可提交 未猜中且未到第 6 行 下一轮
可提交 未猜中且第 6 行 结束失败

十三、鸿蒙适配关注点

13.1 为什么适配风险较低

wordle_clone 不依赖网络、存储、定位、相机或原生输入法插件,主要是 Flutter 标准组件和简单状态管理,所以跨端适配风险较低。

模块 是否依赖平台能力 适配关注度
棋盘渲染
键盘点击 Flutter 标准触摸
列表状态
重置逻辑
后台保存 当前未实现

13.2 鸿蒙设备验证点

适配鸿蒙时建议重点验证:

  1. 键盘按钮点击是否灵敏。
  2. 棋盘 6x5 布局是否在小屏上溢出。
  3. 状态色彩是否足够明确。
  4. 重置按钮点击后是否完全清空。
  5. 进入后台再回来时页面状态是否符合预期。

13.3 小屏适配

棋盘每个格子的尺寸是固定的:

width: 52,
height: 52,

键盘按钮也有固定宽高:

width: isWide ? 56 : 36,
height: 48,

在较小屏幕或横竖屏变化较大的设备上,可能需要通过 MediaQuery 再做自适应。

13.4 游戏类应用的适配特点

游戏类应用和工具类应用不同,除了功能正确,还要关注:

  • 触摸反馈是否及时。
  • 色彩反馈是否易识别。
  • 局部重绘是否顺滑。
  • 文本是否会被挤压或遮挡。

十四、测试设计与默认测试改造

14.1 当前测试入口

项目中的测试文件还是默认计数器测试。对于猜词游戏,更有价值的是验证初始棋盘、输入键盘、提交判定和重置逻辑。

14.2 初始页面测试

可以先验证页面初始状态:

testWidgets('wordle clone renders initial board', (WidgetTester tester) async {
  await tester.pumpWidget(const MyApp());

  expect(find.text('Guess the 5-letter word'), findsOneWidget);
  expect(find.text('Congratulations!'), findsNothing);
  expect(find.text('Game Over!'), findsNothing);
});

14.3 输入测试

可以模拟点击键盘输入字母:

testWidgets('can type letters on board', (WidgetTester tester) async {
  await tester.pumpWidget(const MyApp());

  await tester.tap(find.text('A'));
  await tester.tap(find.text('P'));
  await tester.tap(find.text('P'));
  await tester.tap(find.text('L'));
  await tester.tap(find.text('E'));
  await tester.pump();

  expect(find.text('APPLE'), findsWidgets);
});

14.4 提交测试

输入满 5 个字母后点击 ENTER,应触发提交逻辑:

testWidgets('shows not enough letters when guess is short', (WidgetTester tester) async {
  await tester.pumpWidget(const MyApp());

  await tester.tap(find.text('A'));
  await tester.tap(find.text('ENTER'));
  await tester.pump();

  expect(find.text('Not enough letters'), findsOneWidget);
});

14.5 重置测试

点击刷新按钮后,棋盘应该被清空:

testWidgets('reset clears the board', (WidgetTester tester) async {
  await tester.pumpWidget(const MyApp());

  await tester.tap(find.byIcon(Icons.refresh));
  await tester.pump();

  expect(find.text('Guess the 5-letter word'), findsOneWidget);
});

十五、可维护性优化方向

15.1 抽离棋盘模型

当前棋盘和状态矩阵使用二维列表,简单但不够强类型。可以抽成单元格模型:

class TileCell {
  const TileCell({
    required this.letter,
    required this.state,
  });

  final String letter;
  final int state;
}

15.2 抽离游戏状态

如果后续功能增加,可以把游戏状态做成对象:

class WordleGameState {
  const WordleGameState({
    required this.currentRow,
    required this.currentCol,
    required this.gameOver,
  });

  final int currentRow;
  final int currentCol;
  final bool gameOver;
}

15.3 更精确的字母规则

当前使用 contains() 判断字母是否存在,对于重复字母会比较粗糙。未来可以按 Wordle 官方思路,先匹配位置,再消耗字母计数,这样会更准确。

15.4 增加词库和难度

当前词库只有 8 个单词。可以扩展为:

方向 价值
更多单词 增加可玩性
不同难度 提升挑战
分类词库 适合教学和主题玩法
本地词库文件 便于维护

十六、功能扩展方向

16.1 增加统计面板

可以增加:

  • 胜率。
  • 连胜次数。
  • 当前连胜。
  • 总局数。

16.2 增加动画

棋盘格子可以在提交时加入翻转动画或弹跳动画,让反馈更像真正的 Wordle。

16.3 增加音效

击中绿色、橙色或失败时可以播放不同音效,增强游戏感。

16.4 增加分享功能

猜中后可以生成简短战绩卡片,方便分享结果。

十七、性能与体验优化

17.1 当前性能特点

这个项目的性能压力很小,因为棋盘只有 30 个格子,键盘按钮数量也固定。主要体验点是状态切换是否清晰,而不是计算量。

17.2 减少重复 setState

当前代码里很多操作直接用 setState(),在小项目里很常见。如果后续重构,可以把“计算”和“刷新”拆开,让逻辑更清楚。

17.3 更稳妥的重置

当前重置时用了 fillRange() 重新填充棋盘和状态矩阵,这种做法简洁,但如果后续列表对象更复杂,建议改成重新创建二维数组。

17.4 触摸区域优化

键盘按钮宽度目前是固定值。若在更小屏幕上出现拥挤,可以把键盘整体缩放或者改成自适应布局。

十八、常见问题与优化建议

18.1 为什么输入必须是 5 个字母

因为棋盘就是 5 列,游戏规则要求提交 5 字母单词。少于 5 个字母时,系统会提示 Not enough letters

18.2 为什么键盘字母会变色

这是为了把猜测结果同步到输入面板,让玩家更容易排除无效字母。

18.3 为什么重复字母反馈可能不够精确

当前逻辑使用 contains(),没有完整实现重复字母计数消耗规则。它适合教学和轻量玩法,但不完全等同于标准 Wordle。

18.4 为什么重置后目标词可能变化

因为重置时会重新从预设词库中选词,这让每局游戏都有新鲜感。

18.5 鸿蒙适配最应该关注什么

重点关注小屏布局、触摸反馈、颜色辨识度、重置清空效果和游戏结束后的状态锁定。这个项目没有复杂原生依赖,因此主要问题集中在 UI 和交互上。

十九、完整流程复盘

19.1 页面启动流程

main()
  -> runApp(MyApp)
  -> MaterialApp
  -> MyHomePage
  -> initState 选目标词
  -> build 渲染棋盘和键盘

19.2 输入流程

点击字母
  -> _onKeyPressed
  -> 写入 _board
  -> 当前列加 1
  -> 页面刷新

19.3 提交流程

点击 ENTER
  -> 检查是否输满 5 个字母
  -> 拼接当前行猜测词
  -> 逐字比较目标词
  -> 更新格子状态和键盘状态
  -> 判断是否胜利或继续

19.4 重置流程

点击刷新
  -> 重新选目标词
  -> 清空棋盘
  -> 清空状态矩阵
  -> 清空键盘颜色
  -> 恢复提示文案

二十、相关资源与继续学习

20.1 Flutter 学习资源

猜词游戏涉及布局、状态、动画和测试,可以结合以下资源学习:

资源 内容
Flutter Docs Flutter 官方开发文档
Dart 官方文档 Dart 语言与核心库
Widget catalog Flutter 常用组件
Flutter testing Widget 测试与交互模拟

20.2 游戏类应用扩展方向

后续可以继续增强:

  • 标准 Wordle 重复字母规则。
  • 胜率和历史统计。
  • 动画反馈。
  • 音效反馈。
  • 词库文件化。
  • 深色模式。
  • 结果分享。

20.3 跨端实践价值

wordle_clone 很适合作为 Flutter 适配鸿蒙的小型游戏样例。它依赖很轻,但包含棋盘状态、键盘输入、颜色反馈和局部重置,能帮助开发者验证很多跨端交互细节。

总结

wordle_clone 用简洁的 Flutter 代码实现了一个五字母猜词游戏。它通过 _board 维护棋盘内容,通过 _states 记录每个格子的反馈状态,通过 _letterStates 同步键盘颜色,通过 _currentRow_currentCol 控制输入位置,通过 _gameOver 锁定游戏结束状态。

从工程角度看,这个项目最值得学习的是“棋盘状态 + 键盘反馈 + 胜负判定”的完整闭环。它很适合作为 Flutter 小型游戏和鸿蒙跨端适配的入门样例。若要继续提升完成度,可以再补充更准确的重复字母规则、统计面板和动画效果。

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!


相关资源:

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐