Navigation for the ZIM JavaScript Canvas Framework



1. Follow the steps below
2. Send your code to Dr Abstract on our Forum or Discord
3. Receive your digital certificate!
4. Share your experience
5. Get MORE certificates!


Use a template and add physics!

1. Copy and paste the code below into a text document on your computer - save as game.html. We like using VS Code which is a free text editor with good code syntax coloring.
Note that we are importing zim_physics which will add the BOX2D Physics engine and ZIM physics and game helper modules. Press the MORE link below for more info on templates.
    <!DOCTYPE html>
    <meta charset="utf-8" />
    <title>ZIM - Game Certificate - Code Creativity</title>
    <!-- - JavaScript Canvas Framework -->
    <script type=module>
    import zim from "";
    // See Docs under Frame for FIT, FILL, FULL, and TAG
    new Frame(FIT, 1024, 768, darker, dark, ready);
    function ready(frame, stage, stageW, stageH) {
        // given F (Frame), S (Stage), W (width), H (height)

        // Note: the Certificates were made before the F, S, W, H globals 
        // so we have collected the earlier frame, stage, stageW, stageH variables above

        // put code here
    } // end ready
    <meta name="viewport" content="width=device-width, user-scalable=no" />

2. AFTER it says put your code here, make a variable (const) called cloud and use the new keyword to assign (=) a new Rectangle() with a width of 50, height of 50 and a color of lighter. The rectangle will actually represent the city in the cloud - the cloud will come later.
Click on any of the green links to see the docs about the feature and show you how to use it. Here we need to pass extra information to the new Rectangle( ) when we make it. We pass the extra information in as parameters separated by commas (,). In this case, we need to put the values 50, 50 and lighter in the brackets of Circle( ) and separate them with a comma. The color could be any HTML color such as "red", "#333", etc. (note the quotes) but we have stored a bunch of global colors (without quotes) so we often use those
On the end of this statement, use a dot (.) to chain the Rectangle's centerReg() method which centers the registration point of the rectangle and adds the rectangle to the center of the stage. The registration point is the point about which an object rotates so for physics, we want this in the center.
There is a helpful LEARN section on ZIM but we will tell you some tips in the grey dashed sections (like this one) as we go. Here is tip: a method does something (it is like a verb). To use a method, you put the object first (in this case the object is the new Rectangle( )) then a period (.) and then the method with round brackets. We call this dot syntax because a dot separates the object and the method. Chaining is a technique that can be used with ZIM methods. See the MORE link for a video about Chaining.
Test the app in a Browser by finding the page on your computer and dropping it on an open Browser. (In Atom, you should get the open-in-browser package) HIGHLIGHT MORE ANSWER
3. ABOVE the cloud, make a variable (const) called physics and use the new keyword to to assign (=) a new Physics() object. Chain on to the end of the cloud the addPhysics() method - remember to move the semicolon (;) to the end of the chain. Try the game in the Browser now (refresh with F5) and the rectangle will fall to the ground! HIGHLIGHT MORE ANSWER
4. For this game we do not need to drag the rectangle - but let's show you how that can be done. Just below the new Physics() use the drag() method of the physics object. Test the game and throw around the rectangle.
physics.drag() will turn all objects draggable which is different than ZIM drag() which drags individual objects. See the docs for more options. Only dynamic physics bodies will be draggable and not static physics bodies (more on this later). The rectangle is not very bouncy - but that can be adjusted in the addPhysics() method with the bounciness parameter. There are many other settings like friction, density and damping.

Apply force to rectangle on mouse or key press!

5. Comment out the physics.drag() code using // at the start of the lines (or CTRL / in ATOM). HIGHLIGHT MORE ANSWER
ZIM Learn References
6. Use the function keyword to add a function called playGame( ) { } around the existing code inside the ready event. Call the function using playGame( ) underneath the function.
A function has a block of code between the { } that will run when called. We are looking ahead to when we add sounds. Sounds cannot be played until the user interacts (mouse or presses down) on the app. We will make an intro Pane later to handle this and will use the close event of the pane to call the playGame function. But for now, we will call it manually.
7. ABOVE the physics line inside the playGame() function
  • make a variable (const) called gravity and assign (=) a value of 3
  • also make a variable (const) called force and assign (=) a value of 6
  • then add gravity as the first parameter of Physics()
We are going to capture a keydown event and a stagemousedown event to add an upward force to the cloud. We could use the event object (e) and e.keyCode property to test for which key and make only a spacebar or an up arrow add the force - but we really do not care which key.
8. BELOW the cloud (rectangle) code we will make two event calls but have them both call a function called jump.
  • Use the on( ) method of the frame to capture a keydown event and call the function jump
  • Use the on( ) method of the stage to capture a stagemousedown event and call the function jump
  • use the function keyword to make a function called jump( ){ }
  • inside the function { } add the impulse() method to cloud with x and y parameters of 0, -force*gravity
Test the code in a Browser - press any key or the stage to make the cloud jump. HIGHLIGHT MORE ANSWER
9. We found when testing the game on mobile that the gravity should be a little less. Use the Ternary operator to adjust the gravity constant. Instead of assigning 3 assign mobile() ? 2.5 : 3;
The Ternary (3) operator is like a conditional that can be used inline. The first part is the condition. Above, the mobile() method will be true if mobile otherwise false. Then comes a ? and the second part is what to do if the condtion is true. Assign 2.5. Then comes a : and the third part is what to do if the condition is false. Assign 3.

Create scrolling crystal cave walls!

10. Prepare to build the walls by adding these variables above physics and below the force variable:
  • The first will change value so we will use a let speed equal (=) 2
  • Make const num equal (=) 6 - number of walls at any one time
  • Make const gap equal (=) 700 - vertical starting distance
  • Make const dist equal (=) 400 - horizontal distance
  • Make const walls equal (=) [ ] - an array to hold the walls
  • Make const colors equal (=) a series() of values blue, pink, yellow, orange, purple, green, red
Remember the semi-colons (;) at the end of each. HIGHLIGHT MORE ANSWER
We are going to make walls at the top and at the bottom. We will use Rectangles that are rotated so they form a cave like path. We will start open but later shift the top and bottom pairs up and down to make a more narrow path. There are a number of ways we can do this - we are going to show you STYLE and series().
11. Set STYLE equal to (=) an object literal { } with the following properties. This first set is easy and then we add a series after.
  • width : stageH - the width of the rectangle (note: NOT stageW)
  • height : stageH - the height of the rectangle
  • rotation : 45 - the rotation of the rectangle
  • color : colors - a color from the series of colors
  • blendMode : "difference" - the blendMode of the rectangle
  • centerReg : true - centerReg the rectangle for physics
These styles will get applied to any objects made until the style is changed or cleared with STYLE={} or Style.clear(). So in this case, we will let the styles apply only to the rectangles being made in a loop next. But we have one more important style to set - the move style to position the rectangles.
All the rectangles are centered on the stage at the moment. So we are going to move their location to the right places using a series for each x and y. A series() can have a number of adjustments to handle min and max, steps, reverse, bounce, constrain, and how often to change which is called every.
  • Set a move : {x:series(), y:series()} - this is inside the STYLE above.
Adjust the x series to hold a range object: {min:-410, max:num*dist, step:dist}. And chain on to the end of the series the every() method with a parameter of 2.
The every(2) will make the x series work with pairs of rectangles (for top and bottom). The min of -410 will start the first set of rectangles -410 pixels to the left of center stage. The max is really num*dist-410 and would be where the series loops but we are not going to loop so num*dist or even some larger number like 100000 would work. The step is the horizontal distance stored in the dist variable. This will add another dist pixels to the x of each pair of rectangles.
Adjust the y series to toggle from -gap to gap repeatedly. So the two parameters are -gap, gap We will not see the results of this STYLE until we make the rectangles in the loop below. HIGHLIGHT MORE ANSWER
12. BELOW the STYLE { }, make the Rectangles in a ZIM loop() with the first parameter being num and the second being an arrow function ( )=>{ }. INSIDE the arrow function { } add top and bot variables (let) that store a new Rectangle(). These will get the STYLE properties we made above. Also chain on the addPhysics() method with false as their only parameter.
The false in addPhysics() will make the rectangles static so they do not fall with gravity or get bumped around. We also want to record the starting y positions of the rectangles (for later when repositioning) and add the pairs as an array to the rectangles array.
On two lines, make a startY property for each top and bot and assign (=) the current top and bot y property. On one line, use the push() method of an Array to push an array [ ] of top, bot values onto the rectangles array. Test your game! HIGHLIGHT MORE ANSWER
Now that we have walls, we do not need the default walls that come with Physics(). These are at the top, bottom and the sides. You can remove them one by one as physics.remove(physics.borderTop); etc. Or pass in a ZIM Boundary() object. But passing in "none" as the second parameter will remove the borders to start.
13. UP in the Physics() call, set the the second parameter to "none" to remove the default physics borders. HIGHLIGHT MORE ANSWER
We want to animate the walls to the left. We could do this with ZIM animate(). But the speed of the walls is changing which makes in a little tricky and it is easy enough to do linear animation in a Ticker. A Ticker calls a function at 60 fps - it wraps a JavaScript requestAnimationFrame() and adds a stage update - the same one that is used by drag() and animate().
14. BELOW the loop to the make the walls, use the add() method of the Ticker class directly. (There is only one Ticker - do not make a new Ticker - this is called a Static Class). Pass an arrow function ( )=>{ } as the parameter. Inside the arrow function do the following:
  • loop() with walls as the first parameter
  • the second parameter is an arrow function pair=>{ } - this gets each pair of top and bot walls
  • Inside the arrow function { } make another loop() with pair as the first parameter - this loops through top and bot
  • the second parameter of the inner loop is an arrow function wall=>{ } - this gets each wall
  • inside this function, set the x property of the body property of the wall to -= speed
