Day 3: User Functions and Variables

We have a working robot now. In fact, if you did some tinkering over the weekend, you should have an army of robots ready to fight. These last two lessons will be about improving and tuning your robots to make them stronger, faster, better.

User Variables

Each robot is equipped with 20 value registers (variables) which can be used to store numerical values. For those of you who have done 3DGameStudio programming, these variables are similar to the skills that each entity has. The variables can be accessed by the name value1, value2, value3, on up to value20. And they can be used in normal equations like so:

   value1 = value2 + value3;
   value4 = ang(my_rot_vec.pan – target_rot_vec.pan);

You can use three consecutive user variables for vectors:

   // value1 = robot.x, value2 = robot.y, value3 = robot.z
   vec_set(value1,my_pos_vec);
   
   // value4 = pan to arena center, value5 & value6 = overwriten
   vec_to_angle(value4,value1);

Note: In the above case we are only interested in the value returned in value4 but, since vec_to_angle takes two vectors, value5 and value6 are overwritten in the process.

These value variables are also useful for storing values during consecutive frames. For instance, lets say we want our rocket robot to approach our target, fire 3 shots, run away for 10 seconds, and then repeat the cycle:

function AI_ROBOT()
{
   // value1 = state
   // value2 = counter
   
   // 0: attack state
   if(value1 == 0)
   {
      // fire at target when close
      if(distance_to_target < 2000)
      {
         value1 = 1;   // switch to fire state
         value2 = 0;   // reset counter
         // aim robot
         …
         if(abs(angle_to_target) < 5)
         {
            // fire gun
            …
            value2 += 1; // inc gun counter
         }
         return;
      }
   
      // approach target
      …
      return;
   }
   
   // 1: fire state
   if(value1 == 1)
   {
      // aim robot
      …
      if(abs(angle_to_target) < 5)
      {
         // fire gun
         …
         value2 += 1; // inc gun counter
      }
   
      if(value2 >= 3)   // 3 shots fired?
      {
         value1 = 2;   // run away state
         value2 = 0;   // reset counter
      }
      return;
   }
   
   // 2: run away state
   if(value1 == 2)
   {
      // run away code
      …
   
      value2 += time_delta; // inc counter with ticks
      if(value2 > 160) // 16ticks * 10 seconds
      {
         // start cycle over
         value1 = 0;   // attack state
         value2 = 0; // reset counter
      }
      return;
   }
   
   // catch any error
   value1 = 0;   // default to attack state
   value2 = 0;   
}

Wow, that is the most complicated script we have covered so far! And I left out the longer bits of code like aiming and moving. But don’t worry, I will cover these bits of code next.

 

Local Functions

It would be trivial to use what we already learned on day one and two to fill in those empty pieces of code (moving and aiming) but this would make this one function extremely long and hard to debug. To solve this we are going to use the second topic for today, local functions.

Note: Those of you familiar with 3DGS or other programming functions will immediately recongnize local functions as just normal functions. The only restriction is that you only get eight local functions and they must be named either FUNC_ONE, FUNC_TWO, FUNC_THREE, on up to FUNC_EIGHT.

Local functions work in a similar manner as or main AI function (AI_ROBOT), except we can call this function ourselves.

For example, let’s say we want a function for moving towards our target. This is something we do a lot, so it might be nice to have a separate function to handle it:

function FUNC_ONE()
{
   // clear brakes
   apply_brake = OFF;

   // turn towards target
   if(angle_to_target > 5) // target to the left…
   {
      // turn left
      force_left = -5;
      force_right = 100;
   }
   else
   {
      if(angle_to_target < -5) // target to the right…
      {
         // turn right
         force_left = 100;
         force_right = -5;
      }
      else   // target dead ahead
      {
         // move forward, full speed
         force_right = 100;
         force_left = 100;
      }
   }
}

 

This looks a lot like the AI_ROBOT movement code from day 1. The only real difference is that it is its own function and can be called at any time from the main AI function:

function AI_ROBOT()
{
   // call movement code
   FUNC_ONE();
}

We can add more functions as well for firing and hiding. We can mix and match functions, and even have functions call other functions as well.

NOTE: If you call a local function from inside another function make sure that you don’t create a loop:

function FUNC_ONE()
{
   FUNC_TWO(); // call function two
}

function FUNC_TWO()
{
   FUNC_ONE(); // call function one
}

function AI_ROBOT()
{
   FUNC_ONE(); // call function one
}

The code above will cause an endless loop, making your robot crash.

 

Putting it all Together (Local Functions and User Variables)

Something that is very useful in most programming endeavors is code reuse. In our original example, we have a section for running towards our target and another for running away. Each of these could be done as a separate local function, but there are only two lines of code that we would have to change:

   if(angle_to_target > 5) // target to the left…

with

   if(ang(angle_to_target+180) > 5) // target to the left…

-and-

   if(angle_to_target < -5) // target to the right…

with

   if(ang(angle_to_target+180) < -5) // target to the right…

It would be a waste of time to create two 26 lines functions when only two lines would need to be changed. What’s worse, if we wanted to turn to a 90o angle from our target we would have to write a whole new function (and another if we wanted to do –90o). What we need is a way to use an arbitrate value inside a local function. That way we could set the value to whatever heading we want and the robot will just turn in that direction.

User variables are ‘local’ to the robot, but all functions inside that robot can use and modify them. We can use this feature to pass values to and from local robot functions:

// movement function
// value7 = relative heading direction
function FUNC_ONE()
{
   apply_brake = OFF;

   // turn towards target
   if(value7 > 5) // target to the left…
   {
      // turn left
      force_left = -5;
      force_right = 100;
   }
   else
   {
      if(value7 < -5) // target to the right…
      {
         // turn right
         force_left = 100;
         force_right = -5;
      }
      else   // target dead ahead
      {
         // move forward, full speed
         force_right = 100;
         force_left = 100;
      }
   }
}

