· 5 years ago · Apr 11, 2020, 11:10 AM
1require "drawlib"
2require "drawtext"
3
4levelname = "1-1"
5
6savestateSlotMap = 1 --savestate slot to save the map selection from.
7savestateSlotLevel = 2
8savestateSlotBOSS = 3
9savestateObj = savestate.object(savestateSlotMap)
10savestate.load(savestateObj)
11LM = memory.readbyte(0x0726)
12Player = 1 + LM
13
14BoxRadius = 6 --The radius of the vision box around Luigi
15InitialOscillations = {50,60,90} --Initial set of oscillation timers
16BoxWidth = BoxRadius*2+1 --Full width of the box
17BoxSize = BoxWidth*BoxWidth --Number of neurons in the box
18Inputs = BoxSize + 3 + #InitialOscillations
19
20InitialMutationRates = {
21 linkInputBox=0.1,
22 linkBottomRow=0.05,
23 linkLayers=0.1,
24 linkRandom=2.0,
25 node=0.5,
26 disable=0.4,
27 enable=0.2,
28 oscillation=0.2,
29 nswitch=0.01,
30 step=0.1
31}
32
33FramesPerEval = 5 --Number of frames between each network update
34
35ButtonNames = {"A","B","up","down","left","right"}
36ButtonNumbers = {A=1,B=2,up=3,down=4,left=5,right=6}
37
38DeltaDisjoint = 4.0 --multiplier for disjoint in same species function
39DeltaWeights = 0.4 --multiplier for weights in same species function
40DeltaThreshold = 1.0 --threshold for delta to be in the same species
41DeltaSwitch = 0.6 --threshold for delta of the network switch location
42
43DisplaySlots = false --Display the sprite slots?
44DisplaySprites = false --Display the sprite hitboxes?
45DisplayGrid = 0 --Display a large network inputs grid?
46DisplayNetwork = true --Display the neural network state?
47DisplayStats = true --Display top stats bar?
48DisplayRanges = false --Display special fitness ranges?
49DisplayCounters = false --Display death/time counter?
50ScrollWalkPause = false
51
52Replay = false
53
54ManualInput = false --Input manually rather than by network?
55
56--Penalty coeffs for fitness rank of netswitched species
57NetworkPenaltyInner = 0.8
58NetworkPenaltyOuter = 0.87
59
60CrossoverChance = 0.75 --Chance of crossing 2 genomes rather than copying 1
61
62MajorBreakFitness = 30 --Fitness before a breakthrough is not counted as minor
63
64BaseInterbreedChance = 0.07 --base interbreed chance per species
65InterbreedCutoff = 2.86 --average species size when interbreed is turned off
66InterbreedDegree = 1.0 --degree of interbreed curve
67
68TimeBeforePopOscil = 6 --time in each oscillation before the population changes
69
70MaxStaleness = 25 --max staleness before death
71
72FramesOfDeathAnimation = 50 --amount of the death animation to show
73
74WeightPerturbChance = 0.9
75WeightResetChance = 0.9
76
77MaxNodes = 1000000
78
79FPS = 60
80
81TurboMin = 0
82TurboMax = 0
83CompleteAutoTurbo = false
84currentTurbo = false
85emu.speedmode("normal")
86marioAutoscroll = 0
87basescroll = 0
88marioWhiteblock = 0
89boss = false
90battle = false
91basetimeout = 120
92roottimeout = 120
93preloaded = false
94cardDistance = 9999
95powerupFitness = 150
96roottimeoutboss = 900
97
98dirsep = "/" --forward or backward slash for file separation? depends on OS
99
100ProgramStartTime = os.time()
101Startlocations={["0-1-2"]=true,["1-1-5"]=true,["2-1-5"]=true,["3-1-2"]=true,["4-1-4"]=true,["5-1-3"]=true,["6-1-1.5"]=true,["7-1-2.5"]=true}
102
103marioPrevWhiteblock = 0
104marioPrevAutoscroll = 0
105scrollBonus = 0
106scrollBonusAdjust = 0
107lockBound = false
108function getPositions(initial)
109 local marioHiX = memory.readbyte(0x0075)
110 local marioLoX = memory.readbyte(0x0090)
111 marioX = marioLoX+256*marioHiX
112 marioScreenX = memory.readbyte(0x00AB)
113 marioXVel = memory.readbyte(0x00BD)
114 marioY = 400-(memory.readbyte(0x00A2)+256*memory.readbyte(0x0087))
115 marioScreenY = memory.readbyte(0xB4)--memory.readbyte(0x0228)
116 marioYVel = memory.readbyte(0x00CF)
117 marioScore = memory.readbyte(0x0716)*2560 + memory.readbyte(0x0717)*10
118 marioActive = memory.readbyte(0x00CE) == 0 and memory.readbyte(0x03DE) == 0 --loading and using pipe/door
119 local marioWhiteblock = memory.readbyte(0x0570)
120 local marioAutoscroll = memory.readbyte(0x7A0C)
121 --print(marioScreenY)
122 --local marioAutoscrollY = memory.readbyte(0x7A0D)
123 marioHold = memory.readbyte(0x06A4)
124 marioPower = memory.readbyte(0x00ED)
125 marioMusic = memory.readbyte(0x04E5)
126 koopalingDefeated = memory.readbyte(0x07BD)
127 kingconvo = memory.readbyte(0x0728)
128 local whiteblockAdjustment = 0
129 if (not marioActive and marioScreenY > 184) or doorflag then
130 doorflag = true
131 marioOutOfBound = false
132 if marioActive and marioScreenY < 184 then
133 doorflag = false
134 end
135 else
136 marioOutOfBound = (marioY < 0 or (marioScreenY > 184 and memory.readbyte(0x0544) == 0 and cardDistance > 0.5)) and marioActive -- byte is for above screen
137 end
138 if marioPrevWhiteblock >100 and marioWhiteblock < -100 then
139 whiteblockAdjustment = 256
140 else
141 whiteblockAdjustment = 0
142 end
143 marioPrevWhiteblock = marioWhiteblock
144 WhiteblockBonus = whiteblockAdjustment + marioWhiteblock*10
145 if scrollBonus == 0 and not marioActive then
146 -- scrollBonusAdjust = -marioAutoscroll
147 memory.writebyte(0x7A0C,0)
148 --basemarioA
149 local marioAutoscroll = 0
150 end
151 if marioAutoscroll == 0 and marioPrevAutoscroll ~= 0 and scrollBonus > 0 then
152 basescroll = basescroll + marioPrevAutoscroll
153 -- elseif marioAutoscroll == marioPrevAutoscroll and scrollBonus > 0 then
154 -- scrollBonus = 0
155 -- basescroll = 0
156 end
157 scrollBonus = basescroll + marioAutoscroll + scrollBonusAdjust
158 marioPrevAutoscroll = marioAutoscroll
159 local numSprites = 6
160 sprites = {}
161 for s=1,numSprites do
162 local sprite = {}
163 sprite.x = memory.readbyte(0x0090+s)+256*memory.readbyte(0x0075+s)
164 sprite.y = 416-(memory.readbyte(0x00A2+s)+256*memory.readbyte(0x0087+s))
165 sprite.state = memory.readbyte(0x0660+s)
166 sprite.type = memory.readbyte(0x670+s)
167 if sprite.type == 54 then
168 table.insert(sprites,{x=sprite.x+16,y=sprite.y,state=256})
169 table.insert(sprites,{x=sprite.x+32,y=sprite.y,state=256})
170 --print(sprite)
171 end
172 if sprite.type == 14 then
173 marioScore = marioScore + memory.readbyte(0x0083)*1000
174 end
175 if sprite.type == 74 or sprite.type == 82 then --added this because luigi does not want to pick up the ball once he defeats boomboom
176 if sprite.x - marioX > 0 then
177 for a=1,30 do
178 joypad.set(Player,{right=true})
179
180 coroutine.yield()
181 end
182 else
183 for a=1,30 do
184 joypad.set(Player,{left=true})
185 coroutine.yield()
186 end
187 end
188 end
189 table.insert(sprites,sprite)
190 end
191 for s=1,8 do --special sprites
192 local sprite = {}
193 local id = memory.readbyte(0x7FC5+s)
194 if id ~= 0 then
195 sprite.type = id
196 sprite.y = 416-memory.readbyte(0x05BE+s)+256*memory.readbyte(0x7FD4+s)
197 local xlow = memory.readbyte(0x05C8+s)
198 local xhigh = 0
199 local differ = xlow-marioLoX
200 if differ > 128 then
201 xhigh = marioHiX -1
202 elseif differ < -128 then
203 xhigh = marioHiX +1
204 else
205 xhigh = marioHiX
206 end
207 sprite.x = xlow + 256*xhigh
208 sprite.state=256
209 table.insert(sprites,sprite)
210 end
211 end
212end
213
214function getTile(dx,dy)
215 local x = math.floor((marioX + dx + 8)/16)
216 local y = math.floor((432-marioY + dy - 8)/16)
217 local page = math.floor(x/16)
218 local px = x % 16
219 local tile = memory.readbyte(0x6000+px+y*16+page*432)
220 --print(x .. " " .. marioScreenX/16)
221 local scrollX = (marioX-marioScreenX)/16
222 local scrollY = (marioY+marioScreenY-161)/16
223 if x < scrollX or x > scrollX + 16 or 26-y < scrollY or 26-y > scrollY + 11 then return 0 end
224 return tile
225end
226
227CollisionTileSet = {}
228-- CollisionTileArrays[1]={air=128,0x25,0x26,0x27,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x5F,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0xA0,0xA1,0xA2,0xAD,0xAE,0xAF,0xB1,0xB2,0xB3,0xB4,0xB5,0xB5,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xE2,0xE3,0xE4,0xF0,0xF1,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9}
229-- CollisionTileArrays[3]={air=134,0x2E,0x2F,0x30,0x31,0x32,0x48,0x5F,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x87,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xF0,0xF1,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9}
230-- CollisionTileArrays[4]={air=128,0x11,0x12,0x13,0x22,0x23,0x24,0x25,0x2C,0x2E,0x2F,0x30,0x31,0x32,0x34,0x35,0x36,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0x53,0x54,0x55,0x5B,0x5F,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8E,0x8F,0x90,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xFA,0xFB}
231CollisionTileArray= {0x80,0x02,0x86,0x80,0x80,0x8C,0x42,0x80,0x80,0x80,0x80,0x80,0xCC,0x86,0x09,0x09,0x09,0x02}
232
233function getTileset()
234 local tileset = memory.readbyte(0x070A)
235 local x1a = memory.readbyte(0x7E94)
236 local x1b = memory.readbyte(0x7E95)
237 local x1c = memory.readbyte(0x7E96)
238 local x1d = memory.readbyte(0x7E97)
239 card_x = nil
240 for j=1,48 do
241 local id = memory.readbyte(0x07B3E+(3*j))
242 if id == 0x41 then --0x41 is end card
243
244 card_x = memory.readbyte(0x07B3E+(3*j)+1)*16
245 card_y = memory.readbyte(0x07B3E+(3*j)+2)*16
246 end
247 if id == 0xFF then --ff indicates last entry
248 if card_x == nil then
249 card_x = 9999
250 card_y = 9999
251 end
252 break
253 end
254 end
255 CollisionTileSet = {a=x1a,b=x1b,c=x1c,d=x1d}
256 CollisionTileSet.air=CollisionTileArray[tileset]
257end
258
259function getInputs() --Create the grid around Mario that is the network's vision
260 getPositions() --Get all required memory data
261 local inputs = {} --Grid around Mario that shows blocks/sprites
262 --Loop through each space in the grid
263 local BoxRadius = BoxRadius
264 local BoxWidth = BoxWidth
265 local CollisionTileSet = CollisionTileSet
266 local marioX = marioX
267 local marioY = marioY
268 local sprites = sprites
269 for dy=-BoxRadius*16,BoxRadius*16,16 do
270 for dx=-BoxRadius*16,BoxRadius*16,16 do
271 inputs[#inputs+1] = 0
272 --If the tile contains a block, set to 1
273 local tile = getTile(dx, dy)
274 if tile ~= CollisionTileSet.air and marioY+dy < 0x1B0 then
275 if (tile >= CollisionTileSet.a and tile < 0x40) or (tile >= CollisionTileSet.b and tile < 0x80) or (tile >= CollisionTileSet.c and tile < 0xC0) or (tile >= CollisionTileSet.d and tile < 0xFF) then inputs[#inputs] = 1
276 else inputs[#inputs] = 0.5
277 end
278 end
279 end
280 end
281 --for each sprite set it's location to -1
282 local spriteNearlocal = false
283 for i,sprite in ipairs(sprites) do
284 if sprite.state ~= 0 then
285 local distx = math.floor((sprite.x - marioX)/16 + 0.5)
286 if math.abs(distx) < 3.5 then
287 spriteNearlocal = true
288 end
289 local disty = math.floor((-sprite.y + marioY)/16 + 0.5)
290 if math.abs(distx)<=BoxRadius and math.abs(disty)<=BoxRadius then
291 inputs[distx+BoxRadius+1+(disty+BoxRadius)*BoxWidth] = -1
292 end
293 end
294 end
295 spriteNear = spriteNearlocal
296 return inputs
297end
298
299--Initialization for the genetic system
300function initPool(Boss) --The pool contains all data for the genetics of the AI
301 local poolold = pool
302 pool = {}
303 pool.species = {} --List of species
304 pool.generation = 0 --Generation number
305 pool.innovation = 1 --Innovation tracks genes that are copies of each other vs unique
306 pool.currentGenome = 1 --current genome/species to display on top bar
307 pool.currentSpecies = 1
308 pool.maxFitness = 0 --Maximum fitness
309 pool.bestSpecies = 1 --best species
310 pool.secondFitness = 0 --Second best fitness
311 pool.maxCounter = 0 --Number of times it has gotten close to max fitness
312 pool.gsid = 0 --GSID tracks species so each one gets a unique id
313 pool.bottleneck = 0 --Number of gens since last breakthrough
314 pool.bottleneckloops = 0 --Number of oscillation loops in the bottleneck
315 pool.population = 999 --Number of genomes
316 pool.lastMajorBreak = 0 --Last breakthrough of more than majorBreakFitness
317 pool.current = 0
318 pool.total = 0
319 pool.average = 0
320 pool.lastbreaktime = 0
321 if not Boss then
322 pool.attempts = 1 --number of attempts
323 pool.deaths = 0 --number of deaths (auto timeouts do not count)
324 pool.totalTime = 0 --total time elapsed in-game
325 pool.realTime = 0 --total time elapsed out of game
326
327 pool.history = "" --breakthrough tracker
328 pool.breakthroughX = 0 --indicator stuff
329 pool.breakthroughZ = ""
330 pool.maphistogram = {}
331 pool.breakthroughfiles = {}
332 else
333 pool.attempts = poolold.attempts
334 pool.deaths = poolold.deaths
335 pool.totalTime = poolold.totalTime
336 pool.realTime = poolold.realTime
337 pool.history = poolold.history
338 pool.breakthroughX = poolold.breakthroughX
339 pool.breakthroughZ = poolold.breakthroughZ
340 pool.maphistogram = poolold.maphistogram
341 pool.breakthroughfiles = poolold.breakthroughfiles
342 end
343end
344
345function newSpecies() --Each species is a group of genomes that are similar
346 local species = {}
347 species.maxFitness = 0 --farthest the species has gotten
348 species.maxRightmost = 0 --Farthest the species has gotten, ignoring time
349 species.averageFitness = 0 --Average fitness of the species
350 species.staleness = 0 --Number of gens since the species improved
351 species.genomes = {} --List of genomes
352 species.nick = '' --nickname of the species
353 species.turbo = true --whether the species will use turbo or not
354 species.gsid = pool.gsid --give the species a unique GSID
355 pool.gsid = pool.gsid + 1
356 species.breakthroughX = 0 --indicator stuff
357 species.breakthroughZ = ""
358 return species
359end
360
361function newGene() --A gene is a link between two neurons
362 local gene = {}
363 gene.into = 0 --input neuron
364 gene.out = 0 --output neuron
365 gene.weight = 0.0 --multiplier
366 gene.enabled = true --gene is turned on
367 gene.innovation = 0 --gene ID to find duplicates
368 gene.delay = 0
369 return gene
370end
371
372function copyGene(gene) --copy a gene
373 local newGene = {}
374 newGene.into = gene.into
375 newGene.out = gene.out
376 newGene.weight = gene.weight
377 newGene.enabled = gene.enabled
378 newGene.innovation = gene.innovation
379 newGene.delay = gene.delay
380 return newGene
381end
382
383function newGenome(numNetworks) --Each genome is an evolved 'player' that attempts the level
384 local genome = {}
385 genome.genes = {} --List of networks in raw gene form
386 genome.networks = {} --List of networks in neuron form
387 genome.oscillations = {} --Periods of each of 3 oscillating nodes
388 genome.maxNeuron = Inputs --Index of largest neuron
389 for n=1,numNetworks do --Initialize these as blank arrays for each network
390 genome.genes[n] = {}
391 genome.oscillations[n] = {}
392 for i=1,#InitialOscillations do --Initialize periods to default values
393 genome.oscillations[1][i] = InitialOscillations[i]
394 end
395 end
396 genome.fitstate = {} --information about the genome's final state
397 genome.globalRank = 0 --Ranking of the genome (low is worse)
398 genome.networkswitch = {} --List of positions where the network switches
399 for n=1,numNetworks-1 do
400 genome.networkswitch[n] = 0.0
401 end
402 genome.mutationRates = {} --List of mutation rates
403 for name,rate in pairs(InitialMutationRates) do
404 genome.mutationRates[name] = rate
405 end
406 return genome
407end
408
409function copyGenome(genome) --copy a genome
410 local ngenome = newGenome(#genome.genes) --create basic new genome
411 for n=1,#genome.genes do --copy each network over
412 for i,gene in pairs(genome.genes[n]) do
413 table.insert(ngenome.genes[n],copyGene(gene))
414 end
415 for i=1,#genome.oscillations[n] do
416 ngenome.oscillations[n][i] = genome.oscillations[n][i]
417 end
418 end
419 ngenome.maxNeuron = genome.maxNeuron
420 for n=1,#genome.genes-1 do
421 ngenome.networkswitch[n] = genome.networkswitch[n]
422 end
423 for name,rate in pairs(genome.mutationRates) do
424 ngenome.mutationRates[name] = rate
425 end
426 return ngenome
427end
428
429function newNeuron() --A neuron is a stored value that is calculated based on inputs and broadcasts to its outputs
430 local neuron = {}
431 neuron.incoming = {} --list of incoming genes
432 neuron.queues = {} -- incoming queues for delay
433 neuron.value = 0.0 --current value
434 neuron.layer = 0 --What layer the neuron is in (0 means it has not been calculated)
435 return neuron
436end
437
438--Evaluation of networks
439function generateNetwork(genome)
440 for n=1,#genome.genes do --For each network
441 local neurons = {}
442 for i=1,Inputs do --Put in input nodes
443 neurons[i] = newNeuron()
444 end
445 for g=1,#genome.genes[n] do --Loop through each gene
446 local gene = genome.genes[n][g]
447 if gene.enabled then --Only add enabled genes
448 --If either side of gene is not added to neurons yet then add it
449 if neurons[gene.into] == nil then
450 neurons[gene.into] = newNeuron()
451 end
452 if neurons[gene.out] == nil then
453 neurons[gene.out] = newNeuron()
454 end
455 if gene.into > 0 then
456 table.insert(neurons[gene.out].incoming,gene) --Add gene to the output neuron's incoming
457 queue = {}
458 for i=1,gene.delay do
459 table.insert(queue,0)
460 end
461 table.insert(neurons[gene.out].queues,queue)
462 end
463 end
464 end
465
466 local layers = {}
467 while true do --loop until all layers found
468 local layer = {}
469 local finished = true --if all have been sorted
470 for i,neuron in pairs(neurons) do --loop through and find all possible for calculation
471 if neuron.layer == 0 then --Dont re-check already placed neurons
472 local possible = true
473 for j,gene in pairs(neuron.incoming) do --see if any incoming genes are not calculated yet
474 if neurons[gene.into].layer == 0 then
475 possible = false
476 break
477 end
478 end
479 if possible then --if has enough information to evaluate
480 table.insert(layer,i)
481 finished = false --a neuron was placed so it has not been all sorted yet
482 end
483 end
484 end
485 if finished then --exit the loop
486 break
487 end
488 for i=1,#layer do --For each neuron in the new layer set it to now be sorted
489 neurons[layer[i]].layer = #layers+1
490 end
491 table.insert(layers,layer)
492 end
493 local network = {}
494 network.neurons = neurons
495 network.layers = layers
496 network.genes = genome.genes[n]
497 genome.networks[n] = network
498 end
499end
500
501
502function sigmoid(x)
503 return 2/(1+math.exp(-4.9*x))-1
504end
505
506
507function evaluateNetwork(network, inputs, oscillating, frame)
508 table.insert(inputs,1.0) --Bias node
509 local marioXVel = marioXVel
510 local marioYVel = marioYVel
511 local ScrollWalkPauselocal = ScrollWalkPause
512 local marioScreenX = marioScreenX
513 local endscroll = endscroll
514 local spriteNear = spriteNear
515 local ButtonNames = ButtonNames
516 local scrollBonus = scrollBonus
517 local Inputs = Inputs
518 local mmax = math.max
519 for i=1,#oscillating do --Oscillating nodes
520 if frame % (oscillating[i]*2) < oscillating[i] then
521 table.insert(inputs,1.0)
522 else
523 table.insert(inputs,-1.0)
524 end
525 end
526 --Speed nodes
527 if (marioXVel < 100) then
528 table.insert(inputs,marioXVel / 56)
529 else
530 table.insert(inputs,(marioXVel - 256) / 56)
531 end
532 if (marioYVel < 70) then
533 table.insert(inputs,marioYVel / 69)
534 else
535 table.insert(inputs,mmax((marioYVel - 256) / 69,1))
536 end
537
538 if #inputs ~= Inputs then
539 emu.print("Incorrect number of neural network inputs.")
540 return {}
541 end
542
543 for i=1,Inputs do
544 network.neurons[i].value = inputs[i]
545 end
546
547 -- for _,neuron in pairs(network.neurons) do
548 -- local sum = 0
549 -- for j = 1,#neuron.incoming do
550 -- local incoming = neuron.incoming[j]
551 -- local other = network.neurons[incoming.into]
552 -- sum = sum + incoming.weight * other.value
553 -- end
554
555 -- end
556 for l=2,#network.layers do
557 local layer = network.layers[l]
558 for n=1,#layer do
559 local neuron = network.neurons[layer[n]]
560 local sum = 0.0
561 for j = 1,#neuron.incoming do
562 local incoming = neuron.incoming[j]
563 local other = network.neurons[incoming.into]
564 if incoming.delay == 0 then
565 sum = sum + incoming.weight * other.value
566 else
567 sum = sum + incoming.weight * neuron.queues[j][1]
568 for i=1,#neuron.queues[j]-1 do
569 neuron.queues[j][i] = neuron.queues[j][i+1]
570 end
571 neuron.queues[j][#neuron.queues[j]] = other.value
572 end
573 end
574 neuron.value = sigmoid(sum)
575 end
576 end
577 local output = {}
578 --print("Yvel " .. marioYVel)
579 --print("Xvel " .. marioXVel)
580 --print("x " .. marioScreenX)
581 if (ScrollWalkPauselocal and (marioScreenX < 45 or (marioYVel > 5 and marioYVel < 100) or spriteNear)) or endscroll then
582 ScrollWalkPause = false
583 ScrollWalkPauselocal = false
584 --print("off")
585 end
586 if ((scrollBonus > 0 and marioScreenX > 200 and (marioYVel >240 or marioYVel < 5)) or ScrollWalkPauselocal) and not endscroll then
587 --print("first")
588 for o=1,#ButtonNames do
589 output[ButtonNames[o]] = false
590 end
591 if marioXVel > 10 and marioXVel < 128 then
592 output["left"] = true
593 end
594 if marioXVel > 128 and marioXVel < 245 then
595 output["right"] = true
596 end
597 ScrollWalkPause = true
598 ScrollWalkPauselocal = true
599 elseif (scrollBonus > 0 and marioScreenX > 155 and marioYVel ==0) and not spriteNear and not endscroll then
600 --print("second")
601 for o=1,#ButtonNames do
602 output[ButtonNames[o]] = false
603 end
604 if marioXVel > 10 and marioXVel < 128 then
605 output["left"] = true
606 end
607 if marioXVel > 128 and marioXVel < 245 then
608 output["right"] = true
609 end
610 ScrollWalkPause = true
611 ScrollWalkPauselocal = true
612 else
613 --print("1")
614 for o=1,#ButtonNames do --Find neural network outputs
615 output[ButtonNames[o]] = network.neurons[-o] ~= nil and network.neurons[-o].value > 0
616 end
617 end
618 --print(ScrollWalkPause)
619 --Disable opposite d-pad presses
620 if forcebutton ~= nil then
621 for k,v in pairs(forcebutton) do
622 output[k]=v
623 end
624 --print(output)
625 end
626 if output["up"] and output["down"] then
627 output["up"] = false
628 output["down"] = false
629 end
630 if output["left"] and output["right"] then
631 output["left"] = false
632 output["right"] = false
633 end
634 return output
635end
636
637--Mutation and evolution
638function genomeDelta(genes1, genes2) --sees the delta between 2 networks.
639 local i2 = {} --i2 maps the innovation of a gene to the gene, for the second genome.
640 for i = 1,#genes2 do
641 local gene = genes2[i]
642 i2[gene.innovation] = gene
643 end
644
645 local sum = 0 --total weight difference among matching genes
646 local coincident = 0 --number of matching genes
647 for i,gene in pairs(genes1) do
648 if i2[gene.innovation] ~= nil then
649 local gene2 = i2[gene.innovation]
650 sum = sum + math.abs(gene.weight - gene2.weight)
651 coincident = coincident + 1
652 end
653 end
654 if coincident == 0 then --if there are no matching genes it does not match so return a very large value
655 return 100
656 end
657 local total = #genes1 + #genes2 --number of total genes
658 local disjoint = total - (coincident*2) --number of non-matching genes
659 return DeltaWeights * sum / coincident + DeltaDisjoint * disjoint / total --return the delta
660end
661
662function sameSpecies(genome1,genome2) --sees whether 2 genomes are in the same species or not.
663 if #genome1.networkswitch ~= #genome2.networkswitch then
664 return false --must have same number of networks
665 end
666 for n=1,#genome1.networkswitch do
667 if math.abs(genome1.networkswitch[n] - genome2.networkswitch[n]) > DeltaSwitch then
668 return false --if the network switches are too far apart they are different
669 end
670 end
671 for n=1,#genome1.genes do
672 delta = genomeDelta(genome1.genes[n],genome2.genes[n])
673 if delta > DeltaThreshold then
674 return false --if the delta between corresponding networks is too great they are different
675 end
676 end
677 return true --otherwise, same species
678end
679
680function shuffle(num) --shuffles an array from 1 to num.
681 local output = {}
682 for i=1,num do
683 local offset = i - 1
684 local randomIndex = offset*math.random()
685 local flooredIndex = randomIndex - randomIndex%1
686 if flooredIndex == offset then
687 output[#output + 1] = i
688 else
689 output[#output + 1] = output[flooredIndex + 1]
690 output[flooredIndex + 1] = i
691 end
692 end
693 return output
694end
695
696function addToSpecies(genome)
697
698 local foundSpecies = false
699
700 local order = shuffle(#pool.species) --Used to go through in random order so as to avoid feeding patterns.
701
702 for i=1,#order do
703 local species = pool.species[order[i]]
704 if sameSpecies(genome, species.genomes[1]) and not foundSpecies then
705 table.insert(species.genomes, genome)
706 foundSpecies = true
707 end
708 end
709
710 if not foundSpecies then --create new species
711 local newSpecies = newSpecies()
712 table.insert(newSpecies.genomes, genome)
713 table.insert(pool.species, newSpecies)
714 end
715end
716
717function randomNeuron(genes, nonInput, nonOutput) --pick a random neuron number
718 local neurons = {}
719 if not nonInput then --If inputs are an option add them
720 for i=1,Inputs do
721 neurons[i] = true
722 end
723 end
724 if not nonOutput then --If outputs are an option add them
725 for o=1,#ButtonNames do
726 neurons[-o] = true
727 end
728 end
729 for i,gene in pairs(genes) do --Add input and output of each gene
730 if not nonInput or gene.into > Inputs then --add input as long as it is valid
731 neurons[genes[i].into] = true
732 end
733 if not nonOutput or gene.out > 0 then --add output as long as it is valid
734 neurons[genes[i].out] = true
735 end
736 end
737 local count = 0
738 for _,_ in pairs(neurons) do --count number of neuron possibilities
739 count = count + 1
740 end
741 if count == 0 then return 0 end --return 0 means that there were no possibilities
742 local n = math.random(1, count) --pick a random possibility
743 for k,v in pairs(neurons) do
744 n = n-1 --count down until you get to correct #
745 if n == 0 then
746 return k
747 end
748 end
749end
750
751function linkDirection(genes, link) --Finds a connection's status. 0 means already exists, 1 means >, -1 means < or incomparable
752 for i=1,#genes do --check each gene
753 local gene = genes[i] --if the new added gene already exists return 0
754 if gene.into == link.into and gene.out == link.out then
755 return 0
756 end
757 end
758 --Attempt to find a connection opposing the suggested one (that would create a loop)
759 local afterNodes = {} --each node that is >= than the link.out
760 afterNodes[link.out] = true
761 local readAll = false --has seen every gene without adding any to the list of nodes after link.into
762 while not readAll do --loop until each gene has been read and used
763 readAll = true
764 for i,gene in pairs(genes) do --loop through the list of genes
765 if afterNodes[gene.into] == true and afterNodes[gene.out] == nil then --if new connection found
766 afterNodes[gene.out] = true
767 readAll = false
768 if gene.out == link.into then --connection found
769 return -1
770 end
771 end
772 end
773 end
774 return 1 --Has not found that link.into >= link.out so it is a fine direction
775end
776
777function linkMutate(genome, which, force) --Create a random new gene
778 local genes = genome.genes[which]
779 --create two random neurons
780 local neuronInto
781 if force == 0 then --only input box
782 neuronInto = math.random(1,BoxSize)
783 elseif force == 1 then --only bottom row
784 neuronInto = BoxSize + math.random(1,3+#InitialOscillations)
785 elseif force == 2 then --non-input
786 neuronInto = randomNeuron(genes, true, true)
787 if neuronInto == 0 then return end --no valid non-input nodes
788 else --anything
789 neuronInto = randomNeuron(genes, false, true)
790 end
791 local neuronOut = randomNeuron(genes, true, false)
792 --Create a new link
793 local newLink = newGene()
794 newLink.into = neuronInto
795 newLink.out = neuronOut
796 if neuronInto == neuronOut then return end --must not be the same neuron
797 connectionStatus = linkDirection(genes,newLink) --find current status of that link
798 if connectionStatus == 0 then return end --if link already exists then return
799 --opposite direction
800 if connectionStatus == -1 then
801 newLink.out = neuronInto
802 newLink.into = neuronOut
803 end
804 --create new innovation value and weight
805 newLink.innovation = pool.innovation
806 pool.innovation = pool.innovation + 1
807 newLink.weight = math.random()*4-2
808 newLink.delay = 0
809 if math.random()>0.85 then
810 newLink.delay = math.min(5,math.ceil(math.log(math.random())/math.log(0.5)))
811 end
812 --add to the genes
813 table.insert(genes,newLink)
814end
815
816function nodeMutate(genome, which) --Split a random gene and put a neuron in the middle
817 local genes = genome.genes[which]
818 if #genes == 0 then return end --cannot make a node with no genes
819 local gene = genes[math.random(1,#genes)] --pick a random gene
820 --create the new genes
821 local geneI = newGene()
822 local geneO = newGene()
823 geneI.into = gene.into
824 geneI.out = genome.maxNeuron + 1
825 geneO.into = genome.maxNeuron + 1
826 geneO.out = gene.out
827 --set innovation
828 geneI.innovation = pool.innovation
829 geneO.innovation = pool.innovation + 1
830 pool.innovation = pool.innovation + 2
831 --set weights
832 geneI.weight = math.sqrt(math.abs(gene.weight))
833 if math.random() < 0.5 then
834 geneI.weight = -geneI.weight --half the time first one is negative
835 geneI.delay = gene.delay
836 end
837 if geneI.weight == 0 then
838 geneI.weight = 0.001
839 end
840 geneO.weight = gene.weight / geneI.weight --they should multiply to original weight
841 geneO.delay = gene.delay - geneI.delay
842
843 genome.maxNeuron = genome.maxNeuron + 1
844 gene.enabled = false --disable old gene
845 --insert the new genes
846 table.insert(genes,geneI)
847 table.insert(genes,geneO)
848end
849
850function enableDisableMutate(genome, which, enable) --changes a random gene to enabled or disabled
851 local candidates = {} --list of genes that would be changed by this mutation
852 for _,gene in pairs(genome.genes[which]) do
853 if gene.enabled == not enable then --only insert if the current enabling is different from what we will set it to
854 table.insert(candidates, gene)
855 end
856 end
857
858 if #candidates == 0 then return end --if no valid genes then do nothing
859
860 local gene = candidates[math.random(1,#candidates)] --pick random from candidates
861 gene.enabled = enable --enable that gene
862end
863
864function oscillationMutate(genome, which) --change an oscillation node timer
865 i = math.random(1,#InitialOscillations) --pick a random oscillation to change
866 genome.oscillations[which][i] = genome.oscillations[which][i] + math.random(-10,10) --change by random amount
867 if genome.oscillations[which][i]<0 then --no negative oscillation values
868 genome.oscillations[which][i] = 0
869 end
870end
871
872function nswitchMutate(genome) --change the position of a network switch
873 which = #genome.networkswitch --most of the time pick the most recent switch
874 if math.random() < 0.15 then
875 which = math.random(1,#genome.networkswitch) --occasionally pick a random other one
876 end
877 genome.networkswitch[which] = genome.networkswitch[which] + math.random(-250,250) --change by random amount
878 local netlimit = 0
879 for i,species in ipairs(pool.species) do
880 if species.maxRightmost > netlimit then
881 netlimit = species.maxRightmost
882 end
883 end
884 if genome.networkswitch[which] < 0 or genome.networkswitch[which] > netlimit then --if out of range of where genome can reach
885 table.remove(genome.networkswitch) --remove the latest network
886 table.remove(genome.genes)
887 end
888 table.sort(genome.networkswitch, function(a,b) --make sure network switches are in order through the level
889 return (a < b)
890 end)
891end
892
893function weightsMutate(genome, which) --change the weights of genes around a node
894 local genes = genome.genes[which]
895 local node = randomNeuron(genes,false,false) --pick a random neuron to change weights in the area of
896 local step = genome.mutationRates["step"]
897 for i,gene in pairs(genes) do
898 if gene.into == node or gene.out == node then --if either end
899 if math.random() < WeightPerturbChance then --modify the weight slightly
900 gene.weight = gene.weight + (math.random()-0.5) * step * 2
901 elseif math.random() < WeightResetChance then --reset weight entirely
902 gene.weight = math.random()*4 - 2
903 end
904 end
905 end
906end
907
908MutationFunctions = {} --each mutation function, sorted by name
909
910MutationFunctions.linkInputBox = function(genome, which) return linkMutate(genome, which, 0) end
911MutationFunctions.linkBottomRow = function(genome, which) return linkMutate(genome, which, 1) end
912MutationFunctions.linkLayers = function(genome, which) return linkMutate(genome, which, 2) end
913MutationFunctions.linkRandom = function(genome, which) return linkMutate(genome, which, 3) end
914MutationFunctions.node = nodeMutate
915MutationFunctions.enable = function(genome, which) return enableDisableMutate(genome, which, true) end
916MutationFunctions.disable = function(genome, which) return enableDisableMutate(genome, which, false) end
917MutationFunctions.oscillation = oscillationMutate
918MutationFunctions.nswitch = function(genome, which) if which>1 then nswitchMutate(genome,which-1) end end
919MutationFunctions.weights = weightsMutate
920
921function mutate(genome, which) --Mutates a genome's network based on all it's mutation rates
922 for name,rate in pairs(genome.mutationRates) do --For each mutation type
923 local p = rate --Number of times to do that mutation type
924 if MutationFunctions[name] ~= nil then --is a mutation and not another rate like STEP
925 while p > 0 do --Loop until all mutations are done
926 if math.random() < p then --when p > 1 always do it, if it is a fraction then it is random
927 if genome.genes[which] ~= nil then --if network exists
928 MutationFunctions[name](genome,which) --call that rate
929 end
930 end
931 p = p - 1 --reduce p
932 end
933 end
934 end
935end
936
937function crossover(g1,g2)
938 if g2.fitstate.fitness > g1.fitstate.fitness then
939 g2, g1 = g1, g2 --switch so that g1 is always the greater fitness
940 end
941 local numnets = math.min(#g1.genes,#g2.genes) --take the smaller # of networks
942 local child = newGenome(numnets)
943 for n=1,numnets do --for each network to be calculated
944 local i2 = {} --map from gene2 innovations to gene innovations
945 for i,gene in ipairs(g2.genes[n]) do
946 i2[gene.innovation] = gene
947 end
948 local i1 = {} --map from gene1 innovations to gene innovations
949 for i,gene in ipairs(g1.genes[n]) do
950 i1[gene.innovation] = gene
951 end
952 for i=1,#g1.oscillations[n] do
953 child.oscillations[n][i] = g1.oscillations[n][i] --set oscillations to g1's oscillations
954 end
955 for i,gene1 in ipairs(g1.genes[n]) do --some version of every gene from best genome
956 local gene2 = i2[gene1.innovation] --use innovations2 to find the copied gene in g1
957 if gene2 ~= nil and math.random(1,2) == 1 and gene2.enabled then
958 table.insert(child.genes[n], copyGene(gene2)) --if the copied gene exists pick randomly
959 else
960 table.insert(child.genes[n], copyGene(gene1)) --otherwise take the version from the best genome
961 end
962 end
963 if n > 1 then
964 child.networkswitch[n-1] = g1.networkswitch[n-1]
965 end
966 for i,gene in ipairs(g2.genes[n]) do
967 if i1[gene.innovation] == nil and math.random() < 0.01 then --low chance for every gene in g2 only
968 table.insert(child.genes[n], copyGene(gene)) --add those genes to the child as well
969 end
970 end
971 end
972 child.maxNeuron = math.max(g1.maxNeuron,g2.maxNeuron)
973 return child
974end
975
976function rankGlobally() --Give each species a global rank, 1 is lowest Population is highest
977 local globalRanks = {}
978 for s = 1,#pool.species do
979 local species = pool.species[s]
980 for g = 1,#species.genomes do
981 table.insert(globalRanks, species.genomes[g]) --Insert every genome into the table
982 end
983 end
984 table.sort(globalRanks, function (a,b) --Sort based on fitness
985 return (a.fitstate.fitness < b.fitstate.fitness)
986 end)
987
988 for g=1,#globalRanks do --Put position of each genome into that genome's data
989 globalRanks[g].globalRank = g
990 end
991end
992
993function calculatePoolStats() --Calculate avg and avg deviation of max fitness
994 local total = 0 --Average of each species max fitness
995 for i,species in ipairs(pool.species) do
996 total = total + species.maxFitness
997 end
998 pool.averagemaxfitness = total / #pool.species
999
1000 local deviationtotal = 0 --Average distance between max fit and avg max fit
1001 for i,species in ipairs(pool.species) do
1002 deviationtotal = deviationtotal + math.abs(species.maxFitness - pool.averagemaxfitness)
1003 end
1004 pool.avgDeviation = deviationtotal / #pool.species
1005end
1006
1007function calculateFitnessRank(species) --Returns a number for each species showing how well that species did
1008 local totalRank = 0
1009
1010 for g=1,#species.genomes do --Total rank is equal to the average of the ranks of each genome
1011 local genome = species.genomes[g]
1012 totalRank = totalRank + genome.globalRank
1013 end
1014 totalRank = totalRank / #species.genomes
1015
1016 local multiplier = 1.0 --multiplier based on how many avg deviations from the mean the top fitness is
1017 if species.maxFitness < pool.averagemaxfitness - (pool.avgDeviation *2) then
1018 multiplier = 0.25
1019 elseif species.maxFitness < pool.averagemaxfitness - pool.avgDeviation then
1020 multiplier = 0.4
1021 elseif species.maxFitness > pool.averagemaxfitness + (pool.avgDeviation *3) then
1022 multiplier = 2.0
1023 elseif species.maxFitness > pool.averagemaxfitness + (pool.avgDeviation *2) then
1024 multiplier = 1.5
1025 end
1026
1027 --Penalty for having multiple networks, to cancel out the benefit of consistency that they give
1028 local netMulti = math.pow(NetworkPenaltyOuter*math.pow(NetworkPenaltyInner,#species.genomes[1].networkswitch),#species.genomes[1].networkswitch)
1029
1030 species.fitnessRank = totalRank * multiplier * netMulti
1031end
1032
1033function totalPoolRank() --Sums each species fitness rank
1034 local total = 0
1035 for i,species in ipairs(pool.species) do
1036 total = total + species.fitnessRank
1037 end
1038 return total
1039end
1040
1041function cullSpecies(cutToOne) --Cuts down each species, either to a percent based on bottleneck, or only to it's top genome.
1042 for i,species in ipairs(pool.species) do
1043 table.sort(species.genomes, function (a,b) --sorts the genomes by how well they did
1044 return (a.fitstate.fitness > b.fitstate.fitness)
1045 end)
1046
1047 local percent = 0.5 --calculate percent
1048 local minpercent = math.max(0.15,0.5-0.1*pool.bottleneckloops) --min and max of oscillation
1049 local maxpercent = math.min(0.85,0.5+0.1*pool.bottleneckloops)
1050
1051 local length = math.min(10,(5 + 2*pool.bottleneckloops)) --length used in pop oscillation
1052
1053 local totallength = length*3 + TimeBeforePopOscil --total length of oscillation
1054
1055 if pool.bottleneck < totallength/2 then --if in first half go up to max
1056 percent = minpercent + (0.1+maxpercent-minpercent)*(pool.bottleneck*2/totallength)
1057 else --if in second half go down to min
1058 percent = 0.1+maxpercent - (0.2+maxpercent-minpercent)*(pool.bottleneck*2/totallength-1)
1059 end
1060
1061 local remaining = math.ceil(#species.genomes*percent) --number that remain
1062 if cutToOne or remaining < 1 then --always keep at least 1
1063 remaining = 1
1064 end
1065 while #species.genomes > remaining do --remove all that do not survive
1066 table.remove(species.genomes)
1067 end
1068 end
1069end
1070
1071function breedChild(species) --makes an offspring from a species
1072 local child = {}
1073 if math.random() < CrossoverChance then --chance to cross over two genomes or just copy one
1074 g1 = species.genomes[math.random(1, #species.genomes)]
1075 g2 = species.genomes[math.random(1, #species.genomes)]
1076 child = crossover(g1, g2)
1077 else
1078 g = species.genomes[math.random(1, #species.genomes)]
1079 child = copyGenome(g)
1080 end
1081
1082 local which = #child.genes -- mutate usually at the latest network
1083 if which > 1 and species.maxRightmost > 0 then --if there are multiple networks possibly modify one before most recent based on distance past that
1084 local mutatePrevNetworkChance = 0.5 + (child.networkswitch[#child.networkswitch]-species.maxRightmost)/500
1085 if math.random() < mutatePrevNetworkChance then
1086 which = #child.genes - 1
1087 end
1088 end
1089 mutate(child,which) --mutate the child in that network
1090
1091 return child
1092end
1093
1094function removeStaleSpecies() --update staleness and remove stale species
1095 local survived = {} --species that survive
1096 for i,species in ipairs(pool.species) do
1097 species.staleness = species.staleness + 1
1098 if species.staleness < MaxStaleness or species.maxFitness >= pool.secondFitness then --non stale species and top 2 species survive
1099 table.insert(survived,species)
1100 end
1101 end
1102 pool.species = survived
1103end
1104
1105function removeWeakSpecies()
1106 local survived = {} --species that survive
1107 local sum = totalPoolRank()
1108 for s = 1,#pool.species do
1109 local species = pool.species[s]
1110 local breed = math.floor(species.fitnessRank / sum * pool.population)
1111
1112 if breed+1 >= 999/pool.population or species.fitnessRank > sum / #pool.species then
1113 table.insert(survived, species)
1114 end
1115 end
1116 pool.species = survived
1117end
1118
1119function replaceNetwork(g1,g2) --replaces the most recent network of g1 with a random network of g2
1120 local child = copyGenome(g1) --mostly a copy of genome 1
1121 child.genes[#child.genes] = {} --clear out the most recent network
1122 which = math.random(1,#g2.genes) --pick a random network to copy from which
1123 for i,gene in ipairs(g2.genes[which]) do --copy each gene
1124 table.insert(child.genes[#child.genes], copyGene(gene))
1125 end
1126 return child
1127end
1128
1129function writescene(scene)
1130 local f = io.open('currentscene.txt','w')
1131 if scene == 1 then
1132 f:write("1")
1133 elseif scene == 2 then
1134 f:write("2")
1135 else
1136 f:write("3")
1137 end
1138 f:close()
1139end
1140
1141function writetable(file,tbl) --writes a string of a table to a file
1142 function tablestring(a)
1143 if type(a)=='number' then
1144 local s = string.gsub(tostring(a),",",".")
1145 file:write(s)
1146 elseif type(a)=='boolean' then
1147 file:write(tostring(a))
1148 elseif type(a)=='string' then
1149 file:write('"')
1150 for i=1,string.len(a) do
1151 local c = string.sub(a,i,i)
1152 if c=="\n" then
1153 file:write("\\n")
1154 else
1155 local s = string.gsub(c,'\\','\\\\')
1156 file:write(s)
1157 end
1158 end
1159 file:write('"')
1160 else
1161 file:write('{')
1162 for key,value in pairs(a) do
1163 file:write('[')
1164 tablestring(key)
1165 file:write(']=')
1166 tablestring(value)
1167 file:write(',')
1168 end
1169 file:write('}')
1170 end
1171 end
1172 tablestring(tbl)
1173end
1174savgx={0}
1175function searchstaff()
1176 getPositions()
1177 local marioScreenX = marioScreenX
1178 local koopalingDefeated = koopalingDefeated
1179 local cont = {}
1180 cont['B'] = true
1181 cont['down'] = false
1182 if marioScreenX < 122 then
1183 cont['right'] = true
1184
1185 else
1186 cont['left'] = true
1187 end
1188 if marioScreenX - savgx[1] < 5 then
1189 cont['A'] = true
1190 table.insert(savgx,0)
1191 else
1192 cont['A'] = false
1193 end
1194 table.insert(savgx,marioScreenX)
1195 if #savgx > 120 then
1196 table.remove(savgx,1)
1197 end
1198 joypad.set(Player,cont)
1199 coroutine.yield()
1200end
1201
1202function file_exists(name)
1203 local f=io.open(name,"r")
1204 if f~=nil then io.close(f) return true else return false end
1205end
1206
1207function saveGenome(name,winner) --saves a species containing the winning genome to a file
1208 local lvlf=io.open("level.txt","r")
1209 local lvl = lvlf:read("*line")
1210 lvlf:close()
1211 local levelname = lvl --name of level in the file name
1212 local file = io.open("backups"..dirsep..levelname..dirsep..name..".lua","w")
1213 spec = pool.species[pool.currentSpecies]
1214 genome = spec.genomes[pool.currentGenome]
1215 genome.nick = spec.nick
1216 genome.gen = pool.generation
1217 genome.s = pool.currentSpecies
1218 genome.g = pool.currentGenome
1219 print("backups"..dirsep..levelname..dirsep..name..".lua")
1220 if not winner then
1221 if boss then
1222 file:write("boss=true\nloadedgenome=")
1223 else
1224 file:write("boss=false\nloadedgenome=")
1225 end
1226 else
1227 file:write("loadedgenome=")
1228 end
1229 writetable(file,genome)
1230 file:close()
1231 if winner then
1232 file = io.open("backups"..dirsep.."winners.txt","a")
1233 file:write("backups"..dirsep..levelname..dirsep..name..".lua\n")
1234 file:close()
1235 else
1236 table.insert(pool.breakthroughfiles,"backups"..dirsep..levelname..dirsep..name..".lua")
1237 end
1238end
1239
1240function savePool(filename) --saves the pool into a file
1241 local file = io.open(filename,"w")
1242 file:write("pool=")
1243 writetable(file,pool)
1244 file:close()
1245end
1246
1247function loadPool(filename) --loads the pool from a file
1248 dofile(filename)
1249 ProgramStartTime = ProgramStartTime - pool.realTime
1250end
1251
1252maxNewGenProgress = -1
1253function newgenProgress(progress) --draws a progress bar for new gen calculation
1254 if math.floor(progress)>maxNewGenProgress then
1255 maxNewGenProgress=math.floor(progress)
1256 text = "Calculating Generation"
1257 white = toRGBA(0xFFFFFFFF)
1258 blue = toRGBA(0xFF003FFF)
1259 black = toRGBA(0xFF000000)
1260 gui.drawbox(38,98,218,122,black,blue)
1261 drawtext.draw(text,40,100,white,black)
1262 prog = "["
1263 for i=1,progress do prog=prog.."#" end
1264 for i=progress,19 do prog=prog.." " end
1265 prog=prog.."]"
1266 drawtext.draw(prog,40,112,blue,black)
1267 coroutine.yield()
1268 end
1269end
1270
1271function newGeneration() --runs the evolutionary algorithms to advance a generation
1272 local mfloor = math.floor
1273 maxNewGenProgress = -1
1274 newgenProgress(0)
1275 autoturbo()
1276 generationTime = pool.realTime
1277
1278 os.remove("backups"..dirsep..levelname..dirsep.."gen"..(pool.generation+1)..".lua")
1279 savePool("backups"..dirsep..levelname..dirsep.."gen"..pool.generation..".lua")
1280
1281 local length = math.min(10,(5 + 2*pool.bottleneckloops)) --Length used for the population oscillation
1282 local targetPop = math.min(900,500 + 100*pool.bottleneckloops) --population to rise to
1283 local targetPopLower = math.min(300,600 - targetPop/2) --population to fall back down to
1284 local currentPopLower = math.min(300,targetPopLower+50) --population starting at
1285
1286 if pool.bottleneck <= TimeBeforePopOscil and pool.bottleneckloops == 0 then --decrease very rapidly after a bottleneck reset
1287 pool.population = math.max(math.floor(pool.population * 0.8),300)
1288 end
1289
1290 if pool.bottleneck > TimeBeforePopOscil then --perform the oscillation
1291 if pool.bottleneck < TimeBeforePopOscil + length then --increase up
1292 pool.population = currentPopLower + math.floor((targetPop - currentPopLower)/length*(pool.bottleneck-TimeBeforePopOscil))
1293 elseif pool.bottleneck == TimeBeforePopOscil + length then --peak
1294 pool.population = targetPop
1295 elseif pool.bottleneck <= TimeBeforePopOscil + length*3 then --go back down
1296 pool.population = targetPopLower + math.floor((targetPop - targetPopLower)/length/2*(TimeBeforePopOscil+length*3-pool.bottleneck))
1297 end
1298 if pool.bottleneck == TimeBeforePopOscil + length*3 then --go to next loop
1299 pool.bottleneck = 0
1300 pool.bottleneckloops = pool.bottleneckloops + 1
1301 end
1302 end
1303 newgenProgress(1)
1304 cullSpecies(false) --cut down each species
1305 newgenProgress(2)
1306 rankGlobally() --rank all genomes
1307 newgenProgress(3)
1308 removeStaleSpecies() --remove stale species
1309 newgenProgress(4)
1310 calculatePoolStats() --find pool maxes avg/deviation
1311 newgenProgress(5)
1312 for i,species in ipairs(pool.species) do --find the fitness rank for each species
1313 calculateFitnessRank(species)
1314 end
1315 newgenProgress(6)
1316 removeWeakSpecies() --remove species without a good fitness rank
1317 newgenProgress(7)
1318 local rankSum = totalPoolRank() --used to see how good a fitness rank is in comparison to everything
1319
1320 local children = {} --new genomes that will be added this generation
1321
1322 for i,species in ipairs(pool.species) do --generate children for each species
1323 local breed = mfloor(species.fitnessRank / rankSum * pool.population) - 1 --num of children to generate
1324 for j=1,breed do
1325 table.insert(children, breedChild(species))
1326 end
1327 newgenProgress(7+3*i/#pool.species)
1328 end
1329 newgenProgress(10)
1330 local interbreedchance = BaseInterbreedChance --base interbreed chance
1331 if pool.generation > 5 then --do not reduce interbreed at very beginning, we want lots of it in first 5 gens
1332 --reduce interbreed when there are lots of species with low average size
1333 interbreedchance = interbreedchance * math.pow(1/InterbreedCutoff-math.min(1/InterbreedCutoff,#pool.species/pool.population),InterbreedDegree)
1334 end
1335
1336 local replacechance = interbreedchance/3 --replacements rarer than interbreeds
1337
1338 local minnetwork = 10000 --minimum number of networks a species eligible for a NS has
1339 local netspecies = {} --list of species eligible for a NS with min networks
1340
1341 for i,species in ipairs(pool.species) do --apply interbreed chance to each species
1342 if math.random() < interbreedchance then
1343 --pick which species to breed with, proportional to fitness rank
1344 local ibcheck = math.random() * rankSum
1345 for j,species2 in ipairs(pool.species) do
1346 ibcheck = ibcheck - species2.fitnessRank --reduce by the fitness rank
1347 if ibcheck < 0 then --the higher it is reduced by greater chance it goes below 0 this time
1348 table.insert(children, crossover(species.genomes[1],species2.genomes[1])) --interbreed
1349 break
1350 end
1351 end
1352 end
1353
1354 if math.random() < replacechance then
1355 --pick which species to get network from
1356 local species2 = pool.species[math.random(1,#pool.species)]
1357 table.insert(children, replaceNetwork(species.genomes[1],species2.genomes[1])) --replace network
1358 end
1359
1360 local numnet = #species.genomes[1].genes --number of networks
1361 if math.min(numnet,3) <= pool.bottleneckloops and numnet <= minnetwork and species.maxFitness + 150 > pool.maxFitness then
1362 if numnet < minnetwork then --even smaller network found, clear old array
1363 minnetwork = numnet
1364 netspecies = {}
1365 end
1366 table.insert(netspecies,species)
1367 end
1368 newgenProgress(10+3*i/#pool.species)
1369 end
1370
1371 if #netspecies > 0 then --at least one species eligible for a network switch
1372 local species = netspecies[math.random(1,#netspecies)] --pick random eligible species
1373 local child = copyGenome(species.genomes[1]) --make a copy of best genome
1374 local switchloc = species.maxRightmost - 150 - 150*math.random() --pick a switch location
1375 table.insert(child.networkswitch,switchloc)
1376 table.insert(child.oscillations,{})
1377 for i=1,#InitialOscillations do
1378 child.oscillations[#child.oscillations][i] = InitialOscillations[i]
1379 end
1380 table.insert(child.genes,{})
1381
1382 table.insert(children,child) --add child
1383
1384 mutate(child,#child.genes)
1385 mutate(child,#child.genes)
1386
1387 table.sort(child.networkswitch, function(a,b) --make sure network splits are in order
1388 return (a < b)
1389 end)
1390 end
1391 newgenProgress(13)
1392 while #children+#pool.species < pool.population do --fill from random species
1393 table.insert(children, breedChild(pool.species[math.random(1, #pool.species)]))
1394 end
1395 newgenProgress(14)
1396 cullSpecies(true) --remove all but the best of each species
1397 newgenProgress(15)
1398 for i,child in ipairs(children) do --add all children to species
1399 addToSpecies(child)
1400 newgenProgress(15+3*i/#children)
1401 end
1402 newgenProgress(18)
1403 for i,species in ipairs(pool.species) do --limit size of species
1404 local limit = pool.population/12 --cap on the species size
1405 if species.maxFitness >= pool.secondFitness then --if top two then larger cap
1406 limit = pool.population/7
1407 end
1408 while #species.genomes > limit do --remove
1409 table.remove(species.genomes)
1410 end
1411 end
1412
1413 pool.generation = pool.generation + 1
1414 pool.bottleneck = pool.bottleneck + 1
1415 newgenProgress(19)
1416 savePool("backups/current.lua")
1417 newgenProgress(20)
1418end
1419
1420Ranges = {
1421['0-3-3-1']= {{yrange={}, xrange={min=360, max=480}, timeout=900}},
1422["1-2-3-0"]= {{yrange={}, xrange={min=1173, max=1300}, coeffs={x=1,y=2,c=0}}, {yrange={}, xrange={min=1300, max=1303}, resetoffset=true}, {yrange={min=10, max=95}, xrange={min=1281, max=1450}, coeffs={x=0,y=0,c=0}, P=0},{yrange={min=10, max=36}, xrange={min=1181, max=1290}, coeffs={x=0,y=0,c=0}, P=0}},
1423["1-6-2-0"]= {{yrange={}, xrange={min=2167, max=2300}, timeout=900}, {yrange={min=80,max=96}, xrange={min=2230, max=2249}, coeffs={x=1,y=-1,c=700}},{yrange={min=96,max=112}, xrange={min=2220, max=2279}, coeffs={x=1,y=-1,c=600}},{yrange={min=112,max=128}, xrange={min=2230, max=2249}, coeffs={x=1,y=-1,c=500}},{yrange={min=128,max=144}, xrange={min=2230, max=2249}, coeffs={x=1,y=-1,c=400}}, {yrange={min=144,max=160}, xrange={min=2256,max=2272},coeffs={x=1,y=-1,c=250}},{yrange={}, xrange={min=2272,max=2300},coeffs={x=0,y=0,c=0}},{yrange={min=170}, xrange={min=2200,max=2300},coeffs={x=0,y=0,c=0}},{yrange={min=80,max=90}, xrange={min=2274, max=2300}}},
1424}
1425
1426function fitness(fitstate) --Returns the distance into the level - the non-time component of fitness
1427 local coeffs={x=1,y=1,c=0} --position base coefficients
1428 local marioX = marioX
1429 local marioY = marioY
1430 local marioYVel = marioYVel
1431 local marioScore = marioScore
1432 local marioScreenX = marioScreenX
1433 local marioPower = marioPower
1434 local boss = boss
1435 local battle = battle
1436 local marioActive = marioActive
1437 local scrollBonus = scrollBonus
1438 local mmax = math.max
1439 local mfloor = math.floor
1440 local mabs = math.abs
1441 local card_x = card_x
1442 local card_y = card_y
1443 local lspecialBonus = specialBonus
1444 local CardDistance = cardDistance
1445 local levelname = levelname
1446 local pStatus = math.sqrt(memory.readbyte(0x03DD))
1447
1448 local ranges = Ranges[levelname .. "-" .. fitstate.area]
1449 --[[local levelstring = "" --string to represent the level and subworld, to index Ranges
1450 if LostLevels == 1 then levelstring = "LL" end
1451 levelstring = levelstring .. currentWorld .. "-" .. currentLevel .. " " .. fitstate.area
1452 local ranges = Ranges[levelstring] ]]
1453 if boss then
1454 basetimeout = roottimeoutboss
1455 else
1456 basetimeout = roottimeout
1457 end
1458 forcebutton = nil
1459 if ranges ~= nil then
1460 for r=1,#ranges do --for each special fitness range
1461 local range = ranges[r]
1462 if (range.xrange.min == nil or marioX > range.xrange.min) and (range.xrange.max == nil or marioX <= range.xrange.max) then --in x range
1463 if (range.yrange.min == nil or marioY >= range.yrange.min) and (range.yrange.max == nil or marioY < range.yrange.max) then --in y range
1464 if range.coeffs ~= nil then
1465 coeffs = range.coeffs --replace default coefficients
1466 end
1467 if range.P ~= nil then
1468 pStatus = pStatus * range.P
1469 end
1470 if range.area ~= nil then
1471 fitstate.area = range.area
1472 end
1473 if range.timeout ~= nil then
1474 basetimeout = range.timeout
1475 else
1476 basetimeout = 120
1477 end
1478 if range.turbo ~= nil then
1479 if range.turbo then
1480 currentTurbo = true
1481 emu.speedmode("turbo")
1482 else
1483 currentTurbo = false
1484 emu.speedmode("normal")
1485 end
1486 end
1487 if range.forcebutton ~= nil then
1488 forcebutton = range.forcebutton
1489 else
1490 forcebutton = nil
1491 end
1492 if range.resetoffset ~= nil then
1493 fitstate.offset = fitstate.rightmost - fitstate.position
1494 end
1495 end
1496 end
1497 end
1498 end
1499 if marioXVel ~= 0 and not (marioYVel > 0 and marioYVel < 70) then
1500 pBonus = pBonus + pStatus/5
1501 end
1502 local distance
1503 if boss or battle then
1504 for i,sprite in ipairs(sprites) do
1505 if sprite.state ~= 0 then
1506 local distx = math.floor((sprite.x - marioX)/16 + 0.5)
1507 distance = 16 - mabs(distx)
1508 end
1509 if distance == nil then
1510 distance = 0
1511 end
1512 end
1513 end
1514 local currentCardDistance = math.sqrt(mabs(card_x-marioX)*mabs(card_y-marioY))/50
1515 if currentCardDistance < CardDistance and not battle and not boss then
1516 CardDistance = currentCardDistance
1517 cardDistance = CardDistance
1518 if CardDistance < 0.4 then
1519 --roottimeout = 600
1520 basetimeout = 600
1521 end
1522
1523 end
1524 if CardDistance == 0 then
1525 CardDistance = 0.1
1526 end
1527 fitstate.position = coeffs.x*marioX + coeffs.y*marioY + coeffs.c --set the position value
1528 if marioActive then
1529 prevPower = marioPower
1530 if not fitstate.lastActive then --update offset
1531 fitstate.area = memory.readbyte(0x03DF)
1532 --print(500/CardDistance)
1533 fitstate.offset = fitstate.rightmost - fitstate.position
1534 --print(fitstate.offset)
1535 --print("adjusted offset: " .. fitstate.offset)
1536 if battle then fitstate.offset = - (marioScore * 3 + distance * 10) end
1537 getTileset()
1538 end
1539 fitstate.lastright = fitstate.position --update lastright when in control of mario
1540 if scrollBonus > 0 and scrollBonus == prevScrollBonus then
1541 fitstate.timeout = fitstate.timeout + 1
1542 --endscroll = true
1543 --print(scrollBonus)
1544 --print(prevScrollBonus)
1545 elseif boss then
1546 if marioScore == prevscore then
1547 fitstate.timeout = fitstate.timeout + 1 --increase timeout
1548 else
1549 fitstate.timeout = 0
1550 end
1551 prevscore = marioScore
1552 elseif scrollBonus > 0 then
1553 fitstate.timeout = 0
1554 endscroll = false
1555 elseif battle then
1556 if marioScore == prevscore then
1557 fitstate.timeout = fitstate.timeout + 1 --increase timeout
1558 else
1559 fitstate.timeout = 0
1560 end
1561 if distance > 9 then
1562 basetimeout = 600
1563 else
1564 basetimeout = 120
1565 end
1566 prevscore = marioScore
1567 prevscreenx = marioScreenX
1568 else
1569 fitstate.timeout = fitstate.timeout + 1 --increase timeout
1570 end
1571 if fitstate.timeout > basetimeout then --if has been idle for a second, start penalizing
1572 fitstate.timepenalty = fitstate.timepenalty + 1
1573 endscroll = true
1574 end
1575 if pipe then
1576 pipe = false
1577 end
1578 --print("scrollBonus: " .. scrollBonus)
1579 --print("prev: " .. prevScrollBonus)
1580 prevScrollBonus = scrollBonus
1581 else
1582 fitstate.position = fitstate.lastright --freeze position when not in control of mario
1583 if prevPower == marioPower then
1584 if scrollBonus >0 and not pipe then
1585 specialBonus = specialBonus + 200
1586 lspecialBonus = specialBonus
1587 --fitstate.fitness = scrollBonus + fitstate.rightmost*0.6 + 150
1588 fitstate.rightmost = fitstate.fitness
1589 scrollBonus = 0
1590 basescroll = 0
1591 memory.writebyte(0x7A0C,0)
1592
1593 --fitstate.rightmost = fitstate.rightmost + fitstate.offset
1594 pipe = true
1595 --pipeflip = true
1596 --print("pipe")
1597 --fitstate.rightmost = fitstate.offset + 0.6*fitstate.rightmost
1598
1599 elseif scrollBonus >0 and pipe then
1600 scrollBonus = 0
1601 basescroll = 0
1602 memory.writebyte(0x7A0C,0)
1603 end
1604
1605 else
1606 marioActive = true
1607 end
1608 end
1609 fitstate.lastActive = marioActive
1610 if not (marioYVel > 0 and marioYVel < 70) and fitstate.position + fitstate.offset > fitstate.rightmost then
1611 fitstate.timeout = mmax(0,fitstate.timeout + (fitstate.rightmost - fitstate.position - fitstate.offset)*2) --decrease timeout
1612 fitstate.rightmost = fitstate.position + fitstate.offset --rightmost is maximum that the position+offset has ever been
1613 --fitstate.savedtimepenalty = fitstate.timepenalty DISABLED FOR AUTOSCROLLER TEST
1614 end
1615 if boss then
1616 fitstate.fitness = marioScore + fitstate.frame + distance
1617 elseif battle then
1618 fitstate.fitness = marioScore * 3 + distance * 10 + fitstate.offset
1619 elseif scrollBonus > 0 then
1620 fitstate.fitness = scrollBonus + fitstate.rightmost*0.6 + pBonus/10
1621 --print("scrollBonus")
1622 -- if lspecialBonus > 0 then
1623 -- print("scrollBonus: " .. scrollBonus)
1624 -- print("marioX: " .. marioX)
1625 -- end
1626 else
1627 fitstate.fitness = fitstate.rightmost - mfloor(fitstate.savedtimepenalty / 2) + WhiteblockBonus + lspecialBonus + 100/CardDistance +pBonus/10 + powerupFitness * math.min(marioPower, 2) --+ marioScore --return fitness
1628 -- if fitstate.offset ~= -104 then
1629 -- print("after pipe fitness: " .. fitstate.fitness)
1630 -- end
1631 --print("right: " .. fitstate.rightmost .. " penalty: " .. mfloor(fitstate.savedtimepenalty / 2) .. " whiteblock: " .. WhiteblockBonus .. " special: " .. lspecialBonus .. " card: " .. 500/CardDistance .. " pBonus: " .. pBonus/10)
1632 end
1633end
1634
1635
1636
1637function autoturbo() --Determines the automatic turbo
1638 local pool = pool
1639 if (pool.realTime - pool.lastbreaktime) > 14400 then
1640 if (pool.realTime - generationTime) > 5400 then
1641 TurboMax = TurboMax + 30
1642 autoTurbo = TurboMax
1643 elseif (pool.realTime - generationTime) < 3600 and autoTurbo == TurboMax and autoTurboCounter > 1 then
1644 TurboMax = TurboMax - 50
1645 if TurboMax < 0 then
1646 TurboMax = 0
1647 end
1648 autoTurbo = TurboMax
1649 elseif (pool.realTime - generationTime) < 3600 and autoTurbo == TurboMax then
1650 autoTurboCounter = autoTurboCounter + 1
1651 else
1652 autoTurboCounter = 0
1653 end
1654 end
1655 if (pool.realTime - pool.lastbreaktime) < 43200 then
1656 automaticturboswitch = 0
1657 elseif (pool.realTime - pool.lastbreaktime) > 43200 and automaticturboswitch == 0 then
1658 local increase = math.ceil(pool.maxFitness / 10)
1659 if TurboMax < increase then
1660 TurboMax = increase
1661 autoTurbo = increase
1662 end
1663 automaticturboswitch = 1
1664 elseif (pool.realTime - pool.lastbreaktime) > 86400 and automaticturboswitch == 1 then
1665 local increase = math.ceil(pool.maxFitness / 5)
1666 if TurboMax < increase then
1667 TurboMax = increase
1668 autoTurbo = increase
1669 end
1670 automaticturboswitch = 2
1671 elseif (pool.realTime - pool.lastbreaktime) > 172800 and automaticturboswitch == 2 then
1672 local increase = math.ceil(pool.maxFitness / 3)
1673 if TurboMax < increase then
1674 TurboMax = increase
1675 autoTurbo = increase
1676 end
1677 automaticturboswitch = 3
1678 end
1679end
1680
1681function playGenome(genome) --Run a genome through an attempt at the level
1682 local mmax = math.max
1683 local boss = boss
1684 local Replay = Replay
1685 local FramesPerEval = FramesPerEval
1686 if boss then
1687 savestate.load(bosssavestateObj) --load boss savestate
1688 else
1689 savestate.load(savestateObj) --load savestate
1690 end
1691 --local falseload = false
1692 --[[while memory.readbyte(0x0787) == 0 do --wait until the game has fully loaded in mario
1693 coroutine.yield()
1694 falseload = true
1695 end
1696 if falseload then --move a savestate forward so that it doesn't have any load frames
1697 local groundtime = 0 --must not be falling for a certain time to be on ground
1698 while groundtime < 10 do --wait for mario to hit the ground
1699 coroutine.yield()
1700 if memory.readbyte(0x009F) == 0 then
1701 groundtime = groundtime + 1
1702 else
1703 groundtime = 0
1704 end
1705 end
1706 savestate.save(savestateObj)
1707 end]]--
1708
1709 local fitstate = genome.fitstate
1710 fitstate.frame = 0 --frame counter for the run
1711 fitstate.position = 0 --current position
1712 fitstate.rightmost = 0 --max it has gotten to the right
1713 fitstate.offset = 0 --offset to make sure fitness doesn't jump upon room change
1714 fitstate.lastright = 0 --last position before a transition.cancel()
1715 fitstate.lastActive = false --mario's state the previous frame (to check state transitions)
1716 fitstate.fitness = 0 --current fitness score
1717 fitstate.timeout = 0 --time increasing when mario is idle, kills him if it is too much
1718 fitstate.timepenalty = 0 --number of frames mario was too idle, to subtract from his fitness
1719 --fitstate.area = "Level"
1720 --fitstate.lastarea = ""
1721 fitstate.savedtimepenalty = 0 --does not save time penalty until mario moves forward
1722 basescroll = 0
1723 prevScrollBonus = scrollBonus
1724 marioPrevAutoscroll = 0
1725 specialBonus = 0
1726 cardDistance = 9999
1727 pBonus = 0
1728 lockBound = false
1729 getPositions()
1730
1731 specialBonus = - scrollBonus - 500/cardDistance - powerupFitness * math.min(marioPower, 2)
1732 local nsw = 1 --network switch
1733 generateNetwork(genome) --generate the network
1734 local controller = {} --inputs to the controller
1735 getTileset()
1736 fitstate.area = memory.readbyte(0x03DF)
1737 while true do --main game loop
1738 local koopalingDefeated = koopalingDefeated
1739 local marioMusic = marioMusic
1740 local manualControl = keyboardInput()
1741 local ManualInput = ManualInput
1742 local marioX = marioX
1743 local card_x = card_x
1744 local marioScreenY = marioScreenY
1745 local marioYVel = marioYVel
1746 --Get inputs to the network
1747
1748 if fitstate.frame % FPS == 0 and not Replay then
1749 timerOutput()
1750 end
1751
1752 if fitstate.frame % FramesPerEval == 0 then
1753 local inputs = getInputs()
1754 --Find the current network to be using
1755 local nscompare = fitstate.rightmost
1756 nsw = 1
1757 for n=1,#genome.networkswitch do
1758 if nscompare > genome.networkswitch[n] then
1759 nsw = n+1
1760 end
1761 end
1762 --Put outputs to the controller
1763 if marioX > card_x -74 and marioX < card_x -64 then
1764 forcebutton = {right=true,A=false,B=true}
1765 elseif marioX <= card_x-8 and marioX >= card_x -64 then
1766 forcebutton = {right=true,A=true,B=true}
1767 elseif marioX > card_x+8 then
1768 forcebutton = {left=true,right=false}
1769 end
1770 controller = evaluateNetwork(genome.networks[nsw], inputs, genome.oscillations[nsw], fitstate.frame)
1771 else
1772 getPositions()
1773 end
1774
1775 if ManualInput then
1776 joypad.set(Player,manualControl)
1777 else
1778 if fitstate.frame % (FramesPerEval*2) == 0 then
1779 if (marioPower == 3 or marioPower == 5) and controller['A'] then
1780 if memory.readbyte(0x03DD) == 127 then
1781 controller['A'] = false --allows mario to fly with tanooki suit. this type of network would otherwise not be capable of switching that fast
1782 end
1783 end
1784 end
1785 joypad.set(Player,controller)
1786 end
1787
1788 fitness(fitstate) --update the fitness
1789 if pool.generation == 0 and not Replay then
1790 TurboMax = 1
1791 elseif TurboMax == 1 then
1792 TurboMax = 0
1793 end
1794 local turbocompare = mmax(0,fitstate.rightmost)
1795 if CompleteAutoTurbo or (turbocompare >= TurboMin and turbocompare < TurboMax and pool.species[pool.currentSpecies].turbo) then
1796 if not currentTurbo then
1797 currentTurbo = true
1798 emu.speedmode("turbo")
1799 end
1800 else
1801 if currentTurbo then
1802 currentTurbo = false
1803 emu.speedmode("normal")
1804 end
1805 end
1806
1807 --Display the GUI
1808 displayGUI(genome.networks[nsw], fitstate)
1809 --Advance a frame
1810 coroutine.yield() --coroutine.yield()
1811 fitstate.frame = fitstate.frame + 1
1812
1813 --exit if dead or won
1814 if memory.readbyte(0x00EE) == 75 or marioOutOfBound then --if he dies to an enemy or a pit
1815 if Replay then
1816 genome.networks = {}
1817 return false
1818 end
1819 pool.attempts = pool.attempts + 1
1820 pool.deaths = pool.deaths + 1
1821 addToHistogram()
1822 deathCounterOutput()
1823 for frame=1,FramesOfDeathAnimation do
1824 displayGUI(genome.networks[nsw], fitstate)
1825 coroutine.yield() --coroutine.yield()
1826 end
1827 genome.networks = {} --reset networks to save on RAM
1828 pool.totalTime = pool.totalTime + fitstate.frame
1829 turboOutput()
1830 timerOutput()
1831 return false
1832 end
1833 if fitstate.timeout > fitstate.rightmost/3+basetimeout and not ManualInput then --timeout threshold increases throughout level. kill if timeout is enabled and timer is not frozen
1834 genome.networks = {} --reset networks to save on RAM
1835 if Replay then return false end
1836 pool.attempts = pool.attempts + 1
1837 addToHistogram()
1838 deathCounterOutput()
1839 turboOutput()
1840 timerOutput()
1841 pool.totalTime = pool.totalTime + fitstate.frame
1842 return false
1843 end
1844 if memory.readbyte(0x0014) == 1 then --if he beats the level
1845 print("beat level")
1846 genome.fitstate.fitness = genome.fitstate.fitness + 1000
1847 genome.networks = {} --reset networks to save on RAM
1848 if Replay then return true end
1849 pool.totalTime = pool.totalTime + fitstate.frame
1850 addToHistogram()
1851 TurboMin = 0
1852 TurboMax = 0
1853 turboOutput()
1854 battle = false
1855 return true
1856 end
1857 if (marioMusic == 0x50 or marioMusic == 0xB0) and not boss then
1858 print('activate final state')
1859 genome.fitstate.fitness = genome.fitstate.fitness + 1000
1860 genome.networks = {} --reset networks to save on RAM
1861 if Replay then return true end
1862 pool.totalTime = pool.totalTime + fitstate.frame
1863 addToHistogram()
1864 TurboMin = 0
1865 TurboMax = 0
1866 turboOutput()
1867 --print('playboss')
1868 memory.writebyte(0x0715,0) --set score to 0
1869 memory.writebyte(0x0716,0)
1870 memory.writebyte(0x0717,0)
1871 bosssavestateObj = savestate.object(savestateSlotBOSS)
1872 savestate.save(bosssavestateObj)
1873 savestate.persist(bosssavestateObj)
1874 startboss = true
1875 return false
1876 end
1877 if (marioMusic ~= 0x50 and marioMusic ~= 0xB0 and koopalingDefeated == 0) and boss then
1878 genome.fitstate.fitness = genome.fitstate.fitness + 1000
1879 genome.networks = {} --reset networks to save on RAM
1880 for x=1,800 do
1881 coroutine.yield()
1882 end
1883
1884 if Replay then return true end
1885 pool.totalTime = pool.totalTime + fitstate.frame
1886 addToHistogram()
1887 TurboMin = 0
1888 TurboMax = 0
1889 turboOutput()
1890 return true
1891 end
1892 if koopalingDefeated > 0 and boss then
1893 print("second")
1894 -- print(marioMusic)
1895 -- print(koopalingDefeated)
1896 genome.fitstate.fitness = genome.fitstate.fitness + 1000
1897 genome.networks = {} --reset networks to save on RAM
1898 while koopalingDefeated == 1 do
1899 koopalingDefeated = memory.readbyte(0x07BD)
1900 coroutine.yield()
1901 end
1902 while koopalingDefeated == 2 do
1903 koopalingDefeated = memory.readbyte(0x07BD)
1904 searchstaff()
1905 end
1906 while koopalingDefeated > 1 do
1907 koopalingDefeated = memory.readbyte(0x07BD)
1908 coroutine.yield()
1909 end
1910 while kingconvo < 4 do
1911 getPositions()
1912 coroutine.yield()
1913 end
1914 if kingconvo == 4 then
1915 for a=1,60 do
1916 coroutine.yield()
1917 end
1918 joypad.set(Player,{A = true})
1919 coroutine.yield()
1920 joypad.set(Player,{A = false})
1921 end
1922 if Replay then return true end
1923 pool.totalTime = pool.totalTime + fitstate.frame
1924 addToHistogram()
1925 TurboMin = 0
1926 TurboMax = 0
1927 turboOutput()
1928 return true
1929 end
1930 end
1931end
1932
1933function playSpecies(species,showBest) --Plays through all members of a species
1934 spindicatorOutput(species)
1935 local startGenome = 2 --which genome to start showing from
1936 local oldBest = 0
1937 if showBest or not species.genomes[1].fitstate.fitness then
1938 startGenome = 1
1939 oldBest = species.maxFitness --used to make sure staleness does not reset every run if showBest is toggled on always
1940 species.maxFitness = 0
1941 species.maxRightmost = 0
1942 end
1943 speciesDataOutput()
1944 for g=startGenome,#species.genomes do --loop through each genome
1945 local genome = species.genomes[g]
1946 pool.currentGenome = g
1947 local won = playGenome(genome) --test the genome
1948 if genome.fitstate.fitness + 30 > pool.maxFitness then --if it has gotten very close to the max
1949 pool.maxCounter = pool.maxCounter + 1
1950 end
1951 if genome.fitstate.fitness > pool.maxFitness then --if the fitness is the new best
1952 if species.gsid ~= pool.bestSpecies then --change the second best if the current best is a different species
1953 pool.secondFitness = pool.maxFitness
1954 end
1955 if genome.fitstate.fitness > pool.maxFitness + MajorBreakFitness or genome.fitstate.fitness > pool.lastMajorBreak + MajorBreakFitness*4 then
1956 --Counts as a major breakthrough
1957 pool.lastMajorBreak = genome.fitstate.fitness
1958 pool.bottleneck = 0
1959 pool.bottleneckloops = 0
1960 pool.lastbreaktime = pool.realTime
1961 end
1962 pool.bestSpecies = species.gsid --update the best species number
1963 pool.maxFitness = genome.fitstate.fitness --update the best fitness
1964 writeBreakthroughOutput()
1965 elseif genome.fitstate.fitness > pool.secondFitness then --if the fitness is the new second best
1966 if species.gsid ~= pool.bestSpecies then --change the second best if this is not the current best species
1967 pool.secondFitness = genome.fitstate.fitness
1968 end
1969 end
1970 --print("end calculation fitness: " .. genome.fitstate.fitness)
1971 if genome.fitstate.fitness > species.maxFitness then --update the species max fitness
1972 species.maxFitness = genome.fitstate.fitness
1973 species.breakthroughX = marioX
1974 species.breakthroughZ = genome.fitstate.area
1975 spindicatorOutput(species)
1976 if genome.fitstate.fitness > oldBest then
1977 species.staleness = 0 --reset the staleness
1978 end
1979 end
1980 if genome.fitstate.rightmost > species.maxRightmost then --update the species max right
1981 species.maxRightmost = genome.fitstate.rightmost
1982 end
1983 if startboss then
1984 saveGenome("winner",true)
1985 startboss = false
1986 PlayBoss()
1987 won = true
1988 end
1989 pool.current = pool.current +1
1990 pool.total = pool.total + genome.fitstate.fitness
1991 pool.average = math.ceil(pool.total/pool.current)
1992 if won then return true end --return true if we won
1993 end
1994 return false --return false otherwise
1995end
1996
1997function playGeneration(showBest) --Plays through the entire generation
1998 local NumberWinner = numberWinner
1999 pool.maxCounter = 0
2000 pool.current = 0
2001 pool.total = 0
2002 for s=1,#pool.species do
2003 local species = pool.species[s]
2004
2005 pool.currentSpecies = s
2006 if s > NumberWinner and NumberWinner ~= -1 then
2007 if not currentTurbo then
2008 currentTurbo = true
2009 emu.speedmode("turbo")
2010 end
2011 numberWinner = -1
2012 end
2013 if playSpecies(species,showBest) then
2014 return true
2015 end
2016 end
2017 return false
2018end
2019
2020--File Outputs
2021function deathCounterOutput()
2022 fileDeaths = io.open("deaths.txt","w")
2023 fileDeaths:write(pool.deaths)
2024 fileDeaths:close()
2025 fileAttempts = io.open("attempts.txt","w")
2026 fileAttempts:write(pool.attempts)
2027 fileAttempts:close()
2028end
2029
2030function speciesDataOutput()
2031 local species = pool.species[pool.currentSpecies]
2032 speciesdata = "GSID: "..species.gsid.." SMax: "..species.maxFitness.." Stale: "..species.staleness
2033 if species.nick ~= "" then
2034 speciesdata = speciesdata.." Nick: "..species.nick
2035 end
2036 fileSData = io.open("speciesdata.txt","w")
2037 fileSData:write(speciesdata)
2038 fileSData:close()
2039end
2040
2041function mapupdate()
2042 io.open("../mapupdate.txt","w"):close()
2043end
2044
2045function mapupdateHist()
2046 local f = io.open("../mapupdate.txt","w")
2047 f:write("hist")
2048 f:close()
2049end
2050
2051function spindicatorOutput(species)
2052 fileIPos = io.open("spindicatorpos.txt","w")
2053 fileIPos:write(species.breakthroughX.." "..species.breakthroughZ)
2054 fileIPos:close()
2055 pcall(mapupdate)
2056end
2057
2058function indicatorOutput()
2059 fileIPos = io.open("indicatorpos.txt","w")
2060 fileIPos:write(pool.breakthroughX.." "..pool.breakthroughZ)
2061 fileIPos:close()
2062 pcall(mapupdate)
2063end
2064
2065function addToHistogram()
2066 local i = math.floor(marioX/32)
2067 key = i.." "..pool.species[pool.currentSpecies].genomes[pool.currentGenome].fitstate.area
2068 if not pool.maphistogram[key] then
2069 pool.maphistogram[key] = 0
2070 end
2071 pool.maphistogram[key] = pool.maphistogram[key] + 1
2072end
2073
2074function histogramOutput()
2075 fileHistogram = io.open("histogram.txt","w")
2076 for key,count in pairs(pool.maphistogram) do
2077 fileHistogram:write(key.." "..count.."\n")
2078 end
2079 fileHistogram:close()
2080end
2081
2082function writeBreakthroughOutput()
2083 local species = pool.species[pool.currentSpecies]
2084 local genome = species.genomes[pool.currentGenome]
2085 info = tostring(pool.generation)
2086 for i=1 ,4-string.len(info) do info=info.." " end
2087 info = info..tostring(pool.currentSpecies)
2088 for i=1,9-string.len(info) do info=info.." " end
2089 info = info..tostring(pool.currentGenome)
2090 for i=1,13-string.len(info) do info=info.." " end
2091 info = info..tostring(species.gsid)
2092 for i=1,18-string.len(info) do info=info.." " end
2093 info = info..tostring(math.floor(genome.fitstate.fitness))
2094 for i=1,23-string.len(info) do info=info.." " end
2095 local seconds = pool.realTime
2096 local minutes = math.floor(seconds/60)
2097 seconds = seconds - minutes*60
2098 local hours = math.floor(minutes/60)
2099 minutes = minutes - hours*60
2100 local days = math.floor(hours/24)
2101 hours = hours - days*24
2102 if pool.realTime < 3600 then
2103 info=info..minutes.."m"..seconds.."s"
2104 elseif pool.realTime < 86400 then
2105 info=info..hours.."h"..minutes.."m"
2106 else
2107 info=info..days.."d"..hours.."h"
2108 end
2109 pool.history=pool.history..info.."\n"
2110 fileFTracker = io.open("fitnesstracker.txt","w")
2111 fileFTracker:write(pool.history)
2112 fileFTracker:close()
2113
2114 pool.breakthroughX = marioX
2115 pool.breakthroughZ = genome.fitstate.area
2116 if boss then
2117 saveGenome("Boss-G"..pool.generation.."s"..pool.currentSpecies.."g"..pool.currentGenome,false)
2118 else
2119 saveGenome("G"..pool.generation.."s"..pool.currentSpecies.."g"..pool.currentGenome,false)
2120 end
2121
2122 indicatorOutput()
2123end
2124
2125function levelNameOutput()
2126 getPositions()
2127 fileLevel = io.open("level.txt","w")
2128
2129 fileLevel:write(levelname)
2130 fileLevel:close()
2131end
2132
2133function turboOutput()
2134 fileTurbo = io.open("turbo.txt","w")
2135 if TurboMin == 0 then
2136 fileTurbo:write("T="..TurboMax)
2137 else
2138 fileTurbo:write("T="..TurboMin.."\nto "..TurboMax)
2139 end
2140 fileTurbo:close()
2141end
2142
2143function twoDigit(num)
2144 str = tostring(num)
2145 if string.len(str) == 1 then
2146 str = "0"..str
2147 end
2148 return str
2149end
2150
2151function numToTime(seconds)
2152 local minutes = math.floor(seconds/60)
2153 seconds = seconds - minutes*60
2154 local hours = math.floor(minutes/60)
2155 minutes = minutes - hours*60
2156 local days = math.floor(hours/24)
2157 hours = hours - days*24
2158 return days.."d "..twoDigit(hours)..":"..twoDigit(minutes)..":"..twoDigit(seconds)
2159end
2160
2161function timerOutput()
2162 fileRealTime = io.open("realtime.txt","w")
2163 fileGameTime = io.open("gametime.txt","w")
2164 pool.realTime = os.time()-ProgramStartTime
2165 fileRealTime:write(numToTime(pool.realTime))
2166 fileGameTime:write(numToTime(math.floor(pool.totalTime/FPS)))
2167 fileRealTime:close()
2168 fileGameTime:close()
2169end
2170
2171function fitnessDataOutput()
2172 rankGlobally()
2173 local fitnesses = {}
2174 local genAvg = 0
2175 local smaxAvg = 0
2176 local minGsid = 1000000
2177 local maxGsid = 0
2178 for s,species in pairs(pool.species) do
2179 for g,genome in pairs(species.genomes) do
2180 fitnesses[genome.globalRank] = genome.fitstate.fitness
2181 genAvg = genAvg + genome.fitstate.fitness
2182 end
2183 smaxAvg = smaxAvg + species.maxFitness
2184 if species.gsid > maxGsid then maxGsid = species.gsid end
2185 if species.gsid < minGsid then minGsid = species.gsid end
2186 end
2187 local genMin = fitnesses[1]
2188 local genBqt = fitnesses[math.floor(#fitnesses/4)]
2189 local genMed = fitnesses[math.floor(#fitnesses/2)]
2190 local genTqt = fitnesses[math.floor(3*#fitnesses/4)]
2191 local genMax = fitnesses[#fitnesses]
2192 local genAvg = genAvg / #fitnesses
2193 local smaxAvg = smaxAvg / #pool.species
2194 local smaxSdv = 0
2195 for s,species in pairs(pool.species) do
2196 smaxSdv = smaxSdv + (species.maxFitness - smaxAvg)*(species.maxFitness - smaxAvg)
2197 end
2198 smaxSdv = math.sqrt(smaxSdv / #pool.species)
2199 local avgSdv = 0
2200 for f,fitness in pairs(fitnesses) do
2201 avgSdv = avgSdv + (fitness-genAvg)*(fitness-genAvg)
2202 end
2203 avgSdv = math.sqrt(avgSdv / #fitnesses)
2204 os.remove("data"..dirsep..levelname..dirsep.."data"..(pool.generation+1)..".drd")
2205 fileDRD = io.open("data"..dirsep..levelname..dirsep.."data"..pool.generation..".drd","w")
2206 fileDOut = io.open("discorddataout.txt","w")
2207 fileDRD:write(#fitnesses.." "..pool.population.." "..#pool.species.." ")
2208 fileDOut:write("**Generation: `"..pool.generation.."`** Target Pop: `"..pool.population.."` Actual Pop: `"..#fitnesses.."` Number of Species: `"..#pool.species.."` ")
2209 fileDRD:write(genMin.." "..genBqt.." "..genMed.." "..genTqt.." "..genMax.." ")
2210 fileDOut:write("Minimum: `"..genMin.."` Median: `"..genMed.."` Maximum: `"..genMax.."` ")
2211 fileDRD:write(genAvg.." "..avgSdv.." "..smaxAvg.." "..smaxSdv.."\n")
2212 fileDOut:write("Average: `"..genAvg.."` Standard Deviation: `"..avgSdv.."` Average Max: `"..smaxAvg.."`\n")
2213 for gsid=minGsid,maxGsid do
2214 for s,species in pairs(pool.species) do
2215 if species.gsid == gsid then
2216 local speciesRanks = {}
2217 for g,genome in pairs(species.genomes) do
2218 table.insert(speciesRanks, genome) --Insert every genome into the table
2219 end
2220 table.sort(speciesRanks, function (a,b) --Sort based on fitness
2221 return (a.fitstate.fitness < b.fitstate.fitness)
2222 end)
2223 local specAvg = 0
2224 local sfitnesses = {}
2225 for g=1,#speciesRanks do --Put position of each genome into that genome's data
2226 sfitnesses[g] = speciesRanks[g].fitstate.fitness
2227 specAvg = specAvg + speciesRanks[g].fitstate.fitness
2228 end
2229 local specMin = sfitnesses[1]
2230 local specBqt = sfitnesses[math.ceil(#sfitnesses/4)]
2231 local specMed = sfitnesses[math.ceil(#sfitnesses/2)]
2232 local specTqt = sfitnesses[math.ceil(3*#sfitnesses/4)]
2233 local specMax = sfitnesses[#sfitnesses]
2234 specAvg = specAvg / #sfitnesses
2235 local specSdv = 0
2236 for f,fitness in pairs(sfitnesses) do
2237 specSdv = specSdv + (fitness-specAvg)*(fitness-specAvg)
2238 end
2239 specSdv = math.sqrt(specSdv / #sfitnesses)
2240 fileDRD:write(species.gsid.." "..#species.genomes.." "..#species.genomes[1].genes.." ")
2241 fileDRD:write(specMin.." "..specBqt.." "..specMed.." "..specTqt.." "..specMax.." "..specAvg.." "..specSdv)
2242 for f,fitness in pairs(sfitnesses) do
2243 fileDRD:write(" "..fitness)
2244 end
2245 fileDRD:write("\n")
2246 if species.nick ~= "" then
2247 fileDOut:write("Nickname: `"..species.nick.."` Maximum: `"..specMax.."` ")
2248 fileDOut:write("Staleness: `"..species.staleness.."` Average: `"..specAvg.."` ")
2249 fileDOut:write("Number of Genomes: `"..#sfitnesses.."` Num Networks: `"..#species.genomes[1].genes.."`\n")
2250 end
2251 end
2252 end
2253 end
2254 fileDRD:close()
2255 fileDOut:close()
2256end
2257
2258keyFlag = false
2259function keyboardInput()
2260 local keyboard = input.get()
2261 if keyboard['N'] and keyFlag == false then --N toggles the network display
2262 DisplayNetwork = not DisplayNetwork
2263 end
2264 if keyboard['R'] and keyFlag == false then --R toggles the sprite hitboxes
2265 DisplaySprites = not DisplaySprites
2266 end
2267 if keyboard['G'] and keyFlag == false then --G toggles the large grid display
2268 DisplayGrid = (DisplayGrid + 1) % 3
2269 end
2270 if keyboard['O'] and keyFlag == false then --O toggles the sprite slots
2271 DisplaySlots = not DisplaySlots
2272 end
2273 if keyboard['A'] and keyFlag == false then --A toggles the top stats bar
2274 DisplayStats = not DisplayStats
2275 end
2276 if keyboard['M'] and keyFlag == false then --M toggles manual vs network input
2277 ManualInput = not ManualInput
2278 end
2279 if keyboard['E'] and keyFlag == false then --E toggles
2280 DisplayRanges = not DisplayRanges
2281 end
2282 if keyboard['C'] and keyFlag == false then --E toggles
2283 DisplayCounters = not DisplayCounters
2284 end
2285 if keyboard['L'] and keyFlag == false then --L loads
2286 --to load put an interrupt that will restart the program and initialize with the previous pool
2287 local interrupt = io.open("interrupt.lua","w")
2288 interrupt:write("restartprog = luigiofilename")
2289 interrupt:close()
2290 local initialize = io.open("initialize.lua","w")
2291 initialize:write("loadPool('backups/current.lua')")
2292 initialize:close()
2293 end
2294 local controller = {}
2295 controller['A'] = keyboard['F']
2296 controller['B'] = keyboard['D']
2297 controller['up'] = keyboard['up']
2298 controller['down'] = keyboard['down']
2299 controller['left'] = keyboard['left']
2300 controller['right'] = keyboard['right']
2301 if controller["up"] and controller["down"] then
2302 controller["up"] = false
2303 controller["down"] = false
2304 end
2305 if controller["left"] and controller["right"] then
2306 controller["left"] = false
2307 controller["right"] = false
2308 end
2309 local allkeys = {'N','R','G','O','A','M','E','C','L'}
2310 keyFlag = false --Set keyflag to true if any keys were pressed otherwise it is false
2311 for k=1,#allkeys do
2312 if keyboard[allkeys[k]] then
2313 keyFlag = true
2314 end
2315 end
2316 return controller
2317end
2318
2319function drawStatsBox(fitstate)
2320 local genimage = {' #### #### ## ## ','##---##----#--##--#','#--####--###---#--#','#--#--#----#------#','#--#--#--###--#---#','##----#----#--##--#',' #### #### ## ## '}
2321 local specimage = {' ##### ##### #### ### ## #### #### ','##----#----##----##---#--#----##----#','#--####--#--#--###--###--#--###--### ','##---##----##----#--###--#----##---##','####--#--## #--###--###--#--######--#','#----##--# #----##---#--#----#----# ',' ##### ## #### ### ## #### #### '}
2322 local gnmimage = {' ##### #### ## ## ##### ###### #### ','##---##----#--##--##---####--#--##----#','#--####--###---#--#--#--#--------#--###','#--#--#----#------#--#--#--#--#--#----#','#--#--#--###--#---#--#--#--#--#--#--###','##----#----#--##--##---##--#--#--#----#',' #### #### ## ## ##### ## ## ## #### '}
2323 local popimage = {' ##### ##### ##### ','#----###---##----##','#--#--#--#--#--#--#','#----##--#--#----##','#--####--#--#--## ','#--# ##---##--# ',' ## ##### ## '}
2324 local fitimage = {' #### ## ###### ## ## #### #### #### ','#----#--#------#--##--#----##----#----#','#--###--###--###---#--#--###--###--### ','#----#--# #--# #------#----##---##---##','#--###--# #--# #--#---#--######--###--#','#--###--# #--# #--##--#----#----#----# ',' ## ## ## #### ####### #### ##### '}
2325 local firstimage = {' ### #### ###### ','#---###----#------#','##--##--### ##--## ','##--###---# #--# ','##--#####--# #--# ','#----#----# #--# ',' #### #### ## '}
2326 local secondimage = {' #### ## ## #### ','##----##--###--#----## ','#--##--#---##--#--#--# ','###--###----#--#--##--#','##--####--#----#--#--# ','#-----##--##---#----## ',' ##### ## ### #### '}
2327 local maxnumimage = {' ##### ##### #### ## ## ','##-#-###--#--####----##--##--#','#-----#--------#--##--#--##--#','##-#-##--#--#--#------##----##','#-----#--#--#--#--##--#--##--#','##-#-##--#--#--#--##--#--##--#',' ##### #######################'}
2328 local avgxbar = {' ####### ','#-------# ',' ####### ','#--# #--# #### ## ###### ','#--###--##----#--#------#',' #--#--# #--###--###--## ',' #---# #----#--# #--# ',' #--#--# #--###--# #--# ','#--###--##--###--# #--# ','#--###--##--###--# #--# ',' ### ### ## ## ## '}
2329 local draw = draw
2330 local pool = pool
2331 local fl = math.floor
2332 if currentTurbo then
2333 gui.drawrect(8,194,254,230,draw.CYAN,draw.WHITE)
2334 gui.text(10, 197, "GEN "..pool.generation .. " Species "..pool.currentSpecies.." Genome "..pool.currentGenome, draw.GRAYSCALE, draw.CYAN)
2335 gui.text(10, 210, "Fitness "..math.max(0,fl(fitstate.fitness)) .. " 1ST "..fl(pool.maxFitness).." 2ND "..fl(pool.secondFitness), draw.GRAYSCALE, draw.CYAN)
2336 gui.text(10, 222, "#Max "..pool.maxCounter .. " XFit "..pool.average, draw.GRAYSCALE, draw.CYAN)
2337 else
2338 -- draw.rect(8,194,32,1,draw.BLACK)
2339 -- draw.rect(9,195,246,36,draw.WHITE)
2340 -- draw.rect(10,196,244,34,draw.CYAN)
2341 gui.drawrect(8,194,254,230,draw.CYAN,draw.WHITE)
2342 gui.drawrect(230,204,251,207,draw.GRAYSCALE,draw.CYAN)
2343 --draw.rect(230,205,21,2,draw.BLACK)
2344 draw.image(10,197,genimage,draw.GRAYSCALE)
2345 draw.number(31,197,pool.generation,3)
2346 draw.image(65,197,specimage,draw.GRAYSCALE)
2347 draw.number(104,197,pool.currentSpecies,3)
2348 draw.image(138,197,gnmimage,draw.GRAYSCALE)
2349 draw.number(179,197,pool.currentGenome,3)
2350 --draw.image(176,198,brackets,draw.GRAYSCALE)
2351 --draw.number(182,198,19,2)
2352 draw.image(207,202,popimage,draw.GRAYSCALE)
2353 draw.number(229,197,pool.current,3)
2354 draw.number(229,208,pool.population,3)
2355 draw.image(10,210,fitimage,draw.GRAYSCALE)
2356 draw.number(50,210,math.max(0,fitstate.fitness),4)
2357 draw.image(88,210,firstimage,draw.GRAYSCALE)
2358 draw.number(108,210,pool.maxFitness,4)
2359 draw.image(147,210,secondimage,draw.GRAYSCALE)
2360 draw.number(171,210,pool.secondFitness,4)
2361 draw.image(10,222,maxnumimage,draw.GRAYSCALE)
2362 draw.number(43,222,pool.maxCounter,3)
2363 draw.image(81,218,avgxbar,draw.GRAYSCALE)
2364 draw.number(109,222,pool.average,4)
2365 end
2366end
2367
2368arrowimage = {'### ','#--## ','#----## ','#------#','#----## ','#--## ','### '}
2369pmeterimage = {' ############# ','##-----------##','#----#####----#','#----#---#----#','#----####-----#','##---#-------##',' ############# '}
2370reversePalette = {[' ']=0,['#']=draw.WHITE,['-']=draw.BLACK}
2371function drawPMeter(x,y)
2372 local pStatus = memory.readbyte(0x03DD)
2373 local currentPalette = draw.GRAYSCALE
2374 for i=1,6 do
2375 if pStatus < math.pow(2,i)-1 then
2376 currentPalette = reversePalette
2377 end
2378 draw.image(x-8+i*12,y,arrowimage,currentPalette)
2379 end
2380 if pStatus < 127 then currentPalette = reversePalette end
2381 draw.image(x+75,y,pmeterimage,currentPalette)
2382end
2383
2384clockimage = {' #### ',' #-#--# ','#--#---#','#--###-#','#------#',' #----# ',' #### '}
2385function drawClock(x,y)
2386 local timeLeft = memory.readbyte(0x05EE)*100+memory.readbyte(0x05EF)*10+memory.readbyte(0x05F0)
2387 draw.image(x,y,clockimage,draw.GRAYSCALE)
2388 draw.number(x+8,y,timeLeft,3)
2389end
2390
2391function drawNeuron(network,neuron)
2392 if neuron.x then
2393 inside = draw.tcolor(0x01010100*math.floor(127*(neuron.value+1))+0xE6)
2394 border = draw.tcolor(0x01010100*math.floor(127*(1-neuron.value))+0xE6)
2395 if neuron.blocked then inside = draw.tcolor(0xFF3F00E6) end
2396 if neuron.value ~= 0 then
2397 draw.rect(neuron.x,neuron.y,5,5,border)
2398 draw.rect(neuron.x+1,neuron.y+1,3,3,inside)
2399 else
2400 draw.rect(neuron.x,neuron.y,5,5,draw.tcolor(0x7F7FCFC7))
2401 end
2402 if neuron.incoming then
2403 for g,gene in pairs(neuron.incoming) do
2404 if gene.enabled then
2405 local n1 = network.neurons[gene.into]
2406 local layerdiff = neuron.layer - n1.layer
2407 --Green or red for positive or negative weight
2408 local color = 0x3FFF00
2409 if gene.weight < 0 then
2410 color = 0xFF3F00
2411 end
2412 local opacity = 0xCF
2413 if gene.into > BoxSize and gene.into <= Inputs then --fade or remove if bottom-row neuron
2414 if #network.genes > 100 then
2415 opacity = 0x00
2416 elseif #network.genes > 50 then
2417 opacity = 0x5F
2418 end
2419 end
2420 if n1.value == 0 then --fade or remove if not transmitting a value
2421 if #network.genes > 50 then
2422 opacity = 0x00
2423 else
2424 opacity = 0x5F
2425 end
2426 end
2427 --draw the genome
2428 if n1.x and n1.layer > 0 then
2429 gui.drawline(n1.x+4,n1.y+2,neuron.x,neuron.y+2, draw.tcolor(256*color+opacity))
2430 end
2431 end
2432 end
2433 end
2434 end
2435end
2436
2437charAimage = {' #### ','##--##','#-##-#','#----#','#-##-#','#-##-#',' #### '}
2438charBimage = {' #### ','#---##','#-##-#','#---##','#-##-#','#---##',' #### '}
2439charUimage = {' #### ','#-##-#','#-##-#','#-##-#','#-##-#','##--##',' #### '}
2440charDimage = {' #### ','#---##','#-##-#','#-##-#','#-##-#','#---##',' #### '}
2441charLimage = {' #### ','#-####','#-####','#-####','#-####','#----#',' #### '}
2442charRimage = {' #### ','#---##','#-##-#','#---##','#-#-##','#-##-#',' #### '}
2443buttonImages = {charAimage,charBimage,charUimage,charDimage,charLimage,charRimage}
2444function drawNetwork(network)
2445 local neurons = {} --Array that will contain the position and value of each displayed neuron
2446 local i = 1
2447 local ButtonNumbers = ButtonNumbers
2448 local buttonImages = buttonImages
2449 for dy=-BoxRadius,BoxRadius do --Add the input box neurons
2450 for dx=-BoxRadius,BoxRadius do
2451 network.neurons[i].x = 8+5*(dx+BoxRadius)
2452 network.neurons[i].y = 20+5*(dy+BoxRadius)
2453 i = i + 1
2454 end
2455 end
2456
2457 local botRowSpacing = 10 --Number of pixels between oscillating nodes
2458
2459 local botRowSize = (3 + #InitialOscillations)*2 - 1
2460 if botRowSize > BoxWidth then
2461 botRowSpacing = 5 --If bottom row can't fit then have no spacing
2462 end
2463
2464 for j=0,#InitialOscillations do --Add the bias and oscillation neurons
2465 network.neurons[i].x = 3+BoxWidth*5-botRowSpacing*j
2466 network.neurons[i].y = 25+BoxWidth*5
2467 i = i + 1
2468 end
2469
2470 for j=0,1 do --Add the bias and oscillation neurons
2471 network.neurons[i].x = 8+botRowSpacing*j
2472 network.neurons[i].y = 25+BoxWidth*5
2473 i = i + 1
2474 end
2475
2476 for l=1,#network.layers do --Draw each layer in the NN
2477 local layer = network.layers[l]
2478 for n=1,#layer do
2479 if l > 1 or layer[n] > Inputs then --display only non-inputs in layer 1
2480 network.neurons[layer[n]].x = math.ceil(13+BoxWidth*5+(220-BoxWidth*5)*((l-1) / (#network.layers)))
2481 network.neurons[layer[n]].y = math.ceil(20+(BoxWidth+1)*5*(n / (#layer+1)))
2482 end
2483 end
2484 end
2485
2486 --When to block opposite directional inputs
2487 local blockUD = network.neurons[-3] ~= nil and network.neurons[-4] ~= nil and network.neurons[-3].value>0 and network.neurons[-4].value>0
2488 local blockLR = network.neurons[-5] ~= nil and network.neurons[-6] ~= nil and network.neurons[-5].value>0 and network.neurons[-6].value>0
2489 local fb = {}
2490 if forcebutton ~= nil then
2491 for k,v in pairs(forcebutton) do
2492 fb[ButtonNumbers[k]] = v
2493 end
2494 end
2495 local neuron = {}
2496 for o=1,6 do --outputs
2497 neuron = {}
2498 neuron.x = 238
2499 neuron.y = 25+(BoxWidth-1)*(o-1)
2500 neuron.value = -1
2501 local onode = network.neurons[-o]
2502 if onode and onode.value > 0 then neuron.value = 1 end
2503 if blockUD and (o == 3 or o == 4) then neuron.blocked = true end
2504 if blockLR and (o == 5 or o == 6) then neuron.blocked = true end
2505 if fb[o] ~= nil then
2506 if fb[0] then
2507 neuron.value = 1
2508 else
2509 neuron.value = -1
2510 end
2511 end
2512 drawNeuron(network,neuron)
2513 draw.image(246,24+(BoxWidth-1)*(o-1),buttonImages[o],draw.GRAYSCALE)
2514 if onode ~= nil and network.neurons[o].layer ~= 0 and onode.x then
2515 if neuron.value == 0 then --draw line between output and ouput box.
2516 gui.drawline(onode.x+4,onode.y+2,neuron.x,neuron.y+2,draw.tcolor(0x3FFF005F)) --fade if not sending anything
2517 else
2518 gui.drawline(onode.x+4,onode.y+2,neuron.x,neuron.y+2,draw.tcolor(0x3FFF00CF))
2519 end
2520 end
2521 end
2522
2523 for n,neuron in pairs(network.neurons) do
2524 drawNeuron(network,neuron)
2525 end
2526end
2527
2528function drawRanges()
2529 local levelstring = levelname
2530 local ranges = Ranges[levelstring.."-"..fitstate.area]
2531 local marioX = marioX
2532 local marioScreenX = marioScreenX
2533 local marioY = marioY
2534 local marioScreenY = marioScreenY
2535 if ranges ~= nil then
2536 for r=1,#ranges do
2537 --default values for parts without ranges
2538 local minx = 0
2539 local maxx = 65536
2540 local miny = 0
2541 local maxy = 240
2542 local disp = true
2543 local range = ranges[r]
2544 --if range limits exist set the limits to those
2545 if range.xrange.min ~= nil then minx = range.xrange.min end
2546 if range.xrange.max ~= nil then maxx = range.xrange.max end
2547 if range.yrange.min ~= nil then miny = range.yrange.min end
2548 if range.yrange.max ~= nil then maxy = range.yrange.max end
2549
2550 --set color based on the direction of fitness increase
2551 local rgb = 0x003FFF
2552 textcolor = toRGBA(0xFFFFFFFF)
2553 if range.area ~= nil then
2554 rgb=0xFF00FF
2555 elseif range.coeffs ~= nil then
2556 if range.coeffs.x == 1 and range.coeffs.y == 1 and range.coeffs.c ~=0 then
2557 rgb=0xE5FF00
2558 textcolor = toRGBA(0xFF000000)
2559 end
2560 if range.coeffs.x >0 and range.coeffs.y==0 then
2561 rgb=0X0FFF1B
2562 end
2563 if range.coeffs.y > 0 and range.coeffs.x <1 then
2564 rgb=0x0F37FF
2565 end
2566 if range.coeffs.x == 0 and range.coeffs.y == 0 then
2567 rgb=0xFF3F00
2568 end
2569 if range.coeffs.y <0 then
2570 rgb=0xFFBF00
2571 end
2572 if range.coeffs.x < 0 then
2573 rgb=0xBF7F00
2574 end
2575 elseif range.timeout ~= nil then
2576 rgb=0x42AAFF
2577 end
2578 if rgb~=0x003FFF then
2579 color = toRGBA(0xFF000000 + rgb)
2580 --draw the box
2581 if marioY + marioScreenY == 161 and memory.readbyte(0x0544) == 0 then
2582 gui.drawbox(minx-marioX+marioScreenX-1,193-miny,maxx-marioX+marioScreenX-2,193-maxy-1,toRGBA(0x7F000000 + rgb),toRGBA(0x7F000000 + rgb))
2583 gui.drawbox(minx-marioX+marioScreenX-1,193-miny,minx-marioX+marioScreenX-1,193-maxy-1,color,color)
2584 gui.drawbox(minx-marioX+marioScreenX-1,193-miny,maxx-marioX+marioScreenX-2,193-miny,color,color)
2585 gui.drawbox(minx-marioX+marioScreenX-1,193-maxy-1,maxx-marioX+marioScreenX-2,193-maxy-1,color,color)
2586 gui.drawbox(maxx-marioX+marioScreenX-2,193-miny,maxx-marioX+marioScreenX-2,193-maxy-1,color,color)
2587 --draw the numbers in the corners that show the fitness values
2588 --local TLcorner = minx*range.coeffs.x+(208-miny)*range.coeffs.y+range.coeffs.c
2589 --local TRcorner = maxx*range.coeffs.x+(208-miny)*range.coeffs.y+range.coeffs.c
2590 --local BLcorner = minx*range.coeffs.x+(192-maxy)*range.coeffs.y+range.coeffs.c
2591 --local BRcorner = maxx*range.coeffs.x+(192-maxy)*range.coeffs.y+range.coeffs.c
2592 -- gui.drawtext(minx-marioX+marioScreenX,miny+1,TLcorner,textcolor,color)
2593 -- gui.drawtext(maxx-marioX+marioScreenX-6*string.len(TRcorner)-1,miny+1,TRcorner,textcolor,color)
2594 -- gui.drawtext(minx-marioX+marioScreenX,maxy-8,BLcorner,textcolor,color)
2595 -- gui.drawtext(maxx-marioX+marioScreenX-6*string.len(TRcorner)-1,maxy-8,BRcorner,textcolor,color)
2596 else
2597 local offset = 161 - (marioY + marioScreenY)
2598 gui.drawbox(minx-marioX+marioScreenX-1,193-miny-offset,maxx-marioX+marioScreenX-2,193-maxy-1-offset,toRGBA(0x7F000000 + rgb),toRGBA(0x7F000000 + rgb))
2599 gui.drawbox(minx-marioX+marioScreenX-1,193-miny-offset,minx-marioX+marioScreenX-1,193-maxy-1-offset,color,color)
2600 gui.drawbox(minx-marioX+marioScreenX-1,193-miny-offset,maxx-marioX+marioScreenX-2,193-miny-offset,color,color)
2601 gui.drawbox(minx-marioX+marioScreenX-1,193-maxy+1-offset,maxx-marioX+marioScreenX-2,193-maxy-1-offset,color,color)
2602 gui.drawbox(maxx-marioX+marioScreenX-2,193-miny-offset,maxx-marioX+marioScreenX-2,193-maxy-1-offset,color,color)
2603 end
2604 end
2605 end
2606 end
2607end
2608
2609function displayGUI(network,fitstate)
2610 if DisplayRanges then
2611 drawRanges()
2612 end
2613 if DisplayStats then
2614 drawStatsBox(fitstate)
2615 if not currentTurbo then
2616 drawPMeter(159,222)
2617 --drawClock(222,10)
2618 end
2619 end
2620
2621 if DisplayNetwork and not currentTurbo then
2622 drawNetwork(network)
2623 end
2624end
2625
2626function worldselection()
2627 local selecting = true
2628 local selection = 1
2629 local color = draw.BLACK
2630 local z = 0
2631 local c = 0
2632 for i=1,70 do
2633 draw.rect(8,204-i,248,22+i,draw.BLACK)
2634 draw.rect(9,205-i,246,20+i,draw.WHITE)
2635 draw.rect(10,206-i,244,18+i,draw.CYAN)
2636 gui.text(12, 208-i, "Please select a world. Use !press up/down to \nnavigate and !press A to select and B to cancel", draw.BLACK, draw.CYAN)
2637 color = draw.BLACK
2638 for x=1,8 do
2639 if file_exists("fcs/world-" .. x .. ".fcs") then
2640 color = draw.BLACK
2641 else
2642 color = draw.RED
2643 end
2644 if x < 5 then
2645 z = x
2646 c = 0
2647 else
2648 z = x -4
2649 c = 80
2650 end
2651 if x == selection then
2652 gui.text(12+c, 216+16*z-i, "> World: ".. x, color, draw.CYAN)
2653 else
2654 gui.text(12+c, 216+16*z-i, "World: ".. x, color, draw.CYAN)
2655 end
2656 end
2657 coroutine.yield()
2658 end
2659 while selecting do
2660 draw.rect(8,134,248,94,draw.BLACK)
2661 draw.rect(9,135,246,92,draw.WHITE)
2662 draw.rect(10,136,244,90,draw.CYAN)
2663 gui.text(12, 138, "Please select a world. Use !press up/down to \nnavigate and !press A to select and B to cancel", draw.BLACK, draw.CYAN)
2664 color = draw.BLACK
2665 for x=1,8 do
2666 if file_exists("fcs/world-" .. x .. ".fcs") then
2667 color = draw.BLACK
2668 else
2669 color = draw.RED
2670 end
2671 if x < 5 then
2672 z = x
2673 c = 0
2674 else
2675 z = x -4
2676 c = 80
2677 end
2678 if x == selection then
2679 gui.text(12+c, 146+16*z, "> World: ".. x, color, draw.CYAN)
2680 else
2681 gui.text(12+c, 146+16*z, "World: ".. x, color, draw.CYAN)
2682 end
2683 end
2684 if file_exists("userinput.lua") then
2685 userinput=loadfile("userinput.lua")
2686 if userinput then
2687 os.remove('userinput.lua')
2688 uinput = {}
2689 pcall(userinput)
2690 if uinput.down then
2691 selection = selection + 1
2692 if selection > 8 then
2693 selection = 1
2694 end
2695 elseif uinput.up then
2696 selection = selection - 1
2697 if selection <1 then
2698 selection = 8
2699 end
2700 elseif uinput.left then
2701 selection = selection -4
2702 if selection <1 then
2703 selection = selection + 8
2704 end
2705 elseif uinput.right then
2706 selection = selection +4
2707 if selection >8 then
2708 selection = selection - 8
2709 end
2710 elseif uinput.A and file_exists("fcs/world-" .. selection .. ".fcs") then
2711 local interrupt = io.open("interrupt.lua","w")
2712 interrupt:write("savestateObj = savestate.object('fcs/world-" .. selection ..".fcs')\nprint('one point five')\nsavestate.load(savestateObj)\nprint('two')")
2713 interrupt:close()
2714 for i=1,90 do
2715 draw.rect(8,134+i,248,94-i,draw.BLACK)
2716 draw.rect(9,135+i,246,92-i,draw.WHITE)
2717 draw.rect(10,136+i,244,90-i,draw.CYAN)
2718 gui.text(12, 138+i, "Please select a world. Use !press up/down to \nnavigate and !press A to select and B to cancel", draw.BLACK, draw.CYAN)
2719 color = draw.BLACK
2720 for x=1,8 do
2721 if file_exists("fcs/world-" .. x .. ".fcs") then
2722 color = draw.BLACK
2723 else
2724 color = draw.RED
2725 end
2726 if x < 5 then
2727 z = x
2728 c = 0
2729 else
2730 z = x -4
2731 c = 80
2732 end
2733 if x == selection then
2734 gui.text(12+c, 146+16*z+i, "> World: ".. x, color, draw.CYAN)
2735 else
2736 gui.text(12+c, 146+16*z+i, "World: ".. x, color, draw.CYAN)
2737 end
2738 end
2739 coroutine.yield()
2740 end
2741 return
2742 elseif uinput.B then
2743 for i=1,90 do
2744 draw.rect(8,134+i,248,94-i,draw.BLACK)
2745 draw.rect(9,135+i,246,92-i,draw.WHITE)
2746 draw.rect(10,136+i,244,90-i,draw.CYAN)
2747 gui.text(12, 138+i, "Please select a world. Use !press up/down to \nnavigate and !press A to select and B to cancel", draw.BLACK, draw.CYAN)
2748 color = draw.BLACK
2749 for x=1,8 do
2750 if file_exists("fcs/world-" .. x .. ".fcs") then
2751 color = draw.BLACK
2752 else
2753 color = draw.RED
2754 end
2755 if x < 5 then
2756 z = x
2757 c = 0
2758 else
2759 z = x -4
2760 c = 80
2761 end
2762 if x == selection then
2763 gui.text(12+c, 146+16*z+i, "> World: ".. x, color, draw.CYAN)
2764 else
2765 gui.text(12+c, 146+16*z+i, "World: ".. x, color, draw.CYAN)
2766 end
2767 end
2768 coroutine.yield()
2769 end
2770 return
2771 end
2772 end
2773 end
2774 coroutine.yield()
2775 end
2776end
2777
2778function SolveMemory()
2779 local Tset = memory.readbyte(0x070A)
2780 for a=1,60 do
2781 coroutine.yield()
2782 end
2783 while Tset == 17 do
2784 local index = memory.readbyte(0x428)
2785 local card = {}
2786 local selected = 0
2787 local selectedindex = 0
2788 for x=1,18 do
2789 card[x] = memory.readbyte(0x7E81+x)
2790 end
2791 if card[index+1] < 0x10 then
2792 joypad.set(Player,{A=true})
2793 for a=1,90 do
2794 coroutine.yield()
2795 end
2796 selected = card[index+1]
2797 selectedindex = index+1
2798 local desired = 0
2799 for i=1,#card do
2800 if card[i] == selected and i ~= selectedindex then
2801 desired = i-1
2802 end
2803 end
2804 while index ~= desired do
2805 if desired <6 then
2806 if index >=6 then
2807 joypad.set(Player,{up=true})
2808 elseif desired > index then
2809 joypad.set(Player,{right=true})
2810 else
2811 joypad.set(Player,{left=true})
2812 end
2813 elseif desired >= 12 then
2814 if index <12 then
2815 joypad.set(Player,{down=true})
2816 elseif desired > index then
2817 joypad.set(Player,{right=true})
2818 else
2819 joypad.set(Player,{left=true})
2820 end
2821 else
2822 if index >= 12 then
2823 joypad.set(Player,{up=true})
2824 elseif index < 6 then
2825 joypad.set(Player,{down=true})
2826 elseif desired > index then
2827 joypad.set(Player,{right=true})
2828 else
2829 joypad.set(Player,{left=true})
2830 end
2831 end
2832 for a=1,20 do
2833 coroutine.yield()
2834 end
2835 index = memory.readbyte(0x428)
2836 end
2837 joypad.set(Player,{A=true})
2838 for a=1,90 do
2839 coroutine.yield()
2840 end
2841 else
2842 joypad.set(Player,{right=true})
2843 for a=1,20 do
2844 coroutine.yield()
2845 end
2846 end
2847 Tset = memory.readbyte(0x070A)
2848 end
2849end
2850
2851function writehumanlevel()
2852 local file = io.open("HumanLevelName.txt","w")
2853 local world = memory.readbyte(0x0727)+1
2854 file:write(world.."-" .. HumanLevelName)
2855 file:close()
2856end
2857
2858function PlayBoss()
2859 boss = true
2860 roottimeout = roottimeoutboss
2861 local prevhistory
2862 if pool then prevhistory = pool.history end
2863 initPool(true)
2864 local file = io.open("backups"..dirsep.."winners.txt","r")
2865 i = 1
2866 if file then
2867 while true do
2868 winnername = file:read("*line")
2869 if not winnername then break end
2870 dofile(winnername)
2871 addToSpecies(loadedgenome)
2872 if pool.species[#pool.species].nick == '' then
2873 pool.species[#pool.species].nick = loadedgenome.nick
2874 else
2875 pool.species[#pool.species].nick = pool.species[#pool.species].nick .. "/" .. loadedgenome.nick
2876 end
2877 i=i +1
2878 end
2879 end
2880 for g=i,999 do
2881 local genome = newGenome(1)
2882 mutate(genome,1)
2883 addToSpecies(genome)
2884 end
2885 getPositions()
2886 local score = {memory.readbyte(0x0715),memory.readbyte(0x0716),memory.readbyte(0x0717)}
2887 memory.writebyte(0x0715,0) --set score to 0
2888 memory.writebyte(0x0716,0)
2889 memory.writebyte(0x0717,0)
2890 getTileset()
2891 indicatorOutput()
2892 if prevhistory then pool.history = prevhistory end
2893 redospectop = pool.generation % 10 == 0
2894 while not playGeneration(redospectop) do
2895 fitnessDataOutput()
2896 newGeneration()
2897 redospectop = pool.generation % 10 == 0
2898 end
2899 local score2 = {memory.readbyte(0x0715),memory.readbyte(0x0716),memory.readbyte(0x0717)}
2900 memory.writebyte(0x0715,score[1]+score2[1]) --restore score
2901 memory.writebyte(0x0716,score[2]+score2[2])
2902 memory.writebyte(0x0717,score[3]+score2[3])
2903 savestateBackup = savestate.object(savestateSlotMap)
2904 savestate.save(savestateBackup)
2905 savestate.persist(savestateBackup)
2906 histogramOutput()
2907 saveGenome("bosswinner",true)
2908end
2909
2910
2911function playLevel()
2912 roottimeout = 120
2913 boss = false
2914 battle = false
2915 writehumanlevel()
2916 writescene(1)
2917 local warppipe = false
2918 while memory.readbyte(0x0020) == 1 do
2919 coroutine.yield()
2920 end
2921 if memory.readbyte(0x05FD) == 2 then
2922 while memory.readbyte(0x05FD) == 2 do
2923 joypad.set(Player,{A = true})
2924 coroutine.yield()
2925 joypad.set(Player,{A = false})
2926 coroutine.yield()
2927 end
2928 for x=1,480 do
2929 coroutine.yield()
2930 end
2931 end
2932 local Tset = memory.readbyte(0x070A)
2933 if Tset == 7 then
2934 local TASTime = {0,25,50}
2935 if memory.readbyte(0x7971) == 1 then
2936 TASTime = {25}
2937 end
2938 for i=1,300 do
2939 coroutine.yield()
2940 end
2941 for i=1,50+TASTime[math.random(#TASTime)] do
2942 joypad.set(Player,{right = true})
2943 coroutine.yield()
2944 end
2945 joypad.set(Player,{right = false})
2946 joypad.set(Player,{B = true})
2947 coroutine.yield()
2948 for i=1,300 do
2949 joypad.set(Player,{B = false})
2950 coroutine.yield()
2951 end
2952 savestateBackup = savestate.object(savestateSlotMap)
2953 savestate.save(savestateBackup)
2954 savestate.persist(savestateBackup)
2955 return
2956 end
2957
2958 local prevhistory
2959 if pool then prevhistory = pool.history end
2960 initPool(false)
2961 local file = io.open("backups"..dirsep.."winners.txt","r")
2962 local i = 1
2963 if file then
2964 while true do
2965 winnername = file:read("*line")
2966 if not winnername then break end
2967 dofile(winnername)
2968 addToSpecies(loadedgenome)
2969 if pool.species[#pool.species].nick == '' then
2970 pool.species[#pool.species].nick = loadedgenome.nick
2971 else
2972 pool.species[#pool.species].nick = pool.species[#pool.species].nick .. "/" .. loadedgenome.nick
2973 end
2974 i=i +1
2975 end
2976 end
2977 numberWinner = i
2978 for g=i,999 do
2979 local genome = newGenome(1)
2980 mutate(genome,1)
2981 addToSpecies(genome)
2982 end
2983 preloaded = false
2984 userreplay = false
2985 init = loadfile("initialize.lua")
2986 initializeclear = io.open("initialize.lua","w")
2987 if initializeclear then initializeclear:close() end
2988 if init then init() end
2989 fileFTracker = io.open("fitnesstracker.txt","w")
2990 fileFTracker:write(pool.history)
2991 fileFTracker:close()
2992 coroutine.yield()
2993 getPositions()
2994 local startx = marioX
2995 local starty = marioY
2996 for i,sprite in ipairs(sprites) do
2997 if sprite.type == 0x25 then
2998 warppipe = true
2999 userreplay = true
3000 preloaded = true
3001 end
3002 end
3003 --memory.writebyte(0x0715,0) --set score to 0
3004 --memory.writebyte(0x0716,0)
3005 --memory.writebyte(0x0717,0)
3006 getTileset()
3007 local toad = false
3008 if memory.readbyte(0x7965) == 1 then
3009 toad = true
3010 end
3011 local score = {memory.readbyte(0x0715),memory.readbyte(0x0716),memory.readbyte(0x0717)}
3012 if marioMusic == 0x70 then
3013 battle = true
3014 roottimeout = 900
3015 memory.writebyte(0x0715,0) --set score to 0
3016 memory.writebyte(0x0716,0)
3017 memory.writebyte(0x0717,0)
3018 end
3019 savestateObj = savestate.object(savestateSlotLevel)
3020 if not preloaded then
3021 savestate.save(savestateObj)
3022 savestate.persist(savestateObj)
3023 levelNameOutput()
3024 os.execute("mkdir backups"..dirsep..levelname)
3025 os.execute("mkdir data"..dirsep..levelname)
3026 indicatorOutput()
3027 end
3028 if prevhistory then pool.history = prevhistory end
3029 if warppipe then
3030 local cont = {}
3031 local avgx = {0}
3032 local goal = 0
3033 if startx < 60 then
3034 goal = 218
3035 elseif startx > 180 then
3036 goal = 24
3037 else
3038 goal = 122
3039 end
3040 while memory.readbyte(0x0014) ~= 1 do
3041 getPositions()
3042 if marioScreenX > goal+2 then
3043 cont['left'] = true
3044 cont['right'] = false
3045 elseif marioScreenX < goal-2 then
3046 cont['left'] = false
3047 cont['right'] = true
3048 elseif marioScreenX >= goal-2 and marioScreenX <= goal+2 then
3049 cont['left'] = false
3050 cont['right'] = false
3051 cont['down'] = true
3052 end
3053
3054 if marioScreenX - avgx[1] < 5 then
3055 cont['A'] = true
3056 cont['up'] = true
3057 cont['down']=false
3058 table.insert(avgx,0)
3059 else
3060 cont['A'] = false
3061 end
3062 table.insert(avgx,marioScreenX)
3063 if #avgx > 150 then
3064 table.remove(avgx,1)
3065 end
3066 joypad.set(Player,cont)
3067 coroutine.yield()
3068 end
3069 for i=1,60 do
3070 coroutine.yield()
3071 end
3072 end
3073 if not userreplay then
3074 redospectop = pool.generation == 0
3075 while not playGeneration(redospectop) do
3076 fitnessDataOutput()
3077 newGeneration()
3078 redospectop = false
3079 end
3080 if battle then
3081 local score2 = {memory.readbyte(0x0715),memory.readbyte(0x0716),memory.readbyte(0x0717)}
3082 memory.writebyte(0x0715,score[1]+score2[1]) --restore score
3083 memory.writebyte(0x0716,score[2]+score2[2])
3084 memory.writebyte(0x0717,score[3]+score2[3])
3085 end
3086 for i=1,60 do
3087 if toad then
3088 memory.writebyte(0x7966,8) --coins required
3089 end
3090 coroutine.yield()
3091 end
3092 if boss then
3093 for i=1,600 do
3094 joypad.set(Player,{A = true, left = false, right = false, up = false, down = false, B = false})
3095 coroutine.yield()
3096 joypad.set(Player,{A = false, left = false, right = false, up = false, down = false, B = false})
3097 coroutine.yield()
3098 end
3099 end
3100 savestateBackup = savestate.object(savestateSlotMap)
3101 savestate.save(savestateBackup)
3102 savestate.persist(savestateBackup)
3103 histogramOutput()
3104 saveGenome("winner",true)
3105 mapupdateHist()
3106 savePool("backups".. dirsep .. levelname .. dirsep .. "winpool.lua")
3107 end
3108 if not warppipe then
3109 Replay = true
3110 for i=1,#pool.breakthroughfiles do
3111 print(pool.breakthroughfiles[i])
3112 dofile(pool.breakthroughfiles[i])
3113 pool.generation = loadedgenome.gen
3114 pool.currentSpecies = loadedgenome.s
3115 pool.currentGenome = loadedgenome.g
3116 playGenome(loadedgenome)
3117 end
3118 if preloaded then
3119 savestateObj = savestate.object(savestateSlotMap)
3120 else
3121 savestateObj = savestateBackup
3122 end
3123 local f = io.open("../mapupdate.txt","w")
3124 f:write("")
3125 f:close()
3126 savestate.load(savestateObj)
3127 Replay = false
3128 os.rename("2", 'fcs'..dirsep..levelname ..'.fcs')
3129 if boss then
3130 os.rename("3", 'fcs'..dirsep..levelname ..'BOSS.fcs')
3131 end
3132 local file = io.open("savegamebackup.txt","w")
3133 file:write(memory.readbyte(0x0727))
3134 file:close()
3135 end
3136 writescene(3)
3137 os.remove('userinput.lua')
3138end
3139prevLN = ''
3140local leveldict = {[3]="1",[4]="2",[5]="3",[6]="4",[7]="5",[8]="6",[9]="7",[10]="8",[11]="9",[12]="10",[13]="1",[14]="2",[15]="3",[16]="4",[17]="5",[18]="6",[19]="7",[20]="8",[21]="9",[95]="Tower",[103]="Fortress",[104]="Quicksand",[105]="Piramid",[201]="Castle",[204]="Bowser's castle",[223]="Tower",[229]="Start",[235]="Fortress"}
3141HumanLevelName = ''
3142while true do
3143 if memory.readbyte(0x00EE) ~= 0 then
3144 if Player == 1 then
3145 levelname = memory.readbyte(0x0727).."-"..(memory.readbyte(0x7978)*8+memory.readbyte(0x797A)/32).."-"..memory.readbyte(0x7976)/32
3146 else
3147 levelname = memory.readbyte(0x0727).."-"..(memory.readbyte(0x7979)*8+memory.readbyte(0x797B)/32).."-"..memory.readbyte(0x7977)/32
3148 end
3149 if levelname ~= prevLN then
3150 local LocFile = io.open("OPosition.txt",'w')
3151 LocFile:write(levelname)
3152 LocFile:close()
3153 prevLN = levelname
3154 end
3155 playLevel()
3156 elseif memory.readbyte(0x070A) == 17 then
3157 SolveMemory()
3158 else
3159 if Player == 1 then
3160 levelname = memory.readbyte(0x0727).."-"..(memory.readbyte(0x77)*8+memory.readbyte(0x79)/32).."-"..memory.readbyte(0x75)/32
3161 else
3162 levelname = memory.readbyte(0x0727).."-"..(memory.readbyte(0x78)*8+memory.readbyte(0x7A)/32).."-"..memory.readbyte(0x76)/32
3163 end
3164 if levelname ~= prevLN then
3165 local LocFile = io.open("OPosition.txt",'w')
3166 LocFile:write(levelname)
3167 LocFile:close()
3168 prevLN = levelname
3169 end
3170 if file_exists("userinput.lua") then
3171 userinput=loadfile("userinput.lua")
3172 if userinput then
3173 os.remove('userinput.lua')
3174 uinput = {}
3175 pcall(userinput)
3176 if leveldict[memory.readbyte(0xE5)] ~= nil then
3177 HumanLevelName = leveldict[memory.readbyte(0xE5)]
3178 end
3179 if uinput.A and file_exists("fcs" .. dirsep .. levelname .. ".fcs") and memory.readbyte(0x05F2) == 0 then --0x5F2 1 = item folder. 0 = map
3180 local interrupt = io.open("interrupt.lua","w")
3181 interrupt:write("os.rename('fcs/" .. levelname ..".fcs', "..savestateSlotLevel..")\nsavestateObj = savestate.object(" .. savestateSlotLevel .. ")\nprint('one point five')\nsavestate.load(savestateObj)\nprint('two')")
3182
3183
3184 if file_exists('fcs' .. dirsep .. levelname .. 'BOSS.fcs') then
3185 interrupt:write("os.rename('fcs/" .. levelname .."BOSS.fcs', "..savestateSlotBOSS..")\nbosssavestateObj = savestate.object(" .. savestateSlotBOSS ..")")
3186 end
3187 interrupt:close()
3188 local initialize = io.open("initialize.lua","w")
3189 initialize:write("loadPool('backups/".. levelname .. "/winpool.lua')\n")
3190 initialize:write("preloaded = true\nuserreplay = true\nwritescene(2)")
3191 initialize:close()
3192 elseif uinput.A and Startlocations[levelname] then
3193 worldselection()
3194 else
3195 joypad.set(Player,uinput)
3196 end
3197 end
3198 end
3199 end
3200 coroutine.yield()
3201end
3202
3203return true