Driving while Blind, Directional enemy steering

2-16-2018

In my last article, we were concerned about making sure the enemy follows the same rules as the player, mainly not touching an obstacle. There are a number of ways that this can happen in logic, but let’s try to approach the most simple method. I’m leaning towards more simple because simple in code means less memory and more speed.

We’re going to have to give the function a little more to work with, we not only need to know our player’s whereabouts, but we also need to know the enemies whereabouts. Additionally, let’s push the enemyf variable, as we will eventually be using this variable. Just for reminder sake, the enemyf variable does not directly affect the enemy sprite.

Let’s expand our function to now adjust enemy movements

void enemyCollision(unsigned char playerX, unsigned char playerY, unsigned char enemyXX, unsigned char enemyYY, unsigned char enemyFF)

This will feed our function with the much-needed information to make our enemies a little smarter (a relative statement, they’re still pretty dumb).

Now we need to reuse and create some additional variables to support our reworking of the function.

unsigned char barrierPosition;
unsigned char playerCharX, playerCharY;
unsigned char enemyCharX, enemyCharY;
unsigned char enemyXwest, enemyXeast;
unsigned char enemyYnorth, enemyYsouth;	
unsigned char barrierNorth, barrierSouth, barrierEast, barrierWest;

Since this is a prototype, we will place the variables as local and keep them in the function, later on, we will optimize the variables.

Time for some calculations and variable assignments.

We will take some of the variables from my last article and reuse them here as well as adding new ones to support the enemy movement possibilities.

playerCharX = playerX >> 4;
playerCharY = playerY >> 4;	
enemyCharX = enemyXX >> 4;
enemyCharY = enemyYY >> 4;	
enemyYnorth = (enemyYY + 6) >> 4;
enemyYsouth = (enemyYY - 6) >> 4;
enemyXeast = (enemyXX + 6) >> 4;
enemyXwest = (enemyXX - 6) >> 4;
decision = 0;
barrierPosition = 0;
barrierNorth = tiles[(enemyYnorth - 1) * scrw + enemyCharX];
barrierSouth = tiles[(enemyYsouth + 1) * scrw + enemyCharX];
barrierWest = tiles[enemyCharY * scrw + (enemyXeast - 1)];
barrierEast = tiles[enemyCharY * scrw + (enemyXwest + 1)];

We’ve got a lot going on here, but it’s really not complicated at all, but let’s explain a bit.

Of course, our function is feeding in our enemyX and enemyX (from the main.c) positions to the enemyXX and enemyYY (in the enemyCollision function). We already have some globals defines as enemyX and enemyY, we will be using those shortly.

playerCharX = playerX >> 4;
playerCharY = playerY >> 4;	
enemyCharX = enemyXX >> 4;
enemyCharY = enemyYY >> 4;

Here we are converting the pixel plotting coordinates to the character plotting coordinates. Remember that the pixel-based coordinates are from 0 to 240 in the X-axis and 0 to 160 in the Y-axis. The character-based coordinates are from 0 to 15 in the X-axis and 0 to 9 in the Y-axis. We will do this for both the player and the enemy.

In fact, to keep track of it, we will place a comment of the ranges.

//playerCharX character range 0 - 15
//playerCharY character range 0 - 9	
//enemyCharX character range 0 - 15
//enemyCharY character range 0 - 9
//playerX pixel range 0 - 240
//playerY pixel range 0 - 160
//enemyX pixel range 0 - 240
//enemyY pixel range 0 - 160

Next, let’s place out our “white canes” or our sensors. These will look ahead two pixels to the north, south, east and west of the enemy sprite so it can tell just before it touches an obstacle.

enemyYnorth = (enemyYY + 6) >> 4;
enemyYsouth = (enemyYY - 6) >> 4;
enemyXeast = (enemyXX + 6) >> 4;
enemyXwest = (enemyXX - 6) >> 4;

These variables will take the pixel-based positions of the enemy, add 6 to the position and then convert it to the character-based position.

These next two variables:

barrierPosition = 0;
decision = 0;

will be used to report our barriers to the function by telling our enemy where the barrier lies, either to the north, south, east, west, northeast, southeast, northeast or northwest. The last 4 directions are just a combination of the first four directions. The decision of the enemy will be based on the direction it cannot travel in.

barrierNorth = tiles[(enemyYnorth - 1) * scrw + enemyCharX];
barrierSouth = tiles[(enemyYsouth + 1) * scrw + enemyCharX];
barrierWest = tiles[enemyCharY * scrw + (enemyXeast - 1)];
barrierEast = tiles[enemyCharY * scrw + (enemyXwest + 1)];

Here are our barriers to detect. These will work with our enemy sensors to detect to object in question.

Now that the variables have been setup, we will need to set our barrierPosition to tell the function which direction the barrier is in.

if (barrierNorth > 0)
{
    barrierPosition = barrierPosition + 1;
}
if (barrierSouth > 0)
{
    barrierPosition = barrierPosition + 2;
}
if (barrierEast > 0)
{
    barrierPosition = barrierPosition + 4;
}
if (barrierWest > 0)
{
    barrierPosition = barrierPosition + 8;
}