Now, all we need to do is set value7 to whatever value we want to turn to before we call this function:

   // approach target
   value7 = angle_to_target;
   FUNC_ONE();

   …

   // flee target
   value7 = ang(angle_to_target+180);
   FUNC_ONE();

   …

   // Turn Right
   value7 = -90;
   FUNC_ONE();

Not only does this save us from writing several functions that do roughly the same thing, it helps us when we tune and debug our robots. For instance, if you plan on using the Cannon robot you might want to increase the turning forces otherwise you will have a hard time tracking your targets. If we did three separate functions to handle movement we would have to make three times as many changes. With function reuse we only need to make one.

 

Since we can modify values inside local functions as well as read them, we can use them to pass values back to the main function as well:

// aim at target
// value6 = 1 if we can hit target, 0 otherwise
function FUNC_TWO()
{
   apply_brake = OFF;
   // turn towards target
   if(angle_to_target > 5) // target to the left…
   {
      // turn left
      force_left = -50;
      force_right = 50;
   }
   else
   {
      if(angle_to_target < -5) // target to the right…
      {
         // turn right
         force_left = 50;
         force_right = -50;
      }
      else   // target dead ahead
      {
         // stop moving
         apply_brake = ON;
         if((los_to_target == 1) && (gun1_status >= 1))
         {
            // ready to shoot
            value6 = 1;
            return;
         }
      }
   }
   value6 = 0; // do not have a shot to fire
   return;
}

This function aims the robot at the target. If the robot is already facing the target, we check to see if we have a clear shot and if our main gun is loaded. If so, we set value6 to 1, otherwise we set value6 to 0. We can then use this value elsewhere in our code to see if we should "take the shot":

   // aim robot
   FUNC_TWO();
   if(value6 == 1)
   {
      // fire gun
      gun1_fire = 1;
      gun2_fire = 2;
      value2 += 1; // inc gun counter
   }

 

Conclusion

In the last 4 days I have exposed all the mysteries of Robot AI programming. Really. That’s it! There is only one thing missing, determination. Programming a robot requires a bit of creativity, logic, and lots patients.

But don’t panic! Remember that this is only a game. Your robots will break down in the arena, they will bounce into walls, they might even shoot themselves. This is normal. Finding the cause of these idiosyncrasies is just part of the challenge. In the next lesson I will show you some tricks to make debugging your robots easier.

Below I’ve included the entire "Fire and Fade" script. Before you get too excited, this robot as is does very poorly against all its opponents. I present it here as a working example only. I’m sure somebody can adopt this poor robot and tune it into a killing machine. ;)

// Name: Doug Poston
// Alias: Doug
// Email: dposton@conitec.net
// Robot Name: Fire and Fade
function LOAD_ROBOT()
{
   robot_type = 2;   // use robot #1
}
 
// movement function
// value7 = heading direction
function FUNC_ONE()
{
   apply_brake = OFF;
 
   // turn towards target
   if(value7 > 5) // target to the left…
   {
      // turn left
      force_left = -5;
      force_right = 100;
   }
   else
   {
      if(value7 < -5) // target to the right…
      {
         // turn right
         force_left = 100;
         force_right = -5;
      }
      else   // target dead ahead
      {
         // move forward, full speed
         force_right = 100;
         force_left = 100;
      }
   }
}

// aim at target
function FUNC_TWO()
{
   apply_brake = OFF;

   // turn towards target
   if(angle_to_target > 5) // target to the left…
   {
      // turn left
      force_left = -50;
      force_right = 50;
   }
   else
   {
      if(angle_to_target < -5) // target to the right…
      {
         // turn right
         force_left = 50;
         force_right = -50;
      }
      else   // target dead ahead
      {
         // stop moving
         apply_brake = ON;

         if((los_to_target == 1) && (gun1_status >= 1))
         {
            // ready to shoot
            value6 = 1;
            return;
         }
      }
   }
   value6 = 0; // do not have a shot to fire
   return;
}

 
function AI_ROBOT()
{
   // value1 = state
   // value2 = counter

   // 0: attack state
   if(value1 == 0)
   {
      // fire at target when close
      if(distance_to_target < 1000)
      {
          value1 = 1;   // switch to fire state
         value2 = 0;   // reset counter
         // aim robot
         FUNC_TWO();
         if(value6 == 1)
         {
            // fire gun
            gun1_fire = 1;
            gun2_fire = 1;
            value2 += 1; // inc gun counter
         }
         return;
      }

      // approach target
      value7 = angle_to_target;
      FUNC_ONE();
      return;
   }

   // 1: fire state
   if(value1 == 1)
   {
      // aim robot
      FUNC_TWO();
      if(value6 == 1)
      {
         // fire gun
         gun1_fire = 1;
         gun2_fire = 2;
         value2 += 1; // inc gun counter
      }

      if(value2 >= 3)   // 3 shots fired?
      {
         value1 = 2;   // run away state
         value2 = 0;   // reset counter
      }
      return;
   }

   // 2: run away state
   if(value1 == 2)
   {
      // run away code
      value7 = ang(angle_to_target+180);
      FUNC_ONE();

      value2 += time_delta; // inc counter with ticks
      if(value2 > 160) // 16ticks * 10 seconds
      {
         // start cycle over
         value1 = 0;   // attack state
         value2 = 0; // reset counter
      }
      return;
   }

   // catch any error
   value1 = 0;   // default to attack state
   value2 = 0;
}