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