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

The code below allows you to move an X around on the home screen, within the boundaries of the four sides of the home screen. This is the same screen that you perform math on and execute programs from. You can end the program by pressing either [ENTER] or [CLEAR], in case you don't feel like having to reach all the way down to the bottom row of keys.

:4→A:8→B
:Repeat 30=abs(K-75
:getKey→K
:If max(Ans={24,25,26,34
:Output(A,B," // 1 space
:A-(Ans=25 and A>1)+(Ans=34 and A<8→A
:B-(K=24 and B>1)+(K=26 and B<16→B
:Output(A,Ans,"X
:End

The Explanation

The first thing we need to do in the program is initialize the coordinates of the X character that we will be moving around on the screen. We need two separate variables to keep track of the coordinates: a variable for the row (A) and a variable for the column (B). I have chosen 4 and 8 respectively for the initial values of the variables because that places the X at the center of the screen. This is just a personal preference, though.

While you can use any variables for this (including lists, matrices, and even strings), you should always try to use real variables because they are the smallest and fastest variables. Besides the real variables, you can also use the Ans variable in certain special locations within a program. We will use the Ans variable multiple times later in the program.

Some of you who are somewhat familiar with TI-Basic may have noticed that I have left off the ClrHome command at the beginning of the program. The reason is partly because I want to show you the importance of clearing the home screen before displaying text, but also because it provides you another opportunity to experiment with the commands so that you can become more familiar with them.

:4→A:8→B

After initializing the variables, we then start the main program loop. The main program loop contains the primary code that will be repeated while the program is running. While we could have used any one of the three different loops that exist in TI-Basic for the main program loop (i.e. While, Repeat, or For), the Repeat loop was chosen because it always loops at least once. This allowed us to not have to initialize the variable in the loop condition. The loop condition will be explained later.

:Repeat 30=abs(K-75

Next we read the keypresses of the user with the getKey command. This information is stored into a variable because it is used to determine when the main program loop should end and to move the X character around the screen. I have chosen the K real variable because 'K' is the first letter of keypress, so it makes it easy to remember what the variable is being used for.

While this is primarily just a personal preference, it is good to consistently use the same variables for the same purposes in a program. It's not that important in this program because it is small, but it is definitely important in large programs. After programming for a while, you will begin to develop a habit for which variables you use. Just remember to be consistent.

:getKey→K

We then check for the keypress value. Remember how I talked about using the Ans variable in special locations? Well, this is one of them. The Ans variable assumes the value of the last variable storage that has taken place, and because the variable was on the line above, we can use the Ans variable instead of K. You might notice the complicated looking condition inside the conditional. Each of those numbers corresponds to one of the arrow keys — left (24), up (25), right (26), and down (34).

You might be asking why we even need a conditional there. Couldn't we just have the getKey command by itself? Wouldn't that work just as well? While the program would run the same way, the program would not look the same. The conditional is there to prevent the flickering that would occur when none of the appropriate keys has been pressed but the X is erased anyways. We don't want the X character to be erased unless the user has pressed one of the arrow keys.

If you just want to simply test to see if a key was pressed (you don't check for a particular key value), you can just put Ans by itself. This works because getKey returns zero if no key was pressed, so as long as zero is not returned the conditional is true. Of course, this means that there will still be flickering if the user presses any other key besides one of the arrow keys.

:If max(Ans={24,25,26,34
:Output(A,B," // 1 space

The next two lines are the most complicated, yet most important in the entire program. They are used for moving the X character around on the screen. Each of the two coordinate variables A and B is modified using a piecewise expression based on the key that was pressed.

The first piecewise expression deals with the A variable, which controls the row value. One is subtracted from A when the up arrow key is pressed (K=25) and A is greater than one (i.e. the character is not on the first row of the screen), while one is added to A when the down arrow key is pressed (K=34) and A is less than eight (i.e. the character is not on the eighth row of the screen).

The second expression deals with the B variable, which controls the column value. One is subtracted from B when the left arrow key is pressed (K=24) and B is greater than one (i.e. the character is not on the first column of the screen), while one is added to B when the right arrow key is pressed (K=26) and B is less than sixteen (i.e. the character is not on the sixteenth column of the screen).

Note that we again used the Ans variable, in the first of these two lines. Since there were no variable storages in between the keypress, the Ans variable still has the same value. Although the real variable and Ans variable are both the same size, the Ans variable is somewhat faster. Since the first line modifies the Ans variable, we still have to use the real variable in the second line.

:A-(Ans=25 and A>1)+(Ans=34 and A<8→A
:B-(K=24 and B>1)+(K=26 and B<16→B

Now, you are probably wondering why we just didn't use four separate If conditionals instead of combining them into two piecewise expressions? Why does it need to be so complicated? The reason is that using four If conditionals would slow down the program considerably, due to the addition of six more lines of code.

If you're not convinced that the piecewise expressions eliminate the need for If conditionals in this program and/or that If conditionals would slow down the program, try using the four separate If conditionals and see the affect for yourself. As I want to drive home the point, I have provided the If conditionals code below for you to type into your calculator. Please do type it in and see for yourself because that's the way you learn and grow as a programmer.

:If K=25 and A>1:A-1→A
:If K=34 and A<8:A+1→A
:If K=24 and B>1:B-1→B
:If K=26 and B<16:B+1→B

After adjusting the two coordinate variables, we then display the X character at its new position on the screen. Since the Ans variable currently contains the B variable's value, we can use Ans instead of B for displaying the column. In this case, though, it doesn't have much impact on the program, and is really just a personal preference. I have left it in simply to show you what the optimized version of the program looks like.

:Output(A,Ans,"X

Finally, we close the main program loop with an End command. The condition in the loop is tested when the program reaches the End command, so the program will now check the K variable to see if the user has pressed either [ENTER] or [CLEAR].
If K is neither value, then the main Repeat loop will be repeated again.

Because it's hard to see where the check for [ENTER] or [CLEAR] is at, let me explain. The [ENTER] key has a value of 105, while the [CLEAR] key has a value of 45. These two values might seem completely unrelated, but in actuality they are separated by a positive value (60). A positive value is important because it allows us to split the difference and use the abs( command to check for both values at the same time. This saves us from having to do an unnecessary check for the second value.

:End

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.

:A+1→A
:If A<11
:YE2+X→L1(A

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
:For(X,1,A-1
:L1(X+1→L1(X
:End
:A-1→A
:End

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 simplest method is to use a matrix. Create a matrix with 8 rows and 16 columns, one for each row and column on the home screen.

Leave every space empty (0) except where there should be a wall. To designate a wall, use some other digit such as 1. If you want, other digits can designate the type of tile instead of just having all walls. For example, 1 could mean wall, but 2 means doorway and 3 means water. Here is an example of a matrix set up for two types of collisions: walls (1) and traps (2):

[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
[1,0,0,0,0,0,0,1,2,1,0,1,0,1,0,1]
[1,1,1,1,1,0,0,1,0,1,0,0,0,2,0,1]
[1,2,0,0,1,1,0,0,0,0,0,1,0,0,0,1]
[1,1,1,0,1,0,0,2,1,1,0,1,0,1,0,0]
[0,0,0,0,1,0,1,0,1,1,1,1,1,1,0,1]
[1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1]
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1→[A]

Notice how the "maze" is set up so that the outer boundaries are all walls. This limits the size a bit, but means that there is no need to make sure the player's coordinates are "in bounds" since he can't go through the walls.

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 into an open spot or not. This code is optimized as much as possible, so just try to understand it and see what is going on.

:ClrHome
:4→A:8→B
:[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
[0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0]
[0,0,1,0,0,0,0,1,1,0,0,0,0,1,0,0]
[0,0,1,0,0,0,0,1,1,0,0,0,0,1,0,0]
[0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0→[A]
:For(Y,1,8
:For(X,1,16
:If Ans(Y,X
:Output(Y,X,1
:End:End
:Repeat 30=abs(K-75
:getKey→K
:If max(Ans={24,25,26,34
:Output(A,B," // 1 space
:A-(Ans=25 and A>1 and not([A](A-(A>1),B)))+(Ans=34 and A<8 and not([A](A+(A<8),B→A
:B-(K=24 and B>1 and not([A](A,B-(B>1))))+(K=26 and B<16 and not([A](A,B+(B<16→B
:Output(A,Ans,"X
:End

In addition to using a matrix, you can also use a list or string variable. The code is basically 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.

:ClrHome
:4→A:8→B
:{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0
0,0,1,0,0,0,0,1,1,0,0,0,0,1,0,0
0,0,1,0,0,0,0,1,1,0,0,0,0,1,0,0
0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0→L1
:For(Y,1,8
:For(X,1,16
:If L1(X+16(Y-1
:Output(Y,X,1
:End:End
:Repeat 30=abs(K-75
:Output(A,B,"X
:Repeat Ans
:getKey→K
:End
:Output(A,B," // 1 space
:A-(K=25 and A>1 and not(L1(B+16(A-1-(A>1)))))+(K=34 and A<8 and not(L1(B+16(A-1+(A<8→A
:B-(K=24 and B>1 and not(L1(B-(B>1)+16(A-1))))+(K=26 and B<16 and not(L1(B+(B<16)+16(A-1→B
:End

References

  • Kerm Martian and his post at the UTI TI-Basic forum about keeping track of multiple shots.
Unless stated otherwise Content of this page is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License