├── .gitignore ├── LICENSE ├── README.md ├── actors.py ├── art ├── Intel-logo_blue.png ├── Intel-logo_white.png ├── as-logo.png ├── model.png ├── neuro-blast_logo.png ├── python-game_background.png └── python-sprites.png ├── audio ├── EnemyHit.wav ├── EnemyShoot.wav ├── HeroLaser.wav ├── Respawn.wav └── ShipExplode.wav ├── brain.py ├── font └── De Valencia (beta).otf ├── formulae.py ├── game.py ├── gameover.py ├── gamestates.py ├── go ├── README.md ├── actors.go ├── art │ ├── HeroSheet.png │ ├── HeroSheet.xcf │ ├── Intel-logo_blue.png │ ├── as-logo.png │ ├── bullet.png │ ├── bullethit.png │ ├── enemy.png │ ├── enemybullet.png │ ├── explosion.png │ ├── neuro-blast_logo.png │ └── python-game_background.png ├── audio │ ├── EnemyHit.wav │ ├── EnemyShoot.wav │ ├── HeroLaser.wav │ ├── Respawn.wav │ └── ShipExplode.wav ├── bulletsheet.csv ├── enemysheet.csv ├── explodesheet.csv ├── exported_brain │ ├── saved_model.pb │ └── variables │ │ ├── variables.data-00000-of-00001 │ │ └── variables.index ├── font │ └── DeValencia-Regular.ttf ├── leaderboard.go ├── lock.json ├── main.go ├── manifest.json ├── nn.go ├── sheet.csv ├── traindata.csv ├── utils.go └── weights.csv ├── leaderboard.py ├── neuralnetwork.py ├── neuroblast.jpg ├── parameters.py ├── requirements.txt ├── rungame.cmd └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/ 3 | *.pyc 4 | data/ 5 | boothgo 6 | debug 7 | traindata.csv 8 | weights.csv 9 | scores.db 10 | go/go 11 | joytest.py 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | Portions of this program Copyright (c) 2015 Milo Spencer-Harper 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | --- 25 | De Valencia Font License: https://www.fontsquirrel.com/license/de-valencia 26 | --- 27 | Gopher Artwork appears courtesy of Ashley McNamara. 28 | https://creativecommons.org/licenses/by-nc-sa/4.0/ 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![NeuroBlast](neuroblast.jpg) 2 | # Overview 3 | NeuroBlast is a classic arcade space shooter with ML-powered AI using TensorFlow. In this short demo intended to demonstrate the accessibility of open source tools for machine learning, you can train an enemy AI using machine learning. 4 | 5 | ## How it Works 6 | 7 | If you want to learn more about how the game works under the hood, and for a tour of the insides, see these two blog posts: 8 | 9 | - [Building an ML-Powered AI Using TensorFlow in Go](http://gopherdata.io/post/build_ml_powered_game_ai_tensorflow/) on GopherData.io 10 | - [Building Game AI Using Machine Learning: Working With TensorFlow, Keras, and the Intel MKL in Python](https://www.activestate.com/blog/2017/05/building-game-ai-using-machine-learning-working-tensorflow-keras-and-intel-mkl-python) 11 | 12 | You can also [watch](https://www.youtube.com/watch?v=oiorteQg9n0) my GopherCon 2017 Lightning talk about the game or [view the slides](https://github.com/gophercon/2017-talks/tree/master/lightningtalks/PeteGarcin-BuildingMLPoweredGameAIwithTensorFlow). 13 | 14 | ## Installation - Requirements 15 | 16 | Before you start, you will require the following external libraries/tools: 17 | 18 | - Python 3.5 or Go 1.8 19 | - MongoDB server [Download](https://www.mongodb.com/download-center?jmp=nav#community) 20 | - TensorFlow C libraries for Go version [Instructions](https://www.tensorflow.org/install/install_go) 21 | 22 | *Note: TensorFlow for Go is only available of macOS/Linux. Windows is NOT supported.* 23 | 24 | For any package dependencies, you can either install them via pip/dep or you can also install [ActivePython 3.5.3](https://www.activestate.com/activepython/downloads) or [ActiveGo 1.8](https://www.activestate.com/activego/downloads) to have an environment with nearly all dependencies already pre-installed. 25 | 26 | ## Windows Setup Instructions 27 | 28 | To setup the game to run on Windows in Python 3: 29 | 30 | 1. `git clone https://github.com/ActiveState/neuroblast.git` 31 | 2. `pip3 install -r requirements.txt` 32 | 3. Launch `rungame.cmd` (which will launch the Mongo server). 33 | 34 | ## MacOS/Linux Setup Instructions 35 | 36 | To setup the game to run on MacOS/Linux in Python 3: 37 | 38 | 1. `git clone https://github.com/ActiveState/neuroblast.git` 39 | 2. `cd neuroblast` 40 | 3. `mkdir db` 41 | 2. `pip3 install -r requirements.txt` 42 | 3. Launch Mongo server: `mongod --dbpath ./db` 43 | 4. Launch the game `python3 game.py` 44 | 45 | *Note: If you previously had Keras installed on your machine, and had run it using a different backend, make sure you configure your Keras backend to run using TensorFlow by following these [instructions](https://keras.io/backend/).* 46 | 47 | To setup the game to run on MacOS/Linux in Go 1.8: 48 | 49 | *Reminder: You must have the TensorFlow C libraries installed as per these [instructions](https://www.tensorflow.org/install/install_go).* 50 | 51 | *Note: You must clone the repo into your GOPATH, or add the folder you clone into to your GOPATH in order for `dep ensure` to work.* 52 | 53 | 1. `git clone https://github.com/ActiveState/neuroblast.git` 54 | 2. `cd go` 55 | 2. `dep ensure` 56 | 3. `go build` 57 | 4. Launch the game `./go` 58 | 59 | ## Command Line Arguments 60 | 61 | There are a number of options available when running the game from Python: 62 | 63 | - `-n` changes the Neural Network model from using TensorFlow to using the "home grown" network model which is useful for prototyping/debugging. 64 | - `-f` launches the game in full screen. 65 | - `-v` changes the visualization method to use raw Keras/TensorFlow values. Warning: This is much slower! 66 | 67 | *Note: These options are not available in the Go version.* 68 | 69 | ## Controls 70 | 71 | ### Movement 72 | 73 | Control your ship with the arrow keys or the left-analog stick with a gamepad. 74 | 75 | ### Firing 76 | 77 | Use either `SPACE` or the A button on your gamepad to fire. You can hold the button down for continuous fire. 78 | 79 | ### Debug Menu Commands 80 | 81 | On the main menu of the Python version, you can use the following commands to export debug data: 82 | 83 | `x` - Exports a model using the SavedModelBuilder functionality to use in the Go version 84 | `w` - Exports weights.csv which are the trained weights of the current model 85 | `d` - Exports traindata.csv which is a dump of the training data points currently in memory 86 | 87 | ## Known Issues 88 | 89 | - Collision is not pixel-perfect 90 | - In the Python version, being hit will slow down your rate of movement 91 | - You can fly through the enemy ships with your ship (no collision between enemy ships/hero ship) 92 | - Some gamepad configurations may not work 93 | - Menu navigation with gamepad is inconsistent 94 | 95 | ## License 96 | 97 | Copyright (C) 2017 ActiveState. Licensed under the MIT License. See LICENSE file for details. 98 | 99 | ## Credits 100 | 101 | Written by Pete Garcin [Twitter](https://twitter.com/rawktron)/[GitHub](https://github.com/rawktron) and Tom Radcliffe. 102 | 103 | Gopher Artwork appears courtesy of [Ashley McNamara](https://github.com/ashleymcnamara/gophers). 104 | 105 | ## Contributing 106 | 107 | If you would like to contribute to this project, please visit the Issues tab to see what open issues are available and flagged with Help Wanted. You can also submit a Pull Request with your proposed changes. If they are in response to an issue, please reference the issue in the pull request. 108 | 109 | 110 | -------------------------------------------------------------------------------- /actors.py: -------------------------------------------------------------------------------- 1 | '''The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | 5 | Written by Pete Garcin @rawktron 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE.''' 24 | 25 | from __future__ import division 26 | import pygame 27 | import math 28 | from utils import * 29 | from random import randrange 30 | import numpy as np 31 | 32 | 33 | # TODO Make a proper audio management class 34 | pygame.mixer.init() 35 | shootsfx = pygame.mixer.Sound('audio/HeroLaser.wav') 36 | hitsfx = pygame.mixer.Sound('audio/EnemyHit.wav') 37 | enemyshootsfx = pygame.mixer.Sound('audio/EnemyShoot.wav') 38 | explodesfx = pygame.mixer.Sound('audio/ShipExplode.wav') 39 | respawnsfx = pygame.mixer.Sound('audio/Respawn.wav') 40 | 41 | spritesheet = pygame.image.load("art/python-sprites.png") 42 | 43 | class SpriteSequence(object): 44 | def __init__(self,name,sheet,rect,cols, rows, padding,interval, loop, cb): 45 | self.name = name 46 | self.sheet = sheet 47 | self.rect = rect 48 | self.cols = cols 49 | self.rows = rows 50 | self.padding = padding 51 | self.interval = interval 52 | self.loop = loop 53 | self.currentcol = 0 54 | self.currentrow = 0 55 | self.playing = False 56 | self.counter = 0 57 | self.cb = cb 58 | def play(self): 59 | self.counter = 0 60 | self.currentcol = 0 61 | self.currentrow = 0 62 | self.playing = True 63 | 64 | def stop(self): 65 | self.counter = 0 66 | self.currentcol = 0 67 | self.currentrow = 0 68 | self.playing = False 69 | 70 | def update(self, surface, pos, dt): 71 | if self.playing: 72 | self.counter += dt 73 | if self.counter>=self.interval: 74 | self.counter = 0 75 | self.currentcol += 1 76 | if self.currentcol >= self.cols: 77 | self.currentcol = 0 78 | self.currentrow += 1 79 | if self.currentrow >= self.rows: 80 | if self.loop: 81 | self.currentcol=0 82 | self.currentrow=0 83 | else: 84 | if (self.cb): 85 | self.cb(self.name) 86 | self.playing=False 87 | 88 | if self.playing: 89 | surface.blit(self.sheet,pos,(self.rect.x+self.currentcol*self.rect.w+self.currentcol*self.padding, 90 | self.rect.y+self.currentrow*self.rect.h+self.currentrow*self.padding, 91 | self.rect.w,self.rect.h)) 92 | 93 | class Bullet(pygame.sprite.Sprite): 94 | def __init__(self, x, y, color, direction, speed, container, brain = None): 95 | pygame.sprite.Sprite.__init__(self, container) 96 | 97 | self.image = pygame.Surface((16,16), flags=pygame.SRCALPHA) 98 | self.rect = self.image.get_rect() 99 | basex = 423 100 | if color==RED: 101 | basex += 96 102 | ## Generate the sprite image from spritesheet 103 | ssrect = pygame.Rect((basex,710,16,16)) 104 | global spritesheet 105 | self.image.blit(spritesheet,(0,0),ssrect) 106 | self.rect = self.image.get_rect() 107 | self.rect.center = (x, y) 108 | self.direction = direction 109 | self.speed = speed 110 | self.brain = brain 111 | 112 | def update(self, dt): 113 | (x, y) = self.rect.center 114 | y += self.direction[1] * self.speed * dt 115 | x += self.direction[0] * self.speed * dt 116 | self.rect.center = (x, y) 117 | # TODO make the bounds constants or dynamic 118 | if y <= 0 or y >= 720 or x <= 0 or x >= 640: 119 | if self.brain: 120 | self.brain.record_miss(self) 121 | self.kill() 122 | 123 | class Killable(pygame.sprite.Sprite): 124 | def __init__(self): 125 | super(Killable, self).__init__() 126 | self.lives = 0 127 | self.health = 100 128 | self.blinking = False 129 | self.blinktime = 0.5 130 | self.blinkcycles = 6 131 | self.blinks = 0 132 | self.blinkcount = 0 133 | self.blinkon = False 134 | self.deadcb = None 135 | self.anim = None 136 | self.animoffset = (0,0) 137 | 138 | 139 | def TakeDamage(self, damage): 140 | # Already dead! 141 | if self.health<=0: 142 | return 143 | 144 | self.health -= damage 145 | if self.health <= 0: 146 | self.lives -= 1 147 | if self.deadcb: 148 | self.deadcb() 149 | if (self.lives>=0): 150 | # DO something 151 | # Trigger Particle or something 152 | self.blinking = True 153 | self.blinks = 0 154 | self.health = 100 155 | 156 | 157 | def Die(self): 158 | self.kill() 159 | 160 | class Enemy(Killable): 161 | def __init__(self, bulletgroup, brain, speed): 162 | super(Enemy, self).__init__() 163 | # Enemy specific stuff here 164 | self.x = randrange(0,450) 165 | self.y = -50 166 | self.velx = 0 167 | self.vely = speed # wish there was a vector class 168 | self.bullets = bulletgroup 169 | self.image = pygame.Surface((96,192)) 170 | self.rect = self.image.get_rect() 171 | self.cooldown = 0.1 172 | self.canfire = True 173 | self.bulcount = 0 174 | self.brain = brain 175 | ## Generate the sprite image from spritesheet 176 | ssrect = pygame.Rect((96,192,96,192)) 177 | global spritesheet 178 | self.image.blit(spritesheet,(0,0),ssrect) 179 | self.image.convert() 180 | self.image.set_colorkey(self.image.get_at((0, 0))) 181 | self.rect.center = (self.x, self.y) 182 | self.spawntime = pygame.time.get_ticks() 183 | self.deadcb = self.amdead 184 | self.idleAnim = SpriteSequence("idle",spritesheet,pygame.Rect(96,192,96,192),7,1,0,0.1,True,None) 185 | self.hitAnim = SpriteSequence("hit",spritesheet,pygame.Rect(96,480,96,96),8,1,0,0.1,False,None) 186 | self.blowAnim = SpriteSequence("blow",spritesheet,pygame.Rect(96,384,96,96),8,1,0,0.1,False,self.onAnimComplete) 187 | self.idleAnim.play() 188 | 189 | 190 | 191 | def onAnimComplete(self,name): 192 | if name == "blow": 193 | self.Die() 194 | 195 | def amdead(self): 196 | self.playanim("blow",(self.x-48,self.y-24)) 197 | 198 | 199 | def playanim(self,name,offset): 200 | if self.anim != self.blowAnim and name=="hit": 201 | hitsfx.play() 202 | self.anim = self.hitAnim 203 | self.animoffset = (offset[0]-self.x,offset[1]-self.y) 204 | self.anim.play() 205 | if self.anim != self.blowAnim and name=="blow": 206 | explodesfx.play() 207 | self.anim = self.blowAnim 208 | self.animoffset = (offset[0]-self.x,offset[1]-self.y) 209 | self.anim.play() 210 | 211 | 212 | def update(self, screen, event_queue, dt, ppos, pvel, trainingMode, netmodel): 213 | if not self.alive(): 214 | return 215 | 216 | player_x, player_y = ppos 217 | player_velx, player_vely = pvel 218 | 219 | self.velx = math.sin((pygame.time.get_ticks()-self.spawntime)/1800) * 40 220 | self.x += self.velx * dt 221 | self.y += self.vely * dt 222 | 223 | self.rect.center = (self.x, self.y) 224 | 225 | self.image.fill((0,0,0)) 226 | self.idleAnim.update(self.image,(0,0),dt) 227 | 228 | if not(self.canfire): 229 | self.bulcount += dt 230 | if self.bulcount>self.cooldown: 231 | self.canfire = True 232 | self.bulcount = 0 233 | 234 | # Normalized values 235 | dx = (self.x - player_x) / 640 236 | dy = (self.y - player_y) / 720 237 | du = (self.velx - player_velx) / 60 238 | dv = (self.vely - player_vely) / 60 239 | 240 | 241 | self.brain.currentState = np.array([list((dx,dy,du,dv))]) 242 | 243 | if self.canfire: 244 | if (trainingMode and randrange(0,100)<10) or ((netmodel == 1 and not trainingMode and self.brain.keras.predict(np.array([list((dx,dy,du,dv))]))>=0.5) or (netmodel == 0 and not trainingMode and self.brain.model.think([dx,dy,du,dv])>=0.5)): 245 | # Cheat to do parallel visualization, sort of performance intensive 246 | if (not trainingMode and netmodel == 1): 247 | self.brain.model.think([dx,dy,du,dv]) 248 | bul = Bullet(self.x,self.y+96,RED,(0,1),160,self.bullets,self.brain) 249 | self.brain.add_shot(bul, dx, dy, du, dv) 250 | self.canfire = False 251 | 252 | class Player(Killable): 253 | def __init__(self,bulletgroup): 254 | super(Player, self).__init__() 255 | global spritesheet 256 | spritesheet.convert_alpha() 257 | self.cooldown = 0.5 258 | self.canfire = True 259 | self.bulcount = 0 260 | self.x = 320 261 | self.y = 500 262 | self.velx = 0 263 | self.vely = 0 # wish there was a vector class 264 | self.deadcb = self.amdead 265 | self.bullets = bulletgroup 266 | self.image = pygame.Surface((96,96)) 267 | self.rect = self.image.get_rect() 268 | ## Generate the sprite image from spritesheet 269 | ssrect = pygame.Rect((96,96,96,96)) 270 | self.image.blit(spritesheet,(0,0),ssrect) 271 | self.image.convert() 272 | self.image.set_colorkey(self.image.get_at((0, 0))) 273 | self.hitAnim = SpriteSequence("hit",spritesheet,pygame.Rect(96,480,96,96),8,1,0,0.1,False,None) 274 | self.blowAnim = SpriteSequence("blow",spritesheet,pygame.Rect(96,384,96,96),8,1,0,0.1,False,self.onAnimComplete) 275 | self.idleAnim = SpriteSequence("idle",spritesheet,pygame.Rect(96,576,96,192),8,1,0,0.1,True,None) 276 | self.idleAnim.play() 277 | 278 | def onAnimComplete(self,name): 279 | if name == "blow": 280 | respawnsfx.play() 281 | if self.lives<0: 282 | self.Die() 283 | 284 | 285 | def amdead(self): 286 | self.playanim("blow",(self.x-48,self.y-48)) 287 | 288 | def playanim(self,name,offset): 289 | if self.anim != self.blowAnim and name=="hit": 290 | hitsfx.play() 291 | self.anim = self.hitAnim 292 | self.animoffset = (offset[0]-self.x,offset[1]-self.y) 293 | self.anim.play() 294 | if self.anim != self.blowAnim and name=="blow": 295 | explodesfx.play() 296 | self.anim = self.blowAnim 297 | self.animoffset = (offset[0]-self.x,offset[1]-self.y) 298 | self.anim.play() 299 | 300 | 301 | 302 | def update(self, screen, event_queue, dt, joystick): 303 | 304 | self.rect.center = (self.x, self.y) 305 | 306 | self.image.fill((0,0,0)) 307 | self.idleAnim.update(self.image,(0,0),dt) 308 | 309 | if self.blinking: 310 | self.blinkcount += dt 311 | 312 | if self.blinkcount >= self.blinktime: 313 | self.blinkon = not self.blinkon 314 | self.blinkcount = 0 315 | self.blinks +=1 316 | if (self.blinks == self.blinkcycles): 317 | self.blinking = False 318 | 319 | if not(self.canfire): 320 | self.bulcount += dt 321 | if self.bulcount>self.cooldown: 322 | self.canfire = True 323 | self.bulcount = 0 324 | 325 | keys=pygame.key.get_pressed() 326 | 327 | if keys[pygame.K_LEFT] or (joystick and (joystick.get_axis(0)<-DEADZONE or joystick.get_button(2))): 328 | self.velx = -SHIP_ACC 329 | if keys[pygame.K_RIGHT] or (joystick and (joystick.get_axis(0)>DEADZONE or joystick.get_button(3))): 330 | self.velx = SHIP_ACC 331 | if keys[pygame.K_UP] or (joystick and (joystick.get_axis(1)<-DEADZONE or joystick.get_button(0))): 332 | self.vely = -SHIP_ACC 333 | if keys[pygame.K_DOWN] or (joystick and (joystick.get_axis(1)>DEADZONE or joystick.get_button(1))): 334 | self.vely = SHIP_ACC 335 | if self.canfire and (keys[pygame.K_SPACE] or (joystick and joystick.get_button(0))): 336 | bul = Bullet(self.x,self.y-42,BLUE,(0,-1),320,self.bullets) 337 | self.canfire = False 338 | shootsfx.play() 339 | 340 | self.velx = min(self.velx, self.health*2) 341 | self.velx = max(self.velx, -self.health*2) 342 | self.vely = min(self.vely, self.health) 343 | self.vely = max(self.vely, -self.health) 344 | 345 | if not (keys[pygame.K_UP] or keys[pygame.K_DOWN] or (joystick and (math.fabs(joystick.get_axis(1))>DEADZONE or joystick.get_button(0) or joystick.get_button(1)))): 346 | self.vely = 0 347 | if not (keys[pygame.K_LEFT] or keys[pygame.K_RIGHT] or (joystick and (math.fabs(joystick.get_axis(0))>DEADZONE or joystick.get_button(2) or joystick.get_button(3)))): 348 | self.velx = 0 349 | 350 | if self.x+(self.velx*dt)>640-48 or self.x+(self.velx*dt)<48: 351 | self.velx = 0 352 | if self.y+(self.vely*dt)>720-48 or self.y+(self.vely*dt)<48: 353 | self.vely = 0 354 | 355 | self.x += self.velx * dt 356 | self.y += self.vely * dt 357 | -------------------------------------------------------------------------------- /art/Intel-logo_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/art/Intel-logo_blue.png -------------------------------------------------------------------------------- /art/Intel-logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/art/Intel-logo_white.png -------------------------------------------------------------------------------- /art/as-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/art/as-logo.png -------------------------------------------------------------------------------- /art/model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/art/model.png -------------------------------------------------------------------------------- /art/neuro-blast_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/art/neuro-blast_logo.png -------------------------------------------------------------------------------- /art/python-game_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/art/python-game_background.png -------------------------------------------------------------------------------- /art/python-sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/art/python-sprites.png -------------------------------------------------------------------------------- /audio/EnemyHit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/audio/EnemyHit.wav -------------------------------------------------------------------------------- /audio/EnemyShoot.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/audio/EnemyShoot.wav -------------------------------------------------------------------------------- /audio/HeroLaser.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/audio/HeroLaser.wav -------------------------------------------------------------------------------- /audio/Respawn.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/audio/Respawn.wav -------------------------------------------------------------------------------- /audio/ShipExplode.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/audio/ShipExplode.wav -------------------------------------------------------------------------------- /brain.py: -------------------------------------------------------------------------------- 1 | '''The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | 5 | Written by Tom Radcliffe, Pete Garcin @rawktron 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE.''' 24 | 25 | 26 | from keras.models import Sequential 27 | from keras.layers import Dense, Activation 28 | import numpy as np 29 | from random import randrange 30 | import pygame 31 | 32 | from neuralnetwork import NeuralNetwork 33 | from neuralnetwork import * 34 | from formulae import calculate_average_error, seed_random_number_generator 35 | import parameters 36 | 37 | class TrainingExample(): 38 | def __init__(self, inputs, output): 39 | self.inputs = inputs 40 | self.output = output 41 | 42 | class Brain: 43 | 44 | def __init__(self): 45 | self.mapShots = {} 46 | self.mapHits = {} 47 | self.trained = False 48 | self.currentState = np.array([list((0,0,0,0))]) 49 | self.weights = [] 50 | 51 | self.id = randrange(0,100) 52 | # create model 53 | self.model = NeuralNetwork([4, 6, 4, 4, 1]) 54 | 55 | # create model 56 | self.keras = Sequential() 57 | 58 | # Configure the Keras Model 59 | self.keras.add(Dense(4, input_shape=(4,), activation='relu')) 60 | self.keras.add(Dense(6, activation='relu')) 61 | self.keras.add(Dense(4, activation='relu')) 62 | self.keras.add(Dense(4, activation='relu')) 63 | self.keras.add(Dense(1, activation='sigmoid')) 64 | self.keras.compile(loss='mean_squared_error', optimizer='sgd', metrics=['accuracy']) 65 | 66 | # Initialize weights array 67 | for layer in self.keras.layers: 68 | self.weights.append(layer.get_weights()[0]) 69 | 70 | 71 | # Keras version of learning 72 | def train(self): 73 | # Builds the model based on the dataset to this point 74 | # Create a n * 4 matrix for the input data 75 | x = [] 76 | y = [] 77 | for k,v in self.mapShots.items(): 78 | # Convert our tuple to a numpy array 79 | if k in self.mapHits: 80 | a = list(v) 81 | x.append(a) 82 | y.append(self.mapHits[k]) 83 | 84 | # Fit the data to the model 85 | self.keras.fit(x,y,nb_epoch=150,batch_size=10) 86 | scores = self.keras.evaluate(x, y) 87 | print("\n%s: %.2f%%" % (self.keras.metrics_names[1], scores[1]*100)) 88 | 89 | # Cache trained weights for visualization 90 | # Element 0 is weights, 1 is biases 91 | for layer in self.keras.layers: 92 | self.weights.append(layer.get_weights()[0]) 93 | 94 | # "Home grown" Neural Net implementation 95 | def learn(self): 96 | # Builds the model based on the dataset to this point 97 | # Create a n * 4 matrix for the input data 98 | x = [] 99 | y = [] 100 | cumulative_error = 0 101 | for k,v in self.mapShots.items(): 102 | # Convert our tuple to a numpy array 103 | if k in self.mapHits: 104 | a = list(v) 105 | cumulative_error += self.model.train(TrainingExample(a,self.mapHits[k])) 106 | 107 | # Fit the data to the model 108 | self.trained = True 109 | 110 | def add_shot(self, bullet, dx, dy, du, dv): 111 | self.mapShots[bullet] = (dx, dy, du, dv) 112 | 113 | def record_hit(self, bullet): 114 | self.mapHits[bullet] = 1 115 | 116 | def record_miss(self, bullet): 117 | self.mapHits[bullet] = 0 118 | 119 | def draw(self,screen,vizmodel): 120 | if (vizmodel == 1): 121 | draw_network(screen, self.keras,self.currentState, self.weights) 122 | else: 123 | self.model.draw(screen) -------------------------------------------------------------------------------- /font/De Valencia (beta).otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/font/De Valencia (beta).otf -------------------------------------------------------------------------------- /formulae.py: -------------------------------------------------------------------------------- 1 | '''The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | Copyright (c) 2015 Milo Spencer-Harper 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE.''' 23 | 24 | from numpy import exp, random 25 | from math import atan, sin, cos 26 | import parameters 27 | 28 | 29 | def sigmoid(x): 30 | return 1 / (1 + exp(-x)) 31 | 32 | 33 | def sigmoid_derivative(x): 34 | return x * (1 - x) 35 | 36 | 37 | def seed_random_number_generator(): 38 | random.seed(1) 39 | 40 | 41 | def random_weight(): 42 | return 2 * random.random() - 1 43 | 44 | 45 | def get_synapse_colour(weight): 46 | if weight > 0: 47 | return 0, 255, 0 48 | else: 49 | return 255, 0, 0 50 | 51 | 52 | def adjust_line_to_perimeter_of_circle(x1, x2, y1, y2): 53 | angle = atan((x2 - x1) / float(y2 - y1)) 54 | x_adjustment = parameters.neuron_radius * sin(angle) 55 | y_adjustment = parameters.neuron_radius * cos(angle) 56 | return x1 - x_adjustment, x2 + x_adjustment, y1 - y_adjustment, y2 + y_adjustment 57 | 58 | 59 | def layer_left_margin(number_of_neurons): 60 | return parameters.left_margin + parameters.horizontal_distance_between_neurons * (parameters.number_of_neurons_in_widest_layer - number_of_neurons) / 2 61 | 62 | 63 | def calculate_average_error(cumulative_error, number_of_examples): 64 | if cumulative_error: 65 | return round(cumulative_error * 100 / number_of_examples, 2) 66 | else: 67 | return None -------------------------------------------------------------------------------- /game.py: -------------------------------------------------------------------------------- 1 | '''The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | 5 | Written by Pete Garcin @rawktron 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE.''' 24 | 25 | # game.py 26 | # Core game initialization, main game loop, etc. 27 | 28 | from __future__ import division 29 | import pygame 30 | import pygame.freetype 31 | import math 32 | import numpy as np 33 | import gamestates 34 | from utils import * 35 | import sys 36 | import argparse 37 | 38 | ### GLOBAL GAME INIT AND MAIN LOOP 39 | # Basic library initialization 40 | pygame.init() 41 | loadfont(24) 42 | parser = argparse.ArgumentParser() 43 | parser.add_argument('-f',action='store_true') 44 | parser.add_argument('-n',action='store_true') 45 | parser.add_argument('-v',action='store_true') 46 | 47 | args = parser.parse_args() 48 | 49 | # 0 is internal neural net, 1 is keras/tensorflow (default) 50 | netmodel = 1 51 | 52 | # VizModel is which viz method, direct Keras (1) or simulated neural net (0) (faster) 53 | vizmodel = 0 54 | 55 | # Configure screen TODO: Should there be a config object or something to contain this? 56 | resolution = (1280, 720) 57 | flags = pygame.DOUBLEBUF 58 | 59 | if (args.f == True): 60 | flags |= pygame.FULLSCREEN 61 | 62 | # Netmodel = 1 means Keras/Tensorflow, 0 = internal simple neural net for prototyping 63 | if (args.n == True): 64 | netmodel = 0 65 | 66 | if (args.v == True): 67 | vizmodel = 1 68 | 69 | screen = pygame.display.set_mode(resolution, flags) 70 | screen.set_alpha(None) 71 | 72 | pygame.display.set_caption("Neuro/Blast") 73 | 74 | background = pygame.image.load('art/python-game_background.png') 75 | bgsize = background.get_size() 76 | w, h = bgsize 77 | 78 | # TODO Get dynamic resolution but width is 1280 for now 79 | 80 | aspect = w/h 81 | wscale = 640 82 | hscale = wscale/aspect 83 | 84 | bgscaled = pygame.transform.scale(background, (640, int(hscale))) 85 | 86 | # Loop until the user clicks the close button. 87 | done = False 88 | 89 | # Used to manage how fast the screen updates 90 | clock = pygame.time.Clock() 91 | 92 | # Init gamepads 93 | # Initialize the joystick control, get the first one 94 | pygame.joystick.init() 95 | if (pygame.joystick.get_count()>0): 96 | joystick = pygame.joystick.Joystick(0) 97 | joystick.init() 98 | else: 99 | joystick = None 100 | 101 | init_stars(screen) 102 | # Initial game state is menu 103 | state = gamestates.Menu(None) 104 | 105 | scrollSpeed = -1 106 | 107 | # Optimized method for scrolling background continuously 108 | topY = hscale-720 # As soon as topY because -720, next frame, flip it back to hscale-720 109 | 110 | plotUpdateRate = 1/10.0 111 | plotCounter = 0.0 112 | 113 | while not done: 114 | event_queue = pygame.event.get() 115 | for event in event_queue: 116 | if event.type == pygame.QUIT: 117 | done = True 118 | 119 | topY += scrollSpeed 120 | offset = hscale+topY # If topY becomes negative, we use this to seamlessly blit until it clears itself up 121 | y = topY 122 | blitStartY = 0 123 | height = 720 124 | if (topY<0): 125 | blitStartY = -topY 126 | height = 720+topY 127 | y = 0 128 | screen.blit(bgscaled,(0,0),(0,offset,640,720-height)) 129 | screen.blit(bgscaled,(0,blitStartY),(0,y,640,height+1)) 130 | 131 | if topY<=-720: 132 | topY = hscale-720 133 | move_and_draw_stars(screen) 134 | ## Gamestate update 135 | state = state.update(screen, event_queue, clock.get_time()/1000.0, clock, joystick, netmodel,vizmodel) 136 | ## Trap exits from gamestate 137 | if state == None: 138 | done = True 139 | 140 | pygame.display.flip() 141 | clock.tick(30) 142 | 143 | # Close the window and quit. 144 | pygame.quit() 145 | -------------------------------------------------------------------------------- /gameover.py: -------------------------------------------------------------------------------- 1 | '''The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.''' 22 | 23 | import pygame 24 | from pygame.locals import * 25 | import sys 26 | from itertools import cycle 27 | from utils import * 28 | 29 | pressed = "" 30 | 31 | def enter_text(eq, screen, max_length, lower = False, upper = False, title = False): 32 | global pressed 33 | BLUE = (0,0,255) 34 | allowed_values = [i for i in range(97, 123)] +\ 35 | [i for i in range(48,58)] 36 | 37 | BLINK_EVENT = pygame.USEREVENT + 0 38 | pygame.time.set_timer(BLINK_EVENT, 800) 39 | blinky = cycle(["_", " "]) 40 | next_blink = next(blinky) 41 | displaytext('GAME OVER', 16, 320,60, WHITE, screen) 42 | 43 | displaytext('Enter your name:', 16, 320,125, WHITE, screen) 44 | 45 | for event in eq: 46 | if event.type == BLINK_EVENT: 47 | next_blink = next(blinky) 48 | # if input is in list of allowed characters, add to variable 49 | elif event.type == KEYUP and event.key in allowed_values \ 50 | and len(pressed) < max_length: 51 | # caps entry? 52 | if pygame.key.get_mods() & KMOD_SHIFT or pygame.key.get_mods()\ 53 | & KMOD_CAPS: 54 | pressed += chr(event.key).upper() 55 | # lowercase entry 56 | else: 57 | pressed += chr(event.key) 58 | # otherwise, only the following are valid inputs 59 | elif event.type == KEYUP: 60 | if event.key == K_BACKSPACE: 61 | pressed = pressed[:-1] 62 | elif event.key == K_SPACE: 63 | pressed += " " 64 | 65 | # only draw underscore if input is not at max character length 66 | if len(pressed) < max_length: 67 | displaytext(pressed + next_blink, 16, 320, 180, WHITE, screen) 68 | else: 69 | displaytext(pressed, 15, 320, 180, WHITE, screen) 70 | 71 | # perform any selected string operations 72 | if lower: pressed = pressed.lower() 73 | if upper: pressed = pressed.upper() 74 | if title: pressed = pressed.title() -------------------------------------------------------------------------------- /gamestates.py: -------------------------------------------------------------------------------- 1 | '''The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | 5 | Written by Pete Garcin @rawktron 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE.''' 24 | 25 | import pygame 26 | import utils 27 | from utils import * 28 | from actors import * 29 | from brain import Brain 30 | 31 | import math 32 | import leaderboard 33 | import gameover 34 | 35 | 36 | 37 | # GameState object will return a new state object if it transitions 38 | class GameState(object): 39 | def update(self, screen, event_queue, dt, clock, joystick, netmodel, vizmodel): 40 | return self 41 | 42 | class Play(GameState): 43 | def __init__(self, trainingMode): 44 | if utils.trainedBrain: 45 | self.brain = utils.trainedBrain 46 | else: 47 | self.brain = Brain() 48 | self.enemyspeed = 16 49 | self.enemyBullets = pygame.sprite.Group() 50 | self.userBullets = pygame.sprite.Group() 51 | self.userGroup = pygame.sprite.Group() 52 | self.enemies = pygame.sprite.Group() 53 | self.player = Player(self.userBullets) 54 | self.enemy = Enemy(self.enemyBullets, self.brain,self.enemyspeed) 55 | self.userGroup.add(self.player) 56 | self.enemies.add(self.enemy) 57 | self.player.lives = 3 58 | self.score = 0 59 | self.spawntimer = 0 60 | self.spawnbreak = 8 61 | self.trainingMode = trainingMode 62 | 63 | def update(self, screen, event_queue, dt, clock, joystick, netmodel, vizmodel): 64 | self.player.update(screen, event_queue, dt,joystick) 65 | self.enemies.update(screen, event_queue, dt, (self.player.x,self.player.y), (self.player.velx,self.player.vely), self.trainingMode, netmodel) 66 | 67 | # Spawn new enemies 68 | self.spawntimer += dt 69 | if self.spawntimer > self.spawnbreak: 70 | self.spawnbreak = max(2,self.spawnbreak-0.5) 71 | self.enemyspeed = max(0,self.enemyspeed+2) 72 | self.enemies.add(Enemy(self.enemyBullets, self.brain,self.enemyspeed)) 73 | self.spawntimer = 0 74 | 75 | if not(self.player.blinking): 76 | player_hit = pygame.sprite.spritecollide(self.player,self.enemyBullets, True) 77 | for bullet in player_hit: 78 | self.brain.record_hit(bullet) 79 | if not (self.trainingMode): 80 | self.player.TakeDamage(20) 81 | self.player.playanim("hit",(bullet.rect.x,bullet.rect.y)) 82 | 83 | 84 | if not(self.player.blinking and self.player.blinkon): 85 | self.userGroup.draw(screen) 86 | self.enemies.draw(screen) 87 | self.enemyBullets.update(dt) 88 | self.enemyBullets.draw(screen) 89 | self.userBullets.update(dt) 90 | self.userBullets.draw(screen) 91 | 92 | enemies_hit = pygame.sprite.groupcollide(self.enemies,self.userBullets,False,True) 93 | for enemy, bullets in enemies_hit.items(): 94 | enemy.TakeDamage(10) 95 | for b in bullets: 96 | enemy.playanim("hit",(b.rect.x,b.rect.y)) 97 | self.score += 50 98 | 99 | ## Update enemy animation frames 100 | for enemy in self.enemies: 101 | if enemy.anim: 102 | if enemy.anim.playing: 103 | enemy.anim.update(screen,(enemy.x+enemy.animoffset[0],enemy.y+enemy.animoffset[1]),dt) 104 | else: 105 | enemy.anim = None 106 | 107 | 108 | # Effects go here TODO make them a sprite layer 109 | if self.player.anim: 110 | if self.player.anim.playing: 111 | self.player.anim.update(screen,(self.player.x+self.player.animoffset[0],self.player.y+self.player.animoffset[1]),dt) 112 | else: 113 | self.player.anim = None 114 | 115 | self.brain.draw(screen,vizmodel) 116 | 117 | displaytext("FPS:{:.2f}".format(clock.get_fps()) , 16, 60, 20, WHITE, screen) 118 | displaytext("Score: "+str(self.score), 16, 200, 20, WHITE, screen) 119 | displaytext("Health: "+str(self.player.health), 16, 350, 20, WHITE, screen) 120 | displaytext("Lives: "+str(self.player.lives) , 16, 500, 20, WHITE, screen) 121 | 122 | displaytext("Neural Net Visualization", 16, 960, 20, WHITE, screen) 123 | 124 | for event in event_queue: 125 | if event.type == pygame.KEYDOWN: 126 | if event.key == pygame.K_ESCAPE: 127 | if (self.trainingMode): 128 | self.brain.learn() 129 | utils.trainedBrain = self.brain 130 | if (netmodel == 1): 131 | self.brain.train() # Train the tensorflow version 132 | return Menu(self.brain) 133 | 134 | if self.trainingMode: 135 | self.brain.learn() 136 | 137 | if not(self.player.alive()): 138 | if (self.trainingMode): 139 | self.brain.learn() 140 | utils.trainedBrain = self.brain 141 | return Menu(None) 142 | else: 143 | return GameOver(self.score) 144 | 145 | return self 146 | 147 | class GameOver(GameState): 148 | def __init__(self,score): 149 | self.score = score 150 | self.name = "" 151 | gameover.pressed = "" 152 | def update(self,screen,event_queue,dt,clock, joystick, netmodel, vizmodel): 153 | nextState = self 154 | self.name = gameover.enter_text(event_queue,screen, 8) 155 | for event in event_queue: 156 | if event.type == pygame.KEYUP: 157 | if event.key == pygame.K_RETURN: 158 | self.name = gameover.pressed 159 | leaderboard.StoreScore(self.name,self.score) 160 | nextState = Leaderboard(self.name) 161 | return nextState 162 | 163 | class Leaderboard(GameState): 164 | def __init__(self,name): 165 | self.name = name 166 | self.highscores = leaderboard.GetScores() 167 | 168 | def update(self,screen,event_queue,dt,clock,joystick, netmodel, vizmodel): 169 | nextState = self 170 | leaderboard.DisplayLeaderBoard(screen,self.highscores,self.name) 171 | for event in event_queue: 172 | if event.type == pygame.KEYDOWN: 173 | nextState = Menu(None) 174 | 175 | 176 | return nextState 177 | 178 | # Draws the menu on screen. 179 | # This is a class that is just instantiated 180 | # While that object exists, it processes stuff 181 | # Only one "GameState" object can exist at one time 182 | class Menu(GameState): 183 | def __init__(self, brain): 184 | self.menu_selection = 2 185 | self.brain = brain 186 | self.logo = pygame.image.load("art/neuro-blast_logo.png") 187 | self.intel = pygame.image.load("art/Intel-logo_blue.png") 188 | self.activestate = pygame.image.load("art/as-logo.png") 189 | self.intel = pygame.transform.smoothscale(self.intel,(int(self.intel.get_width()/2),int(self.intel.get_height()/2))) 190 | self.activestate = pygame.transform.smoothscale(self.activestate,(int(self.activestate.get_width()/2),int(self.activestate.get_height()/2))) 191 | 192 | def update(self, screen, event_queue, dt,clock,joystick, netmodel, vizmodel): 193 | # Logos/titles 194 | screen.blit(self.logo,(screen.get_width() / 4 - 265,screen.get_height() * 3 / 4-500)) 195 | screen.blit(self.intel,(screen.get_width() / 4 - 300,screen.get_height()-130)) 196 | screen.blit(self.activestate,(screen.get_width() - 980,screen.get_height() - 130)) 197 | 198 | nextState = self 199 | displaytext('Play', 32, screen.get_width() / 4 - 20, screen.get_height() * 3 / 4 200 | - 80, WHITE, screen) 201 | displaytext('Train', 32, screen.get_width() / 4 - 20, screen.get_height() * 3 / 4 202 | - 40, WHITE, screen) 203 | displaytext('Exit', 32, screen.get_width() / 4 - 20, screen.get_height() * 3 / 4, 204 | WHITE, screen) 205 | displaytext(u'\u00bb', 32, screen.get_width() / 4 - 60, screen.get_height() * 3 / 4 206 | - 40*self.menu_selection, WHITE, screen) 207 | 208 | # Each game state processes its own input queue in its own way to avoid messy input logic 209 | for event in event_queue: 210 | if event.type == pygame.KEYDOWN or event.type == pygame.JOYBUTTONDOWN: 211 | if (event.type == pygame.KEYDOWN and (event.key == pygame.K_DOWN)) or (event.type == pygame.JOYBUTTONDOWN and (event.button == 1)) or (event.type == pygame.JOYAXISMOTION and (event.axis == 1 or event.value >= DEADZONE)): 212 | self.menu_selection -= 1 213 | if self.menu_selection == -1: 214 | self.menu_selection = 2 215 | if (event.type == pygame.KEYDOWN and (event.key == pygame.K_UP)) or (event.type == pygame.JOYBUTTONDOWN and (event.button == 0)) or (event.type == pygame.JOYAXISMOTION and (event.axis == 1 or event.value <= -DEADZONE)): 216 | self.menu_selection += 1 217 | if self.menu_selection == 3: 218 | self.menu_selection = 0 219 | if (event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN) or (event.type == pygame.JOYBUTTONDOWN and event.button == 11): 220 | if self.menu_selection == 2: 221 | nextState = Play(False) 222 | elif self.menu_selection == 1: 223 | nextState = Play(True) 224 | else: 225 | nextState = None 226 | if (event.type == pygame.KEYDOWN and event.key == pygame.K_x): 227 | self.ExportModel() 228 | if (event.type == pygame.KEYDOWN and event.key == pygame.K_d): 229 | self.DumpData() 230 | if (event.type == pygame.KEYDOWN and event.key == pygame.K_w): 231 | self.DumpWeights() 232 | return nextState 233 | 234 | def ExportModel(self): 235 | import keras.backend as K 236 | from tensorflow.python.saved_model import builder as saved_model_builder 237 | from tensorflow.python.saved_model import utils 238 | from tensorflow.python.saved_model import tag_constants, signature_constants 239 | from tensorflow.python.saved_model.signature_def_utils_impl import build_signature_def, predict_signature_def 240 | from tensorflow.contrib.session_bundle import exporter 241 | 242 | print ("EXPORTING MODEL...") 243 | 244 | export_path = 'exported_brain' 245 | builder = saved_model_builder.SavedModelBuilder(export_path) 246 | 247 | signature = predict_signature_def(inputs={'inputs': self.brain.keras.input}, 248 | outputs={'outputs': self.brain.keras.output}) 249 | 250 | with K.get_session() as sess: 251 | builder.add_meta_graph_and_variables(sess=sess, 252 | tags=[tag_constants.TRAINING], 253 | signature_def_map={'predict': signature}) 254 | builder.save() 255 | 256 | print ("...done!") 257 | 258 | 259 | def DumpWeights(self): 260 | f = open('weights.csv', 'w') 261 | self.brain.model.dump(f) 262 | f.close() 263 | 264 | def DumpData(self): 265 | f = open('traindata.csv', 'w') 266 | 267 | for k,v in self.brain.mapShots.iteritems(): 268 | # Convert our tuple to a numpy array 269 | if k in self.brain.mapHits: 270 | a = list(v) 271 | myList = ','.join(map(str, a)) 272 | output = str(self.brain.mapHits[k]) 273 | f.write(myList+","+output+"\n") 274 | 275 | f.close() # you can omit in most cases as the destructor will call it 276 | -------------------------------------------------------------------------------- /go/README.md: -------------------------------------------------------------------------------- 1 | ![NeuroBlast](../neuroblast.jpg) 2 | # Overview 3 | NeuroBlast is a classic arcade space shooter with ML-powered AI using TensorFlow. In this short demo intended to demonstrate the accessibility of open source tools for machine learning, you can train an enemy AI using machine learning. 4 | 5 | ## How it Works 6 | 7 | If you want to learn more about how the game works under the hood, and for a tour of the insides, see these two blog posts: 8 | 9 | - [Building an ML-Powered AI Using TensorFlow in Go](http://gopherdata.io/post/build_ml_powered_game_ai_tensorflow/) on GopherData.io 10 | - [Building Game AI Using Machine Learning: Working With TensorFlow, Keras, and the Intel MKL in Python](https://www.activestate.com/blog/2017/05/building-game-ai-using-machine-learning-working-tensorflow-keras-and-intel-mkl-python) 11 | 12 | You can also [watch](https://www.youtube.com/watch?v=oiorteQg9n0) my GopherCon 2017 Lightning talk about the game or [view the slides](https://github.com/gophercon/2017-talks/tree/master/lightningtalks/PeteGarcin-BuildingMLPoweredGameAIwithTensorFlow). 13 | 14 | ## Installation - Requirements 15 | 16 | Before you start, you will require the following external libraries/tools: 17 | 18 | - TensorFlow C libraries for Go version [Instructions](https://www.tensorflow.org/install/install_go) 19 | 20 | *Note: TensorFlow for Go is only available of macOS/Linux. Windows is NOT supported.* 21 | 22 | For any package dependencies you can either use `dep` or install [ActiveGo 1.8](https://www.activestate.com/activego/downloads) to have an environment with nearly all dependencies already pre-installed. 23 | 24 | ## MacOS/Linux Setup Instructions 25 | 26 | To setup the game to run on MacOS/Linux in Go 1.8: 27 | 28 | *Reminder: You must have the TensorFlow C libraries installed as per these [instructions](https://www.tensorflow.org/install/install_go).* 29 | 30 | *Note: You must clone the repo into your GOPATH, or add the folder you clone into to your GOPATH in order for `dep ensure` to work.* 31 | 32 | 1. `git clone https://github.com/ActiveState/neuroblast.git` 33 | 2. `cd go` 34 | 2. `dep ensure` 35 | 3. `go build` 36 | 4. Launch the game `./go` 37 | 38 | ## License 39 | 40 | Copyright (C) 2017 ActiveState. Licensed under the MIT License. See LICENSE file for details. 41 | 42 | ## Credits 43 | 44 | Written by Pete Garcin [Twitter](https://twitter.com/rawktron)/[GitHub](https://github.com/rawktron) and Tom Radcliffe. 45 | 46 | Gopher Artwork appears courtesy of [Ashley McNamara](https://github.com/ashleymcnamara/gophers). 47 | 48 | ## Contributing 49 | 50 | If you would like to contribute to this project, please visit the Issues tab to see what open issues are available and flagged with Help Wanted. You can also submit a Pull Request with your proposed changes. If they are in response to an issue, please reference the issue in the pull request. 51 | 52 | 53 | -------------------------------------------------------------------------------- /go/actors.go: -------------------------------------------------------------------------------- 1 | /*The MIT License (MIT) 2 | Copyright (c) 2017 ActiveState Software Inc. 3 | Written by Pete Garcin @rawktron 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18 | */ 19 | 20 | package main 21 | 22 | import "github.com/faiface/pixel" 23 | import "time" 24 | 25 | type baseActor struct { 26 | lives int 27 | health int 28 | blinking bool 29 | blinktime float64 30 | blinkcycles int 31 | blinks int 32 | blinkon bool 33 | //Functioncallback 34 | 35 | } 36 | 37 | func (b *baseActor) TakeDamage(dmg int) { 38 | if b.health < 0 { 39 | return 40 | } 41 | 42 | b.health -= dmg 43 | 44 | if b.health <= 0 { 45 | b.lives-- 46 | } 47 | //if b.deadcb: 48 | // self.deadcb() 49 | if b.lives >= 0 { 50 | // DO something 51 | // Trigger Particle or something 52 | b.blinking = true 53 | b.blinks = 0 54 | b.health = 100 55 | } 56 | } 57 | 58 | type bullet struct { 59 | pos pixel.Vec 60 | vel pixel.Vec 61 | rect pixel.Rect 62 | sprite *pixel.Sprite 63 | } 64 | 65 | type actor struct { 66 | hitAnim *spriteAnim 67 | blowAnim *spriteAnim 68 | idleAnim *spriteAnim 69 | cooldown float64 70 | canfire bool 71 | bulcount float64 72 | pos pixel.Vec 73 | vel pixel.Vec 74 | rect pixel.Rect 75 | hitSpot pixel.Rect 76 | spawnTime time.Time 77 | 78 | health int 79 | lives int 80 | score int 81 | 82 | blinking bool 83 | blinktime float64 84 | blinkcycles int 85 | blinks int 86 | blinkon bool 87 | blinkcount float64 88 | } 89 | -------------------------------------------------------------------------------- /go/art/HeroSheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/art/HeroSheet.png -------------------------------------------------------------------------------- /go/art/HeroSheet.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/art/HeroSheet.xcf -------------------------------------------------------------------------------- /go/art/Intel-logo_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/art/Intel-logo_blue.png -------------------------------------------------------------------------------- /go/art/as-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/art/as-logo.png -------------------------------------------------------------------------------- /go/art/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/art/bullet.png -------------------------------------------------------------------------------- /go/art/bullethit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/art/bullethit.png -------------------------------------------------------------------------------- /go/art/enemy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/art/enemy.png -------------------------------------------------------------------------------- /go/art/enemybullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/art/enemybullet.png -------------------------------------------------------------------------------- /go/art/explosion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/art/explosion.png -------------------------------------------------------------------------------- /go/art/neuro-blast_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/art/neuro-blast_logo.png -------------------------------------------------------------------------------- /go/art/python-game_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/art/python-game_background.png -------------------------------------------------------------------------------- /go/audio/EnemyHit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/audio/EnemyHit.wav -------------------------------------------------------------------------------- /go/audio/EnemyShoot.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/audio/EnemyShoot.wav -------------------------------------------------------------------------------- /go/audio/HeroLaser.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/audio/HeroLaser.wav -------------------------------------------------------------------------------- /go/audio/Respawn.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/audio/Respawn.wav -------------------------------------------------------------------------------- /go/audio/ShipExplode.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/audio/ShipExplode.wav -------------------------------------------------------------------------------- /go/bulletsheet.csv: -------------------------------------------------------------------------------- 1 | Hit,0,7,,,, 2 | ,,,,,, 3 | ,,,,,, 4 | ,,,,,, 5 | ,,,,,, 6 | ,,,,,, 7 | ,,,,,, 8 | ,,,,,, 9 | ,,,,,, 10 | ,,,,,, 11 | ,,,,,, 12 | ,,,,,, 13 | ,,,,,, 14 | ,,,,,, 15 | ,,,,,, 16 | ,,,,,, 17 | ,,,,,, 18 | ,,,,,, 19 | ,,,,,, 20 | ,,,,,, 21 | ,,,,,, 22 | ,,,,,, -------------------------------------------------------------------------------- /go/enemysheet.csv: -------------------------------------------------------------------------------- 1 | Idle,0,6,,,, 2 | ,,,,,, 3 | ,,,,,, 4 | ,,,,,, 5 | ,,,,,, 6 | ,,,,,, 7 | ,,,,,, 8 | ,,,,,, 9 | ,,,,,, 10 | ,,,,,, 11 | ,,,,,, 12 | ,,,,,, 13 | ,,,,,, 14 | ,,,,,, 15 | ,,,,,, 16 | ,,,,,, 17 | ,,,,,, 18 | ,,,,,, 19 | ,,,,,, 20 | ,,,,,, 21 | ,,,,,, 22 | ,,,,,, -------------------------------------------------------------------------------- /go/explodesheet.csv: -------------------------------------------------------------------------------- 1 | Explode,0,7,,,, 2 | ,,,,,, 3 | ,,,,,, 4 | ,,,,,, 5 | ,,,,,, 6 | ,,,,,, 7 | ,,,,,, 8 | ,,,,,, 9 | ,,,,,, 10 | ,,,,,, 11 | ,,,,,, 12 | ,,,,,, 13 | ,,,,,, 14 | ,,,,,, 15 | ,,,,,, 16 | ,,,,,, 17 | ,,,,,, 18 | ,,,,,, 19 | ,,,,,, 20 | ,,,,,, 21 | ,,,,,, 22 | ,,,,,, -------------------------------------------------------------------------------- /go/exported_brain/saved_model.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/exported_brain/saved_model.pb -------------------------------------------------------------------------------- /go/exported_brain/variables/variables.data-00000-of-00001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/exported_brain/variables/variables.data-00000-of-00001 -------------------------------------------------------------------------------- /go/exported_brain/variables/variables.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/exported_brain/variables/variables.index -------------------------------------------------------------------------------- /go/font/DeValencia-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/go/font/DeValencia-Regular.ttf -------------------------------------------------------------------------------- /go/leaderboard.go: -------------------------------------------------------------------------------- 1 | /*The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | 5 | Written by Pete Garcin @rawktron 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE.*/ 24 | 25 | package main 26 | 27 | import ( 28 | "fmt" 29 | "sort" 30 | "strconv" 31 | 32 | "github.com/boltdb/bolt" 33 | ) 34 | 35 | type kv struct { 36 | Key string 37 | Value int 38 | } 39 | 40 | func saveScore(db *bolt.DB, name string, score int) error { 41 | err := db.Update(func(tx *bolt.Tx) error { 42 | fmt.Printf("name: %s score: %d", name, score) 43 | b := tx.Bucket([]byte("scores")) 44 | err := b.Put([]byte(name), []byte(strconv.Itoa(score))) 45 | return err 46 | }) 47 | return err 48 | } 49 | 50 | func getScores(db *bolt.DB) []kv { 51 | var m map[string]int 52 | 53 | m = make(map[string]int) 54 | db.View(func(tx *bolt.Tx) error { 55 | // Assume bucket exists and has keys 56 | b := tx.Bucket([]byte("scores")) 57 | 58 | b.ForEach(func(k, v []byte) error { 59 | fmt.Printf("key=%s, value=%s\n", k, v) 60 | 61 | name := make([]byte, len(k)) 62 | copy(name, k) 63 | score, _ := strconv.Atoi(string(v)) 64 | 65 | m[string(name)] = score 66 | 67 | return nil 68 | }) 69 | return nil 70 | }) 71 | 72 | var ss []kv 73 | for k, v := range m { 74 | ss = append(ss, kv{k, v}) 75 | } 76 | 77 | sort.Slice(ss, func(i, j int) bool { 78 | return ss[i].Value > ss[j].Value 79 | }) 80 | 81 | return ss 82 | } 83 | -------------------------------------------------------------------------------- /go/lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "memo": "", 3 | "projects": [ 4 | { 5 | "name": "github.com/boltdb/bolt", 6 | "branch": "master", 7 | "revision": "e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd", 8 | "packages": [ 9 | "." 10 | ] 11 | }, 12 | { 13 | "name": "github.com/faiface/glhf", 14 | "branch": "master", 15 | "revision": "98c0391c0fd3f0b365cfe5d467ac162b79dfb002", 16 | "packages": [ 17 | "." 18 | ] 19 | }, 20 | { 21 | "name": "github.com/faiface/mainthread", 22 | "branch": "master", 23 | "revision": "7127dc8993886d1ce799086ff298ff99bf258fbf", 24 | "packages": [ 25 | "." 26 | ] 27 | }, 28 | { 29 | "name": "github.com/faiface/pixel", 30 | "branch": "master", 31 | "revision": "d7487f1f7abdc3e10098cb67cf458ca3fbb5acfd", 32 | "packages": [ 33 | ".", 34 | "imdraw", 35 | "pixelgl", 36 | "text" 37 | ] 38 | }, 39 | { 40 | "name": "github.com/go-gl/gl", 41 | "branch": "master", 42 | "revision": "b303bcb3e83b7ef645a5104e1c2db7f8d9e8918a", 43 | "packages": [ 44 | "v3.3-core/gl" 45 | ] 46 | }, 47 | { 48 | "name": "github.com/go-gl/glfw", 49 | "branch": "master", 50 | "revision": "45517cf5568747f99bb4b0b4abae9fa3cd5f85ed", 51 | "packages": [ 52 | "v3.2/glfw" 53 | ] 54 | }, 55 | { 56 | "name": "github.com/go-gl/mathgl", 57 | "branch": "master", 58 | "revision": "9e3ea3e806141087fbb83a8a8e7038bf73eff120", 59 | "packages": [ 60 | "mgl32" 61 | ] 62 | }, 63 | { 64 | "name": "github.com/golang/freetype", 65 | "branch": "master", 66 | "revision": "e2365dfdc4a05e4b8299a783240d4a7d5a65d4e4", 67 | "packages": [ 68 | "truetype" 69 | ] 70 | }, 71 | { 72 | "name": "github.com/pkg/errors", 73 | "branch": "master", 74 | "revision": "c605e284fe17294bda444b34710735b29d1a9d90", 75 | "packages": [ 76 | "." 77 | ] 78 | }, 79 | { 80 | "name": "github.com/tensorflow/tensorflow", 81 | "branch": "master", 82 | "revision": "679bb02274de245c3ce00d2229081010c656dc3c", 83 | "packages": [ 84 | "tensorflow/go" 85 | ] 86 | }, 87 | { 88 | "name": "golang.org/x/image", 89 | "branch": "master", 90 | "revision": "3210c0296b49a552ad9668ede9c61008df821b94", 91 | "packages": [ 92 | "colornames", 93 | "font", 94 | "math/f32", 95 | "math/fixed" 96 | ] 97 | }, 98 | { 99 | "name": "golang.org/x/net", 100 | "branch": "master", 101 | "revision": "1c05540f6879653db88113bc4a2b70aec4bd491f", 102 | "packages": [ 103 | "html", 104 | "html/atom" 105 | ] 106 | }, 107 | { 108 | "name": "golang.org/x/sys", 109 | "branch": "master", 110 | "revision": "43e60d72a8e2bd92ee98319ba9a384a0e9837c08", 111 | "packages": [ 112 | "unix" 113 | ] 114 | } 115 | ] 116 | } 117 | -------------------------------------------------------------------------------- /go/main.go: -------------------------------------------------------------------------------- 1 | /*The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | 5 | Written by Pete Garcin @rawktron 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE.*/ 24 | 25 | package main 26 | 27 | import ( 28 | "fmt" 29 | "log" 30 | "math/rand" 31 | "time" 32 | 33 | "math" 34 | 35 | "github.com/boltdb/bolt" 36 | tf "github.com/tensorflow/tensorflow/tensorflow/go" 37 | 38 | "github.com/faiface/pixel" 39 | "github.com/faiface/pixel/imdraw" 40 | "github.com/faiface/pixel/pixelgl" 41 | "github.com/faiface/pixel/text" 42 | "golang.org/x/image/colornames" 43 | ) 44 | 45 | const playerVel float64 = 100 46 | const bulletVel float64 = 320 47 | const showDebug bool = false 48 | const enemyDmg int = 10 49 | const playerDmg int = 20 50 | const health = 100 51 | 52 | const ( 53 | menu = iota 54 | play 55 | gameover 56 | leaderboard 57 | ) 58 | 59 | func drawRect(imd *imdraw.IMDraw, r pixel.Rect) { 60 | 61 | if !showDebug { 62 | return 63 | } 64 | 65 | imd.Color = pixel.RGB(1, 0, 0) 66 | imd.Push(pixel.V(r.Min.X, r.Min.Y)) 67 | imd.Push(pixel.V(r.Min.X, r.Max.Y)) 68 | imd.Push(pixel.V(r.Max.X, r.Max.Y)) 69 | imd.Push(pixel.V(r.Max.X, r.Min.Y)) 70 | imd.Push(pixel.V(r.Min.X, r.Min.Y)) 71 | imd.Rectangle(1) 72 | } 73 | 74 | func run() { 75 | // Open the scores DB 76 | db, err := bolt.Open("scores.db", 0600, nil) 77 | if err != nil { 78 | log.Fatal(err) 79 | } 80 | defer db.Close() 81 | 82 | // Create the scores bucket if it doesn't exist 83 | db.Update(func(tx *bolt.Tx) error { 84 | _, err := tx.CreateBucketIfNotExists([]byte("scores")) 85 | if err != nil { 86 | return fmt.Errorf("create bucket: %s", err) 87 | } 88 | return nil 89 | }) 90 | 91 | // Tensorflow stuff 92 | bundle, err := tf.LoadSavedModel("exported_brain", []string{"train"}, nil) 93 | if err != nil { 94 | panic(err) 95 | } 96 | 97 | inputop := bundle.Graph.Operation("dense_1_input") 98 | outputop := bundle.Graph.Operation("dense_5/Sigmoid") 99 | 100 | var vizUpdate float64 101 | 102 | rand.Seed(time.Now().UnixNano()) 103 | 104 | imd := imdraw.New(nil) 105 | starfield := imdraw.New(nil) 106 | 107 | // Load Bullet sprites 108 | bulletpic, err := loadPicture("art/enemybullet.png") 109 | if err != nil { 110 | panic(err) 111 | } 112 | enemybulletpic, err := loadPicture("art/bullet.png") 113 | if err != nil { 114 | panic(err) 115 | } // Load bg 116 | bg, err := loadPicture("art/python-game_background.png") 117 | if err != nil { 118 | panic(err) 119 | } 120 | logopic, err := loadPicture("art/neuro-blast_logo.png") 121 | if err != nil { 122 | panic(err) 123 | } 124 | aslogopic, err := loadPicture("art/as-logo.png") 125 | if err != nil { 126 | panic(err) 127 | } 128 | 129 | var bullets []*bullet 130 | var enemybullets []*bullet 131 | var enemies []*actor 132 | var stars []*star 133 | 134 | // Generate the star field for parallax 135 | genStars(200, &stars) 136 | 137 | sheet, anims, err := loadAnimationSheet("art/HeroSheet.png", "sheet.csv", 96) 138 | if err != nil { 139 | panic(err) 140 | } 141 | 142 | enemysheet, enemyanims, err := loadAnimationSheet("art/enemy.png", "enemysheet.csv", 96) 143 | 144 | hitsheet, hitanims, err := loadAnimationSheet("art/bullethit.png", "bulletsheet.csv", 96) 145 | explodesheet, explodeanims, err := loadAnimationSheet("art/explosion.png", "explodesheet.csv", 96) 146 | 147 | cfg := pixelgl.WindowConfig{ 148 | Title: "Neuro/Blast", 149 | Bounds: pixel.R(0, 0, 1280, 720), 150 | VSync: true, 151 | } 152 | win, err := pixelgl.NewWindow(cfg) 153 | if err != nil { 154 | panic(err) 155 | } 156 | 157 | // Background, should use 'Set' to reblit the viewport sized hunk 158 | // that is scrolling 159 | background := pixel.NewSprite(bg, bg.Bounds()) 160 | bgslice := pixel.NewSprite(bg, pixel.R(0, 0, 0, 0)) 161 | logo := pixel.NewSprite(logopic, logopic.Bounds()) 162 | aslogo := pixel.NewSprite(aslogopic, aslogopic.Bounds()) 163 | 164 | player := &actor{ 165 | idleAnim: &spriteAnim{ 166 | sheet: sheet, 167 | anims: anims, 168 | rate: 1.0 / 10, 169 | dir: +1, 170 | }, 171 | hitAnim: &spriteAnim{ 172 | sheet: hitsheet, 173 | anims: hitanims, 174 | rate: 1.0 / 10, 175 | dir: +1, 176 | }, 177 | blowAnim: &spriteAnim{ 178 | sheet: explodesheet, 179 | anims: explodeanims, 180 | rate: 1.0 / 10, 181 | dir: +1, 182 | }, 183 | 184 | cooldown: 0.1, 185 | canfire: true, 186 | health: health, 187 | lives: 3, 188 | vel: pixel.ZV, 189 | pos: pixel.V(320, 100), 190 | rect: pixel.R(0, 0, 96, 100), 191 | blinkcycles: 12, 192 | blinking: false, 193 | blinkon: false, 194 | blinks: 0, 195 | blinktime: 0.5, 196 | blinkcount: 0, 197 | } 198 | player.idleAnim.play("Idle", true) 199 | player.rect = player.rect.Moved(player.pos) 200 | 201 | canvas := pixelgl.NewCanvas(pixel.R(0, 0, 640, 720)) 202 | vizcanvas := pixelgl.NewCanvas(pixel.R(0, 0, 640, 720)) 203 | 204 | vizmd := imdraw.New(nil) 205 | 206 | // Load Fonts 207 | face, err := loadTTF("font/DeValencia-Regular.ttf", 24) 208 | if err != nil { 209 | panic(err) 210 | } 211 | 212 | atlas := text.NewAtlas(face, text.ASCII) 213 | txt := text.New(pixel.V(0, 700), atlas) 214 | 215 | txt.Color = colornames.White 216 | 217 | menutxt := text.New(canvas.Bounds().Center().Sub(pixel.V(0, 0)), atlas) 218 | menutxt.Color = colornames.White 219 | 220 | viztext := text.New(vizcanvas.Bounds().Center().Sub(pixel.V(0, 0)), atlas) 221 | viztext.Color = colornames.White 222 | 223 | graphtext := text.New(vizcanvas.Bounds().Center().Sub(pixel.V(0, 0)), atlas) 224 | graphtext.Color = colornames.Black 225 | 226 | // Viz NN 227 | var neuralnet network 228 | neuralnet.NewNetwork(vizmd, graphtext, []int{4, 6, 4, 4, 1}) 229 | 230 | last := time.Now() 231 | 232 | scrollSpeed := 1 233 | spawnTimer := 0.0 234 | spawnBreak := 5.0 235 | var enemySpeed float64 = -10 236 | gameState := menu 237 | selectedOption := 0 238 | topY := 0 // As soon as topY because -720, next frame, flip it back to hscale-720 239 | 240 | quit := false 241 | var topScores []kv 242 | var playerName string 243 | 244 | for !win.Closed() && !quit { 245 | dt := time.Since(last).Seconds() 246 | last = time.Now() 247 | 248 | vizUpdate += dt 249 | 250 | viztext.Dot = viztext.Orig 251 | viztext.WriteString("NEURAL NET VISUALIZATION") 252 | 253 | if gameState == leaderboard { 254 | menutxt.Dot = menutxt.Orig 255 | txt.Dot = txt.Orig 256 | menutxt.WriteString("TOP GOPHERS\n\n") 257 | if win.JustReleased(pixelgl.KeyEnter) { 258 | gameState = menu 259 | } 260 | 261 | i := 0 262 | for _, kv := range topScores { 263 | fmt.Printf("%s, %d\n", kv.Key, kv.Value) 264 | 265 | line := fmt.Sprintf("%s : %d", kv.Key, kv.Value) 266 | txt.Dot.X -= txt.BoundsOf(line).W() 267 | fmt.Fprintln(txt, line) 268 | 269 | i++ 270 | if i == 10 { 271 | break 272 | } 273 | } 274 | 275 | topY = renderBackground(topY, scrollSpeed, bgslice, background, bg, canvas) 276 | 277 | txt.Draw(canvas, pixel.IM.Moved(pixel.V(360, -96))) 278 | menutxt.Draw(canvas, pixel.IM.Moved(pixel.V(-80, 320))) 279 | menutxt.Clear() 280 | txt.Clear() 281 | } else if gameState == gameover { 282 | menutxt.Dot = menutxt.Orig 283 | menutxt.WriteString("GAME OVER\n\n") 284 | menutxt.Dot.X -= menutxt.BoundsOf("GAME OVER").W() / 2 285 | menutxt.WriteString("ENTER YOUR NAME:") 286 | txt.WriteString(win.Typed()) 287 | playerName += win.Typed() 288 | if win.JustReleased(pixelgl.KeyEnter) { 289 | err := saveScore(db, playerName, player.score) 290 | if err != nil { 291 | panic(err) 292 | } 293 | player.score = 0 294 | playerName = "" 295 | topScores = getScores(db) 296 | gameState = leaderboard 297 | } 298 | 299 | topY = renderBackground(topY, scrollSpeed, bgslice, background, bg, canvas) 300 | 301 | txt.Draw(canvas, pixel.IM.Moved(pixel.V(280, -96))) 302 | menutxt.Draw(canvas, pixel.IM.Moved(pixel.V(-80, 320))) 303 | menutxt.Clear() 304 | 305 | } else if gameState == menu { 306 | topY = renderBackground(topY, scrollSpeed, bgslice, background, bg, canvas) 307 | 308 | logo.Draw(canvas, pixel.IM.Moved(canvas.Bounds().Center().Sub(pixel.V(0, -140)))) 309 | aslogo.Draw(canvas, pixel.IM.Moved(canvas.Bounds().Center().Sub(pixel.V(0, 280)))) 310 | 311 | // Draw text menu options 312 | if selectedOption == 0 { 313 | menutxt.WriteRune('\u00bb') 314 | } else { 315 | menutxt.WriteString(" ") 316 | } 317 | menutxt.WriteString("PLAY") 318 | menutxt.WriteRune('\n') 319 | menutxt.Dot.X = 0 320 | if selectedOption == 1 { 321 | menutxt.WriteRune('\u00bb') 322 | } else { 323 | menutxt.WriteString(" ") 324 | } 325 | menutxt.WriteString("EXIT") 326 | menutxt.Draw(canvas, pixel.IM.Moved(pixel.V(260, -480))) 327 | menutxt.Clear() 328 | menutxt.Dot = txt.Orig 329 | 330 | if win.JustReleased(pixelgl.KeyUp) { 331 | selectedOption-- 332 | } 333 | if win.JustReleased(pixelgl.KeyDown) { 334 | selectedOption++ 335 | } 336 | if selectedOption < 0 { 337 | selectedOption = 1 338 | } 339 | if selectedOption > 1 { 340 | selectedOption = 0 341 | } 342 | if win.JustReleased(pixelgl.KeyEnter) { 343 | if selectedOption == 0 { 344 | gameState = play 345 | } 346 | if selectedOption == 1 { 347 | quit = true 348 | } 349 | } 350 | 351 | } else if gameState == play { 352 | // Update cooldown timer 353 | if !player.canfire { 354 | player.bulcount += dt 355 | if player.bulcount >= player.cooldown { 356 | player.bulcount = 0 357 | player.canfire = true 358 | } 359 | } 360 | 361 | // SPAWN NEW ENEMIES HERE 362 | spawnTimer += dt 363 | if spawnTimer > spawnBreak { 364 | spawnBreak = math.Max(2, spawnBreak-0.5) 365 | enemySpeed = math.Max(-20, enemySpeed-2) 366 | enemy := &actor{ 367 | idleAnim: &spriteAnim{ 368 | sheet: enemysheet, 369 | anims: enemyanims, 370 | rate: 1.0 / 10, 371 | dir: +1, 372 | }, 373 | hitAnim: &spriteAnim{ 374 | sheet: hitsheet, 375 | anims: hitanims, 376 | rate: 1.0 / 10, 377 | dir: +1, 378 | }, 379 | blowAnim: &spriteAnim{ 380 | sheet: explodesheet, 381 | anims: explodeanims, 382 | rate: 1.0 / 10, 383 | dir: +1, 384 | }, 385 | 386 | cooldown: 0.1, 387 | canfire: true, 388 | health: health, 389 | vel: pixel.ZV, 390 | pos: pixel.V(rand.Float64()*640, 721), 391 | rect: pixel.R(0, 0, 96, 184), 392 | spawnTime: time.Now(), 393 | } 394 | enemy.idleAnim.play("Idle", true) 395 | enemy.rect = enemy.rect.Moved(enemy.pos) 396 | 397 | enemies = append(enemies, enemy) 398 | 399 | spawnTimer = 0 400 | 401 | } 402 | 403 | // control the gopher with keys 404 | ctrl := pixel.ZV 405 | if win.Pressed(pixelgl.KeyEscape) { 406 | player.health = 100 407 | player.lives = 3 408 | player.score = 0 409 | spawnBreak = 5.0 410 | enemySpeed = -10 411 | enemies = nil 412 | enemybullets = nil 413 | gameState = menu 414 | } 415 | 416 | if win.Pressed(pixelgl.KeyLeft) { 417 | ctrl.X = -1 * playerVel * dt 418 | } 419 | if win.Pressed(pixelgl.KeyRight) { 420 | ctrl.X = 1 * playerVel * dt 421 | } 422 | if win.Pressed(pixelgl.KeyUp) { 423 | ctrl.Y = 1 * playerVel * dt 424 | } 425 | if win.Pressed(pixelgl.KeyDown) { 426 | ctrl.Y = -1 * playerVel * dt 427 | } 428 | if win.Pressed(pixelgl.KeySpace) && player.canfire { 429 | 430 | bullet := &bullet{ 431 | sprite: pixel.NewSprite(bulletpic, bulletpic.Bounds()), 432 | pos: player.pos.Add(pixel.V(40, 96)), 433 | vel: pixel.V(0, bulletVel), 434 | rect: pixel.R(0, 0, 16, 16), 435 | } 436 | bullet.rect = bullet.rect.Moved(bullet.pos) 437 | 438 | bullets = append(bullets, bullet) 439 | player.canfire = false 440 | player.bulcount = 0 441 | } 442 | 443 | if canvas.Bounds().Intersect(player.rect.Moved(ctrl)).Area() < player.rect.Area() { 444 | ctrl = pixel.ZV 445 | } 446 | 447 | player.pos = player.pos.Add(ctrl) 448 | player.rect = player.rect.Moved(ctrl) 449 | player.vel = ctrl.Scaled(1 / dt) 450 | 451 | // Has to be outside the enemy loop in case there are no enemies 452 | for _, b := range bullets { 453 | // Add bullet movement 454 | b.pos = b.pos.Add(b.vel.Scaled(dt)) 455 | b.rect = b.rect.Moved(b.vel.Scaled(dt)) 456 | } 457 | 458 | // UPDATE ENEMIES 459 | j := 0 460 | for _, enemy := range enemies { 461 | 462 | if player.rect.Moved(ctrl).Intersect(enemy.rect).Area() > 0 { 463 | ctrl = pixel.ZV 464 | } 465 | 466 | enemyVel := math.Sin(time.Since(enemy.spawnTime).Seconds()*1000/1800) * 40 467 | 468 | enemyVec := pixel.V(enemyVel, enemySpeed).Scaled(dt) 469 | 470 | enemy.pos = enemy.pos.Add(enemyVec) 471 | enemy.rect = enemy.rect.Moved(enemyVec) 472 | enemy.vel = pixel.V(enemyVel, -10) 473 | 474 | enemy.bulcount += dt 475 | if enemy.bulcount >= enemy.cooldown { 476 | enemy.canfire = true 477 | } 478 | 479 | if enemy.pos.Y > 700 || enemy.blowAnim.playing { 480 | enemy.canfire = false 481 | } 482 | 483 | var dx, dy, du, dv float32 484 | // Normalized values 485 | dx = float32((enemy.pos.X - player.pos.X) / 640) 486 | dy = -float32((enemy.pos.Y - player.pos.Y) / 720) 487 | du = float32((enemy.vel.X - player.vel.X) / 60) 488 | dv = -float32((enemy.vel.Y - player.vel.Y) / 60) 489 | 490 | if vizUpdate >= 1 { 491 | vizUpdate = 0 492 | go neuralnet.Think([]float64{float64(dx), float64(dy), float64(du), float64(dv)}) 493 | } 494 | 495 | // Enemy shooting logic - This is the TensorFlow bit 496 | var column *tf.Tensor 497 | if column, err = tf.NewTensor([1][4]float32{{dx, dy, du, dv}}); err != nil { 498 | panic(err.Error()) 499 | } 500 | 501 | results, err := bundle.Session.Run(map[tf.Output]*tf.Tensor{inputop.Output(0): column}, []tf.Output{outputop.Output(0)}, nil) 502 | if err != nil { 503 | panic(err) 504 | } 505 | 506 | for _, result := range results { 507 | if result.Value().([][]float32)[0][0] >= 0.5 && enemy.canfire { 508 | bullet := &bullet{ 509 | sprite: pixel.NewSprite(enemybulletpic, enemybulletpic.Bounds()), 510 | pos: enemy.pos.Add(pixel.V(40, 0)), 511 | vel: pixel.V(0, -bulletVel), 512 | rect: pixel.R(0, 0, 16, 16), 513 | } 514 | bullet.rect = bullet.rect.Moved(bullet.pos) 515 | 516 | enemybullets = append(enemybullets, bullet) 517 | enemy.canfire = false 518 | enemy.bulcount = 0 519 | } 520 | } 521 | 522 | // END OF ENEMY LOGIC UPDATES 523 | 524 | // UPDATE BULLETS!!! 525 | i := 0 526 | for _, b := range bullets { 527 | if b.rect.Intersect(enemy.rect).Area() > 0 { 528 | enemy.hitSpot = pixel.R(0, 0, 96, 100) 529 | enemy.hitSpot = enemy.hitSpot.Moved(b.rect.Center().Sub(pixel.V(48, 50))) 530 | enemy.hitAnim.play("Hit", false) 531 | enemy.health -= playerDmg 532 | player.score += 50 533 | if enemy.health <= 0 && !enemy.blowAnim.playing { 534 | enemy.blowAnim.play("Explode", false) 535 | player.score += 200 536 | } 537 | b = nil 538 | } 539 | 540 | // Only keep on-screen bullets 541 | if b != nil && b.pos.Y < 720 { 542 | bullets[i] = b 543 | i++ 544 | } 545 | } 546 | bullets = bullets[:i] 547 | 548 | // Delete enemies who are dead AND have finished playing their explosion animation 549 | if enemy.health <= 0 && !enemy.blowAnim.playing { 550 | enemy = nil 551 | } 552 | if enemy != nil { 553 | enemies[j] = enemy 554 | j++ 555 | } 556 | // LOOP FOR ALL ENEMIES 557 | } 558 | 559 | enemies = enemies[:j] 560 | 561 | // UPDATE ENEMY BULLETS!!! 562 | i := 0 563 | for _, b := range enemybullets { 564 | // Add bullet movement 565 | b.pos = b.pos.Add(b.vel.Scaled(dt)) 566 | b.rect = b.rect.Moved(b.vel.Scaled(dt)) 567 | 568 | if b.rect.Intersect(player.rect).Area() > 0 && !player.blinking { 569 | player.hitSpot = pixel.R(0, 0, 96, 100) 570 | player.hitSpot = player.hitSpot.Moved(b.rect.Center().Sub(pixel.V(48, 50))) 571 | player.hitAnim.play("Hit", false) 572 | player.health -= enemyDmg 573 | if player.health <= 0 && !player.blowAnim.playing { 574 | player.blowAnim.play("Explode", false) 575 | player.lives-- 576 | if player.lives >= 0 { 577 | player.blinkcount = 0 578 | player.blinking = true 579 | player.blinks = 0 580 | player.health = 100 581 | } else { 582 | // Remember to erase player score when exiting gameover 583 | player.health = 100 584 | player.lives = 3 585 | spawnBreak = 5.0 586 | enemySpeed = -10 587 | enemies = nil 588 | enemybullets = nil 589 | gameState = gameover 590 | } 591 | } 592 | b = nil 593 | } 594 | 595 | // Only keep on-screen bullets 596 | if b != nil && b.pos.Y > 0 && enemybullets != nil { 597 | enemybullets[i] = b 598 | i++ 599 | } 600 | } 601 | if enemybullets != nil { 602 | enemybullets = enemybullets[:i] 603 | } 604 | 605 | topY = renderBackground(topY, scrollSpeed, bgslice, background, bg, canvas) 606 | 607 | // Physics and animation updates 608 | player.idleAnim.update(dt, player) 609 | player.hitAnim.update(dt, player) 610 | player.blowAnim.update(dt, player) 611 | 612 | renderStars(starfield, stars) 613 | starfield.Draw(canvas) 614 | 615 | // Manage player blinking 616 | if player.blinking { 617 | player.blinkcount += dt 618 | if player.blinkcount >= player.blinktime { 619 | player.blinkon = !player.blinkon 620 | player.blinks++ 621 | player.blinkcount = 0 622 | if player.blinks == player.blinkcycles { 623 | player.blinking = false 624 | player.blinkon = false 625 | player.blinks = 0 626 | } 627 | } 628 | } 629 | 630 | if player.idleAnim.playing && (!player.blinking || (player.blinking && player.blinkon)) { 631 | player.idleAnim.draw(canvas, player.rect) 632 | } 633 | if player.hitAnim.playing { 634 | player.hitAnim.draw(canvas, player.hitSpot) 635 | } 636 | if player.blowAnim.playing { 637 | player.blowAnim.draw(canvas, player.rect) 638 | } 639 | 640 | // Rendering and update for enemies 641 | for _, enemy := range enemies { 642 | enemy.idleAnim.update(dt, enemy) 643 | enemy.hitAnim.update(dt, enemy) 644 | enemy.blowAnim.update(dt, enemy) 645 | if enemy.idleAnim.playing { 646 | enemy.idleAnim.draw(canvas, enemy.rect) 647 | } 648 | if enemy.hitAnim.playing { 649 | enemy.hitAnim.draw(canvas, enemy.hitSpot) 650 | } 651 | if enemy.blowAnim.playing { 652 | enemy.blowAnim.draw(canvas, enemy.rect.Moved(pixel.V(0, -16))) 653 | } 654 | 655 | drawRect(imd, enemy.rect) 656 | } 657 | 658 | for _, b := range bullets { 659 | b.sprite.Draw(canvas, pixel.IM.Moved(b.rect.Center())) 660 | drawRect(imd, b.rect) 661 | } 662 | for _, b := range enemybullets { 663 | b.sprite.Draw(canvas, pixel.IM.Moved(b.rect.Center())) 664 | drawRect(imd, b.rect) 665 | } 666 | 667 | drawRect(imd, player.rect) 668 | imd.Draw(canvas) 669 | imd.Clear() 670 | starfield.Clear() 671 | 672 | // Text Rendering 673 | s := fmt.Sprintf("Score: %d Health: %d Lives: %d", player.score, player.health, player.lives) 674 | txt.WriteString(s) 675 | txt.Draw(canvas, pixel.IM.Moved(pixel.V(0, 0))) 676 | txt.Clear() 677 | txt.Dot = txt.Orig 678 | } 679 | 680 | // Draw the visualization neural network 681 | vizcanvas.Clear(colornames.Black) 682 | graphtext.Dot = graphtext.Orig 683 | neuralnet.Draw() 684 | vizmd.Draw(vizcanvas) 685 | vizmd.Clear() 686 | 687 | viztext.Draw(vizcanvas, pixel.IM.Moved(pixel.V(-160, 320))) 688 | viztext.Clear() 689 | 690 | graphtext.Draw(vizcanvas, pixel.IM) 691 | graphtext.Clear() 692 | // stretch the canvas to the window 693 | win.Clear(colornames.White) 694 | canvas.Draw(win, pixel.IM.Moved(pixel.V(320, 360))) 695 | vizcanvas.Draw(win, pixel.IM.Moved(pixel.V(960, 360))) 696 | win.Update() 697 | } 698 | } 699 | 700 | func main() { 701 | pixelgl.Run(run) 702 | } 703 | -------------------------------------------------------------------------------- /go/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "github.com/boltdb/bolt": { 4 | "branch": "master" 5 | }, 6 | "github.com/faiface/pixel": { 7 | "branch": "master" 8 | }, 9 | "github.com/golang/freetype": { 10 | "branch": "master" 11 | }, 12 | "github.com/pkg/errors": { 13 | "branch": "master" 14 | }, 15 | "github.com/tensorflow/tensorflow": { 16 | "branch": "master" 17 | }, 18 | "golang.org/x/image": { 19 | "branch": "master" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /go/nn.go: -------------------------------------------------------------------------------- 1 | /*The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | 5 | Written by Pete Garcin @rawktron 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE.*/ 24 | 25 | package main 26 | 27 | import ( 28 | "encoding/csv" 29 | "fmt" 30 | "io" 31 | "math" 32 | "os" 33 | 34 | "strconv" 35 | 36 | "github.com/faiface/pixel" 37 | "github.com/faiface/pixel/imdraw" 38 | "github.com/faiface/pixel/text" 39 | ) 40 | 41 | type synapse struct { 42 | inputNeuronIndex int 43 | weight float64 44 | signal float64 45 | x1, y1, x2, y2 float64 46 | } 47 | 48 | type neuron struct { 49 | x, y float64 50 | output float64 51 | synapses []synapse 52 | err float64 53 | index int 54 | } 55 | 56 | type layer struct { 57 | inputLayer bool 58 | previousLayer *layer 59 | x, y float64 60 | neurons []neuron 61 | } 62 | 63 | type network struct { 64 | surface *imdraw.IMDraw 65 | text *text.Text 66 | layers []layer 67 | } 68 | 69 | type example struct { 70 | input []float64 71 | output float64 72 | } 73 | 74 | func layer_left_margin(number_of_neurons int) float64 { 75 | return float64(44.0 + 110.0*(6.0-number_of_neurons)/2.0) 76 | 77 | } 78 | 79 | func sigmoid(x float64) float64 { 80 | return float64(1.0 / (1.0 + math.Exp(-x))) 81 | } 82 | 83 | func sigmoid_derivative(x float64) float64 { 84 | return x * (1 - x) 85 | } 86 | 87 | func (s *synapse) Draw(surface *imdraw.IMDraw) { 88 | if s.weight >= 0 { 89 | surface.Color = pixel.RGB(0, 1, 0) 90 | } else { 91 | surface.Color = pixel.RGB(1, 0, 0) 92 | } 93 | surface.Push(pixel.V(s.x1, s.y1), pixel.V(s.x2, s.y2)) 94 | surface.Line(math.Abs(s.weight) + 1) 95 | } 96 | 97 | func (n *neuron) Draw(surface *imdraw.IMDraw, text *text.Text) { 98 | 99 | for _, s := range n.synapses { 100 | s.Draw(surface) 101 | } 102 | 103 | surface.Color = pixel.RGB(0.8, 0.8, 0.8) 104 | surface.Push(pixel.V(n.x, n.y)) 105 | surface.Circle(40, 0) 106 | s := fmt.Sprintf("%.1f", n.output) 107 | text.Dot = pixel.V(n.x-20, n.y) 108 | text.WriteString(s) 109 | } 110 | 111 | func (n *neuron) Train(prevLayer *layer) layer { 112 | for _, s := range n.synapses { 113 | prevLayer.neurons[s.inputNeuronIndex].err += n.err * s.weight * sigmoid_derivative(n.output) 114 | s.weight += s.signal * n.err * sigmoid_derivative(n.output) 115 | } 116 | return *prevLayer 117 | } 118 | 119 | func (n *neuron) Think(prevLayer *layer) { 120 | activity := 0.0 121 | for i := range n.synapses { 122 | n.synapses[i].signal = prevLayer.neurons[n.synapses[i].inputNeuronIndex].output 123 | activity += n.synapses[i].weight * n.synapses[i].signal 124 | n.output = sigmoid(activity) 125 | } 126 | } 127 | 128 | func (l *layer) Think() { 129 | for i := range l.neurons { 130 | l.neurons[i].Think(l.previousLayer) 131 | } 132 | } 133 | 134 | func (l *layer) Draw(surface *imdraw.IMDraw, text *text.Text) { 135 | for _, neuron := range l.neurons { 136 | neuron.Draw(surface, text) 137 | } 138 | } 139 | 140 | func (n *network) NewNetwork(surface *imdraw.IMDraw, text *text.Text, requested []int) { 141 | n.surface = surface 142 | n.text = text 143 | n.layers = make([]layer, len(requested)) 144 | 145 | f, err := os.Open("weights.csv") 146 | if err != nil { 147 | panic(err) 148 | } 149 | defer f.Close() // this needs to be after the err check 150 | 151 | lines, err := csv.NewReader(f).ReadAll() 152 | if err != nil { 153 | panic(err) 154 | } 155 | 156 | wi := 0 157 | 158 | for i := 0; i < len(requested); i++ { 159 | var newLayer layer 160 | 161 | if i == 0 { 162 | newLayer.inputLayer = true 163 | newLayer.previousLayer = nil 164 | newLayer.y = 600 165 | } else { 166 | newLayer.inputLayer = false 167 | newLayer.previousLayer = &n.layers[i-1] 168 | newLayer.y = newLayer.previousLayer.y - 120 169 | } 170 | 171 | x := layer_left_margin(requested[i]) 172 | for j := 0; j < requested[i]; j++ { 173 | var neuron neuron 174 | neuron.x = x 175 | neuron.y = newLayer.y 176 | neuron.output = 0 177 | neuron.err = 0 178 | index := 0 179 | if newLayer.previousLayer != nil { 180 | for _, input := range newLayer.previousLayer.neurons { 181 | var synapse synapse 182 | synapse.inputNeuronIndex = index 183 | synapse.weight, _ = strconv.ParseFloat(lines[wi][0], 64) 184 | synapse.signal = 0 185 | synapse.x1 = x 186 | synapse.y1 = newLayer.y 187 | synapse.x2 = input.x 188 | synapse.y2 = input.y 189 | neuron.synapses = append(neuron.synapses, synapse) 190 | index++ 191 | wi++ 192 | } 193 | } 194 | 195 | newLayer.neurons = append(newLayer.neurons, neuron) 196 | x += 110 197 | } 198 | 199 | n.layers[i] = newLayer 200 | } 201 | } 202 | 203 | func (n *network) Train(data example) float64 { 204 | err := data.output - n.Think(data.input) 205 | n.ResetErrors() 206 | n.layers[len(n.layers)-1].neurons[0].err = err 207 | for i := len(n.layers) - 1; i > 0; i-- { 208 | for _, neuron := range n.layers[i].neurons { 209 | n.layers[i-1] = neuron.Train(&n.layers[i-1]) 210 | } 211 | } 212 | return math.Abs(err) 213 | } 214 | 215 | func (n *network) ResetErrors() { 216 | for _, layer := range n.layers { 217 | for _, neuron := range layer.neurons { 218 | neuron.err = 0 219 | } 220 | } 221 | } 222 | 223 | func (n *network) Think(inputs []float64) float64 { 224 | for j, layer := range n.layers { 225 | if layer.inputLayer { 226 | for i := 0; i < len(inputs); i++ { 227 | n.layers[0].neurons[i].output = inputs[i] 228 | } 229 | } else { 230 | n.layers[j].Think() 231 | } 232 | } 233 | return n.layers[len(n.layers)-1].neurons[0].output 234 | } 235 | 236 | func (n *network) Draw() *imdraw.IMDraw { 237 | for _, layer := range n.layers { 238 | layer.Draw(n.surface, n.text) 239 | } 240 | 241 | return n.surface 242 | } 243 | 244 | func trainModel(nn *network) { 245 | file, err := os.Open("traindata.csv") 246 | if err != nil { 247 | // err is printable 248 | // elements passed are separated by space automatically 249 | fmt.Println("Error:", err) 250 | return 251 | } 252 | // automatically call Close() at the end of current method 253 | defer file.Close() 254 | // 255 | reader := csv.NewReader(file) 256 | // options are available at: 257 | // http://golang.org/src/pkg/encoding/csv/reader.go?s=3213:3671#L94 258 | reader.Comma = ',' 259 | lineCount := 0 260 | for { 261 | // read just one record, but we could ReadAll() as well 262 | record, err := reader.Read() 263 | // end-of-file is fitted into err 264 | if err == io.EOF { 265 | break 266 | } else if err != nil { 267 | fmt.Println("Error:", err) 268 | return 269 | } 270 | // record is an array of string so is directly printable 271 | fmt.Println("Record", lineCount, "is", record, "and has", len(record), "fields") 272 | // and we can iterate on top of that 273 | for i := 0; i < len(record); i++ { 274 | fmt.Println(" ", record[i]) 275 | } 276 | fmt.Println() 277 | 278 | var trainExample example 279 | 280 | dx, _ := strconv.ParseFloat(record[0], 64) 281 | dy, _ := strconv.ParseFloat(record[1], 64) 282 | du, _ := strconv.ParseFloat(record[2], 64) 283 | dv, _ := strconv.ParseFloat(record[3], 64) 284 | 285 | trainExample.input = []float64{dx, dy, du, dv} 286 | trainExample.output, _ = strconv.ParseFloat(record[4], 64) 287 | 288 | fmt.Println(trainExample.input) 289 | 290 | nn.Train(trainExample) 291 | 292 | lineCount++ 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /go/sheet.csv: -------------------------------------------------------------------------------- 1 | Idle,0,7,,,, 2 | ,,,,,, 3 | ,,,,,, 4 | ,,,,,, 5 | ,,,,,, 6 | ,,,,,, 7 | ,,,,,, 8 | ,,,,,, 9 | ,,,,,, 10 | ,,,,,, 11 | ,,,,,, 12 | ,,,,,, 13 | ,,,,,, 14 | ,,,,,, 15 | ,,,,,, 16 | ,,,,,, 17 | ,,,,,, 18 | ,,,,,, 19 | ,,,,,, 20 | ,,,,,, 21 | ,,,,,, 22 | ,,,,,, -------------------------------------------------------------------------------- /go/traindata.csv: -------------------------------------------------------------------------------- 1 | -0.405099375037,0.852194444444,-1.96559287666,2.0,0 2 | -0.498179864106,0.543669444444,-2.15973596887,2.1,0 3 | -0.334102278544,-0.6265,2.26278558217,-1.33333333333,1 4 | -0.349649642977,1.07695555556,0.298727453577,0.266666666667,0 5 | -0.183939082036,-0.246544444444,1.76482699749,0.3,0 6 | 0.310263648164,-0.511941666667,2.27686854867,0.3,0 7 | -0.0355798177519,0.482625,0.532042233639,0.3,0 8 | -0.12669930941,0.7423,-0.0180222921762,0.4,0 9 | -0.368743099166,0.867747222222,0.373610954285,0.366666666667,0 10 | 0.00241935899223,-0.68925,1.6632840058,-1.33333333333,1 11 | -0.0255629316162,0.120736111111,-1.96187966817,0.3,0 12 | -0.531884374959,0.263655555556,-2.33102089715,0.266666666667,0 13 | -0.31513439187,0.576022222222,2.28219221012,0.266666666667,0 14 | 0.172200065563,0.00610555555556,1.02442017356,0.3,0 15 | -0.227851556758,-0.725311111111,1.97812736242,1.93333333333,0 16 | 0.185560818049,0.00953055555556,1.04025323496,0.3,0 17 | 0.158754550785,0.00260555555556,1.01247034881,0.3,0 18 | 0.154684187066,-0.365061111111,1.15367543566,-1.4,0 19 | -0.252831890592,-0.0573888888889,-1.00397119853,0.333333333333,0 20 | -0.0741581596248,0.196016666667,-1.94606086992,1.93333333333,0 21 | -0.351725393397,-0.0575611111111,0.634371046774,1.96666666667,0 22 | -0.30610252641,-0.762288888889,0.0218479391867,0.266666666667,0 23 | -0.305356645936,-0.759333333333,0.0717128268459,0.266666666667,0 24 | -0.233616878454,-0.659738888889,-1.23126879895,1.93333333333,0 25 | -0.252931468962,-0.747222222222,1.93240621866,0.266666666667,0 26 | 0.0762049681399,-0.275111111111,1.02278589465,-1.4,0 27 | -0.24907121477,-0.637666666667,-1.19414172064,1.93333333333,0 28 | -0.303997293223,-0.756355555556,0.120446042335,0.266666666667,0 29 | -0.83831182461,0.0249888888889,-1.80735410297,1.93333333333,0 30 | -0.201826717803,-0.703077777778,2.02235842132,1.93333333333,0 31 | 0.419522157208,-0.529658333333,2.17523083321,-1.3,1 32 | -0.278316212879,-0.750355555556,1.8837462099,0.266666666667,0 33 | -0.302009092008,-0.753355555556,0.169238553138,0.266666666667,0 34 | -0.21802737261,-0.681327777778,-1.27002326903,1.93333333333,0 35 | -0.0371195760845,-0.558166666667,-2.09334367654,2.0,1 36 | -0.26407175311,-0.615594444444,-1.15951013962,1.93333333333,0 37 | -0.278345834609,-0.594005555556,-1.12873181199,1.93333333333,0 38 | -0.585592360611,0.457938888889,0.443013761837,0.266666666667,0 39 | -0.292461272796,-0.572094444444,-1.10015097144,1.93333333333,0 40 | -0.30655312502,-0.5497,-1.07541085738,1.93333333333,0 41 | -0.320053749208,-0.527788888889,-1.05370334716,1.93333333333,0 42 | -0.333310771598,-0.505877777778,-1.03542573508,1.93333333333,0 43 | -0.346372663159,-0.483966666667,-1.0210408322,1.93333333333,0 44 | -0.359285228683,-0.462055555556,-1.01027494685,1.93333333333,0 45 | -0.658317462055,0.157683333333,2.29083672162,0.266666666667,0 46 | -0.37228590404,-0.439822222222,-1.00338376681,1.93333333333,0 47 | -0.384951820035,-0.418072222222,-1.00021235424,1.93333333333,0 48 | -0.398083263907,-0.395516666667,-1.00091983408,1.93333333333,0 49 | -0.410696631488,-0.373927777778,-1.00541455076,1.93333333333,0 50 | 0.122018974336,-0.329011111111,1.08811434194,-1.4,0 51 | -0.42359153367,-0.352016666667,-1.01370167004,1.93333333333,0 52 | -0.436820445644,-0.329783333333,-1.02571455979,1.93333333333,0 53 | -0.449948417054,-0.308033333333,-1.04138467532,1.93333333333,0 54 | -0.463709508255,-0.285638888889,-1.06124136214,1.93333333333,0 55 | -0.477557341987,-0.263566666667,-1.0842172752,1.93333333333,0 56 | -0.49204632374,-0.241011111111,-1.1107699897,1.93333333333,0 57 | -0.0149635260779,0.299888888889,-1.03948796759,2.0,0 58 | -0.485192020581,-0.218938888889,0.525850546994,1.93333333333,0 59 | -0.477801235691,-0.03295,-0.126681706032,0.266666666667,0 60 | -0.231657159465,0.631322222222,0.665452903858,1.93333333333,0 61 | -0.444430813905,-0.103188888889,0.100675141391,0.266666666667,0 62 | -0.457792302646,-0.118322222222,0.336660029858,0.266666666667,0 63 | -0.479942599819,-0.0298611111111,-0.176797430581,0.266666666667,0 64 | -0.447984738611,-0.1093,0.199441691663,0.266666666667,0 65 | 0.197910435719,0.00480833333333,-0.15609285736,2.1,0 66 | -0.443609839811,-0.100033333333,0.0484264877202,0.266666666667,0 67 | -0.453925085232,-0.1153,0.291932042694,0.266666666667,0 68 | -0.324620021923,-0.186505555556,-1.00104022766,-1.26666666667,0 69 | -0.450679860504,-0.112322222222,0.246890491879,0.266666666667,0 70 | -0.445884321223,-0.106233333333,0.150494104884,0.266666666667,0 71 | -0.516648894613,-0.00592777777778,-0.508148789817,0.266666666667,0 72 | -0.482614730807,-0.0268833333333,-0.224464961172,0.266666666667,0 73 | -0.510369916759,-0.00890555555556,-0.474645061812,0.266666666667,0 74 | -0.083039181098,0.15785,1.68614549755,0.266666666667,0 75 | -0.494011069584,-0.0179722222222,-0.357610921437,0.266666666667,0 76 | -0.504390291218,-0.01195,-0.437674556892,0.266666666667,0 77 | -0.489653662065,-0.02095,-0.314774022226,0.266666666667,0 78 | 0.384135303065,-0.464219444444,-1.00168928248,1.96666666667,0 79 | -0.498890142294,-0.0149944444444,-0.398466864871,0.266666666667,0 80 | -0.502900729515,0.106452777778,1.47754656801,2.03333333333,0 81 | -0.485867560883,-0.0239055555556,-0.270193458659,0.266666666667,0 82 | -0.534335067388,0.0302611111111,-0.58032276852,0.266666666667,0 83 | -0.549609035507,0.0362166666667,-0.622841647676,0.266666666667,0 84 | -0.583650775668,0.0485944444444,-0.665611212424,0.266666666667,0 85 | 0.200453546245,-0.0868222222222,0.381466139038,0.266666666667,0 86 | -0.5816054717,-0.125766666667,0.178177882517,0.3,0 87 | -0.130553485473,-0.299794444444,1.73626791679,0.266666666667,1 88 | -0.557947318785,0.0393277777778,-0.639219743575,0.266666666667,0 89 | -0.584483628061,0.0734166666667,-0.563514734714,0.266666666667,0 90 | -0.721798344393,0.110594444444,-0.0505933666165,0.266666666667,0 91 | -0.541843775182,0.0332388888889,-0.603120414505,0.266666666667,0 92 | 0.0949707908476,-0.656313888889,0.202666866628,0.366666666667,1 93 | -0.482813537577,-0.0630166666667,-1.0028866561,0.3,0 94 | -0.673854696495,0.101283333333,-1.86994510855,0.266666666667,0 95 | -0.869097992045,0.127661111111,0.296717338557,0.266666666667,0 96 | -0.404898387199,-0.0106555555556,-0.380736909502,0.266666666667,0 97 | -0.56634282676,0.0423944444444,-0.651842487839,0.266666666667,0 98 | -0.586089482631,-0.136216666667,0.0251791950624,0.3,0 99 | -0.57511406532,0.04555,-0.660832914035,0.266666666667,0 100 | 0.179811290301,-0.0443944444444,-1.17283385581,1.93333333333,0 101 | -0.58523551215,-0.132666666667,0.0768657044325,0.3,0 102 | -0.583762559805,-0.129241666667,0.127360438997,0.3,0 103 | -0.577291723532,0.482633333333,-0.624161494237,-1.4,0 104 | -0.592336617556,0.0516833333333,-0.66651259058,0.266666666667,0 105 | -0.578829213803,-0.122316666667,0.226858079505,0.3,0 106 | -0.600932850629,0.05475,-0.663420617935,0.266666666667,0 107 | -0.575356565159,-0.118791666667,0.275217900478,0.3,0 108 | 0.143616833588,-0.599077777778,2.00528538271,0.366666666667,1 109 | -0.609636154525,0.0578833333333,-0.656309523363,0.266666666667,0 110 | -0.549413661625,-0.115291666667,1.98790761012,0.3,0 111 | -0.578793627477,0.355533333333,1.85678073674,1.93333333333,0 112 | -0.596269423907,0.0609944444444,1.02143650429,0.266666666667,0 113 | -0.522883432242,-0.111791666667,2.03201595786,0.3,0 114 | -0.582718826385,0.0641055555556,1.03629650357,0.266666666667,0 115 | -0.477474379488,-0.0978166666667,0.518513742937,0.3,0 116 | 0.381149371567,-0.293658333333,0.555432875057,0.3,0 117 | -0.4961845264,-0.108341666667,2.07423986751,0.3,0 118 | -0.576893745742,0.0702611111111,-0.589489526305,0.266666666667,0 119 | -0.318365353216,0.366166666667,1.0001905157,0.333333333333,0 120 | -0.591590363207,0.0765277777778,-0.533911033722,0.266666666667,0 121 | -0.470571238923,-0.0944166666667,0.548874837637,0.3,0 122 | -0.490492595939,-0.104866666667,0.446783282651,0.3,0 123 | 0.115792584525,-0.211983333333,-0.0109132554376,1.93333333333,0 124 | -0.484170372463,-0.101316666667,0.483878402324,0.3,0 125 | -0.0209187966629,-0.411166666667,-1.64000711054,-1.36666666667,1 126 | -0.697395022358,0.104327777778,-1.82071852553,0.266666666667,0 127 | -0.598092681311,0.07955,-0.501765148329,0.266666666667,0 128 | -0.463096369346,-0.0909166666667,0.576244223571,0.3,0 129 | -0.604336909758,0.0826611111111,-0.466671443207,0.266666666667,0 130 | -0.455231412477,-0.0873916666667,0.600871464687,0.3,0 131 | 0.208572830153,0.0669277777778,2.33287018289,-1.4,0 132 | -0.610124343492,0.0857944444444,-0.427984769864,0.266666666667,0 133 | -0.447315507192,-0.0839666666667,0.621225088187,0.3,0 134 | -0.369392122653,-0.683261111111,2.04462192732,1.93333333333,0 135 | -0.615231055089,0.0888388888889,-0.387276452583,0.266666666667,0 136 | -0.438995962337,-0.0804666666667,0.63782140853,0.3,0 137 | -0.619899611908,0.09195,-0.344893971463,0.266666666667,0 138 | -0.430428084544,-0.0769416666667,0.651124720612,0.3,0 139 | 0.27919830226,0.492794444444,-0.215655210391,1.96666666667,0 140 | -0.624000682721,0.0950833333333,-0.298860325043,0.266666666667,0 141 | -0.443662898973,-0.0734416666667,-1.0064419526,0.3,0 142 | -0.42617801969,0.425166666667,-1.92848336228,0.333333333333,0 143 | -0.649328286918,0.0981944444444,-1.91832045944,0.266666666667,0 144 | -0.456723641131,-0.0699666666667,-1.001231685,0.3,0 145 | -0.72095296595,0.107461111111,-1.7694892307,0.266666666667,0 146 | -0.474105293432,-0.0594916666667,0.656768252557,0.3,0 147 | -0.469568969054,-0.0665416666667,-1.00007817934,0.3,0 148 | -0.721949911823,0.11375,0.00120979454961,0.266666666667,0 149 | -0.279249988313,-0.0496944444444,-1.02313282652,0.333333333333,0 150 | -0.298683539197,0.546111111111,2.33182514818,0.333333333333,0 151 | 0.0961500601941,0.40435,1.13887944002,0.366666666667,0 152 | -0.00942272980608,0.376758333333,1.40113104596,0.366666666667,0 153 | -0.310733382285,-0.627594444444,-1.17546296414,0.266666666667,0 154 | 0.237502352164,0.354544444444,-0.647029834011,2.03333333333,0 155 | -0.412044833365,0.608344444444,2.19791905249,0.266666666667,0 156 | 0.103343730782,0.219366666667,2.14548851426,-1.26666666667,0 157 | -0.261472432124,0.438405555556,-1.52098764653,-1.3,0 158 | 0.0735826693618,-0.562594444444,0.469869990828,1.93333333333,1 159 | 0.0917580365415,-0.475425,0.640645598849,0.3,1 160 | 0.00418983234759,0.519944444444,-1.45402795249,0.333333333333,0 161 | -0.00310726535153,0.523283333333,-2.20078446567,-1.36666666667,0 162 | 0.170701230057,0.761944444444,1.08010899652,0.266666666667,0 163 | 0.127616393331,0.4137,1.07810237736,0.366666666667,0 164 | 0.117003823054,0.410491666667,1.09658828025,0.366666666667,0 165 | 0.157078000506,0.39495,1.03431459604,-1.3,0 166 | 0.118631010831,0.0833805555556,-0.425328468615,0.433333333333,0 167 | -0.114086069872,-0.154805555556,-1.01675714056,0.466666666667,0 168 | -0.446423194629,0.576555555556,-0.599527964351,0.333333333333,0 169 | 0.21574677124,0.4739,1.78358985905,0.3,0 170 | 0.231407875515,0.371827777778,-0.637330891359,2.03333333333,0 171 | 0.258261545637,0.6692,1.00006689271,0.266666666667,0 172 | 0.200205027027,0.726944444444,1.03567243797,-1.4,0 173 | -0.159652874433,-0.214027777778,2.18911475173,-1.2,0 174 | -0.203677661516,-0.0740944444444,-1.1623415664,0.466666666667,0 175 | 0.0989893403502,-0.00675833333333,1.18505314152,-1.23333333333,0 176 | 0.143372265617,0.642691666667,1.67325890783,-1.36666666667,0 177 | 0.0830706860842,0.0370722222222,-0.51338511755,2.06666666667,0 178 | -0.217786386572,-0.242944444444,1.85694654886,0.5,1 179 | 0.486152627299,-0.56595,2.08726206452,-1.3,1 180 | 0.0413536666513,0.494555555556,0.347615944501,2.0,0 181 | 0.00901162128628,0.678913888889,-1.1011161311,0.3,0 182 | -0.164668094774,-0.301583333333,-2.31425173347,2.0,0 183 | -0.18188023809,-0.0983333333333,-1.0101543796,2.13333333333,0 184 | 0.0443705614943,0.511555555556,0.315492271169,2.0,0 185 | -0.650580321135,0.275855555556,-2.28383914773,0.266666666667,0 186 | -0.173780611427,0.622888888889,-1.86639083241,0.333333333333,0 187 | -0.244605360849,0.759538888889,0.254166430535,0.266666666667,0 188 | 0.0405363055117,0.413905555556,-2.12741319648,2.03333333333,0 189 | -0.167313767337,0.607194444444,-1.43700125157,2.0,0 190 | 0.0608780929919,0.723438888889,-1.02389320782,-1.36666666667,0 191 | -0.395329180401,-0.705172222222,2.00240983034,1.93333333333,0 192 | -0.289258394365,0.171055555556,-2.19610896151,0.433333333333,0 193 | -0.188422187194,-0.0952777777778,-1.02449308682,2.0,0 194 | -0.0728529311849,0.394583333333,2.24338205951,-1.3,0 195 | -0.362990547546,0.402277777778,-2.10324151491,-1.33333333333,0 196 | 0.338711859297,-0.0562194444444,-1.67274053471,1.96666666667,0 197 | -0.160661302176,0.756361111111,2.08961375438,-1.4,0 198 | -0.142604644498,-0.30615,-0.0365496480698,0.266666666667,1 199 | -0.0955671745375,0.406283333333,2.22290373311,-1.3,0 200 | -0.306612552989,0.4075,-2.12419080273,0.333333333333,0 201 | -0.364207969999,0.510833333333,1.03271442978,-1.33333333333,0 202 | -0.459333778714,0.125752777778,-0.180936313254,0.433333333333,0 203 | 0.120460279643,0.186525,0.508436529936,0.366666666667,0 204 | 0.0704557955225,0.694738888889,2.07630814071,0.3,0 205 | 0.525170037977,-0.567738888889,0.314033809561,0.366666666667,1 206 | 0.0902162595188,0.0347166666667,1.00195734341,-1.26666666667,0 207 | -0.140132032084,-0.269819444444,2.00144944409,-1.16666666667,1 208 | -0.185193840184,0.106502777778,-1.61550721714,0.3,0 209 | 0.100878540536,0.255538888889,1.35573014633,-1.26666666667,0 210 | -0.250655249116,-0.296327777778,1.01013722849,-1.2,1 211 | -0.198881357433,-0.0363305555556,-1.13620580764,-1.23333333333,0 212 | -0.0266467770023,0.371075,2.27941611836,-1.3,0 213 | -0.136305383301,-0.539066666667,0.658967074143,1.93333333333,1 214 | 0.244612128323,0.476052777778,-0.305857785093,-1.36666666667,0 215 | -0.003414335029,0.359375,2.2945621702,-1.3,0 216 | -0.380759284451,0.605655555556,0.42323378557,0.266666666667,0 217 | 0.115121696078,0.183102777778,-1.1319581713,0.366666666667,0 218 | 0.52932960377,-0.0746222222222,-1.40183301772,0.266666666667,0 219 | 0.0868057028945,0.618461111111,2.31624999607,-1.4,0 220 | -0.163249797046,-0.00272222222222,2.11626481127,-1.23333333333,0 221 | 0.0897925720475,0.579961111111,-1.00064796249,-1.4,0 222 | 0.134438406478,0.677033333333,-2.32910797939,1.93333333333,0 223 | 0.058706015183,0.0422166666667,-0.653232647271,0.4,0 224 | -0.131792922537,-0.128830555556,2.32583814269,-1.23333333333,0 225 | -0.349176643375,-0.0688888888889,-1.13383700329,0.333333333333,0 226 | -0.388091250006,0.378166666667,-0.372813432854,-1.33333333333,0 227 | -0.409680423182,0.365388888889,-2.00247403373,-1.33333333333,0 228 | 0.0587802940196,0.0700777777778,1.00411237913,-1.26666666667,0 229 | -0.22825017905,-0.0830944444444,-1.05193115095,-1.23333333333,0 230 | -0.236524222276,0.954005555556,-1.95419599773,1.93333333333,0 231 | -0.325303580905,-0.624594444444,-1.14329961355,0.266666666667,0 232 | -0.680271969002,0.259522222222,-2.26222078981,-1.4,0 233 | -0.217237151389,-0.241944444444,1.2894031934,-1.33333333333,1 234 | -0.298912453199,-0.137316666667,1.92046812986,0.366666666667,0 235 | 0.092406820384,-0.229194444444,-2.26707743184,0.266666666667,1 236 | -0.00466444829139,-0.639797222222,-2.09106284185,1.96666666667,1 237 | 0.236370730217,0.364163888889,-0.65117763682,0.366666666667,0 238 | -0.243853124863,0.116827777778,-1.46461156327,0.3,0 239 | 0.279445495094,-0.553911111111,-1.31007734437,0.3,1 240 | 0.32994339417,0.0570861111111,1.75137553088,-1.3,0 241 | 0.421606883111,-0.240722222222,2.14788745199,1.96666666667,0 242 | -0.521998325247,0.0150833333333,-0.477652182062,2.0,0 243 | 0.113233330142,-0.666755555556,-1.72183802343,-1.36666666667,1 244 | -0.469762286302,-0.7516,1.86474134864,0.266666666667,0 245 | -0.285233394681,-0.243877777778,0.64873462342,0.366666666667,0 246 | 0.302982365523,-0.196372222222,-1.92103863409,-1.4,0 247 | -0.338532305824,0.00568888888889,0.300227920626,0.366666666667,0 248 | -0.720206409713,0.119994444444,0.104846774944,0.266666666667,0 249 | 0.441363959789,-0.0601222222222,2.19960028745,-1.4,0 250 | -0.71162756736,0.199638888889,0.344111685421,0.333333333333,0 251 | -0.569135420862,0.0671722222222,1.05476659919,0.266666666667,0 252 | 0.458335797944,-0.0870555555556,-1.58122311814,-1.4,0 253 | -0.299093862739,-0.100866666667,-1.01880692629,2.03333333333,0 254 | 0.47528456061,-0.219347222222,0.366463945196,0.3,0 255 | -0.706153832635,0.203805555556,0.38924335818,0.333333333333,0 256 | 0.0642808740272,-0.700011111111,-1.8280594229,-1.36666666667,1 257 | -0.317178321354,-0.640405555556,2.12217536726,1.93333333333,0 258 | -0.346110867545,0.0523611111111,1.07762755798,0.333333333333,0 259 | -0.706333278719,0.397116666667,-2.14701518411,-1.4,0 260 | 0.0342376822387,-0.892672222222,-1.54185202686,-1.26666666667,1 261 | -0.304747782002,-0.5605,0.578392750277,2.0,0 262 | 0.0250871620965,0.224636111111,2.18971245036,0.3,0 263 | -0.0843200370484,-0.949338888889,-1.23633898075,0.4,1 264 | -0.12305641559,-0.503944444444,0.641934313764,1.93333333333,1 265 | -0.295619763053,-0.531166666667,0.54412364568,2.0,0 266 | -0.357895685722,-0.207394444444,0.61820353159,0.4,0 267 | -0.0359160088466,-0.430330555556,-1.7249134563,0.3,1 268 | -0.0273241735651,-0.04755,1.14371267828,1.93333333333,1 269 | 0.136945455004,-0.4899,0.477428174316,2.06666666667,1 270 | -0.301842748381,-0.252311111111,0.617805321314,0.366666666667,0 271 | 0.103179535667,-0.645761111111,2.30922339313,2.06666666667,1 272 | 0.24020758246,-0.665183333333,-2.01453519766,-1.3,1 273 | -0.193486727586,-0.300611111111,1.70797364596,2.0,0 274 | -0.052665804405,-0.2615,2.07175371279,1.96666666667,1 275 | 0.0772509234775,-0.396022222222,-1.95879858605,-1.36666666667,1 276 | -0.0382926689536,-0.949777777778,-1.3532429129,-1.26666666667,1 277 | -0.317168458503,-0.494611111111,0.433600191141,0.333333333333,0 278 | 0.0150313237947,-0.283205555556,-0.641840856929,1.93333333333,1 279 | -0.310595821644,-0.499583333333,-1.18442178271,0.333333333333,0 280 | -0.0714119575401,-0.351241666667,-1.46522351392,0.3,1 281 | -0.0870396423524,-0.669563888889,-2.3330476261,0.366666666667,1 282 | -0.0327605565157,0.02495,-2.07165597703,1.93333333333,0 283 | -0.326646290881,-0.0530833333333,-1.05113153017,2.03333333333,0 284 | 0.024236028447,-0.708277777778,2.33178839807,2.06666666667,1 285 | -0.255474432329,0.190933333333,1.42844670152,0.4,0 286 | 0.142100434254,-0.614588888889,2.28787384813,2.06666666667,1 287 | 0.13016990499,-0.4589,-1.23807642225,2.06666666667,1 288 | -0.0742912640067,-0.120533333333,1.05677334573,1.93333333333,1 289 | -0.343720626248,-0.661994444444,2.08413175098,1.93333333333,0 290 | -0.0135967535504,0.676066666667,-1.08837208934,0.266666666667,0 291 | -0.364281339089,-0.015975,0.505437767582,0.366666666667,0 292 | -0.208266772573,-0.135111111111,-0.312900503516,2.0,0 293 | -0.0620406330632,0.0582083333333,2.18337529208,2.03333333333,0 294 | -0.137461820108,-0.240183333333,-1.72796277041,2.03333333333,0 295 | 0.307601041122,0.429291666667,1.76348377273,1.96666666667,0 296 | -0.087686474454,-0.331852777778,-0.255763803136,2.03333333333,0 297 | -0.564782865192,0.473761111111,0.216846263587,0.266666666667,0 298 | -0.0912047818715,-0.301352777778,-0.192668713586,2.03333333333,0 299 | 0.151552870903,0.0544944444444,-2.21324712351,2.06666666667,0 300 | 0.18496814634,-0.0758777777778,1.32259724305,2.06666666667,0 301 | -0.10785187677,-0.270852777778,-1.79465727782,2.03333333333,0 302 | -0.37561383971,0.0165555555556,-2.22632274987,2.0,0 303 | -0.209536719878,-0.0690444444444,1.96801827965,2.03333333333,0 304 | -0.33046160112,0.0187055555556,0.151576325716,0.366666666667,0 305 | 0.0965333414944,0.0934833333333,-1.02700684509,1.96666666667,0 306 | -0.201489405483,-0.160336111111,-1.24660793025,2.1,0 307 | -0.420719703094,-0.727083333333,1.95797696802,1.93333333333,0 308 | 0.182784161948,0.5684,-2.0399299365,0.3,0 309 | -0.284986958311,-0.325711111111,1.77214745953,2.1,0 310 | -0.378741925913,0.0667805555556,-2.32067689584,-1.3,0 311 | -0.0599006104949,0.438683333333,2.13203128507,1.93333333333,0 312 | 0.0231645622874,0.266963888889,-1.0000001401,2.03333333333,0 313 | 0.259897003091,0.538441666667,-1.81528369571,1.96666666667,0 314 | 0.0317039328551,0.278513888889,-1.01455609742,0.366666666667,0 315 | 0.0535426822692,0.1035,-2.31437467792,0.4,0 316 | -0.329790359813,-0.0852777777778,-1.00142150156,-1.23333333333,0 317 | -0.29379334,0.351947222222,-0.666377328009,0.3,0 318 | 0.0433604709193,0.6528,-1.00125885181,1.93333333333,0 319 | -0.0246006091498,0.295930555556,-1.09012461686,0.366666666667,0 320 | -0.219264815935,-0.0998916666667,0.560780458013,0.433333333333,0 321 | -0.0544722109724,0.657772222222,-1.17121126727,-1.4,0 322 | 0.0203009417002,0.732877777778,1.43843954614,0.266666666667,0 323 | 0.00668071724769,0.730633333333,1.4742819753,0.266666666667,0 324 | -0.580231802021,0.461094444444,0.402671067801,0.266666666667,0 325 | -0.290431952025,0.137411111111,-0.367123695065,2.06666666667,0 326 | -0.213910093708,0.2008,1.53322835337,0.4,0 327 | 0.00789118781541,0.563119444444,-2.25176302765,-1.36666666667,0 328 | -0.0974062133365,0.641422222222,0.164601293831,0.266666666667,0 329 | -0.379770808721,-0.703516666667,2.04523188943,1.93333333333,0 330 | -0.274948202307,0.00161944444444,2.03845581159,0.433333333333,0 331 | -0.0875455406402,-0.128352777778,2.33033312401,-1.23333333333,1 332 | 0.00745409220401,-0.436966666667,1.91690222792,0.266666666667,1 333 | 0.0177543260767,-0.296777777778,1.02837302573,0.266666666667,1 334 | -0.0983406465165,-0.276538888889,-1.78701564753,0.466666666667,0 335 | -0.444993904388,-0.748511111111,1.91291478705,0.266666666667,0 336 | 0.0670492033047,-0.219619444444,0.554406570317,-1.23333333333,1 337 | -0.284675416753,0.0936277777778,-2.27025967699,0.366666666667,0 338 | 0.299372264637,0.305547222222,-0.666197960161,-1.36666666667,0 339 | -0.615067831691,-0.7895,1.82944858847,0.333333333333,0 340 | -0.063785716948,-0.139555555556,2.32512036132,-1.23333333333,1 341 | -0.240958158757,-0.316616666667,1.01239914876,-1.2,1 342 | -0.123007608679,-0.453588888889,0.479165463937,0.266666666667,1 343 | -0.219771712437,-0.338616666667,1.03311292452,-1.2,1 344 | 0.428524951687,0.353344444444,1.62402948638,1.93333333333,0 345 | 0.266358156469,-0.150069444444,1.93227045424,2.1,0 346 | 0.182917753089,-0.312355555556,-1.90375631782,1.93333333333,0 347 | 0.504724380012,-0.536366666667,-1.43811447957,0.366666666667,1 348 | -0.561251251044,-0.74975,-1.27750342951,0.333333333333,0 349 | 0.382057955442,0.388686111111,-2.16603471864,1.96666666667,0 350 | -0.266818709453,-0.5945,0.519876746367,2.0,1 351 | -0.452698233076,0.524805555556,1.12575757801,0.266666666667,0 352 | 0.357882890332,0.294613888889,1.05415072686,1.96666666667,0 353 | 0.364353667561,-0.0832611111111,-1.61206112093,1.96666666667,0 354 | -0.0860871889987,-0.62925,1.8604460223,-1.33333333333,1 355 | -0.153304939255,-0.226233333333,0.0504208616546,2.13333333333,0 356 | 0.131555323529,0.0706333333333,-1.98413623958,0.433333333333,0 357 | 0.0663217518568,-0.711088888889,1.98790761012,2.2,1 358 | -0.664534937522,0.227611111111,0.594518528812,0.333333333333,0 359 | 0.0582959990526,-0.632511111111,-1.14861883498,0.533333333333,1 360 | 0.473247495793,0.445822222222,-1.91642109446,1.93333333333,0 361 | 0.288677528016,0.0269055555556,1.58705448083,2.06666666667,0 362 | 0.0570696108426,-0.435233333333,-1.00884675878,0.533333333333,1 363 | 0.219735618881,-0.0970416666667,-1.60837072383,0.433333333333,0 364 | 0.0059425861768,-0.562116666667,0.489195584529,-1.36666666667,1 365 | -0.291517806558,0.416747222222,-1.77195257281,0.3,0 366 | -0.0887768250682,-0.298277777778,-2.33325439653,-1.4,1 367 | 0.242375934393,-0.567102777778,-1.4040328442,-1.36666666667,1 368 | 0.0743616076441,-0.541802777778,0.66472440718,0.3,1 369 | -0.0658263050209,-0.249383333333,-1.21444952808,2.2,1 370 | 0.0461921967328,-0.611427777778,-1.12267151951,2.2,1 371 | 0.234817362523,0.0544111111111,-1.61762474933,0.4,0 372 | 0.349133748222,0.234427777778,-0.463202908128,2.03333333333,0 373 | -0.415711461381,0.231416666667,2.11772149146,0.333333333333,0 374 | 0.284349667028,0.454608333333,-0.295611648237,1.96666666667,0 375 | 0.165255095415,-0.47285,2.2311949851,-1.36666666667,1 376 | 0.201630803913,-0.0564416666667,-0.0275475881463,2.1,0 377 | 0.0459114632642,-0.430033333333,-1.01726701569,0.533333333333,1 378 | 0.123296332471,0.0791555555556,-0.391385152702,0.433333333333,0 379 | 0.120183273747,0.0485666666667,-1.05732316855,1.93333333333,0 380 | -0.291512942419,-0.203333333333,-0.547976412542,0.333333333333,0 381 | 0.0640766184211,-0.596105555556,0.521264988673,1.93333333333,1 382 | 0.244169541048,0.0479111111111,2.32784633126,-1.4,0 383 | 0.325109965047,-0.0407555555556,-0.0667028960956,0.3,0 384 | -0.114788122664,-0.36765,-0.218736412489,0.266666666667,1 385 | 0.334622805438,-0.000855555555556,2.29058489877,-1.4,0 386 | -0.0512841163319,0.481527777778,-1.15353346124,0.333333333333,0 387 | -0.355611277326,-0.134305555556,2.27117903501,0.4,0 388 | -0.140538658024,-0.306944444444,1.4843049267,-1.33333333333,1 389 | 0.0477366038719,0.112444444444,-2.18340646912,2.1,0 390 | -0.0580363685956,-0.383083333333,-0.666340591549,0.333333333333,0 391 | -0.14194015243,-0.309327777778,-1.75556168257,1.93333333333,1 392 | 0.187533596749,0.568058333333,-1.4974045458,0.3,0 393 | -0.0819812279826,-0.459,-1.48741834038,-1.36666666667,1 394 | 0.0786117521036,-0.269866666667,1.18072108485,1.93333333333,1 395 | -0.382204433373,0.239638888889,0.363053785285,0.333333333333,0 396 | 0.239765199413,-0.514305555556,2.1489796831,-1.36666666667,1 397 | -0.151051924715,0.623111111111,-2.10881258606,2.03333333333,0 398 | 0.13681215805,0.50495,1.94536601189,0.3,0 399 | -0.380054405527,0.020825,-2.26176511962,-1.3,0 400 | -0.0498087178872,-0.618294444444,-0.538619501587,0.3,1 401 | -0.045592489192,-0.0261527777778,-2.28839220144,2.16666666667,0 402 | -0.882670260911,0.0907222222222,0.010098297895,1.93333333333,0 403 | -0.213806693802,0.934027777778,-1.99523845109,1.93333333333,0 404 | -0.451749314371,-0.128566666667,-1.15189808926,-1.26666666667,0 405 | -0.310123887064,0.100588888889,-1.00458234141,2.13333333333,0 406 | -0.395159129198,0.456344444444,-2.26338139178,2.1,0 407 | -0.0777886687987,0.561263888889,-2.20001539213,2.03333333333,0 408 | 0.016410794564,0.495222222222,-1.03954643828,2.06666666667,0 409 | -0.410011849201,-0.585833333333,0.666600118675,2.0,1 410 | 0.546645174584,-0.215347222222,1.06701993043,-1.36666666667,0 411 | 0.182554935924,-0.501569444444,2.23721823295,-1.16666666667,0 412 | -0.410445330682,0.00511666666667,-2.23565426427,-1.3,0 413 | 0.185621851321,0.165852777778,2.08883114512,-1.3,0 414 | -0.245631772161,0.201430555556,2.09395044635,0.366666666667,0 415 | -0.0437850018085,-0.3816,0.0335415106675,0.266666666667,1 416 | -0.461538289185,-0.759488888889,0.0687663576175,0.266666666667,0 417 | -0.0657666103147,-0.610894444444,-0.595331942869,0.3,1 418 | -0.0804926150352,-0.53895,-2.3199928411,1.96666666667,1 419 | -0.460149800862,-0.756644444444,0.115707360303,0.266666666667,0 420 | -0.0750070207393,-0.40025,-0.275465332058,0.3,1 421 | -0.721414149297,0.116883333333,0.0537440037198,0.266666666667,0 422 | -0.718292787782,0.12315,0.156764953218,0.266666666667,0 423 | 0.427988781468,-0.199905555556,-2.32493777186,-1.36666666667,0 424 | -0.0335202417102,-0.441966666667,1.9929583518,0.266666666667,1 425 | -0.0236519081333,-0.441808333333,1.72299441178,0.3,0 426 | -0.0594055789415,-0.738066666667,0.353496317103,0.266666666667,1 427 | 0.0633331165022,-0.291938888889,1.21655825677,1.93333333333,0 428 | -0.242763941157,0.2255,1.9265515515,-1.33333333333,0 429 | -0.516758991425,-0.703472222222,2.2691332226,0.333333333333,0 430 | 0.229274451385,-0.306619444444,-2.18528913235,-1.36666666667,1 431 | 0.0909593438045,-0.292844444444,1.04003380139,-1.4,0 432 | 0.107935111015,-0.622472222222,0.644868424347,0.266666666667,1 433 | 0.0229052388529,-0.386583333333,-1.04228862537,2.2,1 434 | 0.100773396558,-0.255844444444,1.13133960412,1.93333333333,1 435 | 0.28430028659,0.606655555556,-2.27819352461,0.266666666667,0 436 | 0.417392020481,0.106847222222,1.54124591124,2.03333333333,0 437 | -0.390007032704,-0.576888888889,0.657699227402,0.333333333333,1 438 | -------------------------------------------------------------------------------- /go/utils.go: -------------------------------------------------------------------------------- 1 | /*The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | 5 | Written by Pete Garcin @rawktron 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE.*/ 24 | 25 | package main 26 | 27 | import ( 28 | "encoding/csv" 29 | "image" 30 | "io" 31 | "io/ioutil" 32 | "math/rand" 33 | "os" 34 | "strconv" 35 | 36 | "golang.org/x/image/colornames" 37 | "golang.org/x/image/font" 38 | 39 | _ "image/png" 40 | 41 | "github.com/faiface/pixel" 42 | "github.com/faiface/pixel/imdraw" 43 | "github.com/faiface/pixel/pixelgl" 44 | "github.com/golang/freetype/truetype" 45 | "github.com/pkg/errors" 46 | ) 47 | 48 | type star struct { 49 | pos pixel.Vec 50 | layer int 51 | } 52 | 53 | type spriteAnim struct { 54 | sheet pixel.Picture 55 | anims map[string][]pixel.Rect 56 | rate float64 57 | 58 | counter float64 59 | dir float64 60 | 61 | currentAnim string 62 | currentFrame int 63 | loop bool 64 | playing bool 65 | 66 | frame pixel.Rect 67 | 68 | sprite *pixel.Sprite 69 | } 70 | 71 | func genStars(numStars int, stars *[]*star) { 72 | for i := 0; i < numStars; i++ { 73 | newStar := &star{ 74 | pos: pixel.V(rand.Float64()*640, rand.Float64()*720), 75 | layer: rand.Intn(3), 76 | } 77 | *stars = append(*stars, newStar) 78 | } 79 | } 80 | 81 | func renderStars(imd *imdraw.IMDraw, stars []*star) { 82 | for _, s := range stars { 83 | s.pos = s.pos.Add(pixel.V(0, -float64(s.layer+2))) 84 | if s.pos.Y < 0 { 85 | s.pos = pixel.V(rand.Float64()*640, 724) 86 | } 87 | if s.layer == 0 { 88 | imd.Color = pixel.RGB(0.75, 0, 0.75).Mul(pixel.Alpha(0.5)) 89 | } else if s.layer == 1 { 90 | imd.Color = pixel.RGB(0, 0.5, 0.75).Mul(pixel.Alpha(0.3)) 91 | } else { 92 | imd.Color = pixel.RGB(1, 1, 1).Mul(pixel.Alpha(0.1)) 93 | } 94 | imd.Push(pixel.V(s.pos.X, s.pos.Y)) 95 | imd.Circle(float64(s.layer+1), 0) 96 | } 97 | } 98 | 99 | func renderBackground(topY, scrollSpeed int, bgslice, background *pixel.Sprite, bg pixel.Picture, canvas *pixelgl.Canvas) int { 100 | // UPDATE THE BACKGROUND SCROLLING 101 | topY += scrollSpeed 102 | height := 720 103 | offset := (topY + height) - 8000 // If topY becomes negative, we use this to seamlessly blit until it clears itself up 104 | if offset < 0 { 105 | offset = 0 106 | } 107 | y := topY 108 | blitStartY := 0 109 | if topY+height >= 8000 { 110 | blitStartY = 720 - offset 111 | height = 720 - offset 112 | y = 8000 - height 113 | 114 | bgslice.Set(bg, pixel.R(0, 0, 640, float64(offset))) 115 | } 116 | background.Set(bg, pixel.R(0, float64(y), 640, float64(y+height))) 117 | 118 | if topY >= 8000 { 119 | topY = 0 120 | } 121 | 122 | // draw the scene to the canvas 123 | canvas.Clear(colornames.Black) 124 | bgslice.Draw(canvas, pixel.IM.Moved(pixel.V(320, float64(360+(blitStartY/2))))) 125 | background.Draw(canvas, pixel.IM.Moved(pixel.V(320, float64(360-(offset/2))))) 126 | 127 | return topY 128 | } 129 | 130 | func loadTTF(path string, size float64) (font.Face, error) { 131 | file, err := os.Open(path) 132 | if err != nil { 133 | return nil, err 134 | } 135 | defer file.Close() 136 | 137 | bytes, err := ioutil.ReadAll(file) 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | font, err := truetype.Parse(bytes) 143 | if err != nil { 144 | return nil, err 145 | } 146 | 147 | return truetype.NewFace(font, &truetype.Options{ 148 | Size: size, 149 | GlyphCacheEntries: 1, 150 | }), nil 151 | } 152 | 153 | func loadPicture(path string) (pixel.Picture, error) { 154 | file, err := os.Open(path) 155 | if err != nil { 156 | return nil, err 157 | } 158 | defer file.Close() 159 | img, _, err := image.Decode(file) 160 | if err != nil { 161 | return nil, err 162 | } 163 | return pixel.PictureDataFromImage(img), nil 164 | } 165 | 166 | func loadAnimationSheet(sheetPath, descPath string, frameWidth float64) (sheet pixel.Picture, anims map[string][]pixel.Rect, err error) { 167 | // total hack, nicely format the error at the end, so I don't have to type it every time 168 | defer func() { 169 | if err != nil { 170 | err = errors.Wrap(err, "error loading animation sheet") 171 | } 172 | }() 173 | 174 | // open and load the spritesheet 175 | sheetFile, err := os.Open(sheetPath) 176 | if err != nil { 177 | return nil, nil, err 178 | } 179 | defer sheetFile.Close() 180 | sheetImg, _, err := image.Decode(sheetFile) 181 | if err != nil { 182 | return nil, nil, err 183 | } 184 | sheet = pixel.PictureDataFromImage(sheetImg) 185 | 186 | // create a slice of frames inside the spritesheet 187 | var frames []pixel.Rect 188 | for x := 0.0; x+frameWidth <= sheet.Bounds().Max.X; x += frameWidth { 189 | frames = append(frames, pixel.R( 190 | x, 191 | 0, 192 | x+frameWidth, 193 | sheet.Bounds().H(), 194 | )) 195 | } 196 | 197 | descFile, err := os.Open(descPath) 198 | if err != nil { 199 | return nil, nil, err 200 | } 201 | defer descFile.Close() 202 | 203 | anims = make(map[string][]pixel.Rect) 204 | 205 | // load the animation information, name and interval inside the spritesheet 206 | desc := csv.NewReader(descFile) 207 | for { 208 | anim, err := desc.Read() 209 | if err == io.EOF { 210 | break 211 | } 212 | if err != nil { 213 | return nil, nil, err 214 | } 215 | 216 | name := anim[0] 217 | start, _ := strconv.Atoi(anim[1]) 218 | end, _ := strconv.Atoi(anim[2]) 219 | 220 | anims[name] = frames[start : end+1] 221 | } 222 | 223 | return sheet, anims, nil 224 | } 225 | 226 | func (ga *spriteAnim) play(anim string, loop bool) { 227 | ga.currentAnim = anim 228 | ga.counter = 0 229 | ga.currentFrame = 0 230 | ga.loop = loop 231 | ga.playing = true 232 | } 233 | 234 | func (ga *spriteAnim) update(dt float64, phys *actor) { 235 | if !ga.playing { 236 | return 237 | } 238 | 239 | ga.counter += dt 240 | 241 | if ga.counter >= ga.rate { 242 | ga.currentFrame++ 243 | ga.counter = 0 244 | if ga.loop { 245 | if ga.currentFrame >= len(ga.anims[ga.currentAnim]) { 246 | ga.currentFrame = 0 247 | } 248 | } else { 249 | if ga.currentFrame >= len(ga.anims[ga.currentAnim]) { 250 | ga.currentFrame = len(ga.anims[ga.currentAnim]) - 1 251 | ga.playing = false 252 | } 253 | } 254 | } 255 | 256 | ga.frame = ga.anims[ga.currentAnim][ga.currentFrame] 257 | } 258 | 259 | func (ga *spriteAnim) draw(t pixel.Target, rect pixel.Rect) { 260 | if ga.sprite == nil { 261 | ga.sprite = pixel.NewSprite(nil, pixel.Rect{}) 262 | } 263 | // draw the correct frame with the correct position and direction 264 | ga.sprite.Set(ga.sheet, ga.frame) 265 | ga.sprite.Draw(t, pixel.IM. 266 | Moved(rect.Center()), 267 | ) 268 | } 269 | -------------------------------------------------------------------------------- /go/weights.csv: -------------------------------------------------------------------------------- 1 | 10.0595530425 2 | 12.0750319741 3 | 1.95447400411 4 | 0.592322941799 5 | -4.2901838159 6 | -7.55473774259 7 | 0.469505349079 8 | -3.62825451911 9 | 1.46817466096 10 | 3.05431320179 11 | -0.620880070693 12 | -3.54723274221 13 | -2.61063207339 14 | -6.14084802165 15 | 0.111108084072 16 | -2.76768688135 17 | -5.02729518374 18 | 12.384397561 19 | -3.13530879304 20 | 0.358674949873 21 | 0.159207330487 22 | -0.868451643757 23 | -0.706897691655 24 | -0.996949737714 25 | 3.2527436977 26 | -2.12501105118 27 | 0.937523659557 28 | -2.03861592859 29 | 2.40470306322 30 | -0.876756617416 31 | 6.12332208811 32 | -3.99844455776 33 | 3.01340173218 34 | -2.19965640777 35 | 7.09490432615 36 | -0.541806714296 37 | -0.961666441709 38 | 0.607792288743 39 | -1.29485269396 40 | 0.289927070047 41 | -1.98028629961 42 | 0.226044149736 43 | -3.46890851853 44 | 3.51912101396 45 | -2.83285004261 46 | 2.15282044555 47 | -5.47990498412 48 | -0.589896871634 49 | -2.04346318668 50 | -2.92943303407 51 | 0.927268718913 52 | 2.15908577026 53 | -1.48410692176 54 | -1.56831173511 55 | -0.763364479977 56 | -0.970017712472 57 | -0.017532910644 58 | 0.0297395477304 59 | -0.875829117483 60 | -2.44492172162 61 | 0.835621658695 62 | 4.3965317164 63 | -2.33486361234 64 | -5.18236220264 65 | 1.99405044209 66 | 1.82224625901 67 | -0.0242637575872 68 | -3.96029074712 69 | -------------------------------------------------------------------------------- /leaderboard.py: -------------------------------------------------------------------------------- 1 | '''The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | 5 | Written by Pete Garcin @rawktron 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE.''' 24 | 25 | from pymongo import MongoClient 26 | from pymongo import DESCENDING 27 | from datetime import datetime 28 | import pygame 29 | from utils import * 30 | import sys 31 | 32 | # Initialize connect at boot 33 | client = MongoClient('localhost', 27017) 34 | db = client.test 35 | scores = db.scores 36 | 37 | def DisplayLeaderBoard(screen, highscores, name): 38 | y = 100 39 | displaytext("TOP PLAYERS", 16, 320, 50, WHITE, screen) 40 | for s in highscores: 41 | if s['name'] == name: 42 | color = (255, 255, 0) # YELLOW 43 | else: 44 | color = WHITE 45 | displaytext(s['name'], 16, 160, y, color, screen) 46 | displaytext(str(s['score']), 16, 480, y, color, screen) 47 | y+=30 48 | 49 | displaytext("PRESS ANY KEY TO CONTINUE", 16, 320, 640, WHITE, screen) 50 | 51 | 52 | # TODO: Check if a player score exists, and instead add another entry instead of 53 | # overwriting. 54 | def StoreScore(name,score): 55 | print ("Storing "+name+" score: "+str(score)) 56 | scores.insert({"name":name,"score":score,"time":str(datetime.now())}) 57 | 58 | def GetScores(): 59 | return list(scores.find().sort("score",DESCENDING).limit(10)) 60 | 61 | def ClearScores(): 62 | scores.remove({}) 63 | 64 | def Test(): 65 | # Clear scores first for testing 66 | scores.remove({}) 67 | 68 | scores.insert({"name":"Jilly Bo","score":400,"time":str(datetime.now())}) 69 | scores.insert({"name":"Bob","score":1539,"time":str(datetime.now())}) 70 | scores.insert({"name":"AAA","score":744,"time":str(datetime.now())}) 71 | 72 | print (list(scores.find().sort("score",DESCENDING))) 73 | 74 | # For clearing db 75 | if (len(sys.argv) > 1) and (sys.argv[1] == '-clear'): 76 | ClearScores() 77 | -------------------------------------------------------------------------------- /neuralnetwork.py: -------------------------------------------------------------------------------- 1 | '''The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | Copyright (c) 2015 Milo Spencer-Harper 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE.''' 23 | 24 | 25 | import pygame 26 | from math import fabs 27 | from formulae import sigmoid, sigmoid_derivative, random_weight, get_synapse_colour, adjust_line_to_perimeter_of_circle, layer_left_margin 28 | import parameters 29 | from utils import * 30 | import keras.backend as K 31 | 32 | # From: https://github.com/philipperemy/keras-visualize-activations 33 | def get_activations(model, model_inputs, print_shape_only=False, layer_name=None): 34 | activations = [] 35 | inp = model.input 36 | 37 | model_multi_inputs_cond = True 38 | if not isinstance(inp, list): 39 | # only one input! let's wrap it in a list. 40 | inp = [inp] 41 | model_multi_inputs_cond = False 42 | 43 | outputs = [layer.output for layer in model.layers if 44 | layer.name == layer_name or layer_name is None] # all layer outputs 45 | 46 | funcs = [K.function(inp + [K.learning_phase()], [out]) for out in outputs] # evaluation functions 47 | 48 | if model_multi_inputs_cond: 49 | list_inputs = [] 50 | list_inputs.extend(model_inputs) 51 | list_inputs.append(1.) 52 | else: 53 | list_inputs = [model_inputs, 1.] 54 | 55 | # Learning phase. 1 = Test mode (no dropout or batch normalization) 56 | # layer_outputs = [func([model_inputs, 1.])[0] for func in funcs] 57 | layer_outputs = [func(list_inputs)[0] for func in funcs] 58 | for layer_activations in layer_outputs: 59 | activations.append(layer_activations) 60 | return activations 61 | 62 | 63 | ## Global vars used for vizmodel == 1 64 | surf = pygame.Surface((640,720)) 65 | nsurf = pygame.Surface((640,720)) 66 | nsurf.fill((255,0,255)) 67 | nsurf.set_colorkey((255,0,255)) 68 | 69 | # Draws a network using the live weights/activations from Keras/Tensorflow 70 | def draw_network(screen, model, model_inputs, weights): 71 | surf.fill((0,0,0)) 72 | nsurf.fill((255,0,255)) 73 | graph = get_activations(model, model_inputs) 74 | y = parameters.bottom_margin 75 | for layer in range(len(graph)): 76 | x = layer_left_margin(len(graph[layer][0])) 77 | for node in range(len(graph[layer][0])): 78 | if (layer+1 != len(graph)): 79 | for synapse in range(len(weights[layer+1][node])): 80 | lo = parameters.left_offset 81 | to = parameters.top_offset 82 | x2 = synapse * parameters.horizontal_distance_between_neurons + layer_left_margin(len(graph[layer+1][0])) 83 | y2 = y+parameters.vertical_distance_between_layers 84 | pygame.draw.line(surf,get_synapse_colour(weights[layer+1][node][synapse]),(int(x+lo), int(y+to)), (int(x2+lo), int(y2+to)),max(1,int(fabs(weights[layer+1][node][synapse])))) 85 | lo = parameters.left_offset 86 | to = parameters.top_offset 87 | pygame.draw.circle(nsurf,(180,180,200),(int(x+lo), int(y+to)),parameters.neuron_radius) 88 | displaytext(str(round(graph[layer][0][node], 2)), 16, x + 2+lo, y+to, BLACK, nsurf) 89 | x += parameters.horizontal_distance_between_neurons 90 | y += parameters.vertical_distance_between_layers 91 | screen.blit(surf,(640,0)) 92 | screen.blit(nsurf,(640,0)) 93 | 94 | # This home grown neural net was an initial version of the visualization as a POC 95 | # It is convenient for dumping debug data and prototyping, so included here for completeness 96 | # However this code is no longer used. 97 | 98 | class Synapse(): 99 | def __init__(self, input_neuron_index, x1, x2, y1, y2): 100 | self.input_neuron_index = input_neuron_index 101 | self.weight = random_weight() 102 | self.signal = 0 103 | self.x1 = x1 104 | self.x2 = x2 105 | self.y1 = y1 106 | self.y2 = y2 107 | 108 | def dump(self,f): 109 | f.write(str(self.weight)+"\n") 110 | 111 | def draw(self,screen): 112 | lo = parameters.left_offset 113 | to = parameters.top_offset 114 | pygame.draw.line(screen,get_synapse_colour(self.weight),(int(self.x1+lo), int(self.y1+to)), (int(self.x2+lo), int(self.y2+to)),max(1,int(fabs(self.weight)))) 115 | 116 | class Neuron(): 117 | def __init__(self, x, y, previous_layer): 118 | self.x = x 119 | self.y = y 120 | self.output = 0 121 | self.synapses = [] 122 | self.error = 0 123 | index = 0 124 | if previous_layer: 125 | for input_neuron in previous_layer.neurons: 126 | synapse = Synapse(index, x, input_neuron.x, y, input_neuron.y) 127 | self.synapses.append(synapse) 128 | index += 1 129 | 130 | def train(self, previous_layer): 131 | for synapse in self.synapses: 132 | # Propagate the error back down the synapse to the neuron in the layer below 133 | previous_layer.neurons[synapse.input_neuron_index].error += self.error * sigmoid_derivative(self.output) * synapse.weight 134 | # Adjust the synapse weight 135 | synapse.weight += synapse.signal * self.error * sigmoid_derivative(self.output) 136 | return previous_layer 137 | 138 | def think(self, previous_layer): 139 | activity = 0 140 | for synapse in self.synapses: 141 | synapse.signal = previous_layer.neurons[synapse.input_neuron_index].output 142 | activity += synapse.weight * synapse.signal 143 | self.output = sigmoid(activity) 144 | 145 | def dump(self,f): 146 | for synapse in self.synapses: 147 | synapse.dump(f) 148 | 149 | def draw(self,screen,nsurf): 150 | for synapse in self.synapses: 151 | synapse.draw(screen) 152 | 153 | lo = parameters.left_offset 154 | to = parameters.top_offset 155 | pygame.draw.circle(nsurf,(180,180,200),(int(self.x+lo), int(self.y+to)),parameters.neuron_radius) 156 | displaytext(str(round(self.output, 2)), 16, self.x + 2+lo, self.y+to, BLACK, nsurf) 157 | 158 | 159 | 160 | class Layer(): 161 | def __init__(self, network, number_of_neurons): 162 | if len(network.layers) > 0: 163 | self.is_input_layer = False 164 | self.previous_layer = network.layers[-1] 165 | self.y = self.previous_layer.y + parameters.vertical_distance_between_layers 166 | else: 167 | self.is_input_layer = True 168 | self.previous_layer = None 169 | self.y = parameters.bottom_margin 170 | self.neurons = [] 171 | x = layer_left_margin(number_of_neurons) 172 | for iteration in range(number_of_neurons): 173 | neuron = Neuron(x, self.y, self.previous_layer) 174 | self.neurons.append(neuron) 175 | x += parameters.horizontal_distance_between_neurons 176 | 177 | def think(self): 178 | for neuron in self.neurons: 179 | neuron.think(self.previous_layer) 180 | 181 | def draw(self,screen,nsurf): 182 | for neuron in self.neurons: 183 | neuron.draw(screen,nsurf) 184 | 185 | def dump(self,f): 186 | for neuron in self.neurons: 187 | neuron.dump(f) 188 | 189 | class NeuralNetwork(): 190 | def __init__(self, requested_layers): 191 | self.surf = pygame.Surface((640,720)) 192 | self.nsurf = pygame.Surface((640,720)) 193 | self.nsurf.fill((255,0,255)) 194 | self.nsurf.set_colorkey((255,0,255)) 195 | 196 | self.layers = [] 197 | for number_of_neurons in requested_layers: 198 | self.layers.append(Layer(self, number_of_neurons)) 199 | 200 | def train(self, example): 201 | error = example.output - self.think(example.inputs) 202 | self.reset_errors() 203 | self.layers[-1].neurons[0].error = error 204 | for l in range(len(self.layers) - 1, 0, -1): 205 | for neuron in self.layers[l].neurons: 206 | self.layers[l - 1] = neuron.train(self.layers[l - 1]) 207 | return fabs(error) 208 | 209 | def do_not_think(self): 210 | for layer in self.layers: 211 | for neuron in layer.neurons: 212 | neuron.output = 0 213 | for synapse in neuron.synapses: 214 | synapse.signal = 0 215 | 216 | def think(self, inputs): 217 | for layer in self.layers: 218 | if layer.is_input_layer: 219 | for index, value in enumerate(inputs): 220 | self.layers[0].neurons[index].output = value 221 | else: 222 | layer.think() 223 | return self.layers[-1].neurons[0].output 224 | 225 | def dump(self,f): 226 | for layer in self.layers: 227 | layer.dump(f) 228 | 229 | def draw(self,screen): 230 | self.surf.fill((0,0,0)) 231 | self.nsurf.fill((255,0,255)) 232 | for layer in self.layers: 233 | layer.draw(self.surf,self.nsurf) 234 | 235 | screen.blit(self.surf,(640,0)) 236 | screen.blit(self.nsurf,(640,0)) 237 | 238 | def reset_errors(self): 239 | for layer in self.layers: 240 | for neuron in layer.neurons: 241 | neuron.error = 0 -------------------------------------------------------------------------------- /neuroblast.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/neuroblast/1e187c7fa4b295829f9ec77c4523e916af9f6c9c/neuroblast.jpg -------------------------------------------------------------------------------- /parameters.py: -------------------------------------------------------------------------------- 1 | '''The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | Copyright (c) 2015 Milo Spencer-Harper 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE.''' 23 | 24 | # Parameters for drawing the network 25 | vertical_distance_between_layers = 120 26 | horizontal_distance_between_neurons = 110 27 | neuron_radius = 40 28 | number_of_neurons_in_widest_layer = 4 29 | width = 25 30 | height = 20 31 | left_margin = 10 32 | bottom_margin = 2 33 | error_bar_x_position = 14 34 | output_y_position = 15 35 | top_offset = 180 36 | left_offset = 145 37 | 38 | # Parameters for training the network 39 | training_iterations = 60000 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.13.1 2 | Keras==1.2.0 3 | pymongo==3.4.0 4 | pygame==1.9.3 5 | tensorflow==1.2.1 6 | -------------------------------------------------------------------------------- /rungame.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | IF EXIST data GOTO DATAEXIST 3 | mkdir data 4 | cd data 5 | mkdir db 6 | cd .. 7 | :DATAEXIST 8 | START "Mongo" "C:\Program Files\MongoDB\Server\3.4\bin\mongod" -dbpath data\db 9 | python3 game.py -f 10 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | '''The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ActiveState Software Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.''' 22 | 23 | import pygame 24 | import pygame.freetype 25 | from random import randrange, choice 26 | 27 | # Define some colors 28 | BLACK = (0, 0, 0) 29 | WHITE = (255, 255, 255) 30 | GREEN = (0, 255, 0) 31 | BLUE = (0, 0, 255) 32 | RED = (255, 0, 0) 33 | 34 | # Acceleration in pixels per second 35 | SHIP_ACC = 100 36 | DEADZONE = 0.25 37 | 38 | ## Constants 39 | MAX_STARS = 250 40 | 41 | trainedBrain = None 42 | 43 | def loadfont(size): 44 | global font 45 | font = pygame.freetype.Font('font/De Valencia (beta).otf', size) 46 | 47 | 48 | # Utility functions 49 | def displaytext(text, fontsize, x, y, color, surface): 50 | global font 51 | texcol = pygame.Color(*color) 52 | text = font.render(text, texcol) 53 | textpos = text[0].get_rect(centerx=x, centery=y) 54 | surface.blit(text[0], textpos) 55 | 56 | def init_stars(screen): 57 | """ Create the starfield """ 58 | global stars 59 | stars = [] 60 | for i in range(MAX_STARS): 61 | # A star is represented as a list with this format: [X,Y,speed] 62 | star = [randrange(0,screen.get_width()/2 - 4), 63 | randrange(0,screen.get_height() - 1), 64 | choice([2,3,4])] 65 | stars.append(star) 66 | 67 | def move_and_draw_stars(screen): 68 | """ Move and draw the stars in the given screen """ 69 | global stars 70 | for star in stars: 71 | star[1] += star[2] 72 | # If the star hit the bottom border then we reposition 73 | # it in the top of the screen with a random X coordinate. 74 | if star[1] >= screen.get_height(): 75 | star[1] = 0 76 | star[0] = randrange(0,636) 77 | star[2] = choice([2,3,4]) 78 | 79 | # Adjust the star color acording to the speed. 80 | # The slower the star, the darker should be its color. 81 | if star[2] == 2: 82 | color = (100,100,255) 83 | elif star[2] == 3: 84 | color = (190,190,190) 85 | elif star[2] == 4: 86 | color = (255,255,255) 87 | 88 | # Draw the star as a rectangle. 89 | # The star size is proportional to its speed. 90 | screen.fill(color,(star[0],star[1],star[2],star[2])) 91 | --------------------------------------------------------------------------------