An example LiveCode Program

Circles

 

this page is part of the annotated examples.

What we do here

Circles draws a number of coloured circles on a coloured canvas, each time the user clicks the canvas.

It is an exercise in simple computer-generated "art" and meant to show beginners how LiveCode can be used for entertainment.

Output may look like this:

example
A sample of what Circles might produce

Specification

We choose to let the program generate the drawing at random, but not without some constraints.  Note that the constraints I choose here can of course be changed to suit your own preferences, rather than my non-existent artistic talents.

First there is the choice of the canvas colour:  I choose it to be always light, i.e. a pastel type colour.

LiveCode's colours are set in RGB (red-green-blue) values, which must lie between 0 (no intensity) and 255 (full intensity).  Therefore a value of 255,255,255 is white, 0,0,0 is black, and any other values result in some colour.  Full red would be 255,0,0 and a very light red (or pink) would be 255,200,200.  If you feel uncertain about how RGB colours work, have a look at the Colours program first:  it lets you set the values and observe the result.

Pastel colours thus all have rather large values, whereas saturated or dark colours would have small values.  To ensure that the canvas colour looks light, we will constrain the RGB values to be above a certain minimum value.

The circles will have centres, diameters and stroke widths chosen at random, but again with some constraints.  For example, it makes no sense to draw a circle that is not visible because its diameter exceeds the canvas size, or because its stroke width is zero.

Finally, the number of circles should also be chosen to fill the canvas reasonably:  not too full, not too sparse.

Elements of the Code

This program will have a single stack with a single card.  The card will be the canvas.

You can download the code, or you can create a new mainstack and then type or copy-paste the complete code you find at the end of this page into the script editor for the stack.  In the rest of the page I discuss how the code grew from nothing.  I strongly encourage you to make the program first, without necessarily understanding what the code does, and to experiment with it by clicking in the stack's window to see the circles drawn.

There will be only one script:  that of the stack.  The script will have only one handler, for the message MouseUp.

Reflection:  the way the user generates the set of circles is by clicking in the canvas.  That is a mouse-up event, and the message MouseUp will be sent to the card.  The card's script is empty, it has no handler for that message.  LiveCode will then "pass the message higher up", to the object that owns the card, viz. the stack.  Placing the MouseUp handler in the script of the stack is a choice, in other circumstances we might choose to put it in the script of the card.

The Loop

To draw the circles we will definitely have something like:

repeat with i = 1 to lNumberOfCirclesToDraw

...

end repeat

That is a loop statement.  It starts with repeat and ends with end repeat.  Strangely enough, those two lines are in some sort of conflict:  you are right in thinking that repeat is an instruction to do some things a number of times, but you are wrong in thinking that end repeat is an instruction to stop repeating!  The end repeat is in fact not an instruction at all.  It is an indication of the end of the list of instructions that have to be repeated.

The dots will be filled in later.

Observation:  I wrote lNumberOfCirclesToDraw.
Why did I not write number of circles to draw?

That's because lNumberOfCirclesToDraw is a name, or identifier, the name of a number (that will become clear in a short while).

Names in LiveCode must consist of letters or digits, they cannot have spaces in them and no other characters

Because I can't use spaces, but sometimes still want a name to consist of multiple words, I write all words in lower case letters but use an upper case letter at the start of every word.

(you can actually use one other character besides letters and digits:  the underscore _ but I never use that, I find it ugly.  I really do prefer lNumberOfCirclesToDraw to lnumber_of_circles_to_draw, but you are of course free to use your own tastes in this matter).

That explains most of the name, but not why it starts with a lower case letter l.  For reasons that will become clear with time, when you have written a fair number of lines of code, it is useful to distinguish the function of some names from that of other names.  In this short program, I have only two different classes of names:  those I invented for constants and those I invented for local variables.  Never mind the details, but I start the names of constants with c and those of local variables with l (if you really can't wait, look at my coding conventions).

In the repeat statement the i is perhaps mysterious:  it is a counting variable.  It will start off by being set to 1.  Then the repeat loop will do whatever needs to be done to place a random circle on the canvas, and then i will be incremented to 2, and the whole loop will be done once more, producing the second circle and so on.

This process will stop after i reaches the value of lNumberOfCirclesToDraw.  So if lNumberOfCirclesToDraw is 7, i will successively take the values 1,2,3,4,5,6,7 and then the repeat loop will stop.  We do not want to write 7 or any other explicit number:  we want to calculate the number of circles we need, because we need more of them on a large canvas than on a small one, which is why we use lNumberOfCirclesToDraw.

The Preparation of the Canvas

Before we can draw the circles with our repeat loop, we have to erase the circles of a previous drawing from the canvas.  The first time that is not necessary, but all other times we need to do so.  This again is a loop:

repeat with j = the number of graphics down to 1

delete graphic j

end repeat

Each circle on the canvas is a graphic.  We don't need to know exactly how many there are (when you first start the program there aren't any!) because LiveCode keeps count of all objects, so we merely need to use the phrase the number of graphics.  That is a phrase, not a name:  it is composed of words separated by spaces and LiveCode will interpret each word of it.

