Skip to content

Commit e8cafb7

Browse files
author
Guled
authored
Merge pull request #4 from Somnibyte/neural-network-revision
Fixed Flappy bird bugs
2 parents a39d082 + cac5032 commit e8cafb7

File tree

6 files changed

+105
-56
lines changed

6 files changed

+105
-56
lines changed

Example/MLKit/GameScene.swift

+72-39
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,12 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
4848
var generationLabel: SKLabelNode!
4949
var fitnessLabel: SKLabelNode!
5050

51+
let groundTexture = SKTexture(imageNamed: "land")
5152

5253
/// Best score (regardless of generation)
5354
var bestScore: Int = 0
5455

56+
5557
/// Label that displays the best score (bestScore attribute)
5658
var bestScoreLabel: SKLabelNode!
5759

@@ -93,7 +95,7 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
9395
moving.addChild(pipes)
9496

9597
// ground
96-
let groundTexture = SKTexture(imageNamed: "land")
98+
9799
groundTexture.filteringMode = .nearest // shorter form for SKTextureFilteringMode.Nearest
98100

99101
let moveGroundSprite = SKAction.moveBy(x: -groundTexture.size().width * 2.0, y: 0, duration: TimeInterval(0.02 * groundTexture.size().width * 2.0))
@@ -207,7 +209,9 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
207209
self.addChild(fitnessLabel)
208210

209211
// Set the current bird
210-
currentBird = flappyBirdGenerationContainer?[currentFlappy]
212+
if let generation = flappyBirdGenerationContainer {
213+
currentBird = generation[currentFlappy]
214+
}
211215

212216
}
213217

@@ -293,22 +297,17 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
293297
currentTimeForFlappyBird = NSDate()
294298

295299
// Evaluate the current birds fitness
296-
currentBird?.generateFitness(score: score, time: Float(timeInterval))
297-
298-
// Current generation, bird, and fitness of current bird information
299-
print("--------------------------- \n")
300-
print("GENERATION: \(generationCounter)")
301-
print("BIRD COUNT: \(currentFlappy)")
302-
print("FITNESS: \(currentBird?.fitness)")
303-
self.generationLabel.text = "Gen: \(self.generationCounter)"
304-
print("--------------------------- \n")
305-
306-
// Go to next flappy bird
307-
currentFlappy += 1
300+
if let bird = currentBird {
301+
bird.generateFitness(score: score, time: Float(timeInterval))
308302

309-
// Filter out the worst birds after gen 6 (can be adjusted)
303+
// Current generation, bird, and fitness of current bird information
304+
print("--------------------------- \n")
305+
print("GENERATION: \(generationCounter)")
306+
print("BIRD COUNT: \(currentFlappy)")
307+
print("FITNESS: \(bird.fitness)")
308+
self.generationLabel.text = "Gen: \(self.generationCounter)"
309+
print("--------------------------- \n")
310310

311-
if let bird = currentBird {
312311
if bird.fitness >= 9.0 {
313312
print("FOUND RARE BIRD")
314313
print(bird.brain?.layers[0].weights)
@@ -318,10 +317,17 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
318317
}
319318
}
320319

321-
if generationCounter >= 3 {
320+
// Go to next flappy bird
321+
currentFlappy += 1
322322

323+
if let generation = flappyBirdGenerationContainer {
323324
// Experiment: Keep some of the last best birds and put them back into the population
324-
lastBestGen = (flappyBirdGenerationContainer?.filter({$0.fitness >= 7.0}))!
325+
lastBestGen = generation.filter({$0.fitness >= 6.0})
326+
327+
if lastBestGen.count > 10 {
328+
// We want to make room for unique birds
329+
lastBestGen = Array<FlappyGenome>(lastBestGen[0...6])
330+
}
325331
}
326332

327333
if let bird = currentBird {
@@ -341,8 +347,11 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
341347
// New generation array
342348
var newGen: [FlappyGenome] = []
343349

344-
newGen += lastBestGen
345-
newGen.append(maxBird!)
350+
newGen = lastBestGen
351+
352+
if let bestBird = maxBird {
353+
flappyBirdGenerationContainer?.append(bestBird)
354+
}
346355

347356
while newGen.count < 20 {
348357

@@ -382,17 +391,26 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
382391
flappyBirdGenerationContainer = newGen
383392

384393
// Set the current bird
385-
if (flappyBirdGenerationContainer?.count)! > currentFlappy {
386-
currentBird = flappyBirdGenerationContainer?[currentFlappy]
387-
} else {
388-
currentBird = maxBird
394+
395+
if let generation = flappyBirdGenerationContainer {
396+
if generation.count > currentFlappy{
397+
currentBird = generation[currentFlappy]
398+
}else{
399+
if let bestBird = maxBird {
400+
currentBird = maxBird
401+
}
402+
}
389403
}
390404

391405
} else {
392406

393407
// Set the current bird
394-
if (flappyBirdGenerationContainer?.count)! > currentFlappy {
395-
currentBird = flappyBirdGenerationContainer?[currentFlappy]
408+
if let generation = flappyBirdGenerationContainer {
409+
if generation.count > currentFlappy {
410+
currentBird = generation[currentFlappy]
411+
}
412+
}else{
413+
currentBird = maxBird
396414
}
397415

398416
}
@@ -402,8 +420,7 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
402420
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
403421
if moving.speed > 0 {
404422
for _ in touches { // do we need all touches?
405-
bird.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
406-
bird.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 30))
423+
407424
}
408425
} else if canRestart {
409426
self.resetScene()
@@ -457,6 +474,16 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
457474

458475
let normalizedDistanceOfNextPipe = (distanceOfNextPipe - 3)/(725-3)
459476

477+
let distanceFromTheGround = abs(self.bird.position.y - self.moving.children[1].position.y)
478+
479+
let normalizedDistanceFromTheGround = (distanceFromTheGround - 135)/(840-135)
480+
481+
let distanceFromTheSky = abs(880 - self.bird.position.y)
482+
483+
let normalizedDistanceFromTheSky = distanceFromTheSky/632
484+
485+
486+
460487
// Bird Y position
461488
let birdYPos = bird.position.y/CGFloat(880)
462489

@@ -465,25 +492,31 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
465492

466493
let normalizedPosToGap = (posToGap - (-439))/(279 - (-439))
467494

468-
// Decision AI makes
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)
495+
if let flappyBird = currentBird {
471496

472-
print("FLAPPY BIRD DECISION: \(decision)")
497+
// Decision AI makes
498+
let input = Matrix<Float>(rows: 6, columns: 1, elements: [Float(normalizedDistanceOfNextPipe), Float(normalizedPosToGap), Float(birdYPos), Float(normalizedDistanceFromBottomPipe), Float(normalizedDistanceFromTheGround), Float(normalizedDistanceFromTheSky)])
499+
let potentialDescision = flappyBird.brain?.feedforward(input: input)
473500

474-
// 0.95 was arbitrary, tweaking is recommended
475-
if (decision?.elements[0])! >= Float(0.5) {
476501

477-
if moving.speed > 0 {
502+
if let decision = potentialDescision {
478503

479-
bird.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
480-
bird.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 30))
504+
print("FLAPPY BIRD DECISION: \(decision)")
481505

482-
}
506+
if (decision.elements[0]) >= Float(0.5) {
483507

484-
} else {
508+
if moving.speed > 0 {
509+
510+
bird.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
511+
bird.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 30))
512+
513+
}
514+
515+
}
516+
}
485517

486518
}
519+
487520
}
488521

