汉字Wordle是一种基于猜词游戏的文字游戏,类似于孪生词游戏(Word Ladder),但与孪生词游戏不同的是,汉字Wordle使用的是汉字,而不是英文单词。

实现该游戏的大致流程是:从成语列表中选取一个成语作为谜底,用户每次猜一个成语,程序根据猜测成语与谜底的匹配情况,将匹配结果提示给用户(本次仿真的游戏用户由程序扮演)。用户需要在尽量少的次数内猜中谜底。总共进行多次游戏,最后计算平均最快次数以及平均游戏用时。
具体实现中,程序通过读取本地的四字成语文本文件,读取所有成语并去重(可能有重复的成语),创建一个副本并选取一个成语作为谜底,然后程序进行多轮游戏,每轮游戏中,程序在备选成语列表中随机选择一个成语,与谜底成语进行字、拼音、声调等方面的匹配,然后用匹配结果作为筛选条件,筛选出符合匹配规则的成语,然后更新备选列表。程序在每轮猜测中统计猜测次数,当用户猜中谜底时,程序停止该轮游戏,记录用户猜中谜底的次数。
其中较为繁杂的是成语匹配筛选的部分。在具体的匹配功能实现中,可分为两步:
第一步通过 lazy_pinyin() 方法提取成语的拼音,用convert_tones() 将拼音声调转换为数字形式,并提取出成语的声母、韵母。
第二步,在猜测成语中提取和成语谜底相同位置的声调数字、声母和韵母,最有可能找到和谜底相同的依次是声调、韵母、声母和汉字,故依次对声调、声母、韵母和汉字进行匹配,具体规则如下:
- 如果猜测成语和谜底成语在某个位置的声调数字匹配,则筛选出总备选列表中该位置声调数字相同的成语组成新的备选列表1;
- 如果猜测成语和谜底成语在某个位置的声母匹配,则筛选出总备选列表中该位置声母相同的成语组成新的备选列表2;
- 如果猜测成语和谜底成语在某个位置的韵母匹配,则筛选出总备选列表中该位置韵母相同的成语组成新的备选列表3;
- 如果猜测成语和谜底成语在某个位置的文字相同,则筛选出总备选列表中该位置文字相同的成语组成新的备选列表4。
以上四种匹配方法按顺序执行,依次更新每个子备选列表。
最后汇总4个子列表(取交集):如果子备选列表中有多个非空,则取它们的共同部分作为下一轮的总备选成语;如果四个备选列表仅有一个非空,它就是下一轮的总备选成语;如果备选列表全部为空,则不更新总备选成语。
不断重复第二步,直至和谜底完全匹配,或者总备选列表为空,即猜中谜底。如下图2所示,我进了100轮游戏,平均猜谜次数为5.3次,平均用时3.25秒。

