Chapter 2. Simple Hard-Coded AI

If the answer to the question, “How will I do the AI?” is “Just write the code,” chances are you will write hard-coded AI. Also known as scripted AI, this method has good and bad points. The most serious challenge with hard-coded AI is knowing when to use it and when not to use it. Because hard-coded AI is the most straightforward of all AI techniques, most of this chapter is devoted to facing that challenge, covering the advantages and disadvantages of using hard-coded AI rather than the methods for hard coding.

The Good, the Bad, and the Ugly

When the code fits the situation well, hard-coded AI is often the fastest and most intelligent AI code possible. When the code is not appropriate, the decisions that result from hard-coded AI are often so bad that they disrupt the player’s suspension of disbelief. Hard-coded AI gets complex very quickly, can be difficult to debug, and scales extremely poorly. Its brittle nature can quickly lead programmers to think there has to be a better way. These are software-engineering issues in addition to being AI issues, but the demands placed on game AI bring the issues out quickly.

The Good

It is very hard to improve on simple, straightforward code for implementing an algorithm. Properly designed and implemented, this kind of code benefits from minimal overhead and fast execution. Simplicity brings many other benefits. Programmers writing this kind of code find it easy to write and debug. Indeed, this is exactly the kind of coding method that is taught to beginners and perfected by the time they become professionals. As long as the code keeps a reasonable level of simplicity and straightforwardness, this kind of code represents the first and best way of getting the job done.

Sophistication is not always a virtue. Imagine a nail-driving tool that is more sophisticated than a hammer but less sophisticated than a power nail gun. Such a tool would fail in the marketplace because it would fail to displace either hammers or power nail guns. AI code is similar. Any methodology more sophisticated than simple hard-coded AI must be sophisticated enough to bring benefits that outweigh its costs. There is no place for methods that fall in between “simple” and “sophisticated enough.”

To extend the nail-gun analogy, consider the fact that hardware stores still sell a wide variety of hammers. Nail guns have not destroyed hammer sales. Carpenters and even roofers still carry and employ hammers. In games, sophisticated AI methods have not wiped out simple AI methods. Professional AI programmers employ both. Beginning AI programmers start with simple methods and move on as they learn more sophisticated ones. That being said, after learning to use sophisticated methods to implement AI, beginning AI programmers should not ignore the simple methods they first learned. It is a beginner’s mistake to forget to check if the simplest way to implement AI is also the best way. Many times it will not be, but surprisingly often, the simple methods are the best.

The Bad

Perhaps the most critical issue for hard-coded AI is that it must determine when its behaviors are appropriate. For tiny behaviors, the determination is so obvious and easy to compute conclusively that the programmer can forget to deal explicitly with the issue when the AI grows more complex. The code, no matter how good, must fit the situation.

Consider the AI for a simulated opera singer—a tenor. We will name him Horatio and refer to him in future chapters. The AI for Horatio evaluates his current situation. He is dressed in a dark formal suit. He is standing before a seated, mostly quiet group of formally dressed people. The lights over the audience are low. Quiet, formally dressed ushers direct people to their seats. Music begins to play. So of course his AI directs Horatio to break into the opening song of the latest opera. Unfortunately, the scene described is a funeral home, not a small theater. No matter how good the AI is at making Horatio sing and portray emotion and move on stage, it is behaving inappropriately. As mentioned in Chapter 1, “Introduction,” one of the overriding goals of any AI programmer is to avoid artificial stupidity.

No matter how good the AI is at the things it does well, players will recoil when the AI is stupid. Inappropriate behavior destroys suspension of disbelief. Simple, hard-coded AI carries with it the risk of selecting an inappropriate behavior. If the AI for Horatio could reason, it might be thinking, “You mean I am not supposed to be singing right now?” Hard-coded AI also tends to exhibit poor default behaviors: “This is what I do when I don’t know what to do.” A simple AI can be hamstrung by having a set of behaviors that is too limited: “These few things are all that I know how to do.” While hard-coded AI lacks formal structures that lead the programmer to deal with any of these issues, action selection is the most noticeable.

Thus, the first challenge when writing hard-coded AI is to make sure that the AI reasons correctly that the action it is about to take is the right one. It should not decide to sing opera at funerals. The second challenge for hard-coded AI is to fake it gracefully when it does not know what it should be doing. The third challenge for the AI programmer when creating hard-coded AI is to give it a sufficiently broad set of behaviors. Answering this third challenge helps mitigate the second one if the additional behaviors have different situations where they are appropriate.

