😻构建你自己的 MonkeyType CLI 版本🙈

2025-06-07

😻构建你自己的 MonkeyType CLI 版本🙈

TL;DR

在这个简单易懂的教程中,您将学习如何在几分钟内构建自己的 MonkeyType CLI 版本。😎

你将学到:✨

  • 使用 Python curses 模块构建具有WPMAccuracy支持的强大打字 CLI 应用程序。

你准备好成为 CLI MonkeyTyper 了吗?😉 无论这是你的第一个 CLI 应用,还是第 n 个应用,都欢迎随时关注。

猴子在笔记本电脑上打字


设置环境🙈

ℹ️ 无需设置虚拟环境,我们不会使用任何外部依赖项。

创建一个文件夹来保存项目的所有源代码:

mkdir cli-monkeytype-python
cd cli-monkeytype-python
Enter fullscreen mode Exit fullscreen mode

创建两个新文件,我们将在其中编写程序:

touch main.py typing_test.py
Enter fullscreen mode Exit fullscreen mode

main.py文件将作为我们应用程序的起点,并且该typing_test.py文件将保存程序的所有逻辑。

ℹ️ 对于 Linux 或 Mac 用户,您不需要下载任何依赖项,我们将主要使用cursestimerandom模块,它们都包含在 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
Enter fullscreen mode Exit fullscreen mode

所有函数名都应该一目了然。如果你连这个提纲都看不懂,还需要帮助理解每个函数的功能,那你读这篇文章还有什么意义呢?开玩笑啦,*其实不然*。😏

🥱 这是一款适合初学者的应用程序。别担心,一起来写代码吧。

真正的乐趣开始了!

演出时间 GIF

我们将从导入模块并编写__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--
Enter fullscreen mode Exit fullscreen mode

用于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--
Enter fullscreen mode Exit fullscreen mode

让我总结一下这些方法,它们非常简单:

🎯 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)
Enter fullscreen mode Exit fullscreen mode

🎯 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)
Enter fullscreen mode Exit fullscreen mode

💡 注意:我们正在从 curses 中调用方法内的 main 函数,wrapper它处理 curses 模块的初始化和清理。

在主程序中,我们创建TypingTest类的一个实例并在无限循环中运行测试,这使用户可以继续运行测试,直到他们决定按ESC退出。

让我们看看它的实际效果。🔥

打字测试演示

🫵 如果你已经读到这里,我想给你分配一个小任务。目前,我们正在从一个文件中随机选择文本进行输入。我希望你从互联网上抓取输入文本,并使用这些内容。欢迎在我的代码库中提交你的修改,并提交拉取请求。

如果你需要帮助,我已经做过一个类似的 Python 数据抓取项目。欢迎查看


总结! 🐒

到目前为止,您已经构建了一个 Python CLI 应用程序来直接在终端中测试您的打字速度。

本文的文档源代码可以在这里找到:

https://github.com/shricodev/blogs/tree/main/cli-monkeytype-python

非常感谢你的阅读!🎉🫡

在下面的评论部分写下你的想法。👇

在 Twitter 上关注我🐥

文章来源:https://dev.to/shricodev/build-your-own-cli-version-of-monkeytype-bm7
PREV
像专业人士一样使用 LLAMA-3 打造你自己的 SIRI!🧙‍♂️ 🪄
NEXT
✨使用这些工具成为 10X Linux 用户😎💫