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