Been working on new scripts. Most useful one so far is a script that will increment a "bin" vector to eventually run through all the possibilities. Special thanks to Nitrodon for helping me make sense of the algorithm. Here it is in a quickly testable format:
Language: lua
local function sum(vector,low,high)
if not(low) then
low=1
end
if not(high) then
high=#vector
end
local total=0
for i=low,high do
total=total+vector[i]
end
return total
end
local function incbins(vector)
local i=1
local total=sum(vector)
while (vector[i]==0 and not(vector[#vector]==total)) do
i=i+1
end
if vector[#vector]==total then
vector={}
elseif not(vector[i]==0) then
vector[i+1]=vector[i+1]+1
vector[i]=0
vector[1]=total-sum(vector,2)
end
return vector
end
local vector={5,0,0,0,0}
while #vector>0 do
print(vector)
vector=incbins(vector)
emu.frameadvance()
end
emu.pause()
Give that a run and I'll explain it.
Did you run it?
Good. Given any vector of "bins", this script will increment it to the next permutation, eventually running through all possible permutations. Suppose the music changes three times in a segment and we wish to test all of the possible ways to manipulate luck that involve delaying input for three frames total. Then our initial bin vector will be {3, 0, 0} which signifies to delay input by three frames at the first change in the music, zero frames at the second change in the music, and zero frames again at the third change in the music.
By repeatedly plugging this vector into the incbins function, all possible permutations will be exhausted. These should be {3, 0, 0}, {2, 1, 0}, {1, 2, 0}, {0, 3, 0}, {2, 0, 1}, {1, 1, 1}, {0, 2, 1}, {1, 0, 2}, {0, 1, 2}, {0, 0, 3} (you can check these by replacing the vector={5, 0, 0, 0, 0} line in my script). This script should be extremely useful to us.
I've also been working on my "ultimate bot", which should essentially solve the game for us. As yet, I only have a very rough draft that does not run, but it helps simply to have the format down. Here it is:
Edit:
After a hard night's work, I've got the basic structure of the bot functional. You may run this script at power on to suboptimally run the game through naming the first character. Once I write the musicmanip function, we should be nearly ready to test it through the first reset.
I think the real test will be running the bot through several segments to compare it with the input of our existing run. If we override some parameters (not sure exactly how...) we should be able to reproduce the existing run exactly. If the two don't match, we can compare their inputs and see where it went wrong.
Progress is accelerating. If the bot executes in any reasonable amount of time (say, less than an hour for one segment), we should be able to
solve this game (for our given route). There's still a whole lot of work to do, but I now feel like the end is within sight.
Edit 2:
New version, with the main (only?) addition being the musicmanip function. (Thanks to BrandonE for help debugging it.) Looking through what remains, the hardest parts will be the walk function (which will also save and reset for us) and my battlebot function, which still isn't finished because it doesn't scroll through the battle narration. After those are written, it should just be a matter of writing some specialized functions and cleaning the whole thing up.
Language: lua
local function sum(vector,low,high)
if not(low) then
low=1
end
if not(high) then
high=#vector
end
local total=0
for i=low,high do
total=total+vector[i]
end
return total
end
local function incbins(vector)
local i=1
local total=sum(vector)
while (vector[i]==0 and not(vector[#vector]==total)) do
i=i+1
end
if vector[#vector]==total then
vector={}
elseif not(vector[i]==0) then
vector[i+1]=vector[i+1]+1
vector[i]=0
vector[1]=total-sum(vector,2)
end
return vector
end
--condition is a table that takes the form {fn=condition function, adr={relevant addresses}, extra 1, extra 2, etc...}
--If executeaction is true, it executes the actions specified, if false, it merely advances frames up to lowguess. Useful in conjunction with musicmanip.
local function guessandcheck(action,condition,framedelay,lowguess,highguess,executeaction)
local state2=savestate.create()
local oldstate={}
for i=1,#condition["adr"] do
oldstate[i]=memory.readbyte(condition["adr"][i])
end
local foundit=false
savestate.save(state2)
while not(foundit) do
savestate.load(state2)
guess=math.floor((highguess+lowguess)/2)
for i=1,guess do
emu.frameadvance()
end
for i=1,#action do
joypad.set(1,action[i])
emu.frameadvance()
end
for i=1,framedelay do
emu.frameadvance()
end
if condition["fn"](unpack(oldstate),unpack(condition)) then
highguess=guess
else
lowguess=guess+1
end
foundit = (highguess==lowguess)
end
savestate.load(state2)
for i=1,lowguess do
emu.frameadvance()
end
if executeaction then
for i=1,#action do
joypad.set(1,action[i])
emu.frameadvance()
end
end
--while not(condition["fn"](oldstate,unpack(condition))) do
-- emu.frameadvance()
--end
end
local function idleframes(n)
for i = 1,n do
vba.frameadvance()
end
end
local function holdA(n)
for i=1,n do
joypad.set(1, {A=1})
vba.frameadvance()
end
end
local function gamestarted(startedflag)
if not(memory.readbyte(0x9822)==startedflag) then --0x9822 may not be the best byte to use. Check others.
return true
else
return false
end
end
local function mutantfselected()
if (memory.readbyte(0x8000)==3 and memory.readbyte(0x803E)==128) then --Check if these are the addresses you really want
return true
else
return false
end
end
local function selectmutantf()
action={{down=1},{down=1, right=1},{down=1,right=1,A=1},{down=1,A=1}}
condition={fn=mutantfselected,adr={}}
guessandcheck(action,condition,10,0,40,true)
end
local function namedR()
if (memory.readbyte(0xFF93)==16 and memory.readbyte(0xFF92)==8) then
return true
else
return false
end
end
local function namechar1()
action={{right=1},{right=1, down=1},{right=1, down=1},{up=1, right=1},{up=1, right=1},{right=1, down=1},{right=1, down=1},{right=1, A=1},{right=1, A=1}}
condition={fn=namedR,adr={}}
guessandcheck(action,condition,10,0,40,true)
end
local function musicmanip(backgroundaction,delayedaction,framesdelay)
local action={{}}
local buttons={"A","B","select","start","down","up","left","right"}
for i=#backgroundaction+1,#delayedaction+framesdelay do
backgroundaction[i]={}
end
for i = 1,(framesdelay+#delayedaction) do
action[i]={}
if i<framesdelay then
for j=1,#buttons do
action[i][buttons[j]]=backgroundaction[i][buttons[j]]
end
else
for j=1,#buttons do
action[i][buttons[j]]=backgroundaction[i][buttons[j]] or delayedaction[i-framesdelay][buttons[j]]
end
end
joypad.set(1,action[i])
emu.frameadvance()
end
end
--anticipatebattle(waypointsx,waypointsy,waypoint,encounterrates)
--Create an updated guessandcheck function that supports background actions
--Change framesdelay to be a two-level table later on, based on the segment
framesdelay = {0,0,0,0,0,0,0,0}
pressA={{A=1},{A=1}}
pressB={{B=1},{B=1}}
pressselect={{select=1},{select=1}}
pressstart={{start=1},{start=1}}
pressdown={{down=1},{down=1}}
pressup={{up=1},{up=1}}
pressleft={{left=1},{left=1}}
pressright={{right=1},{right=1}}
local segment={}
segment[1]={ {fn=idleframes,60},
{fn=guessandcheck,pressA,{fn=gamestarted, adr={0x9822}},10,0,20,true}, --guess and check when to start the game
{fn=idleframes,5},
{fn=holdA,320},
{fn=selectmutantf},
{fn=namechar1}, --cursor should end on the letter R
{fn=idleframes,5}, --change to guessandcheck function with pressstart and executaction=false
{fn=musicmanip,{{}},pressstart,framesdelay[1]},
{fn=walk,1}, --walk to guild
{fn=guessandcheck,...}, --guess and check when to press A to enter the guild
{fn=guessandcheck,...}, --guess and check when to press down to select the second character slot
{fn=guessandcheck,...}, --guess and check when to press A to select OK
{fn=selectmutantf},
{fn=namechar2},
{fn=guessandcheck,...}, --select third character slot
{fn=guessandcheck,...}, --press A
{fn=selectmutantf},
{fn=namechar3},
{fn=guessandcheck,...}, --press up to select fourth character slot
{fn=guessandcheck,...}, --press A
{fn=namechar4},
{fn=guessandcheck,...}, --press B
{fn=walk,2}, --walk out of town
{fn=walk,3}} --the walk script should be modified to save and reset as it nears the encounter
for i=1,#segment do
for j=1,#segment[i] do
segment[i][j]["fn"](unpack(segment[i][j]))
end
end
--should be able to overwrite frame constraints
--(reset)
--save state
--perform actions optimally until just before change of music
--use default action until appropriate frame, then use delayed action (possible overlap to consider)
--repeat previous two steps until "near" reset point (quantify "near")
--pause, save, and reset, much like luckman2 script (also check near misses for holding B)
--when a hit is found, add up total frames "wasted" and number of steps wasted, calculating the likelihood of an additional reset needed due to steps wasted, record these values to file
--set new frame contraint equal to best result so far plus one
--load state
--repeat previous seven steps until frame constraint is reached
--pseudocode (actions) to start the game
--------------------------------------
--press A
--hold A
--select and name character (luck manipulation?)
--walk to guild
--press A
--select and name supporting cast, leave guild menu
--leave town, enter field (luck manipulation?)
--(shuffle party?)
--walk as far as possible, reset
--press A to load game (luck manipulation?)
--walk until battle (luck manipulation?)
--fight
--defeat enemy (luck manipulation?)
--finish battle (luck manipulation?)
--walk until need to reset
Okay, most of that is notes and garbage. The important part is the segment[1] vector, which outlines every action we will take in the first segment of the game. This vector also establishes the syntax I'd like to follow. All that really remains is writing the appropriate functions, then running the code. I'll then incorporate the frame delays for luck manipulation and find an appropriate way to output the results (I was thinking I'd save them to file). Most of the code has already been written (little functions like namechar1 should take no more than three or four lines), so I think we're deceptively close to the testing phase. Polishing the script, however, will take quite a bit of time.