├── .gitattributes ├── .gitignore ├── docs ├── img │ ├── flap.gif │ ├── helmet.gif │ ├── generated.gif │ ├── racket_fire.gif │ └── rainy_day.gif ├── games.txt ├── codes │ ├── flap.gc │ ├── helmet.gc │ ├── homing.gc │ ├── fire.gc │ ├── wall_kick.gc │ ├── defend.gc │ ├── bomb.gc │ ├── pooy.gc │ ├── from_four.gc │ ├── road.gc │ ├── rush.gc │ ├── jump_block.gc │ └── reflect_blocks.gc └── index.html ├── tsconfig.json ├── webpack.config.js ├── package.json ├── LICENSE.txt ├── src ├── random.ts ├── game.ts ├── screen.ts ├── index.ts └── actor.ts └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | web_modules/* linguist-vendored 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode/ 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /docs/img/flap.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abagames/game-combinator/HEAD/docs/img/flap.gif -------------------------------------------------------------------------------- /docs/img/helmet.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abagames/game-combinator/HEAD/docs/img/helmet.gif -------------------------------------------------------------------------------- /docs/img/generated.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abagames/game-combinator/HEAD/docs/img/generated.gif -------------------------------------------------------------------------------- /docs/img/racket_fire.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abagames/game-combinator/HEAD/docs/img/racket_fire.gif -------------------------------------------------------------------------------- /docs/img/rainy_day.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abagames/game-combinator/HEAD/docs/img/rainy_day.gif -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs" 5 | }, 6 | "exclude": [ 7 | "node_modules", 8 | "web_modules" 9 | ] 10 | } -------------------------------------------------------------------------------- /docs/games.txt: -------------------------------------------------------------------------------- 1 | fire.gc 2 | helmet.gc 3 | flap.gc 4 | defend.gc 5 | pooy.gc 6 | bomb.gc 7 | from_four.gc 8 | road.gc 9 | wall_kick.gc 10 | jump_block.gc 11 | homing.gc 12 | reflect_blocks.gc 13 | rush.gc 14 | -------------------------------------------------------------------------------- /docs/codes/flap.gc: -------------------------------------------------------------------------------- 1 | (game flap 2 | (actor stage 3 | (if initial (spawn player)) 4 | (if (random frequently) (spawn enemy)) 5 | ) 6 | (actor player 7 | (if initial (place left_center)) 8 | (if (key up) (accelerate up fast)) 9 | (accelerate down normal) 10 | (if (touch out_of_screen) miss) 11 | (if (touch enemy) miss) 12 | ) 13 | (actor enemy 14 | (if initial (place right)) 15 | (move left) 16 | (if (touch out_of_screen) ( 17 | score 18 | remove 19 | )) 20 | ) 21 | ) -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: require('glob').sync('./src/**/*.ts'), 3 | output: { 4 | path: __dirname + '/docs', 5 | filename: 'bundle.js', 6 | }, 7 | resolve: { 8 | extensions: ['.ts', '.js'], 9 | modules: ['node_modules', 'web_modules'] 10 | }, 11 | devtool: 'source-map', 12 | devServer: { 13 | contentBase: 'docs' 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.ts$/, 19 | exclude: /(node_modules|web_modules)/, 20 | loader: 'awesome-typescript-loader' 21 | } 22 | ] 23 | } 24 | }; -------------------------------------------------------------------------------- /docs/codes/helmet.gc: -------------------------------------------------------------------------------- 1 | (game helmet 2 | (actor stage 3 | (if initial (spawn player)) 4 | (if (random frequently) (spawn enemy)) 5 | ) 6 | (actor player 7 | (if initial (place bottom_left)) 8 | (if (key left) (move left)) 9 | (if (key right) (move right)) 10 | (if (touch out_of_screen_right) ( 11 | score 12 | (place bottom_left) 13 | )) 14 | (if (touch out_of_screen) (move step_back)) 15 | ) 16 | (actor enemy 17 | (if initial (place top)) 18 | (accelerate down normal) 19 | (if (touch player) miss) 20 | (if (touch out_of_screen) remove) 21 | ) 22 | ) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "game-combinator", 3 | "version": "1.0.0", 4 | "description": "Combine games and generate a new one", 5 | "author": "ABA", 6 | "license": "MIT", 7 | "scripts": { 8 | "wds": "webpack-dev-server --inline", 9 | "build": "webpack" 10 | }, 11 | "devDependencies": { 12 | "@types/lodash": "^4.14.63", 13 | "awesome-typescript-loader": "^3.1.3", 14 | "glob": "^7.1.1", 15 | "typescript": "^2.3.2", 16 | "webpack": "^2.4.1", 17 | "webpack-dev-server": "^2.4.5" 18 | }, 19 | "dependencies": { 20 | "lodash": "^4.17.4", 21 | "lz-string": "^1.4.4", 22 | "s-expression": "^3.0.3" 23 | } 24 | } -------------------------------------------------------------------------------- /docs/codes/homing.gc: -------------------------------------------------------------------------------- 1 | (game homing 2 | (actor stage 3 | (if initial (spawn player)) 4 | (if (random often) (spawn enemy)) 5 | ) 6 | (actor player 7 | (if initial (place center)) 8 | (if (key left) (move left)) 9 | (if (key right) (move right)) 10 | (if (key up) (move up)) 11 | (if (key down) (move down)) 12 | (if (touch out_of_screen) (move step_back)) 13 | (if (touch enemy) miss) 14 | ) 15 | (actor enemy 16 | (if initial (place edge)) 17 | (accelerate to_player) 18 | (if (touch out_of_screen) remove) 19 | (if (touch enemy) ( 20 | score 21 | remove 22 | remove_touched 23 | )) 24 | ) 25 | ) 26 | -------------------------------------------------------------------------------- /docs/codes/fire.gc: -------------------------------------------------------------------------------- 1 | (game fire 2 | (actor stage 3 | (if initial (spawn player)) 4 | (if (random rarely) (spawn enemy)) 5 | ) 6 | (actor player 7 | (if initial (place bottom_left)) 8 | (if (key left) (move left)) 9 | (if (key right) (move right)) 10 | (if (touch out_of_screen) (move step_back)) 11 | ) 12 | (actor enemy 13 | (if initial ( 14 | (place top_left) 15 | (accelerate right fast) 16 | )) 17 | (accelerate down normal) 18 | (if (touch player) ( 19 | score 20 | (accelerate bounce_vertical) 21 | (move up) 22 | )) 23 | (if (touch out_of_screen_bottom) miss) 24 | (if (touch out_of_screen_right) remove) 25 | ) 26 | ) 27 | -------------------------------------------------------------------------------- /docs/codes/wall_kick.gc: -------------------------------------------------------------------------------- 1 | (game wall_kick 2 | (actor stage 3 | (if initial (spawn player)) 4 | (if (random frequently) (spawn enemy)) 5 | ) 6 | (actor player 7 | (if initial ( 8 | (place bottom_center) 9 | (accelerate right very_fast) 10 | (accelerate right very_fast) 11 | )) 12 | (if (key left) (move left very_slow)) 13 | (if (key right) (move right very_slow)) 14 | (if (touch enemy) miss) 15 | (if (touch out_of_screen_right) ( 16 | score 17 | (move step_back) 18 | (accelerate bounce_horizontal) 19 | )) 20 | (if (touch out_of_screen_left) ( 21 | score 22 | (move step_back) 23 | (accelerate bounce_horizontal) 24 | )) 25 | ) 26 | (actor enemy 27 | (if initial (place top)) 28 | (move down) 29 | (if (touch out_of_screen) remove) 30 | ) 31 | ) 32 | -------------------------------------------------------------------------------- /docs/codes/defend.gc: -------------------------------------------------------------------------------- 1 | (game defend 2 | (actor stage 3 | (if initial (spawn player)) 4 | (if (random often) (spawn enemy)) 5 | ) 6 | (actor player 7 | (if initial (place left_center)) 8 | (if (key up) (move up)) 9 | (if (key down) (move down)) 10 | (if (interval frequently) (spawn shot)) 11 | (if (touch out_of_screen_top) (place bottom_left)) 12 | (if (touch out_of_screen_bottom) (place top_left)) 13 | ) 14 | (actor enemy 15 | (if initial (place right)) 16 | (accelerate left slow) 17 | (if (touch out_of_screen) miss) 18 | (if (touch shot) ( 19 | score 20 | remove 21 | remove_touched 22 | )) 23 | ) 24 | (actor shot 25 | (move right) 26 | (if (touch out_of_screen) remove) 27 | (if (touch enemy) ( 28 | score 29 | remove 30 | remove_touched 31 | )) 32 | ) 33 | ) 34 | -------------------------------------------------------------------------------- /docs/codes/bomb.gc: -------------------------------------------------------------------------------- 1 | (game bomb 2 | (actor stage 3 | (if initial (spawn player)) 4 | (if (random often) (spawn enemy)) 5 | ) 6 | (actor player 7 | (if initial (place top_left)) 8 | (if (key down) ( 9 | (if (not_exists shot) (spawn shot)) 10 | )) 11 | (move right) 12 | (if (touch out_of_screen_right) (place top_left)) 13 | (if (touch enemy) miss) 14 | (if (touch bullet) miss) 15 | ) 16 | (actor shot 17 | (move down) 18 | (if (touch enemy) ( 19 | score 20 | remove 21 | remove_touched 22 | )) 23 | (if (touch out_of_screen) remove) 24 | ) 25 | (actor enemy 26 | (if initial (place bottom)) 27 | (if (random often) ( 28 | (spawn bullet) 29 | (move up) 30 | )) 31 | (if (touch shot) ( 32 | score 33 | remove 34 | remove_touched 35 | )) 36 | (if (touch out_of_screen) remove) 37 | ) 38 | (actor bullet) 39 | ) 40 | -------------------------------------------------------------------------------- /docs/codes/pooy.gc: -------------------------------------------------------------------------------- 1 | (game pooy 2 | (actor stage 3 | (if initial (spawn player)) 4 | (if (random rarely) (spawn enemy)) 5 | ) 6 | (actor player 7 | (if initial (place right_center)) 8 | (if (key up) (move up)) 9 | (if (key down) (move down)) 10 | (if (interval often) (spawn shot)) 11 | (if (touch out_of_screen) (move step_back)) 12 | (if (touch bullet) miss) 13 | ) 14 | (actor enemy 15 | (if initial ( 16 | (place top) 17 | (move left) 18 | (move left) 19 | )) 20 | (accelerate down slow) 21 | (if (random rarely) (spawn bullet)) 22 | (if (touch out_of_screen) remove) 23 | (if (touch shot) ( 24 | score 25 | remove 26 | )) 27 | ) 28 | (actor shot 29 | (move left) 30 | (accelerate down fast) 31 | (if (touch out_of_screen) remove) 32 | (if (touch enemy) ( 33 | score 34 | remove_touched 35 | )) 36 | ) 37 | (actor bullet 38 | (move right) 39 | (if (touch out_of_screen) remove) 40 | ) 41 | ) -------------------------------------------------------------------------------- /docs/codes/from_four.gc: -------------------------------------------------------------------------------- 1 | (game from_four 2 | (actor stage 3 | (if initial (spawn player)) 4 | (if (random frequently) (spawn bullet)) 5 | ) 6 | (actor player 7 | (if initial (place center)) 8 | (if (key left) (move left)) 9 | (if (key right) (move right)) 10 | (if (key up) (move up)) 11 | (if (key down) (move down)) 12 | (if (touch out_of_screen) (move step_back)) 13 | (if (touch bullet) miss) 14 | ) 15 | (actor bullet 16 | (if initial 17 | (select 18 | ( 19 | (place top) 20 | (accelerate down very_fast) 21 | ) 22 | ( 23 | (place bottom) 24 | (accelerate up very_fast) 25 | ) 26 | ( 27 | (place left) 28 | (accelerate right very_fast) 29 | ) 30 | ( 31 | (place right) 32 | (accelerate left very_fast) 33 | ) 34 | ) 35 | ) 36 | (if (touch player) miss) 37 | (if (touch out_of_screen) ( 38 | score 39 | remove 40 | )) 41 | ) 42 | ) 43 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ABA Games 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 | -------------------------------------------------------------------------------- /src/random.ts: -------------------------------------------------------------------------------- 1 | export default class Random { 2 | x: number; 3 | y: number; 4 | z: number; 5 | w: number; 6 | 7 | get(fromOrTo: number = 1, to: number = null) { 8 | if (to == null) { 9 | to = fromOrTo; 10 | fromOrTo = 0; 11 | } 12 | return this.getToMaxInt() / 0x7fffffff * (to - fromOrTo) + fromOrTo; 13 | } 14 | 15 | getInt(fromOrTo: number = 1, to: number = null) { 16 | return Math.floor(this.get(fromOrTo, to)); 17 | } 18 | 19 | getPm() { 20 | return this.get(2) * 2 - 1; 21 | } 22 | 23 | setSeed(v: number = -0x7fffffff) { 24 | if (v === -0x7fffffff) { 25 | v = Math.floor(Math.random() * 0x7fffffff); 26 | } 27 | this.x = v = 1812433253 * (v ^ (v >> 30)) 28 | this.y = v = 1812433253 * (v ^ (v >> 30)) + 1 29 | this.z = v = 1812433253 * (v ^ (v >> 30)) + 2 30 | this.w = v = 1812433253 * (v ^ (v >> 30)) + 3; 31 | return this; 32 | } 33 | 34 | getToMaxInt() { 35 | var t = this.x ^ (this.x << 11); 36 | this.x = this.y; 37 | this.y = this.z; 38 | this.z = this.w; 39 | this.w = (this.w ^ (this.w >> 19)) ^ (t ^ (t >> 8)); 40 | return this.w; 41 | } 42 | 43 | constructor() { 44 | this.setSeed(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /docs/codes/road.gc: -------------------------------------------------------------------------------- 1 | (game road 2 | (actor stage 3 | (if initial (spawn player)) 4 | (if initial (spawn enemy)) 5 | ) 6 | (actor player 7 | (if initial (place bottom_center)) 8 | (if (key left) (accelerate left very_fast)) 9 | (if (key right) (accelerate right very_fast)) 10 | (accelerate slow_down) 11 | (if (touch out_of_screen) ( 12 | (move step_back) 13 | (accelerate bounce_horizontal) 14 | )) 15 | score 16 | (if (touch shot) miss) 17 | (if (touch bullet) miss) 18 | ) 19 | (actor enemy 20 | (if initial (place top_center)) 21 | (if (random frequently) (accelerate left fast)) 22 | (if (random frequently) (accelerate right fast)) 23 | (if (touch out_of_screen) ( 24 | (move step_back) 25 | (accelerate bounce_horizontal) 26 | )) 27 | (spawn shot) 28 | (spawn bullet) 29 | ) 30 | (actor shot 31 | (if initial (accelerate left very_fast)) 32 | (move down) 33 | (if (random often) (accelerate slow_down)) 34 | (if (touch out_of_screen) remove) 35 | ) 36 | (actor bullet 37 | (if initial (accelerate right very_fast)) 38 | (move down) 39 | (if (random often) (accelerate slow_down)) 40 | (if (touch out_of_screen) remove) 41 | ) 42 | ) 43 | -------------------------------------------------------------------------------- /docs/codes/rush.gc: -------------------------------------------------------------------------------- 1 | (game rush 2 | (actor stage 3 | (if initial (spawn player)) 4 | (if (random often) (spawn enemy)) 5 | ) 6 | (actor player 7 | (if initial (place center)) 8 | (if (key left) (move left)) 9 | (if (key right) (move right)) 10 | (if (key up) (move up)) 11 | (if (key down) (move down)) 12 | (if (touch out_of_screen) (move step_back)) 13 | (if (interval often) (spawn shot)) 14 | ) 15 | (actor shot 16 | (move to_enemy) 17 | (if (touch enemy) ( 18 | score 19 | remove 20 | remove_touched 21 | )) 22 | (if (touch out_of_screen) remove) 23 | ) 24 | (actor enemy 25 | (if initial ( 26 | (place top) 27 | (accelerate down very_fast) 28 | (if (random half) (accelerate down very_fast)) 29 | (if (random half) (accelerate down very_fast)) 30 | )) 31 | (if (touch player) miss) 32 | (if (touch out_of_screen) remove) 33 | (if (interval often) (spawn bullet)) 34 | ) 35 | (actor bullet 36 | (if initial ( 37 | (accelerate to_player very_fast) 38 | (if (random half) (accelerate to_player very_fast)) 39 | (if (random half) (accelerate to_player very_fast)) 40 | )) 41 | (if (touch player) miss) 42 | (if (touch out_of_screen) remove) 43 | ) 44 | ) -------------------------------------------------------------------------------- /docs/codes/jump_block.gc: -------------------------------------------------------------------------------- 1 | (game jump_block 2 | (actor stage 3 | (if initial (spawn player)) 4 | (if (interval frequently) ( 5 | (if (random frequently) (spawn enemy)) 6 | (if (random frequently) (spawn bullet)) 7 | )) 8 | ) 9 | (actor player 10 | (if initial (place bottom_left)) 11 | (if (exists shot) ( 12 | (if (key up) (accelerate up very_fast)) 13 | )) 14 | (if (not_exists shot) ( 15 | (if (key up) (accelerate up normal)) 16 | (if (key down) (accelerate down normal)) 17 | (accelerate down very_fast) 18 | (if (touch out_of_screen_bottom) ( 19 | (accelerate stop) 20 | (place bottom_left) 21 | (spawn shot) 22 | )) 23 | )) 24 | (if (touch enemy) miss) 25 | (if (touch bullet) miss) 26 | ) 27 | (actor shot 28 | (if initial (move up)) 29 | (if (touch player) remove) 30 | ) 31 | (actor enemy 32 | (if initial (place bottom_right)) 33 | (move left) 34 | (if (touch out_of_screen) ( 35 | score 36 | remove 37 | )) 38 | ) 39 | (actor bullet 40 | (if initial ( 41 | (place bottom_right) 42 | (if (random half) (move up)) 43 | (if (random half) (move left)) 44 | )) 45 | (move left) 46 | (if (touch out_of_screen) ( 47 | score 48 | remove 49 | )) 50 | ) 51 | ) 52 | -------------------------------------------------------------------------------- /docs/codes/reflect_blocks.gc: -------------------------------------------------------------------------------- 1 | (game reflect_blocks 2 | (actor stage 3 | (if initial (spawn player)) 4 | (if (random rarely) (spawn enemy)) 5 | (if (interval rarely) (spawn bullet)) 6 | ) 7 | (actor player 8 | (if initial (place center)) 9 | (spawn shot) 10 | (if (key left) (move left)) 11 | (if (key right) (move right)) 12 | (if (key up) (move up)) 13 | (if (key down) (move down)) 14 | (if (touch out_of_screen) (move step_back)) 15 | ) 16 | (actor shot 17 | (if (after soon) remove) 18 | ) 19 | (actor enemy 20 | (if initial 21 | (place top) 22 | (accelerate down very_slow) 23 | ) 24 | (if (touch out_of_screen) miss) 25 | ) 26 | (actor bullet 27 | (if initial ( 28 | (place top) 29 | (accelerate down very_fast) 30 | (select 31 | (accelerate right very_fast) 32 | (accelerate left very_fast) 33 | ) 34 | )) 35 | (if (touch out_of_screen_right) (accelerate bounce_horizontal)) 36 | (if (touch out_of_screen_left) (accelerate bounce_horizontal)) 37 | (if (touch player) ( 38 | (accelerate bounce_vertical) 39 | (move up) 40 | )) 41 | (if (touch shot) ( 42 | (accelerate bounce_vertical) 43 | (move up) 44 | )) 45 | (if (touch enemy) ( 46 | (accelerate bounce_vertical) 47 | remove_touched 48 | score 49 | )) 50 | (if (touch out_of_screen_bottom) remove) 51 | ) 52 | ) -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | game-combinator 7 | 8 | 9 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 | game-combinator 25 |
26 |
27 | GitHub 28 |
29 |
30 |
31 |
32 |
33 | 34 | 35 | 36 |
37 |
38 |
39 |
40 |
41 |
    42 |