如果以上内容觉得我讲的不够明白,可以直接看代码:
import random
import re
from pypinyin import lazy_pinyin, Style, pinyin
import datetime
def convert_tones(pinyin):
"""将拼音声调转换成数字"""
tones1 = []
for t in pinyin:
m1 = re.search(r'\d', t)
if m1:
tone1 = int(m1.group())
if tone1 == 0: # 转换带数字5的轻声
tone1 = 5
tones1.append(tone1)
else: # 如果没有数字,说明是轻声
tones1.append(5)
return tones1
# 在此处进行待计时的操作
start_time = datetime.datetime.now()
with open('四字成语.txt', 'r', encoding='utf-8') as f:
data = f.read()
idiom = data.split("\n")
idiom = list(set(idiom))
num_all = 0 # 记录猜谜次数
for games in range(100): # 进行多次游戏
print(f"\n成语猜谜第 {games + 1} 回合:")
ans = random.choice(idiom) # 选一个成语作为谜底
print("谜底:", ans)
out0 = idiom.copy() # 创建一个成语列表副本,防止修改原列表
tones = convert_tones(lazy_pinyin(ans, style=Style.TONE2)) # 提取谜底声调数字
initials = lazy_pinyin(ans, style=Style.INITIALS) # 提取谜底声母
finals = lazy_pinyin(ans, style=Style.FINALS) # 提取谜底韵母
out = []
out1 = []
out2 = []
out3 = []
out4 = []
num = 0 # 猜谜次数
flag = 1 # 判断是否猜到谜底
while flag:
num += 1
# 猜一个成语
test = random.choice(out0)
out0.remove(test) # 从备选中删掉已猜成语
print(f"第 {num} 次猜:{test} 剩下备选: {len(out0)} 个")
test_tones = convert_tones(lazy_pinyin(test, style=Style.TONE2)) # 提取猜测成语的声调数字
test_initials = lazy_pinyin(test, style=Style.INITIALS)
test_finals = lazy_pinyin(test, style=Style.FINALS)
for i in range(4):
# 通过声调筛选
if test_tones[i] in tones:
idx = tones.index(test_tones[i])
if idx == i:
for ou in out0:
ou_tones = convert_tones(lazy_pinyin(ou, style=Style.TONE2))
if ou_tones[idx] == test_tones[i]:
out1.append(ou)
else:
for ou in out0:
ou_tones = convert_tones(lazy_pinyin(ou, style=Style.TONE2))
if test_tones[i] in ou_tones:
out1.append(ou)
# 通过声母筛选
if test_initials[i] in initials:
idx = initials.index(test_initials[i])
if idx == i:
for ou in out0:
ou_initials = lazy_pinyin(ou, style=Style.INITIALS)
if ou_initials[idx] == test_initials[i]:
out2.append(ou)
else:
for ou in out0:
ou_initials = lazy_pinyin(ou, style=Style.INITIALS)
if test_initials[i] in ou_initials:
out2.append(ou)
# 通过韵母筛选
if test_finals[i] in finals:
idx = finals.index(test_finals[i])
if idx == i:
for ou in out0:
ou_finals = lazy_pinyin(ou, style=Style.FINALS)
if ou_finals[idx] == test_finals[i]:
out3.append(ou)
else:
for ou in out0:
ou_finals = lazy_pinyin(ou, style=Style.FINALS)
if test_finals[i] in ou_finals:
out3.append(ou)
# 通过文字筛选
if test == ans: # 如果全字匹配
flag = 0
break
if test[i] in ans:
idx = finals.index(test_finals[i])
if idx == i:
for ou in out0:
if ou[idx] == test[i]:
out4.append(ou)
else:
for ou in out0:
if test[i] == ou:
out4.append(ou)
common = [lst for lst, length in zip((out1, out2, out3, out4), map(len, (out1, out2, out3, out4))) if
length > 0]
if len(common) == 1:
out = common[0]
elif len(common) > 1:
out = list(set(common[0]).intersection(*common[1:]))
if not out0:
out = min(common, key=len)
if out:
out0 = out.copy() # 不能直接赋值,避免“共享引用”的情况
out1.clear()
out2.clear()
out3.clear()
out4.clear()
print(f"恭喜你,经过 {num} 次猜测,你已猜中谜底:{test}")
num_all += num
end_time = datetime.datetime.now()
games += 1
print(f"\n经过 {games+1} 轮游戏,\n平均每轮猜谜次数:{num_all / games:.1f} 次")
print(f"平均每轮用时:{(end_time - start_time).total_seconds()/games:.2f} 秒")
四字成语txt文档链接:https://pan.baidu.com/s/12sQXE9fweHm6TSaQrQsmLA?pwd=jr5o
提取码:jr5o
这个程序的瑕疵是 lazy_pinyin()函数,其有时候并不能完美地提取声母和韵母,但多次运行发现对结果似乎并没有什么影响,另外程序也比较冗长,执行效率较低,感兴趣的小伙伴可以尝试优化一下,有好的提议,欢迎在评论区留言。^-^
如果想要玩汉兜,可以点这里汉兜 - 汉字 Wordle (antfu.me)https://handle.antfu.me/
哈哈,想要作弊的话,可以参考一下这个↓↓↓
Handle汉兜作弊代码python实现 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/500671686