require "drawlib"
require "drawtext"
levelname = "1-1"
savestateSlotMap = 1 --savestate slot to save the map selection from.
savestateSlotLevel = 2
savestateSlotBOSS = 3
savestateObj = savestate.object(savestateSlotMap)
savestate.load(savestateObj)
LM = memory.readbyte(0x0726)
Player = 1 + LM
BoxRadius = 6 --The radius of the vision box around Luigi
InitialOscillations = {50,60,90} --Initial set of oscillation timers
BoxWidth = BoxRadius*2+1 --Full width of the box
BoxSize = BoxWidth*BoxWidth --Number of neurons in the box
Inputs = BoxSize + 3 + #InitialOscillations
InitialMutationRates = {
linkInputBox=0.1,
linkBottomRow=0.05,
linkLayers=0.1,
linkRandom=2.0,
node=0.5,
disable=0.4,
enable=0.2,
oscillation=0.2,
nswitch=0.01,
step=0.1
}
FramesPerEval = 5 --Number of frames between each network update
ButtonNames = {"A","B","up","down","left","right"}
ButtonNumbers = {A=1,B=2,up=3,down=4,left=5,right=6}
DeltaDisjoint = 4.0 --multiplier for disjoint in same species function
DeltaWeights = 0.4 --multiplier for weights in same species function
DeltaThreshold = 1.0 --threshold for delta to be in the same species
DeltaSwitch = 0.6 --threshold for delta of the network switch location
DisplaySlots = false --Display the sprite slots?
DisplaySprites = false --Display the sprite hitboxes?
DisplayGrid = 0 --Display a large network inputs grid?
DisplayNetwork = true --Display the neural network state?
DisplayStats = true --Display top stats bar?
DisplayRanges = false --Display special fitness ranges?
DisplayCounters = false --Display death/time counter?
ScrollWalkPause = false
Replay = false
ManualInput = false --Input manually rather than by network?
--Penalty coeffs for fitness rank of netswitched species
NetworkPenaltyInner = 0.8
NetworkPenaltyOuter = 0.87
CrossoverChance = 0.75 --Chance of crossing 2 genomes rather than copying 1
MajorBreakFitness = 30 --Fitness before a breakthrough is not counted as minor
BaseInterbreedChance = 0.07 --base interbreed chance per species
InterbreedCutoff = 2.86 --average species size when interbreed is turned off
InterbreedDegree = 1.0 --degree of interbreed curve
TimeBeforePopOscil = 6 --time in each oscillation before the population changes
MaxStaleness = 25 --max staleness before death
FramesOfDeathAnimation = 50 --amount of the death animation to show
WeightPerturbChance = 0.9
WeightResetChance = 0.9
MaxNodes = 1000000
FPS = 60
TurboMin = 0
TurboMax = 0
CompleteAutoTurbo = false
currentTurbo = false
emu.speedmode("normal")
marioAutoscroll = 0
basescroll = 0
marioWhiteblock = 0
boss = false
battle = false
basetimeout = 120
roottimeout = 120
preloaded = false
cardDistance = 9999
powerupFitness = 150
roottimeoutboss = 900
dirsep = "\\" --forward or backward slash for file separation? depends on OS
ProgramStartTime = os.time()
Startlocations={["0-1-2"]=true,["1-1-5"]=true,["2-1-5"]=true,["3-1-2"]=true,["4-1-4"]=true,["5-1-3"]=true,["6-1-1.5"]=true,["7-1-2.5"]=true}
marioPrevWhiteblock = 0
marioPrevAutoscroll = 0
scrollBonus = 0
scrollBonusAdjust = 0
lockBound = false
function getPositions(initial)
local marioHiX = memory.readbyte(0x0075)
local marioLoX = memory.readbyte(0x0090)
marioX = marioLoX+256*marioHiX
marioScreenX = memory.readbyte(0x00AB)
marioXVel = memory.readbyte(0x00BD)
marioY = 400-(memory.readbyte(0x00A2)+256*memory.readbyte(0x0087))
marioScreenY = memory.readbyte(0xB4)--memory.readbyte(0x0228)
marioYVel = memory.readbyte(0x00CF)
marioScore = memory.readbyte(0x0716)*2560 + memory.readbyte(0x0717)*10
marioActive = memory.readbyte(0x00CE) == 0 and memory.readbyte(0x03DE) == 0 --loading and using pipe/door
local marioWhiteblock = memory.readbyte(0x0570)
local marioAutoscroll = memory.readbyte(0x7A0C)
--print(marioScreenY)
--local marioAutoscrollY = memory.readbyte(0x7A0D)
marioHold = memory.readbyte(0x06A4)
marioPower = memory.readbyte(0x00ED)
marioMusic = memory.readbyte(0x04E5)
koopalingDefeated = memory.readbyte(0x07BD)
kingconvo = memory.readbyte(0x0728)
local whiteblockAdjustment = 0
if (not marioActive and marioScreenY > 184) or doorflag then
doorflag = true
marioOutOfBound = false
if marioActive and marioScreenY < 184 then
doorflag = false
end
else
marioOutOfBound = (marioY < 0 or (marioScreenY > 184 and memory.readbyte(0x0544) == 0 and cardDistance > 0.5)) and marioActive -- byte is for above screen
end
if marioPrevWhiteblock >100 and marioWhiteblock < -100 then
whiteblockAdjustment = 256
else
whiteblockAdjustment = 0
end
marioPrevWhiteblock = marioWhiteblock
WhiteblockBonus = whiteblockAdjustment + marioWhiteblock*10
if scrollBonus == 0 and not marioActive then
-- scrollBonusAdjust = -marioAutoscroll
memory.writebyte(0x7A0C,0)
--basemarioA
local marioAutoscroll = 0
end
if marioAutoscroll == 0 and marioPrevAutoscroll ~= 0 and scrollBonus > 0 then
basescroll = basescroll + marioPrevAutoscroll
-- elseif marioAutoscroll == marioPrevAutoscroll and scrollBonus > 0 then
-- scrollBonus = 0
-- basescroll = 0
end
scrollBonus = basescroll + marioAutoscroll + scrollBonusAdjust
marioPrevAutoscroll = marioAutoscroll
local numSprites = 6
sprites = {}
for s=1,numSprites do
local sprite = {}
sprite.x = memory.readbyte(0x0090+s)+256*memory.readbyte(0x0075+s)
sprite.y = 416-(memory.readbyte(0x00A2+s)+256*memory.readbyte(0x0087+s))
sprite.state = memory.readbyte(0x0660+s)
sprite.type = memory.readbyte(0x670+s)
if sprite.type == 54 then
table.insert(sprites,{x=sprite.x+16,y=sprite.y,state=256})
table.insert(sprites,{x=sprite.x+32,y=sprite.y,state=256})
--print(sprite)
end
if sprite.type == 14 then
marioScore = marioScore + memory.readbyte(0x0083)*1000
end
if sprite.type == 74 or sprite.type == 82 then --added this because luigi does not want to pick up the ball once he defeats boomboom
if sprite.x - marioX > 0 then
for a=1,30 do
joypad.set(Player,{right=true})
coroutine.yield()
end
else
for a=1,30 do
joypad.set(Player,{left=true})
coroutine.yield()
end
end
end
table.insert(sprites,sprite)
end
for s=1,8 do --special sprites
local sprite = {}
local id = memory.readbyte(0x7FC5+s)
if id ~= 0 then
sprite.type = id
sprite.y = 416-memory.readbyte(0x05BE+s)+256*memory.readbyte(0x7FD4+s)
local xlow = memory.readbyte(0x05C8+s)
local xhigh = 0
local differ = xlow-marioLoX
if differ > 128 then
xhigh = marioHiX -1
elseif differ < -128 then
xhigh = marioHiX +1
else
xhigh = marioHiX
end
sprite.x = xlow + 256*xhigh
sprite.state=256
table.insert(sprites,sprite)
end
end
end
function getTile(dx,dy)
local x = math.floor((marioX + dx + 8)/16)
local y = math.floor((432-marioY + dy - 8)/16)
local page = math.floor(x/16)
local px = x % 16
local tile = memory.readbyte(0x6000+px+y*16+page*432)
--print(x .. " " .. marioScreenX/16)
local scrollX = (marioX-marioScreenX)/16
local scrollY = (marioY+marioScreenY-161)/16
if x < scrollX or x > scrollX + 16 or 26-y < scrollY or 26-y > scrollY + 11 then return 0 end
return tile
end
CollisionTileSet = {}
-- CollisionTileArrays[1]={air=128,0x25,0x26,0x27,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x5F,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0xA0,0xA1,0xA2,0xAD,0xAE,0xAF,0xB1,0xB2,0xB3,0xB4,0xB5,0xB5,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xE2,0xE3,0xE4,0xF0,0xF1,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9}
-- CollisionTileArrays[3]={air=134,0x2E,0x2F,0x30,0x31,0x32,0x48,0x5F,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x87,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xF0,0xF1,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9}
-- CollisionTileArrays[4]={air=128,0x11,0x12,0x13,0x22,0x23,0x24,0x25,0x2C,0x2E,0x2F,0x30,0x31,0x32,0x34,0x35,0x36,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0x53,0x54,0x55,0x5B,0x5F,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8E,0x8F,0x90,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xFA,0xFB}
CollisionTileArray= {0x80,0x02,0x86,0x80,0x80,0x8C,0x42,0x80,0x80,0x80,0x80,0x80,0xCC,0x86,0x09,0x09,0x09,0x02}
function getTileset()
local tileset = memory.readbyte(0x070A)
local x1a = memory.readbyte(0x7E94)
local x1b = memory.readbyte(0x7E95)
local x1c = memory.readbyte(0x7E96)
local x1d = memory.readbyte(0x7E97)
card_x = nil
for j=1,48 do
local id = memory.readbyte(0x07B3E+(3*j))
if id == 0x41 then --0x41 is end card
card_x = memory.readbyte(0x07B3E+(3*j)+1)*16
card_y = memory.readbyte(0x07B3E+(3*j)+2)*16
end
if id == 0xFF then --ff indicates last entry
if card_x == nil then
card_x = 9999
card_y = 9999
end
break
end
end
CollisionTileSet = {a=x1a,b=x1b,c=x1c,d=x1d}
CollisionTileSet.air=CollisionTileArray[tileset]
end
function getInputs() --Create the grid around Mario that is the network's vision
getPositions() --Get all required memory data
local inputs = {} --Grid around Mario that shows blocks/sprites
--Loop through each space in the grid
local BoxRadius = BoxRadius
local BoxWidth = BoxWidth
local CollisionTileSet = CollisionTileSet
local marioX = marioX
local marioY = marioY
local sprites = sprites
for dy=-BoxRadius*16,BoxRadius*16,16 do
for dx=-BoxRadius*16,BoxRadius*16,16 do
inputs[#inputs+1] = 0
--If the tile contains a block, set to 1
local tile = getTile(dx, dy)
if tile ~= CollisionTileSet.air and marioY+dy < 0x1B0 then
if (tile >= CollisionTileSet.a and tile < 0x40) or (tile >= CollisionTileSet.b and tile < 0x80) or (tile >= CollisionTileSet.c and tile < 0xC0) or (tile >= CollisionTileSet.d and tile < 0xFF) then inputs[#inputs] = 1
else inputs[#inputs] = 0.5
end
end
end
end
--for each sprite set it's location to -1
local spriteNearlocal = false
for i,sprite in ipairs(sprites) do
if sprite.state ~= 0 then
local distx = math.floor((sprite.x - marioX)/16 + 0.5)
if math.abs(distx) < 3.5 then
spriteNearlocal = true
end
local disty = math.floor((-sprite.y + marioY)/16 + 0.5)
if math.abs(distx)<=BoxRadius and math.abs(disty)<=BoxRadius then
inputs[distx+BoxRadius+1+(disty+BoxRadius)*BoxWidth] = -1
end
end
end
spriteNear = spriteNearlocal
return inputs
end
--Initialization for the genetic system
function initPool(Boss) --The pool contains all data for the genetics of the AI
local poolold = pool
pool = {}
pool.species = {} --List of species
pool.generation = 0 --Generation number
pool.innovation = 1 --Innovation tracks genes that are copies of each other vs unique
pool.currentGenome = 1 --current genome/species to display on top bar
pool.currentSpecies = 1
pool.maxFitness = 0 --Maximum fitness
pool.bestSpecies = 1 --best species
pool.secondFitness = 0 --Second best fitness
pool.maxCounter = 0 --Number of times it has gotten close to max fitness
pool.gsid = 0 --GSID tracks species so each one gets a unique id
pool.bottleneck = 0 --Number of gens since last breakthrough
pool.bottleneckloops = 0 --Number of oscillation loops in the bottleneck
pool.population = 999 --Number of genomes
pool.lastMajorBreak = 0 --Last breakthrough of more than majorBreakFitness
pool.current = 0
pool.total = 0
pool.average = 0
pool.lastbreaktime = 0
if not Boss then
pool.attempts = 1 --number of attempts
pool.deaths = 0 --number of deaths (auto timeouts do not count)
pool.totalTime = 0 --total time elapsed in-game
pool.realTime = 0 --total time elapsed out of game
pool.history = "" --breakthrough tracker
pool.breakthroughX = 0 --indicator stuff
pool.breakthroughZ = ""
pool.maphistogram = {}
pool.breakthroughfiles = {}
else
pool.attempts = poolold.attempts
pool.deaths = poolold.deaths
pool.totalTime = poolold.totalTime
pool.realTime = poolold.realTime
pool.history = poolold.history
pool.breakthroughX = poolold.breakthroughX
pool.breakthroughZ = poolold.breakthroughZ
pool.maphistogram = poolold.maphistogram
pool.breakthroughfiles = poolold.breakthroughfiles
end
end
function newSpecies() --Each species is a group of genomes that are similar
local species = {}
species.maxFitness = 0 --farthest the species has gotten
species.maxRightmost = 0 --Farthest the species has gotten, ignoring time
species.averageFitness = 0 --Average fitness of the species
species.staleness = 0 --Number of gens since the species improved
species.genomes = {} --List of genomes
species.nick = '' --nickname of the species
species.turbo = true --whether the species will use turbo or not
species.gsid = pool.gsid --give the species a unique GSID
pool.gsid = pool.gsid + 1
species.breakthroughX = 0 --indicator stuff
species.breakthroughZ = ""
return species
end
function newGene() --A gene is a link between two neurons
local gene = {}
gene.into = 0 --input neuron
gene.out = 0 --output neuron
gene.weight = 0.0 --multiplier
gene.enabled = true --gene is turned on
gene.innovation = 0 --gene ID to find duplicates
gene.delay = 0
return gene
end
function copyGene(gene) --copy a gene
local newGene = {}
newGene.into = gene.into
newGene.out = gene.out
newGene.weight = gene.weight
newGene.enabled = gene.enabled
newGene.innovation = gene.innovation
newGene.delay = gene.delay
return newGene
end
function newGenome(numNetworks) --Each genome is an evolved 'player' that attempts the level
local genome = {}
genome.genes = {} --List of networks in raw gene form
genome.networks = {} --List of networks in neuron form
genome.oscillations = {} --Periods of each of 3 oscillating nodes
genome.maxNeuron = Inputs --Index of largest neuron
for n=1,numNetworks do --Initialize these as blank arrays for each network
genome.genes[n] = {}
genome.oscillations[n] = {}
for i=1,#InitialOscillations do --Initialize periods to default values
genome.oscillations[1][i] = InitialOscillations[i]
end
end
genome.fitstate = {} --information about the genome's final state
genome.globalRank = 0 --Ranking of the genome (low is worse)
genome.networkswitch = {} --List of positions where the network switches
for n=1,numNetworks-1 do
genome.networkswitch[n] = 0.0
end
genome.mutationRates = {} --List of mutation rates
for name,rate in pairs(InitialMutationRates) do
genome.mutationRates[name] = rate
end
return genome
end
function copyGenome(genome) --copy a genome
local ngenome = newGenome(#genome.genes) --create basic new genome
for n=1,#genome.genes do --copy each network over
for i,gene in pairs(genome.genes[n]) do
table.insert(ngenome.genes[n],copyGene(gene))
end
for i=1,#genome.oscillations[n] do
ngenome.oscillations[n][i] = genome.oscillations[n][i]
end
end
ngenome.maxNeuron = genome.maxNeuron
for n=1,#genome.genes-1 do
ngenome.networkswitch[n] = genome.networkswitch[n]
end
for name,rate in pairs(genome.mutationRates) do
ngenome.mutationRates[name] = rate
end
return ngenome
end
function newNeuron() --A neuron is a stored value that is calculated based on inputs and broadcasts to its outputs
local neuron = {}
neuron.incoming = {} --list of incoming genes
neuron.queues = {} -- incoming queues for delay
neuron.value = 0.0 --current value
neuron.layer = 0 --What layer the neuron is in (0 means it has not been calculated)
return neuron
end
--Evaluation of networks
function generateNetwork(genome)
for n=1,#genome.genes do --For each network
local neurons = {}
for i=1,Inputs do --Put in input nodes
neurons[i] = newNeuron()
end
for g=1,#genome.genes[n] do --Loop through each gene
local gene = genome.genes[n][g]
if gene.enabled then --Only add enabled genes
--If either side of gene is not added to neurons yet then add it
if neurons[gene.into] == nil then
neurons[gene.into] = newNeuron()
end
if neurons[gene.out] == nil then
neurons[gene.out] = newNeuron()
end
if gene.into > 0 then
table.insert(neurons[gene.out].incoming,gene) --Add gene to the output neuron's incoming
queue = {}
for i=1,gene.delay do
table.insert(queue,0)
end
table.insert(neurons[gene.out].queues,queue)
end
end
end
local layers = {}
while true do --loop until all layers found
local layer = {}
local finished = true --if all have been sorted
for i,neuron in pairs(neurons) do --loop through and find all possible for calculation
if neuron.layer == 0 then --Dont re-check already placed neurons
local possible = true
for j,gene in pairs(neuron.incoming) do --see if any incoming genes are not calculated yet
if neurons[gene.into].layer == 0 then
possible = false
break
end
end
if possible then --if has enough information to evaluate
table.insert(layer,i)
finished = false --a neuron was placed so it has not been all sorted yet
end
end
end
if finished then --exit the loop
break
end
for i=1,#layer do --For each neuron in the new layer set it to now be sorted
neurons[layer[i]].layer = #layers+1
end
table.insert(layers,layer)
end
local network = {}
network.neurons = neurons
network.layers = layers
network.genes = genome.genes[n]
genome.networks[n] = network
end
end
function sigmoid(x)
return 2/(1+math.exp(-4.9*x))-1
end
function evaluateNetwork(network, inputs, oscillating, frame)
table.insert(inputs,1.0) --Bias node
local marioXVel = marioXVel
local marioYVel = marioYVel
local ScrollWalkPauselocal = ScrollWalkPause
local marioScreenX = marioScreenX
local endscroll = endscroll
local spriteNear = spriteNear
local ButtonNames = ButtonNames
local scrollBonus = scrollBonus
local Inputs = Inputs
local mmax = math.max
for i=1,#oscillating do --Oscillating nodes
if frame % (oscillating[i]*2) < oscillating[i] then
table.insert(inputs,1.0)
else
table.insert(inputs,-1.0)
end
end
--Speed nodes
if (marioXVel < 100) then
table.insert(inputs,marioXVel / 56)
else
table.insert(inputs,(marioXVel - 256) / 56)
end
if (marioYVel < 70) then
table.insert(inputs,marioYVel / 69)
else
table.insert(inputs,mmax((marioYVel - 256) / 69,1))
end
if #inputs ~= Inputs then
emu.print("Incorrect number of neural network inputs.")
return {}
end
for i=1,Inputs do
network.neurons[i].value = inputs[i]
end
-- for _,neuron in pairs(network.neurons) do
-- local sum = 0
-- for j = 1,#neuron.incoming do
-- local incoming = neuron.incoming[j]
-- local other = network.neurons[incoming.into]
-- sum = sum + incoming.weight * other.value
-- end
-- end
for l=2,#network.layers do
local layer = network.layers[l]
for n=1,#layer do
local neuron = network.neurons[layer[n]]
local sum = 0.0
for j = 1,#neuron.incoming do
local incoming = neuron.incoming[j]
local other = network.neurons[incoming.into]
if incoming.delay == 0 then
sum = sum + incoming.weight * other.value
else
sum = sum + incoming.weight * neuron.queues[j][1]
for i=1,#neuron.queues[j]-1 do
neuron.queues[j][i] = neuron.queues[j][i+1]
end
neuron.queues[j][#neuron.queues[j]] = other.value
end
end
neuron.value = sigmoid(sum)
end
end
local output = {}
--print("Yvel " .. marioYVel)
--print("Xvel " .. marioXVel)
--print("x " .. marioScreenX)
if (ScrollWalkPauselocal and (marioScreenX < 45 or (marioYVel > 5 and marioYVel < 100) or spriteNear)) or endscroll then
ScrollWalkPause = false
ScrollWalkPauselocal = false
--print("off")
end
if ((scrollBonus > 0 and marioScreenX > 200 and (marioYVel >240 or marioYVel < 5)) or ScrollWalkPauselocal) and not endscroll then
--print("first")
for o=1,#ButtonNames do
output[ButtonNames[o]] = false
end
if marioXVel > 10 and marioXVel < 128 then
output["left"] = true
end
if marioXVel > 128 and marioXVel < 245 then
output["right"] = true
end
ScrollWalkPause = true
ScrollWalkPauselocal = true
elseif (scrollBonus > 0 and marioScreenX > 155 and marioYVel ==0) and not spriteNear and not endscroll then
--print("second")
for o=1,#ButtonNames do
output[ButtonNames[o]] = false
end
if marioXVel > 10 and marioXVel < 128 then
output["left"] = true
end
if marioXVel > 128 and marioXVel < 245 then
output["right"] = true
end
ScrollWalkPause = true
ScrollWalkPauselocal = true
else
--print("1")
for o=1,#ButtonNames do --Find neural network outputs
output[ButtonNames[o]] = network.neurons[-o] ~= nil and network.neurons[-o].value > 0
end
end
--print(ScrollWalkPause)
--Disable opposite d-pad presses
if forcebutton ~= nil then
for k,v in pairs(forcebutton) do
output[k]=v
end
--print(output)
end
if output["up"] and output["down"] then
output["up"] = false
output["down"] = false
end
if output["left"] and output["right"] then
output["left"] = false
output["right"] = false
end
return output
end
--Mutation and evolution
function genomeDelta(genes1, genes2) --sees the delta between 2 networks.
local i2 = {} --i2 maps the innovation of a gene to the gene, for the second genome.
for i = 1,#genes2 do
local gene = genes2[i]
i2[gene.innovation] = gene
end
local sum = 0 --total weight difference among matching genes
local coincident = 0 --number of matching genes
for i,gene in pairs(genes1) do
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 --if there are no matching genes it does not match so return a very large value
return 100
end
local total = #genes1 + #genes2 --number of total genes
local disjoint = total - (coincident*2) --number of non-matching genes
return DeltaWeights * sum / coincident + DeltaDisjoint * disjoint / total --return the delta
end
function sameSpecies(genome1,genome2) --sees whether 2 genomes are in the same species or not.
if #genome1.networkswitch ~= #genome2.networkswitch then
return false --must have same number of networks
end
for n=1,#genome1.networkswitch do
if math.abs(genome1.networkswitch[n] - genome2.networkswitch[n]) > DeltaSwitch then
return false --if the network switches are too far apart they are different
end
end
for n=1,#genome1.genes do
delta = genomeDelta(genome1.genes[n],genome2.genes[n])
if delta > DeltaThreshold then
return false --if the delta between corresponding networks is too great they are different
end
end
return true --otherwise, same species
end
function shuffle(num) --shuffles an array from 1 to num.
local output = {}
for i=1,num do
local offset = i - 1
local randomIndex = offset*math.random()
local flooredIndex = randomIndex - randomIndex%1
if flooredIndex == offset then
output[#output + 1] = i
else
output[#output + 1] = output[flooredIndex + 1]
output[flooredIndex + 1] = i
end
end
return output
end
function addToSpecies(genome)
local foundSpecies = false
local order = shuffle(#pool.species) --Used to go through in random order so as to avoid feeding patterns.
for i=1,#order do
local species = pool.species[order[i]]
if sameSpecies(genome, species.genomes[1]) and not foundSpecies then
table.insert(species.genomes, genome)
foundSpecies = true
end
end
if not foundSpecies then --create new species
local newSpecies = newSpecies()
table.insert(newSpecies.genomes, genome)
table.insert(pool.species, newSpecies)
end
end
function randomNeuron(genes, nonInput, nonOutput) --pick a random neuron number
local neurons = {}
if not nonInput then --If inputs are an option add them
for i=1,Inputs do
neurons[i] = true
end
end
if not nonOutput then --If outputs are an option add them
for o=1,#ButtonNames do
neurons[-o] = true
end
end
for i,gene in pairs(genes) do --Add input and output of each gene
if not nonInput or gene.into > Inputs then --add input as long as it is valid
neurons[genes[i].into] = true
end
if not nonOutput or gene.out > 0 then --add output as long as it is valid
neurons[genes[i].out] = true
end
end
local count = 0
for _,_ in pairs(neurons) do --count number of neuron possibilities
count = count + 1
end
if count == 0 then return 0 end --return 0 means that there were no possibilities
local n = math.random(1, count) --pick a random possibility
for k,v in pairs(neurons) do
n = n-1 --count down until you get to correct #
if n == 0 then
return k
end
end
end
function linkDirection(genes, link) --Finds a connection's status. 0 means already exists, 1 means >, -1 means < or incomparable
for i=1,#genes do --check each gene
local gene = genes[i] --if the new added gene already exists return 0
if gene.into == link.into and gene.out == link.out then
return 0
end
end
--Attempt to find a connection opposing the suggested one (that would create a loop)
local afterNodes = {} --each node that is >= than the link.out
afterNodes[link.out] = true
local readAll = false --has seen every gene without adding any to the list of nodes after link.into
while not readAll do --loop until each gene has been read and used
readAll = true
for i,gene in pairs(genes) do --loop through the list of genes
if afterNodes[gene.into] == true and afterNodes[gene.out] == nil then --if new connection found
afterNodes[gene.out] = true
readAll = false
if gene.out == link.into then --connection found
return -1
end
end
end
end
return 1 --Has not found that link.into >= link.out so it is a fine direction
end
function linkMutate(genome, which, force) --Create a random new gene
local genes = genome.genes[which]
--create two random neurons
local neuronInto
if force == 0 then --only input box
neuronInto = math.random(1,BoxSize)
elseif force == 1 then --only bottom row
neuronInto = BoxSize + math.random(1,3+#InitialOscillations)
elseif force == 2 then --non-input
neuronInto = randomNeuron(genes, true, true)
if neuronInto == 0 then return end --no valid non-input nodes
else --anything
neuronInto = randomNeuron(genes, false, true)
end
local neuronOut = randomNeuron(genes, true, false)
--Create a new link
local newLink = newGene()
newLink.into = neuronInto
newLink.out = neuronOut
if neuronInto == neuronOut then return end --must not be the same neuron
connectionStatus = linkDirection(genes,newLink) --find current status of that link
if connectionStatus == 0 then return end --if link already exists then return
--opposite direction
if connectionStatus == -1 then
newLink.out = neuronInto
newLink.into = neuronOut
end
--create new innovation value and weight
newLink.innovation = pool.innovation
pool.innovation = pool.innovation + 1
newLink.weight = math.random()*4-2
newLink.delay = 0
if math.random()>0.85 then
newLink.delay = math.min(5,math.ceil(math.log(math.random())/math.log(0.5)))
end
--add to the genes
table.insert(genes,newLink)
end
function nodeMutate(genome, which) --Split a random gene and put a neuron in the middle
local genes = genome.genes[which]
if #genes == 0 then return end --cannot make a node with no genes
local gene = genes[math.random(1,#genes)] --pick a random gene
--create the new genes
local geneI = newGene()
local geneO = newGene()
geneI.into = gene.into
geneI.out = genome.maxNeuron + 1
geneO.into = genome.maxNeuron + 1
geneO.out = gene.out
--set innovation
geneI.innovation = pool.innovation
geneO.innovation = pool.innovation + 1
pool.innovation = pool.innovation + 2
--set weights
geneI.weight = math.sqrt(math.abs(gene.weight))
if math.random() < 0.5 then
geneI.weight = -geneI.weight --half the time first one is negative
geneI.delay = gene.delay
end
if geneI.weight == 0 then
geneI.weight = 0.001
end
geneO.weight = gene.weight / geneI.weight --they should multiply to original weight
geneO.delay = gene.delay - geneI.delay
genome.maxNeuron = genome.maxNeuron + 1
gene.enabled = false --disable old gene
--insert the new genes
table.insert(genes,geneI)
table.insert(genes,geneO)
end
function enableDisableMutate(genome, which, enable) --changes a random gene to enabled or disabled
local candidates = {} --list of genes that would be changed by this mutation
for _,gene in pairs(genome.genes[which]) do
if gene.enabled == not enable then --only insert if the current enabling is different from what we will set it to
table.insert(candidates, gene)
end
end
if #candidates == 0 then return end --if no valid genes then do nothing
local gene = candidates[math.random(1,#candidates)] --pick random from candidates
gene.enabled = enable --enable that gene
end
function oscillationMutate(genome, which) --change an oscillation node timer
i = math.random(1,#InitialOscillations) --pick a random oscillation to change
genome.oscillations[which][i] = genome.oscillations[which][i] + math.random(-10,10) --change by random amount
if genome.oscillations[which][i]<0 then --no negative oscillation values
genome.oscillations[which][i] = 0
end
end
function nswitchMutate(genome) --change the position of a network switch
which = #genome.networkswitch --most of the time pick the most recent switch
if math.random() < 0.15 then
which = math.random(1,#genome.networkswitch) --occasionally pick a random other one
end
genome.networkswitch[which] = genome.networkswitch[which] + math.random(-250,250) --change by random amount
local netlimit = 0
for i,species in ipairs(pool.species) do
if species.maxRightmost > netlimit then
netlimit = species.maxRightmost
end
end
if genome.networkswitch[which] < 0 or genome.networkswitch[which] > netlimit then --if out of range of where genome can reach
table.remove(genome.networkswitch) --remove the latest network
table.remove(genome.genes)
end
table.sort(genome.networkswitch, function(a,b) --make sure network switches are in order through the level
return (a < b)
end)
end
function weightsMutate(genome, which) --change the weights of genes around a node
local genes = genome.genes[which]
local node = randomNeuron(genes,false,false) --pick a random neuron to change weights in the area of
local step = genome.mutationRates["step"]
for i,gene in pairs(genes) do
if gene.into == node or gene.out == node then --if either end
if math.random() < WeightPerturbChance then --modify the weight slightly
gene.weight = gene.weight + (math.random()-0.5) * step * 2
elseif math.random() < WeightResetChance then --reset weight entirely
gene.weight = math.random()*4 - 2
end
end
end
end
MutationFunctions = {} --each mutation function, sorted by name
MutationFunctions.linkInputBox = function(genome, which) return linkMutate(genome, which, 0) end
MutationFunctions.linkBottomRow = function(genome, which) return linkMutate(genome, which, 1) end
MutationFunctions.linkLayers = function(genome, which) return linkMutate(genome, which, 2) end
MutationFunctions.linkRandom = function(genome, which) return linkMutate(genome, which, 3) end
MutationFunctions.node = nodeMutate
MutationFunctions.enable = function(genome, which) return enableDisableMutate(genome, which, true) end
MutationFunctions.disable = function(genome, which) return enableDisableMutate(genome, which, false) end
MutationFunctions.oscillation = oscillationMutate
MutationFunctions.nswitch = function(genome, which) if which>1 then nswitchMutate(genome,which-1) end end
MutationFunctions.weights = weightsMutate
function mutate(genome, which) --Mutates a genome's network based on all it's mutation rates
for name,rate in pairs(genome.mutationRates) do --For each mutation type
local p = rate --Number of times to do that mutation type
if MutationFunctions[name] ~= nil then --is a mutation and not another rate like STEP
while p > 0 do --Loop until all mutations are done
if math.random() < p then --when p > 1 always do it, if it is a fraction then it is random
if genome.genes[which] ~= nil then --if network exists
MutationFunctions[name](genome,which) --call that rate
end
end
p = p - 1 --reduce p
end
end
end
end
function crossover(g1,g2)
if g2.fitstate.fitness > g1.fitstate.fitness then
g2, g1 = g1, g2 --switch so that g1 is always the greater fitness
end
local numnets = math.min(#g1.genes,#g2.genes) --take the smaller # of networks
local child = newGenome(numnets)
for n=1,numnets do --for each network to be calculated
local i2 = {} --map from gene2 innovations to gene innovations
for i,gene in ipairs(g2.genes[n]) do
i2[gene.innovation] = gene
end
local i1 = {} --map from gene1 innovations to gene innovations
for i,gene in ipairs(g1.genes[n]) do
i1[gene.innovation] = gene
end
for i=1,#g1.oscillations[n] do
child.oscillations[n][i] = g1.oscillations[n][i] --set oscillations to g1's oscillations
end
for i,gene1 in ipairs(g1.genes[n]) do --some version of every gene from best genome
local gene2 = i2[gene1.innovation] --use innovations2 to find the copied gene in g1
if gene2 ~= nil and math.random(1,2) == 1 and gene2.enabled then
table.insert(child.genes[n], copyGene(gene2)) --if the copied gene exists pick randomly
else
table.insert(child.genes[n], copyGene(gene1)) --otherwise take the version from the best genome
end
end
if n > 1 then
child.networkswitch[n-1] = g1.networkswitch[n-1]
end
for i,gene in ipairs(g2.genes[n]) do
if i1[gene.innovation] == nil and math.random() < 0.01 then --low chance for every gene in g2 only
table.insert(child.genes[n], copyGene(gene)) --add those genes to the child as well
end
end
end
child.maxNeuron = math.max(g1.maxNeuron,g2.maxNeuron)
return child
end
function rankGlobally() --Give each species a global rank, 1 is lowest Population is highest
local globalRanks = {}
for s = 1,#pool.species do
local species = pool.species[s]
for g = 1,#species.genomes do
table.insert(globalRanks, species.genomes[g]) --Insert every genome into the table
end
end
table.sort(globalRanks, function (a,b) --Sort based on fitness
return (a.fitstate.fitness < b.fitstate.fitness)
end)
for g=1,#globalRanks do --Put position of each genome into that genome's data
globalRanks[g].globalRank = g
end
end
function calculatePoolStats() --Calculate avg and avg deviation of max fitness
local total = 0 --Average of each species max fitness
for i,species in ipairs(pool.species) do
total = total + species.maxFitness
end
pool.averagemaxfitness = total / #pool.species
local deviationtotal = 0 --Average distance between max fit and avg max fit
for i,species in ipairs(pool.species) do
deviationtotal = deviationtotal + math.abs(species.maxFitness - pool.averagemaxfitness)
end
pool.avgDeviation = deviationtotal / #pool.species
end
function calculateFitnessRank(species) --Returns a number for each species showing how well that species did
local totalRank = 0
for g=1,#species.genomes do --Total rank is equal to the average of the ranks of each genome
local genome = species.genomes[g]
totalRank = totalRank + genome.globalRank
end
totalRank = totalRank / #species.genomes
local multiplier = 1.0 --multiplier based on how many avg deviations from the mean the top fitness is
if species.maxFitness < pool.averagemaxfitness - (pool.avgDeviation *2) then
multiplier = 0.25
elseif species.maxFitness < pool.averagemaxfitness - pool.avgDeviation then
multiplier = 0.4
elseif species.maxFitness > pool.averagemaxfitness + (pool.avgDeviation *3) then
multiplier = 2.0
elseif species.maxFitness > pool.averagemaxfitness + (pool.avgDeviation *2) then
multiplier = 1.5
end
--Penalty for having multiple networks, to cancel out the benefit of consistency that they give
local netMulti = math.pow(NetworkPenaltyOuter*math.pow(NetworkPenaltyInner,#species.genomes[1].networkswitch),#species.genomes[1].networkswitch)
species.fitnessRank = totalRank * multiplier * netMulti
end
function totalPoolRank() --Sums each species fitness rank
local total = 0
for i,species in ipairs(pool.species) do
total = total + species.fitnessRank
end
return total
end
function cullSpecies(cutToOne) --Cuts down each species, either to a percent based on bottleneck, or only to it's top genome.
for i,species in ipairs(pool.species) do
table.sort(species.genomes, function (a,b) --sorts the genomes by how well they did
return (a.fitstate.fitness > b.fitstate.fitness)
end)
local percent = 0.5 --calculate percent
local minpercent = math.max(0.15,0.5-0.1*pool.bottleneckloops) --min and max of oscillation
local maxpercent = math.min(0.85,0.5+0.1*pool.bottleneckloops)
local length = math.min(10,(5 + 2*pool.bottleneckloops)) --length used in pop oscillation
local totallength = length*3 + TimeBeforePopOscil --total length of oscillation
if pool.bottleneck < totallength/2 then --if in first half go up to max
percent = minpercent + (0.1+maxpercent-minpercent)*(pool.bottleneck*2/totallength)
else --if in second half go down to min
percent = 0.1+maxpercent - (0.2+maxpercent-minpercent)*(pool.bottleneck*2/totallength-1)
end
local remaining = math.ceil(#species.genomes*percent) --number that remain
if cutToOne or remaining < 1 then --always keep at least 1
remaining = 1
end
while #species.genomes > remaining do --remove all that do not survive
table.remove(species.genomes)
end
end
end
function breedChild(species) --makes an offspring from a species
local child = {}
if math.random() < CrossoverChance then --chance to cross over two genomes or just copy one
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 -- mutate usually at the latest network
if which > 1 and species.maxRightmost > 0 then --if there are multiple networks possibly modify one before most recent based on distance past that
local mutatePrevNetworkChance = 0.5 + (child.networkswitch[#child.networkswitch]-species.maxRightmost)/500
if math.random() < mutatePrevNetworkChance then
which = #child.genes - 1
end
end
mutate(child,which) --mutate the child in that network
return child
end
function removeStaleSpecies() --update staleness and remove stale species
local survived = {} --species that survive
for i,species in ipairs(pool.species) do
species.staleness = species.staleness + 1
if species.staleness < MaxStaleness or species.maxFitness >= pool.secondFitness then --non stale species and top 2 species survive
table.insert(survived,species)
end
end
pool.species = survived
end
function removeWeakSpecies()
local survived = {} --species that survive
local sum = totalPoolRank()
for s = 1,#pool.species do
local species = pool.species[s]
local breed = math.floor(species.fitnessRank / sum * pool.population)
if breed+1 >= 999/pool.population or species.fitnessRank > sum / #pool.species then
table.insert(survived, species)
end
end
pool.species = survived
end
function replaceNetwork(g1,g2) --replaces the most recent network of g1 with a random network of g2
local child = copyGenome(g1) --mostly a copy of genome 1
child.genes[#child.genes] = {} --clear out the most recent network
which = math.random(1,#g2.genes) --pick a random network to copy from which
for i,gene in ipairs(g2.genes[which]) do --copy each gene
table.insert(child.genes[#child.genes], copyGene(gene))
end
return child
end
function writescene(scene)
local f = io.open('currentscene.txt','w')
if scene == 1 then
f:write("1")
elseif scene == 2 then
f:write("2")
else
f:write("3")
end
f:close()
end
function writetable(file,tbl) --writes a string of a table to a file
function tablestring(a)
if type(a)=='number' then
local s = string.gsub(tostring(a),",",".")
file:write(s)
elseif type(a)=='boolean' then
file:write(tostring(a))
elseif type(a)=='string' then
file:write('"')
for i=1,string.len(a) do
local c = string.sub(a,i,i)
if c=="\n" then
file:write("\\n")
else
local s = string.gsub(c,'\\','\\\\')
file:write(s)
end
end
file:write('"')
else
file:write('{')
for key,value in pairs(a) do
file:write('[')
tablestring(key)
file:write(']=')
tablestring(value)
file:write(',')
end
file:write('}')
end
end
tablestring(tbl)
end
savgx={0}
function searchstaff()
getPositions()
local marioScreenX = marioScreenX
local koopalingDefeated = koopalingDefeated
local cont = {}
cont['B'] = true
cont['down'] = false
if marioScreenX < 122 then
cont['right'] = true
else
cont['left'] = true
end
if marioScreenX - savgx[1] < 5 then
cont['A'] = true
table.insert(savgx,0)
else
cont['A'] = false
end
table.insert(savgx,marioScreenX)
if #savgx > 120 then
table.remove(savgx,1)
end
joypad.set(Player,cont)
coroutine.yield()
end
function file_exists(name)
local f=io.open(name,"r")
if f~=nil then io.close(f) return true else return false end
end
function saveGenome(name,winner) --saves a species containing the winning genome to a file
local lvlf=io.open("level.txt","r")
local lvl = lvlf:read("*line")
lvlf:close()
local levelname = lvl --name of level in the file name
local file = io.open("backups"..dirsep..levelname..dirsep..name..".lua","w")
spec = pool.species[pool.currentSpecies]
genome = spec.genomes[pool.currentGenome]
genome.nick = spec.nick
genome.gen = pool.generation
genome.s = pool.currentSpecies
genome.g = pool.currentGenome
print("backups"..dirsep..levelname..dirsep..name..".lua")
if not winner then
if boss then
file:write("boss=true\nloadedgenome=")
else
file:write("boss=false\nloadedgenome=")
end
else
file:write("loadedgenome=")
end
writetable(file,genome)
file:close()
if winner then
file = io.open("backups"..dirsep.."winners.txt","a")
file:write("backups"..dirsep..levelname..dirsep..name..".lua\n")
file:close()
else
table.insert(pool.breakthroughfiles,"backups"..dirsep..levelname..dirsep..name..".lua")
end
end
function savePool(filename) --saves the pool into a file
local file = io.open(filename,"w")
file:write("pool=")
writetable(file,pool)
file:close()
end
function loadPool(filename) --loads the pool from a file
dofile(filename)
ProgramStartTime = ProgramStartTime - pool.realTime
end
maxNewGenProgress = -1
function newgenProgress(progress) --draws a progress bar for new gen calculation
if math.floor(progress)>maxNewGenProgress then
maxNewGenProgress=math.floor(progress)
text = "Calculating Generation"
white = toRGBA(0xFFFFFFFF)
blue = toRGBA(0xFF003FFF)
black = toRGBA(0xFF000000)
gui.drawbox(38,98,218,122,black,blue)
drawtext.draw(text,40,100,white,black)
prog = "["
for i=1,progress do prog=prog.."#" end
for i=progress,19 do prog=prog.." " end
prog=prog.."]"
drawtext.draw(prog,40,112,blue,black)
coroutine.yield()
end
end
function newGeneration() --runs the evolutionary algorithms to advance a generation
local mfloor = math.floor
maxNewGenProgress = -1
newgenProgress(0)
autoturbo()
generationTime = pool.realTime
os.remove("backups"..dirsep..levelname..dirsep.."gen"..(pool.generation+1)..".lua")
savePool("backups"..dirsep..levelname..dirsep.."gen"..pool.generation..".lua")
local length = math.min(10,(5 + 2*pool.bottleneckloops)) --Length used for the population oscillation
local targetPop = math.min(900,500 + 100*pool.bottleneckloops) --population to rise to
local targetPopLower = math.min(300,600 - targetPop/2) --population to fall back down to
local currentPopLower = math.min(300,targetPopLower+50) --population starting at
if pool.bottleneck <= TimeBeforePopOscil and pool.bottleneckloops == 0 then --decrease very rapidly after a bottleneck reset
pool.population = math.max(math.floor(pool.population * 0.8),300)
end
if pool.bottleneck > TimeBeforePopOscil then --perform the oscillation
if pool.bottleneck < TimeBeforePopOscil + length then --increase up
pool.population = currentPopLower + math.floor((targetPop - currentPopLower)/length*(pool.bottleneck-TimeBeforePopOscil))
elseif pool.bottleneck == TimeBeforePopOscil + length then --peak
pool.population = targetPop
elseif pool.bottleneck <= TimeBeforePopOscil + length*3 then --go back down
pool.population = targetPopLower + math.floor((targetPop - targetPopLower)/length/2*(TimeBeforePopOscil+length*3-pool.bottleneck))
end
if pool.bottleneck == TimeBeforePopOscil + length*3 then --go to next loop
pool.bottleneck = 0
pool.bottleneckloops = pool.bottleneckloops + 1
end
end
newgenProgress(1)
cullSpecies(false) --cut down each species
newgenProgress(2)
rankGlobally() --rank all genomes
newgenProgress(3)
removeStaleSpecies() --remove stale species
newgenProgress(4)
calculatePoolStats() --find pool maxes avg/deviation
newgenProgress(5)
for i,species in ipairs(pool.species) do --find the fitness rank for each species
calculateFitnessRank(species)
end
newgenProgress(6)
removeWeakSpecies() --remove species without a good fitness rank
newgenProgress(7)
local rankSum = totalPoolRank() --used to see how good a fitness rank is in comparison to everything
local children = {} --new genomes that will be added this generation
for i,species in ipairs(pool.species) do --generate children for each species
local breed = mfloor(species.fitnessRank / rankSum * pool.population) - 1 --num of children to generate
for j=1,breed do
table.insert(children, breedChild(species))
end
newgenProgress(7+3*i/#pool.species)
end
newgenProgress(10)
local interbreedchance = BaseInterbreedChance --base interbreed chance
if pool.generation > 5 then --do not reduce interbreed at very beginning, we want lots of it in first 5 gens
--reduce interbreed when there are lots of species with low average size
interbreedchance = interbreedchance * math.pow(1/InterbreedCutoff-math.min(1/InterbreedCutoff,#pool.species/pool.population),InterbreedDegree)
end
local replacechance = interbreedchance/3 --replacements rarer than interbreeds
local minnetwork = 10000 --minimum number of networks a species eligible for a NS has
local netspecies = {} --list of species eligible for a NS with min networks
for i,species in ipairs(pool.species) do --apply interbreed chance to each species
if math.random() < interbreedchance then
--pick which species to breed with, proportional to fitness rank
local ibcheck = math.random() * rankSum
for j,species2 in ipairs(pool.species) do
ibcheck = ibcheck - species2.fitnessRank --reduce by the fitness rank
if ibcheck < 0 then --the higher it is reduced by greater chance it goes below 0 this time
table.insert(children, crossover(species.genomes[1],species2.genomes[1])) --interbreed
break
end
end
end
if math.random() < replacechance then
--pick which species to get network from
local species2 = pool.species[math.random(1,#pool.species)]
table.insert(children, replaceNetwork(species.genomes[1],species2.genomes[1])) --replace network
end
local numnet = #species.genomes[1].genes --number of networks
if math.min(numnet,3) <= pool.bottleneckloops and numnet <= minnetwork and species.maxFitness + 150 > pool.maxFitness then
if numnet < minnetwork then --even smaller network found, clear old array
minnetwork = numnet
netspecies = {}
end
table.insert(netspecies,species)
end
newgenProgress(10+3*i/#pool.species)
end
if #netspecies > 0 then --at least one species eligible for a network switch
local species = netspecies[math.random(1,#netspecies)] --pick random eligible species
local child = copyGenome(species.genomes[1]) --make a copy of best genome
local switchloc = species.maxRightmost - 150 - 150*math.random() --pick a switch location
table.insert(child.networkswitch,switchloc)
table.insert(child.oscillations,{})
for i=1,#InitialOscillations do
child.oscillations[#child.oscillations][i] = InitialOscillations[i]
end
table.insert(child.genes,{})
table.insert(children,child) --add child
mutate(child,#child.genes)
mutate(child,#child.genes)
table.sort(child.networkswitch, function(a,b) --make sure network splits are in order
return (a < b)
end)
end
newgenProgress(13)
while #children+#pool.species < pool.population do --fill from random species
table.insert(children, breedChild(pool.species[math.random(1, #pool.species)]))
end
newgenProgress(14)
cullSpecies(true) --remove all but the best of each species
newgenProgress(15)
for i,child in ipairs(children) do --add all children to species
addToSpecies(child)
newgenProgress(15+3*i/#children)
end
newgenProgress(18)
for i,species in ipairs(pool.species) do --limit size of species
local limit = pool.population/12 --cap on the species size
if species.maxFitness >= pool.secondFitness then --if top two then larger cap
limit = pool.population/7
end
while #species.genomes > limit do --remove
table.remove(species.genomes)
end
end
pool.generation = pool.generation + 1
pool.bottleneck = pool.bottleneck + 1
newgenProgress(19)
savePool("backups/current.lua")
newgenProgress(20)
end
Ranges = {
['0-3-3-1']= {{yrange={}, xrange={min=360, max=480}, timeout=900}},
["1-2-3-0"]= {{yrange={}, xrange={min=1173, max=1300}, coeffs={x=1,y=2,c=0}}, {yrange={}, xrange={min=1300, max=1303}, resetoffset=true}, {yrange={min=10, max=95}, xrange={min=1281, max=1450}, coeffs={x=0,y=0,c=0}, P=0},{yrange={min=10, max=36}, xrange={min=1181, max=1290}, coeffs={x=0,y=0,c=0}, P=0}},
["1-6-2-0"]= {{yrange={}, xrange={min=2167, max=2300}, timeout=900}, {yrange={min=80,max=96}, xrange={min=2230, max=2249}, coeffs={x=1,y=-1,c=700}},{yrange={min=96,max=112}, xrange={min=2220, max=2279}, coeffs={x=1,y=-1,c=600}},{yrange={min=112,max=128}, xrange={min=2230, max=2249}, coeffs={x=1,y=-1,c=500}},{yrange={min=128,max=144}, xrange={min=2230, max=2249}, coeffs={x=1,y=-1,c=400}}, {yrange={min=144,max=160}, xrange={min=2256,max=2272},coeffs={x=1,y=-1,c=250}},{yrange={}, xrange={min=2272,max=2300},coeffs={x=0,y=0,c=0}},{yrange={min=170}, xrange={min=2200,max=2300},coeffs={x=0,y=0,c=0}},{yrange={min=80,max=90}, xrange={min=2274, max=2300}}},
}
function fitness(fitstate) --Returns the distance into the level - the non-time component of fitness
local coeffs={x=1,y=1,c=0} --position base coefficients
local marioX = marioX
local marioY = marioY
local marioYVel = marioYVel
local marioScore = marioScore
local marioScreenX = marioScreenX
local marioPower = marioPower
local boss = boss
local battle = battle
local marioActive = marioActive
local scrollBonus = scrollBonus
local mmax = math.max
local mfloor = math.floor
local mabs = math.abs
local card_x = card_x
local card_y = card_y
local lspecialBonus = specialBonus
local CardDistance = cardDistance
local levelname = levelname
local pStatus = math.sqrt(memory.readbyte(0x03DD))
local ranges = Ranges[levelname .. "-" .. fitstate.area]
--[[local levelstring = "" --string to represent the level and subworld, to index Ranges
if LostLevels == 1 then levelstring = "LL" end
levelstring = levelstring .. currentWorld .. "-" .. currentLevel .. " " .. fitstate.area
local ranges = Ranges[levelstring] ]]
if boss then
basetimeout = roottimeoutboss
else
basetimeout = roottimeout
end
forcebutton = nil
if ranges ~= nil then
for r=1,#ranges do --for each special fitness range
local range = ranges[r]
if (range.xrange.min == nil or marioX > range.xrange.min) and (range.xrange.max == nil or marioX <= range.xrange.max) then --in x range
if (range.yrange.min == nil or marioY >= range.yrange.min) and (range.yrange.max == nil or marioY < range.yrange.max) then --in y range
if range.coeffs ~= nil then
coeffs = range.coeffs --replace default coefficients
end
if range.P ~= nil then
pStatus = pStatus * range.P
end
if range.area ~= nil then
fitstate.area = range.area
end
if range.timeout ~= nil then
basetimeout = range.timeout
else
basetimeout = 120
end
if range.turbo ~= nil then
if range.turbo then
currentTurbo = true
emu.speedmode("turbo")
else
currentTurbo = false
emu.speedmode("normal")
end
end
if range.forcebutton ~= nil then
forcebutton = range.forcebutton
else
forcebutton = nil
end
if range.resetoffset ~= nil then
fitstate.offset = fitstate.rightmost - fitstate.position
end
end
end
end
end
if marioXVel ~= 0 and not (marioYVel > 0 and marioYVel < 70) then
pBonus = pBonus + pStatus/5
end
local distance
if boss or battle then
for i,sprite in ipairs(sprites) do
if sprite.state ~= 0 then
local distx = math.floor((sprite.x - marioX)/16 + 0.5)
distance = 16 - mabs(distx)
end
if distance == nil then
distance = 0
end
end
end
local currentCardDistance = math.sqrt(mabs(card_x-marioX)*mabs(card_y-marioY))/50
if currentCardDistance < CardDistance and not battle and not boss then
CardDistance = currentCardDistance
cardDistance = CardDistance
if CardDistance < 0.4 then
--roottimeout = 600
basetimeout = 600
end
end
if CardDistance == 0 then
CardDistance = 0.1
end
fitstate.position = coeffs.x*marioX + coeffs.y*marioY + coeffs.c --set the position value
if marioActive then
prevPower = marioPower
if not fitstate.lastActive then --update offset
fitstate.area = memory.readbyte(0x03DF)
--print(500/CardDistance)
fitstate.offset = fitstate.rightmost - fitstate.position
--print(fitstate.offset)
--print("adjusted offset: " .. fitstate.offset)
if battle then fitstate.offset = - (marioScore * 3 + distance * 10) end
getTileset()
end
fitstate.lastright = fitstate.position --update lastright when in control of mario
if scrollBonus > 0 and scrollBonus == prevScrollBonus then
fitstate.timeout = fitstate.timeout + 1
--endscroll = true
--print(scrollBonus)
--print(prevScrollBonus)
elseif boss then
if marioScore == prevscore then
fitstate.timeout = fitstate.timeout + 1 --increase timeout
else
fitstate.timeout = 0
end
prevscore = marioScore
elseif scrollBonus > 0 then
fitstate.timeout = 0
endscroll = false
elseif battle then
if marioScore == prevscore then
fitstate.timeout = fitstate.timeout + 1 --increase timeout
else
fitstate.timeout = 0
end
if distance > 9 then
basetimeout = 600
else
basetimeout = 120
end
prevscore = marioScore
prevscreenx = marioScreenX
else
fitstate.timeout = fitstate.timeout + 1 --increase timeout
end
if fitstate.timeout > basetimeout then --if has been idle for a second, start penalizing
fitstate.timepenalty = fitstate.timepenalty + 1
endscroll = true
end
if pipe then
pipe = false
end
--print("scrollBonus: " .. scrollBonus)
--print("prev: " .. prevScrollBonus)
prevScrollBonus = scrollBonus
else
fitstate.position = fitstate.lastright --freeze position when not in control of mario
if prevPower == marioPower then
if scrollBonus >0 and not pipe then
specialBonus = specialBonus + 200
lspecialBonus = specialBonus
--fitstate.fitness = scrollBonus + fitstate.rightmost*0.6 + 150
fitstate.rightmost = fitstate.fitness
scrollBonus = 0
basescroll = 0
memory.writebyte(0x7A0C,0)
--fitstate.rightmost = fitstate.rightmost + fitstate.offset
pipe = true
--pipeflip = true
--print("pipe")
--fitstate.rightmost = fitstate.offset + 0.6*fitstate.rightmost
elseif scrollBonus >0 and pipe then
scrollBonus = 0
basescroll = 0
memory.writebyte(0x7A0C,0)
end
else
marioActive = true
end
end
fitstate.lastActive = marioActive
if not (marioYVel > 0 and marioYVel < 70) and fitstate.position + fitstate.offset > fitstate.rightmost then
fitstate.timeout = mmax(0,fitstate.timeout + (fitstate.rightmost - fitstate.position - fitstate.offset)*2) --decrease timeout
fitstate.rightmost = fitstate.position + fitstate.offset --rightmost is maximum that the position+offset has ever been
--fitstate.savedtimepenalty = fitstate.timepenalty DISABLED FOR AUTOSCROLLER TEST
end
if boss then
fitstate.fitness = marioScore + fitstate.frame + distance
elseif battle then
fitstate.fitness = marioScore * 3 + distance * 10 + fitstate.offset
elseif scrollBonus > 0 then
fitstate.fitness = scrollBonus + fitstate.rightmost*0.6 + pBonus/10
--print("scrollBonus")
-- if lspecialBonus > 0 then
-- print("scrollBonus: " .. scrollBonus)
-- print("marioX: " .. marioX)
-- end
else
fitstate.fitness = fitstate.rightmost - mfloor(fitstate.savedtimepenalty / 2) + WhiteblockBonus + lspecialBonus + 100/CardDistance +pBonus/10 + powerupFitness * math.min(marioPower, 2) --+ marioScore --return fitness
-- if fitstate.offset ~= -104 then
-- print("after pipe fitness: " .. fitstate.fitness)
-- end
--print("right: " .. fitstate.rightmost .. " penalty: " .. mfloor(fitstate.savedtimepenalty / 2) .. " whiteblock: " .. WhiteblockBonus .. " special: " .. lspecialBonus .. " card: " .. 500/CardDistance .. " pBonus: " .. pBonus/10)
end
end
function autoturbo() --Determines the automatic turbo
local pool = pool
if (pool.realTime - pool.lastbreaktime) > 14400 then
if (pool.realTime - generationTime) > 5400 then
TurboMax = TurboMax + 30
autoTurbo = TurboMax
elseif (pool.realTime - generationTime) < 3600 and autoTurbo == TurboMax and autoTurboCounter > 1 then
TurboMax = TurboMax - 50
if TurboMax < 0 then
TurboMax = 0
end
autoTurbo = TurboMax
elseif (pool.realTime - generationTime) < 3600 and autoTurbo == TurboMax then
autoTurboCounter = autoTurboCounter + 1
else
autoTurboCounter = 0
end
end
if (pool.realTime - pool.lastbreaktime) < 43200 then
automaticturboswitch = 0
elseif (pool.realTime - pool.lastbreaktime) > 43200 and automaticturboswitch == 0 then
local increase = math.ceil(pool.maxFitness / 10)
if TurboMax < increase then
TurboMax = increase
autoTurbo = increase
end
automaticturboswitch = 1
elseif (pool.realTime - pool.lastbreaktime) > 86400 and automaticturboswitch == 1 then
local increase = math.ceil(pool.maxFitness / 5)
if TurboMax < increase then
TurboMax = increase
autoTurbo = increase
end
automaticturboswitch = 2
elseif (pool.realTime - pool.lastbreaktime) > 172800 and automaticturboswitch == 2 then
local increase = math.ceil(pool.maxFitness / 3)
if TurboMax < increase then
TurboMax = increase
autoTurbo = increase
end
automaticturboswitch = 3
end
end
function playGenome(genome) --Run a genome through an attempt at the level
local mmax = math.max
local boss = boss
local Replay = Replay
local FramesPerEval = FramesPerEval
if boss then
savestate.load(bosssavestateObj) --load boss savestate
else
savestate.load(savestateObj) --load savestate
end
--local falseload = false
--[[while memory.readbyte(0x0787) == 0 do --wait until the game has fully loaded in mario
coroutine.yield()
falseload = true
end
if falseload then --move a savestate forward so that it doesn't have any load frames
local groundtime = 0 --must not be falling for a certain time to be on ground
while groundtime < 10 do --wait for mario to hit the ground
coroutine.yield()
if memory.readbyte(0x009F) == 0 then
groundtime = groundtime + 1
else
groundtime = 0
end
end
savestate.save(savestateObj)
end]]--
local fitstate = genome.fitstate
fitstate.frame = 0 --frame counter for the run
fitstate.position = 0 --current position
fitstate.rightmost = 0 --max it has gotten to the right
fitstate.offset = 0 --offset to make sure fitness doesn't jump upon room change
fitstate.lastright = 0 --last position before a transition.cancel()
fitstate.lastActive = false --mario's state the previous frame (to check state transitions)
fitstate.fitness = 0 --current fitness score
fitstate.timeout = 0 --time increasing when mario is idle, kills him if it is too much
fitstate.timepenalty = 0 --number of frames mario was too idle, to subtract from his fitness
--fitstate.area = "Level"
--fitstate.lastarea = ""
fitstate.savedtimepenalty = 0 --does not save time penalty until mario moves forward
basescroll = 0
prevScrollBonus = scrollBonus
marioPrevAutoscroll = 0
specialBonus = 0
cardDistance = 9999
pBonus = 0
lockBound = false
getPositions()
specialBonus = - scrollBonus - 500/cardDistance - powerupFitness * math.min(marioPower, 2)
local nsw = 1 --network switch
generateNetwork(genome) --generate the network
local controller = {} --inputs to the controller
getTileset()
fitstate.area = memory.readbyte(0x03DF)
while true do --main game loop
local koopalingDefeated = koopalingDefeated
local marioMusic = marioMusic
local manualControl = keyboardInput()
local ManualInput = ManualInput
local marioX = marioX
local card_x = card_x
local marioScreenY = marioScreenY
local marioYVel = marioYVel
--Get inputs to the network
if fitstate.frame % FPS == 0 and not Replay then
timerOutput()
end
if fitstate.frame % FramesPerEval == 0 then
local inputs = getInputs()
--Find the current network to be using
local nscompare = fitstate.rightmost
nsw = 1
for n=1,#genome.networkswitch do
if nscompare > genome.networkswitch[n] then
nsw = n+1
end
end
--Put outputs to the controller
if marioX > card_x -74 and marioX < card_x -64 then
forcebutton = {right=true,A=false,B=true}
elseif marioX <= card_x-8 and marioX >= card_x -64 then
forcebutton = {right=true,A=true,B=true}
elseif marioX > card_x+8 then
forcebutton = {left=true,right=false}
end
controller = evaluateNetwork(genome.networks[nsw], inputs, genome.oscillations[nsw], fitstate.frame)
else
getPositions()
end
if ManualInput then
joypad.set(Player,manualControl)
else
if fitstate.frame % (FramesPerEval*2) == 0 then
if (marioPower == 3 or marioPower == 5) and controller['A'] then
if memory.readbyte(0x03DD) == 127 then
controller['A'] = false --allows mario to fly with tanooki suit. this type of network would otherwise not be capable of switching that fast
end
end
end
joypad.set(Player,controller)
end
fitness(fitstate) --update the fitness
if pool.generation == 0 and not Replay then
TurboMax = 1
elseif TurboMax == 1 then
TurboMax = 0
end
local turbocompare = mmax(0,fitstate.rightmost)
if CompleteAutoTurbo or (turbocompare >= TurboMin and turbocompare < TurboMax and pool.species[pool.currentSpecies].turbo) then
if not currentTurbo then
currentTurbo = true
emu.speedmode("turbo")
end
else
if currentTurbo then
currentTurbo = false
emu.speedmode("normal")
end
end
--Display the GUI
displayGUI(genome.networks[nsw], fitstate)
--Advance a frame
coroutine.yield() --coroutine.yield()
fitstate.frame = fitstate.frame + 1
--exit if dead or won
if memory.readbyte(0x00EE) == 75 or marioOutOfBound then --if he dies to an enemy or a pit
if Replay then
genome.networks = {}
return false
end
pool.attempts = pool.attempts + 1
pool.deaths = pool.deaths + 1
addToHistogram()
deathCounterOutput()
for frame=1,FramesOfDeathAnimation do
displayGUI(genome.networks[nsw], fitstate)
coroutine.yield() --coroutine.yield()
end
genome.networks = {} --reset networks to save on RAM
pool.totalTime = pool.totalTime + fitstate.frame
turboOutput()
timerOutput()
return false
end
if fitstate.timeout > fitstate.rightmost/3+basetimeout and not ManualInput then --timeout threshold increases throughout level. kill if timeout is enabled and timer is not frozen
genome.networks = {} --reset networks to save on RAM
if Replay then return false end
pool.attempts = pool.attempts + 1
addToHistogram()
deathCounterOutput()
turboOutput()
timerOutput()
pool.totalTime = pool.totalTime + fitstate.frame
return false
end
if memory.readbyte(0x0014) == 1 then --if he beats the level
print("beat level")
genome.fitstate.fitness = genome.fitstate.fitness + 1000
genome.networks = {} --reset networks to save on RAM
if Replay then return true end
pool.totalTime = pool.totalTime + fitstate.frame
addToHistogram()
TurboMin = 0
TurboMax = 0
turboOutput()
battle = false
return true
end
if (marioMusic == 0x50 or marioMusic == 0xB0) and not boss then
print('activate final state')
genome.fitstate.fitness = genome.fitstate.fitness + 1000
genome.networks = {} --reset networks to save on RAM
if Replay then return true end
pool.totalTime = pool.totalTime + fitstate.frame
addToHistogram()
TurboMin = 0
TurboMax = 0
turboOutput()
--print('playboss')
memory.writebyte(0x0715,0) --set score to 0
memory.writebyte(0x0716,0)
memory.writebyte(0x0717,0)
bosssavestateObj = savestate.object(savestateSlotBOSS)
savestate.save(bosssavestateObj)
savestate.persist(bosssavestateObj)
startboss = true
return false
end
if (marioMusic ~= 0x50 and marioMusic ~= 0xB0 and koopalingDefeated == 0) and boss then
genome.fitstate.fitness = genome.fitstate.fitness + 1000
genome.networks = {} --reset networks to save on RAM
for x=1,800 do
coroutine.yield()
end
if Replay then return true end
pool.totalTime = pool.totalTime + fitstate.frame
addToHistogram()
TurboMin = 0
TurboMax = 0
turboOutput()
return true
end
if koopalingDefeated > 0 and boss then
print("second")
-- print(marioMusic)
-- print(koopalingDefeated)
genome.fitstate.fitness = genome.fitstate.fitness + 1000
genome.networks = {} --reset networks to save on RAM
while koopalingDefeated == 1 do
koopalingDefeated = memory.readbyte(0x07BD)
coroutine.yield()
end
while koopalingDefeated == 2 do
koopalingDefeated = memory.readbyte(0x07BD)
searchstaff()
end
while koopalingDefeated > 1 do
koopalingDefeated = memory.readbyte(0x07BD)
coroutine.yield()
end
while kingconvo < 4 do
getPositions()
coroutine.yield()
end
if kingconvo == 4 then
for a=1,60 do
coroutine.yield()
end
joypad.set(Player,{A = true})
coroutine.yield()
joypad.set(Player,{A = false})
end
if Replay then return true end
pool.totalTime = pool.totalTime + fitstate.frame
addToHistogram()
TurboMin = 0
TurboMax = 0
turboOutput()
return true
end
end
end
function playSpecies(species,showBest) --Plays through all members of a species
spindicatorOutput(species)
local startGenome = 2 --which genome to start showing from
local oldBest = 0
if showBest or not species.genomes[1].fitstate.fitness then
startGenome = 1
oldBest = species.maxFitness --used to make sure staleness does not reset every run if showBest is toggled on always
species.maxFitness = 0
species.maxRightmost = 0
end
speciesDataOutput()
for g=startGenome,#species.genomes do --loop through each genome
local genome = species.genomes[g]
pool.currentGenome = g
local won = playGenome(genome) --test the genome
if genome.fitstate.fitness + 30 > pool.maxFitness then --if it has gotten very close to the max
pool.maxCounter = pool.maxCounter + 1
end
if genome.fitstate.fitness > pool.maxFitness then --if the fitness is the new best
if species.gsid ~= pool.bestSpecies then --change the second best if the current best is a different species
pool.secondFitness = pool.maxFitness
end
if genome.fitstate.fitness > pool.maxFitness + MajorBreakFitness or genome.fitstate.fitness > pool.lastMajorBreak + MajorBreakFitness*4 then
--Counts as a major breakthrough
pool.lastMajorBreak = genome.fitstate.fitness
pool.bottleneck = 0
pool.bottleneckloops = 0
pool.lastbreaktime = pool.realTime
end
pool.bestSpecies = species.gsid --update the best species number
pool.maxFitness = genome.fitstate.fitness --update the best fitness
writeBreakthroughOutput()
elseif genome.fitstate.fitness > pool.secondFitness then --if the fitness is the new second best
if species.gsid ~= pool.bestSpecies then --change the second best if this is not the current best species
pool.secondFitness = genome.fitstate.fitness
end
end
--print("end calculation fitness: " .. genome.fitstate.fitness)
if genome.fitstate.fitness > species.maxFitness then --update the species max fitness
species.maxFitness = genome.fitstate.fitness
species.breakthroughX = marioX
species.breakthroughZ = genome.fitstate.area
spindicatorOutput(species)
if genome.fitstate.fitness > oldBest then
species.staleness = 0 --reset the staleness
end
end
if genome.fitstate.rightmost > species.maxRightmost then --update the species max right
species.maxRightmost = genome.fitstate.rightmost
end
if startboss then
saveGenome("winner",true)
startboss = false
PlayBoss()
won = true
end
pool.current = pool.current +1
pool.total = pool.total + genome.fitstate.fitness
pool.average = math.ceil(pool.total/pool.current)
if won then return true end --return true if we won
end
return false --return false otherwise
end
function playGeneration(showBest) --Plays through the entire generation
local NumberWinner = numberWinner
pool.maxCounter = 0
pool.current = 0
pool.total = 0
for s=1,#pool.species do
local species = pool.species[s]
pool.currentSpecies = s
if s > NumberWinner and NumberWinner ~= -1 then
if not currentTurbo then
currentTurbo = true
emu.speedmode("turbo")
end
numberWinner = -1
end
if playSpecies(species,showBest) then
return true
end
end
return false
end
--File Outputs
function deathCounterOutput()
fileDeaths = io.open("deaths.txt","w")
fileDeaths:write(pool.deaths)
fileDeaths:close()
fileAttempts = io.open("attempts.txt","w")
fileAttempts:write(pool.attempts)
fileAttempts:close()
end
function speciesDataOutput()
local species = pool.species[pool.currentSpecies]
speciesdata = "GSID: "..species.gsid.." SMax: "..species.maxFitness.." Stale: "..species.staleness
if species.nick ~= "" then
speciesdata = speciesdata.." Nick: "..species.nick
end
fileSData = io.open("speciesdata.txt","w")
fileSData:write(speciesdata)
fileSData:close()
end
function mapupdate()
io.open("../mapupdate.txt","w"):close()
end
function mapupdateHist()
local f = io.open("../mapupdate.txt","w")
f:write("hist")
f:close()
end
function spindicatorOutput(species)
fileIPos = io.open("spindicatorpos.txt","w")
fileIPos:write(species.breakthroughX.." "..species.breakthroughZ)
fileIPos:close()
pcall(mapupdate)
end
function indicatorOutput()
fileIPos = io.open("indicatorpos.txt","w")
fileIPos:write(pool.breakthroughX.." "..pool.breakthroughZ)
fileIPos:close()
pcall(mapupdate)
end
function addToHistogram()
local i = math.floor(marioX/32)
key = i.." "..pool.species[pool.currentSpecies].genomes[pool.currentGenome].fitstate.area
if not pool.maphistogram[key] then
pool.maphistogram[key] = 0
end
pool.maphistogram[key] = pool.maphistogram[key] + 1
end
function histogramOutput()
fileHistogram = io.open("histogram.txt","w")
for key,count in pairs(pool.maphistogram) do
fileHistogram:write(key.." "..count.."\n")
end
fileHistogram:close()
end
function writeBreakthroughOutput()
local species = pool.species[pool.currentSpecies]
local genome = species.genomes[pool.currentGenome]
info = tostring(pool.generation)
for i=1 ,4-string.len(info) do info=info.." " end
info = info..tostring(pool.currentSpecies)
for i=1,9-string.len(info) do info=info.." " end
info = info..tostring(pool.currentGenome)
for i=1,13-string.len(info) do info=info.." " end
info = info..tostring(species.gsid)
for i=1,18-string.len(info) do info=info.." " end
info = info..tostring(math.floor(genome.fitstate.fitness))
for i=1,23-string.len(info) do info=info.." " end
local seconds = pool.realTime
local minutes = math.floor(seconds/60)
seconds = seconds - minutes*60
local hours = math.floor(minutes/60)
minutes = minutes - hours*60
local days = math.floor(hours/24)
hours = hours - days*24
if pool.realTime < 3600 then
info=info..minutes.."m"..seconds.."s"
elseif pool.realTime < 86400 then
info=info..hours.."h"..minutes.."m"
else
info=info..days.."d"..hours.."h"
end
pool.history=pool.history..info.."\n"
fileFTracker = io.open("fitnesstracker.txt","w")
fileFTracker:write(pool.history)
fileFTracker:close()
pool.breakthroughX = marioX
pool.breakthroughZ = genome.fitstate.area
if boss then
saveGenome("Boss-G"..pool.generation.."s"..pool.currentSpecies.."g"..pool.currentGenome,false)
else
saveGenome("G"..pool.generation.."s"..pool.currentSpecies.."g"..pool.currentGenome,false)
end
indicatorOutput()
end
function levelNameOutput()
getPositions()
fileLevel = io.open("level.txt","w")
fileLevel:write(levelname)
fileLevel:close()
end
function turboOutput()
fileTurbo = io.open("turbo.txt","w")
if TurboMin == 0 then
fileTurbo:write("T="..TurboMax)
else
fileTurbo:write("T="..TurboMin.."\nto "..TurboMax)
end
fileTurbo:close()
end
function twoDigit(num)
str = tostring(num)
if string.len(str) == 1 then
str = "0"..str
end
return str
end
function numToTime(seconds)
local minutes = math.floor(seconds/60)
seconds = seconds - minutes*60
local hours = math.floor(minutes/60)
minutes = minutes - hours*60
local days = math.floor(hours/24)
hours = hours - days*24
return days.."d "..twoDigit(hours)..":"..twoDigit(minutes)..":"..twoDigit(seconds)
end
function timerOutput()
fileRealTime = io.open("realtime.txt","w")
fileGameTime = io.open("gametime.txt","w")
pool.realTime = os.time()-ProgramStartTime
fileRealTime:write(numToTime(pool.realTime))
fileGameTime:write(numToTime(math.floor(pool.totalTime/FPS)))
fileRealTime:close()
fileGameTime:close()
end
function fitnessDataOutput()
rankGlobally()
local fitnesses = {}
local genAvg = 0
local smaxAvg = 0
local minGsid = 1000000
local maxGsid = 0
for s,species in pairs(pool.species) do
for g,genome in pairs(species.genomes) do
fitnesses[genome.globalRank] = genome.fitstate.fitness
genAvg = genAvg + genome.fitstate.fitness
end
smaxAvg = smaxAvg + species.maxFitness
if species.gsid > maxGsid then maxGsid = species.gsid end
if species.gsid < minGsid then minGsid = species.gsid end
end
local genMin = fitnesses[1]
local genBqt = fitnesses[math.floor(#fitnesses/4)]
local genMed = fitnesses[math.floor(#fitnesses/2)]
local genTqt = fitnesses[math.floor(3*#fitnesses/4)]
local genMax = fitnesses[#fitnesses]
local genAvg = genAvg / #fitnesses
local smaxAvg = smaxAvg / #pool.species
local smaxSdv = 0
for s,species in pairs(pool.species) do
smaxSdv = smaxSdv + (species.maxFitness - smaxAvg)*(species.maxFitness - smaxAvg)
end
smaxSdv = math.sqrt(smaxSdv / #pool.species)
local avgSdv = 0
for f,fitness in pairs(fitnesses) do
avgSdv = avgSdv + (fitness-genAvg)*(fitness-genAvg)
end
avgSdv = math.sqrt(avgSdv / #fitnesses)
os.remove("data"..dirsep..levelname..dirsep.."data"..(pool.generation+1)..".drd")
fileDRD = io.open("data"..dirsep..levelname..dirsep.."data"..pool.generation..".drd","w")
fileDOut = io.open("discorddataout.txt","w")
fileDRD:write(#fitnesses.." "..pool.population.." "..#pool.species.." ")
fileDOut:write("**Generation: `"..pool.generation.."`** Target Pop: `"..pool.population.."` Actual Pop: `"..#fitnesses.."` Number of Species: `"..#pool.species.."` ")
fileDRD:write(genMin.." "..genBqt.." "..genMed.." "..genTqt.." "..genMax.." ")
fileDOut:write("Minimum: `"..genMin.."` Median: `"..genMed.."` Maximum: `"..genMax.."` ")
fileDRD:write(genAvg.." "..avgSdv.." "..smaxAvg.." "..smaxSdv.."\n")
fileDOut:write("Average: `"..genAvg.."` Standard Deviation: `"..avgSdv.."` Average Max: `"..smaxAvg.."`\n")
for gsid=minGsid,maxGsid do
for s,species in pairs(pool.species) do
if species.gsid == gsid then
local speciesRanks = {}
for g,genome in pairs(species.genomes) do
table.insert(speciesRanks, genome) --Insert every genome into the table
end
table.sort(speciesRanks, function (a,b) --Sort based on fitness
return (a.fitstate.fitness < b.fitstate.fitness)
end)
local specAvg = 0
local sfitnesses = {}
for g=1,#speciesRanks do --Put position of each genome into that genome's data
sfitnesses[g] = speciesRanks[g].fitstate.fitness
specAvg = specAvg + speciesRanks[g].fitstate.fitness
end
local specMin = sfitnesses[1]
local specBqt = sfitnesses[math.ceil(#sfitnesses/4)]
local specMed = sfitnesses[math.ceil(#sfitnesses/2)]
local specTqt = sfitnesses[math.ceil(3*#sfitnesses/4)]
local specMax = sfitnesses[#sfitnesses]
specAvg = specAvg / #sfitnesses
local specSdv = 0
for f,fitness in pairs(sfitnesses) do
specSdv = specSdv + (fitness-specAvg)*(fitness-specAvg)
end
specSdv = math.sqrt(specSdv / #sfitnesses)
fileDRD:write(species.gsid.." "..#species.genomes.." "..#species.genomes[1].genes.." ")
fileDRD:write(specMin.." "..specBqt.." "..specMed.." "..specTqt.." "..specMax.." "..specAvg.." "..specSdv)
for f,fitness in pairs(sfitnesses) do
fileDRD:write(" "..fitness)
end
fileDRD:write("\n")
if species.nick ~= "" then
fileDOut:write("Nickname: `"..species.nick.."` Maximum: `"..specMax.."` ")
fileDOut:write("Staleness: `"..species.staleness.."` Average: `"..specAvg.."` ")
fileDOut:write("Number of Genomes: `"..#sfitnesses.."` Num Networks: `"..#species.genomes[1].genes.."`\n")
end
end
end
end
fileDRD:close()
fileDOut:close()
end
keyFlag = false
function keyboardInput()
local keyboard = input.get()
if keyboard['N'] and keyFlag == false then --N toggles the network display
DisplayNetwork = not DisplayNetwork
end
if keyboard['R'] and keyFlag == false then --R toggles the sprite hitboxes
DisplaySprites = not DisplaySprites
end
if keyboard['G'] and keyFlag == false then --G toggles the large grid display
DisplayGrid = (DisplayGrid + 1) % 3
end
if keyboard['O'] and keyFlag == false then --O toggles the sprite slots
DisplaySlots = not DisplaySlots
end
if keyboard['A'] and keyFlag == false then --A toggles the top stats bar
DisplayStats = not DisplayStats
end
if keyboard['M'] and keyFlag == false then --M toggles manual vs network input
ManualInput = not ManualInput
end
if keyboard['E'] and keyFlag == false then --E toggles
DisplayRanges = not DisplayRanges
end
if keyboard['C'] and keyFlag == false then --E toggles
DisplayCounters = not DisplayCounters
end
if keyboard['L'] and keyFlag == false then --L loads
--to load put an interrupt that will restart the program and initialize with the previous pool
local interrupt = io.open("interrupt.lua","w")
interrupt:write("restartprog = luigiofilename")
interrupt:close()
local initialize = io.open("initialize.lua","w")
initialize:write("loadPool('backups' .. dirsep .. 'current.lua')")
initialize:close()
end
local controller = {}
controller['A'] = keyboard['F']
controller['B'] = keyboard['D']
controller['up'] = keyboard['up']
controller['down'] = keyboard['down']
controller['left'] = keyboard['left']
controller['right'] = keyboard['right']
if controller["up"] and controller["down"] then
controller["up"] = false
controller["down"] = false
end
if controller["left"] and controller["right"] then
controller["left"] = false
controller["right"] = false
end
local allkeys = {'N','R','G','O','A','M','E','C','L'}
keyFlag = false --Set keyflag to true if any keys were pressed otherwise it is false
for k=1,#allkeys do
if keyboard[allkeys[k]] then
keyFlag = true
end
end
return controller
end
function drawStatsBox(fitstate)
local genimage = {' #### #### ## ## ','##---##----#--##--#','#--####--###---#--#','#--#--#----#------#','#--#--#--###--#---#','##----#----#--##--#',' #### #### ## ## '}
local specimage = {' ##### ##### #### ### ## #### #### ','##----#----##----##---#--#----##----#','#--####--#--#--###--###--#--###--### ','##---##----##----#--###--#----##---##','####--#--## #--###--###--#--######--#','#----##--# #----##---#--#----#----# ',' ##### ## #### ### ## #### #### '}
local gnmimage = {' ##### #### ## ## ##### ###### #### ','##---##----#--##--##---####--#--##----#','#--####--###---#--#--#--#--------#--###','#--#--#----#------#--#--#--#--#--#----#','#--#--#--###--#---#--#--#--#--#--#--###','##----#----#--##--##---##--#--#--#----#',' #### #### ## ## ##### ## ## ## #### '}
local popimage = {' ##### ##### ##### ','#----###---##----##','#--#--#--#--#--#--#','#----##--#--#----##','#--####--#--#--## ','#--# ##---##--# ',' ## ##### ## '}
local fitimage = {' #### ## ###### ## ## #### #### #### ','#----#--#------#--##--#----##----#----#','#--###--###--###---#--#--###--###--### ','#----#--# #--# #------#----##---##---##','#--###--# #--# #--#---#--######--###--#','#--###--# #--# #--##--#----#----#----# ',' ## ## ## #### ####### #### ##### '}
local firstimage = {' ### #### ###### ','#---###----#------#','##--##--### ##--## ','##--###---# #--# ','##--#####--# #--# ','#----#----# #--# ',' #### #### ## '}
local secondimage = {' #### ## ## #### ','##----##--###--#----## ','#--##--#---##--#--#--# ','###--###----#--#--##--#','##--####--#----#--#--# ','#-----##--##---#----## ',' ##### ## ### #### '}
local maxnumimage = {' ##### ##### #### ## ## ','##-#-###--#--####----##--##--#','#-----#--------#--##--#--##--#','##-#-##--#--#--#------##----##','#-----#--#--#--#--##--#--##--#','##-#-##--#--#--#--##--#--##--#',' ##### #######################'}
local avgxbar = {' ####### ','#-------# ',' ####### ','#--# #--# #### ## ###### ','#--###--##----#--#------#',' #--#--# #--###--###--## ',' #---# #----#--# #--# ',' #--#--# #--###--# #--# ','#--###--##--###--# #--# ','#--###--##--###--# #--# ',' ### ### ## ## ## '}
local draw = draw
local pool = pool
local fl = math.floor
if currentTurbo then
gui.drawrect(8,194,254,230,draw.CYAN,draw.WHITE)
gui.text(10, 197, "GEN "..pool.generation .. " Species "..pool.currentSpecies.." Genome "..pool.currentGenome, draw.GRAYSCALE, draw.CYAN)
gui.text(10, 210, "Fitness "..math.max(0,fl(fitstate.fitness)) .. " 1ST "..fl(pool.maxFitness).." 2ND "..fl(pool.secondFitness), draw.GRAYSCALE, draw.CYAN)
gui.text(10, 222, "#Max "..pool.maxCounter .. " XFit "..pool.average, draw.GRAYSCALE, draw.CYAN)
else
-- draw.rect(8,194,32,1,draw.BLACK)
-- draw.rect(9,195,246,36,draw.WHITE)
-- draw.rect(10,196,244,34,draw.CYAN)
gui.drawrect(8,194,254,230,draw.CYAN,draw.WHITE)
gui.drawrect(230,204,251,207,draw.GRAYSCALE,draw.CYAN)
--draw.rect(230,205,21,2,draw.BLACK)
draw.image(10,197,genimage,draw.GRAYSCALE)
draw.number(31,197,pool.generation,3)
draw.image(65,197,specimage,draw.GRAYSCALE)
draw.number(104,197,pool.currentSpecies,3)
draw.image(138,197,gnmimage,draw.GRAYSCALE)
draw.number(179,197,pool.currentGenome,3)
--draw.image(176,198,brackets,draw.GRAYSCALE)
--draw.number(182,198,19,2)
draw.image(207,202,popimage,draw.GRAYSCALE)
draw.number(229,197,pool.current,3)
draw.number(229,208,pool.population,3)
draw.image(10,210,fitimage,draw.GRAYSCALE)
draw.number(50,210,math.max(0,fitstate.fitness),4)
draw.image(88,210,firstimage,draw.GRAYSCALE)
draw.number(108,210,pool.maxFitness,4)
draw.image(147,210,secondimage,draw.GRAYSCALE)
draw.number(171,210,pool.secondFitness,4)
draw.image(10,222,maxnumimage,draw.GRAYSCALE)
draw.number(43,222,pool.maxCounter,3)
draw.image(81,218,avgxbar,draw.GRAYSCALE)
draw.number(109,222,pool.average,4)
end
end
arrowimage = {'### ','#--## ','#----## ','#------#','#----## ','#--## ','### '}
pmeterimage = {' ############# ','##-----------##','#----#####----#','#----#---#----#','#----####-----#','##---#-------##',' ############# '}
reversePalette = {[' ']=0,['#']=draw.WHITE,['-']=draw.BLACK}
function drawPMeter(x,y)
local pStatus = memory.readbyte(0x03DD)
local currentPalette = draw.GRAYSCALE
for i=1,6 do
if pStatus < math.pow(2,i)-1 then
currentPalette = reversePalette
end
draw.image(x-8+i*12,y,arrowimage,currentPalette)
end
if pStatus < 127 then currentPalette = reversePalette end
draw.image(x+75,y,pmeterimage,currentPalette)
end
clockimage = {' #### ',' #-#--# ','#--#---#','#--###-#','#------#',' #----# ',' #### '}
function drawClock(x,y)
local timeLeft = memory.readbyte(0x05EE)*100+memory.readbyte(0x05EF)*10+memory.readbyte(0x05F0)
draw.image(x,y,clockimage,draw.GRAYSCALE)
draw.number(x+8,y,timeLeft,3)
end
function drawNeuron(network,neuron)
if neuron.x then
inside = draw.tcolor(0x01010100*math.floor(127*(neuron.value+1))+0xE6)
border = draw.tcolor(0x01010100*math.floor(127*(1-neuron.value))+0xE6)
if neuron.blocked then inside = draw.tcolor(0xFF3F00E6) end
if neuron.value ~= 0 then
draw.rect(neuron.x,neuron.y,5,5,border)
draw.rect(neuron.x+1,neuron.y+1,3,3,inside)
else
draw.rect(neuron.x,neuron.y,5,5,draw.tcolor(0x7F7FCFC7))
end
if neuron.incoming then
for g,gene in pairs(neuron.incoming) do
if gene.enabled then
local n1 = network.neurons[gene.into]
local layerdiff = neuron.layer - n1.layer
--Green or red for positive or negative weight
local color = 0x3FFF00
if gene.weight < 0 then
color = 0xFF3F00
end
local opacity = 0xCF
if gene.into > BoxSize and gene.into <= Inputs then --fade or remove if bottom-row neuron
if #network.genes > 100 then
opacity = 0x00
elseif #network.genes > 50 then
opacity = 0x5F
end
end
if n1.value == 0 then --fade or remove if not transmitting a value
if #network.genes > 50 then
opacity = 0x00
else
opacity = 0x5F
end
end
--draw the genome
if n1.x and n1.layer > 0 then
gui.drawline(n1.x+4,n1.y+2,neuron.x,neuron.y+2, draw.tcolor(256*color+opacity))
end
end
end
end
end
end
charAimage = {' #### ','##--##','#-##-#','#----#','#-##-#','#-##-#',' #### '}
charBimage = {' #### ','#---##','#-##-#','#---##','#-##-#','#---##',' #### '}
charUimage = {' #### ','#-##-#','#-##-#','#-##-#','#-##-#','##--##',' #### '}
charDimage = {' #### ','#---##','#-##-#','#-##-#','#-##-#','#---##',' #### '}
charLimage = {' #### ','#-####','#-####','#-####','#-####','#----#',' #### '}
charRimage = {' #### ','#---##','#-##-#','#---##','#-#-##','#-##-#',' #### '}
buttonImages = {charAimage,charBimage,charUimage,charDimage,charLimage,charRimage}
function drawNetwork(network)
local neurons = {} --Array that will contain the position and value of each displayed neuron
local i = 1
local ButtonNumbers = ButtonNumbers
local buttonImages = buttonImages
for dy=-BoxRadius,BoxRadius do --Add the input box neurons
for dx=-BoxRadius,BoxRadius do
network.neurons[i].x = 8+5*(dx+BoxRadius)
network.neurons[i].y = 20+5*(dy+BoxRadius)
i = i + 1
end
end
local botRowSpacing = 10 --Number of pixels between oscillating nodes
local botRowSize = (3 + #InitialOscillations)*2 - 1
if botRowSize > BoxWidth then
botRowSpacing = 5 --If bottom row can't fit then have no spacing
end
for j=0,#InitialOscillations do --Add the bias and oscillation neurons
network.neurons[i].x = 3+BoxWidth*5-botRowSpacing*j
network.neurons[i].y = 25+BoxWidth*5
i = i + 1
end
for j=0,1 do --Add the bias and oscillation neurons
network.neurons[i].x = 8+botRowSpacing*j
network.neurons[i].y = 25+BoxWidth*5
i = i + 1
end
for l=1,#network.layers do --Draw each layer in the NN
local layer = network.layers[l]
for n=1,#layer do
if l > 1 or layer[n] > Inputs then --display only non-inputs in layer 1
network.neurons[layer[n]].x = math.ceil(13+BoxWidth*5+(220-BoxWidth*5)*((l-1) / (#network.layers)))
network.neurons[layer[n]].y = math.ceil(20+(BoxWidth+1)*5*(n / (#layer+1)))
end
end
end
--When to block opposite directional inputs
local blockUD = network.neurons[-3] ~= nil and network.neurons[-4] ~= nil and network.neurons[-3].value>0 and network.neurons[-4].value>0
local blockLR = network.neurons[-5] ~= nil and network.neurons[-6] ~= nil and network.neurons[-5].value>0 and network.neurons[-6].value>0
local fb = {}
if forcebutton ~= nil then
for k,v in pairs(forcebutton) do
fb[ButtonNumbers[k]] = v
end
end
local neuron = {}
for o=1,6 do --outputs
neuron = {}
neuron.x = 238
neuron.y = 25+(BoxWidth-1)*(o-1)
neuron.value = -1
local onode = network.neurons[-o]
if onode and onode.value > 0 then neuron.value = 1 end
if blockUD and (o == 3 or o == 4) then neuron.blocked = true end
if blockLR and (o == 5 or o == 6) then neuron.blocked = true end
if fb[o] ~= nil then
if fb[0] then
neuron.value = 1
else
neuron.value = -1
end
end
drawNeuron(network,neuron)
draw.image(246,24+(BoxWidth-1)*(o-1),buttonImages[o],draw.GRAYSCALE)
if onode ~= nil and network.neurons[o].layer ~= 0 and onode.x then
if neuron.value == 0 then --draw line between output and ouput box.
gui.drawline(onode.x+4,onode.y+2,neuron.x,neuron.y+2,draw.tcolor(0x3FFF005F)) --fade if not sending anything
else
gui.drawline(onode.x+4,onode.y+2,neuron.x,neuron.y+2,draw.tcolor(0x3FFF00CF))
end
end
end
for n,neuron in pairs(network.neurons) do
drawNeuron(network,neuron)
end
end
function drawRanges()
local levelstring = levelname
local ranges = Ranges[levelstring.."-"..fitstate.area]
local marioX = marioX
local marioScreenX = marioScreenX
local marioY = marioY
local marioScreenY = marioScreenY
if ranges ~= nil then
for r=1,#ranges do
--default values for parts without ranges
local minx = 0
local maxx = 65536
local miny = 0
local maxy = 240
local disp = true
local range = ranges[r]
--if range limits exist set the limits to those
if range.xrange.min ~= nil then minx = range.xrange.min end
if range.xrange.max ~= nil then maxx = range.xrange.max end
if range.yrange.min ~= nil then miny = range.yrange.min end
if range.yrange.max ~= nil then maxy = range.yrange.max end
--set color based on the direction of fitness increase
local rgb = 0x003FFF
textcolor = toRGBA(0xFFFFFFFF)
if range.area ~= nil then
rgb=0xFF00FF
elseif range.coeffs ~= nil then
if range.coeffs.x == 1 and range.coeffs.y == 1 and range.coeffs.c ~=0 then
rgb=0xE5FF00
textcolor = toRGBA(0xFF000000)
end
if range.coeffs.x >0 and range.coeffs.y==0 then
rgb=0X0FFF1B
end
if range.coeffs.y > 0 and range.coeffs.x <1 then
rgb=0x0F37FF
end
if range.coeffs.x == 0 and range.coeffs.y == 0 then
rgb=0xFF3F00
end
if range.coeffs.y <0 then
rgb=0xFFBF00
end
if range.coeffs.x < 0 then
rgb=0xBF7F00
end
elseif range.timeout ~= nil then
rgb=0x42AAFF
end
if rgb~=0x003FFF then
color = toRGBA(0xFF000000 + rgb)
--draw the box
if marioY + marioScreenY == 161 and memory.readbyte(0x0544) == 0 then
gui.drawbox(minx-marioX+marioScreenX-1,193-miny,maxx-marioX+marioScreenX-2,193-maxy-1,toRGBA(0x7F000000 + rgb),toRGBA(0x7F000000 + rgb))
gui.drawbox(minx-marioX+marioScreenX-1,193-miny,minx-marioX+marioScreenX-1,193-maxy-1,color,color)
gui.drawbox(minx-marioX+marioScreenX-1,193-miny,maxx-marioX+marioScreenX-2,193-miny,color,color)
gui.drawbox(minx-marioX+marioScreenX-1,193-maxy-1,maxx-marioX+marioScreenX-2,193-maxy-1,color,color)
gui.drawbox(maxx-marioX+marioScreenX-2,193-miny,maxx-marioX+marioScreenX-2,193-maxy-1,color,color)
--draw the numbers in the corners that show the fitness values
--local TLcorner = minx*range.coeffs.x+(208-miny)*range.coeffs.y+range.coeffs.c
--local TRcorner = maxx*range.coeffs.x+(208-miny)*range.coeffs.y+range.coeffs.c
--local BLcorner = minx*range.coeffs.x+(192-maxy)*range.coeffs.y+range.coeffs.c
--local BRcorner = maxx*range.coeffs.x+(192-maxy)*range.coeffs.y+range.coeffs.c
-- gui.drawtext(minx-marioX+marioScreenX,miny+1,TLcorner,textcolor,color)
-- gui.drawtext(maxx-marioX+marioScreenX-6*string.len(TRcorner)-1,miny+1,TRcorner,textcolor,color)
-- gui.drawtext(minx-marioX+marioScreenX,maxy-8,BLcorner,textcolor,color)
-- gui.drawtext(maxx-marioX+marioScreenX-6*string.len(TRcorner)-1,maxy-8,BRcorner,textcolor,color)
else
local offset = 161 - (marioY + marioScreenY)
gui.drawbox(minx-marioX+marioScreenX-1,193-miny-offset,maxx-marioX+marioScreenX-2,193-maxy-1-offset,toRGBA(0x7F000000 + rgb),toRGBA(0x7F000000 + rgb))
gui.drawbox(minx-marioX+marioScreenX-1,193-miny-offset,minx-marioX+marioScreenX-1,193-maxy-1-offset,color,color)
gui.drawbox(minx-marioX+marioScreenX-1,193-miny-offset,maxx-marioX+marioScreenX-2,193-miny-offset,color,color)
gui.drawbox(minx-marioX+marioScreenX-1,193-maxy+1-offset,maxx-marioX+marioScreenX-2,193-maxy-1-offset,color,color)
gui.drawbox(maxx-marioX+marioScreenX-2,193-miny-offset,maxx-marioX+marioScreenX-2,193-maxy-1-offset,color,color)
end
end
end
end
end
function displayGUI(network,fitstate)
if DisplayRanges then
drawRanges()
end
if DisplayStats then
drawStatsBox(fitstate)
if not currentTurbo then
drawPMeter(159,222)
--drawClock(222,10)
end
end
if DisplayNetwork and not currentTurbo then
drawNetwork(network)
end
end
function worldselection()
local selecting = true
local selection = 1
local color = draw.BLACK
local z = 0
local c = 0
for i=1,70 do
draw.rect(8,204-i,248,22+i,draw.BLACK)
draw.rect(9,205-i,246,20+i,draw.WHITE)
draw.rect(10,206-i,244,18+i,draw.CYAN)
gui.text(12, 208-i, "Please select a world. Use !press up/down to \nnavigate and !press A to select and B to cancel", draw.BLACK, draw.CYAN)
color = draw.BLACK
for x=1,8 do
if file_exists("fcs"..dirsep.."world-" .. x .. ".fcs") then
color = draw.BLACK
else
color = draw.RED
end
if x < 5 then
z = x
c = 0
else
z = x -4
c = 80
end
if x == selection then
gui.text(12+c, 216+16*z-i, "> World: ".. x, color, draw.CYAN)
else
gui.text(12+c, 216+16*z-i, "World: ".. x, color, draw.CYAN)
end
end
coroutine.yield()
end
while selecting do
draw.rect(8,134,248,94,draw.BLACK)
draw.rect(9,135,246,92,draw.WHITE)
draw.rect(10,136,244,90,draw.CYAN)
gui.text(12, 138, "Please select a world. Use !press up/down to \nnavigate and !press A to select and B to cancel", draw.BLACK, draw.CYAN)
color = draw.BLACK
for x=1,8 do
if file_exists("fcs"..dirsep.."world-" .. x .. ".fcs") then
color = draw.BLACK
else
color = draw.RED
end
if x < 5 then
z = x
c = 0
else
z = x -4
c = 80
end
if x == selection then
gui.text(12+c, 146+16*z, "> World: ".. x, color, draw.CYAN)
else
gui.text(12+c, 146+16*z, "World: ".. x, color, draw.CYAN)
end
end
if file_exists("userinput.lua") then
userinput=loadfile("userinput.lua")
if userinput then
os.remove('userinput.lua')
uinput = {}
pcall(userinput)
if uinput.down then
selection = selection + 1
if selection > 8 then
selection = 1
end
elseif uinput.up then
selection = selection - 1
if selection <1 then
selection = 8
end
elseif uinput.left then
selection = selection -4
if selection <1 then
selection = selection + 8
end
elseif uinput.right then
selection = selection +4
if selection >8 then
selection = selection - 8
end
elseif uinput.A and file_exists("fcs"..dirsep.."world-" .. selection .. ".fcs") then
local interrupt = io.open("interrupt.lua","w")
interrupt:write("savestateObj = savestate.object('fcs"..dirsep.."world-" .. selection ..".fcs')\nprint('one point five')\nsavestate.load(savestateObj)\nprint('two')")
interrupt:close()
for i=1,90 do
draw.rect(8,134+i,248,94-i,draw.BLACK)
draw.rect(9,135+i,246,92-i,draw.WHITE)
draw.rect(10,136+i,244,90-i,draw.CYAN)
gui.text(12, 138+i, "Please select a world. Use !press up/down to \nnavigate and !press A to select and B to cancel", draw.BLACK, draw.CYAN)
color = draw.BLACK
for x=1,8 do
if file_exists("fcs"..dirsep.."world-" .. x .. ".fcs") then
color = draw.BLACK
else
color = draw.RED
end
if x < 5 then
z = x
c = 0
else
z = x -4
c = 80
end
if x == selection then
gui.text(12+c, 146+16*z+i, "> World: ".. x, color, draw.CYAN)
else
gui.text(12+c, 146+16*z+i, "World: ".. x, color, draw.CYAN)
end
end
coroutine.yield()
end
return
elseif uinput.B then
for i=1,90 do
draw.rect(8,134+i,248,94-i,draw.BLACK)
draw.rect(9,135+i,246,92-i,draw.WHITE)
draw.rect(10,136+i,244,90-i,draw.CYAN)
gui.text(12, 138+i, "Please select a world. Use !press up/down to \nnavigate and !press A to select and B to cancel", draw.BLACK, draw.CYAN)
color = draw.BLACK
for x=1,8 do
if file_exists("fcs"..dirsep.."world-" .. x .. ".fcs") then
color = draw.BLACK
else
color = draw.RED
end
if x < 5 then
z = x
c = 0
else
z = x -4
c = 80
end
if x == selection then
gui.text(12+c, 146+16*z+i, "> World: ".. x, color, draw.CYAN)
else
gui.text(12+c, 146+16*z+i, "World: ".. x, color, draw.CYAN)
end
end
coroutine.yield()
end
return
end
end
end
coroutine.yield()
end
end
function SolveMemory()
local Tset = memory.readbyte(0x070A)
for a=1,60 do
coroutine.yield()
end
while Tset == 17 do
local index = memory.readbyte(0x428)
local card = {}
local selected = 0
local selectedindex = 0
for x=1,18 do
card[x] = memory.readbyte(0x7E81+x)
end
if card[index+1] < 0x10 then
joypad.set(Player,{A=true})
for a=1,90 do
coroutine.yield()
end
selected = card[index+1]
selectedindex = index+1
local desired = 0
for i=1,#card do
if card[i] == selected and i ~= selectedindex then
desired = i-1
end
end
while index ~= desired do
if desired <6 then
if index >=6 then
joypad.set(Player,{up=true})
elseif desired > index then
joypad.set(Player,{right=true})
else
joypad.set(Player,{left=true})
end
elseif desired >= 12 then
if index <12 then
joypad.set(Player,{down=true})
elseif desired > index then
joypad.set(Player,{right=true})
else
joypad.set(Player,{left=true})
end
else
if index >= 12 then
joypad.set(Player,{up=true})
elseif index < 6 then
joypad.set(Player,{down=true})
elseif desired > index then
joypad.set(Player,{right=true})
else
joypad.set(Player,{left=true})
end
end
for a=1,20 do
coroutine.yield()
end
index = memory.readbyte(0x428)
end
joypad.set(Player,{A=true})
for a=1,90 do
coroutine.yield()
end
else
joypad.set(Player,{right=true})
for a=1,20 do
coroutine.yield()
end
end
Tset = memory.readbyte(0x070A)
end
end
function writehumanlevel()
local file = io.open("HumanLevelName.txt","w")
local world = memory.readbyte(0x0727)+1
file:write(world.."-" .. HumanLevelName)
file:close()
end
function PlayBoss()
boss = true
roottimeout = roottimeoutboss
local prevhistory
if pool then prevhistory = pool.history end
initPool(true)
local file = io.open("backups"..dirsep.."winners.txt","r")
i = 1
if file then
while true do
winnername = file:read("*line")
if not winnername then break end
dofile(winnername)
addToSpecies(loadedgenome)
if pool.species[#pool.species].nick == '' then
pool.species[#pool.species].nick = loadedgenome.nick
else
pool.species[#pool.species].nick = pool.species[#pool.species].nick .. "/" .. loadedgenome.nick
end
i=i +1
end
end
for g=i,999 do
local genome = newGenome(1)
mutate(genome,1)
addToSpecies(genome)
end
getPositions()
local score = {memory.readbyte(0x0715),memory.readbyte(0x0716),memory.readbyte(0x0717)}
memory.writebyte(0x0715,0) --set score to 0
memory.writebyte(0x0716,0)
memory.writebyte(0x0717,0)
getTileset()
indicatorOutput()
if prevhistory then pool.history = prevhistory end
redospectop = pool.generation % 10 == 0
while not playGeneration(redospectop) do
fitnessDataOutput()
newGeneration()
redospectop = pool.generation % 10 == 0
end
local score2 = {memory.readbyte(0x0715),memory.readbyte(0x0716),memory.readbyte(0x0717)}
memory.writebyte(0x0715,score[1]+score2[1]) --restore score
memory.writebyte(0x0716,score[2]+score2[2])
memory.writebyte(0x0717,score[3]+score2[3])
savestateBackup = savestate.object(savestateSlotMap)
savestate.save(savestateBackup)
savestate.persist(savestateBackup)
histogramOutput()
saveGenome("bosswinner",true)
end
function playLevel()
roottimeout = 120
boss = false
battle = false
writehumanlevel()
writescene(1)
local warppipe = false
while memory.readbyte(0x0020) == 1 do
coroutine.yield()
end
if memory.readbyte(0x05FD) == 2 then
while memory.readbyte(0x05FD) == 2 do
joypad.set(Player,{A = true})
coroutine.yield()
joypad.set(Player,{A = false})
coroutine.yield()
end
for x=1,480 do
coroutine.yield()
end
end
local Tset = memory.readbyte(0x070A)
if Tset == 7 then
local TASTime = {0,25,50}
if memory.readbyte(0x7971) == 1 then
TASTime = {25}
end
for i=1,300 do
coroutine.yield()
end
for i=1,50+TASTime[math.random(#TASTime)] do
joypad.set(Player,{right = true})
coroutine.yield()
end
joypad.set(Player,{right = false})
joypad.set(Player,{B = true})
coroutine.yield()
for i=1,300 do
joypad.set(Player,{B = false})
coroutine.yield()
end
savestateBackup = savestate.object(savestateSlotMap)
savestate.save(savestateBackup)
savestate.persist(savestateBackup)
return
end
local prevhistory
if pool then prevhistory = pool.history end
initPool(false)
local file = io.open("backups"..dirsep.."winners.txt","r")
local i = 1
if file then
while true do
winnername = file:read("*line")
if not winnername then break end
dofile(winnername)
addToSpecies(loadedgenome)
if pool.species[#pool.species].nick == '' then
pool.species[#pool.species].nick = loadedgenome.nick
else
pool.species[#pool.species].nick = pool.species[#pool.species].nick .. "/" .. loadedgenome.nick
end
i=i +1
end
end
numberWinner = i
for g=i,999 do
local genome = newGenome(1)
mutate(genome,1)
addToSpecies(genome)
end
preloaded = false
userreplay = false
init = loadfile("initialize.lua")
initializeclear = io.open("initialize.lua","w")
if initializeclear then initializeclear:close() end
if init then init() end
fileFTracker = io.open("fitnesstracker.txt","w")
fileFTracker:write(pool.history)
fileFTracker:close()
coroutine.yield()
getPositions()
local startx = marioX
local starty = marioY
for i,sprite in ipairs(sprites) do
if sprite.type == 0x25 then
warppipe = true
userreplay = true
preloaded = true
end
end
--memory.writebyte(0x0715,0) --set score to 0
--memory.writebyte(0x0716,0)
--memory.writebyte(0x0717,0)
getTileset()
local toad = false
if memory.readbyte(0x7965) == 1 then
toad = true
end
local score = {memory.readbyte(0x0715),memory.readbyte(0x0716),memory.readbyte(0x0717)}
if marioMusic == 0x70 then
battle = true
roottimeout = 900
memory.writebyte(0x0715,0) --set score to 0
memory.writebyte(0x0716,0)
memory.writebyte(0x0717,0)
end
savestateObj = savestate.object(savestateSlotLevel)
if not preloaded then
savestate.save(savestateObj)
savestate.persist(savestateObj)
levelNameOutput()
os.execute("mkdir backups"..dirsep..levelname)
os.execute("mkdir data"..dirsep..levelname)
indicatorOutput()
end
if prevhistory then pool.history = prevhistory end
if warppipe then
local cont = {}
local avgx = {0}
local goal = 0
if startx < 60 then
goal = 218
elseif startx > 180 then
goal = 24
else
goal = 122
end
while memory.readbyte(0x0014) ~= 1 do
getPositions()
if marioScreenX > goal+2 then
cont['left'] = true
cont['right'] = false
elseif marioScreenX < goal-2 then
cont['left'] = false
cont['right'] = true
elseif marioScreenX >= goal-2 and marioScreenX <= goal+2 then
cont['left'] = false
cont['right'] = false
cont['down'] = true
end
if marioScreenX - avgx[1] < 5 then
cont['A'] = true
cont['up'] = true
cont['down']=false
table.insert(avgx,0)
else
cont['A'] = false
end
table.insert(avgx,marioScreenX)
if #avgx > 150 then
table.remove(avgx,1)
end
joypad.set(Player,cont)
coroutine.yield()
end
for i=1,60 do
coroutine.yield()
end
end
if not userreplay then
redospectop = pool.generation == 0
while not playGeneration(redospectop) do
fitnessDataOutput()
newGeneration()
redospectop = false
end
if battle then
local score2 = {memory.readbyte(0x0715),memory.readbyte(0x0716),memory.readbyte(0x0717)}
memory.writebyte(0x0715,score[1]+score2[1]) --restore score
memory.writebyte(0x0716,score[2]+score2[2])
memory.writebyte(0x0717,score[3]+score2[3])
end
for i=1,60 do
if toad then
memory.writebyte(0x7966,8) --coins required
end
coroutine.yield()
end
if boss then
for i=1,600 do
joypad.set(Player,{A = true, left = false, right = false, up = false, down = false, B = false})
coroutine.yield()
joypad.set(Player,{A = false, left = false, right = false, up = false, down = false, B = false})
coroutine.yield()
end
end
savestateBackup = savestate.object(savestateSlotMap)
savestate.save(savestateBackup)
savestate.persist(savestateBackup)
histogramOutput()
saveGenome("winner",true)
mapupdateHist()
savePool("backups".. dirsep .. levelname .. dirsep .. "winpool.lua")
end
if not warppipe then
Replay = true
for i=1,#pool.breakthroughfiles do
print(pool.breakthroughfiles[i])
dofile(pool.breakthroughfiles[i])
pool.generation = loadedgenome.gen
pool.currentSpecies = loadedgenome.s
pool.currentGenome = loadedgenome.g
playGenome(loadedgenome)
end
if preloaded then
savestateObj = savestate.object(savestateSlotMap)
else
savestateObj = savestateBackup
end
local f = io.open("../mapupdate.txt","w")
f:write("")
f:close()
savestate.load(savestateObj)
Replay = false
os.rename("2", 'fcs'..dirsep..levelname ..'.fcs')
if boss then
os.rename("3", 'fcs'..dirsep..levelname ..'BOSS.fcs')
end
local file = io.open("savegamebackup.txt","w")
file:write(memory.readbyte(0x0727))
file:close()
end
writescene(3)
os.remove('userinput.lua')
end
prevLN = ''
local leveldict = {[3]="1",[4]="2",[5]="3",[6]="4",[7]="5",[8]="6",[9]="7",[10]="8",[11]="9",[12]="10",[13]="1",[14]="2",[15]="3",[16]="4",[17]="5",[18]="6",[19]="7",[20]="8",[21]="9",[95]="Tower",[103]="Fortress",[104]="Quicksand",[105]="Piramid",[201]="Castle",[204]="Bowser's castle",[223]="Tower",[229]="Start",[235]="Fortress"}
HumanLevelName = ''
while true do
if memory.readbyte(0x00EE) ~= 0 then
if Player == 1 then
levelname = memory.readbyte(0x0727).."-"..(memory.readbyte(0x7978)*8+memory.readbyte(0x797A)/32).."-"..memory.readbyte(0x7976)/32
else
levelname = memory.readbyte(0x0727).."-"..(memory.readbyte(0x7979)*8+memory.readbyte(0x797B)/32).."-"..memory.readbyte(0x7977)/32
end
if levelname ~= prevLN then
local LocFile = io.open("OPosition.txt",'w')
LocFile:write(levelname)
LocFile:close()
prevLN = levelname
end
playLevel()
elseif memory.readbyte(0x070A) == 17 then
SolveMemory()
else
if Player == 1 then
levelname = memory.readbyte(0x0727).."-"..(memory.readbyte(0x77)*8+memory.readbyte(0x79)/32).."-"..memory.readbyte(0x75)/32
else
levelname = memory.readbyte(0x0727).."-"..(memory.readbyte(0x78)*8+memory.readbyte(0x7A)/32).."-"..memory.readbyte(0x76)/32
end
if levelname ~= prevLN then
local LocFile = io.open("OPosition.txt",'w')
LocFile:write(levelname)
LocFile:close()
prevLN = levelname
end
if file_exists("userinput.lua") then
userinput=loadfile("userinput.lua")
if userinput then
os.remove('userinput.lua')
uinput = {}
pcall(userinput)
if leveldict[memory.readbyte(0xE5)] ~= nil then
HumanLevelName = leveldict[memory.readbyte(0xE5)]
end
if uinput.A and file_exists("fcs" .. dirsep .. levelname .. ".fcs") and memory.readbyte(0x05F2) == 0 then --0x5F2 1 = item folder. 0 = map
local interrupt = io.open("interrupt.lua","w")
interrupt:write("os.rename('fcs/" .. levelname ..".fcs', "..savestateSlotLevel..")\nsavestateObj = savestate.object(" .. savestateSlotLevel .. ")\nprint('one point five')\nsavestate.load(savestateObj)\nprint('two')")
if file_exists('fcs' .. dirsep .. levelname .. 'BOSS.fcs') then
interrupt:write("os.rename('fcs/" .. levelname .."BOSS.fcs', "..savestateSlotBOSS..")\nbosssavestateObj = savestate.object(" .. savestateSlotBOSS ..")")
end
interrupt:close()
local initialize = io.open("initialize.lua","w")
initialize:write("loadPool('backups/".. levelname .. "/winpool.lua')\n")
initialize:write("preloaded = true\nuserreplay = true\nwritescene(2)")
initialize:close()
elseif uinput.A and Startlocations[levelname] then
worldselection()
else
joypad.set(Player,uinput)
end
end
end
end
coroutine.yield()
end
return true
Comments
0 B
|👍
/👎