This article and all its contents are (C) 2000 David Goodlad. They may not be reproduced in any form without permission from the author.
I will very gladly give permission to copy the article, though, so send me an email!
David Goodlad
[email="dgoodlad@junction.net"]dgoodlad@junction.net[/email]
webmaster: http://blackhole.thenexus.bc.ca
[size="5"]Introduction
Welcome to the third part of this series of articles about what should be recognized as one of the most important pieces of game programming: the addition of a simple scripting language. In this part the focus will be on the implementation of control structures into your language. They will all be coded as regular commands, written in the same way as you would implement any other custom commands for your language. A reminder that, as usual, the actual implementation of all of these things was done to maintain the ability to be understood by you people easily. Therefore, I admittedly have made the functions somewhat 'klunky' and not necessarily the best they can be. A bigger explanation of what can be done about this is in the conclusion... But, remember, these examples are designed to be learned from, not copied, because they will not work very well in practical uses. But, anyways, enough chatter, let's get on with it!!
[size="5"]The first requirement
Before we can actually start making 'decisions' in the language, there must be some way for a script to set and/or retrieve variables: in this case, bit flags. These have been chosen for simplicity; they are simple Boolean values stored in a 'compressed' format, so that you can have lots of them. You would probably want named 'variables' in your actual game, but to keep things easy to understand, I'll stick with bit flags for now.
Bit flags are simple to implement. The actual reasons for it working are out of the scope of this article, so I won't bother explaining really. But, here are three functions, one which sets a flag, one which unsets a flag, and one which returns the value of a flag, as well as the definition for the 'Flags' array. As usual, these should be put in your CScriptParser class.
Dim Flags(255) As Byte 'Put this in the Definitions section
Public Function ExecuteCmdSetFlag(a() As String) As Integer
Flags(a(0)) = Flags(a(0)) Or (2 ^ a(1))
'Set bit number a(1) of flag number a(0)
'a(0) is the first parameter, a(1) is the second
ExecuteCmdSetFlag = 1 'Return 1 to indicate success
End Function
Public Function ExecuteCmdUnSetFlag(a() As String) As Integer
Flags(a(0)) = Flags(a(0)) And (255 XOr (2 ^ a(1)))
'Unset bit number a(1) of flag number a(0)
'a(0) is the first parameter, a(1) is the second
ExecuteCmdSetFlag = 1 'Return 1 to indicate success
End Function
Public Function ExecuteCmdGetFlag(a() As String) As Integer
Dim i As Integer
i = Flags(a(0)) And (2 ^ a(1))
'Put the value of bit a(1) of flag a(0) into i
If i > 0 Then ExecuteCmdGetFlag = 1 Else ExecuteCmdGetFlag = 0
'If the bit was set, return 1, otherwise return 0.
End Function
SetFlag (flagnumber,bitnumber)
UnSetFlag (flagnumber,bitnumber)
GetFlag (flagnumber,bitnumber)
SetFlag (17,5)
[size="5"]If Statements
As you should know, If statements have at least two parts to them: a condition, and a command to execute if this condition is met. They can also have a third part (the 'Else' in VB), which is executed if the condition is NOT met. The following function, when added to your CScriptParser class will add the ability to decide which of two command blocks to execute (or just whether or not to execute one) depending on the condition of a flag.
Public Function ExecuteCmdIf(a() As String) As Integer
Dim b(1) As String, c(0) As String
'Two temporary arrays of strings. b is an array
'of two strings, used to pass to the GetFlag
'function. c is an array of one string (yes,
'seems pointless, but it has to be like this to
'pass a single parameter, the name of a command
'block, to ExecuteBlock
Select Case a(0)
'The first parameter of your if statement is what
'KIND of condition it is to check; right now only
'the "Flag" type is implemented, but this allows
'for easy expansion.
Case "Flag"
b(0) = a(1) 'Fill out the b() array with the
b(1) = a(2) 'flag parameters
If ExecuteCmdGetFlag(b()) > 0 Then 'Check if the flag is set
c(0) = a(3)
ExecuteCmdExecuteBlock c()
'Sets the c() array to contain the name of the
'command block to be executed if the specified
'flag is set, then calls that block using
'ExecuteBlock
Else 'The flag was NOT set
If a(4) = "*" Then
'If the script specifies that there is no
'Else block (by using a * for it), exit the
'function
ExecuteCmdIf = 1
Exit Function
End If
'If there *was* an else block specified,
'execute it.
c(0) = a(4)
ExecuteCmdExecuteBlock c()
End If
Case Else
'If the specified condition type wasn't found,
'do a bunch of error type stuff
Beep
Debug.Print "ExecuteCmdIf: Invalid type: " & a(0)
ExecuteCmdIf = 0
Exit Function
End Select
ExecuteCmdIf = 1
End Function
[size="5"]An explanation
(Gee, I like that heading don't I! lol) Anyways, that function should be pretty self-explanatory with the comments in it. All that it does is this:
- Check what type of condition the script specified as the first parameter - a(0)
- Because the "Flag" type is the only one defined at this point, it will proceed into that portion of the code (if not, there's a problem, and an error is printed to the 'Immediate Pane' of VB)
- The second and third parameters, a(1) and a(2), are put into the array b(). This array is used to call the GetFlag function.
- If this call returns 1 (which means the flag is set), the only element of the array c() is filled with the fourth parameter of the If function: the name of the command block to be executed if the condition is true. Then, the ExecuteBlock command is called, using c() as it's parameter list.
- Otherwise, if the call to GetFlag returns 0, meaning the flag isn't set, a check is made on the fifth parameter, a(4). If a(4) is an asterisk (*), then there is no 'else' part to be executed, and the function exits. If it's anything but an asterisk, then the same as in #4 happens, c() is filled with the name of the command block to be executed if the condition was false, and it is passed to ExecuteBlock to be parsed.
If (Flag,5,7,blocktrue,blockfalse)
[size="5"]Loops
Now that we have our first big feat out of the way, the If statement, we can move on to some basic loops. In many ways, looping is actually easier than the If statement that we just learned. So if you got through that last page okay, you will be just fine from here-on-out!
[size="3"]For Loops
The for loop is one of the most often-used loops in VB for games, so we'll start with that. Basically, all that you want it to do is execute a command block a specified number of times. Here is a basic implementation of a simple For loop:
Public Function ExecuteCmdFor(a() As String) As Integer
Dim i As Integer, b(0) As String
'i will be our counter, b() is a 'placeholder'
b(0) = a(1)
'put the name of the command block to be executed
'in b()
For i = 0 To CInt(a(0)) - 1
'Use vb's built-in for loop to go from 0 to the
'first parameter, a(0)
ExecuteCmdExecuteBlock b() 'execute the block!
Next i
ExecuteCmdFor = 1
End Function
[size="3"]Do...Loop Until Loops
This type of loop is also quite simple to implement, again 'wrapping' vb's built-in loop of the same name. Look this code over:
Public Function ExecuteCmdDo(a() As String) As Integer
Dim b(1) As String, c(0) As String
'as usual, some placeholder-type arrays
b(0) = a(1)
b(1) = a(2)
'the b() array will be used for getting the flag
c(0) = a(0)
'the c() array will be used for calling the command block
Do 'VB's built-in Do command...
ExecuteCmdExecuteBlock c() 'execute the command block
Loop Until CStr(ExecuteCmdGetFlag(b())) = a(3)
'Loop until bit a(2) of flag a(1) becomes set to a(3) (0 or 1).
ExecuteCmdDo = 1
End Function
[size="5"]More theory time!
Now, after giving you all that ugly source code to figure out how to implement more efficiently, I will move on to some theory, and let you decide how you want to code it; I won't provide any code in the article for this little discussion.
Now, you're probably curious as to why I have used only bit flags for data-storage in the scripts so far. With a bit of thought, however, the question arises "How can we name a variable, and refer to it as a string?" In other words, how can a script define a variable by itself, and then have vb refer to that variable using a STRING, because the actual VB code can never actually contain the name of that variable, because it was written in the script, not the vb code! A C/C++ coder would immediately jump to something like a linked list, but we can't do this in VB... Or can we? VB can't do it in the usual manner of C/C++, using a struct, the C equivalent of a user-defined type in Visual Basic.
Once again, the magic that is VB's poorly designed, poorly implemented OOP system comes to our rescue. Using a system of classes, we can design a linked list fairly easily. You would have one class, called something like 'CLinkedListItem', and it would have a member variable called, for example, 'm_Next', defined as a CLinkedListItem object. In this way, you can set the m_Next variable to be a 'pointer' to the next item in the list! Then, another variable could be created called 'm_Name' which would contain the name of the variable in the context of the script. A member variable named 'm_Data' would be needed to hold the 'data' of the script variable.
With this type of setup, it would be quite simple to then read up on binary trees, or hashing algorithms, and implement one of these methods to allow for efficiency in searching for the correct element of the linked list. An excellent resource for this type of thing is at http://members.xoom....omasn/s_man.htm. He implements the algorithms in both C and VB, so you should be able to do something with it!
Using named variables will come in very handy with your scripting language, making it much easier to keep track of what variable contains what data With the numbered bits/flags system, you would need to keep some sort of record of what bit/flag represented what.
[size="5"]Suggestions...
As in the previous article, I will leave you with some suggestions for things to do to improve the scripting system for your own personal needs.
I mentioned in the introduction that the implantation of all the loops etc. was not very well done (ie: efficient). They were also somewhat restrictive, and could become quite complicated due to the need for making command blocks 'become' part of a larger one, because the small ones would be part of conditional statements and loops, whereas the logical thing in everyone's mind is that of VB, having the actual statements 'inside' beginning and ending statements for the control structure, whether it be a For loop, of and If...Then...Else statement.
I believe that if you wanted to set this up better, which I really think you should, you could change the layout of the actual script loading script, and the data structures that store the command blocks etc. I am sure that lights are starting to flash on in your head by now, so I will leave this discussion at that, and allow it to progress to your own personal taste.
[size="5"]Conclusion
Well, I finally got this third part done. It was a push, as you can probably tell. I hope it has gone into enough depth while retaining enough generality to allow you to take everything to your own personal liking. You should, by now, have a pretty good understanding of how the basic system works, including how to create your own scripting commands. I would really like to make one thing clear, though. When I wrote all of this code, not only for this part of the series, but for all of them, I did not truly mean for the code to be copied and pasted, and actually used. I mainly intended it, and still do intend it, to be used more as an explanatory tool, and to demonstrate one way of doing things (which, imho, is a very ugly way of actually implementing the scripting system). Therefore, none of the code that has been written for this article is set in stone, and neither is the entire structure of the scripting system. Please feel very free to change things: how the scripts are stored in memory during run-time, add extra 'built-in' commands that aren't run like the normal ones with ExecuteCmdBlah. There are a million possible, and very viable, solutions to all the problems with this system, and I am sure that you can come up with at least one way to fix any problem that you run into!
So, basically what I am saying is, BE INVENTIVE AND CREATIVE. Do what suits YOU, not what you think I would have done. That's how you become a better programmer!
[size="5"]Source Code
The source code to this third part in the series is, as always, available from my website. A change from previous parts, though, is that there is more to it than a single class module. I have included a sample project, as well as a somewhat confusing demonstration script. This is what I used to test all the code, so it's not exactly well commented in itself. But, by stepping through it, and watching what happens, you'll pick it up in no time!
The address of the ZIP file containing the source code is:
[ http://blackhole.the...criptvb-pt3.zip ]
[size="5"]Contact + Future Releases
As before, updated versions of this third part of the series, as well as later parts as they are written, will be available from my website, the black hole:
[ http://blackhole.thenexus.bc.ca/ ]
I welcome any questions or comments, as well as *constructive* criticism I can be reached via email at:
[email="dgoodlad@junction.net"][ dgoodlad@junction.net ][/email]
Thanks for reading!
David Goodlad