diff --git a/.gitignore b/.gitignore index 00b85ff..b5de230 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ catchem/ state/ crashsave* *.backup +*.pool diff --git a/donkutil.lua b/donkutil.lua index 3872798..d5884d7 100644 --- a/donkutil.lua +++ b/donkutil.lua @@ -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 diff --git a/game.lua b/game.lua index 0fa173f..ec67640 100644 --- a/game.lua +++ b/game.lua @@ -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 diff --git a/neat-donk.lua b/neat-donk.lua index c1fdbcd..44a01df 100644 --- a/neat-donk.lua +++ b/neat-donk.lua @@ -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 \ No newline at end of file diff --git a/pool/PiratePanic.lsmv b/pool/PiratePanic.lsmv index 816fa22..5715660 100644 Binary files a/pool/PiratePanic.lsmv and b/pool/PiratePanic.lsmv differ diff --git a/spritelist.lua b/spritelist.lua index 1094bfb..160b65c 100644 --- a/spritelist.lua +++ b/spritelist.lua @@ -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. (!) diff --git a/state-test.lua b/state-test.lua new file mode 100644 index 0000000..01c8514 --- /dev/null +++ b/state-test.lua @@ -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)