In this repeat loop we have one statement (instruction):  delete graphic j which says to remove the jth circle.  If there are four circles present, then the repeat loop will effectively be equivalent to deleting graphic 4, then 3, then 2, then 1 and stop.

You may wonder why I am counting down, deleting the last circle first.  You can read the details in a page on pitfalls in LiveCode, but you should not worry about this yet.

Drawing Circles at random

A "random" circle will mean that it has a randomly chosen diameter, a randomly chosen colour, is drawn with a thickness that is also chosen at random and finally that it is placed on the canvas in a random position.

How do we get anything random out of LiveCode?  There is a function called random that picks a number from a range.

For example:  random(10) will give a number from 1 to 10, and produces a different one each time it is used.  To make a random colour, we need three numbers, picked from the range 1 to 255.  Thus we could write:  random(255),random(255),random(255)

Although that looks like three times the same number, we will in fact get three different ones, but they will all be at least 1 and at most 255.

If you want to split hairs, then yes:  we should get a colour intensity value from the range 0 to 255, so we really should write random(256)-1 but I dare you to distinguish the colour 1,1,1 which is not entirely black, from 0,0,0 which should be entirely black.

For colours it is fairly easy since the RGB values go up to 255, but what about the circle's diameter?  Diameters and positions are given in pixels.  Let's limit the diameter of the circle to half that of the canvas.  Well, half of the largest side say, whether that is the height or the width.

We could let the centre be anywhere, even if it makes the circle invisible, or we could just require that at least a little bit of the circle is visible, or any other sort of limitation.  Let us decide that it can be anywhere as long as it is on the canvas.  Then we can pick the horizontal position between 1 and the width of the canvas, and the vertical position between 1 and the height of the canvas.

How many circles should we draw?  If the canvas is large, we need more of them than if it is small.  Again, that is a decision we make, we could do something else, but suppose we want small canvases to look more or less filled to the same degree as large ones.  Then the number of circles is probably proportional to the surface area of the canvas, i.e. width×height.

Finally, the thickness of the line with which the circle is drawn should not be too thick and never be less than 1 pixel wide.  But we may not want very thick circles on small canvases, so there is a limit there too, similar to that of the diameter.

Enough talk, let's look at some code.  You can find the following piece of the code in its place in the complete script, so it may be useful to have the complete script available too.  Here is the piece:

1 put random(lCardWidth) into x; put random(lCardHeight) into y

2 put random(lMaximumDiameter) into d

3 create graphic i

4 set the style of graphic i to "oval"

5 set the location of graphic i to x,y

6 set the width of graphic i to d; set the height of graphic i to d

7 set the opaque of graphic i to false

8 set the linesize of graphic i to random(lMaximumStrokeWidth)

9 set the textcolor of graphic i to \

10 lLowestIntensity+random(lRange), \

11 lLowestIntensity+random(lRange), \

12 lLowestIntensity+random(lRange)

There is a lot more explanation to come, but I think you can already read this pretty well:

Line 1 decides the horizontal and vertical position of the centre (usually called x and y in mathematics, so we use those letters here too).  Setting those two positions should be done in one go, so I put both statements on the same line, separating them with a semicolon.  Given that we limit the numbers to the width and height, the position will be within the canvas.

Line 2 decides the diameter.  There are no circles as such in LiveCode, only ovals.  A circle is just an oval with the same width and height, which I called d (for diameter).

Line 3 creates a graphic.  The i will become clear later.

Line 4 makes that graphic into an oval;  other possible choices are rectangle, polygon, etc.  Note that oval is a choice, not a word of the LiveCode language, nor something I invent, and so I must put double quote marks around it.  This is also known as a character string.

Line 5 sets the location of the graphic to the position x,y that we decided earlier.

Line 6 sets both the width and height of the oval to the same value, the diameter we decided in line 2, so it now really is a circle.

Line 7 ensures the circle is not opaque, though that is also a decision.  You might try a variation of the program in which the circles are filled with some random colour.

Line 8 sets the width of the line that draws the circle.

