Posts for Bobo_the_King

1 2
16 17 18
34 35
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
r57shell wrote:
TASK: you stay in position X. You have jump, and walk. Both has different accelerations by X. Time in jump differs by C time pressed after jump. Needs to set X at range[a,b]. After this, I can make zip through wall. (Or want to test).
I edited my post above to show how you would test for a range of values using my current script. I hope that helps! If not, I think I need more information about the game you're testing.
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
r57shell wrote:
Is this script able to make right positioning? I want X to be in range [0,2] as fast as possible, and keys: C,Left,Right press/release in any time. Something like that... I don't see how to configure it out. EDIT: you always run whole input from start. You can make savestates (in memory) at events.
I don't quite follow you. What game are you playing? Where in the game are you? What are you trying to do? If you are trying to get a value to be within a certain range, the script can't do that yet. I can change it so that it allows for a range of values. Edit: D'oh! I was totally wrong to say that this isn't possible with my current script. If you want an address in a range of values, enter this at the top of the script:
Language: lua

local function ThisAddress() val = memory.readbyte() --Put the address you're interested in here. if val>=0 and val<=2 then return 1 else return 0 end end IWant=ThisAddress ToBe='equal to' --{'minimized','maximized','equal to','less than','greater than','at least','at most'} -- If ToBe in {'minimized','maximized'}... AndTheBestIveDoneIs=0 InThisManyFrames=0 -- Elseif ToBe in {'equal to','less than','greater than','at least','at most'} ThisValue=1 AndTheFastestIveDoneItIs=
Part of the strength of my program is that the value in question is totally customizable. Use it to your advantage!
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
BadPotato wrote:
In any case, right now when I'm trying to run the script, there is a couple of errors. So far, I think you forgot to post the DepthFirstSearch function, or maybe I missed something?
Ugh, how embarrassing! I shouldn't have gone to sleep immediately after posting my script! It turns out that having HTML enabled was causing a large section of my script (between two inequalities) to be deleted. I've disabled HTML in my two posts and modified the script accordingly. I'll try to incorporate your suggestion later today. My first thought was that it should be easy, but on further thought, the intervals are not necessarily of fixed length, which makes the combinatorics much more difficult. I'll think about it.
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
This post has been updated on August 10th, 2015 to incorporate BizHawk instead of FCEUX. Almost everything is identical. The save file has been updated. The only major difference to look out for is that directional buttons in FCEUX are uncapitalized ("up", "down", "left", "right"), while in BizHawk they are capitalized ("Up", "Down", "Left", "Right"). As a demonstration of my script, I will use a simple example that you should already be familiar with. We're going to pretend that we don't know how to execute a walljump in Super Mario Bros. and we'll have the script execute one for us. Before we begin, I need to point out that I'm using FCEUX. That's important because FCEUX does not execute Lua scripts until the frame after they are loaded. That means even though my state starts at frame 4036, the Lua script will begin executing on the next frame, 4037. Because the framecount is important to the script, I have to be careful to measure all of those frames relative to 4037. You'll see what I mean in a bit. I haven't checked other emulators to see if Lua scripts execute on the frame they're loaded, but you would be wise to check it yourself before using my script. [Note: I just realized that because of how movies are timed, the movie I uploaded resets the frame count to 0. This doesn't change what I said above, except for the numbers I used.] The savestate (compatible with BizHawk) I will be using can be found here. (The old file, compatible with FCEUX can be found here. In both cases, it's saved as a movie file because apparently we don't have a way of exchanging savestates. Just open the movie and immediately stop it.) As you can see, we're at the end of world 1-1 with a nice wall to our left. Let's jump up it, pretending we can't do it by trial and error ourselves. First, we'll need to consider how to define success at the walljump. One relevant RAM address is Mario's y value, stored at 0x00CE. A quick inspection shows that this value decreases as the height of Mario's jump increases. Furthermore, by executing a single running jump, its value reaches a minimum of 93. (Note: the highest Mario can reach from a standing jump is 110. I completed much of this example with 110 as my value before realizing that 93 was the one I should use. Be careful!) Let's define a successful walljump as a series of inputs such that address 00CE is minimized and is less than 93. (We could also define it to be any jump where 00CE is less than 93. This will slightly alter the results because the script will look for the quickest walljump, not the highest walljump.) Our goal is well-defined, so we can enter the first few lines of our program. We're interested in Mario's y position, so we'll insert the value 0x00CE into the ThisAddress function:
Language: lua

local function ThisAddress() return memory.readbyte(0x00CE) end
We want to minimize its value, so we'll set ToBe like so:
Language: lua

ToBe='minimized'
And we want to the height of the jump to be at least 93 (setting this value higher will not affect the output of the script):
Language: lua

AndTheBestIveDoneIs=93
Now we need to know how many frames must pass before checking the result. This is nontrivial, so I'll just do a back-of-the-envelope calculation. It takes about 30 frames for Mario to reach his peak height. If two jumps are fully executed, that would be about 60 frames. Let's tack on another 30 frames in case Mario needs a running start. That means we need to change the script like so:
Language: lua

InThisManyFrames=90
Because this is a minimization/maximization problem, the values of ThisValue and AndTheFastestIveDoneItIs are irrelevant. Let's take a moment to read back the contents of what we put in. In plain English, it should now read, "I want this address (00CE) to be minimized and the best I've done is 93 in 90 frames." Sounds good! Now for the hard part. We need to establish the pattern of the button presses. We're only using one player, so the first element of each element of UsingTheseButtons will be 1. That simplifies it a bit. It seems like it would be a good idea to have Mario run left immediately. Therefore, our first two events are quite simple:
Language: lua

UsingTheseButtons[1]={1,'Left','on','start'} UsingTheseButtons[2]={1,'B','on','start'}
which tells the script to hold left and B at the start of the script's execution, with no exceptions. (The last element of each, 'start', could be equivalently replaced with 's', '[start,start]', 0, '0', 'start+0', or any number of other strings, including almost absurd ones like 'sasquatch'.) Now what? We want Mario to begin his jump at some later frame. By doing a quick test, we can determine that he needs at least a little running start or else he will fall short of the wall. After a few trials, we find that Mario needs about 20 frames of running before beginning his jump. Furthermore, we can see that if we jump too late (after 30 frames), we hit the wall as we're going up. Therefore, we want to press A somewhere between 20 and 30 frames after the start. I'll modify those bounds to 18 and 32 frames so that there's a little forgiveness in case my estimates were off. This comes at the expense of making the program run 1.4 times longer. Here's what the third event will be:
Language: lua

UsingTheseButtons[3]={1,'A','on','[18,32]'}
How long do we want to hold A for? It takes about 30 frames to reach the maximum height of the jump, so it doesn't make sense to hold it longer than that. On the other hand, we want at least a moderately high jump, so let's hold A for at least 20 frames. Note that both of these bounds are written with respect to when we started holding A. Our next event is:
Language: lua

UsingTheseButtons[4]={1,'A','off','[event3+18,event3+30]'}
Here, event3 refers to UsingTheseButtons[3]. We release A between 18 and 30 frames after it was first pressed. Finally, we need to press A again to execute the walljump itself. By running right, we find that we hit the wall at around frame 4096, 59 frames after the Lua script begins executing. Let's press A a second time either on that frame or one of the subsequent three frames, like so:
Language: lua

UsingTheseButtons[5]={1,'A','on','[59,63)'}
Note the close parenthesis there. Like interval notation in mathematics, it means exclude 63 from the interval. That means we'll be pressing A on frames 59, 60, 61, and 62. After entering all of this, the top of our script should read:
Language: lua

local function ThisAddress() return memory.readbyte(0x00CE) end IWant=ThisAddress ToBe='minimized' AndTheBestIveDoneIs=93 InThisManyFrames=90 ThisValue=0 AndTheFastestIveDoneItIs=0 UsingTheseButtons={{1,'Left','on','start'}, {1,'B','on','start'}, {1,'A','on','[18,32]'}, {1,'A','off','[event3+18,event3+30]'}, {1,'A','on','[59,63)'} }
Based on all the intervals we input, how many iterations will this script run through? The first two events don't contribute any iterations because they always execute at a fixed time. Event 3 runs over 15 possibilities, event 4 runs over 13 possibilities, and event 5 runs over 4 possibilities. Therefore, the number of iterations will be 1*1*15*13*4 = 780 iterations. At 90 frames per iteration (one-and-a-half seconds at 100% speed), it will take about 20 minutes to run through all iterations. Of course, you'll want to go through at turbo speed, so it will actually take just a minute or two to finish. Now run the script! You'll see Mario run to the wall and jump over and over again. If you look carefully, you'll notice that each time, his motion is subtly different as the script runs through all the possibilities you gave it. Eventually, he'll successfully execute the walljump! After all iterations are complete, it executes the button presses that produced the best results (in this case, Mario reaches a height of 13 after 90 frames). *Whew!* You may be asking yourself at this point, "Was all of this effort worth it?" For a well-documented trick like walljumping in Super Mario Bros., no, of course it isn't. But suppose you don't know that walljumping is possible. For example, walljumps were discovered in Yoshi's Island when NxCy and Baxter were already well into their TAS. They had to go back and redo much of their run to incorporate the new trick. If they had my script as well as a notion that walljumping might be possible given the right timings, they could have executed my script and seen in a matter of minutes how to perform a walljump. But that's just the tip of the iceberg. Any goal that is well-defined by the RAM and does not rely on too many button presses with loose restrictions can be thoroughly and exhaustively optimized for the input paramters. The sky's the limit! This script is about as powerful as what you put into it. For those of you who would like to execute the script on the savestate without editing it yourself, here it is in its entirety:
Language: lua

-- * * * PARAMETERS * * * -- By default, ThisAddress just returns the value of a single address. However, you can have it return any function of any number of addresses. local function ThisAddress() return memory.readbyte(0xCE) end IWant=ThisAddress ToBe='minimized' --{'minimized','maximized','equal to','less than','greater than','at least','at most'} -- If ToBe in {'minimized','maximized'}... AndTheBestIveDoneIs=93 InThisManyFrames=90 -- Elseif ToBe in {'equal to','less than','greater than','at least','at most'} ThisValue=0 AndTheFastestIveDoneItIs=0 --frames UsingTheseButtons={{1,'Left','on','start'}, {1,'B','on','start'}, {1,'A','on','[18,32]'}, {1,'A','off','[event3+18,event3+30]'}, {1,'A','on','[59,63)'} } -- * * * FUNCTIONS * * * local function tablecopy(t1) local t2={} if not(type(t1)=='table') then t2=t1 return t2 end for k,v in pairs(t1) do if type(v)=='table' then t2[k]=tablecopy(v) else t2[k]=v end end return t2 end local function getframeadvancefunction() if FCEU or bizstring then return emu.frameadvance elseif gens then return gens.emulateframeinvisible else print("Unrecognized emulator.") end end local function jpset(player, buttons) if FCEU or gens then joypad.set(player, buttons) elseif bizstring then -- BizHawk's joypad.set convention is "backwards". WTF, BizHawk? joypad.set(buttons,player) else print("Unrecognized emulator.") end end -- Takes a character code (from a string) and returns true if it corresponds to a numeral. local function isnumeral(charcode) return (charcode>=0x30 and charcode<=0x39) end -- Extracts the first number from a string. For example, running this function on the string "event48blah19" will return 48. local function getnumber(str) local numstart=nil local numend=nil for i=1,string.len(str) do if isnumeral(string.byte(str,i)) then if not(numstart) then numstart=i numend=numstart local j=i+1 while isnumeral(string.byte(str,j)) do numend=j j=j+1 end end end end if not(numstart) then error('No number found!') end local valuetext=string.sub(str,numstart,numend) local value = tonumber(valuetext) return value end -- Parses one of the bounds in an interval, returning the frame value. You would run this function on, say, "(event2+10," to return index[2]+10+1. -- It isn't necessary to type "event", "start", or "finish" in full, it just looks for the first match. Watch out, though, because parsebound('[safe+2,') will return index[2]+2, not 2. local function parsebound(str,index,maxframes) local value=-1 if string.find(str,'e') then --Bound defined relative to event. value=index[getnumber(str)] local plus=string.find(str,'+') if plus then local plusthis=string.sub(str,plus) value=value+getnumber(plusthis) end elseif string.find(str,'f') then --Bound defined relative to finish. value=maxframes local minus=string.find(str,'-') if minus then local minusthis=string.sub(str,minus) value=value+getnumber(minusthis) end elseif string.find(str,'s') then --Bound defined relative to start. Note that because of the elseif statement above, this won't be triggered by "finiSh". value=0 local plus=string.find(str,'+') if plus then local plusthis=string.sub(str,plus) value=value+getnumber(plusthis) end else --Bound defined by number. "+" not allowed! value=getnumber(str) end if string.sub(str,1,1)=='(' then value=value+1 end if string.sub(str,-1)==')' then value=value-1 end return value end -- Parses interval notation into start and finish times. local function parseinterval(UsingTheseButtons,index,depth,maxframes) interval = UsingTheseButtons[depth][4] if type(interval)=='number' then --If interval is just a number, then low and high are both equal to that number. low=interval high=interval elseif string.sub(interval,1,1)=='[' or string.sub(interval,1,1)=='(' then --The interval is explicitly defined in this case. local comma=string.find(interval,',') if not comma then error('Expected comma to define interval.') else local lowerbound=string.sub(interval,1,comma) local upperbound=string.sub(interval,comma) low=parsebound(lowerbound,index,maxframes) high=parsebound(upperbound,index,maxframes) end else --If no interval is explicitly defined, then the interval is taken to be implicit and both low and high are set equal to the output of parsebound acting on interval in its entirety. low=parsebound(interval,index,maxframes) high=low end --This is a little forgiving to the user. Perhaps their interval was defined improperly, such that the lower bound surpassed the upper bound. if high<low then high=low end return low,high end -- Compares IWant with Value according to function specified by ToBe local function check(IWant,ToBe,Value) if not(Value) then return nil end if ToBe=='minimized' then return (IWant()<Value) elseif ToBe=='maximized' then return (IWant()>Value) elseif ToBe=='equal to' then return (IWant()==Value) elseif ToBe=='less than' then return (IWant()<Value) elseif ToBe=='greater than' then return (IWant()>Value) elseif ToBe=='at least' then return (IWant()>=Value) elseif ToBe=='at most' then return (IWant()<=Value) else error('Invalid string for ToBe.') end end -- IWant refers only to the current frame, which isn't useful when checking nodes with branches. Therefore, this function directly compares an old value with a new value, based on ToBe. local function check2(newvalue,ToBe,oldvalue) if not(newvalue) then return nil end if ToBe=='minimized' then return (newvalue<oldvalue) elseif ToBe=='maximized' then return (newvalue>oldvalue) elseif ToBe=='equal to' then return (newvalue==oldvalue) elseif ToBe=='less than' then return (newvalue<oldvalue) elseif ToBe=='greater than' then return (newvalue>oldvalue) elseif ToBe=='at least' then return (newvalue>=oldvalue) elseif ToBe=='at most' then return (newvalue<=oldvalue) else error('Invalid string for ToBe.') end end -- Executes the actual button presses and checks if it is superior to previous attempts. -- The output of this function depends on the string in ToBe. If the string is "minimized" or "maximized", it returns the value. If the string is something else, it returns the number of frames. -- If it doesn't beat the current record, the output is nil. local function attempt(presses,IWant,ToBe,Value,maxframes,frameadvance) if ToBe=='maximized' or ToBe=='minimized' then for frame=0,#presses do for player=1,2 do --Edited for BizHawk support. Try to come up with a solution for all emulators. jpset(player, presses[frame][player]) end frameadvance() end if check(IWant,ToBe,Value) then return IWant() end else for frame=0,#presses do if frame<maxframes then if check(IWant,ToBe,Value) then return frame end end for player=1,2 do jpset(player, presses[frame][player]) end frameadvance() end end return nil end -- Uses UsingTheseButtons and index to construct a table of button presses, then passes that table to the attempt function to execute them. -- presses[frame][player][button]. local function pressbuttons(IWant,ToBe,Value,maxframes,UsingTheseButtons,index,state,frameadvance) local presses={} for frame=0,maxframes do presses[frame]={} for player=1,2 do --Edit the number of players here. presses[frame][player]={} end for i=1,#index do if frame>=index[i] then local OnOrOff = (UsingTheseButtons[i][3]=='on') --Set button if "on", reset otherwise. presses[frame][UsingTheseButtons[i][1]][UsingTheseButtons[i][2]]=OnOrOff end end end output=attempt(presses,IWant,ToBe,Value,maxframes,frameadvance) savestate.load(state) return presses,output end -- local function DepthFirstSearch(IWant,ToBe,Value,maxframes,UsingTheseButtons,index,depth,state,frameadvance) depth=depth+1 local bestpresses,attemptframes,attemptValue,presses low,high=parseinterval(UsingTheseButtons,index,depth,maxframes) if depth==#UsingTheseButtons then for i=low,high do --if i>maxframes then return end --Any input after maxframes is irrelevant and shouldn't be checked. index[depth]=i if ToBe=='minimized' or ToBe=='maximized' then presses,attemptValue=pressbuttons(IWant,ToBe,Value,maxframes,UsingTheseButtons,index,state,frameadvance) if attemptValue then Value=attemptValue bestpresses=tablecopy(presses) end else presses,attemptframes=pressbuttons(IWant,ToBe,Value,maxframes,UsingTheseButtons,index,state,frameadvance) if attemptframes then maxframes=attemptframes bestpresses=tablecopy(presses) end end end else for i=low,high do --if i>maxframes then return end --Any input after maxframes is irrelevant and shouldn't be checked. index[depth]=i if ToBe=='minimized' or ToBe=='maximized' then presses,attemptValue=DepthFirstSearch(IWant,ToBe,Value,maxframes,UsingTheseButtons,index,depth,state,frameadvance) if check2(attemptValue,ToBe,Value) then Value=attemptValue bestpresses=tablecopy(presses) end else presses,attemptframes=DepthFirstSearch(IWant,ToBe,Value,maxframes,UsingTheseButtons,index,depth,state,frameadvance) if attemptframes<maxframes then maxframes=attemptframes bestpresses=tablecopy(presses) end end end end if ToBe=='minimized' or ToBe=='maximized' then return bestpresses,Value else return bestpresses,maxframes end end -- * * * BASE CODE * * * if ToBe == 'minimized' or ToBe == 'maximized' then Value=AndTheBestIveDoneIs maxframes=InThisManyFrames else Value=ThisValue maxframes=AndTheFastestIveDoneItIs end frameadvance=getframeadvancefunction() depth=0 index={} if bizstring then state1="state1" else state1=savestate.create() end savestate.save(state1) presses,best=DepthFirstSearch(IWant,ToBe,Value,maxframes,UsingTheseButtons,index,depth,state1,frameadvance) if presses then for frame=0,#presses do print('Frame: '..frame) for player=1,2 do print('Player ' .. player .. ' buttons: ',presses[frame][player]) jpset(player, presses[frame][player]) end frameadvance() end if ToBe=='minimized' or ToBe=='maximized' then print('Best value: ' .. best) else print('Fewest frames: ' .. best) end else print('No improvement found!') end if FCEU or gens then emu.pause() elseif bizstring then client.pause() end --This function works. It will serve as a template for the final function. --[=[ local function DFS(G,index,depth) depth=depth+1 if G[depth][1] == 'abs' then low=G[depth][2] high=G[depth][3] elseif G[depth][1] == 'rel' then low=index[G[depth][2]] + G[depth][3] high=index[G[depth][2]] + G[depth][4] end if depth==#G then for i=low,high do index[depth]=i for j=1,#index do print(index[j]) end print('Next!') end else for i=low,high do index[depth]=i DFS(G,index,depth) end end end G={{'abs',0,5},{'rel',1,6,16},{'rel',2,8,20}} --G={{'abs',0,1},{'rel',1,10,11},{'rel',2,100,101}} depth=0 index={} DFS(G,index,depth) ]=]-- --[[ {{,,'[start,start+20]'}, {,,'[event1,event1+5]'}, {,,'[event2+10,event2+20]'}} for i=0,20 do for j=i,i+5 do for k=j+10,j+20 do end end end index={} function makeloops(events,index,depth) if depth==#events then for else depth=depth+1 end end Wikipedia pseudocode: procedure DFS(G,v): label v as discovered for all edges from v to w in G.adjacentEdges(v) do if vertex w is not labeled as discovered then recursively call DFS(G,w) ]]-- --[[ UsingTheseButtons has elements with the structure {player,button,'on'/'off',timing} where timing is a string based solely on PREVIOUS events in the UsingTheseButtons vector as well as the start frame (0) and end frame (InThisManyFrames/AndTheFastestIveDoneItIs). Examples of timing: '[start,finish]' -- Test all frames. '[start,37]' -- Test all frames from start to frame 37, inclusive. '(start,37)' -- Test all frames from start to frame 37, exclusive. '[15,37]' -- Any frame from 15 to 37, inclusive. '[event 2,finish]' -- Any frame after event 2 has begun, where event 2 is the second element of the UsingTheseButtons vector. This can be used to force, for example, shooting only after jumping. '[event 2+37,finish]' -- No, that doesn't mean event 39. It means wait at least 37 frames after event 2 has finished. ('[event 2-37,finish]') -- I'm not sure I want to program this, since each event's timing is supposed to depend on PRIOR events. It should always be possible to put them in order. In total, the UsingTheseButtons table might look like this: UsingTheseButtons={{1,'A','on','[start,finish]'}, {1,'A','off','(event 1, finish]'}, {1,'right','on','[37,60]'}, {1,'up','on','[event 3+2,event 3+2]'}, {1,'right','off','[40,finish]'} } which means try pressing A on any frame, hold it for any interval (release A any time AFTER pressing it), hold right beginning sometime between frames 37 and 60, hold up beginning exactly 2 frames after holding right, and release right sometime between frame 40 and the end of input. Up will be held until the end of input. Later events override conflicting earlier events. Generally, it's a good idea to define the timing of 'off' commands with respect to their earlier 'on' counterparts. ]]--
Post subject: Brute-force script for short, well-defined goals
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
For an updated version of my script that works in BizHawk, click here. To see an example of how to use my script, scroll down to the second post or just click here. The topic of brute-force completing even a short task in a game comes up every now and then. The conclusion is inevitably that brute-force searches are infeasible for even short segments under heavy restrictions. (Emulating 30 frames using only one button would take one billion iterations.) Wait, where are you going? Come back! I swear I'm not wasting your time! Hear me out! What always struck me as unreasonable about previous discussions of brute-force solutions is that they assumed the most naive restrictions about the input. For example, truly random input (or exhaustive search through all input combination) would include absurd combinations where a button is pressed and released many times to no great effect. For example, in Super Mario Bros., if all we want is for Mario to run right, we want to hold right, not press and release it over and over again. In an ideal situation, the user wants to complete some short task using a few buttons and they already have a decent idea of what buttons they want to press and when. That's where my script comes in. Purpose: To brute-force a short section of the game in which the goal is well-defined and the user has a decent idea of what buttons they want to press and when to press them to achieve that goal. This severely cuts down on the parameter space, so it isn't really brute-forcing, but it does exhaustively run through all possible timings within the ranges specified by the user. The script: Download BruteForce.lua
Language: lua

-- * * * PARAMETERS * * * -- By default, ThisAddress just returns the value of a single address. However, you can have it return any function of any number of addresses. local function ThisAddress() return memory.readbyte() --Input the address you're interested in here. end IWant=ThisAddress ToBe='' --{'minimized','maximized','equal to','less than','greater than','at least','at most'} -- If ToBe in {'minimized','maximized'}... AndTheBestIveDoneIs= --Input a value of that address that you think this script can beat. InThisManyFrames= --How many frames should each iteration run for? The script will check for success after this many frames. -- Elseif ToBe in {'equal to','less than','greater than','at least','at most'} ThisValue= --Input the value that you'd like to compare the address to. AndTheFastestIveDoneItIs= --Input a number of frames that you think the script can beat. UsingTheseButtons={{}} --Input the buttons you'd like to use. Each element of UsingTheseButtons is a table. The first entry is the player, the second is the button, the third is 'on' or 'off' depending on whether you'd like to press or release the button, and the fourth is the interval (absolute or relative) over which you'd like the script to check. See my comment at the bottom of this script for examples of how to enter it. -- * * * FUNCTIONS * * * local function tablecopy(t1) local t2={} if not(type(t1)=='table') then t2=t1 return t2 end for k,v in pairs(t1) do if type(v)=='table' then t2[k]=tablecopy(v) else t2[k]=v end end return t2 end -- Takes a character code (from a string) and returns true if it corresponds to a numeral. local function isnumeral(charcode) return (charcode>=0x30 and charcode<=0x39) end -- Extracts the first number from a string. For example, running this function on the string "event48blah19" will return 48. local function getnumber(str) local numstart=nil local numend=nil for i=1,string.len(str) do if isnumeral(string.byte(str,i)) then if not(numstart) then numstart=i numend=numstart local j=i+1 while isnumeral(string.byte(str,j)) do numend=j j=j+1 end end end end if not(numstart) then error('No number found!') end local valuetext=string.sub(str,numstart,numend) local value = tonumber(valuetext) return value end -- Parses one of the bounds in an interval, returning the frame value. You would run this function on, say, "(event2+10," to return index[2]+10+1. -- It isn't necessary to type "event", "start", or "finish" in full, it just looks for the first match. Watch out, though, because parsebound('[safe+2,') will return index[2]+2, not 2. local function parsebound(str,index,maxframes) local value=-1 if string.find(str,'e') then --Bound defined relative to event. value=index[getnumber(str)] local plus=string.find(str,'+') if plus then local plusthis=string.sub(str,plus) value=value+getnumber(plusthis) end elseif string.find(str,'f') then --Bound defined relative to finish. value=maxframes local minus=string.find(str,'-') if minus then local minusthis=string.sub(str,minus) value=value+getnumber(minusthis) end elseif string.find(str,'s') then --Bound defined relative to start. Note that because of the elseif statement above, this won't be triggered by "finiSh". value=0 local plus=string.find(str,'+') if plus then local plusthis=string.sub(str,plus) value=value+getnumber(plusthis) end else --Bound defined by number. "+" not allowed! value=getnumber(str) end if string.sub(str,1,1)=='(' then value=value+1 end if string.sub(str,-1)==')' then value=value-1 end return value end -- Parses interval notation into start and finish times. local function parseinterval(UsingTheseButtons,index,depth,maxframes) interval = UsingTheseButtons[depth][4] if type(interval)=='number' then --If interval is just a number, then low and high are both equal to that number. low=interval high=interval elseif string.sub(interval,1,1)=='[' or string.sub(interval,1,1)=='(' then --The interval is explicitly defined in this case. local comma=string.find(interval,',') if not comma then error('Expected comma to define interval.') else local lowerbound=string.sub(interval,1,comma) local upperbound=string.sub(interval,comma) low=parsebound(lowerbound,index,maxframes) high=parsebound(upperbound,index,maxframes) end else --If no interval is explicitly defined, then the interval is taken to be implicit and both low and high are set equal to the output of parsebound acting on interval in its entirety. low=parsebound(interval,index,maxframes) high=low end --This is a little forgiving to the user. Perhaps their interval was defined improperly, such that the lower bound surpassed the upper bound. if high<low then high=low end return low,high end -- Compares IWant with Value according to function specified by ToBe local function check(IWant,ToBe,Value) if not(Value) then return nil end if ToBe=='minimized' then return (IWant()<Value) elseif ToBe=='maximized' then return (IWant()>Value) elseif ToBe=='equal to' then return (IWant()==Value) elseif ToBe=='less than' then return (IWant()<Value) elseif ToBe=='greater than' then return (IWant()>Value) elseif ToBe=='at least' then return (IWant()>=Value) elseif ToBe=='at most' then return (IWant()<=Value) else error('Invalid string for ToBe.') end end -- IWant refers only to the current frame, which isn't useful when checking nodes with branches. Therefore, this function directly compares an old value with a new value, based on ToBe. local function check2(newvalue,ToBe,oldvalue) if not(newvalue) then return nil end if ToBe=='minimized' then return (newvalue<oldvalue) elseif ToBe=='maximized' then return (newvalue>oldvalue) elseif ToBe=='equal to' then return (newvalue==oldvalue) elseif ToBe=='less than' then return (newvalue<oldvalue) elseif ToBe=='greater than' then return (newvalue>oldvalue) elseif ToBe=='at least' then return (newvalue>=oldvalue) elseif ToBe=='at most' then return (newvalue<=oldvalue) else error('Invalid string for ToBe.') end end -- Executes the actual button presses and checks if it is superior to previous attempts. -- The output of this function depends on the string in ToBe. If the string is "minimized" or "maximized", it returns the value. If the string is something else, it returns the number of frames. -- If it doesn't beat the current record, the output is nil. local function attempt(presses,IWant,ToBe,Value,maxframes) if ToBe=='maximized' or ToBe=='minimized' then for frame=0,#presses do for player=1,2 do joypad.set(player,presses[frame][player]) end emu.frameadvance() end if check(IWant,ToBe,Value) then return IWant() end else for frame=0,#presses do if frame<maxframes then if check(IWant,ToBe,Value) then return frame end end for player=1,2 do joypad.set(player,presses[frame][player]) end emu.frameadvance() end end return nil end -- Presses the buttons prescribed by UsingTheseButtons and index. -- presses[frame][player][button]. local function pressbuttons(IWant,ToBe,Value,maxframes,UsingTheseButtons,index,state) local presses={} for frame=0,maxframes do presses[frame]={} for player=1,2 do --Edit the number of players here. presses[frame][player]={} end for i=1,#index do if frame>=index[i] then local OnOrOff = (UsingTheseButtons[i][3]=='on') --Set button if "on", reset otherwise. presses[frame][UsingTheseButtons[i][1]][UsingTheseButtons[i][2]]=OnOrOff end end end output=attempt(presses,IWant,ToBe,Value,maxframes) savestate.load(state) return presses,output end -- local function DepthFirstSearch(IWant,ToBe,Value,maxframes,UsingTheseButtons,index,depth,state) depth=depth+1 local bestpresses,attemptframes,attemptValue,presses low,high=parseinterval(UsingTheseButtons,index,depth,maxframes) if depth==#UsingTheseButtons then for i=low,high do --if i>maxframes then return end --Any input after maxframes is irrelevant and shouldn't be checked. index[depth]=i if ToBe=='minimized' or ToBe=='maximized' then presses,attemptValue=pressbuttons(IWant,ToBe,Value,maxframes,UsingTheseButtons,index,state) if attemptValue then Value=attemptValue bestpresses=tablecopy(presses) end else presses,attemptframes=pressbuttons(IWant,ToBe,Value,maxframes,UsingTheseButtons,index,state) if attemptframes then maxframes=attemptframes bestpresses=tablecopy(presses) end end end else for i=low,high do --if i>maxframes then return end --Any input after maxframes is irrelevant and shouldn't be checked. index[depth]=i if ToBe=='minimized' or ToBe=='maximized' then presses,attemptValue=DepthFirstSearch(IWant,ToBe,Value,maxframes,UsingTheseButtons,index,depth,state) if check2(attemptValue,ToBe,Value) then Value=attemptValue bestpresses=tablecopy(presses) end else presses,attemptframes=DepthFirstSearch(IWant,ToBe,Value,maxframes,UsingTheseButtons,index,depth,state) if attemptframes<maxframes then maxframes=attemptframes bestpresses=tablecopy(presses) end end end end if ToBe=='minimized' or ToBe=='maximized' then return bestpresses,Value else return bestpresses,maxframes end end -- * * * BASE CODE * * * if ToBe == 'minimized' or ToBe == 'maximized' then Value=AndTheBestIveDoneIs maxframes=InThisManyFrames else Value=ThisValue maxframes=AndTheFastestIveDoneItIs end depth=0 index={} state1=savestate.create() savestate.save(state1) presses,best=DepthFirstSearch(IWant,ToBe,Value,maxframes,UsingTheseButtons,index,depth,state1) if presses then for frame=0,#presses do print('Frame: '..frame) print('Buttons: ',presses[frame][1]) for player=1,2 do joypad.set(player,presses[frame][player]) end emu.frameadvance() end if ToBe=='minimized' or ToBe=='maximized' then print('Best value: ' .. best) else print('Fewest frames: ' .. best) end else print('No improvement found!') end emu.pause() --[[ UsingTheseButtons has elements with the structure {player,button,'on'/'off',timing} where timing is a string based solely on PREVIOUS events in the UsingTheseButtons vector as well as the start frame (0) and end frame (InThisManyFrames/AndTheFastestIveDoneItIs). Examples of timing: '[start,finish]' -- Test all frames. '[start,37]' -- Test all frames from start to frame 37, inclusive. '(start,37)' -- Test all frames from start to frame 37, exclusive. '[15,37]' -- Any frame from 15 to 37, inclusive. '[event 2,finish]' -- Any frame after event 2 has begun, where event 2 is the second element of the UsingTheseButtons vector. This can be used to force, for example, shooting only after jumping. '[event 2+37,finish]' -- No, that doesn't mean event 39. It means wait at least 37 frames after event 2 has finished. ('[event 2-37,finish]') -- I'm not sure I want to program this, since each event's timing is supposed to depend on PRIOR events. It should always be possible to put them in order. In total, the UsingTheseButtons table might look like this: UsingTheseButtons={{1,'A','on','[start,finish]'}, {1,'A','off','(event 1, finish]'}, {1,'right','on','[37,60]'}, {1,'up','on','[event 3+2,event 3+2]'}, {1,'right','off','[40,finish]'} } which means try pressing A on any frame, hold it for any interval (release A any time AFTER pressing it), hold right beginning sometime between frames 37 and 60, hold up beginning exactly 2 frames after holding right, and release right sometime between frame 40 and the end of input. Up will be held until the end of input. Later events override conflicting earlier events. Generally, it's a good idea to define the timing of 'off' commands with respect to their earlier 'on' counterparts. ]]--
How to use it: I'll post an example of this script's use after I'm finished writing this post. Here is a formal description of how to use the script. Focus your attention on the uppermost section of the script, in the section labeled "PARAMETERS". Everything you input will be there. 1) Decide what constitutes "success". What addresses are involved? If there is just one address you're interested in, put it in the memory.readbyte() function on line 4. If you're interested in some function of more than one memory address, you'll need to rewrite the entire ThisAddress function. 2) What do you want to do with the value you programmed? Do you want it to be maximized or minimized? Do you want it to be equal to, greater than, less than, at least, or at most some value? Pick one of those options and put that corresponding string on line 8, variable ToBe. 3) Suppose you want to minimize or maximize the value. Then the script needs a benchmark from which to start, as well as a number of frames after which to check. These are defined on lines 10 and 11 as variables AndTheBestIveDoneIs and AfterThisManyFrames. (Note that AndTheBestIveDoneIs is largely irrelevant, since the script will keep trying inputs and replacing its value as it runs.) You can then make ThisValue and AndTheFastestIveDoneItIs whatever values you want; those variables will not be used. 4) Suppose instead you want to compare the address with some predetermined value. Input the value you wish to compare it with on line 13 as ThisValue. The script needs to know how long it will have to run for, so input the number of frames at which to check on line 14 as AndTheFastestIveDoneItIs. Since you aren't minimizing or maximizing the address, you can make AndTheBestIveDoneIs and AfterThisManyFrames on lines 10 and 11 whatever you want. Their values will not be used. 5) Finally, here's the hard part. You need to tell the script what buttons to use. That's done on line 16 under table UsingTheseButtons. Each element is another table with four elements. The first element is the player number. The second is the button (as a string) to press/release. The third is either 'on' or 'off', specifying that the button is to be pressed or released respectively. The last element is tricky. It uses mathematical interval notation. You need to specify the interval over which the button might be pressed or released. This can be done absolutely (relative to 'start'=0 frames) or relatively (relative to any prior event). For relative timings, type "event", followed by a number. The number specifies which element of UsingTheseButtons the timing is relative to. For example, if we enter '[event2+8,event3+12]', that means the button is pressed/released no sooner than 8 frames after the second button is pressed/released and no later than 12 frames after the third button is pressed/released. For an example of how all of these are entered, see the comment at the bottom of my script. Briefly reflect on all the values you entered. It's supposed to be easy to interpret in plain English. We entered either, "I want this address (_____) to be {minimized/maximized} and the best I've done is ______ in ______ frames using these buttons: _____," or, "I want this address (_____) to be {equal to/less than/greater than/at least/at most} ______ and the fastest I've done it is _____ frames using these buttons: _____." Make sense? Now save the script and run it during the relevant part of the game. The script will run through all iterations consistent with the bounds you gave it. When it is done, it will execute the button presses that gave it the best result and output those presses to the Lua console for your convenience. Possible improvements: Very few, I'm proud to say! This script already does just about everything I want it to. As usual, my coding is sloppy and could probably be much more concise. I would like to combine the check and check2 functions into one function and I would also like to allow the user to input the address they're interested in directly into the definition of IWant (both of these changes should be easy). I also should expand the script to allow for three or four player games. This is a very powerful script, so if you think that it's not up to some task, it's probably because you haven't yet figured out how to use it to its full potential. Nevertheless, if you have any suggestions, please let me know. Finally, I'd like to thank FatRatKnight for helping me debug the script when it was almost finished.
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
Derakon wrote:
Holy crap, yeah. I remember watching Morimoto's SMB3 run back in college. It's, uh, it's been awhile since then. ...now I feel old.
Same here, except it was in high school. I think I may have seen it on eBaum's World, so at least something good came from my time on that site.
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
Trinity Rescue Kit was extremely helpful to me when my hard drive failed. It allowed me to transfer the old drive's data to a new one, even though the old one couldn't run for more than about fifteen minutes at a time. It looks like it's still being updated.
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
Tompa wrote:
Just mentioning that I have given up on the run until someone magically shows up and solves this crap for me, I just can't stand it. Thanks for the effort, Omnipotent (He actually sent me a PM with some notes, sadly I didn't manage to solve it anyway). Sorry.
I'll give this a quick shot. Don't expect anything to come of it, but maybe a trace log will reveal something.
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
I agree with Hoe. JDiskReport sucks now. I downloaded and ran all three, so here's my conclusion. SpaceSniffer wins. It has the more sensible treemap view between it and SequoiaView, it updates the treemap in realtime as it scans, and it has excellent features and user-customization of its view. Both it and SequoiaView allow for filters, so without looking into it in great detail, I will assume that they have similar capabilities. SequoiaView is a nice program too, but SpaceSniffer just has it beat.
Post subject: Re: Useful software others might not know about
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
Hoe wrote:
SpaceSniffer - Windows - Head over heels the best "what's using my disk space?" application I've used. Even properly supports filesystem links so no more infinite recursions.
Warp wrote:
I have been using SequoiaView for that purpose, but I might give that a try.
I used JDiskReport on my old computer, but never got around to installing it on my new computer. I have no idea how up-to-date it is. Maybe we should compare all three? I'll download 'em. (Then they can all tell me how much space is being taken up by all three!) JDiskReport sucks by comparison.
Hoe wrote:
PowerCalc - Windows - From the Microsoft Windows XP Powertoys. Easily my favorite calculator software. Supports binary/hex/dec input/output.
I'll check it out, but lately, I've just been using Windows' built-in calculator for my hexadecimal to decimal needs. As for my own recommendation, I offer Notepad++, even though it's likely that many people are familiar with it. I think I installed it in a big package bundle when I first got my new laptop. I tend to shy away from new programs that might clutter up my space or interfere with my system defaults or just infringe on my own preferences, but Notepad++ is a programmer's godsend. Some of the features I regularly use include syntax highlighting for every programming language you can reasonably imagine, collapsible subroutines and loops, and tabs for viewing several projects at once. It even has more advanced features like macros that I've dipped my toes in. If you do any programming whatsoever, I'd say Notepad++ is all but essential and I strongly recommend it. Edit: Patashu beat me to it! But I'll leave my post intact. Just download Notepad++. It really is that awesome. Edit 2: SpaceSniffer, not PowerGrep.
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
Warepire wrote:
I ran into this problem recently, I'm not sure it actually has an answer as I cannot find one, but perhaps my math simply isn't strong enough. Given a sequence of 19 pre-defined numbers, for simplicity sake, lets say 1-19. Is there a sensible way (like an algorithm) to get all possible re-ordered sequences of the given numbers?
I know this was already answered, but it's an interesting problem that happens to be simple enough for me to tackle (very rare in this math challenges thread). I also don't know C++ that well, so I'd like to offer my own solution, even though it may match what is in Ilari's link. Let's work with a smaller set of numbers, just 6 through 10 (I'm using 6 through 10 instead of 1 through 5 because I'd like to use those integers later). First, sort them so our first permutation is 6, 7, 8 , 9, 10. Now, produce five bins whose minimal values are
1, 1, 1, 1, 1
and whose maximal values are
5, 4, 3, 2, 1
The bin values of 1, 1, 1, 1, 1 correspond to the first permutation, 6, 7, 8, 9, 10. To produce the next permutation, increment the leftmost bin until it "carries over" and increments the bin to its immediate right. So to produce the first eleven bin values, we would take...
1, 1, 1, 1, 1
2, 1, 1, 1, 1
3, 1, 1, 1, 1
4, 1, 1, 1, 1
5, 1, 1, 1, 1
1, 2, 1, 1, 1
2, 2, 1, 1, 1
3, 2, 1, 1, 1
4, 2, 1, 1, 1
5, 2, 1, 1, 1
1, 3, 1, 1, 1
And so on. How do we obtain a permutation from the bins? If n is the number in the bin, simply take the nth entry in our first permutation that remains in the list. After it has been added, remove it from the list. (Note this means our first permutation didn't have to be in order, we just need to preserve the list's ordering as we progress.) As an example, suppose our bins contain the values 4, 2, 2, 1, 1. The algorithm would look like this (written lazily in Lua-esque pseudocode):
List = {6, 7, 8, 9, 10}  --This is the original list.
Bins = {4, 2, 2, 1, 1}
Permutation = {}  --The final output is just the empty set so far.

--The first element in Bins is 4, so take the 4th element of List and insert it in Permutation:
Permutation = {9}

--Remove the fourth element of List because we can't use 9 in our permutation anymore:
List = {6, 7, 8, 10}

--(This could be done with Lua's table.remove function or some other programming equivalent.  Also note that algorithmically, it may be helpful to remove the first element of Bins so this entire process is done using table.remove and table.add.)

--Now repeat the process.  The second element of Bins is 2, so insert the second element of List into Permutation:
Permutation = {9, 7}

Don't forget to remove 7 from List:
List = {6, 8, 10}

Finishing the last three steps:
Permutation = {9, 7, 8}
List = {6, 10}
Permutation = {9, 7, 8, 6}
List = {10}
Permutation = {9, 7, 8, 6, 10}
And we're done! Because there are 5! different possible values of the Bins variable and each one corresponds to a unique permutation (this is easy to show), all permutations are accounted for. To produce the next permutation, just follow the process I outlined above, incrementing the leftmost bin until it "carries over" to the one to its immediate right and resets to 1. Well, after typing all of that out, I've suddenly realized that it does not produce unique permutations if some of the elements are identical. My recollection is that the combinatorics gets pretty difficult once identical elements are included, so I'll give the problem a little more thought and see if I can tweak the algorithm to suit it. Perhaps this is taken into account in the C++ algorithm? If not, then I propose that problem as the next math challenge.
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
I'm more than comfortable crossing that bridge when we come to it.
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
Well, I guess my original question still stands: Can a run be demoted to the Vault?
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
Patashu wrote:
For example, concatenating the digits of every number one after the other
This is known as the Champernowne constant. It is one of the few numbers known to be normal (it's practically defined to be so). It is also, in a sense, extremely unrandom (although even that is debatable).
Patashu wrote:
and doing it twice if it contains only 1s
I'm confused about this condition. What does it mean, and does it serve some purpose? Is there some exception that needs to be taken into account? * * * If you're bored, use a program that can perform long division to arbitrary precision (200+ digits; I recommend Mathematica, although I also wrote a Matlab program to do the same. Or just use this website.) and enter the following: 60499999499/490050000000 What is the result? Why can it be expressed so accurately in such a short and alluringly elegant rational quotient? (Honestly, I don't entirely know, but I studied it enough to get a few hints.)
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
henke37 wrote:
I have been thinking about my toast at breakfeast lately. If I pick N slices in order from the bag, toast them and randomly place them in a stack on my plate, what is the chance that all slices touch on the same sides as in the bag? That is either in the same order as in the bag, or the reverse order. And each slice can be flipped face the other way. They must either all not be flipped or must all be flipped, depending on if the slice order is reversed or not.
Well, there are N! different ways of arranging the pieces of toast, two of which are potentially valid. For each of those, there are 2^N different toast orientations, only one of which is valid. Therefore, my answer is simply 2 in N!*2^N or 1 in N!*2^(N-1). Bonus: Generalize this result for all eight possible different orientations of each slice. I'd do it myself, but I'm busy making peanut butter and jelly sandwiches...
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
WST wrote:
It is weird, I never got my movies desyncing when I disable the sound (and I disable it really often). I think it would be good if you put the gmv which gives you such behavior.
jlun2 wrote:
Maybe post the movie file you used and see if someone else has that problem as well.
Well, this is embarrassing. I just tested my movie again with sound disabled and it synced fine. I was getting consistent desyncs last night and the only option I changed to fix it was enabling sound. I'll post if it happens again.
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
I nominate this for the screenshot when this is eventually published:
Post subject: Movies desync when sound is disabled
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
Just posting this to point out that my movies desync when I disable sound. Is this a bug or a feature? I know disabling sound allows for faster emulation, but it seems like a trivial reason for a movie to desync. Are there plans to fix this? Thanks.
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
boct1584 wrote:
Yes vote, suggestion Moons, and also suggesting that a Knuckles any% done on S3&K obsolete this.
I agree, although that's heavily dependent on whether the version differences are small enough to make this run redundant to S3&K. Can a run be demoted to the vault? I'm not adamant that this run be placed there, but I just see it as its most fitting long-term destination.
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
Any reason why this can't/shouldn't be vaulted? That's where I would put it.
Post subject: Reading and writing button presses from/to movie files
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
I ran into a little hurdle in my input minimization script. I was unable to create a new GMV movie file from a series of button presses within a Lua script. This prompted me to create a small tool for reading/writing button presses directly from movie files. It's a pretty general tool, so I'm giving it its own thread. Purpose: To parse a movie file into a table of button presses or to reverse the process, turning a sequence of button presses into a movie file entirely within Lua. The table of button presses has three indices: the first index is the frame, the second is the player, and the third is the button pressed (as used in the Lua's input.set function). This is so that the movie can be edited quickly and intuitively using input.set, with the edited movie output directly to a (possibly new) file. Incidentally, because the format of the buttons table is essentially unchanged between the FM2 and GMV movie formats, this could allow for easy porting of a series of inputs from FM2 movie files to GMV movie files, or vice versa. I have no idea why anyone would want to do this, but hey, you can if you want. The script: Download RWmovies.lua
Language: lua

local function fromFM2(filename) local movie=io.open(filename) if not(movie) then print("File does not exist.") return end io.close(movie) local buttons={} --List of buttons in the order that they appear in the FM2 file. local buttonlist={"right","left","down","up","start","select","B","A"} local frame=1 for line in io.lines(filename) do if string.find(line,"|") then buttons[frame]={} for player=1,2 do buttons[frame][player]={} local start=9*player-6 for i=1,#buttonlist do if not(string.sub(line,start+i,start+i)==".") then buttons[frame][player][buttonlist[i]]=true end end end frame=frame+1 end end return buttons end local function fromGMV(filename) local movie=io.open(filename) if not(movie) then print("File does not exist.") return end local buttons={} --List of buttons in the order that they appear in the GMV file. local buttonlist={[0]="up","down","left","right","A","B","C","start"} movie:seek("set",64) str = movie:read("*a") for frame=1,math.floor(string.len(str)/3) do buttons[frame]={} for player=1,2 do buttons[frame][player]={} local binbuttons=string.byte(str,3*frame-3+player,3*frame-3+player) --This is really just NOT(binbuttons)... binbuttons=XOR(binbuttons,255) for i=0,#buttonlist do if AND(binbuttons,2^i)>0 then buttons[frame][player][buttonlist[i]]=true end end end end return buttons end --The headerfilename argument is optional. If it's there, the code plucks its header and creates a new file. If it's absent, the movie MUST replace an existing file. local function toFM2(buttons,filename,headerfilename) local buttonlist={"right","left","down","up","start","select","B","A"} local buttonlistFM2={"R","L","D","U","T","S","B","A"} if headerfilename then local from=io.open(headerfilename) local to=io.open(filename,"w+") local data=from:read("*a") to:write(data) to:flush() io.close(from) io.close(to) end local movie=io.open(filename,"r+b") local str=movie:read("*a") local starthere=string.find(str,"|") movie:seek("set") header=movie:read(starthere-1) io.close(movie) movie=io.open(filename,"w+") movie:write(header) for frame=1,#buttons do presses="|0|" for player=1,2 do for i=1,#buttonlistFM2 do if buttons[frame][player][buttonlist[i]] then presses=presses..buttonlistFM2[i] else presses=presses.."." end end presses=presses.."|" end presses=presses.."\n" movie:write(presses) end io.flush(movie) end local function toGMV(buttons,filename,headerfilename) local buttonlist={[0]="up","down","left","right","A","B","C","start"} local header="" if headerfilename then local from=io.open(headerfilename) header=from:read(64) io.close(from) else local from=io.open(filename) header=from:read(64) io.close(from) end local to=io.open(filename,"w+") to:write(header) for frame=1,#buttons do local presses="" for player=1,2 do local pressnum=0 for i=0,#buttonlist do if not(buttons[frame][player][buttonlist[i]]) then pressnum=pressnum+2^i end end presses=presses..string.char(pressnum) end presses=presses..string.char(255) to:write(presses) end io.flush(to) end --buttons=fromFM2("C:/someFM2file.fm2") --buttons=fromGMV("C:\\someGMVfile.gmv") --print(buttons) --buttons={{{},{}},{{["A"]=true},{["B"]=true}},{{["up"]=true},{["down"]=true}},{{["left"]=true},{["right"]=true}},{{["start"]=true},{["select"]=true}},{{},{}}} --buttons={{{},{}},{{["A"]=true},{["B"]=true}},{{["up"]=true},{["down"]=true}},{{["left"]=true},{["right"]=true}},{{["start"]=true},{["C"]=true}},{{},{}}} --toFM2(buttons,"test.fm2","C:/SomeDirectories/someFM2file.fm2") --toGMV(buttons,"test.txt","Test.gmv") --Format of buttons table: buttons[frame][player][button]
How to use the script: First, choose an FM2 movie or a GMV movie. Then, decide whether you would like to read the button presses from the movie file, write a series of button presses to the movie file, or both. I've commented out the basic features of the script at its end. You'll need to uncomment whatever you want to do. To read an FM2 movie file: • Uncomment the line starting "buttons=fromFM2(...". • Replace the default file path ("C:/someFM2file.fm2") with whatever movie you'd like to open. • Uncomment the print(buttons) line if you'd like to see the button presses in the Lua console. To read a GMV movie file: • Uncomment the line starting "buttons=fromGMV(...". • Replace the default file path ("C:\\someGMVfile.gmv") with whatever movie you'd like to open. • Uncomment the print(buttons) line if you'd like to see the button presses in the Lua console. To write an FM2 movie file: • Uncomment the line beginning "toFM2(...". • Pick a source for your buttons table. This means either using the fromFM2 function or uncommenting the first line with a sample buttons table ("buttons={{{},{}..."). This is the first argument. • Pick a destination file for your new FM2 movie. This is the second argument (default is "test.fm2"). If you do not have a third argument, this file must already exist. • Optionally, pick a source file from which to pluck the header information. This is the third argument. The third argument is used if you want to copy, instead of replace, a movie file and you still want to preserve things like the emulator settings and rerecord count. To write a GMV movie file: • Uncomment the line beginning "toGMV(...". • Pick a source for your buttons table. This means either using the fromGMV function or uncommenting the second line with a sample buttons table ("buttons={{{},{}..."). This is the first argument. • Pick a destination file for your new GMV movie. This is the second argument (default is "test.txt"). If you do not have a third argument, this file must already exist. • Optionally, pick a source file from which to pluck the header information. This is the third argument. The third argument is used if you want to copy, instead of replace, a movie file and you still want to preserve things like the emulator settings and rerecord count. Things to do: • Implement six-button controls in the GMV movie format (X, Y, Z, and mode are automatically presumed/set to false). • Implement commands and other formats specified here. • Expand to include other movie formats (BKM will likely be a priority). I probably won't be doing any of this, except by request or as I have need to, so just give the script a run, tell me how you like it, and make a request and I can probably add it in a couple of hours. Edit: Apparently T stands for sTart, not selecT. I've switched those two buttons (but I have not re-tested the script).
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
The following has never failed me:
Language: lua

state1=savestate.create() savestate.save(state1) --[[ a bunch of code emu.frameadvance() some more code ]]-- savestate.load(state1)
It has some differences with your code. Try copying and pasting it and working from that framework. Report back if you figure out what the problem was. (My money is on the equals sign, as DeHackEd mentioned, provided that you copied your code accurately.)
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
Bisqwit wrote:
I think that in his zealousness to be politically correct, someone has forgotten to be human.
Good lord. Knock it off. Both of you. I was considering jumping in earlier to tell Warp (as a card-carrying atheist myself) that he should pick his battles more judiciously, but now your comment smacks of such un-Christian condescension that there's now plenty of shame to go around. Stop it. We get it. You're a Christian. Warp is an atheist. (Or agnostic? Either way, he's non-practicing.) What you have in common is that you are both acting extremely childish. Drop it. Now. ... Anyway, Bill Nye the Science Guy gave the commencement address at my college and I swear I saw Hulk Hogan at an airport. I don't know for sure if it was him, but how many enormous 6'6" men with blond mustaches do you see day to day?
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
OgreSlayeR wrote:
The next mission is really easy too.
That's a little funny because Seth's missions are supposed to test the player and you are provided with more resources once Kane shows up. Once again, great run! By the way, I don't know how the PlayStation version compares with the PC version, but do you have a choice between versions of subsequent missions? If so, what choices are you making and why? (I realize that the two options are always very similar, so it doesn't matter much.)
Experienced Forum User, Published Author, Player (79)
Joined: 8/5/2007
Posts: 865
Outstanding work! Unless your strategies evolve tremendously, I'd like to see these submitted as part of a full TAS of the game. Don't scrap them just because they're early work. I'm really excited that we might finally see a Command and Conquer TAS!
1 2
16 17 18
34 35