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: /
Iteration 1 – Create an array of strings
Any array is a simple form of 'data structure' – which is a mechanism for holding multiple, related, data elements (also called 'items')
such that they may be read or modified all together, but also read or modified individually. We are going to write a simple version of the game 'Whack-a-mole', using an array to represent a line of
ten holes from which a mole might briefly appear.
Add a main, and within it write this instruction:
let holes be newArray<ofString>(10, "*")expression?
and note the following:
The array is given a name just like any other named value.
All elements within an array must be of the same type, and we have specified that this type is a string
by writing Array<ofString>, but we could have created an Array<ofFloat>, for example.
An array (unlike other data structures that we will discover in future) is created with a fixed size: in this case
10, to represent 10 holes.
We must also specify an initial value to be applied to each element, which must be of the
correct type – in this case a string. We have chosen an asterisk "*", to represent a mole. Note
that the double quotes are required to delimit the string: the string itself is just the asterisk.
Add an instruction to print the holes, then run the program, noticing that the 10 elements of this array are printed successively – separated by a comma-and-space – and surrounded by square brackets.
Total hints used: /
Iteration 2 – put a value into a specific element
Change the instruction that defined the array so that, instead of an asterisk, each element is initialised to a single
space – to represent an empty hole – and run the program to check that you are now seeing just spaces between the commas.
Insert a new instruction immediately after the let instruction (select that instruction and press Enter to insert new code below it)
and create this instruction:
call holes.put(1, "*")
noting that:
put is a procedure method that can be applied to an array.
The syntax holes.put is known as a 'dot method' call whereby
you specify a thing (holes) and then apply (indicated by the dot) a specified method to it.
Run the program and make careful note of where the asterisk (mole) has appeared.
You might have been surprised that the mole didn't appear at the first hole. That's because the elements in an
array are always numbered from zero. So the last element in an array of 10 elements is number 9.
Add two more similar call instructions (all before the print instruction) to put an asterisk in element numbers
0 and 9. Run the program and check that it shows three moles in the expected places.
Change the instruction that modifies element 9 to specify element 10.
Run the program, which will result in a 'Runtime error'. Make sure you understand the error message, so that you will recognise what is going on
if you see this message unexpectedly in future.
Total hints used: /
Iteration 3 – display the array of holes in a more useful way
We have learned how to write an individual element using the put. We can also
read an individual element just by using an index. For example holes[5]
will access element number 5, and, assuming we have defined an integer value named n then
holes[n] will read element number n.
In the game of Whack-a-mole we just want to present the 10 holes, each with or without a mole coming out of it.
Also, we need to have the holes numbered because the player must be able to identify the hole number
quickly and press the corresponding digit on the keyboard. We can do this with a loop. Start by
deleting the last three instructions within main.
(To delete an instruction, select it by clicking on the keyword at the start of the instruction and then
press Ctrl-Delete or Ctrl-d). You should be back to this:
+mainlet holesname be newArray<ofString>(10, "")call holes.putprocedureName(1, "*")new codeend main
After the instruction that creates the array, add this one to label the holes:
print "0 1 2 3 4 5 6 7 8 9"expression
noting that there are two spaces between each digit.
Now we want to print the 10 holes individually. It is possible to do this with a for loop because we know,
in advance, how many times we want to print something. But when dealing with arrays (or several other data structures)
there's an even simpler mechanism for doing the same thing to, or with, each of the members – called
an 'each loop'. So as the last instruction within
main add an each loop, completed to look like this:
+each holevariableName? in holessource?9new code call each for if let print repeat set throw try variable while #end each
Within the loop add this instruction:
call printNoLineprocedureName?("{hole}"arguments?)10
noting that:
printNoLine (make sure you picked that one and not printLine) is a procedure that works a bit like print but does not print a newline afterwards.
We are printing an 'interpolated string' so that the value of hole is inserted into the string
between the curly-braces (which will not, themselves, appear in the printed string).
There are two spaces after the close-brace – to match the two spaces between digit labels in the earlier print instruction.
Run the program and check that you see see the labels for the holes and the mole appearing in hole 1.
Total hints used: /
Iteration 4 – put the mole in a random hole
To make the mole(s) unpredictable, we need to generate a random integer in the range of 0 to 9.
So, add a new let instruction, immediately after the instruction that defines the array, defining the name
molePosition with the value generated by the expression randomInt(0, 9)
Change the call instruction below this so that instead of putting the mole ( "*" )
specifically into hole 1, you put it into the hole defined as molePosition.
Run the program more than once and check that the mole typically appears at a different hole each time you run (it will occasionally pick the same hole).
Total hints used: /
Iteration 5 – Move the mole randomly, once a second
When we want to repeat a piece of behaviour indefinitely, we use an 'infinite' loop, which is a
loop where the 'condition' for continuing the loop never evaluates to false.
So, after the last instruction within main insert a while instruction,
putting the fixed value true into the condition field.
Cut all the instructions within main except the initial definition of
the holes array and the while, and paste them into the while loop. To do this,
select the top-most instruction you want to move, then press Ctrl-↓ to include successive
instructions below it, and press Ctrl-x to cut them. Then select the new code
within the while loop and paste in the cut instructions with Ctrl-v.
(You may also perform cut and paste from the context menu, which you access with right-mouse-click
on an instruction).
If you try running the program now – noting that because the loop never exits you will need to press the
Stop button to resume editing the code – you will see that the Display repeatedly adds new text
instead of over-writing it. We can fix this with a call to the clearPrintedText
procedure (which requires no arguments between the brackets). Add this as the last instruction within thewhile loop -
after the end each.
Also, we need to slow things down by calling the pause procedure, which requires the length
of pause to be specified as an argument (i.e. between the brackets after the procedure name). The length
is specified in milliseconds so use a value of 2000 to start with.
This can be reduced later to say 1000 (one second) to make the game more challenging once you
have got the hang of it and finished testing.
Add this instruction
before the one that clears the text, then run again.
The results should be better, but still not what we want: instead of moving the mole, we are
adding a mole each time, soon filling all the holes. So now, after the instruction that clears the printed text,
call the put procedure on holes to replace the mole at the current molePosition
with an empty hole (a single space in quotes) – then run again. It should be beginning to look more like a game,
but now we need to be able to interact with it.
Total hints used: /
Iteration 6 – Read a keypress dynamically and whack the mole if the digit matches
Calling the pause procedure was a very simple way to slow down the loop. Unfortunately, during such
a pause we can't do anything else. Doing things in parallel is possible in programming, but the techniques are more complex.
What we need instead is to create a loop that monitors for a keypress by the user, but – whether or not a keypress is
detected – will exit the loop after one second, so that the mole can be moved again.
Start by deleting the instruction doing the pause, and replacing it with:
let stopTime be clock() + 2000
noting that:
The method clock returns a single integer representing the current time in milliseconds.
By adding 2000 to this we are defining a stopTime (time) as two seconds after this instruction is executed.
On the next line, add another while loop, specifying that the condition for continuing the
loop is:
clock() < stopTime
which is saying 'keep repeating the loop while the current time is less than the defined stop time'.
Within this new loop, add the instruction:
let k be getKey()
noting that if a key has been pressed (since the last time it was called), the method getKey
will return that key as a string. If no key has been pressed, it will return the empty string "".
We now need to detect whether the key that the user has pressed (if any) corresponds to the current molePosition.
So, immediately after the instruction just created, add an if instruction, with the condition:
k is molePosition.asString()
noting that whereas k is a string, molePosition is an integer and
the two cannot be compared directly. Adding .asString() returns a string representation
of that integer – in other words it is effectively converting 7 to "7" – so
that it can be directly compared to the string k.
Try removing the .asString() and wait a couple of seconds to see
the compile error. Take note of what the error message says so that if you see this again in future you
will realise that you are trying to compare values of different types. Then restore the .asString() -
you don't need to type it again, you can just press Ctrl-z (undo).
Now within the body of the if instruction add:
print "Hit!"
Run the program and see if you can press the numeric key corresponding to the mole's position.
You will need to press the Stop button to exit the game.
You might find it helpful for your first few games to slow it down further by re-defining the stopTime
time to the current time plus three seconds instead of two seconds. Then run again.
Total hints used: /
Iteration 7 – Keep the score
To keep track of how many moles you have successfully whacked, define a value named score initially
set to 0, right up near the start of main i.e. not within any of the loops. Note that
unlike other named values that we have defined with a let instruction, score will need to
be created as a variable because we are going to change it whenever a mole is whacked.
Immediately after the print "Hit!" instruction,
add a new instruction to set the score to its current value + 1.
Now, above the instruction print "0 1 2 3 4 5 6 7 8 9"
add a new instruction to print the score:
print "Score: {score}"
noting again that we are printing a string that contains some literal text and an 'interpolated field' (in braces)
to get the actual score value.
Play the game and see how many moles you can whack.
Once you've got the hang of it reduce the time interval from 2000 milliseconds
to 1000, say, and try again.
Total hints used: /
Congratulations! You've completed the exercise, written a simple version of Whack-a-mole, and learned how to use an array.
Optional extensions
If you have time, try to refine and/or extend the game in the following ways:
Change scoring so that a hit gains 3 points but every miss loses a point,
noting that to detect a miss you must test for k isnt ""
– otherwise you will be counting as a miss each iteration of the loop even if no key has been pressed.
Make it so that the game doesn't continue indefinitely by changing while true to another
condition. You could either:
Run it for a fixed amount of time – 30 seconds say, or...
Count the number of times the mole has appeared, and run the outer loop until this reaches, say, 30 appearances.
At the start of the game allow the user to specify a difficulty level of, say, 1 to 5. Use this number
to shorten the stopTime progressively with each increase in level.
Make sure you display the difficulty level alongside the score.
Also at the start of the game, ask the user to input their name, and display this with the score and difficulty level.
As the main loop is now getting quite long, and has several levels of indentation, improve the readability
of the code by progressively 'refactoring' – extracting instructions into a new named procedure
and calling that procedure from the main routine. A good aim is to get both the main, and each procedure down to
a maximum of 10 instructions. Don't forget that a procedure can also delegate work to other procedures.