Lines 9 to 12 are in fact a single statement.  It is very long, so I split it over several lines, and that is done by putting a backslash at the points where splits are.  This sets the textcolor…  Well, LiveCode is not perfect:  the word to use should be strokecolor, but for some obscure reason it is textcolor.  There are a few synonyms, but for now let's just accept what is.

There is still a lot of mystery in these lines:  why did I write lMaximumDiameter and what is lLowestIntensity

More Code

We should of course have started with these lines of code (locate them in the entire code, and observe that they come well before the rest):

1 put the width of this card into lCardWidth

2 put the height of this card into lCardHeight

3 put lCardHeight*lCardWidth into lCanvasArea

4 put round(ln(cCircleDensity*lCanvasArea)) into lNumberOfCirclesToDraw

5 put max(lCardWidth,lCardHeight) div cMaximumDiameterProportion into lMaximumDiameter

6 put min(lCardWidth,lCardHeight) div cMaximumStrokeWidthProportion into lMaximumStrokeWidth

Lines 1 and 2 should be clear.  I invent the two variables (containers) called lCardWidth and lCardHeight so I can put the width and height of the canvas (the card of the stack) into them, because I will need them a few times later on.  The numbers that go into them are the number of pixels the width and height take up on the screen.  The card is of course the only card in the stack, and it is also the canvas.

In fact line 3 already needs these values:  I invent yet another container called lCanvasArea that holds the total number of pixels of the canvas.  Because the letter x is used as a letter, we need to use something else, viz. the asterisk, for multiplication.

Line 4 is very difficult, and I do not expect any beginner to come up with it.  If the canvas is 500 pixels wide and 200 pixels high, there are 100'000 pixels in it.  Say we want to draw only 10 circles on such an area, otherwise we feel it is too crowded.  But if we stretch the canvas to 500 by 400, then we have doubled its area and we would then want 20 circles.  So we would want one circle for every 10'000 pixels of canvas area.  One way to get the desired number of circles would then be to write

put lCanvasArea/10000 into lNumberOfCirclesToDraw

and that woud give 10 circles for a 500 by 200 canvas and 20 for a 500 by 400 one.  Unfortunately it would give 12.5 circles for a 500 by 250 canvas, and 12.5 is not a whole number.  OK, so we can round:

put round(lCanvasArea/10000) into lNumberOfCirclesToDraw

and that makes a whole number.  I experimented with that at first, but it did not look good.  There were too many circles on large canvases and too few on small ones.  There was nothing wrong with the actual number of circles, it was just that my human perception found the big canvases overloaded:  an optical illusion no doubt, but probably linked to the fact that we like logarithmic impressions, not linear ones.  To correct for this perception, I used the mathematical function ln (natural logarithm) on the canvas area, multiplied by some factor that I called the circle density.  You may experiment with other transformations.

Line 5 sets the maximum diameter.  It takes the largest one of the width or height of the canvas, using the max function, but that still leaves a big number.  So we divide by a proportion.  Division is normally written as a slash, e.g.:  5/2=2.5 but we need a whole number.  A division of a whole number by another whole number can give a decimal number but may also give a quotient and a remainder.  The operator div returns the whole number quotient:  5 div 2 = 2.

Likewise, we use a proportion of the canvas width or height to determine the maximum stroke width of the circles.

The proportions are defined as constants:

constant \

cCircleDensity = 10, \

cMinimumDiameter = 30, \

cMaximumDiameterProportion = 2, \

cMaximumStrokeWidthProportion = 50, \

cCanvasMinimumLightness = 150, \

cCanvasMaximumLightness = 245, \

cStrokeColourRange = 100

That statement is again very long so I split it up using backslashes.  I also start each name with a lower case letter c to indicate these names are referring to constant values. 

I could equally well have written:

constant cCircleDensity = 10

constant cMinimumDiameter = 30

constant cMaximumDiameterProportion = 2

constant cMaximumStrokeWidthProportion = 50

constant cCanvasMinimumLightness = 150

constant cCanvasMaximumLightness = 245

constant cStrokeColourRange = 100

but I will let you decide which way of presenting this list of named numbers you prefer.

Parameters of programs

The above constants that define the proportions, ranges, density and so on are called program parameters.  They generally decide the behaviour of the program.  Parameters are a very important part of a program.  You should always put such values in a place where they can be found easily and therefore changed easily.  Instead of using the long names, we could have also just typed the numbers, e.g.:

4 put round(ln(10*lCanvasArea)) into lNumberOfCirclesToDraw

5 put max(lCardWidth,lCardHeight) div 2 into lMaximumDiameter

