Bricklayer provides a function, called generateRandomBrickFn, that can be used to generate random bricks. Before describing the details of the generateRandomBrickFn it is important to understand what generating a random brick means (what it should mean).
Slides covering this material can be found here.
Generating Random Values
In this discussion we will ignore the idiosyncrasies surrounding how computers represent real numbers and issues surrounding equality comparisons.
Virtually all programming languages provide a variety of ways to generate a random (real) number that, mathematically speaking, is in the range 0.0 ≤ n < 1.0. Note that, in this range, n can never equal 1.0, but can equal 0.0. To get a random number, one typically calls a special function. The basic random number function is a nullary function. Its name can vary from language to language, but the function is typically called random or rand.
Let random denote a function that when called generates a random number in the range 0.0 ≤ n < 1.0. Let us assume that random is a nullary function which can therefore be called as follows:
random ()
Simulating the Flip of a Coin
The following algorithm shows how the random function can be used to simulate a coin flip. In the algorithm below, H stands for “heads” and T stands for “tails”.
- Let n = random ()
- if n < 0.5 then print (“H”) else print (“T”)
This algorithm can be easily implemented in SML. An important question is: What kind of output could one expect from a program that flipped a coin, say, 10 times?
- Would you expect 5 heads and 5 tails?
- Would you expect 10 heads and 0 tails?
I conducted an experiment where I flipped an actual coin 10 times and got the following sequence of heads (H) and tails (T).
H T T T T T H H H H
One important thing to note about the sequence is that random does not mean “evenly distributed”. For example, after seeing a sequence of 3 tails, it is incorrect to assume that the next coin flip must yield heads.
What would happen if I ran the experiment again? What would be the chances (i.e., the odds) that same sequence of heads and tails would occur? Now let us consider writing a program that conducts the coin-flipping experiment on our behalf. What would you want to have happen if you ran such a program twice?
- Would you want the program to produce the same output every time it is executed?
- Would you want the program to produce the same output if it is executed on a different computer?
- Would you want the program to produce a (possibly) different output everytime it is executed? In this case, how might this impact your ability to test the program?
Simulating the Roll of a Die
The following algorithm shows how the random function can be used to simulate the roll of a six-sided die. The basic idea here is to partition the range 0.0 ≤ n < 1.0 into six equal sections and associate each section with (aka, map each section to) a side of the die.
- Let n = random ()
- if 0.0/6.0 ≤ n < 1.0/6.0 then print (“1”)
- else if 1.0/6.0 ≤ n < 2.0/6.0 then print (“2”)
- else if 2.0/6.0 ≤ n < 3.0/6.0 then print (“3”)
- else if 3.0/6.0 ≤ n < 4.0/6.0 then print (“4”)
- else if 4.0/6.0 ≤ n < 5.0/6.0 then print (“5”)
- else if 5.0/6.0 ≤ n < 6.0/6.0 then print (“6”)
This algorithm can also be easily implemented in SML.
I conducted an experiment where I rolled an actual die 10 times and got the following sequence of numbers.
3 5 1 2 1 5 2 2 5 2
Note that the numbers 4 and 6 never appeared. Does this imply that the die is “loaded”? No. If I roll the die five more times, will I be guaranteed that 4 and/or 6 will appear? No. It is important to appreciate that random number sequences have such properties.
Bricklayer’s Random Brick Function
Bricklayer provides a function, called generateRandomBrickFn, that can be used to generate random bricks. This function implements an algorithm similar to the coin-flipping and die-rolling algorithms described previously.
Let brickList denote a list of bricks. The evaluation of the expression
generateRandomBrickFn brickList
will produce a nullary function value as its result. A val-declaration can be used to bind a variable (i.e., our desired function name) to this nullary function value as follows.
val myName = generateRandomBrickFn brickList;
The evaluation of the function call “myName ()” will return a brick, randomly selected from brickList.
Predefined Brick Lists
Bricklayer provides a set of predefined brick lists that can be used to generate random brick functions. This set includes the following brick lists.
- grayScale
- greenScale
- blueScale
- purpleScale
- redScale
- warmScale
- brownScale
- clearScale
- allOneBitBricks
Consult the Bricklayer documentation on Pieces for a complete listing of all predefined brick lists as well as the bricks they contain.
Example 1 – A one dimensional sequence of RED and BLACK bricks.
This first example represents Bricklayer’s version of a sequence of coin flips. A brickList is defined that contains two bricks, a BLACK brick and a RED brick. We can think of one of these bricks as denoting “heads” and the other as denoting “tails”. Using this brickList, a random nullary funtion is declared called randomBrick. When called, the function randomBrick will return a randomly selected brick from the brickList. Specifically, the randomBrick function will randomly select either a BLACK brick or a RED brick and return this brick as its result.
The function sequence0 creates a sequence, running along the x-axis, consisting of 2 bricks randomly selected from brickList. Val-declarations are used to bind the variables brick1 and brick2 to random bricks obtained by evaluating the function call randomBrick ().
A geometric algorithm is used to create longer and longer sequences along the x-axis. The function sequence1 creates an artifact consisting of two sequence0 artifacts placed side-by-side. The remaining sequence functions construct their artifacts in a similar fashion.
open Level_3; val brickList = [BLACK,RED]; val randomBrick = generateRandomBrickFn brickList; fun sequence0 (x,z) = let val delta = 1; val brick1 = randomBrick (); val brick2 = randomBrick (); in put2D (1,1) brick1 (x + 0 * delta, z); put2D (1,1) brick2 (x + 1 * delta, z) end; fun sequence1 (x,z) = let val delta = 2*1; in sequence0 (x + 0 * delta, z ); sequence0 (x + 1 * delta, z ) end; fun sequence2 (x,z) = let val delta = 2*2*1; in sequence1 (x + 0 * delta, z ); sequence1 (x + 1 * delta, z ) end; fun sequence3 (x,z) = let val delta = 2*2*2*1; in sequence2 (x + 0 * delta, z ); sequence2 (x + 1 * delta, z ) end; fun sequence4 (x,z) = let val delta = 2*2*2*2*1; in sequence3 (x + 0 * delta, z ); sequence3 (x + 1 * delta, z ) end; build2D (32,32); sequence4 (0,0); show2D "random in 1D";
Example 2 – A four-colored two dimensional random brick sequence.
The code in this example creates a chessboard-like artifact in which unit bricks (i.e., bit-bricks) are randomly selected from the brick list
[RED, GREEN, YELLOW, BLUE]
Note that bricks are only created in the body of the function board0. More specifically, the put2D function is used to create four unit bricks in a square configuration. Each brick in this configuration is randomly selected from brickList via the function call randomBrick ().
A standard geometric algorithm is then used to create a 16×16 chessboard-like artifact.
It is worth mentioning that the distribtion of brick colors can be changed by creating a brickList in which desired bricks occur more frequently. For example, the following brick list
[RED, GREEN, YELLOW, BLUE, BLUE]
will randomly select BLUE bricks more frequently than other colored bricks. While this technique offers some control over brick distribution it is not meant to be used as a mechanism to create complex distributions.
open Level_3; val brickList = [RED, GREEN, YELLOW, BLUE]; val randomBrick = generateRandomBrickFn brickList; fun board0 (x,z) = let val delta = 1; val brick1 = randomBrick (); val brick2 = randomBrick (); val brick3 = randomBrick (); val brick4 = randomBrick (); in put2D (1,1) brick1 (x , z ); put2D (1,1) brick2 (x + delta, z ); put2D (1,1) brick3 (x + delta, z + delta); put2D (1,1) brick4 (x , z + delta) end; fun board1 (x,z) = let val delta = 2*1; in board0 (x , z ); board0 (x + delta, z ); board0 (x + delta, z + delta); board0 (x , z + delta) end; fun board2 (x,z) = let val delta = 2*2*1; in board1 (x , z ); board1 (x + delta, z ); board1 (x + delta, z + delta); board1 (x , z + delta) end; fun board3 (x,z) = let val delta = 2*2*2*1; in board2 (x , z ); board2 (x + delta, z ); board2 (x + delta, z + delta); board2 (x , z + delta) end; build2D (32,32); board3 (0,0); show2D "random 2D";