Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --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
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement