blob: f9867373a785d3fc4beb02716d38a608eedcaf50 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001Intermediate LPC
2Descartes of Borg
3November 1993
4
5 Chapter 3: Complex Data Types
6
73.1 Simple Data Types
8In the textbook LPC Basics, you learned about the common, basic LPC
9data types: int, string, object, void. Most important you learned that
10many operations and functions behave differently based on the data type
11of the variables upon which they are operating. Some operators and
12functions will even give errors if you use them with the wrong data
13types. For example, "a"+"b" is handled much differently than 1+1.
14When you ass "a"+"b", you are adding "b" onto the end of "a" to get
15"ab". On the other hand, when you add 1+1, you do not get 11, you get
162 as you would expect.
17
18I refer to these data types as simple data types, because they atomic in
19that they cannot be broken down into smaller component data types.
20The object data type is a sort of exception, but you really cannot refer
21individually to the components which make it up, so I refer to it as a
22simple data type.
23
24This chapter introduces the concept of the complex data type, a data type
25which is made up of units of simple data types. LPC has two common
26complex data types, both kinds of arrays. First, there is the traditional
27array which stores values in consecutive elements accessed by a number
28representing which element they are stored in. Second is an associative
29array called a mapping. A mapping associates to values together to
30allow a more natural access to data.
31
323.2 The Values NULL and 0
33Before getting fully into arrays, there first should be a full understanding
34of the concept of NULL versus the concept of 0. In LPC, a null value is
35represented by the integer 0. Although the integer 0 and NULL are often
36freely interchangeable, this interchangeability often leads to some great
37confusion when you get into the realm of complex data types. You may
38have even encountered such confusion while using strings.
39
400 represents a value which for integers means the value you add to
41another value yet still retain the value added. This for any addition
42operation on any data type, the ZERO value for that data type is the value
43that you can add to any other value and get the original value. Thus: A
44plus ZERO equals A where A is some value of a given data type and
45ZERO is the ZERO value for that data type. This is not any sort of
46official mathematical definition. There exists one, but I am not a
47mathematician, so I have no idea what the term is. Thus for integers, 0
48is the ZERO value since 1 + 0 equals 1.
49
50NULL, on the other hand, is the absence of any value or meaning. The
51LPC driver will interpret NULL as an integer 0 if it can make sense of it
52in that context. In any context besides integer addition, A plus NULL
53causes an error. NULL causes an error because adding valueless fields
54in other data types to those data types makes no sense.
55
56Looking at this from another point of view, we can get the ZERO value
57for strings by knowing what added to "a" will give us "a" as a result.
58The answer is not 0, but instead "". With integers, interchanging NULL
59and 0 was acceptable since 0 represents no value with respect to the
60integer data type. This interchangeability is not true for other data types,
61since their ZERO values do not represent no value. Namely, ""
62represents a string of no length and is very different from 0.
63
64When you first declare any variable of any type, it has no value. Any
65data type except integers therefore must be initialized somehow before
66you perform any operation on it. Generally, initialization is done in the
67create() function for global variables, or at the top of the local function
68for local variables by assigning them some value, often the ZERO value
69for that data type. For example, in the following code I want to build a
70string with random words:
71
72string build_nonsense() {
73 string str;
74 int i;
75
76 str = ""; /* Here str is initialized to the string
77ZERO value */
78 for(i=0; i<6; i++) {
79 switch(random(3)+1) {
80 case 1: str += "bing"; break;
81 case 2: str += "borg"; break;
82 case 3: str += "foo"; break;
83 }
84 if(i==5) str += ".\n";
85 else str += " ";
86 }
87 return capitalize(str);
88}
89
90If we had not initialized the variable str, an error would have resulted
91from trying to add a string to a NULL value. Instead, this code first
92initializes str to the ZERO value for strings, "". After that, it enters a
93loop which makes 6 cycles, each time randomly adding one of three
94possible words to the string. For all words except the last, an additional
95blank character is added. For the last word, a period and a return
96character are added. The function then exits the loop, capitalizes the
97nonsense string, then exits.
98
993.3 Arrays in LPC
100An array is a powerful complex data type of LPC which allows you to
101access multiple values through a single variable. For instance,
102Nightmare has an indefinite number of currencies in which players may
103do business. Only five of those currencies, however, can be considered
104hard currencies. A hard currency for the sake of this example is a
105currency which is readily exchangeable for any other hard currency,
106whereas a soft currency may only be bought, but not sold. In the bank,
107there is a list of hard currencies to allow bank keepers to know which
108currencies are in fact hard currencies. With simple data types, we would
109have to perform the following nasty operation for every exchange
110transaction:
111
112int exchange(string str) {
113 string from, to;
114 int amt;
115
116 if(!str) return 0;
117 if(sscanf(str, "%d %s for %s", amt, from, to) != 3)
118 return 0;
119 if(from != "platinum" && from != "gold" && from !=
120 "silver" &&
121 from != "electrum" && from != "copper") {
122 notify_fail("We do not buy soft currencies!\n");
123 return 0;
124 }
125 ...
126}
127
128With five hard currencies, we have a rather simple example. After all it
129took only two lines of code to represent the if statement which filtered
130out bad currencies. But what if you had to check against all the names
131which cannot be used to make characters in the game? There might be
132100 of those; would you want to write a 100 part if statement?
133What if you wanted to add a currency to the list of hard currencies? That
134means you would have to change every check in the game for hard
135currencies to add one more part to the if clauses. Arrays allow you
136simple access to groups of related data so that you do not have to deal
137with each individual value every time you want to perform a group
138operation.
139
140As a constant, an array might look like this:
141 ({ "platinum", "gold", "silver", "electrum", "copper" })
142which is an array of type string. Individual data values in arrays are
143called elements, or sometimes members. In code, just as constant
144strings are represented by surrounding them with "", constant arrays are
145represented by being surrounded by ({ }), with individual elements of
146the array being separated by a ,.
147
148You may have arrays of any LPC data type, simple or complex. Arrays
149made up of mixes of values are called arrays of mixed type. In most
150LPC drivers, you declare an array using a throw-back to C language
151syntax for arrays. This syntax is often confusing for LPC coders
152because the syntax has a meaning in C that simply does not translate into
153LPC. Nevertheless, if we wanted an array of type string, we would
154declare it in the following manner:
155
156string *arr;
157
158In other words, the data type of the elements it will contain followed by
159a space and an asterisk. Remember, however, that this newly declared
160string array has a NULL value in it at the time of declaration.
161
1623.4 Using Arrays
163You now should understand how to declare and recognize an array in
164code. In order to understand how they work in code, let's review the
165bank code, this time using arrays:
166
167string *hard_currencies;
168
169int exchange(string str) {
170 string from, to;
171 int amt;
172
173 if(!str) return 0;
174 if(sscanf(str, "%d %s for %s", amt, from, to) != 3)
175return 0;
176 if(member_array(from, hard_currencies) == -1) {
177 notify_fail("We do not buy soft currencies!\n");
178 return 0;
179 }
180 ...
181}
182
183This code assumes hard_currencies is a global variable and is initialized
184in create() as:
185 hard_currencies = ({ "platinum", "gold", "electrum", "silver",
186 "copper" });
187Ideally, you would have hard currencies as a #define in a header file for
188all objects to use, but #define is a topic for a later chapter.
189
190Once you know what the member_array() efun does, this method
191certainly is much easier to read as well as is much more efficient and
192easier to code. In fact, you can probably guess what the
193member_array() efun does: It tells you if a given value is a member of
194the array in question. Specifically here, we want to know if the currency
195the player is trying to sell is an element in the hard_curencies array.
196What might be confusing to you is, not only does member_array() tell us
197if the value is an element in the array, but it in fact tells us which element
198of the array the value is.
199
200How does it tell you which element? It is easier to understand arrays if
201you think of the array variable as holding a number. In the value above,
202for the sake of argument, we will say that hard_currencies holds the
203value 179000. This value tells the driver where to look for the array
204hard_currencies represents. Thus, hard_currencies points to a place
205where the array values may be found. When someone is talking about
206the first element of the array, they want the element located at 179000.
207When the object needs the value of the second element of the array, it
208looks at 179000 + one value, then 179000 plus two values for the third,
209and so on. We can therefore access individual elements of an array by
210their index, which is the number of values beyond the starting point of
211the array we need to look to find the value. For the array
212hard_currencies array:
213"platinum" has an index of 0.
214"gold" has an index of 1.
215"electrum" has an index of 2.
216"silver" has an index of 3.
217"copper" has an index of 4.
218
219The efun member_array() thus returns the index of the element being
220tested if it is in the array, or -1 if it is not in the array. In order to
221reference an individual element in an array, you use its index number in
222the following manner:
223array_name[index_no]
224Example:
225hard_currencies[3]
226where hard_currencies[3] would refer to "silver".
227
228So, you now should now several ways in which arrays appear either as
229a whole or as individual elements. As a whole, you refer to an array
230variable by its name and an array constant by enclosing the array in ({ })
231and separating elements by ,. Individually, you refer to array variables
232by the array name followed by the element's index number enclosed in
233[], and to array constants in the same way you would refer to simple data
234types of the same type as the constant. Examples:
235
236Whole arrays:
237variable: arr
238constant: ({ "platinum", "gold", "electrum", "silver", "copper" })
239
240Individual members of arrays:
241variable: arr[2]
242constant: "electrum"
243
244You can use these means of reference to do all the things you are used to
245doing with other data types. You can assign values, use the values in
246operations, pass the values as parameters to functions, and use the
247values as return types. It is important to remember that when you are
248treating an element alone as an individual, the individual element is not
249itself an array (unless you are dealing with an array of arrays). In the
250example above, the individual elements are strings. So that:
251 str = arr[3] + " and " + arr[1];
252will create str to equal "silver and gold". Although this seems simple
253enough, many people new to arrays start to run into trouble when trying
254to add elements to an array. When you are treating an array as a whole
255and you wish to add a new element to it, you must do it by adding
256another array.
257
258Note the following example:
259string str1, str2;
260string *arr;
261
262str1 = "hi";
263str2 = "bye";
264/* str1 + str2 equals "hibye" */
265arr = ({ str1 }) + ({ str2 });
266/* arr is equal to ({ str1, str2 }) */
267Before going any further, I have to note that this example gives an
268extremely horrible way of building an array. You should set it: arr = ({
269str1, str2 }). The point of the example, however, is that you must add
270like types together. If you try adding an element to an array as the data
271type it is, you will get an error. Instead you have to treat it as an array of
272a single element.
273
2743.5 Mappings
275One of the major advances made in LPMuds since they were created is
276the mapping data type. People alternately refer to them as associative
277arrays. Practically speaking, a mapping allows you freedom from the
278association of a numerical index to a value which arrays require.
279Instead, mappings allow you to associate values with indices which
280actually have meaning to you, much like a relational database.
281
282In an array of 5 elements, you access those values solely by their integer
283indices which cover the range 0 to 4. Imagine going back to the example
284of money again. Players have money of different amounts and different
285types. In the player object, you need a way to store the types of money
286that exist as well as relate them to the amount of that currency type the
287player has. The best way to do this with arrays would have been to
288store an array of strings representing money types and an array of
289integers representing values in the player object. This would result in
290CPU-eating ugly code like this:
291
292int query_money(string type) {
293 int i;
294
295 i = member_array(type, currencies);
296 if(i>-1 && i < sizeof(amounts)) /* sizeof efun
297returns # of elements */
298 return amounts[i];
299 else return 0;
300}
301
302And that is a simple query function. Look at an add function:
303
304void add_money(string type, int amt) {
305 string *tmp1;
306 int * tmp2;
307 int i, x, j, maxj;
308
309 i = member_array(type, currencies);
310 if(i >= sizeof(amounts)) /* corrupt data, we are in
311 a bad way */
312 return;
313 else if(i== -1) {
314 currencies += ({ type });
315 amounts += ({ amt });
316 return;
317 }
318 else {
319 amounts[i] += amt;
320 if(amounts[i] < 1) {
321 tmp1 = allocate(sizeof(currencies)-1);
322 tmp2 = allocate(sizeof(amounts)-1);
323 for(j=0, x =0, maxj=sizeof(tmp1); j < maxj;
324 j++) {
325 if(j==i) x = 1;
326 tmp1[j] = currencies[j+x];
327 tmp2[j] = amounts[j+x];
328 }
329 currencies = tmp1;
330 amounts = tmp2;
331 }
332 }
333}
334
335That is really some nasty code to perform the rather simple concept of
336adding some money. First, we figure out if the player has any of that
337kind of money, and if so, which element of the currencies array it is.
338After that, we have to check to see that the integrity of the currency data
339has been maintained. If the index of the type in the currencies array is
340greater than the highest index of the amounts array, then we have a
341problem since the indices are our only way of relating the two arrays.
342Once we know our data is in tact, if the currency type is not currently
343held by the player, we simply tack on the type as a new element to the
344currencies array and the amount as a new element to the amounts array.
345Finally, if it is a currency the player currently has, we just add the
346amount to the corresponding index in the amounts array. If the money
347gets below 1, meaning having no money of that type, we want to clear
348the currency out of memory.
349
350Subtracting an element from an array is no simple matter. Take, for
351example, the result of the following:
352
353string *arr;
354
355arr = ({ "a", "b", "a" });
356arr -= ({ arr[2] });
357
358What do you think the final value of arr is? Well, it is:
359 ({ "b", "a" })
360Subtracting arr[2] from the original array does not remove the third
361element from the array. Instead, it subtracts the value of the third
362element of the array from the array. And array subtraction removes the
363first instance of the value from the array. Since we do not want to be
364<* NOTE Highlander@MorgenGrauen 11.2.94:
365 WRONG in MorgenGrauen (at least). The result is actually ({ "b" }). Array
366 subtraction removes ALL instances of the subtracted value from the array.
367 This holds true for all Amylaar-driver LPMuds.
368*>
369forced on counting on the elements of the array as being unique, we are
370forced to go through some somersaults to remove the correct element
371from both arrays in order to maintain the correspondence of the indices
372in the two arrays.
373
374Mappings provide a better way. They allow you to directly associate the
375money type with its value. Some people think of mappings as arrays
376where you are not restricted to integers as indices. Truth is, mappings
377are an entirely different concept in storing aggregate information. Arrays
378force you to choose an index which is meaningful to the machine for
379locating the appropriate data. The indices tell the machine how many
380elements beyond the first value the value you desire can be found. With
381mappings, you choose indices which are meaningful to you without
382worrying about how that machine locates and stores it.
383
384You may recognize mappings in the following forms:
385
386constant values:
387whole: ([ index:value, index:value ]) Ex: ([ "gold":10, "silver":20 ])
388element: 10
389
390variable values:
391whole: map (where map is the name of a mapping variable)
392element: map["gold"]
393
394So now my monetary functions would look like:
395
396int query_money(string type) { return money[type]; }
397
398void add_money(string type, int amt) {
399 if(!money[type]) money[type] = amt;
400 else money[type] += amt;
401 if(money[type] < 1)
402 map_delete(money, type); /* this is for
403 MudOS */
404 ...OR...
405 money = m_delete(money, type) /* for some
406 LPMud 3.* varieties */
407 ... OR...
408 m_delete(money, type); /* for other LPMud 3.*
409 varieties */
410}
411
412Please notice first that the efuns for clearing a mapping element from the
413mapping vary from driver to driver. Check with your driver's
414documentation for the exact name an syntax of the relevant efun.
415
416As you can see immediately, you do not need to check the integrity of
417your data since the values which interest you are inextricably bound to
418one another in the mapping. Secondly, getting rid of useless values is a
419simple efun call rather than a tricky, CPU-eating loop. Finally, the
420query function is made up solely of a return instruction.
421
422You must declare and initialize any mapping before using it.
423Declarations look like:
424mapping map;
425Whereas common initializations look like:
426map = ([]);
427map = m_allocate(10) ...OR... map = m_allocate(10);
428map = ([ "gold": 20, "silver": 15 ]);
429
430As with other data types, there are rules defining how they work in
431common operations like addition and subtraction:
432 ([ "gold":20, "silver":30 ]) + ([ "electrum":5 ])
433gives:
434 (["gold":20, "silver":30, "electrum":5])
435Although my demonstration shows a continuity of order, there is in fact
436no guarantee of the order in which elements of mappings will stored.
437Equivalence tests among mappings are therefore not a good thing.
438
4393.6 Summary
440Mappings and arrays can be built as complex as you need them to be.
441You can have an array of mappings of arrays. Such a thing would be
442declared like this:
443
444mapping *map_of_arrs;
445which might look like:
446({ ([ ind1: ({ valA1, valA2}), ind2: ({valB1, valB2}) ]), ([ indX:
447({valX1,valX2}) ]) })
448
449Mappings may use any data type as an index, including objects.
450Mapping indices are often referred to as keys as well, a term from
451databases. Always keep in mind that with any non-integer data type,
452you must first initialize a variable before making use of it in common
453operations such as addition and subtraction. In spite of the ease and
454dynamics added to LPC coding by mappings and arrays, errors caused
455by failing to initialize their values can be the most maddening experience
456for people new to these data types. I would venture that a very high
457percentage of all errors people experimenting with mappings and arrays
458for the first time encounter are one of three error messages:
459 Indexing on illegal type.
460 Illegal index.
461 Bad argument 1 to (+ += - -=) /* insert your favourite operator */
462Error messages 1 and 3 are darn near almost always caused by a failure
463to initialize the array or mapping in question. Error message 2 is caused
464generally when you are trying to use an index in an initialized array
465which does not exist. Also, for arrays, often people new to arrays will
466get error message 3 because they try to add a single element to an array
467by adding the initial array to the single element value instead of adding
468an array of the single element to the initial array. Remember, add only
469arrays to arrays.
470
471At this point, you should feel comfortable enough with mappings and
472arrays to play with them. Expect to encounter the above error messages
473a lot when first playing with these. The key to success with mappings is
474in debugging all of these errors and seeing exactly what causes wholes
475in your programming which allow you to try to work with uninitialized
476mappings and arrays. Finally, go back through the basic room code and
477look at things like the set_exits() (or the equivalent on your mudlib)
478function. Chances are it makes use of mappings. In some instances, it
479will use arrays as well for compatibility with mudlib.n.
480
481Copyright (c) George Reese 1993