Physics objects are not supposed to be changed from outside the physics world. We would normally add the bodies and use forces to move things. Above, we go directly to the body property of a wall - this is its physics body. And we set the x position of the physics body. This can run into problems at times, but works fine for our game. The -= will subtract the speed value from the x position each time it is called. If you used += then it would add the speed and the walls would go forward rather than backwards.
Now the cloud will be swept off the left side of the stage if resting on the bottom walls. Eventually the walls will go off the stage so we fix that in the next step. HIGHLIGHT MORE ANSWER
15. Inside the inner loop after setting the -= speed, add a conditional if ( ) { }. Inside the round brackets test to see if the x property of the body of the wall is less than (<) -2*dist. This will be far enough to the left of the stage that it is gone from view. If it is gone from view then set the x property of the body of the wall to (=) (num-2)*dist. This will move the wall to the right side of all the walls. The walls starts at 0 and we just took the first wall away so that is the calculation (num-2). ALSO set the color property of the wall to colors() which calls the next color in the series. HIGHLIGHT MORE ANSWER
We want to change the vertical height of the pair of walls up or down within a range. The top and bot walls move together to keep the same vertical gap. It is their relation to the walls next to them that varies the size of the passage. We want to alternate between negative range and positive range. So one set will go up and the next set will go down, etc. within a certain range. To do this we will use an alternating series() with a negative range and a positive range. Then apply the results to the startY of the wall.
16 a) ABOVE the loops add a variable (const) called shift and assign (=) a series() with two parameters. The first is the range object {min:-300, max:0} and the second is the range object {min:0, max:300}.

16 b) INSIDE the first loop and above the second loop, make a variable (let) called s and assign (=) shift() which gets the next thing in the series.

16 c) INSIDE the second loop after setting the x property of the wall body, set the y property of the wall body to wall.startY + s. This vertically shifts the pair of walls as s is the same for both.

Play the game, we eventually see some cool passage ways! But the game moves a little slowly. Let's fix that in the next part. HIGHLIGHT MORE ANSWER
ZIM Learn References
17. BELOW the Ticker (not inside the Ticker) add a ZIM interval() to speed up the game every second for 40 seconds. Set the first parameter to 1 and the second paramter to an arrow function ( )=>{ }. Set the third parameter to 40. This will be the number of times the Ticker will run. INSIDE the arrow function increase the speed by adding (+=) .1 to it each interval (note the += not +).

Play the game and note the speed change over time! HIGHLIGHT MORE ANSWER

Make Timer and Scorer and reduce score on contact!

18 a) BELOW the loop that makes the rectangles, make STYLE equal (=) an object literal { } with property borderColor : purple.

18 b) Make a variable (const) called timer and assign (=) a new Timer() with a parameter of 120. Chain on a pos() with parameters of 40,40,LEFT,TOP.

18 c) Make a variable (const) called scorer and assign (=) a new Scorer() with a parameter of 5000. Chain on a pos() with parameters of 40,40,RIGHT,TOP.

18 d) Clear the style by assigning STYLE (=) { } an empty object literal.

