Arrays
In the previous examples you have learned how to use varialbles for counting and measuring. You have been using one variable to count seconds, another one for frames per second ratio and so on.
But what if you have got a problem that requires a lot of variables to solve?
What if you need tens of variables to hold some temporary values?
In that case, you should use arrays.
An array is a data structure consisting of a collection of element values, each identified by at least one array index. An array is stored such that the position of each element can be computed from its index
Arrays have many applications: they are used to implement mathematical vectors and matrices, as well as other kinds of rectangular tables.Applications use one-dimentional arrays for storing data records. Arrays are also used to implement other data structures, such as lists, queues and strings. Such data structures are used in game applications extensively. It is an essential part of nearly any game application.
When data objects are stored in an array, individual elements are selected by an index that is a non-negative integer. Indices are also called subscripts.
The first element of the array is indexed by subscript of 0. This leads to simpler implementation where the subscript refers to an offset from the starting position of an array, so the first element has an offset of zero.
Arrays can have multiple dimentions too. Thus, it is not uncommon to access an array using multiplt indices.
For example, a two-dimentional array A with three rows and four columns provides access to the element at the 2nd row and 4th column by the expression A[1][3].
Thus, two indices are used for a two-dimentional array, three for a three-dimentional array, and n for n-dimentional one.
Arrays in Pawn language
Pawn is a typeless language. All data elements are of type
celland a cell can hold an integral number.The size of a cell is 32 bits.
Pawn language supports "array variables" than hold many cells/values. Both single-dimentional and multi-dimentional arrays are supported.
How to declare a new array variable?
The syntax name[constant] declares name to be an array of constant elements, where each element is a single cell, i.e 32 bit value. The name is a placeholder of an identifier name of your choosing, and constant is a positive non-zero value; constant may be absent too. If there is value between the backets, the number of elements is set equal to the number of initializers. We will get back to it later...
The array index range is zero-based which means that the first element is at name[0] and the last element is at name[constant-1].
The syntax name{constant} also declares name as an array of constant elements, but this time elements are bytes rather than cells.
Array variables can be initialized at their declaration. A values that gets passed to initialize array must be a constant.
Uninitialized arrays are getting automatically filled with zeros.
Let't take a look at some examples:
| Declaration | Meaning |
|---|---|
| new a[] = [1,4,9,16,25] | array a has 5 elements |
| new s1[20] = ['a','b'] | array s1 has 2 elements, other 18 are 0 |
| new s2[] = ''Hello world...'' | array s2 is a string |
The ellipsis operator (three dots or "...") continues the progression of the initialization constants for an array, based on the last two initialized elements. It initializes the array up to declared size.
Here's some examples:
| Declaration | Meaning |
|---|---|
| new a[10] = [1,...] | sets all ten elements to 1 |
| new b[10] = [1,2...] | b=1,2,3,4,5,6,7,9,10 |
| new c[8] = [1,2,40,50,...] | c=1,2,40,50,60,70,80,90 |
| new d[10] = [10,9,...] | c=10,9,8,7,6,5,4,3,2,1 |
Multi-dimentional arrays are arrays that contain references to the sub-arrays. That is, a two-dimentional array for example is an "array of single-dimentional arrays".
Currently, arrays with up to three dimensions are supported
Below are the few examples of declarations of two-dimentional arrays:
| Declaration |
|---|
| new a[4][3] |
| new b[3][2] = [ [1,2], [3,4], [5,6] ] |
| new c[3][3] = [ [1], [2,...], [3,4,...] ] |
| new d[2]{10} = ["WOW","CUBE"] |
| new e[2][] = ["OK","CANCEL"] |
| new f[][] = ["OK","CANCEL"] |
The last two examples with variables e and f show that the final dimention of an array may have an unspecified length, in which case the length of each sub-array is determined from the initializer of an element.
Every sub-array may have a different size too. In the example above, e[1][5] contains the letter L from the word CANCEL, but e[0][5] is invalid becasue the length of sub-array e[0] is only three cells (containing the letters O, K and a zero terminator)
The difference between the declarations of arrays e and f is that we let the application count the number of initializing elements for the major dimension.
Ok, but is it possible to get the length of an array after it has been initialized?
Yes, with sizeof operator.
The sizeof operator always returns the number of cells in the array. Look that the following code:
The console output would be
Why?
Let's see.. The HELP string contains 5 characters: four letters one byte each and the string terminator 0x00. Therefore, it does not fit into one cell of 4 bytes. Thus, 2 cells required!
And, as you can see, it doesn not matter how array is declared - the sizeof operator always returns the number of cells.
With multi-dimentional arrays, the sizeof operator can return the number of elements in each dimension. For the last (minor) dimension, the count will be in cells, but for the major dimension(s) the count is a size of a sub-array.
In the following code snippet, see that the syntax of sizeof matrix refers to the major dimension of the two-dimensional array and the syntax of sizeof matrix[] refers to the minor dimension of the array:
The values this snipped prints are
for the major and minor dimensions respectively.
WOWCube SDK supports the following array memory manipulation functions:
| Function Name | Brief Description |
|---|---|
memcpy(dest[], const source[], index=0, numBytes, maxLength = sizeof dest)) | Copy bytes from one location to another |
Strings
Unlike many other languages, Pawn language has no intrinsic string type. Character strings are stored in arrays, with the convention that the array element behind the last valid character is zero - 0x00, a string null-terminator.
This means that you can work with strings exactly the same way as with arrays !
String characters can be accessed by index, the sizeof operator will return string size in cells. Strings can also be used as sub-arrays in a multi-dimentional array.
But, there are some differences to arrays.
As it was mentioned above, the Pawn language does not have variable types. All variables are cells which are 32-bit wide. And a string is basically an array of cells that holds characters and that is terminated with zero.
However, in the character set supported by WOWCube SDK a character takes only a single byte and a cell is a four-byte entity, hence storing a single character per cell is then a 75% waste of memory.
And we know that memory is a prescious resource on WOWCube device, right?
For sake of compactness, Pawn language supports so-called packed strings, whrere each cell holds as many characters as it can fit. Typically, one cell contains four characters.
At the same time, Pawn language also supports unpacked strigns where each cell holds only a single character with the puropse of supporting Unicode and other wide-character sets.
Many programming langauges solve handling of ASCII character sets against Unicode with their typing system. A funciton whll then work either on one or on the other type of string, the the types can not be mixed. Pawn language, on the other hand, does not have types or a typing system, but it can check at runtime whethere a string is
packedorunpacked.
WOWCube SDK supports the following string manipulation funcions:
| Function Name | Brief Description |
|---|---|
bool:ispacked(const string[]) | Determine whether a string is packed or unpacked |
strcat(dest[], const source[], maxLength= sizeof dest) | Concatenate two strings |
strcmp(const string1[], const string2[], bool:ignoreCase=false, length=cellmax) | Compare two strings |
strcopy(dest[], const source[], maxLength= sizeof dest) | Create a copy of a string |
bool:strdel(string[], start, end) | Delete characters from string |
bool:strequal(const string1[], const string1[], bool:ignoreCase=false, length=cellmax) | Compare two strings |
strfind(const string[], const subString[], bool:ignoreCase=false, index=0) | Search for a sub-string in a string |
strformat(dest[], size=sizeof dest, bool:pack=false, const format[]) | Convert values to text |
bool:strins(string[], const subString[], index, maxLength=sizeof string) | Insert a sub-string in a string |
strlen(const string[]) | Return the length of a string in characters |
strmid(dest[], const source[], start=0, end=cellmax, maxLength=sizeof dest) | Extract a range of characters from a string |
strpack(dest[], const source[], maxLength=sizeof dest) | Create a "packed" copy of a string |
strunpack(dest[], const source[], maxLength=sizeof dest) | Create a "unpacked" copy of a string |
strval(const string[], index=0) | Convert from text (string) to numbers |
valstr(dest[], value, bool:pack=false) | Convert a number to text (string) |
Several funcitons have a parameter that specifies the maximum number of cells that the destination buffer can hold. The purpose of this parameter is to avoid an accidental buffer overrun.
The example
Now when you've learned all that theory about arrays and strings, let's get to actual example.
In order to demonstrate the work with arrays and strings, we will use several arrays of different dimentions, initialize them with strings and then show these strings on the sides of WOWCube device with a bit of funky animations.
First, let's declare the arrays:
Pay attention!
Different ways to declare arrays in the code above are used purely for the demonstration purposes. We recommend to choose and use one particular style of string variable declarations in your game cubeapps.
So what exactly is declared here?
arrWOWCUBEdeclares an array of characters that form the word `WOWCUBE'arrWOWCUBE2declares an array of packed strings (or a two-dimensional arryay) that contains 3 strings up to 10 characters eacharrTWISTYalso declares an array of packed strings, but this time dimensions are not specified explicitlyfParamdeclares an array of fixed point valuesprefPosdeclares a two-dimentional array of cells that would be used later for storing some integer values
Second, we need to do some initialization. As always, variables are initialized in ON_Init() callback.
We initialize two timers for animation effects and also arrayIndices structure which values are zeroed.
Then we declare a function that will help us to separate application logic that should be happening on timer tick.
The function receives timerID identifier of a timer as an input parameter. We have two timers now, and we need to know which one produced a tick, right?
We have two timers that tick with different frequencies - one triggers every 100 milliseconds (10 times a second), another one produces a tick once a second.
So when the first timer ticks, OnTimerTick(0) is called. And when the second timer ticks, functions parameter changed to 1 and OnTimerTick(1) gets executed.
So let's see what happens when each timer ticks:
Timer 0
- Take an element at
arrayIndices.i1index fromarrWOWCUBEarray and convert it into a string bufferbuffers.buff - Take
buffers.buffand append it tobuffers.buff2 - Increment
arrayIndices.i1by 1 - If
arrayIndices.i1value gets greater than 7, reset it and delete all characters frombuffers.buff2
Timer 1
- Increment
arrayIndices.i2by 1 - If
arrayIndices.i2gets greater than 3, reset it
This will result in constant change of array index parameters that we use for composing strings we render on screen.
Finally, we would want to render those strings (with a bit of animation, as promised).
What we have here?
- First screen displays the string buffer
buffers.buff2that is dynamically changed every 100 milliseconds on timer 0. - Second screen displays three words from
arrWOWCUBE2array selected by index that gets incremented on timer 1 each second. - Third screen... well, things are getting a little bit tricky here...
Third screen shows the word TWISTY built out of characters stored in arrTWISTY array.
If we would have wanted just to print that word on a straight line, we could just take an index of that array and print all characters one by one, right?
But that would be too easy and too boring. So instead, each letter in the word is animated, it sways up and down leaving a ghostly trace.
Behind every computer animation stands Math.
And our example is not an exclusion - in order to animate word letters we must apply some trigonometry. Cause if we want the letters to sway, we have to find the way to change their coordinates in an endless cyclical manner. And hopefully, make it look neat as well...
So, to generate per-letter vertical offsets, a Sine trigonometrical function is used:
WOWCube SDK does not support floating point values. The
FixedSinfunction is used to do fast calculation of sine of an angle taking a fixed point value of an angle as an input parameter
fParam[i] contains a fixed point value of an angle that gets passed to the sine function on every tick and gets incremented by a small value. The offset parameter receives a value of a sine of an angle, then used for rendering letters at dynamic positions.
And finally, three copies of each letter are getting rendered for creation of a ghost trail effect.