Code-Akisame icon

LuigI/O version 2

Code-Akisame | PRO | 05/30/18 06:17:44 PM UTC | 0 ⭐ | 3169 👁️ | Never ⏰ | []
Lua |

62.7 KB

|

None

|

0 👍

/

0 👎

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

Comments

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

    0 B

    |

    👍

    /

    👎