Skip to content

Instantly share code, notes, and snippets.

@adituv
Last active October 17, 2015 15:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adituv/6bcc89d7d125e604b0b8 to your computer and use it in GitHub Desktop.
Save adituv/6bcc89d7d125e604b0b8 to your computer and use it in GitHub Desktop.
Denki Blocks Lua
--[[
Denki Blocks - BizHawk Lua Overlay
Version 1.0.0
Contributions by AdituV
You may use, edit, and redistribute this file under the
condition that this notice is left intact, and attribution
is provided for all contributions as above.
]]
--#REGION Automatic Solution Entry
function Queue()
local self = {};
local first = 0;
local last = -1;
function self.empty()
return first > last;
end
function self.push(value)
last = last+1;
self[last] = value;
end
function self.pop()
if self.empty() then
return nil;
end
local val = self[first];
self[first] = nil;
first = first+1;
return val;
end
return self;
end;
function createSolutionForm()
local frm = forms.newform(400,115,"Enter Puzzle Solution");
forms.label(frm,"Soln:",10,15,33,25);
local txt = forms.textbox(frm,"",325,25,null,45,12);
local clickHandler = function()
-- Solution passed back as a "message" via the global variable
-- so that the main lua thread can actually input the solution.
-- Attempting to do it from here will cause lua to stop working.
solution = parseSolution(forms.gettext(txt));
forms.destroy(frm);
end;
forms.button(frm,"Ok",clickHandler,160,40,80,25);
end
function parseSolution(str)
local parts = bizstring.split(str," ");
local result = Queue();
-- bizstring.split up until 1.11.1 is zero-indexed rather than
-- one-indexed, so kludge if the 0 key exists
if parts[0] ~= nil then
pushDirection(parts[0],result);
end
for _,v in ipairs(parts) do
pushDirection(v,result);
end
return result;
end
function pushDirection(v,queue)
local k = v:sub(1,1);
local n = tonumber(v:sub(2));
local lastDir = nil;
if k == "R" then
queue.push("Right");
lastDir = "Right";
elseif k == "L" then
queue.push("Left");
lastDir = "Left";
elseif k == "U" then
queue.push("Up");
lastDir = "Up";
elseif k == "D" then
queue.push("Down");
lastDir = "Down";
end
if n ~= nil and lastDir ~= nil then
for ix = 2,n do
queue.push(lastDir);
end
end
end
function inputSolution(soln)
local solnStart = nil;
while soln.empty() == false do
if canMove() then
if solnStart == nil then solnStart = emu.framecount(); end
local dir = soln.pop();
joypad.set({[dir]=true});
emu.frameadvance();
joypad.set({[dir]=false});
end
emu.frameadvance();
end
end
--#ENDREGION
--#REGION GUI Info Icons
indicatorStart = 130;
function drawIndicator(text,p)
local labelx = 200;
local iconx = 230;
local color;
if p then
color = "green";
else
color = "red";
end
gui.drawText(labelx,indicatorStart-2,text,"white",10);
gui.drawEllipse(iconx,indicatorStart,9,9,"black",color);
indicatorStart = indicatorStart + 10;
end
function canMove()
local moving = mainmemory.readbyte(0x5484);
local joining = mainmemory.readbyte(0x6A88);
return moving == 0 and joining == 0;
end
function canMenu()
local canmenu = mainmemory.readbyte(0x414C);
return canmenu == 1;
end
function canAdvText()
local canAdv = mainmemory.readbyte(0x4B50);
return canAdv == 1;
end
function updateGUI()
gui.drawRectangle(198,129,42,31,"black","black");
indicatorStart = 130;
drawIndicator("Menu",canMenu());
drawIndicator("Move",canMove());
drawIndicator("Text",canAdvText());
end
event.onframeend(updateGUI);
--#ENDREGION
function waitFor(p)
if not p() then
client.unpause();
while not p() do
emu.frameadvance();
end
client.pause();
end
end
solution = nil;
prevKeys = {};
while true do
local keys = input.get();
local button = "None";
local triggers = {
-- Input solution form
["Ctrl+M"] = function ()
client.pause();
createSolutionForm();
end,
-- Frame-perfect manual input
["I"] = function () button = "Up"; end,
["J"] = function () button = "Left"; end,
["K"] = function () button = "Down"; end,
["L"] = function () button = "Right"; end,
-- Wait until next move available. At end of puzzle, wait until back at menu
["Period"] = function () waitFor(canMove) end,
-- After selecting a puzzle, waits until you can control the new puzzle
["Semicolon"] = function ()
waitFor(function() return (not canMove()) end);
waitFor(canMove);
end,
-- Wait until you can control the menu cursor
["N"] = function () waitFor(canMenu) end,
};
for k,v in pairs(triggers) do
if prevKeys[k] and not keys[k] then
v()
break
end
end
if solution ~= nil then
client.unpause();
inputSolution(solution);
solution = nil;
client.pause();
elseif button ~= "None" then
waitFor(canMove);
client.unpause();
joypad.set({[button] = true});
emu.frameadvance();
waitFor(canMove);
end
prevKeys = keys;
emu.yield();
end
@adituv
Copy link
Author

adituv commented Sep 9, 2015

Current to-do list: none

Possible future: auto-menuing; auto-dialog to control nearly the entire game from lua? Reset macro. Optimal reset frame search? A better menu-available indicator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment