--LuigI/O v3.0, a neuroevolutionary AI designed to teach itself how to beat Super Mario Bros and Lost Levels --Based on MarI/O created by Sethbling --LuigI/O versions 1, 2, and many minor adjustments and bugfixes made by Akisame --LuigI/O version 3 made by Electra & Akisame --Thanks to LuigI/O testers Akisame, Electra, Jonathan, AlejoGDOfficial, and Niko. --V3 changelog: https://pastebin.com/KxtgyRe6 -- Pressing N will show the Neural net, pressing M will show the mutation rates, pressing L will load the last saved generation, pressing B will display the genome with the top fitness. -- To use this script you will need to download FCEUX and create a savestate in state 1 as as player 1 (mario) for smb or as either player in Lost Levels. -- If you are playing Lost Levels, set this variable to 1: LostLevels = 0 -- If you want to play as Luigi in SMB1, set this variable to 2: player = 2 -- To set the game in turbo, do NES -> Emulation Speed -> Turbo, also make sure to set priority to realtime in Task Manager -- If you want it to go as fast as possible turn off the neural net win N, it doubles the speed basically -- Click file -> lua -> 'new lua script window' and load the script. -- Have fun! -- Keep in mind that this code is provided for Non-Commercial usage and it is not permitted to request financial compensation (either by means of ads, donations, subscriptions or other means) for selling, streaming or otherwise previewing this code or its execution. --FCEUX doesn't allow for user string input but if you press 'L' on your keyboard it should load the latest generation that was saved --for loading specific generations replace nil with the filename as shown in the example below savedpool = nil --"backups/backup.666.SMB8-3.state.pool" --savedpool = "backup.71.SMB2-4.state.pool" --savedpool = "backups/backup.118.SMB2-1.state.pool" -- HUD options. These can be changed on the fly by pressing 'M' for the mutation rates and 'N' for the network mutation_rates_disp = false neural_net_disp = true --set to false if you want warning popups before loading anything that might cause you to lose your current progress nopopup = true savestate_slot = 1 -- Load the level from FCEUX savestate slot. Set to 3 if you are loading a level that mario has reached by completing another level --SAVE_LOAD_FILE = "test.pool" --filename to be used SAVE_LOAD_FILE = nil --auto generate appropriate name from level values in ram (might not completely match with actual level) AllowAutoTurbo = true --Automatic turbo AllowSlowdownNearMax = false --This is slow down the turbo when it nears to furthest it has currently gotten FirstGenSpeedup = false --This will speed up the first generation BottleneckTurbo = false --Activate turbo on bottleneck detection TurboGenBottleneck = 20 --will start the turbo when it is in a bottleneck for 20 generations. Please adjust to match your taste World43SavestateSlot = 2 activateNicknames = true --allows you to give species nicknames displayBasicInfo = true --writes basic info about a species to a file so you can show it in a stream os.execute("mkdir backups") --create the folder to save the pools in os.execute("mkdir v3wins") --create a folder for the v3 winning genomes os.execute("mkdir v2wins") --create a folder for the v2 winning genomes SavestateObj = savestate.object(savestate_slot) ButtonNames = { "A", "B", "up", "down", "left", "right", } BoxRadius = 6 InputSize = (BoxRadius*2+1)*(BoxRadius*2+1) switchtime = {10,12,18} switchtimer = {} initialswitchvalue = {} for i=1,#switchtime do switchtimer[i] = switchtime[i] initialswitchvalue[i] = 1 end Inputs = InputSize+1+#initialswitchvalue+2 Outputs = #ButtonNames Population = 1000 DeltaDisjoint = 2.0 DeltaWeights = 0.4 DeltaThreshold = 1.0 keyflag = false --used for keyboard input endlevel = false --flag to detect a level finish preparenext = false --flag for preparing for the next level secondbest = 0 --second best fitness maxright = 0 --Highest fitness without speed compensation restoreturbo = false --flag for temporary turning off turbo rerunning = false --flag for requiring rerunning the level if it finished with turbo enabled killcounter = 0 --number of deaths StaleSpecies = 25 --increased slightly to allow for longer evolution mariohole = false --flag for mario falling into a hole mariopipe = false --flag for mario exiting a pipe marioPipeEnter = false --new flag for entering a pipe offset = 0 --offset value for when mario exits a pipe timebonus = 0 --timebonus to prevent the hard drops in fitness when exiting a pipe maxcounter = 0 --species that reach the max fitness currentTracker = 0 --tracks location on screen loop = false --flag for when a loop is detected previousmaxfitness = 0 --memory slot for the previous max fitness. used to check if it increased more than a set amount bottleneckcounter = 0 --amount of generations since last "real" breakthrough MutateConnectionsChance = 0.25 PerturbChance = 0.90 CrossoverChance = 0.75 LinkMutationChance = 2.0 NodeMutationChance = 0.50 BiasMutationChance = 0.40 SpeedMutationChance = 0.075 StepSize = 0.1 DisableMutationChance = 0.4 EnableMutationChance = 0.2 oscillationmutationchance = 0.2 interbreedchance = 0.03 networkswitchmutationchance = 0.01 nswitch = 1 ntrigger = true ncount = 0 TimeoutConstant = 120 --set to a higher value but see for yourself what is acceptable MaxNodes = 1000000 MaxNetworks = 95 --Max networks it is able to have, 95 is basically no limit but you can set it lower speciesToAutoNetworkSwitch = 10 --Number of gens after each bottleneck loop where network chance is increased increasedNetworkChance = 0.02 --Increased network chance rsncChanceDecrease = 0.1 --Multiplier to the rsnc chance of adding new network rather than replacing, applied a number of times equal to the number of new networks replaceSecondNetworkChance = 0.02 --Chance of rsnc by default minBnLoopsBeforeNew = 1 --Number of bottleneck loops before the first network switch (increases by 1 per network switch) gensBetweenPopOscil = 6 --generations after a breakthrough where the population decreases before starting a population oscillation popOscilMultiplier = 1 --Multiplier on the base length for an oscillation (default is about 20 for the first, 30 for the second, etc) networkPenaltyInner = 0.7 networkPenaltyOuter = 0.87 -- Multiplies average by (Inner * Outer^networks)^networks to encourage new networks to not take over population until sure they are needed maxIncreaseWithoutBnReset = 30 --Number the max fitness can increase by before triggering bottleneck reset hbValue = 60 function getPositions() marioX = memory.readbyte(0x6D) * 0x100 + memory.readbyte(0x86) marioY = memory.readbyte(0x03B8)+16 mariostate = memory.readbyte(0x0E) --get mario's state. (entering exiting pipes, dying, picking up a mushroom etc etc) yspeed = memory.readbyte(0x009F) --y velocity 1-5 are falling and 250-255 is jumping xspeed = memory.readbyte(0x0057) --x position ranging from -48 to 48 if yspeed >1 and yspeed <10 then falling = true else falling = false end currentscreen = memory.readbyte(0x071A) --finds current screen for loop detection nextscreen = memory.readbyte(0x071B) --finds next screen CurrentWorld = memory.readbyte(0x075F) --finds the current world (value + 1 is world) CurrentLevel = memory.readbyte(0x0760) --finds the current level (0=1 2=2 3=3 4=4) demoruncheck = memory.readbyte(0x0770) --equals 0 for demo and 1 for normal run statuscheck = memory.readbyte(0x0772) -- 3= playing 1 = loading screenX = memory.readbyte(0x03AD) screenY = memory.readbyte(0x03B8) end hiddenblocks = {} hitblocks = 0 function getTile(dx, dy) local x = marioX + dx + 8 local y = marioY + dy - 16 local page = math.floor(x/256)%2 local subx = math.floor((x%256)/16) local suby = math.floor((y - 32)/16) local addr = 0x500 + page*13*16+suby*16+subx local tilex = math.floor(x/16) local tiley = math.floor(y/16) local hbid = 95 - LostLevels if (memory.readbyte(addr) == hbid) then local dupe = false for b=1,#hiddenblocks do if hiddenblocks[b][1] == tilex and hiddenblocks[b][2] == tiley then dupe = true break end end if not dupe and tiley > -1 then table.insert(hiddenblocks,{tilex,tiley}) end end for b=1,#hiddenblocks do if hiddenblocks[b][1] == tilex and hiddenblocks[b][2] == tiley then if memory.readbyte(addr) ~= hbid then table.remove(hiddenblocks,b) hitblocks = hitblocks + 1 break end end end if suby >= 13 or suby < 0 then return 0 end if memory.readbyte(addr) ~= 0 then return 1 else return 0 end end function getSprites() local sprites = {} for slot=0,4 do local enemy = memory.readbyte(0xF+slot) if enemy ~= 0 then local ex = memory.readbyte(0x6E + slot)*0x100 + memory.readbyte(0x87+slot) local ey = memory.readbyte(0xCF + slot)+36 --changed this to stop sprites from floating in mid air on the neural network sprites[#sprites+1] = {["x"]=ex,["y"]=ey} end end return sprites end function getInputs() getPositions() sprites = getSprites() local inputs = {} for dy=-BoxRadius*16,BoxRadius*16,16 do for dx=-BoxRadius*16,BoxRadius*16,16 do inputs[#inputs+1] = 0 tile = getTile(dx, dy) if tile == 1 and marioY+dy < 0x1B0 then inputs[#inputs] = 1 end for i = 1,#sprites do distx = math.abs(sprites[i]["x"] - (marioX+dx)) disty = math.abs(sprites[i]["y"] - (marioY+dy+16)) if distx <= 8 and disty <= 8 then inputs[#inputs] = -1 end end end end --mariovx = memory.read_s8(0x7B) --mariovy = memory.read_s8(0x7D) return inputs end function sigmoid(x) return 2/(1+math.exp(-4.9*x))-1 end function newInnovation() pool.innovation = pool.innovation + 1 return pool.innovation end function newGSID() pool.gsid = pool.gsid + 1 return pool.gsid end function newPool() local pool = {} pool.species = {} pool.generation = 0 pool.innovation = Outputs pool.currentSpecies = 1 pool.currentGenome = 1 pool.currentFrame = 0 pool.measured = 0 pool.total = 0 hitblocks = 0 hiddenblocks = {} pool.maxFitness = 0 pool.maxcounter = 0 --counter for %max species reached pool.gsid = -1 return pool end function newSpecies() local species = {} species.topFitness = 0 species.staleness = 0 species.genomes = {} species.averageFitness = 0 species.nickname = 'none' species.turbo = 'on' species.gsid = newGSID() species.topright = 0 return species end function getCurrentGenome() local species = pool.species[pool.currentSpecies] return species.genomes[pool.currentGenome] end function toRGBA(ARGB) return bit.lshift(ARGB, 8) + bit.rshift(ARGB, 24) end function newGenome() local genome = {} genome.old = 0 genome.genes = {} genome.genes[1] = {} genome.fitness = 0 genome.adjustedFitness = 0 genome.network = {} genome.network[1] = {} genome.maxneuron = 0 genome.globalRank = 0 genome.oscillations = {{}} genome.networkswitch = {} for i=1,#switchtime do genome.oscillations[1][i] = switchtime[i] end genome.mutationRates = {} genome.mutationRates["connections"] = MutateConnectionsChance genome.mutationRates["link"] = LinkMutationChance genome.mutationRates["bias"] = BiasMutationChance genome.mutationRates["node"] = NodeMutationChance genome.mutationRates["enable"] = EnableMutationChance genome.mutationRates["disable"] = DisableMutationChance genome.mutationRates["step"] = StepSize genome.mutationRates["oscillation"] = oscillationmutationchance genome.mutationRates["networkswitch"] = networkswitchmutationchance return genome end function copyGenome(genome) local genome2 = newGenome() genome2.old = genome.old for n=1,#genome.genes do if n > 1 then table.insert(genome2.genes, {}) table.insert(genome2.oscillations, {}) end for g=1,#genome.genes[n] do table.insert(genome2.genes[n], copyGene(genome.genes[n][g])) end for i=1,#genome.oscillations[n] do genome2.oscillations[n][i] = genome.oscillations[n][i] end end for n=1,#genome.networkswitch do table.insert(genome2.networkswitch, genome.networkswitch[n]) 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"] genome2.mutationRates["oscillation"] = genome.mutationRates["oscillation"] genome2.mutationRates["networkswitch"] = genome.mutationRates["networkswitch"] return genome2 end function basicGenome() local genome = newGenome() local innovation = 1 genome.maxneuron = Inputs mutate(genome, 1) 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 copyGeneOld(gene) local gene2 = newGene() gene2.into = gene.into gene2.out = gene.out if gene2.into > Inputs-2 and gene2.into <= MaxNodes then gene2.into = gene2.into + 2 end if gene2.out > Inputs-2 and gene2.out <= MaxNodes then gene2.out = gene2.out + 2 end 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.switcher = false neuron.timer = 60 neuron.multiplier = 1 return neuron end function generateNetwork(genome,printing) for n=1,#genome.genes do local network = {} network.neurons = {} for i=1,Inputs do network.neurons[i] = newNeuron() end for o=1,Outputs do network.neurons[MaxNodes+o] = newNeuron() end table.sort(genome.genes[n], function (a,b) return (a.out < b.out) end) for i=1,#genome.genes[n] do local gene = genome.genes[n][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[n] = network end end function evaluateNetwork(n, inputs, genome) local network = genome.network[n] local Inputs = Inputs if genome.old >= nswitch then Inputs = Inputs -2 end table.insert(inputs, 1) -- bias input node for i = 1,#initialswitchvalue do if genome.oscillations[n][i] > 0 then --oscillating nodes if switchtimer[i] > 0 then switchtimer[i] = switchtimer[i] -1 else switchtimer[i] = genome.oscillations[n][i] initialswitchvalue[i] = initialswitchvalue[i]*-1 end end table.insert(inputs, initialswitchvalue[i]) end if not (genome.old >= nswitch) then --speed nodes if (xspeed < 100) then table.insert(inputs,xspeed / 48) else table.insert(inputs,(xspeed - 256) / 48) end if (yspeed < 10) then table.insert(inputs,yspeed / 5) else table.insert(inputs,(yspeed - 256) / 5) end if #inputs ~= Inputs then return {} end end for i=1,Inputs do 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 if neuron.switcher and sigmoid(sum)>0 then neuron.timer = neuron.timer - 1 elseif neuron.switcher then neuron.timer = 120 neuron.multiplier = 1 end if neuron.switcher and neuron.timer < 0 then neuron.multiplier = -neuron.multiplier neuron.timer = 10 end neuron.value = sigmoid(sum) * neuron.multiplier end end local outputs = {} for o=1,Outputs do local button = ButtonNames[o] if network.neurons[MaxNodes+o].value > 0 then outputs[button] = true else outputs[button] = false end end --emu.print(outputs) --emu.print(pool.currentFrame/5) --emu.print(network.neurons) return outputs end function crossover(g1, g2) if g2.fitness > g1.fitness then tempg = g1 g1 = g2 g2 = tempg end local child = newGenome() for n=1,#g1.networkswitch do table.insert(child.networkswitch, g1.networkswitch[n]) end child.old = math.min(g1.old,g2.old) for n=1,#g1.genes do local innovations2 = {} local g1old = g1.old >= n local g2old = g2.old >= n if n > 1 then table.insert(child.genes,{}) table.insert(child.oscillations,{}) end if #g2.genes >= n then for i=1,#g2.genes[n] do local gene = g2.genes[n][i] innovations2[gene.innovation] = gene end for i=1,#g1.oscillations[n] do child.oscillations[n][i] = g1.oscillations[n][i] end else for i=1,#g1.oscillations[n] do child.oscillations[n][i] = switchtime[i] end end for i=1,#g1.genes[n] do local gene1 = g1.genes[n][i] local gene2 = innovations2[gene1.innovation] if (g1old and not g2old) then if gene2 ~= nil and math.random(1,2) == 1 and gene2.enabled then table.insert(child.genes[n], copyGene(gene2)) else table.insert(child.genes[n], copyGeneOld(gene1)) end elseif (g2old and not g1old) then if gene2 ~= nil and math.random(1,2) == 1 and gene2.enabled then table.insert(child.genes[n], copyGeneOld(gene2)) else table.insert(child.genes[n], copyGene(gene1)) end else if gene2 ~= nil and math.random(1,2) == 1 and gene2.enabled then table.insert(child.genes[n], copyGene(gene2)) else table.insert(child.genes[n], copyGene(gene1)) end end 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[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, which) local step = genome.mutationRates["step"] local gene = {} for i=1,#genome.genes[which] do gene = genome.genes[which][i] if math.random() < 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, forceSpeed, which) local neuron1 = randomNeuron(genome.genes[which], false) local neuron2 = randomNeuron(genome.genes[which], 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 local adjustment = math.random(0,#initialswitchvalue) newLink.into = Inputs - adjustment - 2 end if forceSpeed then local adjustment = math.random(0,1) newLink.into = Inputs - adjustment end if containsLink(genome.genes[which], newLink) then return end newLink.innovation = newInnovation() newLink.weight = math.random()*4-2 table.insert(genome.genes[which], newLink) end function nodeMutate(genome, which) if #genome.genes[which] == 0 then return end genome.maxneuron = genome.maxneuron + 1 local gene = {} gene = genome.genes[which][math.random(1,#genome.genes[which])] 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[which], gene1) local gene2 = copyGene(gene) gene2.into = genome.maxneuron gene2.innovation = newInnovation() gene2.enabled = true table.insert(genome.genes[which], gene2) end function enableDisableMutate(genome, enable, which) local candidates = {} for _,gene in pairs(genome.genes[which]) 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 oscillationMutate(genome, which) mutationpoint = math.random(1,#genome.oscillations[which]) if genome.oscillations[which][mutationpoint]>0 then if math.random(1,2) == 1 then genome.oscillations[which][mutationpoint] = genome.oscillations[which][mutationpoint] + 1 else genome.oscillations[which][mutationpoint] = genome.oscillations[which][mutationpoint] - 1 end end if genome.oscillations[which][mutationpoint]<0 then genome.oscillations[which][mutationpoint] = 0 end end function NswitchMutate(genome) which = #genome.networkswitch if math.random() < 0.25 then which = math.random(1,#genome.networkswitch) end genome.networkswitch[which] = genome.networkswitch[which] + math.random()-0.5 if genome.networkswitch[which] < 0 then genome.networkswitch[which] = math.random(0,12) end if genome.networkswitch[which] > 12 then genome.networkswitch[which] = math.random(0,12) end table.sort(genome.networkswitch, function(a,b) return (a < b) end) end function mutate(genome, which) 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, which) end local p = genome.mutationRates["link"] while p > 0 do if math.random() < p then linkMutate(genome, false, false, which) end p = p - 1 end p = genome.mutationRates["bias"] while p > 0 do if math.random() < p then linkMutate(genome, true, false, which) end p = p - 1 end p = SpeedMutationChance if math.random() < p and genome.old >= which then linkMutate(genome, false, true, which) end p = p - 1 p = genome.mutationRates["node"] while p > 0 do if math.random() < p then nodeMutate(genome, which) end p = p - 1 end p = genome.mutationRates["enable"] while p > 0 do if math.random() < p then enableDisableMutate(genome, true, which) end p = p - 1 end p = genome.mutationRates["disable"] while p > 0 do if math.random() < p then enableDisableMutate(genome, false, which) end p = p - 1 end p = genome.mutationRates["oscillation"] while p > 0 do if math.random() < p then oscillationMutate(genome, which) end p = p - 1 end if #genome.networkswitch > 0 then p = genome.mutationRates["networkswitch"] while p > 0 do if math.random() < p then NswitchMutate(genome) end p = p - 1 end 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 if coincident == 0 then return 10 end return sum / coincident end function sameSpecies(genome1, genome2) if #genome1.networkswitch ~= #genome2.networkswitch then return false end for n=1,#genome1.networkswitch do if math.abs(genome1.networkswitch[n] - genome2.networkswitch[n]) > 0.6 then return false end end for n=1,#genome1.genes do --if genome2.genes[n] == nil then --emu.print(genome1.genes) --emu.print(genome1.networkswitch) --emu.print(genome2.genes) --emu.print(genome2.networkswitch) --end dd = DeltaDisjoint*disjoint(genome1.genes[n],genome2.genes[n]) dw = DeltaWeights*weights(genome1.genes[n],genome2.genes[n]) if dd + dw > DeltaThreshold then return false end end return true 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 if species.topFitness < pool.averagemaxfitness - (pool.standardDeviationMaxFitness *2) then species.averageFitness = (total / #species.genomes) / 4 elseif species.topFitness < pool.averagemaxfitness - pool.standardDeviationMaxFitness then species.averageFitness = (total / #species.genomes) / 2.5 elseif species.topFitness > pool.averagemaxfitness + (pool.standardDeviationMaxFitness *3) then species.averageFitness = (total / #species.genomes) * 2 elseif species.topFitness > pool.averagemaxfitness + (pool.standardDeviationMaxFitness *2) then species.averageFitness = (total / #species.genomes) * 1.5 else species.averageFitness = total / #species.genomes end species.averageFitness = species.averageFitness * math.pow(networkPenaltyOuter*math.pow(networkPenaltyInner,#species.genomes[1].networkswitch),#species.genomes[1].networkswitch) end function calculateStandardDeviationMax() local deviationtotal = 0 for s=1,#pool.species do deviationtotal = deviationtotal + math.abs(pool.species[s].topFitness - pool.averagemaxfitness) end pool.standardDeviationMaxFitness = deviationtotal / #pool.species end function AverageMaxFitness() local total = 0 for s = 1,#pool.species do local species = pool.species[s] total = total + species.topFitness end pool.averagemaxfitness = total / #pool.species 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 percent = 0.5 --if species.staleness > 10 then --percent = math.pow(0.5,(species.staleness+5)/15) --end local remaining = math.ceil(#species.genomes*percent) 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() < 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 local which = #child.genes if which > 1 and species.topright > 0 then emu.print(child.networkswitch[#child.networkswitch]) local mutatePrevNetworkChance = 0.5 + (child.networkswitch[#child.networkswitch]-species.topright/500)/2 if math.random() < mutatePrevNetworkChance then which = #child.genes - 1 end end mutate(child,which) return child end function secondbestspecies() --added this to help prevent the formation of monospecies when LuigI/O encounters a major bottleneck local bestfitness = 0 local secondbestfitness = 0 for s = 1,#pool.species do local species = pool.species[s] if species.topFitness > bestfitness then secondbestfitness = bestfitness bestfitness = species.topFitness elseif species.topFitness > secondbestfitness then secondbestfitness = species.topFitness end end return secondbestfitness end function removeStaleSpecies() local survived = {} --emu.print("Max Fitness: ".. pool.maxFitness ..". second best: ".. secondbest) 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 end secondbest = secondbestspecies() for s = 1,#pool.species do local species = pool.species[s] if species.staleness < StaleSpecies or species.topFitness >= secondbest then --originally species.topFitness >= pool.maxFitness table.insert(survived, species) end end pool.species = survived end function removeWeakSpecies() local survived = {} local breed local sum = totalAverageFitness() for s = 1,#pool.species do local species = pool.species[s] breed = math.floor(species.averageFitness / sum * 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 replaceSecondNetwork(g1,g2,tr) local genome2 = newGenome() for n=1,#g1.genes do if n > 1 then table.insert(genome2.genes,{}) table.insert(genome2.oscillations,{}) end if g1.old >= n then for g=1,#g1.genes[n] do table.insert(genome2.genes[n], copyGeneOld(g1.genes[n][g])) end else for g=1,#g1.genes[n] do table.insert(genome2.genes[n], copyGene(g1.genes[n][g])) end end for i=1,#g1.oscillations[n] do genome2.oscillations[n][i] = g1.oscillations[n][i] end end for n=1,#g1.networkswitch do genome2.networkswitch[n] = g1.networkswitch[n] end genome2.maxneuron = g1.maxneuron genome2.mutationRates["connections"] = g1.mutationRates["connections"] genome2.mutationRates["link"] = g1.mutationRates["link"] genome2.mutationRates["bias"] = g1.mutationRates["bias"] genome2.mutationRates["node"] = g1.mutationRates["node"] genome2.mutationRates["enable"] = g1.mutationRates["enable"] genome2.mutationRates["disable"] = g1.mutationRates["disable"] genome2.mutationRates["oscillation"] = g1.mutationRates["oscillation"] genome2.mutationRates["networkswitch"] = g1.mutationRates["networkswitch"] if g1.fitness + 300 <= pool.maxFitness or math.random() > math.pow(rsncChanceDecrease,#genome2.networkswitch) or #genome2.genes >= MaxNetworks or bnLoops < #genome2.genes then genome2.genes[#genome2.genes] = {} which = math.random(1,#g2.genes) if g2.old >= which then for g=1,#g2.genes[which] do table.insert(genome2.genes[#genome2.genes], copyGeneOld(g2.genes[which][g])) end else for g=1,#g2.genes[which] do table.insert(genome2.genes[#genome2.genes], copyGene(g2.genes[which][g])) end end else table.insert(genome2.genes,{}) which = math.random(1,#g2.genes) if g2.old >= which then for g=1,#g2.genes[which] do table.insert(genome2.genes[#genome2.genes], copyGeneOld(g2.genes[which][g])) end else for g=1,#g2.genes[which] do table.insert(genome2.genes[#genome2.genes], copyGene(g2.genes[which][g])) end end table.insert(genome2.oscillations,{switchtime[1],switchtime[2],switchtime[3]}) table.insert(genome2.networkswitch,tr/500 - 0.2-0.1*math.random()) end table.sort(genome2.networkswitch, function(a,b) return (a < b) end) return genome2 end function FileExists(name) local file = io.open(name,"r") if file~=nil then io.close(file) return true else return false end end bnLoops = 0 function newGeneration() writeFile("backups/backup3." .. pool.generation .. "." .. SAVE_LOAD_FILE) if originalmaxfitness == nil or originalmaxfitness > previousmaxfitness then originalmaxfitness = previousmaxfitness end local population = Population if pool.maxFitness-maxIncreaseWithoutBnReset > previousmaxfitness then previousmaxfitness = pool.maxFitness originalmaxfitness = pool.maxFitness bottleneckcounter = 0 if AllowAutoTurbo then emu.speedmode("normal") turbo = false end bnLoops = 0 elseif pool.maxFitness - (4*maxIncreaseWithoutBnReset) > originalmaxfitness then previousmaxfitness = pool.maxFitness originalmaxfitness = pool.maxFitness bottleneckcounter = 0 if AllowAutoTurbo then emu.speedmode("normal") turbo = false end bnLoops = 0 else bottleneckcounter = bottleneckcounter + 1 previousmaxfitness = pool.maxFitness end local bottleneckc = bottleneckcounter local GBPO = gensBetweenPopOscil local targetPop = math.min(900,500 + 100*bnLoops) local length= math.min(12*popOscilMultiplier,(5 + 2*bnLoops)*popOscilMultiplier) local targetPopLower = math.min(300,600 - targetPop/2) local currentPopLower = math.min(300,targetPopLower+50) if bottleneckc <= GBPO then if population > 300 then population = math.floor(population * 0.905 * 0.905) if population < 300 then population = 300 end end end if bottleneckc > GBPO then if bottleneckc < GBPO + length then population = currentPopLower + math.floor((targetPop - currentPopLower)/length*(bottleneckc-GBPO)) elseif bottleneckc == GBPO + length then population = targetPop elseif bottleneckc <= GBPO + length*3 then population = targetPopLower + math.floor((targetPop - targetPopLower)/length/2*(GBPO+length*3-bottleneckc)) end if bottleneckc == GBPO + length*3 then bottleneckcounter = 0 bnLoops = bnLoops + 1 end end if bottleneckcounter > TurboGenBottleneck and AllowAutoTurbo and BottleneckTurbo then emu.speedmode("turbo") turbo = true end Population = population cullSpecies(false) -- Cull the bottom half of each species rankGlobally() removeStaleSpecies() AverageMaxFitness() calculateStandardDeviationMax() rankGlobally() local numSpecies = #pool.species for s = 1,numSpecies do local species = pool.species[s] calculateAverageFitness(species) end removeWeakSpecies() local sum = totalAverageFitness() local children = {} local breed for s = 1,#pool.species do local species = pool.species[s] breed = math.floor(species.averageFitness / sum * population) - 1 if breed > 10 then breed = math.ceil(breed * math.sqrt(species.topFitness / pool.maxFitness)) end if breed > population/4 then breed = math.floor(Population/4) end for i=1,breed do table.insert(children, breedChild(species)) end end cullSpecies(true) -- Cull all but the top member of each species if #pool.species < 30 then interbreedchance = 0.2 else interbreedchance = 0.03 end for i=1,#pool.species do if math.random() < interbreedchance then local counterbreed = math.random(1, #pool.species) if counterbreed ~= i then table.insert(children, crossover(pool.species[i].genomes[1], pool.species[counterbreed].genomes[1])) end end if pool.species[i].topFitness + 300 > pool.maxFitness and (math.random() < increasedNetworkChance*(bnLoops - #pool.species[i].genomes[1].networkswitch) or #pool.species <= speciesToAutoNetworkSwitch) and #pool.species[i].genomes[1].genes < MaxNetworks then local tfit = pool.species[i].topFitness child = copyGenome(pool.species[i].genomes[1]) switchlocation = pool.species[i].topright/500 - 0.2 - 0.1*math.random() table.insert(child.networkswitch,switchlocation) table.insert(child.oscillations,{switchtime[1],switchtime[2],switchtime[3]}) table.insert(child.genes,{}) table.insert(children, child) table.sort(child.networkswitch, function(a,b) return (a < b) end) emu.print(pool.species[i].genomes[1].fitness) emu.print(switchlocation) end if math.random() < replaceSecondNetworkChance and pool.species[i].topFitness > 500 then if #pool.species[i].genomes[1].networkswitch > 0 then if math.random() < 0.5 then local donornetwork = math.random(1, #pool.species) if donornetwork ~= i then table.insert(children, replaceSecondNetwork(pool.species[i].genomes[1], pool.species[donornetwork].genomes[1],pool.species[i].topright)) end end else local donornetwork = math.random(1, #pool.species) if donornetwork ~= i then table.insert(children, replaceSecondNetwork(pool.species[i].genomes[1], pool.species[donornetwork].genomes[1],pool.species[i].topright)) end end end end while #children + #pool.species < 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 local children = {} local replace = 0 for s=1,#pool.species do local species = pool.species[s] if species.topFitness < secondbest and #species.genomes > Population/12 then while #species.genomes > Population/12 do table.remove(species.genomes) replace = replace + 1 end elseif species.topFitness >= secondbest and #species.genomes>Population/7 then while #species.genomes > Population/7 do table.remove(species.genomes) replace = replace + 1 end end end for r=1,replace 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 count = 0 for s=1,#pool.species do local species = pool.species[s] if species.topFitness < secondbest and #species.genomes > Population/12 then while #species.genomes > Population/12 do table.remove(species.genomes) end elseif species.topFitness >= secondbest and #species.genomes>Population/7 then while #species.genomes > Population/7 do table.remove(species.genomes) end end count = count + #species.genomes end --emu.print("Count: " .. count) secondbest = secondbestspecies() pool.generation = pool.generation + 1 pool.maxcounter = 0 --reset max fitness counter --gsidFile("backups/gsid." .. pool.generation .. "." .. SAVE_LOAD_FILE) writeFile("backups/backup3." .. SAVE_LOAD_FILE) writelatestgen() --tracker for the latest generation end function initializePool() pool = newPool() added = LoadPreviousV2Win() added = added + LoadPreviousV3Win(added) local basic for i=1,Population-added do basic = basicGenome() addToSpecies(basic) end initializeRun() end function clearJoypad() controller = {} for b = 1,#ButtonNames do controller[ButtonNames[b]] = false end joypad.set(player, controller) end function LoadPreviousV3Win(start) if FileExists('v3wins.txt') then local file = io.open('v3wins.txt', "r") local line = file:read("*line") local count = 0 while line ~= nil do PlaceV3Genome(line) count = count + 1 line = file:read("*line") end file:close() local file = io.open('v3nicks.txt', "r") local line = file:read("*line") local count2 = start while line ~= nil do count2 = count2 + 1 pool.species[count2].nickname = line line = file:read("*line") end file:close() return count else return 0 end end function LoadPreviousV2Win() if FileExists('v2wins.txt') then local file = io.open('v2wins.txt', "r") local line = file:read("*line") local count = 0 while line ~= nil do PlaceGenome(line) count = count + 1 line = file:read("*line") end file:close() local file = io.open('v2nicks.txt', "r") local line = file:read("*line") local count2 = 0 while line ~= nil do count2 = count2 + 1 pool.species[count2].nickname = line line = file:read("*line") end file:close() return count else return 0 end end function PlaceV3Genome(filename) if FileExists(filename) then local file = io.open(filename, "r") local genome = newGenome() local innov = pool.innovation genome.fitness = 0 genome.old = 0 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 splits = file:read("*number") local highinnov = 0 print(splits) for s=1,splits do local numGenes = file:read("*number") for n=1,numGenes do local gene = newGene() table.insert(genome.genes[s], gene) local enabled gene.into, gene.out, gene.weight, gene.innovation, enabled = file:read("*number", "*number", "*number", "*number", "*number") if gene.innovation > highinnov then highinnov = gene.innovation end gene.innovation = gene.innovation + innov -- if gene.into > Inputs-2 and gene.into <= Inputs then gene.into = gene.into + 2 end -- if gene.out > Inputs-2 and gene.out <= Inputs then gene.out = gene.out + 2 end --gene.into = gene.into+2 --gene.out = gene.out+2 if enabled == 0 then gene.enabled = false else gene.enabled = true end end if s < splits then table.insert(genome.genes,{}) table.insert(genome.oscillations,{}) end end for n=1,splits-1 do genome.networkswitch[n] = file:read("*number") end local numosci = 3 for n=1,splits do for o=1,numosci do genome.oscillations[n][o] = file:read("*number") end end pool.innovation = pool.innovation + highinnov addToSpecies(genome) file:close() end end function PlaceGenome(filename) filename = filename .. ".txt" if FileExists(filename) then emu.print("loading in genome at location " .. filename) local file = io.open(filename, "r") local genome = newGenome() local innov = pool.innovation genome.fitness = 0 genome.old = 1 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") local highinnov = 0 for n=1,numGenes do local gene = newGene() table.insert(genome.genes[1], gene) local enabled gene.into, gene.out, gene.weight, gene.innovation, enabled = file:read("*number", "*number", "*number", "*number", "*number") if gene.innovation > highinnov then highinnov = gene.innovation end gene.innovation = gene.innovation + innov -- if gene.into > Inputs-2 and gene.into <= Inputs then gene.into = gene.into + 2 end -- if gene.out > Inputs-2 and gene.out <= Inputs then gene.out = gene.out + 2 end --gene.into = gene.into+2 --gene.out = gene.out+2 if enabled == 0 then gene.enabled = false else gene.enabled = true end end --table.insert(genome.genes,{}) table.insert(genome.oscillations,{}) local numGenes2 = file:read("*number") genes2 = {} for n=1,numGenes2 do local gene2 = newGene() table.insert(genes2, gene2) local enabled --I am able to revert back to the original loading script with FCEUX gene2.into, gene2.out, gene2.weight, gene2.innovation, enabled = file:read("*number", "*number", "*number", "*number", "*number") if gene2.innovation > highinnov then highinnov = gene2.innovation end gene2.innovation = gene2.innovation + innov -- if gene2.into > Inputs-2 and gene2.into <= Inputs then gene2.into = gene2.into + 2 end -- if gene2.out > Inputs-2 and gene2.out <= Inputs then gene2.out = gene2.out + 2 end if enabled == 0 then gene2.enabled = false else gene2.enabled = true end end pool.innovation = pool.innovation + highinnov local ns = file:read("*number") local numosci = file:read("*number") for i=1,numosci do genome.oscillations[1][i] = file:read("*number") end if (ns > 0) then genome.old = 2 genome.genes[2] = genes2 for i=1,numosci do genome.oscillations[2][i] = genome.oscillations[1][i] end table.insert(genome.networkswitch,ns) end addToSpecies(genome) file:close() end end function initializeRun() savestate.load(SavestateObj); rightmost = 0 pool.currentFrame = 0 hitblocks = 0 timebonus = 0 hiddenblocks = {} timeout = TimeoutConstant clearJoypad() currentTracker = memory.readbyte(0x071A) --sets the current tracker to the current screen 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 pool.measured = measured pool.total = total local species = pool.species[pool.currentSpecies] local genome = species.genomes[pool.currentGenome] for i=1,#genome.oscillations[1] do switchtimer[i] = genome.oscillations[1][i] initialswitchvalue[i] = 1 end generateNetwork(genome,true) evaluateCurrent() end function reRun() savestate.load(savestate.object(previous_savestate)); rightmost = 0 pool.currentFrame = 0 hitblocks = 0 hiddenblocks = {} timeout = TimeoutConstant clearJoypad() currentTracker = memory.readbyte(0x071A) --sets the current tracker to the current screen offset = 0 --offset in x coordinates for pipe timebonus = 0 --used to freeze fitness mariohole = false --reset the fallen into hole variable mariopipe = false --is mario exiting a pipe or just teleporting loop = false --is this a loop? marioPipeEnter = false --has mario entered a pipe currentTracker = 1 --tracker for the loop killtrigger = false ntrigger = true nswitch = 1 local species = pool.species[pool.currentSpecies] local genome = species.genomes[pool.currentGenome] for i=1,#genome.oscillations[1] do switchtimer[i] = genome.oscillations[1][i] initialswitchvalue[i] = 1 end generateNetwork(genome,true) evaluateCurrent() end function evaluateCurrent() local species = pool.species[pool.currentSpecies] local genome = species.genomes[pool.currentGenome] --emu.print(nswitch) inputs = getInputs() controller = evaluateNetwork(nswitch, inputs, genome) if controller["left"] and controller["right"] then controller["left"] = false controller["right"] = false end if controller["up"] and controller["down"] then controller["up"] = false controller["down"] = false end joypad.set(player, 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 false return genome.fitness ~= 0 end function exportGenome(genome, nickname) local filename = "v3wins/v3-" .. CurrentWorld+1 .."-".. CurrentLevel .. "-H.txt" local file2 = io.open("v3wins.txt", "a") file2:write(filename .. "\n") file2:close() local file3 = io.open("v3nicks.txt", "a") if nickname == "none" then file3:write("nameless winner" .. "\n") else file3:write(nickname .. "\n") end file3:close() local file = io.open(filename, "w") 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 k,network in pairs(genome.genes) do file:write(#network .. "\n") for l,gene in pairs(network) 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 --emu.print(#network) end --emu.print(#genome.genes) for i,ns in pairs(genome.networkswitch) do file:write(ns .. "\n") end --emu.print(genome.oscillations) for i,osc in pairs(genome.oscillations) do file:write(#osc .. "\n") for i,oscn in pairs(osc) do file:write(oscn .. "\n") end end file:write(genome.old) file:close() end function GenerateNewKeepBest(genome, nickname) Population = 1000 previousbest = copyGenome(genome) exportGenome(previousbest, nickname) --saves the winning genome previousmaxfitness = 1 originalmaxfitness = 1 maxright = 1 offset = 0 --offset in x coordinates for pipe timebonus = 0 --used to freeze fitness timeout = TimeoutConstant mariohole = false --reset the fallen into hole variable mariopipe = false --is mario exiting a pipe or just teleporting loop = false --is this a loop? marioPipeEnter = false --has mario entered a pipe currentTracker = 1 --tracker for the loop pool = newPool() added = LoadPreviousV2Win() added = added + LoadPreviousV3Win(added) --addToSpecies(previousbest) for i=1,Population-added do basic = basicGenome() addToSpecies(basic) end initializeRun() end function displayGenome(genome) gui.opacity(0.53) local Inputs = Inputs local nswitch = nswitch local BoxRadius = BoxRadius if genome.old >= nswitch then Inputs = Inputs -2 end if not genome then return end local network = {} network = genome.network[nswitch] local cells = {} local i = 1 local cell = {} for dy=-BoxRadius,BoxRadius do for dx=-BoxRadius,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 local switchCell = {} local speedCell = {} if genome.old >= nswitch then biasCell.value = network.neurons[Inputs-#initialswitchvalue].value cells[Inputs-#initialswitchvalue] = biasCell for i=0,#initialswitchvalue-1 do switchCell = {} switchCell.x = 80-10*#initialswitchvalue+10*i switchCell.y = 110 switchCell.value = network.neurons[Inputs-i].value cells[Inputs-i] = switchCell end else biasCell.value = network.neurons[Inputs-#initialswitchvalue-2].value cells[Inputs-#initialswitchvalue-2] = biasCell for i=0,#initialswitchvalue-1 do switchCell = {} switchCell.x = 80-10*#initialswitchvalue+10*i switchCell.y = 110 switchCell.value = network.neurons[Inputs-i-2].value cells[Inputs-i-2] = switchCell end for i=0,1 do speedCell = {} speedCell.x = 80 - 10*(6)+10*i speedCell.y = 110 speedCell.value = network.neurons[Inputs-i].value cells[Inputs-i] = speedCell end end gui.drawbox(215,32,245,82,toRGBA(0x80808080),toRGBA(0x00000000)) local color gui.opacity(0.85) for o = 1,Outputs do cell = {} cell.x = 220 cell.y = 30 + 8 * o cell.value = network.neurons[MaxNodes + o].value cells[MaxNodes+o] = cell if cell.value > 0 then color = 0xFF0000FF else color = 0xFF777777-- original color = 0xFF000000 end gui.drawtext(225, 26+8*o, ButtonNames[o], toRGBA(color), 0x0) --moved slightly for better alignment end for n,neuron in pairs(network.neurons) do cell = {} if n > Inputs and n <= MaxNodes then cell.x = 140 cell.y = 40 cell.value = neuron.value -- if neuron.value >0 then -- neuron.switcher = true -- end cells[n] = cell end end local genes = {} genes = genome.genes[nswitch] for n=1,4 do for _,gene in pairs(genes) do if gene.enabled then local c1 = cells[gene.into] local c2 = cells[gene.out] if gene.into > Inputs and gene.into <= 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 <= 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-BoxRadius*5-3,70-BoxRadius*5-3,50+BoxRadius*5+2,70+BoxRadius*5+2, toRGBA(0x80808080), toRGBA(0xFF000000)) for n,cell in pairs(cells) do if n > Inputs or cell.value ~= 0 then 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 inversecolor = (color - 255) * (-1) bordercolor = 0x50000000 + inversecolor*0x10000 + inversecolor*0x100 + inversecolor color = opacity + color*0x10000 + color*0x100 + color gui.drawbox(cell.x-2,cell.y-2,cell.x+2,cell.y+2,toRGBA(color), toRGBA(bordercolor)) end end for _,gene in pairs(genes) do if gene.enabled then local c1 = cells[gene.into] local c2 = cells[gene.out] local opacity = 0xF0000000 if pool.generation > 50 then opacity = 0x80000000 end if c1.value == 0 then if pool.generation <10 then --slowly reducing the visibility of non active connections opacity = 0x80000000 elseif pool.generation < 15 then opacity = 0x60000000 elseif pool.generation < 25 then opacity = 0x40000000 end end local color = 0x80-math.floor(math.abs(sigmoid(gene.weight))*0x80) if gene.weight > 0 then color = opacity + 0x8000 + 0x10000*color else color = opacity + 0x800000 + 0x100*color end if c1.value ~= 0 or pool.generation < 5 then -- remove non active connections from generation 50 onwards gui.drawline(c1.x+1, c1.y, c2.x-3, c2.y, toRGBA(color)) end end end gui.drawbox(49,71,51,78,toRGBA(0x80FF0000),toRGBA(0x00000000)) if mutation_rates_disp then local pos = 120 for mutation,rate in pairs(genome.mutationRates) do gui.drawtext(16, pos, mutation .. ": " .. rate, toRGBA(0xFF000000), 0x0) pos = pos + 8 end end end function toBase95(num,places) local str = "" for p=1,places do local numPlace = math.floor(num / math.pow(95,p-1)) str = str .. string.char((numPlace % 95) + 32) end return str end function fromBase95(str,places) local num = 0 for p=1,string.len(str) do local numPlace = string.byte(string.sub(str,p,p))-32 num = num + numPlace * math.pow(95,p-1) end return num end function gsidFile(filename) local file = io.open(filename, "w") file:write("G") for n,species in pairs(pool.species) do file:write(toBase95(species.gsid,3)) end file:close() end function writeFile(filename) local file = io.open(filename, "w") file:write("B" .. "\n") file:write(pool.generation .. "\n") file:write(math.floor(pool.maxFitness*10)/10 .. "\n") file:write(Population .. "\n") file:write(bnLoops .. "\n") file:write(bottleneckcounter .. "\n") file:write(pool.innovation .. "\n") file:write(pool.gsid .. "\n") file:write(#pool.species .. "\n") for n,species in pairs(pool.species) do file:write(math.floor(species.topFitness*10)/10 .. "\n") file:write(species.staleness .. "\n") file:write(species.gsid .. "\n") file:write(#species.genomes .. "\n") for m,genome in pairs(species.genomes) do file:write(math.floor(genome.fitness*10)/10 .. "\n") file:write(genome.maxneuron .. "\n") for i,rate in pairs(genome.mutationRates) do file:write(i .. "\n") file:write(math.floor(rate*10000)/10000 .. "\n") end file:write("done" .. "\n") file:write(#genome.genes .. "\n") for k,network in pairs(genome.genes) do file:write(#network .. "\n") for l,gene in pairs(network) do local enabledChar if(gene.enabled) then enabledChar = "1" else enabledChar = "0" end file:write(gene.into .. " " .. gene.out .. " " .. math.floor(gene.weight*10000000)/10000000 .. " " .. gene.innovation .. " " .. enabledChar .. "\n") end --emu.print(#network) end --emu.print(#genome.genes) for i,ns in pairs(genome.networkswitch) do file:write(math.floor(ns*500)/500 .. "\n") end --emu.print(genome.oscillations) for i,osc in pairs(genome.oscillations) do for i,oscn in pairs(osc) do file:write(oscn .. "\n") end end file:write(genome.old .. "\n") end end file:close() end function savePool() --used to save when a new max fitness is reached local filename = "backups/backup3." .. pool.generation .. "." .. SAVE_LOAD_FILE writeFile(filename) emu.print("saved pool due to new max fitness") --suggestion by DeltaLeeds end function loadFile(filename) emu.print(filename) local file = io.open(filename, "r") fileString = file:read("*line") file:close() if string.sub(fileString,1,1) == "B" then local file = io.open(filename, "r") file:read("*line") pool = newPool() pool.species = {} pool.generation = file:read("*number") pool.maxFitness = file:read("*number") pool.maxFitness = 0 previousmaxfitness = pool.maxFitness Population = file:read("*number") bnLoops = file:read("*number") bottleneckcounter = file:read("*number") pool.innovation = file:read("*number") local gsidMax = file:read("*number") local numSpecies = file:read("*number") for s=1,numSpecies do local species = newSpecies() species.genomes = {} species.topFitness = file:read("*number") species.topFitness = 0 species.staleness = file:read("*number") species.gsid = file:read("*number") local numGenomes = file:read("*number") for g=1,numGenomes do local genome = newGenome() genome.fitness = file:read("*number") genome.fitness = 0 --emu.print(genome.fitness) genome.maxneuron = file:read("*number") local line = file:read("*line") while line ~= "done" do if line == "oscilation" then line = "oscillation" end genome.mutationRates[line] = file:read("*number") line = file:read("*line") end local numNetworks = file:read("*number") for n=1,numNetworks do if n > 1 then table.insert(genome.genes,{}) table.insert(genome.oscillations,{}) end local numGenes = file:read("*number") for gn=1,numGenes do local gene = newGene() local enabled gene.into, gene.out, gene.weight, gene.innovation, enabled = file:read("*number", "*number", "*number", "*number", "*number") if enabled == 0 then gene.enabled = false else gene.enabled = true end table.insert(genome.genes[n],gene) end --emu.print("gn" .. #genome.genes[n]) end --emu.print("N" .. numNetworks) for n=1,numNetworks-1 do table.insert(genome.networkswitch,file:read("*number")) end --emu.print(genome.networkswitch) for n=1,numNetworks do genome.oscillations[n][1]=file:read("*number") genome.oscillations[n][2]=file:read("*number") genome.oscillations[n][3]=file:read("*number") end --emu.print(genome.oscillations) table.insert(species.genomes,genome) --emu.print(genome.oscillations) genome.old = file:read("*number") end table.insert(pool.species,species) end pool.gsid = gsidMax elseif string.sub(fileString,1,1) == "A" then local i = 1 pool = newPool() pool.species = {} pool.generation = fromBase95(string.sub(fileString,i+1,i+2)) pool.maxFitness = 0--fromBase95(string.sub(fileString,i+3,i+5))/10 if pool.maxFitness > 80000 then pool.maxFitness = pool.maxFitness - 85737.5 end previousmaxfitness = pool.maxFitness Population = fromBase95(string.sub(fileString,i+6,i+7)) bnLoops = fromBase95(string.sub(fileString,i+8,i+8)) bottleneckcounter = fromBase95(string.sub(fileString,i+9,i+9)) pool.innovation = fromBase95(string.sub(fileString,i+10,i+13)) local gsidMax = fromBase95(string.sub(fileString,i+14,i+16)) local numSpecies = fromBase95(string.sub(fileString,i+17,i+18)) i = i + 18 for s=1,numSpecies do local species = newSpecies() species.genomes = {} species.topFitness = 0--fromBase95(string.sub(fileString,i+1,i+3))/10 if species.topFitness > 80000 then species.topFitness = species.topFitness - 85737.5 end species.staleness = fromBase95(string.sub(fileString,i+4,i+4)) species.gsid = fromBase95(string.sub(fileString,i+5,i+7)) local numGenomes = fromBase95(string.sub(fileString,i+8,i+9)) i = i + 9 for g=1,numGenomes do local genome = newGenome() genome.fitness = 0--fromBase95(string.sub(fileString,i+1,i+3))/10 if genome.fitness > 80000 then genome.fitness = genome.fitness - 85737.5 end genome.fitness = 0 --emu.print(genome.fitness) genome.maxneuron = fromBase95(string.sub(fileString,i+4,i+5)) genome.mutationRates["connections"] = fromBase95(string.sub(fileString,i+6,i+8))/10000 genome.mutationRates["oscillation"] = fromBase95(string.sub(fileString,i+9,i+11))/10000 genome.mutationRates["link"] = fromBase95(string.sub(fileString,i+12,i+14))/10000 genome.mutationRates["enable"] = fromBase95(string.sub(fileString,i+15,i+17))/10000 genome.mutationRates["step"] = fromBase95(string.sub(fileString,i+18,i+20))/10000 genome.mutationRates["networkswitch"] = fromBase95(string.sub(fileString,i+21,i+23))/10000 genome.mutationRates["bias"] = fromBase95(string.sub(fileString,i+24,i+26))/10000 genome.mutationRates["node"] = fromBase95(string.sub(fileString,i+27,i+29))/10000 genome.mutationRates["disable"] = fromBase95(string.sub(fileString,i+30,i+32))/10000 local numNetworks = fromBase95(string.sub(fileString,i+33,i+33)) i = i + 33 for n=1,numNetworks do if n > 1 then table.insert(genome.genes,{}) table.insert(genome.oscillations,{}) end local numGenes = fromBase95(string.sub(fileString,i+1,i+2)) i = i + 2 for gn=1,numGenes do local gene = newGene() gene.into = fromBase95(string.sub(fileString,i+1,i+2)) gene.out = fromBase95(string.sub(fileString,i+3,i+4)) gene.weight = (fromBase95(string.sub(fileString,i+5,i+8))/10000000)-4 gene.innovation = fromBase95(string.sub(fileString,i+9,i+12)) gene.enabled = (string.sub(fileString,i+13,i+13) == "1") table.insert(genome.genes[n],gene) i = i + 13 if gene.into > 9000 then gene.into = gene.into + 991000 end if gene.out > 9000 then gene.out = gene.out + 991000 end end --emu.print("gn" .. #genome.genes[n]) end --emu.print("N" .. numNetworks) for n=1,numNetworks-1 do table.insert(genome.networkswitch,fromBase95(string.sub(fileString,i+1,i+2))/500) i = i + 2 end --emu.print(genome.networkswitch) for n=1,numNetworks do genome.oscillations[n][1]=fromBase95(string.sub(fileString,i+1,i+1)) genome.oscillations[n][2]=fromBase95(string.sub(fileString,i+2,i+2)) genome.oscillations[n][3]=fromBase95(string.sub(fileString,i+3,i+3)) i = i + 3 end --emu.print(genome.oscillations) table.insert(species.genomes,genome) --emu.print(genome.oscillations) end table.insert(pool.species,species) end pool.gsid = gsidMax else local file = io.open(filename, "r") pool = newPool() pool.generation = file:read("*number") pool.maxFitness = file:read("*number") previousmaxfitness = pool.maxFitness Population = file:read("*number") pool.innovation = file:read("*number") bottleneckcounter = file:read("*number") local numSpecies = file:read("*number") pool.gsid = numSpecies - 1 maxstale = 0 for s=1,numSpecies do local species = newSpecies() table.insert(pool.species, species) species.topFitness = file:read("*number") species.staleness = file:read("*number") if species.staleness > maxstale then maxstale = species.staleness end species.gsid = s - 1 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") genome.fitness = 0 local line = file:read("*line") while line ~= "done" do if line == "oscilation" then line = "oscillation" end genome.mutationRates[line] = file:read("*number") line = file:read("*line") end local numGenes = file:read("*number") genes1 = {} genes1Legacy = {} for n=1,numGenes do local gene = newGene() table.insert(genes1Legacy, gene) local enabled --I am able to revert back to the original loading script with FCEUX gene.into, gene.out, gene.weight, gene.innovation, enabled = file:read("*number", "*number", "*number", "*number", "*number") if enabled == 0 then gene.enabled = false else gene.enabled = true end end local genomeLegacy = newGenome() genomeLegacy.genes = {genes1Legacy} MaxNodes = 1000000 generateNetwork(genomeLegacy,false) --MaxNodes = 9000 local net = genomeLegacy.network[1] local mapping = {} local numMappedNodes = 0 for nid, neuron in pairs(net.neurons) do numMappedNodes = numMappedNodes + 1 if numMappedNodes == 174 then numMappedNodes = 176 end mapping[nid+0.5] = numMappedNodes --emu.print(mapping) end for nid, neuron in pairs(net.neurons) do for inc=1,#neuron.incoming do local geneWrong = neuron.incoming[inc] local geneRight = newGene() geneRight.weight = geneWrong.weight geneRight.enabled = geneWrong.enabled geneRight.innovation = geneWrong.innovation geneRight.out = mapping[nid+0.5] if mapping[geneWrong.into+0.5] ~= nil then geneRight.into = mapping[geneWrong.into+0.5] table.insert(genes1,geneRight) if geneRight.enabled and s == 1 and g == 2 then --emu.print("ACCEPTED " .. geneWrong.into .. " " .. geneWrong.out .. " " .. geneRight.out .. " " .. geneRight.into) end elseif s == 1 and g == 2 then emu.print("REJECTED " .. geneWrong.into .. " " .. geneWrong.out .. " " .. geneRight.out) end end --emu.print(nid) end for outputid=1,6 do local geneRight = newGene() geneRight.weight = 1.0 geneRight.enabled = true geneRight.innovation = newInnovation() geneRight.into = mapping[outputid+1000000.5] geneRight.out = 9000+outputid table.insert(genes1,geneRight) end local numGenes2 = file:read("*number") genes2 = {} genes2Legacy = {} for n=1,numGenes2 do local gene2 = newGene() table.insert(genes2Legacy, gene2) local enabled --I am able to revert back to the original loading script with FCEUX gene2.into, gene2.out, gene2.weight, gene2.innovation, enabled = file:read("*number", "*number", "*number", "*number", "*number") if enabled == 0 then gene2.enabled = false else gene2.enabled = true end end local genome2Legacy = newGenome() genome2Legacy.genes = {genes2Legacy} MaxNodes = 1000000 generateNetwork(genome2Legacy,false) MaxNodes = 9000 net = genome2Legacy.network[1] mapping = {} numMappedNodes = 0 for nid, neuron in pairs(net.neurons) do numMappedNodes = numMappedNodes + 1 if numMappedNodes == 174 then numMappedNodes = 176 end mapping[nid+0.5] = numMappedNodes --emu.print(mapping) end for nid, neuron in pairs(net.neurons) do for inc=1,#neuron.incoming do local geneWrong = neuron.incoming[inc] local geneRight = newGene() geneRight.weight = geneWrong.weight geneRight.enabled = geneWrong.enabled geneRight.innovation = geneWrong.innovation geneRight.out = mapping[nid+0.5] if mapping[geneWrong.into+0.5] ~= nil then geneRight.into = mapping[geneWrong.into+0.5] table.insert(genes2,geneRight) if geneRight.enabled and s == 1 and g == 2 then --emu.print("ACCEPTED " .. geneWrong.into .. " " .. geneWrong.out .. " " .. geneRight.out .. " " .. geneRight.into) end elseif s == 1 and g == 2 then emu.print("REJECTED " .. geneWrong.into .. " " .. geneWrong.out .. " " .. geneRight.out) end end --emu.print(nid) end for outputid=1,6 do local geneRight = newGene() geneRight.weight = 1.0 geneRight.enabled = true geneRight.innovation = newInnovation() geneRight.into = mapping[outputid+1000000.5] geneRight.out = 9000+outputid table.insert(genes2,geneRight) end ns = file:read("*number") if ns == 0 then genome.networkswitch = {} genome.oscillations = {{}} genome.genes = {genes1} else genome.networkswitch = {ns} genome.genes = {genes1,genes2} genome.oscillations = {{},{}} end local numosci = file:read("*number") for i=1,numosci do genome.oscillations[1][i] = file:read("*number") if ns > 0 then genome.oscillations[2][i] = genome.oscillations[1][i] end end if g == 1 then genomeCopy = copyGenome(genome) table.insert(species.genomes,genomeCopy) end end if s == 1 then --emu.print(species.genomes[2].genes[1]) --emu.print("*3") end bnLoops = 0 bottleneck = 0 for i=1,maxstale do bottleneck = bottleneck + 1 length= math.min(12*popOscilMultiplier,(5 + 2*bnLoops)*popOscilMultiplier) if bottleneckcounter == GBPO + length*3 then bottleneckcounter = 0 bnLoops = bnLoops + 1 end end end file:close() end --while fitnessAlreadyMeasured() do -- nextGenome() --end initializeRun() pool.currentFrame = pool.currentFrame + 1 end function playTop() local maxfitness = 0 local maxs, maxg rightmost = 0 pool.currentFrame = 0 hitblocks = 0 hiddenblocks = {} offset = 0 --offset in x coordinates for pipe timebonus = 0 --used to freeze fitness mariohole = false --reset the fallen into hole variable mariopipe = false --is mario exiting a pipe or just teleporting loop = false --is this a loop? marioPipeEnter = false --has mario entered a pipe currentTracker = 1 --tracker for the loop killtrigger = false ntrigger = true nswitch = 1 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 initializeRun() pool.currentFrame = pool.currentFrame + 1 return end function fitnesstracker() if pool.maxFitness > 0 then local file = io.open('fitnesstracker.txt', "a") file:write("Gen: " .. pool.generation .. " Species: " .. pool.currentSpecies .. " Genome: " .. pool.currentGenome .. " Fitness: ".. pool.maxFitness .. "\n") file:close() end end function writelatestgen() --used to write the generation tracker to a file local file = io.open('backups/latestgen', "w") file:write(SAVE_LOAD_FILE .. "\n") file:write(savestate_slot .. "\n") file:write(pool.generation) file:close() end function readlatestgen() --used to retrieve the latest generation from a file if FileExists('backups/latestgen') then local file = io.open('backups/latestgen', "r") SAVE_LOAD_FILE = file:read("*line") savestate_slot = file:read("*number") SavestateObj = savestate.object(savestate_slot) local latestgen = file:read("*number")-1 file:close() local name = 'backups/backup3.' .. SAVE_LOAD_FILE print('loaded: '.. name) return name else print('Please first run the AI normally before trying to fast load the latest generation') end end function keyboardinput() local keyboard = input.get() if keyboard['N'] and keyflag == false then if neural_net_disp then neural_net_disp = false else neural_net_disp = true end keyflag = true end if keyboard['M'] and keyflag == false then if mutation_rates_disp then mutation_rates_disp = false else mutation_rates_disp = true end keyflag = true end if keyboard['L'] and keyflag == false then if not nopopup then local loadyn = input.popup('Are you sure you want to load the last saved generation?') end if loadyn == 'yes' or nopopup then name = readlatestgen() loadFile(name) end keyflag = true end if keyboard['B'] and keyflag == false then if not nopopup then local loadyn = input.popup('Are you sure you want to show the topFitness genome?') end if loadyn == 'yes' or nopopup then playTop() end end if keyboard['S'] and keyflag == false then savePool() keyflag = true end if not (keyboard['N'] or keyboard['L'] or keyboard['M'] or keyboard['T'] or keyboard['B'] or keyboard['S']) and keyflag == true then keyflag = false end if keyboard['T'] and keyflag == false then if turbo then emu.speedmode("normal") turbo = false print('stop turbo') elseif not turbo and not restoreturbo then emu.speedmode("turbo") turbo = true elseif restoreturbo then restoreturbo = false end keyflag = true end end writeFile("temp.pool") requiredFitness = 0 minFitness = 0 if SAVE_LOAD_FILE == nil then if LostLevels == 1 then SAVE_LOAD_FILE = "SMBLL".. CurrentWorld+1 .."-".. CurrentLevel ..".state.pool" else SAVE_LOAD_FILE = "SMB".. CurrentWorld+1 .."-".. CurrentLevel ..".state.pool" end end if savedpool then loadFile(savedpool) end while true do keyboardinput() local backgroundColor = toRGBA(0xD0FFFFFF) gui.drawbox(0, 0, 260, 30, backgroundColor, backgroundColor) if pool.maxcounter == nil then pool.maxcounter = 0 --emu.print("caught missing variable") end local species = pool.species[pool.currentSpecies] local genome = species.genomes[pool.currentGenome] if neural_net_disp then displayGenome(genome) end if pool.generation == 0 and pool.currentSpecies ~= 1 and not turbo and AllowAutoTurbo and FirstGenSpeedup then emu.speedmode("turbo") turbo = true -- elseif pool.generation == 2 and turbo and AllowAutoTurbo and FirstGenSpeedup then -- emu.speedmode("turbo") -- turbo = false end local nscompare = rightmost / 500.0 local nsw = 1 for n=1,#genome.networkswitch do if nscompare > genome.networkswitch[n] then nsw = n+1 end end nswitch = nsw if pool.currentFrame%5 == 0 then evaluateCurrent() end joypad.set(player, controller) getPositions() local mariostate = mariostate local adjustfitness local CurrentWorld = CurrentWorld local CurrentLevel = CurrentLevel local marioX = marioX local marioY = marioY if mariostate == 2 or mariostate == 3 then --detects when mario enters a pipe marioPipeEnter = true if mariostate == 2 then adjustfitness = true else adjustfitness = false end end if (CurrentWorld == 3 and CurrentLevel == 4) or (CurrentWorld == 6 and CurrentLevel == 4) or (CurrentWorld == 7 and CurrentLevel == 3) then if currentTracker == currentscreen or currentTracker == nextscreen and not mariopipe then --follows the progress of mario currentTracker = nextscreen elseif currentTracker > nextscreen and not mariopipe and not marioPipeEnter and mariostate ~= 7 then --if mario suddenly goes back trigger loop detection loop = true elseif currentscreen == 0 and nextscreen == 0 and mariopipe and mariostate ~= 7 then -- if mario enters a piperoom negate loop detection loop = false currentTracker = nextscreen elseif nextscreen >= (currentTracker - 1) and mariopipe and mariostate ~= 7 then -- if mario goes forward in the level by entering a pipe negate loop detection loop = false currentTracker = nextscreen pipeexit = true else --if nothing is true then we are in a loop if not pipeexit then --prevents the extra one frame flicker of the tracker loop = true else pipeexit = false end end end if marioPipeEnter == true and mariostate == 7 then --set mariopipe when exiting the pipe mariopipe = true end if adjustfitness then if mariopipe == true and mariostate ~= 7 and rightmost > marioX and not mariohole then --calculate offset offset = rightmost - marioX mariopipe = false marioPipeEnter = false elseif mariopipe == true and mariostate ~= 7 and rightmost <= marioX and not mariohole then --if the exit coordinates are higher than rightmost after exiting a pipe set offset to 0 offset = 0 mariopipe = false marioPipeEnter = false end else if mariopipe == true and mariostate ~= 7 and not mariohole then --calculate offset --and rightmost > marioX offset = rightmost - marioX mariopipe = false marioPipeEnter = false end end --Custom Fix: Springs --emu.print(marioY + memory.readbyte(0xB5)*255) if (marioY + memory.readbyte(0xB5)*255 > 512)then --added the fallen into a hole variable that will activate when LuigI/O goes below screen if marioY + memory.readbyte(0xB5)*255 > 768 and not mariohole then -- Added this bit to prevent LuigI/O from being treated as fallen when he goes too high on the screen due to Lost Level springs. TimeoutConstant = 800 -- Increase the TimeoutConstant so that the game won't timeout when LuigI/O uses the spring. else mariohole = true end else -- If LuigI/O's marioY + 0xB5 byte * 255 is less than 512, he is visible on the screen TimeoutConstant = 120 --Reset TimeoutConstant back to normal when visible on screen. end local dis = false local specialy = false if marioX > 2370 and marioX < 2460 and (LostLevels == 0 and CurrentWorld == 7 and CurrentLevel == 3) then specialy = true end if marioX > 2460 and marioX < 3000 and (LostLevels == 0 and CurrentWorld == 7 and CurrentLevel == 3) then dis = true end if marioX > 3000 and marioX < 3160 and marioY < 192 and marioY > 64 and (LostLevels == 1 and CurrentWorld == 1 and CurrentLevel == 1) then dis = true end if marioX > 496 and marioX < 928 and marioY < 128 and (LostLevels == 1 and CurrentWorld == 2 and CurrentLevel == 4) then dis = true end if marioX > 928 and marioX < 1264 and marioY < 160 and (LostLevels == 1 and CurrentWorld == 2 and CurrentLevel == 4) then dis = true end if marioX > 1632 and marioX < 2384 and (marioY > 80 or hitblocks == 0) and (LostLevels == 1 and CurrentWorld == 2 and CurrentLevel == 4) then dis = true end local ymulti = 0.2 if (LostLevels == 0 and CurrentWorld == 3 and CurrentLevel > 2) then ymulti = 1 end local newright = marioX + offset - (marioY - 192)*ymulti + (50 *memory.readbyte(0x06D9)) if specialy then newright = 2370 + offset - marioY + 192 + (50 *memory.readbyte(0x06D9)) end if LostLevels == 0 and CurrentWorld == 3 and CurrentLevel == 4 then if marioX > 1680 and marioX < 2288 and marioY <= 144 then dis = true end if marioX > 300 and marioX < 1200 and marioY > 80 then dis = true end if marioX > 1512 and marioX < 1680 then if marioY <= 80 then newright = marioX + offset + 112 + (50 *memory.readbyte(0x06D9)) elseif marioY <= 144 then newright = 3360 - marioX + offset + 176 + (50 *memory.readbyte(0x06D9)) else newright = 352 + marioX + offset + 224 + (50 *memory.readbyte(0x06D9)) end elseif marioX > 1680 then newright = 352 + marioX + offset + 224 + 192 - marioY + (50 *memory.readbyte(0x06D9)) end end if newright > rightmost and not mariohole and not loop and not marioPipeEnter and not killtrigger and not falling and not dis then rightmost = newright if rightmost > species.topright then species.topright = rightmost end timebonus = timebonus + (TimeoutConstant - timeout) * 11/20 timeout = TimeoutConstant end if marioPipeEnter == true then --freeze fitness and timer when mario enters a pipe timeout = timeout + 1 timebonus = timebonus + 2/3 end --Custom Fix: Changes made for Lost Levels 3-1 so that going in the subworld trap pipe won't increase fitness and for the last part "(marioX > 2900 and memory.readbyte(0xB5)*255 > 768)", when LuigI/O goes over the flagpole thanks to the spring (yes, it can happen, I tested), fitness will not increase as well. if(CurrentWorld == 2 and CurrentLevel == 0 and LostLevels == 1) then if (mariopipe and ((marioX > 500 and marioX < 572) or (marioX > 1244 and marioX < 1396) or (marioX > 3558 and marioX < 3708))) or (marioX > 2900 and memory.readbyte(0xB5)*255 > 768) then loop = true end end if (mariostate == 4 or (memory.readbyte(0x000C) == 1 and statuscheck == 0) or ((memory.readbyte(0x06D6) ~= 0 and memory.readbyte(0x06D6) ~= 1) and marioPipeEnter)) or endlevel then --and statuscheck == 0 timeout = timeout +1 timebonus = timebonus + 2/3 endlevel = true end if endlevel and mariostate == 8 and demoruncheck == 1 and statuscheck == 3 + LostLevels then local timeoutBonus = pool.currentFrame / 4 local fitness = rightmost - pool.currentFrame / 2 +40 + timebonus genome.fitness = fitness pool.maxFitness = fitness fitnesstracker() savePool() if not rerunning then if LostLevels==1 then SAVE_LOAD_FILE = "SMBLL".. CurrentWorld+1 .."-".. CurrentLevel ..".state.pool" else SAVE_LOAD_FILE = "SMB".. CurrentWorld+1 .."-".. CurrentLevel ..".state.pool" end previous_savestate = savestate_slot savestate_slot = savestate_slot + 1 if savestate_slot == 10 then savestate_slot = 1 end Savestatebackup = savestate.object(savestate_slot) savestate.save(Savestatebackup) savestate.persist(Savestatebackup) savestate.save(SavestateObj) end SavestateObj = savestate.object(savestate_slot) endlevel = false if (turbo or restoreturbo) and not rerunning then rerunning = true requiredFitness = 0 minFitness = 0 emu.speedmode("normal") turbo = false restoreturbo = false reRun() else rerunning = false preparenext = true end end if CurrentWorld == 0 and CurrentLevel == 0 and demoruncheck == 0 then savestate.load(SavestateObj) end timeout = timeout - 1 if preparenext then rerunning = false preparenext = false GenerateNewKeepBest(genome, species.nickname) end if rightmost > maxright then maxright = rightmost end if rightmost > maxright*0.85 and AllowSlowdownNearMax then if turbo and maxright > 200 and AllowSlowdownNearMax and not restoreturbo then emu.speedmode("normal") turbo = false restoreturbo = true end elseif restoreturbo and not (requiredFitness > 10) then emu.speedmode("turbo") turbo = true restoreturbo = false end --emu.print(marioX .. " " .. marioY) local timeoutBonus = pool.currentFrame / 4 if (mariostate == 11 or mariohole or math.floor(rightmost - (pool.currentFrame) / 2 + - (timeout + timeoutBonus)*2/3 +40 + timebonus) < -50 ) and timeout > 40-timeoutBonus then local temptimeout = timeout timeout = 30 - timeoutBonus local difference = temptimeout - timeout pool.currentFrame = pool.currentFrame + difference killtrigger = true end local timeoutBonus = pool.currentFrame / 4 if timeout + timeoutBonus <= 0 then local fitness = rightmost - pool.currentFrame / 2 +40 + timebonus + hitblocks * hbValue if fitness == 0 then fitness = -1 end genome.fitness = fitness if species.topFitness < secondbest and fitness > secondbest and fitness < pool.maxFitness then --system for automatically updating the second best value secondbest = fitness elseif species.topFitness < pool.maxFitness and fitness > pool.maxFitness then secondbest = pool.maxFitness elseif species.topFitness == secondbest and fitness > secondbest and fitness < pool.maxFitness then secondbest = fitness elseif species.topFitness == secondbest and fitness > pool.maxFitness then secondbest = pool.maxFitness end if fitness > pool.maxFitness then pool.maxFitness = fitness fitnesstracker() savePool() end if fitness > pool.maxFitness - 30 then pool.maxcounter = pool.maxcounter + 1 end emu.print("Gen " .. pool.generation .. " species " .. pool.currentSpecies .. " genome " .. pool.currentGenome .. " fitness: " .. fitness) pool.currentSpecies = 1 pool.currentGenome = 1 while fitnessAlreadyMeasured() do offset = 0 --offset in x coordinates for pipe timebonus = 0 --used to freeze fitness mariohole = false --reset the fallen into hole variable mariopipe = false --is mario exiting a pipe or just teleporting loop = false --is this a loop? marioPipeEnter = false --has mario entered a pipe currentTracker = 1 --tracker for the loop killtrigger = false ntrigger = true nswitch = 1 if rerunning then rerunning = false preparenext = true end nextGenome() end initializeRun() end gui.opacity(1) --made the top banner opaque. you can change this if you want a slightly transparent banner if turbo then gui.drawtext(250, 11, "T", toRGBA(0xFFFF0000), 0x0) end gui.drawtext(5, 11, "Gen " .. pool.generation .. " species " .. pool.currentSpecies .. " genome " .. pool.currentGenome .. " (" .. math.floor(pool.measured/pool.total*100) .. "%) ".. "Pop: ".. Population, toRGBA(0xFF000000), 0x0) gui.drawtext(5, 20, "Fitness: " .. math.floor(hitblocks * hbValue + rightmost - (pool.currentFrame) / 2 + - (timeout + timeoutBonus)*2/3 +40 + timebonus) , toRGBA(0xFF000000), 0x0) --the (timeout + timeoutBonus)*2/3 makes sure that the displayed fitness remains stable for the viewers pleasure gui.drawtext(80, 20, "Max Fitness:" .. math.floor(pool.maxFitness) .. " (".. math.floor(secondbest) .. ")".. " ".. "(" .. pool.maxcounter .. "x)", toRGBA(0xFF000000), 0x0) pool.currentFrame = pool.currentFrame + 1 emu.frameadvance(); end