Code-Akisame icon

LuigI/O Version 3 with Hard mode addons

Code-Akisame | PRO | 12/16/18 10:07:24 AM UTC | 0 ⭐ | 1463 👁️ | Never ⏰ | []
Lua |

98.03 KB

|

None

|

0 👍

/

0 👎

--LuigI/O v3.0, a neuroevolutionary AI designed to teach itself how to beat Super Mario Bros and Lost Levels
 
--Based on MarI/O created by Sethbling
--LuigI/O versions 1, 2, and many minor adjustments and bugfixes made by Akisame
--LuigI/O version 3 made by Electra & Akisame
--Thanks to LuigI/O testers Akisame, Electra, Jonathan, AlejoGDOfficial, and Niko.
 
--V3 changelog: https://pastebin.com/KxtgyRe6
 
-- Pressing N will show the Neural net, pressing M will show the mutation rates, pressing L will load the last saved generation, pressing B will display the genome with the top fitness.
 
-- To use this script you will need to download FCEUX and create a savestate in state 1 as as player 1 (mario) for smb or as either player in Lost Levels.
-- If you are playing Lost Levels, set this variable to 1:
LostLevels = 0
-- If you want to play as Luigi in SMB1, set this variable to 2:
player = 2
-- To set the game in turbo, do NES -> Emulation Speed -> Turbo, also make sure to set priority to realtime in Task Manager
-- If you want it to go as fast as possible turn off the neural net win N, it doubles the speed basically
 
-- Click file -> lua -> 'new lua script window' and load the script.
-- Have fun!
 
-- Keep in mind that this code is provided for Non-Commercial usage and it is not permitted to request financial compensation (either by means of ads, donations, subscriptions or other means) for selling, streaming or otherwise previewing this code or its execution.
 
--FCEUX doesn't allow for user string input but if you press 'L' on your keyboard it should load the latest generation that was saved
--for loading specific generations replace nil with the filename as shown in the example below
savedpool = nil --"backups/backup.666.SMB8-3.state.pool"
--savedpool = "backup.71.SMB2-4.state.pool"
--savedpool = "backups/backup.118.SMB2-1.state.pool"
 
-- HUD options. These can be changed on the fly by pressing 'M' for the mutation rates and 'N' for the network
mutation_rates_disp = false
neural_net_disp = true
 
--set to false if you want warning popups before loading anything that might cause you to lose your current progress
nopopup = true
 
savestate_slot = 1 -- Load the level from FCEUX savestate slot. Set to 3 if you are loading a level that mario has reached by completing another level
 
--SAVE_LOAD_FILE = "test.pool" --filename to be used
SAVE_LOAD_FILE = nil --auto generate appropriate name from level values in ram (might not completely match with actual level)
 
AllowAutoTurbo = true --Automatic turbo
AllowSlowdownNearMax = false --This is slow down the turbo when it nears to furthest it has currently gotten
FirstGenSpeedup = false --This will speed up the first generation
BottleneckTurbo = false --Activate turbo on bottleneck detection
TurboGenBottleneck = 20 --will start the turbo when it is in a bottleneck for 20 generations. Please adjust to match your taste
 
World43SavestateSlot = 2
 
activateNicknames = true --allows you to give species nicknames
displayBasicInfo = true --writes basic info about a species to a file so you can show it in a stream
 
os.execute("mkdir backups") --create the folder to save the pools in
os.execute("mkdir v3wins") --create a folder for the v3 winning genomes
os.execute("mkdir v2wins") --create a folder for the v2 winning genomes
 
 
SavestateObj = savestate.object(savestate_slot)
 
    ButtonNames = {
        "A",
        "B",
        "up",
        "down",
        "left",
        "right",
    }
 
 
BoxRadius = 6
InputSize = (BoxRadius*2+1)*(BoxRadius*2+1)
 
switchtime = {10,12,18}
switchtimer = {}
initialswitchvalue = {}
for i=1,#switchtime do
    switchtimer[i] = switchtime[i]
    initialswitchvalue[i] = 1
end
 
Inputs = InputSize+1+#initialswitchvalue+2
Outputs = #ButtonNames
 
Population = 1000
DeltaDisjoint = 2.0
DeltaWeights = 0.4
DeltaThreshold = 1.0
 
keyflag = false --used for keyboard input
endlevel = false --flag to detect a level finish
preparenext = false --flag for preparing for the next level
secondbest = 0 --second best fitness
maxright = 0 --Highest fitness without speed compensation
restoreturbo = false --flag for temporary turning off turbo
rerunning = false --flag for requiring rerunning the level if it finished with turbo enabled
killcounter = 0 --number of deaths
 
StaleSpecies = 25 --increased slightly to allow for longer evolution
mariohole = false --flag for mario falling into a hole
mariopipe = false --flag for mario exiting a pipe
marioPipeEnter = false --new flag for entering a pipe
offset = 0 --offset value for when mario exits a pipe
timebonus = 0 --timebonus to prevent the hard drops in fitness when exiting a pipe
maxcounter = 0 --species that reach the max fitness
currentTracker = 0 --tracks location on screen
loop = false --flag for when a loop is detected
previousmaxfitness = 0 --memory slot for the previous max fitness. used to check if it increased more than a set amount
bottleneckcounter = 0 --amount of generations since last "real" breakthrough
 
MutateConnectionsChance = 0.25
PerturbChance = 0.90
CrossoverChance = 0.75
LinkMutationChance = 2.0
NodeMutationChance = 0.50
BiasMutationChance = 0.40
SpeedMutationChance = 0.075
StepSize = 0.1
DisableMutationChance = 0.4
EnableMutationChance = 0.2
oscillationmutationchance = 0.2
interbreedchance = 0.03
networkswitchmutationchance = 0.01
 
nswitch = 1
ntrigger = true
 
ncount = 0
 
TimeoutConstant = 120 --set to a higher value but see for yourself what is acceptable
 
MaxNodes = 1000000
 
MaxNetworks = 95 --Max networks it is able to have, 95 is basically no limit but you can set it lower
speciesToAutoNetworkSwitch = 10 --Number of gens after each bottleneck loop where network chance is increased
increasedNetworkChance = 0.02 --Increased network chance
rsncChanceDecrease = 0.1 --Multiplier to the rsnc chance of adding new network rather than replacing, applied a number of times equal to the number of new networks
replaceSecondNetworkChance = 0.02 --Chance of rsnc by default
minBnLoopsBeforeNew = 1 --Number of bottleneck loops before the first network switch (increases by 1 per network switch)
 
gensBetweenPopOscil = 6 --generations after a breakthrough where the population decreases before starting a population oscillation
popOscilMultiplier = 1 --Multiplier on the base length for an oscillation (default is about 20 for the first, 30 for the second, etc)
 
networkPenaltyInner = 0.7
networkPenaltyOuter = 0.87
-- Multiplies average by (Inner * Outer^networks)^networks to encourage new networks to not take over population until sure they are needed
maxIncreaseWithoutBnReset = 30 --Number the max fitness can increase by before triggering bottleneck reset
 
hbValue = 60
 
function getPositions()
        marioX = memory.readbyte(0x6D) * 0x100 + memory.readbyte(0x86)
        marioY = memory.readbyte(0x03B8)+16
        mariostate = memory.readbyte(0x0E) --get mario's state. (entering exiting pipes, dying, picking up a mushroom etc etc)
        yspeed = memory.readbyte(0x009F) --y velocity 1-5 are falling and 250-255 is jumping
        xspeed = memory.readbyte(0x0057) --x position ranging from -48 to 48
        if yspeed >1 and yspeed <10 then
            falling = true
        else
            falling = false
        end
 
        currentscreen = memory.readbyte(0x071A) --finds current screen for loop detection
        nextscreen = memory.readbyte(0x071B) --finds next screen
 
        CurrentWorld = memory.readbyte(0x075F) --finds the current world (value + 1 is world)
        CurrentLevel = memory.readbyte(0x0760) --finds the current level (0=1 2=2 3=3 4=4)
        demoruncheck = memory.readbyte(0x0770) --equals 0 for demo and 1 for normal run
        statuscheck = memory.readbyte(0x0772) -- 3= playing 1 = loading
 
        screenX = memory.readbyte(0x03AD)
        screenY = memory.readbyte(0x03B8)
end
 
hiddenblocks = {}
hitblocks = 0
 
function getTile(dx, dy)
        local x = marioX + dx + 8
        local y = marioY + dy - 16
        local page = math.floor(x/256)%2
 
        local subx = math.floor((x%256)/16)
        local suby = math.floor((y - 32)/16)
        local addr = 0x500 + page*13*16+suby*16+subx
 
    local tilex = math.floor(x/16)
    local tiley = math.floor(y/16)
 
    local hbid = 95 - LostLevels
    if (memory.readbyte(addr) == hbid) then
        local dupe = false
        for b=1,#hiddenblocks do
        if hiddenblocks[b][1] == tilex and hiddenblocks[b][2] == tiley then
            dupe = true
            break
        end
        end
        if not dupe and tiley > -1 then
            table.insert(hiddenblocks,{tilex,tiley})
        end
    end
 
    for b=1,#hiddenblocks do
        if hiddenblocks[b][1] == tilex and hiddenblocks[b][2] == tiley then
                if memory.readbyte(addr) ~= hbid then
                 table.remove(hiddenblocks,b)
             hitblocks = hitblocks + 1
             break
        end
        end
    end
 
        if suby >= 13 or suby < 0 then
            return 0
        end
 
        if memory.readbyte(addr) ~= 0 then
            return 1
        else
            return 0
        end
end
 
