Jump to content

Words Per Minute Typing Speed Calculator


Go to solution Solved by Dan_555,

Recommended Posts

I've asked for help here numerous times and there's never been a time any problem I had with code wasn't solved.

Yes it might be met with scorn, but I expect that since I am not a coder myself and any working script I made is on a wing and a prayer. 😄

Anyway, with the help of Chat GPT and ironing out the bugs, I made an AutoIt GUI that counts your words per minute.

I thought I might as well share what I made.

I know... "Loads of online sites already do that" but they don't do what this does and I could not find any free tool that does what this does either.😉

With the online typing tests that count words per minute, they make you look at words on screen - but what if you aren't a typist and you have to look at the keyboard when you're typing? I know I do. This GUI doesn't give you anything to type, you can just type in whatever you want.

Also missing from any typical online test is accuracy. My GUI shows you the words per minute with 1 decimal place. Not only that, but it keeps a record (in a text file) of your entire typing history, in terms of how many words per minute you typed, then every time you run the GUI, it shows you what your all-time average typing speed is. As you use the GUI more, it builds up a history of your typing speed so you can get a very accurate average over time.

All you have to do is type something in and stop typing for at least 4 seconds and it will tell you how many words per minute you typed. No need to type for a full minute either, you can type for any amount of time. The text typed is then cleared and you're shown your word per minute count, that's then added to all the other times you tested your typing.

The only reason I made this was because none of the online typing speed tests quite worked the way I wanted and why should I need to be on some website anyway. This will work whether you have an internet connection or not.

This still has the odd bug. For example if you just type a space and leave it alone, it will say you typed about 4,000 words per minute, so don't do that. 😇

Anyway here it is. If you're interested in what speed you type at, this could be a great tool, especially because it can build up a very accurate average with a lot of usage. I'm about to get a different keyboard so, it's going to come in handy to test if my typing is any faster on the new keyboard, now I have built up a solid average on this keyboard of about 76 WPM.

 

Enjoy!

 

#include <GUIConstantsEx.au3>
#include <EditConstants.au3>
#include <StaticConstants.au3>
#include <WindowsConstants.au3>

Global $hGUI, $hEdit, $hAverageLabel, $hTypingLabel, $hTimer, $startTime, $endTime, $wordCount
Global $totalCount, $sum, $isFinished, $showAverage, $average

$hFile = FileOpen("WordsPerMinute.txt", 1)

$hGUI = GUICreate("Typing Speed Calculator", 600, 485, -1, -1, BitOR($WS_OVERLAPPEDWINDOW, $WS_CLIPCHILDREN), $WS_EX_CLIENTEDGE)
$hEdit = GUICtrlCreateEdit("", 10, 10, 580, 350, BitOR($ES_AUTOVSCROLL, $ES_WANTRETURN, $WS_VSCROLL))
GUICtrlSetState($hEdit, $GUI_FOCUS)
GUICtrlSetFont($hEdit, 16, 400, "Segoe UI")
$hAverageLabel = GUICtrlCreateLabel("", 10, 428, 580, 40, BitOR($SS_CENTER, $WS_BORDER, $SS_CENTERIMAGE, $SS_NOTIFY)) ; Add $SS_NOTIFY style
GUICtrlSetFont($hAverageLabel, 16, 700, "Segoe UI")
$hTypingLabel = GUICtrlCreateLabel("", 10, 374, 580, 40, BitOR($SS_CENTER, $WS_BORDER, $SS_CENTERIMAGE))
GUICtrlSetFont($hTypingLabel, 16, 700, "Segoe UI")
GUICtrlSetColor($hTypingLabel, 0x00008B) ; Set the color of the typing label to dark blue
GUICtrlSetColor($hAverageLabel, 0x006400) ; Set the color of the average label to dark green
GUISetFont(9, 400, "Segoe UI")
GUISetState(@SW_SHOW)

GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")
GUIRegisterMsg($WM_CLOSE, "WM_CLOSE_HANDLER")

Func UpdateAverage()
    Local $aWordsPerMinute = FileReadToArray("WordsPerMinute.txt")
    If UBound($aWordsPerMinute) > 0 Then
        Local $overallSum = 0
        For $i = 1 To UBound($aWordsPerMinute)
            $overallSum += $aWordsPerMinute[$i - 1]
        Next
        $totalCount = UBound($aWordsPerMinute)
        $sum = $overallSum
        $showAverage = True
        If $totalCount > 0 Then
            $average = Round($sum / $totalCount, 1)
        EndIf
    EndIf
EndFunc

UpdateAverage()

