├── assets ├── body.png ├── food.png ├── mask1.png ├── spyro.png ├── stats.png ├── azo-fire.png ├── short-stack.png ├── short-stack.xml └── azo-fire.xml ├── .gitignore ├── README.md ├── src ├── test.js ├── boot.js ├── share.js ├── preloader.js ├── player.js ├── data.js ├── stats.js ├── mainmenu.js ├── game.js └── snake.js ├── package.json ├── LICENSE ├── index.html ├── cert.pem └── key.pem /assets/body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phaserjs/facebook-instant-games/HEAD/assets/body.png -------------------------------------------------------------------------------- /assets/food.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phaserjs/facebook-instant-games/HEAD/assets/food.png -------------------------------------------------------------------------------- /assets/mask1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phaserjs/facebook-instant-games/HEAD/assets/mask1.png -------------------------------------------------------------------------------- /assets/spyro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phaserjs/facebook-instant-games/HEAD/assets/spyro.png -------------------------------------------------------------------------------- /assets/stats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phaserjs/facebook-instant-games/HEAD/assets/stats.png -------------------------------------------------------------------------------- /assets/azo-fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phaserjs/facebook-instant-games/HEAD/assets/azo-fire.png -------------------------------------------------------------------------------- /assets/short-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phaserjs/facebook-instant-games/HEAD/assets/short-stack.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # System and IDE files 2 | Thumbs.db 3 | .DS_Store 4 | .idea 5 | .vscode 6 | *.suo 7 | *.sublime-project 8 | *.sublime-workspace 9 | 10 | # Vendors 11 | node_modules/ 12 | 13 | # Build 14 | /npm-debug.log 15 | build/ 16 | out/ 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Phaser 3 Facebook Instant Games Examples 2 | 3 | The code in this repository is to go with the tutorial located at [http://phaser.io/tutorials/getting-started-facebook-instant-games/](http://phaser.io/tutorials/getting-started-facebook-instant-games/) -------------------------------------------------------------------------------- /src/test.js: -------------------------------------------------------------------------------- 1 | this.add.text(0, 0).setText([ 2 | 'Player ID: ' + this.facebook.playerID, 3 | 'Player Name: ' + this.facebook.playerName 4 | ]); 5 | 6 | this.facebook.loadPlayerPhoto(this, 'dude').once('photocomplete', this.addPlayerPhoto, this); 7 | -------------------------------------------------------------------------------- /src/boot.js: -------------------------------------------------------------------------------- 1 | FBInstant.initializeAsync().then(function() { 2 | 3 | var config = { 4 | type: Phaser.CANVAS, 5 | width: 800, 6 | height: 600, 7 | backgroundColor: '#222448', 8 | scene: [ Preloader, GameShare ] 9 | }; 10 | 11 | new Phaser.Game(config); 12 | 13 | }); 14 | -------------------------------------------------------------------------------- /src/share.js: -------------------------------------------------------------------------------- 1 | class GameShare extends Phaser.Scene { 2 | 3 | constructor () 4 | { 5 | super({ key: 'GameShare', active: false }); 6 | } 7 | 8 | create () 9 | { 10 | this.input.keyboard.on('keydown_P', function () { 11 | this.facebook.openRequest('Play this now!', 'spyro'); 12 | }, this); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phaser-facebook-instant-games", 3 | "version": "1.0.0", 4 | "description": "Phaser Facebook Instant Games Examples", 5 | "main": "src/boot.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "start": "http-server --ssl -c-1 -p 8080 -a 127.0.0.1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/photonstorm/phaser-facebook-instant-games.git" 15 | }, 16 | "author": "Richard Davey", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/photonstorm/phaser-facebook-instant-games/issues" 20 | }, 21 | "homepage": "https://github.com/photonstorm/phaser-facebook-instant-games#readme", 22 | "dependencies": { 23 | "http-server": "^0.11.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/preloader.js: -------------------------------------------------------------------------------- 1 | class Preloader extends Phaser.Scene { 2 | 3 | constructor () 4 | { 5 | super('Preloader'); 6 | } 7 | 8 | preload () 9 | { 10 | this.facebook.showLoadProgress(this); 11 | this.facebook.once('startgame', this.startGame, this); 12 | 13 | this.load.image('spyro', 'assets/spyro.png'); 14 | this.load.image('mask', 'assets/mask1.png'); 15 | this.load.image('stats', 'assets/stats.png'); 16 | this.load.bitmapFont('short', 'assets/short-stack.png', 'assets/short-stack.xml'); 17 | this.load.bitmapFont('azo', 'assets/azo-fire.png', 'assets/azo-fire.xml'); 18 | 19 | } 20 | 21 | startGame () 22 | { 23 | // this.scene.start('MainMenu'); 24 | // this.scene.start('PlayerDetails'); 25 | // this.scene.start('GameStats'); 26 | // this.scene.start('GameData'); 27 | this.scene.start('GameShare'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Richard Davey 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 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Phaser 3 Facebook Instant Games Test 1 5 | 6 | 7 | 8 | 9 | 10 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDdTCCAl2gAwIBAgIJAJliW1PFjb1QMA0GCSqGSIb3DQEBCwUAMFExCzAJBgNV 3 | BAYTAlVTMRAwDgYDVQQIDAdBbGFiYW1hMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkx 4 | GTAXBgNVBAoMEFBob3RvbiBTdG9ybSBMdGQwHhcNMTgwNzI3MDM0NTE3WhcNMjEw 5 | NDIyMDM0NTE3WjBRMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHQWxhYmFtYTEVMBMG 6 | A1UEBwwMRGVmYXVsdCBDaXR5MRkwFwYDVQQKDBBQaG90b24gU3Rvcm0gTHRkMIIB 7 | IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxfiyProDQh1QADYItWam3KkP 8 | zsMpCWsw16YSpvDNUvYiw0E3UKg0ZxNQ/c3tINZBl8UwZ7R6Y14zkpFjF2b4hv7Q 9 | v2W/nN2ZE/PyvDzy2rJOzzS7k4C2UHm2M1M8o3eZaa0WMZWoKPxcs3kepU5hmAjb 10 | 2UmNzecUVU1fBAznUlzmVziayN6K/fSHShMURsCspZJjxl+l3qZFgv8qGC4yBn2+ 11 | H1pxiwyZ4bf3qzSCyOcsrqUr8Bfqj7O1vYOKVwNJKfG944IF/HsEuVd3TNK9vQGo 12 | qs0mhYAqcp+/Emkogddj1cSE70yrCnrBCM4/M0XT/EoTGS3k6zkjTOI9MI/4+wID 13 | AQABo1AwTjAdBgNVHQ4EFgQU0uc5mvzeYSdUE0nVv3Bcbt1cmncwHwYDVR0jBBgw 14 | FoAU0uc5mvzeYSdUE0nVv3Bcbt1cmncwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B 15 | AQsFAAOCAQEAI1Js3M3hJ7m2XHFcHs5/Zfybms+sZRO3qlRjFkfU0jNfBnsJxh5P 16 | l2Gmze7eO84UOJJXcR5BApOQ2oDwSegW5AUg+NnLKNCRX2CLkPwHZu3sKGpkqaQ9 17 | ANXlCUVDxtMeCghXukfD/Ki2xBhuTpta9mVt4bscxCbtIe0qY8SDU6wJKaR9m+ls 18 | 7nBesyzfF0OD5J20/DgZuv5ZOunRFMupDpRNPXzryiTl2+Bzie0g90onn8eYLfuH 19 | QihgFjd/1/os4RRT3PkcvrvP/4hGNdsTqxlYx49H05IliYXaQzeO347L1hTlAKGr 20 | WfSDr2kQjIE8/i0Go1RVgCWnT8i6dvNemQ== 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAxfiyProDQh1QADYItWam3KkPzsMpCWsw16YSpvDNUvYiw0E3 3 | UKg0ZxNQ/c3tINZBl8UwZ7R6Y14zkpFjF2b4hv7Qv2W/nN2ZE/PyvDzy2rJOzzS7 4 | k4C2UHm2M1M8o3eZaa0WMZWoKPxcs3kepU5hmAjb2UmNzecUVU1fBAznUlzmVzia 5 | yN6K/fSHShMURsCspZJjxl+l3qZFgv8qGC4yBn2+H1pxiwyZ4bf3qzSCyOcsrqUr 6 | 8Bfqj7O1vYOKVwNJKfG944IF/HsEuVd3TNK9vQGoqs0mhYAqcp+/Emkogddj1cSE 7 | 70yrCnrBCM4/M0XT/EoTGS3k6zkjTOI9MI/4+wIDAQABAoIBAA+dQunBoBop17J8 8 | kIlGNGj5CJ8lM2pjwvFvTs9N9NPTSACgdy1sZt2uX1f1UvuhCoFe8ydqy7BksKqh 9 | vvAMlhOHq409CDTmIXm6Zu3D3N+06pRbM2Q55adJQj6gFQtLlpUIKtT0WdQJu4II 10 | Tot5OCdWo4hqzb1suWTU+maGYgRT3eSv0weiOAfgV6cxEYkG1UN27nl1dAizIRod 11 | xa/FUfAGQ5PkO1a/J+Y0pzu7AgopPPG38rQgZ9xQQRqS5iJf7uXFj70LDSMyQaxO 12 | 2M9Xp7JgVlPtIqIFRfaAoq+7NWX0W5reUt2qyiitXh6zb+1kTMYQ32/SBEPhtDm/ 13 | zXcXLvECgYEA7JbqRjXPPLu0EIYwZMeKAIj92/KYbLnWSuTbO1V2gwjLTbRAs6Nv 14 | 2O2rIKvJgyptQuvIFF+ZLUGJomc6ze90uLxu+7Tv8TBh8W12U4TWqGC/gLI9astZ 15 | XqdnVX5MZRURevjQxC4Jf3mRkCVu96mtbi1qeZ8lbNAd3dOLHMwh8jkCgYEA1jav 16 | 6SE8ls9Ij1poqrza740kp4EsIgHwIZUiAsqw3Qh71OYh86Q7gI007yW9ORxCMO6m 17 | ZPhL083WNRKhpk1nYCeOq0Vc7k2jdSHBr1YNRFvo+XP1FdtWsES6QqIIzEgveKzq 18 | Wy1xu7aS+ekwTDOOrrE5kDsDdnn/+iwHYQdv9NMCgYEAxOTPsYCkqGmZ8EqMycJa 19 | MhnQExTxD0MVeRy+6sE0MNbHwzIoypdQxNr+mOD+gl9zsMbiqznoE4bAUVzRzoLi 20 | w79mKaoqKbJXQIIVgGpD7rsdhLBRdqgnByQQVlEWqOUAuDOMIkgh9Wmp36mXkRsv 21 | z0qSmwb8MXN995dO5bdo5NkCgYEAuiSZZfb4xH/pKJDChSNJCILYyCnKJN8UQMpr 22 | 3Isvd1Lo8E7uT951yjZqOinMLTVuG0E2HGVa1vl123aOO4nle7vEEPZcU8Ppd15X 23 | BTq8mptXbnUiEtUfllqSlFZjWMidX7q6h3KM8zYqr7m/u2fN4+WjBL8TOj+3fTgH 24 | TXx9//sCgYA0oJvGtyCyaFJBJEo/fR1D8710jTByQhLvDlRO+f9rXXXrYg/7PuoS 25 | iMPaHKhauyi+XNpZgnXYmkpEIympzdJdfZ/3zvWVER4+N676pJNz5yygkvBE3INK 26 | JinBW3sXzTF0WkXVrycD+mW3eLkOl1pdKC0fKwobQsB1nT6FOAawpQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /src/player.js: -------------------------------------------------------------------------------- 1 | class PlayerDetails extends Phaser.Scene { 2 | 3 | constructor () 4 | { 5 | super({ key: 'PlayerDetails', active: false }); 6 | } 7 | 8 | create () 9 | { 10 | this.add.bitmapText(400, 400, 'azo', this.facebook.playerName).setOrigin(0.5); 11 | 12 | this.load.image('player', this.facebook.playerPhotoURL); 13 | 14 | this.load.once('filecomplete-image-player', this.addPlayerPhoto, this); 15 | 16 | // this.load.once('filecomplete-image-player', this.addRoundedPlayerPhoto, this); 17 | // this.load.once('filecomplete-image-player', this.addMaskedPlayerPhoto, this); 18 | 19 | this.load.start(); 20 | 21 | // this.facebook.loadPlayerPhoto(this, 'player').once('photocomplete', this.addPlayerPhoto, this); 22 | } 23 | 24 | addPlayerPhoto (key) 25 | { 26 | this.add.image(400, 200, key); 27 | } 28 | 29 | addRoundedPlayerPhoto (key) 30 | { 31 | var photo = this.textures.createCanvas('playerMasked', 196, 196); 32 | 33 | var source = this.textures.get('player').getSourceImage(); 34 | 35 | photo.context.beginPath(); 36 | 37 | photo.context.arc(98, 98, 98, 0, Math.PI * 2, false); 38 | 39 | photo.context.clip(); 40 | 41 | photo.draw(0, 0, source); 42 | 43 | this.add.image(400, 200, 'playerMasked'); 44 | } 45 | 46 | addMaskedPlayerPhoto (key) 47 | { 48 | var photo = this.textures.createCanvas('playerMasked', 196, 196); 49 | 50 | var source = this.textures.get('player').getSourceImage(); 51 | var mask = this.textures.get('mask').getSourceImage(); 52 | 53 | photo.draw(0, 0, mask); 54 | 55 | photo.context.globalCompositeOperation = 'source-in'; 56 | 57 | photo.draw(0, 0, source); 58 | 59 | this.add.image(400, 200, 'playerMasked'); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/data.js: -------------------------------------------------------------------------------- 1 | class GameData extends Phaser.Scene { 2 | 3 | constructor () 4 | { 5 | super({ key: 'GameData', active: false }); 6 | 7 | this.mana; 8 | this.gems; 9 | } 10 | 11 | create () 12 | { 13 | this.mana = this.add.text(20, 20, 'Mana: 0'); 14 | this.gems = this.add.text(20, 80, 'Gems: 0'); 15 | 16 | this.facebook.on('getdata', this.dataLoaded, this); 17 | this.facebook.on('savedata', this.dataSaved, this); 18 | 19 | this.facebook.on('getdatafail', function (error) { 20 | console.log('data failed', error); 21 | }); 22 | 23 | this.facebook.on('savedatafail', function (error) { 24 | console.log('save data failed', error); 25 | }); 26 | 27 | this.facebook.getData([ 'mana', 'gems', 'color', 'shaz', 'music' ]); 28 | 29 | this.input.keyboard.on('keydown_M', function () { 30 | this.facebook.data.values.mana += 10; 31 | }, this); 32 | 33 | this.input.keyboard.on('keydown_G', function () { 34 | this.facebook.data.values.gems++; 35 | }, this); 36 | 37 | this.input.keyboard.on('keydown_C', function () { 38 | 39 | this.facebook.saveData({ 40 | mana: 10, 41 | gems: 4, 42 | music: 'rock' 43 | }); 44 | 45 | }, this); 46 | 47 | /* 48 | this.input.keyboard.on('keydown_L', function () { 49 | 50 | console.log('load'); 51 | this.facebook.getData([ 'mana', 'gems' ]).on('getdata', this.updateStats, this); 52 | 53 | }, this); 54 | 55 | this.input.keyboard.on('keydown_P', function () { 56 | this.facebook.openShare('Play this now!', 'zero2'); 57 | }, this); 58 | */ 59 | } 60 | 61 | dataLoaded (data) 62 | { 63 | console.log('data loaded', data); 64 | 65 | this.dataUpdated(data); 66 | } 67 | 68 | dataSaved (data) 69 | { 70 | console.log('data saved', data); 71 | 72 | this.dataUpdated(data); 73 | } 74 | 75 | dataUpdated (data) 76 | { 77 | if (data.mana) 78 | { 79 | this.mana.setText('Mana: ' + data.mana); 80 | } 81 | 82 | if (data.gems) 83 | { 84 | this.gems.setText('Gems: ' + data.gems); 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/stats.js: -------------------------------------------------------------------------------- 1 | class GameStats extends Phaser.Scene { 2 | 3 | constructor () 4 | { 5 | super({ key: 'GameStats', active: false }); 6 | } 7 | 8 | create () 9 | { 10 | this.add.image(25, 100, 'stats').setOrigin(0); 11 | this.add.text(65 + 25, 120, this.facebook.playerName); 12 | // this.mana = this.add.text(280 + 25, 120, '0'); 13 | // this.gems = this.add.text(470 + 25, 120, '0'); 14 | 15 | // this.facebook.getData([ 'mana', 'gems' ]); 16 | 17 | // this.facebook.on('getdata', this.statsLoaded, this); 18 | // this.facebook.on('savedata', this.updateStats, this); 19 | 20 | this.facebook.on('savestats', this.statsSaved, this); 21 | 22 | this.facebook.on('getstats', this.statsLoaded, this); 23 | 24 | this.facebook.on('getstatsfail', function (error) { 25 | console.log('stats failed', error); 26 | }); 27 | 28 | this.facebook.on('savestatsfail', function (error) { 29 | console.log('save stats failed', error); 30 | }); 31 | 32 | this.input.keyboard.on('keydown_S', function () { 33 | console.log('save1'); 34 | this.facebook.saveStats({ level: 1, gold: 100, lives: 4 }); 35 | }, this); 36 | 37 | this.input.keyboard.on('keydown_L', function () { 38 | 39 | console.log('load'); 40 | this.facebook.getStats(); 41 | 42 | }, this); 43 | 44 | this.input.keyboard.on('keydown_I', function () { 45 | this.facebook.incStats({ gold: 3 }).once('incstats', function (data) { 46 | console.log('stats modified', data); 47 | }, this); 48 | }, this); 49 | 50 | this.input.keyboard.on('keydown_K', function () { 51 | console.log('save2'); 52 | this.facebook.saveStats({ level: 2, gold: 200, hp: 100, tommy: 33 }); 53 | }, this); 54 | 55 | /* 56 | this.input.keyboard.on('keydown_L', function () { 57 | 58 | console.log('load'); 59 | this.facebook.getData([ 'mana', 'gems' ]).on('getdata', this.updateStats, this); 60 | 61 | }, this); 62 | 63 | this.input.keyboard.on('keydown_M', function () { 64 | this.facebook.data.values.mana += 10; 65 | }, this); 66 | 67 | this.input.keyboard.on('keydown_G', function () { 68 | this.facebook.data.values.gems++; 69 | }, this); 70 | 71 | this.input.keyboard.on('keydown_S', function () { 72 | this.facebook.saveStats({ level: 2, gold: 1500, bullets: 500, bombs: 4, lives: 9, hp: 100, exp: 80, wisdom: 9, dex: 4 }); 73 | }, this); 74 | 75 | this.input.keyboard.on('keydown_T', function () { 76 | this.facebook.getStats(['gold']).once('getstats', function (data) { 77 | console.log('stats loaded', data); 78 | }, this); 79 | }, this); 80 | 81 | this.input.keyboard.on('keydown_I', function () { 82 | this.facebook.incStats({ gold: 50, hp: -1 }).once('incstats', function (data) { 83 | console.log('stats modified', data); 84 | }, this); 85 | }, this); 86 | 87 | this.input.keyboard.on('keydown_P', function () { 88 | this.facebook.openShare('Play this now!', 'zero2'); 89 | }, this); 90 | */ 91 | } 92 | 93 | statsLoaded (data) 94 | { 95 | console.log('stats loaded', data); 96 | } 97 | 98 | statsSaved (data) 99 | { 100 | console.log('stats saved', data); 101 | } 102 | 103 | statsUpdated (data) 104 | { 105 | console.log('stats updated', data); 106 | 107 | // if (data.mana) 108 | // { 109 | // this.mana.setText(data.mana); 110 | // } 111 | 112 | // if (data.gems) 113 | // { 114 | // this.gems.setText(data.gems); 115 | // } 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/mainmenu.js: -------------------------------------------------------------------------------- 1 | class MainMenu extends Phaser.Scene { 2 | 3 | constructor () 4 | { 5 | super({ key: 'MainMenu', active: false }); 6 | 7 | this.leaderboard; 8 | this.mana; 9 | this.gems; 10 | } 11 | 12 | create () 13 | { 14 | this.add.image((this.sys.game.config.width / 2) - 80, this.sys.game.config.height / 2, 'zero2'); 15 | 16 | this.add.text(0, 0).setText([ 17 | 'Player ID: ' + this.facebook.playerID, 18 | 'Player Name: ' + this.facebook.playerName 19 | ]); 20 | 21 | // 1012910592203052_1042671442560300 22 | 23 | this.facebook.once('getleaderboard', function (leaderboard) 24 | { 25 | this.leaderboard = leaderboard; 26 | 27 | console.log(leaderboard); 28 | 29 | leaderboard.getPlayerScore(); 30 | 31 | this.input.keyboard.on('keydown_S', function () { 32 | console.log('save score'); 33 | leaderboard.setScore(16, '{race: "elf", level: 3}'); 34 | }, this); 35 | 36 | }, this); 37 | 38 | this.facebook.getLeaderboard('test1'); 39 | 40 | this.facebook.preloadAds('1012910592203052_1042671442560300'); 41 | 42 | this.facebook.loadPlayerPhoto(this, 'dude').once('photocomplete', this.addPlayerPhoto, this); 43 | 44 | /* 45 | this.add.image(50, 100, 'stats').setOrigin(0); 46 | this.add.text(65 + 50, 120, this.facebook.playerName); 47 | this.mana = this.add.text(280 + 50, 120, '0'); 48 | this.gems = this.add.text(470 + 50, 120, '0'); 49 | 50 | this.facebook.getData([ 'mana', 'gems' ]); 51 | this.facebook.on('getdata', this.updateStats, this); 52 | this.facebook.on('savedata', this.updateStats, this); 53 | 54 | this.input.keyboard.on('keydown_L', function () { 55 | 56 | console.log('load'); 57 | this.facebook.getData([ 'mana', 'gems' ]).on('getdata', this.updateStats, this); 58 | 59 | }, this); 60 | 61 | this.input.keyboard.on('keydown_M', function () { 62 | this.facebook.data.values.mana += 10; 63 | }, this); 64 | 65 | this.input.keyboard.on('keydown_G', function () { 66 | this.facebook.data.values.gems++; 67 | }, this); 68 | 69 | this.input.keyboard.on('keydown_S', function () { 70 | this.facebook.saveStats({ level: 2, gold: 1500, bullets: 500, bombs: 4, lives: 9, hp: 100, exp: 80, wisdom: 9, dex: 4 }); 71 | }, this); 72 | 73 | this.input.keyboard.on('keydown_T', function () { 74 | this.facebook.getStats(['gold']).once('getstats', function (data) { 75 | console.log('stats loaded', data); 76 | }, this); 77 | }, this); 78 | 79 | this.input.keyboard.on('keydown_I', function () { 80 | this.facebook.incStats({ gold: 50, hp: -1 }).once('incstats', function (data) { 81 | console.log('stats modified', data); 82 | }, this); 83 | }, this); 84 | 85 | this.input.keyboard.on('keydown_P', function () { 86 | this.facebook.openShare('Play this now!', 'zero2'); 87 | }, this); 88 | */ 89 | } 90 | 91 | updateStats (data) 92 | { 93 | console.log('stats', data); 94 | 95 | if (data.mana) 96 | { 97 | this.mana.setText(data.mana); 98 | } 99 | 100 | if (data.gems) 101 | { 102 | this.gems.setText(data.gems); 103 | } 104 | } 105 | 106 | addPlayerPhoto (key) 107 | { 108 | console.log('addplayerphoto'); 109 | 110 | var photo = this.add.image(this.sys.game.config.width / 2, 700, key); 111 | 112 | photo.setInteractive(); 113 | 114 | photo.on('pointerup', function () { 115 | 116 | // this.facebook.data.set('mana', 2000); 117 | // this.facebook.data.set('gems', 43); 118 | 119 | console.log('savedata'); 120 | 121 | this.facebook.saveData({ 122 | 'mana': 2000, 123 | 'gems': 45 124 | }); 125 | 126 | }, this); 127 | } 128 | 129 | } 130 | 131 | console.log('menu'); 132 | -------------------------------------------------------------------------------- /assets/short-stack.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/game.js: -------------------------------------------------------------------------------- 1 | FBInstant.initializeAsync().then(function() { 2 | 3 | FBInstant.setLoadingProgress(100); 4 | 5 | FBInstant.startGameAsync().then(function() { 6 | 7 | var config = { 8 | type: Phaser.AUTO, 9 | width: window.innerWidth, 10 | height: window.innerHeight, 11 | backgroundColor: '#bfcc00', 12 | scene: { 13 | preload: preload, 14 | create: create, 15 | update: update 16 | } 17 | }; 18 | 19 | var snake; 20 | var food; 21 | var gridWidth = Phaser.Math.Snap.Floor(window.innerWidth, 16) / 16; 22 | var gridHeight = Phaser.Math.Snap.Floor(window.innerHeight, 16) / 16; 23 | 24 | // Direction consts 25 | var UP = 0; 26 | var DOWN = 1; 27 | var LEFT = 2; 28 | var RIGHT = 3; 29 | 30 | new Phaser.Game(config); 31 | 32 | function preload () 33 | { 34 | this.load.image('food', 'assets/food.png'); 35 | this.load.image('body', 'assets/body.png'); 36 | } 37 | 38 | function create () 39 | { 40 | var Food = new Phaser.Class({ 41 | 42 | Extends: Phaser.GameObjects.Image, 43 | 44 | initialize: 45 | 46 | function Food (scene, x, y) 47 | { 48 | Phaser.GameObjects.Image.call(this, scene) 49 | 50 | this.setTexture('food'); 51 | this.setPosition(x * 16, y * 16); 52 | this.setOrigin(0); 53 | 54 | this.total = 0; 55 | 56 | scene.children.add(this); 57 | }, 58 | 59 | eat: function () 60 | { 61 | this.total++; 62 | } 63 | 64 | }); 65 | 66 | var Snake = new Phaser.Class({ 67 | 68 | initialize: 69 | 70 | function Snake (scene, x, y) 71 | { 72 | this.headPosition = new Phaser.Geom.Point(x, y); 73 | 74 | this.body = scene.add.group(); 75 | 76 | this.head = this.body.create(x * 16, y * 16, 'body'); 77 | this.head.setOrigin(0); 78 | 79 | this.alive = true; 80 | 81 | this.speed = 100; 82 | 83 | this.moveTime = 0; 84 | 85 | this.tail = new Phaser.Geom.Point(x, y); 86 | 87 | this.heading = RIGHT; 88 | this.direction = RIGHT; 89 | }, 90 | 91 | update: function (time) 92 | { 93 | if (time >= this.moveTime) 94 | { 95 | return this.move(time); 96 | } 97 | }, 98 | 99 | faceLeft: function () 100 | { 101 | if (this.direction === UP || this.direction === DOWN) 102 | { 103 | this.heading = LEFT; 104 | } 105 | }, 106 | 107 | faceRight: function () 108 | { 109 | if (this.direction === UP || this.direction === DOWN) 110 | { 111 | this.heading = RIGHT; 112 | } 113 | }, 114 | 115 | faceUp: function () 116 | { 117 | if (this.direction === LEFT || this.direction === RIGHT) 118 | { 119 | this.heading = UP; 120 | } 121 | }, 122 | 123 | faceDown: function () 124 | { 125 | if (this.direction === LEFT || this.direction === RIGHT) 126 | { 127 | this.heading = DOWN; 128 | } 129 | }, 130 | 131 | move: function (time) 132 | { 133 | /** 134 | * Based on the heading property (which is the direction the pgroup pressed) 135 | * we update the headPosition value accordingly. 136 | * 137 | * The Math.wrap call allow the snake to wrap around the screen, so when 138 | * it goes off any of the sides it re-appears on the other. 139 | */ 140 | switch (this.heading) 141 | { 142 | case LEFT: 143 | this.headPosition.x = Phaser.Math.Wrap(this.headPosition.x - 1, 0, gridWidth); 144 | break; 145 | 146 | case RIGHT: 147 | this.headPosition.x = Phaser.Math.Wrap(this.headPosition.x + 1, 0, gridWidth); 148 | break; 149 | 150 | case UP: 151 | this.headPosition.y = Phaser.Math.Wrap(this.headPosition.y - 1, 0, gridHeight); 152 | break; 153 | 154 | case DOWN: 155 | this.headPosition.y = Phaser.Math.Wrap(this.headPosition.y + 1, 0, gridHeight); 156 | break; 157 | } 158 | 159 | this.direction = this.heading; 160 | 161 | // Update the body segments and place the last coordinate into this.tail 162 | Phaser.Actions.ShiftPosition(this.body.getChildren(), this.headPosition.x * 16, this.headPosition.y * 16, 1, this.tail); 163 | 164 | // Check to see if any of the body pieces have the same x/y as the head 165 | // If they do, the head ran into the body 166 | 167 | var hitBody = Phaser.Actions.GetFirst(this.body.getChildren(), { x: this.head.x, y: this.head.y }, 1); 168 | 169 | if (hitBody) 170 | { 171 | console.log('dead'); 172 | 173 | this.alive = false; 174 | 175 | return false; 176 | } 177 | else 178 | { 179 | // Update the timer ready for the next movement 180 | this.moveTime = time + this.speed; 181 | 182 | return true; 183 | } 184 | }, 185 | 186 | grow: function () 187 | { 188 | var newPart = this.body.create(this.tail.x, this.tail.y, 'body'); 189 | 190 | newPart.setOrigin(0); 191 | }, 192 | 193 | collideWithFood: function (food) 194 | { 195 | if (this.head.x === food.x && this.head.y === food.y) 196 | { 197 | this.grow(); 198 | 199 | food.eat(); 200 | 201 | // For every 5 items of food eaten we'll increase the snake speed a little 202 | if (this.speed > 20 && food.total % 5 === 0) 203 | { 204 | this.speed -= 5; 205 | } 206 | 207 | return true; 208 | } 209 | else 210 | { 211 | return false; 212 | } 213 | }, 214 | 215 | updateGrid: function (grid) 216 | { 217 | // Remove all body pieces from valid positions list 218 | this.body.children.each(function (segment) { 219 | 220 | var bx = segment.x / 16; 221 | var by = segment.y / 16; 222 | 223 | grid[by][bx] = false; 224 | 225 | }); 226 | 227 | return grid; 228 | } 229 | 230 | }); 231 | 232 | food = new Food(this, 3, 4); 233 | 234 | snake = new Snake(this, 8, 8); 235 | 236 | /** 237 | * Check which direction is pressed, and then change the direction the snake 238 | * is heading based on that. The checks ensure you don't double-back 239 | * on yourself, for example if you're moving to the right and you press 240 | * the LEFT cursor, it ignores it, because the only valid directions you 241 | * can move in at that time is up and down. 242 | */ 243 | this.input.on('pointerdown', function (pointer) { 244 | 245 | if (!snake.alive) 246 | { 247 | return; 248 | } 249 | 250 | if (pointer.x < this.sys.game.config.width / 2) 251 | { 252 | // Left of the screen 253 | if (snake.heading === LEFT) 254 | { 255 | snake.faceDown(); 256 | } 257 | else if (snake.heading === RIGHT) 258 | { 259 | snake.faceUp(); 260 | } 261 | else if (snake.heading === UP) 262 | { 263 | snake.faceLeft(); 264 | } 265 | else if (snake.heading === DOWN) 266 | { 267 | snake.faceRight(); 268 | } 269 | } 270 | else 271 | { 272 | // Right of the screen 273 | if (snake.heading === LEFT) 274 | { 275 | snake.faceUp(); 276 | } 277 | else if (snake.heading === RIGHT) 278 | { 279 | snake.faceDown(); 280 | } 281 | else if (snake.heading === UP) 282 | { 283 | snake.faceRight(); 284 | } 285 | else if (snake.heading === DOWN) 286 | { 287 | snake.faceLeft(); 288 | } 289 | } 290 | 291 | }, this); 292 | } 293 | 294 | function update (time, delta) 295 | { 296 | if (!snake.alive) 297 | { 298 | return; 299 | } 300 | 301 | if (snake.update(time)) 302 | { 303 | // If the snake updated, we need to check for collision against food 304 | 305 | if (snake.collideWithFood(food)) 306 | { 307 | repositionFood(); 308 | } 309 | } 310 | } 311 | 312 | /** 313 | * We can place the food anywhere in our gridWidthxgridHeight grid 314 | * *except* on-top of the snake, so we need 315 | * to filter those out of the possible food locations. 316 | * If there aren't any locations left, they've won! 317 | * 318 | * @method repositionFood 319 | * @return {boolean} true if the food was placed, otherwise false 320 | */ 321 | function repositionFood () 322 | { 323 | // First create an array that assumes all positions 324 | // are valid for the new piece of food 325 | 326 | // A Grid we'll use to reposition the food each time it's eaten 327 | var testGrid = []; 328 | 329 | for (var y = 0; y < gridHeight; y++) 330 | { 331 | testGrid[y] = []; 332 | 333 | for (var x = 0; x < gridWidth; x++) 334 | { 335 | testGrid[y][x] = true; 336 | } 337 | } 338 | 339 | snake.updateGrid(testGrid); 340 | 341 | // Purge out false positions 342 | var validLocations = []; 343 | 344 | for (var y = 0; y < gridHeight; y++) 345 | { 346 | for (var x = 0; x < gridWidth; x++) 347 | { 348 | if (testGrid[y][x] === true) 349 | { 350 | // Is this position valid for food? If so, add it here ... 351 | validLocations.push({ x: x, y: y }); 352 | } 353 | } 354 | } 355 | 356 | if (validLocations.length > 0) 357 | { 358 | // Use the RNG to pick a random food position 359 | var pos = Phaser.Math.RND.pick(validLocations); 360 | 361 | // And place it 362 | food.setPosition(pos.x * 16, pos.y * 16); 363 | 364 | return true; 365 | } 366 | else 367 | { 368 | return false; 369 | } 370 | } 371 | 372 | }); 373 | 374 | }); 375 | -------------------------------------------------------------------------------- /src/snake.js: -------------------------------------------------------------------------------- 1 | // https://www.facebook.com/embed/instantgames/1012910592203052/player?game_url=https://localhost:8080 2 | 3 | FBInstant.initializeAsync().then(function() { 4 | 5 | FBInstant.setLoadingProgress(100); 6 | 7 | FBInstant.startGameAsync().then(function() { 8 | 9 | var config = { 10 | type: Phaser.AUTO, 11 | width: window.innerWidth, 12 | height: window.innerHeight, 13 | backgroundColor: '#bfcc00', 14 | scene: { 15 | preload: preload, 16 | create: create, 17 | update: update 18 | } 19 | }; 20 | 21 | var snake; 22 | var food; 23 | var gridWidth = Phaser.Math.Snap.Floor(window.innerWidth, 16) / 16; 24 | var gridHeight = Phaser.Math.Snap.Floor(window.innerHeight, 16) / 16; 25 | 26 | // Direction consts 27 | var UP = 0; 28 | var DOWN = 1; 29 | var LEFT = 2; 30 | var RIGHT = 3; 31 | 32 | new Phaser.Game(config); 33 | 34 | function preload () 35 | { 36 | this.load.image('food', 'assets/food.png'); 37 | this.load.image('body', 'assets/body.png'); 38 | } 39 | 40 | function create () 41 | { 42 | var Food = new Phaser.Class({ 43 | 44 | Extends: Phaser.GameObjects.Image, 45 | 46 | initialize: 47 | 48 | function Food (scene, x, y) 49 | { 50 | Phaser.GameObjects.Image.call(this, scene) 51 | 52 | this.setTexture('food'); 53 | this.setPosition(x * 16, y * 16); 54 | this.setOrigin(0); 55 | 56 | this.total = 0; 57 | 58 | scene.children.add(this); 59 | }, 60 | 61 | eat: function () 62 | { 63 | this.total++; 64 | } 65 | 66 | }); 67 | 68 | var Snake = new Phaser.Class({ 69 | 70 | initialize: 71 | 72 | function Snake (scene, x, y) 73 | { 74 | this.headPosition = new Phaser.Geom.Point(x, y); 75 | 76 | this.body = scene.add.group(); 77 | 78 | this.head = this.body.create(x * 16, y * 16, 'body'); 79 | this.head.setOrigin(0); 80 | 81 | this.alive = true; 82 | 83 | this.speed = 100; 84 | 85 | this.moveTime = 0; 86 | 87 | this.tail = new Phaser.Geom.Point(x, y); 88 | 89 | this.heading = RIGHT; 90 | this.direction = RIGHT; 91 | }, 92 | 93 | update: function (time) 94 | { 95 | if (time >= this.moveTime) 96 | { 97 | return this.move(time); 98 | } 99 | }, 100 | 101 | faceLeft: function () 102 | { 103 | if (this.direction === UP || this.direction === DOWN) 104 | { 105 | this.heading = LEFT; 106 | } 107 | }, 108 | 109 | faceRight: function () 110 | { 111 | if (this.direction === UP || this.direction === DOWN) 112 | { 113 | this.heading = RIGHT; 114 | } 115 | }, 116 | 117 | faceUp: function () 118 | { 119 | if (this.direction === LEFT || this.direction === RIGHT) 120 | { 121 | this.heading = UP; 122 | } 123 | }, 124 | 125 | faceDown: function () 126 | { 127 | if (this.direction === LEFT || this.direction === RIGHT) 128 | { 129 | this.heading = DOWN; 130 | } 131 | }, 132 | 133 | move: function (time) 134 | { 135 | /** 136 | * Based on the heading property (which is the direction the pgroup pressed) 137 | * we update the headPosition value accordingly. 138 | * 139 | * The Math.wrap call allow the snake to wrap around the screen, so when 140 | * it goes off any of the sides it re-appears on the other. 141 | */ 142 | switch (this.heading) 143 | { 144 | case LEFT: 145 | this.headPosition.x = Phaser.Math.Wrap(this.headPosition.x - 1, 0, gridWidth); 146 | break; 147 | 148 | case RIGHT: 149 | this.headPosition.x = Phaser.Math.Wrap(this.headPosition.x + 1, 0, gridWidth); 150 | break; 151 | 152 | case UP: 153 | this.headPosition.y = Phaser.Math.Wrap(this.headPosition.y - 1, 0, gridHeight); 154 | break; 155 | 156 | case DOWN: 157 | this.headPosition.y = Phaser.Math.Wrap(this.headPosition.y + 1, 0, gridHeight); 158 | break; 159 | } 160 | 161 | this.direction = this.heading; 162 | 163 | // Update the body segments and place the last coordinate into this.tail 164 | Phaser.Actions.ShiftPosition(this.body.getChildren(), this.headPosition.x * 16, this.headPosition.y * 16, 1, this.tail); 165 | 166 | // Check to see if any of the body pieces have the same x/y as the head 167 | // If they do, the head ran into the body 168 | 169 | var hitBody = Phaser.Actions.GetFirst(this.body.getChildren(), { x: this.head.x, y: this.head.y }, 1); 170 | 171 | if (hitBody) 172 | { 173 | console.log('dead'); 174 | 175 | this.alive = false; 176 | 177 | return false; 178 | } 179 | else 180 | { 181 | // Update the timer ready for the next movement 182 | this.moveTime = time + this.speed; 183 | 184 | return true; 185 | } 186 | }, 187 | 188 | grow: function () 189 | { 190 | var newPart = this.body.create(this.tail.x, this.tail.y, 'body'); 191 | 192 | newPart.setOrigin(0); 193 | }, 194 | 195 | collideWithFood: function (food) 196 | { 197 | if (this.head.x === food.x && this.head.y === food.y) 198 | { 199 | this.grow(); 200 | 201 | food.eat(); 202 | 203 | // For every 5 items of food eaten we'll increase the snake speed a little 204 | if (this.speed > 20 && food.total % 5 === 0) 205 | { 206 | this.speed -= 5; 207 | } 208 | 209 | return true; 210 | } 211 | else 212 | { 213 | return false; 214 | } 215 | }, 216 | 217 | updateGrid: function (grid) 218 | { 219 | // Remove all body pieces from valid positions list 220 | this.body.children.each(function (segment) { 221 | 222 | var bx = segment.x / 16; 223 | var by = segment.y / 16; 224 | 225 | grid[by][bx] = false; 226 | 227 | }); 228 | 229 | return grid; 230 | } 231 | 232 | }); 233 | 234 | food = new Food(this, 3, 4); 235 | 236 | snake = new Snake(this, 8, 8); 237 | 238 | /** 239 | * Check which direction is pressed, and then change the direction the snake 240 | * is heading based on that. The checks ensure you don't double-back 241 | * on yourself, for example if you're moving to the right and you press 242 | * the LEFT cursor, it ignores it, because the only valid directions you 243 | * can move in at that time is up and down. 244 | */ 245 | this.input.on('pointerdown', function (pointer) { 246 | 247 | if (!snake.alive) 248 | { 249 | return; 250 | } 251 | 252 | if (pointer.x < this.sys.game.config.width / 2) 253 | { 254 | // Left of the screen 255 | if (snake.heading === LEFT) 256 | { 257 | snake.faceDown(); 258 | } 259 | else if (snake.heading === RIGHT) 260 | { 261 | snake.faceUp(); 262 | } 263 | else if (snake.heading === UP) 264 | { 265 | snake.faceLeft(); 266 | } 267 | else if (snake.heading === DOWN) 268 | { 269 | snake.faceRight(); 270 | } 271 | } 272 | else 273 | { 274 | // Right of the screen 275 | if (snake.heading === LEFT) 276 | { 277 | snake.faceUp(); 278 | } 279 | else if (snake.heading === RIGHT) 280 | { 281 | snake.faceDown(); 282 | } 283 | else if (snake.heading === UP) 284 | { 285 | snake.faceRight(); 286 | } 287 | else if (snake.heading === DOWN) 288 | { 289 | snake.faceLeft(); 290 | } 291 | } 292 | 293 | }, this); 294 | } 295 | 296 | function update (time, delta) 297 | { 298 | if (!snake.alive) 299 | { 300 | return; 301 | } 302 | 303 | if (snake.update(time)) 304 | { 305 | // If the snake updated, we need to check for collision against food 306 | 307 | if (snake.collideWithFood(food)) 308 | { 309 | repositionFood(); 310 | } 311 | } 312 | } 313 | 314 | /** 315 | * We can place the food anywhere in our gridWidthxgridHeight grid 316 | * *except* on-top of the snake, so we need 317 | * to filter those out of the possible food locations. 318 | * If there aren't any locations left, they've won! 319 | * 320 | * @method repositionFood 321 | * @return {boolean} true if the food was placed, otherwise false 322 | */ 323 | function repositionFood () 324 | { 325 | // First create an array that assumes all positions 326 | // are valid for the new piece of food 327 | 328 | // A Grid we'll use to reposition the food each time it's eaten 329 | var testGrid = []; 330 | 331 | for (var y = 0; y < gridHeight; y++) 332 | { 333 | testGrid[y] = []; 334 | 335 | for (var x = 0; x < gridWidth; x++) 336 | { 337 | testGrid[y][x] = true; 338 | } 339 | } 340 | 341 | snake.updateGrid(testGrid); 342 | 343 | // Purge out false positions 344 | var validLocations = []; 345 | 346 | for (var y = 0; y < gridHeight; y++) 347 | { 348 | for (var x = 0; x < gridWidth; x++) 349 | { 350 | if (testGrid[y][x] === true) 351 | { 352 | // Is this position valid for food? If so, add it here ... 353 | validLocations.push({ x: x, y: y }); 354 | } 355 | } 356 | } 357 | 358 | if (validLocations.length > 0) 359 | { 360 | // Use the RNG to pick a random food position 361 | var pos = Phaser.Math.RND.pick(validLocations); 362 | 363 | // And place it 364 | food.setPosition(pos.x * 16, pos.y * 16); 365 | 366 | return true; 367 | } 368 | else 369 | { 370 | return false; 371 | } 372 | } 373 | 374 | }); 375 | 376 | }); 377 | -------------------------------------------------------------------------------- /assets/azo-fire.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | --------------------------------------------------------------------------------