最近在整理算法小项目时,想到了一个特别适合练手的主题:把 Wordle 猜词游戏用 Python 实现一遍,再让不同的“AI 选手”自动玩这个游戏,看谁的胜率更高、平均猜中轮数更少。这个项目看起来简单,但拆开后涉及反馈判定、候选词过滤、信息熵计算、批量模拟评估,正好可以把枚举、贪心、信息论这几个基础话题串起来。如果你刚开始学算法,或者想找一个不需要大模型也能讲清楚“AI 思路”的项目,这篇教程应该很适合你。
市面上很多“AI 猜词”项目直接接大模型 API,让模型根据历史反馈猜下一个词。但这一篇会先绕开大模型,用经典算法实现三种策略:随机猜词、词频优先、信息熵最大化。这样能直观看到不同策略对胜率的影响,也能自己动手改策略、加优化。等基础版本跑通后,再考虑把大模型作为竞速选手接入对比,也是顺理成章的事。
1. 项目背景:Wordle 与 AI 猜词挑战
1.1 Wordle 游戏规则
Wordle 是一款猜单词的小游戏。系统会随机选择一个五字母英文单词作为答案,玩家在六次机会内不断猜测。每次猜测后,游戏会给出三色反馈:
- 绿色:字母正确,且位置也正确。
- 黄色:字母在答案中存在,但位置不对。
- 灰色:字母不在答案中,或者超出了答案中该字母的数量。
举个例子,假设答案是apple,猜测是apply,那么反馈为:
| 答案 | 猜测 | 反馈 | 解释 |
|---|---|---|---|
| apple | apply | 绿 绿 绿 绿 灰 | 前四个字母位置正确,y 不在答案中 |
再看一个重复字母的例子。假设答案是eerie,猜测是eject:
| 答案 | 猜测 | 反馈 | 解释 |
|---|---|---|---|
| eerie | eject | 绿 灰 黄 灰 灰 | 第一个 e 位置正确,第三个猜测位置的 e 存在但位置不对 |
这个规则看起来简单,但重复字母的处理很容易出错。后面实现make_feedback时会专门说明。
1.2 为什么叫“小型 AI 挑战”
这里的“AI 挑战”并不是指用神经网络训练一个模型,而是指让程序自主决策:根据之前的反馈,从候选词集合中筛选答案,并决定下一步猜什么。
这个问题的本质是“主动学习”或“最优实验设计”:每一步选择一个猜测,目标是尽快缩小答案范围。经典的信息熵贪心策略是其中的代表方法,它不依赖任何外部模型,也能在真实游戏中达到很高的胜率。
1.3 本文最终完成的功能
整个项目会包含四个文件:
wordle.py:游戏核心,负责计算三色反馈和单局流程。words.py:演示词表,内置 100 个五字母英文单词。player.py:三种 AI 猜词策略。main.py:命令行入口,支持批量模拟和人机对战。
最终你可以用一条命令让 AI 自动玩几百局,并统计胜率和平均猜中轮数,也可以自己扮演玩家,在终端里玩一局原汁原味的 Wordle。
2. 环境准备与项目结构
2.1 环境说明
本项目使用 Python 标准库实现,不依赖任何第三方包。理论上 Python 3.8 及以上版本都能直接运行。我会用到argparse、random、collections.Counter、math这些模块,都属于标准库。
如果你的机器上有多个 Python 版本,创建虚拟环境再运行更稳妥:
python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate当然,本项目零第三方依赖,不创建虚拟环境也可以直接运行。
2.2 项目目录结构
建议新建一个wordle-ai-challenge目录,所有代码都放在里面:
wordle-ai-challenge/ ├── main.py # 命令行入口 ├── player.py # AI 猜词策略 ├── wordle.py # Wordle 游戏核心 └── words.py # 演示词表每个文件的职责非常清晰:
words.py只负责提供词表。wordle.py只负责规则判定。player.py实现不同的决策算法。main.py负责把前面的模块串起来,提供用户交互入口。
这样拆分的好处是:之后如果想把player.py里的策略换成大模型 API,只需要保证guess()和update()两个接口不变即可,游戏核心完全不用改动。
3. 游戏规则与反馈判定
3.1 反馈判定的核心难点
实现 Wordle 反馈判定,最大的坑是重复字母。
例如答案apple,猜测ample:
a在 0 位,正确,绿色。m不在答案中,灰色。p在 2 位,答案 2 位也是p,绿色。l在 3 位,绿色。e在 4 位,绿色。
但如果猜测是peach,answer是apple,情况就会复杂一些。guess中出现了p、e、a,这些字母都存在于答案中,但不能无限给黄色,因为答案里的每个字母只能匹配一次。
正确的处理方式分两步:
- 先扫描一次,把所有“字母和位置都正确”的位置标记为绿色。
- 再扫描一次,对非绿色位置,检查该字母是否还“剩余可用”,如果可用就标记为黄色,并从剩余集合中移除;否则标记为灰色。
这样可以避免把同一个答案字母重复匹配到多个猜测字母上。
3.2 实现游戏核心
创建wordle.py:
""" Wordle 游戏核心逻辑:反馈判定与单局游戏流程。 """ from typing import List, Tuple, Optional GREEN = "green" YELLOW = "yellow" GRAY = "gray" MAX_GUESSES = 6 Feedback = Tuple[str, ...] def make_feedback(secret: str, guess: str) -> Feedback: """ 计算一次猜测的反馈。 - green:字母正确且位置正确 - yellow:字母存在于答案中,但位置不对 - gray:字母不在答案中,或超出答案中该字母的剩余数量 先标记所有 green,再为 yellow 分配剩余字母,确保重复字母处理正确。 """ if len(secret) != len(guess): raise ValueError("secret 和 guess 长度不一致") result: List[str] = [GRAY] * len(secret) unmatched: List[str] = [] # 第一遍:标记 green,并收集非 green 位置的答案字母 for idx, (s, g) in enumerate(zip(secret, guess)): if s == g: result[idx] = GREEN else: unmatched.append(s) # 第二遍:为剩余位置分配 yellow / gray for idx, (s, g) in enumerate(zip(secret, guess)): if result[idx] == GREEN: continue if g in unmatched: result[idx] = YELLOW unmatched.remove(g) else: result[idx] = GRAY return tuple(result) def is_valid_guess(guess: str, word_list) -> bool: return len(guess) == 5 and guess in word_list def play_once(secret: str, player, word_list, max_guesses: int = MAX_GUESSES) -> Optional[int]: """ 使用指定 player 完成一局游戏。 返回猜中所