Post subject: Operating Basic Bot
Joined: 4/25/2004
Posts: 615
Location: The Netherlands
Having just discovered 0.98.16 and the basic bot, being a AI student, this got my attention :D The docs are kind of limited though, only one is the html included with Luke's latest release. I figured I'd write something about it, and pray the ones that took over the project will improve on it... The syntax Luke used is simple but effective. I'll try to explain all the possible commands implemented, and how to use them, with examples (since the excitebike.bot file is lost forever)... I've been working on BB to take on my current project (3D battles of ...) and after some time I think I've got it down. GUI: The first set of boxes tell the bot each frame what the odds are of this button pressed this frame. It's a little crude, I know. Probability is a number from 0 to 1000, where 0 is "never pressed" and 1000 is "always pressed". You set the input for one controller at a time, max of two players. The "edit player 2" button should be intuitive enough. The goal is a little bit trickier. "End when" is the condition under which the bot will stop current attempt and restart with the next. This is not limited to a number of frames! This line is evaluated like the others. Maximize and Tiebreak are simply for ordering and determining what attempt will be "best". More about the contents below. Clear Run/Stop and Close should be obvious. Load Save will load the settings. Play Best will load your default savegame and run the attempt that had the best result. If you want to actually see the playback, press this button, save the result, playback the video and load to that default savegame. Make sure you get out of "use external input" mode, because this causes the speed to be maxed. Well then, the tricky part. Luke has given a script-type of language for us to use. Commands: <value> = last value that wasnt processed (eg: 5 loop(x), 5 would be <value>, same for return values: c(0) loop(x)). For more experienced programmers you can look at it like this: the language uses a stack of one deep and <value> indicates the top :) attempt = x'th attempt on which this code is executed a = static value of A button abs(x) = returns the positive value of x ac(x) = same as addcounter(x) addcounter(x) = add <value> to counter x b = static value of B button button = return all the buttons pressed in this frame (as a flag field) counter(x) = returns value of x c(x) = same ass counter(x) down = static value of down-button frame = frame currently processing i = index of loop left = static value of left-button loop(x) = execute x <value> times mem(x) = fetch value of address x ram(x) = fetch value of address x (but limited in range, use mem instead) right = static value of right-button resetcounter(x) = sets x to 0 rc(x) = same as resetcounter start = static value of start button select = static value of select button setcounter(x) = sets x to <value> up = static value of up button Luke also implemented common arithmetic: +: returns the result of addition -: returns the result of subtraction *: returns the result of multiplication /: returns the result of division %: returns the result of mod |: return result of bit-wise OR &: return result of bit-wise AND ^: return result of bit-wise XOR =, >=, <=, !=: return 1000 for true, 0 for false The code will ignore anything unknown (and no errors are thrown...). It only "remembers" the last returned value, and all operators use just two elements: A operator B. You can use parentheses as much as you want though. Evaluation is right-to-left, right-most value is returned. Everything, except for hex numbers, are lowercase. Hex numbers have to be prefixed by "x" (lowercase). Quick examples: (3+4) (8+2) returns 10 (the 7 is dropped) 10 loop(5+xA3) returns 10 (but adds 0xA3 to 5, 10 times) Due to the implementation, spaces are not required (but recommended!). The extra commands is a 1024 box that is executed after each frame (I think). You can use "frame" to check what frame you are currently in, offset at first frame _the bot_ computed. Both boxes are executed. You can use counters, 255 of them. You can address them by index. They start out 0. You can increment them like: value ac(index) eg: 25 ac(15) You can get the value by: c(value) Eg: c(15) Looping works straightforward: iterations loop(code) Eg: rc(23) 15 loop(2 ac(23)) c(23) will return 30. Loop seems to return the number of iterations... The "iif" has also been implemented: condition ? executed if true : executed otherwise false Well, actually, it's more like: condition ? executed if 1000 : executed else Eg: (1=1?2:3) will return 2. Well then, to conclude this little tutorial: BB can only search for maximized results. You can inverse this by checking for 0-yourresult (negative max). For max values, think about maximum points scored, fastest time, etc. You obviously need to know the memory positions first (use cheat to get these) and probe by calling mem(x). Tiebreak will decide which run to keep if two runs have the same result. End when determines when the code will stop the current run and start over. You can set it to frames (let it compute 500 frames by setting "frame=500" as condition). Or perhaps when dead ("mem(x3A30)", if 0x3A30 is the location that indicates this state), or a combination of factors ("frame=500|mem(x3A30)"). The result box will show the buttons pressed in the run, kudo's if you can actually use this ;) That's about it. I have to go now, but I'll try to post some more example code later. Sorry if I made any errors, I'm sure someone will correct me if I have :)
qfox.nl
Joined: 4/25/2004
Posts: 615
Location: The Netherlands
In part two of our tutorial... ;) Well I was looking at the source to see how easy it would be to add a command that could set the probabilities. I had noticed that the code reserved 24 fields of 1k bytes (that's quite a lot, relatively speaking, if you don't use at least 16 of those fields...). Until I noticed that the code also evaluates the lines of probability before each frame (and suddenly it became clear why there was so much space for "just a number" :p). This suddenly makes the whole thing very very powerfull... For instance, with just 50/50 odds the left and right button will be pressed for one frame, and not the other, both, resulting in going sorta forward. Let's change that...
Left button:  
c(1) ? 800 : 500
Right button: 
c(2) ? 800 : 500

