Code-Akisame icon

FCEUX script for Einfach Nerdig's stream

Code-Akisame | PRO | 04/22/18 06:32:33 PM UTC | 0 ⭐ | 798 👁️ | Never ⏰ | []
text |

39.49 KB

|

None

|

0 👍

/

0 👎

-- 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

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