Check out the two objects in the game. HIGHLIGHT MORE ANSWER
We want to reduce the score when the cloud hits the walls. ZIM has hitTests but for Physics we do not use them as sometimes, a bounce is not captured by a hitTest. Physics comes with contact() which is a calculation of hits and is what we use for physics. You can add contact() to any object with physics and any time it hits something, the event object will provide what caused the hit.
19. BELOW the STYLE clear, add the contact() to the cloud and pass an arrow function (obj)=>{ }. We will collect the obj that is hitting for later usage. INSIDE the arrow function { } decrease the score property of the scorer (-=) 100. Test the game and see the score go down when you hit a wall. HIGHLIGHT MORE ANSWER
20. UP in the code where we make the cloud Rectangle, BEFORE the addPhysics(), chain on a rot() with a property of 45. This will make our crystal city look like a diamond. We will add clouds for the next part. HIGHLIGHT MORE ANSWER
We want to see some added feedback when we hit a wall. We will add sound for the last part. But we can also add an Emitter(). And then we will emitt diamond particles anytime we hit a wall.
21. ABOVE the contact code, make a variable (const) called emitter and assign (=) a new Emitter() object. Give the object a parameter that is a configuration object { } with the following properties:
  • obj : new Rectangle(15, 15, [white, light, lighter, moon, silver]).rot(45)
  • force : 2 - a little smaller than default
  • gravity : 5 - half the size of default
  • startPaused : true - we wait until we hit to spurt
INSIDE the contact function, on the emitter, chain on a loc() with a parameter of cloud. This places the emitter at the cloud location. Chain on the spurt() method of the emitter and pass it a parameter of 10. This will spurt ten particles.

Test the game - isn't it beautiful! HIGHLIGHT MORE ANSWER
We will make the end of game function and call it in two places. One, when the Timer runs out and we win. Two when our city goes off the stage and we lose. For the next part we will work on what happens when we end the game and when we start the game.
22 a) At the BOTTOM above the stage update, make a function called endGame(win) { } INSIDE the function add a conditional if ( ) { } that tests if win then zogg("win") else zogr("lose"). Note we can use colors when we zog. We will see the results in the console (F12) once we call the functions

22 b) UNDER the timer use the on() method of the timer to capture a "complete" event and call an arrow function ( )=>{ }. Inside the arrow function { } call the function endGame() and pass true as the parameter value.

22 c) INSIDE and at the BOTTOM of the Ticker function make a conditional if ( ) { } to test if cloud.x < -100 that means it is off the stage at the left. And if so call the endGame() function passing nothing as the parameter.

Test the game - you can change the Timer() value to 10 seconds to test for instance. You can see that when you lose, you lose a lot! We will adjust that for the next part! HIGHLIGHT MORE ANSWER

Provide Start and End game messages

Sound cannot be played until the player interacts with the game. So we will make a start Pane() and we will make an end Pane() that gives the win or lose message.
23. Comment out (//) the startGame(); call at the bottom of the script. We will use the close method of the Pane we are about to make to start the game. HIGHLIGHT MORE ANSWER
ZIM Learn References
24. Make a variable (const) called start and assign (=) a new Pane() with a single configuration object { } as its parameter. Chain on to the end a show() method which pops up the pane and darkens the background. The configuration object should have the following properties:
  • content : "Guide the Crystal Cloud - city of millions!"
  • width : stageW+50 - so corners go off edges of stage
  • height : 80
  • color : white
  • backgroundColor : purple
  • backdropColor : darker
UNDER this, add an on() method to the start with the first parameter "close" and the second the startGame function (just the name to call - do not add the () to the end). HIGHLIGHT MORE ANSWER
ZIM Learn References
25. ABOVE the jump function set a variable (let) called end equal to (=) false. INSIDE the jump function at the TOP, add a conditional if ( ) { }. Test if end then return. This stops us from jumping when the end of the game is reached. We will set the end variable to true next. HIGHLIGHT MORE ANSWER
26. INSIDE at the TOP of the endGame() function add the following:
  • end (=) true - so we no longer will be able to jump
  • Ticker (.) remove(ticker) - so the walls do not move
  • timer (.) pause() - so the timer stops going down if player loses
  • cloud (.) removePhysics() - so it does not fall
  • declare a variable (let) message - to prepare for a value
  • declare a variable (let) color
Adjust the conditional to add two lines if we win and two lines if we lose:

If we win:
  • message (=) "The city is free! Your score is " + scorer.score (+ is concatenation)
  • color (=) green.darken(.2) - darken the ZIM green a little
If we lose:
  • message (=) "Oh no! Poor Crystal Cloud!"
  • color (=) grey
We will NOT see these until the next step. HIGHLIGHT MORE ANSWER
27 a) AFTER the code below but still INSIDE the endGame() function, make STYLE equal to (=) an object literal { } with a backdropColor of (:) black.toAlpha(.8).