Extra code:
rc(1) rc(2)
(0 < left button) ? 1 ac(1)
(0 < right button) ? 1 ac(2)
Let's start with the extra code. This resets counters 1 and 2. Then increases the counter if left or right is pressed. This state is remembered in subsequent frames (until the extra code resets it, which is done AFTER the input). So when the input fields are processed, 1 and 2 will hold the proper value to whether left and right are pressed. Keep in mind that this information is reset in "button" at the beginning of a new frame! Now this is nice, but you'll notice that a lot of the times left and right are pressed together... We don't want this effect so we'll change the code a little bit...
Left button:
300 setcounter(3) 
c(1) ? 800 setcounter(3) 
c(2) ? 0 setcounter(3) : 0 
c(3)

Right button:
300 setcounter(3) 
c(2) ? 800 setcounter(3) 
c(1) ? 0 setcounter(3) : 0 
c(3)
The extra code remains the same. Now this code looks a little odd, but we can't nest loops and if?then:else expressions. At least it kept borking up when I was experimenting. So we'll use a third temp counter, initiate to one of the values that could be returned (the default probability), and change it if a condition is met (if this button was pressed, set probability to 800, if other button was pressed, dont press this button). You'll now notice that both buttons are never pressed at once. The code is added to the line without returns. No extra brackets are required.
300 setcounter(3) c(1) ? 800 setcounter(3) c(2) ? 0 setcounter(3) : 0 c(3)
300 setcounter(3) c(2) ? 800 setcounter(3) c(1) ? 0 setcounter(3) : 0 c(3)
or look at this file :) The same goes for certain events. Like when you need to jump because there's a gap or an enemy near. If the software can detect it, so can this bot! It's just a little harder for us because we don't have documented source codes to figure out what happens when. You have to reverse-engineer this data before you can use it. But once you have it, you can put it in the bot and put it to good use :) In "3D battles of world runner" you have to jump over gaps. Range 06C0 - 070F is reserved for showing the ground/gaps in visual range. The game only uses the lower byte (seems a waste, but whatever). At level start, this range is set to XE (I've only seen 1E, but maybe this can differ). When a gap shows at the horizon, 06C0 is set to X4 (probably 14). As the gap gets closer, the next bytes get set to XE, the first few after 16 frames, but the closer it gets, the lower the interval. When you are not in the air and 0x070D gets set to X4, you _will_ die. So, this is what we're going to do in the bot for the A button (jump):
((mem(x070C)&xFF)=4)?1000:0
Jump at the last possible moment when a gap approaches, but never anywhere else.
qfox.nl
Post subject: Re: Operating Basic Bot
Editor, Expert player (2080)
Joined: 6/15/2005
Posts: 3284
qFox wrote:
(since the excitebike.bot file is lost forever)...
Fortunately, I still have it on my HD. I'll edit this when I get it up. Edit: (the bottom was clipped off a bit)
Joined: 4/25/2004
Posts: 615
Location: The Netherlands
Great! Unfortunately my last post made little sense with the actual code. It seemed that the parser still has certain limitations, like not being able to bracket if?then:else properly (return value will be something... but not what you'd expect). Fortunately, it's only the evaluate function that needs to be re-written. Like he comments himself "//horribly hacky, not meant to be a real expression evaluator!". Maybe I'll put some work on this over the weekend. But I'll need somebody else to compile the thing for me... Any takers?
qfox.nl
Emulator Coder, Skilled player (1311)
Joined: 12/21/2004
Posts: 2687
qFox wrote:
But I'll need somebody else to compile the thing for me... Any takers?
See How to compile FCEU in Windows, it's not too hard to get started with it. I could compile it if you really want that, but for making changes I think you'll find it easier to compile it yourself so you can test it, so I'd rather help you with being able to compile it yourself than compile it for you.
Joined: 4/25/2004
Posts: 615
Location: The Netherlands
You are right, it's unlikely that it'll be ok the first time. I've ran through the evaluate code. C has been far too long ago for me and I don't know the rules about pointers and strings anymore, so I decided not to mess with them at all. I do believe I fixed a bug or two. I changed a designflaw, you should now be safely able to nest iif's, and anything else (except for loop(), that is another matter). Luke's code does not handle iif's properly, as far as I can see. I've added three commands: sc: which is short for setcounter, uses the same code echo: which should echo the current value to the textarea on the right, but probably won't yet since I've never done GUI stuff in C and I worked in a text-editor, so I don't get the errors the compiler would fish out. stop: should do the same as pressing stop, but again, probably wont. I added a quick error message so you now know when you made a syntactical error (finding out what and where is your job, for now). From now on, you can only use spaces, any character it does not know or expect is considered an error. I changed a few variable names and commented all over the place (all my comments are prefixed with my name). I did not change anything below the evaluate function. When an error occurs, unpredictable results can happen. The "there is an error, cleanup!" code is not there yet. Other then that... I think the code should work fine. Nitsuja could you help me by pointing me to a nice small C IDE for windows that shows syntactical errors out for me? Perhaps even with code-completion so I can mess about with the GUI stuff as well. I'm just a Java coder... basicbot.c (zip) pastebin
qfox.nl
Joined: 1/21/2007
Posts: 14
Location: Montreal, Canada
Is there a way to forbid certain combos of input? Ex (1): I know that at some point in the sequence (more or less 600 frames) D-pad Right & Left will be used but NEVER both together at any specific frame. Because when I see him make attemps I can see sometimes the bot presses both < > on some frames wich is useless. Ex (2): Prevent the bot from having input sequences where he presses: 1.<2> 3.<4> 5.<6>7.< Like putting a command where you tell the bot to press and hold any direction for at least 2~3 frames before changing the input? Thanks for the help ? Anyone...