| |
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.
|