[Tutorial] Looping through arrays (two-dimensional)
#1

Looping Through Arrays

I'm writing this because I see lots of people doing things in an old fashioned style, which leads to a lot of code that is hard to maintain if it needs to be changed. That's why looping through arrays is so intresting. As I think everyone has looped through a single dimensional array many times, I'll only talk about multi dimensional arrays. I'll be covering only 2D arrays for now (I wanted to do 3D too, but the tutorial became so long that I changed my mind).

2D Arrays
Let's say we have a bunch of sets of coordinates and that we want to create a pickup with every set. Some people would probably do it the old fashioned way, like so:
pawn Code:
house1 = CreatePickup(...);
house2 = CreatePickup(...);
//...
house10 = CreatePickup(...);
and then later on:
pawn Code:
if(pickupid == house1) {}
else if(pickupid == house2) {}
//...
else if(pickupid == house10) {}
As you can see, this is very hard to maintain and it leads to a lot of copy-pasted code (which is what we're trying to avoid). Now I'll explain the array method. We'll put all sets of arrays in a 2 dimensional array and we will then proceed to use for-loops to both create and check the pickups.

You can use an enumerator to name the array indices (singular: index), but it's not a necessity. I will demonstrate this example with the help of an enum though. If you decide not to use an enum, you must - since we'll be working with floats - prefix your array variable with the Float: tag.

pawn Code:
enum eCoordVars {
    Float:x,
    Float:y,
    Float:z
}
Now we'll be creating the actual array:
pawn Code:
static const
    g_CoordSets[][eCoordVars] = {

    };
(Consult this wiki article to see why I prefer static over new). The reason the variable is declared const is because the info contained in it will never change.

As you can see I left the first dimension empty (the first pair of braces). This dimension will contain the index, or slotid as you may name it, for each set. Some people prefer to initialize it with a static value (whether or not in a definition), but I prefer not to. The compiler can calculate this value itself and it has the advantage that it will not ever generate a compiler error (052: multi-dimensional arrays must be fully initialized).

The array is now created and we'll proceed to initialize it with info, we're going to add each set of coordinates between the two curly braces that are already present. Each set must be wrapped in its own pair of curly braces, like so:
pawn Code:
{2247.66, 2396.26, 9.8218}
//    x       y       z
// The 3 values mentioned earlier, intended for the second dimension
Our same array, now filled with a couple sets of coordinates:

pawn Code:
static const
    g_CoordSets[][eCoordVars] = {
        {2247.66, 2396.26, 9.8218}, // Index/Slot 0
        {2452.47, 2065.15, 9.8472}, // 1
        {2546.71, 1972.28, 9.8220}, // 2
        {2884.83, 2453.28, 10.061},
        {2194.38, 1991.02, 11.301},
        {2097.76, 2224.20, 10.058},
        {1937.25, 2307.17, 9.8222}  // 6
    };
Every set of coordinates is suffixed with a comma, EXCEPT the last one. (And if anyone's wondering: these coordinates are the entrance points for all 24/7 stores in Las Venturas). If you followed everything correctly untill now, you can try and compile your script. If you did everything right, only this warning should popup: warning 203: symbol is never used: "g_CoordSets". That's okay, since we merely created our array and didn't use it yet.

Now, to create our pickups, we'll search for OnGameModeInit (or OnFilterScriptInit, as you please) and start our for-loop;

pawn Code:
for(initializers ; condition ; increment)
We'll create a new temporary variable in the Initializer section, which will serve as index for the first dimension. The condition is of course 'Have we reached the end of the array already?" and the increment is of course +1 with each itteration. Let's put that in code:

pawn Code:
for(new i; i < sizeof(g_CoordSets); i++)
A note on sizeof: Sizeof is an operator, not a function. Its value is calculated at compile time, not at runtime and it is therefor no slower than using a defenition. To find out the value of the second or third dimension using sizeof, one would respectively use sizeof(array[]) or sizeof(array[][]).

Creating our pickups now is simple:
pawn Code:
for(new i; i < sizeof(g_CoordSets); i++)
{
    CreatePickup(1318, 23, g_CoordSets[i][x], g_CoordSets[i][y], g_CoordSets[i][z]);
}
That's it. All our pickups are created.

Now, how to check what pickup was picked up when someone walks over one? Using our same loop in OnPlayerPickUpPickup we can find out. This is (probably) slightly slower than doing the old fashioned if-if-if structure, but in turn you have much less and much more maintainable code.

pawn Code:
public OnPlayerPickUpPickup(playerid, pickupid)
{
    for(new i; i < sizeof(g_CoordSets); i++)
    {
        if(IsPlayerInRangeOfPoint(playerid, 3.0, g_CoordSets[i][x], g_CoordSets[i][y], g_CoordSets[i][z]))
        {
            printf("[debug] Player %d picked up pickupid: %d, array index is %d.", playerid, pickupid, i);
            // Do stuff
            break;
        }
    }
    return 1;
}
Notice the use of the keyword break. Once a pickup is found, we know that this player is not in range of any other pickups, so it's useless to go on and check those too.

Postscriptum: Once you get the hang of how this works, you can further expand upon this theory. You can, for example, add more coordinates and values to each set which declare an interior or another place to teleport to. You can then set the player's location using the same index.

This for example, is one line of one of my own scripts, see if you can figure out what all the variables are.
pawn Code:
{2247.66, 2396.26, 9.8218, 0.000,   -26.6916, -57.82, 1002.60, 0.000,   6"24-7"              }
Reply
#2

Great tutorial. Good job.
Reply
#3

Just what I was looking for. Nice!
Reply
#4

Sorry for this bump. But I have a question.

pawn Code:
//read the comments

enum CPData
{
    Float:x,
    Float:y,
    Float:z,
    Strings[]
}

new CP[][CPData] =
{
    {123.0, 456.0, 789.0, "You have entered CP 1"},
    {987.0, 654.0, 321.0, "You have entered CP 2"},
    {0.0, 0.0, 1.0, "You have entered CP 3"}

};

//gamemodeinit
for(new i; i < sizeof(CP); i++)
{
    CreateDynamicCP(CP[i][x], CP[i][y], CP[i][z]);
}

public OnPlayerEnterDynamicCP(playerid, checkpointid)
{
    for(new i; i < sizeof(CP); i++)
    {
            //what should i do here to display CP 2(or any) message?
    }
};
Reply
#5

nice tutorial
Reply
#6

Nice tutorial, but it could be better if you stored the pickup-id into the array as well.

pawn Code:
enum eCoordVars
{
    id,
    Float:x,
    Float:y,
    Float:z
}


Then create them and save the id in the array when you create the pickups:

pawn Code:
for(new i; i < sizeof(g_CoordSets); i++)
{
    g_CoordSets[i][id] = CreatePickup(1318, 23, g_CoordSets[i][x], g_CoordSets[i][y], g_CoordSets[i][z]);
}


When looping through them, just check for the correct pickup-id instead of a range-check:

pawn Code:
public OnPlayerPickUpPickup(playerid, pickupid)
{
    for(new i; i < sizeof(g_CoordSets); i++)
    {
        if(g_CoordSets[i][id] == pickupid)
        {
            printf("[debug] Player %d picked up pickupid: %d, array index is %d.", playerid, pickupid, i);
            // Do stuff
            break;
        }
    }
    return 1;
}
Doing a range-check is much slower than comparing 2 integers.
Reply
#7

Nice tutorial.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)