Ask the Plotscripting Moogle #2

Okay, since no one was nice enough to ask any questions this time, I've taken the initiative of deciding what this issue's article should be about. Don't complain; mail me your questions instead.
 

 __ __ __   ___  ___ _  ___   ____ __

Solving Problems and Artificial Intelligence
 

What is AI? More importantly, why do you want or need it in your game? First, let's look at the average Ohrrpgce game using the standard battle system. If enemies only use attacks under the "Standard" category, the battles become uninteresting and flat. More "intelligent" enemies use attacks in the "Alone" and "Desperation" categories. This isn't plotscripting, but it should give you an idea of why AI is important in your game.
 

It is important to note that I use the term "Artificial Intelligence" quite loosely in this article. The stricter definition involves the computer learning better techniques to solve a problem. We're only dealing with a set way of solving a problem here, which will probably be enough for any Ohrrpgce game you make.
 

It is also somewhat significant that I started out this article thinking it was an article about Artificial Intelligence but it turned into one about using accessor and helper functions. Either way, knowledge is knowledge. Maybe you'll learn something about both.
 

 __ __ __   ___  __ _ _  ___   ____ __

A Case Study: OHRRPGCE Tactics
 

Let's dive right into the material with a look at OHRRPGCE Tactics, whose custom battle system involves a much more complex AI than the regular Ohrrpgce battle engine. The AI structure looks something like this:

if (there is an enemy within my attack range)
 then (find the weakest enemy within my attack range and attack him)
if (i can get an enemy into my attack range by walking)
 then (walk to the weakest enemy within my walk+attack range and attack him)
 else (walk towards the nearest enemy)


Now, obviously this is an oversimplification. The actual code to perform those five lines is around three pages. However, this is where you have to start your AI unless you want to end up with a headache and a program that doesn't work. With that in mind, let's think of a problem for our "robot" (a term for the entity that is used to solve a problem). For the purposes of this example, it will be something that isn't ridiculously simple, but something that isn't too complex either.
 

 __ __ __   ___  __ _ _  ___   ____ __

Our Example: The Robot Lifter
 

Let's say we have a big pile of rocks that our AI 'bot wants to gather. He can go around picking them all up at once, but the rocks are heavy and carrying too many of them will slow him down. When he isn't carrying any rocks, his speed is 5. If he's carrying two, his speed is 4. If he's carrying three, his speed falls to 2. Four rocks will take his speed all the way down to 1, and he can't carry any more than that.
 

It would be simple to make a robot systematically pick up rocks until he had four, then carry them to the drop point. Too simple, in fact. Our robot will try to be faster than that robot. First, let's design a "flow chart" for our robot (again, in pseudocode).

while (any rocks remain) do, begin
 if (rocks = 4) then (return to drop)
 if (rocks = 3, and, closest rock within 5 spaces of drop)
  then (pick up closest rock)
 if (rocks = 3, and, closest rock farther than 5 spaces from drop)
  then (return to drop)
 if (rocks = 2, and, closest rock within 10 spaces from drop)
  then (pick up closest rock)
 if (rocks = 2, and, closest rock farther than 10 spaces from drop)
  then (return to drop)
 if (rocks << 2) then (pick up closest rock)
 wait for robot
end
return to drop


Again, this is a simplified version. Notice that this is only one solution to the rocks problem and that it is probably not the fastest solution. However, it should be faster than the robot that always picks up as many rocks as it can. When the rocks are too far from the drop zone, it will go back while its speed is still high.
 

We have our general method of solving the problem, but we need to translate it to HamsterSpeak. First, let's define and write accessor functions that will make the implementation look more like our pseudocode. (Big words! But it works out in the end -- just wait.)
 

Let's give ourselves a few conventions before we define these functions: first, that the robot is the leader of the party; second, that the robot can and will walk diagonally; and third, that the drop point is located at X=0, Y=0.

define script (autonumber, any rocks remain, none)
define script (autonumber, nearest rock x, none)
define script (autonumber, nearest rock y, none)
define script (autonumber, distance, 2, 0, 0)
define script (autonumber, get rock, none)
global variable(1,rocks)
script, distance, x, y, begin #distance from (0,0)
 if (x >> y) then (return x)
  else (return y) #remember, he walks diagonally
end


For more information on return functions, check the plotscripting dictionary and the examples in PLOTSCR.HSD.
 

The functions for the nearest rock, get rock, and rocks left aren't defined here since we haven't defined what the rocks actually are (whether they're NPCs, maptiles, et cetera). The more functions you make now, the easier the implementation will be later.
 

Now, suppose that we actually implemented the "rock" functions. How do we go about translating our pseudocode into HamsterSpeak? The funny thing about the pseudocode is that it will look very much like the finished HS code. Let's take a look. Assuming that "getrock" changes the speed and removes the rock from the field, here's the function:

script, rockscript, begin
setherospeed(me,5)
while (any rocks remain) do, begin
  if (rocks == 4) then
   (moveherotox(0)
    moveherotoy(0)
    waitforhero(me)
    rocks:=0
    setherospeed(me,5))
  if (rocks == 3, and, distance(nearestrockx,nearestrocky) <= 5)
   then
   (moveherotox(nearest rock x)
    moveherotoy(nearest rock y)
    getrock
    rocks:=rocks+1)
  if (rocks == 3, and, distance(nearestrockx,nearestrocky) >> 5)
   then
   (moveherotox(0)
    moveherotoy(0)
    waitforhero(me)
    rocks:=0
    setherospeed(me,5))
  if (rocks == 2, and, distance) then
   (moveherotox(nearest rock x)
    moveherotoy(nearest rock y)
    getrock
    rocks:=rocks+1)
  if (rocks == 2, and, distance(nearestrockx,nearestrocky) >> 10)
   then
   (moveherotox(0)
    moveherotoy(0)
    waitforhero(me)
    rocks:=0
    setherospeed(me,5))
  if (rocks << 2) then
   (moveherotox(nearest rock x)
    moveherotoy(nearest rock y)
    getrock
    rocks:=rocks+1)
  wait for hero(me)
 end
 moveherotox(0),moveherotoy(0)
end


Simple enough, right? Look at the pseudocode and compare it to this function. They're the same.
 

The great thing about this method is that it applies to almost any game (and most non-games as well... as if we were concerned with that). It's a very convenient way to approach scripting and makes your script more readable. 
 

 __ __ __   ___  __ _ _  ___   ____ __

In Conclusion

Umm... I got off track a little this time. Oh, well. The moral of this issue's lesson is to use helper functions. They make your code ten times as readable and often much shorter.


 

Send me your questions! I think I already said I didn't get any response to last month's column. E-mail me at moogle1@wizardmail.com.

 
 
C O V E R
 
C O N T E NT S
D E S I G N
 
T O P   3 0
 
I N T E R V I E W S
 
P R E V I E W S
 
G A M E S