TalesFromTheScript Posted July 28, 2023 Share Posted July 28, 2023 (edited) 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!  expandcollapse popup#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 July 28, 2023 by TalesFromTheScript Dan_555 1 Link to comment Share on other sites More sharing options...
Solution Dan_555 Posted July 28, 2023 Solution Share Posted July 28, 2023 (edited) 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 July 28, 2023 by Dan_555 Some of my script sourcecode Link to comment Share on other sites More sharing options...
TalesFromTheScript Posted July 28, 2023 Author Share Posted July 28, 2023 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 More sharing options...
mistersquirrle Posted July 31, 2023 Share Posted July 31, 2023 (edited) 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: expandcollapse popup#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 July 31, 2023 by mistersquirrle Info We ought not to misbehave, but we should look as though we could. Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now