While 1
    If Not WinActive($hGUI) Then
        $hTimer = 0
        ContinueLoop
    EndIf

    If $hTimer And TimerDiff($hTimer) > 4000 Then
        $endTime = TimerDiff($startTime) - 4000 ; Subtract 4 seconds of inactivity
        $wordCount = _CountWords(GUICtrlRead($hEdit))
        $wordCount = Round($wordCount / ($endTime / 60000), 1) ; Calculate WPM with one decimal place
        GUICtrlSetData($hTypingLabel, "You have just typed " & $wordCount & " WPM")
        $isFinished = True

        GUICtrlSetData($hEdit, "")
        GUICtrlSetState($hEdit, $GUI_FOCUS)
        $hTimer = 0

        If $wordCount > 0 Then
            $totalCount += 1
            $sum += $wordCount
            FileWriteLine($hFile, $wordCount)
        EndIf

        UpdateAverage()
        GUICtrlSetData($hAverageLabel, "All-time average = " & $average & " WPM")
    EndIf

    If $showAverage Then
        If $totalCount > 0 Then
            GUICtrlSetData($hAverageLabel, "All-time average = " & $average & " WPM")
        EndIf
        $showAverage = False
    EndIf

    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            FileClose($hFile)
            Exit
        Case $hEdit
            If Not $hTimer Then
                $startTime = TimerInit()
                $hTimer = TimerInit()
                $isFinished = False
                GUICtrlSetData($hTypingLabel, "Timer running...")
                GUICtrlSetData($hAverageLabel, "")
            EndIf
    EndSwitch

    Sleep(100)
WEnd

Func WM_COMMAND($hWnd, $iMsg, $wParam, $lParam)
    Local $nCode = BitShift($wParam, 16)
    Local $nID = BitAND($wParam, 0xFFFF)

    Switch $nID
        Case $hEdit
            If $nCode = $EN_CHANGE Then
                If Not $hTimer Then
                    $startTime = TimerInit()
                    $hTimer = TimerInit()
                    $isFinished = False
                    GUICtrlSetData($hTypingLabel, "Timer running...")
                    GUICtrlSetData($hAverageLabel, "")
                Else
                    $hTimer = TimerInit()
                EndIf
            EndIf
    EndSwitch

    Return $GUI_RUNDEFMSG
EndFunc

Func WM_CLOSE_HANDLER($hWnd, $iMsg, $wParam, $lParam)
    Return 0 ; Return 0 to prevent the GUI from closing
EndFunc

Func _CountWords($sText)
    Local $aWords = StringRegExp($sText, "[^\s]+", 3)
    Local $wordCount = UBound($aWords)
    If $wordCount = 1 And StringLen($sText) = 1 Then
        Return 0
    EndIf
    Return $wordCount
EndFunc

 

 

Edited by TalesFromTheScript
Link to comment
Share on other sites

  • Solution

Funny.

 

But there are some bugs.

This code will show you the problem:

Test(" ")
Test("a")
test("b ")
Test("aa")
test("ab ")
Test("a a ")
Test("bb aa cc")

Func test($a)
    $b = StringSplit($a, " ")
    ConsoleWrite("Testing '" & $a & "' = " & @error & " Word count: " & $b[0] & @CRLF)
EndFunc   ;==>test

 

Edited by Dan_555

Some of my script sourcecode

Link to comment
Share on other sites

Thanks for pointing that out. I have amended the code to handle consecutive spaces (counts as 0 words) and if just one character is typed without spaces (counts as 0 words). Also put a thing in to not add it to the text file if it's 0 (not that it would change the average). 😛

Link to comment
Share on other sites

I like this @TalesFromTheScript and was testing it out when I noticed some issues, which got me into editing it a bit. Now a couple hours later I've changed quite a bit, but kept your main idea there I believe. Check it out:

#include <Constants.au3>
#include <GUIConstantsEx.au3>
#include <EditConstants.au3>
#include <StaticConstants.au3>
#include <WindowsConstants.au3>
#include <File.au3>
#include <Array.au3>

Global $hEdit, $hAverageLabel, $hTypingLabel, $hFile

Main()

