123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- import {bird} from "./bird.js";
- import {ffnet} from "./ffnet.js";
- import {pipe} from "./pipe.js";
- import {population} from "./population.js"
- const _GRAVITY = 900;
- const _TERMINAL_VELOCITY = 400;
- const _MAX_UPWARDS_VELOCITY = -300;
- const _UPWARDS_ACCELERATION = -450;
- const _PIPE_SPACING_X = 250;
- const _PIPE_SPACING_Y = 100;
- const _TREADMILL_SPEED = -125;
- const _CONFIG_WIDTH = 960;
- const _CONFIG_HEIGHT = 540;
- const _GROUND_Y = _CONFIG_HEIGHT;
- const _BIRD_POS_X = 50;
- class FlappyBirdGame {
- constructor() {
- this._game = this._CreateGame();
- this._previousFrame = null;
- this._gameOver = true;
- this._statsText1 = null;
- this._statsText2 = null;
- this._gameOverText = null;
- this._pipes = [];
- this._birds = [];
- this._InitPopulations();
- }
- _InitPopulations() {
- const NN_DEF1 = [
- {size: 7},
- {size: 5, activation: ffnet.relu},
- {size: 1, activation: ffnet.sigmoid}
- ];
- const NN_DEF2 = [
- {size: 7},
- {size: 9, activation: ffnet.relu},
- {size: 1, activation: ffnet.sigmoid}
- ];
- const NN_DEF3 = [
- {size: 7},
- {size: 9, activation: ffnet.relu},
- {size: 9, activation: ffnet.relu},
- {size: 1, activation: ffnet.sigmoid}
- ];
- this._populations = [
- this._CreatePopulation(100, NN_DEF1, 0xFF0000),
- this._CreatePopulation(100, NN_DEF2, 0x0000FF),
- this._CreatePopulation(100, NN_DEF3, 0x00FF00),
- ];
- }
- _CreatePopulation(sz, shapes, colour) {
- const t = new ffnet.FFNeuralNetwork(shapes);
- const params = {
- population_size: sz,
- genotype: {
- size: t.toArray().length,
- },
- mutation: {
- magnitude: 0.1,
- odds: 0.1,
- decay: 0,
- },
- breed: {
- selectionCutoff: 0.2,
- immortalityCutoff: 0.05,
- childrenPercentage: 0.5,
- },
- shapes: shapes,
- tint: colour,
- };
- return new population.Population(params);
- }
- _Destroy() {
- for (let b of this._birds) {
- b.Destroy();
- }
- for (let p of this._pipes) {
- p.Destroy();
- }
- this._statsText1.destroy();
- this._statsText2.destroy();
- if (this._gameOverText !== null) {
- this._gameOverText.destroy();
- }
- this._birds = [];
- this._pipes = [];
- this._previousFrame = null;
- }
- _Init() {
- for (let i = 0; i < 5; i+=1) {
- this._pipes.push(
- new pipe.PipePairObject({
- scene: this._scene,
- x: 500 + i * _PIPE_SPACING_X,
- spacing: _PIPE_SPACING_Y,
- speed: _TREADMILL_SPEED,
- config_height: _CONFIG_HEIGHT
- }));
- }
- this._gameOver = false;
- this._stats = {
- alive: 0,
- score: 0,
- };
- const style = {
- font: "40px Roboto",
- fill: "#FFFFFF",
- align: "right",
- fixedWidth: 210,
- shadow: {
- offsetX: 2,
- offsetY: 2,
- color: "#000",
- blur: 2,
- fill: true
- }
- };
- this._statsText1 = this._scene.add.text(0, 0, '', style);
- style.align = 'left';
- this._statsText2 = this._scene.add.text(
- this._statsText1.width + 10, 0, '', style);
- this._birds = [];
- for (let curPop of this._populations) {
- curPop.Step();
- this._birds.push(...curPop._population.map(
- p => new bird.FlappyBird_NeuralNet(
- {
- scene: this._scene,
- pop_entity: p,
- pop_params: curPop._params,
- x: _BIRD_POS_X,
- config_width: _CONFIG_WIDTH,
- config_height: _CONFIG_HEIGHT,
- max_upwards_velocity: _MAX_UPWARDS_VELOCITY,
- terminal_velocity: _TERMINAL_VELOCITY,
- treadmill_speed: _TREADMILL_SPEED,
- acceleration: _UPWARDS_ACCELERATION,
- gravity: _GRAVITY
- })));
- }
- }
- _CreateGame() {
- const self = this;
- const config = {
- type: Phaser.AUTO,
- scene: {
- preload: function() { self._OnPreload(this); },
- create: function() { self._OnCreate(this); },
- update: function() { self._OnUpdate(this); },
- },
- scale: {
- mode: Phaser.Scale.FIT,
- autoCenter: Phaser.Scale.CENTER_BOTH,
- treadmill_speed: _TREADMILL_SPEED,
- width: _CONFIG_WIDTH,
- height: _CONFIG_HEIGHT,
- }
- };
- return new Phaser.Game(config);
- }
- _OnPreload(scene) {
- this._scene = scene;
- this._scene.load.image('sky', 'assets/sky.png');
- this._scene.load.image('bird', 'assets/bird.png');
- this._scene.load.image('bird-colour', 'assets/bird-colour.png');
- this._scene.load.image('pipe', 'assets/pipe.png');
- }
- _OnCreate(scene) {
- const s = this._scene.add.image(0, 0, 'sky');
- s.displayOriginX = 0;
- s.displayOriginY = 0;
- s.displayWidth = _CONFIG_WIDTH;
- s.displayHeight = _CONFIG_HEIGHT;
- this._keys = {
- up: this._scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP),
- f: this._scene.input.keyboard.addKey('F'),
- r: this._scene.input.keyboard.addKey('R'),
- }
- this._keys.f.on('down', function () {
- if (this._scene.scale.isFullscreen) {
- this._scene.scale.stopFullscreen();
- } else {
- this._scene.scale.startFullscreen();
- }
- }, this);
- this._keys.r.on('down', function () {
- this._Destroy();
- this._Init();
- }, this);
- this._Init();
- }
- _OnUpdate(scene) {
- if (this._gameOver) {
- this._DrawStats();
- return;
- }
- const currentFrame = scene.time.now;
- if (this._previousFrame == null) {
- this._previousFrame = currentFrame;
- }
- const timeElapsedInS = Math.min(
- (currentFrame - this._previousFrame) / 1000.0, 1.0 / 30.0);
- this._UpdateBirds(timeElapsedInS);
- this._UpdatePipes(timeElapsedInS);
- this._CheckGameOver();
- this._DrawStats();
- this._previousFrame = currentFrame;
- }
- _CheckGameOver() {
- const results = this._birds.map(b => this._IsBirdOutOfBounds(b));
- this._stats.alive = results.reduce((t, r) => (r ? t: t + 1), 0);
- if (results.every(b => b)) {
- this._GameOver();
- }
- }
- _IsBirdOutOfBounds(bird) {
- const birdAABB = bird.Bounds;
- birdAABB.top += 10;
- birdAABB.bottom -= 10;
- birdAABB.left += 10;
- birdAABB.right -= 10;
- if (bird.Dead) {
- return true;
- }
- if (birdAABB.bottom >= _GROUND_Y || birdAABB.top <= 0) {
- bird.Dead = true;
- return true;
- }
- for (const p of this._pipes) {
- if (p.Intersects(birdAABB)) {
- bird.Dead = true;
- return true;
- }
- }
- return false;
- }
- _GetNearestPipes() {
- let index = 0;
- if (this._pipes[0].X + this._pipes[0].Width <= _BIRD_POS_X) {
- index = 1;
- }
- return this._pipes.slice(index, 2);
- }
- _UpdateBirds(timeElapsed) {
- const params = {
- timeElapsed: timeElapsed,
- keys: {up: Phaser.Input.Keyboard.JustDown(this._keys.up)},
- nearestPipes: this._GetNearestPipes(),
- };
- for (let b of this._birds) {
- b.Update(params);
- }
- }
- _UpdatePipes(timeElapsed) {
- const oldPipeX = this._pipes[0].X + this._pipes[0].Width;
- for (const p of this._pipes) {
- p.Update(timeElapsed);
- }
- const newPipeX = this._pipes[0].X + this._pipes[0].Width;
- if (oldPipeX > _BIRD_POS_X && newPipeX <= _BIRD_POS_X) {
- this._stats.score += 1;
- }
- if ((this._pipes[0].X + this._pipes[0].Width) <= 0) {
- const p = this._pipes.shift();
- p.Reset(this._pipes[this._pipes.length - 1].X + _PIPE_SPACING_X);
- this._pipes.push(p);
- }
- }
- _GameOver() {
- const text = "GAME OVER";
- const style = {
- font: "100px Roboto",
- fill: "#FFFFFF",
- align: "center",
- fixedWidth: _CONFIG_WIDTH,
- shadow: {
- offsetX: 2,
- offsetY: 2,
- color: "#000",
- blur: 2,
- fill: true
- }
- };
- this._gameOverText = this._scene.add.text(
- 0, _CONFIG_HEIGHT * 0.25, text, style);
- this._gameOver = true;
- setTimeout(() => {
- this._Destroy();
- this._Init();
- }, 2000);
- }
- _DrawStats() {
- function _Line(t, s) {
- return t + ': ' + s + '\n';
- }
- const text1 = 'Generation:\n' + 'Score:\n' + 'Alive:\n';
- this._statsText1.text = text1;
- const text2 = (
- this._populations[0]._generations + '\n' +
- this._stats.score + '\n' +
- this._stats.alive + '\n');
- this._statsText2.text = text2;
- }
- }
- const _GAME = new FlappyBirdGame();
|