Piecewise Expressions

Piecewise expressions are a shortcut to handling conditions in math statements. They can be used for turning an If block or several such blocks into a single line. Although the TI-83 manual recommends them for graphing piecewise functions, they are very important to programmers as well — it would not be an overstatement to say that the use of piecewise expressions revolutionized TI-Basic when it was introduced.

The Concept

The general form of a piecewise expression is (expr #1)(condition #1)+(expr #2)(condition #2)+…→result. Usually, condition #1, condition #2, and any other conditions are mutually exclusive — only one of them can be true at a time. In this case, the piecewise expression can be interpreted as follows:

  • if condition #1 is true, return the value of expr #1
  • if condition #2 is true, return the value of expr #2
  • if condition #X is true, return the value of expr #X

A classic example of a piecewise function is absolute value, which strips a number of its sign. Forget for a moment that the abs( command exists, and picture code that would do its job. A possible solution relies on the If command:

:If A≥0

Using piecewise expressions, we can write this as:


Most of the parentheses are unnecessary, only here for clarity. If you're comfortable with piecewise expressions, you can strip the extra parentheses to get this version:


Why does this work?

Believe it or not, the calculator does not make special cases for piecewise expressions. Instead, this technique relies on the convention known as Boolean logic. According to Boolean logic the number 1 represents "true" in logical expressions on the TI-83, while 0 represents "false".

In the case of a properly written piecewise expression, only one of the conditions will be true, and the rest will be false. That condition's expression will be multiplied by 1, and the others by 0. When the results are added, this gets rid of the unwanted expressions, leaving only the one with a true condition.


Now that we know how this technique works, we can optimize such expressions while keeping the result the same. For example, here is part of the code for moving a cursor on the screen, as a piecewise expression:

:(X-1)(Ans=24)+(X+1)(Ans=26)+(X)(Ans≠24 and Ans≠26)→X
:(Y-1)(K=34)+(Y+1)(K=25)+(Y)(K≠34 and K≠25)→Y

Notice that all three pieces of the first expression contain X, and all three pieces of the second expression contain Y. In such cases, we can take out the common part of the pieces, without changing the result:

:X-(1)(Ans=24)+(1)(Ans=26)+(0)(Ans≠24 and Ans≠26)→X
:Y-(1)(K=34)+(1)(K=25)+(0)(K≠34 and K≠25)→Y

Finally, we can cancel the unneeded parts. Many of the parentheses are unnecessary, but it's also pointless to multiply something by 1, so we can get rid of the (1) parts entirely. Finally, the parts multiplied by 0 are redundant.


The result is the movement code you may have seen elsewhere in the guide, in its fully optimized form!

Advantages and Disadvantages

Piecewise expressions are usually a better choice than clunky If statements to accomplish the same thing. They give the following benefits:

  • The result is usually faster to compute, and takes less memory in the program.
  • The expression takes less space on the screen to scroll through.
  • Piecewise expressions can be used where If statements can't (for example, equations).

However, there are a few drawbacks you need to be aware of:

  • Unlike an If statement, a piecewise expression will compute all its parts before returning the result.
  • Complicated logic can make piecewise expressions very messy and hard to understand.

So one situation in which piecewise expressions should be avoided is one in which part of the expression takes a long time to compute. For example:

:If N=1

In this case, a very complicated calculation is done in the case N=1 (if L1 is large enough, it may take several seconds). But if N is not 1, the calculation is very simple and will finish quickly. If you made this code a piecewise expression, the very complicated calculation would always be calculated, even if it's not going to be necessary.

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/piecewise-expressions

Friendly Graphing Window

A friendly graphing window is some configuration of window variables that's most useful for your program — most commonly, because it makes the coordinate value of a pixel come out to a round value, such as an integer, or .1 of an integer.

Setting up a square window

The most basic type of friendly window is one in which a vertical distance in pixels is equal, coordinate-wise, to the same horizontal distance in pixels. This means that the ratio between (Xmax-Xmin) and (Ymax-Ymin) is 47:31. Such a setup has the useful effect that drawing a circle (for example, with the Circle( command) actually results in a circle, and not an ellipse.

This can be accomplished simply with the ZSquare command, which will alter the values of the screen so that:

  • the coordinate of the center remains the same
  • the ratio (Xmax-Xmin) : (Ymax-Ymin) is 47:31 (approximately 1.516 : 1)
  • the resulting window is larger rather than smaller than the original.

A common technique is to run the ZStandard command first, so that the center of the screen is at (0,0).

Setting up an integer square window

However, it's possible to take friendliness even further, by adding the condition that coordinate values of a pixel come out to round values without long decimals. This can be done in several ways:

Using the ZDecimal command

This is by far the simplest — the ZDecimal command will set Xmin to -4.7, Xmax to 4.7, Ymin to -3.1, and Ymax to 3.1. As you can see, this satisfies the condition for a square window. Also, the coordinates of pixels have at most one decimal place: pixels that are next to each other differ by 0.1 in the appropriate coordinate.

Zdecimal is useful if you only need to do a few point/line commands, like if you were drawing a border around the screen using Horizontal and Vertical.

However, it has the drawback of still having a decimal point. Your drawing commands may look like Line(-3.1, -1.7, 3.1, -1.7) — if you didn't have to have that decimal point there, you'd save a considerable amount of space. This is possible, using the following three methods:

An integer window with (0,0) in the center

These methods are basically divided over where the point (0,0) should be. Putting it in the center ensures that drawing things in the middle of the screen takes up little space; also, you can achieve symmetry easily by appropriately negating numbers. On the other hand, the negative sign (you'll be using one 3/4 of the time, and 1/4 of the time you'll need two) can be annoying.

The following code sets up an integer square window with (0,0) in the center:

An integer window with (0,0) in the bottom left corner

This approach is optimal in terms of saving space on coordinates: they are all positive numbers with at most two digits. For this reason, it is the most widely used. The following code sets up such a window:

An integer window with (0,0) in the top left corner

This approach is useful for when point and pixel commands need to be used together. Although putting (0,0) in the top left makes every Y-coordinate negative, the window has the useful property that it's very easy to go from point commands to pixel commands: the pixel (R,C) corresponds to the point (C,-R), and equivalently, the point (X,Y) corresponds to the pixel (-Y,X). It's somewhat trickier to set up, though:


Why friendly windows are useful

Throughout this article, we've only made glancing comments as to why you'd want to use friendly windows. Here is a more exhaustive explanation:

Graphs come out nicer

Even with a square window, graphs become more accurate, because they reflect the actual proportions of the equation being graphed. For example, try drawing a circle in the standard graphing window — you'll get some stretched out oval. Now ZSquare and try again. The result is much better, right?

With an integer square window, certain other imperfections of the calculator's graphing go away. For example, try graphing Y=1/(X+1) in the standard graphing window. Pretty accurate, but instead of the asymptote there's a slightly diagonal line. That's because the asymptote doesn't end up corresponding to a pixel of the graph: one pixel, the curve is a very negative number, the next it's a very positive number, so the calculator tries to connect them.

Now ZDecimal and graph 1/(X+1) again. The nearly vertical line at the asymptote disappears: because the value X=-1 matches a pixel on the graph, so the calculator realizes that something is undefined there.

Another similar problem occurs with graphing Y={-1,1}√(9-X²). In most graphing windows, this graph, which should come out to a circle, has some gaps near the sides. In an integer square window, such as with ZDecimal, the gaps disappear, and you're left with a perfect circle.

Coordinates are round numbers

This is a more programming-related application. If you happen to need to draw something on the screen, in a program, you're likely to need a lot of Line( or Pt-On( commands at fairly specific locations. On a normal window, such a "specific location" might end up being (2.553,0.645) or something equally ugly, with unpredictable results if you try to round it to the nearest nice value (since you don't know exactly the point at which pixels change).

With a friendly window (for this purpose, an integer window with (0,0) in the bottom left is the friendliest), every single pixel has a round coordinate value. If each value is no more than a 2-digit number, you can even compress all the coordinates of a line into a single number — this technique was used, for example, in Bryan Thomas' Contra game.

Point and pixel commands are compatible

In the case of an arbitrary window, it's fairly difficult to convert from a pixel coordinate to a point coordinate and back: the exact formula is X=Xmin+CΔX, Y=Ymax-RΔY if (X,Y) is the point and (R,C) is the pixel (going from pixel to point is even more painful). However, there are many cases in which you do need to go back and forth — for example, if you need to draw both text and lines at a coordinate. With friendly windows, this formula becomes much simpler — such as (X,Y)=(C,-R) for the last integer window we discussed.

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/friendly-window


Animation is the rapid display of images on the screen to create an appearance of movement: it works by displaying an image and then moving it to a new location after a short delay has occurred. While there are many different things that you can do for animation (the possibilities are practically infinite; heck, there is an entire program directory at ticalc.org devoted to animations), almost every animation depends on For( loops.

A For( loop is a special kind of While loop, with all of the loop construction built-in, it has the the variable that the loop uses, the starting value, the ending value, and the increment. This is important because you can use all of those things to dictate how many times the animation is displayed, the speed of the animation, and even the animation itself (using the For( loop variable as the coordinates or the text that is displayed).

Animation is commonly used at the beginning of a program or on loading screens to add some visual pop or pizazz, which gives a program an edge over similar programs. At the same time, this does not mean that you can't go overboard with animation; too much animation becomes annoying and lengthy after a while. Selective animation — where it makes sense and complements the program — has the best impact in a program.

You also want to keep in mind the calculator that the animation is running on. If you created your animation on the TI-83+SE or TI-84+SE, then the animation probably won't display as you intended on a TI-83 or TI-83+ calculator (calculators that have a much slower processor; 6MHZ and 8MHZ respectively are slower compared to 15MHZ for the TI-83+SE and TI-84+SE). Of course, there are a few other things that you need to consider, so you should read the portability page for more information.

Animation Examples

This is an example of moving text: the variables of the X or Y coordinate of the text are changed by the for( command.
The spaces before "looks" and after "huh?" are needed to delete the old text.

Text(28,A+9," looks
Text(A+6,40,"<16 spaces>
Running this code gives this program:

TODO: Add more examples

  • Using pictures
  • Drawing/Erasing text (changing position, size, letter by letter)
  • Drawing/Erasing shapes (changing position, size, color)
  • Drawing/Erasing lines (changing position, size, color)

One of the most common examples of animation that you see in games is wiping the graph screen (you can certainly wipe the home screen as well). This is usually done at the end of the game, after the player has lost, or as a transition from one level of the game to the next.

Wiping the screen involves using one or more Line( or Horizontal/Vertical commands, and then displaying the line from one side of the screen to the other:

:Vertical X

As you can see, a vertical line is displayed from the left side of the screen to the right side, effectively shading the entire screen. Since it uses Xmin,Xmax, and ΔX, it will work on any screen.

Another common example is displaying text. This is commonly used on the titlescreen of a game to make the game stand out to the user. There are several different ways that you can display text, but some of the most common are: letter by letter, sliding it in from the screen side, overlapping each letter, and displaying the large text behind the small text.

Displaying text letter by letter involves placing the text in a string, and then displaying the respective substring based on where you are in the For( loop. More plainly stated, display each character by itself at the respective time.

The code for this is fairly simple:


Animation Length

The two different options for animation length are timed and infinite: timed means the animation lasts for a set amount of loop iterations, while infinite means the animation will go on indefinitely with no end (or at least until the user finally presses the ON key).

The way you go about making a timed animation is by simply using an additional For( loop enclosed around the animation. For example, if you want the animation from before to be displayed five times, you can just do:


There are actually two different ways to make an infinite animation: use a For( loop with a really large ending value (such as E5) or use an infinite While 1 or Repeat 0 loop. The infinite While or Repeat loop is the smaller of the two, but the For( loop has the advantage that it still allows the user to exit out of the animation.

Of course, the really long For( loop is not a true infinite loop, since it will eventually end at some point. For our purposes, however, it works quite well because the calculator will actually power down after a certain amount of inactivity (the TI-83+ and above have a built-in APD feature).

Adding a Delay

If you try out any of the examples that have been shown so far, one of the things you will probably notice is that they display so quickly that you can barely see them being displayed until they are almost done. This behavior is acceptable for some animations, such as where there is lot of things being animated at one time, but it can cause havoc for a lot of animations. The way you fix this problem is by adding a delay.

There are two basic ways to create a delay: use a For( loop or use the rand command. The For( loop is just an empty loop, meaning there are no commands or functions inside of it. The rand command's alternate syntax — rand( — generates a list of random numbers, which is a rather time-consuming operation. Both of these delay methods can be worked so that they create a small or large delay simply by changing the size of the For( loop and the number of random numbers generated respectively.

For an example, here is the text animation from before, where the word HELLO is displayed letter by letter on the first line on the home screen, with each of the two respective delay methods added to it:


Each delay method has its own advantages and disadvantages. The For( loop has the advantages that using it still allows the user to do something during the delay, and it does not have any additional memory overhead like rand does. The rand command has the advantage that it is smaller in size than the For( loop.

The rand command does use some additional memory for storing the temporary list of random numbers in Ans, which may be undesirable. To avoid this, you simply have to use this somewhat longer line: If dim(rand(#. Despite the presence of an If statement, you don't have to worry about the next line being skipped, since dim(rand(#)) will always be true.

The other concern when using the rand command is that if the number is large enough, the program will run out of memory from trying to generate such a large list, and subsequently return a ERR:MEMORY error. What number is too large is dependent on how much free RAM is available on the calculator, so for some people it might be 100 while for others it might only be 50. So, if you want to use a large delay, it might be better to go with a For( loop instead of a rand command.

Related to that concern is the issue of portability: a delay may be appropriate on your calculator, but it won't be on another calculator. For example, if you have a TI-83 and you use a delay for twenty iterations of a For( loop, that would be almost unnoticeable on the much speedier TI-83+SE and TI-84+SE calculators. Conversely, if you write your program on a TI-83+SE, the delay would be much longer on a TI-83 and TI-83+, to the point that the animation would slow to a crawl.

With exception to assembly libraries, there is no viable way to check what calculator a program is being run on. A good alternative is to find the appropriate delay for each calculator, and then take the average for the delay that you use. This happy medium is just a simple fix, and really all you can do is just keep the other calculators in mind when deciding how much delay to use.

Allowing User Exiting

One of the main considerations that you have to make when using animation in a program is whether the user can exit the animation at any time they want. This applies to animations of any length, but it especially applies to long animations. This is because the user has to wait until the entire animation is finished before they can move on to the rest of the program, which is extremely annoying from the user's point of view (see program usability for more information).

There are a couple different ways you can fix this problem. The first way is to add some getKey's throughout the animation to check for user key presses; and if you find any, you exit the animation.

Since the animations use For( loops, and we want to exit out of them before they have finished, you can do this by storing something at least equal to the end value to the variable used in the For( loop. For example:

:If getKey:32→C

While this approach works quite well if your animation only consists of one For( loop, it doesn't work when you have two or more For( loops that you need to exit out of. The problem is that if you exit out of the first loop early, you then need to skip the rest of the For( loops in the animation.

Unfortunately, there is no real easy way to go about doing this. One option is to use branching to jump out of the For( loops to go to a While 0 loop internal subprogram. The reason for doing this, of course, is to avoid creating a memory leak.

Because using branching can get rather messy, another option is using an additional variable to act as a flag. You just set the variable to an off state (zero is the standard value), and then change it to an on state (achieved by inverting the flag variable's value) when the user has pressed a key.

For example, here is an animation that displays the word HELLO letter by letter, and then erases each letter starting from the "O". If the user doesn't exit the animation early, it will be played 100 times before it is finally finished. Note the first example uses the branching while the second example uses the A variable as a flag.

:If getKey:Goto A
:Output(1,6-X," "
:If getKey:Goto A
:While 0:While 0
:Lbl A
:DelVar A
:If getKey:not(A→A
:Output(1,6-X," "
:If getKey:not(A→A

Those two options should generally suffice for most animations, but a third option available is to simply rewrite the animation. There is no hard and fast way to rewrite an animation, but it generally just involves thinking about the animation and seeing if there is an alternative way of implementing it.

One common way to rewrite animations where you are moving back and forth (or displaying and erasing text) is by combining the two For( loops into one, and using some additional variables to keep track of the current direction (or if it should be displayed or erased). When an edge is reached, you then just invert the variables values from negative to positive and vice versa.

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/animation

Custom Menus

Menus are often used in programs to choose options, allowing a program to have multiple functions, or a game to have extra features. Though the Menu( command creates a perfectly functional menu, sometimes you want your program to use something more fancy, to have a differently-functioning menu, or just to stand out from the others with a menu that looks different.

However, a custom menu is usually 2 to 3 times the size of the same menu written with the Menu( command - the difference is between a menu that takes up 150-200 bytes and one that takes up 50-100 bytes.

Basic Menus

This covers the basics of creating a functioning custom menu. Remember to set up the graph screen before displaying the menu, to avoid something like axes covering the menu.

Numerical Input

The simplest way to create a custom menu is just to display the title, and a numbered set of options. Then, wait for a number key to be pressed, and act accordingly.

The following code is an efficient way of waiting until a number key is pressed, and converting it to a number N:

:Repeat 2>abs(5-abs(5-abs(Ans-83)))

But you might not always need to convert the number at all. If all you're going to do with the option is go to a different part of your code, you might just want to use the getKey value for comparisons. And if you only have a small number of options, it's easier to check for them all explicitly rather than using the abs( command as above: for example, Repeat max(Ans={92,93,94}) will check for the keys 1, 2, and 3.

Arrow Key Input

If you want to get slightly more fancy, you could provide a cursor, such as an arrow or ">" symbol, that points to the selected option (there are many ways to display this). The first thing you'd do is display the title and the options, just as in the previous case (except you wouldn't need to number them). Then create a variable that stores the option currently chosen.

For the rest of the code, you would need a loop, structured roughly as follows:

Loop until a selecting key (such as enter) is pressed
Display a cursor at the currently chosen option
Wait for a key to be pressed
Erase the cursor
If an arrow key was pressed, change the chosen option variable
End of the loop

It is important that you actually wait for a key to be pressed, instead of just storing the key to getKey. The loop will work both ways, but if you don't wait for the key, then it will go through the loop even when no key is pressed, erasing and redrawing the cursor, which causes flicker. Another way to eliminate the flicker is to only erase the cursor if an arrow key was pressed.

You can use Boolean optimizations to quickly adjust the option based on the arrow keys:

:N+(K=34 and N<(# of options))-(K=25 and N>1→N

Another possible optimization is to use the row coordinate of the option, rather than the option number, for the value you store (but then you need to modify it by the row difference between two options, rather than 1, when arrow keys are pressed)

Labels vs. Values

The Menu( command goes to a label when an option is selected, whereas both of these methods return a value. It's fairly easy to go back and forth between these methods. To convert from a value to going to labels, add If statements like:

:If N=92
:Goto 1

To convert the Menu( command from going to labels to returning a value, use code like this:

:Menu("TITLE","OPTION 1",1,"OPTION 2",2,"OPTION 3",3
:Lbl 3:Ans+1
:Lbl 2:Ans+1
:Lbl 1

1 is stored to Ans. If 1 is chosen, the Menu( command goes to Lbl 1, and this code finishes with Ans=1. If 2 is chosen, the Menu( command goes to Lbl 2, where Ans is increased to 2, then the code goes to Lbl 1 and finishes. If 3 is chosen, the Menu( command goes to Lbl 3, which has 2 Ans+1 commands after it, so Ans is increased twice: to 3.

However, it's usually easy to avoid using labels with menus.

Advanced Menus

This section covers advanced techniques your custom menus might use.

Multi-page Menu

(this section is based on this menu routine by Steve Hartmann)

A multi-page menu could be used for as many options as you wanted, and is another reason to use a custom menu routine. To create a multi-page menu, you would need a loop which displays the current page (most likely with some If statements), then does the necessary operations for a normal menu until either an option is selected or the left/right arrow keys are pressed. If an option is selected, obviously you exit the loop; otherwise, you change the page number but stay in the loop.

The most complicated situation you could be in is a multi-page menu operated completely by arrow keys. Here, you'd use a total of three nested loops: one for the page, another for the menu itself, and a third for waiting for a key.

Selecting Options

In an arrow-key operated menu, you have several options for the cursor. The simplest is to draw some sort of symbol next to the option currently selected. This could be embellished by animating the symbol - a little tricky, because it must be done inside a getKey loop. Here is an outline of the code to do so:

:Repeat K
:For(I,1,(some limit))
:(draw Ith step of animation)

Inside the getKey loop, a For( loop goes through all the frames of an operation. This by itself would take too much time - even if you pressed a key, you would have to wait for the animation to cycle entirely. To prevent this, we add the line I+(limit)*Ans→I. Ans holds the value of getKey, which is 0 if no key was pressed, and not zero otherwise. So if no key was pressed, we're adding 0 to I, which does nothing. If a key was pressed, however, we add a large value to I which puts it beyond the range of the For( loop, to exit the For( loop immediately. If the For( loop has a small limit, like For(I,1,10), you can simply add getKey to I, since getKey is at least 11 if it's not 0.

Program Structure

Most people reading this page probably are familiar with the reasons to avoid Goto and labels, and do stay away from them usually. However, menus complicate the situation enough that a lot of calculator programmers give up and use labels anyway, creating a program that's impossible to understand or to maintain because of the complex web of Goto commands. This doesn't need to happen - you can structure your code so that labels are unnecessary. The important part is to create this structure first, and then build the program around it, rather than writing a program and trying to tack a menu onto it later.

Suppose your menu chooses among several options which should run and go back to the menu, and a final Quit option. The structure for your program could look like this:

:Repeat choice=Quit
:(menu that sets 'choice')
:If choice=Option 1:Then
:(run option 1)
:If choice=Option 35:Then
:(run option 35)
:End (the outermost loop)

The weakness in this code is that the variable your choice is stored in can't be modified by any of the options, or else you risk setting it to the value of an option that's yet to be checked for (which in that case will also run before you get back to the menu). To get around this, any options that modify this variable should DelVar it at the end of their If-Then-End block, then it won't interfere with anything else (theoretically, you could use the variable Ans, and then instead of DelVar add the line :0)


Some sample programs with a simple number menu, cursor-based menu, and animated cursor-based menu:

You can also download all three in one file.

You can find a sample multi-page custom menu program here.

Scroll Bar

Moreover, you can add a vertical scroll bar. The scroll bar is a nice feature which not only looks good but also gives the user an overview of the menu.

First, you need some variables. You can use a list, in this case L(SCBAR).

1 PStart The point where the scroll bar starts
2 PEnd The point where the scroll bar ends
3 ETotal All of your options or elements in total
4 EAbove All of the invisible options above the visible elements
5 EBelow All of the invisible options below the visible elements
6 LStart Point where the scroll bar would start if 100% of the elements were visible
7 LEnd Point where the scroll bar would end if 100% of the elements were visible

First, we need to calculate PStart and PEnd:

LSCBAR(6)+LSCBAR(4)*((LSCBAR(7)-LSCBAR(6))/LSCBAR(3))->LSCBAR(1) // P,,Start,, = L,,Start,, + E,,Above,, * (L,,End,, - L,,Start,,)/E,,Total,,
LSCBAR(7)-LSCBAR(5)*((LSCBAR(7)-LSCBAR(6))/LSCBAR(3))->LSCBAR(2) // P,,End,, = L,,End,, + E,,Below,, * (L,,End,, - L,,Start,,)/E,,Total,,

Simple percent math ;)

For the scroll bar you just draw 3 lines:

Line(93,-LSCBAR(1),93,-LSCBAR(2) // 93 is the X-Coordinate where the scroll bar will be drawn
Line(92,-LSCBAR(1)-1,92,-LSCBAR(2)+1 // Makes the scroll bar look better

Finally, you could theoratically also draw a horizontal scroll bar, you just need to replace the variables with other values and the line commands.

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/custommenus


The most efficient and versatile way to save data when a program exits is by using a custom list. The list can be named after your program, hold up to 999 values, and be archived for long-term storage. Below is an example of a simple saving routine that backs up the variables A,B, and C into ∟SAVE and archives the list. It then unarchives and restores the data from that list.

:Archive ∟SAVE
:SetUpEditor SAVE
:If not(dim(∟SAVE

The Explanation

First, use the syntax {value, value, value, …}→SAVE to back up as many values (usually variables) as you want. If ∟SAVE does not exist, it will be created with those values. If it does, the previous data will be overwritten and replaced.


To prevent losing saved data, the list is stored in Archive memory. While in Archive memory, it will not be erased due to a RAM clear and cannot be overwritten by other programs. You might consider leaving this step out, however, to preserve compatibility with the TI-83 (which doesn't have Archive memory).

:Archive ∟SAVE

To load the saved data, it first must be moved out of archive memory, or an ERR:ARCHIVED will result. The most obvious method would be to use Unarchive ∟SAVE. However, using this command will cause problems if the list does not exist. The best command to use is SetUpEditor, which was intended for use with the built-in list editor.

As a side effect of setting up a list in the editor, SetUpEditor will create the list if it does not exist, unarchive it in archive memory, or leave it alone in RAM. In other words, SetUpEditor will always result in an unarchived ∟SAVE in RAM, without any errors (also see the relevant section on program cleanup). SetUpEditor can also be used on multiple lists separated by a comma.

:SetUpEditor SAVE

But what happens if this is the first time we're running the program? The answer is SetUpEditor will create our list for us, but it will have a length of 0. This allows us to check if we've saved data to it before: if we have, hopefully, it will have a length of more than that (in this case, 3). So this piece of code stores a default of {0,0,0} to the list if it's just been created (of course, you can put in anything you want as the default, or do something else entirely).

:If not(dim(∟SAVE

Lastly, the stored data values are recalled into the variables to be restored.


Alternative Methods

You can also modify list entries (auto-save) directly during the game. Doing so saves the user from having to save the game before exiting, but may take more memory.
If you give the player the option to save in a linear/preset game, you can use variables and the Sto> key to create a way to save without using a list. This method branches off, and is infinitely expandable. Here is an example:

:Lbl 2      // Ignore the other options for learning purposes. 
:If X=2
:Goto 45 :End     // Loads from where the player left off
:Lbl A :X → 2 :Goto 45 :End 
:Lbl 45 :Disp "Hi!" 
...     //Rest of code

Protecting Saved Games

It's quite a pain when you go through all that trouble to get users to follow the game through its entirety without the user changing his/her list data to give himself/herself ultimate powers. There are a few ways to protect this from happening.

Addition Method

To protect your saved lists, you can add up all the values of the list and store it to an element in the list right before the program leaves, and check it before allowing the user to reload that saved game.

Right before quitting:

:sum(∟SAVE,2→∟SAVE(1    // list element 1 is used to save the summation of all other list elements

Checking to make sure list elements add up:

:If sum(∟SAVE)≠2∟SAVE(1

Extra list elements

Another method available to your disposal is to add extra elements that do nothing (or even better, cause errors!). No code will be provided as it is easy enough to add useless (or destructive) list elements. See program protection to get more details on destructive list elements.

Dual List method

Another thing you can do to protect saved games is to use 2 lists. Both lists will contain the same data, and can be compared to for changes made by users. To add further protection mix the order up (one list the opposite of the other).

Simple Dual List code

To get both lists the same:

:∟SAVE1→SAVE2    // make both lists the same

Checking to make sure both lists are the same:

:If not(min(∟SAVE1=∟SAVE2

Backwards Order

To get both lists the same and into reverse order:


Checking to make sure both lists are the same:

:If not(min(∟SAVE1=seq(∟SAVE2(I),I,dim(∟SAVE2),1,-1
:Disp "ERROR: DATA CORRUPTED    // same as above, can change error type

CoSinTan Method

Simple, just add all the list elements except for the last one, then get sin(, cos(, or tan( of it. Then just store the result into the last element.


Obviously "dim(∟SAVE)" should be replaced with the dimensions of your save list, to save bytes. You can also replace sin( with any trigonometric function, such as tanh(, for added protection. Also make sure to execute a Degree or Radian command, to avoid the user being suspected for corrupting data if he's only changed a mode setting…

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/saving


High scores typically involve saving a combination of strings (for names) and numbers (for the scores themselves) after the program is finished. The simplest high score system will only have a single score, while a complicated one might have a series of names with corresponding scores.

Managing High Scores

If there is only one high score, managing it is simple. All you have to do is check if your score is greater than the high score, and if so, change it.

:If S>∟HIGH(1

Managing a table of multiple high scores and names would be more complicated. Here is an example routine for managing a high score table with 7 scores in ∟HIGH and 10-symbol-long names

:If max(S>∟HIGH:Then
:Input "YOUR NAME? ",Str1
:sub(Str1+" (9 spaces) ",1,10→Str1

First, we should check if our score is even good enough to be in the high scores table. We're assuming that our high score table is kept in order, because we presumably initialized it that way (which will be discussed later), and we're going to keep it that way when we're done with this routine. So all we need to do is see if the score is greater than an element in our list:

If max(S>∟HIGH:Then

Next, we should input the high scorer's name. You can make this as easy or as hard as you want to, in this example I used Input for simplicity. We also pad this by appending spaces to the end then truncating the string to 10 letters, because we want it to be exactly 10 letters long.

Input "YOUR NAME?",Str1
sub(Str1+" (9 spaces) ",1,10→Str1

Now, we find the place that the high score S got in the table. This line adds up the number of scores higher than the new one, and by adding one you get the new rank.


Now we insert Str1 into Str0 at the correct place. First we use sub( to find all the characters before the place we're sticking it in. Str1 is added onto this, and then we use sub( again to get all the characters that go after the new name.


We could do the same for lists, but there's an easier way. Since the list of scores is sorted, inserting an element into its correct place is the same as adding it to the end, then sorting the list. Finally, we remove the last score that was "bumped out" of the high score table.


We're done!


Initializing the High Scores

Being able to add scores and names into the table would be useless without a table or names to begin with, so at the start of your program you should put in a block of code to do this.

:SetUpEditor HIGH
:If 7≠dim(∟HIGH:Then
:" (6 spaces)

All SetUpEditor does is initialize the list. If ∟HIGH doesn't exist, it will create one with dimensions of 0. If the list does exist, nothing will be changed. As an extra check, you want to make sure that the list has 7 elements in it. If the list didn't already exist or didn't have 7 elements, the next block of code will execute.

Since the list not being there is a sign of the game being played for the first time, or that somebody tampered with the high scores, you should reset Str0 as well. We need Str0 to be 70 characters long for the names, but also add a space to the beginning and end for our computations when the person is ranked first or last.

Saving High Scores

We usually use a named list to store the high scores, due to the versatility of lists, and the fact that a named list probably won't get used by a different program (for more information, see Saving).

If we just have a score to deal with, it's simple to store it: just make it the first element of the list! However, with a complicated high score table, we'll have to store the names of the high scorers as well as their scores. So we have to find a way to convert a string to a list (and back).

This is simplest if you limit the variety of characters to be used for names (for example, uppercase letters and spaces). Then, you can store all the possible characters to a string, and use inString() to convert each character into a number - an index in that string. You would do this for all the characters, and append to the high scores. The following code is split up for clarity, but it could actually be combined into one line:


Going the other way is equally simple. Unfortunately, there is no seq() command for strings, so you have to use a For loop instead, but other than that it's similar to the above code:

:" // 1 space

High Score Security

This is an optional side to high score saving. It's impossible to to make high scores completely tamper-proof, since someone could just look in the source code of your program and find out how you secure your high scores. However, you can use the random number generator to stop most casual cheaters (this is just one of many methods).

To do this, we first compute some number that depends on the entirety of the high score list. The most obvious is the sum of the elements. However, to obfuscate the process a bit more, you use the sum as the random number seed and save the first random number generated to the end of your list.


To check if the high scores have been tampered with, you compute the sum of all the elements, and check if the first random number generated is the same as the one you saved. If it's not, somebody changed the scores, and the best way to punish the rascal is to reset them.

:If rand=∟HIGH(78:Then
(high scores are okay)
(the cheater has done his dirty work)

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/highscores

Compression Techniques

Compression involves encoding data in an alternative format that has advantages over the un-encoded format. When determining whether to use compression, the main thing you should consider is its effectiveness (i.e., how much size and/or speed gain it results in). Of course, you need to decompress the data before you can use it again.


One of the simplest ways of compressing data is by placing several related command values in a list, instead of listing out each individual command one after the other. A good example of this is when you are displaying a picture on the graph screen using several Pxl-On( commands. The Pxl-On( command has two arguments: an X and Y coordinate. After placing the coordinates in a list, we then just loop through the list with a For( loop:


While this compression is effective, it can be improved upon. If you look at a number, it has an integer and fraction part. These two separate, but related parts can each be isolated using the iPart( and fPart( commands respectively.

Relating this back to our previous example, we should combine the two coordinates together, placing the Y coordinate as the integer and the X coordinate as the fraction. This effectively shrinks the list in half. For extracting each coordinate, you simply use the iPart( command to get the Y coordinate and multiply the fPart( command by 100 (E2) to get the X coordinate:


This compression technique was possible because the Pxl-On( command has two coordinates, but it would not be very effective if we were storing the Line( command's four coordinates: X1,Y1,X2,Y2. A better alternative would be to simply put all four coordinates together in the integer of the number. Probably the best example of this technique put to use is Bryan Thomas's Contra game.

The reason that this works is because a number can have up to 14 digits, so there are plenty of digits available for us to use. To extract each coordinate, you need to use a combination of iPart( and fPart(, multiplying by the related power of 10. The following code draws a line for each element in the list:


(Note that the lines may not display correctly if you don't have the right graph screen coordinates, so you should set your calculator to a friendly graphing window to make all of the coordinates easily-compressible two-digit numbers. In this particular example, the graph screen coordinates are supposed to be X=0…94 and Y=0…62.)

Complex Numbers

Besides using the integer and fraction parts of a number, you can also use complex numbers. A complex number has two parts: the real part and the imaginary part. Just like how you were able to separate the integer and fraction part of a number, you can also separate the real and imaginary parts of a complex number:

:real(-5+8i   // Returns -5
:imag(-5+8i   // Returns 8

While this doesn't have much application because using the integer and fraction part of a number is generally sufficient, it can sometimes be used in place of a 2-by-n matrix; you just use a list of complex numbers, where column 1 is the real part and column 2 is the imaginary part.

Now we'll move on to a different programming situation. In games you sometimes need a switch that tells whether something is in the on or off state. It is fairly common to see beginner programmers utilize two or more variables to keep track of the switch and alternate one variable based on the other's value.

This is an ample place for not only compression but just good logical thinking. If you remember that each variable is considered a Boolean; that means the value indicates either true or false. A false value is zero while a true value is anything else. So, you just need to check to see if the value of the variable is zero:

:If not(F    // Check if the flag variable is zero

Because the F variable can be either true or false, you have the switch built-in for you. Naturally you'll want to change the value of the switch from active to inactive or vice versa, either when a certain condition happens or you have gone through the game loop or whatever, and you can do that by simply using the not operator:

:not(F→F    // Flip the value of the flag variable


The most appropriate and needed place for compression is when storing lots of data, such as levels and maps. The most common variable used by people for storing data is matrices. This is because matrices are simple to use and they make sense since they are two-dimensional. However, matrices have one major disadvantage: size.

Instead of using matrices and wasting lots of precious space, the better approach is to use either lists or strings when storing your levels. Then when you want to use a level, you just convert it to a matrix and delete the matrix after you are done with it.

Compression via Lists

Here is a sample level stored as a list, with each element representing a row to be displayed on the home screen:

:If L=1:Then    // If level one

Using the iPart( and fPart( commands that we discussed previously, you can break apart each number into its own separate integer and fraction elements. This allows us to then store each number into a specific position in the matrix, looping through it with a couple For loops:


Compression via Strings

The formula for storing a level as a string and converting it to a matrix is not much different than it was for the list:

:If L=1:Then    // If level 1

While this probably seems like a waste to go through all of this work just to compress a level, it is very important when you have lots of level that you want to store. In addition, the calculator only has a limited amount of memory to begin with, so you need to take advantage of every opportunity to save memory.

Single Digit Numbers

We can compress a list of up to 14 elements into a single integer whose digits are the elements in reverse. Say you had this 8-element list stored in L₁:


Then to compress, use this.

.1sum(L₁10^(cumSum(1 or L₁

Remember, 10^( is [2ND] then [LOG], not [1][0][^][(].

You should get this:


You'll notice that the digits are in reverse. That might be a bit confusing, but having it in reverse makes it smaller and faster.

So now, we decompress:


In general, replace 8 with the dimension of your list, or log(Ans if the number of digits is unknown.

There you go. It is decompressed back into Ans. So, now we can compress single-digit data. But what about double-digits?

Double Digit Numbers

Double digits are a little more complicated, but they are also more useful because they allow up to 100 different positive integers instead of just 10.

Several methods were mentioned previously for decompressing 2-digit numbers, if you paid attention.

So, on to compressing! Say you had this 4-element list stored in L₁:


To compress it:

.01sum(L₁10^(2cumSum(1 or L₁

The answer:


The decompression:


Multiple digits of any length

It is a little harder to compress multiple digit of any length into one number, so more code is needed to accomplish this.
This method prompts the user for how many values should be entered and then prompts for each individual values. For the current version of this code, you must enter the values with zeroes in front if they are not of the maximum length. To change the maximum length, alter any occurrence of 10^(4) to e.g. 10^(5) for numbers of the length 5.

If I want to input 3 numbers of the length 4. I would enter:
3 // How many values I want to enter

That would then compress those numbers into a single number.

This code will not work if the resulting compressed number is longer than 14 digits long (the maximum calculator precision).

A = compressed number
N = how many values you want to input
V = a new value to be processed
// = Denotes a comment in the code, shouldn't be included in your programs
-> = Denotes the STO command

Prompt N
Prompt V
If B=N:A+B->A

Disp A

// We are now decompressing A into L1

//Displaying the decompressed number in the form of a list
Disp seq(L1(Z),Z,1,dim(L1),1)

I am currently trying to make this more friendly to programs that would possibly be able to use this within their own code.


For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/compression

Making Maps

For many games, the gameplay consists of going through all of the maps in the game. For example, in a maze game where each map is a different maze, when you get through the first maze you go on to the second maze, and so on until you finish all of the mazes. Another common example is an RPG where the player can move their character around on the screen, and each screen is part of a larger map.

How to Store Maps

In order to keep track of all of the different things in a map, it obviously requires that you store the map to a variable. There are three different variables that you can use to store maps, and they each have their own advantages and disadvantages:

  • Matrices — Matrices are best used for two-dimensional data, and are easier to access and manage than both lists and strings. At the same time, matrices are the largest variable, which can be important if you are trying to keep your program as small as possible.
  • Lists — Lists are best used for one-dimensional data, and are faster to access than both matrices and strings. Lists also have the additional advantage that you can create your own custom lists, which decreases the likelihood that they will get messed with.
  • Strings — Strings can be adapted for basically any context, and they are smaller in size than both matrices and lists. In addition, unlike matrices and lists which have a set maximum size (99x99 and 999 respectively), strings can be as big as RAM will allow.

Generally speaking, it's best to use the most appropriate variable for the application. Going back to the maze game, for example, a matrix would probably be the preferred variable to use because a maze has a two-dimensional shape to it.

When storing a map in a variable, you have to assign numbers to represent the different things in the map: an empty space might be zero (0), a wall might be one (1), and the player might be two (2). You would then check for these numbers when determining what to do on the map or what to allow (such as movement by the player).

Here is an example of a simple 8x16 map stored in each of three different variables (note: the respective variable is all on one line, it's just split up to make it easier to read):


As you can see, the string map is the smallest of the three variables because you don't have to add all of the additional characters (the braces and the commas) like you do with the matrix and list. (You can actually get around this problem by storing your maps as a string, and then converting them to a matrix or list when you need to use them.)

How to Display Maps

Once you have your map stored in one of the variables, the next thing to do is to display it on the screen. The calculator has two different screens for displaying things — the home screen and the graph screen. The home screen is generally reserved for text, while the graph screen is generally reserved for graphics.

On the Home Screen

When displaying a map on the home screen, you use the Output( command together with a For( loop. You also need to decide what you want to display for the different things in the map. The easiest option is to just display the literal values stored in the map (i.e., 0, 1, 2, 3, etc.). A better option, although it's a little more complex, is to display a character that is representative of what the value stands for.

For example, in our maze map, we had spaces (0), walls (1), and the player (2). The spaces are what the player is going to be able to move on, so they naturally should not be displayed on the screen. A wall, on the other hand, is something that the player cannot move through, so it should be displayed on the screen. A good choice for a wall character is a 1 or an uppercase X. The player is what the user is in control of, so you want it to stand out. A good choice for a player character is an S or uppercase O.

Besides deciding what character you will use for each type of thing in a map, you also need to have a check for each one when displaying the characters. The most straightforward way to do this would be to have a separate If conditional that goes with each type of character. A better way to do this, however, is to put all of the characters in a string, and use the sub( command to access the appropriate character. For example, here is how you would display the maze from before:

:Output(Y,X,sub(" XO",1+[A](Y,X),1

As you can see, we decided to use an X for the walls and an O for the player. The other important thing to notice is that we used two nested For( loops to display the map. Since the maze is two-dimensional, two For( loops are needed: the first loop gets the Y-coordinates and the second loop gets the X-coordinates. Inside the second For( loop is where we access the respective (Y,X) coordinate of the matrix and display it using Output(.

Displaying a maze level stored in a list or string is very similar, but it requires you to use a simple formula to convert the respective coordinates on the screen: X+16(Y-1). For the string, you also need to use the sub( command to access the individual character in the string, and the expr( command to convert it to a number.

:Output(Y,X,sub(" XO",1+L₁(X+16(Y-1)),1
:Output(Y,X,sub(" XO",1+expr(sub(Str1,X+16(Y-1),1)),1

Where this formula comes from is that each row on the home screen is 16 characters wide, and the first row you just access the X coordinate by itself (i.e., when Y is 1, Y-1=0, and subsequently 16*0=0). If you create a similar map on the graph screen, you need to modify this formula to match the number of characters per row on the graph screen and to take into account that the graph screen coordinates start at zero.

Besides using the formula, the string can also be displayed one other way. The Output( command will wrap any text that goes over the 16 characters of a row to the next row (and likewise with that row), and subsequently you can use a single command to display the entire map across the whole screen.

Since every space is overwritten with the map, this does not require a ClrHome command to clear previously displayed characters. Unfortunately, there is no equivalent for the graph screen.

On the Graph Screen

Displaying a map on the graph screen is essentially the same as displaying a map on the home screen, except you can make the map much more detailed because the graph screen can be manipulated on a pixel level. There are several graphics commands available:

When displaying a particular part of the map, you can use a combination of these commands to create almost anything you want, whether it is a wall, a monster, a rock, or even a smiley face. For example, using our 8x16 maze level from before, instead of outputting the X character for the wall in the matrix, we can draw a wall using lines:

:If 1=[A](Y,X:Then

You should note that the window dimensions need to be X=0…94 and Y=0…62 for this example to show up correctly. In fact, with exception to the Pxl- and Text( commands, all of the graphics commands are dependent upon the window dimensions, so you should always use a friendly graphing window to ensure everything shows up as you intended.

There are several other ways to create graphics, and you should check out the graphics page for more information.

Where to Store Maps

After deciding on how you will store and display your maps, you then need to determine where you will store the maps: in the program itself or in a subprogram (or subprograms). When deciding which route to go, you need to think about how many maps you plan on having. If there aren't many maps (i.e., ten or less), they should usually all be stored in the program itself.

In the Program

For storing the maps in a program, you place each map inside its own If conditional and list the maps one after another. You then check to see which map the player needs and set up the variables for that map. Each map might also have some related information that goes along with it, such as the number of coins the player has to collect or the number of lives, so you would need to use an If-Then conditional instead:

:If A=1:Then  // Check if the player is at map 1

Once you have your maps stored in their individual conditionals, the next thing to do is decide where you you want to store them in the program. An obvious choice is just placing them right at the beginning of the program. In order to do this, however, it requires that you be able to access them. This normally entails placing a label before the maps, and then using a Goto to jump to them.

An important consideration when placing maps at the beginning of a program is what values you use for the If conditional variable. While you could use something simple like one or two, those values have a high probability of being accidentally entered in by the user or being set by another unrelated program, which would cause your program to store the respective map. What works better is to use random decimals (like .193 or 1.857) or math symbols (like e or π).

In a Subprogram

If there are several maps, you might want to consider placing them in a separate subprogram. The main reason is that when the maps are stored in the program, the program has to go through all of the code before the maps to reach a particular map. Depending on the size of the program, this can make for some major slowdowns in between maps. The internal maps also slow down the main program code itself.

Related to the first reason, the second reason to consider using a separate subprogram is that changing the maps is much easier in a subprogram. Instead of having to go through the entire program, looking for the map to change, you can just focus on one map at a time. This makes the maps more manageable, and also prevents you from accidentally changing other parts of the program.

The program code for the maps basically remains the same, it's just in another program. You might notice, though, that if you have lots of maps it takes a while for program execution to go back to the main program. This happens because program execution doesn't return to the main program until after it reaches the end of the program. You can fix this problem by placing the Return command at the end of each map conditional:

:If C=3:Then
:Return  // Stop program execution and return to main program

Now that the maps are in a separate subprogram, you need a way to access them. When you want to access a map, you set the respective variable to the value of the map that you want, and then call the subprogram from the main program using the prgm command and the subprogram name:


Unfortunately, storing the maps in a subprogram does have one major disadvantage. The user now needs another program to use the main program. If somebody tries to run the program and they don't have the maps subprogram, the main program will not work properly, and will actually return an ERR:UNDEFINED error when the program tries to call the non-existent maps subprogram. Even if this isn't your fault, the result is that your program looks very sub par.

Because of this problem, doing an all or nothing map separation (i.e., all of the maps are either stored in the program or in a separate subprogram) is usually a bad idea. The better alternative is to split up the maps so that the first ten maps (or so) are stored in the program, and the rest are stored in the subprogram. The user will now at least have some built-in maps to play, regardless of if they have the maps subprogram. The user simply won't have knowledge of the other maps available for them to play.

Sample Map-Based Game

This is a sample map-based game and the objective in each level is to get the "+" (plus sign), which causes you to advance to the next level. The game is played on the home screen using the arrow keys to move and CLEAR to quit. The maps are stored as lists in a separate program, along with the starting coordinates for your character.

(NOTE: Each list is all on one line, it's just split up to make it easier to read.)

:If not(A:Then
:If A=1:Then
:If A=2:Then
:" // 1 space
:Ans+sub(" =+",L1(I),1
:Repeat 3=L1(Ans+16Y-16
:Repeat Ans
:If Ans=45:Goto Q
:Output(Y,X," // 1 space
:If 2≠L1(C+16Ans-16:Then
:Lbl Q
:DelVar L1
:If A=3

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/maps

Movement in Maps

Movement is commonly used in programs as a way to add user interaction. It is part of user input as it relies exclusively upon the getKey command to work. You can use movement in many programs and for many different things, including moving a cursor in a menu or moving a character around the screen. It is the latter case that we are going to explain and show here.

The Code


This is the basis for the code used in the two later examples. An explanation for why it works can be seen here.

:Repeat K=21
:If Ans
:Output(A,B," // 1 space


This is the same code as the first, but it has the graphscreen initialization process at the beginning, and you have to switch up the keypress codes.

:Repeat K=21

Depending on what is being moved, the code might need to be revised. This particular code will move a pixel, or you can make it a line if you want. However, to move sprites, you will need to add to the coordinate variables instead. If you are moving a group of pixels, it would be ideal to hard code it.

Simultaneous Movement

Once you have learned how to create simple movement, the next natural step is to add some enhancement to make it more complex. One of the most common things desired is simultaneous movement — moving multiple things at the same time. Unfortunately, real simultaneous movement isn't really possible because of the limitations of the calculator, but you can emulate it.

When moving things, you need to be able to keep track of their position on the screen and the number of things. While the fastest way would be to use individual real variables for each thing, the best approach in terms of speed and size is a list and real variable respectively.

Before you initialize the list, it is good to consider how many things you want to allow on the screen at any one time. This is an important consideration because the more things you need to keep track of, the slower the program runs. A good range to shoot for is 5-15.

Here is what the code looks like so far:

:DelVar ADelVar L110→dim(L1

We are using the A real variable as the counter and the L1 list variable to keep track of the 10 object positions on the screen. We chose to initialize the list elements to 0 because that is our flag to determine if the object is active or not.

Now when you want to add another object, you simply need to increment the counter and then store the object's position on the screen to the list. You also need to remember to check that you haven't exceed the maximum number of allowed objects on the screen. You can combine the X and Y screen coordinates together into one list element using compression.

:If A<11

You also need to check for when a thing goes off the screen. When this happens, you first look at the counter to make sure it isn't at 0, and then loop through the thing positions and move all the things to the previous list element. You then decrement the counter.

:If A>1:Then

When moving these things, you simply loop through the positions list and then change the position of whatever thing you want. You basically are moving one thing at a time and then switching to the next thing once it is done.

Collision detection

If you want to restrict your character's movement so that it doesn't move through solid spaces such as walls, you will need some sort of collision detection. Since this example is on the home screen, the best method is to use a string. Create a string with 128 elements, leaving spaces for nothing, which will be represented as zeros for visual aid. Equal and unequal signs make good walls. Here is an example, a maze. For more info maps, go to the page making maps


Notice how the "maze" is set up so that the outer boundaries are all walls. The advantage of this is that it allows us to save space and speed on the calculator by removing the specific boundary check. The disadvantage is that it limits the amount of characters on screen to 6x14 instead of the full 8x16.

Now we can add the collision detection code in with our original movement code. You should notice that the main difference is the player's position for movement is checked to determine if the player is going to move onto an equals sign.

Notice how there is an extra argument after the Repeat. This allows us to have the character switch to the next maze when it reaches the end. You could also use this to switch to another map at the screen's edge.

  ================→Str1   //remember, 0's are spaces
:Repeat K=21 and AB=26   //AB=26 can be changed for different exit point
:If Ans
:Output(A,B,"_  //One space, checks for key press and erases
:A+Ans(" "=sub(Str1,16(A-1+Ans)+B,1→A   //If future coordinate is a space, it moves
:B+Ans(" "=sub(Str1,16A-16+B+Ans,1→B
:"     //second maze

And you can repeat this until all your mazes have run through. In addition to using strings, you can also use lists, matrices, or hardcode the whole map in if statements. The code is fundamentally the same, except there is a different formula used to display the map on the screen and you also check the available spot with that formula. Again, just try to understand the code and play around with it.

On the graph screen, you cannot make a string for collision detection. Otherwise, you would be looking at a 5985 character string! Instead, on the graph screen, you can use a command called pxl-Test( to tell you what is in the next space being moved to.

The pxl-Test( command finds the status of a pixel on the graph screen returning a 1 if the pixel is on or a 0 if the pixel is off. Therefore, if you get a 1, the character shouldn't move to the next space. If the pxl-Test( is 0, then the character moves to the next space. The following code is the base of how this works, and you can alter it to add boundary checks or advanced sprite manipulation.



  • Kerm Martian and his post at the UTI TI-Basic forum about keeping track of multiple shots.
  • darkstone knight's post which led to the latest few updates in the formulas.

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/movement

Custom Text Input

Custom text input is used when you want to get input on the graph screen (or the home screen, if you don't like the look of Input or Prompt). As the Input and Prompt commands only work on the home screen, the only option available is to make your own text input routine.

The Basic Routine

The core of the text input routine is using the getKey command together with a string of acceptable characters organized to follow the order of the respective key codes, and then extracting one substring character and storing it to the outputted text string:

:" →Str1  // 1 space
:Repeat K=105
:Repeat Ans>40 and Ans<94 or Ans=105
:If 25>length(Str1) and 0<Ans and Ans<29
:If 1<length(Str1
:sub(Str1,2,length(Str1)-1→Str1   //removes initial space

If that sounds confusing, please let me break it down for you. We first need to initialize the string variable that we will be using to hold the text the user inputs. You can use whichever one of the ten string variables (Str0-Str9) you want. The reason we initialize the string with a single character is because the calculator returns an error if you have a null string.

:" →Str1

We now begin the main program loop. The program will loop until the user presses ENTER. After that, we loop until the user presses a letter key (as all letters are assigned key codes from 41 to 93) or ENTER.

:Repeat K=105
:Repeat Ans>40 and Ans<94 or Ans=105

After the user has pressed one of the necessary keys, we then need to take the respective action in the program. If the user pressed one of the letters of the alphabet, we first check to see that the string is not already at the end of the screen (i.e. its length is 25). If it is less than 25, we add that text to our string variable and display the whole string:

:If 25>length(Str1) and 0<Ans and Ans<29

We finally close the main program loop. This text input routine just has basic functionality, as it was designed to show you how to do custom text input. It's up to you whether you want to extend it to include a larger range of acceptable characters, word wrapping, or whatever feature you want to include.

Tweaking the Routine

These are advanced features for a custom text input routine.

Backspace functionality

The above routine is very limited: once we type something in, we can't go back and change it. This add-on allows the user to press the DEL (delete) key to delete the last letter typed. To add this functionality, change the first loop code from ":Repeat Ans>40 and Ans<94 or Ans=105" to ":Repeat Ans>40 and Ans<94 or max(Ans={105,23", and add the following code right before the final End that terminates the outer loop.

If the user pressed the DEL key, we first check that the string variable has at least one character already in it (so an error isn't returned), and then remove the last character at the end of the string and redisplay the string (erasing the three spaces to the right of the last character left behind from the deleted character):

:If K=23 and 1<length(Str1 //if DEL pressed and some letters have been entered
:Text(10,1,Str1,"   //three spaces after the "

Flashing Cursor

A flashing cursor makes it clear that you mean business, or that you mean for the user to type in text. The code for a flashing cursor should replace the ':getKey→K' in the basic routine. You must also replace 'Ans' in ':Repeat Ans>40 and Ans<94 or max(Ans={105,23' with 'K'. 'K' must also be added onto a new line after the second 'End' after the code below. This is because the new variable 'I' messes with 'Ans'.

We start the routine normally: repeat until a key is pressed. The two Text( statements will draw a [ then erase its two tails, effectively drawing a horizontal bar. The For( loop creates an artificial delay between drawing and erasing the cursor. However, we want to end the loop if a key is pressed (so we don't have to wait until the cursor finishes flashing to type in a key). That's what the I+5Ans→I statement does: if K isn't 0, it will make I greater than 30, which will end the loop. We want to erase the cursor if a key was pressed or if I=16 (halfway through the delay loop). Finally, we end both loops.

:Text(10,4length(Str1)-1," //1 space after the quote
:I+5Ans→I //if K isn't 0, I will go out of bounds, ending the loop.
:If I=16 or K
:Text(10,4length(Str1)-2," //1 space after the quote

Adding Number Functionality

Although this routine differs from the one above, it accomplishes the same thing. Again, thanks to DarkerLine for the keypress to letter formula. Harrierfalcon came up with the formula to convert keypresses to numbers.

:DelVar A15→B
:Text(0,82," LET
:" →Str1
:Repeat max(M={45,105,21
:Repeat M=23 or max(Ans={21,45,105}) or (not(A)M>40 and not(A)M<95 and M≠44) or max(AAns={92,93,94,102,82,83,84,72,73,74
:Text(29,B,sub(" [",1+(Ans>0),1
:If M=31
:If min(M≠{21,105,45,23
:If A
:If M=23 and 1<length(Str1

A=1 if NumLock is enabled, and A=0 if AlphaLock is enabled. Allow me to break it down.
:DelVar A15→B
:Text(0,82," LET
:" →Str1

I'll let you guess on this one.
:Repeat max(M={45,105,21

This resets the cursor counter, and initializes the loop that won't quit until [ENTER],[2ND], or [CLEAR] is hit.
:Repeat max(Ans={23,21,45,105}) or (not(A)M>40 and not(A)M<95 and M≠44) or max(AAns={92,93,94,102,82,83,84,72,73,74

This initializes the loop which won't quit until proper keys other than [ENTER], [2ND], [CLEAR], or [DEL] is hit.
The second boolean value is structured so that if A=0, it means AlphaLock is enabled, and if a letter key is pressed, then it will exit the loop. If A=1, then it won't work, because not(A)M will equate to 0 every time. If [VARS] is pressed, nothing happens, because there is no letter there.
The second Boolean value was created in a fashion that tells if A=1, then M is left alone. If it is not, then it doesn't count.
:Text(29,B,sub(" [",1+(Ans>0),1
:If M=31

The first line increments C, and returns it to -5 if C=5.
The second line outputs [ or a space, if C<= 0 or C>0, respectively.
The third line clears the bracket's tails, or does nothing.
Lines 4-8 toggles AlphaLock and NumLock, and updates the display.
:If min(M≠{21,105,45,23
:If A

This If-Then is executed only if [2ND],[DEL],[ENTER], and [CLEAR] were NOT pressed. This prevents garbled numbers with would cause ERR:DOMAIN. This is executed only if NumLock was on.

This is executed if NumLock was NOT on, i.e. if AlphaLock was on.
The rest shouldn't have to be explained. Optimizations are out there…can you find them?

Advanced Editing Functionality

This routine adds several features that are not in the other versions. It allows for uppercase and lowercase text with switching in between them. It allows the user to move the cursor throughout the text and insert text and delete text where ever they would like. The user can also clear the current text and start over. Unfortunately, the code is a little hard to decipher, but we'll work through it. The main deficit of this program is that it is incompatible with the TI-83. This can be remedied, as explained below.

:"ABC  abc  DEFGHdefghIJKLMijklmNOPQRnopqrSTUVWstuvwXYZ(Theta)!xyz(Theta).  :?   :? →Str0
:"  →Str1   // 2 spaces in quotes
:DelVar M1→P
:Repeat K=105 and 2<length(Str1
:Text(0,0,sub(Str1,1,length(Str1)-P)+"|"+sub(Str1,length(Str1)-P+1,P)+"        // 5 spaces after quote
:Repeat Ans
:P-(K=26 and Z>1)+(K=24 and Z<length(Str1)-1→P
:M xor K=31→M
:If K>40 and K<105 and K≠44 and K≠45
:If K=23 and P<length(Str1)-1
:If K=45:Then
:"  →Str1   // 2 spaces in quotes

The first three lines take care of variable initialization. M stores whether capitals are enabled or not. M = 0 if its uppercase, M = 1 lowercase. Str0 contains all the uppercase and lowercase letters, including information for the last row of keys on the calculator. Str1 is the string that the text will be stored in. The information will be between the two space characters so that we can insert information at the beginning and end without having problems. P is the number of places before the end of the string to place the cursor. This is the most crucial variable, this will allow for the painless insertion of text and deleting of text anywhere in the string.
TI-83 Compatibility: Change Str0 to be the string listed above for the basic routine ("ABC DEFGHIJ…."). This, along with the other change listed below, will make this TI-83 compatible.
:Repeat K=105 and 2<length(Str1

This repeats until [ENTER] is pressed and makes sure that text has been entered into the string before exiting.
:Text(0,0,sub(Str1,1,length(Str1)-P)+"|"+sub(Str1,length(Str1)-P+1,P)+"        // 5 spaces after quote

This displays the text at 0,0 on the graph screen. This is a difficult line so let's break down what is being outputted onto the screen.

This returns all the characters that are before the cursor and then outputs the cursor
sub(Str1,length(Str1)-P+1,P)+"        // 5 spaces after quote

This returns all the characters that are after the cursor and then outputs some spaces. The spaces are there because when delete is pressed, the string will get smaller, and some characters will be left over. This is to cover those characters up.
The next two lines just get the key press and store it into the variable K.
:P-(K=26 and Z>1)+(K=24 and Z<length(Str1)-1→P
:M xor K=31→M

The first of these two lines control the movement of the cursor (stored in P). If the right arrow is pressed then P will decrement, less spaces from the end of the string. If the left arrow is pressed then P will increment, more spaces from the end of the string. The other conditions make sure that the cursor doesn't go too far in either direction.
The second of these lines controls the current capitalization state. By using the xor command, M will switch back and forth between 1 and 0 (lowercase and uppercase, respectively) every time the [ALPHA] key is pressed.
:If K>40 and K<105 and K≠44 and K≠45

These lines test to see if the key press is a letter key and then inserts the letter into the string. The second line concatenates three strings together. The first is all the characters before the cursor. The second is the letter that was pressed (found in Str0). The third is all the characters after the cursor. This is all stored back into Str1.
TI-83 Compatibility: In order to make this compatible with the TI-83, change the middle string that is concatenated to sub(Str0,K-20-5int(.1K),1). This, along with the other change, listed above will make this TI-83 compatible.
:If K=23 and P<length(Str1)-1

These lines test to see if [DEL] was pressed and then deletes a character from the string. Since the character deleted is from behind the location of the cursor, the conditional tests to see if the cursor is at the beginning of the text (where there is no character before the cursor). The second line concatenates two strings together. The first is all the characters before the cursor minus the last one (the character being deleted). The second is all the characters after the cursor. This is all stored back into Str1.
:If K=45:Then
:"  →Str1   // 2 spaces in quotes

These lines test to see whether [CLEAR] was pressed and then clears the text accordingly. Both Str1 and the location of the cursor are reset to their original values.
After this line, the main loop ends.

This line removes the extra space on each end of the string and returns Str1.

If you want to let the user view the current capitalization state, add the following line before the getKey loop.


The coordinates can obviously be changed to place this anywhere around the graph screen.


  • DarkerLine came up with the formula for translating the letter keys into the short string.
  • Harrierfalcon created the formula to convert number keypresses into a short string.
  • Zaphod Beeblebrox created the advanced editing functionality routine.

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/custominput


There is only so much that can be shown with text alone: sooner or later, any kind of program, whether it is a game or a complicated math tool, will have to use graphics. The purpose of this page is to describe the various methods of rendering graphics, along with their advantages and disadvantages. In many cases, you'll find that no one method suits your needs perfectly, and they will need to be combined to produce something that looks good without sacrificing too much speed or memory space.

Picture Variables

The simplest and fastest way to display just about any image is to save it in a picture variable beforehand, and then use the RecallPic command to instantly display it to the screen. There is no choice in positioning the picture, or displaying anything smaller than the entire screen, so this method is mostly limited to title screens.

The main advantages of this method are:

  • The speed — no other TI-Basic instruction can fill the screen as quickly.
  • The image can be made as complex as desired, without loss of performance.

However, there are numerous drawbacks:

  • Each picture variable uses 767 bytes of precious memory.
  • Furthermore, the picture is stored outside the program, where it can be overwritten.
  • There are only 10 picture variables to be shared between all TI-Basic programs.
  • The picture is static and can't be moved around the screen easily.

There are several ways to get around this problem. A decent solution is to use groups to keep the program and its pictures together and backed up — this ensures that even if a picture is overwritten, that this will not be permanent. Another possibility is to hope that there won't be any other conflicting programs to cause problems.

An example of picture variables at their best is the title screen in Contra: the image is too complicated to reproduce easily in any other way, and the result has become iconic of the game.

Hard-coded Sprites

Hard-coded sprites are, in a sense, the other extreme to go to. The idea is to write out the specific commands (usually, ones like Pt-On( or Pxl-On( to display an image. A friendly window might be useful. Here is an example, drawing a smiley face near the coordinate (X,Y):


Unless the image only needs to be drawn at one point, adding variables such as X and Y above is useful, allowing the same code to draw the same thing at multiple locations. It should also be noted that the Pt-On( and Pt-Off( commands have an optional third argument that allows you to draw a 3x3 box or cross (using 2 or 3, respectively), which is smaller and faster than the individual composite commands.

The advantages of this approach are:

  • It is very flexible: no matter the image, it can be drawn with some amount of commands.
  • It is still fairly fast, although slower than RecallPic.
  • It's easy to make a sprite that moves around on the screen.

The drawbacks, on the other hand, are:

  • Of all the methods on this page, this one uses the most memory.
  • The more complex the image, the more commands it will require to draw.
  • Every different image requires its own instructions, complicating program logic.
  • It's easy to make a mistake or typo, and hard to fix one.

A good use for hard-coded sprites is a fancy cursor in a menu: these are usually small and relatively simple, don't require many different images, and play to the strengths of hard-coded sprites. You can also combine hard-coded sprites with compression to reduce the number of graphic commands needed, although it often causes a hit to the program speed.

Plot Sprites

A somewhat different idea for rendering graphics, plot sprites use the Plot#( commands to draw multiple points or multiple lines very quickly (taking a shortcut, so to speak, to the approach above). In this case, all of the information to draw the sprite (that is, the coordinates of the points or lines) is stored in a pair of lists. Since plots use point coordinates, a friendly window may be useful.

To display a sprite, first store the coordinates to two lists (this article will assume they are L1 and L2). Next, set up the plot variable with Plot#(Scatter,L1,L2) (to draw points at the coordinates) or Plot#(xyLine,L1,L2) (to connect the coordinates with lines). Then, the DispGraph command will update the graph screen with all the plots that are currently in use. There are three plots available, which can be switched on and off with the PlotsOn and PlotsOff commands.

Here is an example of the same image displayed using plot sprites:


Plot sprites are uniquely suited to being moved around the screen: once the setup phase is done, just modify the lists slightly and then use DispGraph again to draw the sprite at a different location, erasing it from where it was. Just using simple arithmetic on the lists lets you move, reflect, rotate, and stretch a sprite. The table below shows the formulas required (some intuitive, some not):

Transformation Formula
Horizontal translation A+L1→L1
Vertical translation B+L2→L2
Reflection about the x-axis -L1→L1
Reflection about the y-axis -L2→L2
Rotation 90°
Rotation 90°
Rotation by an angle of θ L1cos(θ)-L2sin(θ)→L3
L1sin(θ)+L2cos(θ)→ L2
Horizontal stretch AL1→L1
Vertical stretch BL2→L2

The advantages of using plot sprites are:

  • The image data is stored in variables (lists), so the same code can display any sprite.
  • Plot sprites are usually smaller than hard-coded sprites, at a comparable speed.
  • As named lists, images can be saved outside a program without fear of being overwritten.
  • The sprites are uniquely easy to move (arbitrary rotation, for instance, is only possible with plot sprites)

The drawbacks are:

  • Since there are only three plots, only three independent sprites can be displayed at a time.
  • Plot sprites don't work well with other graphics: DispGraph erases most things drawn on the graph screen.
  • DispGraph erases before drawing, producing noticeable flicker.

There are two main avenues for plot sprites. The first is for displaying an image that won't have to be moved around (such as a title screen): this avoids the two problems that DispGraph causes. The other is for displaying a few images in complex motion, where the transformations can really come in handy.

Text Sprites

Text sprites are the most bizarre method (known so far) of displaying graphics. They are efficient in a very limited application: displaying small (usually 5x5) images without the drawbacks of hard-coded sprites. The idea is to display several characters very close to each other using the Text( command, so that the first pixel column of each character is combined into an image. For example:

:Text(0,6,"  ")

To understand what is going on, add a Pause command in the For( loop, and watch the image being drawn piece by piece.

When dealing with text sprites, the image data is stored in strings (in the example above, storing any other 5-character string to Str1 will produce a different 5x5 sprite). In fact, using properly chosen characters (see this chart), nearly any sprite with 5 rows (since small text characters are 5 pixels tall) can be displayed, with only a few rarely-encountered exceptions.

The advantages of using text sprites are:

  • At 5 pixels per byte (usually), they are the most efficient method of storing small images.
  • The sprites are not made up of points and lines, so they can be fairly detailed.

The drawbacks are:

  • Text sprites are usually slower than hard-coded sprites to display.
  • They are limited to a 5xN size, which makes them less flexible than other methods.
  • Displaying a text sprite erases a small space to the right of the sprite (this can be avoided with caching — see below).
  • Code to produce text sprites is harder to learn and understand.

Tilemaps are a good application for text sprites: the code for hard-coded sprites would be too large and unwieldy, and plot sprites are too limited in number. This is demonstrated in Donut Quest, a puzzle game whose graphics play to the strengths of text sprites while avoiding situations that would highlight their drawbacks.

Recently, someone came up with the idea of vertical sprites. It uses the same idea but it displays sprites from bottom to top. You can find more information in the Text Sprites page.

Layered text sprites

Similarly to text sprites, layered text sprites use the Text( command in order to display custom sprites very efficiently and are usually used in order to display an entire level at once rather than to draw a single isolated sprite due to their limitations, however they are faster than most methods for displaying large images and use very little memory (2 bytes for a single sprite). The idea is to draw a first layer of large font characters, saving them using StorePic, drawing a second layer on top of them and finally using the RecallPic command to join the 2 layers together, creating an horizontal line of sprites. For example:


A variation of this method can be found in the game Serenity, where the second layer is shifted 1 pixel to the right, allowing the creation of 6x7 sprites and the removal of the gaps created between sprites when using the regular method.


  • Each sprite only uses 2 bytes of memory
  • Due to the fast rendering speed of layered text sprites, they allow the entire screen to be filled very quickly, making them useful for drawing an entire level at once
  • The sprites allow for a lot of detail compared to other methods


  • They are limited to a 5x7 or a 6x7 size, making them less flexible than other methods
  • Only a few different characters can be used, limiting the amount of shapes possible
  • Code to produce layered text sprites is harder to learn and understand
  • They are inefficient for displaying individual sprites

Due to the drawbacks of this method, layered text sprites are mostly used by platformers whose graphics resemble the home screen, such as Zoith, Metroid Pi and Serenity, for their ability to fill the graph screen very quickly when moving to a new screen.

Assembly Libraries

The fifth way to render graphics is to use an assembly library such as xLIB which includes sprite routines. These routines typically do what we all wish RecallPic could do: nearly instantly recall a small piece of a picture variable to an arbitrary part of the screen, often with extra features thrown in.

Choosing a library to use for your program involves trade-offs: an obscure program might have all the features you want, but a popular library will be used by other games as well. Users probably wouldn't mind installing one library to play a bunch of cool games, but if every game uses its own assembly library, managing them becomes tricky (particularly because they may interfere with each other).

The advantages of using assembly libraries for graphics are:

  • They are fast: usually faster than RecallPic, and definitely faster than any other method.
  • They are versatile: once you make the decision to use a library, it can be applied to any situation.

The drawbacks are:

  • An assembly library takes up lots of memory (not as much of a factor on newer calculators).
  • Typically, you will have to use a picture variable, with all the drawbacks that entails.

It's best to use assembly libraries only for graphics-intensive programs. If you do decide to use an assembly library, make as much use of it as possible: there's usually no point in going halfway and mixing an assembly library with any of these other tricks.

Advanced Techniques

Caching: with almost all of these methods, it takes some time to display an entire screen's worth of graphics. If there's a chance that this screen will have to be displayed more than once, it makes sense to avoid doing the extra work of drawing it all over again. To prevent this, take any unused picture variable, and use StorePic to save the current state of the screen. Then if it ever needs to be redrawn, RecallPic the screen instead of rendering it the normal way. This technique makes sense for a title screen (if it wasn't a picture variable already) or for the initial state of a level of a game (in case the player restarts).

A similar technique can be used when drawing one image will erase another part of the screen, and both are necessary. In this case, StorePic the screen, draw the image, and then RecallPic. Because RecallPic uses "OR" logic (a fancy way of saying it doesn't ever erase dark pixels), this will keep the background while still drawing the new image. This is critical when combining plot sprites with any other method, and often comes in handy with text sprites as well.

Greyscale: if a pixel flashes on and off very quickly, it will appear grey to the eye (because erased pixels need time to fade to white). Switching back and forth between two or more pictures gives the effect of using three colors (black, white, and grey) on a calculator that can normally only handle two. Because this needs to be done quickly, usually only programs using assembly libraries attempt this. An example using xLIB:

:Repeat getKey

In this case, pixels that are on in both Pic1 and Pic2 will be displayed as black; pixels that are off in both of them will be displayed as white; pixels that are on in one and off in the other will be displayed as grey.

With precise timing, this technique can be improved to produce multiple shades of grey. Suppose that in this example, Pic2 were displayed twice as long as Pic1. In that case, black and white pixels would be the same; however, a pixel that was on in Pic1 and off in Pic2 would be on 1/3 of the time, while a pixel that was on in Pic2 and off in Pic1 would be on 2/3 of the time. This would give four colors:

State in Pic1 State in Pic2 Result
off off white
ON off light grey
off ON dark grey
ON ON black

Even more shades are possible, but the more complicated the code, the worse the flicker. If you try for too much, eventually the "greyscale" will turn into two pictures quite obviously alternating onscreen. Given the limitations of TI-Basic, it might be best to stick to 3-color greyscale.

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/graphics

Program Protection

Disclaimer: Program protection not only is rather limited in its effectiveness, but also acts as a hindrance towards maintaining the open and collaborative nature of the TI-Basic community: allowing others to study and learn from your code, and to use the techniques and concepts in their programs, increases the quality of TI-Basic programs being released.

You've just finished working on your latest, greatest program. You put in a lot of time and effort creating the program, and now you want to enjoy the fruits of your labor — showing it off to your friends at school. When your friends try the game, you get positive feedback and they tell you how much fun it is, and even ask if you could put the game on their calculators.

Now, you don't mind putting the game on your friends' calculators, but you want to make sure that no one can mess with it. Once the game is out amongst the school crowd, you know that other people will want the game so you need to come up with a way to protect your program. Fortunately, there are several ways to protect a program.

Before getting to program protection, the first thing you need to do is edit lock your program. You can edit lock a program using either one of the several downloadable assembly programs or the Graph Link software made by TI. It goes without saying that you should never give someone an editable version of your program.

Once your program is edit locked, now you can add a security function. Although there are several ways to protect a program, they each have varying degrees of complexity and success. The general rule is that the more complicated the protection is, the more difficult it will be for someone to circumvent it.

Put the Code Together

Arguably the simplest, yet most crude program protection method is just putting all of the code in the program on one line. You might recognize this as utilizing compact style, except this time it serves as a program safeguard instead of a stylistic choice.

In order to put code together, you need to separate each command with a colon (:). The colon closes everything except a literal string, in which case the colon will actually be included as part of the string. In order to prevent this from happening, you need to close the string with a quote before adding the colon.

:If A=2:Then
:Disp "Hello
can be
:If A=2:Then:Disp "Hello":not(B→B:End  // Note the closed string

There is one command that doesn't need a colon following after it — DelVar — but leaving it off can cause some problems. DelVar's are typically chained together with one variable after another (i.e., DelVar ADelVar B), but the DelVar command also allows you to take the command from the next line (it doesn't matter what command it is) and put it immediately after the DelVar (i.e., DelVar ADisp "TI-Basic").

This works for the majority of commands, but there are two cases in which the command will actually be ignored: the End from an If conditional and a Lbl command. Both of these cases can cause your code to not work correctly anymore, and so you should either add the appropriate colons between the DelVar's or re-organize your code to eliminate the situation entirely.

:If not(X:Then:-Y→Y:DelVar ZEnd
can be
:If not(X:Then:DelVar Z-Y→Y:End  // Note DelVar's position

When you put the code together it will wrap around to the next line (and keep wrapping around for however many lines are needed), which is useful because the average calculator user will not be able to read and understand the code then. More importantly, if they press a key to try to mess with the code it can have dire consequences. Specifically, if the user presses CLEAR, the whole line of code (i.e., the entire program) will be deleted.

Entering a Password

If putting all of the code together on one line seems rather complicated (and maybe not worth all that effort), a simpler program protection method is having the user type in a password at the beginning of a program. You then check the password against the stored password and allow the person to play the game if the passwords match or exit back to the home screen. You can have the password be whatever you want.

:"5552646472→Str1    // Store the password to a string
:For(X,1,.5length(Str1    // Loop every two characters for a key
:Repeat Ans
:If Ans≠expr(sub(Str1,2X-1,2    // Check if the user typed the wrong key
:Stop    // Stop the program and return to the home screen

When editing the password string, you must keep the length divisible by two because of the For( loop and the If conditional check. Besides that, this code does not allow keys 102-105 to be included in the password. That shouldn't be too big of a problem, though.

Entering Seeds

You can use pseudo random number sequences as a sort of password protection. After seeding the rand command, the results generated will be unique to the seed that was chosen. If the seed takes on the behavior of a password, then a comparison of the rand function to one of its precomputed results will act as an authentication for that password.

For instance, 5→rand followed by a single use of rand will return .727803… on all calculators, so a test can be devised as follows:

:Input X    // Request a number
:X→rand    // Seed the random number generator
:If rand≠.7278038625    // Check if the first random number is not equal to this value
:Stop    // Stop the program
:If rand≠.7278038625

Only when the user inputs the correct seed (or in the latter case, stores the correct seed to rand before running the program) will he be able to venture past this part of the code. The upside to this technique is that even if he does see the code, he won't be able to figure out what the password is just by looking at that number.

Going further with this, you can test for a result that is obtained only after a specific number of numtrials (i.e., uses of the rand command). After storing 7 as a seed, the third result will be .577519…, so having a test similar to the one shown above will mean that the code that follows it will only work on its third execution after the seed is stored manually — adding another layer of obscurity.

Hash Functions

While using the seq( command, the calculator can still interpret keypresses and store them to getKey. One possible way you can use this feature is to make a password function that asks the user to enter in the correct password before time expires:

:DelVar L1seq(getKey,X,1,200→L2
:If Ans:Ans→L1(1+dim(L1
:If 5=dim(L1
:If max(L1≠{55,52,64,64,72

The main problem with using this routine is that you have to create a huge list to have enough time to input a reasonable password. This can be fixed by replacing seq(getKey,X,1,200 with something that goes a little slower:


This does lose a bit of sensitivity, but this isn't a huge problem because the routine has a lot of sensitivity to begin with. Even adding +0dim(rand(2)) left the code still sensitive enough that it recorded every keypress of me simply brushing a finger across the keyboard of my TI-83+.

Put this together with the idea that we don't want to store the password itself (because that would be fairly easy to figure out), but rather a hash of the password — a numerical equivalent value for the password. This is easier than extracting the nonzero elements of a list. For example, sum(√(Ans is a decent option that doesn't care about the order of the keypresses. If you want an option that does, take cumSum(Ans)not(not(Ans first — this multiplies the last keypress by 1, the next-to-last by 2, the one before that by 3, and so on.

Here is an example:

:Disp "Input Password
:If 106.322402=sum(√(cumSum(Ans)not(not(Ans

This example will display the message Success! if you enter the password AWESOME. Obviously, one of the main programs with using a hash function is coming up with the different hashes for the passwords, so here is a program that will assist you in making the hashes:

:Repeat Ans=105
:If Ans
:DelVar L1Ans

Input your password and then press ENTER to get the appropriate number to test against.

Example password: HAL
Hashed result: 29.8632681

By replacing 106.322402 in the hash password program with 29.8632681, the password will be reconfigured to HAL.

Self-Modifying Code (SMC)

Another way you can protect your program is by using self-modifying code. SMC makes your code more difficult to understand, and by placing code inside a graphing variable, you are essentially hiding it. This prevents somebody who's not very knowledgeable from figuring out what it is.

A good example of this is where you have an If conditional, and you replace part of the condition with a graphing variable:

:If Xnot(Yint(rand
can be
:If Xu

If this conditional is inside a loop, then you can modify the u variable later so that its code is something different when the If conditional is checked next time. For the average calculator user, this will make your code seem obfuscated, and they will be hesitant to mess with it.

Causing an Error

Depending on the protection used, you usually want to implement an error when it has been breached. The simplest error would be a message to the user. <error status> can be anything you want: see the methods below for when to cause an error.

:If <error status>

Unfortunately, this method allows the user to know when the error occurred and remove the error code by pressing ON when the error is displayed. A more secure method uses an error caused by the calculator that cannot be traced to specific code. The drawback of this method is that a custom error message explaining the problem cannot be displayed.

:If <error status>
:Goto XX

This code will cause a program to display ERR:LABEL because there is no label XX. It is one of the few errors that does not have the option to go to the code causing the problem, which makes it more secure. An experienced user will most likely be able to find the problem Goto, however.

The most complicated method of causing an error is to embed pieces of code that cause problems when <error status> is true. In the examples below, problems are caused when X≠20 (replace this with whatever condition you want). Unless the user is an expert, it will be difficult for the user to fix the errors.

:If 21=getKey(X=20    // Clear getKey
:L1(X=20→LSAVE    // Prevent saving
:A+X→A    // Use as a replacement to A+20→A
    // Screw up a complicated command
    // Extremely difficult for someone else to figure out

Another option is to quit the program immediately. This is most effective in a large program, where users will have to pore through hundreds if not thousands of lines of code to find the problem code. In addition, make the program jump to the default quit routine instead of a new one to confuse users even more.

:If L1(31)=5    // Quit condition test
:Goto XX    // Default quit label
...    // Whatever code is in between the Goto and matching Lbl
:Lbl XX
...    // Stuff to do before quitting

Storing the Protection Status

The other program protection methods all require one variable in which to store the protection status (the number of times the program has run for a "trial period", whether it is protected or not, etc.). You can use any variable for this, but each has its own advantages and disadvantages: a custom list is best (but somewhat difficult to implement) and a finance variable is second best.

  • Regular variables — Have the advantage of being readily accessible, but are not very suitable because they are frequently overwritten by other programs.
  • Finance variables — Built-in to the calculator and are somewhat hidden, so they are unlikely to be erased. You can access these variables by going into the Finance menu. The only uses of the finance variables are the Finance Application and other programs. If another program is using the finance variable you want to use, either use a different one or change the other program. However, all the finance variables are reset to zero when the RAM is cleared.
  • Custom Lists — Can be archived, and it is unlikely that some other program would use the same list name. However, the list is visible in the Memory Management menu, and a perceptive user may realize that it is being used for program protection and change it. To counter this, you can hide the value among other values in another list used by your program (for example, save lists).

Trial Periods

If you wanted your program to only run a certain number of times (a trial period), you will have to have a counter that counts the number of times the program has run and produce an error when the limit is reached. (See above for a discussion on which counter to use.) For this example, we will use the finance variable n for simplicity. Add something like this to your program:

:n+1→n    // Increment the counter
:If n>5    // If this is past the fifth time, free trial is over
:Goto XX    // Replace this with any of error methods explained above

That's it. The above code will cause an error message to be displayed after the user has reached the end of the trial period (used the program five times). You can change this however you want to fit your needs. Since the increment comes after the error, it will continue erring each time it is run.

Authorization Required

You can also set up your program so that only authorized/licensed users can run it. This method can be combined with the above method: Users can use the programs until their free trial is up and they have to become "authorized." To "authorize" an individual calculator, set n to a predetermined value.

There are two ways of doing this: either type in the value manually (and use Clear Entries afterwards to prevent the user from discovering it), or transfer n as part of a group containing your program.

:If n≠20    // If n=20, the calculator is "authorized"
:Goto XX    // If not, cause problems
    // You can replace this with any of the errors mentioned above

You can also use this method to lock some of your program's features in the "unauthorized" versions. For instance, in this example every user can use feature one (which is part of label 1) while only authorized users can use feature two (which is part of label 2):

:Menu("MAIN MENU", "FEATURE 1", 1, "FEATURE 2", 2
:Lbl 1 
<feature one, available to everyone>
:Lbl 2
:If n≠20:Then
<feature two, restricted>

Keeping subprograms un-executable

Say you have a large program with many subprograms, the only correct way to run the program is to run the main one. So to keep subprograms from being run outside of the main one you create a pass-on key and have the subprograms check Ans to see if it matches.

:randInt(1,100→Z    //make a pass-on key, keeping it new each time so the user cant guess it
(...rest of code)
:Z                //Store the key as Ans
:prgrmSUB1            //Call the subprogram
:If Ans≠Z            //Check the key and end the program if it doesn't match
(...rest of code)

Simple enough

Thoughts to Consider

While discussing program protection, it is important to mention that somebody who's a knowledgeable calculator user will be able to circumvent any program protection using either one of the downloadable assembly programs that can unlock TI-Basic programs or the Graph Link software. Because of this, program protection really is only possible when you are dealing with ignorant calculator users.

Besides knowing about knowledgeable calculator users, you should also think about how others would be able to learn from your code. The general consensus among the calculator programming community is that programs should be unrestricted so others are able to study your work, as long as they do not release it as their own.

Putting all the code on one line would be frowned upon in this case because other programmers don't want to have to deal with separating out the code one line at a time to be able to understand and read it; that's just a major headache. Just remember that experimentation is key to learning TI-Basic, so you don't want to deprive that opportunity from someone else.


  • David Martin had the ideas for "free trial" and "authorization" program protection in his TI-Basic guide, which unfortunately is not available on the Internet anymore. The examples given here are based on these ideas, but modified to fit this guide better.
  • Weregoose came up with the plain password code example, while DarkerLine and Weregoose came up with the hash function password code examples; the examples were originally posted on the United-TI TI-Basic forum topic.

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/protection

Look-Up Tables

A lookup table consists of a list (or matrix, depending on the situation) that is used to store calculations, for which the time to look them up in the list is smaller than having to calculate them (hence the name). Lookup tables are commonly created at or near the beginning of a program for later use in the program.

The primary advantage of lookup tables is their speed. Simply getting a number from a list is much faster than calculating the number with an algorithm or using a trigonometric function. The primary disadvantage of lookup tables is their memory usage. Not only do you need to use an extra variable to keep track of all the numbers, but it is very possible that you can end up storing numbers that you won't even use.


For an example, let's look at using the trigonometric functions. Say we want to draw a circle using lines. We want to draw a line every 10 degrees, and because the circle has 360 degrees, that means we would do 36 calculations. Without using lookup tables, the approach would be to simply use the cos( and sin( functions:


Although this code draws pretty fast already, it could be made faster using lookup tables. Every time through the loop we are calculating the cos( and sin( functions, which is quite time-consuming. Speed is especially important in this particular example because we want to have the circle draw as fast as possible (it should be faster than the Circle( command). Here's what the example would look like using lookup tables:


Another example that should help you more fully understand lookup tables is getting user input. More specifically, combining the getKey command with a lookup table. Say you want to display a text character based on which key was pressed. A common way to do this is to check to see what the individual keycodes are and then display the respective character:

:If K=41
:If K=42

What you could do instead is place all the acceptable characters (in this case the alphabet) in a string, and then put all the keycodes in a list, organized to follow the alphabet keycodes. When you want to display a character, you simply search the list for the keycode that the user pressed:

:If K>42 and K<94 and K≠45
:Output(4,X,sub("ABCDEFGHIJKLMNOPQRSTUVWXYZ",max(K=Ans and seq(B,B,1,dim(Ans))),1

(See Custom Text Input page for a smaller and faster way to get the keycodes.)


These are rather simple examples, but they should be enough for you to understand how lookup tables work and what you can use them for. Just remember that the battle of speed vs. size is left up to you to decide which route you take. The two main factors to consider are the playability of the program (if the game is slow, the calculations should go) and the number of times the lookup table will be used (if the use is one, consider it none).


  • The example trigonometric code was borrowed from David Martin's tutorial, which is not available on the Internet anymore.

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/lookuptables

Self-Modifying Code (SMC)
Equation Variables
Function Y0-Y9
Parametric X/Y1T-X/Y6T
Polar r1-r6
Sequence u, v, w

Self-modifying code (SMC) is code that changes itself while it is executing. While TI-Basic does not provide support for SMC like it is found in other programming languages, TI-Basic does allow you to implement a primitive form of SMC using the equation variables that are used when graphing an equation on the graph screen.

The function, parametric, and polar graphing variables are accessible in the VARS menu (options 1, 2, and 3 respectively), while the sequence graphing variables are accessible on the keypad by pressing 2nd 7, 8, and 9 respectively.

Each of these variables is the same size, so there is no real difference between which variable you use. However, since sequence graphing is the least used graphing mode, the sequence variables are probably the best variables to use when using SMC.

How does it Work?

While the equation variables are primarily used for graphing, they are actually stored internally as strings by the calculator. The string operations and commands do not work on them, however, but you can store a string to them and evaluate them in an expression.

Just like how you can evaluate an expression stored in a string using the expr( command, the equation variables can be used the same way implicitly. The expression can contain whatever combination of numbers, variables, and functions that you want, just as long as they evaluate to a number or list of numbers.

For a simple example, let's initialize X with a value of 2 and store the expression "50X" to u:


When you access u, it will have a value of 100. But what would happen to the value of u if you changed the value of X to 5? Well, because the value of u depends on the value of X, u would change accordingly, and it would now have a value of 250. This is the basic premise of SMC: You can modify a variable elsewhere, and it will automatically update the respective equation variable. Hence, a program can change how it runs.

As it turns out, finding an occasion to use this technique is usually rare, so here is a made-up example. This program will count up and down with the arrow keys until you press ENTER. If you press 2ND, however, it will switch the order of the keys:

:"(Ans=25)-(Ans=34→u        // initial expression for u
:Repeat Ans=105
:Disp Ans
:Repeat Ans:getKey:End      // wait for a keypress
:If Ans=21
:"(Ans=34)-(Ans=25→u        // switch the arrow keys

Advanced Uses

While just using SMC for simple expressions doesn't really add any additional value to your programs, you can use it for more complicated purposes. One common situation is optimization.

If you have a lengthy command or formula that you use multiple times throughout your program, you can simply store the statement to an equation variable, and then use the equation variable whenever you want to call the statement. This eliminates duplicate code, which makes your program smaller.

For example, if you want to generate a random integer from 1 to 9, here is what you would use:


Then each time you wanted to create a random integer, just use u.

Limitations of SMC

There are a few limitations you need to be aware of when using SMC:

  • It complicates your code, making it difficult to understand and maintain. This is why you should primarily stick to implementing SMC when you are done with your program.
  • The equation variables will affect the graph settings, and likewise the graph screen will be affected if the respective graph mode is enabled.
  • You can't store the equation variable to itself, or other variables, if they don't have a matching type (i.e., trying to store a string to a real will result in an ERR:DATA TYPE error).
  • Don't abuse SMC; the extra step of reading and executing through variables may slow down your code slightly and even cost a number of bytes if used improperly, so wield it wisely (i.e., only for the benefits it provides over other methods).

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/selfmodify

Grouping A Program

Your friend just asked you to transfer a program on your calculator over to his calculator so that he can play it in class whenever he wants to. You say sure, and he gets his link cable out and you start the transfer process. What you thought was going to be a simple transfer turns out to involve some serious headaches.

The program he wants not only has several subprograms that go with it, but multiple list and matrix variables as well as a few pictures. Unfortunately, you aren't aware of this until later after he tries to run the main program without the necessary subprograms, variables, and pictures.

When he asks you why the program won't work like it did on your calculator, you don't have an answer. You decide to start transferring over other programs and variables and whatever else to try to make the program work, but the program still doesn't cooperate. Finally you just give up and tell him that there must be something wrong with his calculator.

While this seems like a rather difficult problem to fix, there is in fact a simple solution: group files. Groups store files together in a package, where the file can be almost anything, whether it is a program, variable, picture, etc. Because groups reside in the archive, you never have to worry about a RAM clear deleting your group.


Several files that go together can be put in one group, making it easy to transfer the files together — whether between two calculators, or a calculator and a computer.

On a TI-83+ or higher, group variables are stored in the archive, which means that a rogue RAM clear won't delete your files.

Groups are also great for backing up a version of a program being worked on before making major changes to it - even if the program is very large, or split between several files.

Finally, putting several parts of a released program in a group avoids the issue of users that forget to transfer a necessary file (although you should explain how groups work in a readme file).


A group must contain more than one variable. It's possible to get around this by providing a dummy variable in the group (use a variable such as X that probably doesn't hold anything important).

TI-Basic programs cannot modify groups. You will have to recreate the group if you want to change its contents. Usually, this isn't too much trouble.

It's also been reported that in large enough groups, the calculator may change a bit in the data when ungrouping — in practice, this might result in an error when running the newly-ungrouped program. To be on the safe side, you should check that a group "works" before deleting the original files. It's also possible that splitting the large group in two (if this is feasible) will fix the issue.


  • The idea for this grouping article came from Jon Pezzino and Kerm Martian's "The Elite Guide to TI-BASIC". It is a good read with lots of useful knowledge and tips/tricks. You can find the link to it on the Resources page.

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/grouping

toolbar-separator.png This article is part of the coding stage of the development cycle.

Imagine you're creating an RPG, and the current problem you're facing is how to display all of the data for each character on the screen. Without putting too much thought into it, the easiest approach to you seems to be to simply write out the data each time. Unfortunately, when you start implementing this approach, it soon becomes apparent that it will not work in your program.

While you've only programmed in a few of the characters and their respective data, you can already see that there is simply too much data to enter in; and then when you think about the possibility of having to go through and change all of the data again if you update the program, you're now left feeling overwhelmed and wondering if there's another way. Thankfully, there is, and its name is subprograms.

A subprogram is a program called by another program to perform a particular task or function for the program. When a task needs to be performed multiple times (such as displaying character data), you can make it into a separate subprogram. The complete program is thus made up of multiple smaller, independent subprograms that work together with the main program.

There are two different general types of subprograms available:

  • External — They exist as stand-alone programs that are listed in the program menu, and can be executed just like a regular program.
  • Internal — They are contained inside the program itself, so that they can be called by the program whenever needed.

There are certain advantages and disadvantages to using each type of subprogram, and consequently, situations where using one type of subprogram makes more sense than using the other. This doesn't mean you can't use whichever you like, though.

External Subprograms

External subprograms are the simplest type of subprogram, and involve executing one program from inside another program using the prgm command. You just insert the prgm command into the program where you want the subprogram to run, and then type the subprogram's name. You can also go to the program menu, and press ENTER on whichever program you want to use to paste the program's name into the program.


When creating a subprogram, you take whatever code you want from the parent program and put it in its own program (see your first program for more information), and then put a Return command whenever you want to return to the parent program. (A Return command is optional at the end of a program, and you typically leave it off as part of program optimization.)

You should try to name your subprograms Zparentn or θparentn, where parent is the name of the parent program and n is the number (if you have more than one). Because subprograms are relatively unimportant by themselves, you want them to appear at the bottom of the program menu so they don't clutter it up and interfere with the user's ability to find whatever program they're looking for.

Here's a simple example where we are storing maps for our maze game in a subprogram, and then retrieving the desired map from the subprogram as a string, so we can print it out on the home screen. (This example is somewhat contrived, but it should be enough to illustrate external subprograms).


:"X    XX    XXXXXX    XX    XXXXXX    XX    XXXXX
:If A=1:"X X X X X X X X X X X X X X X X X X X X X X X X 
:If not(A:"X              XX              XX              X

When the subprogram's name is encountered (in this case, prgmZMAZE), execution of the program will be put on hold, and program execution will transfer to the subprogram. Once the subprogram is finished running, or the Return command is executed, program execution will go back to the program, continuing right after the subprogram name. (If the Stop command is used instead of Return, however, the complete program hierarchy will stop.) See the image to the right for a graphical view of program flow using subprograms.

Although subprograms can call themselves (i.e., recursion) or other subprograms, each subprogram should return to the parent program and let it handle all the program calling. A structured program hierarchy is easier to understand and update (in fact, it's actually a code convention), and helps cut down the potential for memory leaks.

Each program call is sixteen bytes, and gets put on a stack in RAM by the calculator. This is fine as long as its size isn't larger than the free RAM available, but each additional program call takes more memory until the program Returns. Having many nested program calls can run out of memory and crash the calculator (giving you a ERR:MEMORY error).

External subprograms often are made to increase the speed of a program and decrease the size of the program overall by executing often used code at one place, where it can be called quickly. (as opposed to Labels)

To make subprograms even faster, a Return may be used in the subprogram, but only is useful in subprograms with several methods, and only after a method has been completed. The problem with using a Return in such a method is that it isn't very easy to prevent memory leaks by using a Return before an End command should have been called.

Because the same thing happens with Goto's before End commands as Returns before End commands, we can use this to our advantage, to quickly return from a subprogram. By using a Goto to a label with one or possibly more End commands, you can quickly return from a program, or quickly reset a While, Repeat, or For( loop without having to execute the rest of the code in the subprogram or loop.

Goto ST
Lbl E1
Lbl E0
Lbl ST
If [condition]
Goto E0
If [condition]
Goto E1
If [condition]
Goto E1

This can significantly increase the speed of subprograms or loops, by using Labels without worry of ERR:MEMORY.

Passing Arguments

The main problem associated with trying to use external subprograms is that there is no built-in support for passing arguments to subprograms. This feature is important because it allows the subprogram to act differently based on what values are given to it for processing.

Fortunately, you can mimic this functionality by using variables. Because all variables are global (variables used by one program can be used by another), changing a variable in one program affects that variable everywhere else. While you can use almost any variable that you want, there are three main types of variables that you should choose from:

  • Pre-Defined Variables — Includes reals, strings, matrices, built-in lists, etc. These variables are frequently used by most programs, so you don't have to worry very much about whether the user cares if your mess with them.
  • User-Defined Lists — Uses the individual list elements to store different values. These variables have a certain sense of security that comes with using them because they are the only variable that you can actually create, so a program can have its own custom list to use.
  • Ans — It can take on whatever value and variable you want, so the program doesn't have a specific variable hard-coded in. Its value changes whenever you store something or simply place an expression or string on a line by itself.

When using a pre-defined variable or user-defined list, you simply have to set the variable to the value that you want and then call the subprogram. The subprogram should be able to use that variable without problems, but if you don't properly setup the variable, the subprogram won't work correctly.


Using the Ans variable is essentially the same, except you need to add some additional code to the subprogram. Because the value of Ans changes whenever there is a new variable storage, you should have the first line inside the subprogram save Ans to another, more stable variable.



This change saves some memory in the main program (in our example, we were able to get rid of the →PRIME statement), but the subprogram size is larger, since we really just shifted the variable storage code to the subprogram. However, if the subprogram is called multiple times this extra memory is only used once instead of once per call.

This does create a problem, though, because now when you store Ans to another variable, it will crash the program if Ans isn't the same type of variable. There is only one case where you can avoid crashing when the subprogram receives the wrong variable type. If the subprogram is expecting a real variable (such as A or X) and it is passed a list, it can prevent a crash by using the fact that a parenthesis ("(") has multiple functions.


The reason that this works is because a user-defined list doesn't need the ∟ prefixed character at the beginning when referring to the list. While you may be only asking the user to input a simple real variable, a list would also be allowed. There is nothing you can really do to fix this problem with other types, so just be careful.

Advanced Uses

The main consideration when using external subprograms is how many subprograms your program should have. While you're still putting your program together, it's good to keep it in many small, separate subprograms; but when you're done, all those subprograms become a liability and make your program unwieldy to use. This is because you have to remember all those subprograms in order to use your program.

There are two different ways to resolve this problem. The first way is to put the subprograms back in your program. This should only be done if a subprogram was only called once or twice, and putting it back in won't slow down the program. All you have to do is paste the code from the subprogram in place of the subprogram call. When you're done, you can delete the now unnecessary subprograms.

(The more detailed explanation is to go through your main program, and whenever you see a prgm call for a subprogram, clear that line and press 2nd STO. The Recall option will come up. Press the PRGM key and select the appropriate subprogram from the EXEC menu. The calculator will paste that subprogram's code into the main program.)

This is the same subprogram example from before, but now we've gotten rid of the ZMAZE subprogram and simply placed the subprogram code in the MAZE program itself:

:"X    XX    XXXXXX    XX    XXXXXX    XX    XXXXX
:If A=1:"X X X X X X X X X X X X X X X X X X X X X X X X 
:If not(A:"X              XX              XX              X

The second way to resolve the problem is by simply combining your subprograms together, so that there are fewer subprograms needed. Of course, how many subprograms you decide to use is up to you, but you should try to limit it to three or four subprograms at most, and just one subprogram ideally. This might take some effort on your part, but it will most certainly make your users happy.

When you start combining your subprograms together, you should place the subprograms one after the other, giving each subprogram a unique branching label (note that labels are local, so you can't use Goto in one program to jump to a label in another program). Instead of having to search through each individual subprogram, branching allows you to simply jump to the desired subprogram. You then just use the subprogram's variable argument to determine which subprogram to access.

:If A=e:Goto A  // jump to first subprogram
:If A=π:Goto B  // jump to second subprogram
:Stop  // the subprogram was accidentally called
:Lbl A
: // subprogram code
:Lbl B
: // subprogram code
: // No Return needed here

Looking at the example, the first thing you should notice is the variable values that are used to determine which subprogram to jump to. While you could use something simple like one or two, those values have a high probability of being accidentally entered in by the user or being set by another unrelated program. What works better is to use random decimals (like .193 or 1.857) or math symbols (like e or π).

If none of the variable values for the subprograms match, then none of the subprograms will be executed; instead, program execution will go to the Stop command on the next line, which will immediately stop the entire program. The reason for adding this program protection is to prevent the user from running the subprogram separate from our main program.

This is a real concern since external subprograms are listed in the program menu, and the user most likely at some point will try to run the subprogram just out of pure curiosity. Unless the user is a competent TI-Basic programmer who knows what they are doing, however, you normally don't want to let the user mess with your subprograms. If they change something, or delete some lines of code, then your program might stop working correctly.

The second thing you should notice about the example is the Return command at the end of each subprogram. If you have lots of subprograms, and you're accessing a subprogram near the bottom, it takes a considerable amount of time for program execution to go back to the main program. This happens because program execution doesn't return to the main program until after it reaches the end of the program, or it executes a Return command. So, just remember to include the Return commands as needed.

Using Assembly

Although using assembly programs can limit the compatibility of your program, they can be helpful in reducing the memory your program occupies, and allow for easier calling of subprograms. To use an assembly program for calling subprograms, have your subprograms named using some very simple pattern (e.g. "ZZ10", "ZZ11", "ZZ12", "ZZ13", etc). Then create a program such as the following called ZEXEC (or something similar):

://Assembly program to run program name in StrX

Then you can call this program through the line:


This will create a string with the contents "ZZ13" which will then run the program ZZ13. Depending on the assembly program you are using, you could have "ZZ13" archived (along with the rest of the subroutines) and when ZEXEC is called, it copies it to an unarchived program, runs the copy, and then deletes the copy, thus only one subroutine at a time is unarchived. This is useful for programs with lots of subroutines. One way of doing this is using xLIB, for which the ZEXEC code would look like this:


In order to make this concept more efficient, a number to string routine would come in handy. If one were using such a routine one could have various actions in a program return number values. These numbers would be converted into strings and the program would run the corresponding subprogram. This would become useful with a custom menu, one could have the program return the location of the cursor, convert that into a string and run the subprogram that corresponds to that cursor location. Some assembly programs that can be used may be found on the assembly libraries page.


There are several advantages of using external subprograms. First, and foremost, they reduce program size by eliminating redundant code. Instead of having to type the code multiple times for a task that occurs more than once in a program, you just type it once and put it in a subprogram. You then call the subprogram whenever you want to perform the task in your program.

Second, external subprograms increase program speed by making programs as compact as possible. You separate conditional tasks from the program (they either happen every time or they are skipped over), and put them in a subprogram; you then call the subprogram instead. This improves program speed because the calculator doesn't have to go through all of the conditional code anymore.

Third, external subprograms make editing, debugging, and optimizing easier. Instead of going through the entire program, looking for the code you want to change, you can focus on one subprogram at a time. This makes the code more manageable, allowing you to more thoroughly look at each subprogram and to better keep track of which subprograms you have looked at. It also prevents you from accidentally changing other parts of the program.

Lastly, subprograms are reusable, allowing multiple programs to share and use the same code. Breaking a program into smaller, individual subprograms, which each do a basic function or task, allows other programs to use those subprograms. Consequently, this reduces program size.

Internal Subprograms

Internal subprograms are the most complicated type of subprogram, and involve putting the subprograms in the main program itself. This is not the same thing as pasting the code from the subprogram in place of the subprogram call, like you do with external subprograms; rather, it is designing your main program so that it can take advantage of subprograms, but all the code is self-contained.

There are several different ways that you can make internal subprograms, but the three most common ways are:

  1. Append to the beginning of the program
  2. Structured loops or branching
  3. Branching out of broken loops

Append to Program Beginning

If you remember how we used external programs, then this should be very familiar. Instead of placing the subprograms in their own separate program, we are now just placing the subprograms at the beginning of our main program.

The standard way to make a subprogram is to use an If-Then conditional:

:If A=1.234:Then
: // subprogram code
:DelVar A

Then to call the subprogram, you just set the variable to the desired value:


Of course, there are some important considerations that you need to be aware of. You can use whatever random value for the variable that you want, just as long as it isn't something that the user would typically use. This is to ensure that the subprogram isn't accidentally run when it shouldn't be, which is why you need to reset the variable's value inside the subprogram before exiting.

While you could use any variable that you want (including Ans), the best variables to use are the simple real variables (A-Z and θ). This is because they are small in size and they are constantly being used by other programs, so you don't have to really worry very much about your subprograms being accidentally run. (Ans is not a very good variable to use for the reasons listed above.)

You should always remember to include the Return command at the end of the subprogram. Once the subprogram is finished, the Return command will cause the subprogram to stop and return to the previous place in the program from where it was called. The other reason for the Return command is to prevent any memory errors that can occur if a program recursively calls itself too much.

Advanced Uses

You can have multiple subprograms at the beginning listed one after the other by simply using different values for the the variable:

:If A=1.234:Then
: // subprogram 1
:If A=2.246:Then
: // subprogram 2

While this works quite well when you only have three or four subprograms, with more subprograms it can actually slow down the main program. This happens because the calculator has to go through all the subprograms to get to the main program code.

You could fix this problem in a couple different ways, but the easiest way is to simply place all the subprograms in an If-Then conditional and then make that part of the subprograms. If this conditional is false, all of the subprograms will be skipped over.

A real number has an integer (accessed with the iPart( command) and fraction (accessed with the fPart( command) part, and you can use both of those for the subprograms: the integer will be the subprogram access check on the outside If-Then conditional and the fraction will be the respective subprogram we want to run.

:If 123456=iPart(A:Then  // get integer part of number
:10fPart(A  // get fraction part of number
:If Ans=1:Then
: // subprogram 1
:If Ans=2:Then
: // subprogram 2
: // rest of subprograms

For calling the subprograms, you then just set the variable to the desired value like before:

:123456.1→A  // run subprogram 1

Structured Loops or Branching

If you don't like placing subprograms at the beginning of a program, the next approach that you can try is placing subprograms in the actual program code. While it would appear easy to simply place the subprograms wherever you feel like in your program, you can't readily do this since it would almost certainly cause your program to stop working correctly. Instead, you need to modify your program and subprograms so they can be put together.

What this modification entails is reorganizing your program so that the code works in a modular fashion as individual subprograms. This may not seem like it would be worth the effort, depending on the amount of code in your program, but modularization makes the program easier to understand and update (see planning programs for more information).

While there are several different ways you can structure the code in a modular fashion, the simplest way is to give each subprogram its own individual loop with a respective variable value as the loop condition. You can then access and exit the desired loop by simply changing the value of the variable. Of course, you have to determine which loop and variable you are going to use.

There are three different loops you can choose from (While, Repeat, and For(), but the best loop to use in this circumstance is While. This is because the condition is tested at the top of the loop, so if it's false already before the loop, then the loop will actually be skipped over (which is what allows us to use the loops as subprograms).

Once you have decided upon a particular loop, now you need to choose which variable you want to use. Like with the first way to make internal subprograms, the best variable to use is one of the real variables (A-Z and θ). This is because we just need a single value, and real variables only take up 15 bytes of memory (other variables are just as small, but they take up more memory when you're accessing them).

Now that the loop and variable have been chosen, we need to setup the system of loops to act as the subprograms. What works best is to have a main program loop and then place the subprogram loops inside of it. Putting the variable and loops together, here is what the program skeleton looks like:

:Repeat not(A  // main program loop
:While A=1
: // subprogram 1
:2→A  // enter loop for second subprogram
:While A=2
: // subprogram 2
:DelVar A  // exit main program loop
: // rest of subprograms

You just set the value of the variable in the loop to use the desired subprogram. Then when you are done with the subprogram, you just change the value of the variable to something different to exit the loop. You do the same thing to exit the main program loop. You can use whatever system of values for the variable that you want, but just remember to keep it simple enough so that you can come back to it later and it still makes sense to you.

The one drawback of using this approach is that the calculator has to go through all the subprograms to exit the main program loop, which can really be slow depending on the size of the subprograms. At the same time, this approach is very easy to understand and follow because the loops are organized in a straight forward manner, so it's kind of an even trade off.

Related to using structured loops, the alternative you can use is branching. While using branching by itself to structure a program is generally frowned upon (see planning programs for more information), you can actually use it quite effectively for making internal subprograms that only need to be called a few times. Here is a simple example:

:0→A:Goto A
:Lbl B
: // main program code
:1→A:Goto A
:Lbl C
: // main program code
:Lbl A
: // subprogram code
:If A:Goto C
:Goto B

The A variable is used for determining when to stop the program: a zero value will simply cause the subprogram to jump back to the main program, but a value of one will cause the subprogram to jump to the exit of the program (the C label). Because the calculator doesn't store the label positions, there is no way to get memory leaks using this approach, which is especially important when exiting the program. However, it does get hard to follow and maintain the code the more branching there is.

Branching out of Loops

The last way to make internal subprograms is arguably the most difficult to understand, but once you have it setup in your program, it provides an easy framework for adding additional subprograms. The best time to use these kind of subprograms is when you have a main program loop that you're running and you want to be able to jump out of it and then back into it whenever you want.

The basis of these subprograms is using branching (Goto and Lbl) with loops and conditionals (anything that uses an End command). Branching by itself allows the calculator to jump from one point in a program to another, skipping over whatever code you don't want executed. When you use branching to exit loops and conditionals, however, it has the unwanted effect of causing memory leaks.

Memory leaks happen because the calculator doesn't get to reach the End command for the associated loop or conditional, and the calculator just keeps on storing the End commands in its stack until there is eventually no free memory left. (Memory leaks can also occur with excessive program recursion.) Here is a simple example that has a memory leak:

:Lbl A
:While 1
:Goto A

If you notice, when the Goto A command is executed, it jumps to the matching label A that is on the line before the loop. The While 1 loop is never allowed to finish because the End command never gets reached, and the branching occurs over and over again until the calculator finally slows down to a stop (because there is less and less free memory available) and returns a memory error.

This type of programming is common with beginners, and its use is generally frowned upon; instead you should try to use proper program structure (see planning programs for more information). However, if you know what you are doing, you can actually use these broken loops and conditionals for internal subprograms, and you won't have to worry about memory leaks or the dreaded memory error.

There are two different approaches that you can use. The first approach is to use another Goto and matching label to jump back into the loop. Because the calculator doesn't store the labels, you can freely use whatever branching in the loop that you want, and the calculator will act like it had never even left the loop:

:Repeat getKey
:Goto A
:Lbl B
:Lbl A
: // subprogram code
:Goto B

The key here is that the Goto A command jumps to the matching label A outside the loop, and then the Goto B jumps to the matching label B back inside the loop. The calculator still has the loop's associated End command on its stack, so the loop will just keep looping without problems until you eventually press a key to stop it and it executes the Stop command.

While this first approach works rather nicely in small programs, it is not very practical for use in large programs because all the branching starts to slow the program down. Unlike loops and conditionals, the calculator doesn't keep track of the label positions, so it must start from the beginning of the program to find the matching label to jump to. The further down the label is in the program code, the more time the calculator must spend looking for it.

The second approach solves this problem by using a duplicate End command for the loop or conditional. Since the calculator keeps track of the number of unfinished loops and conditionals by storing the associated End commands in its stack, we can make the calculator believe that our different End command is actually the End command that belongs to the loop. Here is a simple example to illustrate:

:Repeat getKey
:Goto A
:Lbl A
: // subprogram code

Like with the first approach, when Goto A is executed the program will jump to the matching label A, and then the subprogram code will be executed. This time, however, the calculator will read the End command after the subprogram code, which it believes is the end of the loop, and then immediately jump back to the beginning of the loop. This process will be repeated over and over again until the user presses a key, at which time the Stop command will be executed and the program will stop.

The subprogram code for both approaches can be whatever you want, including other loops and conditionals. You just need to remember to close the loops and conditionals before returning to the original loop, otherwise the calculator will have the wrong End command on its stack. You also want to have a matching number of End commands for your loops and conditionals, or you will get a memory leak.

Advanced Uses

There are a couple different ways you can enhance the duplicate End subprogram approach so that you get the most use out of it. The first way is relatively simple, and just involves using a For( loop as the looping structure, instead of a While loop or Repeat loop (which is what we had in our previous examples).

A For( loop is basically a specialized form of a While loop, with the main differences being that it is executed a specific number of times and it has the variable setup and ending condition built-in. You just choose a variable, the range of values, and the increment (it is optional, with one as the default); and then the loop will start the variable at the initial value and increment it each time until it reaches the ending value.

Now when you start using the For( loop for internal subprograms, you need to make sure the For( loop executes at least twice. This is so that the End command of the For( loop gets used along with the End command of the subprogram, otherwise it will simply fall through after the first time through the loop. You can select different subprograms based on the variable's value. Here is our example from above, now using a For( loop instead:

:If not(A:Goto A
: // main program code
:Lbl A
: // subprogram code

When the For( loop is executed, the A variable is set to zero. The not(A condition is true, so the calculator executes the Goto A command and then jumps to the matching label A. The calculator then jumps back to the beginning of the For( loop and increments the A variable to one. This time, however, there is no subprogram jump taking place, and the calculator simply finishes the For( loop like normal.

The second way to enhance the duplicate End subprogram approach is by using a simple trick. Because a While loop loops only when the condition is true, when the calculator comes across a While loop with a false condition, it will simply skip over the entire loop (and everything inside the loop). The easiest way to make a false condition is to use zero, since zero will never be true (based on Boolean logic). Here is a simple example to demonstrate:

:While 0
:Lbl A
: // subprogram code
: // main program code
:If getKey:Then
: // program code
:Goto A

When the calculator encounters the While 0 loop, it won't execute it because 0 is false. After the calculator enters the subprogram conditional, it executes some program code and then hits the Goto A command and jumps to the matching label A inside the While 0 loop. The calculator takes the End command of the loop as the End command of the conditional, and thus no memory leak occurs.

The reason that this trick is so valuable is because it allows you to place your subprograms at the beginning of the program, and you don't ever have to worry about them being accidentally executed (they will always be skipped over). In addition, now that the labels are at the beginning of the program, there is no more speed problem to deal with, since the calculator doesn't have to search through the entire program to find the labels.


The main advantage of using internal subprograms is that there is only one program needed to make your program run. When you give someone your program, you don't have to worry about forgetting to include any subprograms; or somebody deleting your subprograms afterwards, causing your program to stop working correctly. These things are mostly out of your hands, but users will think your program is at fault.

Related to the first advantage, the other advantage is that the user's program menu doesn't get cluttered up with insignificant subprograms. This problem is relative to how many subprograms a program has, but it can become tiresome to have to sort through the program menu in order to find the program that you want. If anything, this is just a nice courtesy to the users of your programs.


<< Commenting Code Overview Program Cleanup >>

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/subprograms

toolbar-separator.png This article is currently in development. You can help TI-Basic Developer by expanding it. I will get around to finishing this, probably during the Summer Holidays. ~ James Kanjo

Multiplayer is where two or more people play a game together at the same time. Although you can create AI opponents for a player to play against, AI does not offer as much fun compared to playing against other human opponents — you can have the players work together, compete against each other, and even one player manage the other players.

Multiplayer games are generally divided into two categories: the players share a single calculator, and the players each play on their own calculator connected together with a link cable (either I/O or USB).

Single-Calculator Multiplayer

Multiplayer games on one calculator generally fall into two categories: real-time and turn-based. Real-time is where the game action is constantly changing, not stopping for the player input. Some classic examples of real-time games are Galaxian and Pong. Turn-based is where each player is allowed to make a move, and the game action only changes after each player has moved. Some classic examples of turn-based games are Battleship and Scorched Earth.

Making real-time games involves using the getKey command, except you can't wait for a key to be pressed. The general form is something along the lines of:

While <game not done>
If K=<player 1 key>:Then
// player 1 action
If K=<player 2 key>:Then
// player 2 action
<Update rest of game>

As you can see, the player action only occurs when a player has pressed a particular key; otherwise the game just continues on like regular, with the main game loop being repeated over and over again.

There are some problems with making real-time games, however.

The first, and foremost, problem is that TI-Basic is rather slow with user input. If you do anything remotely time-intensive, such as displaying lots of graphics or complex variable manipulation, then there will be some lag-time between when a player presses a key and when the calculator gets around to processing it. Although there's not really much you can do about this, you can make sure your game is as optimized as possible (especially breaking the more time-intensive parts into their own subprograms).

The second problem is that the calculator only keeps track of the last key pressed since the last time getKey was executed, so that means only one player can have their input read and processed each time through the game loop. In addition, if you enter a large block of code for the player, it will take a while before the other players have a chance to do anything.

Related to the second problem, the third problem with making real-time games is that unlike the other keys, the arrow and DEL keys can actually be held down, which will cause them to keep being repeated until they are unpressed. The effectively disables the other keys from being able to be pressed. There is no viable way to get around this problem, except asking the players to not press those keys.

The fourth problem is that the keypad is quite small, and having two or more people try to share the calculator can be rather difficult. The best way to work around this problem is choosing keys for each player that are a good distance away from each other. When choosing keys you should also keep in mind the calculator screen — if somebody has to press keys that make it difficult to see the screen, then you should choose different keys.

Because making real-time games is not very practical, a better alternative is turn-based games: you hand the calculator from player to player, allowing each each player to move one at a time. These games are much easier to program: you simply use a variable to keep track of whose turn it is, increment the variable after each player's turn, and when everybody has completed their turn, you reset the variable. The only real downfall of turn-based games is that they can be slow because you have to wait until the other players are done before you can move.

Multi-Calculator Multiplayer

So I guess you're wondering how to program a multi-calculator multiplayer experience into one of your games. One of the first things you will need to do is familiarize yourself with the GetCalc( command. Basically, it retrieves a specified variable from another calculator and stores it to that variable on yours.

Creating multiplayer programs over two calculators is a much less simple process as it is to make single-calculator multiplayer programs. However, doing so could be the main selling point of your program and would certainly be worth the effort. You will notice that in each of our examples we tend to transfer lists, which we recommend you do too. Whilst it is possible to transfer a variety of real variables, it is much faster to transfer a list of numbers than a number of real variables.

There are two general ways to programming multi-calculator programs; one screen processing and two screen processing. The one screen processing method is simply making a program use the statistics from another calculator, and the whole multiplayer experience is processed on that calculator. The two screen processing method is much more complex, where we can share the multiplayer processing across two calculators by using a "turn-by-turn" interface.


When it comes to multi-calculator multiplayer games, it is absolutely necessary to give each calculator its own identity. As both calculators are running the exact same program, we need a way to be able to determine one calculator as "Calculator A" and the other calculator as "Calculator B". This makes it possible for both calculators to know what data to send and receive. For example, if both calculators were "Calculator A", then both calculators would be doing exactly the same thing, or keep trying to receive the same variable from each other in an endless loop

This is code determines which assigns each calculator with a unique identity:


How it works is that the calculator gets the variable A from the other calculator, and checks whether it equals π (pi). If A equals π, then e is stored to A; however, if A does not equal π, then π is stored to A. Here is a table to demonstrate the results:

Calculator A Calculator B
A = π A = e
A = 3.141592654 A = 2.718281828

The calculator can therefore identify itself like this:

:If A=π:Disp "I'M CALC A
:If A=e:Disp "I'M CALC B

If we use a not( routine to make Calculator A = 1 and Calculator B = 0 instead, then we are unable to determine whether a link has been initiated. Simply explained, because variable A is more likely to equal zero than any other number, Calculator A may accidentally assume Calculator B has initiated the multi-calculator sequence. Variable A is not likely to ever equal π (or e), which is why it's useful as a "connection initiated" checker for the calculator.

The beauty of the core code is that it doesn't matter which player executes the core code first, both calculators will be able to give themselves a unique identity, be able to distinguish which calculator they are and be able to see whether the other calculator is initiated yet.

One Screen

If you are looking to save space and valuable time, this is the multiplayer for you. This method has the sending calculator in a power-saving state the whole time while the receiving calculator does all of the hard work such as processing and animation.

First off, we put in the core multiplayer code to determine which calculator is which. Then, Calculator B will retrieve the opponent's statistics for battling. Because the program uses the same variables on every calculator, we need to find a way to store Calculator A's statistics onto Calculator B without overwriting Calculator B's statistics. Surprisingly, this is not as hard as it seems:

:If A=π:Then

Now that each calculator has created its unique identity, and Calculator A has stored its statistics to L₁, we can finally make Calculator B receive Calculator A's statistics and process all of the data:

:If A=e:Then
<interactive code>

After this, write the rest as though this was a single-calculator multiplayer game, where you're statistics are in ∟STATS and your opponent's statistics are in L₁. Here is a side-by-side comparison on how the program runs:

Calculator A Calculator B

Two Screens

Here we show you a turn by turn based method of a battle game.

Like with all multi-calculator multiplayer programs, we first provide the program with the core. This time, however, we will first reset variables A and F. Then we will add code for which stores each calculator's statistical data to its individually named list. Calculator B is then instructed to go elsewhere in the program (note that Goto is within an If-Then loop):

:DelVarADelVar FGetCalc(A
:If A=π:Then
:Goto W

Because this is turn by turn battle game, we need to repeat the battle code until the battle is finished. We will make variable F determine this. Also, we want Calculator A to be able to attack first, so we shall put the code for attacking as the first thing in the Repeat loop. This is where it starts to get a bit sticky. First we delete variable B, which is going to determine what command the user has chosen (whether it be a kind of attack, or to run away).

:Repeat F
:Lbl A:1→B:Goto S
:Lbl B:2→B:Goto S
:Lbl R:‾1→B
:Lbl S:1→θ

So when the user selects an option from the menu, a number is stored to B and 1 is stored to θ. Theta tells the receiving calculator that it the sending calculator is not ready yet. Then we create a second menu. This is to give the receiving calculator a chance to receive certain variables. That process is almost instant, and so the user then presses ENTER. θ is erased and 1 is stored to S, just before the "animation" program starts. S simply tells the animation program that it has just sent the attack.

:Lbl SA:1→S
:DelVar θprgmθANIMAT

The animation program is shared by both the attacker and the attacked. The program makes particular animation depending on whether variable S is equal to one. If S=1, then this program will only display the opponent getting hurt. If S=0, then this program will display YOU getting hurt, calculate how much HP you have left, and if you died, sets variable F to zero. By using the subprogram "prgmθANIMAT", it means we don't need to worry about memory leaks or program changes.

Now we have the receiving code. You will notice that Lbl W is the first line here. This is so that Calculator B can jump straight here on the first move. Because this label is within a repeat loop, and the Goto came from an If-Then section, there are no memory leaks. The first thing we do is make the program wait until the sending calculator has issued a move. If the game is over (F=1), then this process stops, and the program exits the "Repeat F" loop. If the game is not over, and the opponent issued a move, then the calculator receives the opponent's updated statistics.

:Lbl W:Disp "WAITING...
:DelVar BRepeat B or F
:If not(F:Then
:If A=e:GetCalc(L₁
:If A=π:GetCalc(L₂

At the moment upon entering the loop, we know that the opponent's θ equals 1. In this loop, we clear our θ variable and then retrieve the opponent's θ. Because of the nature of GetCalc(, we can not receive variables whilst the other calculator is processing. Remember that as soon as the other calculator exits the "SENDING ATTACK…" menu, the animation subprogram starts.

Using this knowledge, if we delete θ and then retrieve θ whilst the opponent is in the "SENDING ATTACK…" menu, θ will equal 1 (and hence the loop is repeated). But when the opponent starts the animation process, we will be unable to retrieve θ, and so θ will equal what it all ready was, zero (hence we exit the loop).

:Repeat not(θ
:DelVar θGetCalc(θ
:DelVar SprgmθANIMAT

This code is rather remarkable because it makes it possible to start the animation on both calculators, at the same time, automatically. You don't need to go messing about with "both users press ENTER at the same time" routines, only one user needs to press ENTER and both calculators begin — a foolproof technique.

If, after the animation process, the battle is not yet over, then the program continues into attack mode again (at the start of the "Repeat F" loop).

Now we need a routine which restores the statistics to the ∟STATS list and says who won after the battle is over! This is the easiest part of the routine. If the player's health points do not equal zero, then that player won — otherwise the other player won.

:If A=e:L₂
:If Ans(6:Then
:Pause "YOU WON!
:Pause "YOU LOST!

There are a couple of things that you need to be aware of for this routine to be work:

  1. When the "SENDING ATTACK" menu pops up, you must wait for a second for the attack to actually send before pressing ENTER;
  2. Variable A must NEVER equal π during the program, (obviously with the except of the multi-calculator code itself). If it does, and a different calculator starts this routine before the other one, then the link process will fail.

To prevent the second problem from ever happening, you should reset variable A at the start of your program, and never use this variable throughout the program.

This whole routine is certainly complex, but it does work, and works pretty well too. Here is a side-by-side comparison of how the program runs. For reference, here is a table showing the values:

Calculator A Calculator B
Max HP ∟STATS(1)=2 ∟STATS(1)=5
Level ∟STATS(2)=2 ∟STATS(2)=5
Attack ∟STATS(3)=2 ∟STATS(3)=5
Defence ∟STATS(4)=2 ∟STATS(4)=5
HP ∟STATS(6)=2 ∟STATS(6)=5
Calculator A Calculator B

Final Notes

Whilst the abilities of the GetCalc( command make it harder to create multi-calculator programs, it certainly is not impossible. You just need to think extra hard and create very clever workarounds to its boundaries.

If you have any questions or comments about these routines please ask in the discussion area for this page.


  • James Kanjo came up with the "Multi-Calculator Core" code in his IM program, and also came up with the "turn by turn battle" code, including the function to make one calculator respond to another calculator's ENTER keypress

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/multiplayer

Validation of User Input

Validation is the process of evaluating user input to ensure it satisfies the specified requirements. Most programs just blindly accept any input that the user enters, assuming that it was entered in correctly and that it is valid. This is a dangerous assumption, because there is a great likelihood that somebody at some point will enter in bad input. When that finally happens, the program will crash when it tries to use the input. In order to ensure this doesn't happen, you should validate user input.

When validating user input, there are three main approaches you can take:

  • Stopping the program — This validation approach involves asking the user for input, and then checking to see if it is a bad input value; if it is, you then just stop the program with a Return or Stop. The idea is that there is no point continuing on to the rest of the program, since the input would only cause the program to crash or get messed up when the input finally gets used.
  • Keep asking for input — This validation approach involves placing your validation inside of a Repeat or While loop, and using a variable as a flag to indicate whether the input is valid or not. The idea is that after asking the user for input, you check to make sure it is not a bad input value; if it is, the bad input flag will be set, and input will be asked for again.
  • Make bad input valid — This validation approach involves asking the user for input, and then trying to filter out any bad parts of the input that might be present. You can make the filters as in depth as you want, but because there is almost an infinite number of things that a user can enter in, there is no way to cover everything. Ultimately, it comes down to knowing the user, and what they are likely to enter in.

How to Validate Variables

There are three main variable types that a user will be asked to input values for in a program: reals, lists, and strings. Each of these variables has their own set of standard validation considerations to check for, along with whatever additional considerations you have in your particular program.


When validating a real variable, the main things to check for are:

  • Is it within the appropriate range?
  • Is it not a complex number (i.e., there is no imaginary i part)?
  • Is it an integer (i.e., no fraction part)?
  • Is it positive (or zero, if appropriate)?

Testing for these four conditions is relatively easy, considering that there are built-in commands that will take care of it for you: relational operators, the imag( command, and the fPart( command. Generally, you should do the complex number check before the other three checks, because an imaginary part in a number can cause problems.

There's another problem that comes up with using Input specifically. If the input is for a real number A, the user might enter a list, which will be stored to ∟A instead. A simple way of fixing this problem is to store an invalid value to A to begin with, which will be dealt with if the user enters a list and doesn't change A. Many times, this is as simple as using DelVar.

For a simple example of validating a real number, imagine asking the user to specify how many rounds of a game they want to play. A For( loop is used as the main game loop, with the number of rounds being the upper boundary. Since the lower boundary is zero, we want the user to input a number greater than zero. In addition, a For( loop does not work with complex numbers, and we do not want to allow a fraction part because the number of rounds is used to calculate the scoring.

Here is what the validation code for our game might look like with each of the three different validation approaches:

:DelVar AClrHome
:Input "ROUNDS: ",A
:If imag(A
:If A<1 or fPart(A
:Repeat Ans
:Input "ROUNDS: ",A
:If not(imag(A
:not(A<1 or fPart(A
:DelVar AClrHome
:Input "ROUNDS: ",A

There are a couple things you should note. In the stopping the program code, we used the Return command to stop the program, instead of the Stop command. This is so that the program will work correctly with subprograms and assembly shells. We used the opposite commands in the making bad input valid code: iPart( instead of fPart( and real( instead of imag(. We are also using the max( command to make one the minimum value for the input.


When validating a list, the main things to check for are:

  • Is the list length within the appropriate range?
  • Does each list element pass the real validation?

Testing for the list length condition and each of the list elements involves using the built-in dim( command. You first check to see that the list length is acceptable, and then use the dim( command as the upper boundary in a For( loop to go over the list elements one at a time. Each element is validated just like a real variable.

For a simple example of validating a list, imagine you have a lottery game and you want the user to specify three numbers. We want the numbers to be between 1-100, as well as not having an imaginary or fraction part. Here is what the validation code for our game might look like with each of the three different validation approaches:

:Input "NUMBERS: ",L1
:If 3≠dim(L1:Return
:If imag(Ans:Return
:If Ans<1 or Ans>E2 or fPart(Ans
:Repeat A=3
:Input "NUMBERS: ",L1
:DelVar A
:If not(imag(Ans
:A+not(Ans<1 or Ans>E2 or fPart(Ans→A
:Input "NUMBERS: ",L1

Like with the example from before, we had to check for the complex number before checking for the number boundaries and fraction part. This is because neither of those commands work with complex numbers; they will actually throw a ERR:DATA TYPE error. Also important is the optimization that we used to move the list dimension check into the For( loop's upper boundary. This allowed us to eliminate a conditional that we would have had to add.


When validating a string, the main things to check for are:

  • Is the string length within the appropriate range?
  • Does the string only contain the appropriate characters?

Testing for the string length involves using the built-in length( command. This check by itself is not enough, however, because a string treats commands and functions as just one character (i.e., a string of "ABOutput(" is considered to be three characters long). The way you resolve this problem is by making sure the string only contains certain characters. This involves creating a string of acceptable characters, and then checking the user input against it.

For a simple example of validating a string, imagine you have a two-player hangman game and you want the user to enter in an eight letter word, so that the other player can guess it. The only characters that are allowed are the uppercase alphabet (A-Z), and there is no restriction that the word has to actually exist. (Programming in a check for that would involve keeping a dictionary of words, and that could potentially take up a lot of memory.)

Here is what the validation code for our game might look like with each of the three different validation approaches:

:Input "WORD: ",Str1
:If 8≠length(Str1:Return
:If not(min(seq(inString(Ans,sub(Str1,I,1)),I,1,8
:0:Repeat Ans
:Input "WORD: ",Str1
:If 8=length(Str1
:Input "WORD: ",Str1
:If not(inString(Str0,sub(Str1,I,1
:sub(sub(" "+Str1,1,I)+sub(Str0,randInt(1,26),1)+sub(Str1+" ",I+1,9-I),2,8→Str1

When the user inputs a word, we loop through all of the characters in the word and get their positions in our acceptable characters string. If any of the characters weren't in the string, then their position will be zero, and when we take the minimum of all the positions, the smallest will be zero. With the making bad input valid code, we also concatenate how ever many characters we need to make the word eight characters long.

Making Validation More Friendly

There are a couple different ways you can make validation more user-friendly: displaying error messages when there is bad input, and storing input as a string and converting it to the appropriate variable type.

Displaying error messages to the user when they enter bad input helps the user correct their mistake, and provides some direction on what input you are expecting them to enter. An error message does not need to be complicated or long — just enough so that you can get the point across. For example, say a program is a number guessing game, and the user is expected to enter in a number between 1-1000. If they enter 5000, you can display the message "Between 1-1000".

Storing input as a string allows you to accept any input that the user may enter, even if it is messed up, entered in in the wrong format, or inappropriate for the variable that you are storing it to. This way instead of the program crashing when it gets bad input, it can actually handle it and do whatever it needs to to make it work. You then just check to see if the string has the appropriate value(s), and convert it to the desired variable using the expr( command.

The validation for the real variable, for example, did not include a check for whether it is a list or string. This is because you can only really check for those things when you have a string that you can manipulate. If we wanted to add that check, we can search the string for an opening curly brace and commas or any characters besides numbers. If we find those things, we know that the input is bad, and we can reject it and ask for input again.

In the case of the real variable, the other advantage of using a string is that you don't have to worry about whether the calculator is storing the variable to a list. More specifically, if a list is entered for input, the Input and Prompt commands will actually store the input to a list with the same name. This is possible because you don't need to include the character when referring to a user-defined list. (Entering a string would also work, and the string would become associated with the list.)

Besides checking the list for whether it is a real variable or string, you also can check that it is in the appropriate format. When a list is entered, it needs to start with an opening curly brace, and then have each element separated by a comma. Because most users forget to include the opening curly brace, it is very convenient to place that at the beginning of the list yourself, so that the user never even knows about needing it.

You can take that idea even further, and allow a list to be entered in many different ways: with commas and curly brackets, with commas and no brackets, as a list name (L1 through L6), or as a name starting with ∟, or as a name without the ∟. Instead of requiring one of these, the program might very well be programmed to handle all of them. This all comes down to finding alternate ways of making user input valid.

Thoughts to Consider

The amount of validation you put in a program depends on how large and/or complicated the program is. If you have an extremely complex game with all sorts of user input, then it would be appropriate to include validation for some or most of those things. The general guideline is that the amount of validation needed correlates to the size of the game — i.e., a short math routine probably wouldn't need validation.

While discussing validation, it is also important to mention that since the Input and Prompt commands only work on the home screen, you need to write your own custom input routine using the getKey command if you want to get input on the graph screen. If your entire program is already on the graph screen, however, this should not be an issue, because it makes perfect sense to maintain the user's attention on the graph screen.

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/validation


Good programmers usually design their programs to utilize subprograms (calling another program from within the program) for optimization but another alternative that is available, but less often used, is the program simply calling itself — more commonly known as recursion.

The basic premise behind recursion is breaking up a problem into smaller problems, and then working your way through each problem until they are all completed. By tackling one small problem at a time, instead of the entire problem, the code needed is typically not only smaller and easier to understand (i.e., more manageable), but also tends to be faster.

However, recursion isn't always the most appropriate approach. You can usually rewrite a recursive program to use iteration instead (whether it's a While, Repeat, or For( loop). While the iteration code may be larger, it doesn't need the additional memory for each call that the program makes like recursion does. Iteration is also better when trying to implement an algorithm with recursion isn't very practical.

Problems with Recursion

There are some problems you will come across when trying to use recursion in your programs. Each of these problems is inherent to TI-Basic because of the way TI designed it, which means you can't change them. Fortunately, you can use some creative thinking to work around them.

The first problem you will come across is that you can only call a program a set number of times before you run out of memory and the program crashes — giving you the dreaded ERR:MEMORY error. The reason that this happens is because the calculator places each program call on a stack.

The program call stack is kept in RAM, so it is fine as long as its size doesn't exceed the amount of free RAM available. Each program call takes up approximately sixteen bytes, so just divide that by the free RAM to see how many program calls you can make.

Besides simply limiting the number of program calls you make in a program (i.e., trying to keep recursion to a minimum), a work around to this problem is storing a special value to a variable (something unique that wouldn't be entered by accident), displaying a message to the user telling them to "Press ENTER" and then stopping the program with the Return command after a set number of program calls have occurred.

:Output(4,4,"Press ENTER

Once the user presses ENTER, you will want to include a check at the beginning of the program for the variable you used to see if its value is equal to the unique value you assigned it. If it is, you then can jump to the place in the program where you left off before. You also want to give the variable a new value so that the program won't accidentally jump to the place in the program when the program is next executed.

:If A=312958  // Check if variable equal to unique value
:Goto A
:Lbl A
:1→A  // Reset variable to a new value

Another problem you will cross across is that TI-Basic programs don't have return values. In 68k TI-Basic (which is much more powerful overall), a return value can be passed to the calling program, which can then use it however they want (for example, to determine which course of action to take next).

While you can't add a return value to a program, you can mimic that functionality using a variable. The best variable to use is Ans because it can take on whatever value and variable type you want, so the program doesn't have a specific variable hard-coded in. This is especially important because variables are shared by every program.

Related to creating a return value is the problem of creating (pseudo) local variables. As you deal with each program call in recursion, it is useful to be able to keep track of variables and how they change from one program call to the next. While there are no local variables, you can make a list perform in that capacity.

In addition to the list itself, an index variable that keeps track of where you are in the list is also required. Whenever you enter a program that needs a local variable, increase the index variable and add a new element to the end of the list with augment(∟NAME,{var}). When you exit the program, decrease the index variable and remove the element from the list with var→dim(∟NAME). You can access the local variable at any time with ∟NAME(var).

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/recursion


Besides TI-BASIC, assembly is the other primary programming language available for the TI-83 series calculators. Unlike TI-BASIC, which uses commands and functions that are easy to understand, assembly is programmed in the calculator's own machine language. Thus, it is much harder to program in and read.

This lack of usability, however, is more than made up for when you consider the fact that assembly is much faster and more feature rich than TI-BASIC. Games that normally can't be done (or, if they can be done, they aren't done very well) in TI-BASIC are just considered average in assembly.

At the same time, there aren't as many assembly programmers compared to TI-BASIC programmers; and subsequently, effort has been made to enhance TI-BASIC using assembly, to make it more capable of quality games and programs. This includes:

  • Shells — A shell allows a person to run a program from inside one central place, and since most assembly programs are run through a shell, it makes sense to run your TI-BASIC programs there as well.
  • Libraries — A library enhances a TI-BASIC program in some way, providing support to an internal function of the calculator (such as lowercase text) or access to a peripheral of the calculator (such as the USB port on the TI-84+/SE). If the ones available do not suit you, you can even make your own.
  • Applications — BASIC Builder allows you to package your TI-BASIC program(s) into a Flash application, which appears in the APPS menu and gets executed just like a regular assembly application.

The TI-BASIC language itself provides three commands — Asm(, AsmPrgm, and AsmComp( — for running and compiling shell-independent assembly programs, which you simply run from the home screen or inside a program. Writing these kinds of assembly programs is actually more difficult, however, because the assembly language instructions are represented as hexadecimal numbers. There are some short assembly programs that can be written on the calculator and be of great use to BASIC programmers.

Two additional commands for running assembly programs have been added on the TI-84 Plus and TI-84 Plus SE calculators: OpenLib( and ExecLib. They can be used for running routines from Flash application libraries that have been specifically written for use with these commands. So far, however, most major libraries use other methods, for compatibility with pre-TI-84 calculators. The only existing software that uses OpenLib( and ExecLib is usb8x, a library for advanced use of the TI-84 Plus/SE USB port. Here, compatibility is obviously out of the question.

(For more on assembly, visit z80 Heaven and WikiTI.)

For the most up-to-date version of this command, see http://tibasicdev.wikidot.com/assembly

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License