blob: 39f311b79b8485e522ac0bd77db05b62072923b1 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001 LPC Basics
2 Written by Descartes of Borg
3 first edition: 23 april 1993
4 second edition: 10 july 1993
5
6CHAPTER 7: Flow Control
7
87.1 Review of variables
9Variables may be manipulated by assigning or changing values with the
10expressions =, +=, -=, ++, --. Those expressions may be combined with
11the expressions -, +, *, /, %. However, so far, you have only been
12shown how to use a function to do these in a linear way. For example:
13
14int hello(int x) {
15 x--;
16 write("Hello, x is "+x+".\n");
17 return x;
18}
19
20is a function you should know how to write and understand. But what
21if you wanted to write the value of x only if x = 1? Or what if
22you wanted it to keep writing x over and over until x = 1 before
23returning? LPC uses flow control in exactly the same way as C and C++.
24
257.2 The LPC flow control statements
26LPC uses the following expressions:
27
28if(expression) instruction;
29
30if(expression) instruction;
31else instruction;
32
33if(expression) instruction;
34else if(expression) instruction;
35else instruction;
36
37while(expression) instruction;
38
39do { instruction; } while(expression);
40
41switch(expression) {
42 case (expression): instruction; break;
43 default: instruction;
44}
45
46Before we discuss these, first something on what is meant by expression and
47instruction. An expression is anything with a value like a variable,
48a comparison (like x>5, where if x is 6 or more, the value is 1, else the
49value is 0), or an assignment(like x += 2). An instruction can be any
50single line of lpc code like a function call, a value assignment or
51modification, etc.
52
53You should know also the operators &&, ||, ==, !=, and !. These are the
54logical operators. They return a nonzero value when true, and 0 when false.
55Make note of the values of the following expressions:
56
57(1 && 1) value: 1 (1 and 1)
58(1 && 0) value: 0 (1 and 0)
59(1 || 0) value: 1 (1 or 0)
60(1 == 1) value: 1 (1 is equal to 1)
61(1 != 1) value: 0 (1 is not equal to 1)
62(!1) value: 0 (not 1)
63(!0) value: 1 (not 0)
64
65In expressions using &&, if the value of the first item being compared
66is 0, the second is never tested even. When using ||, if the first is
67true (1), then the second is not tested.
68
697.3 if()
70The first expression to look at that alters flow control is if(). Take
71a look at the following example:
72
731 void reset() {
742 int x;
753
764 ::reset();
775 x = random(10);
786 if(x > 50) set_search_func("floorboards", "search_floor");
797 }
80
81The line numbers are for reference only.
82In line 2, of course we declare a variable of type int called x. Line 3
83is aethetic whitespace to clearly show where the declarations end and the
84function code begins. The variable x is only available to the function
85reset().
86Line 4 makes a call to the room.c version of reset().
87Line 5 uses the driver efun random() to return a random number between
880 and the parameter minus 1. So here we are looking for a number between
890 and 99.
90In line 6, we test the value of the expression (x>50) to see if it is true
91or false. If it is true, then it makes a call to the room.c function
92set_search_func(). If it is false, the call to set_search_func() is never
93executed.
94In line 7, the function returns driver control to the calling function
95(the driver itself in this case) without returning any value.
96
97If you had wanted to execute multiple instructions instead of just the one,
98you would have done it in the following manner:
99
100if(x>50) {
101 set_search_func("floorboards", "search_floor");
102 if(!present("beggar", this_object())) make_beggar();
103}
104
105Notice the {} encapsulate the instructions to be executed if the test
106expression is true. In the example, again we call the room.c function
107which sets a function (search_floor()) that you will later define yourself
108to be called when the player types "search floorboards" (NOTE: This is
109highly mudlib dependent. Nightmare mudlibs have this function call.
110Others may have something similar, while others may not have this feature
111under any name). Next, there is another if() expression that tests the
112truth of the expression (!present("beggar",this_object())). The ! in the
113test expression changes the truth of the expression which follows it. In
114this case, it changes the truth of the efun present(), which will return
115the object that is a beggar if it is in the room (this_object()), or it
116will return 0 if there is no beggar in the room. So if there is a beggar
117still living in the room, (present("beggar", this_object())) will have
118a value equal to the beggar object (data type object), otherwise it will
119be 0. The ! will change a 0 to a 1, or any nonzero value (like the
120beggar object) to a 0. Therefore, the expression
121(!present("beggar", this_object())) is true if there is no beggar in the
122room, and false if there is. So, if there is no beggar in the room,
123then it calls the function you define in your room code that makes a
124new beggar and puts it in the room. (If there is a beggar in the room,
125we do not want to add yet another one :))
126
127Of course, if()'s often comes with ands or buts :). In LPC, the formal
128reading of the if() statement is:
129
130if(expression) { set of intructions }
131else if(expression) { set of instructions }
132else { set of instructions }
133
134This means:
135
136If expression is true, then do these instructions.
137Otherise, if this second expression is true, do this second set.
138And if none of those were true, then do this last set.
139
140You can have if() alone:
141
142if(x>5) write("Foo,\n");
143
144with an else if():
145
146if(x > 5) write("X is greater than 5.\n");
147else if(x >2) write("X is less than 6, but greater than 2.\n");
148
149with an else:
150
151if(x>5) write("X is greater than 5.\n");
152else write("X is less than 6.\n");
153
154or the whole lot of them as listed above. You can have any number of
155else if()'s in the expression, but you must have one and only one
156if() and at most one else. Of course, as with the beggar example,
157you may nest if() statements inside if() instructions. (For example,
158 if(x>5) {
159 if(x==7) write("Lucky number!\n");
160 else write("Roll again.\n");
161 }
162 else write("You lose.\n");
163
1647.4 The statements: while() and do {} while()
165Prototype:
166while(expression) { set of instructions }
167do { set of instructions } while(expression);
168
169These allow you to create a set of instructions which continue to
170execute so long as some expression is true. Suppose you wanted to
171set a variable equal to a player's level and keep subtracting random
172amounts of either money or hp from a player until that variable equals
1730 (so that player's of higher levels would lose more). You might do it
174this way:
175
1761 int x;
1772
1783 x = (int)this_player()->query_level(); /* this has yet to be explained */
1794 while(x > 0) {
1805 if(random(2)) this_player()->add_money("silver", -random(50));
1816 else this_player()->add_hp(-(random(10));
1827 x--;
1838 }
184
185The expression this_player()->query_level() calIn line 4, we start a loop that executes so long as x is greater than 0.
186 Another way we could have done this line would be:
187 while(x) {
188 The problem with that would be if we later made a change to the funtion
189y anywhere between 0 and 49 coins.
190In line 6, if instead it returns 0, we call the add_hp() function in the
191 player which reduces the player's hit points anywhere between 0 and 9 hp.
192In line 7, we reduce x by 1.
193At line 8, the execution comes to the end of the while() instructions and
194 goes back up to line 4 to see if x is still greater than 0. This
195 loop will keep executing until x is finally less than 1.
196
197You might, however, want to test an expression *after* you execute some
198instructions. For instance, in the above, if you wanted to execute
199the instructions at least once for everyone, even if their level is
200below the test level:
201
202 int x;
203
204 x = (int)this_player()->query_level();
205 do {
206 if(random(2)) this_player()->add_money("silver", -random(50));
207 else this_player()->add_hp(-random(10));
208 x--;
209 } while(x > 0);
210
211This is a rather bizarre example, being as few muds have level 0 players.
212And even still, you could have done it using the original loop with
213a different test. Nevertheless, it is intended to show how a do{} while()
214works. As you see, instead of initiating the test at the beginning of the
215loop (which would immediately exclude some values of x), it tests after
216the loop has been executed. This assures that the instructions of the loop
217get executed at least one time, no matter what x is.
218
2197.5 for() loops
220Prototype:
221for(initialize values ; test expression ; instruction) { instructions }
222
223initialize values:
224This allows you to set starting values of variables which will be used
225in the loop. This part is optional.
226
227test expression:
228Same as the expression in if() and while(). The loop is executed
229as long as this expression (or expressions) is true. You must have a
230test expression.
231
232instruction:
233An expression (or expressions) which is to be executed at the end of each
234loop. This is optional.
235
236Note:
237for(;expression;) {}
238IS EXACTLY THE SAME AS
239while(expression) {}
240
241Example:
242
2431 int x;
2442
2453 for(x= (int)this_player()->query_level(); x>0; x--) {
2464 if(random(2)) this_player()->add_money("silver", -random(50));
2475 else this_player()->add_hp(-random(10));
2486 }
249
250This for() loop behaves EXACTLY like the while() example.
251Additionally, if you wanted to initialize 2 variables:
252
253for(x=0, y=random(20); x<y; x++) { write(x+"\n"); }
254
255Here, we initialize 2 variables, x and y, and we separate them by a
256comma. You can do the same with any of the 3 parts of the for()
257expression.
258
2597.6 The statement: switch()
260Prototype:
261switch(expression) {
262 case constant: instructions
263 case constant: instructions
264 ...
265 case constant: instructions
266 default: instructions
267}
268
269This is functionally much like if() expressions, and much nicer to the
270CPU, however most rarely used because it looks so damn complicated.
271But it is not.
272
273First off, the expression is not a test. The cases are tests. A English
274sounding way to read:
275
2761 int x;
2772
2783 x = random(5);
2794 switch(x) {
2805 case 1: write("X is 1.\n");
2816 case 2: x++;
2827 default: x--;
2838 }
2849 write(x+"\n");
285
286is:
287
288set variable x to a random number between 0 and 4.
289In case 1 of variable x write its value add 1 to it and subtract 1.
290In case 2 of variable x, add 1 to its value and then subtract 1.
291In other cases subtract 1.
292Write the value of x.
293
294switch(x) basically tells the driver that the variable x is the value
295we are trying to match to a case.
296Once the driver finds a case which matches, that case *and all following
297cases* will be acted upon. You may break out of the switch statement
298as well as any other flow control statement with a break instruction in
299order only to execute a single case. But that will be explained later.
300The default statement is one that will be executed for any value of
301x so long as the switch() flow has not been broken. You may use any
302data type in a switch statement:
303
304string name;
305
306name = (string)this_player()->query_name();
307switch(name) {
308 case "descartes": write("You borg.\n");
309 case "flamme":
310 case "forlock":
311 case "shadowwolf": write("You are a Nightmare head arch.\n");
312 default: write("You exist.\n");
313}
314
315For me, I would see:
316You borg.
317You are a Nightmare head arch.
318You exist.
319
320Flamme, Forlock, or Shadowwolf would see:
321You are a Nightmare head arch.
322You exist.
323
324Everyone else would see:
325You exist.
326
3277.7 Altering the flow of functions and flow control statements
328The following instructions:
329return continue break
330
331alter the natural flow of things as described above.
332First of all,
333return
334no matter where it occurs in a function, will cease the execution of that
335function and return control to the function which called the one the
336return statement is in. If the function is NOT of type void, then a
337value must follow the return statement, and that value must be of a
338type matching the function. An absolute value function would look
339like this:
340
341int absolute_value(int x) {
342 if(x>-1) return x;
343 else return -x;
344}
345
346In the second line, the function ceases execution and returns to the calling
347function because the desired value has been found if x is a positive
348number.
349
350continue is most often used in for() and while statements. It serves
351to stop the execution of the current loop and send the execution back
352to the beginning of the loop. For instance, say you wanted to avoid
353division by 0:
354
355x= 4;
356while( x > -5) {
357 x--
358 if(!x) continue;
359 write((100/x)+"\n");
360}
361write("Done.\n")
362
363You would see the following output:
36433
36550
366100
367-100
368-50
369-33
370-25
371Done.
372To avoid an error, it checks in each loop to make sure x is not 0.
373If x is zero, then it starts back with the test expression without
374finishing its current loop.
375
376In a for() expression
377 for(x=3; x>-5; x--) {
378 if(!x) continue;
379 write((100/x)+"\n");
380 }
381 write("Done.\n");
382It works much the same way. Note this gives exactly the same output
383as before. At x=1, it tests to see if x is zero, it is not, so it
384writes 100/x, then goes back to the top, subtracts one from x, checks to
385see if it is zero again, and it is zero, so it goes back to the top
386and subtracts 1 again.
387
388break
389This one ceases the function of a flow control statement. No matter
390where you are in the statement, the control of the program will go
391to the end of the loop. So, if in the above examples, we had
392used break instead of continue, the output would have looked like this:
393
39433
39550
396100
397Done.
398
399continue is most often used with the for() and while() statements.
400break however is mostly used with switch()
401
402switch(name) {
403 case "descartes": write("You are borg.\n"); break;
404 case "flamme": write("You are flamme.\n"); break;
405 case "forlock": write("You are forlock.\n"); break;
406 case "shadowwolf": write("You are shadowwolf.\n"); break;
407 default: write("You will be assimilated.\n");
408}
409
410This functions just like:
411
412if(name == "descartes") write("You are borg.\n");
413else if(name == "flamme") write("You are flamme.\n");
414else if(name == "forlock") write("You are forlock.\n");
415else if(name == "shadowwolf") write("You are shadowwolf.\n");
416else write("You will be assimilated.\n");
417
418except the switch statement is much better on the CPU.
419If any of these are placed in nested statements, then they alter the
420flow of the most immediate statement.
421
4227.8 Chapter summary
423This chapter covered one hell of a lot, but it was stuff that needed to
424be seen all at once. You should now completely understand if() for()
425while() do{} while() and switch(), as well as how to alter their flow
426using return, continue, and break. Effeciency says if it can be done in
427a natural way using switch() instead of a lot of if() else if()'s, then
428by all means do it. You were also introduced to the idea of calling
429functions in other objects. That however, is a topic to be detailed later.
430You now should be completely at ease writing simple rooms (if you have
431read your mudlib's room building document), simple monsters, and
432other sorts of simple objects.