» Home

  » 
Developer Guide

Brass SSE Developer Guide - Tutorials

 

Tutorial 5 - Arrays and Drawing Images

We've got our grid on screen and we know how to respond to user input. Now we need to do something with that input, and seeing as we're coding a tictactoe game it would be handy if let the user actually play it ;)

Download the source for this tutorial

 

Drawing Images

So far we've seen how to put styled text on screen, and how to draw some flat lines. One of the major components of Brass plugins is the ability to display images, so let's do that now.

We're going to load an X and an O image to signify the player and computer moves. Here are the 2 GIFs we'll be using:

They look a bit odd because they were drawn on a black backround which was then made transparent. They'll look perfect in the display panel though.

In the SSE folder in the Brass installation folder, make a new folder called TicTacImages

Save the 2 GIFs above to the TicTacImages folder

 

I'll assume you've installed Brass to C:\Program Files\Brass, so your TicTacImages folder will be at C:\Program Files\Brass\SSE\TicTacImages and will contain tictac-o.gif and tictac-x.gif.

 

Loading images with Shiny is extremely easy - just like fonts and pens, images have their own advanced type.

 

The Advanced Variable Type: Image

The image type represents a bitmap image, and supports JPG, GIF (including transparency), PNG (including per-pixel alpha transparency) and BMP. There are 3 steps to drawing an image in the display panel:

  1. Declare a variable of type image
  2. Load the file from disk into the variable using createimage
  3. Draw the bitmap using drawimage

Simple enough! Let's declare 2 global variables, one for the X image and one for the O image.

At the top of your code, add the following declarations:

// The pen for the grid
declare global GridPen pen;


// The O and X images
declare global SymbolX image;
declare global SymbolO image;

 

Next we need to load the GIF files from disk into our image variables.

In the Init handler, add the following code:

function Init()
{
    createfont(MyFont, "Arial", 12);
    createpen(GridPen, 0, 2, RGB(255, 255, 255));

    createimage(SymbolX, "SSE\\TicTacImages\\tictac-x.gif");
    createimage(SymbolO, "SSE\\TicTacImages\\tictac-o.gif");
}

The createimage statement takes 2 parameters. The first parameter is the variable to load the image into. The second parameter is the path to the image to load.

Did you notice that we specified a relative path ("SSE\TicTacImages") instead of an absolute path ("C:\Brass\SSE\TicTacImages")? Brass automatically assumes you want to use the installation folder as the start of the path if you don't supply an absolute path. This is very helpful if you don't always know where Brass was installed, especially if you're sharing your plugin with others.

Take careful note of how the path has been written - where you would normally expect to see one \ symbol there are 2. This is called "escaping" and is used by most programming languages.

When we cover reading data from files you'll see that there are some special codes that are represented by a blackslash and a character, such as "\n" represents a new line. By adding an extra backslash we are telling Shiny that we don't actually want a special escape code, we really do just want a backslash.

That's all we need to do to load an image, let's test it out.

 

Testing the Image

Drawing an image is extremely easy with the drawimage statement. We're just going to test our image first of all to make sure it looks like we expect.

Add the following code to the bottom of the Render handler:

drawimage(SymbolX, 1, 1);

 

Compile and execute your code, and you should see something that looks like this:

 

Great! We know our image loading code works, so let's delete the test call to drawimage that we just wrote. It's no good just blindly drawing an image, we have to respond to user input.

Delete the drawimage line of test code you just added to the Render handler

 

Storing Data in Arrays

We've got our grid, we can respond to user input and we've got our X and O symbols. That's all the visual elements of tictactoe, now we need to add the game logic.

Somehow we need to remember which symbols have been placed in what grid section, otherwise it's less of a game and more of a random clicking thing. The best way to do this is to store the grid data in an array.

Programmers skip this section

Arrays are groups of variables accessed under the same variable name, and they're a handy way to store separate but related data without using lots of variables. They can also be accessed programmatically, which is a major benefit.

Imagine you wanted to store the number of days in each month. You could (quite rightly) declare a variable for each month and store the number of days in that:

int Jan = 31;
int Feb = 28;
int Mar = 31;
...etc...

That's fine - if we want to display the number of days in Feb we can just use the Feb variable directly. But what if we were writing a calendar and wanted to find the number of days for "next month". Next month obviously changes each month, so the only way to display the number of days for next month is like this:

int Jan = 31;
int Feb = 28;
int Mar = 31;
...etc...

if(month == "January")
NextMonthDays = Feb;

if(month == "February")
NextMonthDays = Mar;

...etc..

That's 24 extra lines of code just to display the days of the next month - imagine if we were storing more than just days of the month. Wouldn't it be much easier if we could associate the number of days of each month with each other? Some pseudo-code like this would solve our problem:

