Algorithm works kind of. It figures out how to go forward

and reset when dying. GUI is messed up. Enemies not yet
added to inputs.
This commit is contained in:
Empathic Qubit 2021-03-05 10:10:05 -05:00
parent 210d625e2c
commit 34b4528953
7 changed files with 557 additions and 350 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ catchem/
state/
crashsave*
*.backup
*.pool

View file

@ -248,7 +248,7 @@ Sprite Details:
local partyX = memory.readword(PARTY_X)
local partyY = memory.readword(PARTY_Y)
local partyTileOffset = tile_offset_calculation(partyX, partyY, vertical)
local partyTileOffset = tileOffsetCalculation(partyX, partyY, vertical)
local stats = string.format([[
%s camera %d,%d
@ -310,11 +310,11 @@ Tile offset: %04x
local tileX = math.floor((partyX + x * TILE_SIZE) / TILE_SIZE) * TILE_SIZE
local tileY = math.floor((partyY + y * TILE_SIZE) / TILE_SIZE) * TILE_SIZE
local offset = tile_offset_calculation(tileX, tileY, vertical)
local offset = tileOffsetCalculation(tileX, tileY, vertical)
local tile = memory.readword(tilePtr + offset)
if not tile_is_solid(tileX, tileY, tile, offset) then
if not tileIsSolid(tileX, tileY, tile, offset) then
goto continue
end
@ -325,7 +325,7 @@ Tile offset: %04x
--goto continue
end
gui.text(screenX, screenY, string.format("%04x\n%02x", offset & 0xffff, tile), FG_COLOR, 0x66888800)
gui.text(screenX, screenY, string.format("%04x\n%02x", bit.band(offset, 0xffff), tile), FG_COLOR, 0x66888800)
::continue::
end
@ -335,9 +335,9 @@ Tile offset: %04x
local oam = memory2.OAM:readregion(0x00, 0x220)
for idx=0,0x200/4-1,1 do
local twoBits = (oam[0x201 + math.floor(idx / 4)] >> ((idx % 4) * 2)) & 0x03
local twoBits = bit.band(bit.lrshift(oam[0x201 + math.floor(idx / 4)], ((idx % 4) * 2)), 0x03)
local screenSprite = {
x = math.floor(oam[idx * 4 + 1] * ((-1) ^ (twoBits & 0x01))),
x = math.floor(oam[idx * 4 + 1] * ((-1) ^ bit.band(twoBits, 0x01))),
y = oam[idx * 4 + 2],
tile = oam[idx * 4 + 3],
flags = oam[idx * 4 + 4],
@ -359,7 +359,7 @@ Tile offset: %04x
::nextsprite::
end
if screenSprite.flags & 0x21 ~= 0x00 and screenSprite.tile >= 224 and screenSprite.tile <= 238 then
if bit.band(screenSprite.flags, 0x21) ~= 0x00 and screenSprite.tile >= 224 and screenSprite.tile <= 238 then
gui.text(screenSprite.x * 2, screenSprite.y * 2, screenSprite.tile, 0x00000000, 0x00ffff00)
end
@ -376,8 +376,38 @@ Tile offset: %04x
text(guiWidth - 125, 20, "Help [Hold 0]")
end
-- Logic from 0xb5c3e1, 0xb5c414, 0xb5c82c
function tileOffsetCalculation (x, y, vertical)
local newX = x - 256
local newY = y - 256
if not vertical then
if newY < 0 then
newY = 0
elseif newY >= 0x1ff then
newY = 0x1ff
end
newY = bit.band(bit.band(bit.bnot(newY), 0xffff) + 1, 0x1e0)
newX = bit.band(newX, 0xffe0)
newY = bit.lrshift(bit.band(bit.bxor(newY, 0x1e0), 0xffff), 4)
return newY + newX
else
newY = bit.band(bit.band(bit.bnot(newY), 0xffff) + 1, 0xffe0)
newX = bit.lrshift(bit.band(newX, 0xffe0), 4)
newY = bit.band(bit.lshift(bit.band(bit.bxor(newY, 0xffe0), 0xffff), 1), 0xffff)
return newY + newX
end
end
-- 0xb5c94d
function tile_is_solid(x, y, tileVal, tileOffset)
function tileIsSolid(x, y, tileVal, tileOffset)
local origTileVal = tileVal
if tileVal == 0 or tileOffset == 0 then
@ -388,13 +418,13 @@ function tile_is_solid(x, y, tileVal, tileOffset)
return true
end
local a2 = x & 0x1f
local a2 = bit.band(x, 0x1f)
if tileVal & 0x4000 ~= 0 then
a2 = (a2 ~ 0x1f) & 0xffff
if bit.band(tileVal, 0x4000) ~= 0 then
a2 = bit.band(bit.bxor(a2, 0x1f), 0xffff)
end
tileVal = tileVal & 0x3fff
tileVal = bit.band(tileVal, 0x3fff)
local solidLessThan = memory.readword(SOLID_LESS_THAN)
@ -402,65 +432,35 @@ function tile_is_solid(x, y, tileVal, tileOffset)
return false
end
tileVal = (tileVal << 2) & 0xffff
tileVal = bit.band(bit.lshift(tileVal, 2), 0xffff)
if a2 & 0x10 ~= 0 then
if bit.band(a2, 0x10) ~= 0 then
tileVal = tileVal + 2
end
local tileMeta = memory.readword(memory.readword(0x7e009c) + tileVal)
if tileMeta & 0x8000 ~=0 then
a2 = (a2 ~ 0x000f) & 0xffff
if bit.band(tileMeta, 0x8000) ~=0 then
a2 = bit.band(bit.bxor(a2, 0x000f), 0xffff)
end
if tileMeta & tileVal & 0x2000 ~= 0 then
tileMeta = (tileMeta ~ 0x8000) & 0xffff
if bit.band(tileMeta, tileVal, 0x2000) ~= 0 then
tileMeta = bit.band(bit.bxor(tileMeta, 0x8000), 0xffff)
end
tileMeta = tileMeta & 0x00ff
tileMeta = bit.band(tileMeta, 0x00ff)
if tileMeta == 0 then
return false
end
tileMeta = (tileMeta << 1) & 0xffff
tileMeta = bit.band(bit.bxor(tileMeta, 1), 0xffff)
-- FIXME further tests?
return true
end
-- Logic from 0xb5c3e1, 0xb5c414, 0xb5c82c
function tile_offset_calculation (x, y, vertical)
local partyX = x - 256
local partyY = y - 256
if not vertical then
if partyY < 0 then
partyY = 0
elseif partyY >= 0x1ff then
partyY = 0x1ff
end
partyY = (((~partyY) & 0xffff) + 1) & 0x1e0
partyX = partyX & 0xffe0
partyY = ((partyY ~ 0x1e0) & 0xffff) >> 4
return partyY + partyX
else
partyY = (((~partyY) & 0xffff) + 1) & 0xffe0
partyX = (partyX & 0xffe0) >> 4
partyY = (((partyY ~ 0xffe0) & 0xffff) << 1) & 0xffff
return partyY + partyX
end
end
function on_timer()
set_timer_timeout(100 * 1000)
end

153
game.lua
View file

@ -3,15 +3,27 @@ config = require "config"
spritelist = require "spritelist"
local _M = {}
TILE_SIZE = 32
TILE_COLLISION_MATH_POINTER = 0x7e17b2
VERTICAL_POINTER = 0xc414
TILEDATA_POINTER = 0x7e0098
CAMERA_X = 0x7e17ba
CAMERA_Y = 0x7e17c0
PARTY_X = 0x7e0a2a
PARTY_Y = 0x7e0a2c
SOLID_LESS_THAN = 0x7e00a0
function _M.getPositions()
partyX = memory.readword(0x7e0a2a) - 256
partyY = memory.readword(0x7e0a2c) - 256
tilePtr = memory.readhword(TILEDATA_POINTER)
vertical = memory.readword(TILE_COLLISION_MATH_POINTER) == VERTICAL_POINTER
partyX = memory.readword(PARTY_X)
partyY = memory.readword(PARTY_Y)
local cameraX = memory.readword(0x7e17ba) - 256
local cameraY = memory.readword(0x7e17c0) - 256
local cameraX = memory.readword(CAMERA_X) - 256
local cameraY = memory.readword(CAMERA_Y) - 256
_M.screenX = (partyX-cameraX)*2
_M.screenY = (partyY-cameraY)*2
_M.screenX = (partyX-256-cameraX)*2
_M.screenY = (partyY-256-cameraY)*2
end
function _M.getBananas()
@ -52,14 +64,104 @@ function _M.getHitTimer()
return memory.readbyte(0x7e08c0) - memory.readbyte(0x7e08be)
end
function _M.getTile(dx, dy)
local partyScreenX = (partyX - cameraX) * 2
local partyScreenY = (partyY - cameraY) * 2
-- Logic from 0xb5c3e1, 0xb5c414, 0xb5c82c
function _M.tileOffsetCalculation (x, y, vertical)
local newX = x - 256
local newY = y - 256
x = math.floor((partyX+dx+8)/16)
y = math.floor((partyY+dy)/16)
return memory.readbyte(0x1C800 + math.floor(x/0x10)*0x1B0 + y*0x10 + x%0x10)
if not vertical then
if newY < 0 then
newY = 0
elseif newY >= 0x1ff then
newY = 0x1ff
end
newY = bit.band(bit.band(bit.bnot(newY), 0xffff) + 1, 0x1e0)
newX = bit.band(newX, 0xffe0)
newY = bit.lrshift(bit.band(bit.bxor(newY, 0x1e0), 0xffff), 4)
return newY + newX
else
newY = bit.band(bit.band(bit.bnot(newY), 0xffff) + 1, 0xffe0)
newX = bit.lrshift(bit.band(newX, 0xffe0), 4)
newY = bit.band(bit.lshift(bit.band(bit.bxor(newY, 0xffe0), 0xffff), 1), 0xffff)
return newY + newX
end
end
-- 0xb5c94d
function _M.tileIsSolid(x, y, tileVal, tileOffset)
local origTileVal = tileVal
if tileVal == 0 or tileOffset == 0 then
return false
end
if questionable_tiles then
return true
end
local a2 = bit.band(x, 0x1f)
if bit.band(tileVal, 0x4000) ~= 0 then
a2 = bit.band(bit.bxor(a2, 0x1f), 0xffff)
end
tileVal = bit.band(tileVal, 0x3fff)
local solidLessThan = memory.readword(SOLID_LESS_THAN)
if tileVal >= solidLessThan then
return false
end
tileVal = bit.band(bit.lshift(tileVal, 2), 0xffff)
if bit.band(a2, 0x10) ~= 0 then
tileVal = tileVal + 2
end
local tileMeta = memory.readword(memory.readword(0x7e009c) + tileVal)
if bit.band(tileMeta, 0x8000) ~=0 then
a2 = bit.band(bit.bxor(a2, 0x000f), 0xffff)
end
if bit.band(tileMeta, tileVal, 0x2000) ~= 0 then
tileMeta = bit.band(bit.bxor(tileMeta, 0x8000), 0xffff)
end
tileMeta = bit.band(tileMeta, 0x00ff)
if tileMeta == 0 then
return false
end
tileMeta = bit.band(bit.bxor(tileMeta, 1), 0xffff)
-- FIXME further tests?
return true
end
function _M.getTile(dx, dy)
local tileX = math.floor((partyX + dx * TILE_SIZE) / TILE_SIZE) * TILE_SIZE
local tileY = math.floor((partyY + dy * TILE_SIZE) / TILE_SIZE) * TILE_SIZE
local offset = _M.tileOffsetCalculation(tileX, tileY, vertical)
local tile = memory.readword(tilePtr + offset)
if not _M.tileIsSolid(tileX, tileY, tile, offset) then
return 0
end
return 1
end
function _M.getSprites()
@ -93,26 +195,23 @@ end
function _M.getInputs()
_M.getPositions()
sprites = _M.getSprites()
extended = _M.getExtendedSprites()
-- sprites = _M.getSprites()
-- extended = _M.getExtendedSprites()
local inputs = {}
local inputDeltaDistance = {}
local layer1x = memory.readword(0x7f0000);
local layer1y = memory.read_s16_le(0x1C);
for dy=-config.BoxRadius*16,config.BoxRadius*16,16 do
for dx=-config.BoxRadius*16,config.BoxRadius*16,16 do
for dy = -config.BoxRadius, config.BoxRadius, 1 do
for dx = -config.BoxRadius, config.BoxRadius, 1 do
inputs[#inputs+1] = 0
inputDeltaDistance[#inputDeltaDistance+1] = 1
tile = _M.getTile(dx, dy)
if tile == 1 and partyY+dy < 0x1B0 then
if tile == 1 --[[and partyY+dy < 0x1B0]] then
inputs[#inputs] = 1
end
for i = 1,#sprites do
--[[ for i = 1,#sprites do
distx = math.abs(sprites[i]["x"] - (partyX+dx))
disty = math.abs(sprites[i]["y"] - (partyY+dy))
if distx <= 8 and disty <= 8 then
@ -124,14 +223,14 @@ function _M.getInputs()
--gui.drawLine(screenX, screenY, sprites[i]["x"] - layer1x, sprites[i]["y"] - layer1y, 0x50000000)
end
end
end
end ]]
for i = 1,#extended do
--[[ for i = 1,#extended do
distx = math.abs(extended[i]["x"] - (partyX+dx))
disty = math.abs(extended[i]["y"] - (partyY+dy))
if distx < 8 and disty < 8 then
--console.writeline(screenX .. "," .. screenY .. " to " .. extended[i]["x"]-layer1x .. "," .. extended[i]["y"]-layer1y)
--print(screenX .. "," .. screenY .. " to " .. extended[i]["x"]-layer1x .. "," .. extended[i]["y"]-layer1y)
inputs[#inputs] = extended[i]["good"]
local dist = math.sqrt((distx * distx) + (disty * disty))
if dist > 8 then
@ -140,12 +239,12 @@ function _M.getInputs()
end
--if dist > 100 then
--dw = mathFunctions.squashDistance(dist)
--console.writeline(dist .. " to " .. dw)
--print(dist .. " to " .. dw)
--gui.drawLine(screenX, screenY, extended[i]["x"] - layer1x, extended[i]["y"] - layer1y, 0x50000000)
--end
--inputs[#inputs] = {["value"]=-1, ["dw"]=dw}
end
end
end ]]
end
end

View file

@ -6,6 +6,40 @@ game = require "game"
mathFunctions = require "mathFunctions"
util = require "util"
guiWidth, guiHeight = gui.resolution()
form = gui.renderctx.new(500, 500)
netPicture = gui.renderctx.new(470, 200)
runInitialized = {}
frameAdvanced = {}
--int forms.pictureBox(int formhandle, [int? x = null], [int? y = null], [int? width = null], [int? height = null])
--[[ GenerationLabel = forms.label(form, "Generation: " .. pool.generation, 5, 5)
SpeciesLabel = forms.label(form, "Species: " .. pool.currentSpecies, 130, 5)
GenomeLabel = forms.label(form, "Genome: " .. pool.currentGenome, 230, 5)
MeasuredLabel = forms.label(form, "Measured: " .. "", 330, 5)
FitnessLabel = forms.label(form, "Fitness: " .. "", 5, 30)
MaxLabel = forms.label(form, "Max: " .. "", 130, 30)
BananasLabel = forms.label(form, "Bananas: " .. "", 5, 65)
CoinsLabel = forms.label(form, "Coins: " .. "", 130, 65, 90, 14)
LivesLabel = forms.label(form, "Lives: " .. "", 130, 80, 90, 14)
DmgLabel = forms.label(form, "Damage: " .. "", 230, 65, 110, 14)
PowerUpLabel = forms.label(form, "PowerUp: " .. "", 230, 80, 110, 14) ]]
--[[ startButton = forms.button(form, "Start", flipState, 155, 102)
restartButton = forms.button(form, "Restart", initializePool, 155, 102)
saveButton = forms.button(form, "Save", savePool, 5, 102)
loadButton = forms.button(form, "Load", loadPool, 80, 102)
playTopButton = forms.button(form, "Play Top", playTop, 230, 102)
saveLoadLabel = forms.label(form, "Save/Load:", 5, 129) ]]
saveLoadFile = config.NeatConfig.Filename .. ".pool"
spritelist.InitSpriteList()
spritelist.InitExtSpriteList()
Inputs = config.InputSize+1
Outputs = #config.ButtonNames
@ -149,14 +183,13 @@ function evaluateNetwork(network, inputs, inputDeltas)
table.insert(inputs, 1)
table.insert(inputDeltas,99)
if #inputs ~= Inputs then
console.writeline("Incorrect number of neural network inputs.")
print("Incorrect number of neural network inputs.")
return {}
end
for i=1,Inputs do
network.neurons[i].value = inputs[i] * inputDeltas[i]
--network.neurons[i].value = inputs[i]
end
for _,neuron in pairs(network.neurons) do
@ -174,7 +207,7 @@ function evaluateNetwork(network, inputs, inputDeltas)
local outputs = {}
for o=1,Outputs do
local button = "P1 " .. config.ButtonNames[o]
local button = o - 1
if network.neurons[config.NeatConfig.MaxNodes+o].value > 0 then
outputs[button] = true
else
@ -625,11 +658,10 @@ function newGeneration()
pool.generation = pool.generation + 1
--writeFile("backup." .. pool.generation .. "." .. forms.gettext(saveLoadFile))
writeFile(forms.gettext(saveLoadFile) .. ".gen" .. pool.generation .. ".pool")
writeFile(saveLoadFile .. ".gen" .. pool.generation .. ".pool")
end
function initializePool()
function initializePool(after)
pool = newPool()
for i=1,config.NeatConfig.Population do
@ -637,32 +669,48 @@ function initializePool()
addToSpecies(basic)
end
initializeRun()
initializeRun(after)
end
function initializeRun()
print("Hello")
print(config.NeatConfig.Filename)
local rew = movie.to_rewind(config.NeatConfig.Filename)
movie.unsafe_rewind(rew)
if config.StartPowerup ~= NIL then
game.writePowerup(config.StartPowerup)
end
rightmost = 0
pool.currentFrame = 0
timeout = config.NeatConfig.TimeoutConstant
game.clearJoypad()
startBananas = game.getBananas()
startCoins = game.getCoins()
startLives = game.getLives()
checkMarioCollision = true
marioHitCounter = 0
powerUpCounter = 0
powerUpBefore = game.getPowerup()
local species = pool.species[pool.currentSpecies]
local genome = species.genomes[pool.currentGenome]
generateNetwork(genome)
evaluateCurrent()
function on_timer()
if config.StartPowerup ~= NIL then
game.writePowerup(config.StartPowerup)
end
rightmost = 0
pool.currentFrame = 0
timeout = config.NeatConfig.TimeoutConstant
game.clearJoypad()
startBananas = game.getBananas()
startCoins = game.getCoins()
startLives = game.getLives()
checkPartyCollision = true
partyHitCounter = 0
powerUpCounter = 0
powerUpBefore = game.getPowerup()
local species = pool.species[pool.currentSpecies]
local genome = species.genomes[pool.currentGenome]
generateNetwork(genome)
evaluateCurrent()
for i=#runInitialized,1,-1 do
table.remove(runInitialized, i)()
end
end
local rew = movie.to_rewind(config.NeatConfig.Filename)
function on_post_rewind()
set_timer_timeout(1)
end
function on_video()
gui.kill_frame()
end
function initializeRun(after)
settings.set_speed(1)
gui.subframe_update(false)
table.insert(runInitialized, after)
movie.unsafe_rewind(rew)
end
function evaluateCurrent()
@ -673,21 +721,219 @@ function evaluateCurrent()
inputs, inputDeltas = game.getInputs()
controller = evaluateNetwork(genome.network, inputs, inputDeltas)
if controller["P1 Left"] and controller["P1 Right"] then
controller["P1 Left"] = false
controller["P1 Right"] = false
if controller[6] and controller[7] then
controller[6] = false
controller[7] = false
end
if controller["P1 Up"] and controller["P1 Down"] then
controller["P1 Up"] = false
controller["P1 Down"] = false
if controller[4] and controller[5] then
controller[4] = false
controller[5] = false
end
joypad.set(controller)
for b=1,#config.ButtonNames,1 do
if controller[b] then
input.set(0, b - 1, 1)
else
input.set(0, b - 1, 0)
end
end
end
if pool == nil then
initializePool()
function on_input()
for i=#frameAdvanced,1,-1 do
table.remove(frameAdvanced, i)()
end
end
function on_paint()
gui.left_gap(500)
gui.top_gap(0)
gui.bottom_gap(0)
gui.right_gap(0)
if genomeDisplay ~= nil and movie.currentframe() % 10 == 0 then
displayGenome(genomeDisplay)
end
gui.renderctx.setnull()
form:render():draw(-500, 0)
end
function advanceFrame(after)
table.insert(frameAdvanced, after)
--exec("+advance-frame")
end
function mainLoop (species, genome)
advanceFrame(function()
if not config.Running then
return
end
if species ~= nil and genome ~= nil then
local measured = 0
local total = 0
for _,species in pairs(pool.species) do
for _,genome in pairs(species.genomes) do
total = total + 1
if genome.fitness ~= 0 then
measured = measured + 1
end
end
end
gui.circle(game.screenX-84, game.screenY-84, 192 / 2, 1, 0x50000000)
--[[ forms.settext(FitnessLabel, "Fitness: " .. math.floor(rightmost - (pool.currentFrame) / 2 - (timeout + timeoutBonus)*2/3))
forms.settext(GenerationLabel, "Generation: " .. pool.generation)
forms.settext(SpeciesLabel, "Species: " .. pool.currentSpecies)
forms.settext(GenomeLabel, "Genome: " .. pool.currentGenome)
forms.settext(MaxLabel, "Max: " .. math.floor(pool.maxFitness))
forms.settext(MeasuredLabel, "Measured: " .. math.floor(measured/total*100) .. "%")
forms.settext(BananasLabel, "Bananas: " .. (game.getBananas() - startBananas))
forms.settext(CoinsLabel, "Coins: " .. (game.getCoins() - startCoins))
forms.settext(LivesLabel, "Lives: " .. Lives)
forms.settext(DmgLabel, "Damage: " .. partyHitCounter)
forms.settext(PowerUpLabel, "PowerUp: " .. powerUpCounter)
]]
pool.currentFrame = pool.currentFrame + 1
end
species = pool.species[pool.currentSpecies]
genome = species.genomes[pool.currentGenome]
genomeDisplay = genome
if pool.currentFrame%5 == 0 then
evaluateCurrent()
end
for b=1,#config.ButtonNames,1 do
if controller[b] then
input.set(0, b - 1, 1)
else
input.set(0, b - 1, 0)
end
end
game.getPositions()
if partyX > rightmost then
rightmost = partyX
timeout = config.NeatConfig.TimeoutConstant
end
local hitTimer = game.getHitTimer()
if checkPartyCollision == true then
if hitTimer > 0 then
partyHitCounter = partyHitCounter + 1
--print("party took damage, hit counter: " .. partyHitCounter)
checkPartyCollision = false
end
end
if hitTimer == 0 then
checkPartyCollision = true
end
powerUp = game.getPowerup()
if powerUp > 0 then
if powerUp ~= powerUpBefore then
powerUpCounter = powerUpCounter+1
powerUpBefore = powerUp
end
end
Lives = game.getLives()
timeout = timeout - 1
local timeoutBonus = pool.currentFrame / 4
if timeout + timeoutBonus <= 0 then
local bananas = game.getBananas() - startBananas
local coins = game.getCoins() - startCoins
print("Bananas: " .. bananas .. " coins: " .. coins)
local bananaCoinsFitness = (bananas * 50) + (coins * 0.2)
if (bananas + coins) > 0 then
print("Bananas and Coins added " .. bananaCoinsFitness .. " fitness")
end
local hitPenalty = partyHitCounter * 100
local powerUpBonus = powerUpCounter * 100
local fitness = bananaCoinsFitness - hitPenalty + powerUpBonus + rightmost - pool.currentFrame / 2
if startLives < Lives then
local ExtraLiveBonus = (Lives - startLives)*1000
fitness = fitness + ExtraLiveBonus
print("ExtraLiveBonus added " .. ExtraLiveBonus)
end
if rightmost > 4816 then
fitness = fitness + 1000
print("!!!!!!Beat level!!!!!!!")
end
if fitness == 0 then
fitness = -1
end
genome.fitness = fitness
if fitness > pool.maxFitness then
pool.maxFitness = fitness
writeFile(saveLoadFile .. ".gen" .. pool.generation .. ".pool")
end
print("Gen " .. pool.generation .. " species " .. pool.currentSpecies .. " genome " .. pool.currentGenome .. " fitness: " .. fitness)
pool.currentSpecies = 1
pool.currentGenome = 1
while fitnessAlreadyMeasured() do
nextGenome()
end
initializeRun(function()
mainLoop(species, genome)
end)
return
end
advanceFrame(mainLoop)
end)
end
function writeFile(filename)
local file = io.open(filename, "w")
file:write(pool.generation .. "\n")
file:write(pool.maxFitness .. "\n")
file:write(#pool.species .. "\n")
for n,species in pairs(pool.species) do
file:write(species.topFitness .. "\n")
file:write(species.staleness .. "\n")
file:write(#species.genomes .. "\n")
for m,genome in pairs(species.genomes) do
file:write(genome.fitness .. "\n")
file:write(genome.maxneuron .. "\n")
for mutation,rate in pairs(genome.mutationRates) do
file:write(mutation .. "\n")
file:write(rate .. "\n")
end
file:write("done\n")
file:write(#genome.genes .. "\n")
for l,gene in pairs(genome.genes) do
file:write(gene.into .. " ")
file:write(gene.out .. " ")
file:write(gene.weight .. " ")
file:write(gene.innovation .. " ")
if(gene.enabled) then
file:write("1\n")
else
file:write("0\n")
end
end
end
end
file:close()
end
function nextGenome()
@ -709,13 +955,10 @@ function fitnessAlreadyMeasured()
return genome.fitness ~= 0
end
form = forms.newform(500, 500, "Mario-Neat")
netPicture = forms.pictureBox(form, 5, 250,470, 200)
--int forms.pictureBox(int formhandle, [int? x = null], [int? y = null], [int? width = null], [int? height = null])
function displayGenome(genome)
forms.clear(netPicture,0x80808080)
netPicture:clear()
netPicture:set()
gui.solidrectangle(0, 0, 470, 200, 0x80808080)
local network = genome.network
local cells = {}
local i = 1
@ -739,17 +982,16 @@ function displayGenome(genome)
for o = 1,Outputs do
cell = {}
cell.x = 220
cell.y = 30 + 8 * o
cell.y = 30 + 14 * o
cell.value = network.neurons[config.NeatConfig.MaxNodes + o].value
cells[config.NeatConfig.MaxNodes+o] = cell
local color
if cell.value > 0 then
color = 0xFF0000FF
color = 0x000000FF
else
color = 0xFF000000
color = 0x00000000
end
--gui.drawText(223, 24+8*o, config.ButtonNames[o], color, 9)
forms.drawText(netPicture,223, 24+8*o, config.ButtonNames[o], color, 9)
gui.text(223, 24+14*o, config.ButtonNames[o], color, 0xff000000)
end
for n,neuron in pairs(network.neurons) do
@ -799,9 +1041,15 @@ function displayGenome(genome)
end
end
--gui.drawBox(50-config.BoxRadius*5-3,70-config.BoxRadius*5-3,50+config.BoxRadius*5+2,70+config.BoxRadius*5+2,0xFF000000, 0x80808080)
forms.drawBox(netPicture, 50-config.BoxRadius*5-3,70-config.BoxRadius*5-3,50+config.BoxRadius*5+2,70+config.BoxRadius*5+2,0xFF000000, 0x80808080)
--oid forms.drawBox(int componenthandle, int x, int y, int x2, int y2, [color? line = null], [color? background = null])
gui.rectangle(
50-config.BoxRadius*5-3,
70-config.BoxRadius*5-3,
config.BoxRadius*10+5,
config.BoxRadius*10+5,
2,
0xFF000000,
0x80808080
)
for n,cell in pairs(cells) do
if n > Inputs or cell.value ~= 0 then
local color = math.floor((cell.value+1)/2*256)
@ -812,8 +1060,15 @@ function displayGenome(genome)
opacity = 0x50000000
end
color = opacity + color*0x10000 + color*0x100 + color
forms.drawBox(netPicture,cell.x-2,cell.y-2,cell.x+2,cell.y+2,opacity,color)
--gui.drawBox(cell.x-2,cell.y-2,cell.x+2,cell.y+2,opacity,color)
gui.rectangle(
math.floor(cell.x-5),
math.floor(cell.y-5),
5,
5,
1,
0x00,
color
)
end
end
for _,gene in pairs(genome.genes) do
@ -831,65 +1086,40 @@ function displayGenome(genome)
else
color = opacity + 0x800000 + 0x100*color
end
--gui.drawLine(c1.x+1, c1.y, c2.x-3, c2.y, color)
forms.drawLine(netPicture,c1.x+1, c1.y, c2.x-3, c2.y, color)
gui.line(
math.floor(c1.x+1),
math.floor(c1.y),
math.floor(c2.x-3),
math.floor(c2.y),
color
)
end
end
--gui.drawBox(49,71,51,78,0x00000000,0x80FF0000)
forms.drawBox(netPicture, 49,71,51,78,0x00000000,0x80FF0000)
--if forms.ischecked(showMutationRates) then
local pos = 100
for mutation,rate in pairs(genome.mutationRates) do
--gui.drawText(100, pos, mutation .. ": " .. rate, 0xFF000000, 10)
forms.drawText(netPicture,100, pos, mutation .. ": " .. rate, 0xFF000000, 10)
--forms.drawText(pictureBox,400,pos, mutation .. ": " .. rate)
--void forms.drawText(int componenthandle, int x, int y, string message, [color? forecolor = null], [color? backcolor = null], [int? fontsize = null], [string fontfamily = null], [string fontstyle = null], [string horizalign = null], [string vertalign = null])
gui.rectangle(
49,
71,
2,
7,
0x00000000,
0x80FF0000
)
pos = pos + 8
end
--end
forms.refresh(netPicture)
end
local pos = 100
for mutation,rate in pairs(genome.mutationRates) do
gui.text(100, pos, mutation .. ": " .. rate, 0x00000000, 0xff000000)
function writeFile(filename)
local file = io.open(filename, "w")
file:write(pool.generation .. "\n")
file:write(pool.maxFitness .. "\n")
file:write(#pool.species .. "\n")
for n,species in pairs(pool.species) do
file:write(species.topFitness .. "\n")
file:write(species.staleness .. "\n")
file:write(#species.genomes .. "\n")
for m,genome in pairs(species.genomes) do
file:write(genome.fitness .. "\n")
file:write(genome.maxneuron .. "\n")
for mutation,rate in pairs(genome.mutationRates) do
file:write(mutation .. "\n")
file:write(rate .. "\n")
end
file:write("done\n")
file:write(#genome.genes .. "\n")
for l,gene in pairs(genome.genes) do
file:write(gene.into .. " ")
file:write(gene.out .. " ")
file:write(gene.weight .. " ")
file:write(gene.innovation .. " ")
if(gene.enabled) then
file:write("1\n")
else
file:write("0\n")
end
end
end
end
file:close()
pos = pos + 14
end
local bitmap = netPicture:render()
form:set()
bitmap:draw(5, 250)
gui.renderctx.setnull()
end
function savePool()
local filename = forms.gettext(saveLoadFile)
local filename = saveLoadFile
print(filename)
writeFile(filename)
end
@ -906,7 +1136,7 @@ function mysplit(inputstr, sep)
return t
end
function loadFile(filename)
function loadFile(filename, after)
print("Loading pool from " .. filename)
local file = io.open(filename, "r")
pool = newPool()
@ -961,9 +1191,11 @@ function loadFile(filename)
while fitnessAlreadyMeasured() do
nextGenome()
end
initializeRun()
pool.currentFrame = pool.currentFrame + 1
print("Pool loaded.")
initializeRun(function()
pool.currentFrame = pool.currentFrame + 1
print("Pool loaded.")
after()
end)
end
function flipState()
@ -976,11 +1208,9 @@ function flipState()
end
end
function loadPool()
function loadPool(after)
filename = forms.openfile("DP1.state.pool",config.PoolDir)
--local filename = forms.gettext(saveLoadFile)
forms.settext(saveLoadFile, filename)
loadFile(filename)
loadFile(filename, after)
end
function playTop()
@ -999,167 +1229,24 @@ function playTop()
pool.currentSpecies = maxs
pool.currentGenome = maxg
pool.maxFitness = maxfitness
forms.settext(MaxLabel, "Max Fitness: " .. math.floor(pool.maxFitness))
--forms.settext(MaxLabel, "Max Fitness: " .. math.floor(pool.maxFitness))
initializeRun()
pool.currentFrame = pool.currentFrame + 1
return
end
function onExit()
forms.destroy(form)
function on_quit()
netPicture:clear()
form:clear()
end
writeFile(config.PoolDir.."temp.pool")
if pool == nil then
initializePool(function()
writeFile(config.PoolDir.."temp.pool")
mainLoop()
end)
else
writeFile(config.PoolDir.."temp.pool")
mainLoop()
end
event.onexit(onExit)
GenerationLabel = forms.label(form, "Generation: " .. pool.generation, 5, 5)
SpeciesLabel = forms.label(form, "Species: " .. pool.currentSpecies, 130, 5)
GenomeLabel = forms.label(form, "Genome: " .. pool.currentGenome, 230, 5)
MeasuredLabel = forms.label(form, "Measured: " .. "", 330, 5)
FitnessLabel = forms.label(form, "Fitness: " .. "", 5, 30)
MaxLabel = forms.label(form, "Max: " .. "", 130, 30)
BananasLabel = forms.label(form, "Bananas: " .. "", 5, 65)
CoinsLabel = forms.label(form, "Coins: " .. "", 130, 65, 90, 14)
LivesLabel = forms.label(form, "Lives: " .. "", 130, 80, 90, 14)
DmgLabel = forms.label(form, "Damage: " .. "", 230, 65, 110, 14)
PowerUpLabel = forms.label(form, "PowerUp: " .. "", 230, 80, 110, 14)
startButton = forms.button(form, "Start", flipState, 155, 102)
restartButton = forms.button(form, "Restart", initializePool, 155, 102)
saveButton = forms.button(form, "Save", savePool, 5, 102)
loadButton = forms.button(form, "Load", loadPool, 80, 102)
playTopButton = forms.button(form, "Play Top", playTop, 230, 102)
saveLoadFile = forms.textbox(form, config.NeatConfig.Filename .. ".pool", 170, 25, nil, 5, 148)
saveLoadLabel = forms.label(form, "Save/Load:", 5, 129)
spritelist.InitSpriteList()
spritelist.InitExtSpriteList()
while true do
if config.Running == true then
local species = pool.species[pool.currentSpecies]
local genome = species.genomes[pool.currentGenome]
displayGenome(genome)
if pool.currentFrame%5 == 0 then
evaluateCurrent()
end
joypad.set(controller)
game.getPositions()
if partyX > rightmost then
rightmost = partyX
timeout = config.NeatConfig.TimeoutConstant
end
local hitTimer = game.getHitTimer()
if checkMarioCollision == true then
if hitTimer > 0 then
marioHitCounter = marioHitCounter + 1
--console.writeline("Mario took damage, hit counter: " .. marioHitCounter)
checkMarioCollision = false
end
end
if hitTimer == 0 then
checkMarioCollision = true
end
powerUp = game.getPowerup()
if powerUp > 0 then
if powerUp ~= powerUpBefore then
powerUpCounter = powerUpCounter+1
powerUpBefore = powerUp
end
end
Lives = game.getLives()
timeout = timeout - 1
local timeoutBonus = pool.currentFrame / 4
if timeout + timeoutBonus <= 0 then
local bananas = game.getBananas() - startBananas
local coins = game.getCoins() - startCoins
--console.writeline("Bananas: " .. bananas .. " coins: " .. coins)
local bananaCoinsFitness = (bananas * 50) + (coins * 0.2)
if (bananas + coins) > 0 then
console.writeline("Bananas and Coins added " .. bananaCoinsFitness .. " fitness")
end
local hitPenalty = marioHitCounter * 100
local powerUpBonus = powerUpCounter * 100
local fitness = bananaCoinsFitness - hitPenalty + powerUpBonus + rightmost - pool.currentFrame / 2
if startLives < Lives then
local ExtraLiveBonus = (Lives - startLives)*1000
fitness = fitness + ExtraLiveBonus
console.writeline("ExtraLiveBonus added " .. ExtraLiveBonus)
end
if rightmost > 4816 then
fitness = fitness + 1000
console.writeline("!!!!!!Beat level!!!!!!!")
end
if fitness == 0 then
fitness = -1
end
genome.fitness = fitness
if fitness > pool.maxFitness then
pool.maxFitness = fitness
--writeFile("backup." .. pool.generation .. "." .. forms.gettext(saveLoadFile))
writeFile(forms.gettext(saveLoadFile) .. ".gen" .. pool.generation .. ".pool")
end
console.writeline("Gen " .. pool.generation .. " species " .. pool.currentSpecies .. " genome " .. pool.currentGenome .. " fitness: " .. fitness)
pool.currentSpecies = 1
pool.currentGenome = 1
while fitnessAlreadyMeasured() do
nextGenome()
end
initializeRun()
end
local measured = 0
local total = 0
for _,species in pairs(pool.species) do
for _,genome in pairs(species.genomes) do
total = total + 1
if genome.fitness ~= 0 then
measured = measured + 1
end
end
end
gui.drawEllipse(game.screenX-84, game.screenY-84, 192, 192, 0x50000000)
forms.settext(FitnessLabel, "Fitness: " .. math.floor(rightmost - (pool.currentFrame) / 2 - (timeout + timeoutBonus)*2/3))
forms.settext(GenerationLabel, "Generation: " .. pool.generation)
forms.settext(SpeciesLabel, "Species: " .. pool.currentSpecies)
forms.settext(GenomeLabel, "Genome: " .. pool.currentGenome)
forms.settext(MaxLabel, "Max: " .. math.floor(pool.maxFitness))
forms.settext(MeasuredLabel, "Measured: " .. math.floor(measured/total*100) .. "%")
forms.settext(BananasLabel, "Bananas: " .. (game.getBananas() - startBananas))
forms.settext(CoinsLabel, "Coins: " .. (game.getCoins() - startCoins))
forms.settext(LivesLabel, "Lives: " .. Lives)
forms.settext(DmgLabel, "Damage: " .. marioHitCounter)
forms.settext(PowerUpLabel, "PowerUp: " .. powerUpCounter)
pool.currentFrame = pool.currentFrame + 1
end
emu.frameadvance();
end

Binary file not shown.

View file

@ -1,4 +1,4 @@
-- Idea from: https://github.com/kevino5233/MarIO_Enhanced/
-- Idea from: https://github.com/kevino5233/party_Enhanced/
-- Spritelist from: https://www.smwcentral.net/?p=viewthread&t=7562
-- Extended spritelist: https://web.archive.org/web/20170709102356/www.smwiki.net/wiki/RAM_Address/$7E:170B
--
@ -163,7 +163,7 @@ _M.BadSprites = {
0x71, -- Super Koopa, red cape, swoop.
0x72, -- Super Koopa, yellow cape, swoop.
0x73, -- Super Koopa, feather/yellow cape (X&1).
0x7A, -- Firework, makes Mario temporarily invisible.
0x7A, -- Firework, makes party temporarily invisible.
0x86, -- Wiggler.
0x8D, -- Ghost house exit sign and door.
0x8E, -- Invisible "Warp Hole" blocks. (!)

20
state-test.lua Normal file
View file

@ -0,0 +1,20 @@
PARTY_X = 0x7e0a2a
TILE_SIZE = 32
print(memory.readword(PARTY_X))
function on_post_rewind()
print("Async?")
print(memory.readword(PARTY_X))
end
function movement(addr, val)
if memory.readword(addr) > TILE_SIZE * 20 then
local rew = movie.to_rewind("pool/PiratePanic.lsmv")
movie.unsafe_rewind(rew)
print("Sync?")
print(memory.readword(PARTY_X))
end
end
memory2.WRAM:registerwrite(0x0a2a, movement)