Here is our bit of math to calculate the position of the barrier. It uses a base 2 (binary) numbering system to provide a unique number for each of the 4 base cardinal positions (north, south, east, west) and also combine 2 of the four to calculate the other 4 cardinal positions (northeast, northwest, southeast, southwest).

For right now, let’s place in a random number generator.

decision = (SHR3 % 4);//our random number from the macro , range is 1 to 4

We are going to use this to make a decision on which way to travel if there is a barrier ahead. I’m generating a number between 1 and 4 so that when the enemy encounters a barrier, it can make a random decision of which way to travel. In reality, we only need a number between 1 and 3, since the 4th number would be the direction you cannot travel. So, for that decision (possibly the wrong direction), we just will leave blank.

So, at this juncture, we are reading the player position, the enemy position, seeing if the enemy is about ready to run into a barrier, check to see which direction the obstacle lies and finally come up with a random number.

As I wrote in my last article, the barrierPosition variable will give us a number between 0 and 15. 0 means there is no obstacle, 15 means that the enemy is surrounded by obstacles. In between, each of those extremes we have to use logic to determine which way we should travel and along with a random number, move the enemy.

We will do this with 15 If statements. Our first example will be if there is no obstacle in the way.

If there is no barrier blocking us, we are in barrierPosition 0, and our code looks like.

if (barrierPosition == 0)
{
    //move toward player
    //move toward player x and y		
    if (enemyX > playerX)
    {
        enemyX--;
    }
        if (enemyX  playerY)
    {
        enemyY--;
    }
    if (enemyY < playerY)
    {
        enemyY++;
    }
}

Here we are purely moving toward the player. If the player is above the enemy, it will move up, the same for below and side to side.

If the enemy is surrounded by obstacles then we use the following:

if (barrierPosition == 15)//cannot move any direction
{
    //do nothing
}

The next easiest scenario is if the enemy is surrounded on all 3 sides. In that case, we will move in the direction that is open. There are going to be 4 times that it will happen for example if it is blocked to the north, south, and east, then we have to move west.

Here are our 4 scenarios

    if (barrierPosition == 7)//cannot move north, south, east
    {
        //move west
        enemyX--;//go west
    }

    if (barrierPosition == 11)//cannot move north, south, west
    {        
        //move east
        enemyX++;//go east
    }

    if (barrierPosition == 13)//cannot move north, east, west
    {        
        //move south
        enemyY++;//go south
    }

    if (barrierPosition == 14)//cannot south, east, west
    {
        //move north
        enemyY--;//go north
    }

Notice, there are a number of comments, gotta comment on your code otherwise it’s so easy to lose track.

The next scenario is if the enemy is caught in a corner between 2 obstacles, we will need to move in a direction away from that corner. Here we will use our random number to make that move away from the corner. There will be 4 scenarios that will cover that.

    if (barrierPosition == 5)//cannot move north or east
    {
        //move south, west
        if (decision == 1)
        {
            enemyY++;//go south
        }
        if (decision == 2)
        {
            enemyX--;//go west
        }
    }    
    if (barrierPosition == 6)//cannot move south or east
    {        
        //move north, west
        if (decision == 1)
        {            
            enemyY--;//go north
        }
        if (decision == 2)
        {
            enemyX--;//go west
        }
    }
    if (barrierPosition == 9)//cannot move north, west
    {        
        //move south, east
        if (decision == 1)
        {
            enemyY++;//go south
        }
        if (decision == 2)
        {
            enemyX ++;//go east
        }
    }    
    if (barrierPosition == 10)//cannot move south, west
    {        
        //move north, east
        if (decision == 1)
        {            
            enemyY--;//go north
        }
        if (decision == 2)
        {
            enemyX++;//go east
        }
    }

Since our random number is from 1 to 4, we have a 50 percent chance that the enemy will move at all, the other 50 percent of the time, it will move either north, south, east or west depending on what is set in the If statement.

The next scenario is if the enemy is enclosed via top and bottom and the sides are clear or if the enemy is surrounded on the sides while the top and bottom are free. There are two times that will happen.

    if (barrierPosition == 3)//cannot move north or south
    {        
        //move east, west
        if (decision == 1)
        {
            enemyX++;//go east
        }
        if (decision == 2)
        {
            enemyX--;//go west
        }
    }

    if (barrierPosition == 12)//cannot move east, west
    {        
        //move north, south
        if (decision == 1)
        {            
            enemyY--;//go north
        }
        if (decision == 2)
        {
            enemyY++;//go south
        }
    }

If the enemy is between two items in either the X or Y position, then move then opposite axis.

Finally, the last 4 scenarios are when the enemy is blocked in one direction. There are 3 other directions the enemy can travel in, so on a random number between 1 and 4, they have a 75 percent chance they will move.

