Grassfire using a kernel approach – the code.

5-23-2018
The last article I wrote started to get a bit lengthy, so I decided to split it into several parts. Our last article, covered the algorithm in general, here we are going to delve into the code itself

The first thing we need to cover is creating our array, this is done in our variables.h header.

unsigned char grassfire[150] = {
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

The next thing we will need to do is to prepare the grassfire array.

The next thing we will need to do is to prepare the grassfire array. To do this we create a new function.

// prep grassfire array
// using info from game array we will need to clear the array
// and read the obstacles into the array 
// and obstacle encountered will be treated the same and 
// be marked with a 255
void prepGrassFire()
{
    arrayCounter = 0;//
    center = 0;    
    screenScanX = screenScanY = 0;
    
    //array is 150 slots big
    for(arrayCounter=0; arrayCounter  0)
            grassfire[center] = 255;
        else
            grassfire[center] = 0;

        screenScanX++;//
    }
}

Here we are scanning our tile array, anything in our tile array that does not have a 0 in it, is marked as a 255 and placed in the grassfire array. The grassfire array is going to be used as a guidance for the enemies to move. This is used to mark our barriers so the grassfire will plot a course around barriers. Anything that is printed to the screen is counted as a barrier. Your game may be different if you want the enemy to not worry about some items and not others.

Now, let’s look at our grassfire algorithm.

void continousGrassFire()
{
    grassfire[playerCharX + scrw * playerCharY] = 1;    
    screenCounterEnd = screenCounterStart + scrw;//scan an entire line
    
    for(screenCounter = screenCounterStart; screenCounter  150)
            {
                screenCounterStart = 0;
                screenCounter = 0;
                screenCounterEnd = 0;
            }
        }//makes sure the array is properly traveresed
        
        center = screenScanX + scrw * screenScanY;
        north = screenScanX + scrw * (screenScanY - 1);
        south = screenScanX + scrw * (screenScanY + 1);
        east = screenScanX + 1 + scrw * screenScanY;
        west = screenScanX - 1 + scrw * screenScanY;

        centerValue = grassfire[center];
        //refresh the barriers to make sure they stay intact
        if (tiles[center] > 0)
        {
            grassfire[center] = 255;
        }
        
        //not a barrier
        if (centerValue != 255)
        {
            // if the kernel edge is out of range of the array
            // assign a high value so, it will not be taken into account
            
            if (screenScanY  scrw - 2)//control borders of array
                eastValue = 255;
            else//read array value
                eastValue = grassfire[east];
            
            compare1 = min(northValue, southValue);
            compare2 = min(eastValue, westValue);
            compare3 = min(compare1, compare2);
            //here is where the central tile is assigned a value
            if (centerValue != 1)
            {
                centerValue = compare3 + 1;
            }
            else
            {
                centerValue = 1;
            }
                
            grassfire[center] = centerValue;
        }//end center value not 255
        
        screenScanX++;        
        screenCounterStart += screenCounterEnd;
    }//end arrayCounter look    
}

There’s a lot going on, so let’s explore this in a little more detail.

grassfire[playerCharX + scrw * playerCharY] = 1;

Here we are placing a 1 in the grassfire tile our player is in. This way, no matter where on the array, the player is, it is always marked with a 1. The grassfire is always based on where the player (target is).

screenCounterEnd = screenCounterStart + scrw;//scan an entire line

Here we are calculating the end of the scan, this scan is going to be 1 row at a time (15 tiles).

for(screenCounter = screenCounterStart; screenCounter < screenCounterEnd; screenCounter++)

This is our loop, basically scanning from the start to the end of the row.

 //so we do not go out of bounds of the array
        {
            //makes sure the array is properly traveresed
            if(screenScanX == scrw)
            {
                screenScanX = 0;//set to first column
                screenScanY ++;//move down a row
            }
            if(screenScanY == scrh)
            {
                screenScanY = 0;//set to first row
                screenScanX = 0;//set to first column
            }
            if (screenCounterEnd > 150)
            {
                screenCounterStart = 0;
                screenCounter = 0;
                screenCounterEnd = 0;
            }
            //makes sure the array is properly traversed
        }

