| LPC Basics |
| Written by Descartes of Borg |
| first edition: 23 april 1993 |
| second edition: 10 july 1993 |
| |
| CHAPTER 7: Flow Control |
| |
| 7.1 Review of variables |
| Variables may be manipulated by assigning or changing values with the |
| expressions =, +=, -=, ++, --. Those expressions may be combined with |
| the expressions -, +, *, /, %. However, so far, you have only been |
| shown how to use a function to do these in a linear way. For example: |
| |
| int hello(int x) { |
| x--; |
| write("Hello, x is "+x+".\n"); |
| return x; |
| } |
| |
| is a function you should know how to write and understand. But what |
| if you wanted to write the value of x only if x = 1? Or what if |
| you wanted it to keep writing x over and over until x = 1 before |
| returning? LPC uses flow control in exactly the same way as C and C++. |
| |
| 7.2 The LPC flow control statements |
| LPC uses the following expressions: |
| |
| if(expression) instruction; |
| |
| if(expression) instruction; |
| else instruction; |
| |
| if(expression) instruction; |
| else if(expression) instruction; |
| else instruction; |
| |
| while(expression) instruction; |
| |
| do { instruction; } while(expression); |
| |
| switch(expression) { |
| case (expression): instruction; break; |
| default: instruction; |
| } |
| |
| Before we discuss these, first something on what is meant by expression and |
| instruction. An expression is anything with a value like a variable, |
| a comparison (like x>5, where if x is 6 or more, the value is 1, else the |
| value is 0), or an assignment(like x += 2). An instruction can be any |
| single line of lpc code like a function call, a value assignment or |
| modification, etc. |
| |
| You should know also the operators &&, ||, ==, !=, and !. These are the |
| logical operators. They return a nonzero value when true, and 0 when false. |
| Make note of the values of the following expressions: |
| |
| (1 && 1) value: 1 (1 and 1) |
| (1 && 0) value: 0 (1 and 0) |
| (1 || 0) value: 1 (1 or 0) |
| (1 == 1) value: 1 (1 is equal to 1) |
| (1 != 1) value: 0 (1 is not equal to 1) |
| (!1) value: 0 (not 1) |
| (!0) value: 1 (not 0) |
| |
| In expressions using &&, if the value of the first item being compared |
| is 0, the second is never tested even. When using ||, if the first is |
| true (1), then the second is not tested. |
| |
| 7.3 if() |
| The first expression to look at that alters flow control is if(). Take |
| a look at the following example: |
| |
| 1 void reset() { |
| 2 int x; |
| 3 |
| 4 ::reset(); |
| 5 x = random(10); |
| 6 if(x > 50) set_search_func("floorboards", "search_floor"); |
| 7 } |
| |
| The line numbers are for reference only. |
| In line 2, of course we declare a variable of type int called x. Line 3 |
| is aethetic whitespace to clearly show where the declarations end and the |
| function code begins. The variable x is only available to the function |
| reset(). |
| Line 4 makes a call to the room.c version of reset(). |
| Line 5 uses the driver efun random() to return a random number between |
| 0 and the parameter minus 1. So here we are looking for a number between |
| 0 and 99. |
| In line 6, we test the value of the expression (x>50) to see if it is true |
| or false. If it is true, then it makes a call to the room.c function |
| set_search_func(). If it is false, the call to set_search_func() is never |
| executed. |
| In line 7, the function returns driver control to the calling function |
| (the driver itself in this case) without returning any value. |
| |
| If you had wanted to execute multiple instructions instead of just the one, |
| you would have done it in the following manner: |
| |
| if(x>50) { |
| set_search_func("floorboards", "search_floor"); |
| if(!present("beggar", this_object())) make_beggar(); |
| } |
| |
| Notice the {} encapsulate the instructions to be executed if the test |
| expression is true. In the example, again we call the room.c function |
| which sets a function (search_floor()) that you will later define yourself |
| to be called when the player types "search floorboards" (NOTE: This is |
| highly mudlib dependent. Nightmare mudlibs have this function call. |
| Others may have something similar, while others may not have this feature |
| under any name). Next, there is another if() expression that tests the |
| truth of the expression (!present("beggar",this_object())). The ! in the |
| test expression changes the truth of the expression which follows it. In |
| this case, it changes the truth of the efun present(), which will return |
| the object that is a beggar if it is in the room (this_object()), or it |
| will return 0 if there is no beggar in the room. So if there is a beggar |
| still living in the room, (present("beggar", this_object())) will have |
| a value equal to the beggar object (data type object), otherwise it will |
| be 0. The ! will change a 0 to a 1, or any nonzero value (like the |
| beggar object) to a 0. Therefore, the expression |
| (!present("beggar", this_object())) is true if there is no beggar in the |
| room, and false if there is. So, if there is no beggar in the room, |
| then it calls the function you define in your room code that makes a |
| new beggar and puts it in the room. (If there is a beggar in the room, |
| we do not want to add yet another one :)) |
| |
| Of course, if()'s often comes with ands or buts :). In LPC, the formal |
| reading of the if() statement is: |
| |
| if(expression) { set of intructions } |
| else if(expression) { set of instructions } |
| else { set of instructions } |
| |
| This means: |
| |
| If expression is true, then do these instructions. |
| Otherise, if this second expression is true, do this second set. |
| And if none of those were true, then do this last set. |
| |
| You can have if() alone: |
| |
| if(x>5) write("Foo,\n"); |
| |
| with an else if(): |
| |
| if(x > 5) write("X is greater than 5.\n"); |
| else if(x >2) write("X is less than 6, but greater than 2.\n"); |
| |
| with an else: |
| |
| if(x>5) write("X is greater than 5.\n"); |
| else write("X is less than 6.\n"); |
| |
| or the whole lot of them as listed above. You can have any number of |
| else if()'s in the expression, but you must have one and only one |
| if() and at most one else. Of course, as with the beggar example, |
| you may nest if() statements inside if() instructions. (For example, |
| if(x>5) { |
| if(x==7) write("Lucky number!\n"); |
| else write("Roll again.\n"); |
| } |
| else write("You lose.\n"); |
| |
| 7.4 The statements: while() and do {} while() |
| Prototype: |
| while(expression) { set of instructions } |
| do { set of instructions } while(expression); |
| |
| These allow you to create a set of instructions which continue to |
| execute so long as some expression is true. Suppose you wanted to |
| set a variable equal to a player's level and keep subtracting random |
| amounts of either money or hp from a player until that variable equals |
| 0 (so that player's of higher levels would lose more). You might do it |
| this way: |
| |
| 1 int x; |
| 2 |
| 3 x = (int)this_player()->query_level(); /* this has yet to be explained */ |
| 4 while(x > 0) { |
| 5 if(random(2)) this_player()->add_money("silver", -random(50)); |
| 6 else this_player()->add_hp(-(random(10)); |
| 7 x--; |
| 8 } |
| |
| The expression this_player()->query_level() calIn line 4, we start a loop that executes so long as x is greater than 0. |
| Another way we could have done this line would be: |
| while(x) { |
| The problem with that would be if we later made a change to the funtion |
| y anywhere between 0 and 49 coins. |
| In line 6, if instead it returns 0, we call the add_hp() function in the |
| player which reduces the player's hit points anywhere between 0 and 9 hp. |
| In line 7, we reduce x by 1. |
| At line 8, the execution comes to the end of the while() instructions and |
| goes back up to line 4 to see if x is still greater than 0. This |
| loop will keep executing until x is finally less than 1. |
| |
| You might, however, want to test an expression *after* you execute some |
| instructions. For instance, in the above, if you wanted to execute |
| the instructions at least once for everyone, even if their level is |
| below the test level: |
| |
| int x; |
| |
| x = (int)this_player()->query_level(); |
| do { |
| if(random(2)) this_player()->add_money("silver", -random(50)); |
| else this_player()->add_hp(-random(10)); |
| x--; |
| } while(x > 0); |
| |
| This is a rather bizarre example, being as few muds have level 0 players. |
| And even still, you could have done it using the original loop with |
| a different test. Nevertheless, it is intended to show how a do{} while() |
| works. As you see, instead of initiating the test at the beginning of the |
| loop (which would immediately exclude some values of x), it tests after |
| the loop has been executed. This assures that the instructions of the loop |
| get executed at least one time, no matter what x is. |
| |
| 7.5 for() loops |
| Prototype: |
| for(initialize values ; test expression ; instruction) { instructions } |
| |
| initialize values: |
| This allows you to set starting values of variables which will be used |
| in the loop. This part is optional. |
| |
| test expression: |
| Same as the expression in if() and while(). The loop is executed |
| as long as this expression (or expressions) is true. You must have a |
| test expression. |
| |
| instruction: |
| An expression (or expressions) which is to be executed at the end of each |
| loop. This is optional. |
| |
| Note: |
| for(;expression;) {} |
| IS EXACTLY THE SAME AS |
| while(expression) {} |
| |
| Example: |
| |
| 1 int x; |
| 2 |
| 3 for(x= (int)this_player()->query_level(); x>0; x--) { |
| 4 if(random(2)) this_player()->add_money("silver", -random(50)); |
| 5 else this_player()->add_hp(-random(10)); |
| 6 } |
| |
| This for() loop behaves EXACTLY like the while() example. |
| Additionally, if you wanted to initialize 2 variables: |
| |
| for(x=0, y=random(20); x<y; x++) { write(x+"\n"); } |
| |
| Here, we initialize 2 variables, x and y, and we separate them by a |
| comma. You can do the same with any of the 3 parts of the for() |
| expression. |
| |
| 7.6 The statement: switch() |
| Prototype: |
| switch(expression) { |
| case constant: instructions |
| case constant: instructions |
| ... |
| case constant: instructions |
| default: instructions |
| } |
| |
| This is functionally much like if() expressions, and much nicer to the |
| CPU, however most rarely used because it looks so damn complicated. |
| But it is not. |
| |
| First off, the expression is not a test. The cases are tests. A English |
| sounding way to read: |
| |
| 1 int x; |
| 2 |
| 3 x = random(5); |
| 4 switch(x) { |
| 5 case 1: write("X is 1.\n"); |
| 6 case 2: x++; |
| 7 default: x--; |
| 8 } |
| 9 write(x+"\n"); |
| |
| is: |
| |
| set variable x to a random number between 0 and 4. |
| In case 1 of variable x write its value add 1 to it and subtract 1. |
| In case 2 of variable x, add 1 to its value and then subtract 1. |
| In other cases subtract 1. |
| Write the value of x. |
| |
| switch(x) basically tells the driver that the variable x is the value |
| we are trying to match to a case. |
| Once the driver finds a case which matches, that case *and all following |
| cases* will be acted upon. You may break out of the switch statement |
| as well as any other flow control statement with a break instruction in |
| order only to execute a single case. But that will be explained later. |
| The default statement is one that will be executed for any value of |
| x so long as the switch() flow has not been broken. You may use any |
| data type in a switch statement: |
| |
| string name; |
| |
| name = (string)this_player()->query_name(); |
| switch(name) { |
| case "descartes": write("You borg.\n"); |
| case "flamme": |
| case "forlock": |
| case "shadowwolf": write("You are a Nightmare head arch.\n"); |
| default: write("You exist.\n"); |
| } |
| |
| For me, I would see: |
| You borg. |
| You are a Nightmare head arch. |
| You exist. |
| |
| Flamme, Forlock, or Shadowwolf would see: |
| You are a Nightmare head arch. |
| You exist. |
| |
| Everyone else would see: |
| You exist. |
| |
| 7.7 Altering the flow of functions and flow control statements |
| The following instructions: |
| return continue break |
| |
| alter the natural flow of things as described above. |
| First of all, |
| return |
| no matter where it occurs in a function, will cease the execution of that |
| function and return control to the function which called the one the |
| return statement is in. If the function is NOT of type void, then a |
| value must follow the return statement, and that value must be of a |
| type matching the function. An absolute value function would look |
| like this: |
| |
| int absolute_value(int x) { |
| if(x>-1) return x; |
| else return -x; |
| } |
| |
| In the second line, the function ceases execution and returns to the calling |
| function because the desired value has been found if x is a positive |
| number. |
| |
| continue is most often used in for() and while statements. It serves |
| to stop the execution of the current loop and send the execution back |
| to the beginning of the loop. For instance, say you wanted to avoid |
| division by 0: |
| |
| x= 4; |
| while( x > -5) { |
| x-- |
| if(!x) continue; |
| write((100/x)+"\n"); |
| } |
| write("Done.\n") |
| |
| You would see the following output: |
| 33 |
| 50 |
| 100 |
| -100 |
| -50 |
| -33 |
| -25 |
| Done. |
| To avoid an error, it checks in each loop to make sure x is not 0. |
| If x is zero, then it starts back with the test expression without |
| finishing its current loop. |
| |
| In a for() expression |
| for(x=3; x>-5; x--) { |
| if(!x) continue; |
| write((100/x)+"\n"); |
| } |
| write("Done.\n"); |
| It works much the same way. Note this gives exactly the same output |
| as before. At x=1, it tests to see if x is zero, it is not, so it |
| writes 100/x, then goes back to the top, subtracts one from x, checks to |
| see if it is zero again, and it is zero, so it goes back to the top |
| and subtracts 1 again. |
| |
| break |
| This one ceases the function of a flow control statement. No matter |
| where you are in the statement, the control of the program will go |
| to the end of the loop. So, if in the above examples, we had |
| used break instead of continue, the output would have looked like this: |
| |
| 33 |
| 50 |
| 100 |
| Done. |
| |
| continue is most often used with the for() and while() statements. |
| break however is mostly used with switch() |
| |
| switch(name) { |
| case "descartes": write("You are borg.\n"); break; |
| case "flamme": write("You are flamme.\n"); break; |
| case "forlock": write("You are forlock.\n"); break; |
| case "shadowwolf": write("You are shadowwolf.\n"); break; |
| default: write("You will be assimilated.\n"); |
| } |
| |
| This functions just like: |
| |
| if(name == "descartes") write("You are borg.\n"); |
| else if(name == "flamme") write("You are flamme.\n"); |
| else if(name == "forlock") write("You are forlock.\n"); |
| else if(name == "shadowwolf") write("You are shadowwolf.\n"); |
| else write("You will be assimilated.\n"); |
| |
| except the switch statement is much better on the CPU. |
| If any of these are placed in nested statements, then they alter the |
| flow of the most immediate statement. |
| |
| 7.8 Chapter summary |
| This chapter covered one hell of a lot, but it was stuff that needed to |
| be seen all at once. You should now completely understand if() for() |
| while() do{} while() and switch(), as well as how to alter their flow |
| using return, continue, and break. Effeciency says if it can be done in |
| a natural way using switch() instead of a lot of if() else if()'s, then |
| by all means do it. You were also introduced to the idea of calling |
| functions in other objects. That however, is a topic to be detailed later. |
| You now should be completely at ease writing simple rooms (if you have |
| read your mudlib's room building document), simple monsters, and |
| other sorts of simple objects. |