The Ugly

The major enemies of hard-coded AI are size and complexity. Code organization is ad hoc unless the programmer actively takes steps to regularize it. Changes to the code often entail a full rewrite or major refactoring of the code—and failing to refactor the code carries the risk that the new code will never work properly. This kind of code is said to be “brittle.” It has certain strengths, but beyond a certain point, the method fails catastrophically. Ad-hoc organization provides no clear guidance for the programmer with respect to where more code should be added as new capabilities are required. This method fails to scale up.

Reconsider hard-coded AI when size and size-related complexity threaten to become overwhelming. Hard-coded AI is a good place for small, complex algorithms, but it is not well suited when the complexity is mostly due to large size. “Overwhelming” will mean different things to different programmers, and it will change for the same programmer in different parts of his or her career. An evaluation of what is too complex should be made in real time by the people who have to deal with the code. Hard and fast rules in this area are suspect, but in general you should take pains to organize your code and be wiling to refactor it readily.

Note

Refactoring means that you improve the internal structure of the code without changing its external function. It’s saying “knowing what I know now, and knowing what I have to change in this code today, I should have written it differently,” and then taking the time to rewrite it accordingly.

Refactoring might not get rid of complexity, but it should make it more manageable. The ability to visualize complex software is a very saleable skill. Companies seek and attempt to retain programmers who can keep a clear picture of a large and complex program in their heads and reason about it, but all people have limits.

Projects

The projects for this chapter are based on the AI for a series of household thermostats. While the simplest of thermostats hardly requires a computer, the most sophisticated thermostats certainly depend on the tiny computers inside them.

Note

If you are new to using Visual Studio, you may want to review the projects in Chapter 1 before proceeding.

At first blush, a thermostat may seem to be far removed from game AI. But although it may not seem like it does, a thermostat does meet our definition of an AI insofar as it reacts intelligently to changing conditions. Yes, game AI tends to bring to mind images of the clever, hard-to-overcome bosses found on the last level of a 40-hour game. It is worth noting, however, that such games do not start with the boss level—and for good reason. So it is with learning to program AI. AI game programmers are responsible for turning what would otherwise be a museum walkthrough into an entertainment experience. Their tasks include programming many small, less obvious decision-making capabilities, such as camera AI.

Consider camera AI. Some aspects of camera AI are readily handled by simple, short, hard-coded scripts. A small chunk of AI allows game designers to create a compelling dramatic experience by taking temporary control of the camera. Imagine a first-person-perspective game. The camera shows what the player’s character in the game sees. The character deals with the last enemy in a stairwell, opens the door to the roof, and steps out, hoping that the promised helicopter will come and pick him up. At this point, the game freezes and the camera pulls back, showing the character standing there, up high and alone. It then pans a full circle, allowing the player to see the burning city below. Then the camera moves forward and jumps back to the character’s perspective. The player walks that character around the roof and gets a bad feeling about the helicopter before giving up and heading back down the stairwell. Halfway down, however, he decides that one last look for the helicopter is in order. The player’s character opens the door to the roof and steps out—but this time the AI does not take control of the camera.

The hard-coded script for that bit of camera AI is an if-then statement with two conditions. If the character is walking out the door and is doing so for the first time, then the camera AI should take control and run the pan script. The core decision-making logic is well within the capabilities of a beginning programmer. It is not a great teaching example, however, because it demands that a complex game program—complete with compelling art assets and good interfaces for the AI programmer—already exist.

Thermostat AI places low demands on the programmer in terms of the amount of effort needed to handle the software that is not the decision-making part of the thermostat. While few games use thermostat code, many games use code of similar complexity, as seen in the camera example. For example, level designs often involve traps and triggers, and they use AI comparable to our thermostat examples.

A Simple Thermostat

Consider the AI of a very simple thermostat. A mechanical switch in the thermostat decides whether the heating system should run or not run. We will create this type of a thermostat AI as part of a new project.

  1. Using Visual Studio, create a new Windows Forms Application and call it Thermostat.

  2. Double-click My Project in the Solution Explorer.

  3. VB will bring up a window with a column of tabs such as Application and Compile on the left side of the window. Click the Compile tab if it is not already selected.

  4. One of the Compile options is Option Strict. Click the drop-down and set it to On.

  5. Right-click Form1.vb in the Solution Explorer and rename it House.vb.

  6. Click the form in the designer and change the Text property from Form1 to House Simulator, as shown in Figure 2.1.

    The Thermostat project, before the placement of controls.

    Figure 2.1. The Thermostat project, before the placement of controls.