Here we are making sure our array is properly scanned. If you go past the width of the tileset, it will reset back to 0 and increase our Y position. If we exceed our height, it will reset both the X and Y position back to 0. If we exceed the total number of tiles, we reset all back to 0. The last If is a catch-all to make sure we do not exceed the bounds of the array.

Now, we need to set up our kernel, that is done with the next few lines.

center = screenScanX + scrw * screenScanY;
north = screenScanX + scrw * (screenScanY - 1);
south = screenScanX + scrw * (screenScanY + 1);
east = screenScanX + 1 + scrw * screenScanY;
west = screenScanX - 1 + scrw * screenScanY;

Now, we will know the neighbors to the north, south, east, and west as well as our center tile. That will come into play very soon.

centerValue = grassfire[center];

Now we know the value of the center tile.

// no need to evaluate a barrier
if (centerValue == 255)
{
    screenScanX++;
}

If the center tile is a barrier (marked by a 255), then we will just skip the tile, no need to do any calculations on a center tile.
We now need to do the business end of the algorithm, however, we need to make sure that it is as simple as possible.

//not a barrier
        if (centerValue != 255)
        {
            // if the kernel edge is out of range of the array
            // assign a high value so, it will not be taken into account
            
            if (screenScanY  scrw - 2)//control borders of array
                eastValue = 255;
            else//read array value
                eastValue = grassfire[east];
            
            compare1 = min(northValue, southValue);
            compare2 = min(eastValue, westValue);
            compare3 = min(compare1, compare2);

            //here is where the central tile is assigned a value
            if (centerValue != 1)
            {
                centerValue = compare3 + 1;
            }
            else
            {
                centerValue = 1;
            }
                
            grassfire[center] = centerValue;
        }//end center value not 255

This portion requires its own explanation.

if (centerValue != 255)

I could have done an else here, however for my sanity and so that others could more easily follow, I decided to write it like this.

It basically means that if the center tile is not 255 (meaning it is not a barrier) then we will then proceed.

//control borders of array
if (screenScanY < 1)
    northValue = 128;
 else
    northValue = grassfire[north];

Here, we are making sure that the north of the kernel is within range, this only takes place on the top edge of the kernel, which is on tiles x =0 to 15 and Y = 1. If it is, then we assign a 128 to the value, if it is not, then just read the tile of the wavefront in. We are going to repeat the same for all edges. In this manner, we are avoiding exceeding the edges of the array and stay within the limits.

At this point, if we are within valid ranges, we now know not only the center value but also the north, south, east and west values. Now we can do our comparisons.

compare1 = min(northValue, southValue);
compare2 = min(eastValue, westValue);
compare3 = min(compare1, compare2);

Here, we are looking to see which of the 4 tiles are the smallest value, this is based on the min macro that we built during another blog entry, just for a refresher it’s written as.

#define min(x,y) (((x)<(y))?(x):(y))

Now, we need to write our calculation in the array.

if (centerValue != 1)
{
    centerValue = compare3 + 1;
}
else
{
    centerValue = 1;
}
grassfire[center] = centerValue;

Now, one thing I will point out here, is we are making sure that we do not write over our target. That done with our NOT boolean logic.

if (centerValue != 1)

if our value is not 1, then we change the array. Else we write a 1 into the position.

Finally we can write the array with.

grassfire[center] = centerValue;

We now move the kernel over by 1 with the line.

screenScanX++;

Finally, we increase our start position by using the line.

screenCounterStart += screenCounterEnd;

And now our method is complete. Congratulations, we now have a way for the enemy to find the target (the player).

However, we encounter another problem. The enemies on the other side of the screen will stand still for a little while before starting to move. The reason for this is, the enemy does not know where to go until the grassfire fills in. The next thing that is noticed is that the enemy does not move in the way one expects. This is because we have to update our enemy movement function, so there seems to be a bit more work that has to be done.

Coming up next, some more changes to the grassfire as well as moving the enemy and estimations.

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