main.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. import {bird} from "./bird.js";
  2. import {ffnet} from "./ffnet.js";
  3. import {pipe} from "./pipe.js";
  4. import {population} from "./population.js"
  5. const _GRAVITY = 900;
  6. const _TERMINAL_VELOCITY = 400;
  7. const _MAX_UPWARDS_VELOCITY = -300;
  8. const _UPWARDS_ACCELERATION = -450;
  9. const _PIPE_SPACING_X = 250;
  10. const _PIPE_SPACING_Y = 100;
  11. const _TREADMILL_SPEED = -125;
  12. const _CONFIG_WIDTH = 960;
  13. const _CONFIG_HEIGHT = 540;
  14. const _GROUND_Y = _CONFIG_HEIGHT;
  15. const _BIRD_POS_X = 50;
  16. class FlappyBirdGame {
  17. constructor() {
  18. this._game = this._CreateGame();
  19. this._previousFrame = null;
  20. this._gameOver = true;
  21. this._statsText1 = null;
  22. this._statsText2 = null;
  23. this._gameOverText = null;
  24. this._pipes = [];
  25. this._birds = [];
  26. this._InitPopulations();
  27. }
  28. _InitPopulations() {
  29. const NN_DEF1 = [
  30. {size: 7},
  31. {size: 5, activation: ffnet.relu},
  32. {size: 1, activation: ffnet.sigmoid}
  33. ];
  34. const NN_DEF2 = [
  35. {size: 7},
  36. {size: 9, activation: ffnet.relu},
  37. {size: 1, activation: ffnet.sigmoid}
  38. ];
  39. const NN_DEF3 = [
  40. {size: 7},
  41. {size: 9, activation: ffnet.relu},
  42. {size: 9, activation: ffnet.relu},
  43. {size: 1, activation: ffnet.sigmoid}
  44. ];
  45. this._populations = [
  46. this._CreatePopulation(100, NN_DEF1, 0xFF0000),
  47. this._CreatePopulation(100, NN_DEF2, 0x0000FF),
  48. this._CreatePopulation(100, NN_DEF3, 0x00FF00),
  49. ];
  50. }
  51. _CreatePopulation(sz, shapes, colour) {
  52. const t = new ffnet.FFNeuralNetwork(shapes);
  53. const params = {
  54. population_size: sz,
  55. genotype: {
  56. size: t.toArray().length,
  57. },
  58. mutation: {
  59. magnitude: 0.1,
  60. odds: 0.1,
  61. decay: 0,
  62. },
  63. breed: {
  64. selectionCutoff: 0.2,
  65. immortalityCutoff: 0.05,
  66. childrenPercentage: 0.5,
  67. },
  68. shapes: shapes,
  69. tint: colour,
  70. };
  71. return new population.Population(params);
  72. }
  73. _Destroy() {
  74. for (let b of this._birds) {
  75. b.Destroy();
  76. }
  77. for (let p of this._pipes) {
  78. p.Destroy();
  79. }
  80. this._statsText1.destroy();
  81. this._statsText2.destroy();
  82. if (this._gameOverText !== null) {
  83. this._gameOverText.destroy();
  84. }
  85. this._birds = [];
  86. this._pipes = [];
  87. this._previousFrame = null;
  88. }
  89. _Init() {
  90. for (let i = 0; i < 5; i+=1) {
  91. this._pipes.push(
  92. new pipe.PipePairObject({
  93. scene: this._scene,
  94. x: 500 + i * _PIPE_SPACING_X,
  95. spacing: _PIPE_SPACING_Y,
  96. speed: _TREADMILL_SPEED,
  97. config_height: _CONFIG_HEIGHT
  98. }));
  99. }
  100. this._gameOver = false;
  101. this._stats = {
  102. alive: 0,
  103. score: 0,
  104. };
  105. const style = {
  106. font: "40px Roboto",
  107. fill: "#FFFFFF",
  108. align: "right",
  109. fixedWidth: 210,
  110. shadow: {
  111. offsetX: 2,
  112. offsetY: 2,
  113. color: "#000",
  114. blur: 2,
  115. fill: true
  116. }
  117. };
  118. this._statsText1 = this._scene.add.text(0, 0, '', style);
  119. style.align = 'left';
  120. this._statsText2 = this._scene.add.text(
  121. this._statsText1.width + 10, 0, '', style);
  122. this._birds = [];
  123. for (let curPop of this._populations) {
  124. curPop.Step();
  125. this._birds.push(...curPop._population.map(
  126. p => new bird.FlappyBird_NeuralNet(
  127. {
  128. scene: this._scene,
  129. pop_entity: p,
  130. pop_params: curPop._params,
  131. x: _BIRD_POS_X,
  132. config_width: _CONFIG_WIDTH,
  133. config_height: _CONFIG_HEIGHT,
  134. max_upwards_velocity: _MAX_UPWARDS_VELOCITY,
  135. terminal_velocity: _TERMINAL_VELOCITY,
  136. treadmill_speed: _TREADMILL_SPEED,
  137. acceleration: _UPWARDS_ACCELERATION,
  138. gravity: _GRAVITY
  139. })));
  140. }
  141. }
  142. _CreateGame() {
  143. const self = this;
  144. const config = {
  145. type: Phaser.AUTO,
  146. scene: {
  147. preload: function() { self._OnPreload(this); },
  148. create: function() { self._OnCreate(this); },
  149. update: function() { self._OnUpdate(this); },
  150. },
  151. scale: {
  152. mode: Phaser.Scale.FIT,
  153. autoCenter: Phaser.Scale.CENTER_BOTH,
  154. treadmill_speed: _TREADMILL_SPEED,
  155. width: _CONFIG_WIDTH,
  156. height: _CONFIG_HEIGHT,
  157. }
  158. };
  159. return new Phaser.Game(config);
  160. }
  161. _OnPreload(scene) {
  162. this._scene = scene;
  163. this._scene.load.image('sky', 'assets/sky.png');
  164. this._scene.load.image('bird', 'assets/bird.png');
  165. this._scene.load.image('bird-colour', 'assets/bird-colour.png');
  166. this._scene.load.image('pipe', 'assets/pipe.png');
  167. }
  168. _OnCreate(scene) {
  169. const s = this._scene.add.image(0, 0, 'sky');
  170. s.displayOriginX = 0;
  171. s.displayOriginY = 0;
  172. s.displayWidth = _CONFIG_WIDTH;
  173. s.displayHeight = _CONFIG_HEIGHT;
  174. this._keys = {
  175. up: this._scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP),
  176. f: this._scene.input.keyboard.addKey('F'),
  177. r: this._scene.input.keyboard.addKey('R'),
  178. }
  179. this._keys.f.on('down', function () {
  180. if (this._scene.scale.isFullscreen) {
  181. this._scene.scale.stopFullscreen();
  182. } else {
  183. this._scene.scale.startFullscreen();
  184. }
  185. }, this);
  186. this._keys.r.on('down', function () {
  187. this._Destroy();
  188. this._Init();
  189. }, this);
  190. this._Init();
  191. }
  192. _OnUpdate(scene) {
  193. if (this._gameOver) {
  194. this._DrawStats();
  195. return;
  196. }
  197. const currentFrame = scene.time.now;
  198. if (this._previousFrame == null) {
  199. this._previousFrame = currentFrame;
  200. }
  201. const timeElapsedInS = Math.min(
  202. (currentFrame - this._previousFrame) / 1000.0, 1.0 / 30.0);
  203. this._UpdateBirds(timeElapsedInS);
  204. this._UpdatePipes(timeElapsedInS);
  205. this._CheckGameOver();
  206. this._DrawStats();
  207. this._previousFrame = currentFrame;
  208. }
  209. _CheckGameOver() {
  210. const results = this._birds.map(b => this._IsBirdOutOfBounds(b));
  211. this._stats.alive = results.reduce((t, r) => (r ? t: t + 1), 0);
  212. if (results.every(b => b)) {
  213. this._GameOver();
  214. }
  215. }
  216. _IsBirdOutOfBounds(bird) {
  217. const birdAABB = bird.Bounds;
  218. birdAABB.top += 10;
  219. birdAABB.bottom -= 10;
  220. birdAABB.left += 10;
  221. birdAABB.right -= 10;
  222. if (bird.Dead) {
  223. return true;
  224. }
  225. if (birdAABB.bottom >= _GROUND_Y || birdAABB.top <= 0) {
  226. bird.Dead = true;
  227. return true;
  228. }
  229. for (const p of this._pipes) {
  230. if (p.Intersects(birdAABB)) {
  231. bird.Dead = true;
  232. return true;
  233. }
  234. }
  235. return false;
  236. }
  237. _GetNearestPipes() {
  238. let index = 0;
  239. if (this._pipes[0].X + this._pipes[0].Width <= _BIRD_POS_X) {
  240. index = 1;
  241. }
  242. return this._pipes.slice(index, 2);
  243. }
  244. _UpdateBirds(timeElapsed) {
  245. const params = {
  246. timeElapsed: timeElapsed,
  247. keys: {up: Phaser.Input.Keyboard.JustDown(this._keys.up)},
  248. nearestPipes: this._GetNearestPipes(),
  249. };
  250. for (let b of this._birds) {
  251. b.Update(params);
  252. }
  253. }
  254. _UpdatePipes(timeElapsed) {
  255. const oldPipeX = this._pipes[0].X + this._pipes[0].Width;
  256. for (const p of this._pipes) {
  257. p.Update(timeElapsed);
  258. }
  259. const newPipeX = this._pipes[0].X + this._pipes[0].Width;
  260. if (oldPipeX > _BIRD_POS_X && newPipeX <= _BIRD_POS_X) {
  261. this._stats.score += 1;
  262. }
  263. if ((this._pipes[0].X + this._pipes[0].Width) <= 0) {
  264. const p = this._pipes.shift();
  265. p.Reset(this._pipes[this._pipes.length - 1].X + _PIPE_SPACING_X);
  266. this._pipes.push(p);
  267. }
  268. }
  269. _GameOver() {
  270. const text = "GAME OVER";
  271. const style = {
  272. font: "100px Roboto",
  273. fill: "#FFFFFF",
  274. align: "center",
  275. fixedWidth: _CONFIG_WIDTH,
  276. shadow: {
  277. offsetX: 2,
  278. offsetY: 2,
  279. color: "#000",
  280. blur: 2,
  281. fill: true
  282. }
  283. };
  284. this._gameOverText = this._scene.add.text(
  285. 0, _CONFIG_HEIGHT * 0.25, text, style);
  286. this._gameOver = true;
  287. setTimeout(() => {
  288. this._Destroy();
  289. this._Init();
  290. }, 2000);
  291. }
  292. _DrawStats() {
  293. function _Line(t, s) {
  294. return t + ': ' + s + '\n';
  295. }
  296. const text1 = 'Generation:\n' + 'Score:\n' + 'Alive:\n';
  297. this._statsText1.text = text1;
  298. const text2 = (
  299. this._populations[0]._generations + '\n' +
  300. this._stats.score + '\n' +
  301. this._stats.alive + '\n');
  302. this._statsText2.text = text2;
  303. }
  304. }
  305. const _GAME = new FlappyBirdGame();