Profile


Name
Fuzes 3.0.3
ID
103223
Shared with
Public
Parent
Fuzes 3.0
Children
None
Created on
February 21, 2020 at 06:09 AM UTC
Updated on
February 21, 2020 at 06:09 AM UTC
Description

Updated version of Rav3n_pl Fuzes. For sketchbook puzzles, version 3.0.2 adds a minimum gain setting and fixes a bug handling the Move Count Limit condition. Version 3.0.3 corrects a bug in the cleanup routine, as reported by robgee.

Best for


Code


--[[ Rav3n_pl Fuzes Thanks to Rav3n_pl for the original, and Timo van der Laan, bertro, KarenCH, Bruno Kestemont, Jean-Bob, and others for code and ideas. version 2.0 - 2017/06/13 - LociOiling -- rachet down band strength -- do away with recentbest -- racheting band strength -- streamline band toggling logic -- don't loop in cleanup, added CLEANUPENTRY check version 3.0 - 2019/11/13 - 2019/12/11 - LociOiling -- new tech showcase + adjust wiggle power in the main dialog + (example of dialog with error messages) + detect filters by filter.GetNames, bonus by filter.GetBonusTotal -- latest version of CPT (formerly chunquePoint) + report on individual filters when their score changes + report on density changes + thanks to bertro for setting the bonus/filter reporting format -- added CIFactor, scaling factor to clashing importance in Fuze + adapted from TvdL logic in EDRW/DRemixW -- updated seedRandom (thanks, KarenCH) -- band adjustment logic, used bertro's wording in dialog -- packaged core functions as FUZE + demonstrates using "methods" in Lua (as does CPT) version 3.0.1 - 2020/01/08 - LociOiling -- fix typoed variable, scoping error -- move cleanup call to very end of main version 3.0.2 - 2020/02/20 - LociOiling -- fix condition checking for Move Count Limit and similar "binary" conditions -- add "throwback" feature to reject small gains for sketchbook puzzles version 3.0.3 - 2020/02/20 - LociOiling -- update cleanup routine to fix error with "error", as spotted by robgee (don't create a variable called "error", there's Lua built-in by that name...) ]]-- -- -- globals section -- Recipe = "Rav3n_pl Fuzes" Version = "3.0.2" ReVersion = Recipe .. " v." .. Version -- options VVVV daCapo = 2 -- minimum gain from loop to run again HASFILTERS = false FILTERBONUS = 0 WigglePower = "" WPOut = "" PowerNames = { l = "low", m = "medium", h = "high", a = "auto", } WFDefaults = { l = 2, m = 4, h = 4, a = 3, } WPPrompt = "" HighPowerOK = false InitialCI = 1.0 UserBands = 0 -- initial band count MeanBandStr = 0 -- mean band strength BandReduce = 0 -- reduce strength of band BandReduceFact = 0 -- multiplier -- end of options ^^^^^^ segCnt = structure.GetCount () bestScore = 0 startChunque = nil -- initial CPT -- -- end of globals section -- -- -- 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 CPT = { -- CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT-- --[[ CPT package version 0.7 For a given section of code, the CPT package tracks the time used and the gain produced. The package provides two routines, CPT:start and CPT:stop, to be used at the start and end of the code section. Calls may also be nested. A second call to begin in effect creates a new nested level. A remix recipe is a typical example of nesting chunque point calls. The recipe would call CPT:start somewhere near the start. Such a recipe might remix multiple lengths. The recipe would call CPT:start at the start of each length. Within a length, the recipe might remix multiple ranges of segments. For each range, the recipe would call CPT:start at the start, and CPT:stop at the end of the remix process. The CPT:stop would report the time and gain for that range of segments. When all ranges of segments are remixed, the length is complete, and the recipe would call CPT:stop. The CPT:stop would report the time and gain for the entire length. When all lengths are complete, the recipe would call CPT:stop one last time, reporting the time and gain for the entire recipe. Each CPT:start returns a "chunque", a table containing information such as the starting time and score. The chunque is used to link calls. For example, the chunque returned by a CPT:start call should be used on the corresponding CPT:stop call to report results. For nested calls, the chunque from one CPT:start call is passed to the nested CPT:start. In the remix example, to chunque from the initial CPT:start call is passed to the CPT:start calls for each length. The chunques from the length-level beginChunques are in turn passed to the beginChunques for each range of segments. For long-running recipes, the package also prints status reports at intervals of an hour or more. These reports show the total time elapsed and the gain since the start of the recipe. The first CPT:start call sets the internal "root chunque" used to calculate the reports. See this wiki page for an explanation of how this package is constructed: https://foldit.fandom.com/wiki/Lua_packaging_for_Foldit Each chunque contains the following fields: prefix - text identifying the chunque, output in messages score - score returned by the score function bonus - bonus of chunque, from score function runDate - date of chunque, from os.date runTime - time of chunque, from os.time version history --------------- 0.5 - LociOiling - 2019/11/03 - 2019/12/08 * rename to CPT (was chunquePoint) * align with new packaging strategy * add internal documentation * eliminate CSV output option * include bonus detection and tracking * convert chunques to named fields * remove runClock (os.clock), never used * eliminate pose parm, always use "current" * change external function names to "start" and "stop" * eliminate printChunque, add fmtGain for gain formatting * eliminate second line of start message * include gain or loss for each condition (filter) in status reports 0.6 - LociOiling - 2019/12/11 * fixed format of condition reporting in CPT:stop 0.7 - LociOiling - 2020/02/18 * handle "MoveCountLimit" and similar binary conditions/filters ]]-- segCnt = 0, -- segment count repInterval = 60 * 60, -- seconds between status reports lastStatusRep = 0, -- time of last status report rootChunque = nil, -- chunque to use as basis for status reports filters = false, -- whether filters active fnames = {}, -- filter names density = false, -- whether this is a density puzzle -- -- start - start tracking time, points, and bonus -- -- the start and stop functions are the external interface -- -- parameters: -- -- startChunque - if nil, set the internal rootChunque -- if not nil, nested call, report the gain -- prefix - text to identify the chunque -- scoreFunc - (optional) function to use for score checking -- -- calls self:newChunque to create a new chunque -- -- returns the new chunque -- start = function ( self, startChunque, prefix, scoreFunc ) -- -- initialize if startChunque omitted -- if startChunque == nil then self.segCnt = structure.GetCount () -- -- determine if filters apply -- local fnames = filter.GetNames () if #fnames > 0 then self.filters = true self.fnames = fnames end -- -- check a few segments to determine if this is an ED puzzle -- if current.GetSegmentEnergySubscore ( 1, "Density" ) ~= 0 or current.GetSegmentEnergySubscore ( self.segCnt / 2, "Density" ) ~= 0 or current.GetSegmentEnergySubscore ( self.segCnt, "Density" ) ~= 0 then self.density = true end end -- -- create the chunque -- local chunque = self:newChunque ( prefix, scoreFunc ) -- -- set the root chunque if needed -- if startChunque == nil then self.rootChunque = chunque self.lastStatusRep = os.time () -- last status message end local sscr = self:fmtScore ( chunque ) if startChunque == nil then -- no gain yet print ( chunque.prefix .. ", score: " .. sscr .. ", " .. chunque.runDate ) else -- report gain relative to start chunque local gstr, hours, pph = self:fmtGain ( startChunque, chunque ) print ( chunque.prefix .. ", score: " .. sscr .. ", total gain: " .. gstr .. ", " .. chunque.runDate ) end return chunque end, -- -- stop - stop tracking time, points, and bonus, then report result -- -- startChunque - the chunque returned by the corresponding CPT:start call -- prefix - text to identify the chunque -- scoreFunc - (optional) function to use for score checking -- stop = function ( self, startChunque, prefix, scoreFunc ) local chunque = self:newChunque ( prefix, scoreFunc ) self:chunqueGain ( startChunque, chunque ) -- -- if print overall progress at hourly interval -- local Clocktime = os.difftime ( os.time (), self.lastStatusRep ) if Clocktime > self.repInterval then self:chunqueReport ( self.rootChunque, chunque, "--- " ) -- bonus/density since start self.lastStatusRep = os.time () else -- report just bonus changes in this chunque self:prtExtra ( startChunque, chunque, prefix .. ", " ) end end, -- -- scorePlus - determine score and bonus from conditions/filters, -- used if user doesnt' provide a score function -- -- turn filters on, -- use current.GetEnergyScore to determine score, -- and filter.GetBonusTotal to determine bonus, -- restore previous status of filters -- scorePlus = function ( self ) local filts = behavior.GetFiltersDisabled () -- remember setting behavior.SetFiltersDisabled ( false ) -- always turn filters off local score = current.GetEnergyScore () local bonus = filter.GetBonusTotal () behavior.SetFiltersDisabled ( filts ) -- restore setting return score, bonus end, -- -- get filter scores -- getFilters = function ( self, fnames ) local filttab = {} for ii = 1, #fnames do local hasbonus = filter.HasBonus ( fnames [ ii ] ) local fscore = nil if hasbonus then score = filter.GetBonus ( fnames [ ii ] ) else score = nil end local satisfied = filter.ConditionSatisfied ( fnames [ ii ] ) filttab [ #filttab + 1 ] = { name = fnames [ ii ], hasbonus = hasbonus, score = score, satisfied = satisfied, } end return filttab end, -- -- get total density scores across all segments -- getDensity = function ( self ) local density = 0 for ii = 1, self.segCnt do density = density + current.GetSegmentEnergySubscore ( ii, "Density" ) end return density end, -- -- newChunque - create a new chunque -- -- used internally by CPT:start and CPT:stop -- newChunque = function ( self, prefix, scoreFunc ) if prefix == nil then prefix = "" end if scoreFunc == nil then scoreFunc = self.scorePlus end local score = 0 local bonus = 0 score, bonus = scoreFunc () if score == nil then score = 0 end if bonus == nil then bonus = 0 end local runDate local runTime runDate = os.date () runTime = os.time () local ch = { prefix = prefix, score = score, bonus = bonus, runDate = runDate, runTime = runTime } if self.filters then ch.filters = self:getFilters ( self.fnames ) end if self.density then ch.density = self:getDensity () end return ch end, -- -- fmtScore - return formatted score, showing bonus if applicable -- fmtScore = function ( self, chunque ) local scout = self:round ( chunque.score ) if self.filters then scout = scout .. " (Bonus: " .. self:round ( chunque.bonus ) .. ")" end if self.density then scout = scout .. " (ED: " .. self:round ( chunque.density ) .. ")" end return scout end, -- -- fmtGain - return formatted gain, hours, and points per hour -- fmtGain = function ( self, startChunque, stopChunque ) local Clocktime = os.difftime ( stopChunque.runTime , startChunque.runTime ) local gain = self:round ( stopChunque.score - startChunque.score ) local bgain = self:round ( stopChunque.bonus - startChunque.bonus ) local hours = Clocktime / ( 60 * 60 ) if hours == 0 then hours = 0.0036 -- nothing happens in no time at all end local pph = gain / hours local gstr = gain if self.filters then local bgstr = "" if bgain > 0 then bgstr = "+" .. bgain elseif bgain == 0 then bgstr = "0" else bgstr = tostring ( bgain ) end gstr = gstr .. " (Bonus: " .. bgstr .. ")" end if self.density then local dgain = self:round ( stopChunque.density - startChunque.density ) local dgstr = "" if dgain > 0 then dgstr = "+" .. dgain elseif dgain == 0 then dgstr = "0" else dgstr = tostring ( dgain ) end gstr = gstr .. " (ED: " .. dgstr .. ")" end return gstr, hours, pph end, -- -- chunqueGain - print gain and time for a chunque -- -- used internally -- chunqueGain = function ( self, startChunque, stopChunque ) local sstr = self:fmtScore ( stopChunque ) local gstr, hours, pph = self:fmtGain ( startChunque, stopChunque ) print ( stopChunque.prefix .. ", score: " .. sstr .. ", gain: " .. gstr .. ", " .. stopChunque.runDate ) print ( stopChunque.prefix .. ", hours: " .. self:round ( hours ) .. ", points per hour: " .. self:round ( pph ) ) end, -- -- chunqueReport - issue periodic progress report -- -- used internally, similar to chunqueGain -- chunqueReport = function ( self, startChunque, stopChunque, prefix ) local gstr, hours, pph = self:fmtGain ( startChunque, stopChunque ) local statuz = "--- status: total hours = " .. self:round ( hours ) .. ", gain = " .. gstr .. ", points per hour = " .. self:round ( pph ) if self.lastPPH ~= nil then statuz = statuz .. ", change = " .. self:round ( pph - self.lastPPH ) .. " points per hour" end self.lastPPH = pph print ( statuz ) self:prtExtra ( startChunque, stopChunque, "--- " ) end, prtExtra = function ( self, startChunque, stopChunque, prefix ) if prefix == nil then prefix = "" end -- -- detailed report on filters -- if self.filters then local chg = 0 local condstr = "" for ii = 1, #stopChunque.filters do if stopChunque.filters [ ii ].hasbonus then local delta = stopChunque.filters [ ii ].score - startChunque.filters [ ii ].score if delta ~= 0 then chg = chg + 1 condstr = condstr .. prefix .. "condition \'" .. stopChunque.filters [ ii ].name .. "\', bonus = " .. self:round ( stopChunque.filters [ ii ].score ) .. ", change = " .. self:round ( delta ) .. "\n" end else if stopChunque.filters [ ii ].satisfied ~= startChunque.filters [ ii ].satisfied then chg = chg + 1 condstr = condstr .. prefix .. "condition \'" .. stopChunque.filters [ ii ].name .. "\', satisfied = " .. tostring ( stopChunque.filters [ ii ].satisfied ) .. ", previously satisfied = " .. tostring ( startChunque.filters [ ii ].satisfied ) .. "\n" end end end if chg > 0 then print ( condstr ) end end -- -- overall change in density already reported, nothing to add here -- end, round = function ( self, ii ) return ii - ii % 0.001 end, } -- CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT-- -- -- begin decentbest Beta package version 0.1 -- -- decentbest can be used to replace key recentbest functions, -- specifically recentbest.Save and recentbest.Restore -- -- recentbest has been unreliable in some cases, such as in design puzzles -- where the recentbest score may overlook certain filter penalties -- -- decentbest is not yet 100% compatible with recentbest, -- since it doesn't include the functions that recentbest best does, -- such as GetSegmentEnergyScore -- -- version 0.1 is packaged as a psuedo-class or psuedo-module -- containing a mix of data fields and functions -- -- all entries must be terminated with a comma to keep Lua happy -- -- the commas aren't necessary if only function definitions are present -- -- decentbest = { DECENTSLOT = 1, -- quicksave slot for decentbest decentscore = -999999, -- score of the saved pose decentbonus = -999999, -- filter bonus of the saved pose -- -- decentbest.Save () saves the current pose as the "decent best" -- it's intended to replace recentbest.Save, but the previous -- decent best is *unconditionally* replaced -- Save = function ( ) local filt = behavior.GetFiltersDisabled () save.Quicksave ( decentbest.DECENTSLOT ) behavior.SetFiltersDisabled ( false ) decentbest.decentscore = current.GetEnergyScore () behavior.SetFiltersDisabled ( true ) local nofiltscore = current.GetEnergyScore () decentbest.decentbonus = decentbest.decentscore - nofiltscore behavior.SetFiltersDisabled ( filt ) end, -- -- decentbest.Restore () the "decent best" if it's better than the current pose -- it's intended to replace recentbest.Restore -- Restore = function ( ) local filt = behavior.GetFiltersDisabled () behavior.SetFiltersDisabled ( false ) local tscore = current.GetEnergyScore () if tscore > decentbest.decentscore then decentbest.Save () else save.Quickload ( decentbest.DECENTSLOT ) end behavior.SetFiltersDisabled ( filt ) end, -- -- decentbest.GetScore () returns the score of the "decent best" pose -- GetScore = function ( ) return decentbest.decentscore end, -- -- decentbest.GetBonus () returns the filter bonus of the "decent best" pose -- GetBonus = function ( ) return decentbest.decentbonus end, } -- -- end decentbest Beta package version 0.1 -- FUZE = { -- FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE-- --[[ FUZE - classic Rav3n_pl Fuzes package See Rav3n_pl Fuzes 1.2 for base code - https://fold.it/portal/recipe/26572 + depends on decentbest package + Score function is compatible with CPT package, but CPT is not required V3.0 - LociOiling - 20191113 * wrap it up in nice little package * subtle changes to FuzeEnd, call it from both Fuze1 and Fuze2 ]]-- WF = 2, -- wiggle factor, similar to the value in TvdL's EDRW or DRemix CIFactor = 1.0, -- clashing importance scaling factor DISABLEFILTERS = false, -- if true, disable filters during shake, wiggle, etc. IRFF = 0.00, -- irreproducible random fuzz factor THROWBACK = 99, -- slot for throwback mingain = 0, -- minimum gain for throwback -- -- Score - return current score and any filter bonus -- the bonus return is for compatibility with the CPT package -- Score = function ( self ) local filt = behavior.GetFiltersDisabled () behavior.SetFiltersDisabled ( false ) local zscore = current.GetEnergyScore () local zbonus = filter.GetBonusTotal () if filt then behavior.SetFiltersDisabled ( true ) end return zscore, zbonus end, round = function ( self, x )--cut all afer 3-rd place return x - x % 0.001 end, Wiggle = function ( self, how ) if how == nil then how = "wa" end if self.DISABLEFILTERS then behavior.SetFiltersDisabled ( true ) end if how == "s" then structure.ShakeSidechainsAll ( 1 ) else if how == "wb" then structure.WiggleAll ( 2 * self.WF, true, false ) elseif how == "ws" then structure.WiggleAll ( 2 * self.WF, false, true ) elseif how == "wa" then structure.WiggleAll ( 2 * self.WF, true, true ) end end if self.DISABLEFILTERS then behavior.SetFiltersDisabled ( false ) end end, SaveBest = function ( self ) local zscore = self:Score () local gGain = zscore - bestScore if gGain > self.mingain then if gGain > 0.001 then print ( "Gained another " .. self:round ( gGain ) .. ", score = " .. self:round ( zscore ) ) end bestScore = zscore decentbest.Save () save.Quicksave ( self.THROWBACK ) else decentbest.Restore () save.Quickload ( self.THROWBACK ) end end, doRFF = function ( self, iCI ) local oCI if self.IRFF > 0 and iCI < 1.0 then tRFF = math.random ( self.IRFF * 100 ) / 100 if math.random () > 0.5 then oCI = iCI + tRFF else oCI = iCI - tRFF end else oCI = iCI end oCI = oCI * self.CIFactor if oCI > 1 then oCI = 1 end if oCI < 0 then oCI = 0 end return oCI end, FuzeEnd = function ( self ) if behavior.GetClashImportance () < 1.0 then behavior.SetClashImportance ( 1 * self.CIFactor ) self:Wiggle () self:Wiggle ( "s" ) self:Wiggle () end decentbest.Restore () self:SaveBest () end, Fuze1 = function ( self, ci1, ci2 ) behavior.SetClashImportance ( self:doRFF ( ci1 ) ) self:Wiggle ( "s" ) behavior.SetClashImportance ( self:doRFF ( ci2 ) ) self:Wiggle () self:FuzeEnd ( ) end, Fuze2 = function ( self, ci1, ci2 ) behavior.SetClashImportance ( self:doRFF ( ci1 ) ) self:Wiggle () behavior.SetClashImportance ( 1 * self.CIFactor ) self:Wiggle () behavior.SetClashImportance ( self:doRFF ( ci2 ) ) self:Wiggle () self:FuzeEnd () end, } -- FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE--FUZE-- function MeanBandStrength () local jCnt = 0 local jTotStr = 0 local tBnd = band.GetCount () local meanStr = 0 --[[ if tBnd > 0 then print ( "total number of bands = " .. tBnd ) end ]]-- if FUZE.DISABLEFILTERS then behavior.SetFiltersDisabled ( true ) end for jj = 1, tBnd do local jStr = band.GetStrength ( jj ) if band.IsEnabled ( jj ) then jCnt = jCnt + 1 jTotStr = jTotStr + jStr end end if jCnt > 0 then meanStr = jTotStr / jCnt print ( "mean strength of " .. jCnt .. " enabled bands = " .. FUZE:round ( meanStr ) ) else meanStr = 0 end if FUZE.DISABLEFILTERS then behavior.SetFiltersDisabled ( false ) end return meanStr end function ReduceBandStrength ( reduceFact ) local jCnt = 0 local jChg = 0 local tBnd = band.GetCount () local tStr = 0 local tNewStr = 0 --print ( "total number of bands = " .. tBnd ) if FUZE.DISABLEFILTERS then behavior.SetFiltersDisabled ( true ) end for jj = 1, tBnd do local jStr = band.GetStrength ( jj ) if band.IsEnabled ( jj ) and jStr > 0 then tStr = tStr + jStr local newstr = jStr * ( 1 - reduceFact ) tNewStr = tNewStr + newstr band.SetStrength ( jj, newstr ) jCnt = jCnt + 1 jChg = jChg + ( jStr - newstr ) end end if FUZE.DISABLEFILTERS then behavior.SetFiltersDisabled ( false ) end if jCnt > 0 then print ( "mean strength change for " .. jCnt .. " enabled bands = " .. FUZE:round ( jChg / jCnt ) ) print ( "mean strength of enabled bands = " .. FUZE:round ( tNewStr / jCnt ) ) end return tNewStr / jCnt end function GetParams () local changed = false local uerror = "" local rc repeat local dlg = dialog.CreateDialog ( ReVersion ) dlg.WigglePower = dialog.AddTextbox ( "Wiggle Power", WPOut ) dlg.WPkey = dialog.AddLabel ( WPPrompt ) dlg.WF = dialog.AddSlider ( "Wiggle Factor", FUZE.WF, 1, 5, 0 ) dlg.CIFactor = dialog.AddSlider ( "CI factor", FUZE.CIFactor, 0.1, 1, 2 ) dlg.IRFF = dialog.AddSlider ( "CI fuzz factor" , FUZE.IRFF, 0.00, 0.50, 2 ) dlg.mingain = dialog.AddSlider ( "min gain to keep" , FUZE.mingain, 0, 100, 1 ) dlg.daCapo = dialog.AddSlider ( "min gain to repeat" , daCapo, 0.1, 25, 1 ) dlg.DISABLEFILTERS = dialog.AddCheckbox ( "Disable filters? (filter bonus = " .. FUZE:round ( FILTERBONUS ) .. ")", FUZE.DISABLEFILTERS ) if UserBands > 0 then dlg.Banded1 = dialog.AddLabel ( UserBands .. " bands found, enter strength reduction per cycle" ) dlg.Banded2 = dialog.AddLabel ( "0 = no reduction, 100 = bands disabled after first cycle" ) dlg.BandReduce = dialog.AddSlider ( "% each cycle", BandReduce, 0, 100, 0 ) end if uerror:len () > 0 then dlg.lerr1 = dialog.AddLabel ( "" ) dlg.lerr2 = dialog.AddLabel ( "ERROR: " .. uerror ) end dlg.ok = dialog.AddButton ( "OK", 1 ) dlg.cancel = dialog.AddButton ( "Cancel", -1 ) rc = dialog.Show ( dlg ) uerror = "" if rc > 0 then local WPtest = dlg.WigglePower.value:lower() WPtest = WPtest:sub ( 1, 1 ) local WPtnam = PowerNames [ WPtest ] if WPtnam ~= nil then if WPtest ~= "h" or HighPowerOK then print ( "new wiggle power = " .. WPtnam ) WPOut = WPtnam behavior.SetWigglePower ( WPtest ) else uerror = "high wiggle power not allowed on this puzzle" print ( "ERROR: " .. uerror ) end else uerror = "invalid wiggle power entered: \"" .. dlg.WigglePower.value .. "\"" print ( "ERROR: " .. uerror ) end FUZE.CIFactor = dlg.CIFactor.value FUZE.mingain = dlg.mingain.value daCapo = dlg.daCapo.value FUZE.WF = dlg.WF.value FUZE.DISABLEFILTERS = dlg.DISABLEFILTERS.value FUZE.IRFF = dlg.IRFF.value if UserBands > 0 then BandReduce = dlg.BandReduce.value BandReduceFact = BandReduce / 100 end end until rc < 0 or ( not changed and uerror:len () == 0 ) return rc > 0 end function main () Ident ( ReVersion ) -- -- initialize -- seedRandom () local fnames = filter.GetNames () if #fnames > 0 then local filterz = {} HASFILTERS = true for ii = 1, #fnames do local fn = fnames [ ii ] local sat = filter.ConditionSatisfied ( fn ) local bon = filter.HasBonus ( fn ) local val = 0 if bon then val = filter.GetBonus ( fn ) end filterz [ #filterz + 1 ] = { name = fn, satisfied = sat, hasbonus = bon, value = val } end print ( "filters/conditions detected" ) for ii = 1, #filterz do print ( "filter/condition \"" .. filterz [ ii ].name .. "\", satisfied = " .. tostring ( filterz [ ii ].satisfied ) .. ", has bonus = " .. tostring ( filterz [ ii ].hasbonus ) .. ", bonus value = " .. FUZE:round ( filterz [ ii ].value ) ) end end if HASFILTERS then behavior.SetFiltersDisabled ( false ) -- make sure filters are on at this point FILTERBONUS = filter.GetBonusTotal () FUZE.DISABLEFILTERS = true end UserBands = band.GetCount () if UserBands > 0 then MeanBandStr = MeanBandStrength () if MeanBandStr > 0 then BandReduce = 50 end end WigglePower = behavior.GetWigglePower () WPOut = PowerNames [ WigglePower ] FUZE.WF = WFDefaults [ WigglePower ] HighPowerOK = behavior.HighPowerAllowed () if HighPowerOK then WPPrompt = "wiggle power: l = low, m = medium, h = high, a = auto" else WPPrompt = "wiggle power: l = low, m = medium, a = auto" end InitialCI = behavior.GetClashImportance () FUZE.CIFactor = InitialCI if GetParams () then -- -- print recipe parms -- print ( "options:" ) print ( "Wiggle Power = " .. WPOut ) print ( "Wiggle Factor = " .. FUZE.WF ) print ( "CI scaling factor = " .. FUZE.CIFactor ) behavior.SetClashImportance ( FUZE.CIFactor ) if FUZE.IRFF > 0 then print ( "max CI fuzz factor = " .. FUZE.IRFF ) end print ( "minimum gain to keep (sketchbook) = " .. FUZE.mingain ) print ( "minimum gain per cycle = " .. daCapo ) if UserBands then print ( "Bands found = " .. UserBands ) print ( "Band strength reduction = " .. BandReduce .. "% per cycle" ) end print ( "disable filters = " .. tostring ( FUZE.DISABLEFILTERS ) ) print ( "--" ) save.Quicksave ( FUZE.THROWBACK ) startChunque = CPT:start ( nil, "Start", FUZE.Score ) bestScore = FUZE:Score () decentbest.Save () selection.SelectAll () local fuzeCount = 1 local retryFuze = false repeat retryFuze = false local ss = FUZE:Score () local runName = "Fuze run " .. fuzeCount local begChunque = CPT:start ( startChunque, runName, FUZE.Score ) -- -- pink fuse -- print ( "Run " .. fuzeCount .. ", Fuze 1/6" ) FUZE:Fuze1 ( 0.3, 0.6 ) print ( "Run " .. fuzeCount .. ", Fuze 2/6" ) FUZE:Fuze2 ( 0.5, 0.7 ) print ( "Run " .. fuzeCount .. ", Fuze 3/6" ) FUZE:Fuze2 ( 0.7, 0.5 ) -- -- blue fuse -- print ( "Run " .. fuzeCount .. ", Fuze 4/6" ) FUZE:Fuze1 ( 0.05, 1 ) print ( "Run " .. fuzeCount .. ", Fuze 5/6" ) FUZE:Fuze1 ( 0.07, 1 ) print ( "Run " .. fuzeCount .. ", Fuze 6/6" ) FUZE:Fuze2 ( 0.3, 1 ) local runGain = FUZE:Score () - ss CPT:stop ( begChunque, "end run " .. fuzeCount, FUZE.Score ) fuzeCount = fuzeCount + 1 if runGain < daCapo then if runGain > FUZE.mingain then save.Quicksave ( FUZE.THROWBACK ) else save.Quickload ( FUZE.THROWBACK ) end if band.GetCount () > 0 and MeanBandStr >= 0.1 then MeanBandStr = ReduceBandStrength ( BandReduceFact ) retryFuze = true decentbest.Save () end end until runGain < daCapo and not retryFuze end cleanup () -- always clean up after the party is over 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 -- 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 () ) --[[ user.GetGroupName not working local gname = user.GetGroupName () if gname ~= nil then gname = " (" .. gname .. ")" else gname = "" end print ( "User: " .. user.GetPlayerName () .. gname ) ]]-- 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 -- -- model 120 - 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 -- -- model 100 - print recipe name, puzzle, track, time, score, and gain -- Ident ( ReVersion .. " " .. reason ) if reason == "error" then print ( "Unexpected error detected" ) print ( "Error line: " .. line ) print ( "Error: \"" .. errmsg .. "\"" ) end behavior.SetClashImportance ( InitialCI ) selection.DeselectAll () if error ~= nil then decentbest.Restore () end behavior.SetFiltersDisabled ( false ) -- make sure filters are on at end if startChunque ~= nil then CPT:stop ( startChunque, "Final", FUZE.Score ) end end -- main call xpcall ( main, cleanup ) --end of script

Comments


LociOiling Lv 1

Version 3.0.2 adds a "min gain to keep" slider for sketchbook puzzles. The recipe skips smaller gains to save moves.

This version also fixes a bug in handling the "Move Count Limit" condition found in sketchbook puzzles. Move Count Limit doesn't have a bonus (no points), but can be satisfied or not. The previous version attempted to get the bonus for Move Count Limit, which caused the recipe to terminate.

LociOiling Lv 1

Version 3.0.3 fixes a problem spotted by robgee. The recipe had an old version of the cleanup routine, which had a couple of issues. First, the parameter to the cleanup routine was called "error", which is bad because it overrides the Lua built-in function of the same name. Second, after some processing, a print statement used "err", which was never defined. The new version uses "errmsg" in both spots.