Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- LuigI/O by Akisame based on SethBling's MarI/O with modifications described here: https://pastebin.com/7qkeVVxi.
- -- 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, T will activate the turbo and S will save the current location in your pool.
- -- This is an early version and still a work in progress.
- -- To use this script you will need to download FCEUX and create a savestate in state 1 as player 2(luigi) for smb. You will then need to go to config -> input and set player 2 to gamepad.
- -- 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
- --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.
- --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)
- player = 2 --1 for mario 2 for luigi
- AllowAutoTurbo = true --Automatic turbo
- AllowSlowdownNearMax = true --This is slow down the turbo when it nears to furthest it has currently gotten
- FirstGenSpeedup = true --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
- os.execute("mkdir backups") --create the folder to save the pools in
- 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
- Outputs = #ButtonNames
- Population = 100
- DeltaDisjoint = 2.0
- DeltaWeights = 0.4
- DeltaThreshold = 1.0
- keyflag = false --used for keyboard input
- endlevel = false
- preparenext = false
- secondbest = 0
- maxright = 0
- restoreturbo = false
- rerunning = false
- killcounter = 0
- 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
- bottleneckcounter = 0
- MutateConnectionsChance = 0.25
- PerturbChance = 0.90
- CrossoverChance = 0.75
- LinkMutationChance = 2.0
- NodeMutationChance = 0.50
- BiasMutationChance = 0.40
- StepSize = 0.1
- DisableMutationChance = 0.4
- EnableMutationChance = 0.2
- oscilationmutationchance = 0.2
- interbreedchance = 0.03
- networkswitchmutationchance = 0.01
- replaceSecondNetworkChance = 0.01
- nswitch = true
- ntrigger = true
- ncount = 0
- TimeoutConstant = 120 --set to a higher value but see for yourself what is acceptable
- MaxNodes = 1000000
- 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
- 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
- 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
- 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
- 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 newPool()
- local pool = {}
- pool.species = {}
- pool.generation = 0
- pool.innovation = Outputs
- pool.currentSpecies = 1
- pool.currentGenome = 1
- pool.currentFrame = 0
- pool.maxFitness = 0
- pool.maxcounter = 0 --counter for %max species reached
- pool.averagemaxfitness = 0
- return pool
- end
- function newSpecies()
- local species = {}
- species.topFitness = 0
- species.staleness = 0
- species.genomes = {}
- species.averageFitness = 0
- species.nickname = 'none'
- species.turbo = 'on'
- 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.genes = {}
- genome.genes2 = {}
- genome.fitness = 0
- genome.adjustedFitness = 0
- genome.network = {}
- genome.network2 = {}
- genome.maxneuron = 0
- genome.globalRank = 0
- genome.oscilations = {}
- genome.networkswitch = 0
- for i=1,#switchtime do
- genome.oscilations[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["oscilation"] = oscilationmutationchance
- genome.mutationRates["networkswitch"] = networkswitchmutationchance
- return genome
- end
- function copyGenome(genome)
- local genome2 = newGenome()
- for g=1,#genome.genes do
- table.insert(genome2.genes, copyGene(genome.genes[g]))
- end
- for g=1,#genome.genes2 do
- table.insert(genome2.genes2, copyGene(genome.genes2[g]))
- end
- for i=1,#genome.oscilations do
- genome2.oscilations[i] = genome.oscilations[i]
- end
- genome2.networkswitch = genome.networkswitch
- 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["oscilation"] = genome.mutationRates["oscilation"]
- genome2.mutationRates["networkswitch"] = genome.mutationRates["networkswitch"]
- return genome2
- end
- function basicGenome()
- local genome = newGenome()
- local innovation = 1
- genome.maxneuron = Inputs
- mutate(genome, 1)
- mutate(genome, 2)
- return genome
- end
- function newGene()
- local gene = {}
- gene.into = 0
- gene.out = 0
- gene.weight = 0.0
- gene.enabled = true
- gene.innovation = 0
- return gene
- end
- function copyGene(gene)
- local gene2 = newGene()
- gene2.into = gene.into
- gene2.out = gene.out
- gene2.weight = gene.weight
- gene2.enabled = gene.enabled
- gene2.innovation = gene.innovation
- return gene2
- end
- function newNeuron()
- local neuron = {}
- neuron.incoming = {}
- neuron.value = 0.0
- neuron.switcher = false
- neuron.timer = 60
- neuron.multiplier = 1
- return neuron
- end
- function generateNetwork(genome)
- local network = {}
- network.neurons = {}
- for i=1,Inputs do
- network.neurons[i] = newNeuron()
- end
- for o=1,Outputs do
- network.neurons[MaxNodes+o] = newNeuron()
- end
- table.sort(genome.genes, function (a,b)
- return (a.out < b.out)
- end)
- for i=1,#genome.genes do
- local gene = genome.genes[i]
- if gene.enabled then
- if network.neurons[gene.out] == nil then
- network.neurons[gene.out] = newNeuron()
- end
- local neuron = network.neurons[gene.out]
- table.insert(neuron.incoming, gene)
- if network.neurons[gene.into] == nil then
- network.neurons[gene.into] = newNeuron()
- end
- end
- end
- genome.network = network
- local network2 = {}
- network2.neurons = {}
- for i=1,Inputs do
- network2.neurons[i] = newNeuron()
- end
- for o=1,Outputs do
- network2.neurons[MaxNodes+o] = newNeuron()
- end
- table.sort(genome.genes2, function (a,b)
- return (a.out < b.out)
- end)
- for i=1,#genome.genes2 do
- local gene = genome.genes2[i]
- if gene.enabled then
- if network2.neurons[gene.out] == nil then
- network2.neurons[gene.out] = newNeuron()
- end
- local neuron = network2.neurons[gene.out]
- table.insert(neuron.incoming, gene)
- if network2.neurons[gene.into] == nil then
- network2.neurons[gene.into] = newNeuron()
- end
- end
- end
- genome.network2 = network2
- end
- function evaluateNetwork(network, inputs, genome)
- table.insert(inputs, 1) -- bias input node
- for i = 1,#initialswitchvalue do
- if genome.oscilations[i] > 0 then
- if switchtimer[i] > 0 then
- switchtimer[i] = switchtimer[i] -1
- else
- switchtimer[i] = genome.oscilations[i]
- initialswitchvalue[i] = initialswitchvalue[i]*-1
- end
- end
- table.insert(inputs, initialswitchvalue[i])
- end
- if #inputs ~= Inputs then
- emu.print("Incorrect number of neural network inputs.")
- return {}
- 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
- return outputs
- end
- function crossover(g1, g2)
- -- Make sure g1 is the higher fitness genome
- if g2.fitness > g1.fitness then
- tempg = g1
- g1 = g2
- g2 = tempg
- end
- local child = newGenome()
- for i=1,#g1.oscilations do
- child.oscilations[i] = g1.oscilations[i]
- end
- child.networkswitch = g1.networkswitch
- local innovations2 = {}
- for i=1,#g2.genes do
- local gene = g2.genes[i]
- innovations2[gene.innovation] = gene
- end
- for i=1,#g1.genes do
- local gene1 = g1.genes[i]
- local gene2 = innovations2[gene1.innovation]
- if gene2 ~= nil and math.random(1,2) == 1 and gene2.enabled then
- table.insert(child.genes, copyGene(gene2))
- else
- table.insert(child.genes, copyGene(gene1))
- end
- end
- for i=1,#g1.genes2 do
- local gene1 = g1.genes2[i]
- local gene2 = innovations2[gene1.innovation]
- if gene2 ~= nil and math.random(1,2) == 1 and gene2.enabled then
- table.insert(child.genes2, copyGene(gene2))
- else
- table.insert(child.genes2, copyGene(gene1))
- end
- end
- child.maxneuron = math.max(g1.maxneuron,g2.maxneuron)
- for mutation,rate in pairs(g1.mutationRates) do
- child.mutationRates[mutation] = rate
- end
- return child
- end
- function randomNeuron(genes, nonInput)
- local neurons = {}
- if not nonInput then
- for i=1,Inputs do
- neurons[i] = true
- end
- end
- for o=1,Outputs do
- neurons[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 = {}
- if which == 1 then
- for i=1,#genome.genes do
- gene = genome.genes[i]
- if math.random() < PerturbChance then
- gene.weight = gene.weight + math.random() * step*2 - step
- else
- gene.weight = math.random()*4-2
- end
- end
- else
- for i=1,#genome.genes2 do
- gene = genome.genes2[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
- end
- function linkMutate(genome, forceBias, which)
- local neuron1 = 0
- local neuron2 = 0
- if which == 1 then
- neuron1 = randomNeuron(genome.genes, false)
- neuron2 = randomNeuron(genome.genes, true)
- else
- neuron1 = randomNeuron(genome.genes2, false)
- neuron2 = randomNeuron(genome.genes2, true)
- end
- 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
- end
- if which == 1 then
- if containsLink(genome.genes, newLink) then
- return
- end
- else
- if containsLink(genome.genes2, newLink) then
- return
- end
- end
- newLink.innovation = newInnovation()
- newLink.weight = math.random()*4-2
- if which == 1 then
- table.insert(genome.genes, newLink)
- else
- table.insert(genome.genes2, newLink)
- end
- end
- function nodeMutate(genome, which)
- if which == 1 then
- if #genome.genes == 0 then
- return
- end
- else
- if #genome.genes2 == 0 then
- return
- end
- end
- genome.maxneuron = genome.maxneuron + 1
- local gene = {}
- if which == 1 then
- gene = genome.genes[math.random(1,#genome.genes)]
- else
- gene = genome.genes2[math.random(1,#genome.genes2)]
- end
- 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
- if which == 1 then
- table.insert(genome.genes, gene1)
- else
- table.insert(genome.genes2, gene1)
- end
- local gene2 = copyGene(gene)
- gene2.into = genome.maxneuron
- gene2.innovation = newInnovation()
- gene2.enabled = true
- if which == 1 then
- table.insert(genome.genes, gene2)
- else
- table.insert(genome.genes2, gene2)
- end
- end
- function enableDisableMutate(genome, enable, which)
- local candidates = {}
- if which == 1 then
- for _,gene in pairs(genome.genes) do
- if gene.enabled == not enable then
- table.insert(candidates, gene)
- end
- end
- else
- for _,gene in pairs(genome.genes2) do
- if gene.enabled == not enable then
- table.insert(candidates, gene)
- end
- end
- end
- if #candidates == 0 then
- return
- end
- local gene = candidates[math.random(1,#candidates)]
- gene.enabled = not gene.enabled
- end
- function oscilationMutate(genome)
- mutationpoint = math.random(1,#genome.oscilations)
- if genome.oscilations[mutationpoint]>0 then
- if math.random(1,2) == 1 then
- genome.oscilations[mutationpoint] = genome.oscilations[mutationpoint] + 1
- else
- genome.oscilations[mutationpoint] = genome.oscilations[mutationpoint] - 1
- end
- end
- if genome.oscilations[mutationpoint]<0 then
- genome.oscilations[mutationpoint] = 0
- end
- end
- function NswitchMutate(genome)
- if math.random(1,2) == 1 then
- if math.random(1,2) == 1 then
- genome.networkswitch = genome.networkswitch + 0.1
- else
- genome.networkswitch = genome.networkswitch - 0.1
- end
- else
- if math.random(1,2) == 1 then
- genome.networkswitch = genome.networkswitch + 0.5
- else
- genome.networkswitch = genome.networkswitch - 0.5
- end
- end
- if genome.networkswitch < 1 then
- genome.networkswitch = math.random(0,8)
- end
- if genome.networkswitch > 8 then
- genome.networkswitch = math.random(0,8)
- 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, which)
- end
- p = p - 1
- end
- p = genome.mutationRates["bias"]
- while p > 0 do
- if math.random() < p then
- linkMutate(genome, true, which)
- end
- p = p - 1
- end
- 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["oscilation"]
- while p > 0 do
- if math.random() < p then
- oscilationMutate(genome)
- 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
- return sum / coincident
- end
- function sameSpecies(genome1, genome2)
- if genome1.networkswitch == 0 and genome2.networkswitch == 0 then
- local dd = DeltaDisjoint*disjoint(genome1.genes, genome2.genes)
- local dw = DeltaWeights*weights(genome1.genes, genome2.genes)
- return dd + dw < DeltaThreshold
- elseif math.abs(genome1.networkswitch - genome2.networkswitch) > 0.6 then
- return false
- else
- local dd = DeltaDisjoint*disjoint(genome1.genes, genome2.genes)
- local dw = DeltaWeights*weights(genome1.genes, genome2.genes)
- local dd2 = DeltaDisjoint*disjoint(genome1.genes2, genome2.genes2)
- local dw2 = DeltaWeights*weights(genome1.genes2, genome2.genes2)
- if dd + dw < DeltaThreshold and dd2 + dw2 < DeltaThreshold then
- return true
- else
- return false
- end
- end
- 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
- 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 remaining = math.ceil(#species.genomes/2)
- if cutToOne then
- remaining = 1
- end
- while #species.genomes > remaining do
- table.remove(species.genomes)
- end
- end
- end
- function breedChild(species)
- local child = {}
- if math.random() < 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
- if child.networkswitch > 0 then
- if math.random(1,2) == 1 then
- mutate(child, 1)
- else
- mutate(child, 2)
- end
- else
- mutate(child, 1)
- end
- 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 = {}
- secondbest = secondbestspecies()
- 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
- 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 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)
- local genome2 = newGenome()
- for g=1,#g1.genes do
- table.insert(genome2.genes, copyGene(g1.genes[g]))
- end
- for i=1,#g1.oscilations do
- genome2.oscilations[i] = g1.oscilations[i]
- end
- genome2.networkswitch = g1.networkswitch
- 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["oscilation"] = g1.mutationRates["oscilation"]
- genome2.mutationRates["networkswitch"] = g1.mutationRates["networkswitch"]
- if g2.networkswitch > 0 then
- if math.random(1,2) == 1 then
- for g=1,#g2.genes do
- table.insert(genome2.genes2, copyGene(g2.genes[g]))
- end
- else
- for g=1,#g2.genes2 do
- table.insert(genome2.genes2, copyGene(g2.genes2[g]))
- end
- end
- else
- for g=1,#g2.genes do
- table.insert(genome2.genes2, copyGene(g2.genes[g]))
- end
- end
- if genome2.networkswitch == 0 then
- local switch = math.pow(math.floor(g1.fitness/500),2)
- genome2.networkswitch = math.ceil(math.sqrt(math.random(1,switch)))+(math.random(0,9)/10)
- end
- print('network switch: '.. genome2.networkswitch)
- print('end number of genes2: '.. #genome2.genes2)
- return genome2
- end
- function newGeneration()
- if pool.maxFitness > previousmaxfitness then
- previousmaxfitness = pool.maxFitness
- bottleneckcounter = 0
- if AllowAutoTurbo then
- emu.speedmode("normal")
- turbo = false
- end
- else
- bottleneckcounter = bottleneckcounter + 1
- end
- if bottleneckcounter < 7 and Population > 300 then
- if pool.generation < 5 then
- Population = math.floor(Population * 0.9)
- else
- Population = math.floor(Population * 0.95)
- end
- if Population < 300 then
- Population = 300
- end
- elseif bottleneckcounter > 10 and Population <900 then
- Population = math.floor(Population * 1.10)
- if Population > 900 then
- Population = 900
- end
- end
- if bottleneckcounter > 25 then
- bottleneckcounter = 0
- end
- if bottleneckcounter > TurboGenBottleneck and AllowAutoTurbo and BottleneckTurbo then
- emu.speedmode("turbo")
- turbo = true
- end
- cullSpecies(false) -- Cull the bottom half of each species
- rankGlobally()
- removeStaleSpecies()
- AverageMaxFitness()
- calculateStandardDeviationMax()
- rankGlobally()
- for s = 1,#pool.species do
- local species = pool.species[s]
- calculateAverageFitness(species)
- end
- removeWeakSpecies()
- local sum = totalAverageFitness()
- local children = {}
- for s = 1,#pool.species do
- local species = pool.species[s]
- breed = math.floor(species.averageFitness / sum * Population) - 1
- if breed > 10 then
- breed = math.ceil(breed * math.sqrt(species.topFitness / pool.maxFitness))
- end
- if breed > Population/4 then
- breed = 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 bottleneckcounter > 23 then
- if math.random() < 0.1 then
- local tfit = pool.species[i].topFitness
- child = copyGenome(pool.species[i].genomes[1])
- switchlocation = math.floor(tfit/500)
- if child.networkswitch == 0 then
- if math.random(1,2) == 1 then
- switchlocation = switchlocation + (math.random(0,10)/10)
- end
- child.networkswitch = switchlocation
- table.insert(children, child)
- end
- end
- 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]))
- 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]))
- 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 rem_gen = 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)
- rem_gen = rem_gen +1
- end
- elseif species.topFitness >= secondbest and #species.genomes>Population/7 then
- while #species.genomes > Population/7 do
- table.remove(species.genomes)
- rem_gen = rem_gen +1
- end
- end
- end
- for x=1,rem_gen 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
- 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
- end
- pool.generation = pool.generation + 1
- pool.maxcounter = 0 --reset max fitness counter
- writeFile("backups/backup." .. pool.generation .. "." .. SAVE_LOAD_FILE)
- writelatestgen() --tracker for the latest generation
- end
- function initializePool()
- pool = newPool()
- for i=1,Population 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 initializeRun()
- savestate.load(SavestateObj);
- rightmost = 0
- pool.currentFrame = 0
- timeout = TimeoutConstant
- clearJoypad()
- currentTracker = memory.readbyte(0x071A) --sets the current tracker to the current screen
- local species = pool.species[pool.currentSpecies]
- local genome = species.genomes[pool.currentGenome]
- for i=1,#genome.oscilations do
- switchtimer[i] = genome.oscilations[i]
- initialswitchvalue[i] = 1
- end
- generateNetwork(genome)
- evaluateCurrent()
- end
- function reRun()
- savestate.load(savestate.object(previous_savestate));
- rightmost = 0
- pool.currentFrame = 0
- 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 = true
- local species = pool.species[pool.currentSpecies]
- local genome = species.genomes[pool.currentGenome]
- for i=1,#genome.oscilations do
- switchtimer[i] = genome.oscilations[i]
- initialswitchvalue[i] = 1
- end
- generateNetwork(genome)
- evaluateCurrent()
- end
- function evaluateCurrent()
- local species = pool.species[pool.currentSpecies]
- local genome = species.genomes[pool.currentGenome]
- inputs = getInputs()
- if nswitch then
- controller = evaluateNetwork(genome.network, inputs, genome)
- else
- controller = evaluateNetwork(genome.network2, inputs, genome)
- end
- 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 genome.fitness ~= 0
- end
- function GenerateNewKeepBest(genome)
- Population = 1000
- previousbest = copyGenome(genome)
- previousmaxfitness = 1
- maxright = 1
- 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
- pool = newPool()
- addToSpecies(previousbest)
- for i=1,Population-1 do
- basic = basicGenome()
- addToSpecies(basic)
- end
- initializeRun()
- end
- function displayGenome(genome)
- gui.opacity(0.53)
- if not genome then return end
- local network = {}
- if nswitch then
- network = genome.network
- else
- network = genome.network2
- end
- 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
- biasCell.value = network.neurons[Inputs-#initialswitchvalue].value
- cells[Inputs-#initialswitchvalue] = biasCell
- for i=0,#initialswitchvalue-1 do
- local switchCell = {}
- switchCell.x = 80-10*#initialswitchvalue+10*i
- switchCell.y = 110
- switchCell.value = network.neurons[Inputs-i].value
- cells[Inputs-i] = switchCell
- end
- gui.drawbox(215,32,245,82,toRGBA(0x80808080),toRGBA(0x00000000))
- 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
- local color
- 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 = {}
- if nswitch then
- genes = genome.genes
- else
- genes = genome.genes2
- end
- 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
- local color = math.floor((cell.value+1)/2*256)
- if color > 255 then color = 255 end
- if color < 0 then color = 0 end
- local opacity = 0xFF000000
- if cell.value == 0 then
- opacity = 0x50000000
- end
- 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 writeFile(filename)
- local file = io.open(filename, "w")
- file:write(pool.generation .. "\n")
- file:write(pool.maxFitness .. "\n")
- file:write(Population .. "\n")
- file:write(pool.innovation .. "\n")
- file:write(bottleneckcounter .. "\n")
- file:write(#pool.species .. "\n")
- for n,species in pairs(pool.species) do
- file:write(species.topFitness .. "\n")
- file:write(species.staleness .. "\n")
- file:write(#species.genomes .. "\n")
- for m,genome in pairs(species.genomes) do
- file:write(genome.fitness .. "\n")
- file:write(genome.maxneuron .. "\n")
- for mutation,rate in pairs(genome.mutationRates) do
- file:write(mutation .. "\n")
- file:write(rate .. "\n")
- end
- file:write("done\n")
- file:write(#genome.genes .. "\n")
- for l,gene in pairs(genome.genes) do
- file:write(gene.into .. " ")
- file:write(gene.out .. " ")
- file:write(gene.weight .. " ")
- file:write(gene.innovation .. " ")
- if(gene.enabled) then
- file:write("1\n")
- else
- file:write("0\n")
- end
- end
- file:write(#genome.genes2 .. "\n")
- for l,gene in pairs(genome.genes2) 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
- file:write(genome.networkswitch .. "\n")
- file:write(#genome.oscilations .. "\n")
- for i=1,#genome.oscilations do
- file:write(genome.oscilations[i] .. "\n")
- end
- end
- end
- file:close()
- end
- function savePool() --used to save when a new max fitness is reached
- local filename = "backups/backup." .. pool.generation .. "." .. SAVE_LOAD_FILE
- writeFile(filename)
- emu.print("saved pool due to new max fitness") --suggestion by DeltaLeeds
- end
- function loadFile(filename)
- local file = io.open(filename, "r")
- pool = newPool()
- pool.generation = file:read("*number")
- pool.maxFitness = file:read("*number")
- Population = file:read("*number")
- pool.innovation = file:read("*number")
- bottleneckcounter = file:read("*number")
- local numSpecies = file:read("*number")
- for s=1,numSpecies do
- local species = newSpecies()
- table.insert(pool.species, species)
- species.topFitness = file:read("*number")
- species.staleness = file:read("*number")
- local numGenomes = file:read("*number")
- for g=1,numGenomes do
- local genome = newGenome()
- table.insert(species.genomes, genome)
- genome.fitness = file:read("*number")
- genome.maxneuron = file:read("*number")
- local line = file:read("*line")
- while line ~= "done" do
- genome.mutationRates[line] = file:read("*number")
- line = file:read("*line")
- end
- local numGenes = file:read("*number")
- for n=1,numGenes do
- local gene = newGene()
- table.insert(genome.genes, 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 numGenes2 = file:read("*number")
- for n=1,numGenes2 do
- local gene2 = newGene()
- table.insert(genome.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 enabled == 0 then
- gene2.enabled = false
- else
- gene2.enabled = true
- end
- end
- genome.networkswitch = file:read("*number")
- local numosci = file:read("*number")
- for i=1,numosci do
- genome.oscilations[i] = file:read("*number")
- end
- end
- end
- file:close()
- while fitnessAlreadyMeasured() do
- nextGenome()
- end
- initializeRun()
- pool.currentFrame = pool.currentFrame + 1
- end
- function playTop()
- local maxfitness = 0
- local maxs, maxg
- for s,species in pairs(pool.species) do
- for g,genome in pairs(species.genomes) do
- if genome.fitness > maxfitness then
- maxfitness = genome.fitness
- maxs = s
- maxg = g
- end
- end
- end
- pool.currentSpecies = maxs
- pool.currentGenome = maxg
- pool.maxFitness = maxfitness
- 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
- 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")
- file:close()
- local name = 'backups/backup.'.. latestgen .. '.' .. SAVE_LOAD_FILE
- print('loaded: '.. name)
- return name
- 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['O'] and keyflag == false then
- pcall(InsertGenome)
- keyflag = true
- end
- if keyboard['U'] and keyflag == false then
- pcall(PlaceGenome)
- keyflag = true
- 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
- if savedpool then
- loadFile(savedpool)
- end
- writeFile("temp.pool")
- requiredFitness = 0
- if SAVE_LOAD_FILE == nil then
- SAVE_LOAD_FILE = "SMB".. CurrentWorld+1 .."-".. CurrentLevel ..".state.pool"
- 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
- end
- if genome.networkswitch>0 then
- if math.floor(rightmost/10 / genome.networkswitch) % 50 == 0 and ntrigger == false then
- if nswitch == true then
- nswitch = false
- else
- nswitch = true
- end
- ntrigger = true
- elseif math.floor(rightmost/10 / genome.networkswitch) % 50 ~= 0 and ntrigger == true then
- ntrigger = false
- end
- end
- if pool.currentFrame%5 == 0 then
- evaluateCurrent()
- end
- joypad.set(player, controller)
- getPositions()
- 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
- if marioY + memory.readbyte(0xB5)*255 > 512 then --added the fallen into a hole variable that will activate when mario goes below screen
- mariohole = true
- end
- if marioX > 2370 and marioX < 3000 and (CurrentWorld == 7 and CurrentLevel == 3) then
- if marioX < 2460 then
- if 2370 + offset - marioY + 192 + (50 *memory.readbyte(0x06D9)) > rightmost and not mariohole and not loop and not marioPipeEnter and not killtrigger and not falling then
- rightmost = 2370 + offset - marioY + 192 + (50 *memory.readbyte(0x06D9))
- timebonus = timebonus + (TimeoutConstant - timeout) * 11/20
- timeout = TimeoutConstant
- end
- end
- else
- if marioX + offset - marioY/5 + 192/5 + (50 *memory.readbyte(0x06D9)) > rightmost and not mariohole and not loop and not marioPipeEnter and not killtrigger and not falling then
- rightmost = marioX + offset - marioY/5 + 192/5 + (50 *memory.readbyte(0x06D9))
- timebonus = timebonus + (TimeoutConstant - timeout) * 11/20
- timeout = TimeoutConstant
- end
- end
- if marioPipeEnter == true then --freeze fitness and timer when mario enters a pipe
- timeout = timeout + 1
- timebonus = timebonus + 2/3
- 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 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
- SAVE_LOAD_FILE = "SMB".. CurrentWorld+1 .."-".. CurrentLevel ..".state.pool"
- 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)
- killcounter = 0
- end
- SavestateObj = savestate.object(savestate_slot)
- endlevel = false
- if (turbo or restoreturbo) and not rerunning then
- rerunning = true
- requiredFitness = 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)
- 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 species.turbo == 'off' then
- if turbo then
- emu.speedmode("normal")
- turbo = false
- end
- elseif requiredFitness > rightmost and requiredFitness > 10 and not turbo then
- emu.speedmode("turbo")
- turbo = true
- restoreturbo = false
- elseif requiredFitness < rightmost and turbo and requiredFitness > 10 then
- emu.speedmode("normal")
- turbo = false
- restoreturbo = true
- elseif restoreturbo and not (requiredFitness > 10) then
- emu.speedmode("turbo")
- turbo = true
- restoreturbo = false
- end
- 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
- if fitness == 0 then
- fitness = -1
- end
- genome.fitness = fitness
- 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 = true
- if rerunning then
- rerunning = false
- preparenext = true
- end
- nextGenome()
- end
- initializeRun()
- end
- local measured = 0
- local total = 0
- for _,species in pairs(pool.species) do
- for _,genome in pairs(species.genomes) do
- total = total + 1
- if genome.fitness ~= 0 then
- measured = measured + 1
- end
- end
- end
- gui.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(measured/total*100) .. "%) ".. "Pop: ".. Population, toRGBA(0xFF000000), 0x0)
- gui.drawtext(5, 20, "Fitness: " .. math.floor(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