阿川私房教材:學程式,拿 offer!

63 個專案實戰,直接上手!
無需補習,按步驟打造你的面試作品。

立即解鎖你的轉職秘笈

長話短說

在這個易於理解的教程中,您將學習如何在幾分鐘內建立自己的 MonkeyType CLI 版本。 😎

您將學到什麼:✨

  • 使用 Pythoncurses 模組建立具有WPMAccuracy支援的強大打字 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用戶,您不需要下載任何依賴項,我們將主要使用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

所有函數名稱都應該是不言自明的。如果您需要協助理解每個函數的作用,即使在查看了此大綱之後,為什麼還要閱讀這篇文章?只是開玩笑*不是真的*。 😏

🥱 這是一個適合初學者的應用程式。別擔心,一起編碼吧。

真正的樂趣開始了!

表演時間 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--

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。

🎯 display_accuracy(self) :在螢幕上第 2 行顯示準確率百分比。準確率由我們即將編寫的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 wrapper方法內的main 函數,該函數處理curses 模組的初始化和清理。

在 main 中,我們建立TypingTest類別的實例並在無限循環中執行測試,這讓使用者可以繼續執行測試,直到他們決定按ESC退出。

讓我們看看它的實際效果。 🔥

打字測試演示

🫵 如果你已經做到了這一步,我想指派一個小任務給你。目前,我們正在從文件中隨機選擇文字進行輸入。我希望您從網路上抓取輸入文字並使用該內容。請隨意在我的儲存庫中開啟包含您的變更的拉取請求。

如果您需要幫助,我已經參與了一個類似的 Python 抓取專案。請隨意檢查一下。


包起來! 🐒

到目前為止,您已經建立了一個 Python CLI 應用程式來測試您在終端機中的打字速度。

本文的記錄原始碼可在此處取得:

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

非常感謝您的閱讀! 🎉🫡

在下面的評論部分寫下你的想法。 👇

{% cta https://twitter.com/shricodev

%} 在 Twitter 上追蹤我 🐥 {% endcta %}

{% 嵌入 https://dev.to/shricodev %}


原文出處:https://dev.to/shricodev/build-your-own-cli-version-of-monkeytype-bm7


共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。

阿川私房教材:學程式,拿 offer!

63 個專案實戰,直接上手!
無需補習,按步驟打造你的面試作品。

立即解鎖你的轉職秘笈