Find the exits by moving the characters and camera in a grid pattern

This commit is contained in:
Empathic Qubit 2021-05-03 03:23:04 -04:00
parent 529f5dc67a
commit 52bdebfc81
10 changed files with 365 additions and 185 deletions

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
*.lsmv
!pool/*.lsmv
*.log
catchem/
state/

View file

@ -54,6 +54,7 @@ Located at [tools/bsnes-launcher.lua](tools/bsnes-launcher.lua), this script giv
* [Serpent](https://github.com/pkulchenko/serpent)
* [LibDeflate](https://github.com/SafeteeWoW/LibDeflate)
* [watchexec](https://github.com/watchexec/watchexec/blob/main/LICENSE)
* [Billiam's Promise library](https://github.com/Billiam/promise.lua)
## TODO

171
game.lua
View file

@ -1,12 +1,14 @@
--Notes here
local memory, bit, memory2, input = memory, bit, memory2, input
local memory, bit, memory2, input, callback, movie = memory, bit, memory2, input, callback, movie
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
local Promise = nil
local util = nil
local mathFunctions = dofile(base.."/mathFunctions.lua")
local config = dofile(base.."/config.lua")
local spritelist = dofile(base.."/spritelist.lua")
local util = dofile(base.."/util.lua")()
local mem = dofile(base.."/mem.lua")
local _M = {
leader = 0,
@ -39,6 +41,27 @@ function _M.getPositions()
_M.screenY = (_M.partyY-256-_M.cameraY)*2
end
function _M.setPartyPosition(x, y)
memory.writeword(mem.addr.partyX, x)
memory.writeword(mem.addr.partyY, y)
_M.setSpritePosition(0, x, y)
_M.setSpritePosition(1, x, y)
end
function _M.setCameraPosition(x, y)
memory.writeword(mem.addr.cameraX, x)
memory.writeword(mem.addr.cameraY, y)
memory.writeword(mem.addr.cameraX2, x)
memory.writeword(mem.addr.cameraY2, y)
end
function _M.setSpritePosition(index, x, y)
local offsets = mem.offset.sprite
local spriteBase = mem.addr.spriteBase + index * mem.size.sprite
memory.writeword(spriteBase + offsets.x, x)
memory.writeword(spriteBase + offsets.y, y)
end
function _M.getBananas()
local bananas = memory.readword(0x7e08bc)
return bananas
@ -54,6 +77,126 @@ function _M.getKremCoins()
return krem
end
function _M.getAreaWidth()
return memory.readword(mem.addr.areaWidth) + 256
end
function _M.getAreaHeight()
return memory.readword(mem.addr.areaHeight)
end
function _M.getAreaLength()
return memory.readword(mem.addr.areaLength)
end
local onFrameAdvancedQueue = {}
function _M.advanceFrame()
local promise = Promise.new()
table.insert(onFrameAdvancedQueue, promise)
return promise
end
local function processFrameAdvanced()
for i=#onFrameAdvancedQueue,1,-1 do
table.remove(onFrameAdvancedQueue, i):resolve()
end
end
local onSetRewindQueue = {}
function _M.setRewindPoint()
local promise = Promise.new()
table.insert(onSetRewindQueue, promise)
movie.unsafe_rewind()
return promise
end
local function processSetRewind(state)
for i=#onSetRewindQueue,1,-1 do
table.remove(onSetRewindQueue, i):resolve(state)
end
end
local onRewindQueue = {}
function _M.rewind(rew)
local promise = Promise.new()
movie.unsafe_rewind(rew)
table.insert(onRewindQueue, promise)
return promise
end
local function processRewind()
for i=#onRewindQueue,1,-1 do
local promise = table.remove(onRewindQueue, i)
promise:resolve()
end
end
local function findPreferredExitLoop(frame, searchX, searchY, found, uniqueExits)
return _M.advanceFrame():next(function()
frame = frame + 1
if frame % 2 ~=0 then
return
end
local areaWidth = _M.getAreaWidth()
memory.writebyte(0x7e19ce, 0x16)
memory.writebyte(0x7e0e12, 0x99)
memory.writebyte(0x7e0e70, 0x99)
local sprites = _M.getSprites()
for i=1,#sprites,1 do
local sprite = sprites[i]
local name = spritelist.SpriteNames[sprite.control]
if sprite.control == spritelist.GoodSprites.goalTarget or
sprite.control == spritelist.GoodSprites.areaExit then
found = true
uniqueExits[sprite.y * areaWidth + sprite.x] = sprite
end
end
_M.setPartyPosition(searchX, searchY)
_M.setCameraPosition(searchX, searchY)
searchX = searchX + 0x100
if searchX > areaWidth then
searchX = 0
searchY = searchY + 0xe0
if searchY > _M.getAreaHeight() then
table.sort(uniqueExits, function(a, b)
return a.control < b.control
end)
-- Return upper right corner if we can't find anything
if found then
for id,sprite in pairs(uniqueExits) do
return { x = sprite.x, y = sprite.y }
end
else
return { x = areaWidth, y = 0}
end
end
end
end):next(function(ret)
if ret == nil then
return findPreferredExitLoop(frame, searchX, searchY, found, uniqueExits)
else
return ret
end
end)
end
function _M.findPreferredExit()
local point = nil
local result = nil
return _M.setRewindPoint():next(function(p)
point = p
return findPreferredExitLoop(0, 0, 0, false, {})
end):next(function(r)
result = r
return _M.rewind(point)
end):next(function()
return result
end)
end
function _M.getGoalHit()
local sprites = _M.getSprites()
for i=1,#sprites,1 do
@ -454,7 +597,16 @@ local function registerHandler(space, regname, addr, callback)
})
end
local inputHandler = nil
local setRewindHandler = nil
local rewindHandler = nil
function _M.unregisterHandlers()
callback.unregister('input', inputHandler)
callback.unregister('set_rewind', setRewindHandler)
callback.unregister('post_rewind', rewindHandler)
inputHandler = nil
setRewindHandler = nil
rewindHandler = nil
for i=#handlers,1,-1 do
local handler = table.remove(handlers, i)
handler.unregisterFn(handler.space, handler.addr, handler.fn)
@ -462,6 +614,13 @@ function _M.unregisterHandlers()
end
function _M.registerHandlers()
if inputHandler ~= nil then
error("Only call register handlers once")
end
inputHandler = callback.register('input', processFrameAdvanced)
setRewindHandler = callback.register('set_rewind', processSetRewind)
rewindHandler = callback.register('post_rewind', processRewind)
registerHandler(memory2.BUS, 'registerwrite', 0xb517b2, processAreaLoad)
registerHandler(memory2.WRAM, 'registerread', 0x06b1, processMapLoad)
for i=2,22,1 do
@ -469,4 +628,10 @@ function _M.registerHandlers()
end
end
return _M
return function(promise)
Promise = promise
if util == nil then
util = dofile(base.."/util.lua")(Promise)
end
return _M
end

View file

@ -6,8 +6,16 @@ local _M = {
verticalPointer = 0xc414,
tiledataPointer = 0x7e0098,
haveBoth = 0x7e08c2,
---Height in game units for vertical levels, width for horizontal
areaLength = 0x7e17b4,
---This is always the traditional width no matter the level type
areaWidth = 0x7e0afc,
---This is always the traditional height no matter the level type
areaHeight = 0x7e0afe,
cameraX = 0x7e17ba,
cameraY = 0x7e17c0,
cameraX2 = 0x7e0ad7,
cameraY2 = 0x7e0adb,
leadChar = 0x7e08a4,
partyX = 0x7e0a2a,
partyY = 0x7e0a2c,

View file

@ -169,5 +169,6 @@ outFile:close()
print(string.format('Wrote init to output at %d', ts))
waiter:next(waitLoop):catch(function(error)
print('ERROR: '..error)
print('Runner process error: '..error)
io.stderr:write('Runner process error: '..error..'\n')
end)

View file

@ -102,7 +102,9 @@ end
return function(promise)
-- FIXME Should this be a global???
Promise = promise
util = dofile(base.."/util.lua")(Promise)
if util == nil then
util = dofile(base.."/util.lua")(Promise)
end
-- FIXME Maybe don't do this in the "constructor"?
if util.isWin then
util.downloadFile('https://github.com/watchexec/watchexec/releases/download/1.13.1/watchexec-1.13.1-x86_64-pc-windows-gnu.zip', base..'/watchexec.zip')

View file

@ -5,7 +5,7 @@ local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
local Promise = nil
local config = dofile(base.."/config.lua")
local game = dofile(base.."/game.lua")
local game = nil
local mathFunctions = dofile(base.."/mathFunctions.lua")
local util = dofile(base.."/util.lua")()
@ -216,18 +216,6 @@ local function displayGenome(genome)
gui.renderctx.setnull()
end
local function advanceFrame(_M)
local promise = Promise.new()
table.insert(_M.onFrameAdvancedHandler, promise)
return promise
end
local function processFrameAdvanced(_M)
for i=#_M.onFrameAdvancedHandler,1,-1 do
table.remove(_M.onFrameAdvancedHandler, i):resolve()
end
end
local buttons = nil
local buttonCtx = gui.renderctx.new(500, 70)
local function displayButtons(_M)
@ -287,9 +275,10 @@ local function displayForm(_M)
gui.rectangle(0, 0, 500, guiHeight, 1, 0x00ffffff, 0xbb000000)
--gui.circle(game.screenX-84, game.screenY-84, 192 / 2, 1, 0x50000000)
local rightmost = _M.rightmost[_M.currentArea]
if rightmost == nil then
rightmost = 0
local areaInfo = _M.areaInfo[_M.currentArea]
local distanceTraversed = 0
if areaInfo ~= nil then
distanceTraversed = areaInfo.startDistance - areaInfo.shortest
end
gui.text(5, 30, "Timeout: " .. _M.timeout)
@ -307,7 +296,7 @@ local function displayForm(_M)
gui.text(230, 65, "Damage: " .. _M.partyHitCounter)
gui.text(230, 80, "PowerUp: " .. _M.powerUpCounter)
gui.text(320, 65, string.format("Current Area: %04x", _M.currentArea))
gui.text(320, 80, "Rightmost: "..rightmost)
gui.text(320, 80, string.format("Traveled: %d", distanceTraversed))
displayButtons(_M)
formCtx:set()
@ -417,24 +406,6 @@ local function fitnessAlreadyMeasured(_M)
return genome.fitness ~= 0
end
local rewinds = {}
local rew = movie.to_rewind(config.NeatConfig.Filename)
local function rewind()
local promise = Promise.new()
movie.unsafe_rewind(rew)
table.insert(rewinds, promise)
return promise
end
local function rewound()
frame = 0
lastFrame = 0
for i=#rewinds,1,-1 do
local promise = table.remove(rewinds, i)
promise:resolve()
end
end
local function newNeuron()
local neuron = {}
neuron.incoming = {}
@ -481,6 +452,13 @@ local function generateNetwork(genome)
genome.network = network
end
local rew = movie.to_rewind(config.NeatConfig.Filename)
local function rewind()
return game.rewind(rew):next(function()
frame = 0
lastFrame = 0
end)
end
local function initializeRun(_M)
settings.set_speed("turbo")
@ -493,7 +471,7 @@ local function initializeRun(_M)
exec('enable-sound '..enableSound)
gui.subframe_update(false)
return rewind():next(function()
return rewind():next(function(preferredExit)
if config.StartPowerup ~= nil then
game.writePowerup(config.StartPowerup)
end
@ -520,18 +498,49 @@ local function initializeRun(_M)
_M.powerUpBefore = game.getBoth()
_M.currentArea = game.getCurrentArea()
_M.lastArea = _M.currentArea
_M.rightmost = { [_M.currentArea] = 0 }
_M.upmost = { [_M.currentArea] = 0 }
for areaId,areaInfo in pairs(_M.areaInfo) do
areaInfo.shortest = areaInfo.startDistance
end
local genome = _M.currentSpecies.genomes[_M.currentGenomeIndex]
generateNetwork(genome)
evaluateCurrent(_M)
end)
end
local function getDistanceTraversed(areaInfo)
local distanceTraversed = 0
for areaId,areaInfo in pairs(areaInfo) do
distanceTraversed = areaInfo.startDistance - areaInfo.shortest
end
return distanceTraversed
end
local function mainLoop(_M, genome)
return advanceFrame(_M):next(function()
return game.advanceFrame():next(function()
local nextArea = game.getCurrentArea()
if nextArea ~= _M.lastArea then
_M.lastArea = nextArea
game.onceAreaLoaded(function()
_M.timeout = _M.timeout + 60 * 5
_M.currentArea = nextArea
_M.lastArea = _M.currentArea
end)
elseif _M.currentArea == _M.lastArea and _M.areaInfo[_M.currentArea] == nil then
message(_M, 'Searching for the main exit in this area')
return game.findPreferredExit():next(function(preferredExit)
local startDistance = math.floor(math.sqrt((preferredExit.y - game.partyY) ^ 2 + (preferredExit.x - game.partyX) ^ 2))
_M.areaInfo[_M.currentArea] = {
startDistance = startDistance,
preferredExit = preferredExit,
shortest = startDistance,
}
end)
end
end):next(function()
if lastFrame + 1 ~= frame then
message(_M, string.format("We missed %d frames", frame - lastFrame), 0x00990000)
message(_M, string.format("We missed %d frames", frame - lastFrame), 0x00ff0000)
end
lastFrame = frame
@ -566,37 +575,16 @@ local function mainLoop(_M, genome)
end
end
local nextArea = game.getCurrentArea()
if nextArea ~= _M.lastArea then
_M.lastArea = nextArea
game.onceAreaLoaded(function()
_M.timeout = _M.timeout + 60 * 5
_M.currentArea = nextArea
_M.lastArea = _M.currentArea
if _M.rightmost[_M.currentArea] == nil then
_M.rightmost[_M.currentArea] = 0
_M.upmost[_M.currentArea] = 0
end
end)
end
if not game.vertical then
if game.partyX > _M.rightmost[_M.currentArea] then
_M.rightmost[_M.currentArea] = game.partyX
if _M.timeout < timeoutConst then
_M.timeout = timeoutConst
end
end
else
if game.partyY > _M.upmost[_M.currentArea] then
_M.upmost[_M.currentArea] = game.partyY
local areaInfo = _M.areaInfo[_M.currentArea]
if areaInfo ~= nil then
local exitDist = math.floor(math.sqrt((areaInfo.preferredExit.y - game.partyY) ^ 2 + (areaInfo.preferredExit.x - game.partyX) ^ 2))
if exitDist < areaInfo.shortest then
areaInfo.shortest = exitDist
if _M.timeout < timeoutConst then
_M.timeout = timeoutConst
end
end
end
-- FIXME Measure distance to target / area exit
-- We might not always be horizontal
local hitTimer = game.getHitTimer(_M.lastBoth)
@ -651,20 +639,9 @@ local function mainLoop(_M, genome)
local bumpPenalty = _M.bumps * 100
local powerUpBonus = _M.powerUpCounter * 100
local most = 0
if not game.vertical then
for k,v in pairs(_M.rightmost) do
most = most + v
end
most = most - _M.currentFrame / 2
else
for k,v in pairs(_M.upmost) do
most = most + v
end
most = most - _M.currentFrame / 2
end
local distanceTraversed = getDistanceTraversed(_M.areaInfo) - _M.currentFrame / 2
local fitness = bananaCoinsFitness - bumpPenalty - hitPenalty + powerUpBonus + most + game.getJumpHeight() / 100
local fitness = bananaCoinsFitness - bumpPenalty - hitPenalty + powerUpBonus + distanceTraversed + game.getJumpHeight() / 100
local lives = game.getLives()
@ -855,13 +832,11 @@ local function run(_M, species, generationIdx, genomeCallback)
register(_M, 'input', function()
frame = frame + 1
updateController()
processFrameAdvanced(_M)
saveLoadInput(_M)
end)
register(_M, 'keyhook', function(key, state)
keyhook(_M, key, state)
end)
register(_M, 'post_rewind', rewound)
input.keyhook("1", true)
input.keyhook("4", true)
@ -885,6 +860,9 @@ end
return function(promise)
Promise = promise
if game == nil then
game = dofile(base.."/game.lua")(Promise)
end
local _M = {
currentGenerationIndex = 1,
currentSpecies = nil,
@ -912,16 +890,13 @@ return function(promise)
powerUpBefore = 0,
currentArea = 0,
lastArea = 0,
rightmost = {},
upmost = {},
areaInfo = {},
lastBoth = 0,
onMessageHandler = {},
onSaveHandler = {},
onLoadHandler = {},
onRenderFormHandler = {},
onFrameAdvancedHandler = {},
}
_M.onRenderForm = function(handler)

View file

@ -4,100 +4,114 @@ _M.Sprites = {}
-- Make sure this list is sorted before initialization.
_M.NeutralSprites = {
0x0020, -- Krow egg fragments
0x0060, -- Barrel fragments
0x0064, -- Barrel fragments
krowEggFragments = 0x0020,
barrelFragments = 0x0060,
barrelFragments2 = 0x0064,
-- Our heroes
0x00e4, -- Diddy
0x00e8, -- Dixie
0x0100, -- Stars
diddy = 0x00e4,
dixie = 0x00e8,
stars = 0x0100,
-- Items that require too much interaction
0x01a4, -- Barrel
0x01b0, -- Cannonball (immobile)
0x01c0, -- Chest
0x01bc, -- Small crate
0x011c, -- Barrel
0x013c, -- Cannon
0x014c, -- Hook
0x01b8, -- TNT
barrel = 0x01a4, -- Barrel
cannonball = 0x01b0,
chest = 0x01c0,
smallCrate = 0x01bc,
barrel2 = 0x011c,
cannon = 0x013c,
hook = 0x014c,
tnt = 0x01b8,
-- Inert
0x0168, -- Goal pole
0x016c, -- Goal roulette
0x0160, -- Goal base
0x0164, -- Goal barrel
goalPole = 0x0168,
goalroulette = 0x016c,
goalBase = 0x0160,
goalBarrel = 0x0164,
0x0238, -- Pow
0x023c, -- Exploding crate
0x0258, -- No Animals Sign
pow = 0x0238,
explodingCrate = 0x023c,
noAnimalsSign = 0x0258,
}
-- Make sure this list is sorted before initialization.
_M.GoodSprites = {
-- Destinations
0x0094, -- Area exit
0x00b0, -- Goal target
areaExit = 0x0094,
goalTarget = 0x00b0,
0x0120, -- Bonus barrel
0x0128, -- Hot air balloon
0x0140, -- Launch barrel
0x0148, -- Animal crate
0x0150, -- Invincibility barrel
0x0154, -- Midpoint
0x015c, -- Banana Coin/Kremkoin/DK Coin
0x0170, -- Banana bunch
0x0174, -- KONG letters
0x0178, -- xUP balloon
bonusBarrel = 0x0120,
hotAirBalloon = 0x0128,
launchBarrel = 0x0140,
animalCrate = 0x0148,
invincibilityBarrel = 0x0150,
midpoint = 0x0154,
allCoins = 0x015c, -- Banana Coin/Kremkoin/DK Coin
bananaBunch = 0x0170,
kongLetter = 0x0174,
upBalloon = 0x0178, -- xUP balloon
-- Animals
0x0190, -- Squitter
0x0194, -- Rattly
0x0198, -- Squawks
0x019c, -- Rambi
0x0304, -- Clapper
squitter = 0x0190,
rattly = 0x0194,
squawks = 0x0198,
rambi = 0x019c,
clapper = 0x0304,
0x01a8, -- DK Barrel label
dkBarrelLabel = 0x01a8,
0x01b4, -- Krow's eggs
krowEgg = 0x01b4,
0x0220, -- Flitter (used as unavoidable platforms in some levels)
0x02d4, -- Krochead (red and green)
flitter = 0x0220,
krocheadAllColors = 0x02d4,
}
-- Currently not used.
_M.BadSprites = {
-- Baddies
0x006c, -- Kannon
0x01ac, -- Klobber (yellow and green)
0x01d0, -- Kannon's fodder (Ball/barrel)
0x01d8, -- Krusha
0x01dc, -- Click-Clack
0x01e4, -- Neek
0x01ec, -- Klomp
0x01e8, -- Klobber (awake)
0x01f0, -- Klampon
0x01f8, -- Flotsam
0x0200, -- Klinger
0x0208, -- Puftup
0x0218, -- Zinger (red and yellow)
0x0214, -- Mini-Necky
0x020c, -- Lockjaw
0x021c, -- Kaboing
0x0224, -- Krow (Boss)
0x025c, -- Krook (very large)
kannon = 0x006c,
klobberAllColors = 0x01ac,
kannonFodder = 0x01d0,
krusha = 0x01d8,
clickClack = 0x01dc,
neek = 0x01e4,
klomp = 0x01ec,
klobberAwake = 0x01e8,
klampon = 0x01f0,
flotsam = 0x01f8,
klinger = 0x0200,
puftup = 0x0208,
zingerAllColors = 0x0218,
miniNecky = 0x0214,
lockjaw = 0x020c,
kaboing = 0x021c,
krow = 0x0224, -- Boss
krook = 0x025c,
}
_M.SpriteNames = {}
function _M.InitSpriteNames()
for v,k in pairs(_M.GoodSprites) do
_M.SpriteNames[k] = v
end
for v,k in pairs(_M.BadSprites) do
_M.SpriteNames[k] = v
end
for v,k in pairs(_M.NeutralSprites) do
_M.SpriteNames[k] = v
end
end
function _M.InitSpriteList()
for i=1,#_M.GoodSprites,1 do
_M.Sprites[_M.GoodSprites[i]] = 1
for k,v in pairs(_M.GoodSprites) do
_M.extSprites[v] = 1
end
for i=1,#_M.BadSprites,1 do
_M.Sprites[_M.BadSprites[i]] = -1
for k,v in pairs(_M.BadSprites) do
_M.extSprites[v] = -1
end
for i=1,#_M.NeutralSprites,1 do
_M.Sprites[_M.NeutralSprites[i]] = 0
for k,v in pairs(_M.NeutralSprites) do
_M.extSprites[v] = 0
end
end
@ -108,21 +122,21 @@ _M.ExtNeutralSprites = {
}
_M.ExtGoodSprites = {
0xe0, -- banana
0xe1, -- banana
0xe2, -- banana
0xe3, -- banana
0xe4, -- banana
0xe5, -- banana
0xe6, -- banana
0xe7, -- banana
0xe8, -- banana
0xe9, -- banana
0xea, -- banana
0xeb, -- banana
0xec, -- banana
0xed, -- banana
0xee, -- banana
banana01 = 0xe0, -- banana
banana02 = 0xe1, -- banana
banana03 = 0xe2, -- banana
banana04 = 0xe3, -- banana
banana05 = 0xe4, -- banana
banana06 = 0xe5, -- banana
banana07 = 0xe6, -- banana
banana08 = 0xe7, -- banana
banana09 = 0xe8, -- banana
banana10 = 0xe9, -- banana
banana11 = 0xea, -- banana
banana12 = 0xeb, -- banana
banana13 = 0xec, -- banana
banana14 = 0xed, -- banana
banana15 = 0xee, -- banana
}
-- Currently not used.
@ -130,14 +144,14 @@ _M.ExtBadSprites = {
}
function _M.InitExtSpriteList()
for i=1,#_M.ExtGoodSprites,1 do
_M.extSprites[_M.ExtGoodSprites[i]] = 1
for k,v in pairs(_M.ExtGoodSprites) do
_M.extSprites[v] = 1
end
for i=1,#_M.ExtBadSprites,1 do
_M.extSprites[_M.ExtBadSprites[i]] = -1
for k,v in pairs(_M.ExtBadSprites) do
_M.extSprites[v] = -1
end
for i=1,#_M.ExtNeutralSprites,1 do
_M.extSprites[_M.ExtNeutralSprites[i]] = 0
for k,v in pairs(_M.ExtNeutralSprites) do
_M.extSprites[v] = 0
end
end

View file

@ -1,6 +1,18 @@
print(string.hex(bit.compose(0xef, 0xbe)))
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
local game = dofile(base.."/game.lua")
local memory, movie, utime, callback, set_timer_timeout = memory, movie, utime, callback, set_timer_timeout
function on_input()
end
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
local Promise = dofile(base.."/promise.lua")
callback.register('timer', function()
Promise.update()
set_timer_timeout(1)
end)
set_timer_timeout(1)
local game = dofile(base.."/game.lua")(Promise)
local util = dofile(base.."/util.lua")(Promise)
game.registerHandlers()
game.findPreferredExit():next(function(exit)
io.stderr:write(util.table_to_string(exit))
io.stderr:write('\n')
end)

View file

@ -9,7 +9,7 @@ print(warn)
local util = dofile(base.."/util.lua")()
local mem = dofile(base.."/mem.lua")
local spritelist = dofile(base.."/spritelist.lua")
local game = dofile(base.."/game.lua")
local game = dofile(base.."/game.lua")()
local config = dofile(base.."/config.lua")
spritelist.InitSpriteList()