Tutorial 8 (Arrays) - Aerll/rpp GitHub Wiki

Arrays

Arrays are yet another type in r++. They let you store multiple values in a single variable. Each value has an index assigned to it, which we can later use to access them individually. We already worked with arrays indirectly through objects, since object is nothing more than a specialized array of integers, therefore most of the things covered in this tutorial will apply to objects as well.

To create an array we declare it like any other variable. The only difference is that we add a keyword array in front of the type:

array int integers; // array of ints
array float floats; // array of floats
array object objects; // array of objects

In case of arrays, we don't have to assign anything to them, since it's perfectly valid to have an empty collection. However most of the time we want arrays to store some values, and luckily base.r gives us a set of useful utility functions. Here are some examples:

array int integers = util:ArrayInt(1, 2.V, 3.H, 3.VHR); // integers = { 1, 2.V, 3.H, 3.VHR }
array float floats = util:ArrayFloat(5.61, 3.13); // floats = { 5.61, 3.13 }
array object objects = util:ArrayObject(Indices(1, 2), Rect(1, 17)); // objects = { { 1*, 2 }.N, { 1*, 17 }.N }

Utilities for remaining types are named accordingly.

Properties

Arrays have only 2 properties, which we can access using . operator:

  • count - number of values
  • last - index of the last value, same as count - 1

Here's a short example demonstrating the use of both:

int i = 0;

array int a = util:ArrayInt(1, 2, 3);
i = a.last; // i = 2
i = a.count; // i = 3

array int e;
i = e.last; // i = -1
i = e.count; // i = 0

Accessing values

To access individual values, we have to use an index operator [] in the following way:

array int a = util:ArrayInt(1, 2, 3);
int i = 0;
i = a[0]; // i = 1
i = a[1]; // i = 2
i = a[2]; // i = 3

Note: arrays are indexed from 0, meaning that the first value is at [0].

Here we need to be careful to not try to access a nonexistent value. In the example above, if we were to write i = a[3];, the program would crash.

How can we avoid this? The answer is array's properties. They give us enough insight, to determine if we can safely access the value.
For example we can use count to check whether the array is empty:

array int a;

if (a.count == 0)
    error("a.count == 0 -> Array cannot be empty for this code to work");
end

int i = a[0];

The program will throw an error a.count == 0 -> Array cannot be empty for this code to work and stop the execution, therefore avoiding a crash.

Note: This example applies mainly to general purpose functions, where data comes from the user and it needs to be validated. A good example of this is base.r.

Another example is when we want to access every value using a loop. In this case we can use last to make sure we're not going too far:

array int a = util:ArrayInt(1, 2, 3);

// Note: if the array was empty, loop would still execute causing a crash
for (i = 0 to a.last)
    warning(a[i].str());
end

for (i = a.last to 0)
    warning(a[i].str());
end

This will display values from 1 to 3 and from 3 to 1.

Pushing new values

In most cases, we'll be creating an array at the beginning of the automapper and never change it. This is exactly what we've been doing so far. However there are cases, where we might want to add a new value to our array. This can be achieved with a builtin function push(). It's a function similar to str(), but supported only by arrays. It will insert given values at the end of the array.

Let's have a look at some examples:

array int ai;

ai.push(1); // { 1 }
ai.push(2, 3, 4); // { 1, 2, 3, 4 }
ai.push(util:ArrayInt(5, 6, 7, 8, 9)); // { 1, 2, 3, 4, 5, 6, 7, 8, 9 }

array string as;

as.push("r"); // { r }
as.push("p", "p"); // { r, p, p }

Other functions

There are 3 other builtin functions supported by arrays:

  • has() - checks whether the array contains a given value
  • find() - returns an index to the first occurrence of the given sequence or -1 if not found
  • unique() - returns a new array with duplicates removed

Example:

array int a = util:ArrayInt(1, 5, 9, 9, 2, 1, 2, 3, 7, 6, 6, 8);

bool b1 = a.has(7); // b1 = true
bool b2 = a.has(4); // b2 = false

int i1 = a.find(7); // i1 = 8
int i2 = a.find(4); // i2 = -1
int i3 = a.find(2, 1, 2); // i3 = 4

a = a.unique(); // a = { 1, 5, 9, 2, 3, 7, 6, 8 }

Example

In this example we will see how arrays can help us simplify our code even further. We will use our good old unhook randomizer for this. Here's how we were declaring our tiles so far:

int silver1 = 1;
int silver2 = 2;
int silver3 = 16;
int silver4 = 17;
int silver5 = 18;
int silver6 = 32;
int silver7 = 33;
int silver8 = 34;

We can quickly notice, that there's no reason to name every tile separately. Instead we can store them in a single array:

array int silver = util:ArrayInt(1, 2, 16, 17, 18, 32, 33, 34);

However this won't quite work yet. Our old rule generated all rotations for all of the tiles except 34:

Insert(util:All(silver1, silver2, silver3, silver4, silver5, silver6, silver7), silver8).If(
    IndexAt([0, 0]).Is(g:mask)
).Chance(util:CopiesOf(48, 5), util:CopiesOf(8, 6), 15);

Therefore with this array, we would still need to access each tile separately, which is no better. Fortunately we are no longer tied to single ints, which means we can pack as many values into our array as we wish, including rotations:

array int silver = util:ArrayInt(util:All(1, 2, 16, 17, 18, 32, 33), 34);

This allows us to shorten our rule:

Insert(silver).If(
    IndexAt([0, 0]).Is(g:mask)
).Chance(util:CopiesOf(48, 5), util:CopiesOf(8, 6), 15);

We could also do the same with big tiles, however these are not as obvious. It will be better, if we keep the names and use them to create the array, which we can later use in our rules:

array object silver:big = util:All(silver2x2, silver2x2:bolts, silver3x2, silver3x3, silver2x2:1, silver3x2:1);

Now we can write our rule as:

Insert(g:mask).If(
    IndexAt([0, 0]).IsNotOverlapping(silver:big)
);

Which is better than listing all of our objects every single time.

Full code

#include "base.r"
#output "generic_unhookable.rules"

array int silver = util:ArrayInt(util:All(1, 2, 16, 17, 18, 32, 33), 34);

object silver2x2       = Rect(3, 20);
object silver2x2:bolts = Rect(5, 22);
object silver3x2       = Rect(67, 85);
object silver3x3       = Rect(80, 114);

object silver2x2:1 = Indices(48_49, 64_66);
object silver3x2:1 = Indices(35_37, 50_53);

array object silver:big = util:All(silver2x2, silver2x2:bolts, silver3x2, silver3x3, silver2x2:1, silver3x2:1);

AutoMapper("Rolling Stones");
NewRun();

Insert(g:mask).If(
    IndexAt([0, 0]).IsNotOverlapping(silver:big)
);
Insert(silver).If(
    IndexAt([0, 0]).Is(g:mask)
).Chance(util:CopiesOf(48, 5), util:CopiesOf(8, 6), 15);
⚠️ **GitHub.com Fallback** ⚠️