')
21 | .addClass('enemy-child');
22 | world.addElement(this._$el);
23 | this.draw();
24 | }
25 |
26 | static isName(name: string): boolean {
27 | return _.first(name.split('_')) === 'enemyChild';
28 | }
29 | }
30 |
31 | export default EnemyChild;
32 |
--------------------------------------------------------------------------------
/web/static/js/EnemyShot.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import Movable from './Movable';
4 | import World from './World';
5 | import type {Position} from './Types';
6 |
7 | class EnemyShot extends Movable {
8 | _speed: number = 0.3;
9 | id: string;
10 | _dx: number;
11 | _dy: number;
12 |
13 | constructor({id, x, y, svg, world, targetPos}: {id: string, x: number, y: number, svg: Object, world: World, targetPos: Position}) {
14 | super({svg, world});
15 | this._x = x;
16 | this._y = y;
17 | this.id = id;
18 | this._el = svg.circle(10)
19 | .addClass('enemy-shot')
20 | .fill('#ff0')
21 | .move(this._x, this._y);
22 | const d = Math.sqrt((targetPos.x - x) ** 2 + (targetPos.y - y) ** 2);
23 | this._dx = (targetPos.x - x) / d;
24 | this._dy = (targetPos.y - y) / d;
25 | }
26 |
27 | canDestroy({x, y}: Position): boolean {
28 | return ((this._x - x) ** 2 + (this._y - y) ** 2) < 3 ** 2;
29 | }
30 |
31 | move(progress: number) {
32 | this._x += this._speed * this._dx * progress;
33 | this._y += this._speed * this._dy * progress;
34 | this._el.move(this._x, this._y);
35 | this._world.checkEnemyShot(this);
36 | }
37 | }
38 |
39 | export default EnemyShot;
40 |
--------------------------------------------------------------------------------
/web/static/js/KeyManager.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import _ from 'lodash';
3 |
4 | const LEFT = 37;
5 | const UP = 38;
6 | const RIGHT = 39;
7 | const DOWM = 40;
8 | const dirMap = {
9 | [LEFT]: {x: -1, y: 0},
10 | [UP]: {x: 0, y: -1},
11 | [DOWM]: {x: 0, y: 1},
12 | [RIGHT]: {x: 1, y: 0},
13 | };
14 |
15 | class KeyManager {
16 | static dirStrMap = {
17 | [LEFT]: 'left',
18 | [UP]: 'up',
19 | [DOWM]: 'down',
20 | [RIGHT]: 'right',
21 | };
22 |
23 | _isPressed = {};
24 | _update: Function;
25 |
26 | constructor({update}: {update: Function}) {
27 | this._update = update;
28 | $(window).keydown((e: Object) => {
29 | const d = dirMap[e.keyCode];
30 | if (!d) {
31 | return;
32 | }
33 | e.preventDefault();
34 | this._isPressed[e.keyCode] = true;
35 | this._onUpdate();
36 | });
37 | $(window).keyup((e: Object) => {
38 | const d = dirMap[e.keyCode];
39 | if (!d) {
40 | return;
41 | }
42 | e.preventDefault();
43 | delete this._isPressed[e.keyCode];
44 | this._onUpdate();
45 | });
46 | }
47 |
48 | getDirections() {
49 | return _.reduce(this._isPressed, (acc, _v, key) => {
50 | const d = dirMap[key];
51 | return {x: acc.x + d.x, y: acc.y + d.y};
52 | }, {x: 0, y: 0});
53 | }
54 |
55 | _onUpdate() {
56 | const dir = _.isEmpty(this._isPressed) ? -1 : parseInt(_.first(_.keys(this._isPressed)), 10);
57 | this._update(dir);
58 | }
59 | }
60 |
61 | export default KeyManager;
62 |
--------------------------------------------------------------------------------
/web/static/js/Movable.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import World from './World';
3 |
4 | class Movable {
5 | _svg: Object;
6 | _x: number;
7 | _y: number;
8 | _world: World;
9 | _el: Object;
10 | _$el: Object;
11 |
12 | constructor({svg, world}: {svg: Object, world: World}) {
13 | this._svg = svg;
14 | this._world = world;
15 | }
16 |
17 | move(_progress: number) {
18 | console.assert(false);
19 | }
20 |
21 | getPosition() {
22 | return {x: this._x, y: this._y};
23 | }
24 |
25 | destroy() {
26 | if (this._el) {
27 | this._el.remove();
28 | }
29 | if (this._$el) {
30 | this._$el.remove();
31 | }
32 | }
33 | }
34 |
35 | export default Movable;
36 |
--------------------------------------------------------------------------------
/web/static/js/Pid.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | class Pid {
4 | static toSafe(id: string) {
5 | return id.replace(/\./g, '_').replace(/[\<\>]/g, '');
6 | }
7 |
8 | static toUnSafe(safeId: string) {
9 | return `<${safeId.replace(/_/g, '.')}>`;
10 | }
11 | }
12 |
13 | export default Pid;
14 |
--------------------------------------------------------------------------------
/web/static/js/Player.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import _ from 'lodash';
3 |
4 | import Movable from './Movable';
5 | import KeyManager from './KeyManager';
6 | import World from './World';
7 |
8 | const WIDTH = 36;
9 | const HEIGHT = 32;
10 |
11 | class Player extends Movable {
12 | _keyManager: KeyManager;
13 | _speed: number = 0.5;
14 | _$el: Object;
15 |
16 | constructor({svg, world}: {svg: Object, world: World}) {
17 | super({svg, world});
18 | this._x = this._world.width / 2;
19 | this._y = this._world.height - 100;
20 | this._$el = $('
')
21 | .addClass('player');
22 | world.addElement(this._$el);
23 | this.draw();
24 | this._keyManager = new KeyManager({update: (dir) => {
25 | this.updateDir(dir);
26 | }});
27 | $(window).keypress((e: Object) => {
28 | if (e.keyCode !== 32) {
29 | return;
30 | }
31 | e.preventDefault();
32 | this._world.createPlayerShot({x: this._x, y: this._y});
33 | });
34 | }
35 |
36 | move(progress: number) {
37 | const d = this._keyManager.getDirections();
38 | this._x += d.x * this._speed * progress;
39 | this._y += d.y * this._speed * progress;
40 | this.draw();
41 | }
42 |
43 | draw() {
44 | this._$el.css({
45 | top: `${this._y - HEIGHT / 2}px`,
46 | left: `${this._x - WIDTH / 2}px`,
47 | });
48 | }
49 |
50 | updateDir(dir: number) {
51 | const dirStr = dir === -1 ? '' : KeyManager.dirStrMap[dir];
52 | $(this._el)
53 | .removeClass(_.values(KeyManager.dirStrMap).join(' '))
54 | .addClass(dirStr);
55 | }
56 |
57 | destroy() {
58 | this._$el.addClass('explosion');
59 | setTimeout(() => {
60 | super.destroy();
61 | console.log('Player.destroyed');
62 | }, 800);
63 | }
64 | }
65 |
66 | export default Player;
67 |
--------------------------------------------------------------------------------
/web/static/js/PlayerShot.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import Movable from './Movable';
4 | import World from './World';
5 | import type {Position} from './Types';
6 |
7 | class PlayerShot extends Movable {
8 | _speed: number = 0.5;
9 | id: string;
10 |
11 | constructor({id, x, y, svg, world}: {id: string, x: number, y: number, svg: Object, world: World}) {
12 | super({svg, world});
13 | this._x = x;
14 | this._y = y;
15 | this.id = id;
16 | this._el = svg.circle(10)
17 | .addClass('playerShot')
18 | .fill('#0ff')
19 | .move(this._x, this._y);
20 | }
21 |
22 | canDestroy({x, y}: Position): boolean {
23 | return ((this._x - x) ** 2 + (this._y - y) ** 2) < 50 ** 2;
24 | }
25 |
26 | move(progress: number) {
27 | this._y -= this._speed * progress;
28 | this._el.move(this._x, this._y);
29 | this._world.checkPlayerShot(this);
30 | }
31 | }
32 |
33 | export default PlayerShot;
34 |
--------------------------------------------------------------------------------
/web/static/js/Types.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | export type Position = {
4 | x: number,
5 | y: number,
6 | }
7 |
--------------------------------------------------------------------------------
/web/static/js/WorldTimer.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | class WorldTimer {
4 | _enemies: Array