Blumblebee
08-06-2010, 10:05 PM
This will be a fairly quick tutorial and I will try to keep it as simple as possible.
This tutorial will require a basic knowledge in the following area(s):
Pointers
conditional statements (if..then statements etc)
system/program flow
I. Intro
Well first of all I'm sure you're wondering "what is FSM"? FSM stands for Finite-state machine (http://en.wikipedia.org/wiki/Finite-State_Machine). Basically this type of mainloop mirrors that of RSBot's in the fact that the mainloop responds to "states" set by a previous function.
A FSM loop should look something like this:
const
{enum}
BANK = 1;
WALK = 2;
COOK = 3;
ANTIBAN = 4;
{enum: an enumeration of a set is an exact listing of all of its elements (perhaps with repetition).}
type
tStatus = record
ID: Integer;
Name: string;
end;
tPointerSet = record
isProc: Boolean;
proc: procedure;
func: function: Boolean;
name: string;
end;
var
state: tStatus;
Pointers: tPointerSet;
TileArray: array of tTile;
IDArray: array of Integer;
function getState(): Integer;
var
tempID: Integer;
i: Integer;
begin
if( (getAnimation() > -1) or (CharacterMoving()) )then
result := ANTIBAN;
if( result <> 0 )then
exit;
if( distanceFrom(TileArray[_BANK]) < 4 ) then
begin
tempID := getItemIDAt(1);
if state.ID = tempID then
result := WALK
else
result := BANK;
end;
if( result <> 0 )then
exit;
if( distanceFrom(TileArray[_COOK]) < 5 ) then
begin
tempID := getItemIDAt(1);
if( state.ID = tempID )then
result := COOK
else
result := WALK;
end;
if( result = 0 )then
begin
writeLn('getState is having issues. Checking for randoms.');
for i := 0 to 15 do
begin
if( FindNormalRandoms() )then
Exit;
wait(50);
end;
writeLn('not in a Random and state enum = 0. NextPlayer');
NextPlayer(False);
Exit;
end;
end;
procedure Loop();
begin
with Pointers do
begin
case getState() of
ANTIBAN:
begin
name := 'antiban';
isProc := true;
Proc := @Anti_Ban;
Func := nil;
end;
WALK:
begin
name := 'walk';
isProc := false;
Proc := nil;
Func := @createWalkLocation;
end;
BANK:
begin
name := 'bank';
isProc := false;
Proc := nil;
Func := @Bank_Loop;
end;
COOK:
begin
name := 'cook';
isProc := true;
Proc := @Cook_Loop;
Func := nil;
end;
end;
end;
end;
var
mtile: ttile;
p: tpoint;
begin
SMART_Signed := true;
SMART_Server := 81;
setUpSRL();
setUpReflectionEx(true);
DeclarePlayers();
TileArray := [tile(3269, randomRange(3169, 3166)), tile(3273, 3181)];
setArrayLength(IDArray, 1);
IDArray := [25730];
repeat
while not LoggedIn() do
LogInPlayer();
wait(randomRange(250, 500));
setAngle(true);
while Players[CurrentPlayer].Active do
begin
Loop();
with Pointers do
begin
writeLn('entering '+name+' cycle.');
if isProc then
Proc()
else
Func();
end;
end;
until AllPlayersInactive();
end.
Don't worry I will break it all down later. For now that is just a simple example.
II. Advantages of FSM
I have found in my 2 years of scripting recalibrating a script when it is lost is a difficult task. When a random mis-click or mis-hap occurs during the runtime of the script, it can alter the script and screw it up. This is because scripts normally flow with (for lack of a better phrase) a conditional flow. By this I mean,
if not Conditonal_Function then
begin
NextPlayer(False);
Exit;
end;
if not Conditonal_Function2 then
begin
NextPlayer(False);
Exit;
end;
// and so on and so fourth.
A FSM style loop allows for a recalibration of errors and ensures a longer runtime. When the script dynamically outputs functions/procedures in a response to it's environment the script should leave few area's to falter and should pick up on these issue's.
III. Warnings/Disadvantages
First of all there should be a simple warning cautioned that developing a well functioning FSM can become difficult/complicated. This is where you will require knowledge in Program Flow. I will now address the getState() function.
function getState(): Integer;
begin
if This_is_true then
result := CORRESPONDING_ENUM; // enums must begin at 1 not 0. or else the result could/would always = 0
//after this first state.
if result <> 0 then
Exit;
if This_is_true then
result := NEXT_ENUM;
// so on and so fourth.
end;
now addressing the flow problem here goes. So the issue lies here, what if two results are present on the screen, yet you can only have one result correct? So You must order the flow of the function to work according to the script. (That's a terrible explanation I know). Ummm, here's an example.
function getState(): Integer;
begin
if distanceFrom(tile(xxx, yyy) < 3 {<--- atTrees()} then
result := CORRESPONDING_ENUM; // enums must begin at 1 not 0. or else the result could/would always = 0
//after this first state.
if result <> 0 then
Exit;
if not invfull() atTrees() then
result := NEXT_ENUM;
end;
so let's say the character is on the specified tile that the function results with CORRESPONDING_ENUM. However, the script should have been chopping as that statement is also true because the player is by the tree's. So what happens is the player walks back to the bank instead of chopping like he should be.
This is where you are faced with one of two things. Either
A) You have to make each condition specific, meaning the first statement would become
if invfull() and distanceFrom(tile(xxx, yyy) < 3 then
or
B) You simply reconstruct the flow and put the Chop_Tree condition infront of the walking condition so the function returns that result first.
Either way works, I like to use a conjunction of both, as the more complicated you make each if..then statement the harder to understand the code becomes and tweaking turns into a bitch.
Sorry if that is confusing, I will try to readdress that later.
IV. Setting Pointers
Part A:
Pointers are simple tools that are often overlooked. I'll do a quick, simple breakdown of pointers, link some good tutorials on them and then continue with the tutorial.
A Pointer is simply a variable that can hold a function/procedure.
example
program new;
var
point: procedure;
procedure blah();
begin
writeLn('meow');
end;
begin
point := @blah;
point();
end.
same thing works with functions, however functions are a bit tricky. Say you have two functions, and you want one universal pointer. You have to keep in mind that these two functions must have the same parameters to used in conjunction with a single pointer.
example:
bad
program new;
var
point: function(): integer;
function blah(): Integer;
begin
result := 1;
end;
function blahblah(int: Integer): Integer;
begin
result := int;
end;
begin
if random(2) = 1 then
point := @blah
else
point := @blahblah;
point();
end.
good
program new;
var
point: function(): integer;
Globally_set_Var: integer;
function blah(): Integer;
begin
result := 1;
end;
function blahblah(): Integer;
begin
result := Globally_Set_Var;
end;
begin
Globally_Set_Var := 9;
if random(2) = 1 then
point := @blah
else
point := @blahblah;
writeLn(inttostr(point()));
end.
I hope that makes sense. Further explanation can be given upon request.
Part B:
Now Back to the mainloops. So we now have a setState() function created. Our conditionals set and results enumerated. Next step is to link these enums to specific functions/procedures. This is where pointers come into play.
first let's make a type to hold our pointer variables.
type
tPointerSet = record
isProc: Boolean; // This will be used later in the mainloop execution
proc: procedure;
func: function: Boolean;
name: string; // helps us keep track of the function being called
end;
next let's make an empty case statement that corresponds to the getState() function created.
procedure Loop();
begin
case getState() of
ANTIBAN: // empty
WALK: // empty
BANK: // empty
COOK: // empty
end;
end;
now we simply fill the Loop() procedure in with our pointers. It will look like so.
procedure Loop();
begin
with Pointers do
begin
case getState() of
ANTIBAN:
begin
name := 'antiban';
isProc := true;
Proc := @Anti_Ban;
Func := nil;
end;
WALK:
begin
name := 'walk';
isProc := false;
Proc := nil;
Func := @createWalkLocation;
end;
BANK:
begin
name := 'bank';
isProc := false;
Proc := nil;
Func := @Bank_Loop;
end;
COOK:
begin
name := 'cook';
isProc := true;
Proc := @Cook_Loop;
Func := nil;
end;
end;
end;
end;
so if you haven't already figured out when you wish to keep a pointer empty simply setting it to "nil" will do. The isProc variable will come into play later, bear with me for that. It is infact useful ;)
V. The MainLoop
This is the fun part. That code in an FSM Mainloop is infact quite simple (as you've seen from the example at the start of the tutorial).
here is the example, then I will go in detail about it (although it's probably not even needed).
repeat
while not LoggedIn() do
LogInPlayer();
wait(randomRange(250, 500));
setAngle(true);
while Players[CurrentPlayer].Active do
begin
Loop();
with Pointers do
begin
writeLn('entering '+name+' cycle.');
if isProc then
Proc()
else
Func();
end;
end;
until AllPlayersInactive();
the main gyst we're going to be looking at really is:
while Players[CurrentPlayer].Active do
begin
Loop();
with Pointers do
begin
writeLn('entering '+name+' cycle.');
if isProc then
Proc()
else
Func();
end;
end;
Now the isProc boolean was a failsafe to ensure that we did not call a procedure/function that has been set to nil. It ensures that the right variable is called upon.
Aside from that the loop is quite simple really. Loop() is called at the start of each succession and the proc/func is set in correspondence.
VI. Final Notes
A few final notes on my tutorial.
Regarding breaking the loop, I did not show any failsafes within the MainLoop itself. Inside my functions/procedures I simply call NextPlayer(false) or Players[CurrentPlayer].Active := false to break out of the 'while active do' loop.
If there are any questions/concerns feel free to PM me or ask here, I will be happy to help. Same goes if there are any corrections that need to be made, I wrote this quite fast and under a bit of pressure :p
I Hope you (the reader) found this, if nothing else interesting. Comments and suggestions are appreciated.
Some tutorials that you may find interesting:
Records and Psuedo-OOP (http://villavu.com/forum/showthread.php?t=43158)
Function and Procedure 'variables'. (http://villavu.com/forum/showthread.php?t=27218)
Will Edit with more later
This tutorial will require a basic knowledge in the following area(s):
Pointers
conditional statements (if..then statements etc)
system/program flow
I. Intro
Well first of all I'm sure you're wondering "what is FSM"? FSM stands for Finite-state machine (http://en.wikipedia.org/wiki/Finite-State_Machine). Basically this type of mainloop mirrors that of RSBot's in the fact that the mainloop responds to "states" set by a previous function.
A FSM loop should look something like this:
const
{enum}
BANK = 1;
WALK = 2;
COOK = 3;
ANTIBAN = 4;
{enum: an enumeration of a set is an exact listing of all of its elements (perhaps with repetition).}
type
tStatus = record
ID: Integer;
Name: string;
end;
tPointerSet = record
isProc: Boolean;
proc: procedure;
func: function: Boolean;
name: string;
end;
var
state: tStatus;
Pointers: tPointerSet;
TileArray: array of tTile;
IDArray: array of Integer;
function getState(): Integer;
var
tempID: Integer;
i: Integer;
begin
if( (getAnimation() > -1) or (CharacterMoving()) )then
result := ANTIBAN;
if( result <> 0 )then
exit;
if( distanceFrom(TileArray[_BANK]) < 4 ) then
begin
tempID := getItemIDAt(1);
if state.ID = tempID then
result := WALK
else
result := BANK;
end;
if( result <> 0 )then
exit;
if( distanceFrom(TileArray[_COOK]) < 5 ) then
begin
tempID := getItemIDAt(1);
if( state.ID = tempID )then
result := COOK
else
result := WALK;
end;
if( result = 0 )then
begin
writeLn('getState is having issues. Checking for randoms.');
for i := 0 to 15 do
begin
if( FindNormalRandoms() )then
Exit;
wait(50);
end;
writeLn('not in a Random and state enum = 0. NextPlayer');
NextPlayer(False);
Exit;
end;
end;
procedure Loop();
begin
with Pointers do
begin
case getState() of
ANTIBAN:
begin
name := 'antiban';
isProc := true;
Proc := @Anti_Ban;
Func := nil;
end;
WALK:
begin
name := 'walk';
isProc := false;
Proc := nil;
Func := @createWalkLocation;
end;
BANK:
begin
name := 'bank';
isProc := false;
Proc := nil;
Func := @Bank_Loop;
end;
COOK:
begin
name := 'cook';
isProc := true;
Proc := @Cook_Loop;
Func := nil;
end;
end;
end;
end;
var
mtile: ttile;
p: tpoint;
begin
SMART_Signed := true;
SMART_Server := 81;
setUpSRL();
setUpReflectionEx(true);
DeclarePlayers();
TileArray := [tile(3269, randomRange(3169, 3166)), tile(3273, 3181)];
setArrayLength(IDArray, 1);
IDArray := [25730];
repeat
while not LoggedIn() do
LogInPlayer();
wait(randomRange(250, 500));
setAngle(true);
while Players[CurrentPlayer].Active do
begin
Loop();
with Pointers do
begin
writeLn('entering '+name+' cycle.');
if isProc then
Proc()
else
Func();
end;
end;
until AllPlayersInactive();
end.
Don't worry I will break it all down later. For now that is just a simple example.
II. Advantages of FSM
I have found in my 2 years of scripting recalibrating a script when it is lost is a difficult task. When a random mis-click or mis-hap occurs during the runtime of the script, it can alter the script and screw it up. This is because scripts normally flow with (for lack of a better phrase) a conditional flow. By this I mean,
if not Conditonal_Function then
begin
NextPlayer(False);
Exit;
end;
if not Conditonal_Function2 then
begin
NextPlayer(False);
Exit;
end;
// and so on and so fourth.
A FSM style loop allows for a recalibration of errors and ensures a longer runtime. When the script dynamically outputs functions/procedures in a response to it's environment the script should leave few area's to falter and should pick up on these issue's.
III. Warnings/Disadvantages
First of all there should be a simple warning cautioned that developing a well functioning FSM can become difficult/complicated. This is where you will require knowledge in Program Flow. I will now address the getState() function.
function getState(): Integer;
begin
if This_is_true then
result := CORRESPONDING_ENUM; // enums must begin at 1 not 0. or else the result could/would always = 0
//after this first state.
if result <> 0 then
Exit;
if This_is_true then
result := NEXT_ENUM;
// so on and so fourth.
end;
now addressing the flow problem here goes. So the issue lies here, what if two results are present on the screen, yet you can only have one result correct? So You must order the flow of the function to work according to the script. (That's a terrible explanation I know). Ummm, here's an example.
function getState(): Integer;
begin
if distanceFrom(tile(xxx, yyy) < 3 {<--- atTrees()} then
result := CORRESPONDING_ENUM; // enums must begin at 1 not 0. or else the result could/would always = 0
//after this first state.
if result <> 0 then
Exit;
if not invfull() atTrees() then
result := NEXT_ENUM;
end;
so let's say the character is on the specified tile that the function results with CORRESPONDING_ENUM. However, the script should have been chopping as that statement is also true because the player is by the tree's. So what happens is the player walks back to the bank instead of chopping like he should be.
This is where you are faced with one of two things. Either
A) You have to make each condition specific, meaning the first statement would become
if invfull() and distanceFrom(tile(xxx, yyy) < 3 then
or
B) You simply reconstruct the flow and put the Chop_Tree condition infront of the walking condition so the function returns that result first.
Either way works, I like to use a conjunction of both, as the more complicated you make each if..then statement the harder to understand the code becomes and tweaking turns into a bitch.
Sorry if that is confusing, I will try to readdress that later.
IV. Setting Pointers
Part A:
Pointers are simple tools that are often overlooked. I'll do a quick, simple breakdown of pointers, link some good tutorials on them and then continue with the tutorial.
A Pointer is simply a variable that can hold a function/procedure.
example
program new;
var
point: procedure;
procedure blah();
begin
writeLn('meow');
end;
begin
point := @blah;
point();
end.
same thing works with functions, however functions are a bit tricky. Say you have two functions, and you want one universal pointer. You have to keep in mind that these two functions must have the same parameters to used in conjunction with a single pointer.
example:
bad
program new;
var
point: function(): integer;
function blah(): Integer;
begin
result := 1;
end;
function blahblah(int: Integer): Integer;
begin
result := int;
end;
begin
if random(2) = 1 then
point := @blah
else
point := @blahblah;
point();
end.
good
program new;
var
point: function(): integer;
Globally_set_Var: integer;
function blah(): Integer;
begin
result := 1;
end;
function blahblah(): Integer;
begin
result := Globally_Set_Var;
end;
begin
Globally_Set_Var := 9;
if random(2) = 1 then
point := @blah
else
point := @blahblah;
writeLn(inttostr(point()));
end.
I hope that makes sense. Further explanation can be given upon request.
Part B:
Now Back to the mainloops. So we now have a setState() function created. Our conditionals set and results enumerated. Next step is to link these enums to specific functions/procedures. This is where pointers come into play.
first let's make a type to hold our pointer variables.
type
tPointerSet = record
isProc: Boolean; // This will be used later in the mainloop execution
proc: procedure;
func: function: Boolean;
name: string; // helps us keep track of the function being called
end;
next let's make an empty case statement that corresponds to the getState() function created.
procedure Loop();
begin
case getState() of
ANTIBAN: // empty
WALK: // empty
BANK: // empty
COOK: // empty
end;
end;
now we simply fill the Loop() procedure in with our pointers. It will look like so.
procedure Loop();
begin
with Pointers do
begin
case getState() of
ANTIBAN:
begin
name := 'antiban';
isProc := true;
Proc := @Anti_Ban;
Func := nil;
end;
WALK:
begin
name := 'walk';
isProc := false;
Proc := nil;
Func := @createWalkLocation;
end;
BANK:
begin
name := 'bank';
isProc := false;
Proc := nil;
Func := @Bank_Loop;
end;
COOK:
begin
name := 'cook';
isProc := true;
Proc := @Cook_Loop;
Func := nil;
end;
end;
end;
end;
so if you haven't already figured out when you wish to keep a pointer empty simply setting it to "nil" will do. The isProc variable will come into play later, bear with me for that. It is infact useful ;)
V. The MainLoop
This is the fun part. That code in an FSM Mainloop is infact quite simple (as you've seen from the example at the start of the tutorial).
here is the example, then I will go in detail about it (although it's probably not even needed).
repeat
while not LoggedIn() do
LogInPlayer();
wait(randomRange(250, 500));
setAngle(true);
while Players[CurrentPlayer].Active do
begin
Loop();
with Pointers do
begin
writeLn('entering '+name+' cycle.');
if isProc then
Proc()
else
Func();
end;
end;
until AllPlayersInactive();
the main gyst we're going to be looking at really is:
while Players[CurrentPlayer].Active do
begin
Loop();
with Pointers do
begin
writeLn('entering '+name+' cycle.');
if isProc then
Proc()
else
Func();
end;
end;
Now the isProc boolean was a failsafe to ensure that we did not call a procedure/function that has been set to nil. It ensures that the right variable is called upon.
Aside from that the loop is quite simple really. Loop() is called at the start of each succession and the proc/func is set in correspondence.
VI. Final Notes
A few final notes on my tutorial.
Regarding breaking the loop, I did not show any failsafes within the MainLoop itself. Inside my functions/procedures I simply call NextPlayer(false) or Players[CurrentPlayer].Active := false to break out of the 'while active do' loop.
If there are any questions/concerns feel free to PM me or ask here, I will be happy to help. Same goes if there are any corrections that need to be made, I wrote this quite fast and under a bit of pressure :p
I Hope you (the reader) found this, if nothing else interesting. Comments and suggestions are appreciated.
Some tutorials that you may find interesting:
Records and Psuedo-OOP (http://villavu.com/forum/showthread.php?t=43158)
Function and Procedure 'variables'. (http://villavu.com/forum/showthread.php?t=27218)
Will Edit with more later