Last active
October 17, 2015 15:33
-
-
Save adituv/6bcc89d7d125e604b0b8 to your computer and use it in GitHub Desktop.
Denki Blocks Lua
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.