Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ClusterM/fceux.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorqfox <qfox@users.sf.net>2008-08-01 02:57:32 +0400
committerqfox <qfox@users.sf.net>2008-08-01 02:57:32 +0400
commit590240af68970c9b0229b7795c410c847a842afc (patch)
tree5ab257a3d72b738b3ddead07e778aa0cbf76faef /documentation
parent561936d536f35bd5f5f4213dfd7c02e6860011d1 (diff)
Right. Add files before committing...
Diffstat (limited to 'documentation')
-rw-r--r--documentation/basicbot_framework.lua581
-rw-r--r--documentation/basicbot_front.lua268
2 files changed, 849 insertions, 0 deletions
diff --git a/documentation/basicbot_framework.lua b/documentation/basicbot_framework.lua
new file mode 100644
index 00000000..d4559b98
--- /dev/null
+++ b/documentation/basicbot_framework.lua
@@ -0,0 +1,581 @@
+-- LuaBot, concept bot for FCEU and ZSnes and any other emu that has compatible Lua engine
+-- qFox, 30 July 2008
+-- version 1.04
+
+-- Botscript data following
+-- Rom: ROMNAME
+-- Comment: COMMENT
+-- Version: VERSION
+
+local function realcount(t) -- accurately count the number of elements of a table, even if they contain nil values. not fast, but accurate.
+ local n = 0;
+ for i,_ in pairs(t) do
+ n = n + 1;
+ end;
+ return n;
+end;
+
+local maxvalue = 100; -- always "true", yes, pressed
+local minvalue = 0; -- always "false", no, released
+
+-- the bot kind of works like this function. for every option, a value is requested from the programmer.
+-- if that value is higher then a random value 0-99, this function returns true, else false.
+-- the idea is that keys are pressed based on a heuristic value. if you want absolute values, simply
+-- return values like minvalue or maxvalue, or their syntactic equals yes/no and press/release (to make
+-- the sourcecode easier to read). you can use the variable maybe for 50% chance.
+-- this function also reduces a lot of calls to random() if the user uses absolute values.
+local function rand_if(n)
+ if (n <= minvalue) then return false; end;
+ if (n >= maxvalue) then return true; end;
+ if (n > math.random(minvalue, maxvalue-1)) then return true; end;
+ return false;
+end;
+
+local loopcounter = 0; -- counts the main loop
+local key1 = {}; -- holds the to be pressed keys this frame for player 1
+local key2 = {}; -- holds the to be pressed keys this frame for player 2
+local lastkey1 = {}; -- keys pressed in previous frame for player 1
+local lastkey2 = {}; -- keys pressed in previous frame for player 2
+local frame = 0; -- number of frames (current value is current frame count, incremented at the start of a new frame)
+local attempt = 1; -- number of attempts (current value is current attempt, incremented after the end of an attempt)
+local segment = 1; -- number of segments (current value is current segment, incremented after the end of a segment)
+local okattempts = 0; -- number of successfull attempts (including rollback)
+local failattempts = 0; -- number of failed attempts (including rollback)
+
+local segments = {}; -- table that holds every segment, each segment is another table that consists of the score, ties, savestate (begin of segment), lastkeys, keys pressed, etc.
+segments[1] = {}; -- initialize the first segment, we initialize the savestate right after the before code has ran
+
+-- these dont have to be used, but it makes it easier to control here
+local maxframes = 400;
+local maxattempts = 200;
+local maxsegments = 100;
+
+local playingbest = false; -- when going to the next segment, we need to play the best segment to record, this indicates when we're doing so
+local keyrecording1 = {}; -- every key pressed for player 1 is put in here
+local keyrecording2 = {}; -- every key pressed for player 2 is put in here
+
+-- some constants/macro's/whatever to make source easier to read
+local press = maxvalue;
+local release = minvalue;
+local yes = maxvalue;
+local maybe = maxvalue/2; -- 50%
+local no = minvalue;
+
+-- static constants, will be used by the frontend later
+local X = 95;
+local Y = 30;
+local Z = 0;
+local P = 0;
+local Q = 0;
+
+local vars = {}; -- variable table. each cell holds a variable. variables are remembered accross segments
+
+local outputcounter = 0;
+local updateevery = 1000;
+
+-- user defined functions
+
+local function getScore() -- score of current attempt
+ local result = no;
+ -- SCORE
+ return result;
+end;
+
+local function getTie1() -- tie breaker of current attempt in case score is equal
+ local result = no;
+ -- TIE1
+ return result;
+end;
+
+local function getTie2() -- second tie breaker
+ local result = no;
+ -- TIE2
+ return result;
+end;
+
+local function getTie3() -- third tie breaker
+ local result = no;
+ -- TIE3
+ return result;
+end;
+
+local function getTie4() -- fourth tie breaker
+ local result = no;
+ -- TIE4
+ return result;
+end;
+
+local function isRunEnd() -- gets called 3x! twice in the main loop (every frame). determines whether the bot should quit.
+ local result = no;
+ -- ISRUNEND
+ return result;
+end;
+
+local function mustRollBack() -- drop back to previous segment? called at the end of a segment
+ local result = no;
+ -- MUSTROLLBACK
+ return result;
+end;
+
+local function isSegmentEnd() -- end of current segment? (usually just x frames or being really stuck (to rollback))
+ local result = no;
+ -- ISSEGMENTEND
+ return result;
+end;
+
+local function isAttemptOk() -- is current run ok? like, did you die? (then the run is NOT ok... :). return no for no and yes for yes or be left by chance.
+ local result = yes;
+ -- ISATTEMPTOK
+ return result;
+end;
+
+local function isAttemptEnd() -- end of current attempt? (like when you die or reach a goal)
+ local result = no;
+ -- ISATTEMPTEND
+ return result;
+end;
+
+-- the next 2x8 functions determine whether a button should be pressed for player 1 and 2
+-- return yes or no for absolute values, return anything between minvalue and maxvalue
+-- to set a chance of pressing that button.
+
+local function pressKeyA1()
+ local result = no;
+ -- bA1
+ return result;
+end;
+
+local function pressKeyB1()
+ local result = no;
+ -- bB1
+ return result;
+end;
+
+local function pressKeyStart1()
+ local result = no;
+ -- START1
+ return result;
+end;
+
+local function pressKeySelect1()
+ local result = no;
+ -- SELECT1
+ return result;
+end;
+
+local function pressKeyUp1()
+ local result = no;
+ -- UP1
+ return result;
+end;
+
+local function pressKeyDown1()
+ local result = no;
+ -- DOWN1
+ return result;
+end;
+
+local function pressKeyLeft1()
+ local result = no;
+ -- LEFT1
+ return result;
+end;
+
+local function pressKeyRight1()
+ local result = no;
+ -- RIGHT1
+ return result;
+end;
+
+local function pressKeyA2()
+ local result = no;
+ -- bA2
+ return result;
+end;
+
+local function pressKeyB2()
+ local result = no;
+ -- bB2
+ return result;
+end;
+
+local function pressKeyStart2()
+ local result = no;
+ -- START2
+ return result;
+end;
+
+local function pressKeySelect2()
+ local result = no;
+ -- SELECT2
+ return result;
+end;
+
+local function pressKeyUp2()
+ local result = no;
+ -- UP2
+ return result;
+end;
+
+local function pressKeyDown2()
+ local result = no;
+ -- DOWN2
+ return result;
+end;
+
+local function pressKeyLeft2()
+ local result = no;
+ -- LEFT2
+ return result;
+end;
+
+local function pressKeyRight2()
+ local result = no;
+ -- RIGHT2
+ return result;
+end;
+
+local function pressKeyA3()
+ local result = no;
+ -- bA3
+ return result;
+end;
+
+local function pressKeyB3()
+ local result = no;
+ -- bB3
+ return result;
+end;
+
+local function pressKeyStart3()
+ local result = no;
+ -- START3
+ return result;
+end;
+
+local function pressKeySelect3()
+ local result = no;
+ -- SELECT3
+ return result;
+end;
+
+local function pressKeyUp3()
+ local result = no;
+ -- UP3
+ return result;
+end;
+
+local function pressKeyDown3()
+ local result = no;
+ -- DOWN3
+ return result;
+end;
+
+local function pressKeyLeft3()
+ local result = no;
+ -- LEFT3
+ return result;
+end;
+
+local function pressKeyRight3()
+ local result = no;
+ -- RIGHT3
+ return result;
+end;
+
+local function pressKeyA4()
+ local result = no;
+ -- bA4
+ return result;
+end;
+
+local function pressKeyB4()
+ local result = no;
+ -- bB4
+ return result;
+end;
+
+local function pressKeyStart4()
+ local result = no;
+ -- START4
+ return result;
+end;
+
+local function pressKeySelect4()
+ local result = no;
+ -- SELECT4
+ return result;
+end;
+
+local function pressKeyUp4()
+ local result = no;
+ -- UP4
+ return result;
+end;
+
+local function pressKeyDown4()
+ local result = no;
+ -- DOWN4
+ return result;
+end;
+
+local function pressKeyLeft4()
+ local result = no;
+ -- LEFT4
+ return result;
+end;
+
+local function pressKeyRight4()
+ local result = no;
+ -- RIGHT4
+ return result;
+end;
+
+-- now follow the "events", one for the start and end of a frame, attempt, segment and whole bot. none of them need to return anything
+
+local function onStart() -- this code should run before the bot starts, for instance to start the game from power on and get setup the game
+ -- ONSTART
+end;
+
+local function onFinish() -- code ran after the bot finishes
+ -- ONFINISH
+end;
+
+local function onSegmentStart() -- code ran after initializing a new segment, before onAttemptStart(). framecount is always one fewer then actual frame!
+ -- ONSEGMENTSTART
+end;
+
+local function onSegmentEnd() -- code ran after a segment finishes, before cleanup of segment vars
+ -- ONSEGMENTEND
+end;
+
+local function onAttemptStart() -- code ran after initalizing a new attempt, before onInputStart(). not ran when playing back. framecount is always one fewer then actual frame!
+ -- ONATTEMPTSTART
+end;
+
+local function onAttemptEnd(wasOk) -- code ran after an attempt ends before cleanup code, argument is boolean true when attempt was ok, boolean false otherwise. not ran when playing back
+ -- ONATTEMPTEND
+end;
+
+local function onInputStart() -- code ran prior to getting input (keys are empty). not ran when playing back
+ -- ONINPUTSTART
+end;
+
+local function onInputEnd() -- code ran after getting input (lastkey are still valid) (last function before frame ends, you can still manipulate the input here!). not ran when playing back
+ -- ONINPUTEND
+end;
+
+-- the bot starts here..
+
+onStart(); -- run this code first
+
+segments[segment].savestate = savestate.create(); -- create anonymous savestate obj for start of first segment
+savestate.save(segments[segment].savestate); -- save current state to it, it will be reloaded at the start of each frame
+local startkey1 = key1; -- save the last key pressed in the onStart. serves as an anchor for the first segment
+local startkey2 = key2;
+local startvars = vars; -- save the vars array (it might have been used by the onStart)
+lastkey1 = key1; -- to enter the loop...
+lastkey2 = key2;
+--FCEU.speedmode("maximum"); -- uncomment this line to make the bot run faster ("normal","turbo","maximum")
+
+onSegmentStart();
+onAttemptStart();
+
+collectgarbage(); -- just in case...
+
+-- This will loops for each frame, at the end of the while
+-- the frameadvance is called, causing it to advance
+while (rand_if(isRunEnd())) do
+ loopcounter = loopcounter + 1; -- count the number of botloops
+ --gui.text(200,10,loopcounter); -- print it on the right side if you want to see the number of total frames
+
+ if (not playingbest and rand_if(isAttemptEnd())) then -- load save state, continue with next attempt (disabled when playing back best)
+ -- record this attempt as the last attempt
+ if (not segments[segment].prev) then segments[segment].prev = {}; end;
+ segments[segment].prev.frames = frame;
+ segments[segment].prev.attempt = attempt;
+ segments[segment].prev.score = getScore();
+ segments[segment].prev.tie1 = getTie1();
+ segments[segment].prev.tie2 = getTie2();
+ segments[segment].prev.tie3 = getTie3();
+ segments[segment].prev.tie4 = getTie4();
+ segments[segment].prev.ok = rand_if(isAttemptOk()); -- this is the check whether this attempt was valid or not. if not, it cannot become the best attempt.
+ -- update ok/failed attempt counters
+ if (segments[segment].prev.ok) then
+ okattempts = okattempts + 1;
+ onAttemptEnd(true);
+ else
+ failattempts = failattempts + 1;
+ onAttemptEnd(false);
+ end;
+
+ -- if this attempt was better then the previous one, replace it
+ -- its a long IF, but all it checks (lazy eval) is whether the current
+ -- score is better then the previous one, or if its equal and the tie1
+ -- is better then the previous tie1 or if the tie1 is equal to the prev
+ -- etc... for all four ties. Only tie4 actually needs to be better, tie1
+ -- through tie3 can be equal as well, as long as the next tie breaks the
+ -- same tie of the previous attempt :)
+ if (segments[segment].prev.ok and (not segments[segment].best or (getScore() > segments[segment].best.score or (getScore() == segments[segment].best.score and (getTie1() > segments[segment].best.tie1 or (getTie1() == segments[segment].best.tie1 and (getTie1() > segments[segment].best.tie1 or (getTie1() == segments[segment].best.tie1 and (getTie1() > segments[segment].best.tie1 or (getTie1() == segments[segment].best.tie1 and getTie1() > segments[segment].best.tie1)))))))))) then
+ -- previous attempt was better then current best (or no current best
+ -- exists), so we (re)place it.
+ if (not segments[segment].best) then segments[segment].best = {}; end;
+ segments[segment].best.frames = segments[segment].prev.frames;
+ segments[segment].best.attempt = segments[segment].prev.attempt;
+ segments[segment].best.score = segments[segment].prev.score;
+ segments[segment].best.tie1 = segments[segment].prev.tie1;
+ segments[segment].best.tie2 = segments[segment].prev.tie2;
+ segments[segment].best.tie3 = segments[segment].prev.tie3;
+ segments[segment].best.tie4 = segments[segment].prev.tie4;
+ segments[segment].best.keys1 = keyrecording1; -- backup the recorded keys
+ segments[segment].best.keys2 = keyrecording2; -- backup the recorded keys player 2
+ segments[segment].best.lastkey1 = lastkey1; -- backup the lastkey
+ segments[segment].best.lastkey2 = lastkey2; -- backup the lastkey
+ segments[segment].best.vars = vars; -- backup the vars table
+ end
+
+ if (rand_if(isSegmentEnd())) then -- the current segment ends, replay the best attempt and continue from there onwards...
+ onSegmentEnd();
+ if (rand_if(mustRollBack())) then -- rollback to previous segment
+ gui.text(50,50,"Rolling back to segment "..(segment-1));
+ segments[segment] = nil; -- remove current segment data
+ attempt = 0; -- will be incremented in a few lines to be 1
+ segment = segment - 1;
+ segments[segment].best = nil;
+ segments[segment].prev = nil;
+ collectgarbage(); -- collect the removed segment please
+ else
+ playingbest = true; -- this will start playing back the best attempt in this frame
+ end;
+ end;
+
+ -- reset vars
+ attempt = attempt + 1;
+ frame = 0;
+ keyrecording1 = {}; -- reset the recordings :)
+ keyrecording2 = {};
+ -- set lastkey to lastkey of previous segment (or start, if first segment)
+ -- also set the vars table to the table of the previous segment
+ if (segment == 1) then
+ lastkey1 = startkey1;
+ lastkey1 = startkey1;
+ vars = startvars;
+ else
+ lastkey1 = segments[segment-1].best.lastkey1;
+ lastkey2 = segments[segment-1].best.lastkey2;
+ vars = segments[segment-1].best.vars;
+ end;
+ -- load the segment savestate to go back to the start of this segment
+ if (segments[segment].savestate) then -- load segment savestate and try again :)
+ savestate.load(segments[segment].savestate);
+ else
+ fceu.crash(); -- this crashes because fceu is a nil table :) as long as gui.popup() doesnt work... we're crashing because no save state exists..? it should never happen.
+ end;
+
+ if (rand_if(isRunEnd())) then break; end; -- if end of run, break out of main loop and run in end loop.
+
+ if (not playingbest) then onAttemptStart(); end; -- only call this when not playing back best attempt. has decreased frame counter!
+ end; -- continues with (new) attempt
+
+ -- increase framecounter _after_ processing attempt-end
+ frame = frame + 1; -- inrease the frame count (++frame?)
+
+ if (playingbest and segments[segment].best) then -- press keys from memory (if there are any)
+ gui.text(10,150,"frame "..frame.." of "..segments[segment].best.frames);
+ if (frame >= segments[segment].best.frames) then -- end of playback, start new segment
+ playingbest = false;
+ lastkey1 = segments[segment].best.lastkey1;
+ lastkey2 = segments[segment].best.lastkey2;
+ vars = segments[segment].best.vars;
+ segment = segment + 1;
+ segments[segment] = {};
+ -- create a new savestate for the start of this segment
+ segments[segment].savestate = savestate.create();
+ savestate.save(segments[segment].savestate);
+ -- reset vars
+ frame = 0; -- onSegmentStart and onAttemptStart expect this to be one fewer...
+ attempt = 1;
+ --key1 = {}; -- i dont think this has to be done just for the two events below. they get reset anyways before input.
+ --key2 = {};
+ keyrecording1 = {}; -- reset recordings :)
+ keyrecording2 = {};
+ -- after this, the next segment starts because playingbest is no longer true
+ onSegmentStart();
+ onAttemptStart();
+ frame = 1; -- now set it to 1
+ else
+ key1 = segments[segment].best.keys1[frame]; -- fill keys with that of the best attempt
+ key2 = segments[segment].best.keys2[frame];
+ gui.text(10,10,"Playback best of segment "..segment.."\nFrame: "..frame);
+ end;
+ end;
+
+ if (rand_if(isRunEnd())) then break; end; -- if end of run, break out of main loop
+ -- note this is the middle, this is where an attempt or segment has ended if it would and started if it would!
+ -- now comes the input part for this frame
+
+ if (not playingbest) then -- when playing best, the keys have been filled above.
+ -- press keys from bot
+ gui.text(10,10,"Attempt: "..attempt.." / "..maxattempts.."\nFrame: "..frame.." / "..maxframes);
+ if (segments[segment] and segments[segment].best and segments[segment].prev) then
+ gui.text(10,30,"Last score: "..segments[segment].prev.score.." ok="..okattempts..", fail="..failattempts.."\nBest score: "..segments[segment].best.score);
+ elseif (segments[segment] and segments[segment].prev) then
+ gui.text(10,30,"Last score: "..segments[segment].prev.score.."\nBest score: none, fails="..failattempts);
+ end;
+ gui.text(10,50,"Segment: "..segment);
+
+ key1 = {};
+ key2 = {};
+
+ onInputStart();
+
+ -- player 1
+ if (rand_if(pressKeyUp1()) ) then key1.up = 1; end;
+ if (rand_if(pressKeyDown1())) then key1.down = 1; end;
+ if (rand_if(pressKeyLeft1())) then key1.left = 1; end;
+ if (rand_if(pressKeyRight1())) then key1.right = 1; end;
+ if (rand_if(pressKeyA1())) then key1.A = 1; end;
+ if (rand_if(pressKeyB1())) then key1.B = 1; end;
+ if (rand_if(pressKeySelect1())) then key1.select = 1; end;
+ if (rand_if(pressKeyStart1())) then key1.start = 1; end;
+
+ -- player 2
+ if (rand_if(pressKeyUp2()) ) then key2.up = 1; end;
+ if (rand_if(pressKeyDown2())) then key2.down = 1; end;
+ if (rand_if(pressKeyLeft2())) then key2.left = 1; end;
+ if (rand_if(pressKeyRight2())) then key2.right = 1; end;
+ if (rand_if(pressKeyA2())) then key2.A = 1; end;
+ if (rand_if(pressKeyB2())) then key2.B = 1; end;
+ if (rand_if(pressKeySelect2())) then key2.select = 1; end;
+ if (rand_if(pressKeyStart2())) then key2.start = 1; end;
+
+ onInputEnd();
+
+ lastkey1 = key1;
+ lastkey2 = key2;
+
+ keyrecording1[frame] = key1; -- record these keys
+ keyrecording2[frame] = key2; -- record these keys
+ end;
+
+ -- actually set the keys here.
+ joypad.set(1, key1);
+ joypad.set(2, key2);
+
+ -- next frame
+ FCEU.frameadvance();
+end;
+
+onFinish(); -- allow user cleanup before starting the final botloop
+
+-- now enter an endless loop displaying the results of this run.
+while (true) do
+ if (segments[segment].best) then gui.text(30,100,"end: max attempt ["..segment.."] had score = "..segments[segment].best.score);
+ elseif (segment > 1 and segments[segment-1].best) then gui.text(30,100,"end: no best attempt ["..segment.."]\nPrevious best score: "..segments[segment-1].best.score);
+ else gui.text(30,100,"end: no best attempt ["..segment.."] ..."); end;
+ FCEU.frameadvance();
+end;
+
+-- i dont think it ever reaches this place... perhaps it should, or some event or whatever...
+segments = nil;
+collectgarbage(); -- collect the segment data... anything else is probably not worth it...
diff --git a/documentation/basicbot_front.lua b/documentation/basicbot_front.lua
new file mode 100644
index 00000000..d798e45c
--- /dev/null
+++ b/documentation/basicbot_front.lua
@@ -0,0 +1,268 @@
+-- BasicBot, a LuaBot frontend
+-- qFox, 30 July 2008
+-- version 1.04 (unstable!)
+
+-- we need iup, so include it here
+local iuplua_open = package.loadlib("iuplua51.dll", "iuplua_open");
+iuplua_open();
+local iupcontrolslua_open = package.loadlib("iupluacontrols51.dll", "iupcontrolslua_open");
+iupcontrolslua_open();
+
+local botVersion = 1; -- check this version when saving/loading. this will change whenever the botsave-file changes.
+
+-- callback function to clean up our mess
+function emu.OnClose.iuplua()
+ --iup.Message ("IupMessage", "OnClose!");
+ if(emu and emu.OnCloseIup ~= nil) then
+ emu.OnCloseIup();
+ end
+ iup.Close();
+end
+
+local handles = {}; -- this table should hold the handle to all dialogs created in lua
+local dialogs = 0; -- should be incremented PRIOR to creating a new dialog
+-- called by the onclose event
+function emu.OnCloseIup()
+ if (handles) then -- just in case the user was "smart" enough to clear this
+ local i = 1;
+ while (handles[i] ~= nil) do -- cycle through all handles, false handles are skipped, nil denotes the end
+ if (handles[i] and handles[i].destroy) then -- check for the existence of what we need
+ -- close this dialog
+ handles[i]:destroy();
+ handles[i] = nil;
+ end;
+ i = i + 1;
+ end;
+ end;
+end;
+
+function createTextareaTab(reftable, tmptable, token, tab, fun, val) -- specific one, at that :)
+ reftable[token] = iup.multiline{title="Contents",expand="YES", border="YES",value=val};
+ tmptable[token] = iup.vbox{iup.label{title="function "..fun.."()\n local result = no;"},reftable[token],iup.label{title=" return result;\nend;"}};
+ tmptable[token].tabtitle = tab;
+end;
+function createTextareaTab2(reftable, tmptable, token, fun, arg) -- specific one, at that :) this one generates no return values
+ reftable[token] = iup.multiline{title="Contents",expand="YES", border="YES",value=fun};
+ if (arg) then
+ tmptable[token] = iup.vbox{iup.label{title="function "..fun.."(wasOk) -- wasOk (boolean) is true when the attempt was ok\n"},reftable[token],iup.label{title="end;"}};
+ else
+ tmptable[token] = iup.vbox{iup.label{title="function "..fun.."()\n"},reftable[token],iup.label{title="end;"}};
+ end;
+ tmptable[token].tabtitle = fun;
+end;
+
+function createGUI(n)
+ -- this table will keep the references for easy and fast lookup. <STRID, itemobj>
+ local reftable = {};
+ -- this table we wont keep, it holds references to (mostly) cosmetic elements of the dialog
+ local tmptable = {};
+
+ -- ok, dont be intimidated by the next eight blocks of code. they basically all say the same!
+ -- every line creates an element for the gui and sets it up. every block is a tabbed pane and
+ -- they are all put into another tabbed pane themselves. all references are put into a table
+ -- paired with the tokens in the basicbot framework. this allows us to easily walk through
+ -- all the pairs of <tokens,references> and replace them in the file, uppon writing.
+
+ reftable.ROMNAME = iup.text{title="rom name", size="300x", value="something_or_the_other.rom"};
+ tmptable.ROMNAME = iup.hbox{iup.label{title="Rom name: ", size="50x"}, reftable.ROMNAME, iup.fill{}};
+ reftable.COMMENT = iup.text{title="comment", size="300x",value="a botscript for some game"};
+ tmptable.COMMENT = iup.hbox{iup.label{title="Comment: ", size="50x"}, reftable.COMMENT, iup.fill{}};
+ reftable.VERSION = iup.text{title="version", size="70x",value="1.00"};
+ tmptable.VERSION = iup.hbox{iup.label{title="Version: ", size="50x"}, reftable.VERSION, iup.fill{}};
+ tmptable.SAVE = iup.button{title="Save contents"};
+ -- the callback is set after the dialog is created. we need the references to all controls for saving to work :)
+ tmptable.LOAD = iup.button{title="Load contents"};
+ tmptable.WRITE = iup.button{title="Write bot script"};
+ general = iup.vbox{tmptable.ROMNAME,tmptable.COMMENT,tmptable.VERSION,iup.fill{size="5x",},tmptable.SAVE,iup.fill{size="5x",},tmptable.LOAD,iup.fill{size="5x",},tmptable.WRITE,iup.fill{}};
+ general.tabtitle = "General";
+
+ createTextareaTab(reftable, tmptable, "bA1", "A", "isPressedA1", "a1");
+ createTextareaTab(reftable, tmptable, "bB1", "B", "isPressedB1", "b1");
+ createTextareaTab(reftable, tmptable, "START1", "Start", "isPressedStart1", "start1");
+ createTextareaTab(reftable, tmptable, "SELECT1","Select", "isPressedSelect1", "select1");
+ createTextareaTab(reftable, tmptable, "UP1", "Up", "isPressedUp1", "up1");
+ createTextareaTab(reftable, tmptable, "DOWN1", "Down", "isPressedDown1", "down1");
+ createTextareaTab(reftable, tmptable, "LEFT1", "Left", "isPressedLeft1", "left1");
+ createTextareaTab(reftable, tmptable, "RIGHT1", "Right", "isPressedRight1", "right1");
+ tabs1 = iup.vbox{iup.tabs{tmptable.bA1,tmptable.bB1,tmptable.START1,tmptable.SELECT1,tmptable.UP1,tmptable.DOWN1,tmptable.LEFT1,tmptable.RIGHT1}};
+ tabs1.tabtitle = "Player 1";
+
+ createTextareaTab(reftable, tmptable, "bA2", "A", "isPressedA2", "a2");
+ createTextareaTab(reftable, tmptable, "bB2", "B", "isPressedB2", "b2");
+ createTextareaTab(reftable, tmptable, "START2", "Start", "isPressedStart2", "start2");
+ createTextareaTab(reftable, tmptable, "SELECT2","Select", "isPressedSelect2", "select2");
+ createTextareaTab(reftable, tmptable, "UP2", "Up", "isPressedUp2", "up2");
+ createTextareaTab(reftable, tmptable, "DOWN2", "Down", "isPressedDown2", "down2");
+ createTextareaTab(reftable, tmptable, "LEFT2", "Left", "isPressedLeft2", "left2");
+ createTextareaTab(reftable, tmptable, "RIGHT2", "Right", "isPressedRight2", "right2");
+ tabs2 = iup.vbox{iup.tabs{tmptable.bA2,tmptable.bB2,tmptable.START2,tmptable.SELECT2,tmptable.UP2,tmptable.DOWN2,tmptable.LEFT2,tmptable.RIGHT2}};
+ tabs2.tabtitle = "Player 2";
+
+ createTextareaTab(reftable, tmptable, "bA3", "A", "isPressedA3", "a3");
+ createTextareaTab(reftable, tmptable, "bB3", "B", "isPressedB3", "b3");
+ createTextareaTab(reftable, tmptable, "START3", "Start", "isPressedStart3", "start3");
+ createTextareaTab(reftable, tmptable, "SELECT3","Select", "isPressedSelect3", "select3");
+ createTextareaTab(reftable, tmptable, "UP3", "Up", "isPressedUp3", "up3");
+ createTextareaTab(reftable, tmptable, "DOWN3", "Down", "isPressedDown3", "down3");
+ createTextareaTab(reftable, tmptable, "LEFT3", "Left", "isPressedLeft3", "left3");
+ createTextareaTab(reftable, tmptable, "RIGHT3", "Right", "isPressedRight3", "right3");
+ tabs3 = iup.vbox{iup.tabs{tmptable.bA3,tmptable.bB3,tmptable.START3,tmptable.SELECT3,tmptable.UP3,tmptable.DOWN3,tmptable.LEFT3,tmptable.RIGHT3}};
+ tabs3.tabtitle = "Player 3";
+
+ createTextareaTab(reftable, tmptable, "bA4", "A", "isPressedA4", "a4");
+ createTextareaTab(reftable, tmptable, "bB4", "B", "isPressedB4", "b4");
+ createTextareaTab(reftable, tmptable, "START4", "Start", "isPressedStart4", "start4");
+ createTextareaTab(reftable, tmptable, "SELECT4","Select", "isPressedSelect4", "select4");
+ createTextareaTab(reftable, tmptable, "UP4", "Up", "isPressedUp4", "up4");
+ createTextareaTab(reftable, tmptable, "DOWN4", "Down", "isPressedDown4", "down4");
+ createTextareaTab(reftable, tmptable, "LEFT4", "Left", "isPressedLeft4", "left4");
+ createTextareaTab(reftable, tmptable, "RIGHT4", "Right", "isPressedRight4", "right4");
+ tabs4 = iup.vbox{iup.tabs{tmptable.bA4,tmptable.bB4,tmptable.START4,tmptable.SELECT4,tmptable.UP4,tmptable.DOWN4,tmptable.LEFT4,tmptable.RIGHT4}};
+ tabs4.tabtitle = "Player 4";
+
+ createTextareaTab2(reftable, tmptable, "ONSTART", "onStart", false);
+ createTextareaTab2(reftable, tmptable, "ONFINISH", "onFinish", false);
+ createTextareaTab2(reftable, tmptable, "ONSEGMENTSTART","onSegmentStart", false);
+ createTextareaTab2(reftable, tmptable, "ONSEGMENTEND", "onSegmentEnd", false);
+ createTextareaTab2(reftable, tmptable, "ONATTEMPTSTART","onAttemptStart", false);
+ createTextareaTab2(reftable, tmptable, "ONATTEMPTEND", "onAttemptEnd", true);
+ createTextareaTab2(reftable, tmptable, "ONINPUTSTART", "onInputStart", false);
+ createTextareaTab2(reftable, tmptable, "ONINPUTEND", "onInputEnd", false);
+ tabs5 = iup.vbox{iup.tabs{tmptable.ONSTART, tmptable.ONFINISH, tmptable.ONSEGMENTSTART, tmptable.ONSEGMENTEND, tmptable.ONATTEMPTSTART, tmptable.ONATTEMPTEND, tmptable.ONINPUTSTART, tmptable.ONINPUTEND}};
+ tabs5.tabtitle = "Events";
+
+ createTextareaTab(reftable, tmptable, "SCORE", "score", "getScore", "score");
+ createTextareaTab(reftable, tmptable, "TIE1", "tie1", "getTie1", "tie1");
+ createTextareaTab(reftable, tmptable, "TIE2", "tie2", "getTie2", "tie2");
+ createTextareaTab(reftable, tmptable, "TIE3", "tie3", "getTie3", "tie3");
+ createTextareaTab(reftable, tmptable, "TIE4", "tie4", "getTie4", "tie4");
+ tabs6 = iup.vbox{iup.tabs{tmptable.SCORE,tmptable.TIE1,tmptable.TIE2,tmptable.TIE3,tmptable.TIE4}};
+ tabs6.tabtitle = "Score";
+
+ createTextareaTab(reftable, tmptable, "ISRUNEND", "isRunEnd", "isRunEnd", "isRunEnd");
+ createTextareaTab(reftable, tmptable, "MUSTROLLBACK", "mustRollBack", "mustRollBack", "mustRollBack");
+ createTextareaTab(reftable, tmptable, "ISSEGMENTEND", "isSegmentEnd", "isSegmentEnd", "isSegmentEnd");
+ createTextareaTab(reftable, tmptable, "ISATTEMPTEND", "isAttemptEnd", "isAttemptEnd", "isAttemptEnd");
+ createTextareaTab(reftable, tmptable, "ISATTEMPTOK", "isAttemptOk", "isAttemptOk", "isAttemptOk");
+ tabs7 = iup.vbox{iup.tabs{tmptable.ISRUNEND,tmptable.MUSTROLLBACK,tmptable.ISSEGMENTEND,tmptable.ISATTEMPTEND,tmptable.ISATTEMPTOK}};
+ tabs7.tabtitle = "Selection";
+
+ playertabs = iup.tabs{general,tabs1,tabs2,tabs3,tabs4,tabs5,tabs6,tabs7,title};
+ handles[n] = iup.dialog{playertabs, title="Basic Bot Frontend", size="450x200"}
+ handles[n]:showxy(iup.CENTER, iup.CENTER)
+
+ -- now set the callback function for the save button. this will use all the references above.
+ -- these remain ok in the anonymous function by something called "closures". this means that
+ -- these variables, although local to the scope of the function, will remain their value in
+ -- the anonymous function. hence we can refer to them and fetch their contents, even though
+ -- you cant refer to them outside the context of the createGUI function.
+ tmptable.WRITE.action =
+ function(self, n)
+ local file = iup.filedlg{allownew="YES",dialogtype="SAVE",directory="./lua",showhidden="YES",title="Save botfile"};
+ file:popup(iup.ANYWHERE,iup.ANYWHERE);
+
+ if (file.value == "NULL") then
+ iup.Message("An error occurred trying to save your settings");
+ return;
+ elseif (file.status == "-1") then
+ iup.Message("IupFileDlg","Operation canceled");
+ return;
+ end
+
+ -- ok, file selected, if an error occurred or user canceled, the function already returned, so lets write the bot!
+
+ -- get the framework first. we need it to find the relevant tokens
+ local fh = assert(io.open("basicbot_framework.lua","r"));
+ local framework = fh:read("*a");
+ fh:close();
+
+ -- now replace all tokens by gui values
+ -- this is where the reftable comes in very handy :p
+ for token,obj in pairs(reftable) do
+ local st00pid = (reftable[token].value or "");
+ framework = string.gsub(framework, "-- "..token, st00pid, 1); -- if nothing was entered, obj.value returns nil (not ""), so we have to make that translation
+ end;
+
+ -- open the file, if old file, clear it
+ if (file.status == "1") then
+ fh = assert(io.open(file.value,"wb"));
+ else -- (file.status == "0")
+ fh = assert(io.open(file.value,"w+b")); -- clear file contents
+ end;
+
+ -- write it
+ fh:write(framework);
+
+ -- close it (automatically flushed)
+ fh:close();
+ fh = nil;
+
+ iup.Message ("Success", "Bot written to "..file.value.."!");
+ end;
+ tmptable.SAVE.action =
+ function(self, n)
+ local file = iup.filedlg{allownew="YES",dialogtype="SAVE",directory="./lua",showhidden="YES",title="Save botfile",extfilter="BasicBot (*.bot)|*.bot|All files (*.*)|*.*|"};
+ file:popup(iup.ANYWHERE,iup.ANYWHERE);
+
+ if (file.status == 1) then -- cancel
+ return;
+ end;
+
+ -- open the file, if old file, clear it
+ if (file.status == "1") then
+ fh = assert(io.open(file.value,"wb"));
+ else -- (file.status == "0")
+ fh = assert(io.open(file.value,"w+b")); -- clear file contents
+ end;
+
+ -- allow us to detect the botfile version (warn the user if it's different?)
+ fh:write(botVersion.."\n");
+
+ -- now replace all tokens by gui values
+ -- this is where the reftable comes in very handy :p
+ for token,obj in pairs(reftable) do
+ print("------");
+ print(token.." control -> "..tostring(obj));
+ print(".value: "..tostring(obj.value));
+ local st00pid = obj.value;
+ if (not st00pid) then st00pid = ""; end;
+ print(string.len(st00pid));
+ fh:write(string.len(st00pid).."\n");
+ if (string.len(st00pid) > 0) then fh:write(st00pid); end;
+ fh:write("\n");
+ end;
+
+ fh:close();
+ iup.Message ("Success", "Settings saved!");
+ end;
+ tmptable.LOAD.action =
+ function (self, n)
+ -- this function currently crashes fceux without notification
+ -- possibly because offsets are badly calculated, but serves as an example now
+ local file = iup.filedlg{allownew="NO",dialogtype="OPEN",directory="./lua",showhidden="YES",title="Save botfile",extfilter="BasicBot (*.bot)|*.bot|All files (*.*)|*.*|"};
+ file:popup(iup.ANYWHERE,iup.ANYWHERE);
+ if (file.status == 1) then -- cancel
+ return;
+ end;
+ local nellen = string.len("\n"); -- platform independent
+ fh = assert(io.open(file.value,"r"));
+ print("seek: "..fh:seek());
+ print("version: "..fh:read("*l"));
+ print("seek: "..fh:seek());
+ if (true) then return; end;
+ for token,crap in pairs(reftable) do
+ local len = fh:read("*n"); -- read line (length)
+ if (not len) then break; end;
+ fh:seek("set",fh:seek("cur", nellen)); -- remove the aesthetic return
+ print(len);
+ reftable[token].value = fh:read(len);
+ fh:seek("set",fh:seek("cur", nellen)); -- remove the aesthetic return
+ end;
+ end;
+end;
+
+dialogs = dialogs + 1;
+createGUI(dialogs);
+while (true) do
+ FCEU.frameadvance();
+end; \ No newline at end of file