-- MarI/O by SethBling

-- Feel free to use this code, but please do not redistribute it.

-- Intended for use with the BizHawk emulator and Super Mario World or Super Mario Bros. ROM.

romtype_SMW = gameinfo . getromname ( ) == "Super Mario World (USA)"

if romtype_SMW then

Filename = "DP1.state"

ButtonNames = {

"A" ,

"B" ,

"X" ,

"Y" ,

"Up" ,

"Down" ,

"Left" ,

"Right" ,

}

console . writeline ( "Detected Super Mario World (USA)" )

else

Filename = "SMB1-1.state"

ButtonNames = {

"A" ,

"B" ,

"Up" ,

"Down" ,

"Left" ,

"Right" ,

}

end

BoxRadius = 6

InputSize = ( BoxRadius * 2 + 1 ) * ( BoxRadius * 2 + 1 )

Inputs = InputSize + 1

Outputs = # ButtonNames

Population = 50

DeltaDisjoint = 2.0

DeltaWeights = 0.4

DeltaThreshold = 1.0

StaleSpecies = 15

MutateConnectionsChance = 0.25

PerturbChance = 0.90

CrossoverChance = 0.75

LinkMutationChance = 2.0

NodeMutationChance = 0.50

BiasMutationChance = 0.40

StepSize = 0.1

DisableMutationChance = 0.4

EnableMutationChance = 0.3

TimeoutConstant = 20

MaxNodes = 1000000

debugMode = false

forceNewRun = false

function getPositions ( )

if romtype_SMW then

marioX = memory . read_s16_le ( 0x94 )

marioY = memory . read_s16_le ( 0x96 )

local layer1x = memory . read_s16_le ( 0x1A ) ;

local layer1y = memory . read_s16_le ( 0x1C ) ;

screenX = marioX - layer1x

screenY = marioY - layer1y

else

marioX = memory . readbyte ( 0x6D ) * 0x100 + memory . readbyte ( 0x86 )

marioY = memory . readbyte ( 0x03B8 ) + 16

screenX = memory . readbyte ( 0x03AD )

screenY = memory . readbyte ( 0x03B8 )

end

end

function getTile ( dx , dy )

if romtype_SMW then

x = math.floor ( ( marioX + dx + 8 ) / 16 )

y = math.floor ( ( marioY + dy ) / 16 )

return memory . readbyte ( 0x1C800 + math.floor ( x / 0x10 ) * 0x1B0 + y * 0x10 + x % 0x10 )

else

local x = marioX + dx + 8

local y = marioY + dy - 16

local page = math.floor ( x / 256 ) % 2

local subx = math.floor ( ( x % 256 ) / 16 )

local suby = math.floor ( ( y - 32 ) / 16 )

local addr = 0x500 + page * 13 * 16 + suby * 16 + subx

if suby >= 13 or suby < 0 then

return 0

end

if memory . readbyte ( addr ) ~= 0 then

return 1

else

return 0

end

end

end

function getSprites ( )

if romtype_SMW then

local sprites = { }

for slot = 0 , 11 do

local status = memory . readbyte ( 0x14C8 + slot )

if status ~= 0 then

spritex = memory . readbyte ( 0xE4 + slot ) + memory . readbyte ( 0x14E0 + slot ) * 256

spritey = memory . readbyte ( 0xD8 + slot ) + memory . readbyte ( 0x14D4 + slot ) * 256

sprites [ # sprites + 1 ] = { [ "x" ] = spritex , [ "y" ] = spritey }

end

end

return sprites

else

local sprites = { }

for slot = 0 , 4 do

local enemy = memory . readbyte ( 0xF + slot )

if enemy ~= 0 then

local ex = memory . readbyte ( 0x6E + slot ) * 0x100 + memory . readbyte ( 0x87 + slot )

local ey = memory . readbyte ( 0xCF + slot ) + 24

sprites [ # sprites + 1 ] = { [ "x" ] = ex , [ "y" ] = ey }

end

end

return sprites

end

end

function getExtendedSprites ( )

if gameinfo . getromname ( ) == "Super Mario World (USA)" then

local extended = { }

for slot = 0 , 11 do

local number = memory . readbyte ( 0x170B + slot )

if number ~= 0 then

spritex = memory . readbyte ( 0x171F + slot ) + memory . readbyte ( 0x1733 + slot ) * 256

spritey = memory . readbyte ( 0x1715 + slot ) + memory . readbyte ( 0x1729 + slot ) * 256

extended [ # extended + 1 ] = { [ "x" ] = spritex , [ "y" ] = spritey }

end

end

return extended

elseif gameinfo . getromname ( ) == "Super Mario Bros." then

return { }

end

end

function getInputs ( )

getPositions ( )

sprites = getSprites ( )

extended = getExtendedSprites ( )

local inputs = { }

for dy =- BoxRadius * 16 , BoxRadius * 16 , 16 do

for dx =- BoxRadius * 16 , BoxRadius * 16 , 16 do

inputs [ # inputs + 1 ] = 0

tile = getTile ( dx , dy )

if tile == 1 and marioY + dy < 0x1B0 then

inputs [ # inputs ] = 1

end

for i = 1 ,# sprites do

distx = math.abs ( sprites [ i ] [ "x" ] - ( marioX + dx ) )

disty = math.abs ( sprites [ i ] [ "y" ] - ( marioY + dy ) )

if distx <= 8 and disty <= 8 then

inputs [ # inputs ] = - 1

end

end

for i = 1 ,# extended do

distx = math.abs ( extended [ i ] [ "x" ] - ( marioX + dx ) )

disty = math.abs ( extended [ i ] [ "y" ] - ( marioY + dy ) )

if distx < 8 and disty < 8 then

inputs [ # inputs ] = - 1

end

end

end

end

--mariovx = memory.read_s8(0x7B)

--mariovy = memory.read_s8(0x7D)

return inputs

end

function sigmoid ( x )

return 2 / ( 1 + math.exp ( - 4.9 * x ) ) - 1

end

function newInnovation ( )

pool . innovation = pool . innovation + 1

return pool . innovation

end

function newPool ( )

local pool = { }

pool . species = { }

pool . generation = 0

pool . innovation = Outputs

pool . currentSpecies = 1

pool . currentGenome = 1

pool . currentFrame = 0

pool . maxFitness = 0

return pool

end

function newSpecies ( )

local species = { }

species . topFitness = 0

species . staleness = 0

species . genomes = { }

species . averageFitness = 0

return species

end

function newGenome ( )

local genome = { }

genome . genes = { }

genome . fitness = 0

genome . adjustedFitness = 0

genome . network = { }

genome . maxneuron = 0

genome . globalRank = 0

genome . mutationRates = { }

genome . mutationRates [ "connections" ] = MutateConnectionsChance

genome . mutationRates [ "link" ] = LinkMutationChance

genome . mutationRates [ "bias" ] = BiasMutationChance

genome . mutationRates [ "node" ] = NodeMutationChance

genome . mutationRates [ "enable" ] = EnableMutationChance

genome . mutationRates [ "disable" ] = DisableMutationChance

genome . mutationRates [ "step" ] = StepSize

return genome

end

function copyGenome ( genome )

local genome2 = newGenome ( )

for g = 1 ,# genome . genes do

table.insert ( genome2 . genes , copyGene ( genome . genes [ g ] ) )

end

genome2 . maxneuron = genome . maxneuron

genome2 . mutationRates [ "connections" ] = genome . mutationRates [ "connections" ]

genome2 . mutationRates [ "link" ] = genome . mutationRates [ "link" ]

genome2 . mutationRates [ "bias" ] = genome . mutationRates [ "bias" ]

genome2 . mutationRates [ "node" ] = genome . mutationRates [ "node" ]

genome2 . mutationRates [ "enable" ] = genome . mutationRates [ "enable" ]

genome2 . mutationRates [ "disable" ] = genome . mutationRates [ "disable" ]

return genome2

end

function basicGenome ( )

local genome = newGenome ( )

local innovation = 1

genome . maxneuron = Inputs

mutate ( genome )

return genome

end

function newGene ( )

local gene = { }

gene . into = 0

gene . out = 0

gene . weight = 0.0

gene . enabled = true

gene . innovation = 0

return gene

end

function copyGene ( gene )

local gene2 = newGene ( )

gene2 . into = gene . into

gene2 . out = gene . out

gene2 . weight = gene . weight

gene2 . enabled = gene . enabled

gene2 . innovation = gene . innovation

return gene2

end

function newNeuron ( )

local neuron = { }

neuron . incoming = { }

neuron . value = 0.0

return neuron

end

function generateNetwork ( genome )

local network = { }

network . neurons = { }

for i = 1 , Inputs do

network . neurons [ i ] = newNeuron ( )

end

for o = 1 , Outputs do

network . neurons [ MaxNodes + o ] = newNeuron ( )

end

table.sort ( genome . genes , function ( a , b )

return ( a . out < b . out )

end )

for i = 1 ,# genome . genes do

local gene = genome . genes [ i ]

if gene . enabled then

if network . neurons [ gene . out ] == nil then

network . neurons [ gene . out ] = newNeuron ( )

end

local neuron = network . neurons [ gene . out ]

table.insert ( neuron . incoming , gene )

if network . neurons [ gene . into ] == nil then

network . neurons [ gene . into ] = newNeuron ( )

end

end

end

genome . network = network

end

function evaluateNetwork ( network , inputs )

table.insert ( inputs , 1 )

if # inputs ~= Inputs then

console . writeline ( "Incorrect number of neural network inputs." )

return { }

end

for i = 1 , Inputs do

network . neurons [ i ] . value = inputs [ i ]

end

for _ , neuron in pairs ( network . neurons ) do

local sum = 0

for j = 1 ,# neuron . incoming do

local incoming = neuron . incoming [ j ]

local other = network . neurons [ incoming . into ]

sum = sum + incoming . weight * other . value

end

if # neuron . incoming > 0 then

neuron . value = sigmoid ( sum )

end

end

local outputs = { }

for o = 1 , Outputs do

local button = "P1 " .. ButtonNames [ o ]

if network . neurons [ MaxNodes + o ] . value > 0 then

outputs [ button ] = true

else

outputs [ button ] = false

end

end

return outputs

end

function crossover ( g1 , g2 )

-- Make sure g1 is the higher fitness genome

if g2 . fitness > g1 . fitness then

tempg = g1

g1 = g2

g2 = tempg

end

local child = newGenome ( )

local innovations2 = { }

for i = 1 ,# g2 . genes do

local gene = g2 . genes [ i ]

innovations2 [ gene . innovation ] = gene

end

for i = 1 ,# g1 . genes do

local gene1 = g1 . genes [ i ]

local gene2 = innovations2 [ gene1 . innovation ]

if gene2 ~= nil and math.random ( 2 ) == 1 and gene2 . enabled then

table.insert ( child . genes , copyGene ( gene2 ) )

else

table.insert ( child . genes , copyGene ( gene1 ) )

end

end

child . maxneuron = math.max ( g1 . maxneuron , g2 . maxneuron )

for mutation , rate in pairs ( g1 . mutationRates ) do

child . mutationRates [ mutation ] = rate

end

return child

end

function randomNeuron ( genes , nonInput )

local neurons = { }

if not nonInput then

for i = 1 , Inputs do

neurons [ i ] = true

end

end

for o = 1 , Outputs do

neurons [ MaxNodes + o ] = true

end

for i = 1 ,# genes do

if ( not nonInput ) or genes [ i ] . into > Inputs then

neurons [ genes [ i ] . into ] = true

end

if ( not nonInput ) or genes [ i ] . out > Inputs then

neurons [ genes [ i ] . out ] = true

end

end

local count = 0

for _ , _ in pairs ( neurons ) do

count = count + 1

end

local n = math.random ( 1 , count )

for k , v in pairs ( neurons ) do

n = n - 1

if n == 0 then

return k

end

end

return 0

end

function containsLink ( genes , link )

for i = 1 ,# genes do

local gene = genes [ i ]

if gene . into == link . into and gene . out == link . out then

return true

end

end

end

function pointMutate ( genome )

local step = genome . mutationRates [ "step" ]

for i = 1 ,# genome . genes do

local gene = genome . genes [ i ]

if math.random ( ) < PerturbChance then

gene . weight = gene . weight + math.random ( ) * step * 2 - step

else

gene . weight = math.random ( ) * 4 - 2

end

end

end

function linkMutate ( genome , forceBias )

local neuron1 = randomNeuron ( genome . genes , false )

local neuron2 = randomNeuron ( genome . genes , true )

local newLink = newGene ( )

if neuron1 <= Inputs and neuron2 <= Inputs then

--Both input nodes

return

end

if neuron2 <= Inputs then

-- Swap output and input

local temp = neuron1

neuron1 = neuron2

neuron2 = temp

end

newLink . into = neuron1

newLink . out = neuron2

if forceBias then

newLink . into = Inputs

end

if containsLink ( genome . genes , newLink ) then

return

end

newLink . innovation = newInnovation ( )

newLink . weight = math.random ( ) * 4 - 2

table.insert ( genome . genes , newLink )

end

function nodeMutate ( genome )

if # genome . genes == 0 then

return

end

genome . maxneuron = genome . maxneuron + 1

local gene = genome . genes [ math.random ( 1 ,# genome . genes ) ]

if not gene . enabled then

return

end

gene . enabled = false

local gene1 = copyGene ( gene )

gene1 . out = genome . maxneuron

gene1 . weight = 1.0

gene1 . innovation = newInnovation ( )

gene1 . enabled = true

table.insert ( genome . genes , gene1 )

local gene2 = copyGene ( gene )

gene2 . into = genome . maxneuron

gene2 . innovation = newInnovation ( )

gene2 . enabled = true

table.insert ( genome . genes , gene2 )

end

function enableDisableMutate ( genome , enable )

local candidates = { }

for _ , gene in pairs ( genome . genes ) do

if gene . enabled == not enable then

table.insert ( candidates , gene )

end

end

if # candidates == 0 then

return

end

local gene = candidates [ math.random ( 1 ,# candidates ) ]

gene . enabled = not gene . enabled

end

function mutate ( genome )

for mutation , rate in pairs ( genome . mutationRates ) do

if math.random ( 1 , 2 ) == 1 then

genome . mutationRates [ mutation ] = 0.95 * rate

else

genome . mutationRates [ mutation ] = 1.05263 * rate

end

end

if math.random ( ) < genome . mutationRates [ "connections" ] then

pointMutate ( genome )

end

local p = genome . mutationRates [ "link" ]

while p > 0 do

if math.random ( ) < p then

linkMutate ( genome , false )

end

p = p - 1

end

p = genome . mutationRates [ "bias" ]

while p > 0 do

if math.random ( ) < p then

linkMutate ( genome , true )

end

p = p - 1

end

p = genome . mutationRates [ "node" ]

while p > 0 do

if math.random ( ) < p then

nodeMutate ( genome )

end

p = p - 1

end

p = genome . mutationRates [ "enable" ]

while p > 0 do

if math.random ( ) < p then

enableDisableMutate ( genome , true )

end

p = p - 1

end

p = genome . mutationRates [ "disable" ]

while p > 0 do

if math.random ( ) < p then

enableDisableMutate ( genome , false )

end

p = p - 1

end

end

function disjoint ( genes1 , genes2 )

local i1 = { }

for i = 1 ,# genes1 do

local gene = genes1 [ i ]

i1 [ gene . innovation ] = true

end

local i2 = { }

for i = 1 ,# genes2 do

local gene = genes2 [ i ]

i2 [ gene . innovation ] = true

end

local disjointGenes = 0

for i = 1 ,# genes1 do

local gene = genes1 [ i ]

if not i2 [ gene . innovation ] then

disjointGenes = disjointGenes + 1

end

end

for i = 1 ,# genes2 do

local gene = genes2 [ i ]

if not i1 [ gene . innovation ] then

disjointGenes = disjointGenes + 1

end

end

local n = math.max ( # genes1 , # genes2 )

return disjointGenes / n

end

function weights ( genes1 , genes2 )

local i2 = { }

for i = 1 ,# genes2 do

local gene = genes2 [ i ]

i2 [ gene . innovation ] = gene

end

local sum = 0

local coincident = 0

for i = 1 ,# genes1 do

local gene = genes1 [ i ]

if i2 [ gene . innovation ] ~= nil then

local gene2 = i2 [ gene . innovation ]

sum = sum + math.abs ( gene . weight - gene2 . weight )

coincident = coincident + 1

end

end

return sum / coincident

end

function sameSpecies ( genome1 , genome2 )

local dd = DeltaDisjoint * disjoint ( genome1 . genes , genome2 . genes )

local dw = DeltaWeights * weights ( genome1 . genes , genome2 . genes )

return dd + dw < DeltaThreshold

end

function rankGlobally ( )

local global = { }

for s = 1 ,# pool . species do

local species = pool . species [ s ]

for g = 1 ,# species . genomes do

table.insert ( global , species . genomes [ g ] )

end

end

table.sort ( global , function ( a , b )

return ( a . fitness < b . fitness )

end )

for g = 1 ,# global do

global [ g ] . globalRank = g

end

end

function calculateAverageFitness ( species )

local total = 0

for g = 1 ,# species . genomes do

local genome = species . genomes [ g ]

total = total + genome . globalRank

end

species . averageFitness = total / # species . genomes

end

function totalAverageFitness ( )

local total = 0

for s = 1 ,# pool . species do

local species = pool . species [ s ]

total = total + species . averageFitness

end

return total

end

function cullSpecies ( cutToOne )

for s = 1 ,# pool . species do

local species = pool . species [ s ]

table.sort ( species . genomes , function ( a , b )

return ( a . fitness > b . fitness )

end )

local remaining = math.ceil ( # species . genomes / 2 )

if cutToOne then

remaining = 1

end

while # species . genomes > remaining do

table.remove ( species . genomes )

end

end

end

function breedChild ( species )

local child = { }

if math.random ( ) < CrossoverChance then

g1 = species . genomes [ math.random ( 1 , # species . genomes ) ]

g2 = species . genomes [ math.random ( 1 , # species . genomes ) ]

child = crossover ( g1 , g2 )

else

g = species . genomes [ math.random ( 1 , # species . genomes ) ]

child = copyGenome ( g )

end

mutate ( child )

return child

end

function removeStaleSpecies ( )

local survived = { }

for s = 1 ,# pool . species do

local species = pool . species [ s ]

table.sort ( species . genomes , function ( a , b )

return ( a . fitness > b . fitness )

end )

if species . genomes [ 1 ] . fitness > species . topFitness then

species . topFitness = species . genomes [ 1 ] . fitness

species . staleness = 0

else

species . staleness = species . staleness + 1

end

if species . staleness < StaleSpecies or species . topFitness >= pool . maxFitness then

table.insert ( survived , species )

end

end

pool . species = survived

end

function removeWeakSpecies ( )

local survived = { }

local sum = totalAverageFitness ( )

for s = 1 ,# pool . species do

local species = pool . species [ s ]

breed = math.floor ( species . averageFitness / sum * Population )

if breed >= 1 then

table.insert ( survived , species )

end

end

pool . species = survived

end

function addToSpecies ( child )

local foundSpecies = false

for s = 1 ,# pool . species do

local species = pool . species [ s ]

if not foundSpecies and sameSpecies ( child , species . genomes [ 1 ] ) then

table.insert ( species . genomes , child )

foundSpecies = true

end

end

if not foundSpecies then

local childSpecies = newSpecies ( )

table.insert ( childSpecies . genomes , child )

table.insert ( pool . species , childSpecies )

end

end

function newGeneration ( )

cullSpecies ( false ) -- Cull the bottom half of each species

rankGlobally ( )

removeStaleSpecies ( )

rankGlobally ( )

for s = 1 ,# pool . species do

local species = pool . species [ s ]

calculateAverageFitness ( species )

end

removeWeakSpecies ( )

local sum = totalAverageFitness ( )

local children = { }

for s = 1 ,# pool . species do

local species = pool . species [ s ]

breed = math.floor ( species . averageFitness / sum * Population ) - 1

for i = 1 , breed do

table.insert ( children , breedChild ( species ) )

end

end

cullSpecies ( true ) -- Cull all but the top member of each species

while # children + # pool . species < Population do

local species = pool . species [ math.random ( 1 , # pool . species ) ]

table.insert ( children , breedChild ( species ) )

end

for c = 1 ,# children do

local child = children [ c ]

addToSpecies ( child )

end

pool . generation = pool . generation + 1

writeFile ( forms . gettext ( saveLoadFile ) )

writeFile ( "backup." .. pool . generation .. "." .. forms . gettext ( saveLoadFile ) )

end

function initializePool ( )

if debugMode then console . writeline ( "Initializing Pool..." ) end

pool = newPool ( )

if debugMode then console . writeline ( "Generating initial population... (" .. Population .. " Genomes)" ) end

for i = 1 , Population do

basic = basicGenome ( )

addToSpecies ( basic )

end

initializeRun ( )

end

function clearJoypad ( )

controller = { }

for b = 1 ,# ButtonNames do

controller [ "P1 " .. ButtonNames [ b ] ] = false

end

joypad . set ( controller )

end

function createPool ( )

if forms . ischecked ( initSave ) or not ( file_exists ( Filename ) ) then

console . writeline ( "Saving savestate: " .. Filename )

savestate . save ( Filename )

forms . setproperty ( initSave , "Checked" , false )

end

initializePool ( )

savePool ( )

end

function initializeRun ( )

if debugMode then console . writeline ( "Loading savestate: " .. Filename ) end

savestate . load ( Filename ) ;

if debugMode then console . writeline ( "Setting basic parameters..." ) end

rightmost = 0

pool . currentFrame = 0

timeout = TimeoutConstant

clearJoypad ( )

if debugMode then console . writeline ( "Initializing species" ) end

local species = pool . species [ pool . currentSpecies ]

if debugMode then console . writeline ( "Initializing genome" ) end

local genome = species . genomes [ pool . currentGenome ]

if debugMode then console . writeline ( "Generating network for current genome" ) end

generateNetwork ( genome )

if debugMode then console . writeline ( "Setting initial current status" ) end

evaluateCurrent ( )

end

function evaluateCurrent ( )

local species = pool . species [ pool . currentSpecies ]

local genome = species . genomes [ pool . currentGenome ]

inputs = getInputs ( )

controller = evaluateNetwork ( genome . network , inputs )

if controller [ "P1 Left" ] and controller [ "P1 Right" ] then

controller [ "P1 Left" ] = false

controller [ "P1 Right" ] = false

end

if controller [ "P1 Up" ] and controller [ "P1 Down" ] then

controller [ "P1 Up" ] = false

controller [ "P1 Down" ] = false

end

end

function nextGenome ( )

pool . currentGenome = pool . currentGenome + 1

if pool . currentGenome > # pool . species [ pool . currentSpecies ] . genomes then

pool . currentGenome = 1

pool . currentSpecies = pool . currentSpecies + 1

if pool . currentSpecies > # pool . species then

newGeneration ( )

pool . currentSpecies = 1

end

end

end

function fitnessAlreadyMeasured ( )

local species = pool . species [ pool . currentSpecies ]

local genome = species . genomes [ pool . currentGenome ]

return genome . fitness ~= 0

end

function displayGenome ( genome )

local network = genome . network

local cells = { }

local i = 1

local cell = { }

for dy =- BoxRadius , BoxRadius do

for dx =- BoxRadius , BoxRadius do

cell = { }

cell . x = 50 + 5 * dx

cell . y = 70 + 5 * dy

cell . value = network . neurons [ i ] . value

cells [ i ] = cell

i = i + 1

end

end

local biasCell = { }

biasCell . x = 80

biasCell . y = 110

biasCell . value = network . neurons [ Inputs ] . value

cells [ Inputs ] = biasCell

for o = 1 , Outputs do

cell = { }

cell . x = 220

cell . y = 30 + 8 * o

cell . value = network . neurons [ MaxNodes + o ] . value

cells [ MaxNodes + o ] = cell

local color

if cell . value > 0 then

color = 0xFF0000FF

else

color = 0xFF000000

end

gui . drawText ( 223 , 24 + 8 * o , ButtonNames [ o ] , color , 9 )

end

for n , neuron in pairs ( network . neurons ) do

cell = { }

if n > Inputs and n <= MaxNodes then

cell . x = 140

cell . y = 40

cell . value = neuron . value

cells [ n ] = cell

end

end

for n = 1 , 4 do

for _ , gene in pairs ( genome . genes ) do

if gene . enabled then

local c1 = cells [ gene . into ]

local c2 = cells [ gene . out ]

if gene . into > Inputs and gene . into <= MaxNodes then

c1 . x = 0.75 * c1 . x + 0.25 * c2 . x

if c1 . x >= c2 . x then

c1 . x = c1 . x - 40

end

if c1 . x < 90 then

c1 . x = 90

end

if c1 . x > 220 then

c1 . x = 220

end

c1 . y = 0.75 * c1 . y + 0.25 * c2 . y

end

if gene . out > Inputs and gene . out <= MaxNodes then

c2 . x = 0.25 * c1 . x + 0.75 * c2 . x

if c1 . x >= c2 . x then

c2 . x = c2 . x + 40

end

if c2 . x < 90 then

c2 . x = 90

end

if c2 . x > 220 then

c2 . x = 220

end

c2 . y = 0.25 * c1 . y + 0.75 * c2 . y

end

end

end

end

gui . drawBox ( 50 - BoxRadius * 5 - 3 , 70 - BoxRadius * 5 - 3 , 50 + BoxRadius * 5 + 2 , 70 + BoxRadius * 5 + 2 , 0xFF000000 , 0x80808080 )

for n , cell in pairs ( cells ) do

if n > Inputs or cell . value ~= 0 then

local color = math.floor ( ( cell . value + 1 ) / 2 * 256 )

if color > 255 then color = 255 end

if color < 0 then color = 0 end

local opacity = 0xFF000000

if cell . value == 0 then

opacity = 0x50000000

end

color = opacity + color * 0x10000 + color * 0x100 + color

gui . drawBox ( cell . x - 2 , cell . y - 2 , cell . x + 2 , cell . y + 2 , opacity , color )

end

end

for _ , gene in pairs ( genome . genes ) do

if gene . enabled then

local c1 = cells [ gene . into ]

local c2 = cells [ gene . out ]

local opacity = 0xA0000000

if c1 . value == 0 then

opacity = 0x20000000

end

local color = 0x80 - math.floor ( math.abs ( sigmoid ( gene . weight ) ) * 0x80 )

if gene . weight > 0 then

color = opacity + 0x8000 + 0x10000 * color

else

color = opacity + 0x800000 + 0x100 * color

end

gui . drawLine ( c1 . x + 1 , c1 . y , c2 . x - 3 , c2 . y , color )

end

end

gui . drawBox ( 49 , 71 , 51 , 78 , 0x00000000 , 0x80FF0000 )

if forms . ischecked ( showMutationRates ) then

local pos = 100

for mutation , rate in pairs ( genome . mutationRates ) do

gui . drawText ( 100 , pos , mutation .. ": " .. rate , 0xFF000000 , 10 )

pos = pos + 8

end

end

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 writeFile ( filename )

local file = io.open ( filename , "w" )

if file == nil then

console . writeline ( "Failed to open for write: " .. filename )

else

console . writeline ( "Saving " .. filename )

file : write ( pool . generation .. "

" )

file : write ( pool . maxFitness .. "

" )

file : write ( # pool . species .. "

" )

if debugMode then console . write ( "Saving Species" ) end

for n , species in pairs ( pool . species ) do

if debugMode then console . write ( "." ) end

file : write ( species . topFitness .. "

" )

file : write ( species . staleness .. "

" )

file : write ( # species . genomes .. "

" )

for m , genome in pairs ( species . genomes ) do

file : write ( genome . fitness .. "

" )

file : write ( genome . maxneuron .. "

" )

for mutation , rate in pairs ( genome . mutationRates ) do

file : write ( mutation .. "

" )

file : write ( rate .. "

" )

end

file : write ( "done

" )

file : write ( # genome . genes .. "

" )

for l , gene in pairs ( genome . genes ) do

file : write ( gene . into .. " " )

file : write ( gene . out .. " " )

file : write ( gene . weight .. " " )

file : write ( gene . innovation .. " " )

if ( gene . enabled ) then

file : write ( "1

" )

else

file : write ( "0

" )

end

end

end

end

file : close ( )

if debugMode then console . writeline ( "Saved." ) end

end

end

function savePool ( )

if saveLoadFile == nil then

else

local filename = forms . gettext ( saveLoadFile )

writeFile ( filename )

end

end

function loadPool ( )

pool = newPool ( )

local filename = forms . gettext ( saveLoadFile )

local file = io.open ( filename , "r" )

if file == nil then

console . writeline ( "Couldn't load " .. filename )

else

if debugMode then console . writeline ( "Loading " .. filename ) end

pool . generation = file : read ( "*number" )

pool . maxFitness = file : read ( "*number" )

forms . settext ( maxFitnessLabel , "Max Fitness: " .. math.floor ( pool . maxFitness ) )

local numSpecies = file : read ( "*number" )

if debugMode then console . write ( "Loading Species" ) end

for s = 1 , numSpecies do

if debugMode then console . write ( "." ) end

local species = newSpecies ( )

table.insert ( pool . species , species )

species . topFitness = file : read ( "*number" )

species . staleness = file : read ( "*number" )

local numGenomes = file : read ( "*number" )

for g = 1 , numGenomes do

local genome = newGenome ( )

table.insert ( species . genomes , genome )

genome . fitness = file : read ( "*number" )

genome . maxneuron = file : read ( "*number" )

local line = file : read ( "*line" )

while line ~= "done" do

genome . mutationRates [ line ] = file : read ( "*number" )

line = file : read ( "*line" )

end

local numGenes = file : read ( "*number" )

for n = 1 , numGenes do

local gene = newGene ( )

table.insert ( genome . genes , gene )

local enabled

gene . into , gene . out , gene . weight , gene . innovation , enabled = file : read ( "*number" , "*number" , "*number" , "*number" , "*number" )

if enabled == 0 then

gene . enabled = false

else

gene . enabled = true

end

end

end

end

if debugMode then console . writeline ( "Loaded." ) end

file : close ( )

end

while fitnessAlreadyMeasured ( ) do

nextGenome ( )

end

forceNewRun = true

console . writeline ( "Loaded " .. filename )

end

function playTop ( )

local maxfitness = 0

local maxs , maxg

for s , species in pairs ( pool . species ) do

for g , genome in pairs ( species . genomes ) do

if genome . fitness > maxfitness then

maxfitness = genome . fitness

maxs = s

maxg = g

end

end

end

pool . currentSpecies = maxs

pool . currentGenome = maxg

pool . maxFitness = maxfitness

forms . settext ( maxFitnessLabel , "Max Fitness: " .. math.floor ( pool . maxFitness ) )

console . writeline ( "Best run: Spec:" .. maxs .. " Gnm:" .. maxg )

forceNewRun = true

return

end

function onExit ( )

forms . destroy ( form )

end

event . onexit ( onExit )

form = forms . newform ( 200 , 265 , "Fitness" )

maxFitnessLabel = forms . label ( form , "Max Fitness: ?" , 5 , 8 )

showNetwork = forms . checkbox ( form , "Show Network" , 5 , 30 )

showMutationRates = forms . checkbox ( form , "Show M-Rates" , 5 , 52 )

--restartButton = forms.button(form, "Reset AI", initializePool, 5, 77)

--initButton = forms.button(form, "Init", createPool, 80, 77)

initSave = forms . checkbox ( form , "Init Save" , 80 , 77 )

resetAI = forms . checkbox ( form , "Reset AI" , 5 , 77 )

saveButton = forms . button ( form , "Save" , savePool , 5 , 102 )

loadButton = forms . button ( form , "Load" , loadPool , 80 , 102 )

saveLoadFile = forms . textbox ( form , Filename .. ".pool" , 170 , 25 , nil , 5 , 148 )

saveLoadLabel = forms . label ( form , "Save/Load:" , 5 , 129 )

playTopButton = forms . button ( form , "Play Top" , playTop , 5 , 170 )

showBanner = forms . checkbox ( form , "Show Banner" , 5 , 190 )

debugOutput = forms . checkbox ( form , "ConsoleDebug" , 5 , 210 )

enableRun = forms . checkbox ( form , "Run" , 130 , 210 )

local species

local genome

while not ( forms . gettext ( form ) == "" ) do

debugMode = forms . ischecked ( debugOutput )

--Check if we're enabled

if forms . ischecked ( enableRun ) then

-- Some pre-checks to ensure we /can/ run

if pool == nil or forms . ischecked ( resetAI ) then

if pool == null then console . writeline ( "Huh. Trying to run without a gene pool!" ) end

if forms . ischecked ( initSave ) or not ( file_exists ( Filename ) ) then

--No prior pool

console . writeline ( "I'm going to create one then." )

createPool ( )

else

loadPool ( )

end

forms . setproperty ( resetAI , "Checked" , false )

--Set the initial MaxFitness

forms . settext ( maxFitnessLabel , "Max Fitness: " .. math.floor ( pool . maxFitness ) )

end

if forceNewRun then

forceNewRun = false

console . writeline ( "New run called." )

initializeRun ( )

pool . currentFrame = pool . currentFrame + 1

end

species = pool . species [ pool . currentSpecies ]

genome = species . genomes [ pool . currentGenome ]

if pool . currentFrame % 5 == 0 then

evaluateCurrent ( )

end

joypad . set ( controller )

getPositions ( )

if marioX > rightmost then

rightmost = marioX

timeout = TimeoutConstant

end

timeout = timeout - 1

local timeoutBonus = pool . currentFrame / 4

if timeout + timeoutBonus <= 0 then

local fitness = rightmost - pool . currentFrame / 2

if romtype_SMW and rightmost > 4816 then

fitness = fitness + 1000

end

if ( not romtype_SMW ) and rightmost > 3186 then

fitness = fitness + 1000

end

if fitness == 0 then

fitness = - 1

end

genome . fitness = fitness

if fitness > pool . maxFitness then

pool . maxFitness = fitness

forms . settext ( maxFitnessLabel , "Max Fitness: " .. math.floor ( pool . maxFitness ) )

if debugMode then console . writeline ( "Saving Pool (New High Fit)" ) end

writeFile ( forms . gettext ( saveLoadFile ) )

end

console . writeline ( "Gen " .. pool . generation .. " Spec " .. pool . currentSpecies .. " Gnm " .. pool . currentGenome .. " Fit: " .. fitness )

pool . currentSpecies = 1

pool . currentGenome = 1

while fitnessAlreadyMeasured ( ) do

nextGenome ( )

end

initializeRun ( )

end

local measured = 0

local total = 0

for _ , species in pairs ( pool . species ) do

for _ , genome in pairs ( species . genomes ) do

total = total + 1

if genome . fitness ~= 0 then

measured = measured + 1

end

end

end

pool . currentFrame = pool . currentFrame + 1

local backgroundColor = 0xD0FFFFFF

if forms . ischecked ( showBanner ) then

gui . drawBox ( 0 , 0 , 300 , 26 , backgroundColor , backgroundColor )

gui . drawText ( 0 , 0 , "Gen " .. pool . generation .. " species " .. pool . currentSpecies .. " genome " .. pool . currentGenome .. " (" .. math.floor ( measured / total * 100 ) .. "%)" , 0xFF000000 , 11 )

gui . drawText ( 0 , 12 , "Fitness: " .. math.floor ( rightmost - ( pool . currentFrame ) / 2 - ( timeout + timeoutBonus ) * 2 / 3 ) , 0xFF000000 , 11 )

gui . drawText ( 100 , 12 , "Max Fitness: " .. math.floor ( pool . maxFitness ) , 0xFF000000 , 11 )

end

elseif forms . ischecked ( showNetwork ) then

evaluateCurrent ( )

end

--Draw the GUI elements

if forms . ischecked ( showNetwork ) then

displayGenome ( genome )

end

emu . frameadvance ( ) ;

end