├── .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 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
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 |  |  | ...
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 | 
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 | ||
51 |
52 | |[racket fire](https://abagames.github.io/game-combinator/?v=1&c=NoIg5ghgtgpiA0IBmBLATneoIGMAuA9mgiAM54RiZkAOEA7gHYKgECueA+gUp6ThhjNE7Ljz4CYQkqTpMSNADYQAnjGIBdLCCW5qunHC2gUSFiDQRGAEwJQSljIpUhjtBsJBCYUFxv-auITEiLpqISZm2gDWMC6IijBIeK7aUAQAbtSJya5upuax8RYoYAAWKW7pWQ6lFXnaBdqEbDhlJKLcvPyCzFWZRgHY+EQk3r7mTSAojCh4KBCKLKAG1IQ0nDmVgTiGiZZ41Gh1KYhIEOR5brh76hCHJLbyiIxEUIupkeYtbQrK4Z8yDgiJhhrcDtQAEbsRiGThZNDzHAffo1RBsGhXRpRUA-doiDhdCS9TjQvCEexaEBQFCkUiApq49i-AlibqSIScY7lbYWHwDK5AA)|
53 | |---|
54 | ||
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 |
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 |
--------------------------------------------------------------------------------