27 b) Make a variable (const) called pane equal to (=) a new Pane() with the following parameters message, color, white, stageW+50, 80. add on a show() method to show the Pane.

27 c) On the NEXT line add the on() method to pane with the first parameter of "closing" and the second parameter an arrow function ( )=>{ }. INSIDE the arrow function use zgo() with a parameter value of "game.html".
This will make the page reload itself and the game start again. We use the "closing" event rather than the "close" event so that the pane does not refresh the page before loading the game page - that causes a flash. The "closing" event happens before the pane updates the stage and was made exactly for this situation.
Test the game! We are nearly there. We just need to add sound and make the cloud city a little nicer! HIGHLIGHT MORE ANSWER

Add clouds and add sounds!

Let's make our intro screen a little more exciting and add clouds to our rectangle. The clouds will just follow and not be part of physics. We will want this to show before the game plays so we will move our cloud up outside the startGame.
28 a) MOVE the cloud code to under the intro pane. We cannot add physics there so remove the addPhysics() - part b will address this. When we start the game, we will see the cloud - so lets make it a bit bigger and moved up. Chain on to the cloud sca() and mov() methods with 2 for the sca parameter and 0,-230 parameters for the mov. Also chain on a noMouse() method so it will not stop the pane from starting the game if the cloud is pressed.

28 b) DOWN where the cloud was UNDER the physics, put the cloud (do NOT re-declare it with const) and chain on a sca() with value of 1 to make the scale back to the original size. Also center() and mov() an x and y of 0,-100. Then FINALLY, chain on the addPhysics().

Test the game and make sure the cloud shows at the start then works when the game starts. HIGHLIGHT MORE ANSWER
We will now add the decorative puffs to the cloud city. These are a bunch of grey shade circles in a container placed by eye around the bottom the diamond.
const puffs = new Container() .sca(2) .loc(cloud) .noMouse(); STYLE = { alpha:.4, addTo:puffs } new Circle(22,lighter).mov(-42,35); new Circle(18,lighter).mov(42,35); new Circle(30,moon).mov(-20,30); new Circle(30,moon).mov(20,30); new Circle(40,lighter).mov(0,30); STYLE = {}; 29 a) Make a variable (const) called puffs and assign (=) new Container then sca(2), loc(cloud) and chain on a noMouse() so the pane is activated if the clouds are pressed on.

29 b) Make STYLE (=) a { } with alpha : .4 and addTo : puffs as properties. This means that everything made until the STYLE is cleared will be added to the puffs container with alpha .4.

29 c) Make FIVE Circle() objects - do not assign to variables. All will have radii and color and be moved by an x and y with mov() as follows:
  • Circle 1 is 22, lighter then moved -42,35
  • Circle 2 is 18, lighter then moved 42,35
  • Circle 3 is 30, moon then moved -20,30
  • Circle 4 is 30, moon then moved 20,30
  • Circle 5 is 40, lighter then moved 0,30
29 d) set STYLE equal to (=) { } an empty object to clear it (or use Style.clear()). HIGHLIGHT MORE ANSWER
ZIM Learn References
30. BELOW the puffs add the ZIM logo using frame then add the madeWith() method passing in parameters of tin,null,null,darker. Then sca(1.7), center(start) and mov(0,160). Note we centered on the pane, start, rather than the stage. This means the logo will go away when the pane is closed. HIGHLIGHT MORE ANSWER
31. UNDER physics BELOW the cloud adjust the sca() of puffs to 1 HIGHLIGHT MORE ANSWER
We want the puffs to follow the cloud so we will loc() them at the cloud in the Ticker that constantly runs.
32. INSIDE the Ticker function after the loop, loc() the puffs at the cloud.