function getSprites()
        local sprites = {}
        for slot=0,4 do
            local enemy = memory.readbyte(0xF+slot)
            if enemy ~= 0 then
                local ex = memory.readbyte(0x6E + slot)*0x100 + memory.readbyte(0x87+slot)
                local ey = memory.readbyte(0xCF + slot)+36 --changed this to stop sprites from floating in mid air on the neural network
                sprites[#sprites+1] = {["x"]=ex,["y"]=ey}
            end
        end
 
        return sprites
end
 
function getInputs()
    getPositions()
 
    sprites = getSprites()
 
    local inputs = {}
 
    for dy=-BoxRadius*16,BoxRadius*16,16 do
        for dx=-BoxRadius*16,BoxRadius*16,16 do
            inputs[#inputs+1] = 0
 
            tile = getTile(dx, dy)
            if tile == 1 and marioY+dy < 0x1B0 then
                inputs[#inputs] = 1
            end
 
            for i = 1,#sprites do
                distx = math.abs(sprites[i]["x"] - (marioX+dx))
                disty = math.abs(sprites[i]["y"] - (marioY+dy+16))
                if distx <= 8 and disty <= 8 then
                    inputs[#inputs] = -1
                end
            end
        end
    end
 
    --mariovx = memory.read_s8(0x7B)
    --mariovy = memory.read_s8(0x7D)
 
    return inputs
end
 
function sigmoid(x)
    return 2/(1+math.exp(-4.9*x))-1
end
 
function newInnovation()
    pool.innovation = pool.innovation + 1
    return pool.innovation
end
 
function newGSID()
    pool.gsid = pool.gsid + 1
    return pool.gsid
end
 
function newPool()
    local pool = {}
    pool.species = {}
    pool.generation = 0
    pool.innovation = Outputs
    pool.currentSpecies = 1
    pool.currentGenome = 1
    pool.currentFrame = 0
    pool.measured = 0
    pool.total = 0
    hitblocks = 0
    hiddenblocks = {}
    pool.maxFitness = 0
    pool.maxcounter = 0  --counter for %max species reached
    pool.gsid = -1
    return pool
end
 
function newSpecies()
    local species = {}
    species.topFitness = 0
    species.staleness = 0
    species.genomes = {}
    species.averageFitness = 0
    species.nickname = 'none'
    species.turbo = 'on'
    species.gsid = newGSID()
    species.topright = 0
    return species
end
 
function getCurrentGenome()
    local species = pool.species[pool.currentSpecies]
    return species.genomes[pool.currentGenome]
end
 
function toRGBA(ARGB)
    return bit.lshift(ARGB, 8) + bit.rshift(ARGB, 24)
end
 
 
function newGenome()
    local genome = {}
    genome.old = 0
    genome.genes = {}
    genome.genes[1] = {}
    genome.fitness = 0
    genome.adjustedFitness = 0
    genome.network = {}
    genome.network[1] = {}
    genome.maxneuron = 0
    genome.globalRank = 0
    genome.oscillations = {{}}
    genome.networkswitch = {}
    for i=1,#switchtime do
        genome.oscillations[1][i] = switchtime[i]
    end
    genome.mutationRates = {}
    genome.mutationRates["connections"] = MutateConnectionsChance
    genome.mutationRates["link"] = LinkMutationChance
    genome.mutationRates["bias"] = BiasMutationChance
    genome.mutationRates["node"] = NodeMutationChance
    genome.mutationRates["enable"] = EnableMutationChance
    genome.mutationRates["disable"] = DisableMutationChance
    genome.mutationRates["step"] = StepSize
    genome.mutationRates["oscillation"] = oscillationmutationchance
    genome.mutationRates["networkswitch"] = networkswitchmutationchance
 
    return genome
end
 
function copyGenome(genome)
    local genome2 = newGenome()
    genome2.old = genome.old
    for n=1,#genome.genes do
        if n > 1 then
            table.insert(genome2.genes, {})
            table.insert(genome2.oscillations, {})
        end
        for g=1,#genome.genes[n] do
            table.insert(genome2.genes[n], copyGene(genome.genes[n][g]))
        end
 
        for i=1,#genome.oscillations[n] do
            genome2.oscillations[n][i] = genome.oscillations[n][i]
        end
    end
    for n=1,#genome.networkswitch do
        table.insert(genome2.networkswitch, genome.networkswitch[n])
    end
    genome2.maxneuron = genome.maxneuron
    genome2.mutationRates["connections"] = genome.mutationRates["connections"]
    genome2.mutationRates["link"] = genome.mutationRates["link"]
    genome2.mutationRates["bias"] = genome.mutationRates["bias"]
    genome2.mutationRates["node"] = genome.mutationRates["node"]
    genome2.mutationRates["enable"] = genome.mutationRates["enable"]
    genome2.mutationRates["disable"] = genome.mutationRates["disable"]
    genome2.mutationRates["oscillation"] = genome.mutationRates["oscillation"]
    genome2.mutationRates["networkswitch"] = genome.mutationRates["networkswitch"]
 
    return genome2
end
 
function basicGenome()
    local genome = newGenome()
    local innovation = 1
 
    genome.maxneuron = Inputs
    mutate(genome, 1)
 
    return genome
end
 
function newGene()
    local gene = {}
    gene.into = 0
    gene.out = 0
    gene.weight = 0.0
    gene.enabled = true
    gene.innovation = 0
 
    return gene
end
 
function copyGene(gene)
    local gene2 = newGene()
    gene2.into = gene.into
    gene2.out = gene.out
    gene2.weight = gene.weight
    gene2.enabled = gene.enabled
    gene2.innovation = gene.innovation
 
    return gene2
end
 
function copyGeneOld(gene)
    local gene2 = newGene()
    gene2.into = gene.into
    gene2.out = gene.out
    if gene2.into > Inputs-2 and gene2.into <= MaxNodes then gene2.into = gene2.into + 2 end
    if gene2.out > Inputs-2 and gene2.out <= MaxNodes then gene2.out = gene2.out + 2 end
        
    gene2.weight = gene.weight
    gene2.enabled = gene.enabled
    gene2.innovation = gene.innovation
 
    return gene2
end
 
function newNeuron()
    local neuron = {}
    neuron.incoming = {}
    neuron.value = 0.0
    neuron.switcher = false
    neuron.timer = 60
    neuron.multiplier = 1
 
    return neuron
end
 
function generateNetwork(genome,printing)
    for n=1,#genome.genes do
        local network = {}
        network.neurons = {}
 
        for i=1,Inputs do
            network.neurons[i] = newNeuron()
        end
 
        for o=1,Outputs do
            network.neurons[MaxNodes+o] = newNeuron()
        end
 
        table.sort(genome.genes[n], function (a,b)
            return (a.out < b.out)
        end)
        for i=1,#genome.genes[n] do
            local gene = genome.genes[n][i]
            if gene.enabled then
                if network.neurons[gene.out] == nil then
                    network.neurons[gene.out] = newNeuron()
                end
                local neuron = network.neurons[gene.out]
                table.insert(neuron.incoming, gene)
                if network.neurons[gene.into] == nil then
                    network.neurons[gene.into] = newNeuron()
                end
            end
        end
 
        genome.network[n] = network
    end
end
 
function evaluateNetwork(n, inputs, genome)
    local network = genome.network[n]
    local Inputs = Inputs
    if genome.old >= nswitch then
        Inputs = Inputs -2
    end
    table.insert(inputs, 1) -- bias input node
    for i = 1,#initialswitchvalue do
        if genome.oscillations[n][i] > 0 then --oscillating nodes
            if switchtimer[i] > 0 then
                switchtimer[i] = switchtimer[i] -1
            else
                switchtimer[i] = genome.oscillations[n][i]
                initialswitchvalue[i] = initialswitchvalue[i]*-1
            end
        end
        table.insert(inputs, initialswitchvalue[i])
    end
    if not (genome.old >= nswitch) then --speed nodes
        if (xspeed < 100) then
            table.insert(inputs,xspeed / 48)
        else
            table.insert(inputs,(xspeed - 256) / 48)
        end
        if (yspeed < 10) then
            table.insert(inputs,yspeed / 5)
        else
            table.insert(inputs,(yspeed - 256) / 5)
        end
 
        if #inputs ~= Inputs then
            return {}
        end
    end
    for i=1,Inputs do
        network.neurons[i].value = inputs[i]
    end
    for _,neuron in pairs(network.neurons) do
        local sum = 0
        for j = 1,#neuron.incoming do
            local incoming = neuron.incoming[j]
            local other = network.neurons[incoming.into]
            sum = sum + incoming.weight * other.value
        end
       
        if #neuron.incoming > 0 then
            if neuron.switcher and sigmoid(sum)>0 then
                neuron.timer = neuron.timer - 1
            elseif neuron.switcher then
                neuron.timer = 120
                neuron.multiplier = 1
            end
            
            if neuron.switcher and neuron.timer < 0 then
                neuron.multiplier = -neuron.multiplier
                neuron.timer = 10
            end
            neuron.value = sigmoid(sum) * neuron.multiplier
            
        end
    end
   
    local outputs = {}
    for o=1,Outputs do
        local button = ButtonNames[o]
        if network.neurons[MaxNodes+o].value > 0 then
            outputs[button] = true
        else
            outputs[button] = false
        end
    end
   
    --emu.print(outputs)
    --emu.print(pool.currentFrame/5)
    --emu.print(network.neurons)
    return outputs
end
 
function crossover(g1, g2)
    if g2.fitness > g1.fitness then
        tempg = g1
        g1 = g2
        g2 = tempg
    end
    local child = newGenome()
    for n=1,#g1.networkswitch do
        table.insert(child.networkswitch, g1.networkswitch[n])
    end
    child.old = math.min(g1.old,g2.old)
    for n=1,#g1.genes do
        local innovations2 = {}
        local g1old = g1.old >= n
        local g2old = g2.old >= n
        if n > 1 then
            table.insert(child.genes,{})
            table.insert(child.oscillations,{})
        end
        if #g2.genes >= n then
            for i=1,#g2.genes[n] do
                local gene = g2.genes[n][i]
                innovations2[gene.innovation] = gene
            end
            for i=1,#g1.oscillations[n] do
                child.oscillations[n][i] = g1.oscillations[n][i]
            end
        else
            for i=1,#g1.oscillations[n] do
                child.oscillations[n][i] = switchtime[i]
            end
        end
        for i=1,#g1.genes[n] do
            local gene1 = g1.genes[n][i]
            local gene2 = innovations2[gene1.innovation]
            if (g1old and not g2old) then
                if gene2 ~= nil and math.random(1,2) == 1 and gene2.enabled then
                    table.insert(child.genes[n], copyGene(gene2))
                else
                    table.insert(child.genes[n], copyGeneOld(gene1))
                end
            elseif (g2old and not g1old) then
                if gene2 ~= nil and math.random(1,2) == 1 and gene2.enabled then
                    table.insert(child.genes[n], copyGeneOld(gene2))
                else
                    table.insert(child.genes[n], copyGene(gene1))
                end
            else
                if gene2 ~= nil and math.random(1,2) == 1 and gene2.enabled then
                    table.insert(child.genes[n], copyGene(gene2))
                else
                    table.insert(child.genes[n], copyGene(gene1))
                end
            end
        end
    end
 
    child.maxneuron = math.max(g1.maxneuron,g2.maxneuron)
 
    for mutation,rate in pairs(g1.mutationRates) do
        child.mutationRates[mutation] = rate
    end
 
    return child
end
 
function randomNeuron(genes, nonInput)
    local neurons = {}
    if not nonInput then
        for i=1,Inputs do
            neurons[i] = true
        end
    end
    for o=1,Outputs do
        neurons[MaxNodes+o] = true
    end
    for i=1,#genes do
        if (not nonInput) or genes[i].into > Inputs then
            neurons[genes[i].into] = true
        end
        if (not nonInput) or genes[i].out > Inputs then
            neurons[genes[i].out] = true
        end
    end
    local count = 0
    for _,_ in pairs(neurons) do
        count = count + 1
    end
    local n = math.random(1, count)
    for k,v in pairs(neurons) do
        n = n-1
        if n == 0 then
            return k
        end
    end
 
    return 0
end
 
function containsLink(genes, link)
    for i=1,#genes do
        local gene = genes[i]
        if gene.into == link.into and gene.out == link.out then
            return true
        end
    end
end
 
function pointMutate(genome, which)
    local step = genome.mutationRates["step"]
    local gene = {}
    for i=1,#genome.genes[which] do
        gene = genome.genes[which][i]
        if math.random() < PerturbChance then
            gene.weight = gene.weight + math.random() * step*2 - step
        else
            gene.weight = math.random()*4-2
        end
    end
end
 
function linkMutate(genome, forceBias, forceSpeed, which)
    local neuron1 = randomNeuron(genome.genes[which], false)
    local neuron2 = randomNeuron(genome.genes[which], true)
 
    local newLink = newGene()
    if neuron1 <= Inputs and neuron2 <= Inputs then
        --Both input nodes
        return
    end
 
    if neuron2 <= Inputs then
        -- Swap output and input
        local temp = neuron1
        neuron1 = neuron2
        neuron2 = temp
    end
 
    newLink.into = neuron1
    newLink.out = neuron2
    if forceBias then
        local adjustment = math.random(0,#initialswitchvalue)
        newLink.into = Inputs - adjustment - 2
    end
    if forceSpeed then
        local adjustment = math.random(0,1)
        newLink.into = Inputs - adjustment
    end
    if containsLink(genome.genes[which], newLink) then
        return
    end
    newLink.innovation = newInnovation()
    newLink.weight = math.random()*4-2
    table.insert(genome.genes[which], newLink)
end
 
function nodeMutate(genome, which)
    if #genome.genes[which] == 0 then
        return
    end
 
    genome.maxneuron = genome.maxneuron + 1
    local gene = {}
    gene = genome.genes[which][math.random(1,#genome.genes[which])]
    if not gene.enabled then
        return
    end
    gene.enabled = false
 
    local gene1 = copyGene(gene)
    gene1.out = genome.maxneuron
    gene1.weight = 1.0
    gene1.innovation = newInnovation()
    gene1.enabled = true
    table.insert(genome.genes[which], gene1)
 
    local gene2 = copyGene(gene)
    gene2.into = genome.maxneuron
    gene2.innovation = newInnovation()
    gene2.enabled = true
    table.insert(genome.genes[which], gene2)
end
 
function enableDisableMutate(genome, enable, which)
    local candidates = {}
    for _,gene in pairs(genome.genes[which]) do
        if gene.enabled == not enable then
            table.insert(candidates, gene)
        end
    end
 
    if #candidates == 0 then
        return
    end
 
    local gene = candidates[math.random(1,#candidates)]
    gene.enabled = not gene.enabled
end
 
function oscillationMutate(genome, which)
    mutationpoint = math.random(1,#genome.oscillations[which])
    if genome.oscillations[which][mutationpoint]>0 then
        if math.random(1,2) == 1 then
            genome.oscillations[which][mutationpoint] = genome.oscillations[which][mutationpoint] + 1
        else
            genome.oscillations[which][mutationpoint] = genome.oscillations[which][mutationpoint] - 1
        end
    end
    if genome.oscillations[which][mutationpoint]<0 then
        genome.oscillations[which][mutationpoint] = 0
    end
end
 
function NswitchMutate(genome)
    which = #genome.networkswitch
    if math.random() < 0.25 then
        which = math.random(1,#genome.networkswitch)
    end
    genome.networkswitch[which] = genome.networkswitch[which] + math.random()-0.5
    if genome.networkswitch[which] < 0 then
        genome.networkswitch[which] = math.random(0,12)
    end
    if genome.networkswitch[which] > 12 then
        genome.networkswitch[which] = math.random(0,12)
    end
    table.sort(genome.networkswitch, function(a,b)
        return (a < b)
    end)
end
 
function mutate(genome, which)
    for mutation,rate in pairs(genome.mutationRates) do
        if math.random(1,2) == 1 then
            genome.mutationRates[mutation] = 0.95*rate
        else
            genome.mutationRates[mutation] = 1.05263*rate
        end
    end
    if math.random() < genome.mutationRates["connections"] then
        pointMutate(genome, which)
    end
 
    local p = genome.mutationRates["link"]
    while p > 0 do
        if math.random() < p then
            linkMutate(genome, false, false, which)
        end
        p = p - 1
    end
 
    p = genome.mutationRates["bias"]
    while p > 0 do
        if math.random() < p then
            linkMutate(genome, true, false, which)
        end
        p = p - 1
    end
    
    p = SpeedMutationChance
    if math.random() < p and genome.old >= which then
        linkMutate(genome, false, true, which)
    end
    p = p - 1
 
    p = genome.mutationRates["node"]
    while p > 0 do
        if math.random() < p then
            nodeMutate(genome, which)
        end
        p = p - 1
    end
 
    p = genome.mutationRates["enable"]
    while p > 0 do
        if math.random() < p then
            enableDisableMutate(genome, true, which)
        end
        p = p - 1
    end
 
    p = genome.mutationRates["disable"]
    while p > 0 do
        if math.random() < p then
            enableDisableMutate(genome, false, which)
        end
        p = p - 1
    end
 
    p = genome.mutationRates["oscillation"]
    while p > 0 do
        if math.random() < p then
            oscillationMutate(genome, which)
        end
        p = p - 1
    end
 
    if #genome.networkswitch > 0 then
        p = genome.mutationRates["networkswitch"]
        while p > 0 do
            if math.random() < p then
                NswitchMutate(genome)
            end
            p = p - 1
        end
    end
end
 
function disjoint(genes1, genes2)
    local i1 = {}
    for i = 1,#genes1 do
        local gene = genes1[i]
        i1[gene.innovation] = true
    end
 
    local i2 = {}
    for i = 1,#genes2 do
        local gene = genes2[i]
        i2[gene.innovation] = true
    end
 
    local disjointGenes = 0
    for i = 1,#genes1 do
        local gene = genes1[i]
        if not i2[gene.innovation] then
            disjointGenes = disjointGenes+1
        end
    end
 
    for i = 1,#genes2 do
        local gene = genes2[i]
        if not i1[gene.innovation] then
            disjointGenes = disjointGenes+1
        end
    end
 
    local n = math.max(#genes1, #genes2)
 
    return disjointGenes / n
end
 
function weights(genes1, genes2)
    local i2 = {}
    for i = 1,#genes2 do
        local gene = genes2[i]
        i2[gene.innovation] = gene
    end
 
    local sum = 0
    local coincident = 0
    for i = 1,#genes1 do
        local gene = genes1[i]
        if i2[gene.innovation] ~= nil then
            local gene2 = i2[gene.innovation]
            sum = sum + math.abs(gene.weight - gene2.weight)
            coincident = coincident + 1
        end
    end
    if coincident == 0 then
        return 10
    end
    return sum / coincident
end
 
function sameSpecies(genome1, genome2)
    if #genome1.networkswitch ~= #genome2.networkswitch then
        return false
    end
    for n=1,#genome1.networkswitch do
        if math.abs(genome1.networkswitch[n] - genome2.networkswitch[n]) > 0.6 then
            return false
        end
    end
    for n=1,#genome1.genes do
        --if genome2.genes[n] == nil then
            --emu.print(genome1.genes)
            --emu.print(genome1.networkswitch)
            --emu.print(genome2.genes)
            --emu.print(genome2.networkswitch)
        --end
        dd = DeltaDisjoint*disjoint(genome1.genes[n],genome2.genes[n])
        dw = DeltaWeights*weights(genome1.genes[n],genome2.genes[n])
        if dd + dw > DeltaThreshold then
            return false
        end
    end
    return true
end
 
function rankGlobally()
    local global = {}
    for s = 1,#pool.species do
        local species = pool.species[s]
        for g = 1,#species.genomes do
            table.insert(global, species.genomes[g])
        end
    end
    table.sort(global, function (a,b)
        return (a.fitness < b.fitness)
    end)
 
    for g=1,#global do
        global[g].globalRank = g
    end
end
 
function calculateAverageFitness(species)
    local total = 0
 
    for g=1,#species.genomes do
        local genome = species.genomes[g]
        total = total + genome.globalRank
    end
        if species.topFitness < pool.averagemaxfitness - (pool.standardDeviationMaxFitness *2) then
            species.averageFitness = (total / #species.genomes) / 4
        elseif species.topFitness < pool.averagemaxfitness - pool.standardDeviationMaxFitness then
            species.averageFitness = (total / #species.genomes) / 2.5
        elseif species.topFitness > pool.averagemaxfitness + (pool.standardDeviationMaxFitness *3) then
            species.averageFitness = (total / #species.genomes) * 2
        elseif species.topFitness > pool.averagemaxfitness + (pool.standardDeviationMaxFitness *2) then
            species.averageFitness = (total / #species.genomes) * 1.5
        else
            species.averageFitness = total / #species.genomes
        end
        species.averageFitness = species.averageFitness * math.pow(networkPenaltyOuter*math.pow(networkPenaltyInner,#species.genomes[1].networkswitch),#species.genomes[1].networkswitch)
end
 
function calculateStandardDeviationMax()
    local deviationtotal = 0
    for s=1,#pool.species do
        deviationtotal = deviationtotal + math.abs(pool.species[s].topFitness - pool.averagemaxfitness)
    end
    pool.standardDeviationMaxFitness = deviationtotal / #pool.species
end
 
function AverageMaxFitness()
    local total = 0
    for s = 1,#pool.species do
        local species = pool.species[s]
        total = total + species.topFitness
    end
 
    pool.averagemaxfitness = total / #pool.species
end
 
function totalAverageFitness()
    local total = 0
    for s = 1,#pool.species do
        local species = pool.species[s]
        total = total + species.averageFitness
    end
 
    return total
end
 
function cullSpecies(cutToOne)
    for s = 1,#pool.species do
        local species = pool.species[s]
 
        table.sort(species.genomes, function (a,b)
            return (a.fitness > b.fitness)
        end)
 
        local percent = 0.5
        --if species.staleness > 10 then
            --percent = math.pow(0.5,(species.staleness+5)/15)
        --end
        local remaining = math.ceil(#species.genomes*percent)
        if cutToOne then
            remaining = 1
        end
        while #species.genomes > remaining do
            table.remove(species.genomes)
        end
    end
end
 
function breedChild(species)
    local child = {}
    if math.random() < CrossoverChance then
        g1 = species.genomes[math.random(1, #species.genomes)]
        g2 = species.genomes[math.random(1, #species.genomes)]
        child = crossover(g1, g2)
    else
        g = species.genomes[math.random(1, #species.genomes)]
        child = copyGenome(g)
    end
 
    local which = #child.genes
    if which > 1 and species.topright > 0 then
        emu.print(child.networkswitch[#child.networkswitch])
        local mutatePrevNetworkChance = 0.5 + (child.networkswitch[#child.networkswitch]-species.topright/500)/2
        if math.random() < mutatePrevNetworkChance then
            which = #child.genes - 1
        end
    end
    
    mutate(child,which)
 
    return child
end
 
function secondbestspecies() --added this to help prevent the formation of monospecies when LuigI/O encounters a major bottleneck
    local bestfitness = 0
    local secondbestfitness = 0
    for s = 1,#pool.species do
        local species = pool.species[s]
 
        if species.topFitness > bestfitness then
            secondbestfitness = bestfitness
            bestfitness = species.topFitness
        elseif species.topFitness > secondbestfitness then
            secondbestfitness = species.topFitness
        end
    end
    return secondbestfitness
end
 
 
function removeStaleSpecies()
    local survived = {}
    --emu.print("Max Fitness: ".. pool.maxFitness ..". second best: ".. secondbest)
    for s = 1,#pool.species do
        local species = pool.species[s]
 
        table.sort(species.genomes, function (a,b)
            return (a.fitness > b.fitness)
        end)
 
        if species.genomes[1].fitness > species.topFitness then
            species.topFitness = species.genomes[1].fitness
            species.staleness = 0
        else
            species.staleness = species.staleness + 1
        end
    end
    secondbest = secondbestspecies()
 
    for s = 1,#pool.species do
        local species = pool.species[s]
        if species.staleness < StaleSpecies or species.topFitness >= secondbest then --originally species.topFitness >= pool.maxFitness
            table.insert(survived, species)
        end
    end
 
    pool.species = survived
end
 
function removeWeakSpecies()
    local survived = {}
    local breed
    local sum = totalAverageFitness()
    for s = 1,#pool.species do
        local species = pool.species[s]
        breed = math.floor(species.averageFitness / sum * Population)
        if breed >= 1 then
            table.insert(survived, species)
        end
    end
 
    pool.species = survived
end
 
 
function addToSpecies(child)
    local foundSpecies = false
    for s=1,#pool.species do
        local species = pool.species[s]
        if not foundSpecies and sameSpecies(child, species.genomes[1]) then
            table.insert(species.genomes, child)
            foundSpecies = true
        end
    end
 
    if not foundSpecies then
        local childSpecies = newSpecies()
        table.insert(childSpecies.genomes, child)
        table.insert(pool.species, childSpecies)
    end
end
 
function replaceSecondNetwork(g1,g2,tr)
    local genome2 = newGenome()
    
    for n=1,#g1.genes do
        if n > 1 then
            table.insert(genome2.genes,{})
            table.insert(genome2.oscillations,{})
        end
        if g1.old >= n then
            for g=1,#g1.genes[n] do
                table.insert(genome2.genes[n], copyGeneOld(g1.genes[n][g]))
            end
        else
            for g=1,#g1.genes[n] do
                table.insert(genome2.genes[n], copyGene(g1.genes[n][g]))
            end
        end
        for i=1,#g1.oscillations[n] do
            genome2.oscillations[n][i] = g1.oscillations[n][i]
        end
    end
    for n=1,#g1.networkswitch do
        genome2.networkswitch[n] = g1.networkswitch[n]
    end
    genome2.maxneuron = g1.maxneuron
    genome2.mutationRates["connections"] = g1.mutationRates["connections"]
    genome2.mutationRates["link"] = g1.mutationRates["link"]
    genome2.mutationRates["bias"] = g1.mutationRates["bias"]
    genome2.mutationRates["node"] = g1.mutationRates["node"]
    genome2.mutationRates["enable"] = g1.mutationRates["enable"]
    genome2.mutationRates["disable"] = g1.mutationRates["disable"]
    genome2.mutationRates["oscillation"] = g1.mutationRates["oscillation"]
    genome2.mutationRates["networkswitch"] = g1.mutationRates["networkswitch"]
 
    if g1.fitness + 300 <= pool.maxFitness or math.random() > math.pow(rsncChanceDecrease,#genome2.networkswitch) or #genome2.genes >= MaxNetworks or bnLoops < #genome2.genes then
        
        genome2.genes[#genome2.genes] = {}
        which = math.random(1,#g2.genes)
        if g2.old >= which then
            for g=1,#g2.genes[which] do
                table.insert(genome2.genes[#genome2.genes], copyGeneOld(g2.genes[which][g]))
            end
        else
            for g=1,#g2.genes[which] do
                table.insert(genome2.genes[#genome2.genes], copyGene(g2.genes[which][g]))
            end
        end
    else
        table.insert(genome2.genes,{})
        which = math.random(1,#g2.genes)
        if g2.old >= which then
            for g=1,#g2.genes[which] do
                table.insert(genome2.genes[#genome2.genes], copyGeneOld(g2.genes[which][g]))
            end
        else
            for g=1,#g2.genes[which] do
                table.insert(genome2.genes[#genome2.genes], copyGene(g2.genes[which][g]))
            end
        end
        table.insert(genome2.oscillations,{switchtime[1],switchtime[2],switchtime[3]})
        table.insert(genome2.networkswitch,tr/500 - 0.2-0.1*math.random())
    end
    table.sort(genome2.networkswitch, function(a,b)
        return (a < b)
    end)
    return genome2
end
 
function FileExists(name)
    local file = io.open(name,"r")
    if file~=nil then io.close(file) return true else return false end
end
 
bnLoops = 0
function newGeneration()
    writeFile("backups/backup3." .. pool.generation .. "." .. SAVE_LOAD_FILE)
    if originalmaxfitness == nil or originalmaxfitness > previousmaxfitness then
        originalmaxfitness = previousmaxfitness
    end
    local population = Population
    if pool.maxFitness-maxIncreaseWithoutBnReset > previousmaxfitness then
        previousmaxfitness = pool.maxFitness
        originalmaxfitness = pool.maxFitness
        bottleneckcounter = 0
        if AllowAutoTurbo then
            emu.speedmode("normal")
            turbo = false
        end
        bnLoops = 0
    elseif pool.maxFitness - (4*maxIncreaseWithoutBnReset) > originalmaxfitness then
        previousmaxfitness = pool.maxFitness
        originalmaxfitness = pool.maxFitness
        bottleneckcounter = 0
        if AllowAutoTurbo then
            emu.speedmode("normal")
            turbo = false
        end
        bnLoops = 0
    else
        bottleneckcounter = bottleneckcounter + 1
        previousmaxfitness = pool.maxFitness
    end
    local bottleneckc = bottleneckcounter
    local GBPO = gensBetweenPopOscil
    local targetPop = math.min(900,500 + 100*bnLoops)
    local length= math.min(12*popOscilMultiplier,(5 + 2*bnLoops)*popOscilMultiplier)
    local targetPopLower = math.min(300,600 - targetPop/2)
    local currentPopLower = math.min(300,targetPopLower+50)
 
    if bottleneckc <= GBPO then
    if population > 300 then
            population = math.floor(population * 0.905 * 0.905)
            if population < 300 then population = 300 end
        end
    end
    if bottleneckc > GBPO then
        if bottleneckc < GBPO + length then
            population = currentPopLower + math.floor((targetPop - currentPopLower)/length*(bottleneckc-GBPO))
        elseif bottleneckc == GBPO + length then
            population = targetPop
        elseif bottleneckc <= GBPO + length*3 then
            population = targetPopLower + math.floor((targetPop - targetPopLower)/length/2*(GBPO+length*3-bottleneckc))
        end
        if bottleneckc == GBPO + length*3 then
            bottleneckcounter = 0
            bnLoops = bnLoops + 1
        end
    end
    if bottleneckcounter > TurboGenBottleneck and AllowAutoTurbo and BottleneckTurbo then
        emu.speedmode("turbo")
        turbo = true
    end
    
    Population = population
 
 
    cullSpecies(false) -- Cull the bottom half of each species
    rankGlobally()
    removeStaleSpecies()
    AverageMaxFitness()
    calculateStandardDeviationMax()
    rankGlobally()
    local numSpecies = #pool.species
    for s = 1,numSpecies do
        local species = pool.species[s]
        calculateAverageFitness(species)
    end
    removeWeakSpecies()
    local sum = totalAverageFitness()
    local children = {}
    local breed
    for s = 1,#pool.species do
        local species = pool.species[s]
        breed = math.floor(species.averageFitness / sum * population) - 1
        if breed > 10 then
            breed = math.ceil(breed * math.sqrt(species.topFitness / pool.maxFitness))
        end
        if breed > population/4 then
            breed = math.floor(Population/4)
        end
        for i=1,breed do
            table.insert(children, breedChild(species))
        end
    end
    cullSpecies(true) -- Cull all but the top member of each species
    if #pool.species < 30 then
        interbreedchance = 0.2
    else
        interbreedchance = 0.03
    end
    for i=1,#pool.species do
        if math.random() < interbreedchance then
            local counterbreed = math.random(1, #pool.species)
            if counterbreed ~= i then
                table.insert(children, crossover(pool.species[i].genomes[1], pool.species[counterbreed].genomes[1]))
 
            end
        end
            if pool.species[i].topFitness + 300 > pool.maxFitness and (math.random() < increasedNetworkChance*(bnLoops - #pool.species[i].genomes[1].networkswitch) or #pool.species <= speciesToAutoNetworkSwitch) and #pool.species[i].genomes[1].genes < MaxNetworks then
                local tfit = pool.species[i].topFitness
                child = copyGenome(pool.species[i].genomes[1])
                switchlocation = pool.species[i].topright/500 - 0.2 - 0.1*math.random()
                table.insert(child.networkswitch,switchlocation)
                table.insert(child.oscillations,{switchtime[1],switchtime[2],switchtime[3]})
                table.insert(child.genes,{})
                table.insert(children, child)
                table.sort(child.networkswitch, function(a,b)
                    return (a < b)
            end)
                emu.print(pool.species[i].genomes[1].fitness)
                emu.print(switchlocation)
            end
        if math.random() < replaceSecondNetworkChance and pool.species[i].topFitness > 500 then
            if #pool.species[i].genomes[1].networkswitch > 0 then
                if math.random() < 0.5 then
                    local donornetwork = math.random(1, #pool.species)
                    if donornetwork ~= i then
                        table.insert(children, replaceSecondNetwork(pool.species[i].genomes[1], pool.species[donornetwork].genomes[1],pool.species[i].topright))
                    end
                end
            else
                local donornetwork = math.random(1, #pool.species)
                if donornetwork ~= i then
                    table.insert(children, replaceSecondNetwork(pool.species[i].genomes[1], pool.species[donornetwork].genomes[1],pool.species[i].topright))
                end
            end
        end
    end
    while #children + #pool.species < Population do
        local species = pool.species[math.random(1, #pool.species)]
        table.insert(children, breedChild(species))
    end
    for c=1,#children do
        local child = children[c]
        addToSpecies(child)
    end
    local children = {}
    local replace = 0
    for s=1,#pool.species do
        local species = pool.species[s]
        if species.topFitness < secondbest and #species.genomes > Population/12 then
            while #species.genomes > Population/12 do
                table.remove(species.genomes)
                replace = replace + 1
            end
        elseif species.topFitness >= secondbest and #species.genomes>Population/7 then
            while #species.genomes > Population/7 do
                table.remove(species.genomes)
                replace = replace + 1
            end
        end
    end
    for r=1,replace do
        local species = pool.species[math.random(1, #pool.species)]
        table.insert(children, breedChild(species))
    end
    for c=1,#children do
        local child = children[c]
        addToSpecies(child)
    end
    count = 0
    for s=1,#pool.species do
        local species = pool.species[s]
        if species.topFitness < secondbest and #species.genomes > Population/12 then
            while #species.genomes > Population/12 do
                table.remove(species.genomes)
            end
        elseif species.topFitness >= secondbest and #species.genomes>Population/7 then
            while #species.genomes > Population/7 do
                table.remove(species.genomes)
            end
        end
        count = count + #species.genomes
    end
 
    --emu.print("Count: " .. count)
    secondbest = secondbestspecies()
    pool.generation = pool.generation + 1
    pool.maxcounter = 0 --reset max fitness counter
 
    --gsidFile("backups/gsid." .. pool.generation .. "." .. SAVE_LOAD_FILE)
    writeFile("backups/backup3." .. SAVE_LOAD_FILE)
    writelatestgen() --tracker for the latest generation
end
 
function initializePool()
    pool = newPool()
    added = LoadPreviousV2Win()
    added = added + LoadPreviousV3Win(added)
    local basic
    for i=1,Population-added do
        basic = basicGenome()
        addToSpecies(basic)
    end
 
    initializeRun()
end
 
function clearJoypad()
    controller = {}
    for b = 1,#ButtonNames do
        controller[ButtonNames[b]] = false
    end
    joypad.set(player, controller)
end
 
function LoadPreviousV3Win(start)
    if FileExists('v3wins.txt') then
        local file = io.open('v3wins.txt', "r")
        local line = file:read("*line")
        local count = 0
        while line ~= nil do
            PlaceV3Genome(line)
            count = count + 1
            line = file:read("*line")
        end
        file:close()
        
        local file = io.open('v3nicks.txt', "r")
        local line = file:read("*line")
        local count2 = start
        while line ~= nil do
            count2 = count2 + 1
            pool.species[count2].nickname = line
            line = file:read("*line")
        end
        file:close()
        return count
    else
        return 0
    end
end
 
function LoadPreviousV2Win()
    if FileExists('v2wins.txt') then
        local file = io.open('v2wins.txt', "r")
        local line = file:read("*line")
        local count = 0
        while line ~= nil do
            PlaceGenome(line)
            count = count + 1
            line = file:read("*line")
        end
        file:close()
        local file = io.open('v2nicks.txt', "r")
        local line = file:read("*line")
        local count2 = 0
        while line ~= nil do
            count2 = count2 + 1
            pool.species[count2].nickname = line
            line = file:read("*line")
        end
        file:close()
        return count
    else
        return 0
    end
end
 
function PlaceV3Genome(filename)
    if FileExists(filename) then
        local file = io.open(filename, "r")
        local genome = newGenome()
        local innov = pool.innovation
        genome.fitness = 0
        genome.old = 0
        genome.maxneuron = file:read("*number")
        local line = file:read("*line")
        while line ~= "done" do
            genome.mutationRates[line] = file:read("*number")
            line = file:read("*line")
        end
        local splits = file:read("*number")
        
        local highinnov = 0
        print(splits)
        for s=1,splits do
            local numGenes = file:read("*number")
            for n=1,numGenes do
                local gene = newGene()
                table.insert(genome.genes[s], gene)
                local enabled
                gene.into, gene.out, gene.weight, gene.innovation, enabled = file:read("*number", "*number", "*number", "*number", "*number")
                if gene.innovation > highinnov then highinnov = gene.innovation end
                gene.innovation = gene.innovation + innov
                -- if gene.into > Inputs-2 and gene.into <= Inputs then gene.into = gene.into + 2 end
                -- if gene.out > Inputs-2 and gene.out <= Inputs then gene.out = gene.out + 2 end
                --gene.into = gene.into+2
                --gene.out = gene.out+2
                if enabled == 0 then
                    gene.enabled = false
                else
                    gene.enabled = true
                end
            end
            if s < splits then
                table.insert(genome.genes,{})
                table.insert(genome.oscillations,{})
            end
        end
        for n=1,splits-1 do
            genome.networkswitch[n] = file:read("*number")
        end
        local numosci = 3
        for n=1,splits do
 
            for o=1,numosci do
                genome.oscillations[n][o] = file:read("*number")
            end
        end
        pool.innovation = pool.innovation + highinnov
        addToSpecies(genome)
        file:close()
    end
end
 
function PlaceGenome(filename)
    filename = filename .. ".txt"
    if FileExists(filename) then
        emu.print("loading in genome at location " .. filename)
        
        local file = io.open(filename, "r")
        local genome = newGenome()
        local innov = pool.innovation
        genome.fitness = 0
        genome.old = 1
        genome.maxneuron = file:read("*number")
        local line = file:read("*line")
        while line ~= "done" do
            genome.mutationRates[line] = file:read("*number")
            line = file:read("*line")
        end
        local numGenes = file:read("*number")
        local highinnov = 0
        for n=1,numGenes do
            local gene = newGene()
            table.insert(genome.genes[1], gene)
            local enabled
            gene.into, gene.out, gene.weight, gene.innovation, enabled = file:read("*number", "*number", "*number", "*number", "*number")
            if gene.innovation > highinnov then highinnov = gene.innovation end
            gene.innovation = gene.innovation + innov
            -- if gene.into > Inputs-2 and gene.into <= Inputs then gene.into = gene.into + 2 end
            -- if gene.out > Inputs-2 and gene.out <= Inputs then gene.out = gene.out + 2 end
            --gene.into = gene.into+2
            --gene.out = gene.out+2
            if enabled == 0 then
                gene.enabled = false
            else
                gene.enabled = true
            end
        end
        --table.insert(genome.genes,{})
        table.insert(genome.oscillations,{})
        local numGenes2 = file:read("*number")
        genes2 = {}
        for n=1,numGenes2 do
            local gene2 = newGene()
            table.insert(genes2, gene2)
            local enabled
            --I am able to revert back to the original loading script with FCEUX
            gene2.into, gene2.out, gene2.weight, gene2.innovation, enabled = file:read("*number", "*number", "*number", "*number", "*number")
            if gene2.innovation > highinnov then highinnov = gene2.innovation end
            gene2.innovation = gene2.innovation + innov
            -- if gene2.into > Inputs-2 and gene2.into <= Inputs then gene2.into = gene2.into + 2 end
            -- if gene2.out > Inputs-2 and gene2.out <= Inputs then gene2.out = gene2.out + 2 end
            
            if enabled == 0 then
                gene2.enabled = false
            else
                gene2.enabled = true
            end
        end
        pool.innovation = pool.innovation + highinnov
        local ns = file:read("*number")
        local numosci = file:read("*number")
        for i=1,numosci do
            genome.oscillations[1][i] = file:read("*number")
        end
        if (ns > 0) then
            genome.old = 2
            genome.genes[2] = genes2
            for i=1,numosci do
                genome.oscillations[2][i] = genome.oscillations[1][i]
            end
            table.insert(genome.networkswitch,ns)
        end
        
        
        addToSpecies(genome)
        file:close()
    end
end
 
function initializeRun()
 
    savestate.load(SavestateObj);
    rightmost = 0
    pool.currentFrame = 0
    hitblocks = 0
    timebonus = 0
    hiddenblocks = {}
    timeout = TimeoutConstant
    clearJoypad()
    currentTracker = memory.readbyte(0x071A) --sets the current tracker to the current screen
    local measured = 0
    local total = 0
    for _,species in pairs(pool.species) do
        for _,genome in pairs(species.genomes) do
            total = total + 1
            if genome.fitness ~= 0 then
                measured = measured + 1
            end
        end
    end
    pool.measured = measured
    pool.total = total
    local species = pool.species[pool.currentSpecies]
    local genome = species.genomes[pool.currentGenome]
    for i=1,#genome.oscillations[1] do
        switchtimer[i] = genome.oscillations[1][i]
        initialswitchvalue[i] = 1
    end
    generateNetwork(genome,true)
    evaluateCurrent()
end
 
function reRun()
    savestate.load(savestate.object(previous_savestate));
    rightmost = 0
    pool.currentFrame = 0
    hitblocks = 0
    hiddenblocks = {}
    timeout = TimeoutConstant
    clearJoypad()
    currentTracker = memory.readbyte(0x071A) --sets the current tracker to the current screen
    offset = 0 --offset in x coordinates for pipe
    timebonus = 0 --used to freeze fitness
    mariohole = false --reset the fallen into hole variable
    mariopipe = false --is mario exiting a pipe or just teleporting
    loop = false --is this a loop?
    marioPipeEnter = false --has mario entered a pipe
    currentTracker = 1 --tracker for the loop
    killtrigger = false
    ntrigger = true
    nswitch = 1
    local species = pool.species[pool.currentSpecies]
    local genome = species.genomes[pool.currentGenome]
    for i=1,#genome.oscillations[1] do
        switchtimer[i] = genome.oscillations[1][i]
        initialswitchvalue[i] = 1
    end
    generateNetwork(genome,true)
    evaluateCurrent()
end
 
function evaluateCurrent()
    local species = pool.species[pool.currentSpecies]
    local genome = species.genomes[pool.currentGenome]
 
    --emu.print(nswitch)
    inputs = getInputs()
    controller = evaluateNetwork(nswitch, inputs, genome)
 
    if controller["left"] and controller["right"] then
        controller["left"] = false
        controller["right"] = false
    end
    if controller["up"] and controller["down"] then
        controller["up"] = false
        controller["down"] = false
    end
 
    joypad.set(player, controller)
end
 
if pool == nil then
    initializePool() --------------------------------------------------------------------------------------------------------------------------------------------------------
end
 
 
function nextGenome()
    pool.currentGenome = pool.currentGenome + 1
    if pool.currentGenome > #pool.species[pool.currentSpecies].genomes then
        pool.currentGenome = 1
        pool.currentSpecies = pool.currentSpecies+1
        if pool.currentSpecies > #pool.species then
            newGeneration()
            pool.currentSpecies = 1
        end
    end
end
 
function fitnessAlreadyMeasured()
    local species = pool.species[pool.currentSpecies]
    local genome = species.genomes[pool.currentGenome]
    --return false
    return genome.fitness ~= 0
end
 
function exportGenome(genome, nickname)
    local filename = "v3wins/v3-" .. CurrentWorld+1 .."-".. CurrentLevel .. "-H.txt"
    local file2 = io.open("v3wins.txt", "a")
    file2:write(filename .. "\n")
    file2:close()
    local file3 = io.open("v3nicks.txt", "a")
    if nickname == "none" then
        file3:write("nameless winner" .. "\n")
    else
        file3:write(nickname .. "\n")
    end
    file3:close()
    local file = io.open(filename, "w")
    file:write(genome.maxneuron .. "\n")
    for mutation,rate in pairs(genome.mutationRates) do
        file:write(mutation .. "\n")
        file:write(rate .. "\n")
    end
    file:write("done\n")
    file:write(#genome.genes .. "\n")
    for k,network in pairs(genome.genes) do
        file:write(#network .. "\n")
        for l,gene in pairs(network) do
            file:write(gene.into .. " ")
            file:write(gene.out .. " ")
            file:write(gene.weight .. " ")
            file:write(gene.innovation .. " ")
            if(gene.enabled) then
                file:write("1\n")
            else
                file:write("0\n")
            end
        end
        --emu.print(#network)
    end
    --emu.print(#genome.genes)
    for i,ns in pairs(genome.networkswitch) do
        file:write(ns .. "\n")
    end
    --emu.print(genome.oscillations)
    for i,osc in pairs(genome.oscillations) do
        file:write(#osc .. "\n")
        for i,oscn in pairs(osc) do
            file:write(oscn .. "\n")
        end
    end
    file:write(genome.old)
    file:close()
end
 
function GenerateNewKeepBest(genome, nickname)
    Population = 1000
    previousbest = copyGenome(genome)
    exportGenome(previousbest, nickname) --saves the winning genome
    previousmaxfitness = 1
    originalmaxfitness = 1
    maxright = 1
    offset = 0 --offset in x coordinates for pipe
    timebonus = 0 --used to freeze fitness
    timeout = TimeoutConstant
    mariohole = false --reset the fallen into hole variable
    mariopipe = false --is mario exiting a pipe or just teleporting
    loop = false --is this a loop?
    marioPipeEnter = false --has mario entered a pipe
    currentTracker = 1 --tracker for the loop
    pool = newPool()
    added = LoadPreviousV2Win()
    added = added + LoadPreviousV3Win(added)
    --addToSpecies(previousbest)
    for i=1,Population-added do
        basic = basicGenome()
        addToSpecies(basic)
    end
    initializeRun()
end
 
function displayGenome(genome)
    gui.opacity(0.53)
    local Inputs = Inputs
    local nswitch = nswitch
    local BoxRadius = BoxRadius
    if genome.old >= nswitch then
        Inputs = Inputs -2
    end
    if not genome then return end
    local network = {}
    network = genome.network[nswitch]
    local cells = {}
    local i = 1
    local cell = {}
    for dy=-BoxRadius,BoxRadius do
        for dx=-BoxRadius,BoxRadius do
            cell = {}
            cell.x = 50+5*dx
            cell.y = 70+5*dy
            cell.value = network.neurons[i].value
            cells[i] = cell
            i = i + 1
        end
    end
    local biasCell = {}
    biasCell.x = 80
    biasCell.y = 110
    local switchCell = {}
    local speedCell = {}
    if genome.old >= nswitch then
        biasCell.value = network.neurons[Inputs-#initialswitchvalue].value
        cells[Inputs-#initialswitchvalue] = biasCell
        for i=0,#initialswitchvalue-1 do
            switchCell = {}
            switchCell.x = 80-10*#initialswitchvalue+10*i
            switchCell.y = 110
            switchCell.value = network.neurons[Inputs-i].value
            cells[Inputs-i] = switchCell
        end
    else
        biasCell.value = network.neurons[Inputs-#initialswitchvalue-2].value
        cells[Inputs-#initialswitchvalue-2] = biasCell
        for i=0,#initialswitchvalue-1 do
            switchCell = {}
            switchCell.x = 80-10*#initialswitchvalue+10*i
            switchCell.y = 110
            switchCell.value = network.neurons[Inputs-i-2].value
            cells[Inputs-i-2] = switchCell
        end
        for i=0,1 do
            speedCell = {}
            speedCell.x = 80 - 10*(6)+10*i
            speedCell.y = 110
            speedCell.value = network.neurons[Inputs-i].value
            cells[Inputs-i] = speedCell
        end
    end
 
    gui.drawbox(215,32,245,82,toRGBA(0x80808080),toRGBA(0x00000000))
    local color
    gui.opacity(0.85)
    for o = 1,Outputs do
        cell = {}
        cell.x = 220
        cell.y = 30 + 8 * o
        cell.value = network.neurons[MaxNodes + o].value
        cells[MaxNodes+o] = cell
        
        if cell.value > 0 then
            color = 0xFF0000FF
        else
            color = 0xFF777777-- original color = 0xFF000000
        end
        gui.drawtext(225, 26+8*o, ButtonNames[o], toRGBA(color), 0x0) --moved slightly for better alignment
    end
    for n,neuron in pairs(network.neurons) do
        cell = {}
        if n > Inputs and n <= MaxNodes then
            cell.x = 140
            cell.y = 40
            cell.value = neuron.value
            -- if neuron.value >0 then
                -- neuron.switcher = true
            -- end
            cells[n] = cell
        end
    end
    local genes = {}
    genes = genome.genes[nswitch]
    for n=1,4 do
        for _,gene in pairs(genes) do
            if gene.enabled then
                local c1 = cells[gene.into]
                local c2 = cells[gene.out]
                if gene.into > Inputs and gene.into <= MaxNodes then
                    c1.x = 0.75*c1.x + 0.25*c2.x
                    if c1.x >= c2.x then
                        c1.x = c1.x - 40
                    end
                    if c1.x < 90 then
                        c1.x = 90
                    end
 
                    if c1.x > 220 then
                        c1.x = 220
                    end
                    c1.y = 0.75*c1.y + 0.25*c2.y
 
                end
                if gene.out > Inputs and gene.out <= MaxNodes then
                    c2.x = 0.25*c1.x + 0.75*c2.x
                    if c1.x >= c2.x then
                        c2.x = c2.x + 40
                    end
                    if c2.x < 90 then
                        c2.x = 90
                    end
                    if c2.x > 220 then
                        c2.x = 220
                    end
                    c2.y = 0.25*c1.y + 0.75*c2.y
                end
            end
        end
    end
 
    gui.drawbox(50-BoxRadius*5-3,70-BoxRadius*5-3,50+BoxRadius*5+2,70+BoxRadius*5+2, toRGBA(0x80808080), toRGBA(0xFF000000))
    for n,cell in pairs(cells) do
        if n > Inputs or cell.value ~= 0 then
            color = math.floor((cell.value+1)/2*256)
            if color > 255 then color = 255 end
            if color < 0 then color = 0 end
            local opacity = 0xFF000000
            if cell.value == 0 then
                opacity = 0x50000000
            end
            inversecolor = (color - 255) * (-1)
            bordercolor = 0x50000000 + inversecolor*0x10000 + inversecolor*0x100 + inversecolor
            color = opacity + color*0x10000 + color*0x100 + color
            gui.drawbox(cell.x-2,cell.y-2,cell.x+2,cell.y+2,toRGBA(color), toRGBA(bordercolor))
        end
    end
    for _,gene in pairs(genes) do
        if gene.enabled then
            local c1 = cells[gene.into]
            local c2 = cells[gene.out]
            local opacity = 0xF0000000
            if pool.generation > 50 then
                    opacity = 0x80000000
                end
            if c1.value == 0 then
                if pool.generation <10 then --slowly reducing the visibility of non active connections
                    opacity = 0x80000000
                elseif pool.generation < 15 then
                    opacity = 0x60000000
                elseif pool.generation < 25 then
                    opacity = 0x40000000
                end
            end
            local color = 0x80-math.floor(math.abs(sigmoid(gene.weight))*0x80)
            if gene.weight > 0 then
                color = opacity + 0x8000 + 0x10000*color
            else
                color = opacity + 0x800000 + 0x100*color
            end
            if c1.value ~= 0 or pool.generation < 5 then -- remove non active connections from generation 50 onwards
                gui.drawline(c1.x+1, c1.y, c2.x-3, c2.y, toRGBA(color))
            end
        end
    end
 
    gui.drawbox(49,71,51,78,toRGBA(0x80FF0000),toRGBA(0x00000000))
    if mutation_rates_disp then
        local pos = 120
        for mutation,rate in pairs(genome.mutationRates) do
            gui.drawtext(16, pos, mutation .. ": " .. rate, toRGBA(0xFF000000), 0x0)
            pos = pos + 8
        end
    end
end
 
function toBase95(num,places)
    local str = ""
    for p=1,places do
        local numPlace = math.floor(num / math.pow(95,p-1))
        str = str .. string.char((numPlace % 95) + 32)
    end
    return str
end
 
function fromBase95(str,places)
    local num = 0
    for p=1,string.len(str) do
        local numPlace = string.byte(string.sub(str,p,p))-32
        num = num + numPlace * math.pow(95,p-1)
    end
    return num
end
 
function gsidFile(filename)
    local file = io.open(filename, "w")
    file:write("G")
    for n,species in pairs(pool.species) do
        file:write(toBase95(species.gsid,3))
    end
    file:close()
end
 
function writeFile(filename)
    local file = io.open(filename, "w")
    file:write("B" .. "\n")
    file:write(pool.generation .. "\n")
    file:write(math.floor(pool.maxFitness*10)/10 .. "\n")
    file:write(Population .. "\n")
    file:write(bnLoops .. "\n")
    file:write(bottleneckcounter .. "\n")
    file:write(pool.innovation .. "\n")
    file:write(pool.gsid .. "\n")
    file:write(#pool.species .. "\n")
    for n,species in pairs(pool.species) do
        file:write(math.floor(species.topFitness*10)/10 .. "\n")
        file:write(species.staleness .. "\n")
        file:write(species.gsid .. "\n")
        file:write(#species.genomes .. "\n")
        for m,genome in pairs(species.genomes) do
            file:write(math.floor(genome.fitness*10)/10 .. "\n")
            file:write(genome.maxneuron .. "\n")
            for i,rate in pairs(genome.mutationRates) do
                file:write(i .. "\n")
                file:write(math.floor(rate*10000)/10000 .. "\n")
            end
            file:write("done" .. "\n")
            file:write(#genome.genes .. "\n")
            for k,network in pairs(genome.genes) do
                file:write(#network .. "\n")
                for l,gene in pairs(network) do
                    local enabledChar
                    if(gene.enabled) then
                        enabledChar = "1"
                    else
                        enabledChar = "0"
                    end
                    file:write(gene.into .. " " .. gene.out .. " " .. math.floor(gene.weight*10000000)/10000000 .. " " .. gene.innovation .. " " .. enabledChar .. "\n")
                end
                --emu.print(#network)
            end
            --emu.print(#genome.genes)
            for i,ns in pairs(genome.networkswitch) do
                file:write(math.floor(ns*500)/500 .. "\n")
            end
            --emu.print(genome.oscillations)
            for i,osc in pairs(genome.oscillations) do
                for i,oscn in pairs(osc) do
                    file:write(oscn .. "\n")
                end
            end
            file:write(genome.old .. "\n")
        end
    end
    file:close()
end
 
function savePool() --used to save when a new max fitness is reached
    local filename = "backups/backup3." .. pool.generation .. "." .. SAVE_LOAD_FILE
    writeFile(filename)
    emu.print("saved pool due to new max fitness")  --suggestion by DeltaLeeds
end
 
function loadFile(filename)
    emu.print(filename)
    local file = io.open(filename, "r")
    fileString = file:read("*line")
    file:close()
    if string.sub(fileString,1,1) == "B" then
        local file = io.open(filename, "r")
        file:read("*line")
        pool = newPool()
        pool.species = {}
        pool.generation = file:read("*number")
        pool.maxFitness = file:read("*number")
        pool.maxFitness = 0
        previousmaxfitness = pool.maxFitness
        Population = file:read("*number")
        bnLoops = file:read("*number")
        bottleneckcounter = file:read("*number")
        pool.innovation = file:read("*number")
        local gsidMax = file:read("*number")
        local numSpecies = file:read("*number")
        for s=1,numSpecies do
            local species = newSpecies()
            species.genomes = {}
            species.topFitness = file:read("*number")
            species.topFitness = 0
            species.staleness = file:read("*number")
            species.gsid = file:read("*number")
            local numGenomes = file:read("*number")
            for g=1,numGenomes do
                local genome = newGenome()
                genome.fitness = file:read("*number")
                genome.fitness = 0
                --emu.print(genome.fitness)
                genome.maxneuron = file:read("*number")
                local line = file:read("*line")
                while line ~= "done" do
                    if line == "oscilation" then line = "oscillation" end
                    genome.mutationRates[line] = file:read("*number")
                    line = file:read("*line")
                end
                local numNetworks = file:read("*number")
                for n=1,numNetworks do
                    if n > 1 then
                        table.insert(genome.genes,{})
                        table.insert(genome.oscillations,{})
                    end
                    local numGenes = file:read("*number")
                    for gn=1,numGenes do
                        local gene = newGene()
                        local enabled
                        gene.into, gene.out, gene.weight, gene.innovation, enabled = file:read("*number", "*number", "*number", "*number", "*number")
                        if enabled == 0 then
                            gene.enabled = false
                        else
                            gene.enabled = true
                        end
                        table.insert(genome.genes[n],gene)
                    end
                    --emu.print("gn" .. #genome.genes[n])
                end
                --emu.print("N" .. numNetworks)
                for n=1,numNetworks-1 do
                    table.insert(genome.networkswitch,file:read("*number"))
                end
 
                --emu.print(genome.networkswitch)
                for n=1,numNetworks do
                    genome.oscillations[n][1]=file:read("*number")
                    genome.oscillations[n][2]=file:read("*number")
                    genome.oscillations[n][3]=file:read("*number")
                end
                --emu.print(genome.oscillations)
                table.insert(species.genomes,genome)
                --emu.print(genome.oscillations)
                genome.old = file:read("*number")
            end
            table.insert(pool.species,species)
        end
        pool.gsid = gsidMax
    elseif string.sub(fileString,1,1) == "A" then
        local i = 1
        pool = newPool()
        pool.species = {}
        pool.generation = fromBase95(string.sub(fileString,i+1,i+2))
        pool.maxFitness = 0--fromBase95(string.sub(fileString,i+3,i+5))/10
        if pool.maxFitness > 80000 then
          pool.maxFitness = pool.maxFitness - 85737.5
        end
        previousmaxfitness = pool.maxFitness
        Population = fromBase95(string.sub(fileString,i+6,i+7))
        bnLoops = fromBase95(string.sub(fileString,i+8,i+8))
        bottleneckcounter = fromBase95(string.sub(fileString,i+9,i+9))
        pool.innovation = fromBase95(string.sub(fileString,i+10,i+13))
        local gsidMax = fromBase95(string.sub(fileString,i+14,i+16))
        local numSpecies = fromBase95(string.sub(fileString,i+17,i+18))
        i = i + 18
        for s=1,numSpecies do
            local species = newSpecies()
            species.genomes = {}
            species.topFitness = 0--fromBase95(string.sub(fileString,i+1,i+3))/10
            if species.topFitness > 80000 then
              species.topFitness = species.topFitness - 85737.5
            end
            species.staleness = fromBase95(string.sub(fileString,i+4,i+4))
            species.gsid = fromBase95(string.sub(fileString,i+5,i+7))
            local numGenomes = fromBase95(string.sub(fileString,i+8,i+9))
            i = i + 9
            for g=1,numGenomes do
                local genome = newGenome()
                genome.fitness = 0--fromBase95(string.sub(fileString,i+1,i+3))/10
 
                if genome.fitness > 80000 then
                  genome.fitness = genome.fitness - 85737.5
                end
                genome.fitness = 0
                --emu.print(genome.fitness)
                genome.maxneuron = fromBase95(string.sub(fileString,i+4,i+5))
                genome.mutationRates["connections"] = fromBase95(string.sub(fileString,i+6,i+8))/10000
                genome.mutationRates["oscillation"] = fromBase95(string.sub(fileString,i+9,i+11))/10000
                genome.mutationRates["link"] = fromBase95(string.sub(fileString,i+12,i+14))/10000
                genome.mutationRates["enable"] = fromBase95(string.sub(fileString,i+15,i+17))/10000
                genome.mutationRates["step"] = fromBase95(string.sub(fileString,i+18,i+20))/10000
                genome.mutationRates["networkswitch"] = fromBase95(string.sub(fileString,i+21,i+23))/10000
                genome.mutationRates["bias"] = fromBase95(string.sub(fileString,i+24,i+26))/10000
                genome.mutationRates["node"] = fromBase95(string.sub(fileString,i+27,i+29))/10000
                genome.mutationRates["disable"] = fromBase95(string.sub(fileString,i+30,i+32))/10000
                local numNetworks = fromBase95(string.sub(fileString,i+33,i+33))
                i = i + 33
                for n=1,numNetworks do
                    if n > 1 then
                        table.insert(genome.genes,{})
                        table.insert(genome.oscillations,{})
                    end
                    local numGenes = fromBase95(string.sub(fileString,i+1,i+2))
                    i = i + 2
                    for gn=1,numGenes do
                        local gene = newGene()
                        gene.into = fromBase95(string.sub(fileString,i+1,i+2))
                        gene.out = fromBase95(string.sub(fileString,i+3,i+4))
                        gene.weight = (fromBase95(string.sub(fileString,i+5,i+8))/10000000)-4
                        gene.innovation = fromBase95(string.sub(fileString,i+9,i+12))
                        gene.enabled = (string.sub(fileString,i+13,i+13) == "1")
                        table.insert(genome.genes[n],gene)
                        i = i + 13
                        if gene.into > 9000 then gene.into = gene.into + 991000 end
                        if gene.out > 9000 then gene.out = gene.out + 991000 end
                    end
                    --emu.print("gn" .. #genome.genes[n])
                end
                --emu.print("N" .. numNetworks)
                for n=1,numNetworks-1 do
                    table.insert(genome.networkswitch,fromBase95(string.sub(fileString,i+1,i+2))/500)
                    i = i + 2
                end
 
                --emu.print(genome.networkswitch)
                for n=1,numNetworks do
                    genome.oscillations[n][1]=fromBase95(string.sub(fileString,i+1,i+1))
                    genome.oscillations[n][2]=fromBase95(string.sub(fileString,i+2,i+2))
                    genome.oscillations[n][3]=fromBase95(string.sub(fileString,i+3,i+3))
                    i = i + 3
                end
                --emu.print(genome.oscillations)
                table.insert(species.genomes,genome)
                --emu.print(genome.oscillations)
            end
            table.insert(pool.species,species)
        end
        pool.gsid = gsidMax
    else
        local file = io.open(filename, "r")
        pool = newPool()
        pool.generation = file:read("*number")
        pool.maxFitness = file:read("*number")
        previousmaxfitness = pool.maxFitness
        Population = file:read("*number")
        pool.innovation = file:read("*number")
        bottleneckcounter = file:read("*number")
        local numSpecies = file:read("*number")
        pool.gsid = numSpecies - 1
        maxstale = 0
        for s=1,numSpecies do
            local species = newSpecies()
            table.insert(pool.species, species)
            species.topFitness = file:read("*number")
            species.staleness = file:read("*number")
            if species.staleness > maxstale then maxstale = species.staleness end
            species.gsid = s - 1
            local numGenomes = file:read("*number")
            for g=1,numGenomes do
                local genome = newGenome()
                table.insert(species.genomes, genome)
                genome.fitness = file:read("*number")
                genome.maxneuron = file:read("*number")
                genome.fitness = 0
                local line = file:read("*line")
                while line ~= "done" do
                    if line == "oscilation" then line = "oscillation" end
                    genome.mutationRates[line] = file:read("*number")
                    line = file:read("*line")
                end
                local numGenes = file:read("*number")
                genes1 = {}
                genes1Legacy = {}
                for n=1,numGenes do
                    local gene = newGene()
                    table.insert(genes1Legacy, gene)
                    local enabled
                    --I am able to revert back to the original loading script with FCEUX
                    gene.into, gene.out, gene.weight, gene.innovation, enabled = file:read("*number", "*number", "*number", "*number", "*number")
                    if enabled == 0 then
                        gene.enabled = false
                    else
                        gene.enabled = true
                    end
                end
                
                local genomeLegacy = newGenome()
                genomeLegacy.genes = {genes1Legacy}
                MaxNodes = 1000000
                generateNetwork(genomeLegacy,false)
                --MaxNodes = 9000
                local net = genomeLegacy.network[1]
                local mapping = {}
                local numMappedNodes = 0
                for nid, neuron in pairs(net.neurons) do
                    numMappedNodes = numMappedNodes + 1
                    if numMappedNodes == 174 then numMappedNodes = 176 end
                    mapping[nid+0.5] = numMappedNodes
                    --emu.print(mapping)
                end
                for nid, neuron in pairs(net.neurons) do
                    for inc=1,#neuron.incoming do
                        local geneWrong = neuron.incoming[inc]
                        local geneRight = newGene()
                        geneRight.weight = geneWrong.weight
                        geneRight.enabled = geneWrong.enabled
                        geneRight.innovation = geneWrong.innovation
                        geneRight.out = mapping[nid+0.5]
                        if mapping[geneWrong.into+0.5] ~= nil then
                            geneRight.into = mapping[geneWrong.into+0.5]
                            table.insert(genes1,geneRight)
                            if geneRight.enabled and s == 1 and g == 2 then
                                --emu.print("ACCEPTED " .. geneWrong.into .. " " .. geneWrong.out .. " " .. geneRight.out .. " " .. geneRight.into)
                            end
                        elseif s == 1 and g == 2 then
                            emu.print("REJECTED " .. geneWrong.into .. " " .. geneWrong.out .. " " .. geneRight.out)
                        end
                    end
                    
                    --emu.print(nid)
                end
                
                for outputid=1,6 do
                    local geneRight = newGene()
                    geneRight.weight = 1.0
                    geneRight.enabled = true
                    geneRight.innovation = newInnovation()
                    geneRight.into = mapping[outputid+1000000.5]
                    geneRight.out = 9000+outputid
                    table.insert(genes1,geneRight)
                end
                
                
                local numGenes2 = file:read("*number")
                genes2 = {}
                genes2Legacy = {}
                for n=1,numGenes2 do
                    local gene2 = newGene()
                    table.insert(genes2Legacy, gene2)
                    local enabled
                    --I am able to revert back to the original loading script with FCEUX
                    gene2.into, gene2.out, gene2.weight, gene2.innovation, enabled = file:read("*number", "*number", "*number", "*number", "*number")
                    if enabled == 0 then
                        gene2.enabled = false
                    else
                        gene2.enabled = true
                    end
 
                end
                
                local genome2Legacy = newGenome()
                genome2Legacy.genes = {genes2Legacy}
                MaxNodes = 1000000
                generateNetwork(genome2Legacy,false)
                MaxNodes = 9000
                net = genome2Legacy.network[1]
                mapping = {}
                numMappedNodes = 0
                for nid, neuron in pairs(net.neurons) do
                    numMappedNodes = numMappedNodes + 1
                    if numMappedNodes == 174 then numMappedNodes = 176 end
                    mapping[nid+0.5] = numMappedNodes
                    --emu.print(mapping)
                end
                for nid, neuron in pairs(net.neurons) do
                    for inc=1,#neuron.incoming do
                        local geneWrong = neuron.incoming[inc]
                        local geneRight = newGene()
                        geneRight.weight = geneWrong.weight
                        geneRight.enabled = geneWrong.enabled
                        geneRight.innovation = geneWrong.innovation
                        geneRight.out = mapping[nid+0.5]
                        if mapping[geneWrong.into+0.5] ~= nil then
                            geneRight.into = mapping[geneWrong.into+0.5]
                            table.insert(genes2,geneRight)
                            if geneRight.enabled and s == 1 and g == 2 then
                                --emu.print("ACCEPTED " .. geneWrong.into .. " " .. geneWrong.out .. " " .. geneRight.out .. " " .. geneRight.into)
                            end
                        elseif s == 1 and g == 2 then
                            emu.print("REJECTED " .. geneWrong.into .. " " .. geneWrong.out .. " " .. geneRight.out)
                        end
                    end
                    
                    --emu.print(nid)
                end
                
                for outputid=1,6 do
                    local geneRight = newGene()
                    geneRight.weight = 1.0
                    geneRight.enabled = true
                    geneRight.innovation = newInnovation()
                    geneRight.into = mapping[outputid+1000000.5]
                    geneRight.out = 9000+outputid
                    table.insert(genes2,geneRight)
                end
                
                
                
                ns = file:read("*number")
                if ns == 0 then
                    genome.networkswitch = {}
                    genome.oscillations = {{}}
                    genome.genes = {genes1}
                else
                    genome.networkswitch = {ns}
                    genome.genes = {genes1,genes2}
                    genome.oscillations = {{},{}}
                end
                local numosci = file:read("*number")
                for i=1,numosci do
                    genome.oscillations[1][i] = file:read("*number")
                    if ns > 0 then
                        genome.oscillations[2][i] = genome.oscillations[1][i]
                    end
                end
                if g == 1 then
                    genomeCopy = copyGenome(genome)
                    table.insert(species.genomes,genomeCopy)
                end
            end
            if s == 1 then
                --emu.print(species.genomes[2].genes[1])
                --emu.print("*3")
            end
            bnLoops = 0
            bottleneck = 0
            for i=1,maxstale do
                bottleneck = bottleneck + 1
                length= math.min(12*popOscilMultiplier,(5 + 2*bnLoops)*popOscilMultiplier)
                if bottleneckcounter == GBPO + length*3 then
                    bottleneckcounter = 0
                    bnLoops = bnLoops + 1
                end
            end
        end
        file:close()
    end
 
    --while fitnessAlreadyMeasured() do
    --    nextGenome()
    --end
    initializeRun()
    pool.currentFrame = pool.currentFrame + 1
end
 
 
function playTop()
    local maxfitness = 0
    local maxs, maxg
    rightmost = 0
    pool.currentFrame = 0
    hitblocks = 0
    hiddenblocks = {}
    offset = 0 --offset in x coordinates for pipe
    timebonus = 0 --used to freeze fitness
    mariohole = false --reset the fallen into hole variable
    mariopipe = false --is mario exiting a pipe or just teleporting
    loop = false --is this a loop?
    marioPipeEnter = false --has mario entered a pipe
    currentTracker = 1 --tracker for the loop
    killtrigger = false
    ntrigger = true
    nswitch = 1
    for s,species in pairs(pool.species) do
        for g,genome in pairs(species.genomes) do
            if genome.fitness > maxfitness then
                maxfitness = genome.fitness
                maxs = s
                maxg = g
            end
        end
    end
 
    pool.currentSpecies = maxs
    pool.currentGenome = maxg
    pool.maxFitness = maxfitness
    initializeRun()
    pool.currentFrame = pool.currentFrame + 1
    return
end
 
function fitnesstracker()
    if pool.maxFitness > 0 then
        local file = io.open('fitnesstracker.txt', "a")
        file:write("Gen: " .. pool.generation .. " Species: " .. pool.currentSpecies .. " Genome: " .. pool.currentGenome .. " Fitness: ".. pool.maxFitness .. "\n")
        file:close()
    end
end
 
function writelatestgen() --used to write the generation tracker to a file
    local file = io.open('backups/latestgen', "w")
    file:write(SAVE_LOAD_FILE .. "\n")
    file:write(savestate_slot .. "\n")
    file:write(pool.generation)
    file:close()
end
 
function readlatestgen() --used to retrieve the latest generation from a file
    if FileExists('backups/latestgen') then
        local file = io.open('backups/latestgen', "r")
        SAVE_LOAD_FILE = file:read("*line")
        savestate_slot = file:read("*number")
        SavestateObj = savestate.object(savestate_slot)
        local latestgen = file:read("*number")-1
        file:close()
        local name = 'backups/backup3.' .. SAVE_LOAD_FILE
        print('loaded: '.. name)
        return name
    else
        print('Please first run the AI normally before trying to fast load the latest generation')
    end
end
 
function keyboardinput()
    local keyboard = input.get()
    if keyboard['N'] and keyflag == false then
        if neural_net_disp then
            neural_net_disp = false
        else
            neural_net_disp = true
        end
        keyflag = true
    end
    if keyboard['M'] and keyflag == false then
        if mutation_rates_disp then
            mutation_rates_disp = false
        else
            mutation_rates_disp = true
        end
        keyflag = true
    end
    if keyboard['L'] and keyflag == false then
        if not nopopup then
            local loadyn = input.popup('Are you sure you want to load the last saved generation?')
        end
        if loadyn == 'yes' or nopopup then
            name = readlatestgen()
            loadFile(name)
        end
        keyflag = true
    end
    if keyboard['B'] and keyflag == false then
        if not nopopup then
            local loadyn = input.popup('Are you sure you want to show the topFitness genome?')
        end
        if loadyn == 'yes' or nopopup then
            playTop()
        end
    end
 
 
    if keyboard['S'] and keyflag == false then
        savePool()
        keyflag = true
    end
    if not (keyboard['N'] or keyboard['L'] or keyboard['M'] or keyboard['T'] or keyboard['B'] or keyboard['S']) and keyflag == true then
        keyflag = false
    end
    if keyboard['T'] and keyflag == false then
        if turbo then
            emu.speedmode("normal")
            turbo = false
            print('stop turbo')
        elseif not turbo and not restoreturbo then
            emu.speedmode("turbo")
            turbo = true
        elseif restoreturbo then
            restoreturbo = false
        end
        keyflag = true
    end
end
 
 
writeFile("temp.pool")
requiredFitness = 0
minFitness = 0
if SAVE_LOAD_FILE == nil then
    if LostLevels == 1 then
        SAVE_LOAD_FILE = "SMBLL".. CurrentWorld+1 .."-".. CurrentLevel ..".state.pool"
    else
        SAVE_LOAD_FILE = "SMB".. CurrentWorld+1 .."-".. CurrentLevel ..".state.pool"
    end
end
 
if savedpool then
    loadFile(savedpool)
end
 
 
while true do
    keyboardinput()
    local backgroundColor = toRGBA(0xD0FFFFFF)
        gui.drawbox(0, 0, 260, 30, backgroundColor, backgroundColor)
 
    if pool.maxcounter == nil then
        pool.maxcounter = 0
        --emu.print("caught missing variable")
    end
 
    local species = pool.species[pool.currentSpecies]
    local genome = species.genomes[pool.currentGenome]
 
    if neural_net_disp then
        displayGenome(genome)
    end
    if pool.generation == 0 and pool.currentSpecies ~= 1 and not turbo and AllowAutoTurbo and FirstGenSpeedup then
        emu.speedmode("turbo")
        turbo = true
    -- elseif pool.generation == 2 and turbo and AllowAutoTurbo and FirstGenSpeedup then
        -- emu.speedmode("turbo")
        -- turbo = false
    end
 
    local nscompare = rightmost / 500.0
    local nsw = 1
    for n=1,#genome.networkswitch do
        if nscompare > genome.networkswitch[n] then
            nsw = n+1
        end
    end
    nswitch = nsw
 
    if pool.currentFrame%5 == 0 then
        evaluateCurrent()
    end
 
    joypad.set(player, controller)
 
    getPositions()
    local mariostate = mariostate
    local adjustfitness
    local CurrentWorld = CurrentWorld
    local CurrentLevel = CurrentLevel
    local marioX = marioX
    local marioY = marioY
    if mariostate == 2 or mariostate == 3 then --detects when mario enters a pipe
        marioPipeEnter = true
        if mariostate == 2 then
            adjustfitness = true
        else
            adjustfitness = false
        end
    end
    if (CurrentWorld == 3 and CurrentLevel == 4) or (CurrentWorld == 6 and CurrentLevel == 4) or (CurrentWorld == 7 and CurrentLevel == 3) then
        if currentTracker == currentscreen or currentTracker == nextscreen and not mariopipe then --follows the progress of mario
            currentTracker = nextscreen
        elseif currentTracker > nextscreen and not mariopipe and not marioPipeEnter and mariostate ~= 7 then --if mario suddenly goes back trigger loop detection
            loop = true
        elseif currentscreen == 0 and nextscreen == 0 and mariopipe and mariostate ~= 7 then -- if mario enters a piperoom negate loop detection
            loop = false
            currentTracker = nextscreen
        elseif nextscreen >= (currentTracker - 1) and mariopipe and mariostate ~= 7 then -- if mario goes forward in the level by entering a pipe negate loop detection
            loop = false
            currentTracker = nextscreen
            pipeexit = true
        else --if nothing is true then we are in a loop
            if not pipeexit then --prevents the extra one frame flicker of the tracker
            loop = true
            else
            pipeexit = false
            end
        end
    end
    if marioPipeEnter == true and mariostate == 7 then --set mariopipe when exiting the pipe
        mariopipe = true
    end
    if adjustfitness then
        if mariopipe == true and mariostate ~= 7 and rightmost > marioX and not mariohole then --calculate offset
            offset = rightmost - marioX
            mariopipe = false
            marioPipeEnter = false
        elseif mariopipe == true and mariostate ~= 7 and rightmost <= marioX and not mariohole then --if the exit coordinates are higher than rightmost after exiting a pipe set offset to 0
            offset = 0
            mariopipe = false
            marioPipeEnter = false
        end
    else
        if mariopipe == true and mariostate ~= 7 and not mariohole then --calculate offset --and rightmost > marioX
            offset = rightmost - marioX
            mariopipe = false
            marioPipeEnter = false
        end
    end
--Custom Fix: Springs
    --emu.print(marioY + memory.readbyte(0xB5)*255)
    if (marioY + memory.readbyte(0xB5)*255 > 512)then --added the fallen into a hole variable that will activate when LuigI/O goes below screen 
        if marioY + memory.readbyte(0xB5)*255 > 768 and not mariohole then -- Added this bit to prevent LuigI/O from being treated as fallen when he goes too high on the screen due to Lost Level springs. 
        TimeoutConstant = 800 -- Increase the TimeoutConstant so that the game won't timeout when LuigI/O uses the spring.
        else
        mariohole = true
        
        end
    else -- If LuigI/O's marioY + 0xB5 byte * 255 is less than 512, he is visible on the screen
        TimeoutConstant = 120 --Reset TimeoutConstant back to normal when visible on screen.
    end
    local dis = false
    local specialy = false
    if marioX > 2370 and marioX < 2460 and (LostLevels == 0 and CurrentWorld == 7 and CurrentLevel == 3) then
    specialy = true
    end
    if marioX > 2460 and marioX < 3000 and (LostLevels == 0 and CurrentWorld == 7 and CurrentLevel == 3) then
    dis = true
    end
    if marioX > 3000 and marioX < 3160 and marioY < 192 and marioY > 64 and (LostLevels == 1 and CurrentWorld == 1 and CurrentLevel == 1) then
    dis = true
    end
 
    if marioX > 496 and marioX < 928 and marioY < 128 and (LostLevels == 1 and CurrentWorld == 2 and CurrentLevel == 4) then
    dis = true
    end
    if marioX > 928 and marioX < 1264 and marioY < 160 and (LostLevels == 1 and CurrentWorld == 2 and CurrentLevel == 4) then
    dis = true
    end
    if marioX > 1632 and marioX < 2384 and (marioY > 80 or hitblocks == 0) and (LostLevels == 1 and CurrentWorld == 2 and CurrentLevel == 4) then
    dis = true
    end
    
    local ymulti = 0.2
    if (LostLevels == 0 and CurrentWorld == 3 and CurrentLevel > 2) then ymulti = 1 end
    
    local newright = marioX + offset - (marioY - 192)*ymulti + (50 *memory.readbyte(0x06D9))
    if specialy then
        newright = 2370 + offset - marioY + 192 + (50 *memory.readbyte(0x06D9))
    end
    
    if LostLevels == 0 and CurrentWorld == 3 and CurrentLevel == 4 then
        if marioX > 1680 and marioX < 2288 and marioY <= 144 then
            dis = true
        end
        if marioX > 300 and marioX < 1200 and marioY > 80 then
            dis = true
        end
        if marioX > 1512 and marioX < 1680 then
            if marioY <= 80 then
                newright = marioX + offset + 112 + (50 *memory.readbyte(0x06D9))
            elseif marioY <= 144 then
                newright = 3360 - marioX + offset + 176 + (50 *memory.readbyte(0x06D9))
            else
                newright = 352 + marioX + offset + 224 + (50 *memory.readbyte(0x06D9))
            end
        elseif marioX > 1680 then
            newright = 352 + marioX + offset + 224 + 192 - marioY + (50 *memory.readbyte(0x06D9))
        end
    end
    
    
    if newright > rightmost and not mariohole and not loop and not marioPipeEnter and not killtrigger and not falling and not dis then
        rightmost = newright
        if rightmost > species.topright then
            species.topright = rightmost
        end
        timebonus = timebonus + (TimeoutConstant - timeout) * 11/20
        timeout = TimeoutConstant
    end
 
    if marioPipeEnter == true then --freeze fitness and timer when mario enters a pipe
        timeout = timeout + 1
        timebonus = timebonus + 2/3
    end
 
--Custom Fix: Changes made for Lost Levels 3-1 so that going in the subworld trap pipe won't increase fitness and for the last part "(marioX > 2900 and memory.readbyte(0xB5)*255 > 768)", when LuigI/O goes over the flagpole thanks to the spring (yes, it can happen, I tested), fitness will not increase as well.
    if(CurrentWorld == 2 and CurrentLevel == 0 and LostLevels == 1) then
        if (mariopipe and ((marioX > 500 and marioX < 572) or (marioX > 1244 and marioX < 1396) or (marioX > 3558 and marioX < 3708))) or (marioX > 2900 and memory.readbyte(0xB5)*255 > 768)  then
            loop = true
        end
    end
 
    if (mariostate == 4 or (memory.readbyte(0x000C) == 1 and statuscheck == 0) or ((memory.readbyte(0x06D6) ~= 0 and memory.readbyte(0x06D6) ~= 1) and marioPipeEnter)) or endlevel then --and statuscheck == 0
        timeout = timeout +1
        timebonus = timebonus + 2/3
        endlevel = true
    end
 
 
    if endlevel and mariostate == 8 and demoruncheck == 1 and statuscheck == 3 + LostLevels then
        local timeoutBonus = pool.currentFrame / 4
        local fitness = rightmost - pool.currentFrame / 2 +40 + timebonus
        genome.fitness = fitness
        pool.maxFitness = fitness
        fitnesstracker()
        savePool()
        if not rerunning then
            if LostLevels==1 then
                SAVE_LOAD_FILE = "SMBLL".. CurrentWorld+1 .."-".. CurrentLevel ..".state.pool"
            else
                SAVE_LOAD_FILE = "SMB".. CurrentWorld+1 .."-".. CurrentLevel ..".state.pool"
            end
            previous_savestate = savestate_slot
            savestate_slot = savestate_slot + 1
            if savestate_slot == 10 then
                savestate_slot = 1
            end
 
            Savestatebackup = savestate.object(savestate_slot)
            savestate.save(Savestatebackup)
            savestate.persist(Savestatebackup)
            savestate.save(SavestateObj)
 
        end
        SavestateObj = savestate.object(savestate_slot)
        endlevel = false
        if (turbo or restoreturbo) and not rerunning then
            rerunning = true
            requiredFitness = 0
            minFitness = 0
            emu.speedmode("normal")
            turbo = false
            restoreturbo = false
            reRun()
        else
            rerunning = false
            preparenext = true
        end
    end
 
    if CurrentWorld == 0 and CurrentLevel == 0 and demoruncheck == 0 then
        savestate.load(SavestateObj)
    end
 
    timeout = timeout - 1
    if preparenext then
        rerunning = false
        preparenext = false
        GenerateNewKeepBest(genome, species.nickname)
    end
    if rightmost > maxright then
        maxright = rightmost
    end
    if rightmost > maxright*0.85 and AllowSlowdownNearMax then
        if turbo and maxright > 200 and AllowSlowdownNearMax and not restoreturbo then
            emu.speedmode("normal")
            turbo = false
            restoreturbo = true
        end
    elseif restoreturbo and not (requiredFitness > 10) then
        emu.speedmode("turbo")
        turbo = true
        restoreturbo = false
    end
    --emu.print(marioX .. " " .. marioY)
    local timeoutBonus = pool.currentFrame / 4
    if (mariostate == 11 or mariohole or math.floor(rightmost - (pool.currentFrame) / 2 + - (timeout + timeoutBonus)*2/3 +40 + timebonus) < -50 ) and timeout > 40-timeoutBonus then
        local temptimeout = timeout
        timeout = 30 - timeoutBonus
        local difference = temptimeout - timeout
        pool.currentFrame = pool.currentFrame + difference
        killtrigger = true
    end
    local timeoutBonus = pool.currentFrame / 4
    if timeout + timeoutBonus <= 0 then
        local fitness = rightmost - pool.currentFrame / 2 +40 + timebonus + hitblocks * hbValue
        if fitness == 0 then
            fitness = -1
        end
 
            genome.fitness = fitness
 
        if species.topFitness < secondbest and fitness > secondbest and fitness < pool.maxFitness then --system for automatically updating the second best value
            secondbest = fitness
        elseif species.topFitness < pool.maxFitness and fitness > pool.maxFitness then
            secondbest = pool.maxFitness
        elseif species.topFitness == secondbest and fitness > secondbest and fitness < pool.maxFitness then
            secondbest = fitness
        elseif species.topFitness == secondbest and fitness > pool.maxFitness then
            secondbest = pool.maxFitness
        end
        
        if fitness > pool.maxFitness then
            pool.maxFitness = fitness
            fitnesstracker()
            savePool()
        end
 
        if fitness > pool.maxFitness - 30 then
            pool.maxcounter = pool.maxcounter + 1
        end
 
        emu.print("Gen " .. pool.generation .. " species " .. pool.currentSpecies .. " genome " .. pool.currentGenome .. " fitness: " .. fitness)
        pool.currentSpecies = 1
        pool.currentGenome = 1
        while fitnessAlreadyMeasured() do
            offset = 0 --offset in x coordinates for pipe
            timebonus = 0 --used to freeze fitness
            mariohole = false --reset the fallen into hole variable
            mariopipe = false --is mario exiting a pipe or just teleporting
            loop = false --is this a loop?
            marioPipeEnter = false --has mario entered a pipe
            currentTracker = 1 --tracker for the loop
            killtrigger = false
            ntrigger = true
            nswitch = 1
            if rerunning then
                rerunning = false
                preparenext = true
            end
            nextGenome()
        end
        
 
        initializeRun()
    end
 
    
        gui.opacity(1) --made the top banner opaque. you can change this if you want a slightly transparent banner
        if turbo then
            gui.drawtext(250, 11, "T", toRGBA(0xFFFF0000), 0x0)
        end
        gui.drawtext(5, 11, "Gen " .. pool.generation .. " species " .. pool.currentSpecies .. " genome " .. pool.currentGenome .. " (" .. math.floor(pool.measured/pool.total*100) .. "%) ".. "Pop: ".. Population, toRGBA(0xFF000000), 0x0)
        gui.drawtext(5, 20, "Fitness: " .. math.floor(hitblocks * hbValue + rightmost - (pool.currentFrame) / 2 + - (timeout + timeoutBonus)*2/3 +40 + timebonus) , toRGBA(0xFF000000), 0x0) --the (timeout + timeoutBonus)*2/3 makes sure that the displayed fitness remains stable for the viewers pleasure
        gui.drawtext(80, 20, "Max Fitness:" .. math.floor(pool.maxFitness) .. " (".. math.floor(secondbest) .. ")".. " ".. "(" .. pool.maxcounter .. "x)", toRGBA(0xFF000000), 0x0)
 
    pool.currentFrame = pool.currentFrame + 1
 
    emu.frameadvance();
end

Comments

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

    0 B

    |

    👍

    /

    👎