Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- MarI/O by SethBling rewritten for use with FCEUX by Akisame (used 3DI70R's version as a reference to save time but I have been told by him that he used juvester's script as a reference https://github.com/juvester/mari-o-fceux.)
- -- I would like to stress that we tried to keep the algorithm as close to the version of SethBling as possible.
- -- Pressing N will show the Neural net, pressing M will show the mutation rates, pressing L will load the last saved generation and pressing T will display the genome with the top fitness
- -- This file includes all the fixes from my bizhawk version (https://pastebin.com/dKYFKg2E) and an extra fix to hopefully prevent the formation of a monospecies when we encounter a major bottleneck
- -- If you want to start your own stream with this script I would appreciate it if you would include this file as well as SethBling's original version in the description
- -- To use this script just download FCEUX and create a savestate in state 1 for smb. click file -> lua -> 'new lua script window' and load the script.
- -- Have fun!
- --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 = 'backups/backup.429.SMB1-1.state.pool'
- --savedpool = "backups/backup.5.SMB1-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 = "SMB1-1.state.pool" --filename to be used
- 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)
- Inputs = InputSize+1
- Outputs = #ButtonNames
- Population = 300
- DeltaDisjoint = 2.0
- DeltaWeights = 0.4
- DeltaThreshold = 1.0
- keyflag = false --used for turning the neural network on and off by pressin 'n'
- 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
- MutateConnectionsChance = 0.25
- PerturbChance = 0.90
- CrossoverChance = 0.75
- LinkMutationChance = 2.0
- NodeMutationChance = 0.50
- BiasMutationChance = 0.40
- StepSize = 0.1
- DisableMutationChance = 0.4
- EnableMutationChance = 0.2
- TimeoutConstant = 90 --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)
- 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)
- 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
- --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 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
- return pool
- end
- function newSpecies()
- local species = {}
- species.topFitness = 0
- species.staleness = 0
- species.genomes = {}
- species.averageFitness = 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.genes = {}
- genome.fitness = 0
- genome.adjustedFitness = 0
- genome.network = {}
- genome.maxneuron = 0
- genome.globalRank = 0
- 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
- return genome
- end
- function copyGenome(genome)
- local genome2 = newGenome()
- for g=1,#genome.genes do
- table.insert(genome2.genes, copyGene(genome.genes[g]))
- end
- genome2.maxneuron = genome.maxneuron
- genome2.mutationRates["connections"] = genome.mutationRates["connections"]
- genome2.mutationRates["link"] = genome.mutationRates["link"]
- genome2.mutationRates["bias"] = genome.mutationRates["bias"]
- genome2.mutationRates["node"] = genome.mutationRates["node"]
- genome2.mutationRates["enable"] = genome.mutationRates["enable"]
- genome2.mutationRates["disable"] = genome.mutationRates["disable"]
- return genome2
- end
- function basicGenome()
- local genome = newGenome()
- local innovation = 1
- genome.maxneuron = Inputs
- mutate(genome)
- return genome
- end
- function newGene()
- local gene = {}
- gene.into = 0
- gene.out = 0
- gene.weight = 0.0
- gene.enabled = true
- gene.innovation = 0
- return gene
- end
- function copyGene(gene)
- local gene2 = newGene()
- gene2.into = gene.into
- gene2.out = gene.out
- gene2.weight = gene.weight
- gene2.enabled = gene.enabled
- gene2.innovation = gene.innovation
- return gene2
- end
- function newNeuron()
- local neuron = {}
- neuron.incoming = {}
- neuron.value = 0.0
- 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
- end
- function evaluateNetwork(network, inputs)
- table.insert(inputs, 1)
- 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
- neuron.value = sigmoid(sum)
- 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()
- local innovations2 = {}
- for i=1,#g2.genes do
- local gene = g2.genes[i]
- innovations2[gene.innovation] = gene
- end
- for i=1,#g1.genes do
- local gene1 = g1.genes[i]
- local gene2 = innovations2[gene1.innovation]
- if gene2 ~= nil and math.random(2) == 1 and gene2.enabled then
- table.insert(child.genes, copyGene(gene2))
- else
- table.insert(child.genes, copyGene(gene1))
- end
- end
- child.maxneuron = math.max(g1.maxneuron,g2.maxneuron)
- for mutation,rate in pairs(g1.mutationRates) do
- child.mutationRates[mutation] = rate
- end
- return child
- end
- function randomNeuron(genes, nonInput)
- local neurons = {}
- if not nonInput then
- for i=1,Inputs do
- neurons[i] = true
- end
- end
- for o=1,Outputs do
- neurons[MaxNodes+o] = true
- end
- for i=1,#genes do
- if (not nonInput) or genes[i].into > Inputs then
- neurons[genes[i].into] = true
- end
- if (not nonInput) or genes[i].out > Inputs then
- neurons[genes[i].out] = true
- end
- end
- local count = 0
- for _,_ in pairs(neurons) do
- count = count + 1
- end
- local n = math.random(1, count)
- for k,v in pairs(neurons) do
- n = n-1
- if n == 0 then
- return k
- end
- end
- return 0
- end
- function containsLink(genes, link)
- for i=1,#genes do
- local gene = genes[i]
- if gene.into == link.into and gene.out == link.out then
- return true
- end
- end
- end
- function pointMutate(genome)
- local step = genome.mutationRates["step"]
- for i=1,#genome.genes do
- local gene = genome.genes[i]
- if math.random() < PerturbChance then
- gene.weight = gene.weight + math.random() * step*2 - step
- else
- gene.weight = math.random()*4-2
- end
- end
- end
- function linkMutate(genome, forceBias)
- local neuron1 = randomNeuron(genome.genes, false)
- local neuron2 = randomNeuron(genome.genes, true)
- local newLink = newGene()
- if neuron1 <= Inputs and neuron2 <= Inputs then
- --Both input nodes
- return
- end
- if neuron2 <= Inputs then
- -- Swap output and input
- local temp = neuron1
- neuron1 = neuron2
- neuron2 = temp
- end
- newLink.into = neuron1
- newLink.out = neuron2
- if forceBias then
- newLink.into = Inputs
- end
- if containsLink(genome.genes, newLink) then
- return
- end
- newLink.innovation = newInnovation()
- newLink.weight = math.random()*4-2
- table.insert(genome.genes, newLink)
- end
- function nodeMutate(genome)
- if #genome.genes == 0 then
- return
- end
- genome.maxneuron = genome.maxneuron + 1
- local gene = genome.genes[math.random(1,#genome.genes)]
- if not gene.enabled then
- return
- end
- gene.enabled = false
- local gene1 = copyGene(gene)
- gene1.out = genome.maxneuron
- gene1.weight = 1.0
- gene1.innovation = newInnovation()
- gene1.enabled = true
- table.insert(genome.genes, gene1)
- local gene2 = copyGene(gene)
- gene2.into = genome.maxneuron
- gene2.innovation = newInnovation()
- gene2.enabled = true
- table.insert(genome.genes, gene2)
- end
- function enableDisableMutate(genome, enable)
- local candidates = {}
- for _,gene in pairs(genome.genes) do
- if gene.enabled == not enable then
- table.insert(candidates, gene)
- end
- end
- if #candidates == 0 then
- return
- end
- local gene = candidates[math.random(1,#candidates)]
- gene.enabled = not gene.enabled
- end
- function mutate(genome)
- for mutation,rate in pairs(genome.mutationRates) do
- if math.random(1,2) == 1 then
- genome.mutationRates[mutation] = 0.95*rate
- else
- genome.mutationRates[mutation] = 1.05263*rate
- end
- end
- if math.random() < genome.mutationRates["connections"] then
- pointMutate(genome)
- end
- local p = genome.mutationRates["link"]
- while p > 0 do
- if math.random() < p then
- linkMutate(genome, false)
- end
- p = p - 1
- end
- p = genome.mutationRates["bias"]
- while p > 0 do
- if math.random() < p then
- linkMutate(genome, true)
- end
- p = p - 1
- end
- p = genome.mutationRates["node"]
- while p > 0 do
- if math.random() < p then
- nodeMutate(genome)
- end
- p = p - 1
- end
- p = genome.mutationRates["enable"]
- while p > 0 do
- if math.random() < p then
- enableDisableMutate(genome, true)
- end
- p = p - 1
- end
- p = genome.mutationRates["disable"]
- while p > 0 do
- if math.random() < p then
- enableDisableMutate(genome, false)
- end
- p = p - 1
- end
- end
- function disjoint(genes1, genes2)
- local i1 = {}
- for i = 1,#genes1 do
- local gene = genes1[i]
- i1[gene.innovation] = true
- end
- local i2 = {}
- for i = 1,#genes2 do
- local gene = genes2[i]
- i2[gene.innovation] = true
- end
- local disjointGenes = 0
- for i = 1,#genes1 do
- local gene = genes1[i]
- if not i2[gene.innovation] then
- disjointGenes = disjointGenes+1
- end
- end
- for i = 1,#genes2 do
- local gene = genes2[i]
- if not i1[gene.innovation] then
- disjointGenes = disjointGenes+1
- end
- end
- local n = math.max(#genes1, #genes2)
- return disjointGenes / n
- end
- function weights(genes1, genes2)
- local i2 = {}
- for i = 1,#genes2 do
- local gene = genes2[i]
- i2[gene.innovation] = gene
- end
- local sum = 0
- local coincident = 0
- for i = 1,#genes1 do
- local gene = genes1[i]
- if i2[gene.innovation] ~= nil then
- local gene2 = i2[gene.innovation]
- sum = sum + math.abs(gene.weight - gene2.weight)
- coincident = coincident + 1
- end
- end
- return sum / coincident
- end
- function sameSpecies(genome1, genome2)
- local dd = DeltaDisjoint*disjoint(genome1.genes, genome2.genes)
- local dw = DeltaWeights*weights(genome1.genes, genome2.genes)
- return dd + dw < DeltaThreshold
- end
- function rankGlobally()
- local global = {}
- for s = 1,#pool.species do
- local species = pool.species[s]
- for g = 1,#species.genomes do
- table.insert(global, species.genomes[g])
- end
- end
- table.sort(global, function (a,b)
- return (a.fitness < b.fitness)
- end)
- for g=1,#global do
- global[g].globalRank = g
- end
- end
- function calculateAverageFitness(species)
- local total = 0
- for g=1,#species.genomes do
- local genome = species.genomes[g]
- total = total + genome.globalRank
- end
- species.averageFitness = total / #species.genomes
- end
- function totalAverageFitness()
- local total = 0
- for s = 1,#pool.species do
- local species = pool.species[s]
- total = total + species.averageFitness
- end
- return total
- end
- function cullSpecies(cutToOne)
- for s = 1,#pool.species do
- local species = pool.species[s]
- table.sort(species.genomes, function (a,b)
- return (a.fitness > b.fitness)
- end)
- local remaining = math.ceil(#species.genomes/2)
- if cutToOne then
- remaining = 1
- end
- while #species.genomes > remaining do
- table.remove(species.genomes)
- end
- end
- end
- function breedChild(species)
- local child = {}
- if math.random() < CrossoverChance then
- g1 = species.genomes[math.random(1, #species.genomes)]
- g2 = species.genomes[math.random(1, #species.genomes)]
- child = crossover(g1, g2)
- else
- g = species.genomes[math.random(1, #species.genomes)]
- child = copyGenome(g)
- end
- mutate(child)
- return child
- end
- function secondbestspecies() --added this to help prevent the formation of monospecies when MarI/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 = {}
- local 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 newGeneration()
- cullSpecies(false) -- Cull the bottom half of each species
- rankGlobally()
- removeStaleSpecies()
- rankGlobally()
- for s = 1,#pool.species do
- local species = pool.species[s]
- calculateAverageFitness(species)
- end
- removeWeakSpecies()
- local sum = totalAverageFitness()
- local children = {}
- for s = 1,#pool.species do
- local species = pool.species[s]
- breed = math.floor(species.averageFitness / sum * Population) - 1
- for i=1,breed do
- table.insert(children, breedChild(species))
- end
- end
- cullSpecies(true) -- Cull all but the top member of each species
- while #children + #pool.species < Population do
- local species = pool.species[math.random(1, #pool.species)]
- table.insert(children, breedChild(species))
- end
- for c=1,#children do
- local child = children[c]
- addToSpecies(child)
- end
- pool.generation = pool.generation + 1
- 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(1, 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]
- generateNetwork(genome)
- evaluateCurrent()
- end
- function evaluateCurrent()
- local species = pool.species[pool.currentSpecies]
- local genome = species.genomes[pool.currentGenome]
- inputs = getInputs()
- controller = evaluateNetwork(genome.network, inputs)
- 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(1, 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 displayGenome(genome)
- gui.opacity(0.53)
- if not genome then return end
- local network = genome.network
- 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].value
- cells[Inputs] = biasCell
- 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
- cells[n] = cell
- end
- end
- for n=1,4 do
- for _,gene in pairs(genome.genes) do
- if gene.enabled then
- local c1 = cells[gene.into]
- local c2 = cells[gene.out]
- if gene.into > Inputs and gene.into <= 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
- color = opacity + color*0x10000 + color*0x100 + color
- gui.drawbox(cell.x-2,cell.y-2,cell.x+2,cell.y+2,toRGBA(color), toRGBA(opacity))
- end
- end
- for _,gene in pairs(genome.genes) do
- if gene.enabled then
- local c1 = cells[gene.into]
- local c2 = cells[gene.out]
- local opacity = 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 nodes
- 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 < 50 then -- remove non active nodes 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(#pool.species .. "\n")
- for n,species in pairs(pool.species) do
- file:write(species.topFitness .. "\n")
- file:write(species.staleness .. "\n")
- file:write(#species.genomes .. "\n")
- for m,genome in pairs(species.genomes) do
- file:write(genome.fitness .. "\n")
- file:write(genome.maxneuron .. "\n")
- for mutation,rate in pairs(genome.mutationRates) do
- file:write(mutation .. "\n")
- file:write(rate .. "\n")
- end
- file:write("done\n")
- file:write(#genome.genes .. "\n")
- for l,gene in pairs(genome.genes) do
- file:write(gene.into .. " ")
- file:write(gene.out .. " ")
- file:write(gene.weight .. " ")
- file:write(gene.innovation .. " ")
- if(gene.enabled) then
- file:write("1\n")
- else
- file:write("0\n")
- end
- end
- end
- end
- file:close()
- end
- function savePool() --used to save when a new max fitness is reached
- local filename = 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")
- 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
- 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 writelatestgen() --used to write the generation tracker to a file
- local file = io.open('backups/latestgen', "w")
- 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")
- local latestgen = file:read()
- 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['T'] 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 not (keyboard['N'] or keyboard['L'] or keyboard['M']) and keyflag == true then
- keyflag = false
- end
- end
- if savedpool then
- loadFile(savedpool)
- end
- writeFile("temp.pool")
- while true do
- keyboardinput()
- local backgroundColor = toRGBA(0xD0FFFFFF)
- gui.drawbox(0, 0, 200, 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.currentFrame%5 == 0 then
- evaluateCurrent()
- end
- joypad.set(1, controller)
- getPositions()
- if mariostate == 2 or mariostate == 3 then --detects when mario enters a pipe
- marioPipeEnter = true
- 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 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
- 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 + offset > rightmost and mariohole == false and loop == false and marioPipeEnter == false then
- rightmost = marioX + offset
- if TimeoutConstant - timeout > 20 then --this is required for 4-4 so walking left will not give a big penalty
- timebonus = timebonus + (TimeoutConstant - timeout) * 13/20
- end
- timeout = TimeoutConstant
- end
- if marioPipeEnter == true then --freeze fitness and timer when mario enters a pipe
- timeout = timeout + 1
- timebonus = timebonus + 2/3
- end
- timeout = timeout - 1
- local timeoutBonus = pool.currentFrame / 4
- if timeout + timeoutBonus <= 0 then
- local fitness = rightmost - pool.currentFrame / 2 +20 + timebonus
- if fitness == 0 then
- fitness = -1
- end
- genome.fitness = fitness
- if fitness > pool.maxFitness then
- pool.maxFitness = fitness
- 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
- 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
- gui.drawtext(5, 11, "Gen " .. pool.generation .. " species " .. pool.currentSpecies .. " genome " .. pool.currentGenome .. " (" .. math.floor(measured/total*100) .. "%)", toRGBA(0xFF000000), 0x0)
- gui.drawtext(5, 20, "Fitness: " .. math.floor(rightmost - (pool.currentFrame) / 2 + - (timeout + timeoutBonus)*2/3 +20 + 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.ceil(pool.maxcounter/Population*100) .. "%)", toRGBA(0xFF000000), 0x0)
- pool.currentFrame = pool.currentFrame + 1
- emu.frameadvance();
- end
Add Comment
Please, Sign In to add comment