489522
if canRestart {

Example/MLKit/GameViewController.swift

+22-5
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,40 @@ class GameViewController: UIViewController {
6464
// Create First Generation of Flappy Birds
6565
var generation1: [FlappyGenome] = []
6666

67-
for _ in 1...20 {
67+
for _ in 1...19 {
6868

6969
// Bias already included
70-
let brain = NeuralNetwork(size: (4, 1))
71-
brain.addLayer(layer: Layer(size: (4, 4), activationType: .siglog))
72-
brain.addLayer(layer: Layer(size: (4, 1), activationType: .siglog))
70+
let brain = NeuralNetwork(size: (6, 1))
71+
brain.addLayer(layer: Layer(size: (6, 12), activationType: .siglog))
72+
brain.addLayer(layer: Layer(size: (12, 1), activationType: .siglog))
7373

7474
let newBird = FlappyGenome(genotype: GeneticOperations.encode(network: brain), network: brain)
7575

7676
generation1.append(newBird)
7777
}
7878

79+
// Best Bird Weights
80+
let brain = NeuralNetwork(size: (6, 1))
81+
brain.addLayer(layer: Layer(size: (6, 12), activationType: .siglog))
82+
brain.addLayer(layer: Layer(size: (12, 1), activationType: .siglog))
83+
84+
brain.layers[0].weights = Matrix<Float>(rows: 12, columns: 6, elements: ValueArray<Float>([1.14517, 0.691113, -0.938394, 0.798185, -1.20595, 0.732543, 0.174731, -1.0585,-0.500974,-1.02413,0.841067, -0.530047,-0.336522, -1.68883, -1.47241, 0.907576, 0.71408, 0.646764, -0.331544, 0.141004, 2.42381, 0.0683608, 1.01601, 1.42153, -0.672598, 0.889775, -1.55454, -0.530047, 0.307019, -0.483846, 0.0292488, 0.478605, 0.000960251 , -0.379445, -0.336532, -0.17253, 0.892149, -0.301041, 1.06579, -0.230897, 0.39673, -1.93952, 1.69516, 0.185731, -1.48985, -0.17253, -0.336532, -0.379445, 2.12388, 0.0292488, -0.483846, 0.307019, -1.29687, 0.941488, -1.50857 , -1.47241, 0.594132, 1.69516, 0.185731, -1.48985, -0.17253 , 1.06579, -0.301041, 0.892149, -1.15464, 1.15181,0.000960251, 0.478605, 0.0292488 , -0.483846, 0.307019, -1.29687]))
85+
86+
brain.layers[1].weights = Matrix<Float>(rows: 1, columns: 12, elements: ValueArray<Float>([1.10186, -1.68883, -0.336522, -2.54774, 0.202769, 1.50816 , -3.25252, 0.830278 , 0.104464, -1.26191, 0.698875, -0.447793]))
87+
88+
brain.layers[0].bias = Matrix<Float>(rows: 12, columns: 1, elements: ValueArray<Float>([0.941488, -1.50857, -1.47241, 0.594132, -0.189659, 0.804515, -1.60174, 0.741886, -0.811568, 0.0985006, -0.863954, -0.729362]))
89+
brain.layers[1].bias = Matrix<Float>(rows: 1, columns: 1, elements: ValueArray<Float>([0.440734]))
90+
91+
let bestBird = FlappyGenome(genotype: GeneticOperations.encode(network: brain), network: brain)
92+
generation1.append(bestBird)
93+
94+
7995
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
8096

8197
// Set the first generation of Flappy Birds
8298
scene.flappyBirdGenerationContainer = generation1
83-
99+
scene.maxBird = bestBird
100+
84101
// Configure the view.
85102
let skView = self.view as! SKView
86103
skView.showsFPS = false

Example/MLKit/GeneticOperations.swift

+8-9
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ final class GeneticOperations {
3333
genotypeRepresentation += Array<Float>(layer.bias!.elements)
3434
}
3535

36-
3736
return genotypeRepresentation
3837
}
3938

@@ -47,14 +46,14 @@ final class GeneticOperations {
4746
public static func decode(genotype: [Float]) -> NeuralNetwork {
4847

4948
// Create a new NueralNet
50-
let brain = NeuralNetwork(size: (4, 1))
51-
brain.addLayer(layer: Layer(size: (4, 4), activationType: .siglog))
52-
brain.addLayer(layer: Layer(size: (4, 1), activationType: .siglog))
53-
54-
brain.layers[0].weights = Matrix<Float>(rows: 4, columns: 4, elements: ValueArray<Float>(Array<Float>(genotype[0...15])))
55-
brain.layers[0].bias = Matrix<Float>(rows: 4, columns: 1, elements: ValueArray<Float>(Array<Float>(genotype[20...23])))
56-
brain.layers[1].weights = Matrix<Float>(rows: 1, columns: 4, elements: ValueArray<Float>(Array<Float>(genotype[16...19])))
57-
brain.layers[1].bias = Matrix<Float>(rows: 1, columns: 1, elements: ValueArray<Float>([genotype[24]]))
49+
let brain = NeuralNetwork(size: (6, 1))
50+
brain.addLayer(layer: Layer(size: (6, 12), activationType: .siglog))
51+
brain.addLayer(layer: Layer(size: (12, 1), activationType: .siglog))
52+
53+
brain.layers[0].weights = Matrix<Float>(rows: 12, columns: 6, elements: ValueArray<Float>(Array<Float>(genotype[0...71])))
54+
brain.layers[0].bias = Matrix<Float>(rows: 12, columns: 1, elements: ValueArray<Float>(Array<Float>(genotype[72...83])))
55+
brain.layers[1].weights = Matrix<Float>(rows: 1, columns: 12, elements: ValueArray<Float>(Array<Float>(genotype[84...95])))
56+
brain.layers[1].bias = Matrix<Float>(rows: 1, columns: 1, elements: ValueArray<Float>([genotype[96]]))
5857

5958
return brain
6059
}

MLKit/Classes/ANN/Layer.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ open class Layer {
103103
var a = input
104104

105105
self.input = input
106-
106+
107107
a = (self.weights! * a)
108108

109109
a = Matrix<Float>(rows: a.rows, columns: a.columns, elements: a.elements + self.bias!.elements)

MachineLearningKit.podspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
Pod::Spec.new do |s|
1010
s.name = 'MachineLearningKit'
11-
s.version = '0.1.6'
11+
s.version = '0.1.7'
1212
s.summary = 'A simple machine learning framework written in Swift 🤖'
1313

1414
s.description = <<-DESC

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ MLKit is available through [CocoaPods](http://cocoapods.org). To install
2020
it, simply add the following line to your Podfile:
2121

2222
```ruby
23-
pod 'MachineLearningKit', '0.1.6'
23+
pod 'MachineLearningKit', '0.1.7'
2424
```
2525

2626

0 commit comments

Comments
 (0)