6 put min(lCardWidth,lCardHeight) div 50 into lMaximumStrokeWidth

When you just begin to program, or when you are in a hurry to get a result, you will be tempted to type just numbers.  It is quicker, easier and much more compact.  But when your programs grow larger, and when you want to modify a program that you wrote some months earlier, you will no longer remember what the numbers were for.  If you give a program to a friend, they will not be able to read it, because the naked numbers do not say what is going on.

Remember this important rule of writing computer programs:

you will need to read your code a lot more times than you write it.

Therefore it is very important that you can understand what you wrote, and that is the main reason for giving good names to variables (containers) and to use constants with good names.

The rest of the program should now be clear, as well as its structure.

Broadly speaking, there is:

  1. a big block-comment between /* and */, which is of course not important to the function, but is important to the human reader,
  2. a set of definitions of numbers that determine the program's behaviour, the parameters (constants)
  3. the MouseUp handler, as the only handler in the script,
  4. inside the handler are the first lines that calculate the number of circles to draw and other numbers,
  5. a loop to delete the previously drawn circles,
  6. painting the canvas,
  7. restricting the stroke colours for the circles,
  8. a loop to draw all the circles randomly.

And again, that's it!

Code !

Here is the entire code for the Circles program:

/*

Draw some coloured circles on a coloured canvas.

The canvas colour is always a light (pastel-type) colour.

The number of circles is proportional to the surface area of the canvas.

Each circle has a different colour , but the range is restricted so that

the whole looks reasonably assorted.

The diameter of the circles is limited by the size of the canvas as is the

stroke width.

Variants on this program could be:

- inverted colours: darker canvas and lighter circles,

- no restrictions at all,

- ovals instead of circles,

- filled instead of stroked,

- adapt the circle colour range to that of the canvas, e.g. inverted,

- etc.

Improvements could be:

- menus, save drawing,

- pop-up sliders to set the parameters (e.g. on option-click)

The CircleDensity is the proportion of circles to canvas; the higher it is the more circles will be drawn.

The MinimumDiameter is the diameter of the smallest circle that can be generated.

The MaximunDiameterProportion determines the diameter of the largest circle that can be drawn, as a

part of the largest dimension of the canvas (width or height, depending on the chosen aspect)

The MaximumStrokeWidthProportion determines the largest stroke a circle can have, as a part of the

smallest dimension of the canvas.

Strange effect: the number of circles is proportional to the area of the canvas but larger ones look

much busier than smaller ones! Why? To correct fo the effet, the number is calculated as proportional

to the logarithm of the area. That looks better, but now the larger canvases seem to look somewhat less buy.

So ln() is too strong?

*/

constant \

cCircleDensity = 10, \

cMinimumDiameter = 30, \

cMaximumDiameterProportion = 2, \

cMaximumStrokeWidthProportion = 50, \

cCanvasMinimumLightness = 150, \

cCanvasMaximumLightness = 245, \

cStrokeColourRange = 100

on MouseUp

put the width of this card into lCardWidth

put the height of this card into lCardHeight

put lCardHeight*lCardWidth into lCanvasArea

put round(ln(cCircleDensity*lCanvasArea)) into lNumberOfCirclesToDraw

put max(lCardWidth,lCardHeight) div cMaximumDiameterProportion into lMaximumDiameter

put min(lCardWidth,lCardHeight) div cMaximumStrokeWidthProportion into lMaximumStrokeWidth

lock screen

-- Get rid of the existing circles:

repeat with j = the number of graphics down to 1

delete graphic j

end repeat

-- Paint the canvas with a pastel colour:

put cCanvasMaximumLightness - cCanvasMinimumLightness into lCanvasLightRange

set the backgroundcolor of this card to \

cCanvasMinimumLightness+random(lCanvasLightRange), \

cCanvasMinimumLightness+random(lCanvasLightRange), \

cCanvasMinimumLightness+random(lCanvasLightRange)

-- Restrict circle stroke colour palette:

put random(cStrokeColourRange) into lRange; put random(255-lRange) into lLowestIntensity

repeat with i = 1 to lNumberOfCirclesToDraw

put random(lCardWidth) into x; put random(lCardHeight) into y

put random(lMaximumDiameter) into d

create graphic i

set the style of graphic i to "oval"

set the location of graphic i to x,y

set the width of graphic i to d; set the height of graphic i to d

set the opaque of graphic i to false

set the linesize of graphic i to random(lMaximumStrokeWidth)

set the textcolor of graphic i to \

lLowestIntensity+random(lRange), \

lLowestIntensity+random(lRange), \

lLowestIntensity+random(lRange)

end repeat

end MouseUp