😻构建你自己的 MonkeyType CLI 版本🙈
TL;DR
在这个简单易懂的教程中,您将学习如何在几分钟内构建自己的 MonkeyType CLI 版本。😎
你将学到:✨
- 使用 Python curses 模块构建具有WPM和Accuracy支持的强大打字 CLI 应用程序。
你准备好成为 CLI MonkeyTyper 了吗?😉 无论这是你的第一个 CLI 应用,还是第 n 个应用,都欢迎随时关注。
设置环境🙈
ℹ️ 无需设置虚拟环境,我们不会使用任何外部依赖项。
创建一个文件夹来保存项目的所有源代码:
mkdir cli-monkeytype-python
cd cli-monkeytype-python
创建两个新文件,我们将在其中编写程序:
touch main.py typing_test.py
该main.py
文件将作为我们应用程序的起点,并且该typing_test.py
文件将保存程序的所有逻辑。
ℹ️ 对于 Linux 或 Mac 用户,您不需要下载任何依赖项,我们将主要使用curses、time和random模块,它们都包含在 Python 标准库中。
⚠️注意
Windows 用户可能需要安装 curses,因为它不包含在 Windows 版 Python 标准库中。请确保在继续操作之前已安装它。
让我们开始编码吧🐵
💡 在本节中,我们将研究应用程序的方法、大纲和实际编码部分。😵💫
方法和概要👀
这里我们采用一种不同的方法,而不是把所有代码都塞进一个main
文件中。我们会把代码拆分成不同的类,放在不同的文件中。
将会有一个单独的文件,其中包含一个类,负责封装与打字测试相关的所有逻辑。在主文件中,我们将调用这个类中的方法。听起来不错吧?让我们开始吧。🚀
这是我们课程的骨架以及我们将要研究的所有方法。
class TypingTest:
def __init__(self, stdscr):
pass
def get_line_to_type(self):
pass
def display_wpm(self):
pass
def display_accuracy(self):
pass
def display_typed_chars(self):
pass
def display_details(self):
pass
def test_accuracy(self):
pass
def test_wpm(self):
pass
所有函数名都应该一目了然。如果你连这个提纲都看不懂,还需要帮助理解每个函数的功能,那你读这篇文章还有什么意义呢?开玩笑啦,*其实不然*。😏
🥱 这是一款适合初学者的应用程序。别担心,一起来写代码吧。
真正的乐趣开始了!
我们将从导入模块并编写__init__
方法开始。这将初始化程序运行所需的所有术语。
import curses
import random
import time
class TypingTest:
def __init__(self, stdscr):
self.stdscr = stdscr
self.to_type_text = self.get_line_to_type()
self.user_typed_text = []
self.wpm = 0
self.start_time = time.time()
# Initialize color pairs
curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK)
curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK)
# --SNIP--
用于stdscr
控制终端屏幕,对于创建用户可以看到其击键的基于文本的用户界面至关重要。⌨️
该get_line_to_type
方法获取一行文本供用户输入。该文本存储在self.to_type_text
变量中。用户输入时,输入的字符会保存在self.user_typed_text
列表中。我们使用列表是因为当用户更正错误字符时,弹出最后一项会更容易。
初始每分钟字数 (WPM) 分数设为 0,并记录测试的开始时间。我们还初始化了一些颜色对,用于根据字符是否正确来指示颜色。稍后,我们将根据用户输入的时间计算WPM 。
现在,添加以下函数的代码
ℹ️ 确保
typing_texts.txt
在项目根目录中创建一个名为 的新文件,其中包含几行文本。参考:点击此处。
# --SNIP--
def get_line_to_type(self):
with open("typing_texts.txt", "r", encoding="utf-8") as file:
lines = file.readlines()
return random.choice(lines).strip()
def display_wpm(self):
self.stdscr.addstr(1, 0, f"WPM: {self.wpm}", curses.color_pair(3))
def display_accuracy(self):
self.stdscr.addstr(
2,
0,
f"Accuracy: {self.test_accuracy()}%",
curses.color_pair(3),
)
def display_typed_chars(self):
for i, char in enumerate(self.user_typed_text):
correct_character = self.to_type_text[i]
# Use color pair 1 if correct, else color pair 2.
color = 1 if char == correct_character else 2
self.stdscr.addstr(0, i, char, curses.color_pair(color))
def display_details(self):
self.stdscr.addstr(self.to_type_text)
self.display_wpm()
self.display_accuracy()
self.display_typed_chars()
# --SNIP--
让我总结一下这些方法,它们非常简单:
🎯 get_line_to_type(self)
:从名为“typing_texts.txt”的文件中检索随机行,并删除尾随空格。
🎯 display_wpm(self)
:在用户输入时在屏幕上的第一行显示 WPM。
🎯 :在屏幕第 2 行display_accuracy(self)
显示准确率百分比test_accuracy()
。准确率是通过我们即将编写的方法计算的。
🎯 display_typed_chars(self)
:在屏幕上显示用户输入的字符,用一个颜色对(颜色 1)突出显示正确的字符,用另一个颜色对(颜色 2)突出显示不正确的字符。
🎯 display_details(self)
:它本质上是一个辅助函数,有助于显示上述所有显示函数的内容。
好的,现在我们已经编写了这些显示方法,让我们实现实际的逻辑来测试准确性和WPM本身。
添加以下代码行:
# --SNIP--
def test_accuracy(self):
total_characters = min(len(self.user_typed_text), len(self.to_type_text))
# If there are no typed chars, show accuracy 0.
if total_characters == 0:
return 0.0
matching_characters = 0
for current_char, target_char in zip(self.user_typed_text, self.to_type_text):
if current_char == target_char:
matching_characters += 1
matching_percentage = (matching_characters / total_characters) * 100
return matching_percentage
def test_wpm(self):
# getkey method by default is blocking.
# We do not want to wait until the user types each char to check WPM.
# Else the entire logic will be faulty.
self.stdscr.nodelay(True)
while True:
# Since we have nodelay = True, if not using max(),
# users might end up with time.time() equal to start_time,
# resulting in 0 and potentially causing a zero-divisible error in the below line.
time_elapsed = max(time.time() - self.start_time, 1)
# Considering the average word length in English is 5 characters
self.wpm = round((len(self.user_typed_text) / (time_elapsed / 60)) / 5)
self.stdscr.clear()
self.display_details()
self.stdscr.refresh()
# Exit the loop when the user types in the total length of the text.
if len(self.user_typed_text) == len(self.to_type_text):
self.stdscr.nodelay(False)
break
# We have `nodelay = True`, so we don't want to wait for the keystroke.
# If we do not get a key, it will throw an exception
# in the below lines when accessing the key.
try:
key = self.stdscr.getkey()
except Exception:
continue
# Check if the key is a single character before using ord()
if isinstance(key, str) and len(key) == 1:
if ord(key) == 27: # ASCII value for ESC
break
# If the user has not typed anything reset to the current time
if not self.user_typed_text:
self.start_time = time.time()
if key in ("KEY_BACKSPACE", "\b", "\x7f"):
if len(self.user_typed_text) > 0:
self.user_typed_text.pop()
elif len(self.user_typed_text) < len(self.to_type_text):
self.user_typed_text.append(key)
🎯 test_accuracy(self)
:通过将用户输入的字符与目标文本进行比较,计算并以百分比形式返回输入准确率。如果字符匹配,则将匹配字符的数量加 1。最后,计算匹配的百分比。
🎯 test_wpm(self)
:计算每分钟字数 (WPM) 并实时更新显示。我们使用一个公式来计算 WPM,这个公式不是我自己想出来的,而是我从网上抄来的。它会跟踪用户输入的内容,处理退格键,并在用户完成目标文本输入或按下ESC键时停止。
太棒了!这就是我们的TypingTest课程。🎉
✅ 我们以这样的方式编写代码,它将帮助我们轻松地将此代码导入到任何未来的项目中,并使维护变得更加容易。
是时候测试我们的实现了。🙈
在该main.py
文件中,添加以下代码行:
from curses import wrapper
from typing_test import TypingTest
def main(stdscr):
stdscr.clear()
stdscr.addstr("Welcome to the typing speed test")
stdscr.addstr("\nPress any key to continue!")
while True:
typing_test = TypingTest(stdscr)
stdscr.getkey()
typing_test.test_wpm()
stdscr.addstr(
3,
0,
"Congratulations! You have completed the test! Press any key to continue...",
)
stdscr.nodelay(False)
key = stdscr.getkey()
# Check if the key is a single character before using ord()
if isinstance(key, str) and len(key) == 1:
if ord(key) == 27: # ASCII value for ESC
break
if __name__ == "__main__":
wrapper(main)
💡 注意:我们正在从 curses 中调用方法内的 main 函数,
wrapper
它处理 curses 模块的初始化和清理。
在主程序中,我们创建TypingTest类的一个实例并在无限循环中运行测试,这使用户可以继续运行测试,直到他们决定按ESC退出。
让我们看看它的实际效果。🔥

🫵 如果你已经读到这里,我想给你分配一个小任务。目前,我们正在从一个文件中随机选择文本进行输入。我希望你从互联网上抓取输入文本,并使用这些内容。欢迎在我的代码库中提交你的修改,并提交拉取请求。
如果你需要帮助,我已经做过一个类似的 Python 数据抓取项目。欢迎查看。
总结! 🐒
到目前为止,您已经构建了一个 Python CLI 应用程序来直接在终端中测试您的打字速度。
本文的文档源代码可以在这里找到:
https://github.com/shricodev/blogs/tree/main/cli-monkeytype-python
非常感谢你的阅读!🎉🫡
文章来源:https://dev.to/shricodev/build-your-own-cli-version-of-monkeytype-bm7在下面的评论部分写下你的想法。👇