This is only a preview of the May 2021 issue of Practical Electronics. You can view 0 of the 72 pages in the full issue. Articles in this series:
|
Max’s Cool Beans
By Max the Magnificent
Flashing LEDs and drooling engineers – Part 15
E
very now and then,* a once-
in-a-generation mind appears
on the scene (*by some strange
quirk of fate, this occurs approximately
once in a generation). Such a mind sees
things beyond the ken of the average
Joe or Josephina, shining the bright
light of understanding on previously
unsurmountable problems, rending
the veils asunder, and revealing the
mysteries of the universe.
What? Who? Me?
Well, you are very kind, and I can see
how you might have come to that conclusion (especially if you’ve been talking to
my dear old mother), but I was actually
talking about the English mathematician
John Horton Conway (1937–2020), who
made his appearance in the generation
before mine, thereby ensuring that future
historians would not be obliged to pick
and choose between us.
Speaking of generations, I’m reminded
of the question I posed in, With Which
Generation Do You Identify Yourself?
(https://bit.ly/3brBsfv), where I asked:
‘Are you from the Silent Generation, a
Baby Boomer, a member of the Generation Jones crew, a Millennial, or do you
have ‘Generation X, Y, or Z’ tattooed
on your forehead?’ As for myself, I’m
a proud member of Generation Jones
(seriously; it really is a thing – http://
bit.ly/318NfcD).
It’s alive!
Conway was a larger-than-life character
who has been described as ‘Archimedes,
Mick Jagger, Salvador Dali, and Richard
Feynman all rolled into one.’ Among his
many contributions to diverse branches
of mathematics, Conway invented (maybe
we should say ‘discovered’) the ‘surreal
numbers’. As the Wikipedia observes, ‘If
formulated in Von Neumann-BernaysGödel set theory, the surreal numbers
are a universal ordered field in the sense
that all other ordered fields, such as the
rationals, the reals, the rational functions, the Levi-Civita field, the superreal numbers, and the hyperreal numbers, can be realised as subfields of the
surreals.’ Well, duh!
Of particular interest to us here is that
Conway was the originator of a famous
40
The great John Conway observing his Game of Life cellular automoton program on an
early computer screen.
two-dimensional cellular automaton
The first generation is created by ap(plural cellular automata, abbreviation
plying the above rules simultaneously to
CA) now known as Conway’s Game of
every cell in the seed generation. Births
Life (GOL), which he unleashed on the
and deaths occur simultaneously, and the
world in 1970. The ‘universe’ in which
discrete moment at which this happens is
the GOL takes place is an infinite, twosometimes called a ‘tick.’ Each generation
dimensional orthogonal grid of
square ‘cells,’ each of which is in
one of two possible states, ‘live’ or
‘dead.’ Every cell interacts with
D ead c ell
its eight ‘neighbours,’ which are
L iv e c ell
those cells that are horizontally,
vertically or diagonally adjacent.
At the beginning of the game,
( a) S eed ( G eneration 0 )
the universe is populated with an
initial pattern, which constitutes
L iv e c ell with 0 or 1 liv e neighbors
the ‘seed’ of the system. The game
L iv e c ell with 2 liv e neighbors
then evolves from generation to
L iv e c ell with 3 liv e neighbors
generation according to the folL iv e c ell with > 3 liv e neighbors
lowing rules:
D ead c ell with 3 liv e neighbors
1. Any live cell with fewer than
( b) E v aluating G eneration 1
two live neighbours dies, as if
by underpopulation.
2. Any live cell with two or three
live neighbours lives on to the
D ead c ell
next generation.
L iv e c ell
3. Any live cell with more than
three live neighbours dies, as
( c ) G eneration 1
if by overpopulation.
4. Any dead cell with exactly three
live neighbours becomes a live Fig.1. A simple example reflecting a small portion
cell, as if by reproduction.
of the universe.
Practical Electronics | May | 2021
( a) S eed ( G eneration 0 )
( b) G eneration 1
( b) G eneration 2
( b) G eneration 3
( b) G eneration 4
Fig.2. This Glider translates across the grid in a south-easterly direction.
is a pure function of the preceding one.
The rules continue to be applied repeatedly to create further generations.
Let’s consider a simple example to get
our mental juices flowing (don’t worry,
we’ll mop everything up later). We’ll
start by looking at a small part of the
universe that’s been seeded as illustrated in Fig.1a. We will consider this to be
Generation 0. Next, we start to evaluate
the new generation, which will be Generation 1 in this case (Fig.1b). As we see,
there’s one live cell that doesn’t have any
neighbours, so that cell won’t make it to
the next generation (as if by underpopulation). We have one live cell that has
two live neighbours and two live cells
that have three live neighbours, all of
which will survive to the next generation. We have two live cells with four
live neighbours, which means they won’t
survive (as if by overpopulation). And
we have two dead cells that have three
live neighbours, so these will spring to
life in the next generation (as if by reproduction). Once we apply all of these
rules, the new generation, Generation 1,
will be as shown in Fig.1c.
Although the GOL may appear to be
simple at first, this is deceptive because
there are hidden layers of sophistication.
For example, different groupings of live
cells can result in very different behaviors
from generation to generation. Some patterns are known as ‘Still Lifes’ because
they don’t change from one generation
to another unless something happens to
disturb them. Slightly more complicated
1 3 3
( a) P hysic al wiring and pix el num bering
are the ‘Oscillators,’ which will return to
their initial state after some finite number
of generations.
Things start to get really interesting
when we meet patterns that can translate
(move) themselves across the universe
(grid or array). The first of these patterns,
and the simplest, is called the ‘Glider’,
which was discovered by Richard Guy in
1970. Consider the Glider shown in Fig.2.
After four generations we see the original
patten reappear, but it’s moved one cell to
the right and one cell down as compared
to its original location in Generation 0. If
we keep on running, the Glider will continue to make its way across the array.
When you first meet the rules for the
GOL, you might think of them as being
relatively easy to define. In reality, it took
Conway and his colleagues a lot of time
and mental effort to arrive at an ideal
balance that wouldn’t result in the game
experiencing rapid underpopulation or
overpopulation. The amazing thing to
me is that Conway didn’t have access
to a computer when he developed these
rules. Instead, he used the board and
pieces from the game of Go to experiment
with alternative scenarios. (If you want
to learn more about Conway, one book I
really enjoyed (but that may now be out
of print) is Genius at Play: The Curious
Mind of John Horton Conway by Siobhan Roberts: https://amzn.to/3kSZCT5).
Great (ping-pong) balls of life!
On the off chance you’ve only just started subscribing to Practical Electronics, I
( b) T he way we’ v e been thinking
1 4 4
1 3 3
should probably make mention of
the fact that, since the March 2020
issue, here in the world of Cool
Beans (where the flowers are more
colourful and smell nicer, the butterflies are bodacious and brighter,
and the birds are fluffier and sing
sweeter), we’ve been performing
experiments with a 12×12 array of
ping-pong balls, each equipped with
a tricolor LED in the form of a WS2812B
(a.k.a. ‘NeoPixel’) device.
What we are going to do is to implement a simple GOL on our 12×12 array,
but we immediately run into a problem,
which is the modest size of our universe
(array). Do you recall earlier when we
noted that, ‘The universe of the GOL is
an infinite, two-dimensional orthogonal grid of square cells’? Well, our universe is only 12×12 cells in size, which
some may feel to be a tad less imposing
than ‘infinite’.
Of course, the ‘infinite’ descriptor is associated with a theoretical GOL. In practice, every GOL creator has to handle a
bounded universe by one means or another. In my previous column (PE, April
2021), we discussed three ways in which
we could treat the edges of our universe.
We called the first option ‘Keep on trucking.’ The idea here is that when something like a Glider in the GOL reaches
the edge of the array it disappears from
the picture and we all shrug our shoulders and pretend that it’s heading out
into the great beyond.
We referred to our second option as
‘Bouncing off the edge.’ As this name
implies, when something reaches the
edge of the array, it bounces back into
the body of the array. Now, although this
was easy to achieve with the individual
pixels discussed in my previous column,
it would be a lot harder to implement for
something like a GOL Glider. If we were
desperate to realise a ‘Bouncing off the
edge’ scenario in our GOL simulation,
one approach we might try would be to
= [0 ,0 ]
( c ) ‘ Opposite D ay’ thinking
0
1 1
1
1 3 2
1 2 1
1 0 9
1 2 0
9
2
1 0 8
9 7
8
3
8 5
9 6
7
4
8 4
7 3
6 1
7 2
6 0
4 9
3 7
4 8
3
3 6
2 5
2
9
1 3
2 4
1
1 0
1 2
R ows ( Y )
R ows ( Y )
1 0
6
5
4
1 1
1 0
9
8
7
6
5
4
3
2
1
5
6
7
8
1 1
0
1 2
= [0 ,0 ]
0
1
2
3
4
5
6
7
C olum ns ( X )
8
9
1 0
1 1
0
1
2
3
4
5
6
7
C olum ns ( X )
8
9
1 0
1 1
Fig.3. Welcome to ‘Opposite Day’ thinking.
Practical Electronics | May | 2021
41
0
1
2
3
(Y - 1 )
4
R ows ( Y )
implement a 1-pixel wide band of imaginary
pixels surrounding our array containing virtual cells that are permanently alive (or dead).
Our third option was the ‘Curved spacetime’
scenario, in which we imagine the universe (in the
form of our ping-pong array) as being curled into
a vertical cylinder such that the right-hand side is
joined to the left-hand side, while simultaneously
being curled into a horizontal cylinder such that
the upper side is joined to the lower side. In this
scenario, when something like our GOL Glider
passes through the right-hand edge of our array,
for example, it will reappear on the left-hand side
of the array. It’s this ‘Curved spacetime’ scenario
that we are going to implement for our GOL.
5
[X ,Y ]
6
Y
7
(Y + 1 )
8
9
(X – 1 )
1 0
X
(X + 1 )
1 1
0
1
2
3
4
5
6
7
C olum ns ( X )
8
9
1 0
1 1
Opposite day
Fig.4. If only we didn’t have to take the edges into account.
Have you ever watched the American animated
comic series SpongeBob SquarePants? In one of the episodes
Happy dance or bad-hair day?
in Season 1, Squidward tells SpongeBob that it is ‘Opposite
Before we plunge into the fray with gusto and abandon, let’s
Day,’ which means everyone must act opposite to how they
pause for a moment to cogitate, ruminate, and deliberate on
usually act. The reason I mention this here is that, as you may
what we wish to do. When it comes to evaluating which cells
recall from our earlier discussions on the physical construcare going to perform their ‘happy dance’ and which are going
tion of the array (PE, August 2020), looking from the front,
to have a ‘bad hair day’ in the next generation, one of our
the pixels are wired in a serpentine fashion commencing in
tasks is going to be determining how many live neighbours
the lower right-hand corner and progressing back and forth
each cell currently has.
until we reach the upper right-hand corner (Fig.3a).
Let’s start by playing a simple thought experiment. Let’s
In the real world, these pixels are numbered from 1 to 144
assume we are interested only in a single cell referenced by
(we also have a ‘sacrificial’ pixel 0 that acts as a voltage level
[X,Y] that’s located somewhere in the middle of our array
shifter and that isn’t visible from the front of the array). For
and we want to determine how many of its neighbours are
reasons that made sense at the time, we decided to visualise
alive (Fig.4).
the array as comprising 12 columns (the X axis) and 12 rows
Purely for the sake of this experiment, let’s assume that
(the Y axis), both numbered from 0 to 11, with location [X,Y]
we’ve declared a two-dimensional (2D) array of cells called
= [0,0] appearing in the lower left-hand corner of the array
Cells[NUM_Y][NUM_X], where NUM_Y and NUM_X have
(Fig.3b). As part of this, we created a GetNeoNum() function
both been defined as 12. For reasons that will become apthat accepts X and Y values and returns the corresponding
parent, we are going to visualise this array as being presentnumber (iNeo) of the pixel in the array. The core of this funced in the form Cells[Y][X] (as opposed to Cells[X][Y],
tion, which we’ve been using in all of our programs since the
which would be a tad more intuitive). Let’s also assume that
beginning, was as follows (where NUM_XY is defined as 12):
these cells can be in one of two states: ALIVE or DEAD. Furthermore, let’s assume that we’ve declared an integer called
iNeo = yInd * NUM_XY;
numNeigh in which we are going to record the number of
neighbouring cells that are alive. If we don’t have to acif ( (yInd % 2) == 0)
count for the effects at the edges, we can simply employ dY
{
// Even row
(‘delta Y’) and dX (‘delta X’) values that each range from –1
iNeo = iNeo + (12 - xInd);
to +1 as follows:
}
else
numNeigh = 0;
{
// Odd row
iNeo = iNeo + (xInd + 1);
for (int dY = -1; dY <= 1; dY++)
}
for (int dX = -1; dX <=1; dX++)
if ( !( (dX == 0) && (dY == 0) ) )
In order to refresh yourself as to how this works, jot down
if (Cells[Y + dY][X + dX] == ALIVE)
a couple of sample [X,Y] combinations on a piece of paper
numNeigh = numNeigh + 1;
and then run them through this function as a mental exercise. For example, if we assume [X,Y] = [0,0], then when
The way this works is that the nested for() loops take us
we apply these values to our function it returns 12, which
through every one of the nine cells in the ‘cluster’ centered
is the physical number of the pixel in the lower left-hand
on the cell at [X,Y]. The first if() test makes sure that we
corner. Hurray!
aren’t counting the cell at [X,Y] itself. The second if() test
Unfortunately, for reasons that will become apparent as we
checks to see if the surrounding cell in question is ALIVE
proceed, when it comes to our implementation of the GOL,
and, if so, increments our count of live neighbours.
it’s going to make our lives a lot easier if we think of location
Before we proceed, let’s remind ourselves that, in the not[X,Y] = [0,0] as appearing in the upper left-hand corner (Fig.3c).
so-distant past, microcontrollers were much less powerful
There are a variety of solutions we could employ, but the
than they are today. At that time, it was important to coneasiest way to make this happen is to modify our GetNeoserve every clock cycle and/or memory location. In fact, this
Num() function so that it returns the values we want to see.
may still be the case in some of today’s real-world systems.
Why don’t you ponder this for a while to determine what
Assuming our goal is to minimise clock cycles, how might
changes need to be made (you’ll find my solution at the end
we modify our code snippet above? Well, one way would
of this column).
be as follows:
42
Practical Electronics | May | 2021
if (Cells[Y][X] == ALIVE)
numNeigh = -1;
else
numNeigh = 0;
for (int dY = -1; dY <= 1; dY++)
for (int dX = -1; dX <=1; dX++)
if (Cells[Y + dY][X + dX] == ALIVE)
numNeigh = numNeigh + 1;
What we’ve done is to take the test that makes sure that we
aren’t counting the cell at [X,Y] itself outside of the nested loop
in which it used to be performed nine times. Instead, we’ve
replaced it with a single test outside the loop that uses the
state of this cell to initialise our numNeigh count variable. If
cell [X,Y] is alive, we set numNeigh to –1, which means that
when we come to count this cell in the nested loop, it cancels
itself out, if you see what I mean.
Master the modulo
Of course, we do need to handle the cells at the edge of the array.
Let’s remind ourselves how we dealt with this in our previous
column. Purely for the purposes of providing an example, consider a single row of 12 ping-pong balls numbered left-to-right
from 0 to 11. Now assume we are looking at pixel 6 (Fig.5).
I’m not going to repeat our previous discussions. The important part is that if we are looking at pixel 6, then the pixel to
the left will be (Pixel + M1) % NUM_XY, which equates to
(6 + 11) % 12 = 17 % 12 = 5. Similarly, the pixel to the right
will be (Pixel + P1) % NUM_XY, which equates to (6 + 13)
% 12 = 19 % 12 = 7.
The important part of all this is that this technique implements our curved spacetime universe. In the case of pixel 11,
for example, the pixel to the right will equate to (11 + 13) %12
= 24 %12 = 0. Similarly, in the case of pixel 0, the pixel to the
left will equate to (0 + 11) % 12 = 11 % 12 = 11. Once again,
I feel a little ‘Tra-la’ wouldn’t go amiss.
Unfortunately, there is a slight ‘gotcha’ because we can no
longer directly use dX and dY delta values ranging from –1 to
+1. On the other hand, this is a small issue in the scheme of
things. Assume that we’ve already defined our M1 and PI values
as illustrated in Fig.5. Also, that we’ve defined NUM_DELTAS
(‘number of delta values’) as being 3. Consider the following
snippet of code, which will work for any cell in the array, including the little rascals at the edges:
int Deltas[NUM_DELTAS] = {M1, 0, P1};
if (Cells[Y][X] == ALIVE)
numNeigh = -1;
else
numNeigh = 0;
for (int dY = 0; dY < NUM_DVALS; dY++)
for (int dX = 0; dX < NUM_DVALS; dX++)
{
int tX = (X + Deltas[dX]) % NUM_XY;
int tY = (Y + Deltas[dY]) % NUM_XY;
if (Cells[tY][tX] == ALIVE)
numNeigh = numNeigh + 1;
}
I don’t know about you, but when I look at this it makes me
think that it’s almost as if we had a plan. Sad to relate, if that’s
what you think, then you’d be sadly mistaken because I am
literally making this up as we go along.
Practical Electronics | May | 2021
N U M _ X Y = 1 2
M A X _ X Y = (N U M _ X Y – 1 )
0
1
2
( P ix el + M 1 ) %
3
4
N U M _ X Y
5
P 1 ( “ P lus One” )
= (N U M _ X Y + 1 )
M 1 ( “ M inus One” ) = M A X _ X Y
6
P ix el = 6
7
8
9
( P ix el + P 1 ) %
1 0
1 1
N U M _ X Y
Fig.5. The masterful % modulo operator.
One is the loneliest number
Do you recall the song ‘One’ (more commonly thought of as
‘One is the loneliest number’), which was originally written
and recorded by Harry Nilsson? This song was made famous
by the American rock band Three Dog Night, whose recording in the key of F minor reached number five on the US Billboard Hot 100 in 1969.
I loathe this song with a passion. The only reason I mention it here is to remind ourselves that all we’ve done so far
is to consider the processing of a single hand-picked cell.
We’re going to have to scale this up to evaluate every cell in
our array. Also, we’re going to have to use this information
to determine which cells will live and which will die in the
next generation.
At this stage it would be a great idea for you to print
out the entire sketch (program) so you can follow along
(file CB-May21-01.txt, which is available on the May 2021
page of the PE website https://bit.ly/3oouhbl). We won’t go
through the entire program here, but let’s take a quick peek
at a couple of key elements.
First, we declare two enumerated types as shown below
(we introduced the concepts of typedef (type definitions),
enum (enumerated types), and struct (structures) in Tips
and Tricks, PE, December 2020):
typedef enum nGenOptions
{
ALIVE,
DEAD
};
typedef enum xGenOptions
{
STAYING_ALIVE,
DYING,
STAYING_DEAD,
COMING_ALIVE
};
We also define a structure called CellData and then we declare a two-dimensional (2D) array called Cells[][], where
each element of the array is a copy of our structure as follows (nGenState is the state of the cells in the current generation; xGenState is what we are going to use to define the
state in the next generation; and numNeigh is the number of
live neighbours):
typedef struct CellData
{
nGenOptions nGenState;
xGenOptions xGenState;
int
numNeigh;
};
CellData Cells[NUM_Y][NUM_X];
43
Exciting Freelance Design
Engineer Opportunity
i n t
{
We have a product idea that needs engineering input to
produce a proof-of-concept prototype.
Essentially a Vending Machine dispenser of liquid that can take
digital payments, the project will require an individual – or
small team – to design a fully operational machine that can
demonstrate the core design and key functionality features.
The main sub-systems and skills required to achieve success
will be:
n Robust microcontroller design
n Touchscreen user interface
n Food-safe operation – in particular, liquid dispensing from a
small, cooled storage tank to a customer vessel
n Overall systems integration
n Web connected
n Able to take digital payment – for example, chip and pin,
contactless payment and Apple Pay
The mechanical engineering side of the project is relatively
straightforward and is likely to involve little more than a
cooler, a pump, some solenoid valves, a few system monitoring
sensors (flow/pressure/temperature) and connecting plumbing.
However, understanding and appreciation of the engineering
requirements of handling food served to the public is critical.
At this stage the design is open-ended, so it is not tied to a
particular microcontroller platform. The machine will not be
expected to accept traditional cash.
If this sounds like your kind of challenge then please email me at:
iancrosby668<at>gmail.com
If you feel able to complete most but not all of the above then do
not be put off, just let me know what you can but also cannot do.
Do you remember earlier when we said, ‘We are going to visualise this array as being presented in the form Cells[Y][X]
(as opposed to Cells[X][Y], which would be a tad more intuitive).’ Well, this is where we discover the rationale behind
this scheme, and also why we decided to think of our array in
its ‘Opposite Day’ configuration.
One of the things we want to be able to do is load our array
with an initial ‘Seed’ configuration. The way I decided to specify
this seed is as a 2D array of integers, as shown in Fig.6. Observe
that I’ve specified a Glider (shown as red ‘1’ characters) in the
lower left-hand corner. You can observe this Glider in action
in a video I just captured: https://bit.ly/3bMZbGQ
The way the C/C++ compiler works, when we declare a 2D
array, the first index is for the rows (our Y axis, with Y = 0 at
the top and Y = 11 at the bottom) and the second index is for
the columns (our X axis, with X = 0 on the left and X = 11 on
the right).
Now all becomes clear. When we specify a starting pattern in
our Seed[][] array, we want that pattern to be replicated ‘as-is’
on the physical array, otherwise we’re going to end up spending an inordinate amount of time performing mental gymnastics as we try to convert from one representation to another in
our heads (and I fear my gymnastic days are behind me). In the
same way that there’s no point in barking when you have a dog,
there’s also no point making your head hurt performing wacky
transformations when you have a computer to do them for you.
I think you’ll find the rest of the program easy to understand.
As you’ll see, in addition to our GetNeoNum() function, we’ve
declared four other functions. The InitializeUniverse() function loads the
current generation (Generation 0, in this
case) in our Cells[][] array with ALIVE
or DEAD values, as defined in our Seed[][]
array. The EvaluateNextGeneration()
44
} ;
S eed [ N U M_ Y ] [ N U M_ X ]
=
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ,
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ,
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ,
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ,
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ,
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ,
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ,
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ,
{ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0} ,
{ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0} ,
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} ,
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
/ /
/ /
/ /
/ /
/ /
/ /
/ /
/ /
/ /
/ /
/ /
/ /
/ /
Y
0
1
2
3
4
5
7
6
8
9
1 0
1 1
|
X
0 < - >
0 < - >
0 < - >
0 < - >
0 < - >
0 < - >
0 < - >
0 < - >
0 < - >
0 < - >
0 < - >
0 < - >
1 1
|
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
Fig.6. An example seed array.
function scans the current generation to determine the number
of live neighbours for each cell, after which it decides what
each cell is going to do going forward to the next generation. If
a cell is currently alive, the two choices are for it to stay alive
or to die; if the cell is currently dead, then the two options are
for it to stay dead or to spring into life.
The UpdateCurrentGeneration() function takes the results from the EvaluateNextGeneration() function and
uses them to define the next generation; that is, the new current generation. Finally, the DisplayCurrentGeneration()
function takes the current generation data and displays it on
the physical array.
After initialising the NeoPixels by turning them all off, the
setup() function calls the InitializeUniverse() function
followed by the DisplayCurrentGeneration() function, after
which we pause for a couple of seconds to make sure what we
see on the physical array is what we expected to see as specified
in our Seed[][] array. Next, the loop() function cycles around
calling the EvaluateNextGeneration(), UpdateCurrentGeneration(), and DisplayCurrentGeneration() functions.
Next time...
In our earlier experiments with the array, we faded from one
colour to another. Consider the Glider sequence shown in Fig.2.
How might we modify our code to fade from one pattern to the
next, as opposed to the ‘bang-bang-bang...’ approach we are currently using? Similarly, consider the sequence shown in Fig.1.
How might we modify our program to (a) display the intermediate (half-generation) steps, (b) use colours to reflect the four
cases of staying alive, staying dead, coming alive, and dying,
and (c) to do all this using our fading techniques?
In my next column, we will answer these questions, after
which we will start to look at a new form of display that – if
you are anything like me – will make you squeal in delight.
Until then, have a good one!
Solution to GetNeoNum()
The modifications we need to make to our GetNeoNum() function to reflect our ‘Opposite Day’ view of the world are easypeasy-lemon-squeezy. All we have to do is add the following
statement before any of the existing statements (where MAX_XY
is defined as being 11):
yInd = MAX_XY - yInd;
Now, if we assume [X,Y] = [0,0], then when we apply these values
to our modified function it returns 133, which is the physical
number of the pixel in the upper left-hand corner. Hurray Squared!
Cool bean Max Maxfield (Hawaiian shirt, on the right) is emperor
of all he surveys at CliveMaxfield.com – the go-to site for the
latest and greatest in technological geekdom.
Comments or questions? Email Max at: max<at>CliveMaxfield.com
Practical Electronics | May | 2021
|