But since the number is stored as 32 bits, for lua, wouldn't this result in numbers always being read as negative? EG: you get "1 - 256 = -255" rather than the "1 - 256 = 1" that you'd get with a char or signed char in C?
How fleeting are all human passions compared with the massive continuity of ducks.
Okay then. I'll add it. memory.read{byte,word}signed will return a number in the range of -32768 to 32767 or -128 to 127 as the case may be.
Added to the API.
typedef unsigned long DWORD;
typedef int BOOL;
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef float FLOAT;
typedef FLOAT *PFLOAT;
typedef BOOL near *PBOOL;
typedef BOOL far *LPBOOL;
typedef BYTE near *PBYTE;
typedef BYTE far *LPBYTE;
typedef int near *PINT;
typedef int far *LPINT;
typedef WORD near *PWORD;
typedef WORD far *LPWORD;
typedef long far *LPLONG;
typedef DWORD near *PDWORD;
typedef DWORD far *LPDWORD;
typedef void far *LPVOID;
typedef CONST void far *LPCVOID;
Just when I thought this Lua thing couldn't get more awesome, I realized it could be used for botting, and created a rudimentary bot script for SMW. How it works is, it runs to the right while holding Y, and randomly presses B. Brilliant, isn't it?
Here's a movie of it in action... sort of. I had to help it a lot with the savestates, because it does not yet know how to avoid getting caught in an infinite death loop.
Linky
Here's the general outline of what I'm planning:
local buttons = { Y = 1, right = 1 }
local sol = savestate.create(5)
savestate.save(sol)
--start movie recording to random filename, set speed to maximum
while true do
joypad.set(1, buttons)
--if mario dies, reload.
if (memory.readbyte(0x7e0071)) == 9 then
savestate.load(sol)
--compare input to previous inputs, dump results into a buttons table
end
--TODO: pseudo-randomize input, weighted according to previous attempts
--but for now, just jump up and down like an idiot
resetbuttons = math.random(0,20)
if resetbuttons == 1 then
buttons.B = math.random(0,2)
if buttons.B == 0 then buttons.B = nil end
end
--incrementally save every N frames
--detect instant death
--detect end of level, drop out of warp
snes9x.frameadvance();
end
I think it has potential. More ideas, anyone?
It would be nice (if not incredibly handy) if movies could be handled as objects, the way savestates are- particularly with regards to the creation of temporary files.
Edit: Also, I couldn't get savestate.create() to work without passing an integer.
Bah. It sorted itself out. Nevermind. Sorry for the edits, IRC. ;)
You mean, like this? Uniracers Stunts & Jump Optimizer v13 (file too big to be pasted in a code block)
This bot produces frame-perfect input to create the most elaborate stunt possible for a given jump (for the game Uniracers, obviously). You set the emulator before a jump, and run the LUA script. The bot will then intelligently attempt different stunt combos and optimize for speed, and when it's done, it replays the best stunt it could find, and if you happened to be recording, it records it for you!
DeHackEd: I think this bot is a good example of an alternate usage of lua scripting that isn't just for gui displays. You might want to take an in-depth look at this ;)
EDIT: Forgot to add, this bot is best watched holding down Frame Advance. It optimizes in 10s of seconds what takes a human hours. If you don't watch it in Frame Advance, you might miss all the action as it happens too fast.
Joined: 1/16/2008
Posts: 358
Location: The Netherlands
looks very nice to me tbh, you plan to expand it? :) would be great to see a more general version (e.g. one that does not assume that holding right+Y is best, and one that might start flying? :D)
I tried something similar for Mr. Nutz using several different approaches but failed miserably :P (it'd only get to about 30% of the (by my knowledge) best possible)
@Halamantariel, very nice script :D perhaps using more savestates a tree-search would result in a major speedup for scripts like that? (default backtracking)
Yeah, definitely. Hal's ridiculously awesome script has blown my mind and inspired me to brute force the hell out of 7th Saga, so I'm somewhat diverted, though.
The inputs will become more random in the next version. It might also be cool to use hard-coded input sets to affect flying and certain glitches from the TASes, but I expect that it will be stuck as a dodo, considering the unlikelihood of Mario grabbing a feather. ;)
Joined: 1/16/2008
Posts: 358
Location: The Netherlands
perhaps writing an evaluation/utility function that is as general as possible would improve the bot (and then just go for a greedy approach)
it could incorporate anything from having a feather, being in flight, being in the water, or level-specific stuff such as 'having gone through pipe X' etc etc)
all these ideas .. ;)
Reminds me of when I created this topic? I've fiddled with smw bots this LUA scripting, mainly for determining fadeout lag (pseudo code here)
while(score < MAX_SCORE){
score = 0;
save state 1 frame before the gate;
poke score with desired score;
when lagging increment a lag counter; //hard part
stop incrementing lag counter;//stop time not important as long as all lag is recorded
file I/O;
score++;
load state
}
It's touch-and-go for me to say the least, but the only thing stopping it from working perfectly were finding out how to correctly determine what a lag frame was, and make sure that the amount of lag does correspond with a "poked" score.
Also, I haven't touched my work in a while, because work has been draining my time. Also, leaving this bot on overnight when before I realized there used to be a memory leak wasn't fun.
Source code. Windows users are encouraged to build using Makefile.mingw because you're on your own any other way. Linux users should build as usual. Some assembly required.
The memory.register function is finally written!
gui code expanded. You might want to look into this one.
Binary functions added For Your Convenience (tm)
If you accidentally send a script running unchecked, you will be prompted to kill it.
The signed version of the memory functions now exist
Windows EXE will load external libs now. As a side effect, there's now a lua51.dll pulled out of the main EXE because it's needed.
I think that covers it.
Version 0.04 windows EXE
Edit: EXE replaced with a fixed version. Lua DLL will cause a crash otherwise. I previously fixed this bug, then zipped up the wrong EXE. Stupid me.
Umm.. I DID kinda plan for that possibility. That's why you have the savestate engine at your disposal and the ability to push your own buttons. The GUI didn't come until the third release.
Kind of. I did want to make a "learning program" that didn't need heuristics, but heuristics and randomization is all I've used so far. Now it's stuck at the start of Yoshi's Island 3 because I've not been able to explain that "you're just running back and forth, not getting anywhere! Try walking slowly and jumping more."
Methinks it's time Mario became acquainted with his own X/Y coords and a little modal logic...
Wouldn't the overall frame count be adequate? I guess it's one thing if you want to figure out how it works, but for the purposes of TASing, I wouldn't think it really matters.
Unrelated note: v0.04 is crashing whenever I load a ROM. I'm just going to assume the problem is my system, until someone else reports the same problem.
To gocha: I knew I forgot something about 0x7e0000... I just couldn't remember what... ugh, I do that kind of thing a LOT
To Dromiceius: I accidentally zipped up the wrong EXE when making the 7z. It was replaced with a fixed one. Something went wrong in a recompile of the EXE. Just redownload. Or use improvement 12...
I must say the new features look really nice, but I'm a bit lost on how to use the memory.register function. When I use the following code:
function func(address)
snes9x.message("Written: "..address)
end
memory.register(0x7E0521,func(0x7E0521))
while true do
snes9x.frameadvance()
end
I get an error about the second argument not being a function or nil. The same thing happens when I put the memory.register function inside the while loop.
EDIT: I know this function is kind of silly since it shows the address in decimals, but that shouldn't matter. ;)
While Datel's style of writing function bodies is no different (lua silently rewrites gunty's code to look identical to Datel's during compilation), there's a two problems to be addressed as well. The first is the change to the actual memory.register line. You want to give the function by name, not by actually calling it, which is what you do. The second regards the parameters the function itself gets.
No parameter is given to the called function. Lua fills in the name you given with nil to compensate.
It may sound stupid, but it's sorta built on the assumption that you'd use a different function for each memory address.
Alternatively, you can use upvalues...
-- a "do/end" is a block of scope, just like within a function, while loop, or other indented code block.
do
local addr = 0x7e0101
local function registered()
snes9x.message("Written: "..addr)
end
memory.register(addr, registered)
end
Even though "addr" falls out of scope, the function "registered" can still access it, and get its value from there.
Every time the flow of code would execute a function() block, a function is actually created out of the local variables in place at the time.
-- We construct 3 "functions" (technically call closures) with identical code,
-- but different upvalues for "addr" in each one. So while it's technically
-- one function, we can give the illusion that it knows some magic value
-- for its specific invocation
addresses = { 0x7e0001, 0x7e0002, 0x7e0003} -- Just go with it
for key,value in pairs(addresses) do
-- We need a distinct local for each function
local addr = value
local function registered()
... -- Use "addr" in here somewhere
end
memory.register(addr, registered)
end
I don't think it's necessary to add arguments to the registered functions, I only wrote this (useless) function as an example.
However, when using the (edited) code DaTeL237 posted, I'm getting an error about attempting to index a nil value. Even when removing all arguments like this:
myFunction = function()
snes9x.message("Written: 7E0521")
end
memory.register(0x7E0521,myFunction)
while true do
snes9x.frameadvance()
end
Thanks for the new code. I've tracked it down to a bug in my own code. It's crashing in memory.register itself, isn't it....
I can fix this in a matter of minutes. While I'm at it, is there any suggestion for features that should be added? I have a few minor additions to gui to allow for pop-ups to the user and stuff.... good idea?
I'm not sure if this is possible yet, but it might be nice if there's a way to make the emulator pause without having to stop the script or use the "memory.register" function.
Something simple like snes9x.pause() or snes9x.speedmode("pause").