From ee2198b93fe7645f8d7e75964066047793f85705 Mon Sep 17 00:00:00 2001 From: empathicqubit Date: Wed, 3 Mar 2021 15:40:19 -0500 Subject: [PATCH] Stage rulers. Try to hijack algorithm used to determine tile location of characters. --- .editorconfig | 5 + .gitignore | 4 +- config.lua | 137 +-- donkutil.lua | 330 +++--- game.lua | 325 +++--- mathFunctions.lua | 56 +- neat-donk.lua | 2328 +++++++++++++++++++++-------------------- pool/PiratePanic.lsmv | Bin 0 -> 125377 bytes util.lua | 44 + 9 files changed, 1687 insertions(+), 1542 deletions(-) create mode 100644 .editorconfig create mode 100644 pool/PiratePanic.lsmv create mode 100644 util.lua diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..88d9686 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +[*] +indent_size = 4 +indent_style = space +charset = utf-8 +end_of_line = lf diff --git a/.gitignore b/.gitignore index 7585af4..00b85ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ -*.lsmv *.log catchem/ +state/ +crashsave* +*.backup diff --git a/config.lua b/config.lua index 6d727c6..4f2f0ac 100644 --- a/config.lua +++ b/config.lua @@ -1,69 +1,70 @@ -local _M = {} - ---[[ - Change BizhawkDir to your BizHawk directory. ---]] ---_M.BizhawkDir = "C:/Users/mmill/Downloads/BizHawk-2.2/" -_M.BizhawkDir = "X:/B2_BizHawkLab/BizHawk-2.2.2/" - -_M.StateDir = _M.BizhawkDir .. "Lua/SNES/neat-mario/state/" -_M.PoolDir = _M.BizhawkDir .. "Lua/SNES/neat-mario/pool/" - ---[[ - At the moment the first in list will get loaded. - Rearrange for other savestates. (will be redone soon) ---]] -_M.State = { - "DP1.state", -- Donut Plains 1 - "YI1.state", -- Yoshi's Island 1 - "YI2.state", -- Yoshi's Island 2 -} - ---[[ - Start game with specific powerup. - 0 = No powerup - 1 = Mushroom - 2 = Feather - 3 = Flower - Comment out to disable. ---]] -_M.StartPowerup = 0 - -_M.NeatConfig = { ---Filename = "DP1.state", -Filename = _M.PoolDir .. _M.State[1], -Population = 300, -DeltaDisjoint = 2.0, -DeltaWeights = 0.4, -DeltaThreshold = 1.0, -StaleSpecies = 15, -MutateConnectionsChance = 0.25, -PerturbChance = 0.90, -CrossoverChance = 0.75, -LinkMutationChance = 2.0, -NodeMutationChance = 0.50, -BiasMutationChance = 0.40, -StepSize = 0.1, -DisableMutationChance = 0.4, -EnableMutationChance = 0.2, -TimeoutConstant = 20, -MaxNodes = 1000000, -} - -_M.ButtonNames = { - "A", - "B", - "X", - "Y", - "Up", - "Down", - "Left", - "Right", - } - -_M.BoxRadius = 6 -_M.InputSize = (_M.BoxRadius*2+1)*(_M.BoxRadius*2+1) - -_M.Running = false - +local _M = {} + +--[[ + Change script dir to your script directory +--]] +_M.ScriptDir = "/media/removable/Main/user1000/neat-donk" + +_M.StateDir = _M.ScriptDir .. "/state/" +_M.PoolDir = _M.ScriptDir .. "/pool/" + +--[[ + At the moment the first in list will get loaded. + Rearrange for other savestates. (will be redone soon) +--]] +_M.State = { + "PiratePanic.lsmv", +} + +--[[ + Start game with specific powerup. + 0 = No powerup + 1 = Mushroom + 2 = Feather + 3 = Flower + Comment out to disable. +--]] +_M.StartPowerup = 0 + +_M.NeatConfig = { +--Filename = "DP1.state", +Filename = _M.PoolDir .. _M.State[1], +Population = 300, +DeltaDisjoint = 2.0, +DeltaWeights = 0.4, +DeltaThreshold = 1.0, +StaleSpecies = 15, +MutateConnectionsChance = 0.25, +PerturbChance = 0.90, +CrossoverChance = 0.75, +LinkMutationChance = 2.0, +NodeMutationChance = 0.50, +BiasMutationChance = 0.40, +StepSize = 0.1, +DisableMutationChance = 0.4, +EnableMutationChance = 0.2, +TimeoutConstant = 20, +MaxNodes = 1000000, +} + +_M.ButtonNames = { + "B", + "Y", + "Select", + "Start", + "Up", + "Down", + "Left", + "Right", + "A", + "X", + "L", + "R", +} + +_M.BoxRadius = 6 +_M.InputSize = (_M.BoxRadius*2+1)*(_M.BoxRadius*2+1) + +_M.Running = true + return _M \ No newline at end of file diff --git a/donkutil.lua b/donkutil.lua index 496d88b..9897037 100644 --- a/donkutil.lua +++ b/donkutil.lua @@ -1,63 +1,65 @@ +util = require "util" + +FG_COLOR = 0x00ffffff +BG_COLOR = 0x99000000 +TILEDATA_POINTER = 0x7e0098 +TILE_SIZE = 32 +TILE_RADIUS = 4 +SPRITE_BASE = 0x7e0de2 +SOLID_LESS_THAN = 0x7e00a0 +DIDDY_X_VELOCITY = 0x7e0e02 +DIDDY_Y_VELOCITY = 0x7e0e06 +DIXIE_X_VELOCITY = 0x7e0e60 +DIXIE_Y_VELOCITY = 0x7e0e64 +CAMERA_X = 0x7e17ba +CAMERA_Y = 0x7e17c0 +CAMERA_MODE = 0x7e054f +TILE_COLLISION_MATH_POINTER = 0x7e17b2 +VERTICAL_POINTER = 0xc414 +PARTY_X = 0x7e0a2a +PARTY_Y = 0x7e0a2c + count = 0 detailsidx = -1 +jumping = false helddown = false floatmode = false +rulers = true pokemon = false pokecount = 0 showhelp = false locked = false lockdata = nil incsprite = 0 -fgcolor = 0x00ffffff -bgcolor = 0x99000000 -function table_to_string(tbl) - local result = "{" - local keys = {} - for k in pairs(tbl) do - table.insert(keys, k) - end - table.sort(keys) - for _, k in ipairs(keys) do - local v = tbl[k] - if type(v) == "number" and v == 0 then - goto continue - end +party_tile_offset = 0 +party_y_ground = 0 - -- Check the key type (ignore any numerical keys - assume its an array) - if type(k) == "string" then - result = result.."[\""..k.."\"]".."=" - end +last_called = 0 +function set_party_tile_offset (val) + if party_tile_offset_debounce == val then + return + end + local sec, usec = utime() + last_called = sec * 1000000 + usec + party_tile_offset_debounce = val +end - -- Check the value type - if type(v) == "table" then - result = result..table_to_string(v) - elseif type(v) == "boolean" then - result = result..tostring(v) - else - result = result.."\""..v.."\"" - end - result = result..",\n" - ::continue:: - end - -- Remove leading commas from the result - if result ~= "" then - result = result:sub(1, result:len()-1) - end - return result.."}" +function text(x, y, msg) + gui.text(x, y, msg, FG_COLOR, BG_COLOR) end function on_keyhook (key, state) - if not helddown and state["value"] == 1 then + if not helddown and state.value == 1 then if key == "1" and not locked then helddown = true detailsidx = detailsidx - 1 if detailsidx < -1 then - detailsidx = 20 + detailsidx = 22 end elseif key == "2" and not locked then helddown = true detailsidx = detailsidx + 1 - if detailsidx > 20 then + if detailsidx > 22 then detailsidx = -1 end elseif key == "3" then @@ -80,89 +82,90 @@ function on_keyhook (key, state) elseif key == "7" then helddown = true floatmode = not floatmode + elseif key == "8" then + helddown = true + rulers = not rulers elseif key == "0" then showhelp = true end - elseif state["value"] == 0 then + elseif state.value == 0 then helddown = false showhelp = false end end function on_input (subframe) + jumping = input.get(0,0) ~= 0 + if floatmode then memory.writebyte(0x7e19ce, 0x16) memory.writebyte(0x7e0e12, 0x99) memory.writebyte(0x7e0e70, 0x99) if input.get(0, 6) == 1 then - memory.writeword(0x7e0e02, -0x5ff) - memory.writeword(0x7e0e60, -0x5ff) + memory.writeword(DIDDY_X_VELOCITY, -0x5ff) + memory.writeword(DIXIE_X_VELOCITY, -0x5ff) - memory.writeword(0x7e0e06, 0) - memory.writeword(0x7e0e64, 0) + memory.writeword(DIDDY_Y_VELOCITY, 0) + memory.writeword(DIXIE_Y_VELOCITY, 0) elseif input.get(0, 7) == 1 then - memory.writeword(0x7e0e02, 0x5ff) - memory.writeword(0x7e0e60, 0x5ff) + memory.writeword(DIDDY_X_VELOCITY, 0x5ff) + memory.writeword(DIXIE_X_VELOCITY, 0x5ff) - memory.writeword(0x7e0e06, 0) - memory.writeword(0x7e0e64, 0) + memory.writeword(DIDDY_Y_VELOCITY, 0) + memory.writeword(DIXIE_Y_VELOCITY, 0) end if input.get(0, 4) == 1 then - memory.writeword(0x7e0e06, -0x05ff) - memory.writeword(0x7e0e64, -0x05ff) + memory.writeword(DIDDY_Y_VELOCITY, -0x05ff) + memory.writeword(DIXIE_Y_VELOCITY, -0x05ff) elseif input.get(0, 5) == 1 then - memory.writeword(0x7e0e06, 0x5ff) - memory.writeword(0x7e0e64, 0x5ff) + memory.writeword(DIDDY_Y_VELOCITY, 0x5ff) + memory.writeword(DIXIE_Y_VELOCITY, 0x5ff) end end end -function file_exists(name) - local f=io.open(name,"r") - if f~=nil then io.close(f) return true else return false end -end - function get_sprite(base_addr) return { - ["control"] = memory.readword(base_addr), - ["draworder"] = memory.readword(base_addr + 0x02), - ["x"] = memory.readword(base_addr + 0x06), - ["y"] = memory.readword(base_addr + 0x0a), - ["jumpheight"] = memory.readword(base_addr + 0x0e), - ["style"] = memory.readword(base_addr + 0x12), - ["currentframe"] = memory.readword(base_addr + 0x18), - ["nextframe"] = memory.readword(base_addr + 0x1a), - ["state"] = memory.readword(base_addr + 0x1e), - ["velox"] = memory.readsword(base_addr + 0x20), - ["veloy"] = memory.readsword(base_addr + 0x24), - ["velomaxx"] = memory.readsword(base_addr + 0x26), - ["velomaxy"] = memory.readsword(base_addr + 0x2a), - ["motion"] = memory.readword(base_addr + 0x2e), - ["attr"] = memory.readword(base_addr + 0x30), - ["animnum"] = memory.readword(base_addr + 0x36), - ["remainingframe"] = memory.readword(base_addr + 0x38), - ["animcontrol"] = memory.readword(base_addr + 0x3a), - ["animreadpos"] = memory.readword(base_addr + 0x3c), - ["animcontrol2"] = memory.readword(base_addr + 0x3e), - ["animformat"] = memory.readword(base_addr + 0x40), - ["damage1"] = memory.readword(base_addr + 0x44), - ["damage2"] = memory.readword(base_addr + 0x46), - ["damage3"] = memory.readword(base_addr + 0x48), - ["damage4"] = memory.readword(base_addr + 0x4a), - ["damage5"] = memory.readword(base_addr + 0x4c), - ["damage6"] = memory.readword(base_addr + 0x4e), - ["spriteparam"] = memory.readword(base_addr + 0x58), + base_addr = string.format("%04x", base_addr), + control = memory.readword(base_addr), + draworder = memory.readword(base_addr + 0x02), + x = memory.readword(base_addr + 0x06), + y = memory.readword(base_addr + 0x0a), + jumpheight = memory.readword(base_addr + 0x0e), + style = memory.readword(base_addr + 0x12), + currentframe = memory.readword(base_addr + 0x18), + nextframe = memory.readword(base_addr + 0x1a), + state = memory.readword(base_addr + 0x1e), + velox = memory.readsword(base_addr + 0x20), + veloy = memory.readsword(base_addr + 0x24), + velomaxx = memory.readsword(base_addr + 0x26), + velomaxy = memory.readsword(base_addr + 0x2a), + motion = memory.readword(base_addr + 0x2e), + attr = memory.readword(base_addr + 0x30), + animnum = memory.readword(base_addr + 0x36), + remainingframe = memory.readword(base_addr + 0x38), + animcontrol = memory.readword(base_addr + 0x3a), + animreadpos = memory.readword(base_addr + 0x3c), + animcontrol2 = memory.readword(base_addr + 0x3e), + animformat = memory.readword(base_addr + 0x40), + damage1 = memory.readword(base_addr + 0x44), + damage2 = memory.readword(base_addr + 0x46), + damage3 = memory.readword(base_addr + 0x48), + damage4 = memory.readword(base_addr + 0x4a), + damage5 = memory.readword(base_addr + 0x4c), + damage6 = memory.readword(base_addr + 0x4e), + spriteparam = memory.readword(base_addr + 0x58), } end function sprite_details(idx) - local base_addr = idx * 94 + 0x7e0e9e + local base_addr = idx * 94 + SPRITE_BASE local sprite = get_sprite(base_addr) - if sprite["control"] == 0 then - gui.text(0, 0, "Sprite "..idx.." (Empty)", fgcolor, bgcolor) + if sprite.control == 0 then + text(0, 0, "Sprite "..idx.." (Empty)") incsprite = 0 locked = false lockdata = nil @@ -170,7 +173,7 @@ function sprite_details(idx) end if incsprite ~= 0 then - memory.writeword(base_addr + 0x36, sprite["animnum"] + incsprite) + memory.writeword(base_addr + 0x36, sprite.animnum + incsprite) lockdata = nil incsprite = 0 @@ -184,7 +187,7 @@ function sprite_details(idx) memory.writeregion(base_addr, 94, lockdata) end - gui.text(0, 0, "Sprite "..idx..(locked and " (Locked)" or "")..":\n\n"..table_to_string(sprite), fgcolor, bgcolor) + text(0, 0, "Sprite "..idx..(locked and " (Locked)" or "")..":\n\n"..util.table_to_string(sprite)) end function on_paint (not_synth) @@ -193,7 +196,7 @@ function on_paint (not_synth) local guiWidth, guiHeight = gui.resolution() if showhelp then - gui.text(0, 0, [[ + text(0, 0, [[ Keyboard Help =============== @@ -207,70 +210,156 @@ Sprite Details: [6] Enable / Disable Pokemon mode (take screenshots of enemies) [7] Enable / Disable float mode (fly with up/down) -]], fgcolor, bgcolor) +[8] Enable / Disable stage tile rulers +]]) return end - gui.text(guiWidth - 75, 0, "Help [0]", fgcolor, bgcolor) - - local stats = "" + local toggles = "" if pokemon then - stats = stats.."Pokemon: "..pokecount.."\n" + toggles = toggles..string.format("Pokemon: %d\n", pokecount) end if floatmode then - stats = stats.."Float on\n" + toggles = toggles.."Float on\n" end - gui.text(0, guiHeight - 40, stats, fgcolor, bgcolor) + text(0, guiHeight - 40, toggles) - stats = stats.."\nPokemon: "..pokecount + local directions = { + "Standard", + "Blur", + "Up" + } - 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 + local cameraDir = memory.readbyte(CAMERA_MODE) - local partyScreenX = (memory.readword(0x7e0a2a) - 256 - cameraX) * 2 - local partyScreenY = (memory.readword(0x7e0a2c) - 256 - cameraY) * 2 + local direction = directions[cameraDir+1] - if detailsidx ~= -1 then - sprite_details(detailsidx) - else - gui.text(0, 0, "[1] <- Sprite Details Off -> [2]", fgcolor, bgcolor) - end + local vertical = memory.readword(TILE_COLLISION_MATH_POINTER) == VERTICAL_POINTER - gui.text(guiWidth - 200, guiHeight - 20, "Camera: "..tostring(cameraX)..","..tostring(cameraY), fgcolor, bgcolor) + local stats = string.format([[ +%s camera %d,%d +Vertical: %s +Tile offset: %04x +]], direction, cameraX, cameraY, vertical, party_tile_offset) - gui.text(partyScreenX, partyScreenY, "Party", fgcolor, bgcolor) + text(guiWidth - 200, guiHeight - 60, stats) + + local partyX = memory.readword(PARTY_X) - 256 + local partyY = memory.readword(PARTY_Y) - 256 + + text((partyX - cameraX) * 2, (partyY - cameraY) * 2 + 20, "Party") local sprites = {} - for idx = 0,20,1 do - local base_addr = idx * 94 + 0x7e0e9e + for idx = 0,22,1 do + local base_addr = idx * 94 + SPRITE_BASE local sprite = get_sprite(base_addr) sprites[idx] = sprite - if sprite["control"] == 0 then + if sprite.control == 0 then goto continue end - local spriteScreenX = (sprite["x"] - 256 - cameraX) * 2 - local spriteScreenY = (sprite["y"] - 256 - cameraY) * 2 + local spriteScreenX = (sprite.x - 256 - cameraX) * 2 + local spriteScreenY = (sprite.y - 256 - cameraY) * 2 - local sprcolor = bgcolor + local sprcolor = BG_COLOR if detailsidx == idx then sprcolor = 0x00ff0000 end - gui.text(spriteScreenX, spriteScreenY, sprite["animnum"]..","..sprite["attr"], fgcolor, sprcolor) + gui.text(spriteScreenX, spriteScreenY, sprite.control..","..sprite.animnum..","..sprite.attr, FG_COLOR, sprcolor) - local filename = os.getenv("HOME").."/neat-donk/catchem/"..sprite["animnum"]..","..sprite["attr"]..".png" - if pokemon and spriteScreenX > (guiWidth / 4) and spriteScreenX < (guiWidth / 4) * 3 and spriteScreenY > (guiHeight / 3) and spriteScreenY < guiHeight and not file_exists(filename) then + local filename = os.getenv("HOME").."/neat-donk/catchem/"..sprite.animnum..","..sprite.attr..".png" + if pokemon and spriteScreenX > (guiWidth / 4) and spriteScreenX < (guiWidth / 4) * 3 and spriteScreenY > (guiHeight / 3) and spriteScreenY < guiHeight and not util.file_exists(filename) then gui.screenshot(filename) pokecount = pokecount + 1 end ::continue:: end + + if rulers and cameraX >= 0 then + local halfWidth = math.floor(guiWidth / 2) + local halfHeight = math.floor(guiHeight / 2) + + local cameraTileX = math.floor(cameraX / TILE_SIZE) + gui.line(0, halfHeight, guiWidth, halfHeight, BG_COLOR) + for i = cameraTileX, cameraTileX + guiWidth / TILE_SIZE / 2,1 do + gui.text((i * TILE_SIZE - cameraX) * 2, halfHeight, tostring(i), FG_COLOR, BG_COLOR) + end + + local cameraTileY = math.floor(cameraY / TILE_SIZE) + gui.line(halfWidth, 0, halfWidth, guiHeight, BG_COLOR) + for i = cameraTileY, cameraTileY + guiHeight / TILE_SIZE / 2,1 do + gui.text(halfWidth, (i * TILE_SIZE - cameraY) * 2, tostring(i), FG_COLOR, BG_COLOR) + end + end + + local tilePtr = memory.readhword(TILEDATA_POINTER) + local solidLessThan = memory.readword(SOLID_LESS_THAN) + + for x = -TILE_RADIUS, TILE_RADIUS, 1 do + for y = -TILE_RADIUS, TILE_RADIUS, 1 do + local offset = 0 + if vertical then + offset = party_tile_offset + (y * 24 + x) * 2 + else + offset = party_tile_offset + (x * 16 + y) * 2 + end + + local tile = memory.readword(tilePtr + offset) + + if tile == 0 or tile >= solidLessThan then + goto continue + end + + local tileX = (math.floor(partyX / TILE_SIZE + x) * TILE_SIZE - cameraX) + local tileY = (math.floor(party_y_ground / TILE_SIZE + y) * TILE_SIZE - cameraY) + gui.text(tileX * 2, tileY * 2, string.format("%04x,%02x", offset & 0xffff, tile), FG_COLOR, 0x66888800) + + ::continue:: + end + end + + + + if detailsidx ~= -1 then + sprite_details(detailsidx) + else + text(0, 20, "[1] <- Sprite Details Off -> [2]") + end + + text(guiWidth - 125, 20, "Help [Hold 0]") +end + +function tile_retrieval() + local tile = math.floor(memory.getregister("y") / 2) * 2 + local newX = memory.readword(0x7e00a6) + local partyX = memory.readword(PARTY_X) + local oldX = partyX & 0x1f + local partyY = memory.readword(PARTY_Y) + + if oldX - 5 < newX and newX < oldX + 5 and + not jumping and + memory.readword(0x7e0034) == partyY then + set_party_tile_offset(tile) + party_y_ground = partyY - 256 + end +end + +function on_timer() + local sec, usec = utime() + local now = sec * 1000000 + usec + if last_called + 100 * 1000 < now then + party_tile_offset = party_tile_offset_debounce + end + + set_timer_timeout(100 * 1000) end input.keyhook("1", true) @@ -280,4 +369,9 @@ input.keyhook("4", true) input.keyhook("5", true) input.keyhook("6", true) input.keyhook("7", true) -input.keyhook("0", true) \ No newline at end of file +input.keyhook("8", true) +input.keyhook("0", true) + +memory2.BUS:registerexec(TILE_RETRIEVAL, tile_retrieval) + +set_timer_timeout(100 * 1000) \ No newline at end of file diff --git a/game.lua b/game.lua index 3cf43e0..0fa173f 100644 --- a/game.lua +++ b/game.lua @@ -1,164 +1,161 @@ ---Notes here -config = require "config" -spritelist = require "spritelist" -local _M = {} - -function _M.getPositions() - partyX = memory.readword(0x7e0a2a) - 256 - partyY = memory.readword(0x7e0a2c) - 256 - - local cameraX = memory.readword(0x7e17ba) - 256 - local cameraY = memory.readword(0x7e17c0) - 256 - - _M.screenX = partyX-layer1x - _M.screenY = partyY-layer1y -end - -function _M.getBananas() - local bananas = memory.readword(0x7e08bc) - return bananas -end - -function _M.getCoins() - local coins = memory.readword(0x7e08ca) - return coins -end - -function _M.getLives() - local lives = memory.readsbyte(0x7e08be) + 1 - return lives -end - -function _M.writeLives(lives) - memory.writebyte(0x7e08be, lives - 1) - memory.writebyte(0x7e08c0, lives - 1) -end - -function _M.getPowerup() - return 0 -end - -function _M.writePowerup(powerup) - return - -- memory.writebyte(0x0019, powerup) -end - - -function _M.getHit(alreadyHit) - return not alreadyHit and memory.readbyte(0x7e08be) < memory.readbyte(0x7e08c0) -end - -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 - - 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) -end - -function _M.getSprites() - local sprites = {} - for slot=0,11 do - local status = memory.readbyte(0x14C8+slot) - if status ~= 0 then - spritex = memory.readbyte(0xE4+slot) + memory.readbyte(0x14E0+slot)*256 - spritey = memory.readbyte(0xD8+slot) + memory.readbyte(0x14D4+slot)*256 - sprites[#sprites+1] = {["x"]=spritex, ["y"]=spritey, ["good"] = spritelist.Sprites[memory.readbyte(0x009e + slot) + 1]} - end - end - - return sprites -end - -function _M.getExtendedSprites() - local extended = {} - for slot=0,11 do - local number = memory.readbyte(0x170B+slot) - if number ~= 0 then - spritex = memory.readbyte(0x171F+slot) + memory.readbyte(0x1733+slot)*256 - spritey = memory.readbyte(0x1715+slot) + memory.readbyte(0x1729+slot)*256 - extended[#extended+1] = {["x"]=spritex, ["y"]=spritey, ["good"] = spritelist.extSprites[memory.readbyte(0x170B + slot) + 1]} - end - end - - return extended -end - -function _M.getInputs() - _M.getPositions() - - sprites = _M.getSprites() - extended = _M.getExtendedSprites() - - local inputs = {} - local inputDeltaDistance = {} - - local layer1x = memory.read_s16_le(0x1A); - 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 - inputs[#inputs+1] = 0 - inputDeltaDistance[#inputDeltaDistance+1] = 1 - - tile = _M.getTile(dx, dy) - if tile == 1 and partyY+dy < 0x1B0 then - inputs[#inputs] = 1 - end - - 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 - inputs[#inputs] = sprites[i]["good"] - - local dist = math.sqrt((distx * distx) + (disty * disty)) - if dist > 8 then - inputDeltaDistance[#inputDeltaDistance] = mathFunctions.squashDistance(dist) - --gui.drawLine(screenX, screenY, sprites[i]["x"] - layer1x, sprites[i]["y"] - layer1y, 0x50000000) - end - end - end - - 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) - inputs[#inputs] = extended[i]["good"] - local dist = math.sqrt((distx * distx) + (disty * disty)) - if dist > 8 then - inputDeltaDistance[#inputDeltaDistance] = mathFunctions.squashDistance(dist) - --gui.drawLine(screenX, screenY, extended[i]["x"] - layer1x, extended[i]["y"] - layer1y, 0x50000000) - end - --if dist > 100 then - --dw = mathFunctions.squashDistance(dist) - --console.writeline(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 - - return inputs, inputDeltaDistance -end - -function _M.clearJoypad() - controller = {} - for b = 1,#config.ButtonNames do - controller["P1 " .. config.ButtonNames[b]] = false - end - joypad.set(controller) -end - -return _M +--Notes here +config = require "config" +spritelist = require "spritelist" +local _M = {} + +function _M.getPositions() + partyX = memory.readword(0x7e0a2a) - 256 + partyY = memory.readword(0x7e0a2c) - 256 + + local cameraX = memory.readword(0x7e17ba) - 256 + local cameraY = memory.readword(0x7e17c0) - 256 + + _M.screenX = (partyX-cameraX)*2 + _M.screenY = (partyY-cameraY)*2 +end + +function _M.getBananas() + local bananas = memory.readword(0x7e08bc) + return bananas +end + +function _M.getCoins() + local coins = memory.readword(0x7e08ca) + return coins +end + +function _M.getLives() + local lives = memory.readsbyte(0x7e08be) + 1 + return lives +end + +function _M.writeLives(lives) + memory.writebyte(0x7e08be, lives - 1) + memory.writebyte(0x7e08c0, lives - 1) +end + +function _M.getPowerup() + return 0 +end + +function _M.writePowerup(powerup) + return + -- memory.writebyte(0x0019, powerup) +end + + +function _M.getHit(alreadyHit) + return not alreadyHit and memory.readbyte(0x7e08be) < memory.readbyte(0x7e08c0) +end + +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 + + 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) +end + +function _M.getSprites() + local sprites = {} + for slot=0,11 do + local status = memory.readbyte(0x14C8+slot) + if status ~= 0 then + spritex = memory.readbyte(0xE4+slot) + memory.readbyte(0x14E0+slot)*256 + spritey = memory.readbyte(0xD8+slot) + memory.readbyte(0x14D4+slot)*256 + sprites[#sprites+1] = {["x"]=spritex, ["y"]=spritey, ["good"] = spritelist.Sprites[memory.readbyte(0x009e + slot) + 1]} + end + end + + return sprites +end + +function _M.getExtendedSprites() + local extended = {} + for slot=0,11 do + local number = memory.readbyte(0x170B+slot) + if number ~= 0 then + spritex = memory.readbyte(0x171F+slot) + memory.readbyte(0x1733+slot)*256 + spritey = memory.readbyte(0x1715+slot) + memory.readbyte(0x1729+slot)*256 + extended[#extended+1] = {["x"]=spritex, ["y"]=spritey, ["good"] = spritelist.extSprites[memory.readbyte(0x170B + slot) + 1]} + end + end + + return extended +end + +function _M.getInputs() + _M.getPositions() + + 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 + inputs[#inputs+1] = 0 + inputDeltaDistance[#inputDeltaDistance+1] = 1 + + tile = _M.getTile(dx, dy) + if tile == 1 and partyY+dy < 0x1B0 then + inputs[#inputs] = 1 + end + + 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 + inputs[#inputs] = sprites[i]["good"] + + local dist = math.sqrt((distx * distx) + (disty * disty)) + if dist > 8 then + inputDeltaDistance[#inputDeltaDistance] = mathFunctions.squashDistance(dist) + --gui.drawLine(screenX, screenY, sprites[i]["x"] - layer1x, sprites[i]["y"] - layer1y, 0x50000000) + end + end + end + + 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) + inputs[#inputs] = extended[i]["good"] + local dist = math.sqrt((distx * distx) + (disty * disty)) + if dist > 8 then + inputDeltaDistance[#inputDeltaDistance] = mathFunctions.squashDistance(dist) + --gui.drawLine(screenX, screenY, extended[i]["x"] - layer1x, extended[i]["y"] - layer1y, 0x50000000) + end + --if dist > 100 then + --dw = mathFunctions.squashDistance(dist) + --console.writeline(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 + + return inputs, inputDeltaDistance +end + +function _M.clearJoypad() + for b = 1,#config.ButtonNames do + input.set(0, b - 1, 0) + end +end + +return _M diff --git a/mathFunctions.lua b/mathFunctions.lua index e9c9823..8870847 100644 --- a/mathFunctions.lua +++ b/mathFunctions.lua @@ -1,29 +1,29 @@ ---Notes here - -local _M = {} - -function _M.sigmoid(x) - return 2/(1+math.exp(-4.9*x))-1 -end - -function _M.squashDistance(x) - local window = 0.20 - local delta = 0.25 - - local dist = (x-8) - local newDist = 1 - - while dist > 0 do - newDist = newDist - (window*delta) - dist = dist - 1 - end - - if newDist < 0.80 then - newDist = 0.80 - end - - return newDist -end - - +--Notes here + +local _M = {} + +function _M.sigmoid(x) + return 2/(1+math.exp(-4.9*x))-1 +end + +function _M.squashDistance(x) + local window = 0.20 + local delta = 0.25 + + local dist = (x-8) + local newDist = 1 + + while dist > 0 do + newDist = newDist - (window*delta) + dist = dist - 1 + end + + if newDist < 0.80 then + newDist = 0.80 + end + + return newDist +end + + return _M \ No newline at end of file diff --git a/neat-donk.lua b/neat-donk.lua index ef36548..c1fdbcd 100644 --- a/neat-donk.lua +++ b/neat-donk.lua @@ -1,1163 +1,1165 @@ ---Update to Seth-Bling's MarI/O app - -config = require "config" -spritelist = require "spritelist" -game = require "game" -mathFunctions = require "mathFunctions" - -Inputs = config.InputSize+1 -Outputs = #config.ButtonNames - -function newInnovation() - pool.innovation = pool.innovation + 1 - return pool.innovation -end - -function newPool() - local pool = {} - pool.species = {} - pool.generation = 0 - pool.innovation = Outputs - pool.currentSpecies = 1 - pool.currentGenome = 1 - pool.currentFrame = 0 - pool.maxFitness = 0 - - return pool -end - -function newSpecies() - local species = {} - species.topFitness = 0 - species.staleness = 0 - species.genomes = {} - species.averageFitness = 0 - - return species -end - -function newGenome() - local genome = {} - genome.genes = {} - genome.fitness = 0 - genome.adjustedFitness = 0 - genome.network = {} - genome.maxneuron = 0 - genome.globalRank = 0 - genome.mutationRates = {} - genome.mutationRates["connections"] = config.NeatConfig.MutateConnectionsChance - genome.mutationRates["link"] = config.NeatConfig.LinkMutationChance - genome.mutationRates["bias"] = config.NeatConfig.BiasMutationChance - genome.mutationRates["node"] = config.NeatConfig.NodeMutationChance - genome.mutationRates["enable"] = config.NeatConfig.EnableMutationChance - genome.mutationRates["disable"] = config.NeatConfig.DisableMutationChance - genome.mutationRates["step"] = config.NeatConfig.StepSize - - return genome -end - -function copyGenome(genome) - local genome2 = newGenome() - for g=1,#genome.genes do - table.insert(genome2.genes, copyGene(genome.genes[g])) - end - genome2.maxneuron = genome.maxneuron - genome2.mutationRates["connections"] = genome.mutationRates["connections"] - genome2.mutationRates["link"] = genome.mutationRates["link"] - genome2.mutationRates["bias"] = genome.mutationRates["bias"] - genome2.mutationRates["node"] = genome.mutationRates["node"] - genome2.mutationRates["enable"] = genome.mutationRates["enable"] - genome2.mutationRates["disable"] = genome.mutationRates["disable"] - - return genome2 -end - -function basicGenome() - local genome = newGenome() - local innovation = 1 - - genome.maxneuron = Inputs - mutate(genome) - - return genome -end - -function newGene() - local gene = {} - gene.into = 0 - gene.out = 0 - gene.weight = 0.0 - gene.enabled = true - gene.innovation = 0 - - return gene -end - -function copyGene(gene) - local gene2 = newGene() - gene2.into = gene.into - gene2.out = gene.out - gene2.weight = gene.weight - gene2.enabled = gene.enabled - gene2.innovation = gene.innovation - - return gene2 -end - -function newNeuron() - local neuron = {} - neuron.incoming = {} - neuron.value = 0.0 - --neuron.dw = 1 - return neuron -end - -function generateNetwork(genome) - local network = {} - network.neurons = {} - - for i=1,Inputs do - network.neurons[i] = newNeuron() - end - - for o=1,Outputs do - network.neurons[config.NeatConfig.MaxNodes+o] = newNeuron() - end - - table.sort(genome.genes, function (a,b) - return (a.out < b.out) - end) - for i=1,#genome.genes do - local gene = genome.genes[i] - if gene.enabled then - if network.neurons[gene.out] == nil then - network.neurons[gene.out] = newNeuron() - end - local neuron = network.neurons[gene.out] - table.insert(neuron.incoming, gene) - if network.neurons[gene.into] == nil then - network.neurons[gene.into] = newNeuron() - end - end - end - - genome.network = network -end - -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.") - 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 - local sum = 0 - for j = 1,#neuron.incoming do - local incoming = neuron.incoming[j] - local other = network.neurons[incoming.into] - sum = sum + incoming.weight * other.value - end - - if #neuron.incoming > 0 then - neuron.value = mathFunctions.sigmoid(sum) - end - end - - local outputs = {} - for o=1,Outputs do - local button = "P1 " .. config.ButtonNames[o] - if network.neurons[config.NeatConfig.MaxNodes+o].value > 0 then - outputs[button] = true - else - outputs[button] = false - end - end - - return outputs -end - -function crossover(g1, g2) - -- Make sure g1 is the higher fitness genome - if g2.fitness > g1.fitness then - tempg = g1 - g1 = g2 - g2 = tempg - end - - local child = newGenome() - - local innovations2 = {} - for i=1,#g2.genes do - local gene = g2.genes[i] - innovations2[gene.innovation] = gene - end - - for i=1,#g1.genes do - local gene1 = g1.genes[i] - local gene2 = innovations2[gene1.innovation] - if gene2 ~= nil and math.random(2) == 1 and gene2.enabled then - table.insert(child.genes, copyGene(gene2)) - else - table.insert(child.genes, copyGene(gene1)) - end - end - - child.maxneuron = math.max(g1.maxneuron,g2.maxneuron) - - for mutation,rate in pairs(g1.mutationRates) do - child.mutationRates[mutation] = rate - end - - return child -end - -function randomNeuron(genes, nonInput) - local neurons = {} - if not nonInput then - for i=1,Inputs do - neurons[i] = true - end - end - for o=1,Outputs do - neurons[config.NeatConfig.MaxNodes+o] = true - end - for i=1,#genes do - if (not nonInput) or genes[i].into > Inputs then - neurons[genes[i].into] = true - end - if (not nonInput) or genes[i].out > Inputs then - neurons[genes[i].out] = true - end - end - - local count = 0 - for _,_ in pairs(neurons) do - count = count + 1 - end - local n = math.random(1, count) - - for k,v in pairs(neurons) do - n = n-1 - if n == 0 then - return k - end - end - - return 0 -end - -function containsLink(genes, link) - for i=1,#genes do - local gene = genes[i] - if gene.into == link.into and gene.out == link.out then - return true - end - end -end - -function pointMutate(genome) - local step = genome.mutationRates["step"] - - for i=1,#genome.genes do - local gene = genome.genes[i] - if math.random() < config.NeatConfig.PerturbChance then - gene.weight = gene.weight + math.random() * step*2 - step - else - gene.weight = math.random()*4-2 - end - end -end - -function linkMutate(genome, forceBias) - local neuron1 = randomNeuron(genome.genes, false) - local neuron2 = randomNeuron(genome.genes, true) - - local newLink = newGene() - if neuron1 <= Inputs and neuron2 <= Inputs then - --Both input nodes - return - end - if neuron2 <= Inputs then - -- Swap output and input - local temp = neuron1 - neuron1 = neuron2 - neuron2 = temp - end - - newLink.into = neuron1 - newLink.out = neuron2 - if forceBias then - newLink.into = Inputs - end - - if containsLink(genome.genes, newLink) then - return - end - newLink.innovation = newInnovation() - newLink.weight = math.random()*4-2 - - table.insert(genome.genes, newLink) -end - -function nodeMutate(genome) - if #genome.genes == 0 then - return - end - - genome.maxneuron = genome.maxneuron + 1 - - local gene = genome.genes[math.random(1,#genome.genes)] - if not gene.enabled then - return - end - gene.enabled = false - - local gene1 = copyGene(gene) - gene1.out = genome.maxneuron - gene1.weight = 1.0 - gene1.innovation = newInnovation() - gene1.enabled = true - table.insert(genome.genes, gene1) - - local gene2 = copyGene(gene) - gene2.into = genome.maxneuron - gene2.innovation = newInnovation() - gene2.enabled = true - table.insert(genome.genes, gene2) -end - -function enableDisableMutate(genome, enable) - local candidates = {} - for _,gene in pairs(genome.genes) do - if gene.enabled == not enable then - table.insert(candidates, gene) - end - end - - if #candidates == 0 then - return - end - - local gene = candidates[math.random(1,#candidates)] - gene.enabled = not gene.enabled -end - -function mutate(genome) - for mutation,rate in pairs(genome.mutationRates) do - if math.random(1,2) == 1 then - genome.mutationRates[mutation] = 0.95*rate - else - genome.mutationRates[mutation] = 1.05263*rate - end - end - - if math.random() < genome.mutationRates["connections"] then - pointMutate(genome) - end - - local p = genome.mutationRates["link"] - while p > 0 do - if math.random() < p then - linkMutate(genome, false) - end - p = p - 1 - end - - p = genome.mutationRates["bias"] - while p > 0 do - if math.random() < p then - linkMutate(genome, true) - end - p = p - 1 - end - - p = genome.mutationRates["node"] - while p > 0 do - if math.random() < p then - nodeMutate(genome) - end - p = p - 1 - end - - p = genome.mutationRates["enable"] - while p > 0 do - if math.random() < p then - enableDisableMutate(genome, true) - end - p = p - 1 - end - - p = genome.mutationRates["disable"] - while p > 0 do - if math.random() < p then - enableDisableMutate(genome, false) - end - p = p - 1 - end -end - -function disjoint(genes1, genes2) - local i1 = {} - for i = 1,#genes1 do - local gene = genes1[i] - i1[gene.innovation] = true - end - - local i2 = {} - for i = 1,#genes2 do - local gene = genes2[i] - i2[gene.innovation] = true - end - - local disjointGenes = 0 - for i = 1,#genes1 do - local gene = genes1[i] - if not i2[gene.innovation] then - disjointGenes = disjointGenes+1 - end - end - - for i = 1,#genes2 do - local gene = genes2[i] - if not i1[gene.innovation] then - disjointGenes = disjointGenes+1 - end - end - - local n = math.max(#genes1, #genes2) - - return disjointGenes / n -end - -function weights(genes1, genes2) - local i2 = {} - for i = 1,#genes2 do - local gene = genes2[i] - i2[gene.innovation] = gene - end - - local sum = 0 - local coincident = 0 - for i = 1,#genes1 do - local gene = genes1[i] - if i2[gene.innovation] ~= nil then - local gene2 = i2[gene.innovation] - sum = sum + math.abs(gene.weight - gene2.weight) - coincident = coincident + 1 - end - end - - return sum / coincident -end - -function sameSpecies(genome1, genome2) - local dd = config.NeatConfig.DeltaDisjoint*disjoint(genome1.genes, genome2.genes) - local dw = config.NeatConfig.DeltaWeights*weights(genome1.genes, genome2.genes) - return dd + dw < config.NeatConfig.DeltaThreshold -end - -function rankGlobally() - local global = {} - for s = 1,#pool.species do - local species = pool.species[s] - for g = 1,#species.genomes do - table.insert(global, species.genomes[g]) - end - end - table.sort(global, function (a,b) - return (a.fitness < b.fitness) - end) - - for g=1,#global do - global[g].globalRank = g - end -end - -function calculateAverageFitness(species) - local total = 0 - - for g=1,#species.genomes do - local genome = species.genomes[g] - total = total + genome.globalRank - end - - species.averageFitness = total / #species.genomes -end - -function totalAverageFitness() - local total = 0 - for s = 1,#pool.species do - local species = pool.species[s] - total = total + species.averageFitness - end - - return total -end - -function cullSpecies(cutToOne) - for s = 1,#pool.species do - local species = pool.species[s] - - table.sort(species.genomes, function (a,b) - return (a.fitness > b.fitness) - end) - - local remaining = math.ceil(#species.genomes/2) - if cutToOne then - remaining = 1 - end - while #species.genomes > remaining do - table.remove(species.genomes) - end - end -end - -function breedChild(species) - local child = {} - if math.random() < config.NeatConfig.CrossoverChance then - g1 = species.genomes[math.random(1, #species.genomes)] - g2 = species.genomes[math.random(1, #species.genomes)] - child = crossover(g1, g2) - else - g = species.genomes[math.random(1, #species.genomes)] - child = copyGenome(g) - end - - mutate(child) - - return child -end - -function removeStaleSpecies() - local survived = {} - - for s = 1,#pool.species do - local species = pool.species[s] - - table.sort(species.genomes, function (a,b) - return (a.fitness > b.fitness) - end) - - if species.genomes[1].fitness > species.topFitness then - species.topFitness = species.genomes[1].fitness - species.staleness = 0 - else - species.staleness = species.staleness + 1 - end - if species.staleness < config.NeatConfig.StaleSpecies or species.topFitness >= pool.maxFitness then - table.insert(survived, species) - end - end - - pool.species = survived -end - -function removeWeakSpecies() - local survived = {} - - local sum = totalAverageFitness() - for s = 1,#pool.species do - local species = pool.species[s] - breed = math.floor(species.averageFitness / sum * config.NeatConfig.Population) - if breed >= 1 then - table.insert(survived, species) - end - end - - pool.species = survived -end - - -function addToSpecies(child) - local foundSpecies = false - for s=1,#pool.species do - local species = pool.species[s] - if not foundSpecies and sameSpecies(child, species.genomes[1]) then - table.insert(species.genomes, child) - foundSpecies = true - end - end - - if not foundSpecies then - local childSpecies = newSpecies() - table.insert(childSpecies.genomes, child) - table.insert(pool.species, childSpecies) - end -end - -function newGeneration() - cullSpecies(false) -- Cull the bottom half of each species - rankGlobally() - removeStaleSpecies() - rankGlobally() - for s = 1,#pool.species do - local species = pool.species[s] - calculateAverageFitness(species) - end - removeWeakSpecies() - local sum = totalAverageFitness() - local children = {} - for s = 1,#pool.species do - local species = pool.species[s] - breed = math.floor(species.averageFitness / sum * config.NeatConfig.Population) - 1 - for i=1,breed do - table.insert(children, breedChild(species)) - end - end - cullSpecies(true) -- Cull all but the top member of each species - while #children + #pool.species < config.NeatConfig.Population do - local species = pool.species[math.random(1, #pool.species)] - table.insert(children, breedChild(species)) - end - for c=1,#children do - local child = children[c] - addToSpecies(child) - end - - pool.generation = pool.generation + 1 - - --writeFile("backup." .. pool.generation .. "." .. forms.gettext(saveLoadFile)) - writeFile(forms.gettext(saveLoadFile) .. ".gen" .. pool.generation .. ".pool") -end - -function initializePool() - pool = newPool() - - for i=1,config.NeatConfig.Population do - basic = basicGenome() - addToSpecies(basic) - end - - initializeRun() -end - -function initializeRun() - savestate.load(config.NeatConfig.Filename); - 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() -end - -function evaluateCurrent() - local species = pool.species[pool.currentSpecies] - local genome = species.genomes[pool.currentGenome] - - local inputDeltas = {} - 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 - end - if controller["P1 Up"] and controller["P1 Down"] then - controller["P1 Up"] = false - controller["P1 Down"] = false - end - - joypad.set(controller) -end - -if pool == nil then - initializePool() -end - - -function nextGenome() - pool.currentGenome = pool.currentGenome + 1 - if pool.currentGenome > #pool.species[pool.currentSpecies].genomes then - pool.currentGenome = 1 - pool.currentSpecies = pool.currentSpecies+1 - if pool.currentSpecies > #pool.species then - newGeneration() - pool.currentSpecies = 1 - end - end -end - -function fitnessAlreadyMeasured() - local species = pool.species[pool.currentSpecies] - local genome = species.genomes[pool.currentGenome] - - 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) - local network = genome.network - local cells = {} - local i = 1 - local cell = {} - for dy=-config.BoxRadius,config.BoxRadius do - for dx=-config.BoxRadius,config.BoxRadius do - cell = {} - cell.x = 50+5*dx - cell.y = 70+5*dy - cell.value = network.neurons[i].value - cells[i] = cell - i = i + 1 - end - end - local biasCell = {} - biasCell.x = 80 - biasCell.y = 110 - biasCell.value = network.neurons[Inputs].value - cells[Inputs] = biasCell - - for o = 1,Outputs do - cell = {} - cell.x = 220 - cell.y = 30 + 8 * 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 - else - color = 0xFF000000 - end - --gui.drawText(223, 24+8*o, config.ButtonNames[o], color, 9) - forms.drawText(netPicture,223, 24+8*o, config.ButtonNames[o], color, 9) - end - - for n,neuron in pairs(network.neurons) do - cell = {} - if n > Inputs and n <= config.NeatConfig.MaxNodes then - cell.x = 140 - cell.y = 40 - cell.value = neuron.value - cells[n] = cell - end - end - - for n=1,4 do - for _,gene in pairs(genome.genes) do - if gene.enabled then - local c1 = cells[gene.into] - local c2 = cells[gene.out] - if gene.into > Inputs and gene.into <= config.NeatConfig.MaxNodes then - c1.x = 0.75*c1.x + 0.25*c2.x - if c1.x >= c2.x then - c1.x = c1.x - 40 - end - if c1.x < 90 then - c1.x = 90 - end - - if c1.x > 220 then - c1.x = 220 - end - c1.y = 0.75*c1.y + 0.25*c2.y - - end - if gene.out > Inputs and gene.out <= config.NeatConfig.MaxNodes then - c2.x = 0.25*c1.x + 0.75*c2.x - if c1.x >= c2.x then - c2.x = c2.x + 40 - end - if c2.x < 90 then - c2.x = 90 - end - if c2.x > 220 then - c2.x = 220 - end - c2.y = 0.25*c1.y + 0.75*c2.y - end - end - 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]) - for n,cell in pairs(cells) do - if n > Inputs or cell.value ~= 0 then - local color = math.floor((cell.value+1)/2*256) - if color > 255 then color = 255 end - if color < 0 then color = 0 end - local opacity = 0xFF000000 - if cell.value == 0 then - 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) - end - end - for _,gene in pairs(genome.genes) do - if gene.enabled then - local c1 = cells[gene.into] - local c2 = cells[gene.out] - local opacity = 0xA0000000 - if c1.value == 0 then - opacity = 0x20000000 - end - - local color = 0x80-math.floor(math.abs(mathFunctions.sigmoid(gene.weight))*0x80) - if gene.weight > 0 then - color = opacity + 0x8000 + 0x10000*color - 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) - 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]) - - pos = pos + 8 - end - --end - forms.refresh(netPicture) -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 savePool() - local filename = forms.gettext(saveLoadFile) - print(filename) - writeFile(filename) -end - -function mysplit(inputstr, sep) - if sep == nil then - sep = "%s" - end - local t={} ; i=1 - for str in string.gmatch(inputstr, "([^"..sep.."]+)") do - t[i] = str - i = i + 1 - end - return t -end - -function loadFile(filename) - print("Loading pool from " .. filename) - local file = io.open(filename, "r") - pool = newPool() - pool.generation = file:read("*number") - pool.maxFitness = file:read("*number") - forms.settext(MaxLabel, "Max Fitness: " .. math.floor(pool.maxFitness)) - local numSpecies = file:read("*number") - for s=1,numSpecies do - local species = newSpecies() - table.insert(pool.species, species) - species.topFitness = file:read("*number") - species.staleness = file:read("*number") - local numGenomes = file:read("*number") - for g=1,numGenomes do - local genome = newGenome() - table.insert(species.genomes, genome) - genome.fitness = file:read("*number") - genome.maxneuron = file:read("*number") - local line = file:read("*line") - while line ~= "done" do - - genome.mutationRates[line] = file:read("*number") - line = file:read("*line") - end - local numGenes = file:read("*number") - for n=1,numGenes do - - local gene = newGene() - local enabled - - local geneStr = file:read("*line") - local geneArr = mysplit(geneStr) - gene.into = tonumber(geneArr[1]) - gene.out = tonumber(geneArr[2]) - gene.weight = tonumber(geneArr[3]) - gene.innovation = tonumber(geneArr[4]) - enabled = tonumber(geneArr[5]) - - - if enabled == 0 then - gene.enabled = false - else - gene.enabled = true - end - - table.insert(genome.genes, gene) - end - end - end - file:close() - - while fitnessAlreadyMeasured() do - nextGenome() - end - initializeRun() - pool.currentFrame = pool.currentFrame + 1 - print("Pool loaded.") -end - -function flipState() - if config.Running == true then - config.Running = false - forms.settext(startButton, "Start") - else - config.Running = true - forms.settext(startButton, "Stop") - end -end - -function loadPool() - filename = forms.openfile("DP1.state.pool",config.PoolDir) - --local filename = forms.gettext(saveLoadFile) - forms.settext(saveLoadFile, filename) - loadFile(filename) -end - -function playTop() - local maxfitness = 0 - local maxs, maxg - for s,species in pairs(pool.species) do - for g,genome in pairs(species.genomes) do - if genome.fitness > maxfitness then - maxfitness = genome.fitness - maxs = s - maxg = g - end - end - end - - pool.currentSpecies = maxs - pool.currentGenome = maxg - pool.maxFitness = maxfitness - forms.settext(MaxLabel, "Max Fitness: " .. math.floor(pool.maxFitness)) - initializeRun() - pool.currentFrame = pool.currentFrame + 1 - return -end - -function onExit() - forms.destroy(form) -end - -writeFile(config.PoolDir.."temp.pool") - -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 +--Update to Seth-Bling's MarI/O app + +config = require "config" +spritelist = require "spritelist" +game = require "game" +mathFunctions = require "mathFunctions" +util = require "util" + +Inputs = config.InputSize+1 +Outputs = #config.ButtonNames + +function newInnovation() + pool.innovation = pool.innovation + 1 + return pool.innovation +end + +function newPool() + local pool = {} + pool.species = {} + pool.generation = 0 + pool.innovation = Outputs + pool.currentSpecies = 1 + pool.currentGenome = 1 + pool.currentFrame = 0 + pool.maxFitness = 0 + + return pool +end + +function newSpecies() + local species = {} + species.topFitness = 0 + species.staleness = 0 + species.genomes = {} + species.averageFitness = 0 + + return species +end + +function newGenome() + local genome = {} + genome.genes = {} + genome.fitness = 0 + genome.adjustedFitness = 0 + genome.network = {} + genome.maxneuron = 0 + genome.globalRank = 0 + genome.mutationRates = {} + genome.mutationRates["connections"] = config.NeatConfig.MutateConnectionsChance + genome.mutationRates["link"] = config.NeatConfig.LinkMutationChance + genome.mutationRates["bias"] = config.NeatConfig.BiasMutationChance + genome.mutationRates["node"] = config.NeatConfig.NodeMutationChance + genome.mutationRates["enable"] = config.NeatConfig.EnableMutationChance + genome.mutationRates["disable"] = config.NeatConfig.DisableMutationChance + genome.mutationRates["step"] = config.NeatConfig.StepSize + + return genome +end + +function copyGenome(genome) + local genome2 = newGenome() + for g=1,#genome.genes do + table.insert(genome2.genes, copyGene(genome.genes[g])) + end + genome2.maxneuron = genome.maxneuron + genome2.mutationRates["connections"] = genome.mutationRates["connections"] + genome2.mutationRates["link"] = genome.mutationRates["link"] + genome2.mutationRates["bias"] = genome.mutationRates["bias"] + genome2.mutationRates["node"] = genome.mutationRates["node"] + genome2.mutationRates["enable"] = genome.mutationRates["enable"] + genome2.mutationRates["disable"] = genome.mutationRates["disable"] + + return genome2 +end + +function basicGenome() + local genome = newGenome() + local innovation = 1 + + genome.maxneuron = Inputs + mutate(genome) + + return genome +end + +function newGene() + local gene = {} + gene.into = 0 + gene.out = 0 + gene.weight = 0.0 + gene.enabled = true + gene.innovation = 0 + + return gene +end + +function copyGene(gene) + local gene2 = newGene() + gene2.into = gene.into + gene2.out = gene.out + gene2.weight = gene.weight + gene2.enabled = gene.enabled + gene2.innovation = gene.innovation + + return gene2 +end + +function newNeuron() + local neuron = {} + neuron.incoming = {} + neuron.value = 0.0 + --neuron.dw = 1 + return neuron +end + +function generateNetwork(genome) + local network = {} + network.neurons = {} + + for i=1,Inputs do + network.neurons[i] = newNeuron() + end + + for o=1,Outputs do + network.neurons[config.NeatConfig.MaxNodes+o] = newNeuron() + end + + table.sort(genome.genes, function (a,b) + return (a.out < b.out) + end) + for i=1,#genome.genes do + local gene = genome.genes[i] + if gene.enabled then + if network.neurons[gene.out] == nil then + network.neurons[gene.out] = newNeuron() + end + local neuron = network.neurons[gene.out] + table.insert(neuron.incoming, gene) + if network.neurons[gene.into] == nil then + network.neurons[gene.into] = newNeuron() + end + end + end + + genome.network = network +end + +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.") + 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 + local sum = 0 + for j = 1,#neuron.incoming do + local incoming = neuron.incoming[j] + local other = network.neurons[incoming.into] + sum = sum + incoming.weight * other.value + end + + if #neuron.incoming > 0 then + neuron.value = mathFunctions.sigmoid(sum) + end + end + + local outputs = {} + for o=1,Outputs do + local button = "P1 " .. config.ButtonNames[o] + if network.neurons[config.NeatConfig.MaxNodes+o].value > 0 then + outputs[button] = true + else + outputs[button] = false + end + end + + return outputs +end + +function crossover(g1, g2) + -- Make sure g1 is the higher fitness genome + if g2.fitness > g1.fitness then + tempg = g1 + g1 = g2 + g2 = tempg + end + + local child = newGenome() + + local innovations2 = {} + for i=1,#g2.genes do + local gene = g2.genes[i] + innovations2[gene.innovation] = gene + end + + for i=1,#g1.genes do + local gene1 = g1.genes[i] + local gene2 = innovations2[gene1.innovation] + if gene2 ~= nil and math.random(2) == 1 and gene2.enabled then + table.insert(child.genes, copyGene(gene2)) + else + table.insert(child.genes, copyGene(gene1)) + end + end + + child.maxneuron = math.max(g1.maxneuron,g2.maxneuron) + + for mutation,rate in pairs(g1.mutationRates) do + child.mutationRates[mutation] = rate + end + + return child +end + +function randomNeuron(genes, nonInput) + local neurons = {} + if not nonInput then + for i=1,Inputs do + neurons[i] = true + end + end + for o=1,Outputs do + neurons[config.NeatConfig.MaxNodes+o] = true + end + for i=1,#genes do + if (not nonInput) or genes[i].into > Inputs then + neurons[genes[i].into] = true + end + if (not nonInput) or genes[i].out > Inputs then + neurons[genes[i].out] = true + end + end + + local count = 0 + for _,_ in pairs(neurons) do + count = count + 1 + end + local n = math.random(1, count) + + for k,v in pairs(neurons) do + n = n-1 + if n == 0 then + return k + end + end + + return 0 +end + +function containsLink(genes, link) + for i=1,#genes do + local gene = genes[i] + if gene.into == link.into and gene.out == link.out then + return true + end + end +end + +function pointMutate(genome) + local step = genome.mutationRates["step"] + + for i=1,#genome.genes do + local gene = genome.genes[i] + if math.random() < config.NeatConfig.PerturbChance then + gene.weight = gene.weight + math.random() * step*2 - step + else + gene.weight = math.random()*4-2 + end + end +end + +function linkMutate(genome, forceBias) + local neuron1 = randomNeuron(genome.genes, false) + local neuron2 = randomNeuron(genome.genes, true) + + local newLink = newGene() + if neuron1 <= Inputs and neuron2 <= Inputs then + --Both input nodes + return + end + if neuron2 <= Inputs then + -- Swap output and input + local temp = neuron1 + neuron1 = neuron2 + neuron2 = temp + end + + newLink.into = neuron1 + newLink.out = neuron2 + if forceBias then + newLink.into = Inputs + end + + if containsLink(genome.genes, newLink) then + return + end + newLink.innovation = newInnovation() + newLink.weight = math.random()*4-2 + + table.insert(genome.genes, newLink) +end + +function nodeMutate(genome) + if #genome.genes == 0 then + return + end + + genome.maxneuron = genome.maxneuron + 1 + + local gene = genome.genes[math.random(1,#genome.genes)] + if not gene.enabled then + return + end + gene.enabled = false + + local gene1 = copyGene(gene) + gene1.out = genome.maxneuron + gene1.weight = 1.0 + gene1.innovation = newInnovation() + gene1.enabled = true + table.insert(genome.genes, gene1) + + local gene2 = copyGene(gene) + gene2.into = genome.maxneuron + gene2.innovation = newInnovation() + gene2.enabled = true + table.insert(genome.genes, gene2) +end + +function enableDisableMutate(genome, enable) + local candidates = {} + for _,gene in pairs(genome.genes) do + if gene.enabled == not enable then + table.insert(candidates, gene) + end + end + + if #candidates == 0 then + return + end + + local gene = candidates[math.random(1,#candidates)] + gene.enabled = not gene.enabled +end + +function mutate(genome) + for mutation,rate in pairs(genome.mutationRates) do + if math.random(1,2) == 1 then + genome.mutationRates[mutation] = 0.95*rate + else + genome.mutationRates[mutation] = 1.05263*rate + end + end + + if math.random() < genome.mutationRates["connections"] then + pointMutate(genome) + end + + local p = genome.mutationRates["link"] + while p > 0 do + if math.random() < p then + linkMutate(genome, false) + end + p = p - 1 + end + + p = genome.mutationRates["bias"] + while p > 0 do + if math.random() < p then + linkMutate(genome, true) + end + p = p - 1 + end + + p = genome.mutationRates["node"] + while p > 0 do + if math.random() < p then + nodeMutate(genome) + end + p = p - 1 + end + + p = genome.mutationRates["enable"] + while p > 0 do + if math.random() < p then + enableDisableMutate(genome, true) + end + p = p - 1 + end + + p = genome.mutationRates["disable"] + while p > 0 do + if math.random() < p then + enableDisableMutate(genome, false) + end + p = p - 1 + end +end + +function disjoint(genes1, genes2) + local i1 = {} + for i = 1,#genes1 do + local gene = genes1[i] + i1[gene.innovation] = true + end + + local i2 = {} + for i = 1,#genes2 do + local gene = genes2[i] + i2[gene.innovation] = true + end + + local disjointGenes = 0 + for i = 1,#genes1 do + local gene = genes1[i] + if not i2[gene.innovation] then + disjointGenes = disjointGenes+1 + end + end + + for i = 1,#genes2 do + local gene = genes2[i] + if not i1[gene.innovation] then + disjointGenes = disjointGenes+1 + end + end + + local n = math.max(#genes1, #genes2) + + return disjointGenes / n +end + +function weights(genes1, genes2) + local i2 = {} + for i = 1,#genes2 do + local gene = genes2[i] + i2[gene.innovation] = gene + end + + local sum = 0 + local coincident = 0 + for i = 1,#genes1 do + local gene = genes1[i] + if i2[gene.innovation] ~= nil then + local gene2 = i2[gene.innovation] + sum = sum + math.abs(gene.weight - gene2.weight) + coincident = coincident + 1 + end + end + + return sum / coincident +end + +function sameSpecies(genome1, genome2) + local dd = config.NeatConfig.DeltaDisjoint*disjoint(genome1.genes, genome2.genes) + local dw = config.NeatConfig.DeltaWeights*weights(genome1.genes, genome2.genes) + return dd + dw < config.NeatConfig.DeltaThreshold +end + +function rankGlobally() + local global = {} + for s = 1,#pool.species do + local species = pool.species[s] + for g = 1,#species.genomes do + table.insert(global, species.genomes[g]) + end + end + table.sort(global, function (a,b) + return (a.fitness < b.fitness) + end) + + for g=1,#global do + global[g].globalRank = g + end +end + +function calculateAverageFitness(species) + local total = 0 + + for g=1,#species.genomes do + local genome = species.genomes[g] + total = total + genome.globalRank + end + + species.averageFitness = total / #species.genomes +end + +function totalAverageFitness() + local total = 0 + for s = 1,#pool.species do + local species = pool.species[s] + total = total + species.averageFitness + end + + return total +end + +function cullSpecies(cutToOne) + for s = 1,#pool.species do + local species = pool.species[s] + + table.sort(species.genomes, function (a,b) + return (a.fitness > b.fitness) + end) + + local remaining = math.ceil(#species.genomes/2) + if cutToOne then + remaining = 1 + end + while #species.genomes > remaining do + table.remove(species.genomes) + end + end +end + +function breedChild(species) + local child = {} + if math.random() < config.NeatConfig.CrossoverChance then + g1 = species.genomes[math.random(1, #species.genomes)] + g2 = species.genomes[math.random(1, #species.genomes)] + child = crossover(g1, g2) + else + g = species.genomes[math.random(1, #species.genomes)] + child = copyGenome(g) + end + + mutate(child) + + return child +end + +function removeStaleSpecies() + local survived = {} + + for s = 1,#pool.species do + local species = pool.species[s] + + table.sort(species.genomes, function (a,b) + return (a.fitness > b.fitness) + end) + + if species.genomes[1].fitness > species.topFitness then + species.topFitness = species.genomes[1].fitness + species.staleness = 0 + else + species.staleness = species.staleness + 1 + end + if species.staleness < config.NeatConfig.StaleSpecies or species.topFitness >= pool.maxFitness then + table.insert(survived, species) + end + end + + pool.species = survived +end + +function removeWeakSpecies() + local survived = {} + + local sum = totalAverageFitness() + for s = 1,#pool.species do + local species = pool.species[s] + breed = math.floor(species.averageFitness / sum * config.NeatConfig.Population) + if breed >= 1 then + table.insert(survived, species) + end + end + + pool.species = survived +end + + +function addToSpecies(child) + local foundSpecies = false + for s=1,#pool.species do + local species = pool.species[s] + if not foundSpecies and sameSpecies(child, species.genomes[1]) then + table.insert(species.genomes, child) + foundSpecies = true + end + end + + if not foundSpecies then + local childSpecies = newSpecies() + table.insert(childSpecies.genomes, child) + table.insert(pool.species, childSpecies) + end +end + +function newGeneration() + cullSpecies(false) -- Cull the bottom half of each species + rankGlobally() + removeStaleSpecies() + rankGlobally() + for s = 1,#pool.species do + local species = pool.species[s] + calculateAverageFitness(species) + end + removeWeakSpecies() + local sum = totalAverageFitness() + local children = {} + for s = 1,#pool.species do + local species = pool.species[s] + breed = math.floor(species.averageFitness / sum * config.NeatConfig.Population) - 1 + for i=1,breed do + table.insert(children, breedChild(species)) + end + end + cullSpecies(true) -- Cull all but the top member of each species + while #children + #pool.species < config.NeatConfig.Population do + local species = pool.species[math.random(1, #pool.species)] + table.insert(children, breedChild(species)) + end + for c=1,#children do + local child = children[c] + addToSpecies(child) + end + + pool.generation = pool.generation + 1 + + --writeFile("backup." .. pool.generation .. "." .. forms.gettext(saveLoadFile)) + writeFile(forms.gettext(saveLoadFile) .. ".gen" .. pool.generation .. ".pool") +end + +function initializePool() + pool = newPool() + + for i=1,config.NeatConfig.Population do + basic = basicGenome() + addToSpecies(basic) + end + + initializeRun() +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() +end + +function evaluateCurrent() + local species = pool.species[pool.currentSpecies] + local genome = species.genomes[pool.currentGenome] + + local inputDeltas = {} + 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 + end + if controller["P1 Up"] and controller["P1 Down"] then + controller["P1 Up"] = false + controller["P1 Down"] = false + end + + joypad.set(controller) +end + +if pool == nil then + initializePool() +end + +function nextGenome() + pool.currentGenome = pool.currentGenome + 1 + if pool.currentGenome > #pool.species[pool.currentSpecies].genomes then + pool.currentGenome = 1 + pool.currentSpecies = pool.currentSpecies+1 + if pool.currentSpecies > #pool.species then + newGeneration() + pool.currentSpecies = 1 + end + end +end + +function fitnessAlreadyMeasured() + local species = pool.species[pool.currentSpecies] + local genome = species.genomes[pool.currentGenome] + + 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) + local network = genome.network + local cells = {} + local i = 1 + local cell = {} + for dy=-config.BoxRadius,config.BoxRadius do + for dx=-config.BoxRadius,config.BoxRadius do + cell = {} + cell.x = 50+5*dx + cell.y = 70+5*dy + cell.value = network.neurons[i].value + cells[i] = cell + i = i + 1 + end + end + local biasCell = {} + biasCell.x = 80 + biasCell.y = 110 + biasCell.value = network.neurons[Inputs].value + cells[Inputs] = biasCell + + for o = 1,Outputs do + cell = {} + cell.x = 220 + cell.y = 30 + 8 * 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 + else + color = 0xFF000000 + end + --gui.drawText(223, 24+8*o, config.ButtonNames[o], color, 9) + forms.drawText(netPicture,223, 24+8*o, config.ButtonNames[o], color, 9) + end + + for n,neuron in pairs(network.neurons) do + cell = {} + if n > Inputs and n <= config.NeatConfig.MaxNodes then + cell.x = 140 + cell.y = 40 + cell.value = neuron.value + cells[n] = cell + end + end + + for n=1,4 do + for _,gene in pairs(genome.genes) do + if gene.enabled then + local c1 = cells[gene.into] + local c2 = cells[gene.out] + if gene.into > Inputs and gene.into <= config.NeatConfig.MaxNodes then + c1.x = 0.75*c1.x + 0.25*c2.x + if c1.x >= c2.x then + c1.x = c1.x - 40 + end + if c1.x < 90 then + c1.x = 90 + end + + if c1.x > 220 then + c1.x = 220 + end + c1.y = 0.75*c1.y + 0.25*c2.y + + end + if gene.out > Inputs and gene.out <= config.NeatConfig.MaxNodes then + c2.x = 0.25*c1.x + 0.75*c2.x + if c1.x >= c2.x then + c2.x = c2.x + 40 + end + if c2.x < 90 then + c2.x = 90 + end + if c2.x > 220 then + c2.x = 220 + end + c2.y = 0.25*c1.y + 0.75*c2.y + end + end + 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]) + for n,cell in pairs(cells) do + if n > Inputs or cell.value ~= 0 then + local color = math.floor((cell.value+1)/2*256) + if color > 255 then color = 255 end + if color < 0 then color = 0 end + local opacity = 0xFF000000 + if cell.value == 0 then + 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) + end + end + for _,gene in pairs(genome.genes) do + if gene.enabled then + local c1 = cells[gene.into] + local c2 = cells[gene.out] + local opacity = 0xA0000000 + if c1.value == 0 then + opacity = 0x20000000 + end + + local color = 0x80-math.floor(math.abs(mathFunctions.sigmoid(gene.weight))*0x80) + if gene.weight > 0 then + color = opacity + 0x8000 + 0x10000*color + 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) + 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]) + + pos = pos + 8 + end + --end + forms.refresh(netPicture) +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 savePool() + local filename = forms.gettext(saveLoadFile) + print(filename) + writeFile(filename) +end + +function mysplit(inputstr, sep) + if sep == nil then + sep = "%s" + end + local t={} ; i=1 + for str in string.gmatch(inputstr, "([^"..sep.."]+)") do + t[i] = str + i = i + 1 + end + return t +end + +function loadFile(filename) + print("Loading pool from " .. filename) + local file = io.open(filename, "r") + pool = newPool() + pool.generation = file:read("*number") + pool.maxFitness = file:read("*number") + forms.settext(MaxLabel, "Max Fitness: " .. math.floor(pool.maxFitness)) + local numSpecies = file:read("*number") + for s=1,numSpecies do + local species = newSpecies() + table.insert(pool.species, species) + species.topFitness = file:read("*number") + species.staleness = file:read("*number") + local numGenomes = file:read("*number") + for g=1,numGenomes do + local genome = newGenome() + table.insert(species.genomes, genome) + genome.fitness = file:read("*number") + genome.maxneuron = file:read("*number") + local line = file:read("*line") + while line ~= "done" do + + genome.mutationRates[line] = file:read("*number") + line = file:read("*line") + end + local numGenes = file:read("*number") + for n=1,numGenes do + + local gene = newGene() + local enabled + + local geneStr = file:read("*line") + local geneArr = mysplit(geneStr) + gene.into = tonumber(geneArr[1]) + gene.out = tonumber(geneArr[2]) + gene.weight = tonumber(geneArr[3]) + gene.innovation = tonumber(geneArr[4]) + enabled = tonumber(geneArr[5]) + + + if enabled == 0 then + gene.enabled = false + else + gene.enabled = true + end + + table.insert(genome.genes, gene) + end + end + end + file:close() + + while fitnessAlreadyMeasured() do + nextGenome() + end + initializeRun() + pool.currentFrame = pool.currentFrame + 1 + print("Pool loaded.") +end + +function flipState() + if config.Running == true then + config.Running = false + forms.settext(startButton, "Start") + else + config.Running = true + forms.settext(startButton, "Stop") + end +end + +function loadPool() + filename = forms.openfile("DP1.state.pool",config.PoolDir) + --local filename = forms.gettext(saveLoadFile) + forms.settext(saveLoadFile, filename) + loadFile(filename) +end + +function playTop() + local maxfitness = 0 + local maxs, maxg + for s,species in pairs(pool.species) do + for g,genome in pairs(species.genomes) do + if genome.fitness > maxfitness then + maxfitness = genome.fitness + maxs = s + maxg = g + end + end + end + + pool.currentSpecies = maxs + pool.currentGenome = maxg + pool.maxFitness = maxfitness + forms.settext(MaxLabel, "Max Fitness: " .. math.floor(pool.maxFitness)) + initializeRun() + pool.currentFrame = pool.currentFrame + 1 + return +end + +function onExit() + forms.destroy(form) +end + +writeFile(config.PoolDir.."temp.pool") + +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 new file mode 100644 index 0000000000000000000000000000000000000000..816fa2238b44e274902dd15cc0edf3e2d3de390f GIT binary patch literal 125377 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

k6*YR)p=4bi76u&o99ly^bL{ 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 WgZMB1V#Z^}3kM^fcOLXFy#E1ee#0#Q literal 0 HcmV?d00001 diff --git a/util.lua b/util.lua new file mode 100644 index 0000000..b3ad09c --- /dev/null +++ b/util.lua @@ -0,0 +1,44 @@ +local _M = {} + +function _M.table_to_string(tbl) + local result = "{" + local keys = {} + for k in pairs(tbl) do + table.insert(keys, k) + end + table.sort(keys) + for _, k in ipairs(keys) do + local v = tbl[k] + if type(v) == "number" and v == 0 then + goto continue + end + + -- Check the key type (ignore any numerical keys - assume its an array) + if type(k) == "string" then + result = result.."[\""..k.."\"]".."=" + end + + -- Check the value type + if type(v) == "table" then + result = result..table_to_string(v) + elseif type(v) == "boolean" then + result = result..tostring(v) + else + result = result.."\""..v.."\"" + end + result = result..",\n" + ::continue:: + end + -- Remove leading commas from the result + if result ~= "" then + result = result:sub(1, result:len()-1) + end + return result.."}" +end + +function _M.file_exists(name) + local f=io.open(name,"r") + if f~=nil then io.close(f) return true else return false end +end + +return _M \ No newline at end of file