The logic for that will be:

    if (barrierPosition == 1)//cannot move north
    {        
        //move south, east, west        
        if (decision == 1)
        {
            enemyY++;//go south
        }
        if (decision == 2)
        {
            enemyX++;//go east
        }
        if (decision == 3)
        {
            enemyX--;//go west
        }
    }    
    if (barrierPosition == 2)//cannot move south
    {        
        //move north, east, west        
        if (decision == 1)
        {
            enemyY--;//go north
        }
        if (decision == 2)
        {
            enemyX++;//go east
        }
        if (decision == 3)
        {
            enemyX--;//go west
        }
    }    
    if (barrierPosition == 4)//cannot move east
    {        
        //move north, south, west
        if (decision == 1)
        {
            enemyY--;//go north
        }
        if (decision == 2)
        {
            enemyY++;//go south
        }
        if (decision == 3)
        {
            enemyX--;//go west
        }
    }    
    if (barrierPosition == 8)//cannot move west
    {        
        //move north, south, east
        if (decision == 1)
        {
            enemyX++;//go east
        }
        if (decision == 2)
        {            
            enemyY--;//go north
        }
        if (decision == 3)
        {
            enemyY++;//go south
        }
    }

If we compile this now, we will discover another problem. Enemies will not respect the border edges, so we have to place a little more logic in the code to counter that.

    //keep enemies from going past border
    //need hard numbers
    if (enemyX > 232)
    {
        enemyX--;
    }
    if (enemyX  152)
    {
        enemyY--;
    }
    if (enemyY < 8)
    {
        enemyY++;
    }

So, here’s a complete function. Code dump warning.

void enemyCollision(unsigned char playerX, unsigned char playerY, unsigned char enemyXX, unsigned char enemyYY, unsigned char enemyF)
{
    unsigned char barrierPosition;
    unsigned char playerCharX, playerCharY;
    unsigned char enemyCharX, enemyCharY;
    unsigned char enemyXwest, enemyXeast;
    unsigned char enemyYnorth, enemyYsouth;    
    unsigned char barrierNorth, barrierSouth, barrierEast, barrierWest;
    
    //playerCharX character range 0 - 15
    //playerCharY character range 0 - 9    
    //enemyCharX character range 0 - 15
    //enemyCharY character range 0 - 9
    //playerX pixel range 0 - 240
    //playerY pixel range 0 - 160
    //enemyX pixel range 0 - 240
    //enemyY pixel range 0 - 160

    playerCharX = playerX >> 4;
    playerCharY = playerY >> 4;
    
    enemyCharX = enemyXX >> 4;
    enemyCharY = enemyYY >> 4;    
    enemyYnorth = (enemyYY + 6) >> 4;
    enemyYsouth = (enemyYY - 6) >> 4;
    enemyXeast = (enemyXX + 6) >> 4;
    enemyXwest = (enemyXX - 6) >> 4;
    
    decision = 0;
    barrierPosition = 0;

    barrierNorth = tiles[(enemyYnorth - 1) * scrw + enemyCharX];
    barrierSouth = tiles[(enemyYsouth + 1) * scrw + enemyCharX];
    barrierWest = tiles[enemyCharY * scrw + (enemyXeast - 1)];
    barrierEast = tiles[enemyCharY * scrw + (enemyXwest + 1)];

    //if a tile to the north + 1
    //if a tile to the south + 2
    //if a tile to the east + 4
    //if a tile to the west + 8    
    
    if (barrierNorth > 0)
    {
        barrierPosition = barrierPosition + 1;
    }
    if (barrierSouth > 0)
    {
        barrierPosition = barrierPosition + 2;
    }
    if (barrierEast > 0)
    {
        barrierPosition = barrierPosition + 4;
    }
    if (barrierWest > 0)
    {
        barrierPosition = barrierPosition + 8;
    }

    decision = (SHR3 % 4);//our random number from the macro , range is 1 to 4
    //let's make a decision based on a random direction
    
    if (barrierPosition == 0)
    {
        //move toward player
        //move toward player x and y        
        if (enemyX > playerX)
        {
            enemyX--;
        }
        if (enemyX  playerY)
        {
            enemyY--;
        }
        if (enemyY  232)
    {
        enemyX--;
    }
    if (enemyX  152)
    {
        enemyY--;
    }
    if (enemyY < 8)
    {
        enemyY++;
    }
}

We call our new function via.

{
    enemyX = sprites[i].x;
    enemyY = sprites[i].y;
    enemyImage = sprites[i].n;
    enemyF = sprites[i].f;

    enemyCollision(playerXpos, playerYpos, enemyX, enemyY, enemyF);

    sprites[i].x = enemyX;
    sprites[i].y = enemyY;
    sprites[i].n = enemyImage;
    sprites[i].f = enemyF;
} 

We’re finished for now, however, there are still some items we need to address, as the enemy sprites start to move in a jerky manner when encountering a barrier. That jerking motion is directly related to the random number. Pretty soon, we will try to adjust that a bit. Perhaps by giving some bias to the random numbers or by adding some additional variables.

Until then, happy coding.

Advertisements

Author: andydansby

I'm a hobbyist coder working with the ZX Spectrum. Living in New York state near the Syracuse area. I grew up in Virgina. The first computer my parents bought for me was a Timex Sinclair 2068.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s