8
8
9
9
import SpriteKit
10
10
import MachineLearningKit
11
+ import Upsurge
11
12
12
13
class GameScene : SKScene , SKPhysicsContactDelegate {
13
14
@@ -26,7 +27,7 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
26
27
var generationCounter = 1
27
28
28
29
/// Variable to keep track of the current time (this is used to determine fitness later)
29
- var currentTime = NSDate ( )
30
+ var currentTimeForFlappyBird = NSDate ( )
30
31
31
32
/// The red square (our goal area)
32
33
var goalArea : SKShapeNode !
@@ -43,6 +44,18 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
43
44
/// The best birds from the previous generation
44
45
var lastBestGen : [ FlappyGenome ] = [ ]
45
46
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
+
46
59
// END of ADDITIONS
47
60
48
61
let verticalPipeGap = 150.0
@@ -173,6 +186,26 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
173
186
scoreLabelNode. text = String ( score)
174
187
self . addChild ( scoreLabelNode)
175
188
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
+
176
209
// Set the current bird
177
210
currentBird = flappyBirdGenerationContainer ? [ currentFlappy]
178
211
@@ -256,8 +289,8 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
256
289
257
290
// Calculate the amount of time it took until the AI lost.
258
291
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 ( )
261
294
262
295
// Evaluate the current birds fitness
263
296
currentBird? . generateFitness ( score: score, time: Float ( timeInterval) )
@@ -267,36 +300,39 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
267
300
print ( " GENERATION: \( generationCounter) " )
268
301
print ( " BIRD COUNT: \( currentFlappy) " )
269
302
print ( " FITNESS: \( currentBird? . fitness) " )
303
+ self . generationLabel. text = " Gen: \( self . generationCounter) "
270
304
print ( " --------------------------- \n " )
271
305
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
-
283
306
// Go to next flappy bird
284
307
currentFlappy += 1
285
308
286
309
// 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
+
287
321
if generationCounter >= 3 {
288
322
289
323
// 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 } ) ) !
291
325
}
292
326
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
+ }
296
332
}
297
333
298
334
// If we have hit the 20th bird, we need to move on to the next generation
299
- if currentFlappy == 10 {
335
+ if currentFlappy == 20 {
300
336
301
337
print ( " GENERATING NEW GEN! " )
302
338
@@ -308,7 +344,7 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
308
344
newGen += lastBestGen
309
345
newGen. append ( maxBird!)
310
346
311
- while newGen. count < 10 {
347
+ while newGen. count < 20 {
312
348
313
349
// Select the best parents
314
350
let parents = PopulationManager . selectParents ( genomes: flappyBirdGenerationContainer!)
@@ -320,10 +356,10 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
320
356
var offspring = BiologicalProcessManager . onePointCrossover ( crossoverRate: 0.5 , parentOneGenotype: parents. 0 . genotypeRepresentation, parentTwoGenotype: parents. 1 . genotypeRepresentation)
321
357
322
358
// Mutate their genes
323
-
324
359
BiologicalProcessManager . inverseMutation ( mutationRate: 0.7 , genotype: & offspring. 0 )
325
360
BiologicalProcessManager . inverseMutation ( mutationRate: 0.7 , genotype: & offspring. 1 )
326
361
362
+
327
363
// Create a separate neural network for the birds based on their genes
328
364
let brainofOffspring1 = GeneticOperations . decode ( genotype: offspring. 0 )
329
365
@@ -346,12 +382,19 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
346
382
flappyBirdGenerationContainer = newGen
347
383
348
384
// Set the current bird
349
- currentBird = flappyBirdGenerationContainer ? [ currentFlappy]
385
+ if ( flappyBirdGenerationContainer? . count) ! > currentFlappy {
386
+ currentBird = flappyBirdGenerationContainer ? [ currentFlappy]
387
+ } else {
388
+ currentBird = maxBird
389
+ }
350
390
351
391
} else {
352
392
353
393
// Set the current bird
354
- currentBird = flappyBirdGenerationContainer ? [ currentFlappy]
394
+ if ( flappyBirdGenerationContainer? . count) ! > currentFlappy {
395
+ currentBird = flappyBirdGenerationContainer ? [ currentFlappy]
396
+ }
397
+
355
398
}
356
399
357
400
}
@@ -385,6 +428,10 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
385
428
386
429
override func update( _ currentTime: TimeInterval ) {
387
430
431
+ let endDate : NSDate = NSDate ( )
432
+ let timeInterval : Double = endDate. timeIntervalSince ( currentTimeForFlappyBird as Date )
433
+ self . fitnessLabel. text = " Fitness: \( NSString ( format: " %.2f " , timeInterval) ) "
434
+
388
435
checkIfOutOfBounds ( bird. position. y)
389
436
390
437
/* Called before each frame is rendered */
@@ -418,21 +465,14 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
418
465
419
466
let normalizedPosToGap = ( posToGap - ( - 439 ) ) / ( 279 - ( - 439 ) )
420
467
421
- /*
422
- print("======================== \n")
423
- print(" DIST: \((finalDistanceOfNextPipe))")
424
- print("PIPE POSITION: \(finalPosToGap)")
425
- print("Bird POS Y: \(birdPos)")
426
- print("======================== \n")
427
- */
428
-
429
468
// 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)
431
471
432
472
print ( " FLAPPY BIRD DECISION: \( decision) " )
433
473
434
474
// 0.95 was arbitrary, tweaking is recommended
435
- if decision [ 0 ] >= Float ( 0.89 ) {
475
+ if ( decision? . elements [ 0 ] ) ! >= Float ( 0.5 ) {
436
476
437
477
if moving. speed > 0 {
438
478
@@ -491,6 +531,12 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
491
531
score += 1
492
532
scoreLabelNode. text = String ( score)
493
533
534
+ // Update best score label
535
+ if score > bestScore {
536
+ bestScore = score
537
+ bestScoreLabel. text = " Best Score: \( self . bestScore) "
538
+ }
539
+
494
540
// Add a little visual feedback for the score increment
495
541
scoreLabelNode. run ( SKAction . sequence ( [ SKAction . scale ( to: 1.5 , duration: TimeInterval ( 0.1 ) ) , SKAction . scale ( to: 1.0 , duration: TimeInterval ( 0.1 ) ) ] ) )
496
542
} else {
0 commit comments