--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
0 B
|👍
/👎