Simon game
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).
Enter your name:

### 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.

Preliminaries completed 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 new CircleVG() 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 displayVectorGraphics requires 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
+main let circle be new CircleVG() with
centreX set to 15,
centreY set to 15,
radius set to 10,
strokeWidth set to 0 call displayVectorGraphics([circle]) end main
Step completed 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

Note that it doesn't matter which value (1 to 4) is specified for move at this stage.

+main let move be 1 let colours be [red, yellow, blue, green] let circle be new CircleVG() with
centreX set to move*20 - 5,
centreY set to 15,
radius set to 10,
strokeWidth set to 0,
fillColour set to colours[move - 1] call displayVectorGraphics([circle]) end main
Step completed 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
The structure of your code should look like this:
main let call end main
procedure let let call end procedure
Hint 2: The whole code
main let move be 1 call displayMove(move) end main
procedure displayMove(move as Int) let colours be [red, yellow, blue, green] let circle be new CircleVG() with
centreX set to move*20 - 5,
centreY set to 15,
radius set to 10,
strokeWidth set to 0,
fillColour set to colours[move - 1] call displayVectorGraphics([circle]) end procedure

Step completed 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
+main let +each call end each end main
+procedure let let call call end procedure
Hint: Show the whole code.
+main let sequence be [3, 4, 1, 1, 2] +each move in sequence call displayMove(move) end each end main
+procedure displayMove(move as Int) let colours be [red, yellow, blue, green] let circle be new CircleVG() with
centreX set to move*20 - 5,
centreY set to 15,
radius set to 10,
strokeWidth set to 0,
fillColour set to colours[move - 1] call displayVectorGraphics([circle]) call pause(1000) end procedure
- 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
  +main variable let +each call end each end main
  +procedure let let call call end procedure
  Hint 2: Show the whole code
  +main variable interval set to 1000 let sequence be [3, 4, 1, 1, 2] +each move in sequence call displayMove(move, interval) end each end main
  +procedure displayMove(move as Int, interval as Int) let colours be [red, yellow, blue, green] let circle be new CircleVG() with
  centreX set to move*20 - 5,
  centreY set to 15,
  radius set to 10,
  strokeWidth set to 0,
  fillColour set to colours[move - 1] call displayVectorGraphics([circle]) call pause(interval) end procedure
- 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 existing pause 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
  call clearVectorGraphics() call pause(100)
  Hint: Show the whole procedure
  +procedure displayMove(move as Int, interval as Int) let colours be [red, yellow, blue, green] let circle be new CircleVG() with
  centreX set to move*20 - 5,
  centreY set to 15,
  radius set to 10,
  strokeWidth set to 0,
  fillColour set to colours[move - 1] call displayVectorGraphics([circle]) call pause(interval) call clearVectorGraphics() call pause(100) end procedure

Step completed 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
let tones be [164.81, 220, 277.18, 329.63] ... call tone(interval, tones[move - 1], 0.1)
Hint 2: complete procedure
+procedure displayMove(move as Int, interval as Int) let colours be [red, yellow, blue, green] let tones be [164.81, 220, 277.18, 329.63] let circle be new CircleVG() with
centreX set to move*20 - 5,
centreY set to 15,
radius set to 10,
strokeWidth set to 0,
fillColour set to colours[move - 1] call displayVectorGraphics([circle]) call tone(interval, tones[move - 1], 0.1) call clearVectorGraphics() call pause(100) end procedure
Step completed 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
  call sequence.append(4)
- 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
  randomInt(1, 4)
  Hint2: the complete changed instruction
  call sequence.append(randomInt(1, 4))
- 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
  - Use a for loop to execute something a fixed number of times.
  - For the counter use a short name that hasn't been used elsewhere - for example n
  Hint 2: The complete revised main
  +main variable interval set to 1000 let sequence be [3, 4, 1, 1, 2] +for n from 1 to 5 step 1 call sequence.append(randomInt(1, 4)) end for +each move in sequence call displayMove(move, interval) end each end 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<of Int> Then run the program and test that it generates a sequence of five random moves.
Hint 1: complete revised main
+main variable interval set to 1000 let sequence be empty List<of Int> +for n from 1 to 5 step 1 call sequence.append(randomInt(1, 4)) end for +each move in sequence call displayMove(move, interval) end each end main

Step completed 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: sequence as List<of Int>, interval as Int 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
  +procedure simonsTurn(sequence as List<of Int>, interval as Int) call +each call end each end procedure
  Hint: The complete procedure
  +procedure simonsTurn(sequence as List<of Int>, interval as Int) call sequence.append(randomInt(1, 4)) +each move in sequence call displayMove(move, interval) end each end 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
+main variable interval set to 1000 let sequence be empty List<of Int> +for n from 1 to 5 step 1 call simonsTurn(sequence, interval) call clearVectorGraphics() call pause(1000) end for end main

Step completed 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.
  +if valid then call displayMove(move, interval) end if
  Hint: The full playersTurn procedure so far.
  +procedure playersTurn(sequence as List<of Int>, interval as Int) let k be waitForKey() let valid, move be parseAsInt(k) +if valid then call displayMove(move, interval) end if end procedure
- 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.
  +main variable interval set to 1000 let sequence be empty List<of Int> +for n from 1 to 5 step 1 call simonsTurn(sequence, interval) call clearVectorGraphics() call pause(1000) call playersTurn(sequence, interval) end for end 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.

Step completed 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
+procedure playersTurn(sequence as List<of Int>, interval as Int) variable counter set to 0 variable correctSoFar set to true +while (counter < sequence.length()) and correctSoFar let k be waitForKey() let valid, move be parseAsInt(k) +if valid then call displayMove(move, interval) end if set counter to counter + 1 end while end 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
  +procedure playersTurn(sequence as List<of Int>, interval as Int) variable variable +while let let +if call else set print end if set end while end procedure
  Hint: Complete revised playersTurn procedure
  +procedure playersTurn(sequence as List<of Int>, interval as Int) variable counter set to 0 variable correctSoFar set to true +while (counter < sequence.length()) and correctSoFar let k be waitForKey() let valid, move be parseAsInt(k) +if valid and (sequence[counter] is move) then call displayMove(move, interval) else set correctSoFar to false print "Wrong!" end if set counter to counter + 1 end while end 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.

Step completed 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
  call clearVectorGraphics() call pause(2000)
- 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
  +main variable variable let +while call call call call call call end while end main
  Hint: The complete modified main
  +main variable ok set to true variable interval set to 1000 let sequence be empty List<of Int> +while ok call simonsTurn(sequence, interval) call clearVectorGraphics() call pause(1000) call playersTurn(sequence, interval) call clearVectorGraphics() call clearVectorGraphics() call pause(2000) end while end 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
  +procedure playersTurn(sequence as List<of Int>, interval as Int, out ok as Boolean)
- 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
  +procedure playersTurn(sequence as List<of Int>, interval as Int, out ok as Boolean) variable counter set to 0 variable correctSoFar set to true +while (counter < sequence.length()) and correctSoFar let k be waitForKey() let valid, move be parseAsInt(k) +if valid and (sequence[counter] is move) then call displayMove(move, interval) else set correctSoFar to false set ok to false end if set counter to counter + 1 end while end procedure
- Within main add a new instruction after end 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.

Step completed 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.

This worksheet is copyright © Richard Pawson 2025, and protected by the Creative Commons license: Attribution-NonCommercial-NoDerivatives 4.0 International. If you copy and modify this worksheet you may not distribute your modified version (outside your own teaching institution) without the author's permission. Please email the author to report errors or suggest improvements.