Procedures & Functions for Beginners
By me, Incurable!
Last updated 30th October, 2015
NOTE: I wrote this in a couple of hours and then rewrote the whole thing. I think I got everything but there might still be a few remnants of the previous tutorial leftover. If you spot anything that looks like it shouldn't be there, let me know!
Over the last few months I've noticed that quite a lot of new scripters don't fully understand the difference between procedures and functions and don't know when to use them or how. This brief tutorial aims to explain the difference between procedures and functions and how to use them effectively. It will do this by writing the code for a rudimentary power miner. It assumes that you have already completed The Mayor's AIO scripting tutorial. It's important to note that this tutorial specifically discusses the use of procedures and functions in Lape and other Pascal derived languages, but it can be applied to other languages in a very general context.
Now, let's get started!
Section 1: The Difference
We'll begin with the difference between procedures, functions, methods and routines. You've heard these words before, but the difference probably isn't very clear to you.
Procedure: A (usually small) block of code that acts as a sub-program in a (usually larger) program. It executes its code line-by-line in a procedural fashion until no more lines remain to be executed. It can contain other procedures and functions, but it does not return a value.
Example: This could be used to login a player. Note that the SRL-6 function players[currentPlayer].login() calls another function named isLoggedIn() which means that we don't need to use any if statement to check if we're logged in before calling it.
Simba Code:
procedure LoginPlayer(); begin writeLn('Logging in'); players[currentPlayer].login(); wait(randomRange(2000, 5000)); end;
In this case, our procedure will check to see if the player is logged in. If they're not, it will write a line to the Simba debug console, log the player in (if they're not already logged in), and wait for a random time between 2 and 5 seconds. No value has been returned, and we cannot use this procedure as a condition in any test, such as in an if statement or while loop.
Function: A (usually small) block of code that acts as a sub-program in a (usually larger) program. It executes its code line-by-line in a procedural fashion until no more lines remain to be executed. It can contain other procedures and functions and should* return a value.
Example: A simple demonstration of a function that will click on an ore rock and return a boolean defined by the results of the SRL-6 function mainScreen.findObject().
Simba Code:
// clickOre will return a boolean defined by whether or not an ore rock // was found and clicked on with mainScreen.findObject function clickOre(): boolean; var x, y: integer; begin writeLn('Clicking ore'); if (mainscreen.findObject(x, y, 7446732, 9, ['opper'], MOUSE_LEFT)) then result := true; end;
Unlike our procedure, a function can be used in a conditional test. We can actually make this code even shorter by directly calling the mainScreen.findObject() function and assigning its result to our own function's boolean result! Confused? I don't blame you. Here's how we do that:
Simba Code:
// clickOre will return a boolean defined by whether or not an ore rock // was found and clicked on with mainScreen.findObject() function clickOre(): boolean; var x, y: integer; begin writeLn('Clicking ore'); result := mainscreen.findObject(x, y, 7446732, 9, ['opper'], MOUSE_LEFT); end;
Now that we've written a function, here's an example of the same function being used in some pseudo-code:
Simba Code:
function mineOre(): boolean; begin while the backpack isn't full do if clickOre() then // <--- Here as a condition begin write: we found some ore! wait until ore is mined end if backpack is full result = true end;
* A function doesn't have to return a value, but it would be bad practice to declare a function and use it as a procedure. If you're not going to return anything then you should be using a procedure instead.
Data types that can be returned: Everything! No really, as far as I know functions in Lape are able to return practically any type that you like. The most common return type in SRL-6 is a boolean, but you can return integers, doubles, strings, and even custom data types! This makes functions incredibly versatile and useful in almost every scenario.
Method/routine: These two words are used to refer to all of the procedures and functions in a program. In other words, if someone mentions that they like your "methods" when talking about your script, they are usually referring to all of your procedures and functions. The same goes for routines. It's worth noting that not everybody uses them this way and may instead confuse them with the word procedure, but when talking about Lape specifically, it's not very useful to refer to a procedure as a method or routine when it is defined by the keyword procedure.
This is a point of confusion for a lot of people that stems from the use of the words method and routine in other languages to refer to their versions of procedures and functions. However, in our usage (Lape) we use procedure and function as actual keywords, so they should not be referred to by any other name (thanks to @the bank; for clarification on this).
Section 2: Correct Usage
Now that we know the difference between procedures and functions we can discuss when you should actually use them. I'm just going to assume that this isn't completely clear to you yet. If it is, awesome, you can skip to section 3.
Procedure: A procedure should be used when you need to execute a block of code but don't need to return any values. For example, our LoginPlayer() procedure wouldn't do us any good as a function because we aren't testing any conditions and will know if it worked by observing the results. We also can't turn it into a function in its current form because we have no conditions to test! Procedures can also be used to create a neater, more readable, and more maintainable script. By placing chunks of code into useful procedures, we can simplify a script and quickly find problems when they arise.
Function: A function should be used when you want to test a condition and return the result of that test. Yup, pretty simple, right? That's because it really is. Because of their versatility, functions can be used in almost any scenario to test conditions and return the result. This is especially useful in RuneScape scripting where we need to know the state of our player and the game world at all times. I'll give you an example.
Example:You're writing a power miner and want to keep mining until the backpack is full. Using a conditional test such as an if statement, we can test whether the backpack is full using the SRL-6 function tabBackpack.isFull(). First, let's take another look at our first example function:
Simba Code:
// clickOre will return a boolean defined by whether or not an ore rock // was found and clicked on with mainScreen.findObject() function clickOre(): boolean; var x, y: integer; begin writeLn('Clicking ore'); result := mainscreen.findObject(x, y, 7446732, 9, ['opper'], MOUSE_LEFT); end;
... we can use this function as a test inside of another function that uses a repeat..until loop nested inside of a while. This is the fleshed out version of the pseudo-code example above:
Simba Code:
// This function will mine ore while the SRL-6 function "tabBackpack.isFull()" is // returning false! By using the function as a test condition in a while loop, // we can use its result to know whether or not we need to mine another ore rock! function mineOre(): boolean; var currentCount: integer; begin // Using "not" is the same as "while (tabBackpack.isFull() = false) do" while (not tabBackpack.isFull()) do begin currentCount := tabBackpack.count(); // Get the number of items in the backpack // Again, this is the same as "if (clickOre() = true) then" if (clickOre()) then begin writeLn('Waiting while mining'); // Try and understand what this does and how it works! repeat wait(randomRange(100, 250)); until (tabBackpack.count() > currentCount) end else begin writeLn('Cannot find ore, exiting'); exit(false); // See note below for an explanation of this end; end; result := true; // This line is only reachable if tabBackpack.isFull() is true! end;
Did you notice the use of exit(false)? That should be fairly self-explanatory, but here's a quick explanation anyway. The statement exit is used to stop a method and exit out of it completely, moving on to the next step in the program. If a boolean parameter is added when called inside of a function, such as in this case, the result of that function will be the value of that boolean. So, to demonstrate again, the following function will always return true despite result being assigned as false.
Simba Code:program ExitDemo;
function someFunc(): boolean;
var
param: boolean;
begin
result := false;
if (not result) then
writeLn('result = false');
exit(true);
end;
begin
clearDebug();
writeLn(someFunc());
end.
Try it for yourself. Copy/paste the code into a new program and hit run. What result gets printed out? What happens if you change result to true and the parameter of exit to false?
Section 3: Effective Use
This is a brief sample of SRL-6 functions and their return types:
- tabBackpack.isFull(): boolean;
- tabBackpack.count(): integer;
- bankScreen.isOpen(): boolean;
- bankScreen.open(): boolean;
- collectBox.isOpen(): boolean;
- chatBox.getXPBar(): integer;
- chatBox.getTextOnLine(line): String;
- lootScreen.getSlotBox(slot): TBox;
... as you can see, the majority of methods in the SRL-6 include are functions that return some value relevant to the function. This is done so that we may use these functions to be constantly testing the state of the game world and reacting accordingly. For example, in our mineOre() function we use tabBackpack.isFull() to test whether we should keep mining. Similarly, we use tabBackpack.count() to make the script wait until the backpack count is greater than the value stored in the integer type variable currentCount.
It's important to quickly note that a function used as a condition is always executed and tested in order to get its result. An if statement with the condition not tabBackpack.isFull() will still execute the function tabBackpack.isFull() and use its condition as a result, regardless of whether we use the keyword not. Functions can be used to constantly test conditions and react accordingly. The following is a seemingly complex example of a number of functions being used in an event loop procedure in a smelting script. It is heavily commented to make it easier to understand. It's actually really simple once you understand how it works, but the number of conditionals can be daunting for some.
Simba Code:
procedure EventLoop(); begin // A failsafe to stop the script if the global variable of type boolean // "IsScriptRunning" is false if (not IsScriptRunning) then exit; players[currentPlayer].login(); // Remember: this only logs in if we aren't logged in already SetupScreen(); // Setup the screen ProgRep(); // Write a progress report to the Simba debug console // Test the result of a lodestone teleporting function if (IL.lodeTele(LOCATION_LUMBRIDGE)) then TelesDone += 1; // increment the global variable of type integer "telesDone" by 1 // Test the result of a walking function if (IL.walkThePath(SPS, PathLodeToBank)) then repeat // Test the result of a banking function if (not openBank()) then begin // Report an error since openBank() didn't work. We know this because it returned false. ReportError('openBank() failed', 'openbank'); break(); end else begin // Test the result of an ore withdrawing function if (not withdrawOre()) then begin // Report an error since withdrawOre() didn't work. We know this because it returned false. ReportError('withdrawOre() failed', 'withdrawore'); IsScriptRunning := false; end; end; // Test the result of a minimap rotating function if (not minimap.setAngle(gaussRangeInt(288, 318))) then begin // Try walking to the furnace instead of setting the angle of the minimap, test the result if (not IL.walkThePath(SPS, PathBankToFurn)) then // Try walking begin // Report an error since IL.walkThePath() didn't work. We know this because it returned false. ReportError('walkPath(PathBankToFurn) failed', 'walkPath_BankToFurn'); break(); end; end; wait(gaussRangeInt(200, 400)); // Wait a period of time between 200-400 seconds randomised to a Gaussian value // Test the result of a furnace finding function if (not findFurnace()) then begin // Report an error since findFurnace() didn't work. We know this because it returned false. ReportError('findFurnace() failed', 'findFurnace'); break(); end else // Since findFurnace() returned true, test the result of a function that smelts the ore if (not smeltOre()) then begin // Report an error since smeltOre() didn't work. We know this because it returned false. ReportError('smeltOre() failed', 'smeltOre'); break(); end; // Test the result of a minimap rotating function if (not minimap.setAngle(gaussRangeInt(18, 38))) then // Try walking to the bank instead of setting the angle of the minimap, test the result if (not IL.walkThePath(SPS, PathFurnToBank)) then // Try walking begin // Report an error since IL.walkThePath() didn't work. We know this because it returned false. ReportError('walkPath(PathFurnToBank) failed', 'walkPath_FurnToBank'); break(); end; wait(gaussRangeInt(200, 400)); // Wait a period of time between 200-400 seconds randomised to a Gaussian value ProgRep(); // Write a progress report to the Simba debug console BreakHandler(); // Execute the break handling procedure until ((not IsScriptRunning) or (not isLoggedIn())); // ^ The previous repeat..until loop runs until IsScriptRunning is false or the player isn't // logged in. IsScriptRunning is set to false if any of the condition checks fails. // Although this code is unreachable at first, it will still only execute if IsScriptRunning is false. // It will not execute if the player has only logged out for some reason (for example, by disconnecting). if (not IsScriptRunning) then begin writeLn('Ending script, be sure to post the proggy and any debug info ' + 'to the thread! :D'); end; end;
Hopefully that procedure wasn't too complicated and the comments did a good enough job of explaining how multiple functions can be used to test certain conditions and react accordingly.
Section 4: Rudimentary Powerminer Demo
The following code is the complete program that we have written a couple of functions for already: a rudimentary power miner! It should work, so go ahead and copy/paste it or write it out manually (recommended). Once that's done, you can run it after standing at the Varrock East Mine logged in OR out in the following position. Remember that you're running it to try and understand it, not to use as an actual script!
Setup location:
Script:
Simba Code:
program ProcAndFuncTutMiner; {$DEFINE SMART} {$I SRL-6/SRL.simba} // Remember that this is a RUDIMENTARY power miner. It should not be used // for an extended period of time nor should it be used on anything but a // throw away account. It is for demonstration purposes only. var KeepMining: boolean; // A global variable that will be used as a condition to // test whether the script should continue running! procedure DeclarePlayers(); begin setLength(players, 1); with players[0] do begin loginName := 'username'; password := 'password'; isActive := true; // Ignore isMember := false; // Is the account a member? world := 0; // Ignore end currentPlayer := 0; // Ignore end; // This procedure will setup the script by executing a series of pre-written // procedures and setting a few boolean variables! procedure SetupScript(); begin smartShowConsole := false; // Disable the SMART console box SetupSRL(); // Setup the SRL-6 include disableSRLDebug := true; // Disable the SRL-6 include debug options clearDebug(); // Clear the Simba debug console so we can see our own writeLn's DeclarePlayers(); // Declare the player(s) to be used writeLn('Script setup complete!'); // Write a line to the Simba debug console KeepMining := true; // Set the global variable "KeepMining" to true for use in // the "main" procedure end; // This procedure acts as a simple wrapper to login and wait 2-5 seconds. It's // actually not totally necessary and just serves as a demo of a potentially // useful procedure. procedure LoginPlayer(); begin if (not isLoggedIn()) then begin writeLn('Logging in'); players[currentPlayer].login(); wait(randomRange(2000, 5000)); end; end; // Again, this is just a wrapper for some basic functions. It's not totally // necessary, but serves as a good demo of a potentially useful procedure. // Can you figure out why I didn't combine SetupScreen and LoginPlayer into a // bigger procedure? Or why I didn't include them both in SetupScript? procedure SetupScreen(); begin writeLn('Setting up the screen'); mainScreen.setAngle(MS_ANGLE_HIGH); mainScreen.setZoom(false); minimap.setAngle(MM_DIRECTION_SOUTH); end; // clickOre will return a boolean defined by whether or not an ore rock // was found and clicked on with mainScreen.findObject(). We can use this // boolean in the next function! function clickOre(): boolean; var x, y: integer; begin writeLn('Clicking ore'); result := mainscreen.findObject(x, y, 7446732, 9, ['opper'], MOUSE_LEFT); end; // This function will mine ore while the SRL-6 function "tabBackpack.isFull()" is // returning false! By using the function as a test condition in a while loop, // we can use its result to know whether or not we need to mine another ore rock! function mineOre(): boolean; var currentCount: integer; begin // Using "not" is the same as "while (tabBackpack.isFull() = false) do" while (not tabBackpack.isFull()) do begin currentCount := tabBackpack.count(); // Get the number of items in the backpack // Again, this is the same as "if (clickOre() = true) then" if (clickOre()) then begin writeLn('Waiting while mining'); // Try and understand what this does and how it works! while (tabBackpack.count() = currentCount) do wait(randomRange(100, 250)); end else begin writeLn('Cannot find ore, exiting'); exit(false); end; end; result := tabBackpack.isFull() // Assign the result of the function tabBackpack.isFull() // to this function's result end; // This is the procedure that will be repeatedly executed in the "while (KeepRunning) do" // loop setup in our main procedure! procedure EventLoop(); begin writeLn('Entering the event loop...'); LoginPlayer(); SetupScreen(); if (mineOre()) then tabBackpack.dropItems() else KeepMining := false; writeLn('We have reached the end of the event loop...'); end; // This procedure will log the player out. Usually you would do some more stuff // in a procedure like this such as freeing DTMs and bitmaps, but since we don't // have any of those, this is just a very basic wrapper for logging out. procedure EndScript(); begin writeLn('Logging out and ending script'); players[currentPlayer].logout(); end; // Do you understand what's going on in this main procedure? // Why is it so short? begin SetupScript(); while (KeepMining) do EventLoop(); EndScript(); end.
... and that's everything! By now you should understand the difference between a procedure and a function and have a good idea of how and when you can utilise them best. If you don't, then it's probably my fault for writing an unclear tutorial! If you have any problems or questions, please don't hesitate to post them here no matter how old this thread is. If you tag me by wrapping my name in tags I will get a notification and be able to answer you sooner rather than never.
Good luck with your scripting adventures, be sure to tag me in your first script if you utilise functions: I want to know if I actually taught anyone anything.![]()