88
99import SpriteKit
1010import MachineLearningKit
11+ import Upsurge
1112
1213class GameScene : SKScene , SKPhysicsContactDelegate {
1314
@@ -26,7 +27,7 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
2627 var generationCounter = 1
2728
2829 /// Variable to keep track of the current time (this is used to determine fitness later)
29- var currentTime = NSDate ( )
30+ var currentTimeForFlappyBird = NSDate ( )
3031
3132 /// The red square (our goal area)
3233 var goalArea : SKShapeNode !
@@ -43,6 +44,18 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
4344 /// The best birds from the previous generation
4445 var lastBestGen : [ FlappyGenome ] = [ ]
4546
47+ /// SKLabel
48+ var generationLabel : SKLabelNode !
49+ var fitnessLabel : SKLabelNode !
50+
51+
52+ /// Best score (regardless of generation)
53+ var bestScore : Int = 0
54+
55+ /// Label that displays the best score (bestScore attribute)
56+ var bestScoreLabel : SKLabelNode !
57+
58+
4659 // END of ADDITIONS
4760
4861 let verticalPipeGap = 150.0
@@ -173,6 +186,26 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
173186 scoreLabelNode. text = String ( score)
174187 self . addChild ( scoreLabelNode)
175188
189+
190+ bestScore = 0
191+ bestScoreLabel = SKLabelNode ( fontNamed: " MarkerFelt-Wide " )
192+ bestScoreLabel. position = CGPoint ( x: self . frame. midX - 120.0 , y: 3 * self . frame. size. height / 4 + 110.0 )
193+ bestScoreLabel. zPosition = 100
194+ bestScoreLabel. text = " bestScore: \( self . bestScore) "
195+ self . addChild ( bestScoreLabel)
196+
197+ generationLabel = SKLabelNode ( fontNamed: " MarkerFelt-Wide " )
198+ generationLabel. position = CGPoint ( x: self . frame. midX - 150.0 , y: 3 * self . frame. size. height / 4 + 140.0 )
199+ generationLabel. zPosition = 100
200+ generationLabel. text = " Gen: 1 "
201+ self . addChild ( generationLabel)
202+
203+ fitnessLabel = SKLabelNode ( fontNamed: " MarkerFelt-Wide " )
204+ fitnessLabel. position = CGPoint ( x: self . frame. midX + 110.0 , y: 3 * self . frame. size. height / 4 + 140.0 )
205+ fitnessLabel. zPosition = 100
206+ fitnessLabel. text = " Fitness: 0 "
207+ self . addChild ( fitnessLabel)
208+
176209 // Set the current bird
177210 currentBird = flappyBirdGenerationContainer ? [ currentFlappy]
178211
@@ -256,8 +289,8 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
256289
257290 // Calculate the amount of time it took until the AI lost.
258291 let endDate : NSDate = NSDate ( )
259- let timeInterval : Double = endDate. timeIntervalSince ( currentTime as Date )
260- currentTime = NSDate ( )
292+ let timeInterval : Double = endDate. timeIntervalSince ( currentTimeForFlappyBird as Date )
293+ currentTimeForFlappyBird = NSDate ( )
261294
262295 // Evaluate the current birds fitness
263296 currentBird? . generateFitness ( score: score, time: Float ( timeInterval) )
@@ -267,36 +300,39 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
267300 print ( " GENERATION: \( generationCounter) " )
268301 print ( " BIRD COUNT: \( currentFlappy) " )
269302 print ( " FITNESS: \( currentBird? . fitness) " )
303+ self . generationLabel. text = " Gen: \( self . generationCounter) "
270304 print ( " --------------------------- \n " )
271305
272- /* DEBUGGING
273- if (currentBird?.fitness)! >= Float(7.0) {
274- print("-----BEST BIRD FOUND------")
275-
276- currentBird?.brain?.inputLayer.printLayer(layer: (currentBird?.brain?.inputLayer)!)
277- currentBird?.brain?.hiddenLayer.printLayer(listOfHiddenLayers: (currentBird?.brain?.listOfHiddenLayers)!)
278- currentBird?.brain?.outputLayer.printLayer(layer: (currentBird?.brain?.outputLayer)!)
279-
280- }
281- */
282-
283306 // Go to next flappy bird
284307 currentFlappy += 1
285308
286309 // Filter out the worst birds after gen 6 (can be adjusted)
310+
311+ if let bird = currentBird {
312+ if bird. fitness >= 9.0 {
313+ print ( " FOUND RARE BIRD " )
314+ print ( bird. brain? . layers [ 0 ] . weights)
315+ print ( bird. brain? . layers [ 1 ] . weights)
316+ print ( bird. brain? . layers [ 0 ] . bias)
317+ print ( bird. brain? . layers [ 1 ] . bias)
318+ }
319+ }
320+
287321 if generationCounter >= 3 {
288322
289323 // Experiment: Keep some of the last best birds and put them back into the population
290- lastBestGen = ( flappyBirdGenerationContainer? . filter ( { $0. fitness >= 4 } ) ) !
324+ lastBestGen = ( flappyBirdGenerationContainer? . filter ( { $0. fitness >= 7.0 } ) ) !
291325 }
292326
293- if ( currentBird? . fitness) ! > maxFitness {
294- maxFitness = ( currentBird? . fitness) !
295- maxBird = currentBird
327+ if let bird = currentBird {
328+ if bird. fitness > maxFitness {
329+ maxFitness = bird. fitness
330+ maxBird = bird
331+ }
296332 }
297333
298334 // If we have hit the 20th bird, we need to move on to the next generation
299- if currentFlappy == 10 {
335+ if currentFlappy == 20 {
300336
301337 print ( " GENERATING NEW GEN! " )
302338
@@ -308,7 +344,7 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
308344 newGen += lastBestGen
309345 newGen. append ( maxBird!)
310346
311- while newGen. count < 10 {
347+ while newGen. count < 20 {
312348
313349 // Select the best parents
314350 let parents = PopulationManager . selectParents ( genomes: flappyBirdGenerationContainer!)
@@ -320,10 +356,10 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
320356 var offspring = BiologicalProcessManager . onePointCrossover ( crossoverRate: 0.5 , parentOneGenotype: parents. 0 . genotypeRepresentation, parentTwoGenotype: parents. 1 . genotypeRepresentation)
321357
322358 // Mutate their genes
323-
324359 BiologicalProcessManager . inverseMutation ( mutationRate: 0.7 , genotype: & offspring. 0 )
325360 BiologicalProcessManager . inverseMutation ( mutationRate: 0.7 , genotype: & offspring. 1 )
326361
362+
327363 // Create a separate neural network for the birds based on their genes
328364 let brainofOffspring1 = GeneticOperations . decode ( genotype: offspring. 0 )
329365
@@ -346,12 +382,19 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
346382 flappyBirdGenerationContainer = newGen
347383
348384 // Set the current bird
349- currentBird = flappyBirdGenerationContainer ? [ currentFlappy]
385+ if ( flappyBirdGenerationContainer? . count) ! > currentFlappy {
386+ currentBird = flappyBirdGenerationContainer ? [ currentFlappy]
387+ } else {
388+ currentBird = maxBird
389+ }
350390
351391 } else {
352392
353393 // Set the current bird
354- currentBird = flappyBirdGenerationContainer ? [ currentFlappy]
394+ if ( flappyBirdGenerationContainer? . count) ! > currentFlappy {
395+ currentBird = flappyBirdGenerationContainer ? [ currentFlappy]
396+ }
397+
355398 }
356399
357400 }
@@ -385,6 +428,10 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
385428
386429 override func update( _ currentTime: TimeInterval ) {
387430
431+ let endDate : NSDate = NSDate ( )
432+ let timeInterval : Double = endDate. timeIntervalSince ( currentTimeForFlappyBird as Date )
433+ self . fitnessLabel. text = " Fitness: \( NSString ( format: " %.2f " , timeInterval) ) "
434+
388435 checkIfOutOfBounds ( bird. position. y)
389436
390437 /* Called before each frame is rendered */
@@ -418,21 +465,14 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
418465
419466 let normalizedPosToGap = ( posToGap - ( - 439 ) ) / ( 279 - ( - 439 ) )
420467
421- /*
422- print("======================== \n")
423- print(" DIST: \((finalDistanceOfNextPipe))")
424- print("PIPE POSITION: \(finalPosToGap)")
425- print("Bird POS Y: \(birdPos)")
426- print("======================== \n")
427- */
428-
429468 // Decision AI makes
430- let decision = ( currentBird? . brain? . forward ( input: [ Float ( 1 ) , Float ( normalizedDistanceOfNextPipe) , Float ( normalizedPosToGap) , Float ( birdYPos) , Float ( normalizedDistanceFromBottomPipe) ] ) ) !
469+ let input = Matrix < Float > ( rows: 4 , columns: 1 , elements: [ Float ( normalizedDistanceOfNextPipe) , Float ( normalizedPosToGap) , Float ( birdYPos) , Float ( normalizedDistanceFromBottomPipe) ] )
470+ let decision = currentBird? . brain? . feedforward ( input: input)
431471
432472 print ( " FLAPPY BIRD DECISION: \( decision) " )
433473
434474 // 0.95 was arbitrary, tweaking is recommended
435- if decision [ 0 ] >= Float ( 0.89 ) {
475+ if ( decision? . elements [ 0 ] ) ! >= Float ( 0.5 ) {
436476
437477 if moving. speed > 0 {
438478
@@ -491,6 +531,12 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
491531 score += 1
492532 scoreLabelNode. text = String ( score)
493533
534+ // Update best score label
535+ if score > bestScore {
536+ bestScore = score
537+ bestScoreLabel. text = " Best Score: \( self . bestScore) "
538+ }
539+
494540 // Add a little visual feedback for the score increment
495541 scoreLabelNode. run ( SKAction . sequence ( [ SKAction . scale ( to: 1.5 , duration: TimeInterval ( 0.1 ) ) , SKAction . scale ( to: 1.0 , duration: TimeInterval ( 0.1 ) ) ] ) )
496542 } else {
0 commit comments