Test the game and see if the puffs now follow the cloud. HIGHLIGHT MORE ANSWER
We want the player to clear the cave walls before the game ends. To do this will we will only make walls if the timer is greater than 5 seconds.
33. INSIDE the double loop INSIDE the Ticker, where there is a conditional to test the wall body x, add a further condtional with the && to also test if timer time property is greater than (>) 5. This will only make walls if NOT in last five seconds of the game. HIGHLIGHT MORE ANSWER
A final thing before we start adding the sounds. We will give the player a nice Emitter() reward when winning.
34. At the BOTTOM of the endGame() function below the pane code, add a conditional if ( ) { } That tests if we win. If so then make a new Emitter() with the following properties in a configuration object { }:
  • obj : new Rectangle(100, 100, [red, green, blue,pink, yellow])
  • force : 10 - bigger than usual
  • random : {scale:{min:.5, max:1}} - randomize scale
  • fade : false - do not fade the particles
  • shrink : false - do not shrink the particles
  • animation : {rotation:[-700,700]} - spin them
  • startPaused : true - do not run until spurt is called
Chain on to the emitter center() with parameters of pane and 1 this will put the emitter on the pane and above the backdrop but below the message. And then chain on spurt(50). Woot!

Test the end of the game - again, you may want to set the Timer(10) so you can get to the end quickly. HIGHLIGHT MORE ANSWER
FINALLY, let's add sound. For sound and images to load locally on our computer we have to load the file in a special way to avoid SECURITY errors. Please read the instructions at The solution is to use an in app browser such as Browser Plus for Atom. Or adjust how you load chrome by adding a special flag to the shortcut - then loading chrome from that shortcut - mac is slightly different. Read the instructions in the link above.
35. At the TOP of the SCRIPT before the frame call, add the following code:
    const assets = [
        "backing.mp3", "bells_top.mp3", "bells_bot.mp3", 
        "powerup.mp3", "powerdown.mp3", 
        "jump1.mp3", "jump2.mp3", "jump3.mp3"
    const path = "https://assets/";
    const waiter = new Waiter({backgroundColor:purple, corner:0});
ADJUST the Frame call to add THREE more parameters - assets, path, waiter.
The above code loads sounds from Amazon cloud. Sounds are sometimes larger than images and can take a while to load so we provide a waiter. Check the console (F12) for errors - and if there is a red error about CORS then follow the instructions here
Remember to add the THREE parameters to the the Frame call or you will not get any sounds! We will test a sound next. HIGHLIGHT MORE ANSWER
36. At the TOP of the startGame() function add TWO sounds - a backing sound and the powerup sound.
We now have new Pic(), new Aud(), new Vid() and new SVG() rather than asset(). But asset() still works and the certificate code was made before the latest addition.
Make a variable (const) called backingSound and assign (=) asset() with "backing.mp3" as the parameter. Then dot (.) on the play() method with parameters of .5, true for half volume and loop. We store this in a variable so we can lower its volume when we win.

Next, call asset() with "powerup.mp3" as the parameter and call the play() method. That will play the sound once at regular volume.

Test the game and hear if the sounds play at the start. See how easy that is! HIGHLIGHT MORE ANSWER
ZIM Learn References
Every time we jump we want to play a sound. We have three slightly different jump sounds to make this less monotonous. We will pass the asset an array of sounds and the asset() method will pick from them as they are a ZIM VEE value.
37. INSIDE at the BOTTOM of the jump() function use asset() with ["jump1.mp3", "jump2.mp3", "jump3.mp3"] as the parameter and call the play() method. HIGHLIGHT MORE ANSWER
38. INSIDE at the BOTTOM of the contact() function play the "bells_top.mp3" if the obj.startY < stageH/2. Else play the "bells_bot.mp3". You have used conditionals enough and seen how to play() asset() objects so you should be able to do it.

Test the app and make sure the sounds are playing! HIGHLIGHT MORE ANSWER
We want to reduce the backgroundSound when the game ends. And play winning or losing sounds depending on if the player wins or loses.
39. INSIDE the endGame() function BEFORE the win conditional, set the volume property of the backingSound to .2. Also play the "powerup.mp3" in the win part of the conditional. Play the "powerdown.mp3" in the lose part of the conditional. HIGHLIGHT MORE ANSWER
CONCLUSION: ZIM is amazing for games! See the GAME GO - section from the main page banners. This game would be considered a medium size game. There are many quick games found in ZIM EXAMPLES and fast things you can make in FIVE MINUTES. Longer examples are Alone Droid and Alone Droid Two. A basic falling and avoiding game can be found on ZIM Bits.

All the best.

Dr. Abstract and Pragma
Dr.Abstract and Pragma at ZIM
FINAL STEP: Send your code to Dr Abstract on our Forum or Discord to receive a digital certificate! Dr Abstract won a Hamilton Arts Award in the Media Arts category for his interactive works - many were created in ZIM.