Handling Script Errors
Overview
Every programmer, no matter how good he or she may be, runs into errors when writing and testing scripts and programs. Like any other programming language, VBScript is subject to a number of different types of errors. Errors may be inevitable, but there are several things you can do to minimize their number or lessen their effects. In this chapter I'll demonstrate a number of scripting errors and show you ways of dealing with them. Specifically, you will learn how to:
- Fix errors by reading and analyzing error messages
- Write VBScripts that can ignore errors and keep going
- Create error-handling routines to recover from many error situations
- Generate test errors in order to validate the performance of your error-handling routines
- Keep a record of errors using log files and the Windows application event log
Project Preview The Hangman Game
This chapter's programming project is the creation of a VBScript version of the classic children's game Hangman. The development of this game will require you to use all of the VBScript knowledge you've accumulated so far, including applying advanced conditional logic, organizing critical processes into procedures, and validating player input in order to prevent errors from prematurely terminating the game.
The Hangman game begins by presenting the player with a number of blank spaces representing the letters of the game's mystery word. The player is then allowed to begin guessing the letters that make up the word. If the player guesses the word before making six incorrect guesses, he or she wins. Otherwise, the game ends by displaying the mystery word, and the player is asked if he or she would like to play again. Unfortunately, because of the display limitations of the WSH, you won't be able to actually animate a hanging in the event that the player looses. Still, by creating a well-formatted output, the player will probably never even notice.
Figures 8.1 through 8.5 demonstrate the overall flow of the game from beginning to end.
Figure 8.1: The Hangman game begins with a graphical welcome message and an invitation to play.
Figure 8.2: The game displays both the letters that the player has correctly guessed and those that he or she has incorrectly guessed.
Figure 8.3: A number of possible messages may be displayed if the player does not play the game correctly.
Figure 8.4: Each game ends by displaying the hidden word, the results of the game, and an invitation to play again.
Figure 8.5: A splash screen is displayed when the player decides to stop playing.
Understanding VBScript Errors
Errors can appear, even in small scripts, for a number of reasons. Errors may be generated when a script is first loaded or interpreted. Errors occurring at this stage are referred to as syntax errors. Syntax errors are often the result of typos. For example, you might accidentally type a single quote when you meant to type a closing double quote. Syntax errors also occur when you inadvertently mistype a VBScript keyword. Because syntax errors are discovered during the initial loading of a script, they are usually easily caught and corrected during script development.
Definition
A syntax error is an error that occurs as a result of not properly formatting statements within scripts.
Errors may also be generated during script execution. These types of errors are referred to as run-time errors. Run-time errors only appear when the statements that generate them are executed. As a result, some run-time errors may not be detected when the script executes (if the statement containing the error is not executed). For example, a run-time error may be hidden deep within a function or subroutine that is seldom called.
Definition
A run-time error is an error that occurs when a script tries to perform an illegal action, such as multiplying a numeric and a character value.
With proper testing of all the components of a script, most run-time errors can be discovered and fixed during script development. I say "most" because not all runtime errors can be caught—those caused by unforeseen circumstances may be impossible to detect during script development. Perhaps the person running the script incorrectly supplied input in a manner that you could not have anticipated, or perhaps something is wrong with the environment in which the script is being executed. Or maybe the hard disk has become full, preventing your VBScript from writing to a file, or the network goes down as your script is executing a file copy or move operation. In cases such as these, often the best you can do is to end the script gracefully, without confusing the user or making the situation worse.
Definition
A logical error is an error produced as a result of a programming mistake on the part of the script developer.
Another category of error to which scripts are susceptible is logical errors. Logical errors are mistakes made by the script developer. For example, instead of looping ten times, you might accidentally set up an endless loop. Another example of a logical error is a situation in which a script adds two numbers that should have been multiplied.
Understanding Error Messages
VBScript error messages are generated for both syntax and run-time errors. These errors are displayed in the form of popup dialogs, as demonstrated in Figure 8.6. Each error message displays information about the error, including a brief description of the error, an error number, and the source of the error.
Figure 8.6: A typical VBScript error message
TRICK |
Each VBScript syntax and run-time error is assigned an error number. Depending on the execution host, this number may be displayed either as a decimal number or a hexadecimal number. Error messages produced by VBScripts processed by the WSH are displayed with a hexadecimal number. Microsoft's VBScript error message documentation, which you will find at www.msdn.microsoft.com/scripting, only lists VBScript's error messages by their decimal numbers. You can translate between a VBScript error's hexadecimal and decimal number. First, drop the 800A portion of the message. Then open the Windows Calculator application in scientific mode, select the hexadecimal setting and type the last four digits of the hexadecimal number. Select the decimal setting, and the calculator will show the decimal equivalent. In Figure 8.6, 800A03F9 is the hexadecimal equivalent to the decimal number 1017. If this seems like too much work, just refer to this chapter's Tables 8.1 and 8.2, which list VBScript syntax and run-time errors, and you'll see that I've already done the math for you.
|
The error message displayed in Figure 8.6 is the result of a syntax error. As you can see, a lot of useful information about the error is automatically provided. The ability to interpret and understand this information is critical for trouble-shooting and fixing your VBScripts.
The following information has been provided in this error message:
- Script. The name and location of the VBScript that produced the error.
- Line. The line number within the VBScript where the error was detected.
- Char. The column number position within the line where the error was detected.
- Error. A brief description of the error.
- Code. An error number identifying the type of error.
- Source. The resource that reported the error.
You can see in Figure 8.6 that a VBScript named X.VBS located in C:Temp generated the error. Line 6 of the script that generated this error looks like the following statement:
If X > 5 MsgBox "Hello World!"
This If statement uses the VBScript MsgBox() function to display a text string. The error message indicates that the problem is that VBScript expected to find the 'Then' keyword and did not. If you take a look at the middle of this statement you'll see that, in fact, the Then keyword is absent. To correct this error, you would add the missing keyword, like this:
If X > 5 Then MsgBox "Hello World!"
To verify that the error has been eliminated, you could then save and run the script again.
Fixing Syntax Errors
VBScripts are subject to many different types of syntax errors, which Microsoft documents as part of the VBScript documentation available at http://www.msdn.microsoft.com/scripting. For your convenience, I've provided this information in Table 8.1. In addition, I listed both the decimal and hexadecimal error number associated with each error.
Catching Run Time Errors
VBScripts are also subject to a wide range of possible run-time errors. Microsoft documents these errors as part of their VBScript documentation, which is available at http://msdn.microsoft.com/scripting. For your convenience, I've provided this information in Table 8.2. In addition, I listed both the decimal and hexadecimal error numbers for each run-time error.
Preventing Logical Errors
Your VBScripts will do exactly what you tell them to do, even if that's not what you really mean for them to do. Therefore, it's extremely important that you plan your VBScript project carefully. For example, you should begin with a pseudo code outline and then translate that into a flowchart, outlining each of the script's major components. Then you should, as much as possible, develop the script a component at a time, testing each component as you go. I'll show you some different ways to test individual script components as you develop the Hangman game.
Logical errors often make their presence known by presenting incorrect or unexpected results, and can be the most difficult type of error to track down. Unlike syntax and run-time errors, which display messages that describe the nature of their problems, logical errors force you to look through some or all of your script, a line at a time in order to find the faulty logic. The good news is that with careful planning and design, logical errors can be avoided.
Dealing with Errors
There are many measures you can take to prevent errors from occurring in your VBScripts. I have already mentioned the need to plan and carefully design and test your scripts. In addition, you can avoid many errors by taking the following advice:
- Provide a simple and easy to use interface (such as popup dialogs).
- Provide clear instructions, so that the user will understand exactly what is expected of him or her.
- Reuse code from existing scripts whenever possible by cutting and pasting code that has already been thoroughly tested.
- Validate input data as much as possible.
- Explicitly declare all your variables.
- Use a consistent naming scheme for all constants, variables, object references, arrays, functions, and subroutines.
- Be on guard for endless loops.
- Do your best to anticipate and handle specific situations where errors are likely to occur.
Unfortunately, errors will occur. You have three basic ways that you can deal with them as they arise in your VBScripts. One option is to simply let them happen and then deal with the consequences, as problems are uncovered. Another option is to tell VBScript to ignore errors and keep going. Finally, you can attempt to anticipate where errors are most likely to occur and try to handle them in such a way as to either terminate the script's execution gracefully, or allow the script to recover and keep going.
Letting Errors Happen
Errors are going to happen. One way of dealing with them is to simply let them happen and instruct users to report them when they occur, along with as much information as possible about what the user was doing when the error occurred.
This way, you can attempt to reproduce the error, figure out what caused it, and then fix it.
Normally I would not recommend this approach. After all, your reputation as a VBScript guru depends on the soundness and reliability of your scripts. However, there is a cost associated with every VBScript that you write. You may measure this cost in terms of time, effort, or by some other scale. Each time you sit down to create a new script, you must make a judgement call as to how much time and energy you have available to put into the project. You also need to consider the consequences of an error occurring in the script that you're developing. After all, it is entirely possible to develop a simple script in a matter of minutes and spend another hour or more trying to make it bulletproof, only to find that something has gone wrong anyway.
If you're developing an extremely important script that will have a high visibility, and for which you will be held accountable if a problem arises, then you will want to do everything that you can to keep errors from happening. On the other hand, if you have been asked to create a "quick and dirty" script to help someone perform a noncritical task, you may be able to get away with ignoring any errors that occur. All you may need to do is tell the person for whom you wrote the script to give you call if a problem arises, so that you can make a quick modification to the script in order to fix it.
Just keep this thought in mind: Most users will have no idea at all what a typical VBScript error message means or what to do if they receive one. It is therefore important to, at a minimum, provide clear instructions on how to use your VBScripts and what to do if an error does occur.
Ignoring Errors
Another option that you may wish to consider when developing your scripts is to tell VBScript to ignore any errors that occur. In some cases this will work just fine. For example, suppose you wrote a VBScript that was supposed to connect to a number of networked computers and copy over a file located in a certain folder at regular intervals throughout the day. As problems sometimes occur on networks, it may be acceptable to ignore situations in which the script is unable to connect to a particular network drive, especially if you know that the script will run again later and get another chance to copy the missing file. This approach, while effective, should be used with caution. There are few situations in which skipping an error will not result in the generation of another error later in a script. For example, if your script was supposed to perform another operation on each file copied from the network drive, then, depending on how you wrote the script, the part of the script that performs this next step might generate an error.
To tell VBScript to ignore errors within your script, add the following statement, exactly as shown, to the beginning of your script:
On Error Resume Next
However, certain errors will still be reported, even if you have added this statement to your scripts. The key to using the previous statement is that it must be placed in your script before any statements in which you think an error is likely to occur. You can later cancel the effects of this statement using the following statement:
On Error GoTo 0
For example, the following statement will produce an error message because the keyword WScript is misspelled:
WScrip.Echo "Hello world!"
Adding On Error Resume Next before the statement prevents the error from appearing and allows the rest of the script to continue.
On Error Resume Next WScrip.Echo "Hello world!"
Now look at two more statements.
On Error Resume Next WScrip.Echo "Hello world!" On Error Goto 0 WScrip.Echo "Goodbye world!"
The On Error Goto 0 statement nullifies the effects of the On Error Resume Next statements for all statements that follow. Therefore, the first error is ignored, but the second error will be reported and the script will halt its execution.
Like with variables, VBScript allows you to localize the effects of the On Error Resume Next statement to the procedure level. In other words, if this statement is placed within a procedure, then it will only be in effect for as long as the procedure executes, and will be nullified when the procedure (that is, the function or subroutine) finishes executing. The advantage of combining the On Error Resume Next statement with procedures is that doing so allows you to significantly limit the effects of this powerful statement.
Creating Error Handlers
The third option that you have for dealing with errors in your VBScripts is to create error handlers. In order to effectively use error handlers, you need to be able to anticipate locations within your scripts where errors are likely to occur and then develop the appropriate programming logic to deal with or handle these errors.
You can handle errors in different ways. For example, you can create error handlers that
- Reword cryptic VBScript errors
- Provide the user with instructions
- Give the user another try
- Apologize for the error
- Ask the user to report the error
- Take a corrective action
- Log the occurrence of the error
Definition
An event handler is an error-triggered routine that alters the execution environment's default handling of an error condition.
In order to set up an error handler, you need to know how to work with the Err object. The Err object provides a number of properties and methods that allow your scripts to access error information and clear error conditions. To access information about an error, you'll need to reference the following three properties:
- Number. Retrieves the last error number.
- Description. Retrieves the last error message.
- Source. Retrieves the name of the object that raised (or caused) the error.
You can also modify the contents of any of these three properties. In doing so, you are able to reassign a custom error number and message, and even modify source information. For example, in a particularly complex script you might want to create and document your own custom set of error messages.
The first step in creating an error handler is to add the On Error Resume Next statement to your VBScript. You can then add the error handling statements like this:
On Error Resume Next NonExistentFunction() If Err > 0 then Err.Number = 9999 Err.Description = "This script is still a work in progress." MsgBox "Error: " & Err.Number & " - " & Err.description Err.Clear End if
Save these statements as a script and execute them. As the script does not contain a procedure named NonExistentFunction(), an error will be generated. However, instead of displaying a VBScript run-time error message, the error handling routine creates and displays the error message shown in Figure 8.7.
Figure 8.7: A custom error message generated by a VBScript error handler
The Err object also provides two very useful methods. One of these methods is the clear() method. This method clears out or removes the previous error, ensuring that the next time a script checks for an error, it will not get a false status (that is, it won't see an already handled error).
To use the clear() method, place it at the end of your error handling routine, as demonstrated in the previous example. VBScript automatically executes the clear() method on a number of occasions, including
- Whenever the On Error Resume Next statement executes
- Whenever an Exit Sub statement executes
- Whenever an Exit Function statement executes
The second Err object method is the raise() method. This method allows you to generate error messages in order to test your error-handling routines. Without this method, the only way that you could test your error-handling routines would be to deliberately introduce an error situation into your code. This method is easy to use, as demonstrated by the following:
Err.Raise(500)
For example, if you save the previous statement as a script and run it, you will see the error message shown in Figure 8.8.
Figure 8.8: Using the Err object Raise() method to generate a test error
To use the Raise() method add it, along with the error number indicating the error that you wish to generate, just before the error handling procedure that you wish to test in your script. Once you have validated that the error handler is working as expected, remove the Raise() statement from your VBScript.
Reporting Errors
The best solution for errors is to prevent them from occurring in the first place. However, that's not always possible. The next best solution is to devise a way of dealing with errors, whether it be handling them or simply ignoring them. Another option you have is to report errors by recording them to a log file for later review. This allows to you come back and check to see if any errors have occurred. This is important because many time users will not report errors when they occur, allowing errors to go on forever. By logging error messages you provide yourself with an audit log that you can come back to and review from time to time in order to identify and fix any errors that may have occurred.
When logging error messages, you have two options. The first option is to create your own custom log file. The second option is to record error messages in the Windows application event log. The second option, however, is only available if the script is running on a computer running Windows NT, 2000, or XP.
Creating a Custom Log File
To create a custom log file, you must instantiate the FileSystemObject in your VBScript.
Set FsoObject = WScript.CreateObject("Scripting.FileSystemObject")
It's a good idea to check and see if the file to which you want to write already exists before trying to create it or write to it. You can accomplish this task using the FileSystemObject object's FileExists() method.
If (fsoObject.FileExists("C:ScriptLog.txt")) Then
Based on the results of the previous statement, you can elect to either create a new file if one does not already exist, or open the file if it does exist. Either way, you will need to use the FileSystemObject object's OpenTextFile() method. This method requires that you tell it the name and location of the file, how you want to open it, and what to do it if does not already exist.
There are three ways to open a file. Table 8.3 lists them.
Constant |
Value |
Description |
---|---|---|
ForReading |
1 |
Opens a file for reading |
ForWriting |
2 |
Opens a file for writing |
ForAppending |
8 |
Opens a file for appending |
Table 8.4 lists the options that tell VBScript what to you if the file does or does not already exist.
Value |
Description |
---|---|
True |
If the file already exists, then open it. Otherwise create and open it. |
False |
If the file already exists, then open it. Otherwise, do not create and open it. |
Look at the next example. The On Error Resume Next statement allows the script to recover from errors. The Err.Raise(7) statement simulates an "Out of memory" error. The rest of the script logs the error in a file called ScriptLog.txt, located on the computer's C: drive. If the file does not exist, it is created. Error messages are appended to the bottom of the file each time they are written, allowing a running history of information to accumulate.
On Error Resume Next Err.Raise(7) Set FsoObject = WScript.CreateObject("Scripting.FileSystemObject") If (fsoObject.FileExists("C:ScriptLog.txt")) Then Set LogFile = fsoObject.OpenTextFile("C:ScriptLog.txt", 8) Else Set LogFile = fsoObject.OpenTextFile("C:ScriptLog.txt", 2, "True") End If LogFile.WriteLine "Test.vbs Error: " & Err.Number & ", Description = " & _ Err.Description & ", Source = " & Err.Source open_File.Close()
You could adapt the previous example as the basis for developing an error-logging routine for your VBScripts. Just copy and paste all but the first two lines into a function and call it whenever errors occur. Just make sure that you call the function before clearing the error. Alternatively, you could modify the example to use variable substitution and pass the function the error number and description as arguments.
TRAP |
Make sure that you always close any file that you open before allowing your script to terminate. If you don't, you may have problems with the file the next time you want to open it because its end-of-file marker may be missing. |
Recording an Error Message in the Application Event Log
An alternative to creating custom log files for your scripts is to record error messages in the Windows application event log. This is achieved using the WshShell object's LogEvent() method.
On Error Resume Next Err.Raise(7) Set WshShl = WScript.CreateObject("WScript.Shell") WshShl.LogEvent 1, "Test.vbs Error: " & Err.Number & ", Description = " & _ Err.Description & ", Source = " & Err.Source
In this example, an "Out of memory" error has again been simulated, only this time, the error has been written to the Windows application event log using the WshShell object's LogEvent() method. Only two arguments were processed. The first is a number indicating the type of event being logged. Table 8.5 lists the different types of events that are supported by Windows. The second argument was the message to be recorded. Figure 8.9 shows how the message will appear when viewed from the Event Viewer.
Value |
Description |
---|---|
0 |
Indicates a successful event |
1 |
Indicates an error event |
2 |
Indicates a warning event |
4 |
Indicates an informational event |
8 |
Indicates a successful audit event |
16 |
Indicates a failed audit event |
Figure 8.9: Writing error messages to the Windows application event log using the WshShell object's LogEvent() method.
Back to the Hangman Game
Now that you've reviewed the basic steps involved in overcoming VBScript errors, let's return to the Hangman game and begin its development. I'm going to cover the development of this game from a different angle than in previous chapters. By now, you should have a pretty good idea of how things work, and you should be able to read and understand the scripts that you'll see throughout the remainder of this book (just in case, I'll leave plenty of comments in the code to help you along). What I am going to do this time is provide a much higher explanation of what is going on and offer you suggestions for ways to test and develop this script one step at a time. I'll also point out techniques that you can use to test and track the results of functions within the script, so that you can validate their operation without having to first complete the entire script.
Designing the Game
The overall design of the Hangman game is fairly complex. To simplify things, I thought I'd begin the game development process by designing a flowchart, shown in Figure 8.10, that breaks the game down into distinct units, each of which is responsible for performing a unique task.
Figure 8.10: A flowchart providing a high-level design for the Hangman game
In addition to the Initialization and Main Processing sections, this script will be made up of 13 separate procedures. Therefore you will develop this game in 15 steps, as follows:
- Create a new script, adding your VBScript template and defining the variables, constants, and arrays, used by this script.
- Develop the controlling logic for the main processing section.
- Using the DoYouWantToPlay() function, create a introductory game splash screen and determine if the user wants to play.
- Assign a list of game words to an array using the FillArray() function.
- Create a loop in the PlayTheGame() function that controls the actual flow of the game, collecting player guesses and calling other functions as required.
- Retrieve a randomly selected game word using the RetrieveWord() function.
- Display space-separated underscore characters representing each letter in the game word using the InitialDisplayString() function.
- Using the FirstLevelValidation() function, validate the player's input to make sure the player is providing valid guesses
- Using the SecondLevelValidation() function, test to determine if the player has already tried guessing a letter before accepting it as input.
- Using the TestLetterGuess() function, check to see if the player made an incorrect guess.
- Using the NonGuessedString() function, create a temporary string blanking out the letters correctly guessed by the player
- Using the CheckIfGameWon() function, check to see if the player has guessed all the letters that make up the mystery word.
- Using the FlipString() function, spin through the script created in step 11, and reverse the display of each character of the string (that is, now only show the correctly guessed letters).
- Tell the player whether he or she won or lost using the DisplayGameResults() function.
- Display information about the game as it finishes using the SplashScreen() function.
Setting Up the Script Template and Initialization Section
This portion of the script, shown below, should look pretty familiar to you by now, and does not require much explanation. As you can see from the code below, this section consists of the script template and the definition of the script's constant, variables, and an array.
'************************************************************************* 'Script Name: Hangman.vbs 'Author: Jerry Ford 'Created: 02/30/02 'Description: This script demonstrates how to create a game of Hangman 'using VBScript and the WSH '************************************************************************* 'Initialization Section Option Explicit Const cTitlebarMsg = "VBScript HANGMAN" Dim Choice, GameWord, NoMisses, NoRight, SplashImage, PlayOrNot, MsgText Dim PlayAgain, WrongGuesses, RightGuesses, WordGuessed, LetterCounter Dim TempStringTwo, WordLetter, DisplayString, FlipCounter, TempStringOne Dim RandomNo, ProcessGuess, GameStatus, CheckAnswer Dim WordList(9) 'Define an array that can hold 10 game words
Putting Together the Logic for the Main Processing Section
Like the other scripts that you have seen in this book, the logic located in the script's main processing section is very straightforward. It calls upon procedures that determine if the user wants to play, loads the game words into an array, starts the game, and ultimately ends the game by displaying a splash screen and executing the WScript.Quit() statement.
'Main Processing Section PlayOrNot = DoYouWantToPlay() If PlayOrNot = 6 Then 'User elected to play the game FillArray() PlayTheGame() End If SplashScreen() WScript.Quit()
TRICK |
At this point in the script, you have enough code in place to run your first test and see whether there are any syntax errors. For now, I recommend that you go ahead and define a procedure for each of the above functions, placing a MsgBox() function that simply displays the name of the function inside each one. Then save and execute the script and make sure that the popup dialogs all appear when they should. You can leave the functions as they are until you are ready to complete them. |
TRICK |
Using the WScript.Quit() method, as I did in this section, is not required. Script execution would have ceased after the display of the splash screen anyway. I decided to add this statement for the sake of clarity, and to prevent any statements that I might have inadvertently left outside of a function in the procedure section from accidentally being executed. |
Building the DoYouWantToPlay() Function
You've seen functions very similar to this one in previous chapters. All it does is display a clever graphic and ask the user if he or she wants to play a game of Hangman.
Function DoYouWantToPlay() 'Display the splash screen and ask the user if he or she wants to play SplashImage = Space(100) & "***********" & vbCrLf & _ "W E L C O M E T O " & Space(68) & "*" & Space(18) & "*" & vbCrLf & _ Space(100) & "0" & Space(18) & "*" & vbCrLf & _ "V B S c r i p t H A N G M A N !" & Space(50) & "--||--" & Space(15) & _ "*" & vbCrLf & Space(99) & "/" & Space(1) & "" & Space(17) & "*" & _ vbCrLf & Space(120) & "*" & vbCrLf & Space(120) & "*" & vbCrLf & _ space(113) & "******* " & vbCrLf & _ "Would you like to play a game?" & vbCrLf & " " DoYouWantToPlay = MsgBox(SplashImage, 36, cTitlebarMsg) End Function
This is a good place to pause and perform another test of your script to ensure that this function looks and works like it should. This test will allow you to evaluate the operation of all the controlling logic in the Main Processing section.
Building the FillArray() Function
The FillArray() function, show next, simply loads a list of words into an array. Later, another procedure will randomly select a game word from the array.
Function FillArray() 'Add the words to the array WordList(0) = "AUTOMOBILE" WordList(1) = "NETWORKING" WordList(2) = "PRACTICAL" WordList(3) = "CONGRESS" WordList(4) = "COMMANDER" WordList(5) = "STAPLER" WordList(6) = "ENTERPRISE" WordList(7) = "ESCALATION" WordList(8) = "HAPPINESS" WordList(9) = "WEDNESDAY" End Function
You are not in a position to perform much of a test on this function at this point. But you can always save and run the script again to see if you have any syntax problems. I recommend that you create a temporary script and copy this function into it and then create a For...Next loop that processes and displays the contents of the array in order to ensure that the function is loading as expected. Next delete the For...Next loop and add the following statements to the beginning of the temporary script:
Dim WordList(9) 'Define an array that can hold 10 game words FillArray()
Save this script again. A little later I am going to suggest that you modify and use this temporary script to perform another test.
Building the PlayTheGame() Function
The PlayTheGame() function, shown next, controls the play of the Hangman game. When I developed this function I wrote a few lines of it, stopped and tested it, and then wrote some more. Specifically, each time I added a call to an external function I stopped, wrote the function that I called, and then did a test to make sure that everything worked before continuing. However, it would take me too long to guide you through every step along the way. Instead, I'll leave it up to you to follow this basic process, and will instead focus the discussion on the development of the other functions that make up the script, most of which are called from within the PlayTheGame() function.
Function PlayTheGame() 'Initialize variables displayed in the game's initial popup dialog NoMisses = 0 NoRight = 0 WrongGuesses = "" RightGuesses = "" 'Get the game a mystery word GameWord = RetrieveWord() 'Call the function that formats the initial popup dialog's display string DisplayString = InitialDisplayString() TempStringOne = GameWord 'Let the player start guessing Do Until NoMisses = 6 'Collect the player's guess Choice = InputBox(vbCrLf & vbTab & DisplayString & vbCrLf & vbCrLf & _ vbCrLf & "No. of Misses: " & NoMisses & " " & vbTab & _ "Incorrect:" & WrongGuesses & vbCrLf & vbCrLf & vbCrLf & _ "Type a letter and click on OK." , cTitleBarMsg) 'Determine if the player has quit If Choice = "" Then Exit Function End If ProcessGuess = FirstLevelValidation() 'The Player wants to quit the game If ProcessGuess = "ExitFunction" Then Exit Function End If 'The player typed invalid input If ProcessGuess <> "SkipRest" Then ProcessGuess = SecondLevelValidation() Select Case ProcessGuess Case "DuplicateWrongAnswer" MsgBox "Invalid: You've already guessed this incorrect letter.", , cTitlebarMsg Case "DuplicateRightAnswer" MsgBox "Invalid: You've already guessed this correct letter.", , cTitlebarMsg Case Else CheckAnswer = TestLetterGuess() If CheckAnswer <> "IncorrectAnswer" Then 'Reset the value of the variable used to build a string containing 'the interim stage of the word as currently guessed by the player TempStringTwo = "" NonGuessedString() 'Check to see if the player has guessed the word GameStatus = CheckIfGameWon() If GameStatus = "yes" Then WordGuessed = "True" Exit Do End If 'Set the value of the temporary string equal to the string created 'by the 'Previous For...Next loop TempStringOne = TempStringTwo 'Clear out the value of the DisplayString variable DisplayString = "" FlipString() End If End Select End If Loop DisplayGameResults() End Function
Building the RetrieveWord() Function
This function is designed to retrieve a randomly selected word to be used by the game. It first selects a random number between 1 and 10, and then it uses that number to retrieve a game word from the WordList() array.
'This function randomly retrieves a word from an array Function RetrieveWord()11 Randomize RandomNo = FormatNumber(Int(10 * Rnd)) RetrieveWord = WordList(RandomNo) End Function
This is a good place to perform another test. This time, open up the temporary script that you created a little earlier and cut and paste it into the statements located in the previous function. Paste the three statements into the temporary file, making them lines 3 though 5 in the script. Next, add the following statement as line 6.
MsgBox RetrieveWord
Save and run the script. Each time you execute the temporary script, a different randomly selected word should be displayed. If this is not the case, then something is wrong. Locate and fix any errors that may occur until the temporary script works as expected. Then, cut and paste any corrected script statements back into your Hangman script and move on to the next section.
Building the InitialDisplayString() Function
This function is used to display a series of underscore characters representing each letter that makes up the mystery game word.
Function InitialDisplayString() 'Create a loop that processes each letter of the word For LetterCounter = 1 to Len(GameWord) 'Use underscore characters to display a string representing each letter InitialDisplayString = InitialDisplayString & "_ " Next End Function
You can run a quick test of this function by creating a new temporary VBScript and cutting and pasting the statements from within this function into the temporary script and modifying it.
For LetterCounter = 1 to Len("DOG") 'Use underscore characters to display a string representing each letter InitialDisplayString = InitialDisplayString & "_ " Next MsgBox InitialDisplayString
You should see three underscore characters separated by blank spaces, indicating the length of the word. If anything is wrong, fix it and then copy the corrected statement(s) back into the Hangman script.
Building the FirstLevelValidation() Function
The FirstLevelValidation() function, shown next, ensures that the player is providing valid input. It checks to make sure that the player typed in something, that the player did not type in more than one character, and that a number was not provided as input.
'Validate the player's input Function FirstLevelValidation() 'See if the player clicked on cancel or failed to enter any input If Choice = "" Then FirstLevelValidation = "ExitFunction" Exit Function End If 'Make sure the player only typed 1 letter If Len(Choice) > 1 Then MsgBox "Invalid: You must only enter 1 letter at a time!", , cTitle- barMsg FirstLevelValidation = "SkipRest" Else 'Make sure the player did not type a number by accident If IsNumeric(Choice) = "True" Then MsgBox "Invalid: Only letters can be accepted as valid input!", , cTitlebarMsg FirstLevelValidation = "SkipRest" Else FirstLevelValidation = "Continue" End If End If End Function
Building the SecondLevelValidation() Function
Like the previous function, the SecondLevelValidation() function, shown below, performs additional tests on the player's guess to make sure that the player is not trying to guess the same letter twice.
Function SecondLevelValidation() 'Check to see if this letter is already on the incorrectly guessed list If Instr(1, WrongGuesses, UCase(Choice), 1) <> 0 Then SecondLevelValidation = "DuplicateWrongAnswer" Else 'Check to see if this letter is already on the correctly guessed list If Instr(1, RightGuesses, UCase(Choice), 1) <> 0 Then SecondLevelValidation = "DuplicateRightAnswer" End If End If End Function
Building the TestLetterGuess() Function
The TestLetterGuess() function, shown below, checks to see whether the letter is part of the word and keeps track of missed guesses. If the total number of missed guesses equals 6, then this function assigns a value of False to the WordGuessed variable. This variable is a flag that is later checked to see whether the player has lost the game.
Function TestLetterGuess() If Instr(1, UCase(GameWord), UCase(Choice), 1) = 0 Then 'Add the letter to the list of incorrectly guessed letters WrongGuesses = WrongGuesses & " " & UCase(Choice) 'Increment the number of guesses that the player has made by 1 NoMisses = NoMisses + 1 'If the player has missed 6 guesses then he has used up all his chances If NoMisses = 6 Then WordGuessed = "False" End If TestLetterGuess = "IncorrectGuess" Else TestLetterGuess = "CorrectGuess" End If End Function
Building the NonGuessedString() Function
This game displays as a string the letters that make up the game's mystery word and uses VBScript string manipulation functions to control the display of correctly and incorrectly guessed letters. As I was creating the game, I wanted an easy way of seeing what game word had been randomly selected and of tracking which letters had yet to be guessed. The NonGuessedString() function, shown next, builds a string that, if it were displayed, would show all of the letters that make up the word, less the letters that the player has correctly guessed. This function gave me a tool for displaying the way the game was keeping track of the game word.
Function NonGuessedString() 'Loop through the temporary string For LetterCounter = 1 to Len(TempStringOne) 'Examine each letter in the word one at a time WordLetter = Mid(TempStringOne, LetterCounter, 1) 'Otherwise add a underscore character indicating a non-matching guess If UCase(WordLetter) <> UCase(Choice) Then TempStringTwo = TempStringTwo & WordLetter Else 'The letter matches the player's guess so add it to the temporary string NoRight = NoRight + 1 RightGuesses = RightGuesses & " " & UCase(Choice) TempStringTwo = TempStringTwo & "_" End If Next End Function
After I had developed this function, I added the following statement as the last statement in the function:
MsgBox "**** = " & TempStringTwo
This way, each time the function ran, I was able to see the contents of the sting. For example, if the game word is DOG and the player has missed his or her first guess, this string would be displayed in a popup dialog as D O G. If the player then guessed the letter O, then the next time this function ran the string would display as D _ G. This function allowed me to visually track the progress of the string as the game ran and manipulated its contents.
Building the CheckIfGameWon() Function
The CheckIfGameWon() function checks to see if the number of correctly guessed letters is equal to the length of the word. If this is the case, then the player has guessed all the letters that make up the word and won the game.
Function CheckIfGameWon() 'Check and see if the player has guessed all the letters that make up 'the word. If so, set the indicator variable and exit the Do...Until loop If NoRight = Len(GameWord) Then CheckIfGameWon = "yes" End If End Function
Again, a well-placed MsgBox() function in this function can be used to track the value of the CheckIfGameWon variable.
Building the FlipString() Function
The problem with the string produced by the NonGuessedString() function was that it displayed a string in exactly the opposite format that I wanted to ultimately display. In order words, if the game word was DOG and the player had correctly guessed the letter O, then I wanted the game to display the word as _ O _ and not as D _ G. So I developed the FlipString() function. It loops through each character of the string created by the NonGuessedString() function and reverses the display of character data.
Function FlipString() 'Spin through and reverse the letters in the TempStringTwo variable 'In order to turn switch letters to underscore characters and underscore 'characters to the appropriate letters For FlipCounter = 1 to Len(TempStringTwo) 'Examine each letter in the word one at a time WordLetter = Mid(TempStringTwo, FlipCounter, 1) 'Replace each letter with the underscore character If WordLetter <> "_" Then DisplayString = DisplayString & "_ " Else 'Replace each underscore with its appropriate letter DisplayString = DisplayString & Right(Left(GameWord,FlipCounter),1) & " " End If Next End Function
Here again, a well-placed statement that contains the MsgBox() function can be used to display the activity of this function as it attempts to spin through and reverse the display of the letters that make up the game word.
Building the DisplayGameResults() Function
The DisplayGameResults() function, shown below, determines if the player won or lost the game and is responsible for displaying the results of the game and for determining whether the player wants to play again. If the user elects to play another game, the strings that are used to track the status of the game word are blanked out and the PlayTheGame() function is called. Otherwise, the function ends and processing control is passed back to the end of the current iteration of the PlayTheGame() function, which then returns control to the Main Processing section, where the SplashScreen() function is called.
'Determine if the player won or lost and display game results Function DisplayGameResults() 'Select a message based on whether or not the player figured out the word? If WordGuessed = "True" Then MsgText = "Congratulations, You Win!" Else MsgText = "Sorry, You Loose." End If 'Display the results of the game PlayAgain = MsgBox(vbCrLf & "The word was: " & UCase(GameWord) & vbCrLf & _ vbCrLf & vbCrLf & MsgText & vbCrLf & vbCrLf & vbCrLf & _ "Would you like to play again?" , 4, cTitleBarMsg) 'Find out if the player play to play another game If PlayAgain = 6 Then 'If the answer is yes, reset the following variables and start a new game DisplayString = "" TempStringTwo = "" PlayTheGame() End If End Function
Building the SplashScreen() Function
The SplashScreen() function is the last function in the script. As you have seen in other games in this book, this function displays some information about the game and its creator. Once this function is processed, the Main Processing section executes the WScript.Quit() method, terminating the game's execution.
'This function displays the game splash screen Function SplashScreen() MsgBox "Thank you for playing VBScript Hangman Jerry Ford 2002." & _ vbCrLf & vbCrLf & "Please play again soon!", , cTitlebarMsg End Function
The Final Result
By now you should have all of the pieces and parts of the Hangman script assembled and ready for execution. Save your work and give it a shot. Once you have everything working correctly, you can remove or comment out any of the extra statements that use the MsgBox() function, in order to track the game's intermediate results.
'************************************************************************* 'Script Name: Hangman.vbs 'Author: Jerry Ford 'Created: 02/30/02 'Description: This script demonstrates how to create a game of hangman using 'VBScript and the WSH '************************************************************************* 'Initialization Section Option Explicit Const cTitlebarMsg = "VBScript HANGMAN" Dim Choice, GameWord, NoMisses, NoRight, SplashImage, PlayOrNot, MsgText Dim PlayAgain, WrongGuesses, RightGuesses, WordGuessed, LetterCounter Dim TempStringTwo, WordLetter, DisplayString, FlipCounter, TempStringOne Dim RandomNo, ProcessGuess, GameStatus, CheckAnswer Dim WordList(9) 'Define an array that can hold 10 game words 'Main Processing Section PlayOrNot = DoYouWantToPlay() If PlayOrNot = 6 Then 'User elected to play the game FillArray() PlayTheGame() End If SplashScreen() WScript.Quit() 'Procedure Section Function DoYouWantToPlay() 'Display the splash screen and ask the user if he or she wants to play SplashImage = Space(100) & "***********" & vbCrLf & _ "W E L C O M E T O " & Space(68) & "*" & Space(18) & "*" & vbCrLf & _ Space(100) & "0" & Space(18) & "*" & vbCrLf & _ "V B S c r i p t H A N G M A N !" & Space(50) & "--||--" & Space(15) & _ "*" & vbCrLf & Space(99) & "/" & Space(1) & "" & Space(17) & "*" & _ vbCrLf & Space(120) & "*" & vbCrLf & Space(120) & "*" & vbCrLf & _ space(113) & "******* " & vbCrLf & _ "Would you like to play a game?" & vbCrLf & " " DoYouWantToPlay = MsgBox(SplashImage, 36, cTitlebarMsg) End Function Function FillArray() 'Add the words to the array WordList(0) = "AUTOMOBILE" WordList(1) = "NETWORKING" WordList(2) = "PRACTICAL" WordList(3) = "CONGRESS" WordList(4) = "COMMANDER" WordList(5) = "STAPLER" WordList(6) = "ENTERPRISE" WordList(7) = "ESCALATION" WordList(8) = "HAPPINESS" WordList(9) = "WEDNESDAY" End Function Function PlayTheGame() 'Initialize variables displayed by the game's initial popup dialog NoMisses = 0 NoRight = 0 WrongGuesses = "" RightGuesses = "" 'Get the game a mystery word GameWord = RetrieveWord() 'Call the function that formats the initial popup dialog's display string DisplayString = InitialDisplayString() TempStringOne = GameWord 'Let the player start guessing Do Until NoMisses = 6 'Collect the player's guess Choice = InputBox(vbCrLf & vbTab & DisplayString & vbCrLf & vbCrLf & _ vbCrLf & "No. of Misses: " & NoMisses & " " & vbTab & _ "Incorrect:" & WrongGuesses & vbCrLf & vbCrLf & vbCrLf & _ "Type a letter and click on OK." , cTitleBarMsg) 'Determine if the player has quit If Choice = "" Then Exit Function End If ProcessGuess = FirstLevelValidation() 'The Player wants to quit the game If ProcessGuess = "ExitFunction" Then Exit Function End If 'The player typed invalid input If ProcessGuess <> "SkipRest" Then ProcessGuess = SecondLevelValidation() Select Case ProcessGuess Case "DuplicateWrongAnswer" MsgBox "Invalid: You've already guessed this incorrect letter.", , cTitlebarMsg Case "DuplicateRightAnswer" MsgBox "Invalid: You've already guessed this correct letter.", , cTitlebarMsg Case Else CheckAnswer = TestLetterGuess() If CheckAnswer <> "IncorrectAnswer" Then 'Reset the value of the variable used to build a string containing 'the interim stage of the word as currently guessed by the player TempStringTwo = "" NonGuessedString() 'Check to see if the player has guessed the word GameStatus = CheckIfGameWon() If GameStatus = "yes" Then WordGuessed = "True" Exit Do End If 'Set the value of the temporary string equal to the string created 'by the 'Previous For...Next loop TempStringOne = TempStringTwo 'Clear out the value of the DisplayString variable DisplayString = "" FlipString() End If End Select End If Loop DisplayGameResults() End Function 'This function randomly retrieves a word from an array Function RetrieveWord() Randomize RandomNo = FormatNumber(Int(10 * Rnd)) RetrieveWord = WordList(RandomNo) End Function Function InitialDisplayString() 'Create a loop that processes each letter of the word For LetterCounter = 1 to Len(GameWord) 'Use underscore characters to display a string representing each letter InitialDisplayString = InitialDisplayString & "_ " Next End Function 'Validate the player's input Function FirstLevelValidation() 'See if the player clicked on cancel or failed to enter any input If Choice = "" Then FirstLevelValidation = "ExitFunction" Exit Function End If 'Make sure the player only typed 1 letter If Len(Choice) > 1 Then MsgBox "Invalid: You must only enter 1 letter at a time!", , cTitlebarMsg FirstLevelValidation = "SkipRest" Else 'Make sure the player did not type a number by accident If IsNumeric(Choice) = "True" Then MsgBox "Invalid: Only letters can be accepted as valid input!", , cTitlebarMsg FirstLevelValidation = "SkipRest" Else FirstLevelValidation = "Continue" End If End If End Function Function SecondLevelValidation() 'Check to see if this letter is already on the incorrectly guessed list If Instr(1, WrongGuesses, UCase(Choice), 1) <> 0 Then SecondLevelValidation = "DuplicateWrongAnswer" Else 'Check to see if this letter is already on the correctly guessed list If Instr(1, RightGuesses, UCase(Choice), 1) <> 0 Then SecondLevelValidation = "DuplicateRightAnswer" End If End If End Function Function TestLetterGuess() If Instr(1, UCase(GameWord), UCase(Choice), 1) = 0 Then 'Add the letter to the list of incorrectly guessed letters WrongGuesses = WrongGuesses & " " & UCase(Choice) 'Increment the number of guesses that the player has made by 1 NoMisses = NoMisses + 1 'If the player has missed 6 guesses, then he has used up all his chances If NoMisses = 6 Then WordGuessed = "False" End If TestLetterGuess = "IncorrectGuess" Else TestLetterGuess = "CorrectGuess" End If End Function Function NonGuessedString() 'Loop through the temporary string For LetterCounter = 1 to Len(TempStringOne) 'Examine each letter in the word one at a time WordLetter = Mid(TempStringOne, LetterCounter, 1) 'Otherwise add a underscore character indicating a non-matching guess If UCase(WordLetter) <> UCase(Choice) Then TempStringTwo = TempStringTwo & WordLetter Else 'The letter matches the player's guess so add it to the temporary string NoRight = NoRight + 1 RightGuesses = RightGuesses & " " & UCase(Choice) TempStringTwo = TempStringTwo & "_" End If Next End Function Function CheckIfGameWon() 'Check and see if the player has guessed all the letters that make up 'the word. If so set the indicator variable and exit the Do...Until loop If NoRight = Len(GameWord) Then CheckIfGameWon = "yes" End If End Function Function FlipString() 'Spin through and reverse the letters in the TempStringTwo variable 'In order to switch letters to underscore characters and underscore 'characters to the appropriate letters For FlipCounter = 1 to Len(TempStringTwo) 'Examine each letter in the word one at a time WordLetter = Mid(TempStringTwo, FlipCounter, 1) 'Replace each letter with the underscore character If WordLetter <> "_" Then DisplayString = DisplayString & "_ " Else 'Replace each underscore with its appropriate letter DisplayString = DisplayString & Right(Left(GameWord,FlipCounter),1) & " " End If Next End Function 'Determine if the player won or lost and display game results Function DisplayGameResults() 'Set a message depending on whether or not the player figured out the word? If WordGuessed = "True" Then MsgText = "Congratulations, You Win!" Else MsgText = "Sorry, You Loose." End If 'Display the results of the game PlayAgain = MsgBox(vbCrLf & "The word was: " & UCase(GameWord) & vbCrLf & _ vbCrLf & vbCrLf & MsgText & vbCrLf & vbCrLf & vbCrLf & _ "Would you like to play again?" , 4, cTitleBarMsg) 'Find out if the player wants to play another game If PlayAgain = 6 Then 'If the answer is yes reset the following variables and start a new game DisplayString = "" TempStringTwo = "" PlayTheGame() End If End Function 'This function displays the game splash screen Function SplashScreen() MsgBox "Thank you for playing VBScript Hangman Jerry Ford 2002." & _ vbCrLf & vbCrLf & "Please play again soon!", , cTitlebarMsg End Function
While the script, as shown here, should work just fine, there is always the chance that you'll miss something or make a typo when creating it. After all, this is a rather large script and with size generally comes complexity, which only increases the probability that something will go wrong.
Once you have thoroughly tested the script, give it to somebody else to test. Ask your tester to try playing the game according to the rules, and then ask him to play it by not following the rules. Ask your tester to keep track of any problems that he experiences and to record any error messages that might appear. If an error does appear, try to get the player to write down exactly what steps he took, so that you can go back and generate the error yourself and begin debugging it.
Summary
In this chapter, you learned how to add programming logic to your scripts to help deal with errors. This included everything from rewriting error messages to making them more user-friendly to ignoring errors or creating error handling routines that allow your scripts to recover from certain types of errors. I also provided advice that can help you prevent errors from occurring in the first place, or at least minimize their number. Finally, I reviewed the different ways of reporting errors that cannot otherwise be handled. On top of all this, you learned how to create the Hangman game and were shown how to test it at various stages of its development.
Challenges
- Make the Hangman game more fun and interesting by expanding the pool of game words.
- Improve the Hangman program by keeping track of the number of games played during a session and displaying a summary of the overall number of times the player won and lost.
- Add logic to the Hangman game that allows you to track its use. For example, prompt the player for his or her name, and then write a message to either a log file or the Windows application event log each time the player plays the game.