· 7 years ago · Feb 02, 2019, 03:08 AM
1--v4 BETA
2
3LostLevels = 0
4Player = 1
5
6savestateSlot = 6
7savestateObj = savestate.object(savestateSlot)
8
9BoxRadius = 6 --The radius of the vision box around Luigi
10InitialOscillations = {50,60,90} --Initial set of oscillation timers
11BoxWidth = BoxRadius*2+1 --Full width of the box
12BoxSize = BoxWidth*BoxWidth --Number of neurons in the box
13Inputs = BoxSize + 3 + #InitialOscillations
14
15InitialMutationRates = {
16 linkInputBox=0.1,
17 linkBottomRow=0.05,
18 linkLayers=0.1,
19 linkRandom=2.0,
20 node=0.5,
21 disable=0.4,
22 enable=0.2,
23 oscillation=0.2,
24 nswitch=0.01,
25 step=0.1
26}
27
28FramesPerEval = 5 --Number of frames between each network update
29
30ButtonNames = {"A","B","up","down","left","right"}
31
32DeltaDisjoint = 4.0 --multiplier for disjoint in same species function
33DeltaWeights = 0.4 --multiplier for weights in same species function
34DeltaThreshold = 1.0 --threshold for delta to be in the same species
35DeltaSwitch = 0.6 --threshold for delta of the network switch location
36
37DisplaySlots = false --Display the sprite slots?
38DisplaySprites = false --Display the sprite hitboxes?
39DisplayGrid = 0 --Display a large network inputs grid?
40DisplayNetwork = false --Display the neural network state?
41DisplayStats = false --Display top stats bar?
42DisplayRanges = false --Display special fitness ranges?
43DisplayCounters = false --Display death/time counter?
44
45ManualInput = false --Input manually rather than by network?
46
47--Penalty coeffs for fitness rank of netswitched species
48NetworkPenaltyInner = 0.8
49NetworkPenaltyOuter = 0.87
50
51CrossoverChance = 0.75 --Chance of crossing 2 genomes rather than copying 1
52
53MajorBreakFitness = 30 --Fitness before a breakthrough is not counted as minor
54
55BaseInterbreedChance = 0.07 --base interbreed chance per species
56InterbreedCutoff = 2.86 --average species size when interbreed is turned off
57InterbreedDegree = 1.0 --degree of interbreed curve
58
59TimeBeforePopOscil = 6 --time in each oscillation before the population changes
60
61MaxStaleness = 25 --max staleness before death
62
63--Stuff for getting inputs to the AI
64function platformSize() --Finds whether the platforms are large or small in the current level
65 local platsize = true --platsize is true for large platforms
66 if LostLevels == 0 and hardMode == 1 then --hard mode always has small platforms
67 platsize = false
68 end
69 if currentWorld > 4 or currentLevel == 4 then --castles and past world 4 always have small platforms
70 platsize = false
71 end
72 return platsize
73end
74
75function getPositions() --Returns all needed game state information from the game's RAM
76 --Gets necessary info for Mario and the game state
77 marioX = memory.readbyte(0x6D) * 0x100 + memory.readbyte(0x86) --Mario's X position
78 marioY = memory.readbyte(0x03B8)+16 + memory.readbyte(0xB5)*256 - 256 --Mario's Y position
79
80 if marioX > 60000 then marioX = 0 end --to prevent maze overflow bug
81 if marioY > 60000 or marioY < 0 then marioY = 0 end --above screen due to spring
82
83 marioScreenX = (marioX - memory.readbyte(0x071D)) % 256 --Mario's X position relative to the screen
84 marioScreenY = memory.readbyte(0x03B8)+16 --Mario's Y position not counting high vertical pos (mod 256)
85
86 marioState = memory.readbyte(0x0E) --Mario's current state (loading, in pipe, dying, normal, etc)
87
88 marioXVel = memory.readbyte(0x0057) --Mario's current X velocity
89 marioYVel = memory.readbyte(0x009F) --Mario's current Y velocity
90
91 currentScreen = memory.readbyte(0x071A) --Currently loaded in screen
92 nextScreen = memory.readbyte(0x071B) --Next loaded in screen
93
94 currentWorld = memory.readbyte(0x075F)+1 --Current world
95 currentLevel = memory.readbyte(0x075C)+1 --Current level
96
97 notInDemo = memory.readbyte(0x0770) --0 if demo, 1 if in gameplay
98 prelevel = memory.readbyte(0x0757) --if on the prelevel loading screen
99
100 mazeCP = memory.readbyte(0x06D9) --number of checkpoints it has passed
101 mazeCPGoal = memory.readbyte(0x06DA) --number of checkpoints it should have passed
102
103 timerCounter = memory.readbyte(0x0787) --counter uesd for the timer. if stays at 0 means timer is frozen
104
105 screenX = memory.readbyte(0x03AD) --X of current screen
106 screenY = memory.readbyte(0x03B8) --Y of current screen
107
108 hardMode = memory.readbyte(0x07FC) --For SMB, 0 if normal quest 1 if second quest
109
110 --Gets necesary info for each loaded sprite
111 spriteSlots = {} --From 0 to 5, each sprite currently loaded
112 spriteHitboxes = {} --Locations of each sprite, larger spriteHitboxes have multiples to cover a larger hitbox
113 local platsize = platformSize()
114 for slot=0,5 do
115 local isLoaded = memory.readbyte(0xF+slot) --Is the sprite being rendered
116 if isLoaded ~= 0 then
117 local ex = memory.readbyte(0x6E + slot)*0x100 + memory.readbyte(0x87+slot) --Sprite X position
118 local ey = memory.readbyte(0xCF + slot)+36 --Sprite Y position
119 local typ = memory.readbyte(0x16 + slot) --Sprite ID (what it is)
120 local state = memory.readbyte(0xA0 + slot) --Sprite state (animation state, rotation of firebars, etc)
121 --Correct for off-center sprite locations and spriteHitboxes sized larger than one block
122 if typ == 21 then --bowser fire
123 ex = ex + 4
124 ey = ey - 10
125 elseif typ >= 27 and typ <= 30 then --short firebar
126 ex = ex - 4
127 ey = ey - 11
128 cycle = state / 16 * math.pi
129 ymul = math.cos(cycle)
130 xmul = math.sin(cycle)
131 spriteHitboxes[#spriteHitboxes+1] = {["x"]=ex+xmul*16,["y"]=ey-ymul*16}
132 spriteHitboxes[#spriteHitboxes+1] = {["x"]=ex+xmul*32,["y"]=ey-ymul*32}
133 elseif typ == 31 or typ == 32 then --long firebar
134 ex = ex - 4
135 ey = ey - 12
136 cycle = state / 16 * math.pi
137 ymul = math.cos(cycle)
138 xmul = math.sin(cycle)
139 spriteHitboxes[#spriteHitboxes+1] = {["x"]=ex+xmul*16,["y"]=ey-ymul*16}
140 spriteHitboxes[#spriteHitboxes+1] = {["x"]=ex+xmul*32,["y"]=ey-ymul*32}
141 spriteHitboxes[#spriteHitboxes+1] = {["x"]=ex+xmul*48,["y"]=ey-ymul*48}
142 spriteHitboxes[#spriteHitboxes+1] = {["x"]=ex+xmul*64,["y"]=ey-ymul*64}
143 spriteHitboxes[#spriteHitboxes+1] = {["x"]=ex+xmul*80,["y"]=ey-ymul*80}
144 elseif typ >= 36 and typ <= 42 then --platform
145 ey = ey - 7
146 ex = ex - 1
147 spriteHitboxes[#spriteHitboxes+1] = {["x"]=ex+16,["y"]=ey}
148 if platsize then
149 spriteHitboxes[#spriteHitboxes+1] = {["x"]=ex+32,["y"]=ey}
150 end
151 elseif typ == 43 or typ == 44 then --elevator
152 ey = ey - typ + 36
153 ex = ex - 1
154 local eyalt = (ey+128) % 256
155 spriteHitboxes[#spriteHitboxes+1] = {["x"]=ex+8,["y"]=ey}
156 spriteHitboxes[#spriteHitboxes+1] = {["x"]=ex,["y"]=eyalt}
157 spriteHitboxes[#spriteHitboxes+1] = {["x"]=ex+8,["y"]=eyalt}
158 elseif typ == 45 then --bowser
159 spriteHitboxes[#spriteHitboxes+1] = {["x"]=ex,["y"]=ey-8}
160 end
161 if state ~= 4-LostLevels or typ ~= 12 then --If the podoboo's state is 4 don't draw it, it is not tangible, otherwise add the sprite
162 spriteHitboxes[#spriteHitboxes+1] = {["x"]=ex,["y"]=ey,["t"]=typ,["d"]=slot}
163 end
164 spriteSlots[slot] = typ
165 end
166 end
167end
168
169hiddenblocks = {} --List of hidden blocks Mario has been near
170hitblocks = 0 --Number of hidden blocks Mario has revealed
171
172function getTile(dx, dy) --Finds the tile ID of a particular block
173 --Find the memory address
174 local x = marioX + dx + 8
175 local y = marioY + dy - 8
176 local page = math.floor(x/256)%2
177 local subx = math.floor((x%256)/16)
178 local suby = math.floor((y - 32)/16)
179 local addr = 0x500 + page*13*16+suby*16+subx
180 --Find tile coordinates stored to keep track of hidden blocks
181 local tilex = math.floor(x/16)
182 local tiley = math.floor(y/16)
183 --Find the block ID of the block
184 local tile = memory.readbyte(addr)
185
186 local hbid = 95 - LostLevels --hidden block
187 local vbid = 84 --vine block
188 --If the block is offscreen it is empty
189 if suby >= 13 or suby < 0 then
190 tile = 0
191 end
192 --If the block is a hidden block
193 if (tile == hbid or tile == vbid) then
194 --If that block has not already been recorded add it to the list of hidden blocks
195 local dupe = false
196 for b=1,#hiddenblocks do
197 if hiddenblocks[b][1] == tilex and hiddenblocks[b][2] == tiley then
198 dupe = true
199 break
200 end
201 end
202 if not dupe then
203 table.insert(hiddenblocks,{tilex,tiley,tile})
204 end
205 end
206 --If this block was previously a hidden block and now is not it has been hit.
207 for b=1,#hiddenblocks do
208 if hiddenblocks[b][1] == tilex and hiddenblocks[b][2] == tiley then
209 if tile ~= hiddenblocks[b][3] then
210 hitblocks = hitblocks + 1
211 if hiddenblocks[b][3] == vbid then hitblocks = hitblocks + 1 end --vine blocks count as an extra hidden block hit
212 table.remove(hiddenblocks,b)
213 break
214 end
215 end
216 end
217 --Return what the block type is
218 return tile
219end
220
221function getInputs() --Create the grid around Mario that is the network's vision
222 getPositions() --Get all required memory data
223 local inputs = {} --Grid around Mario that shows blocks/sprites
224 --Loop through each space in the grid
225 for dy=-BoxRadius*16,BoxRadius*16,16 do
226 for dx=-BoxRadius*16,BoxRadius*16,16 do
227 inputs[#inputs+1] = 0
228 --If the tile contains a block, set to 1
229 tile = getTile(dx, dy)
230 if tile ~= 0 and marioY+dy < 0x1B0 then
231 inputs[#inputs] = 1
232 end
233 end
234 end
235 --for each sprite set it's location to -1
236 for i = 1,#spriteHitboxes do
237 distx = math.floor((spriteHitboxes[i]["x"] - marioX)/16 + 0.5)
238 disty = math.floor((spriteHitboxes[i]["y"] - marioY)/16 - 0.5)
239 if math.abs(distx)<=BoxRadius and math.abs(disty)<=BoxRadius then
240 inputs[distx+BoxRadius+1+(disty+BoxRadius)*BoxWidth] = -1
241 end
242 end
243 return inputs
244end
245
246--Initialization for the genetic system
247function initPool() --The pool contains all data for the genetics of the AI
248 pool = {}
249 pool.species = {} --List of species
250 pool.generation = 0 --Generation number
251 pool.innovation = 1 --Innovation tracks genes that are copies of each other vs unique
252 pool.currentGenome = 1 --current genome/species to display on top bar
253 pool.currentSpeices = 1
254 pool.maxFitness = 0 --Maximum fitness
255 pool.bestSpecies = 1 --best species
256 pool.secondFitness = 0 --Second best fitness
257 pool.maxCounter = 0 --Number of times it has gotten close to max fitness
258 pool.gsid = -1 --GSID tracks species so each one gets a unique id
259 pool.bottleneck = 0 --Number of gens since last breakthrough
260 pool.bottleneckloops = 0 --Number of oscillation loops in the bottleneck
261 pool.population = 1000 --Number of genomes
262 pool.lastMajorBreak = 0 --Last breakthrough of more than majorBreakFitness
263 pool.attempts = 0 --number of attempts
264 pool.deaths = 0 --number of deaths (auto timeouts do not count)
265 pool.totalTime = 0 --total time elapsed in-game
266end
267
268function newSpecies() --Each species is a group of genomes that are similar
269 local species = {}
270 species.maxFitness = 0 --farthest the species has gotten
271 species.maxRightmost = 0 --Farthest the species has gotten, ignoring time
272 species.averageFitness = 0 --Average fitness of the species
273 species.staleness = 0 --Number of gens since the species improved
274 species.genomes = {} --List of genomes
275 species.nickname = '' --nickname of the species
276 species.turbo = true --whether the species will use turbo or not
277 species.gsid = pool.gsid --give the species a unique GSID
278 pool.gsid = pool.gsid + 1
279 return species
280end
281
282function newGene() --A gene is a link between two neurons
283 local gene = {}
284 gene.into = 0 --input neuron
285 gene.out = 0 --output neuron
286 gene.weight = 0.0 --multiplier
287 gene.enabled = true --gene is turned on
288 gene.innovation = 0 --gene ID to find duplicates
289 return gene
290end
291
292function copyGene(gene) --copy a gene
293 local newGene = {}
294 newGene.into = gene.into
295 newGene.out = gene.out
296 newGene.weight = gene.weight
297 newGene.enabled = gene.enabled
298 newGene.innovation = gene.innovation
299 return newGene
300end
301
302function newGenome(numNetworks) --Each genome is an evolved 'player' that attempts the level
303 local genome = {}
304 genome.genes = {} --List of networks in raw gene form
305 genome.networks = {} --List of networks in neuron form
306 genome.oscillations = {} --Periods of each of 3 oscillating nodes
307 genome.maxNeuron = Inputs --Index of largest neuron
308 for n=1,numNetworks do --Initialize these as blank arrays for each network
309 genome.genes[n] = {}
310 genome.oscillations[n] = {}
311 for i=1,#InitialOscillations do --Initialize periods to default values
312 genome.oscillations[1][i] = InitialOscillations[i]
313 end
314 end
315 genome.fitstate = {} --information about the genome's final state
316 genome.globalRank = 0 --Ranking of the genome (low is worse)
317 genome.networkswitch = {} --List of positions where the network switches
318 for n=1,numNetworks-1 do
319 genome.networkswitch[n] = 0.0
320 end
321 genome.mutationRates = {} --List of mutation rates
322 for name,rate in pairs(InitialMutationRates) do
323 genome.mutationRates[name] = rate
324 end
325 return genome
326end
327
328function copyGenome(genome) --copy a genome
329 local ngenome = newGenome(#genome.genes) --create basic new genome
330 for n=1,#genome.genes do --copy each network over
331 for i,gene in pairs(genome.genes[n]) do
332 table.insert(ngenome.genes[n],copyGene(gene))
333 end
334 for i=1,#genome.oscillations[n] do
335 ngenome.oscillations[n][i] = genome.oscillations[n][i]
336 end
337 end
338 ngenome.maxNeuron = genome.maxNeuron
339 for n=1,#genome.genes-1 do
340 ngenome.networkswitch[n] = genome.networkswitch[n]
341 end
342 for name,rate in pairs(genome.mutationRates) do
343 ngenome.mutationRates[name] = rate
344 end
345 return ngenome
346end
347
348function newNeuron() --A neuron is a stored value that is calculated based on inputs and broadcasts to its outputs
349 local neuron = {}
350 neuron.incoming = {} --list of incoming genes
351 neuron.value = 0.0 --current value
352 neuron.layer = 0 --What layer the neuron is in (0 means it has not been calculated)
353 return neuron
354end
355
356--Evaluation of networks
357function generateNetwork(genome) --Convert each list of genes into a network that can be evaluated
358 for n=1,#genome.genes do --For each network
359 local neurons = {}
360 for i=1,Inputs do --Put in input nodes
361 neurons[i] = newNeuron()
362 end
363 for g=1,#genome.genes[n] do --Loop through each gene
364 local gene = genome.genes[n][g]
365 if gene.enabled then --Only add enabled genes
366 --If either side of gene is not added to neurons yet then add it
367 if neurons[gene.into] == nil then
368 neurons[gene.into] = newNeuron()
369 end
370 if neurons[gene.out] == nil then
371 neurons[gene.out] = newNeuron()
372 end
373 if gene.into > 0 then
374 table.insert(neurons[gene.out].incoming,gene) --Add gene to the output neuron's incoming
375 end
376 end
377 end
378 local layers = {}
379 while true do --loop until all layers found
380 local layer = {}
381 local finished = true --if all have been sorted
382 for i,neuron in pairs(neurons) do --loop through and find all possible for calculation
383 if neuron.layer == 0 then --Dont re-check already placed neurons
384 local possible = true
385 for j,gene in pairs(neuron.incoming) do --see if any incoming genes are not calculated yet
386 if neurons[gene.into].layer == 0 then
387 possible = false
388 break
389 end
390 end
391 if possible then --if has enough information to evaluate
392 table.insert(layer,i)
393 finished = false --a neuron was placed so it has not been all sorted yet
394 end
395 end
396 end
397 if finished then --exit the loop
398 break
399 end
400 for i=1,#layer do --For each neuron in the new layer set it to now be sorted
401 neurons[layer[i]].layer = #layers+1
402 end
403 table.insert(layers,layer)
404 end
405 local network = {}
406 network.neurons = neurons
407 network.layers = layers
408 network.genes = genome.genes[n]
409 genome.networks[n] = network
410 end
411end
412
413function sigmoid(x)
414 return 2/(1+math.exp(-4.9*x))-1
415end
416
417function evaluateNetwork(network, inputs, oscillating, frame)
418 table.insert(inputs,1.0) --Bias node
419 for i=1,#oscillating do --Oscillating nodes
420 if frame % (oscillating[i]*2) < oscillating[i] then
421 table.insert(inputs,1.0)
422 else
423 table.insert(inputs,-1.0)
424 end
425 end
426 --Speed nodes
427 if (marioXVel < 100) then
428 table.insert(inputs,marioXVel / 48)
429 else
430 table.insert(inputs,(marioXVel - 256) / 48)
431 end
432 if (marioYVel < 10) then
433 table.insert(inputs,marioYVel / 5)
434 else
435 table.insert(inputs,(marioYVel - 256) / 5)
436 end
437 --Add inputs to network
438 for i=1,Inputs do
439 network.neurons[i].value = inputs[i]
440 end
441 --Evaluate each layer
442 for l=2,#network.layers do
443 local layer = network.layers[l]
444 for n=1,#layer do
445 local neuron = network.neurons[layer[n]]
446 local sum = 0.0
447 for j = 1,#neuron.incoming do
448 local incoming = neuron.incoming[j]
449 local other = network.neurons[incoming.into]
450 sum = sum + incoming.weight * other.value
451 end
452 neuron.value = sigmoid(sum)
453 end
454 end
455 local output = {}
456 for o=1,#ButtonNames do --Find neural network outputs
457 output[ButtonNames[o]] = network.neurons[-o] ~= nil and network.neurons[-o].value > 0
458 end
459 --Disable opposite d-pad presses
460 if output["up"] and output["down"] then
461 output["up"] = false
462 output["down"] = false
463 end
464 if output["left"] and output["right"] then
465 output["left"] = false
466 output["right"] = false
467 end
468 return output
469end
470
471--Mutation and evolution
472function genomeDelta(genes1, genes2) --sees the delta between 2 networks.
473 local i2 = {} --i2 maps the innovation of a gene to the gene, for the second genome.
474 for i = 1,#genes2 do
475 local gene = genes2[i]
476 i2[gene.innovation] = gene
477 end
478
479 local sum = 0 --total weight difference among matching genes
480 local coincident = 0 --number of matching genes
481 for i,gene in pairs(genes1) do
482 if i2[gene.innovation] ~= nil then
483 local gene2 = i2[gene.innovation]
484 sum = sum + math.abs(gene.weight - gene2.weight)
485 coincident = coincident + 1
486 end
487 end
488 if coincident == 0 then --if there are no matching genes it does not match so return a very large value
489 return 100
490 end
491 local total = #genes1 + #genes2 --number of total genes
492 local disjoint = total - (coincident*2) --number of non-matching genes
493 return DeltaWeights * sum / coincident + DeltaDisjoint * disjoint / total --return the delta
494end
495
496function sameSpecies(genome1,genome2) --sees whether 2 genomes are in the same species or not.
497 if #genome1.networkswitch ~= #genome2.networkswitch then
498 return false --must have same number of networks
499 end
500 for n=1,#genome1.networkswitch do
501 if math.abs(genome1.networkswitch[n] - genome2.networkswitch[n]) > DeltaSwitch then
502 return false --if the network switches are too far apart they are different
503 end
504 end
505 for n=1,#genome1.genes do
506 delta = genomeDelta(genome1.genes[n],genome2.genes[n])
507 if delta > DeltaThreshold then
508 return false --if the delta between corresponding networks is too great they are different
509 end
510 end
511 return true --otherwise, same species
512end
513
514function shuffle(num) --shuffles an array from 1 to num.
515 local output = {}
516 for i=1,num do
517 local offset = i - 1
518 local randomIndex = offset*math.random()
519 local flooredIndex = randomIndex - randomIndex%1
520 if flooredIndex == offset then
521 output[#output + 1] = i
522 else
523 output[#output + 1] = output[flooredIndex + 1]
524 output[flooredIndex + 1] = i
525 end
526 end
527 return output
528end
529
530function addToSpecies(genome)
531
532 local foundSpecies = false
533
534 local order = shuffle(#pool.species) --Used to go through in random order so as to avoid feeding patterns.
535
536 for i=1,#order do
537 local species = pool.species[order[i]]
538 if sameSpecies(genome, species.genomes[1]) and not foundSpecies then
539 table.insert(species.genomes, genome)
540 foundSpecies = true
541 end
542 end
543
544 if not foundSpecies then --create new species
545 local newSpecies = newSpecies()
546 table.insert(newSpecies.genomes, genome)
547 table.insert(pool.species, newSpecies)
548 end
549end
550
551function randomNeuron(genes, nonInput, nonOutput) --pick a random neuron number
552 local neurons = {}
553 if not nonInput then --If inputs are an option add them
554 for i=1,Inputs do
555 neurons[i] = true
556 end
557 end
558 if not nonOutput then --If outputs are an option add them
559 for o=1,#ButtonNames do
560 neurons[-o] = true
561 end
562 end
563 for i,gene in pairs(genes) do --Add input and output of each gene
564 if not nonInput or gene.into > Inputs then --add input as long as it is valid
565 neurons[genes[i].into] = true
566 end
567 if not nonOutput or gene.out > 0 then --add output as long as it is valid
568 neurons[genes[i].out] = true
569 end
570 end
571 local count = 0
572 for _,_ in pairs(neurons) do --count number of neuron possibilities
573 count = count + 1
574 end
575 if count == 0 then return 0 end --return 0 means that there were no possibilities
576 local n = math.random(1, count) --pick a random possibility
577 for k,v in pairs(neurons) do
578 n = n-1 --count down until you get to correct #
579 if n == 0 then
580 return k
581 end
582 end
583end
584
585function linkDirection(genes, link) --Finds a connection's status. 0 means already exists, 1 means >, -1 means < or incomparable
586 for i=1,#genes do --check each gene
587 local gene = genes[i] --if the new added gene already exists return 0
588 if gene.into == link.into and gene.out == link.out then
589 return 0
590 end
591 end
592 --Attempt to find a connection opposing the suggested one (that would create a loop)
593 local afterNodes = {} --each node that is >= than the link.out
594 afterNodes[link.out] = true
595 local readAll = false --has seen every gene without adding any to the list of nodes after link.into
596 while not readAll do --loop until each gene has been read and used
597 readAll = true
598 for i,gene in pairs(genes) do --loop through the list of genes
599 if afterNodes[gene.into] == true and afterNodes[gene.out] == nil then --if new connection found
600 afterNodes[gene.out] = true
601 readAll = false
602 if gene.out == link.into then --connection found
603 return -1
604 end
605 end
606 end
607 end
608 return 1 --Has not found that link.into >= link.out so it is a fine direction
609end
610
611function linkMutate(genome, which, force) --Create a random new gene
612 local genes = genome.genes[which]
613 --create two random neurons
614 local neuronInto
615 if force == 0 then --only input box
616 neuronInto = math.random(1,BoxSize)
617 elseif force == 1 then --only bottom row
618 neuronInto = BoxSize + math.random(1,3+#InitialOscillations)
619 elseif force == 2 then --non-input
620 neuronInto = randomNeuron(genes, true, true)
621 if neuronInto == 0 then return end --no valid non-input nodes
622 else --anything
623 neuronInto = randomNeuron(genes, false, true)
624 end
625 local neuronOut = randomNeuron(genes, true, false)
626 --Create a new link
627 local newLink = newGene()
628 newLink.into = neuronInto
629 newLink.out = neuronOut
630 if neuronInto == neuronOut then return end --must not be the same neuron
631 connectionStatus = linkDirection(genes,newLink) --find current status of that link
632 if connectionStatus == 0 then return end --if link already exists then return
633 --opposite direction
634 if connectionStatus == -1 then
635 newLink.out = neuronInto
636 newLink.into = neuronOut
637 end
638 --create new innovation value and weight
639 newLink.innovation = pool.innovation
640 pool.innovation = pool.innovation + 1
641 newLink.weight = math.random()*4-2
642 --add to the genes
643 table.insert(genes,newLink)
644end
645
646function nodeMutate(genome, which) --Split a random gene and put a neuron in the middle
647 local genes = genome.genes[which]
648 if #genes == 0 then return end --cannot make a node with no genes
649 local gene = genes[math.random(1,#genes)] --pick a random gene
650 --create the new genes
651 local geneI = newGene()
652 local geneO = newGene()
653 geneI.into = gene.into
654 geneI.out = genome.maxNeuron + 1
655 geneO.into = genome.maxNeuron + 1
656 geneO.out = gene.out
657 --set innovation
658 geneI.innovation = pool.innovation
659 geneO.innovation = pool.innovation + 1
660 pool.innovation = pool.innovation + 2
661 --set weights
662 geneI.weight = math.sqrt(math.abs(gene.weight))
663 if math.random() < 0.5 then
664 geneI.weight = -geneI.weight --half the time first one is negative
665 end
666 geneO.weight = gene.weight / geneI.weight --they should multiply to original weight
667
668 genome.maxNeuron = genome.maxNeuron + 1
669 gene.enabled = false --disable old gene
670 --insert the new genes
671 table.insert(genes,geneI)
672 table.insert(genes,geneO)
673end
674
675function enableDisableMutate(genome, which, enable) --changes a random gene to enabled or disabled
676 local candidates = {} --list of genes that would be changed by this mutation
677 for _,gene in pairs(genome.genes[which]) do
678 if gene.enabled == not enable then --only insert if the current enabling is different from what we will set it to
679 table.insert(candidates, gene)
680 end
681 end
682
683 if #candidates == 0 then return end --if no valid genes then do nothing
684
685 local gene = candidates[math.random(1,#candidates)] --pick random from candidates
686 gene.enabled = enable --enable that gene
687end
688
689function oscillationMutate(genome, which) --change an oscillation node timer
690 i = math.random(1,#InitialOscillations) --pick a random oscillation to change
691 genome.oscillations[which][i] = genome.oscillations[which][i] + math.random(-10,10) --change by random amount
692 if genome.oscillations[which][i]<0 then --no negative oscillation values
693 genome.oscillations[which][i] = 0
694 end
695end
696
697function nswitchMutate(genome) --change the position of a network switch
698 which = #genome.networkswitch --most of the time pick the most recent switch
699 if math.random() < 0.25 then
700 which = math.random(1,#genome.networkswitch) --occasionally pick a random other one
701 end
702 genome.networkswitch[which] = genome.networkswitch[which] + math.random(-250,250) --change by random amount
703 local netlimit = 0
704 for i,species in ipairs(pool.species) do
705 if species.maxRightmost > netlimit then
706 netlimit = species.maxRightmost
707 end
708 end
709 if genome.networkswitch[which] < 0 or genome.networkswitch[which] > netlimit then --if out of range of where genome can reach
710 table.remove(genome.networkswitch) --remove the latest network
711 table.remove(genome.genes)
712 end
713 table.sort(genome.networkswitch, function(a,b) --make sure network switches are in order through the level
714 return (a < b)
715 end)
716end
717
718function weightsMutate(genome, which) --change the weights of genes around a node
719 local genes = genome.genes[which]
720 local node = randomNeuron(genes,false,false) --pick a random neuron to change weights in the area of
721 local step = genome.mutationRates["step"]
722 for i,gene in pairs(genes) do
723 if gene.into == node or gene.out == node then --if either end
724 if math.random() < WeightPerturbChance then --modify the weight slightly
725 gene.weight = gene.weight + (math.random()-0.5) * step * 2
726 elseif math.random() < WeightResetChance then --reset weight entirely
727 gene.weight = math.random()*4 - 2
728 end
729 end
730 end
731end
732
733MutationFunctions = {} --each mutation function, sorted by name
734
735MutationFunctions.linkInputBox = function(genome, which) return linkMutate(genome, which, 0) end
736MutationFunctions.linkBottomRow = function(genome, which) return linkMutate(genome, which, 1) end
737MutationFunctions.linkLayers = function(genome, which) return linkMutate(genome, which, 2) end
738MutationFunctions.linkRandom = function(genome, which) return linkMutate(genome, which, 3) end
739MutationFunctions.node = nodeMutate
740MutationFunctions.enable = function(genome, which) return enableDisableMutate(genome, which, true) end
741MutationFunctions.disable = function(genome, which) return enableDisableMutate(genome, which, false) end
742MutationFunctions.oscillation = oscillationMutate
743MutationFunctions.nswitch = function(genome, which) if which>1 then nswitchMutate(genome,which-1) end end
744MutationFunctions.weights = weightsMutate
745
746ChangingRates = {}
747ChangingRates.linkInputBox = true
748ChangingRates.linkBottomRow = true
749ChangingRates.linkLayers = true
750ChangingRates.node = true
751ChangingRates.enable = true
752ChangingRates.disable = true
753ChangingRates.oscillation = true
754ChangingRates.weights = true
755ChangingRates.step = true
756
757function mutate(genome, which) --Mutates a genome's network based on all it's mutation rates
758 for name,rate in pairs(genome.mutationRates) do --For each mutation type
759 local p = rate --Number of times to do that mutation type
760 if MutationFunctions[name] ~= nil then --is a mutation and not another rate like STEP
761 while p > 0 do --Loop until all mutations are done
762 if math.random() < p then --when p > 1 always do it, if it is a fraction then it is random
763 MutationFunctions[name](genome,which) --call that rate
764 end
765 p = p - 1 --reduce p
766 end
767 end
768 if ChangingRates[name] then
769 genome.mutationRates[name] = rate*(math.random()/10+0.95) --change the rate by a random amount
770 end
771 end
772end
773
774function crossover(g1,g2)
775 if g2.fitstate.fitness > g1.fitstate.fitness then
776 g2, g1 = g1, g2 --switch so that g1 is always the greater fitness
777 end
778 local numnets = math.min(#g1.genes,#g2.genes) --take the smaller # of networks
779 local child = newGenome(numnets)
780 for n=1,numnets do --for each network to be calculated
781 local i2 = {} --map from gene2 innovations to gene innovations
782 for i,gene in ipairs(g2.genes[n]) do
783 i2[gene.innovation] = gene
784 end
785 local i1 = {} --map from gene1 innovations to gene innovations
786 for i,gene in ipairs(g1.genes[n]) do
787 i1[gene.innovation] = gene
788 end
789 for i=1,#g1.oscillations[n] do
790 child.oscillations[n][i] = g1.oscillations[n][i] --set oscillations to g1's oscillations
791 end
792 for i,gene1 in ipairs(g1.genes[n]) do --some version of every gene from best genome
793 local gene2 = i2[gene1.innovation] --use innovations2 to find the copied gene in g1
794 if gene2 ~= nil and math.random(1,2) == 1 and gene2.enabled then
795 table.insert(child.genes[n], copyGene(gene2)) --if the copied gene exists pick randomly
796 else
797 table.insert(child.genes[n], copyGene(gene1)) --otherwise take the version from the best genome
798 end
799 end
800 if n > 1 then
801 child.networkswitch[n] = g1.networkswitch[n]
802 end
803 for i,gene in ipairs(g2.genes[n]) do
804 if i1[gene.innovation] == nil and math.random() < 0.01 then --low chance for every gene in g2 only
805 table.insert(child.genes[n], copyGene(gene)) --add those genes to the child as well
806 end
807 end
808 end
809 child.maxNeuron = math.max(g1.maxNeuron,g2.maxNeuron)
810 return child
811end
812
813function rankGlobally() --Give each species a global rank, 1 is lowest Population is highest
814 local globalRanks = {}
815 for s = 1,#pool.species do
816 local species = pool.species[s]
817 for g = 1,#species.genomes do
818 table.insert(globalRanks, species.genomes[g]) --Insert every genome into the table
819 end
820 end
821 table.sort(globalRanks, function (a,b) --Sort based on fitness
822 return (a.fitstate.fitness < b.fitstate.fitness)
823 end)
824
825 for g=1,#globalRanks do --Put position of each genome into that genome's data
826 globalRanks[g].globalRank = g
827 end
828end
829
830function calculatePoolStats() --Calculate avg and avg deviation of max fitness
831 local total = 0 --Average of each species max fitness
832 for i,species in ipairs(pool.species) do
833 total = total + species.maxFitness
834 end
835 pool.averagemaxfitness = total / #pool.species
836
837 local deviationtotal = 0 --Average distance between max fit and avg max fit
838 for i,species in ipairs(pool.species) do
839 deviationtotal = deviationtotal + math.abs(species.maxFitness - pool.averagemaxfitness)
840 end
841 pool.avgDeviation = deviationtotal / #pool.species
842end
843
844function calculateFitnessRank(species) --Returns a number for each species showing how well that species did
845 local totalRank = 0
846
847 for g=1,#species.genomes do --Total rank is equal to the average of the ranks of each genome
848 local genome = species.genomes[g]
849 totalRank = totalRank + genome.globalRank
850 end
851 totalRank = totalRank / #species.genomes
852
853 local multiplier = 1.0 --multiplier based on how many avg deviations from the mean the top fitness is
854 if species.maxFitness < pool.averagemaxfitness - (pool.avgDeviation *2) then
855 multiplier = 0.25
856 elseif species.maxFitness < pool.averagemaxfitness - pool.avgDeviation then
857 multiplier = 0.4
858 elseif species.maxFitness > pool.averagemaxfitness + (pool.avgDeviation *3) then
859 multiplier = 2.0
860 elseif species.maxFitness > pool.averagemaxfitness + (pool.avgDeviation *2) then
861 multiplier = 1.5
862 end
863
864 --Penalty for having multiple networks, to cancel out the benefit of consistency that they give
865 local netMulti = math.pow(NetworkPenaltyOuter*math.pow(NetworkPenaltyInner,#species.genomes[1].networkswitch),#species.genomes[1].networkswitch)
866
867 species.fitnessRank = totalRank * multiplier * netMulti
868end
869
870function totalPoolRank() --Sums each species fitness rank
871 local total = 0
872 for i,species in ipairs(pool.species) do
873 total = total + species.fitnessRank
874 end
875 return total
876end
877
878function cullSpecies(cutToOne) --Cuts down each species, either to a percent based on bottleneck, or only to it's top genome.
879 for i,species in ipairs(pool.species) do
880 table.sort(species.genomes, function (a,b) --sorts the genomes by how well they did
881 return (a.fitstate.fitness > b.fitstate.fitness)
882 end)
883
884 local percent = 0.5 --calculate percent
885 local minpercent = math.max(0.15,0.5-0.1*pool.bottleneckloops) --min and max of oscillation
886 local maxpercent = math.min(0.85,0.5+0.1*pool.bottleneckloops)
887
888 local length = math.min(10,(5 + 2*pool.bottleneckloops)) --length used in pop oscillation
889
890 local totallength = length*3 + TimeBeforePopOscil --total length of oscillation
891
892 if pool.bottleneck < totallength/2 then --if in first half go up to max
893 percent = minpercent + (0.1+maxpercent-minpercent)*(pool.bottleneck*2/totallength)
894 else --if in second half go down to min
895 percent = 0.1+maxpercent - (0.2+maxpercent-minpercent)*(pool.bottleneck*2/totallength-1)
896 end
897
898 local remaining = math.ceil(#species.genomes*percent) --number that remain
899 if cutToOne or remaining < 1 then --always keep at least 1
900 remaining = 1
901 end
902 while #species.genomes > remaining do --remove all that do not survive
903 table.remove(species.genomes)
904 end
905 end
906end
907
908function breedChild(species) --makes an offspring from a species
909 local child = {}
910 if math.random() < CrossoverChance then --chance to cross over two genomes or just copy one
911 g1 = species.genomes[math.random(1, #species.genomes)]
912 g2 = species.genomes[math.random(1, #species.genomes)]
913 child = crossover(g1, g2)
914 else
915 g = species.genomes[math.random(1, #species.genomes)]
916 child = copyGenome(g)
917 end
918
919 local which = #child.genes -- mutate usually at the latest network
920 if which > 1 and species.maxRightmost > 0 then --if there are multiple networks possibly modify one before most recent based on distance past that
921 local mutatePrevNetworkChance = 0.5 + (child.networkswitch[#child.networkswitch]-species.maxRightmost)/500
922 if math.random() < mutatePrevNetworkChance then
923 which = #child.genes - 1
924 end
925 end
926
927 mutate(child,which) --mutate the child in that network
928
929 return child
930end
931
932function removeStaleSpecies() --update staleness and remove stale species
933 local survived = {} --species that survive
934 for i,species in ipairs(pool.species) do
935 species.staleness = species.staleness + 1
936 if species.staleness < MaxStaleness or species.maxFitness >= pool.secondFitness then --non stale species and top 2 species survive
937 table.insert(survived,species)
938 end
939 end
940 pool.species = survived
941end
942
943function removeWeakSpecies()
944 local survived = {} --species that survive
945 local sum = totalPoolRank()
946 for s = 1,#pool.species do
947 local species = pool.species[s]
948 local breed = math.floor(species.fitnessRank / sum * pool.population)
949
950 if breed >= 1000/pool.population then
951 table.insert(survived, species)
952 end
953 end
954 pool.species = survived
955end
956
957function replaceNetwork(g1,g2) --replaces the most recent network of g1 with a random network of g2
958 local child = copyGenome(g1) --mostly a copy of genome 1
959 child.genes[#child.genes] = {} --clear out the most recent network
960 which = math.random(1,#g2.genes) --pick a random network to copy from which
961 for i,gene in ipairs(g2.genes[which]) do --copy each gene
962 table.insert(child.genes[#child.genes], copyGene(gene))
963 end
964 return child
965end
966
967function savePool(filename) --saves the pool into a file
968 local file = io.open(filename,"w")
969 local function tablestring(a)
970 if type(a)=='number' or type(a)=='boolean' then
971 file:write(tostring(a))
972 elseif type(a)=='string' then
973 file:write('"'..a..'"')
974 else
975 file:write('{')
976 for key,value in pairs(a) do
977 file:write('[')
978 tablestring(key)
979 file:write(']=')
980 tablestring(value)
981 file:write(',')
982 end
983 file:write('}')
984 end
985 end
986 file:write("pool=")
987 tablestring(pool)
988 file:close()
989end
990
991function loadPool(filename) --loads the pool from a file
992 loadfile(filename)()
993end
994
995function newGeneration() --runs the evolutionary algorithms to advance a generation
996 local levelname = currentWorld.."-"..currentLevel --name of level in the file name
997 if LostLevels == 1 then levelname = "LL"..currentWorld.."-"..currentLevel end
998 if pool.generation == 0 then --first gen so make the directory
999 os.execute("mkdir backups\\"..levelname)
1000 end
1001 savePool("backups/"..levelname.."/gen"..pool.generation..".lua")
1002
1003 local length = math.min(10,(5 + 2*pool.bottleneckloops)) --Length used for the population oscillation
1004 local targetPop = math.min(900,500 + 100*pool.bottleneckloops) --population to rise to
1005 local targetPopLower = math.min(300,600 - targetPop/2) --population to fall back down to
1006 local currentPopLower = math.min(300,targetPopLower+50) --population starting at
1007
1008 if pool.bottleneck <= TimeBeforePopOscil and pool.bottleneckloops == 0 then --decrease very rapidly after a bottleneck reset
1009 pool.population = math.max(math.floor(pool.population * 0.8),300)
1010 end
1011
1012 if pool.bottleneck > TimeBeforePopOscil then --perform the oscillation
1013 if pool.bottleneck < TimeBeforePopOscil + length then --increase up
1014 pool.population = currentPopLower + math.floor((targetPop - currentPopLower)/length*(pool.bottleneck-TimeBeforePopOscil))
1015 elseif pool.bottleneck == TimeBeforePopOscil + length then --peak
1016 pool.population = targetPop
1017 elseif pool.bottleneck <= TimeBeforePopOscil + length*3 then --go back down
1018 pool.population = targetPopLower + math.floor((targetPop - targetPopLower)/length/2*(TimeBeforePopOscil+length*3-pool.bottleneck))
1019 end
1020 if pool.bottleneck == TimeBeforePopOscil + length*3 then --go to next loop
1021 pool.bottleneck = 0
1022 pool.bottleneckloops = pool.bottleneckloops + 1
1023 end
1024 end
1025
1026 cullSpecies(false) --cut down each species
1027 rankGlobally() --rank all genomes
1028 removeStaleSpecies() --remove stale species
1029 calculatePoolStats() --find pool maxes avg/deviation
1030 for i,species in ipairs(pool.species) do --find the fitness rank for each species
1031 calculateFitnessRank(species)
1032 end
1033 removeWeakSpecies() --remove species without a good fitness rank
1034 local rankSum = totalPoolRank() --used to see how good a fitness rank is in comparison to everything
1035
1036 local children = {} --new genomes that will be added this generation
1037
1038 for i,species in ipairs(pool.species) do --generate children for each species
1039 local breed = math.floor(species.fitnessRank / rankSum * pool.population) - 1 --num of children to generate
1040 for j=1,breed do
1041 table.insert(children, breedChild(species))
1042 end
1043 end
1044
1045 local interbreedchance = BaseInterbreedChance --base interbreed chance
1046 if pool.generation > 5 then --do not reduce interbreed at very beginning, we want lots of it in first 5 gens
1047 --reduce interbreed when there are lots of species with low average size
1048 interbreedchance = interbreedchance * math.pow(1/InterbreedCutoff-math.min(1/InterbreedCutoff,#pool.species/pool.population),InterbreedDegree)
1049 end
1050
1051 local replacechance = interbreedchance/3 --replacements rarer than interbreeds
1052
1053 local minnetwork = 10000 --minimum number of networks a species eligible for a NS has
1054 local netspecies = {} --list of species eligible for a NS with min networks
1055
1056 for i,species in ipairs(pool.species) do --apply interbreed chance to each species
1057 if math.random() < interbreedchance then
1058 --pick which species to breed with, proportional to fitness rank
1059 local ibcheck = math.random() * rankSum
1060 for j,species2 in ipairs(pool.species) do
1061 ibcheck = ibcheck - species2.fitnessRank --reduce by the fitness rank
1062 if ibcheck < 0 then --the higher it is reduced by greater chance it goes below 0 this time
1063 table.insert(children, crossover(species.genomes[1],species2.genomes[1])) --interbreed
1064 break
1065 end
1066 end
1067 end
1068
1069 if math.random() < replacechance then
1070 --pick which species to get network from
1071 local species2 = pool.species[math.random(1,#pool.species)]
1072 table.insert(children, replaceNetwork(species.genomes[1],species2.genomes[1])) --replace network
1073 end
1074
1075 local numnet = #species.genomes[1].genes --number of networks
1076 if math.min(numnet,3) <= pool.bottleneckloops and numnet <= minnetwork and species.maxFitness + 150 > pool.maxFitness then
1077 if numnet < minnetwork then --even smaller network found, clear old array
1078 minnetwork = numnet
1079 netspecies = {}
1080 end
1081 table.insert(netspecies,species)
1082 end
1083 end
1084
1085 if #netspecies > 0 then --at least one species eligible for a network switch
1086 local species = netspecies[math.random(1,#netspecies)] --pick random eligible species
1087 local child = copyGenome(species.genomes[1]) --make a copy of best genome
1088 local switchloc = species.maxRightmost - 100 - 100*math.random() --pick a switch location
1089 table.insert(child.networkswitch,switchloc)
1090 table.insert(child.oscillations,{})
1091 for i=1,#InitialOscillations do
1092 child.oscillations[#child.oscillations][i] = InitialOscillations[i]
1093 end
1094 table.insert(child.genes,{})
1095
1096 table.insert(children,child) --add child
1097
1098 table.sort(child.networkswitch, function(a,b) --make sure network splits are in order
1099 return (a < b)
1100 end)
1101 end
1102
1103 while #children+#pool.species < pool.population do --fill from random species
1104 table.insert(children, breedChild(pool.species[math.random(1, #pool.species)]))
1105 end
1106
1107 cullSpecies(true) --remove all but the best of each species
1108
1109 for i,child in ipairs(children) do --add all children to species
1110 addToSpecies(child)
1111 end
1112
1113 for i,species in ipairs(pool.species) do --limit size of species
1114 local limit = pool.population/12 --cap on the species size
1115 if species.maxFitness >= pool.secondFitness then --if top two then larger cap
1116 limit = pool.population/7
1117 end
1118 while #species.genomes > limit do --remove
1119 table.remove(species.genomes)
1120 end
1121 end
1122
1123 pool.generation = pool.generation + 1
1124 pool.bottleneck = pool.bottleneck + 1
1125
1126 savePool("backups/current.lua")
1127end
1128
1129--Running the game
1130BonusArea = {[0]="WaterBonus",[2]="84WaterSection",[14]="SkyBonusA",[18]="WarpZone",[23]="SkyBonusB",[27]="UndergroundBonus",[34]="LL31Underground"}
1131
1132Ranges = { --special ranges in which there is a fitness function modification
1133 ["4-4 Level"]={
1134 {xrange={min=1512,max=1680},yrange={max=80},coeffs={x=1,y=0,c=112}},
1135 {xrange={min=1512,max=1680},yrange={min=80,max=144},coeffs={x=-1,y=0,c=3536}},
1136 {xrange={min=1512,max=1680},yrange={min=144},coeffs={x=1,y=0,c=576}},
1137 {xrange={min=1680},yrange={},coeffs={x=1,y=1,c=576}},
1138 {xrange={min=288,max=1200},yrange={min=80},coeffs={x=0,y=0,c=0}},
1139 {xrange={min=1680,max=2288},yrange={max=144},coeffs={x=0,y=0,c=0}}},
1140 ["8-4 Level"]={
1141 {xrange={min=2370,max=2460},yrange={},coeffs={x=0,y=1,c=2370}},
1142 {xrange={min=2460,max=2600},yrange={},coeffs={x=0,y=0,c=0}},
1143 {xrange={min=3678,max=3900},yrange={},coeffs={x=0,y=0,c=0}}},
1144 ["LL2-2 Level"]={
1145 {xrange={min=3000,max=3160},yrange={min=64,max=192},coeffs={x=0,y=0,c=0}}},
1146 ["LL3-1 Level"]={
1147 {xrange={min=3000},yrange={},coeffs={x=0,y=0,c=0}}},
1148 ["LL3-1 LL31Underground"]={
1149 {xrange={},yrange={},coeffs={x=0,y=0,c=0}}},
1150 ["LL3-4 Level"]={
1151 {xrange={min=1632,max=2384},yrange={min=96},coeffs={x=0,y=0,c=0}}},
1152 ["LL5-3 Level"]={
1153 {xrange={min=592,max=630},yrange={min=96},coeffs={x=0,y=0,c=0}},
1154 {xrange={min=630,max=1300},yrange={},coeffs={x=0,y=0,c=0}}},
1155 ["LL7-2 Level"]={
1156 {xrange={min=792,max=840},yrange={min=80},coeffs={x=0,y=0,c=0}},
1157 {xrange={min=840,max=1280},yrange={},coeffs={x=0,y=0,c=0}}},
1158 ["LL7-4 Level"]={
1159 {xrange={min=1278,max=1340},yrange={min=80,max=144},coeffs={x=-1,y=1,c=2660}},
1160 {xrange={min=1278,max=1340},yrange={max=80},coeffs={x=1,y=1,c=104}},
1161 {xrange={min=1340},yrange={},coeffs={x=1,y=1,c=104}},
1162 {xrange={min=1132},yrange={max=16},coeffs={x=0,y=0,c=0}}}
1163} --Currently missing Lost Levels World 8
1164
1165function fitness(fitstate) --Returns the distance into the level - the non-time component of fitness
1166 local coeffs={x=1,y=1,c=0} --position base coefficients
1167 local marioX = marioX
1168 local marioY = marioY
1169 local levelstring = "" --string to represent the level and subworld, to index Ranges
1170 if LostLevels == 1 then levelstring = "LL" end
1171 levelstring = levelstring .. currentWorld .. "-" .. currentLevel .. " " .. fitstate.area
1172 local ranges = Ranges[levelstring]
1173 if ranges ~= nil then
1174 for r=1,#ranges do --for each special fitness range
1175 local range = ranges[r]
1176 if (range.xrange.min == nil or marioX > range.xrange.min) and (range.xrange.max == nil or marioX <= range.xrange.max) then --in x range
1177 if (range.yrange.min == nil or marioY > range.yrange.min) and (range.yrange.max == nil or marioY <= range.yrange.max) then --in y range
1178 coeffs = range.coeffs --replace default coefficients
1179 end
1180 end
1181 end
1182 end
1183 fitstate.position = coeffs.x*marioX + coeffs.y*(192 - marioY) + coeffs.c --set the position value
1184 if mazeCPGoal > mazeCP then --missed a maze checkpoint
1185 fitstate.position = 0
1186 end
1187 if fitstate.laststate == 7 and marioState == 8 then --update offset
1188 fitstate.offset = fitstate.lastright + fitstate.offset - fitstate.position
1189 end
1190 if marioState == 8 then
1191 fitstate.lastright = fitstate.position --update lastright when in control of mario
1192 fitstate.timeout = fitstate.timeout + 1 --increase timeout
1193 if fitstate.timeout > 60 then --if has been idle for a second, start penalizing
1194 fitstate.timepenalty = fitstate.timepenalty + 1
1195 end
1196 else
1197 fitstate.position = fitstate.lastright --freeze position when not in control of mario
1198 end
1199 if marioState == 2 or marioState == 3 then
1200 local area = memory.readbyte(0x0750) --bonus area id
1201 if BonusArea[area%32 + LostLevels*32] ~= nil then
1202 fitstate.area = BonusArea[area%32 + LostLevels*32] --name of bonus room
1203 else
1204 fitstate.area = "Level"
1205 end
1206 end
1207 fitstate.hitblocks = hitblocks --transfer global hitblocks to local fitstate
1208 fitstate.laststate = marioState --set laststate
1209 if not (marioYVel > 0 and marioYVel < 10) and fitstate.position + fitstate.offset + (hitblocks+mazeCP)*60 > fitstate.rightmost then
1210 fitstate.timeout = math.max(0,fitstate.timeout + (fitstate.rightmost - fitstate.position - fitstate.offset - (hitblocks+mazeCP)*60)*2) --decrease timeout
1211 fitstate.rightmost = fitstate.position + fitstate.offset + (hitblocks+mazeCP)*60 --rightmost is maximum that the position+offset has ever been
1212 end
1213 fitstate.fitness = fitstate.rightmost - math.floor(fitstate.timepenalty / 2) --return fitness
1214end
1215
1216function playGenome(genome) --Run a genome through an attempt at the level
1217 savestate.load(savestateObj) --load savestate
1218 falseload = false
1219 while memory.readbyte(0x0787) == 0 do --wait until the game has fully loaded in mario
1220 emu.frameadvance() --coroutine.yield()
1221 falseload = true
1222 end
1223 if falseload then --move a savestate forward so that it doesn't have any load frames
1224 savestate.save(savestateObj)
1225 end
1226 local fitstate = genome.fitstate
1227 fitstate.frame = 0 --frame counter for the run
1228 fitstate.position = 0 --current position
1229 fitstate.rightmost = 0 --max it has gotten to the right
1230 fitstate.offset = 0 --offset to make sure fitness doesn't jump upon room change
1231 fitstate.lastright = 0 --last position before a transition.cancel()
1232 fitstate.laststate = 7 --mario's state the previous frame (to check state transitions)
1233 fitstate.fitness = 0 --current fitness score
1234 fitstate.timeout = 0 --time increasing when mario is idle, kills him if it is too much
1235 fitstate.timepenalty = 0 --number of frames mario was too idle, to subtract from his fitness
1236 fitstate.area = "Level"
1237
1238 hitblocks = 0 --number of hidden blocks it has hit
1239 hiddenblocks = {} --hidden blocks it has gotten near
1240
1241 getPositions()
1242 local nsw = 1 --network switch
1243 generateNetwork(genome) --generate the network
1244 local controller = {} --inputs to the controller
1245
1246 while true do --main game loop
1247 local manualControl = keyboardInput()
1248 --Get inputs to the network
1249
1250 if fitstate.frame % FramesPerEval == 0 then
1251 inputs = getInputs()
1252 --Find the current network to be using
1253 local nscompare = fitstate.rightmost
1254 nsw = 1
1255 for n=1,#genome.networkswitch do
1256 emu.print(genome.networkswitch[n])
1257 if nscompare > genome.networkswitch[n] then
1258 nsw = n+1
1259 end
1260 end
1261 --Put outputs to the controller
1262 controller = evaluateNetwork(genome.networks[nsw], inputs, genome.oscillations[nsw], fitstate.frame)
1263 else
1264 getPositions()
1265 end
1266
1267 if ManualInput then
1268 joypad.set(Player,manualControl)
1269 else
1270 joypad.set(Player,controller)
1271 end
1272
1273 fitness(fitstate) --update the fitness
1274
1275 --Display the GUI
1276 displayGUI(genome.networks[nsw], fitstate)
1277 --Advance a frame
1278 emu.frameadvance() --coroutine.yield()
1279 fitstate.frame = fitstate.frame + 1
1280
1281 --exit if dead or won
1282 if marioState == 11 or marioY > 256 then --if he dies to an enemy or a pit
1283 genome.networks = {} --reset networks to save on RAM
1284 pool.attempts = pool.attempts + 1
1285 pool.deaths = pool.deaths + 1
1286 pool.totalTime = pool.totalTime + fitstate.frame
1287 return false
1288 end
1289 if fitstate.timeout > fitstate.rightmost/3+120 and not ManualInput and timerCounter > 0 then --timeout threshold increases throughout level. kill if timeout is enabled and timer is not frozen
1290 genome.networks = {} --reset networks to save on RAM
1291 pool.attempts = pool.attempts + 1
1292 pool.totalTime = pool.totalTime + fitstate.frame
1293 return false
1294 end
1295 if prelevel == 1 then --if he beats the level
1296 genome.networks = {} --reset networks to save on RAM
1297 pool.attempts = pool.attempts + 1
1298 pool.totalTime = pool.totalTime + fitstate.frame
1299 return true
1300 end
1301 end
1302end
1303
1304function playSpecies(species,showBest) --Plays through all members of a species
1305 local startGenome = 2 --which genome to start showing from
1306 local oldBest = 0
1307 if showBest then
1308 startGenome = 1
1309 oldBest = species.maxFitness --used to make sure staleness does not reset every run if showBest is toggled on always
1310 species.maxFitness = 0
1311 species.maxRightmost = 0
1312 end
1313 for g=startGenome,#species.genomes do --loop through each genome
1314 local genome = species.genomes[g]
1315 pool.currentGenome = g
1316 local won = playGenome(genome) --test the genome
1317 if genome.fitstate.fitness + 30 > pool.maxFitness then --if it has gotten very close to the max
1318 pool.maxCounter = pool.maxCounter + 1
1319 end
1320 if genome.fitstate.fitness > pool.maxFitness then --if the fitness is the new best
1321 if species.gsid ~= pool.bestSpecies then --change the second best if the current best is a different species
1322 pool.secondFitness = pool.maxFitness
1323 end
1324 if genome.fitstate.fitness > pool.maxFitness + MajorBreakFitness or genome.fitstate.fitness > pool.lastMajorBreak + MajorBreakFitness*4 then
1325 --Counts as a major breakthrough
1326 pool.lastMajorBreak = genome.fitstate.fitness
1327 pool.bottleneck = 0
1328 pool.bottleneckloops = 0
1329 end
1330 pool.bestSpecies = species.gsid --update the best species number
1331 pool.maxFitness = genome.fitstate.fitness --update the best fitness
1332 elseif genome.fitstate.fitness > pool.secondFitness then --if the fitness is the new second best
1333 if species.gsid ~= pool.bestSpecies then --change the second best if this is not the current best species
1334 pool.secondFitness = genome.fitstate.fitness
1335 end
1336 end
1337 if genome.fitstate.fitness > species.maxFitness then --update the species max fitness
1338 species.maxFitness = genome.fitstate.fitness
1339 if genome.fitstate.fitness > oldBest then
1340 species.staleness = 0 --reset the staleness
1341 end
1342 end
1343 if genome.fitstate.rightmost > species.maxRightmost then --update the species max right
1344 species.maxRightmost = genome.fitstate.rightmost
1345 end
1346 if won then return true end --return true if we won
1347 end
1348 return false --return false otherwise
1349end
1350
1351function playGeneration(showBest) --Plays through the entire generation
1352 pool.maxCounter = 0
1353 for s=1,#pool.species do
1354 local species = pool.species[s]
1355 pool.currentSpecies = s
1356 if playSpecies(species,showBest) then
1357 return true
1358 end
1359 end
1360 return false
1361end
1362
1363--Drawing/GUI functions
1364function toRGBA(ARGB) --Converts to a color
1365 return bit.lshift(ARGB, 8) + bit.rshift(ARGB, 24)
1366end
1367
1368function drawbox(x,y,color) --Draws a hollow 16x16 box
1369 gui.drawbox(x,y,x+15,y,color,color)
1370 gui.drawbox(x,y,x,y+15,color,color)
1371 gui.drawbox(x+15,y,x+15,y+15,color,color)
1372 gui.drawbox(x,y+15,x+15,y+15,color,color)
1373end
1374
1375function fillbox(x,y,color,colorF) --Fills in a 16x16 box
1376 gui.drawbox(x,y,x+15,y+15,colorF,colorF)
1377 drawbox(x,y,text,color)
1378end
1379
1380keyFlag = false
1381function keyboardInput()
1382 local keyboard = input.get()
1383 if keyboard['N'] and keyFlag == false then --N toggles the network display
1384 DisplayNetwork = not DisplayNetwork
1385 end
1386 if keyboard['R'] and keyFlag == false then --R toggles the sprite hitboxes
1387 DisplaySprites = not DisplaySprites
1388 end
1389 if keyboard['G'] and keyFlag == false then --G toggles the large grid display
1390 DisplayGrid = (DisplayGrid + 1) % 3
1391 end
1392 if keyboard['O'] and keyFlag == false then --O toggles the sprite slots
1393 DisplaySlots = not DisplaySlots
1394 end
1395 if keyboard['A'] and keyFlag == false then --A toggles the top stats bar
1396 DisplayStats = not DisplayStats
1397 end
1398 if keyboard['M'] and keyFlag == false then --M toggles manual vs network input
1399 ManualInput = not ManualInput
1400 end
1401 if keyboard['E'] and keyFlag == false then --E toggles
1402 DisplayRanges = not DisplayRanges
1403 end
1404 if keyboard['C'] and keyFlag == false then --E toggles
1405 DisplayCounters = not DisplayCounters
1406 end
1407 local controller = {}
1408 controller['A'] = keyboard['F']
1409 controller['B'] = keyboard['D']
1410 controller['up'] = keyboard['up']
1411 controller['down'] = keyboard['down']
1412 controller['left'] = keyboard['left']
1413 controller['right'] = keyboard['right']
1414 if controller["up"] and controller["down"] then
1415 controller["up"] = false
1416 controller["down"] = false
1417 end
1418 if controller["left"] and controller["right"] then
1419 controller["left"] = false
1420 controller["right"] = false
1421 end
1422 local allkeys = {'N','R','G','O','A','M','E','C'}
1423 keyFlag = false --Set keyflag to true if any keys were pressed otherwise it is false
1424 for k=1,#allkeys do
1425 if keyboard[allkeys[k]] then
1426 keyFlag = true
1427 end
1428 end
1429 return controller
1430end
1431
1432function displayGUI(network, fitstate) --Displays various toggleable components of the GUI
1433 if DisplayGrid > 0 then --Display a large input grid around Mario's actual position
1434 --Loop through each position
1435 for x=-BoxRadius,BoxRadius do
1436 for y=-BoxRadius,BoxRadius do
1437 i = network.neurons[1+x+BoxRadius+(y+BoxRadius)*BoxWidth].value --Find the correct neuron of the inputs
1438 color = toRGBA(0x00000000) --Transparent color scheme
1439 if i == 1 then
1440 color = toRGBA(0xCF7F7F7F)
1441 end
1442 if i == -1 then
1443 color = toRGBA(0x7FFF3F00)
1444 end
1445 if DisplayGrid == 2 then --Solid color scheme
1446 color = toRGBA(0xFF000000)
1447 if i == 1 then
1448 color = toRGBA(0xFF7F7F7F)
1449 end
1450 if i == -1 then
1451 color = toRGBA(0xFFFF3F00)
1452 end
1453 end
1454 --Draw that box
1455 fillbox(marioScreenX+x*16,marioY+y*16 - 16,toRGBA(0xFFFFFFFF),color)
1456 end
1457 end
1458 end
1459 if DisplayRanges then --Display special fitness ranges
1460 local levelstring = "" --string to represent the level and subworld, to index Ranges
1461 if LostLevels == 1 then levelstring = "LL" end
1462 levelstring = levelstring .. currentWorld .. "-" .. currentLevel .. " " .. fitstate.area
1463 local ranges = Ranges[levelstring]
1464 if ranges ~= nil then
1465 for r=1,#ranges do
1466 --default values for parts without ranges
1467 local minx = 0
1468 local maxx = 65536
1469 local miny = 0
1470 local maxy = 240
1471 local range = ranges[r]
1472 --if range limits exist set the limits to those
1473 if range.xrange.min ~= nil then minx = range.xrange.min end
1474 if range.xrange.max ~= nil then maxx = range.xrange.max end
1475 if range.yrange.min ~= nil then miny = range.yrange.min+16 end
1476 if range.yrange.max ~= nil then maxy = range.yrange.max+16 end
1477
1478 --set color based on the direction of fitness increase
1479 local rgb = 0x003FFF
1480 textcolor = toRGBA(0xFFFFFFFF)
1481 if range.coeffs.x == 1 and range.coeffs.y == 1 then
1482 rgb=0x3FFF00
1483 textcolor = toRGBA(0xFF000000)
1484 end
1485 if range.coeffs.x == 0 and range.coeffs.y == 0 then
1486 rgb=0xFF3F00
1487 end
1488 if range.coeffs.y == -1 then
1489 rgb=0xFFBF00
1490 end
1491 if range.coeffs.x == -1 then
1492 rgb=0xBF7F00
1493 end
1494
1495 color = toRGBA(0xFF000000 + rgb)
1496 --draw the box
1497 gui.drawbox(minx-marioX+marioScreenX-1,miny,maxx-marioX+marioScreenX-2,maxy-1,toRGBA(0x7F000000 + rgb),toRGBA(0x7F000000 + rgb))
1498 gui.drawbox(minx-marioX+marioScreenX-1,miny,minx-marioX+marioScreenX-1,maxy-1,color,color)
1499 gui.drawbox(minx-marioX+marioScreenX-1,miny,maxx-marioX+marioScreenX-2,miny,color,color)
1500 gui.drawbox(minx-marioX+marioScreenX-1,maxy-1,maxx-marioX+marioScreenX-2,maxy-1,color,color)
1501 gui.drawbox(maxx-marioX+marioScreenX-2,miny,maxx-marioX+marioScreenX-2,maxy-1,color,color)
1502 --draw the numbers in the corners that show the fitness values
1503 local TLcorner = minx*range.coeffs.x+(208-miny)*range.coeffs.y+range.coeffs.c
1504 local TRcorner = maxx*range.coeffs.x+(208-miny)*range.coeffs.y+range.coeffs.c
1505 local BLcorner = minx*range.coeffs.x+(192-maxy)*range.coeffs.y+range.coeffs.c
1506 local BRcorner = maxx*range.coeffs.x+(192-maxy)*range.coeffs.y+range.coeffs.c
1507 gui.drawtext(minx-marioX+marioScreenX,miny+1,TLcorner,textcolor,color)
1508 gui.drawtext(maxx-marioX+marioScreenX-6*string.len(TRcorner)-1,miny+1,TRcorner,textcolor,color)
1509 gui.drawtext(minx-marioX+marioScreenX,maxy-8,BLcorner,textcolor,color)
1510 gui.drawtext(maxx-marioX+marioScreenX-6*string.len(TRcorner)-1,maxy-8,BRcorner,textcolor,color)
1511 end
1512 end
1513 end
1514
1515 if DisplaySprites then --Display a hitbox around each sprite
1516 for s=1,#spriteHitboxes do
1517 local ex = spriteHitboxes[s]["x"]
1518 local ey = spriteHitboxes[s]["y"]
1519 local typ = spriteHitboxes[s]["t"]
1520 local data = spriteHitboxes[s]["d"]
1521 --Draw hitbox
1522 drawbox(ex-marioX+marioScreenX,ey-28,toRGBA(0xFF7F3F00))
1523 if typ ~= nil then --If original sprite and not an enlarged box, also put sprite data
1524 gui.drawtext(ex-marioX+marioScreenX,ey-28,data,-1)
1525 gui.drawtext(ex-marioX+marioScreenX,ey-20,typ,-1)
1526 end
1527 end
1528 --Draw a box around Mario
1529 drawbox(marioScreenX,marioY,toRGBA(0xFF00FF00))
1530 end
1531 if DisplaySlots then --Display what type of sprite is in each slot
1532 for slot=0,5 do
1533 gui.drawtext(230,159+slot*12,slot,-1) --Draw the slot number
1534 local typ = spriteSlots[slot]
1535 if typ ~= nil then --If the slot is full put the sprite ID
1536 local typstr = tostring(typ)
1537 if typ < 10 then --If single digit put a 0 in front
1538 typstr = "0" .. typstr
1539 end
1540 gui.drawtext(240,159+slot*12,typstr,-1)
1541 else --If the slot is empty put XX
1542 gui.drawtext(240,159+slot*12,"XX",-1)
1543 end
1544 end
1545 gui.drawtext(230,219,"*",-1) --Slot 5 is a special slot so put a *
1546 end
1547 if DisplayNetwork then --Display the neural network state
1548 local neurons = {} --Array that will contain the position and value of each displayed neuron
1549 local i = 1
1550 for dy=-BoxRadius,BoxRadius do --Add the input box neurons
1551 for dx=-BoxRadius,BoxRadius do
1552 network.neurons[i].x = 20+5*(dx+BoxRadius)
1553 network.neurons[i].y = 40+5*(dy+BoxRadius)
1554 i = i + 1
1555 end
1556 end
1557
1558 local botRowSpacing = 10 --Number of pixels between oscillating nodes
1559
1560 local botRowSize = (3 + #InitialOscillations)*2 - 1
1561 if botRowSize > BoxWidth then
1562 botRowSpacing = 5 --If bottom row can't fit then have no spacing
1563 end
1564
1565 for j=0,#InitialOscillations do --Add the bias and oscillation neurons
1566 network.neurons[i].x = 15+BoxWidth*5-botRowSpacing*j
1567 network.neurons[i].y = 45+BoxWidth*5
1568 i = i + 1
1569 end
1570
1571 for j=0,1 do --Add the bias and oscillation neurons
1572 network.neurons[i].x = 20+botRowSpacing*j
1573 network.neurons[i].y = 45+BoxWidth*5
1574 i = i + 1
1575 end
1576
1577 for l=1,#network.layers do --Draw each layer in the NN
1578 local layer = network.layers[l]
1579 for n=1,#layer do
1580 if l > 1 or layer[n] >= Inputs then --display only non-inputs in layer 1
1581 network.neurons[layer[n]].x = math.ceil(15+BoxWidth*5+(216-BoxWidth*5)*((l-1) / (#network.layers))-0.5)
1582 network.neurons[layer[n]].y = math.ceil(35+(BoxWidth+3)*5*(n / (#layer+1))-0.5)
1583 end
1584 end
1585 end
1586
1587 --Orange box to surround the input box area
1588 gui.drawbox(17,37,18+BoxWidth*5,37,toRGBA(0xFF000000),toRGBA(0xFF000000))
1589 gui.drawbox(17,37,17,38+BoxWidth*5,toRGBA(0xFF000000),toRGBA(0xFF000000))
1590 gui.drawbox(18+BoxWidth*5,37,18+BoxWidth*5,38+BoxWidth*5,toRGBA(0xFF000000),toRGBA(0xFF000000))
1591 gui.drawbox(17,38+BoxWidth*5,18+BoxWidth*5,38+BoxWidth*5,toRGBA(0xFF000000),toRGBA(0xFF000000))
1592 gui.drawbox(17,37,18+BoxWidth*5,38+BoxWidth*5,toRGBA(0x7FFF7F00),toRGBA(0x7FFF7F00))
1593 for n,neuron in pairs(network.neurons) do --Draw each neuron
1594 color = math.floor((neuron.value+1)/2*256) --Color from white to black to represent -1 to 1
1595 if color > 255 then color = 255 end
1596 if color < 0 then color = 0 end
1597 local opacity = 0x1000000*math.floor(math.abs(neuron.value)*255) --More transparent closer to 0
1598 inversecolor = (color - 255) * (-1) --Opposite color for the border
1599 bordercolor = 0xAF000000 + inversecolor*0x10000 + inversecolor*0x100 + inversecolor
1600 color = opacity + color*0x10101
1601 if neuron.x == nil then
1602 neuron.x = 0
1603 neuron.y = 0
1604 end
1605 if n < 0 or n > BoxSize or neuron.value ~= 0 then --Don't draw an unfilled border for input box
1606 gui.drawbox(neuron.x-2,neuron.y-2,neuron.x+2,neuron.y+2,toRGBA(bordercolor),toRGBA(bordercolor)) --Draw border
1607 end
1608 gui.drawbox(neuron.x-1,neuron.y-1,neuron.x+1,neuron.y+1,toRGBA(color),toRGBA(color)) --Draw interior
1609 --Draw each gene coming into that neuron
1610 for g,gene in pairs(neuron.incoming) do
1611 if gene.enabled then
1612 local n1 = network.neurons[gene.into]
1613 local layerdiff = neuron.layer - n1.layer
1614 --Green or red for positive or negative weight
1615 local color = 0x3FFF00
1616 if gene.weight < 0 then
1617 color = 0xFF3F00
1618 end
1619 local opacity = 0xFF000000
1620 if gene.into > BoxSize and gene.into <= Inputs then --fade or remove if bottom-row neuron
1621 if #network.genes > 100 then
1622 opacity = 0x00000000
1623 elseif #network.genes > 50 then
1624 opacity = 0x7F000000
1625 end
1626 end
1627 if n1.value == 0 then --fade or remove if not transmitting a value
1628 if #network.genes > 50 then
1629 opacity = 0x00000000
1630 else
1631 opacity = 0x7F000000
1632 end
1633 end
1634 --draw the genome
1635 color = opacity + color
1636 gui.drawline(n1.x+2,n1.y,neuron.x-2,neuron.y, toRGBA(color))
1637 end
1638 end
1639 end
1640 --When to block opposite directional inputs
1641 local blockUD = network.neurons[-3] ~= nil and network.neurons[-4] ~= nil and network.neurons[-3].value>0 and network.neurons[-4].value>0
1642 local blockLR = network.neurons[-5] ~= nil and network.neurons[-6] ~= nil and network.neurons[-5].value>0 and network.neurons[-6].value>0
1643 for o = 1,6 do
1644 local x = 230
1645 local y = math.ceil(35+(BoxWidth+3)*5*(o / 7)-0.5)
1646 local neuron = network.neurons[-o]
1647 local blocked = false --if the input was blocked because of an opposite directional input
1648 if blockUD and (o == 3 or o == 4) then blocked = true end
1649 if blockLR and (o == 5 or o == 6) then blocked = true end
1650 --pick the colors
1651 if neuron == nil or neuron.value <= 0 then
1652 color = 0xFF000000
1653 bordercolor = 0xAFFFFFFF
1654 tcolor = 0xFF777777
1655 elseif blocked then
1656 color = 0xFFFF3F00
1657 bordercolor = 0xAF000000
1658 tcolor = 0xFF777777
1659 else
1660 color = 0xFFFFFFFF
1661 bordercolor = 0xAF000000
1662 tcolor = 0xFF0000FF
1663 end
1664 gui.drawbox(x-2,y-2,x+2,y+2,toRGBA(bordercolor),toRGBA(bordercolor)) --Draw border
1665 gui.drawbox(x-1,y-1,x+1,y+1,toRGBA(color),toRGBA(color)) --Draw interior
1666 gui.drawtext(x+5, y-4, string.sub(ButtonNames[o],1,1), toRGBA(tcolor), 0x0) --Draw the first letter of the button name
1667 if neuron ~= nil then
1668 if neuron.value == 0 then --draw line between output and ouput box.
1669 gui.drawline(neuron.x+2,neuron.y,x-2,y,toRGBA(0x7F3FFF00)) --fade if not sending anything
1670 else
1671 gui.drawline(neuron.x+2,neuron.y,x-2,y,toRGBA(0xFF3FFF00))
1672 end
1673 end
1674 end
1675 end
1676 if DisplayStats then
1677 local completed = pool.currentGenome
1678 local total = 0
1679 for s=1,#pool.species do
1680 total = total + #pool.species[s].genomes
1681 if s < pool.currentSpecies then
1682 completed = completed + #pool.species[s].genomes
1683 end
1684 end
1685 local backgroundColor = toRGBA(0xFFFFFFFF)
1686 gui.drawbox(0, 0, 260, 31, backgroundColor, backgroundColor)
1687 gui.drawtext(5, 11, "Gen " .. pool.generation .. " species " .. pool.currentSpecies .. " genome " .. pool.currentGenome .. " (" .. math.floor(completed/total*100) .. "%) ".. "Pop: ".. total, toRGBA(0xFF000000), 0x0)
1688 gui.drawtext(5, 21, "Fitness: " .. fitstate.fitness , toRGBA(0xFF000000), 0x0) --the (timeout + timeoutBonus)*2/3 makes sure that the displayed fitness remains stable for the viewers pleasure
1689 gui.drawtext(80, 21, "Max Fitness: " .. pool.maxFitness .. " (".. pool.secondFitness .. ") (" .. pool.maxCounter .. "x)", toRGBA(0xFF000000), 0x0)
1690 end
1691 if DisplayCounters then
1692 local days = math.floor(pool.totalTime/86400/50)
1693 local hours = math.floor((pool.totalTime-days*86400*50)/3600/50)
1694 local minutes = math.floor((pool.totalTime-(days*24+hours)*3600*50)/60/50)
1695 gui.drawtext(5,210,"Time: "..days.."d "..hours.."h "..minutes.."m",-1)
1696 gui.drawtext(5,220,"Deaths: "..pool.deaths,-1)
1697 gui.drawtext(5,230,"Attempts: "..pool.attempts,-1)
1698 end
1699end
1700--loadPool("backups/current.lua")
1701initPool()
1702for g=1,1000 do
1703 local genome = newGenome(1)
1704 mutate(genome,1)
1705 addToSpecies(genome)
1706end
1707while true do
1708 while not playGeneration(true) do
1709 newGeneration()
1710 end
1711 savestate.save(savestateObj)
1712 initPool()
1713 for g=1,1000 do
1714 local genome = newGenome(1)
1715 mutate(genome,1)
1716 addToSpecies(genome)
1717 end
1718end
1719
1720return true