Code
--[[
HNetwork Probe 2.1 - spvincent - 8 October 2020
2.2 - LociOiling - 12 October 2020
+ optionally skip initial mutation of all segments
+ optionally shuffle the list of mutable segments
+ gracefully handle cases where there's nothing to do
+ fluffier dialog
+ more complete cleanup, catch script errors
+ some minor polishing, Luafication, and Locifaction
+ print progress messages, more if VERBOSE
+ filch some EDRW code from TvdL
+ pilfer wiggle factor from Timo, too
+ outright theft of better randomseed from KarenCH
+ "borrow" ShuffleTable from Rav3n_pl
+ validate those AA codes
2.3 - LociOiling - 13 October 2020
+ make named saves when hydrogen bond network score improves
+ report initial score and filter scores
+ track overall score and filter changes
+ reset filters at end, report final score
+ don't save solutions with the same overall score
+ reserve slot 1 for starting pose
+ restore starting pose at end if no gain
]]--
Recipe = "HNetworkProbe"
Version = "2.3"
ReVersion = Recipe .. " " .. Version
--
-- globals section
--
polar_aas = "stnqdehyw"
n_polar_aas = 0
n_residues = 0
first_qs_slot = 13
last_qs_slot = 35
next_qs_slot = 0
start_slot = 1
FIRST_FREE_SLOT = start_slot + 1
MAX_SLOT = 99
select_unfrozen = true
sidechain_minimum = -70
n_it = 0
mutate_list_ui = {} -- will be a list of 1's & 0's for each residue. 1 means can mutate. 0 means don't mutate.
mutable_list = {}
filter_list = {}
filter_stat = {}
init_score = 0
best_score = 0
init_filter_score = 0
best_filter_score = 0
WF = 1 -- wiggle like Timo
VERBOSE = false -- what's happening?
skip_initial_mutate = false
shuffle_mutables = true
slot_stat = {} -- track the score saved in each slot
--
-- AminoAcids
--
-- short version of amino acids table for validation and display
--
--
AminoAcids = {
a = { polar = false, short = "Ala", long = "Alanine", },
c = { polar = false, short = "Cys", long = "Cysteine", },
d = { polar = true, short = "Asp", long = "Aspartate", },
e = { polar = true, short = "Glu", long = "Glutamate", },
f = { polar = false, short = "Phe", long = "Phenylalanine", },
g = { polar = false, short = "Gly", long = "Glycine", },
h = { polar = true, short = "His", long = "Histidine", },
i = { polar = false, short = "Ile", long = "Isoleucine", },
k = { polar = false, short = "Lys", long = "Lysine", },
l = { polar = true, short = "Leu", long = "Leucine", },
m = { polar = false, short = "Met", long = "Methionine", },
n = { polar = true, short = "Asn", long = "Asparagine", },
p = { polar = false, short = "Pro", long = "Proline", },
q = { polar = true, short = "Gln", long = "Glutamine", },
r = { polar = true, short = "Arg", long = "Arginine", },
s = { polar = true, short = "Ser", long = "Serine", },
t = { polar = true, short = "Thr", long = "Threonine", },
v = { polar = false, short = "Val", long = "Valine", },
w = { polar = true, short = "Trp", long = "Tryptophan", },
y = { polar = true, short = "Tyr", long = "Tyrosine", },
}
--
-- end of globals section
--
function r3 ( x )
-- Round to 3 decimal places
t = 10 ^ 3
return math.floor ( x*t + 0.5 ) / t
end
--
-- get score, including metrics
--
function GetScore ()
local scor = current.GetEnergyScore ()
local mbon = 0
--
-- check for the "metric" functions,
-- report the total metric bonus if
-- this puzzle has metrics defined
--
if metric ~= nil then
if metric.GetNames () ~= nil then
mbon = metric.GetBonusTotal ()
end
end
return scor + mbon, mbon
end
function GetFilterScore ()
filter_bonus = filter.GetBonus ( "Hydrogen Bond Network" )
return filter_bonus
end
function GetListOfMutablesUnfrozen ()
for ii = 1, n_residues do
localseg_backbone_frozen, seg_sidechain_frozen = freeze.IsFrozen ( ii )
if seg_sidechain_frozen == false
-- and seg_backbone_frozen == false
and structure.IsMutable ( ii ) == true then
table.insert ( mutable_list , ii )
end
end
end
function GetListOfMutablesSelected ()
for ii = 1, n_residues do
local seg_backbone_frozen, seg_sidechain_frozen = freeze.IsFrozen ( ii )
if seg_sidechain_frozen == false
and mutate_list_ui [ ii ] == 1
and structure.IsMutable ( ii ) == true then
table.insert ( mutable_list , ii )
end
end
end
--
-- getlist - pinched from Loop Rebuild 9.0
-- http://www.lua.org/manual/5.2/manual.html#6.4
-- helped make this function 11/16/17
--
function getlist ( liststr )
local newlist = {}
local ilo, ihi, idir, substr
--
-- the variable ii here exists for the scope
-- of the for loop, no need to define another
-- one, which would have different scope
--
for ii = 1, n_residues do
newlist [ ii ] = 0
end -- for ii
--
-- the Lua regular expression below,
-- used with string.gmatch,
-- reads a series of substrs from liststr,
-- where each substr contains an integer
-- followed by one or more '-' signs,
-- followed by an integer
--
for substr in liststr:gmatch ( "(%d+%-+%d+)" ) do
-- substr includes one or more '-' characters in a row
--
-- get ilo & ihi from substr using string.gsub
-- the same regex is used twice
-- the regex contains two expressions in parens
-- the first gsub extracts the first expression,
-- the "from", and the second gsub gets the "to"
--
ilo = substr:gsub ( "(%d+)%-+(%d+)", "%1" ) + 0
ihi = substr:gsub ( "(%d+)%-+(%d+)", "%2" ) + 0
idir = 1 -- the increment to use from ilo to ihi
if ilo > ihi then
idir = -1
end -- if ilo
--
-- add an entry for each segment in the range
-- any invalid entries are silently rejected here
--
for ii = ilo, ihi, idir do -- for i=ilo to ihi step idir
if ii >= 1 and ii <= n_residues then
newlist [ ii ] = 1
end -- if ii
end -- for ii
end -- for substr
--
-- the Lua regex below
-- reads a series of substrs from liststr,
-- where each substr contains an integer
-- by itself
--
for substr in liststr:gmatch ( "(%d+)" ) do
local ij = substr + 0 -- converts substr into the number ij
if ij >= 1 and ij <= n_residues then
newlist [ ij ] = 1
end -- if i
end -- for substr
return newlist
end
--
-- SegmentListToSet, SegmentSetToString
-- Originals by Timo van der Laan:
-- 02-05-2012 TvdL Free to use for non commercial purposes
--
SegmentListToSet = function ( list ) -- retirer doublons
local result = {}
local ff = 0
local ll = -1
table.sort ( list )
for ii = 1, #list do
if list [ ii ] ~= ll + 1 and list [ ii ] ~= ll then
-- note: duplicates are removed
if ll > 0 then
result [ #result + 1 ] = { ff, ll }
end
ff = list [ ii ]
end
ll = list [ ii ]
end
if ll > 0 then
result [ #result + 1 ] = { ff, ll }
end
return result
end
SegmentSetToString = function ( set ) -- pour pouvoir imprimer
local line = ""
for ii = 1, #set do
if ii ~= 1 then
line = line .. ", "
end
line = line .. set [ ii ] [ 1 ] .. "-" .. set [ ii ] [ 2 ]
end
return line
end
--
-- seedRandom -- original by KarenCH
--
-- looks for a seed > 10,000,000 and < 2 ^ 32
--
-- v2 - LociOiling - 20191103
-- * added 2 ^ 32 overflow check
--
function seedRandom()
local seed = os.time () / math.abs ( current.GetEnergyScore () )
seed = seed % 0.001
seed = 1 / seed
while seed < 10000000 do
seed = seed * 1000
end
while seed > 2 ^ 32 do
seed = seed / 10
end
seed = seed - seed % 1
print ( "Random number seed = " .. seed )
math.randomseed( seed )
-- throw away a couple of randoms
math.random ()
math.random ()
end
--
-- Thanks to Rav3n_pl
--
function ShuffleTable ( tab ) --randomize order of elements
local cnt = #tab
for i = 1, cnt do
local r = math.random ( cnt )
tab [ i ], tab [ r ]= tab [ r ], tab [ i ]
end
return tab
end
function Init ()
save.Quickload ( start_slot )
if not skip_initial_mutate then
if VERBOSE then
print ( "initial mutations" )
end
for ii = 1, #mutable_list do
local kk = math.random ( n_polar_aas )
local replacement_aa = polar_aas:sub ( kk, kk )
if VERBOSE then
print ( "changing segment " .. mutable_list [ ii ] .. " to \"" .. replacement_aa .. "\"" )
end
if structure.CanMutate ( mutable_list [ ii ] , replacement_aa ) then
structure.SetAminoAcid ( mutable_list [ ii ] , replacement_aa )
end
end
end
selection.SelectAll ()
structure.WiggleSelected ( 2 * WF, false, true ) -- sidechains
end
function PrintParameters ()
print ( "--" )
print ( "wiggle factor = " .. WF )
print ( "skip initial mutate = " .. tostring ( skip_initial_mutate ) )
print ( "shuffle mutables = " .. tostring ( shuffle_mutables ) )
print ( #mutable_list .. " mutable segments =\"" .. SegmentSetToString ( SegmentListToSet ( mutable_list ) ) .. "\"" )
print ( polar_aas:len() .. " amino acids to use = \"" .. polar_aas .. "\"" )
print ( "Sidechain minimum = " .. r3 ( sidechain_minimum ) )
print ( "Using slots " .. first_qs_slot .. " to " .. last_qs_slot )
print ( "verbose output = " .. tostring ( VERBOSE ) )
end
function GetParameters ()
local uerror = ""
local rc
repeat
changed = false
local dlog = dialog.CreateDialog ( ReVersion )
dlog.WF = dialog.AddSlider ( "Wiggle factor" , WF, 1, 5, 0 )
dlog.skip_initial_mutate = dialog.AddCheckbox ( "Skip initial mutates" , skip_initial_mutate )
dlog.shuffle_mutables = dialog.AddCheckbox ( "Shuffle mutable list" , shuffle_mutables )
dlog.M0000 = dialog.AddLabel ( "" )
dlog.M0001 = dialog.AddLabel ( "Select unfrozen works on unfrozen sidechains:" )
dlog.use_unfrozen = dialog.AddCheckbox ( "Select unfrozen" , select_unfrozen )
dlog.M0002 = dialog.AddLabel ( "If select unfrozen is not checked," )
dlog.M0003 = dialog.AddLabel ( "segment ranges are used." )
dlog.mutate_list = dialog.AddTextbox ( "Use ranges" , "" )
dlog.mlabela = dialog.AddLabel('Specify segment ranges like 1-3,6,19-30,45-62')
dlog.M0005 = dialog.AddLabel ( "Specify the amino acids to try" )
dlog.aa_list = dialog.AddTextbox ( "Amino acids" , polar_aas )
dlog.M0009 = dialog.AddLabel ( "" )
dlog.M0010 = dialog.AddLabel ( "Minimum sidechain score after mutation," )
dlog.M0011 = dialog.AddLabel ( "lower scoring mutations are skipped." )
dlog.sidechain_minimum = dialog.AddSlider ( "Sidechain minimum" , sidechain_minimum , -200 , -20 , 0 )
dlog.M0019 = dialog.AddLabel ( "" )
dlog.M0020 = dialog.AddLabel ( "Quick save slot range" )
dlog.start_qs_slot = dialog.AddSlider ( "Start quick save", first_qs_slot, FIRST_FREE_SLOT, MAX_SLOT, 0 )
dlog.end_qs_slot = dialog.AddSlider ( "End quick save", last_qs_slot, FIRST_FREE_SLOT, MAX_SLOT, 0 )
dlog.M0029 = dialog.AddLabel ( "" )
dlog.VERBOSE = dialog.AddCheckbox ( "Verbose output" , VERBOSE )
if uerror:len () > 0 then
dlog.lerr1 = dialog.AddLabel ( "" )
dlog.lerr2 = dialog.AddLabel ( "ERROR: " .. uerror )
end
dlog.ok = dialog.AddButton ( "OK" , 1 )
dlog.cancel = dialog.AddButton ( "Cancel" , -1 )
rc = dialog.Show ( dlog )
uerror = ""
if rc > 0 then
WF = dlog.WF.value
skip_initial_mutate = dlog.skip_initial_mutate.value
shuffle_mutables = dlog.shuffle_mutables.value
select_unfrozen = dlog.use_unfrozen.value
mutate_list_ui = getlist(dlog.mutate_list.value)
polar_aas = dlog.aa_list.value
uerror = CheckAAs ( polar_aas )
sidechain_minimum = dlog.sidechain_minimum.value
first_qs_slot = dlog.start_qs_slot.value
last_qs_slot = dlog.end_qs_slot.value
if first_qs_slot > last_qs_slot then
uerror = "first quick save " .. first_qs_slot .. " greater than last quick save " .. last_qs_slot
end
--
-- these are Should Not Occur Errors (SNOCR), since the dialog uses a slider...
--
if first_qs_slot < 1 or first_qs_slot > 99 then
uerror = "first quick save " .. first_qs_slot .. " invalid, valid range 1-99"
end
if last_qs_slot < 1 or last_qs_slot > 99 then
uerror = "last quick save " .. last_qs_slot .. " invalid, valid range 1-99"
end
VERBOSE = dlog.VERBOSE.value
end
until rc < 0 or uerror:len () == 0
if rc > 0 then
return true
else
return false
end
end
--
-- just being thorough here
--
function CheckAAs ( aalist )
local err = ""
for ii = 1, aalist:len () do
local aac = aalist:sub ( ii, ii )
local aar = AminoAcids [ aac ]
if aar == nil then
err = "invalid amino acid \"" .. aac .. "\" specified"
break
else
if not aar.polar then
err = "amino acid \"" .. aac .. "\" is not polar"
break
end
end
end
return err
end
function NoMutablesFound ()
local ask = dialog.CreateDialog ( ReVersion .. " no mutables found" )
ask.l15 = dialog.AddLabel ( "No mutable segments found in this puzzle." )
ask.l20 = dialog.AddLabel ( "This recipe only works on design puzzles," )
ask.l30 = dialog.AddLabel ( "which have segments that can be mutated (mutable segments)." )
ask.l35 = dialog.AddLabel ( "Also, a \"Hydrogen Bond Network\" condition is required." )
ask.OK = dialog.AddButton ( "OK", 1 )
dialog.Show ( ask )
end
function NoMutablesSpecd ()
local ask = dialog.CreateDialog ( ReVersion .. " no mutables specified" )
ask.l15 = dialog.AddLabel ( "No mutable segments specified." )
ask.l20 = dialog.AddLabel ( "You can specify which segments to mutate in two ways:" )
ask.l25 = dialog.AddLabel ( "1) freeze all, then unfreeze the sidechains you want to mutate" )
ask.l35 = dialog.AddLabel ( "2) specifying numeric ranges in the segment ranges box" )
ask.OK = dialog.AddButton ( "OK", 1 )
dialog.Show ( ask )
end
function NoHbondFilter ()
local ask = dialog.CreateDialog ( ReVersion .. " no Hydrogen Bond Network condition" )
ask.l15 = dialog.AddLabel ( "This puzzle doesn't have a condition (filter)" )
ask.l20 = dialog.AddLabel ( "called \"Hydrogen Bond Network\"." )
ask.l25 = dialog.AddLabel ( "This recipe only works on puzzles with this condition." )
ask.OK = dialog.AddButton ( "OK", 1 )
dialog.Show ( ask )
end
function main ()
Ident ( ReVersion )
--
-- initialize
--
seedRandom ()
n_residues = structure.GetCount ()
n_mutables = 0
for ii = 1, n_residues do
if structure.IsMutable ( ii ) then
n_mutables = n_mutables + 1
end
end
if n_mutables == 0 then
NoMutablesFound ()
cleanup () -- cleanup is just a regular function call here
return -- we still need to leave once cleanup is done
end
filter.EnableAll ()
init_score = GetScore ()
print ( "initial score = " .. r3 ( init_score ) )
best_score = init_score
local hbond_filter = false
filter_list = filter.GetNames ()
print ( #filter_list .. " Filters" )
for ii = 1, #filter_list do
local filt = {
name = "",
hasbonus = false,
initbonus = 0,
currbonus = 0,
initsatisfied = false,
currsatisfied = false,
}
filt.name = filter_list [ ii ]
if not filter.IsEnabled ( filt.name ) then
filter.Enable ( filt.name )
end
filt.hasbonus = filter.HasBonus ( filt.name )
if filt.hasbonus then
filt.initbonus = filter.GetBonus ( filt.name )
end
filt.initsatisfied = filter.ConditionSatisfied ( filt.name )
filter_stat [ #filter_stat + 1 ] = filt
local filtinfo = ""
if filt.hasbonus then
filtinfo = filtinfo .. "score = " .. r3 ( filt.initbonus )
else
if filt.initsatisfied then
filtinfo = filtinfo .. "satsified"
else
filtinfo = filtinfo .. "not satisfied"
end
end
if filt.name == "Hydrogen Bond Network" then
hbond_filter = true
filter.Enable ( filt.name )
print ( "Enabling " .. filt.name .. ", " .. filtinfo )
else
filter.Disable ( filt.name )
print ( "Disabling " .. filt.name .. ", " .. filtinfo )
end
end
if not hbond_filter then
NoHbondFilter ()
cleanup ()
return
end
init_filter_score = GetFilterScore ()
print ( "initial hydrogen bond network score " .. r3 ( init_filter_score ) )
band.DisableAll ()
undo.SetUndo ( false )
save.Quicksave ( start_slot )
if GetParameters () == false then
cleanup ()
return -- graceful exit
end
--
-- Lua method calls can be used where available
-- in this case, polar_aa:len() is the method version,
-- equivalent to string.len ( polar_aas )
--
n_polar_aas = polar_aas:len()
if select_unfrozen == true then
GetListOfMutablesUnfrozen ()
else
GetListOfMutablesSelected ()
end
PrintParameters ()
if #mutable_list == 0 then
NoMutablesSpecd ()
cleanup ()
return
end
print ( "--" )
next_qs_slot = first_qs_slot
Init ()
local mutidx = 0
if shuffle_mutables then
mutable_list = ShuffleTable ( mutable_list )
end
while next_qs_slot <= last_qs_slot do
n_it = n_it + 1
if VERBOSE then
print ( "begin run " .. n_it )
end
--
-- shuffle the mutables each time, process each entry once per iteration
--
if shuffle_mutables then
mutidx = mutidx + 1
if mutidx > #mutable_list then
mutidx = 0
end
if mutidx == 0 then
mutable_list = ShuffleTable ( mutable_list )
mutidx = 1
end
--
-- else pick a random mutable, can repeat the same segment multiple times
--
else
mutidx = math.random ( #mutable_list )
end
local mutate_seg = mutable_list [ mutidx ]
if VERBOSE then
print ( "run " .. n_it .. ", probing segment " .. mutate_seg )
end
local nn = 0
local valid_replacement_found = false
local replacement_aa = ""
-- a bit untidy this code
repeat
nn = nn + 1
local kk = math.random ( n_polar_aas )
replacement_aa = string.sub ( polar_aas, kk, kk )
if structure.CanMutate ( mutate_seg, replacement_aa ) == true then
structure.SetAminoAcid ( mutate_seg, replacement_aa )
sidechain_score = current.GetSegmentEnergySubscore ( mutate_seg , "Sidechain" )
if VERBOSE then
print ( "mutation "
.. nn ..
" for segment "
.. mutate_seg ..
" to \""
.. replacement_aa ..
"\", sidechain score "
.. r3 ( sidechain_score ) )
end
if sidechain_score > sidechain_minimum then
valid_replacement_found = true
end
end
until nn > 5 or valid_replacement_found == true
selection.SelectAll ()
--structure.ShakeSidechainsSelected ( 1 )
structure.WiggleSelected ( 2 * WF, false, true ) -- all selected, but wiggle sidechains only
filter_score = GetFilterScore ()
if filter_score > init_filter_score then
print ( "run " .. n_it .. ", segment " .. mutate_seg .. ", mutated to \"" .. replacement_aa .. "\"" )
print ( "hydrogen bond network score = " .. filter_score .. ", gain = " .. r3 ( filter_score - init_filter_score ) )
--
-- check overall score and all filters, report on changes
--
filter.EnableAll ()
local curr_score = GetScore ()
print ( "current score = " .. r3 ( curr_score ) .. ", change = " .. r3 ( curr_score - init_score ) )
for ii = 1, #filter_stat do
local filt = filter_stat [ ii ]
if filt.hasbonus then
filt.currbonus = filter.GetBonus ( filt.name )
if filt.initbonus ~= filt.currbonus then
print ( "filter \""
.. filt.name ..
"\", current bonus = "
.. r3 ( filt.currbonus ) ..
", change = "
.. r3 ( filt.currbonus - filt.initbonus ) )
end
end
filt.currsatisfied = filter.ConditionSatisfied ( filt.name )
if filt.initsatisfied ~= filt.currsatisfied then
print ( "filter \""
.. filt.name ..
"\" satisfied, current = "
.. tostring ( filt.currsatisfied ) ..
", initial = "
.. tostring ( filt.initsatisfied ) )
end
end
--
-- only make saves if the current solution if the different than
-- what we have
--
local saveit = true
for ii = 1, #slot_stat do
if slot_stat [ ii ].score == curr_score then
print ( "same score found in slot " .. slot_stat [ ii ].slot .. ", not saving" )
saveit = false
break
end
end
if saveit then
print ( "storing in slot " .. next_qs_slot )
save.Quicksave ( next_qs_slot )
slot_stat [ #slot_stat + 1 ] = { slot = next_qs_slot, score = curr_score }
next_qs_slot = next_qs_slot + 1
--
-- make a named save
--
local solname = r3 ( curr_score ) ..
", HBN score = "
.. r3 ( filter_score ) ..
", gain = "
.. r3 ( filter_score - init_filter_score )
save.SaveSolution ( solname )
print ( "saved solution as \"" .. solname .. "\"" )
end
--
-- reset filters
--
for ii = 1, #filter_stat do
local filt = filter_stat [ ii ]
if filt.name == "Hydrogen Bond Network" then
filter.Enable ( filt.name )
else
filter.Disable ( filt.name )
end
end
Init ()
elseif VERBOSE then
print ( "no gain, run " .. n_it .. ", segment " .. mutate_seg )
end
--
-- let the user know something's happening
--
if n_it % 100 == 0 then
print ( "number of runs completed = " .. n_it )
end
end -- while next_qs_slot <= last_qs_slot
print ( "maximum quick save slot " .. last_qs_slot .. " reached, recipe complete" )
cleanup ()
end
--
-- Ident - print identifying information at beginning and end of recipe
--
-- slugline - first line to print - normally recipe name and version
--
-- v0.2 - LociOiling - 20191118
-- + streamline the format
-- + user.GetGroupName not working, remove
-- v0.3 - LociOiling - 20201012
-- + combine user name and rank in one line
-- + don't report on group at all
--
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 .. " (" .. scort .. " #" .. scoreboard.GetRank ( scoretype ) .. ")" )
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
--
-- civilized error reporting, thanks to Bruno K. and Jean-Bob
--
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
print ( "number of runs " .. n_it )
undo.SetUndo ( true )
if next_qs_slot > first_qs_slot then
print ( "Loading quick saves " .. first_qs_slot .. " to " .. next_qs_slot - 1 )
for ii = first_qs_slot , next_qs_slot - 1 do
save.Quickload ( ii )
end
else
print ( "No gain, restoring starting pose" )
save.Quickload ( start_slot )
end
band.EnableAll ()
filter.EnableAll ()
local final_score = GetScore ()
print ( "final score = " .. r3 ( final_score ) )
print ( "gain = " .. r3 ( final_score - init_score ) )
for ii = 1, #filter_stat do
local filt = filter_stat [ ii ]
if filt.hasbonus then
filt.currbonus = filter.GetBonus ( filt.name )
if filt.initbonus ~= filt.currbonus then
print ( "filter \""
.. filt.name ..
"\", current bonus = "
.. r3 ( filt.currbonus ) ..
", change = "
.. r3 ( filt.currbonus - filt.initbonus ) )
end
end
filt.currsatisfied = filter.ConditionSatisfied ( filt.name )
if filt.initsatisfied ~= filt.currsatisfied then
print ( "filter \""
.. filt.name ..
"\" satisfied, current = "
.. tostring ( filt.currsatisfied ) ..
", initial = "
.. tostring ( filt.initsatisfied ) )
end
end
end
xpcall ( main, cleanup )