This is going to be a monster tutorial that'll take a while for me to write, so here are the first five parts:
Arrays and Bullet CodeAxe doesn't natively support any data structures, but that doesn't mean you can't use any in your Axe programs. Because Axe allows you to manipulate the bytes and bits of your calculator all you want, you can make your structures, made in whatever way best suits you. Axe is a versatile tool.
One of the most useful data structures (by far) is the array. An array is just a list of data elements, which could be anything—bullets, enemies, lobsters, etc. In fact, if you think about it, a tilemap is a type of array (a two-dimensional one).
So how do you make an array in Axe? Well, first you need to decide where to put it. Any safe RAM area (L1, L2, etc.) will do. Just make sure it's reasonably big enough. Arrays can take a lot of memory.
Now decide how each element is going to be stored. What are you representing with each element? Just for the sake of example, let's say you're making a program to keep track of some squares floating around the screen but always going in one direction. You'd need to keep track of its X and Y values, as well as how fast it's moving horizontally and vertically. That would be four bytes per element. (You could probably cut it down to three or even two if you desparately neede to, but I'll keep things simple.) The array could look like this:
Like with everything in Axe, we start counting from 0 because it makes our lives much, much easier.
To start working on our example, make a program called ASQUARE as follows:
This will be our main program. It should be pretty simple to understand: it first initializes some data (the code for which we're going ot put in a subprogram called ASQUI), then goes in a loop where it draws the squares until you quit. prgmASQUR will hold our subroutines.
For program ASQUI, just put this in:
That's it. L will hold the current size (length, number of elements) of the array. It'll get updated whenever we modify the array (in the subroutine itself).
Displaying objects in the arrayAn array of data (or any other data structure) is pretty much useless unless you do something with it. Here we'll make the subroutine DA that displays all the elements in the array.
First, make a new program called ASQUR and put this inside. All our routines will go in it.
Variables:
The only global variable you really need will be one giving you the size (length) of the array (L).
L - current length of array (initialized to 0 at the beginning of the program and updated by the routine itself)
Input:
None.
Output:
None. It draws the sprites to the buffer, but that's pretty much it.
Code:
You probably don't need too much explaining, so I'll keep it short. First, there's "If L." This is to avoid an (almost) infinite loop when L happens to be zero. (We loop from 1 to L and subtract four more instead of from L-1 to L-1 because it's a lot faster to check each pass of the loop.) Then the routine loops through every element, using Pt-Change( to display each one.
We use Pt-Change( here because it's the easiest to work with when you want to draw a moving object. It works like this: Just before the main program calls DispGraph, we draw all the squares onto the screen so they appear, and once the screen finishes displaying, all the squares get removed with the same routine to make it "clean" again. This makes things easier if you're making a complex tilemap game where redrawing the entire screen every frame takes too much time.
Anyway, now that you've defined the subroutine, you can compile your program now! And run it! what does it do?
Nothing. It waits until you press CLEAR. That's because you haven't added any elements to the array yet. We'll get there in the next step.
Manipulating the arrayNow a routine to add (push) an element to the end of the array. Put this in program ASQUR.
Variables:
The only global variable you really need will be one giving you the size (length) of the array. Let's call it L.
L - current length of array (initialized to 0 at the beginning of the program and updated by the routine itself)
Input:
Since each element holds four values, let's make each one an argument.
r1 - X-value of new square
r2 - Y-value of new square
r3 - X-speed of new square
r4 - Y-speed of new square
Output:
None. (Of course, you could easily modify it to return a useful value, such as a pointer to the element added.)
Code:
Scary? Fine, I'll break it down.
Before it does anything, it checks if there are 177 elements in the list. This is because L1 can only hold 714 bytes of data, which is approximately 178*4 elements. If you're using any safe RAM location, make sure you change this limit accordingly. Having too many elements is called an overflow, which could mess up whatever data comes after L1 (in this case the variables A through T.
First look at the very inside of the mess of braces, at the line "L+1?L*4+L1-4." The real action starts there. First, it increments L by one (you probably know why). Since that command returns the value of L, you can keep doing operations on it (multiplying by four in this case). Since each element in our example is four bytes long, L*4 gets the offset of the next element in the array. But there's a problem here: Since you incremented L already, this now points four bytes ahead of where you're supposed to be. We take care of this by adding only L1-4 to the total.
That gives us a pointer to where to store the first byte of the element, so "r1?{L+1?L*4+L1-4}" would store the first byte there.
Now here's the fun part: By storing to a variable location, the pointer you stored to is returned in HL. That means that you can keep on storing to the byte after it by simply adding one! That's why the line above is enclosed by "r2?{ ... +1}": you just add one to get the next byte, then store to it. You can keep going like this for as long as you want; it's the single most optimized way to store a mass of variable data in Axe!
Next up we'll actually add the pretty squares. Promise.
Actually doing somethingFinally we're going to add the actual enemies (squares). Here are some ideas for how they should be added:
- Enemies always start at the center of the screen. That would be (44, 28).
- Enemies should move in a random direction.
- Enemies spawn at random times.
To do this, we need to back to the main program, into the main loop (Repeat getKey(15):End). Change prgmASQUARES into this:
All the changes come just before the first sub(DA).
You probably understand this too. If a random two-byte integer is less than 4096 (that's a chance of 4096/65536, or 1/16), stick a square in the middle of the screen and give it a random X- and Y-speed. Then quit when the user presses CLEAR.
Before we compile, there's one thing that's missing. Something that every enemy/bullet system needs to have—movement. So change prgmASQUARES again:
All the new additions are right after the ones you just added. The point here is that it has to be before the first sub(DA). Otherwise, you'd be changing the squares' position after they get drawn but before they get erased, so they'd end up being "erased" from a different location.
Well? Compile and run!
It works! Amazing, eh? You can let it run for as long as you want, and even though it slows down quite a bit with a lot of enemies on the screen, they all move on their own!
The only problem now is that they keep wrapping around the screen. Usually that's not what you want to happen, since if an enemy or bullet goes off the screen, it should stay off the screen. We'll take care of that in the next section.
Getting rid of the extrasTo make the squares more realistic enemies, we're going to remove them when they go off the screen. That calls for a new routine:
Variables:
And yet again, you need L.
L - current length of array (initialized to 0 at the beginning of the program and updated by the routine itself)
Input:
Well, we need to know which element ot remove.
r1 - Index of element to remove
Output:
None. (Again, you can modify it to return something useful, but we won't here.)
Code:
You can probably tell that it's a copy statement. What we're trying to do is copy everything after the element to be removed four bytes back—overwriting the element you want to remove. The routine also takes care of the array length variable (L) by subtracting one.
The only thing that should seem weird here is the extra "+1" at the end. It might not make sense, but it takes care of the case where you're trying to remove the very last element. If that extra little bit of code weren't there, the Copy( statement would try to copy 0 bytes backwards, which gets translated to a copy a 65536 bytes, which is definitely not what you want. This extra bit doesn't do us any harm besides slowing the program down a tiny, tiny bit, but it takes care of that scenario for us.
So now that you have the routine down, let's actually use it. Go back into the main program and change it to this:
All the changes are in the For(I,1,L) loop. It basically tests the X- and Y-values after they're changed to see if they're completely off the screen, and if so, they get removed. The reason I jump to a lable RM instead of writing the code twice is because it's smaller and even gets rid of any chance of some certain nasty coincidences I won't talk about here. The point is it works.
Arrays in arrays (in arrays)So there you are. Arrays in Axe.
Before we leave you to coding in peace, there's one last trick we're here to teach you: how to make arrays whose elements are arrays in their own right, or arrays of arrays.
Arrays in arrays? What's the point of that?
Surprisingly, you can do a lot with them. For example, say you have a puzzle game where each level has a fixed set of enemies. Instead of making a gigantic if-elseif-elseif-elseif... structure, you can put the enemy data for each level in an array, then organize that whole thing into an array.
But here's the problem: How do you store an array of elements that aren't a fixed size? The real problem comes in parsing it—how could you loop through a list whose elements could be any number of bytes long?
Like with any varying array, the solution comes in embracing the pointers. By that we mean simply that you don't have to put the structures themselves into the array; just point to them. Here's an example.
First, create the raw data you're going to put into an array.
Here are the first-level arrays:
Notice we use Data( with each pointer followed by an r (since pointers are always two bytes). The 3 and 2 in the beginning denote the number of elements in that array, since this value can change. Now it's the arrays' turn to be referenced from an array, in much the same way:
The increments are there to offset the length prefixes on those arrays.
To do something with this, let's loop through the array and all its arrays to print whatever is stored there:
With that, let's call it a day. Enjoy your newfound powers and happy coding!
[img]http://clrhome.org/