Foundations in Scripting
Although scripting is not a product of AI research, it's certainly very commonly used in AI—and particularly in computer games. The need for convenient scripting languages extends beyond traditional game AI approaches; scripting will not die out as AI improves; it will serve a different purpose (other than behaviors). Systems with modern AI techniques benefit from scripting languages providing the glue between basic components.
This section briefly covers scripting languages, without focusing on any one solution in particular. Much of the lower-level details of each language (for instance, Python or Lua) can be found in their thorough documentation.
Scripts are generally much simpler to write than standard programs. Thanks to loosely typed—or even typeless—syntax (variable types do not always need to be declared explicitly), more flexibility is immediately available to the programmer. Scripting languages also benefit from dynamic typing; the type-checking only happens during execution of the instructions. For these reasons, fewer lines are generally required to implement the same algorithms.
Scripting languages are often ideal complements to standard programming languages. For example, C++ is a compile-time, statically typed language that offers the potential for amazing performance. Scripting languages are usually interpreted at runtime and dynamically typed, implying they are very flexible, albeit slower. Scripts allow easy modification and reduce the need for compiling and hard-linked code (builds are expensive), customization is much easier, and prototyping is faster.
The major advantage of scripts is that they can be loaded and executed at runtime without a separate compilation phase. There generally is a compilation phase, but this is often done dynamically—at the same time the script is loaded. For efficiency, the precompiled code can often be saved for later as bytecode (that is, sequences of instructions as platform-independent binary code).
Then, the bytecode is processed thanks to an interpreter, or virtual machine. The interpreter executes each of the statements in the script as operations (for instance, to modify variables, or call core functions). This results in the capability of scripts to provide the same functionality as normal programs.
The scripting environment is the area of memory where all the necessary variables reside, as well as each of the functions defined by the script. This is the core of the scripting system, and the main part of the code required to support the scripting language.
Integration as a Component
Most other AI techniques can be considered as black box components with well-defined interfaces exposing their functionality. Scripting environments are no different. This is one of the reasons we can provide a high-level overview without going into any details of specific languages.
Integrating the scripting environment into the application is called embedding it. Conversely, integrating the native language into a scripting environment is known as extending it. After that's done, the AI engine has the capability to load and run any script, combined with native code. There are two ways to weave these scripts into the architecture, described in the next two sections.
Components higher up in the architecture hierarchy that include the scripting language often need to interact with it. Just like other AI components, this is done by exporting interfaces. Third-party components can then easily call the underlying functions.
Using a high-level interface, it's possible to pass all the data to the script before it runs. This means the script is fully isolated, and can only deal with the information it is provided. In Figure 25.1, on the left, only a high-level interface is exported, allowing the parent component to interact with the script. On the right, the scripting environment imports the input and output interfaces, so the script can interact directly with the world.
A Scripting Module
The scripting environment can be accessed by an interface that fits into our AI framework. All we really need is the ability to call functions and query the scripting environment. The interface in Listing 25.1 is not directly reflected in the scripts; it merely interacts with the interpreter.
def EvaluateWeapon(type,ammo): if ammo > 0: if type == "railgun": return 2 return 1 return 0
Function calls are relatively simple to handle because we can refer to the function by name:
bool Run( const string& function );
In some cases, parameters are required and the result must be queried (for example, in the Python snippet shown in Listing 25.1). Supporting such arbitrary function prototypes is quite difficult because the interface needs to handle any parameter types. Providing two overloaded functions, Param and Result achieves this in a flexible fashion; only floating-point numbers are shown here, but integers and strings could be handled just as easily:
void Param( const float f ); void Result( const float& f );
Finally, generic functions for querying and modifying the scripting environment are sometimes necessary. These overloaded functions set and retrieve the content of global variables of arbitrary types:
void Set( const string& name, const float f ); bool Get( const string& name, const float& f );
The process of importing interfaces into the scripting environment is handled differently because there is no need for a runtime interface for this. Instead, this will generally be handled during initialization time. The idea is to convert the existing C++ interfaces (for instance, vision or motion) into Python code, using an automated interface wrapper.