43 |
44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /src/game.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import Actor from './actor'; 3 | import Screen from './screen'; 4 | import Random from './random'; 5 | 6 | export default class Game { 7 | maxActorCount = 100; 8 | actors: Actor[]; 9 | originalCode: any = {}; 10 | codes: any = {}; 11 | ticks = -1; 12 | score = 0; 13 | missCount = 0; 14 | isValid = true; 15 | actorAddingCount = 0; 16 | random = new Random(); 17 | isAutoPressing = false; 18 | autoPressingKeys = [37, 38, 39, 40]; 19 | autoPressingRandom = new Random(); 20 | actorNames = ['stage', 'player', 'enemy', 'shot', 'bullet']; 21 | 22 | constructor(public screen: Screen, public isKeyDown: boolean[], 23 | randomSeed: number = null, 24 | autoPressingRandomSeed: number = null) { 25 | if (randomSeed != null) { 26 | this.random.setSeed(randomSeed); 27 | } 28 | if (autoPressingRandomSeed != null) { 29 | this.autoPressingRandom.setSeed(autoPressingRandomSeed); 30 | this.isAutoPressing = true; 31 | this.isKeyDown = _.times(256, () => false); 32 | } 33 | this.update = this.update.bind(this); 34 | } 35 | 36 | begin(gameCode) { 37 | this.originalCode = _.cloneDeep(gameCode); 38 | const code = _.cloneDeep(gameCode); 39 | code.splice(0, 2); // Remove 'game', [name] 40 | _.forEach(code, ac => { 41 | const name = ac[1]; 42 | if (_.some(this.actorNames, an => an === name)) { 43 | ac.splice(0, 2); // Remove 'actor', [name] 44 | this.codes[name] = ac; 45 | } 46 | }); 47 | this.restart(); 48 | } 49 | 50 | restart() { 51 | this.actors = []; 52 | this.addActor('stage'); 53 | } 54 | 55 | end() { 56 | this.screen.remove(); 57 | } 58 | 59 | addActor(name: string) { 60 | this.actorAddingCount++; 61 | if (this.actorAddingCount > 16) { 62 | this.isValid = false; 63 | return null; 64 | } 65 | if (!this.codes.hasOwnProperty(name)) { 66 | //console.log(`Actor '${name}' is not defined`); 67 | return null; 68 | } 69 | const actor = new Actor(name, this.codes[name], this); 70 | this.actors.push(actor); 71 | return actor; 72 | } 73 | 74 | getActors(name: string) { 75 | return _.filter(this.actors, a => a.name === name); 76 | } 77 | 78 | update() { 79 | if (!this.screen.hasDom) { 80 | this.score = this.missCount = 0; 81 | } 82 | this.ticks++; 83 | if (this.screen.hasDom && this.ticks % 10 > 0) { 84 | return; 85 | } 86 | if (this.isAutoPressing) { 87 | if (this.autoPressingRandom.get() < 0.2) { 88 | _.forEach(this.autoPressingKeys, k => { 89 | this.isKeyDown[k] = false; 90 | }); 91 | if (this.autoPressingRandom.get() > 0.2) { 92 | this.isKeyDown[ 93 | this.autoPressingKeys[ 94 | this.autoPressingRandom.getInt(this.autoPressingKeys.length) 95 | ] 96 | ] = true; 97 | } 98 | } 99 | } 100 | this.screen.clear(); 101 | this.actorAddingCount = 0; 102 | for (let i = 0; i < this.actors.length;) { 103 | const a = this.actors[i]; 104 | if (a.isAlive) { 105 | a.update(); 106 | } 107 | if (a.isAlive) { 108 | i++; 109 | } else { 110 | this.actors.splice(i, 1); 111 | } 112 | } 113 | this.screen.showStatus(`score: ${this.score} miss: ${this.missCount}`); 114 | } 115 | 116 | miss() { 117 | this.actors = []; 118 | this.missCount++; 119 | this.restart(); 120 | } 121 | 122 | addScore(score = 1) { 123 | this.score += score; 124 | } 125 | 126 | diff(otherGame: Game) { 127 | return { 128 | screen: this.screen.diff(otherGame.screen), 129 | score: Math.abs(this.score - otherGame.score), 130 | miss: Math.abs(this.missCount - otherGame.missCount) 131 | }; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | game-combinator ([Demo](https://abagames.github.io/game-combinator/)) 2 | ====================== 3 | Combine games and generate a new one. (Experimental) 4 | 5 | ### How to work 6 | 7 | Combine the games randomly... 8 | 9 | [helmet](https://abagames.github.io/game-combinator/?v=1&g=helmet.gc) | [flap](https://abagames.github.io/game-combinator/?v=1&g=flap.gc) | [...](https://github.com/abagames/game-combinator/tree/master/docs/codes) 10 | --- | --- | --- 11 | ![helmet](https://abagames.github.io/game-combinator/img/helmet.gif) | ![flap](https://abagames.github.io/game-combinator/img/flap.gif) | ... 12 | 13 | ``` 14 | (game helmet (game flap 15 | (actor stage (actor stage 16 | (if initial (spawn player)) (if initial (spawn player)) 17 | (if (random frequently) (spawn enemy)) (if (random frequently) (spawn enemy)) 18 | ) ) 19 | (actor player (actor player 20 | (if initial (place bottom_left)) (if initial (place left_center)) 21 | (if (key left) (move left)) CROSSOVER (if (key up) (accelerate up fast)) 22 | (accelerate down normal) <------------------ 23 | ------------------> (if (key right) (move right)) 24 | (if (touch out_of_screen_right) ( (if (touch out_of_screen) miss) 25 | score (if (touch enemy) miss) 26 | (place bottom_left) ) 27 | )) 28 | (if (touch out_of_screen) (move step_back)) 29 | ) 30 | (actor enemy (actor enemy 31 | (if initial (place top)) CROSSOVER (if initial (place right)) 32 | ------------------> (accelerate down normal) 33 | (move left) <------------------ 34 | (if (touch player) miss) (if (touch out_of_screen) ( 35 | (if (touch out_of_screen) remove) score 36 | ) remove 37 | ) )) 38 | ) 39 | ) 40 | ``` 41 | 42 | and pray that it becomes a game. 43 | 44 | ![generated](https://abagames.github.io/game-combinator/img/generated.gif) 45 | 46 | ### Examples of generated games 47 | 48 | |[rainy day](https://abagames.github.io/game-combinator/?v=1&c=NoIg5ghgtgpiA0IBmAnA9lA+ktBXFCoEAxgC5oGIDOpEYc8oAlkoSChAHYAmGCIaJKRicQAXTGJyuYgAs2Lfk05NSTCABs2KJmFml+VAA4QA7qMRGNEAJ4wCExiEVOOPPolQwAjrhGkNG3EnYzMLEAAjXA0NGAMJSSIyCn4rW3sFVkRlVXUtJzTiBhAizmEHROcs0ABrGCDEWKFg0Cg0ADdipvjKl1r6-h09eKc2zsHdfXFe6pA6hpBcIxaQMeKl6ac+http://localhost:8080/?v=1&c=NoIg5ghgtgpiA0IBOB7CATBoIGMAuKSCIAznhGHPKAJYBmxNAdjXjRADZakAOEA7k2I8OEAJ4wiAXSnUQ9RizacsoEn0HDRE6YnUChiGExhQxIGbOz5CW8ZO4LEzVuy5yRuKiABGKPARQAPo4xngOMnJOoADWMOaIHDB0eBZyuKFJSBDhxEkpxABukmJBdBBkFlbyDHJxCcg0YAAWqdUZMFk53khNrUUlZRVt7TiZkt3EJBwo-EHos0LV0SAEAK44zcQoa3hBKHRBJDhIMMZpwKBQKMVT4TxBPrgxFyAdXbmIfmtMoUHNhBoAC8UExyFxLHocIQqLRaqB1pspgC2ogoDQSCRXitEVsvmsOElUSB0ZiqukbEQjCYzI5avIlG5uJ5QsQCA9QmCIst4cgIEwFlBiHRTgBHNZhDjmUbjbKfED5VKIcqVSJwxSuFRyfSaPQaQwgTw6cnquTZAUoIXKsUSsFS17vCby3otJUgFUjKK83HbXb7Q7HU7nWTIUw3OA87g+xA7PYHI4nM5LahXcN3GAPJ44F4yzpO7zfX4wf6AkFglSQtT65H+V46g0+AlEk1vSk1pWmhma9ygFnedktpxd5Q91uyyaJZJu4pIUoelvXW6IBaaSNm-mC7YpYMU8fy6azeaLQfenZImN++OBpNpUOLiOjAhU3xNmAdmrEU737H0lwj7iOnKPR9NOgzzmqJJpsux5rqA5qbjG27JtYe7eAecwrkssGrGeeIgLG-oJkGyZ3mmlhAA)| 49 | |---| 50 | |![rainy_day](https://abagames.github.io/game-combinator/img/rainy_day.gif)| 51 | 52 | |[racket fire](https://abagames.github.io/game-combinator/?v=1&c=NoIg5ghgtgpiA0IBmBLATneoIGMAuA9mgiAM54RiZkAOEA7gHYKgECueA+gUp6ThhjNE7Ljz4CYQkqTpMSNADYQAnjGIBdLCCW5qunHC2gUSFiDQRGAEwJQSljIpUhjtBsJBCYUFxv-auITEiLpqISZm2gDWMC6IijBIeK7aUAQAbtSJya5upuax8RYoYAAWKW7pWQ6lFXnaBdqEbDhlJKLcvPyCzFWZRgHY+EQk3r7mTSAojCh4KBCKLKAG1IQ0nDmVgTiGiZZ41Gh1KYhIEOR5brh76hCHJLbyiIxEUIupkeYtbQrK4Z8yDgiJhhrcDtQAEbsRiGThZNDzHAffo1RBsGhXRpRUA-doiDhdCS9TjQvCEexaEBQFCkUiApq49i-AlibqSIScY7lbYWHwDK5AA)| 53 | |---| 54 | |![racket_fire](https://abagames.github.io/game-combinator/img/racket_fire.gif)| 55 | 56 | ### Libraries 57 | 58 | [s-expression](https://github.com/fwg/s-expression) / 59 | [lz-string](http://pieroxy.net/blog/pages/lz-string/index.html) / 60 | [lodash](https://lodash.com/) / 61 | [Material Components](https://material.io/components/) 62 | -------------------------------------------------------------------------------- /src/screen.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | export default class Screen { 4 | canvas: HTMLCanvasElement; 5 | context: CanvasRenderingContext2D; 6 | pixels: number[][]; 7 | colorPatterns = [ 8 | '#8f8', '#f88', '#88f', '#f8f' 9 | ]; 10 | tile: HTMLLIElement; 11 | statusDom: HTMLElement; 12 | likedCheckBox: HTMLInputElement; 13 | playOrBackButton: HTMLButtonElement; 14 | 15 | constructor(public hasDom = true, 16 | public mode = 'generated', 17 | public width = 15, public height = 15) { 18 | this.pixels = _.times(width, () => _.times(height, () => -1)); 19 | if (!hasDom) { 20 | return; 21 | } 22 | this.tile = document.createElement('li'); 23 | this.tile.className = 'mdc-grid-tile'; 24 | if (mode !== 'generated') { 25 | this.tile.style.cssText = 'width: 380px'; 26 | } 27 | const primary = document.createElement('div'); 28 | primary.className = 'mdc-grid-tile__primary'; 29 | primary.style.cssText = 'text-align: center; color: white'; 30 | this.statusDom = document.createElement('div'); 31 | primary.appendChild(this.statusDom); 32 | const styleSize = mode === 'generated' ? 120 : 300; 33 | this.canvas = document.createElement('canvas'); 34 | this.canvas.width = width; 35 | this.canvas.height = height; 36 | this.canvas.style.cssText = ` 37 | width: ${styleSize}px; 38 | height: ${styleSize}px; 39 | background: white; 40 | image-rendering: -moz-crisp-edges; 41 | image-rendering: -webkit-optimize-contrast; 42 | image-rendering: -o-crisp-edges; 43 | image-rendering: pixelated; 44 | `; 45 | this.context = this.canvas.getContext('2d'); 46 | primary.appendChild(this.canvas); 47 | const secondary = document.createElement('div'); 48 | secondary.className = 'mdc-grid-tile__secondary'; 49 | if (mode === 'generated') { 50 | const checkbox = document.createElement('div'); 51 | checkbox.className = 'mdc-checkbox'; 52 | checkbox.style.cssText = 'top: -12px; float: left'; 53 | this.likedCheckBox = document.createElement('input'); 54 | this.likedCheckBox.type = 'checkbox'; 55 | this.likedCheckBox.className = 'mdc-checkbox__native-control'; 56 | this.likedCheckBox.checked = true; 57 | checkbox.appendChild(this.likedCheckBox); 58 | const div = document.createElement('div'); 59 | div.className = 'mdc-checkbox__background'; 60 | div.innerHTML = ` 61 | 62 | 66 | 67 |
68 | `; 69 | checkbox.appendChild(div); 70 | const label = document.createElement('label'); 71 | label.textContent = 'Like'; 72 | secondary.appendChild(checkbox); 73 | secondary.appendChild(label); 74 | } 75 | if (mode !== 'loaded') { 76 | this.playOrBackButton = document.createElement('button'); 77 | this.playOrBackButton.className = 'mdc-button'; 78 | this.playOrBackButton.textContent = mode === 'generated' ? 'Play' : 'Back'; 79 | this.playOrBackButton.style.cssText = 'top: -10px; float: right'; 80 | secondary.appendChild(this.playOrBackButton); 81 | } 82 | this.tile.appendChild(primary); 83 | this.tile.appendChild(secondary); 84 | document.getElementById('tiles').appendChild(this.tile); 85 | } 86 | 87 | setOnButtonClicked(handler: (this: HTMLElement, e: MouseEvent) => any) { 88 | this.playOrBackButton.onclick = handler; 89 | } 90 | 91 | clear() { 92 | if (!this.hasDom) { 93 | return; 94 | } 95 | this.context.clearRect(0, 0, this.width, this.height); 96 | _.times(this.width, x => _.times(this.height, y => { 97 | this.pixels[x][y] = -1; 98 | })); 99 | } 100 | 101 | setPoint(x: number, y: number, colorIndex = -1) { 102 | const px = Math.floor(x); 103 | const py = Math.floor(y); 104 | if (colorIndex === -1 || 105 | px < 0 || px >= this.width || py < 0 || py >= this.height) { 106 | return; 107 | } 108 | this.pixels[px][py] = colorIndex; 109 | if (!this.hasDom) { 110 | return; 111 | } 112 | this.context.fillStyle = this.colorPatterns[colorIndex]; 113 | this.context.fillRect(px, py, 1, 1); 114 | } 115 | 116 | diff(otherScreen: Screen) { 117 | let score = 0; 118 | _.times(this.width, x => { 119 | _.times(this.height, y => { 120 | if (this.pixels[x][y] !== otherScreen.pixels[x][y]) { 121 | score++; 122 | } 123 | }); 124 | }); 125 | return score; 126 | } 127 | 128 | showStatus(text: string) { 129 | if (!this.hasDom) { 130 | return; 131 | } 132 | this.statusDom.textContent = text; 133 | } 134 | 135 | remove() { 136 | document.getElementById('tiles').removeChild(this.tile); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as parseSE from 's-expression'; 2 | import * as LZString from 'lz-string'; 3 | import * as _ from 'lodash'; 4 | import Game from './game'; 5 | import Screen from './screen'; 6 | import Random from './random'; 7 | 8 | window.onload = init; 9 | 10 | const codeCount = 100; 11 | const aliveCount = 10; 12 | const crossoverCount = 5; 13 | const fitnessCalcTicks = 100; 14 | const version = '1'; 15 | let baseCodeCount: number; 16 | let baseCodeNames: string[]; 17 | let baseCodes: any[]; 18 | let baseCodesDiffParams: any; 19 | let loadedBaseCodeCount = 0; 20 | let codeSeed: number; 21 | let codes: { code: any[], fitness: number }[]; 22 | let codeIndex: number; 23 | let codesWithFitness: { code: any[], fitness: number }[]; 24 | let fitnessIndex: number; 25 | let random: Random; 26 | let isKeyDown = _.times(256, () => false); 27 | let games: Game[] = []; 28 | let isLiked: boolean[] = []; 29 | let isGamesBegun = false; 30 | 31 | function init() { 32 | showInfo('Loading...'); 33 | initEventHandlers(); 34 | loadGameList(); 35 | update(); 36 | } 37 | 38 | function initEventHandlers() { 39 | document.onkeydown = e => { 40 | isKeyDown[e.keyCode] = true; 41 | }; 42 | document.onkeyup = e => { 43 | isKeyDown[e.keyCode] = false; 44 | }; 45 | document.getElementById('generate').onclick = () => { 46 | beginGenerating(); 47 | }; 48 | document.getElementById('generate_from_liked').onclick = () => { 49 | goToNextGeneration(); 50 | }; 51 | } 52 | 53 | function loadGameList() { 54 | loadFile('games.txt', text => { 55 | baseCodeNames = _.filter(text.split('\n'), n => n.length > 0); 56 | baseCodeNames = _.map(baseCodeNames, n => n.trim()); 57 | baseCodeCount = baseCodeNames.length; 58 | baseCodes = _.times(baseCodeCount, () => null); 59 | _.forEach(baseCodeNames, loadCode); 60 | }); 61 | } 62 | 63 | function loadCode(name: string, index: number) { 64 | loadFile(`codes/${name}`, text => { 65 | const parsed = parseSE(text); 66 | if (parsed instanceof Error) { 67 | const err: any = parsed; 68 | console.error(`${err} line: ${err.line} col: ${err.col} (${name})`); 69 | return; 70 | } 71 | baseCodes[index] = parsed; 72 | loadedBaseCodeCount++; 73 | if (loadedBaseCodeCount >= baseCodeCount) { 74 | calcBaseCodesDiffParams(); 75 | if (!loadFromUrl()) { 76 | beginGenerating(); 77 | } 78 | } 79 | }); 80 | } 81 | 82 | function loadFile(name: string, callback: (name: string) => void) { 83 | const request = new XMLHttpRequest(); 84 | request.open('GET', name); 85 | request.send(); 86 | request.onload = () => { 87 | callback(request.responseText); 88 | }; 89 | } 90 | 91 | function beginBaseGame(name: string) { 92 | const code = baseCodes[baseCodeNames.indexOf(name)]; 93 | if (code == null) { 94 | const errMsg = `Game "${name}" not found`; 95 | showInfo(errMsg); 96 | return; 97 | } 98 | games = [beginGame({ code, fitness: 0 }, 'loaded')]; 99 | enableButtons(true, ['generate']); 100 | beginGames(); 101 | } 102 | 103 | function beginGenerating(randomSeed: number = null) { 104 | enableButtons(false); 105 | endGames(); 106 | codeSeed = randomSeed != null ? randomSeed : new Random().getToMaxInt(); 107 | random = new Random().setSeed(codeSeed); 108 | const codeOffset = random.getInt(baseCodeCount); 109 | codes = _.times(codeCount, i => { 110 | return { 111 | code: _.cloneDeep(baseCodes[(i + codeOffset) % baseCodeCount]), 112 | fitness: 0 113 | }; 114 | }); 115 | setTimeout(crossoverCodes, 1); 116 | } 117 | 118 | function goToNextGeneration() { 119 | enableButtons(false); 120 | endGames(); 121 | let likedCodes = []; 122 | _.forEach(games, g => { 123 | if (g.screen.likedCheckBox.checked) { 124 | likedCodes.push(g.originalCode); 125 | } 126 | }); 127 | if (likedCodes.length <= 0) { 128 | beginGenerating(); 129 | return; 130 | } 131 | const codeOffset = random.getInt(baseCodeCount); 132 | codes = _.times(codeCount, i => { 133 | let code; 134 | if (i <= codeCount * 0.8) { 135 | code = _.cloneDeep(likedCodes[i % likedCodes.length]); 136 | } else { 137 | code = _.cloneDeep(baseCodes[(i + codeOffset) % baseCodeCount]); 138 | } 139 | return { 140 | code, 141 | fitness: 0 142 | }; 143 | }); 144 | setTimeout(crossoverCodes, 1); 145 | } 146 | 147 | function beginGeneratedGames() { 148 | resetUrl(); 149 | games = _.map(codes, c => beginGame(c)); 150 | _.forEach(isLiked, (il, i) => { 151 | games[i].screen.likedCheckBox.checked = il; 152 | }); 153 | enableButtons(); 154 | beginGames(); 155 | } 156 | 157 | function beginGames() { 158 | showInfo('Use [WASD] keys to control'); 159 | isGamesBegun = true; 160 | } 161 | 162 | function endGames() { 163 | isGamesBegun = false; 164 | _.forEach(games, g => { 165 | g.end(); 166 | }); 167 | } 168 | 169 | function update() { 170 | requestAnimationFrame(update); 171 | if (!isGamesBegun) { 172 | return; 173 | } 174 | _.forEach(games, g => { 175 | g.update(); 176 | }); 177 | } 178 | 179 | function beginGame(code: any, mode = 'generated') { 180 | const fitness = Math.floor(code.fitness); 181 | const screen = new Screen(true, mode); 182 | const game = new Game(screen, isKeyDown); 183 | if (mode === 'generated') { 184 | screen.setOnButtonClicked(() => { 185 | _.forEach(games, (g, i) => { 186 | isLiked[i] = games[i].screen.likedCheckBox.checked; 187 | }); 188 | endGames(); 189 | enableButtons(false, ['generate_from_liked']); 190 | saveAsUrl(game.originalCode); 191 | games = [beginGame({ code: game.originalCode, fitness: 0 }, 'selected')]; 192 | beginGames(); 193 | }); 194 | } else if (mode === 'selected') { 195 | screen.setOnButtonClicked(() => { 196 | endGames(); 197 | beginGeneratedGames(); 198 | }); 199 | } 200 | game.begin(code.code); 201 | return game; 202 | } 203 | 204 | function crossoverCodes() { 205 | _.times(codeCount * crossoverCount, i => { 206 | crossover(i % codeCount); 207 | }); 208 | selectCodes(); 209 | } 210 | 211 | function selectCodes() { 212 | codesWithFitness = []; 213 | fitnessIndex = 0; 214 | setTimeout(addFitnessToCode, 1); 215 | } 216 | 217 | function endSelectingCodes() { 218 | let ci = 0; 219 | _.times(codeCount - aliveCount, () => { 220 | let nci = ci + 1; 221 | if (nci >= codesWithFitness.length) { 222 | nci = 0; 223 | } 224 | const si = (codesWithFitness[ci].fitness > codesWithFitness[nci].fitness) ? nci : ci; 225 | codesWithFitness.splice(si, 1); 226 | ci++; 227 | if (ci >= codesWithFitness.length) { 228 | ci = 0; 229 | } 230 | }); 231 | codes = codesWithFitness; 232 | isLiked = _.times(aliveCount, () => true); 233 | beginGeneratedGames(); 234 | } 235 | 236 | function addFitnessToCode() { 237 | showInfo(`Generating... ${fitnessIndex} / ${codeCount}`); 238 | const code = codes[fitnessIndex].code; 239 | const fitness = calcFitness(code); 240 | codesWithFitness.push({ code, fitness }); 241 | fitnessIndex++; 242 | if (fitnessIndex >= codeCount) { 243 | endSelectingCodes(); 244 | } else { 245 | setTimeout(addFitnessToCode, 0); 246 | } 247 | } 248 | 249 | const diffParams = ['screen', 'score', 'miss']; 250 | 251 | function calcFitness(code: any[]) { 252 | const diff = calcDiff(code); 253 | if (diff == null) { 254 | return 0; 255 | } 256 | let fitness = 0; 257 | _.forEach(diffParams, p => { 258 | const dp = baseCodesDiffParams[p]; 259 | fitness += fitnessValue(diff[p], dp.min, dp.median, dp.max); 260 | }); 261 | return fitness; 262 | } 263 | 264 | function calcDiff(code: any[]) { 265 | const gameCount = 8; 266 | const games = _.times(gameCount, i => { 267 | const game = new Game(new Screen(false), null, 0, i); 268 | game.begin(code); 269 | return game; 270 | }); 271 | const result: any = {}; 272 | _.forEach(diffParams, p => { 273 | result[p] = 0; 274 | }); 275 | for (let i = 0; i < fitnessCalcTicks; i++) { 276 | let isValid = true; 277 | _.forEach(games, game => { 278 | game.update(); 279 | if (!game.isValid) { 280 | isValid = false; 281 | return false; 282 | } 283 | }); 284 | if (!isValid) { 285 | return null; 286 | } 287 | for (let j = 0; j < gameCount - 1; j++) { 288 | for (let k = j + 1; k < gameCount; k++) { 289 | const diff = games[j].diff(games[k]); 290 | _.forEach(diffParams, p => { 291 | result[p] += diff[p]; 292 | }); 293 | } 294 | } 295 | } 296 | return result; 297 | } 298 | 299 | function calcBaseCodesDiffParams() { 300 | baseCodesDiffParams = {}; 301 | _.forEach(diffParams, p => { 302 | baseCodesDiffParams[p] = {}; 303 | baseCodesDiffParams[p].values = []; 304 | }); 305 | let codeCount = 0; 306 | _.forEach(baseCodes, c => { 307 | const diff = calcDiff(c); 308 | if (diff == null) { 309 | return; 310 | } 311 | _.forEach(diffParams, p => { 312 | baseCodesDiffParams[p].values.push(diff[p]); 313 | }); 314 | }); 315 | _.forEach(diffParams, p => { 316 | const dp = baseCodesDiffParams[p]; 317 | const vs = dp.values; 318 | const svs = _.sortBy(vs); 319 | dp.min = svs[0]; 320 | dp.median = svs[Math.floor(svs.length / 2)]; 321 | dp.max = svs[svs.length - 1]; 322 | }); 323 | return baseCodesDiffParams; 324 | } 325 | 326 | function fitnessValue(v: number, min: number, center: number, max: number) { 327 | if (v <= min || v >= max) { 328 | return 0; 329 | } 330 | if (v < center) { 331 | return (v - min) / (center - min) * 100; 332 | } else { 333 | return (max - v) / (max - center) * 100; 334 | } 335 | } 336 | 337 | function crossover(codeIndex: number) { 338 | const p1 = getCodePart(codes[codeIndex].code); 339 | const p2 = getCodePart(codes[random.getInt(codeCount)].code); 340 | p1.parent.splice(p1.index, 0, _.cloneDeep(p2.parent[p2.index])); 341 | p2.parent.splice(p2.index, 1); 342 | } 343 | 344 | function getCodePart(code: any[], targetDepth = 1, depth = 0) { 345 | let ci = random.getInt(code.length); 346 | let part; 347 | for (let i = 0; i < code.length; i++) { 348 | part = code[ci]; 349 | if (part instanceof Array) { 350 | break; 351 | } 352 | ci++; 353 | if (ci >= code.length) { 354 | ci = 0; 355 | } 356 | } 357 | if (!(part instanceof Array) || 358 | depth >= targetDepth && random.get() > 0.5) { 359 | return { parent: code, index: ci }; 360 | } 361 | return getCodePart(part, targetDepth, depth + 1); 362 | } 363 | 364 | function enableButtons(isEnabled = true, 365 | buttonIds = ['generate', 'generate_from_liked']) { 366 | _.forEach(buttonIds, id => { 367 | (document.getElementById(id)).disabled = !isEnabled; 368 | }); 369 | } 370 | 371 | function showInfo(text: string) { 372 | document.getElementById('game_info').textContent = text; 373 | } 374 | 375 | function saveAsUrl(code: any[]) { 376 | const baseUrl = window.location.href.split('?')[0]; 377 | const codeStr = LZString.compressToEncodedURIComponent(JSON.stringify(code)); 378 | const url = `${baseUrl}?v=${version}&c=${codeStr}`; 379 | return changeUrl(url); 380 | } 381 | 382 | function resetUrl() { 383 | const baseUrl = window.location.href.split('?')[0]; 384 | return changeUrl(baseUrl); 385 | } 386 | 387 | function changeUrl(url: string) { 388 | try { 389 | window.history.replaceState({}, '', url); 390 | return true; 391 | } catch (e) { 392 | console.log(e); 393 | return false; 394 | } 395 | } 396 | 397 | function loadFromUrl() { 398 | const query = window.location.search.substring(1); 399 | if (query == null) { 400 | return false; 401 | } 402 | let params = query.split('&'); 403 | let versionStr: string; 404 | let codeStr: string; 405 | let gameNameStr: string; 406 | for (let i = 0; i < params.length; i++) { 407 | const param = params[i]; 408 | const pair = param.split('='); 409 | if (pair[0] === 'v') { 410 | versionStr = pair[1]; 411 | } else if (pair[0] === 'c') { 412 | codeStr = pair[1]; 413 | } else if (pair[0] === 'g') { 414 | gameNameStr = pair[1]; 415 | } 416 | } 417 | if (versionStr == null && codeStr == null && gameNameStr == null) { 418 | return false; 419 | } 420 | if (versionStr !== version) { 421 | showInfo('Invalid version number'); 422 | } else if (codeStr != null) { 423 | try { 424 | const code = JSON.parse(LZString.decompressFromEncodedURIComponent(codeStr)); 425 | games = [beginGame({ code, fitness: 0 }, 'loaded')]; 426 | enableButtons(true, ['generate']); 427 | beginGames(); 428 | } catch (e) { 429 | showInfo('Load a code from URL failed'); 430 | } 431 | } else if (gameNameStr != null) { 432 | beginBaseGame(gameNameStr); 433 | } 434 | return true; 435 | } 436 | -------------------------------------------------------------------------------- /src/actor.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import Game from './game'; 3 | import Screen from './screen'; 4 | 5 | export default class Actor { 6 | screen: Screen; 7 | pos = new Vector(-1, -1); 8 | prevPos = new Vector(); 9 | vel = new Vector(); 10 | resultValue: any; 11 | touchedActor: Actor; 12 | isAlive = true; 13 | ticks = 0; 14 | colorIndex = -1; 15 | parseCount = 0; 16 | freqNamePatterns = { 17 | 'rarely': 0.05, 18 | 'often': 0.1, 19 | 'frequently': 0.2, 20 | 'half': 0.5 21 | }; 22 | defaultFreqName = 'often'; 23 | durationNamePatterns = { 24 | 'tick': 1, 25 | 'soon': 5, 26 | 'later': 10 27 | }; 28 | defaultDurationName = 'soon'; 29 | posNamePatterns = { 30 | 'top_left': { x: 0, y: 0 }, 31 | 'bottom_left': { x: 0, y: 1 }, 32 | 'left_center': { x: 0, y: 0.5 }, 33 | 'top_right': { x: 1, y: 0 }, 34 | 'bottom_right': { x: 1, y: 1 }, 35 | 'right_center': { x: 1, y: 0.5 }, 36 | 'top_center': { x: 0.5, y: 0 }, 37 | 'bottom_center': { x: 0.5, y: 1 }, 38 | 'center': { x: 0.5, y: 0.5 }, 39 | 'top': { x: null, y: 0 }, 40 | 'bottom': { x: null, y: 1 }, 41 | 'left': { x: 0, y: null }, 42 | 'right': { x: 1, y: null } 43 | }; 44 | defaultPosName = 'center'; 45 | edgePosNames = [ 46 | 'top', 'bottom', 'left', 'right' 47 | ]; 48 | keyNamePatterns = { 49 | 'left': [65, 37], 50 | 'right': [68, 39], 51 | 'up': [87, 38], 52 | 'down': [83, 40], 53 | }; 54 | defaultKeyName = 'right'; 55 | angleNamePatterns = { 56 | 'left': new Vector(-1, 0), 57 | 'right': new Vector(1, 0), 58 | 'up': new Vector(0, -1), 59 | 'down': new Vector(0, 1) 60 | }; 61 | defaultAngleName = 'right'; 62 | accelerateNamePatterns = { 63 | 'very_fast': 0.3, 64 | 'fast': 0.1, 65 | 'normal': 0.03, 66 | 'slow': 0.01, 67 | 'very_slow': 0.003 68 | }; 69 | defaultAccelerateName = 'normal'; 70 | moveNamePatterns = { 71 | 'very_fast': 3, 72 | 'fast': 2, 73 | 'normal': 1, 74 | 'slow': 0.5, 75 | 'very_slow': 0.25 76 | }; 77 | defaultMoveName = 'normal'; 78 | 79 | constructor(public name: string, public code: any[], public game: Game) { 80 | this.screen = game.screen; 81 | const ci = game.actorNames.indexOf(name) - 1; 82 | if (ci >= 0) { 83 | this.colorIndex = ci; 84 | } 85 | } 86 | 87 | update() { 88 | this.prevPos.set(this.pos); 89 | this.parseCount = 0; 90 | this.pos.x += this.vel.x; 91 | this.pos.y += this.vel.y; 92 | this.vel.x *= 0.99; 93 | this.vel.y *= 0.99; 94 | this.parse(_.cloneDeep(this.code)); 95 | this.screen.setPoint(this.pos.x, this.pos.y, this.colorIndex); 96 | if (this.pos.x < -this.screen.width * 0.5 || 97 | this.pos.x >= this.screen.width * 1.5 || 98 | this.pos.y < -this.screen.height * 0.5 || 99 | this.pos.y >= this.screen.height * 1.5) { 100 | this.remove(); 101 | } 102 | this.ticks++; 103 | } 104 | 105 | remove() { 106 | this.isAlive = false; 107 | } 108 | 109 | parse(currentCode: any) { 110 | const stack = []; 111 | this.parseCount++; 112 | if (this.parseCount > 48) { 113 | this.game.isValid = false; 114 | return this.resultValue; 115 | } 116 | if (currentCode == null) { 117 | return this.resultValue; 118 | } 119 | for (let i = 0; i < 32; i++) { 120 | if (!(currentCode instanceof Array)) { 121 | currentCode = [currentCode]; 122 | } 123 | if (currentCode.length <= 0) { 124 | if (stack.length <= 0) { 125 | return this.resultValue; 126 | } 127 | currentCode = stack.pop(); 128 | continue; 129 | } 130 | const c = currentCode.shift(); 131 | if (c instanceof Array) { 132 | stack.push(currentCode); 133 | currentCode = c; 134 | continue; 135 | } 136 | switch (c) { 137 | case 'if': 138 | const cond = currentCode.shift(); 139 | const condResult = this.parse(cond); 140 | const thenCode = currentCode.shift(); 141 | const elseCode = currentCode.shift(); 142 | if (condResult) { 143 | this.parse(thenCode); 144 | } else { 145 | this.parse(elseCode); 146 | } 147 | break; 148 | case 'initial': 149 | this.resultValue = this.ticks === 0; 150 | break; 151 | case 'exists': 152 | case 'not_exists': 153 | const actorName = this.parse(currentCode.shift()); 154 | if (c === 'exists') { 155 | this.resultValue = this.game.getActors(actorName).length > 0; 156 | } else { 157 | this.resultValue = this.game.getActors(actorName).length <= 0; 158 | } 159 | break; 160 | case 'random': 161 | case 'interval': 162 | const freqName = this.parse(currentCode.shift()); 163 | let freq = this.freqNamePatterns[freqName]; 164 | if (freq == null) { 165 | freq = this.freqNamePatterns[this.defaultFreqName]; 166 | } 167 | if (c === 'random') { 168 | this.resultValue = this.game.random.get() < freq; 169 | } else { 170 | const it = Math.floor(1 / freq); 171 | this.resultValue = this.ticks % it === 0; 172 | } 173 | break; 174 | case 'before': 175 | case 'after': 176 | const durationName = this.parse(currentCode.shift()); 177 | let duration = this.durationNamePatterns[durationName]; 178 | if (duration == null) { 179 | duration = this.durationNamePatterns[this.defaultDurationName]; 180 | } 181 | if (c === 'before') { 182 | this.resultValue = this.ticks < duration; 183 | } else { 184 | this.resultValue = this.ticks >= duration; 185 | } 186 | break; 187 | case 'key': 188 | if (this.game.isKeyDown == null) { 189 | this.resultValue = false; 190 | break; 191 | } 192 | const keyName = this.parse(currentCode.shift()); 193 | let kp: number[] = this.keyNamePatterns[keyName]; 194 | if (kp == null) { 195 | kp = this.keyNamePatterns[this.defaultKeyName]; 196 | } 197 | this.resultValue = _.some(kp, k => this.game.isKeyDown[k]); 198 | break; 199 | case 'touch': 200 | const targetName = this.parse(currentCode.shift()); 201 | this.resultValue = this.checkTouch(targetName); 202 | break; 203 | case 'select': 204 | const selectedIndex = this.game.random.getInt(currentCode.length); 205 | this.parse(currentCode[selectedIndex]); 206 | currentCode = []; 207 | break; 208 | case 'spawn': 209 | const spawningActorName = this.parse(currentCode.shift()); 210 | const actor = this.game.addActor(spawningActorName); 211 | if (actor != null) { 212 | actor.pos.set(this.pos); 213 | } 214 | break; 215 | case 'place': 216 | this.place(currentCode); 217 | break; 218 | case 'move': 219 | this.move(currentCode); 220 | break; 221 | case 'accelerate': 222 | this.accelerate(currentCode); 223 | break; 224 | case 'remove': 225 | this.remove(); 226 | break; 227 | case 'remove_touched': 228 | if (this.touchedActor != null) { 229 | this.touchedActor.remove(); 230 | } 231 | break; 232 | case 'miss': 233 | this.game.miss(); 234 | break; 235 | case 'score': 236 | this.game.addScore(); 237 | break; 238 | default: 239 | this.resultValue = c; 240 | break; 241 | } 242 | } 243 | } 244 | 245 | checkTouch(targetName: string) { 246 | if (targetName == null) { 247 | return false; 248 | } 249 | const sw = this.screen.width; 250 | const sh = this.screen.height; 251 | const ti = this.game.actorNames.indexOf(targetName); 252 | if (ti >= 0) { 253 | this.touchedActor = null; 254 | const x = Math.floor(this.pos.x); 255 | const y = Math.floor(this.pos.y); 256 | return _.some(this.game.actors, a => { 257 | const isTouched = (a !== this && a.name === targetName && 258 | Math.floor(a.pos.x) === x && Math.floor(a.pos.y) === y); 259 | if (isTouched) { 260 | this.touchedActor = a; 261 | } 262 | return isTouched; 263 | }); 264 | } 265 | switch (targetName) { 266 | case 'out_of_screen': 267 | return this.pos.x < 0 || this.pos.x >= sw || 268 | this.pos.y < 0 || this.pos.y >= sh; 269 | case 'out_of_screen_top': 270 | return this.pos.x >= 0 && this.pos.x < sw && this.pos.y < 0; 271 | case 'out_of_screen_bottom': 272 | return this.pos.x >= 0 && this.pos.x < sw && this.pos.y >= sh; 273 | case 'out_of_screen_left': 274 | return this.pos.y >= 0 && this.pos.y < sh && this.pos.x < 0; 275 | case 'out_of_screen_right': 276 | return this.pos.y >= 0 && this.pos.y < sh && this.pos.x >= sw; 277 | } 278 | } 279 | 280 | place(currentCode: any[]) { 281 | let posName = this.parse(currentCode.shift()); 282 | if (posName === 'edge') { 283 | posName = this.edgePosNames[this.game.random.getInt(4)]; 284 | } 285 | let pp = this.posNamePatterns[posName]; 286 | if (pp == null) { 287 | pp = this.posNamePatterns[this.defaultPosName]; 288 | } 289 | if (pp.x == null) { 290 | pp.x = this.game.random.get(); 291 | } 292 | if (pp.y == null) { 293 | pp.y = this.game.random.get(); 294 | } 295 | this.pos.set( 296 | Math.floor(pp.x * (this.screen.width - 0.01)), 297 | Math.floor(pp.y * (this.screen.height - 0.01))); 298 | this.prevPos.set(this.pos); 299 | } 300 | 301 | move(currentCode: any[]) { 302 | const moveName = this.parse(currentCode.shift()); 303 | if (moveName === 'step_back') { 304 | this.pos.set(this.prevPos.x, this.prevPos.y); 305 | return; 306 | } 307 | const av = this.getAngleVector(moveName); 308 | const speedName = this.parse(currentCode.shift()); 309 | let speed = this.moveNamePatterns[speedName]; 310 | if (speed == null) { 311 | speed = this.moveNamePatterns[this.defaultMoveName]; 312 | } 313 | this.pos.x += av.x * speed; 314 | this.pos.y += av.y * speed; 315 | } 316 | 317 | accelerate(currentCode: any[]) { 318 | const angleName = this.parse(currentCode.shift()); 319 | if (angleName === 'bounce_horizontal') { 320 | this.vel.x *= -1; 321 | return; 322 | } 323 | if (angleName === 'bounce_vertical') { 324 | this.vel.y *= -1; 325 | return; 326 | } 327 | if (angleName === 'stop') { 328 | this.vel.set(0, 0); 329 | return; 330 | } 331 | if (angleName === 'slow_down') { 332 | this.vel.x *= 0.9; 333 | this.vel.y *= 0.9; 334 | return; 335 | } 336 | const av = this.getAngleVector(angleName); 337 | const speedName = this.parse(currentCode.shift()); 338 | let speed = this.accelerateNamePatterns[speedName]; 339 | if (speed == null) { 340 | speed = this.accelerateNamePatterns[this.defaultAccelerateName]; 341 | } 342 | this.vel.x += av.x * speed; 343 | this.vel.y += av.y * speed; 344 | } 345 | 346 | getAngleVector(angleName): Vector { 347 | let ap = this.angleNamePatterns[angleName]; 348 | if (_.startsWith(angleName, 'to_')) { 349 | const target = angleName.substring(3); 350 | ap = this.getAngleTo(target); 351 | } 352 | if (ap == null) { 353 | ap = this.angleNamePatterns[this.defaultAngleName]; 354 | } 355 | return ap; 356 | } 357 | 358 | getAngleTo(name: string) { 359 | const actors = this.game.getActors(name); 360 | let dist = 9999999; 361 | let target: Actor; 362 | let ox: number; 363 | let oy: number; 364 | _.forEach(actors, a => { 365 | if (a === this) { 366 | return; 367 | } 368 | ox = a.pos.x - this.pos.x; 369 | oy = a.pos.y - this.pos.y; 370 | const d = Math.sqrt(ox * ox + oy * oy); 371 | if (d < dist) { 372 | target = a; 373 | dist = d; 374 | } 375 | }); 376 | if (target == null) { 377 | return null; 378 | } 379 | ox = target.pos.x - this.pos.x; 380 | oy = target.pos.y - this.pos.y; 381 | const angle = Math.atan2(oy, ox); 382 | return new Vector(Math.cos(angle), Math.sin(angle)); 383 | } 384 | } 385 | 386 | class Vector { 387 | x = 0; 388 | y = 0; 389 | 390 | constructor(x: number | Vector = null, y: number = null) { 391 | if (x != null) { 392 | this.set(x, y); 393 | } 394 | } 395 | 396 | set(x: number | Vector, y: number = null) { 397 | if (x instanceof Vector) { 398 | this.x = x.x; 399 | this.y = x.y; 400 | return; 401 | } 402 | this.x = x; 403 | this.y = y; 404 | } 405 | } 406 | --------------------------------------------------------------------------------