This article has links to websites or programs outside of Scratch and Wikipedia. Remember to stay safe while using the internet, as we cannot guarantee the safety of other websites. |
A common problem for Scratchers is when a script that may seem to be perfect does not do what it is expected to. These are usually small mistakes the user made and can be fixed. This is commonly known as debugging, the term for which was popularized in the 20th century when Grace Hopper, a computer scientist, removed an actual moth from a machine and said she was "debugging the machine".[1][2]
Inspecting Scripts
Inspecting a script that does not work is often not easy. Finding a problem with a script is difficult because it makes sense to the writer. Often, thinking from the computer's perspective will highlight the issue. For example, to find the sum of the fractions 1/2+1/3+1/4+…+1/100 one may use the following script:
set [n v] to [100] set [total v] to [0] repeat until <(n) = (1)> change [n v] by (-1) change [total v] by ((1) / (n)) end
This will not work, because on inspecting the script, n is between 100 and 2 at the beginning of each loop iteration, but it is changed to something between 99 and 1 before the fraction 1/n is calculated, so the script is adding 1/1 + 1/2 + 1/3 + ⋯ + 1/99.
Other issues can occur when actions do take place but are not seen by the user. For example, if a sprite is hidden or has a ghost effect of 100. A practical example is a continuation of the script above:
say [I have found the sum!] say (total) say [Wasn't that great?]
The script will just say "Wasn't that great", because the previous two actions happen in a split second, or sometimes, within a single render cycle of the evaluator. To fix these problems, either use the wait () seconds
block in the "Control" section between the blocks or use the say () for () seconds
block instead.
Sometimes, the error is purely logical and appears sensible only at the moment. These are often the hardest to track down because there is no true "error". For example, accidentally inserting the wrong variable, or changing the order in subtraction will mess up a script without giving a trace. The best way to find these mistakes is to ask a friend. Walk through the entire program, block by block, and make sure the chosen friend understands each step. If there is a logical mistake, they will probably catch it.
Preventing Errors
- The best way to prevent silly mistakes is to have neat, organized code. Order scripts by their functions or order of evaluation will help find mistakes and prevent them.
- Leave comments wherever a lot of time was spent on a script; this way, if it breaks again, they can be referred back to for a glimpse of how the script worked in the past. Anyone with enough experience should be able to understand the script at first glance.
- Do not blindly copy scripts from other projects unless that was the intention of the project. Other than moral copy theft issues, chances are the script will not work in the context of the program. To learn how to make easy, integrative tools, see Advanced Clone Usage.
Checking Values
If a value needs to be known to determine whether or not a script is functioning, there are a few ways to find it.
Using the Say () block. The say block can be used to constantly check a value.
forever say (((var1) + (var2)) * (var3)) //Outputs the value of (var1 + var2) * var3 end
Updating a variable. A variable can also be updated constantly.
forever set [check v] to (((var1) + (var2)) * (var3)) //Outputs the value of (var1 + var2) * var3 end
These methods can also be used to check a boolean:
forever say <touching (Sprite1 v)?> //Will say whether or not the sprite is touching Sprite1 end
Testing Code
Testing a large, complex project is daunting because every change can potentially wreck something.
Often, the best way is to simply run the project again and again. However, large games or animations would be painful to debug like this. Instead, introduce certain testing codes, which can be removed in the final release. For example, to test level 27 of a game, add a script where when "space" is pressed, the game goes to the next level.
To make sure that the code remains functional, automate certain tests. For example, scripts that should return a numerical answer can be automatically tested against some built-in cases:
define get cube of (x) set [result v] to (((x) * (x)) * (x)) define test CUBE with (n) expect (k) get cube of (n):: custom if <not <(result) = (k)>> then say [Oops!] for (2) seconds say (join [error with input ] (n)) for (2) seconds say (join (join [result was ] (result)) (join [, expected ] (k))) for (2) seconds stop [all v] end when gf clicked test CUBE with (1) expect (1) test CUBE with (2) expect (8) test CUBE with (3) expect (27)
Adding Waits and showing variables
A way to debug scripts is to add waits and show variables so things can be seen things step by step.
Debug List
Creating a Debug list and printing step to step can also help. For example, with the fraction addition example listed above:
set [n v] to [100] set [total v] to [0] repeat until <(n) = (1)> change [n v] by (-1) change [total v] by ((1) / (n)) end
To log the variable n and total to the list debug, add to list blocks can be added:
delete all of [debug v] set [n v] to [100] set [total v] to [0] repeat until <(n) = (1)> change [n v] by (-1) change [total v] by ((1) / (n)) add (n) to [debug v] add (total) to [debug v] end
Common causes of errors
Invalid operations
In Scratch 1.x, mathematical mistakes could cause Script Errors. In Scratch 3.0, most impossible math like 0÷0, asin(2), or log(-1) returns NaN, which are treated like a zero. However, 1÷0 and -1÷0 are treated as Infinity and -Infinity respectively.
Often, Scratchers write programs with a script similar to the following:
set x to (10) forever change x by (-1) say ((1) / (x position))
This script will cause an error after the 10th repeat of the forever block, when x = 0, as nothing can be divided by 0 in Scratch 1.4. When this occurs, the script will be highlighted in red and the ()/() block will turn red. In Scratch 3.0, 1÷0 returns Infinity instead. To counter this error, the best way would be to start with x = 10.0001 rather than 10. This way, x will never reach 0, it will hit 0.0001 and -0.9999.
Similar problems occur with square roots, logarithms, and trigonometric functions:
set x to (10) forever change x by (-1) say ([sqrt v] of (x position))// this will cause an error when x = -1. say ([sqrt v] of ([abs v] of (x position)))// this will not cause an error as abs x is always positive.
Order of Commands
The most common problem for Scratchers is for the commands to be in the wrong order. For example, to make a sprite lunge at another sprite, a Scratcher may use the following script:
point in direction (45) wait (0.5) secs glide (2) secs to x: (54) y: (0) point in direction (90)
However, if they attempt to run the script, the script will not look like it's lunging. A good solution for this problem is to just move around the commands until the script works. For example, to fix the previous script, the Scratcher needs to move the "point in direction 90" block after the "wait 0.5 secs" block, thus making the sprite look like it's lunging:
point in direction (45) wait (0.5) secs point in direction (90) glide (2) secs to x: (54) y: (0)
Timing and Race Conditions
Another common problem for Scratchers is the timing of commands. This is often seen in projects that involve 2 scripts cooperating without broadcasts or variables, such as in the sample project Joke. The same scripts can behave in different ways depending on the order the scripts were made. If more that one scripts run at the same time, one block runs from from every script. The next block runs from every script run. the scripts run in order of when the hat blocks were created. For example,
when gf clicked set [number v] to (1) when gf clicked set [number v] to (2)
The number the variable gets set to will change depending on what order the hat blocks were made. Whatever hat block was made second will be what the variable is set to. In more complex projects, these errors can be hard to find.
when gf clicked set [number v] to (0) forever change [number v] by (1) end when gf clicked wait until <(number) = (100)> stop [all v]
Depending on which green flag block was made first, the project might end with the number being equal to 100 or 101.
This problem can be solved in many different ways.
Only 1 script for logic
It is often best to only have one script for the game's logic, however, more than one script may need to be controlled the script. This can be accomplished using variables and/or broadcasts. For example, a platformer game might check if a player should be dead or win and go to the next level. The game might use the following script to respawn the player:
when I receive [dead v] change [death v] by (1) go to x: (0) y: (0) clear objects:: custom spawn level (level) objects:: custom
To go to the next level, the game might use the script:
when I receive [next level v] next backdrop change [level v] by (1) go to x: (0) y: (0) clear objects:: custom spawn level (level) objects:: custom
If the two scripts are run at the same time, it can cause issues, causing objects from both levels to appear. To avoid this, it is best to have one main loop. If you need to run code under another sprite, use the broadcast and wait block. Broadcasts won't affect the main logic script, such as, for example, updating the status bar.
Broadcasts and Variables
Using broadcasts and variables will allow the scripts to coordinate with each other. If variables are used in a project like Joke, the "wait until" block will most likely be used. Otherwise the "if" block could be used instead or possibly the "repeat until" block. Either way, the scripts will be more accurately coordinated. This is the recommended way to do it.
Careful Timing
Alternatively, the amount of time between blocks can be adjusted, by using the "wait () secs" block. This would be easy for a project like Joke but not for a complicated game.
Repeated Process
This method is mainly for very complicated games. To make accurate detection, tests should be repeated a few extra times before activating a reaction. It may be unnoticeable but it takes a small amount of time to run a script. If a test block is repeated, it will give the detection a little bit longer to change itself. An example of this can be found in this project.
Timer
Blocks can also revolve around the timer. Scratch's timer can be reset and manipulated, so much can be done with it. Sometimes, 2 scripts that use the timer can conflict if one or both of them have a reset timer block.
When gf clicked forever if <(timer)>(3)> then reset timer say [I run every 3 seconds.] for (1) seconds end end When gf clicked forever if <(timer)>(7)> then reset timer say [Why don't I run every 7 seconds?] for (1) seconds end end
In this case, the first script resets the timer and runs some code if the timer hits 3, so it runs every 3 seconds. The second script resets the timer and runs some code if the timer hits 7. However, the timer never hits 7 because it keeps being reset by the first script, so the second script never runs. We can fix this by making our own timers.
when gf clicked forever if <(((days since 2000)-(t1start))/(86400))>(3)> then set [t1start v] to (days since 2000) say [I run every 3 seconds.] for (1) seconds end end when gf clicked forever if <(((days since 2000)-(t2start))/(86400))>(7)> then set [t2start v] to (days since 2000) say [I run every 7 seconds.] for (1) seconds end end
How this example works is the days since 2000 that the timer started (when the timer is 0) are stored in a variable, like t1start. To get what the timer is at, subtract the start time from the current time. To convert to seconds, multiply the number of days by 86400 (the number of seconds in a day). The timer is reset by setting its start variable to the current days since 2000. The regular timer can also be used in place of days since 2000. In that case, the step to multiply by 86400 must be omitted. These timers might not start at 0 without being reset first. Make sure to reset the timers at the start of the project.
Incompatibility
The third and least common reason for scripts not to work would be that the commands just do not work with each other. When this happens, the script will be highlighted in red. The sprite is "stumped". When this is the case there isn't anything to do about it except to rebuild the script completely, with a different approach.
when gf clicked say [Hello World!] for (2) secs if <visible?> then say [I see you!] for (1) secs end
Obsolete Blocks
- Main article: Obsolete Blocks
When using older versions of Scratch, blocks may be labeled "obsolete!". These are usually blocks in the newer versions of Scratch and are not compatible with older versions. Using these blocks in the older versions will often cause the script to fail.
In 2.0:
when gf clicked create clone of (myself v) reset timer
In 1.4:
when gf clicked obsolete! reset timer
Blocks like "say nothing" are hidden in Scratch 1.4, but will not cause a script error.