Func Main()
    $hFile = FileOpen("WordsPerMinute.txt", BitOR($FO_APPEND, $FO_CREATEPATH))
    If @error Or $hFile = -1 Then
        MsgBox($MB_ICONERROR, 'Unable to open stats file', 'Unable to open/create "WordsPerMinute.txt" file. Please check that the file exists, or that there are permissions to create/open files in this directory')
        Exit
    EndIf

    CreateGUI()
    UpdateAverage()

    Local $hTimer = False, $hStartTime = False
    Local $bIsFinished = True
    Local $iWordCount = 0, $iRemainingSeconds = 0, $iTimeoutSeconds = 4, $iCharacterCount = 0
    Local $nAverage = 0, $nMsg = 0, $nActiveTime = 0
    Local $sCurrentEdit = '', $sPreviousEdit = ''

    While 1
        Do ; This is just to process any/all messages from the GUI first, to make sure it's responsive to closing
            $nMsg = GUIGetMsg()
            Switch $nMsg
                Case $GUI_EVENT_NONE
                    ExitLoop
                Case $GUI_EVENT_CLOSE
                    FileClose($hFile)
                    Exit
            EndSwitch
        Until $nMsg <= 0

        $sCurrentEdit = GUICtrlRead($hEdit)
        If $sCurrentEdit <> $sPreviousEdit Then
            If $bIsFinished Then
                $hStartTime = TimerInit()
                GUICtrlSetData($hTypingLabel, "Timer running...")
            EndIf

            $hTimer = TimerInit()
            $bIsFinished = False

            $sPreviousEdit = $sCurrentEdit
        EndIf

        If Not $bIsFinished And TimerDiff($hTimer) > ($iTimeoutSeconds * 1000) Then
            $bIsFinished = True
            $nActiveTime = TimerDiff($hStartTime) - TimerDiff($hTimer) ; Subtract inactivity since the last input
            $iWordCount = _CountWords($sCurrentEdit)
            $iCharacterCount = StringLen($sCurrentEdit)
            $nAverage = Round($iWordCount / ($nActiveTime / 60000), 1) ; Calculate WPM with one decimal place
            GUICtrlSetData($hTypingLabel, "You have just typed " & $nAverage & " WPM")

            GUICtrlSetData($hEdit, "")
            ; Previous and Current need to be set to the same value so we can track a new 'session'
            $sPreviousEdit = ''
            $sCurrentEdit = ''
            GUICtrlSetState($hEdit, $GUI_FOCUS)

            If $iWordCount > 0 Then
                ; Write a CSV of:
                ; ComputedAverage,WordCount,ActiveTypingTime,CharacterCount
                FileWriteLine($hFile, $nAverage & ',' & $iWordCount & ',' & $nActiveTime & ',' & $iCharacterCount)
                UpdateAverage()
            EndIf
        EndIf

        If Not $bIsFinished And TimerDiff($hTimer) < ($iTimeoutSeconds * 1000) And TimerDiff($hTimer) >= 1000 Then
            $iRemainingSeconds = $iTimeoutSeconds - Floor(TimerDiff($hTimer) / 1000)
            If GUICtrlRead($hTypingLabel) <> 'Ending in ' & $iRemainingSeconds & '...' Then
                GUICtrlSetData($hTypingLabel, 'Ending in ' & $iRemainingSeconds & '...')
            EndIf
        EndIf
    WEnd

    Return True
EndFunc   ;==>Main

Func UpdateAverage()
    Local Enum $e_WPM_CSV_AVERAGE, $e_WPM_CSV_WORDCOUNT, $e_WPM_CSV_TIME, $e_WPM_CSV_CHARACTERCOUNT, $e_WPM_CSV_MAX
    Local $aWordsPerMinute

    ; Set cursor position to the beginning of the file to read it
    FileSetPos($hFile, 0, $FILE_BEGIN)
    _FileReadToArray($hFile, $aWordsPerMinute, $FRTA_NOCOUNT, ',')
    ; And then set the cursor back to the end of the file so we can write to it
    FileSetPos($hFile, 0, $FILE_END)
;~  _ArrayDisplay($aWordsPerMinute)

    If UBound($aWordsPerMinute) <= 0 Then
        Return SetError(1, 0, False)
    EndIf

    Local $nOverallSum = 0, $nAverage = 0
    Local $iRecords = 0

    For $iLine = 0 To UBound($aWordsPerMinute) - 1
;~      ConsoleWrite('$aWordsPerMinute[' & $iLine & '][' & $e_WPM_CSV_AVERAGE & ']: ' & $aWordsPerMinute[$iLine][$e_WPM_CSV_AVERAGE] & @CRLF)
        If Number($aWordsPerMinute[$iLine][$e_WPM_CSV_AVERAGE]) <= 0 Then ContinueLoop
        $nOverallSum += $aWordsPerMinute[$iLine][$e_WPM_CSV_AVERAGE]
        $iRecords += 1
    Next

    If $iRecords > 0 Then
        $nAverage = Round($nOverallSum / $iRecords, 1)
        GUICtrlSetData($hAverageLabel, "All-time average = " & $nAverage & " WPM")
        Return SetExtended($iRecords, $nAverage)
    EndIf

    Return SetError(2, 0, False)
