to continue. (After that any entries made into the worksheet will be automatically saved, and you can re-load the partially-completed worksheet in future - at which point you will be asked to auto-save it again).
Preliminaries
Set the browser to Full Screen view, to give this worksheet, and your code, as much space as possible.
Select File > New to start a new program,
then select File > Auto Save and complete the dialog to ensure that your code is always saved.
Remember that you can use the Undo button (or Ctrl-z) to undo recent changes.
Remember that if the code editor has a tinted background it does not have focus and will not respond
to edits. To give it focus click within the editor. (If a program is running, you will need to stop it first.)
Use the auto-completion options as much as possible, because this reduces errors.
Use the Hints only when you need them - as all use of hints is recorded. Use of Help is encouraged and not recorded.
Use the Trim button periodically to remove leftover new code prompts.
Total hints used: /
In the classic hand-held electronic game Simon,
the device played a sequence of 'moves' and the players task was to remember and reproduce
the sequence. Each of the four possible moves had a different colour (red, yellow, blue, green)
associated with a different physical position and with a different audible tone. If the player
reproduced the sequence correctly, the device played the sequence again with one more move added,
and as the sequence lengthened, each move was shown for a shorter duration.
If the player made a single incorrect move the game was over and the score
reflected the longest length of sequence that the player reproduced correctly.
The game was surprisingly engaging, and proved very popular.
We are going to build a simulation of the Simon game, with the four moves shown
as large coloured circles in a row accompanied by audible tones.
The player will use the four numeric keys 1 2 3 4 to input their memory
of the sequence - the numbers corresponding to the four positions in the row.
We will make more than one use of a 'List' data structure in this program.
A List is similar to an Array, but may be extended indefinitely
(within in the limits of a computer's memory).
A list may even start off empty and grow from there.
Iteration 1 - Draw a solid circle
Add a main, and within that add a let
instruction, defining the name circle to be newCircleVG()
noting that the 'VG'' in CircleVG is short for 'Vector Graphic' which is
a way to draw very high resolution graphics,with more flexibility and greater speed than is possible using
Turtle graphics, for example.
Follow this with:
call displayVectorGraphics([circle])
noting the use of square brackets around [circle]. The square brackets
define a literal List, in this case with just one member. The procedure method
displayVectorGraphicsrequires a List as its argument because a typical application using Vector Graphics
would display multiple shapes - lines, circles, rectangles and more - and we could
just include them all within the square brackets, separated by commas.
We will be making a more significant uses of Lists later on.
Run the application and note that it displays a circle in the centre of the Display. But
why is it yellow, and what determined the size and position? It uses some default values - just so
that you see something. Now we will specify those properties.
Select the second field in the let statement, and at the right hand end
enter with (followed by a space). You will now be presented with a
a list of the properties that can be specified with for a CircleVG.
Select the first option, centreX, then Tab to add the prompted words
15,set to and then enter the value 15 followed by a comma.
Repeat this sequence to set the centreY to 15, the radius to 10,
and the strokeWidth to 0 - the last one being to get rid of the black line around the circle.
When you press Enter at the end of this field, the instruction will automatically reformat for
over several lines for readability, but note that this is still one instruction, and if you were to edit it again the field would still be one long line.
How do we know what values to use, even approximately? The Display measures 100 units wide by 75 units deep,
with the top-left corner having coordinates 0,0, and the bottom-right 100, 75.
Run the program again and confirm that a larger yellow circle, now with no edge line at the
top-left of the Display.
Hint: The main so far
Total hints used: /
Iteration 2- Draw circle in four different locations and colours
Although the game will display only one circle at a time, we want to position it
in one of four places in a row, with a different colour for each place.
We already know that we will be using the 1 2 3 4 keys so, for
the time being, we will simply define a name move which holds
one of those four integer values.
Above the first let instruction add another
let that defines move to be 4.
Then we'll use that to adjust the horizontal position of the circle.
Given that the circle has radius of 10, we want the four positions
to be spaced 20 apart, so that the locations are distinct. So change
the value specified (lower down) for centreX from 15
to:
move*20 - 5
noting that:
the text will revert to a single line while being edited
as you are editing the field it will change colour at certain points when the text does not yet form a valid expression
but will turn green again when you are done.
Then press Enter to exit the field.
Run the program and check that the circle is now near the right-hand edge of the Display.
Now we want to be able to specify the colour according to the position
(i.e. according to the value of move). So let's first
define the four colour-values as a list.
Add the following immediately above the let
that defines circle:
let colours be [red, yellow, blue, green]
Again, this could have been an Array - because we won't be needing to extend it.
But if we already know the values, it is easier to define a 'literal' List - using
the square brackets - than it is to define and then populate an Array.
Back on the definition of the circle, add the following to the properties being set
(the order in which the properties are specified after a with is not important):
, fillColour set to colours[move - 1]
This is using move as the index to select a numbered element within the list of colours,
much as you have already done when using an Array. The reason we had to subtract
1 is that move will have the values 1,2,3, or 4
but the indexes for a list, as for an Array, are always numbered from 0.
Run the program more than once, varying the value defined for move from 1 to 4
to check that each value draws the circle in a different place and colour.
Hint: The main so far
Total hints used: /
Iteration 3 - Move the display capability into a procedure
Whenever you find yourself with multiple instructions that together perform something that you could put a single name on,
it is often good practice to move them into a named procedure.
This keeps your code easier to read and to maintain.
It is even more useful if you subsequently find yourself wanting to perform that same thing elsewhere in the code.
Select the new code prompt that follows end main
(or if there isn't one, select main and hit Enter).
Add a procedure and specify
its name as displayMove. Then in the field between
the brackets, add move as Int.
This specifies that when you call displayMove you must
provide a value that is an integer.
Now cut and paste the last three instructions from main into the new procedure
leaving only the definition of move in main.
It doesn't matter whether or not you also include any new code prompts -
they don't count as instructions - but it is neater to periodically Trim them.
Add a new instruction at the bottom of main to call the procedure,
using the defined value move as the argument.
Then run the program and check that it does the same as the previous iteration.
Hint 1: Abbreviated instructions
Hint 2: The whole code
Total hints used: /
Iteration 4: Play a sequence
Edit the first let in main, replacing the name move
with sequence and replacing the single value with this literal list of integers:
[3, 4, 1, 1, 2]
. This change will produce a temporary compile warning
because move is now unknown - but we'll fix that...
Immediately below the let just altered,
insert an each instruction that defines the elementName (first field) as move
and uses sequence as the source.
Cut and paste the existing call to displayMove into the new each loop.
Your code should now parse and compile correctly again.
Within the displayMove procedure add, at the end, an instruction to pause for a full second -
otherwise things will happen too quickly to see.
Run the program. What is it doing?
Hint: Show the abbreviated instructions
Hint: Show the whole code.
At a future stage we will want to be able to change the duration for which the circles appear, so define a
variable named interval at the very top of main, initialised to 1000.
Then pass interval as a second argument into the call to displayMove, separated from
move by a comma. This will initially give you an error because the definition of the procedure displayMove
currently expects only one parameter. So, within that procedure definition, define a second parameter, separated from the first
by a comma, giving it the name interval and the type for an integer. Then use that parameter name interval
instead of the literal number currently given as an argument to pause. Run the program to check that
it still works the same way.
Hint 1: Show the abbreviated instructions
Hint 2: Show the whole code
You might have noticed that where the sequence has the same move twice in succession, this just displays as a one longer
move - which will be confusing, especially if the game speeds up. To fix this we should clear the display
after each move and add a shorter pause. So after the existingpause instruction within displayMove
add a call to clearVectorGraphics and follow this by a fixed pause of 1/10th of a second. Run again
and check that any repeated moves are now distinct.
Hint: Show the new instructions
Hint: Show the whole procedure
Total hints used: /
Iteration 5: Add sound
Edit both fields of the instruction call pause(interval) so that it reads
call tone(interval, 256, 0.1)
which instead of just pausing, plays a tone for the same interval. The second argument defines the frequency of the tone - in
this case 256Hz, which musicians might recognise as 'middle C'. The third argument specifies the volume. Run the program.
If you do not hear any tones, investigate the sound settings on your computer. Then adjust the value up or down from 0.1,
but please, if you are working in a classroom set the volume to the lowest setting where you can still just hear the tones.
Just as the four moves are represented by different colours, in different locations, they
should also have different tones. The original Simon game used four musical notes - E, A, C#, and E (one octave higher) -
such that they would combine in any order for a pleasing effect. The frequencies, in Hz, for these four notes are: 164.81, 220, 277.18, and 329.63
(source reference).
Earlier, you defined a list of colours. Now, just under that definition of colours, create a similar definition named
tones (with an 's') and containing the four numeric values just given. Then, just as you earlier used
colours[move - 1] to get the colour corresponding to the move,
in the call to the tone procedure,
replace the specific value of 256 with code to get the tone corresponding to the move.
Then run the program to verify that you now hear a sequence of tones.
Hint 1: the new and modified instructions
Hint 2: complete procedure
Total hints used: /
Iteration 6: Build the random sequence
To append a value to a list we call the append procedure on the list (as a 'dotted method'). So,
within main, immediately after the sequence has been defined, write an instruction to append
the value 4 to that list. The run the program and check that the new move was added to the sequence
Hint: the new instruction
Now change that new instruction so that instead of appending a specific value, it appends a new random integer
in the range 1-4. Run the program a few times to check that the final move is unpredictable.
Hint 1: expression to generate the random number
Hint2: the complete changed instruction
Now we will do this repeatedly. Underneath the instruction just added (and above the each), add a loop that
will execute exactly 5 times, and then cut the instruction doing the append
and paste it inside that loop. Run the program to check that the sequence now has five random moves after the fixed initial sequence.
Hint 1: the loop
Hint 2: The complete revised main
Now that we can generate multiple random moves, we should start from an empty list.
Find the instruction (within main) that defines the initial list. We can't just
reduce the list to empty square brackets [] - because then Elan doesn't know what type of element
the list is intended to hold. So instead change the value to: empty List<ofInt>
Then run the program and test that it generates a sequence of five random moves.
Hint 1: complete revised main
Total hints used: /
Iteration 7: Create the simonsTurn procedure
Now we're ready to complete the role of Simon (played by the computer) in the game, which is to repeatedly play a sequence of moves, adding
a new random move each time. (We will later add the Player's role, interleaving between these plays). To keep the growing
program easy to read, we will build the Simon's role into a new procedure.
Create a new procedure named simonsTurn we suggest just after main
(though, actually, it doesn't matter what order 'global' instructions like main and procedures are defined)
and define parameters for the information it is going to need:
sequenceasList<ofInt>, intervalasInt
noting that whenever a List or Array is passed into a procedure as a parameter, the type of the elements
in the list must be specified in the form shown.
Cut and paste the instruction that does the append out of its for loop and into
the new procedure, and follow this by cutting and pastint the whole of the each loop to follow the append.
Hint: Abbreviated instructions in the new procedure
Go back to the (now empty) for loop within the main, and define three new instructions (in the order listed here):
a new instruction to call simonsTurn,
passing in the named values corresponding to the two arguments it will require.
an instruction to clear the vector graphics (you have already done this somewhere else in the code).
an instruction to pause for a full second (at the end of the sequence).
Run the program the program and test that it plays a growing sequence, one move, then two moves, up to five moves, with
a one second pause between.
Hint: The revised main
Total hints used: /
Iteration 8: Start work on Player's turn
We're now ready to make a start on the player's turn. Create a new procedure
named playersTurn (located underneath simonsTurn ), and initially defining exactly
the same parameters as for simonsTurn.
Now we need a way to detect a single key stroke, while the programming is running. You might or might not have already
used an input method, but that would also require Enter to be pressed after each key
which would be cumbersome in the game Simon. So instead add these two instructions within playersTurn:
let k be waitForKey()let valid, move be parseAsInt(k)
The intent of the first instruction should be fairly clear, but what is the second one for? Why couldn't we just write, say:
let move be waitForKey()
The problem is that if you press, say, the 1 key, the waitForKey method returns a single-character
string - equivalent to this instruction, say:let k be "1"
but we really need it as an integer value so that we can compare it to the integer values stored in sequence.
parseAsInt takes a string argument and attempts to turn it into an integer value.
The problem then is: what would happen if you had inadvertently pressed the q key underneath
the 1? That can't be be converted into an integer - which would cause the whole program to fail
with a 'run time error'.
To handle this scenario the parseAsInt actually returns two values: the first
is a Boolean value (true/false) that tells us whether or not the given string successfully parsed into an integer.
If it did (i.e. the first returned value is true) then the second value will hold the integer.
If the first returned value is false then the second value should be ignored - as it will be meaningless.
Our instruction names these two returned values valid and move respectively.
So, to use this information add a third instruction within playersTurn, an if instruction
that uses valid as its condition. (You could make the condition valid is true
and that would work, but it would be 'redundant code' because valid is itself always either true or false.)
Within the if call displayMove exactly the same as you did within simonsTurn.
Hint: The full if instruction.
Hint: The full playersTurn procedure so far.
playersTurn is far from complete - but to test that it works at all, go back to the for
loop in main, and add a last instruction to call playersTurn passing the same arguments as to
simonsTurn above it.
Hint: The modified main.
Run the program. Simon should play a sequence (initially just one move) and then do nothing until
you press any of the keys 1 - 4 - which should then immediately initiate Simon's turn. Stop the
program at that point.
Total hints used: /
Iteration 9: Require the player to reproduce the whole sequence
We now need a loop that requires the player to reproduce each move in the sequence.
We can determine the length of the sequence using
sequence.length(), which might
suggest using a for, or an each loop. The problem
is that if the user makes a mistake (enters the wrong move, or a key that isn't a valid move)
that's game over: we don't want to continue with the loop. So when the number of iterations
through the loop is not fixed up-front we must use a 'conditional
loop'. The condition for the loop continuing is that the user has hit the correct keys so far
and that they haven't yet got to the end of the sequence.
At the start of playersTurn, define two variables: counter initialised to zero, and
correctSoFar (meaning that the player's sequence is correct so far) initialised to true -
as the player hasn't had any chance to make a mistake yet!
Follow these with a while loop, defining its condition field as follows:
(counter < sequence.length()) and correctSoFar
noting the use of brackets to specify operator priority. This is almost always necessary
when you combined logical operators (and, or, not with
comparison operators (is, isnt, >, etc).
Cut and paste all the instructions remaining below it (within playersTurn) into the while loop.
Add a new last instruction within the loop, add one to the existing value of counter.
Hint: Complete revised playersTurn procedure
Run the program. Simon will make a move then pause for you to enter the same sequence (1 move).
Note that it isn't yet checking whether you pressed the correct key!
Pressing any key (1 -4) will pass for now. Then simon will make two moves, and you must enter two moves, and so on...
Play just up to a sequence of three moves, say - then Stop the program.
Now we'll check the correctness of the player's sequence. Find the if
instruction, and change the condition from just valid to
valid and (sequence[counter] is move)
again noting the use of brackets to specify operator priority.
Within the if, add an else clause after the call to displayMove
and underneath the else, set the value of correctSoFar to false followed by an
instruction to print the message 'Wrong!' (this is a temporary action - to be refined later).
Hint: Abbreviated instructions for playersTurn procedure
Hint: Complete revised playersTurn procedure
Run the program. You will have to be honest, and stop the program if you see the 'Wrong!' message. Also the game
isn't at all polished yet, but the essence of the game should be working.
Total hints used: /
Iteration 10: Polishing the game
Three bits of polishing are urgent - but write down anything else you can think of:
If you make a mistake when mimicking the sequence the game should stop.
There should be a more distinct pause between the end of your turn and Simon starting the next sequence.
When the game is over it should tell you your 'score' - the length of the largest sequence you correctly reproduced.
So ...
As the last instructions within the for in main, add instructions to clear the vector graphics
display and then to pause for two seconds. Run and test it.
Hint: the two new instructions
At the start of main, define a variable named ok initially set to true.
Replace the for loop in main with a while loop with a condition of ok,
(you'll need to create the while loop first, then cut and paste the contents from the old loop into the new, then delete the former.)
Hint: Abbreviated instructions in main
Hint: The complete modified main
Go to the definition of the playersTurn procedure, and add a third parameter definition
like this:
out ok as Boolean
Adding the keyword out in front of a parameter definition means that, within the procedure
you can assign a new value to that name (just as if it was a variable) and this change will be visible
to the code that calls the procedure. This will become clearer, shortly. This change will
give you a compile error in the call to playersTurn within main. So, in that call,
add ok (the local variable you defined there) as the third argument.
Hint: the revised procedure first line
Within the playersTurn procedure, delete the instruction print "Wrong!" and
replace it with one that sets the value of ok to false.
Hint: the whole revised procedure
Within main add a new instruction afterend while to print a message that
says, for example Game over! You scored: 10 but instead of literally 10 make it print the length
of the sequence minus 1. Why 'minus 1'?
Hint: The complete print instruction
print "Game over! You scored: {sequence.length() - 1}"
Run the program and test that it now seems a little more polished.
Total hints used: /
You have completed the worksheet. Well done!
If you have time try to make the following refinement, or think of some of your own:
Trim the time delays and starting interval so that the game flows nicely.
With each turn of the game (i.e. each extension of the sequence) slightly
reduce the size of the interval (e.g. by 25 milliseconds) so that the playing
of the sequence gradually speeds up.