Color Guide:
Red indicates a command (or commands) you enter into the MUCK.
Navy indicates text returned by the MUCK, usually a result of a command you typed.
|
|
MUF: Hyacinth's Amulet-- Arrays on Fuzzball 5.x
by Silver of A Bug's Muck
Fairies have magical powers, and teleporting themselves around in true fairy-fashion is often one of them. Hyacinth the fairy-ant is no exception, and here's the magic behind how she does it. This program is essentially a glorified teleporter. It can give an object special teleport functions through an action set on that object. For example, using an action called 'wish' linked to the program, the user can say 'wish Walter, Tara and me to Gem', and Walter, Tara and the user will be moved to Gem's location, with a few messaging effects thrown in.
This program illustrates an implementation of an array in Fuzzball 5.x. I code in a FB 5.x environment, and arrays are not implemented on versions of Fuzzball less than 6.0. so being a bit contrary I made one anyway, and here's how you can too. This array solution makes use of MUF's variable space which limits you to approximately 50 array elements, but it's still handy.
First, here's the complete amulet program:
The Basic Trick
If you poke around with MUF long enough, you'll notice that the pre-defined variables me, loc and trigger are the same thing as 0 variable, 1 variable and 2 variable respectively. So if I want the user's dbref, I can say 'me @' or I can say '0 variable @'. Notice their integer sequencing: 0, 1, 2. If I make my own variables using lvar, the first one I make will occupy the next available interger position, 3. Here's a variable list where I've defined two custom local variables:
MUF code:
(lvar me)
(lvar loc)
(lvar trigger)
lvar myvar_1
lvar myvar_2
|
Variable sequence:
0
1
2
3
4
|
Me, loc and trigger are represented by comments in this example. We don't declare them (they're pre-declared for you), but they're there all the same. You can get at the value of myvar_1 by saying 'myvar_1 @', or by saying '3 variable @'. That was a case with 2 local variables, but I could just as easily have declared n local variables:
MUF code:
(lvar me)
(lvar loc)
(lvar trigger)
lvar myvar_1
lvar myvar_2
. . .
lvar myvar_n
|
Variable sequence:
0
1
2
3
4
...
n+2
|
If I store something in myvar_2, I can fetch its value with 'myvar_2 @' or with '4 variable @'. If we had n custom variables declared, then you could get to myvar_n by saying n+2 variable @. For example, if the number of custom local variables was stored in a variable called max_vars, we could say 'max_vars @ 2 + variable @' We need the +2 to skip over the standard MUF variables, me, loc and trigger. The +2 can be thought of as an 'offset'.
Now here's the cool thing: The variable space still exists even if you dispense with the declaring of variable names. For example, if we declare no local variables at all, you can still do this: '3 variable !' to store a value in a variable slot just above trigger, then retrieve the value at some later time with '3 variable @'. Why declare variables at all? Because remembering a name is easier than remembering some dumb number. But, it's not essential.
Put all this together, and all the components are there to make an array, and better yet, an array that you don't have to pre-define its maximum size. The array can grow as large as we need at run-time. This type of array is called a 'dynamic array' as opposed to a 'static array' where you have to declare its size. Essentially you have a second stack at your disposal!
Hyacinth's amulet uses this happy situation to store the dbrefs of those characters who are being teleported. It's easy, and a lot handier than kludges like property-arrays or string-based arrays. It's also faster. All you have to do is determine the offset to your array in the variable list, and make a few small functions to handle reading and writing to your array. These functions can be standardized and used in all of your programs where an array would be useful.
A Second Stack
To make a dynamic array from the MUF local variable space, you have to track two things: the initial position of the array, and the last position occupied by the array. Look at the following example:
MUF code:
(lvar me)
(lvar loc)
(lvar trigger)
lvar myvar
(array [1])
(array [2])
(array [3])
(array [4])
|
Variable sequence:
0
1
2
3
4
5
6
7
|
Here we declared one local variable and we have an array currently with 4 elements. The index of the first position in the array is 4. That is, we can get to it by '4 variable @'. The index of the last position is 7. We can also find the last position if we know the number of elements in the array. In this array the number of elements is 4, so the last position can be found by: first position + number of elements - 1, in this case 4 + 4 - 1 = 7. Because we're implementing a dynamic array (one that grows or shrinks in size like a stack), tracking the number of elements is handier than tracking the last position.
So we need to know the first position of the array, and the size of the array. We can do this by adding one local variable associated with the array called an 'offset' that we can always use to find the first position in the array. Remember we aren't using names for the array elements, we're using the variable sequence which is just a number, so we need a fixed number (the offset) that we always add to the index of an array element to skip over any regular named local variables and get to the variable representing the array element. This will make more sense in a moment. Next we need a way to track the number of elements in the array. I do this by making the first position of the array a kind of counter and referring to it as element 0, or array[0]. Given the last example with an array containing 4 values, here's what it now looks like:
MUF code:
(lvar me)
(lvar loc)
(lvar trigger)
lvar myvar
lvar offset
(array [0])
(array [1])
(array [2])
(array [3])
(array [4])
|
Variable sequence:
0
1
2
3
4
5
6
7
8
9
|
Notes:
Initialize this to 5
Array element counter, currently should be 4
|
Using the above example of an array with 4 elements, say we want the 2nd element of the array, written as array[2]. We can't say '2 variable @', that would give us the trigger. We have to add the offset to the element we're looking for. The offset in this case is 5, so we can say '2 offset @ + variable @'. If we want to know the current size of the array (stored in array[0]) we can say '0 offset @ + variable @'. This translates to '5 variable @', which if you look at the above table, is retrieving the value stored at position 5, the number of elements in the array. Obviously the addition of 0 is unnecessary, but I put it there to illustrate that we're retrieving the element array[0].
Here's a test to see if you're following. Given that the number of elements in the array is stored in array[0]. What's the MUF code to retrieve the value stored in the last element of the array no matter what the number of elements is? Find the answer at the bottom of the page.
How To Do It
Despite all that explanation, implementing this stuff is pretty easy. Remember our array works just like the MUF stack. If we want to add something to the array, we'd increment the size of the array by 1 and then save whatever it is at the maximum array position:
(Pushing an element onto the array:)
"Locus"
offset @ variable @ (Get current size)
1 + (Increment size of array)
dup offset @ variable ! (Save new size)
offset @ + (Get the last array position)
variable ! (Save 'Locus' in the last position)
Dropping the last element of the array is easier:
(Popping the last element of the array:)
offset @ variable @ (Get current size)
1 - (Decrement size of array)
offset @ variable ! (Save new size)
All we did was decrement the array counter. Whatever was in the former last position is still there, but we don't care about it. MUF's 'pop' primitive operates on the stack in the same way.
Two more tools and our second-stack implementation is complete. We need a way to retrieve elements from our array, and replace the value of an existing element:
(Retrieve the value of an array element:)
3 (Assume we want the third element for this example)
offset @ + (Get the element's position)
variable @ (Fetch it)
(Replace the value of an array element:)
"Ampillion"
3 (Assume we want to replace the third element for this example)
offset @ + (Get the element's position)
variable ! (Save Ampillion as the third element)
I've coded up some general-use versions of the above four functions plus a few more that might be useful and packaged them as a library:
array_push [ x a -- ]
Push value x onto array designated by an offset lvar a.
Example: 'Howdy' my_array array_push
array_pop [ a -- ]
Pop the last element of an array designated by an offset a.
Example: my_array array_pop
array_pick [ i a -- ]
Retrieve the ith element of an array designated by an offset a.
Example: 1 my_array array_pick
array_put [ x i a -- ]
Replace the ith element of an array designated by an offset a.
with the value x. Example: 'Howdy' 1 my_array array_put
array_size [ a -- ]
Retrieve the size of an array designated by an offset a.
Example: my_array array_size
array_setsize [ i a -- ]
Sets the size of an array a to i.
Example: 4 my_array array_setsize
array_init [ x a -- ]
If the array size is not 0, initializes all elements to the value x.
Example: 0 my_array array_init
array_dump [ a -- xn...x1 n]
Push all elements of the specified array onto the stack, with the
number of elements on top. First in array is top of stack.
If you don't want to fool with adding a library, you can just select the functions you want and copy and paste them into your program.
If you use this stuff to make an array in your programs, remember three basic things:
- Declare a variable for your array offset, and name it something representative of your array, since it's the handle used by all the array functions.
- In your main program (or before using the array), set the variable offset of the array.
- In your main program (or before using the array), initialize the size of the array. You can do this easily with array_setsize. (Note that this is really only necessary with static arrays that have a declared size (see below). For a stack-like array, you can ignore this step, because MUF appears to initialize variables to 0 by default.)
Multiple Arrays
Can you make more than one array? Sure! Remember I mentioned static arrays, whose number of elements is pre-determined, and dynamic arrays, whose number of elements grows or shrinks like a stack. You can have any number of static arrays, but only one dynamic array using these methods. Let's set up an example and I'll show you why.
MUF code:
(lvar me)
(lvar loc)
(lvar trigger)
lvar who
lvar what
lvar array1
lvar array2
(array1 [0])
(array1 [1])
(array1 [2])
(array1 [3])
(array1 [4])
(array2 [0])
(array2 [1])
(array2 [2])
. . .
|
Variable sequence:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
|
Notes:
Initialize this to 7
Initialize this to 12
Array1 size, initialize to 4
Array2 size, initialize to 0
|
Array 1 is a static array that has space reserved for 4 elements. Array 2 is a dynamic array, just like our previous examples. The variables array1 and array2 are the offsets for the arrays. If you look at the example, you can see that if you tried to push 5 elements onto array 1, you'd start over-writing the positions of array 2, and things would quickly get messed-up. Array 2 can grow without restrictions, since there's no reserved space for an array beneath it. Most programming languages that support arrays have 'range checking' to throw an error if you try to do this sort of thing. You can implement your own range checking in your MUF functions, or just be careful in what you do.
To work with a static array you need only two of the functions I've coded for you, array_put and array_pick. You set the size of the array (in array[0]) for your static arrays when your program starts, and never touch it again. For example, 4 0 array1 array_put. Never use the array_push or array_pop functions on static arrays, because they will change the number of elements in the array, a Bad Thing for static arrays.
In the above example, you could do the following:
2 array1 array_pick
#1184 2 array1 array_put
"Cool-O" array2 array_push
array2 array_size array2 array_pick
|
(This gets the 2nd element of array 1)
(This replaces the 2nd element of array 1 with a dbref)
(This adds a string to the second array)
(This gets the last element or top of the second array)
|
When using static arrays, it's often a good idea to initialize your array elements with some value, like 0 or a null string, "". You can do this with the array_init function. Normally you wouldn't do this with a dynamic array.
That's pretty much all there is to my solution for arrays in FB 5. I hope this wasn't too confusing and gives you the confidence to use arrays in your programs.
Answer: offset @ variable @ offset @ + variable @
First get the value of the location pointed to by the offset, which is array[0]. This will be the number of elements in the array. Then add the number of elements to the offset to get the location of the last element, and fetch it.