This tutorial is a continuation of Lua Scripting Tutorial (Beginner 2), which covered working with recipes in the Foldit recipe editor, and how to use external editors.
The previous two tutorials sample recipes, but didn't really examine how the recipes worked. The sample Lua recipes were just a simple list of instructions, similar to the original GUI recipes in Foldit.
In this tutorial, you'll begin to make changes to make the sample recipes more like the Lua recipes commonly used in Foldit.
This tutorial introduces some key programming concepts not found in GUI recipes:
- if-then-else statements
This tutorial also demonstrates the Foldit "recentbest" functions, which are one way that Lua recipes keep track of the best score.
This tutorial includes some programming terms in italics, but it doesn't always define these terms completely. The Lua 5.3 reference manual provides somewhat technical explanations of everything, or there's always Google....
New recipe featuresEdit
As in the previous tutorials, open one of the beginner or revisting puzzles. You may want to reset the puzzle if you've already used it in the previous tutorials.
Download Beginner Rebuild SCRIPT 3.01 and try running it. If you check the recipe output window, you'll see there are several changes from the previous version of the recipe:
- scores are now rounded to three decimal places
- a gain or loss is reported at the end of the recipe
- if there's a loss, the score is reset to the previous score
Open the new recipe in the recipe editor. Optionally, you can save the new recipe to a file, and open it in an external editor, as discussed in Lua Scripting Tutorial (Beginner 2).
The basic idea of the recipe hasn't changed, but there are several new sections of code.
The first new bit of code is a function called "round". Previous versions of this recipe have called existing functions like print, and all the Foldit commands, but this is a new "user-defined" function.
The function is:
function round ( x ) return x - x % 0.001 end
These lines define what the function does, but they don't do anything by themselves. You must "call" the function elsewhere in the recipe for it to do anything. The place where the function is called from is referred to as "the caller".
The first line just tell Lua that this is a function called round, and it takes one argument, a variable called "x". The value of "x" is available inside the function, between the function statement and the end statement.
The second line is a return statement, which is where the function finishes. This function returns a value to its caller. Some functions have just "return", with nothing else, in which case nothing gets returned to the caller.
The returned value from round is "x - x % 0.001". This takes a little explaining. First, "x" is the value passed by the caller. For this function to work, "x" should be a number.
Next, "%" is the "mod" or "modulus" operator, just like "+" is the addition operator or "-" is the subtraction operator. The mod operator gives you the remainder of when the first number is divided by the second. The mod operator has higher precedence than "+" or "-", so the "x % 0.001" part is done first.
The next step is to subtract the remainder from the original value of "x". This gives the number rounded to three places, which is what gets returned to the caller.
If this is confusing, try inserting this version of function round:
function round ( x ) print ( "initial value of x = " .. x ) remX = x % 0.001 print ( "x mod 0.001 = " .. remX ) newX = x - remX print ( "new value of x = " .. newX ) return newX end
in place of the one in the recipe, then run the recipe again.
In this example, "x", "remX", and "newX" are all variables. Variables are just placeholders that let you store something for future use.
Variations on this function are used in many Foldit recipes.
The next section includes an example of calling function round.
The main part of the new script uses two variables: "preScore", the score before the rebuild, and "postScore" the score after the rebuild. It's common for recipes to keep a starting score handy, so they can tell whether they've improved the protein.
This statement saves the starting score in "preScore":
preScore = current.GetEnergyScore ()
As seen in the previous version of this script, current.GetEnergyScore is one of the Foldit Lua Functions. It returns the score of the protein. The previous script used current.GetEnergyScore in print statement, but this version saves the result for later.
The saved value is used right away in this print statement
print ( "Score before rebuilding is " .. round ( preScore ) )
The round function seen in the previous section is used to round the score to three decimal places.
The "preScore" variable is also used again at the end of the recipe, along with the similar "postScore" variable, which is the score after the rebuild.
The new recipe ends with these new lines:
if postScore > preScore then print ( "gain is " .. round ( postScore - preScore ) ) else print ( "loss is " .. round ( preScore - postScore ) .. ", resetting to recentbest pose" ) recentbest.Restore () -- reset to previous pose end
If you're using a programming editor like Notepad++, the words "if", "then", "else", and "end" are highlighted.
The if statement includes a logical expression, which is either true or false. If the expression is true, the statements aafter the if but before the else are performed. If the expression is false, the expressions between the else and the end are performed instead.
The logical expression is "postScore > preScore", which translates to "is the value of the variable postScore greater than the value of the variable preScore?".
The else part is optional. If it's omitted the statements between if and end are executed if the logical expression is true.
Both the if part and the else part can have multiple statements. In this example, both the if and the else include a print statement, but the else includes one addtional statement: recentbest.Restore. This new function is explained in the next section.
The new recipe introduces two new statements:
These functions are part of the Foldit Lua Interface, and correspond to "Set Recent Best" and "Restore Recent Best" in the Undo menu.
At the beginning of the recipe, recentbest.Save sets the recent best to the current position and score.
At the end of the recipe, if there's a loss, recentbest.Restore restores the protein to its best recent position.
The tricky part is the "best recent position" is either where things were at the last recentbest.Save, or the best position reached since then.
In this recipe, it's just possible that the rebuild could have improved the starting score, only to have the shake and wiggle generate a loss. This is very unlikely with these specific commands, but its possible with a different series of operations. Foldit automatically updates the "recentbest" as it goes along. So recentbest.Restore may result in a better score than the one recentbest.Save saved.
Most Foldit recipes try to leave you with the best score so far. A few special-purpose recipes may generate a loss, hoping for a gain with a later recipe. The recentbest functions are one way of keeping track of the best score.
This tutorial focused more on the programming aspects of Lua scripts in Foldit. Functions, variables, and if-then-else statements are things you should understand when changing or creating scripts.
See Lua Scripting Tutorial (Beginner 3 Tech Supplement) for more on the new programming features in this tutorial.
Lua Scripting Tutorial (Beginner 4) will continue to refine the sample recipe, adding a new idea: looping.
|Back to Lua Scripting Tutorial (Beginner 2)||Continue with Lua Scripting Tutorial (Beginner 4)|