打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
【老万】破解 wordle 游戏:从半自动到全自动
userphoto

2022.07.13 江苏

关注

收录于合集#老万谈技术4个

前几天给大家介绍了我们如何从无到有,反复迭代,把一个 wordle 游戏玩得体无完肤。经过几轮改进,这个战胜 wordle 的机器(简称战斗机)已经做到百分之百的胜率了。一码在手,江湖我有。从此在朋友圈装逼无往不利。

等等,真的无可改进了吗?

当然不是。

程序员的最高原则是懒惰是第一生产力,凡能自动化的必须自动化。以这个标准看,虽然我们用程咬金++算法实现了解题自动化,程序还缺少眼睛和双手,需要人类配合才能完成工作,充其量只能算半自动化。

看看用战斗机解题的过程:首先程序告诉你应该猜哪个单词,你手动把这个词在游戏网站上输入,再把游戏给你的提示翻译成战斗机能懂的代码告诉它,让它猜下一步。

基本上,就是俩机器人一个出题一个答题,一个人类在它们中间当搬运工。这到底是你玩游戏还是游戏玩你?

事关人类尊严,不可等闲视之。为了不沦为机器的码仔和干电池,我要奋起反抗,实现游戏的彻底自动化。

~~~~

这个游戏是在网页上玩的,所以先要解决如何用代码和浏览器交互。

我们运气不错:这个问题是开发网站时常见的,已经有了现成的方案。

比如你写了一个网站,如何保证它功能正确不会挂?一种方法是雇一堆测试员,不停按各种按钮,然后看正确的事是不是发生了。

更可靠也更省钱的方法是写一段测试程序去按按钮,让程序自动验证网页的结果。(凡能自动化的必须自动化。)

做前端开发的工程师应该都知道一个做这种测试的免费软件,叫 selenium

因为战斗机是用 python 写的,我们先去下载 selenium 的 python 客户端。这很简单,在 terminal 窗口运行下面的命令就好:

pip install seleniumpip install webdriver-manager

这里的 webdriver 是 selenium 的一个部件,负责驱赶浏览器做各种事情。

在 terminal 窗口运行 python3,然后敲入以下 python 代码:

from selenium import webdriverfrom selenium.webdriver.chrome.service import Service as ChromeServicefrom webdriver_manager.chrome import ChromeDriverManagerdriver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))

你应该看见一个新的 Chrome 浏览器窗口蹦出来。恭喜!你已经学会了如何用代码打开浏览器。

下一步要让浏览器跳转到游戏网站。向 driver 对象发一行命令就行了:

driver.get("https://www.nytimes.com/games/wordle/index.html")

你是不是看到浏览器打开了纽约时报的 wordle 游戏网页?

如果要关闭浏览器,只需

driver.close()

~~~~

怎么完成和游戏的交互呢?我们需要看网页的源代码,了解页面上的各种控件是如何实现的,然后找到对应于这些控件的对象,向它们发出各种指令完成交互。

Chrome 浏览器对开发者非常友好,在任何一个网页上,只需按下 shift-⌘-C 三键组合,窗口就会一分为二,在右边显示当前网页的各种信息:

在网页上面移动鼠标,每到一个控件上方,右边调试窗口里面对应的代码就会被展开,你就可以知道这个控件是怎么实现的了。

我最关心的是两组控件:网页下方的键盘,和显示单词用的一堆字母牌。

把鼠标移到页面上的键盘上,我们可以看到键盘分为三排,每一排是一个

<div class="Keyboard-module_row__YWe5w">...</div>

元素。它包含了若干按钮,每个按钮长这样(以字母 q 为例):

<button type="button" data-key="q" class="Key-module_key__Rv-Vp">q</button>

通常每个控件元素有一个 id 属性,相当于它的名字,我们可以通过 id 找到指定对象进行操作。可这些按钮都没有 id,我们如何在代码里拿到它们对应的对象呢?

好在 webdriver 还支持按元素的 class 属性进行搜索。和 id 不同,通常控件的 class 不是唯一的,好几个控件可以有相同的 class,所以搜索的结果是一个列表。

敲入下面这行代码,找出所有按钮:

from selenium.webdriver.common.by import Bykeyboard = driver.find_elements(By.CLASS_NAME, "Key-module_key__Rv-Vp")

如果再敲入

len(keyboard)

我们可以发现这个列表包括 28 个部件:除 26 个字母外,还包括左下角的 enter 键和右下角的后退键。因为战斗机不会反悔,我们用不到后退键,但是在输完一个单词后需要按下回车键确认。

我们试一试

keyboard[0].click()

是不是第一张字母牌变成 Q 了?这是因为 Q 是键盘上的第一个键,上面这行代码模拟了点击 Q 键的动作。

用同样的方法,我们可以找到字母牌的实现是这样的:

<div class="Tile-module_tile__3ayIZ" data-state="empty" data-animation="idle" data-testid="tile"></div>

和键盘类似,这些控件也都没有 id,无法直接访问。所以我们也要通过 class 属性把它们找出来:

tiles=driver.find_elements(By.CLASS_NAME, "Tile-module_tile__3ayIZ")

检查一下有几块牌子:

len(tiles)

不出所料,结果是 30。

字母牌都有一个 data-state 属性,一开始是 empty(空着)。按下一个键后,对应牌子的 data-state 会变成 tbd(待定)。按下 enter 后,游戏会给每个牌子刷上不同的颜色,对应于不同的 data-state

  • 灰色absent,错误。

  • 棕色present,有戏。

  • 绿色correct,正确。