declare DaysInAMonth as an integer with 12 elements

set DaysInAMonth element 1 to 31 (for January)
set DaysInAMonth element 2 to 28 (for February)
set DaysInAMonth element 3 to 31 (for March)

Now all we need to do to display the number of days in next month is this:

NextMonthDays = the value in the DaysInAMonth element corresponding to This Month + 1

That's precisely what arrays do - allow us to store a collection of data

Programmers resume reading here

 

If we declare an array that stores the status of each of our 9 grid squares we can just check the array whenever we need to determine what's in a square - either when the player makes a move (to check it's a valid move), or when redrawing the display. So how do we declare arrays?

Type 1: The Visual Basic "Dim" style:

declare MyStringArray [Number_Of_Elements] string;
declare global MyStringArray [Number_Of_Elements] string;

 

Type 2: The C/C++ declaration style:

string MyStringArray [Number_Of_Elements];
string global MyStringArray [Number_Of_Elements];

 

There is a very important restriction when declaring arrays. You may only declare arrays of a fixed size. For example, this code is legal, and declares an MyStringArray as an array of 10 string elements:

string MyStringArray [10];

 

However this code is illegal:

int ElementCount = 10;
string MyStringArray [ElementCount];

 

Arrays must be declared with a constant size so that the Virtual Machine knows how much space to allocate to begin with. If a constant size wasn't used it would be possible to write code to allocate zero space, causing a crash the first time the variable was used (you'd be trying to use a variable that didn't technically exist as it has no size!).

If you need to resize arrays or perform checks on an array size you can use the REALLOC and GETALLOC functions. Read the notes in the language reference carefully though!

 

Now that we know how to declare arrays, let's create one to store our grid data in. We have 9 grid squares to keep track of so we'll create an integer array of 9 elements. The first 3 elements will correspond to row 1, the next 3 to row 2 and the last 3 to row 3. Here's a visual representation:

Take a close look at the diagram. Notice how the top left element is labelled "Element 0" and not "Element 1"? This is very important - Shiny arrays are just like C arrays, they start at zero and the highest element is size - 1. So our 9 element array runs from element 0 to element 8 inclusive.

It's very important to remember that arrays are zero-based. If you try to access an array element outside of the array boundary (eg: accessing element 10 on an array declared as "int myarray[10]", which only has elements 0 - 9) then bad things will happen. Your plugin may crash, not work at all or just display strange bugs.

 

Array Boundary Checking

The Brass Virtual Machine is able to catch array boundary overflows and safely prevent complete crashes. To do this it actually returns the last element in the array you were trying to access. As an example:

int MyArray[10];
MyArray[50] = 99;

Element 50 is obviously an invalid element, so the Virtual Machine catches this bug and returns MyArray[9] (the last element in MyArray) instead.

This of course might cause further bugs in your code, but that's up to you to track down. In a later tutorial you'll see how Brass warns you when an array boundary overflow was found.

 

Implementing the Grid Array

Now that we understand arrays, let's add a grid array to our plugin.

Add the following global declaration to the top of your code:

declare global SymbolX image;
declare global SymbolO image;

// The grid array
declare global GridStatus[9] int;
int global FREESQUARE = 0;
int global XSQUARE = 1;
int global OSQUARE = 2;

I've switched to C-like declarations for the bottom 3 ints, because this syntax lets me declare a variable and assign it a value in one step.

The bottom 3 ints will be used as status flags to track the contents of each grid square. If the grid square is empty, its corresponding GridStatus array element will equal FREESQUARE. Similarly if the square has an X in it it's array element will equal XSQUARE, and if it has an O in it the array element will equal OSQUARE.

We could of course just assign these values directly to the array elements and not bother declaring them as variables, but it's good programming style not to have "magic numbers".

"Magic numbers" is a term used to describe values used in a piece of code that don't immediately have any significance. They make code very hard to understand and maintain. Code like: "array[1] = FREESQUARE" is much clearer than "array[1] = 0";

 

We've declared our array, now we need to initialize it so that all elements are set to FREESQUARE. The best way to do this is with a loop.

 

The FOR Loop

Shiny uses C syntax for a for loop. The syntax is as follows:

for ( start condition; end condition; each iteration condition )
{
    // Code to be executed each iteration

}

When a for loop is executed, the code in the start condition is evaluated first. This is normally an initialization of a counting variable:

for (int MonthCounter = 1; .....

At the beginning of each loop iteration the end condition is evaluated. This is normally a check against the counting variable:

for (int MonthCounter = 1; MonthCounter != 12; .....

At the end of each loop iteration, the iteration condition is evaluated. This is normally an operation on the counting variable:

for (int MonthCounter = 1; MonthCounter != 12; MonthCounter = MonthCounter + 1)

 

We can use the for loop to iterate through each array element and set it to FREESQUARE.

Add the following code to the bottom of the Init handler:

for(int iGridElement = 0; iGridElement < 9; iGridElement = iGridElement + 1)
{
    GridStatus[iGridElement] = FREESQUARE;
}

 

The array is initialized and ready for use. Even though there's nothing in it yet, let's add some code to draw the symbols in the grid based on the contents of the grid array.

If you're a C coder you might like to know Shiny supports the ++ operator, allowing iGridElement++ instead of the longer format above.

If you're a VB coder you might like to know Shiny supports the VB style inequality check of <> as well as the C style !=

 

Drawing The Symbols

We already have a loop that iterates through the array and resets it. We just need to copy it to the Render handler and make a few modifications to draw the symbols into the grid.

The main modification is to calculate where to draw a symbol so it lands in the grid square. There are lots of ways to do this, but we'll do it the easiest way to understand.

In the Render handler we've already calculated the positions of the horizontal and vertical lines. As we iterate through the grid array we'll maintain an X and Y position for each grid square.

Add the following code to the bottom of the Render handler:

// Start the symbol position at the top left of the grid
int iSymbolPosX = iGridTopX;
int iSymbolPosY = iGridTopY;

// Iterate through the grid array and draw any symbols
for( int iGridElement = 0; iGridElement < 9; iGridElement++)
{
    // Is this grid element an X? If so, draw the X symbol
    if(GridStatus[iGridElement] == XSQUARE)
        drawimage(SymbolX, iSymbolPosX, iSymbolPosY);

    // Is this grid element an O? If so, draw the O symbol
    if(GridStatus[iGridElement] == OSQUARE)
        drawimage(SymbolO, iSymbolPosX, iSymbolPosY);

    // Update the positioning variables. If we're currently
    // processing element 2 or 5, the next symbol must be
    // reset to the left hand side of the grid, on the
    // next line.

    if(iGridElement == 2 or iGridElement == 5)
    {
        iSymbolPosX = iGridTopX;
        iSymbolPosY = iSymbolPosY + (iGridHeight / 3);
    }
    else
        iSymbolPosX = iSymbolPosX + iGridWidth / 3;
}

The code is commented well, so read through it and make sure you understand each part of it. The logic is straightforward - we iterate through each element of the grid status array. If the element being processed has an X stored in it, draw the X symbol. If it has an O stored in it, draw the O. If it's a free square we don't need to draw anything.

At the end of each iteration we update the positioning variables. After elements 2 & 5, the next elements (3 and 6 of course!) are at the start of the next line. To draw them in the grid correctly we simply move the positioning variables down one grid line and reset them to the left of the grid.

If you want to test the code before moving on, simply change the initialization loop in the Init handler to set all the array elements to XSQUARE instead of FREESQUARE. When you run the plugin, the grid will be filled with X symbols instead. Handy and fast test!

 

The If...Else... Statement

There are a couple of new constructs in this code. First we have a new conditional statement - if. The if statement evaluates a condition and, if it evaluates to boolean True, executes the code immediately after the if statement. If the code after the if statement is inside braces ( { } ) then all the code is executed, otherwise only the first line after the if statement is executed. Here are some samples:

int a = 1;
if(a == 1)
    drawimage(....);

int b = 2;

In this code we use the equality operator ( == ) to test whether a is equal to 1. If it is, the drawimage line of code is executed. If it's not, execution resumes at the int b = 2; line.

int a = 1;
if(a == 1)
{
    drawimage(....);
    drawtext(...);
}

int b = 2;

In this code we perform the same equality test. However if a is equal to 1, we want to execute 2 lines of code. To do this we enclose all the conditional code within braces. If a is not equal to 1, execution resumes at the int b = 2; line.

If the if statement does not evaluate to true, we can perform an action using the else statement. This is handy when we want to perform one of 2 actions depending on a condition. Here's a sample:

int a = 1;
if(a == 1)
    drawtext("a is equal to 1", 0, 0);
else
    drawtext("a is not equal to 1", 0, 0);

 

Just as before, braces are optional if we want to execute one line of code but required if we want to execute more than one line:

int a = 1;
if(a == 1)
{
    drawtext("a is equal to 1", 0, 0);
    drawtext("Are you ready?", 0, 20);
}
else
{
    drawtext("a is not equal to 1", 0, 0);
    drawtext("You can't be ready yet.", 0, 20);
}

 

You may have noticed that a couple of conditional statements (namely if and for) are based on a boolean status. A boolean is a variable that can either be True or False and nothing else (booleans were discussed in tutorial 2). In actual fact all conditional statements are based on booleans - if the test is True they do something, otherwise they do something else. We'll cover the other conditional statement - while - in a later tutorial.

 

Boolean Tests

You've already seen 2 boolean tests in the plugin code. The first was in our for loop:

for( int iGridElement = 0; iGridElement < 9; iGridElement++)

The iGridElement < 9 statement is a boolean conditional test. Each time the for loop loops it checks this conditional. If the conditional evaluates to True then the loop continues, otherwise the loop ends.

The second was in the if statements:

if(GridStatus[iGridElement] == OSQUARE)

Again this is a boolean test. The if statement evaluates the condition in the brackets and if it's True, it executes the code in the clause afterwards.

Tests introduce the concept of left value (lvalue) and right value (rvalue) expressions. This is very easy to understand - it just refers to what's on the left and right of the test expression. As an example, in this code:
if( a == b)
the lvalue is a and the rvalue is b.

Shiny supports all the standard boolean tests you'd expect:

== Equality Returns True if the left value equals the right value
     
!= Inequality Returns True if the left value does not equal the right value
<>   Alternative to != for Visual Basic users
     
< Less than Returns true if the left value is numericaly less than the right value
     
> Greater than Returns True if the left value is numerically greater than the right value
     
<= Less than or
equal to
Returns true if the left value is numericaly less than or exactly equal to the right value
     
>= Greater than or
equal to
Returns True if the left value is numerically greater than or exactly equal to the right value
     

C, PHP and Java coders will be used to this, but everyone should pay special attention:

To test for equality use a double equals: ==

A single equals sign assigns values, it does NOT test for equality

This is extremely important and very easy to forget. This code assigns the value 1 to the variable a:

if( a = 1 )
{
    // Do something
}

Because you are assigning the value 1 to a, the condition will always be True! Therefore your if clause is now useless.

The Shiny compiler will attempt to catch this bug where possible and will show you this error:

(Line 10) Warning: Assign (=) in if statement, did you mean equality (==)?

 

Don't rely on this error catch though because the compiler cannot detect this bug in nested logical operations (which we're covering next).

 

Logical Operations

Logical operations are the partner to boolean tests. Where boolean tests produce a True or False based on one condition, logical operations link these tests together to produce an overall result.

Let's say you only wanted to draw some text if today was a Monday in February. You could write this type of code:

if(ThisMonth == "February")
{
    if(Today == "Monday")
    {
        drawtext("It's a Monday in February", 0, 0);
    }
}

but that's a bit long winded and awkward, especially if you want to test more than one or 2 conditions. A much easier solution is to combine the boolean tests with a logical operator. That's what we did in the bottom of the Render handler code with this statement:

if(iGridElement == 2 or iGridElement == 5)

(in the downloadable sample code this is on line 97). As you can see there is a little or statement in between the 2 boolean tests. The or statement links 2 tests together and evaluates to True if one or both of the tests evaluates to True itself.

There are 3 logical operators:

or Evaluates 2 boolean tests, returns True if one or both tests return True
   
Examples:  
True or False Returns True
   
True or True Returns True
   
False or False Returns False
   
a or b If a or b are True, returns True. If both are False, returns false

 

and Evaluates 2 boolean tests, returns True only if both tests return true
   
Examples:  
True and False Returns False
   
True and True Returns True
   
False and False Returns False
   
a and b If a and b are both True, returns True. Otherwise returns False

 

not Inverse operator, if the rvalue is True it returns False and vice versa
   
Examples:  
not False Returns True
   
not True Returns False
   
not a If a is True, returns False. If a is False, returns True

 

Note that the not operator only takes an rvalue. Here is some sample code:

bool Result;

int a = 1;
int b = 9;

// Result will be True, a is less than b
Result = a < b;

// Result will be True, b doesn't equal 0 but a is less than b

Result = a < b or b == 0;

// Result will be False, both conditions do not evaluate to True

Result = a < b and b == 0;

// Result will be False, a is less than b but the result is inversed

Result = not a < b;

 

C/PHP/Java coders:

Shiny supports your usual notation for logical operations:

Operator Equivalent to Performs
     
|| or Logical OR
     
&& and Logical AND
     
! not Logical NOT

 

A Quick Recap

Wow, we covered a lot in this tutorial. You've learned all about drawing images, arrays, loops, logical operations and much more. This was the hardest tutorial in the series - all that's left to do now is to respond to user input and add some extra frills to the game.

In this tutorial you saw:

  • How to load and draw BMP, GIF and JPG images
  • How to declare and use arrays
  • How to use if...then...else statements
  • How logical operators work

Next time we're going to respond to user input so we can actually play the game, then look at some error handling, how to use functions, and how to use the debugging features of Brass.