Next, we will place the controls that make up the house simulation. This will correspond to the “game” in which our AI will operate. As the projects get more complex, we will rely more on the code on the CD, but doing them step-by-step here ensures familiarity with Windows applications written in VB.

We need a reasonably rich world for our AI to operate. The split between what is part of the AI and what is part of the rest of the game is a game-design issue. We will consider all decision making as part of the AI, but will minimize the amount of AI code devoted to carrying out the decisions of the AI. Once the decisions are made, implementation of the actions is deemed to be something the game world provides. For now, we will also minimize the amount of AI code devoted to sensing the world. Reasoning about the world is the purview of the AI, but the raw state data about the world is something the world should provide to the AI. Professional AI programmers have to be vigilant to ensure that the world will indeed provide the AI with critical data it needs. “Vigilant” in this usage often means that professional game AI programmers wind up writing a large portion of the sensing and action code needed by their reasoning code.

The rich world for the thermostat AI begins with the room temperature. The room temperature will provide the changing conditions that prompt the AI to react intelligently. The AI also needs a furnace to control in order for it to react. The AI itself will do the intelligent part, but that code will be separated out and not part of the simulation.

Note that our AI keeps no memory of the past. Our AI deals only in the current temperature. If the AI needed knowledge about prior temperatures to help it reason, it would have to remember them itself. The world simulation should not keep this data because it exists solely to help the AI. More sophisticated AI will retain memories of the past or suppositions about the future.