有了这些基础知识,代码就好写了。大体框架是:

for attempt in range(6):    guess = solver.SuggestGuess()    if not guess:        return    for char in guess + "\n":        keyboard[GetKeyIndexInGameKeyboard(char)].click()        time.sleep(0.1)    time.sleep(1.9)    hints = GetHintsFromWeb(tiles[5*attempt:])    if hints == "MMMMM":  # Just won!        return    solver.MakeGuess(guess, hints)

循环六次,每次猜一个词,逐字母输入,两次击键之间歇 0.1 秒,让游戏有机会反应。

按完 enter 键后耐心多等一会儿,让游戏显示翻牌动画。然后就是见证奇迹的时候了:我们去看字母牌的状态,会发现它们的 data-state 属性已经变了,根据新的属性可以知道每个字母猜得对不对,然后利用这个信息猜下一个,直到成功。

用代码把以上动作串联起来,就可以开挂了。跑一跑这个升级版的战斗机,只见它以迅雷不及掩耳之势一路高歌猛进,游戏只有招架之功没有还手之力。这种一泻千里势如破竹的打法,看起来真是过瘾。

下面这段实时录屏,除了迈克尔杰克逊的 Beat It 伴唱,没有任何后期处理:

00:24

我们再加入一个循环,让战斗机每小时重复一次这样的操作。因为每天 wordle 游戏网站会放出一道新题,每小时刷一次肯定不会漏过。只要你让战斗机不停地跑,不去打扰它,你的游戏历史记录就会不断被刷新。每天起床视察一下战绩,适当的时候发发朋友圈就 OK 了。

~~~~

但我们还有一个问题:这种玩法非常脆弱,可以说是弱不禁风。

如果你不小心关掉了这个游戏或者战斗机窗口,或者重启了机器,重新开始跑战斗机的时候你会发现,它忘了曾经的辉煌,开了一个全新的游戏窗口从零开始,风流都被雨打风吹去,过去积攒的战果都消失无影迹。这时候你会不会七窍生烟想要拔网线?

如何增强系统的鲁棒性?我们只需改变 webdriver 的用法,让它不要每次都开一个新的浏览器,而是和一个已经在跑的浏览器对话,这样历史就不会丢失了。

首先我们要做些一次性的设置:

打开 ~/.bash_profile 文件,加入下面几行:

# Set up for running Chrome from the command line.export PATH="/Applications/Google Chrome.app/Contents/MacOS:$PATH"alias chrome-for-wordle='Google\ Chrome --remote-debugging-port=9222 --user-data-dir="~/WordleChromeProfile"'

然后在 terminal 窗口中执行

source ~/.bash_profilemkdir ~/WordleChromeProfilechrome-for-wordle

你会看到一个新的浏览器窗口。这个浏览器是我们特别配置过的。

解释一下:

  • /Applications/Google Chrome.app/Contents/MacOS” 是苹果电脑上 Chrome 程序的位置。

  • --remote-debugging-port=9222 让 Chrome 通过 9222 号端口听取 webdriver 的指令。你也可以用其它任何一个还没有被占用的端口号。

  • --user-data-dir="~/WordleChromeProfile" 让 Chrome 在 ~/WordleChromeProfile 目录下保存历史信息,这样玩游戏不会影响正常浏览的历史,重启战斗机时可以从上次的状态继续。

然后修改战斗机程序,让 driver 通过 9222 端口指挥浏览器:

from selenium.webdriver.chrome.options import Options...    chrome_options = Options()    chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")    driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()),                              chrome_options=chrome_options)

这里 127.0.0.1 是本机的 IP 地址。

再跑战斗机,你会发现,果然它没有打开一个新的窗口,而是直接在已经开着的浏览器里玩起了 wordle。

如果流程被打断了,只需重新跑 chrome-for-wordle 和战斗机就好了,它会哪里跌倒就从哪里爬起来,一点历史都不会丢掉。

有了这个神器,你需要做的就是让它一直在后台欢快地跑着,过一段时间看看历史结果,再根据心情截屏炫智。

现在我家的战斗机跑了两天,截屏是这样的:

如果你此时跃跃欲试,请到 https://github.com/zhanyong-wan/wordle-solver 阅读、下载战斗机全部源代码。或者,点文末的“阅读原文”也行。

~~~~

有人问这种玩法到底还有什么乐趣。

问得好。

乐趣就在自动化的过程。

当你不满足于手动玩游戏,开始思考如何从日复一日的劳动中解脱出来,你就发明了一种高阶的玩法。这种玩法带来的成就感,不是手动可以相比的。

等你实现了自动玩游戏,说不定你还不满足,会进一步思考怎么去自动化这个自动化,于是开发出通用人工智能,解决了机器写代码的问题,顺便名垂青史。

在程序员的字典里,懒惰是世间第一美德。

~~~~~~~~~~

确定

  • 不看此公众号

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
在Python Selenium中为Chrome和Firefox浏览器开启headless模式 | 李辉的个人网站
selenium+python自动化85-Chrome静默模式(headless)
selenium webdriver 你所不知道的quit 和close
python selenium浏览器调用(chrome、ie、firefox)
Python+selenium 自动化-启用带插件的chrome浏览器
【我问Crossin】学会 Python 离成为一名程序员还差多远?
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服