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:
- Declare a variable of type image
- Load the file from disk into the variable using createimage
- 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.
|