PDA

View Full Version : Procedures & Functions for Beginners



Incurable
10-29-2015, 11:48 AM
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 (http://docs.villavu.com/srl-6/index.html) 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.


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 (http://docs.villavu.com/srl-6/index.html) function mainScreen.findObject().


// 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:


// 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:


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 (http://docs.villavu.com/srl-6/index.html) function tabBackpack.isFull(). First, let's take another look at our first example function:


// 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:


// 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.


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 (http://docs.villavu.com/srl-6/index.html) 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 (http://docs.villavu.com/srl-6/index.html) 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.


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:

http://i.imgur.com/I7OORWo.png

Script:


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 [mention] 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. :p

StickToTheScript
10-29-2015, 12:54 PM
Congratz on the tutorial! Great work! :thumbsup:

Harrier
10-29-2015, 12:59 PM
// Login only if we're not logged in
if (not isLoggedIn()) then
players[currentPlayer].login();

isLoggedIn is called by players[currentPlayer].login(); so no need to use isLoggedIn in your script! Don't be inefficient ;)
Nice work :p

Incurable
10-29-2015, 08:20 PM
// Login only if we're not logged in
if (not isLoggedIn()) then
players[currentPlayer].login();

isLoggedIn is called by players[currentPlayer].login(); so no need to use isLoggedIn in your script! Don't be inefficient ;)
Nice work :p

I didn't know that, thanks. Seems a bit silly, but whateverz. :p

Vusn
02-12-2016, 09:17 AM
This should be posted outside of RS3 scripting section. An awesome tutorial that doesn't only pertain to RS3, but to scripting in general. I've been on these forums for awhile, and have never come across this tutorial. I even looked for this specific type of tutorial at one point!

the bank
02-12-2016, 02:09 PM
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, in fact, a procedure.

This is a point of confusion for a lot of people and it stems from the usage of methods and routines in other languages (Java is a good example) as actual keywords, but in our case they are not keywords and should not be referred to in place of an actual keyword.


Meh. You're right that the terminology comes from other languages. However, neither routine or method are actual keywords in Java, even if "method" is a popular word when talking about java. A method is essentially a member function, keep in mind that Java forces OOP.

Therefore, one could think of TTimeMarker.getTime() as a method.

Additionally, procedures very well can return something, or multiple things, if you pass by reference rather than passing by value.

KeepBotting
02-12-2016, 06:29 PM
Additionally, procedures very well can return something, or multiple things, if you pass by reference rather than passing by value.
That's when you use the out keyword in the procedure's declaration to specify a variable that data will be outputted to, correct?

rj
02-12-2016, 06:40 PM
That's when you use the out keyword in the procedure's declaration to specify a variable that data will be outputted to, correct?

Or like function ReadFileString(FileNum:Integer;var s:string;x:integer):Boolean;

slacky
02-12-2016, 06:40 PM
That's when you use the out keyword in the procedure's declaration to specify a variable that data will be outputted to, correct?
Yes, tho out is currently just an alias for var. out is preferred whenever the current value stored in the variable is going to be ignored.

the bank
02-13-2016, 12:12 AM
That's when you use the out keyword in the procedure's declaration to specify a variable that data will be outputted to, correct?

Correct. Essentially, passing by value will make a physical copy of the variable in memory and use that within the scope of the function. Passing by reference passes that exact object in memory (think: pointer) and anything you do to it within the function scope will affect it outside of the scope as well.

Incurable
03-07-2016, 05:10 AM
Meh. You're right that the terminology comes from other languages. However, neither routine or method are actual keywords in Java, even if "method" is a popular word when talking about java. A method is essentially a member function, keep in mind that Java forces OOP.

Therefore, one could think of TTimeMarker.getTime() as a method.

Additionally, procedures very well can return something, or multiple things, if you pass by reference rather than passing by value.

Sorry for the late reply.

You're right, I don't know why I remembered "method" as a keyword. I'll edit that to make it more clear. Thanks!


This should be posted outside of RS3 scripting section. An awesome tutorial that doesn't only pertain to RS3, but to scripting in general. I've been on these forums for awhile, and have never come across this tutorial. I even looked for this specific type of tutorial at one point!

Thank you, I'll see what I can do. :)

EDIT: I've taken another look around the forum and can't really see where else I would put this. Do you have a suggestion? Where did you go when looking for this kind of tutorial?