EndFunc   ;==>UpdateAverage

Func _CountWords($sText)
    Local $aWords = StringRegExp(StringStripWS($sText, BitOR($STR_STRIPLEADING, $STR_STRIPTRAILING, $STR_STRIPSPACES)), "[^\s]+", 3)
    Local $iWordCount = UBound($aWords)
    If $iWordCount = 1 And StringLen($sText) = 1 Then
        Return 0
    EndIf
    Return $iWordCount
EndFunc   ;==>_CountWords

Func CreateGUI()
    Local $hGUI = GUICreate("Typing Speed Calculator", 600, 485, -1, -1, BitOR($WS_OVERLAPPEDWINDOW, $WS_CLIPCHILDREN), $WS_EX_CLIENTEDGE)
    If @error Then Return False
    $hEdit = GUICtrlCreateEdit("", 10, 10, 580, 350, BitOR($ES_AUTOVSCROLL, $ES_WANTRETURN, $WS_VSCROLL))
    GUICtrlSetState($hEdit, $GUI_FOCUS)
    GUICtrlSetFont($hEdit, 16, 400, "Segoe UI")
    $hAverageLabel = GUICtrlCreateLabel("All-time average: 0 WPM", 10, 428, 580, 40, BitOR($SS_CENTER, $WS_BORDER, $SS_CENTERIMAGE, $SS_NOTIFY)) ; Add $SS_NOTIFY style
    GUICtrlSetFont($hAverageLabel, 16, 700, "Segoe UI")
    $hTypingLabel = GUICtrlCreateLabel("Start typing anything to begin", 10, 374, 580, 40, BitOR($SS_CENTER, $WS_BORDER, $SS_CENTERIMAGE))
    GUICtrlSetFont($hTypingLabel, 16, 700, "Segoe UI")
    GUICtrlSetColor($hTypingLabel, 0x00008B) ; Set the color of the typing label to dark blue
    GUICtrlSetColor($hAverageLabel, 0x006400) ; Set the color of the average label to dark green
    GUISetFont(9, 400, "Segoe UI")
    GUISetState(@SW_SHOW, $hGUI)

    Return True
EndFunc   ;==>CreateGUI

I originally started editing it because I noticed you had a Sleep(100), which is much longer than is needed (Sleep(10) which is the minimum is still WAY more than enough to keep it at/near 0% CPU). Additionally since you're using GUIGetMsg(), a Sleep isn't needed at all. From the helpfile: 

Quote

This function automatically idles the CPU when required so that it can be safely used in tight loops without hogging all the CPU.

Also, because of this:

While 1
    If Not WinActive($hGUI) Then
        $hTimer = 0
        ContinueLoop
    EndIf

You were creating two issues. First, since this was at the top of the loop with no Sleep or GUIGetMsg, when the window wasn't active you were using 100% of the CPU core that the AutoIt process was on. I assume you did this to try and prevent unnecessary processing, but created a loop that ended up eating resources doing nothing. This also let GUI msgs stack up since they weren't being processed by GUIGetMsg, so closing the window could take several seconds if you just moved your mouse over it a bit before closing it, since it has to go through your whole loop several times to finally get to and process the close message.

It started with that, and then I decided to just start changing things to be more inline with how I would write things. I may not be your style or what you wanted, but feel free to use my script or anything in it for yourself.

I also noticed that you had:

If $hTimer And TimerDiff($hTimer) > 4000 Then
        $endTime = TimerDiff($startTime) - 4000 ; Subtract 4 seconds of inactivity

Which seemed a bit incorrect to me, since you're assuming a set timer value, but which could be off by up to at least 350ms (100 from Sleep, up to 250 from GUIGetMsg). So I changed it to the time from the last action.

I do also like the WM_COMMAND function and what it can do, however I have experienced input delay/lag when using it with other projects, and I figured there was an easier way. I switched it to just a GUICtrlRead comparing a previous value to the current.

I added some additional logging to the file besides just the average WPM. I included the word count, time spent typing, and character count. This way the data is there if you wanted to add some more features/stats into it later.

I definitely changed a lot, and it's just more to what my preferences are. If you have any questions about some of the changes, let me know.

Edit: Also since I changed the .txt format from a single value to a CSV, you will need to create a new file since _FileReadToArray will error if there's a line that has a different size unless you use $FRTA_INTARRAYS

Edited by mistersquirrle
Info

We ought not to misbehave, but we should look as though we could.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...