Small amounts of data are well served by ad hoc organization, but larger amounts need formal organization. This is known as knowledge representation (KR). Our thermostat AI cannot directly change the world temperature; it can only turn on the furnace. If our AI needed to reason about a world that was warmer, it would need to simulate or partly simulate that world and reason using the simulation. As the programmer, we would need to design the simulation, and that design would be the KR for it. (We will cover KR more explicitly in future chapters.)

  1. Drag a Label control to the top-left part of the form and change the Text property to Ambient.

  2. Drag another Label control to the right of the first label and change its Text property to Set Point.

  3. Drag a third Label control to the right of the others and change its Text property to Status.

  4. Drag a NumericUpDown control below the Ambient label.

  5. Drag the tiny box on the right side to the left to make the control small enough to fit under the label.

  6. Change the Name property of NumericUpDown to AmbientUpDown.

  7. The default value of 0 for the Minimum property and 100 for the Maximum property do not need to be changed for a thermostat using the Fahrenheit scale. If you use Centigrade, change the Minimum property to –15 and the Maximum property to 45.

  8. Similarly drag a NumericUpDown control below the Set Point label and rename it SetPointUpDown.

  9. Resize SetPointUpDown and optionally change the Minimum and Maximum properties.

  10. Drag a Label control below the Status label.

  11. Change the Label control’s Name property to StatusLabel and its Text property to Undefined.

  12. Change its BackColor property to White and the BorderStyle property to FixedSingle. Note that when you go to change the color, there will be three tabs showing: System, Web, and Custom. The default tab is System, and White is not listed as an option in the drop-down for that tab. Instead, White is listed in the Web tab, along with many common color names. You can pick it from the drop-down for the Web tab or you can simply type over the existing color with the name of the color that you want. Your project should resemble Figure 2.2.

    Thermostat project, ready for code.

    Figure 2.2. Thermostat project, ready for code.

  13. We are ready to add the code. We will put the AI code in a separate file to help differentiate between the world simulation and the AI. Right-click the Thermostat project in the Solution Explorer, choose Add, and choose Module.

    Tip

    You could also add the module by opening the Project menu and choosing Add Module.

  14. The Add New Item dialog box opens with the filename highlighted at the bottom. Change the name to AI.vb and click Add.

  15. We will start with the core AI routine. Designing it first will show us what inputs the AI needs from the world and what outputs it will want to implement. Add the following code to the AI.vb file between the Module AI and End Module lines:

    'This function evaluates world conditions and gives back a
    ' response for the furnace as a string
    Private Function CoreAI(ByVal currentTemp As Integer, _
            ByVal desiredTemp As Integer) As String
        If currentTemp < desiredTemp Then
            Return ("Heat")
        Else
            Return ("Off")
        End If
    End Function
    

    This is only the core code. We need additional code to extract the inputs from the world and to implement the output. The function is marked private because we expect it to be called by other AI code that will provide the translations. Note that comment lines in VB start with a single quote character. As mentioned, the underscore character is the line continuation character in VB. Since the language does not use a termination character, like the semi-colon in C, it has a continuation character for when a single line of code should span multiple lines for readability.

  16. Now add the following wrapper function to AI.vb:

    'This is the public wrapper. It knows about the world.
    Public Sub RunAI(ByVal World As House)
        World.StatusLabel.Text = CoreAI(CInt(World.AmbientUpDown.Value), _
            CInt(World.SetPointUpDown.Value))
    End Sub
    

    The wrapper isolates the AI implementation from the world implementation. If how the world is implemented changes, then only the wrapper needs to change, not the core AI routine. CInt converts the UpDown values from decimal to integer.

  17. All that remains is to connect the world to the AI. When does the AI need to run? It needs to run upon startup and whenever either of the two temperatures changes.

  18. Right-click House.vb in the Solution Explorer and select View Code.

  19. We need to get to the form load event. Above the code-editing pane (the big center area) are two drop-down lists. Change the selected entry in the drop-down list on the left from House to (House Events).

  20. Change the selected entry in the drop-down list on the right from Declarations to Load. Visual Studio takes you to the event handler or creates the skeleton for it if it does not exist. (This procedure is useful for creating event handlers other than the default event handler and to navigate to a particular event handler.)

  21. Change the selected entry in the left drop-down list to AmbientUpDown.

  22. Change the selected entry in the right drop-down list to ValueChanged. Visual Studio will create the skeleton for the event handler.

  23. Change the selected entry in the left drop-down list to SetPointUpDown.

  24. Again change the selected entry in the right drop-down list to Value-Changed. Visual Studio will create the skeleton for this event handler.

  25. Add the following line of code to all three event handlers:

    Call AI.RunAI(Me)
    
  26. The Me in this case refers to the running instance of the form. Observant readers will have noticed that the Sub that handles the form load event is marked as Handles Me.Load in the code. Add a comment, and your code should look like the following:

    'We check the furnace at startup and whenever conditions change.
    
    Private Sub House_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
            Handles Me.Load
        Call AI.RunAI(Me)
    End Sub
    
    Private Sub Ambient_ValueChanged(ByVal sender As Object, ByVal e As _
            System.EventArgs) Handles AmbientUpDown.ValueChanged
        Call AI.RunAI(Me)
    End Sub
    
    Private Sub SetPointUpDown_ValueChanged(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles SetPointUpDown.ValueChanged
        Call AI.RunAI(Me)
    End Sub
    
  27. Run the application in the debugger. Note that the status label has the value Off even though the label starts with the value Undefined. This means that the form load event triggered. Manipulate both temperature controls and watch the status change back and forth between Off and Heat. If we had forgotten either event handler, our furnace would ignore changing conditions of interest to it.

Analysis

The AI code, especially the core AI code, is fast, simple, and reasonably bulletproof. But suppose the thermostat were also asked to control the windows, closing them whenever the furnace is running. The window AI knows exactly the right thing to do and can execute that action so well that it is a given. (We are not considering how easy or hard opening or closing the windows might be.) At first blush, this makes good sense. The windows should be closed when the furnace is running. But the thermostat will also leave the windows open all summer, even when it rains, and it may open the windows when it is cold outside if the room is comfortably warm. Piggybacking the window AI onto the furnace AI results in a poor window AI. The AI response does not fit the conditions.

Making sure that the AI response fits the conditions makes or breaks hard-coded AI. It is rather easy to forget to guard against all the situations where the AI acts inappropriately; indeed, it can be impossible to be completely effective in all cases. Beginning AI programmers must learn to always do the analysis. Hard-coded AI is simply too fast and effective to be discarded out of hand, but knowing when not to use it takes practice.

Complexity is the enemy of hard-coded AI. As the number of decisions that govern a particular behavior rises, complexity explodes. Complexity also increases as the number of actions needed to implement a behavior increases and with the number of inputs and the amount of state data that must be examined to make the decisions. At a certain point, the complexity overwhelms the ability of the programmer to write, debug, or modify the code. Usually, the ability to modify the code is the first to succumb, followed by the ability to debug the code.

The thermostat we just coded has one decision to make: whether to call for heat. It can implement that decision with a single action. It has one item of state data: the desired temperature. It has one input from the outside world: the current temperature in the room. So the answer to each of the “how many” questions is 1, the lowest possible number that can still expect an intelligent decision to be made. Is it too low for it to meet our definition of AI?

To react intelligently to changing conditions, the AI must be able to act. This thermostat has one output action. For it to detect changing conditions, it must be influenced by at least one outside piece of data: the current temperature. For it to act intelligently, it needs guidance on the decisions—and that guidance is the set point of the thermostat. This AI is almost the simplest possible AI; it is no surprise that hard-coded design is more than adequate to the task. A more complex design would be overkill. It would be harder to write and debug. The overhead of a more complex method would certainly make it slower to run. It is hard to beat simple methods!

Consider, though, a very simple set-back thermostat with a day setting and a night setting. It still has only one decision to make: whether to call for heat. The internal state data has gone up, however, because there are two set points to track. And the set points themselves have become more complex: Instead of a simple number for the temperature, each set point also has a start time. One number has become four numbers. Also, this thermostat has two inputs from the outside world: the current temperature and the current time.

Real thermostats usually rely on their own clock instead of asking the outside world what time it is. That said, real thermostats, depending on their implementation details, often fail to stay synchronized with the correct time. Power failures, daylight saving time, and even changes in which weekend daylight savings time changes conspire to make real thermostats make bad decisions. To avoid this form of artificial stupidity, our thermostat will lack a time-of-day clock and will ask the outside world what time it is whenever it wants to know.

At first glance, our original thermostat had three things to deal with: one action, one piece of state data, and one piece of world data. This new thermostat has more. It has one action, four pieces of state data, and two pieces of world data. If the different categories do not interact, overall complexity relates to the sum of the different elements. In that case, our complexity has gone up seven-fold. Life for an AI programmer is rarely so kind, however. If the different categories do interact, we multiply to get a gauge of complexity. In that case, our complexity has gone up eight-fold. Not surprisingly, this new thermostat is hard to find on the market. While it saves money compared to the first one, it is not intelligent enough to compete with more complex offerings.

Implementing this thermostat is left as an exercise for the reader. Note that the core AI function call does not need to be changed, only the wrapper. Readers taking the slow and steady approach will want to take the time and help cement their skills with Visual Studio and VB. More advanced readers will hold off until we get to a more realistic example.

Our first two thermostats deal with only heat. Adding air conditioning means adding another output and another piece of state data. Our complexity count is then two actions, five pieces of state data, and two pieces of world data. These interact at least partially, giving us a potential comparative complexity product of 20. The code can no longer be written without thought or debugged at a glance. And like the heat-only version of this thermostat, this thermostat is not intelligent enough to compete in the marketplace. Implementation is again left to the reader.

Our fourth thermostat has four set points instead of two. This level of complexity is suitable for many households, and such thermostats are widely available. The set points are matched to getting up in the morning, being away all day, being home in the evening, and being asleep at night. The amount of state data has gone from five items to nine. There are four set points, each with a time and temperature. There is also the mode switch, which decides between heating and cooling. This sums to nine pieces of state data. So two actions, nine pieces of state data, and two pieces of world data multiply to 36. Care must be taken in the coding and design to minimize the number of interactions between all of the data.

A More Sophisticated Implementation

We could implement a fully generalized user interface for this thermostat, but that would go beyond what is needed to illustrate the point. Our implementation will have the expected four set points, but we will not create a user interface for setting them. At this point, it is worth asking, “Where does the state data live? Is it part of the world or is it part of the AI?” The ambient temperature and the time are clearly world data. Our set points could be in either place. If the thermostat needed to remember what it was doing the last time it ran, that data would be part of the AI. It would be part of the AI’s knowledge representation of how the world used to be—a piece of data it is remembering to help it think about how it wants to act now.

  1. Go to the code for House.vb and delete the three lines that make up the SetPointUpDown ValueChanged event handler. We will keep the four set points in the world data.

  2. Add the following three lines to House.vb:

    'Here are the thermostat programmed values.
    Public ReadOnly SetTemps() As Integer = {70, 64, 68, 60}
    Public ReadOnly SetTimes() As Integer = {6, 9, 17, 21}
    

    The () by the names denote that the variables are arrays. The arrays are public so that they can be accessed by the AI code in a different file. They are read-only because we do not expect to change them, and any attempt to do so is a bug we want to catch. The arrays are initialized with the values shown in {}. The temperature values are in Fahrenheit degrees. The times are in hours, using a 24-hour clock familiar to people who have experience with the military or a European train schedule. Our thermostat will not bother with minutes, only the hour. The sequence of values corresponds to morning, day, evening, and night.

  3. Click the House.vb[Design] tab.

  4. Click the SetPointUpDown control.

  5. Right-click it and delete it.

    Note

    At some point, the error list will show an error because the AI wrapper function as currently written references the deleted control. For now, we will ignore the errors, work on the user interface elements, and update the AI last.

  6. Drag a Label control to where the deleted control used to be.

  7. Change the label’s Name property to SetPointLabel and the Text property to Not Set.

  8. Change the BackColor property to White and the BorderStyle property to FixedSingle.

  9. Drag a Label control just below the Temperature controls.

  10. Change the Text property to Time.

  11. Drag a NumericUpDown control just below the new label and make it smaller.

  12. Change the Name property to TimeUpDown and the Maximum property to 23.

  13. Drag another new Label control just below the Time controls.

  14. Change the Text property to Mode.

  15. Drag two RadioButton controls onto the form and stack them below the Mode label.

  16. Change the Text property of the first RadioButton control to Air and the Name property to AirRadio.

  17. Change the Text property of the second RadioButton control to Heat and the Name property to HeatRadio.

  18. Change the Checked property of the Heat radio button to True. The form should resemble Figure 2.3.

    The complete user interface for the set-back thermostat.

    Figure 2.3. The complete user interface for the set-back thermostat.

  19. With the new controls, our application needs to handle new events. Double-click the TimeUpDown, AirRadio, and HeatRadio controls. Before each double-click, you will have to switch to the Design view of House.vb. Visual Studio will create the skeletons of the three event handlers we are interested in. Double-clicking the control in the Design view is an alternative to using the drop-down lists at the top of the Code view. Double-clicking takes you to the most commonly used event; to get to other events, you will have to use the drop-down menus.

  20. Add the following familiar line of code to all three event handlers:

    Call AI.RunAI(Me)
    

    The code for House.vb should now look like the following:

    'Here are the thermostat programmed values.
    Public ReadOnly SetTemps() As Integer = {70, 64, 68, 60}
    Public ReadOnly SetTimes() As Integer = {6, 9, 17, 21}
    
    'We check the furnace at startup and whenever conditions change.
    Private Sub House_Load(ByVal sender As Object, _
            ByVal e As System.EventArgs) Handles Me.Load
        Call AI.RunAI(Me)
    End Sub
    
    Private Sub Ambient_ValueChanged(ByVal sender As Object, ByVal e As _
            System.EventArgs) Handles AmbientUpDown.ValueChanged
        Call AI.RunAI(Me)
    End Sub
    
    Private Sub TimeUpDown_ValueChanged(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles TimeUpDown.ValueChanged
        Call AI.RunAI(Me)
    End Sub
    
    Private Sub AirRadio_CheckedChanged(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles AirRadio.CheckedChanged
        Call AI.RunAI(Me)
    End Sub
    
    Private Sub HeatRadio_CheckedChanged(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles HeatRadio.CheckedChanged
        Call AI.RunAI(Me)
    End Sub
    
  21. Now we move to AI.vb to create our more sophisticated AI. We will work from the wrapper toward the core AI. The core AI will need to know what operating mode to use, so the wrapper will need to get that from the world. Likewise, the core AI will need to know the right set-point temperature. The wrapper will need to know what time it is to get the right temperature value, but once that value is available, the core AI does not care what time it is. Change the wrapper to match the following code (every line changed):

    'This is the public wrapper. It knows about the world.
    Public Sub RunAI(ByVal World As House)
        Dim mode As CurrentMode
        Dim desired As Integer
    
        'interrogate the world about our settings
        desired = DesiredTemp(World)
        mode = FurnaceMode(World)
        'and let the core AI figure out what to do.
        World.StatusLabel.Text = CoreAI(CInt(World.AmbientUpDown.Value), _
            desired, mode)
    End Sub
    
  22. We have not yet written the code that gets the mode and desired temperature, so Visual Studio will quietly complain about the names not being declared. We have not yet defined what CurrentMode means either. It will also complain about the fact that we added an argument to the wrapper function’s call to the core but we have not yet changed the core AI. These complaints appear in the Error List tab at the bottom. They are also marked in the code the same way Microsoft Word marks spelling errors. First we will interrogate the world. Add the following code to get the mode of operation:

    'These modes should match up with radio buttons
    Private Enum CurrentMode
        Heat
        Cool
        'Off would go here
        Unknown
    End Enum
    Private Function FurnaceMode(ByVal World As House) As CurrentMode
        'we put all of the modes into parallel arrays
        'They MUST have the same number of entries
    
        Dim ModeRadios() As RadioButton = {World.AirRadio, World.HeatRadio}
        Dim ModeValues() As CurrentMode = {CurrentMode.Cool, CurrentMode.Heat}
    
        'we need a variable to iterate the arrays
        Dim i As Integer
    
        'Go through the array. Find the one that is checked
        'This code automatically adjusts to adding
        For i = 0 T o ModeRadios.GetUpperBound(0)
            If ModeRadios(i).Checked Then
                 Return ModeValues(i)
            End If
        Next
    
        'In case we forgot to check one of them
        Return CurrentMode.Unknown
    End Function
    

    The GetUpperBound function returns the highest valid subscript for the array. Writing the code this way means fewer things to keep synchronized. The loop always goes from the beginning of the array to the end. As long as the two arrays have the same number of items, the same subscript can be used for both.

    Note

    The function knows about the radio buttons we added to the form and puts them in an array. If we wanted to add a third mode, such as an explicit off mode, we would add another radio button to the form and add the name of that radio button to the array. We would also add a corresponding entry into the Enum and put that entry in the ModeValues array. On the form, VB groups all radio buttons that have the same container, such as a form, so that checking one unchecks all of the others.

    Note

    It is worth noting that explicit knowledge of the world implementation is creeping into the AI code. We will be successful at keeping it out of the core AI, but the wrapper and the world are two different files that have to be kept in sync.

  23. We still need to interrogate the world about the desired temperature. Add the following code to AI.vb:

    Private Function DesiredTemp(ByVal World As House) As Integer
        'We need some subscript variables
        Dim ss As Integer
    
        'the hours after midnight but before morning count as night
        '02:00 is after 21:00 but before 06:00, use the 21:00 value
        Dim foundss As Integer = 3
    
        'exploit the fact that we know that there are exactly 4 points
        'and that they are in time-sorted order
        For ss = 0 To 3
            'if it is at or past this set time, use this set time.
            If World.TimeUpDown.Value >= World.SetTimes(ss) Then
                foundss = ss
            End If
        Next
    
        'The times and temps are parallel arrays. A subscript for one
        'can be used on the other
    
        'Side effect: show what temp we are using
        World.SetPointLabel.Text = CStr(World.SetTemps(foundss))
    
        'pass that same value back to the AI.
        Return World.SetTemps(foundss)
    End Function
    

    This code also knows more than is healthy about how the world implemented the set points, but since that data is directly related to the AI code, it is not as likely to cause problems. The side effect of setting the text of the label is intentional. In this project, it helps debugging by showing that the right set point was selected. In a much broader sense, this is an important part of game AI. As pointed out in Chapter 1, the intelligence must be made noticeable to the player. In addition to finding ways to make the AI smarter or less stupid, the AI programmer must always be looking for ways to make the AI visible to the player.

  24. Having gathered data from the world, it is time to upgrade the core AI to make use of it. We will change the signature to include the mode and then make the rest of the code mode aware. Here is the new core code:

    'This function evaluates input temperatures and mode and gives back a
    'response for the furnace as a string
    Private Function CoreAI(ByVal currentTemp As Integer, _
            ByVal desiredTemp As Integer, _
            ByVal mode As CurrentMode) As String
        Select Case mode
            Case CurrentMode.Heat
                 'the same exact code as before
                 If currentTemp < desiredTemp Then
                     Return ("Heat")
                 Else
                     Return ("Ready")
                 End If
            Case CurrentMode.Cool
                 'note that we flipped the comparison
                 If currentTemp > desiredTemp Then
                     Return ("Cool")
                 Else
                     Return ("Ready")
                 End If
            Case Else
                 'this helps debug in case we forget to add
                 ' a new mode here as well as everywhere else.
                 Return "Bad Mode"
        End Select
    
        'this helps debug because we should never get here
        Return "Broken"
    End Function
    
  25. Run the application in the debugger and change the settings. Does the operation seem reasonable?

The heat side is perfectly reasonable, especially for a drafty old house that loses heat quickly and is expensive to heat. The air-conditioning settings seem positively frigid, especially the night setting. A more realistic implementation would have different temperatures for each mode, even if they kept the same times. Doing so adds four more numbers—easily done as another parallel array, but now the numbers interact with the mode. The DesiredTemp function now has to be mode aware. This means that the wrapper has to be changed to get the mode first before it gets the temperature, when before it did not matter. The existing code, which currently works, would have to be changed and the order of the calls fixed. When the two pieces of data were independent, there was less complexity. If we make them interact, the complexity increases. The increased complexity does not show up as increased length of code the way it did in the core AI, but in a nearly hidden way pertaining to statement order. The statements are currently close together, and the interaction would be obvious, but as the code grows, this might now always be the case.

State of the Art

Our last thermostat is modeled after those found in the author’s home. It has up to four set points per day, with each day having independent set points, giving 28 set points for heating and 28 more for cooling. It controls a geothermal heat pump that has two stages of cooling and three stages of heating. It also has a fan-only setting. The set points in this thermostat are treated differently. This thermostat anticipates the set points and attempts to have the temperature of the house at the set-point temperature by the set-point time. If the set point is for 68 degrees F at 06:00, this thermostat tries to have the room hit 68 degrees F at 06:00.

The other thermostats we have analyzed so far used their set points as start times, not end times. These latest thermostats determine the stage of heating or cooling required based on the number of degrees between the set point and the current temperature, the amount of time the current staging level has been running, and the current stage of operation.

Recall the discussion from Chapter 1 that AI is not physics. With our latest thermostat, we now have two different methods of operation. Old-style thermostats start heating at the set point time, and newer thermostats attempt to finish heating at the set point time. The fact that both methods of operation are valid can be seen as evidence that our thermostat example is more like AI than like physics. Rocks on cliffs do not get to choose the method of their falling.

The addition of more set points does not raise the level of complexity in the operation of the thermostat very much. The prior thermostat isolated the set points reasonably well, so the number of them will influence the speed of the code but not the complexity. This decoupling allows the code to scale up without increasing in complexity.

The increase in the level of complexity in this example comes from the implementation of anticipation and the staged output. The thermostat knows that large swings require a second stage. The first stage can almost always hold the house at a given temperature, but changes larger than five degrees are best satisfied with a second stage. The thermostat also calls for the next stage if the temperature does not climb rapidly enough. To make this calculation, the thermostat must track run time at the current stage and well as temperature gain at that stage. The stage called for also depends on the current output, because the unit will not revert to a lower stage if the set point has not yet been met. This algorithm calls for new data and many interactions. The thermostat is thinking about the process, so once again, knowledge representation creeps into the picture.

Implementation is left to the student.

Chapter Summary

The projects in this chapter lead the programmer from simple and effective if-then statements to a more data-driven approach. Hard-coded methods start out simple, fast, and effective, but can wind up brittle and hard to manage. Hard-coded AI can be both the smartest possible AI and the stupidest.

Chapter Review

Answers are in the appendix.

1.

What are the common drawbacks to hard-coded AI?

2.

What are the advantages to hard-coded AI?

3.

Complexity can be as low as the sum of the parts and as high as the product of the parts. What is the relationship between the parts when complexity is the sum? What is it when complexity is the product?

4.

What is the design of the data called when the data is information the AI uses to help it think about (or even imagine about) the world?

5.

Critique the expediencies in the code that interrogates the world in the four-set-point thermostat. Comment on the dangers versus the additional complexity needed to mitigate the risks.

6.

Why is the side effect in the code that gets the set-point temperature in the four-set-point thermostat important?

Exercises

1.

Add an explicit Off mode to the thermostat. You will need a additional radio button on the form, an Off entry in the Enum, and an entry in each of the two arrays that are used to turn a checked radio button into a mode value, and you will need to deal with the new mode in the core AI.

2.

Implement as many of the features of the last thermostat described as you can. If the specifications seem incomplete, search the Internet or document your reasonable assumptions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.224.226