Code
--
-- Freeze Selected - freezes selected segments
--
-- As suggested by Keresto on Foldit Discord, 22 March 2020
--
-- Optionally, adds a strong zero-length band to each segment,
-- thanks to Paul Dunn for the original
--
-- 1.0 - LociOiling - 22 March 2020
-- + slightly streamlined version of a complete recipe, see notes at end
--
--
-- global variables
--
Recipe = "Freeze Selected"
Version = "1.0"
ReVersion = Recipe .. " " .. Version
frbb = true -- dialog option: freeze backbone
frsc = false -- dialog option: freeze backbone
pegbb = false -- dialog option: peg backbone with short zero-length band
pegstr = 10 -- dialog option: peg strength
--
-- end of global variables
--
--
-- GetOpt - create a dialog, prompt user for options
--
function GetOpt ()
--
-- create the dialog - "menu" can be any name
--
local menu = dialog.CreateDialog ( ReVersion )
--
-- add fields to the dialog
--
menu.frbb = dialog.AddCheckbox ( "freeze backbone", frbb )
menu.frsc = dialog.AddCheckbox ( "freeze sidechain", frsc )
menu.backbone = dialog.AddCheckbox ( "peg backbone", pegbb )
menu.strength = dialog.AddSlider ( "peg strength", pegstr, 0, 10, 1 )
--
-- add buttons to the dialog, and the value that's returned when they're clicked
--
menu.Run = dialog.AddButton ( "Run", 1 )
menu.Cancel = dialog.AddButton ( "Cancel", 0 )
--
-- display the dialog
--
local rc = dialog.Show ( menu )
--
-- if the user clicked "Run", retrieve the values using the "value" operator
if rc == 1 then
frbb = menu.frbb.value
frsc = menu.frsc.value
pegbb = menu.backbone.value
pegstr = menu.strength.value
end
return rc > 0
end
function main ()
Ident ( ReVersion ) -- identify the recipe and the puzzle
print ( "--" )
--
-- call the GetOpt function to get the options,
-- then go ahead if the user clicks "Run"
--
if GetOpt () then
print ( "Options:" )
print ( "freeze backbone = " .. tostring ( frbb ) ) -- use tostring for true/false boolean values
print ( "freeze sidechain = " .. tostring ( frsc ) ) -- use tostring for true/false boolean values
print ( "peg backbone = " .. tostring ( pegbb ) ) -- use tostring for true/false boolean values
if pegbb then
print ( "peg strength = " .. pegstr ) -- number and string don't need tostring
end
undo.SetUndo ( false ) -- allow user to undo in one step
behavior.SetFiltersDisabled ( true ) -- turn off filters for speed
lastseg = structure.GetCount() -- get the total number of segments
local slcount = 0 -- number of selected segments
local bbcount = 0 -- number of backbone freezes
local sccount = 0 -- number of sidechain freezes
local pegcount = 0 -- number of backbone pegs added
--
-- for each segment
--
for seg = 1, lastseg do
--
-- if the segment is selected
--
if selection.IsSelected ( seg ) then
--
-- freeze the segment (don't worry about whether it's already frozen)
--
-- the backbone and sidechain can be frozen separately
--
freeze.Freeze ( seg, frbb, frsc )
--
-- check to see what's frozen
--
local bbfrz, scfrz = freeze.IsFrozen ( seg )
if bbfrz then
bbcount = bbcount + 1
end
if scfrz then
sccount = sccount + 1
end
--
-- if we're added "pegs"...
--
if pegbb then
--
-- determine a couple of reference segments
--
local segx = seg - 1
local segy = seg + 1
if segx < 1 then
segx = lastseg
end
if segy > lastseg then
segy = 1
end
--
-- add a short (length 1e-10 angstrom) band
--
local b = band.Add ( seg, segx, segy, 1e-10, 0, 0)
if b ~= nil and b ~= 0 then
band.SetGoalLength ( b, 0 ) -- this band wants to be zero angstroms long
band.SetStrength ( b, pegstr ) -- set strength of band
pegcount = pegcount + 1
end
end
end
end
--
-- print out the totals
--
print ( slcount .. " segments selected" )
print ( bbcount .. " segment backbones frozen" )
print ( sccount .. " segment sidechains frozen" )
print ( pegcount .. " backbone pegs added" )
print ( band.GetCount () .. " total bands" )
end
--
-- exit using the cleanup routine
--
cleanup ()
end
--
-- Ident - print identifying information at beginning and end of recipe
--
function Ident ( slugline )
local function round ( ii )
return ii - ii % 0.001
end
print ( slugline )
print ( "Puzzle: " .. puzzle.GetName () .. " (" .. puzzle.GetPuzzleID () .. ")" )
print ( "Track: " .. ui.GetTrackName () )
local luser = user.GetPlayerName ()
local scoretype = scoreboard.GetScoreType ()
local scort = ""
if scoretype == 0 then
scort = "soloist"
elseif scoretype == 1 then
scort = "evolver"
elseif scoretype == 2 then
scort = "all hands"
elseif scoretype == 3 then
scort = "no score"
else
scort = "unknown/error"
end
print ( "User: " .. luser )
print ( "Rank: " .. scoreboard.GetRank ( scoretype ) .. " (" .. scort .. ")" )
local sGroup = scoreboard.GetGroupScore ()
if sGroup ~= nil then
print ( "Group rank / score: " .. scoreboard.GetGroupRank () .. " / " .. round ( 10 * ( 800 - sGroup ) ) )
end
end
function cleanup ( errmsg )
if CLEANUPENTRY ~= nil then
return
end
CLEANUPENTRY = true
print ( "---" )
local reason
local start, stop, line, msg
if errmsg == nil then
reason = "complete"
else
start, stop, line, msg = errmsg:find ( ":(%d+):%s()" )
if msg ~= nil then
errmsg = errmsg:sub ( msg, #errmsg )
end
if errmsg:find ( "Cancelled" ) ~= nil then
reason = "cancelled"
else
reason = "error"
end
end
Ident ( ReVersion .. " " .. reason )
if reason == "error" then
print ( "Unexpected error detected" )
print ( "Error line: " .. line )
print ( "Error: \"" .. errmsg .. "\"" )
end
--
-- turn on undo again
--
undo.SetUndo ( true )
--
-- turn on filters again
--
behavior.SetFiltersDisabled ( false )
end
xpcall ( main, cleanup )
--
--
-- Freeze Selected - program notes
--
-- Thanks to Keresto for the suggestion
--
-- Thanks for Paul Dunn for the original "peg" logic
--
-- Thanks to Bruno Kestemont and Jean-Bob for the cleanup logic
-- that deciphers the error message
--
-- As the name implies, Freeze Selected freezes any selected segments.
--
-- This recipe really requires the selection interface, since you
-- can't select segments in the original interface.
--
-- Optionally, Freeze Selected adds a zero-length band to the backbone
-- of each segment it freezes.
--
-- Freezing and zero-length bands are useful during hand-folding.
-- They can help keep the protein from changing shape too much
-- during wiggle.
--
-- This recipe also demonstrates a typical pattern, plus some
-- bells and whistles I like to add.
--
-- First, the recipe is bottom-up. The first statement that actually
-- "runs" is the xpcall, located just above these comments:
--
-- xpcall ( main, cleanup )
--
-- xpcall is built in to Lua, and provides error handling.
--
-- The names "main" and "cleanup" can be any Lua name, but you
-- must provide matching functions.
--
-- xpcall is going to call the function "main", and if any errors
-- occur, the function "cleanup" will catch the error.
--
-- In this example, all the cleanup function does is reformat the
-- Lua error message a little, making it easier to read. The cleanup
-- can also reset things. In this case, cleanup turns filters back on,
-- since they were likely turned off earlier in the recipe.
--
-- The "main" function is where most of the action is. It first calls
-- the "Ident" function, which just prints some information. Ident
-- is in the "bells and whistles" category, but its output can help
-- to reduce confusion.
--
-- The next step is to call the "GetOpt" function, which displays a
-- dialog, prompting the user for options. GetOpt is a typically
-- dialog. It calls dialog.CreateDialog, creating a dialog instance
-- called "menu". Again, "menu" can be any Lua name. Then GetOpt
-- adds various fields to the dialog, and calls dialog.Show to display
-- the dialog.
--
-- In GetOpt, "menu" is actually a Lua table. Tables Are Everywhere
-- in Lua.
--
-- The dialog.Show returns different integer value, depending on which
-- button the user clicked. In this case, the "Run" button returns 1,
-- which means "go ahead and freeze". GetOpt updates the options in
-- this case.
--
-- Each of the options in "menu" is in turn a table, and each option
-- table has a field "value", which contains the final value of the
-- option. So a line like
--
-- frbb = menu.frbb.value
--
-- retrieves the value of the option frbb from the menu table, and
-- stores it in the variable "frbb". Using the name "frbb" for both
-- the lone variable and the menu table entry is just a convention,
-- again any valid Lua name will do, but keeping them the same is
-- less confusing.
--
-- One other thing to note is scoping in Lua. The options variables
-- like "frbb" are defined up near the top, with a comment indicating
-- they are global variables. Actually any variable or function in
-- Lua is global, unless you make it "local". This can cause problems
-- as your recipe grows, so it's better to be conscious about using
-- "local" unless you really really want a global.
--
-- Back in main, after the call to GetOpt, these a very typical "for"
-- loop, one you'll see in many recipes. It covers each segment in the
-- puzzle, from 1 to structure.GetCount.
--
-- In the for loop, the first step is to call select.IsSelected to see
-- whether the segment ("seg") is selected. Again, the selection
-- interface is required for anything to be selected at this point.
--
-- If the segment is selected, the recipe calls freeze.Freeze to freeze it.
--
-- Optionally, the recipe adds a "peg" at this point, using band.Add.
--
-- The wiki explains the very complicated band.Add function:
--
-- https://foldit.fandom.com/wiki/Foldit_Lua_Function_band.Add
--
-- band.Add's complexity means it's likely to produce an error, which
-- is where the cleanup function would come in. The values specified
-- in this recipe are pretty safe, however, so not likely to fail.
--
-- The same process of checking, freezing, and band is repeated for
-- each segment.
--
-- The recipe doesn't check to see if a selected segment is already
-- frozen. If it's already frozen, it will stay frozen, and freezing
-- again doesn't produce an error.
--
-- If the user unchecks both "freeze backbone" and "freeze sidechains",
-- then nothing gets frozen, but a band will still be added if
-- the "peg" option is checked.
--
-- The recipe also makes a couple of more obscure calls:
--
-- undo.SetUndo ( false )
-- behavior.SetFiltersDisabled ( true )
--
-- The undo.SetUndo call temporarily turns off the undo "stack".
-- The player will be able to undo everything the recipe does
-- with a single undo (control-z).
--
-- The recipe turns on undo again in the cleanup routine.
--
-- The behavior.SetFiltersDisabled temporarily turns off any
-- filters, to speed things up. Turning off filters also makes
-- the score temporarily invalid. The user will see a red line
-- through the score while the recipe is running.
--
-- The cleanup routine turns filters on again at the end.
--
-- The very last thing the "main" function does is call the
-- "cleanup" function. This isn't required, but it saves having
-- to do things like turning on undo and filters in two places.
--
-- When main calls cleanup, it doesn't pass any arguments, so
-- cleanup knows it's a normal end, and skips the error checking
-- logic.
--