├── README.md
├── src
├── assets
│ └── styles.css
├── spec-bundle.js
├── index.spec.ts
├── index.html
├── game1
│ ├── stars.ts
│ ├── heroShip.ts
│ ├── stars.spec.ts
│ ├── enemyShips.ts
│ ├── heroShots.ts
│ ├── renderer.ts
│ ├── on-continue.ts
│ └── shared.ts
├── hero-ship.ts
├── hero-shots.ts
├── stars.ts
├── enemy-ships.ts
├── game.ts
├── renderer.ts
├── enemy-shots.ts
├── constants.ts
├── on-continue.ts
└── index.ts
├── tsconfig.json
├── tsd.json
├── webpack.test.js
├── karma.conf.js
├── webpack.config.js
├── .gitignore
├── package.json
└── screencast_script.md
/README.md:
--------------------------------------------------------------------------------
1 | # rx-space
--------------------------------------------------------------------------------
/src/assets/styles.css:
--------------------------------------------------------------------------------
1 | button {
2 | width: 7em;
3 | }
4 |
5 | #container {
6 | margin-top: 1em;
7 | }
--------------------------------------------------------------------------------
/src/spec-bundle.js:
--------------------------------------------------------------------------------
1 | var testsContext = require.context(".", true, /.spec.ts$/);
2 | testsContext.keys().forEach(testsContext);
--------------------------------------------------------------------------------
/src/index.spec.ts:
--------------------------------------------------------------------------------
1 | describe('index', () => {
2 |
3 | it('should do', () => {
4 | const val = true;
5 | expect(val).toBe(true);
6 | })
7 | })
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noImplicitAny": true,
4 | "removeComments": true
5 | },
6 | "exclude": [
7 | "node_modules"
8 | ],
9 | "awesomeTypescriptLoaderOptions": {
10 | /* ... */
11 | }
12 | }
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | RX-SPACE
6 |
7 |
8 |
9 |
14 |
15 |
--------------------------------------------------------------------------------
/src/game1/stars.ts:
--------------------------------------------------------------------------------
1 | import { Observable, TestScheduler } from 'rxjs/Rx';
2 | import { config, iStar } from './shared';
3 |
4 | export const stars$Fac = (stars: iStar[], scheduler?: TestScheduler): Observable => {
5 | return Observable.interval(config.star.moveInterval, scheduler)
6 | .map(tick => {
7 | return stars.map(star => {
8 | star.y += config.star.moveSpeed.y;
9 | if(star.y > config.canvas.height) {
10 | star.y = star.y - config.canvas.height;
11 | }
12 | return star
13 | })
14 | })
15 | }
--------------------------------------------------------------------------------
/tsd.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "v4",
3 | "repo": "borisyankov/DefinitelyTyped",
4 | "ref": "master",
5 | "path": "typings",
6 | "bundle": "typings/tsd.d.ts",
7 | "installed": {
8 | "core-js/core-js.d.ts": {
9 | "commit": "611669c15328d2356355e0cec6cc15df2abe42d6"
10 | },
11 | "jasmine/jasmine.d.ts": {
12 | "commit": "611669c15328d2356355e0cec6cc15df2abe42d6"
13 | },
14 | "lodash/lodash.d.ts": {
15 | "commit": "084e55916674ff7128722bf72150aa55acea08d9"
16 | },
17 | "node/node.d.ts": {
18 | "commit": "0c5c7a2d2bd0ce7dcab963a8402a9042749ca2da"
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/webpack.test.js:
--------------------------------------------------------------------------------
1 | // const ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin;
2 | // const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | module.exports = {
5 |
6 | resolve: {
7 | extensions: ['', '.ts', '.js']
8 | },
9 | devtool: 'inline-source-map',
10 | module: {
11 | loaders: [
12 | // { test: /\.css$/, loader: "style!raw" },
13 | // { test: /\.css$/, loader: "style!css" },
14 | // { test: /\.css$/, loader: "style/url!file" },
15 | { test: /\.ts$/, loader: 'awesome-typescript-loader' }
16 | ]
17 | },
18 | plugins: [
19 | // new ForkCheckerPlugin(),
20 | // new HtmlWebpackPlugin({
21 | // template: 'src/index.html',
22 | // chunksSortMode: 'dependency'
23 | // })
24 | ]
25 | };
26 |
--------------------------------------------------------------------------------
/src/game1/heroShip.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 | import { iHeroShip, canvas, config } from './shared';
3 |
4 | export const heroShip$Fac = (heroShip: iHeroShip): Observable => {
5 | return Observable.fromEvent(canvas, 'mousemove')
6 | .switchMap((event: {clientX: number}) => {
7 | return Observable.interval(config.commons.moveInterval)
8 | .map(tick => {
9 | const mouseTipOnCanvas = event.clientX - 8 - config.canvas.margin
10 | if(heroShip.x < mouseTipOnCanvas) {
11 | heroShip.x += config.heroShip.moveSpeed.x
12 | }
13 | if(heroShip.x > mouseTipOnCanvas) {
14 | heroShip.x -= config.heroShip.moveSpeed.x
15 | }
16 | return heroShip;
17 | })
18 | })
19 | .startWith(heroShip)
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/src/game1/stars.spec.ts:
--------------------------------------------------------------------------------
1 | import { Observable, TestScheduler } from 'rxjs';
2 | import { ColdObservable } from '../../node_modules/rxjs/testing/ColdObservable'
3 | import { config, iStar } from './shared';
4 | import { stars$Fac } from './stars';
5 |
6 | describe('starsMove$Fac', () => {
7 | it('should add 1 to star.y', () => {
8 | const scheduler = new TestScheduler((a, b) => expect(a).toEqual(b));
9 | const stars0: iStar[] = [{x: 1, y: 1, size: 1, speed: 1, color: 'a'}]
10 | const stars1: iStar[] = [{x: 1, y: 1 + config.star.moveSpeed.y, size: 1, speed: 1, color: 'a'}]
11 | const actual1$ = stars$Fac(stars0, scheduler).take(1);
12 | const expected1$ = '-'.repeat(config.star.moveInterval/10) + '(a|)' // '--(a|)'
13 | scheduler.expectObservable(actual1$).toBe(expected1$, {a: stars1});
14 | scheduler.flush(); // flush comes after expectObservable.toBe
15 | })
16 | })
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/hero-ship.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 | import {
3 | config,
4 | iHeroShip,
5 | canvas
6 | } from './constants';
7 |
8 | export const heroShip$Fac = (heroShip: iHeroShip): Observable => {
9 | return Observable.fromEvent(canvas, 'mousemove')
10 | .switchMap((event: {clientX: number;})=> {
11 | return Observable.interval(config.heroShip.moveInterval)
12 | .map(tick => {
13 | const mouseTipX = event.clientX - 7
14 | switch (true) {
15 | case mouseTipX < heroShip.x:
16 | return Object.assign(heroShip, {x: heroShip.x - config.heroShip.speedX})
17 | case heroShip.x < mouseTipX:
18 | return Object.assign(heroShip, {x: heroShip.x + config.heroShip.speedX})
19 | default:
20 | return heroShip;
21 | }
22 | })
23 | })
24 | .startWith(heroShip);
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | module.exports = function(config) {
4 | var webpackTest = require('./webpack.test.js');
5 | config.set({
6 | frameworks: ['jasmine'],
7 | browsers: [
8 | 'Chrome'
9 | ],
10 | // ... normal karma configuration
11 | files: [
12 | // only specify one entry point
13 | // and require all tests in there
14 | './src/spec-bundle.js'
15 | ],
16 |
17 | preprocessors: {
18 | // add webpack as preprocessor
19 | './src/spec-bundle.js': ['webpack', 'sourcemap']
20 | },
21 |
22 | webpack: webpackTest,
23 |
24 | webpackMiddleware: {
25 | // webpack-dev-middleware configuration
26 | // i. e.
27 | stats: 'errors-only'
28 | },
29 |
30 | // plugins: [
31 | // 'karma-webpack',
32 | // 'karma-chrome-launcher',
33 | // 'karma-sourcemap-loader',
34 | // 'karma-jasmine'
35 | // ]
36 | });
37 | };
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | const ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin;
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | module.exports = {
6 | entry: "./src/index.ts",
7 | output: {
8 | path: path.join(__dirname, 'dist'),
9 | filename: "bundle.js"
10 | },
11 | resolve: {
12 | extensions: ['', '.ts', '.js']
13 | },
14 | devtool: 'source-map',
15 | module: {
16 | loaders: [
17 | // { test: /\.css$/, loader: "style!raw" },
18 | // { test: /\.css$/, loader: "style!css" },
19 | // { test: /\.css$/, loader: "style/url!file" },
20 | { test: /\.ts$/, loader: 'awesome-typescript-loader' }
21 | ]
22 | },
23 | plugins: [
24 | new ForkCheckerPlugin(),
25 | new HtmlWebpackPlugin({
26 | template: 'src/index.html',
27 | chunksSortMode: 'dependency'
28 | })
29 | ]
30 | };
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # @AngularClass
2 |
3 | # Logs
4 | logs
5 | *.log
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
19 | .grunt
20 |
21 | # Compiled binary addons (http://nodejs.org/api/addons.html)
22 | build/Release
23 |
24 | # Users Environment Variables
25 | .lock-wscript
26 |
27 | # OS generated files #
28 | .DS_Store
29 | ehthumbs.db
30 | Icon?
31 | Thumbs.db
32 |
33 | # Node Files #
34 | /node_modules
35 | /bower_components
36 | npm-debug.log
37 |
38 | # Coverage #
39 | /coverage/
40 |
41 | # Typing #
42 | /src/typings/tsd/
43 | /typings/
44 | /tsd_typings/
45 |
46 | # Dist #
47 | /dist
48 | /public/__build__/
49 | /src/*/__build__/
50 | /__build__/**
51 | /public/dist/
52 | /src/*/dist/
53 | /dist/**
54 | .webpack.json
55 |
56 | # Doc #
57 | /doc/
58 |
59 | # IDE #
60 | .idea/
61 | *.swp
62 |
--------------------------------------------------------------------------------
/src/game1/enemyShips.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 | import { iEnemyShip, config } from './shared';
3 |
4 | export const enemyShips$Fac = (enemyShips: iEnemyShip[]): Observable => {
5 | // return Observable.of([])
6 | return Observable.interval(config.enemyShip.dispatchInterval)
7 | .scan((acc, tick) => {
8 | const newEnemy = {
9 | x: Math.random() * config.canvas.width,
10 | y: 0,
11 | directionOnX: Math.random() > 0.5 ? 1 : -1
12 | }
13 | acc.push(newEnemy)
14 | return acc
15 | }, enemyShips)
16 | .startWith(enemyShips)
17 | .switchMap(enemyShips => {
18 | return Observable.interval(config.commons.moveInterval)
19 | .map(tick => {
20 | enemyShips.forEach((enemyShip, index, enemyShips) => {
21 | if(enemyShip.x>config.canvas.width || enemyShip.x < 0) {
22 | enemyShip.directionOnX = enemyShip.directionOnX * -1;
23 | }
24 | enemyShip.x += config.enemyShip.moveSpeed.x * enemyShip.directionOnX;
25 | enemyShip.y += config.enemyShip.moveSpeed.y;
26 | if (enemyShip.y > config.canvas.height + config.enemyShip.halfBottomLength) {
27 | enemyShips.splice(index, 1);
28 | }
29 | })
30 | return enemyShips;
31 | })
32 | })
33 | }
--------------------------------------------------------------------------------
/src/hero-shots.ts:
--------------------------------------------------------------------------------
1 | import {
2 | config,
3 | iHeroShot, iHeroShip,
4 | canvas
5 | } from './constants';
6 | import { Observable } from 'rxjs';
7 |
8 | const mouseClick$ = Observable.fromEvent(canvas, 'click');
9 | const spaceKeyPress$ = Observable.fromEvent(document, 'keypress')
10 | .filter((event: {keyCode: number;}) => {
11 | return event.keyCode === 0 || event.keyCode === 32
12 | })
13 |
14 | export const heroShots$Fac = (heroShip$: Observable, heroShots: iHeroShot[]): Observable => {
15 | return Observable.merge(mouseClick$, spaceKeyPress$)
16 | .throttleTime(100) // at least 100 ms interval between shots
17 | .withLatestFrom(heroShip$, (event, heroShip) => {
18 | return Object.assign({}, heroShip, {collided: false});
19 | })
20 | .scan((acc, heroShot) => {
21 | acc.push(heroShot);
22 | return acc;
23 | }, heroShots)
24 | .startWith(heroShots)
25 | .switchMap(heroShots => {
26 | return Observable.interval(config.heroShot.moveInterval)
27 | .map(tick => {
28 | heroShots.forEach((heroShot, index, arr) => {
29 | heroShot.y -= config.heroShot.speedY;
30 | if (heroShot.y < 0 - config.heroShot.halfBottomLength) {
31 | arr.splice(index, 1);
32 | }
33 | })
34 | return heroShots;
35 | })
36 | })
37 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rx-space",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "postinstall": "tsd install",
8 | "test": "karma start",
9 | "start": "webpack-dev-server --inline --progress --profile --colors --watch --display-error-details --display-cached --content-base src/",
10 | "build": "webpack -p"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/timathon/rx-space.git"
15 | },
16 | "keywords": [],
17 | "author": "",
18 | "license": "ISC",
19 | "bugs": {
20 | "url": "https://github.com/timathon/rx-space/issues"
21 | },
22 | "homepage": "https://github.com/timathon/rx-space#readme",
23 | "dependencies": {
24 | "core-js": "^2.4.1",
25 | "rxjs": "^5.0.0-beta.10",
26 | "typescript": "^1.8.10"
27 | },
28 | "devDependencies": {
29 | "awesome-typescript-loader": "1.1.1",
30 | "file-loader": "^0.9.0",
31 | "html-webpack-plugin": "^2.22.0",
32 | "jasmine-core": "^2.4.1",
33 | "karma": "^1.1.2",
34 | "karma-chrome-launcher": "^1.0.1",
35 | "karma-jasmine": "^1.0.2",
36 | "karma-sourcemap-loader": "^0.3.7",
37 | "karma-webpack": "^1.8.0",
38 | "lodash": "^4.15.0",
39 | "raw-loader": "^0.5.1",
40 | "style-loader": "^0.13.1",
41 | "webpack": "^1.13.2",
42 | "webpack-dev-server": "^1.14.1"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/game1/heroShots.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 | import { iHeroShip, iHeroShot, canvas, config } from './shared';
3 |
4 | export const heroShots$Fac = (heroShots: iHeroShot[], heroShip$: Observable): Observable => {
5 | return Observable.fromEvent(canvas, 'click')
6 | .throttleTime(100)
7 | .withLatestFrom(heroShip$, (event, heroShip) => heroShip)
8 | .scan((acc, heroShip) => {
9 | acc.push(Object.assign({}, heroShip))
10 | return acc
11 | }, heroShots)
12 | .startWith(heroShots)
13 | .switchMap(heroShots => {
14 | return Observable.interval(config.commons.moveInterval)
15 | .map(tick => {
16 | // return heroShots.reduce((newHeroShots, heroShot) => {
17 | // heroShot.y -= config.heroShot.moveSpeed.y;
18 | // if (heroShot.y >= 0 - config.heroShot.halfBottomLength) {
19 | // newHeroShots.push(Object.assign({}, heroShot));
20 | // }
21 | // return newHeroShots;
22 | // }, []);
23 | heroShots.forEach((heroShot, index, heroShots) => {
24 | heroShot.y -= config.heroShot.moveSpeed.y;
25 | if (heroShot.y <= 0 - config.heroShot.halfBottomLength) {
26 | heroShots.splice(index, 1);
27 | }
28 | })
29 | return heroShots
30 | })
31 | })
32 | .startWith(heroShots)
33 | }
34 |
--------------------------------------------------------------------------------
/src/stars.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 | import { config, iStar } from './constants';
3 |
4 | const starsInit$Fac = (stars: iStar[]): Observable => {
5 | if (stars.length === 0) { // at START_CLICKED, generate a starsInit$
6 | return Observable.range(1, config.stars.count)
7 | .map(item => {
8 | const size = 1 + Math.random() * 1.5;
9 | const speed = size > 1.6 ? 0.25 : 0.05;
10 | const color = '#ffffff'; // whilte
11 | return {
12 | x: Math.random() * config.canvas.width,
13 | y: Math.random() * config.canvas.height,
14 | size: size,
15 | speed: speed,
16 | color: color
17 | }
18 | })
19 | .toArray()
20 | }
21 | if (stars.length > 0) { // at CONTINUE_CLICKED, use the stars in the game state
22 | return Observable.of(stars);
23 | }
24 | }
25 |
26 | const starsMove$Fac = (stars: iStar[]): Observable => {
27 | return Observable.interval(config.stars.moveInterval)
28 | .map(tick => {
29 | stars.forEach(star => {
30 | star.y += star.speed
31 | if (star.y > config.canvas.height) {
32 | star.y -= config.canvas.height;
33 | }
34 | const shouldChangeColor = Math.random() > 0.95 ? true : false; // the probability of executing color change is 5%; but still color may remain the same;
35 | star.color = shouldChangeColor ? config.stars.colors[
36 | Math.floor(Math.random() * config.stars.colors.length)
37 | ] : star.color;
38 | })
39 | return stars
40 | })
41 | .startWith(stars); // begin emitting stars at time 0, not at (0 + config.stars.moveInterval)
42 | }
43 |
44 | export const stars$Fac = (stars: iStar[]): Observable => {
45 | return starsInit$Fac(stars)
46 | .mergeMap(starsMove$Fac)
47 | }
48 |
--------------------------------------------------------------------------------
/src/game1/renderer.ts:
--------------------------------------------------------------------------------
1 | import {
2 | config,
3 | iGame, iStar, iHeroShip, iHeroShot, iEnemyShip,
4 | ctx, canvas
5 | } from './shared';
6 |
7 |
8 | const renderStars = (stars: iStar[]): void => {
9 | // fill canvas with black
10 | ctx.fillStyle = '#000000';
11 | ctx.fillRect(0, 0, canvas.width, canvas.height);
12 |
13 | // draw stars
14 | stars.forEach(star => {
15 | ctx.fillStyle = star.color;
16 | ctx.fillRect(star.x, star.y, star.size, star.size);
17 | })
18 | }
19 |
20 | const drawTriangle = (point: {x: number; y: number}, halfBottomLength: number, color: string, up: boolean): void => {
21 | ctx.fillStyle = color;
22 | ctx.beginPath();
23 | ctx.moveTo(point.x, point.y); // begin with the point
24 | ctx.lineTo(point.x - halfBottomLength, up ? point.y + halfBottomLength : point.y - halfBottomLength); // to left_down or left_up
25 | ctx.lineTo(point.x + halfBottomLength, up ? point.y + halfBottomLength : point.y - halfBottomLength); // to right
26 | ctx.lineTo(point.x, point.y); // to the point, the last line is not necessary for filling the color
27 | ctx.fill();
28 | }
29 |
30 | const renderHeroShip = (heroShip: iHeroShip): void => {
31 | drawTriangle(heroShip, config.heroShip.halfBottomLength, config.heroShip.color, true)
32 | }
33 |
34 | const renderHeroShots = (heroShots: iHeroShot[]): void => {
35 | heroShots.forEach(heroShot => {
36 | drawTriangle(heroShot, config.heroShot.halfBottomLength, config.heroShot.color, true)
37 | })
38 |
39 | }
40 |
41 | const renderEnemyShips = (enemyShips: iEnemyShip[]): void => {
42 | enemyShips.forEach(enemyShip => {
43 | drawTriangle(enemyShip, config.enemyShip.halfBottomLength, config.enemyShip.color, false)
44 | })
45 | }
46 |
47 | export const renderer = (game: iGame) => {
48 | renderStars(game.actors.stars);
49 | renderHeroShip(game.actors.heroShip);
50 | renderHeroShots(game.actors.heroShots);
51 | renderEnemyShips(game.actors.enemyShips);
52 | }
--------------------------------------------------------------------------------
/src/enemy-ships.ts:
--------------------------------------------------------------------------------
1 | import {
2 | config,
3 | iEnemyShip
4 | } from './constants';
5 | import { Observable } from 'rxjs';
6 |
7 | export const enemyShips$Fac = (enemyShips: iEnemyShip[]): Observable => {
8 | return Observable.interval(config.enemyShip.generationInterval)
9 | .scan((acc, genTick) => {
10 | let shouldGenerate = false;
11 | let speed = config.enemyShip.speedYInit;
12 | switch(true) {
13 | case genTick < 30:
14 | if (Math.random() > 0.4) { // 60% chance, there will be a new enemyShip on each prodTick
15 | shouldGenerate = true;
16 | }
17 | break;
18 | case genTick >=30 && genTick < 90:
19 | if (Math.random() > 0.2) { // 80% chance, there will be a new enemyShip on each prodTick
20 | shouldGenerate = true;
21 | speed *= 1.3;
22 | }
23 | break;
24 | case genTick >= 90:
25 | shouldGenerate = true; // 100% chance, there will be a new enemyShip on each prodTick
26 | speed *= 2;
27 | break;
28 | default:
29 | break;
30 | }
31 |
32 | if (shouldGenerate) {
33 | let newEnemyShip = {
34 | x: Math.random() * config.canvas.width,
35 | y: 0,
36 | speed: speed,
37 | collided: false
38 | }
39 | return [...acc, newEnemyShip]
40 | } else {
41 | return [...acc]
42 | }
43 | }, enemyShips)
44 | .startWith(enemyShips)
45 | .switchMap(enemyShips => {
46 | return Observable.interval(config.enemyShip.moveInterval)
47 | .map(moveTick => {
48 | enemyShips.forEach((enemyShip, index, arr) => {
49 | enemyShip.y += enemyShip.speed;
50 | if (enemyShip.y > config.canvas.height + config.enemyShip.halfBottomLength) {
51 | arr.splice(index, 1);
52 | }
53 | });
54 | return enemyShips;
55 | })
56 | })
57 |
58 | }
--------------------------------------------------------------------------------
/src/game.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Observable } from 'rxjs';
3 | import {
4 | config,
5 | iGame,
6 | PAUSE, PAUSE_CLICKED, CONTINUE, CONTINUE_CLICKED, RESTART,
7 | startButton, pauseButton
8 | } from './constants';
9 | import onContinue$Fac from './on-continue'
10 |
11 | const gameInit: iGame = {
12 | val: 0,
13 | actors: {
14 | stars: [],
15 | heroShip: { x: config.canvas.width / 2, y: config.canvas.height - 30, collided: false },
16 | heroShots: [],
17 | enemyShips: [],
18 | enemyShots: []
19 | },
20 | running: true
21 | }
22 |
23 | const pauseButtonClick$ = Observable.fromEvent(pauseButton, 'click')
24 |
25 |
26 | const tabVisibilityChange$ = Observable.fromEvent(document, 'visibilitychange')
27 | .filter(event => document.visibilityState === 'hidden')
28 |
29 | const pauseContinueEvent$ = Observable.merge(pauseButtonClick$, tabVisibilityChange$)
30 | .scan((lastEvent, event) => {
31 | switch (lastEvent) {
32 | case CONTINUE_CLICKED:
33 | pauseButton.textContent = CONTINUE;
34 | return PAUSE_CLICKED;
35 | case PAUSE_CLICKED:
36 | pauseButton.textContent = PAUSE;
37 | return CONTINUE_CLICKED;
38 | default:
39 | break;
40 | }
41 | }, CONTINUE_CLICKED)
42 | .startWith(CONTINUE_CLICKED)
43 |
44 |
45 | const onPause$Fac = (game: iGame) => {
46 | return Observable.of(game);
47 | }
48 |
49 | export const game$: Observable = Observable.fromEvent(startButton, 'click')
50 | .switchMap(event => {
51 | /* init setup - start */
52 | startButton.textContent = RESTART;
53 | pauseButton.textContent = PAUSE;
54 | pauseButton.disabled = false;
55 | gameInit.startTime = new Date(Date.now());
56 | let game = gameInit;
57 | /* init setup - end */
58 | return pauseContinueEvent$
59 | .switchMap(event => {
60 | switch (event) {
61 | case CONTINUE_CLICKED:
62 | return onContinue$Fac(game);
63 | case PAUSE_CLICKED:
64 | return onPause$Fac(game);
65 | // default:
66 | // return Observable.of(game);
67 | }
68 | });
69 | })
70 |
--------------------------------------------------------------------------------
/src/game1/on-continue.ts:
--------------------------------------------------------------------------------
1 | import { config, iGame, iStar, iHeroShip, isCollided } from './shared';
2 | import { Observable, BehaviorSubject } from 'rxjs/Rx';
3 | import { stars$Fac } from './stars';
4 | import { heroShip$Fac } from './heroShip';
5 | import { heroShots$Fac } from './heroShots';
6 | import { enemyShips$Fac } from './enemyShips';
7 |
8 | export const onContinue$Fac = (game: iGame): Observable => {
9 | const stars$ = stars$Fac(game.actors.stars);
10 | const heroShip$ = heroShip$Fac(game.actors.heroShip);
11 | const heroShots$ = heroShots$Fac(game.actors.heroShots, heroShip$);
12 | const enemyShips$ = enemyShips$Fac(game.actors.enemyShips);
13 | return Observable.combineLatest(
14 | stars$,
15 | heroShip$,
16 | heroShots$,
17 | enemyShips$,
18 | (stars, heroShip, heroShots, enemyShips) => {
19 | // check collide between heroShip and enemyShips
20 | enemyShips.forEach((enemyShip, index, enemyShips) => {
21 | const enemyShipLeftTip = {x: enemyShip.x - config.enemyShip.halfBottomLength, y: enemyShip.y - config.enemyShip.halfBottomLength}
22 | const enemyShipRightTip = {x: enemyShip.x + config.enemyShip.halfBottomLength, y: enemyShip.y - config.enemyShip.halfBottomLength}
23 | if (
24 | isCollided(heroShip, enemyShip) || isCollided(heroShip, enemyShipLeftTip) || isCollided(heroShip, enemyShipRightTip)
25 | ) {
26 | game.gameOver = true;
27 | }
28 | })
29 | // check collide between heroShots and enemyShips
30 | heroShots.forEach((heroShot, index, heroShots) => {
31 | enemyShips.forEach((enemyShip, jindex, enemyShips) => {
32 | if (isCollided(heroShot, enemyShip)) {
33 | enemyShips.splice(jindex, 1);
34 | heroShots.splice(index, 1);
35 | }
36 | })
37 | })
38 | return {
39 | actors: {
40 | stars,
41 | heroShip,
42 | heroShots,
43 | enemyShips
44 | },
45 | paused: game.paused,
46 | firstRun: game.firstRun,
47 | gameOver: game.gameOver
48 | }
49 | }
50 | )
51 |
52 |
53 | }
--------------------------------------------------------------------------------
/src/renderer.ts:
--------------------------------------------------------------------------------
1 | import {
2 | config,
3 | iGame, iStar, iHeroShip, iHeroShot, iEnemyShip, iEnemyShot,
4 | ctx, canvas
5 | } from './constants';
6 |
7 |
8 | const renderStars = (stars: iStar[]): void => {
9 | // fill canvas with black
10 | ctx.fillStyle = '#000000';
11 | ctx.fillRect(0, 0, canvas.width, canvas.height);
12 |
13 | // draw stars
14 | stars.forEach(star => {
15 | ctx.fillStyle = star.color;
16 | ctx.fillRect(star.x, star.y, star.size, star.size);
17 | })
18 | }
19 |
20 | const drawTriangle = (point: {x: number; y: number}, halfBottomLength: number, color: string, up: boolean): void => {
21 | ctx.fillStyle = color;
22 | ctx.beginPath();
23 | ctx.moveTo(point.x, point.y); // begin with the point
24 | ctx.lineTo(point.x - halfBottomLength, up ? point.y + halfBottomLength : point.y - halfBottomLength); // to left_down or left_up
25 | ctx.lineTo(point.x + halfBottomLength, up ? point.y + halfBottomLength : point.y - halfBottomLength); // to right
26 | ctx.lineTo(point.x, point.y); // to the point
27 | ctx.fill();
28 | }
29 |
30 | const renderHeroShip = (heroShip: iHeroShip): void => {
31 | drawTriangle(heroShip, config.heroShip.halfBottomLength, config.heroShip.color, true);
32 | }
33 |
34 | const renderHeroShots = (heroShots: iHeroShot[]): void => {
35 | if (heroShots.length > 0) {
36 | heroShots.forEach(heroShot => {
37 | drawTriangle(heroShot, config.heroShot.halfBottomLength, config.heroShot.color, true);
38 | })
39 | }
40 |
41 | }
42 |
43 | const renderEnemyShips = (enemyShips: iEnemyShip[]): void => {
44 | if (enemyShips.length > 0) {
45 | enemyShips.forEach(enemyShip => {
46 | drawTriangle(enemyShip, config.enemyShip.halfBottomLength, config.enemyShip.color, false);
47 | })
48 | }
49 | }
50 |
51 | const renderEnemyShots = (enemyShots: iEnemyShot[]): void => {
52 | if (enemyShots.length > 0) {
53 | enemyShots.forEach(enemyShot => {
54 | drawTriangle(enemyShot, config.enemyShot.halfBottomLength, config.enemyShot.color, false);
55 | })
56 | }
57 | }
58 |
59 | export default (game: iGame): void => {
60 | renderStars(game.actors.stars);
61 | renderHeroShip(game.actors.heroShip);
62 | renderHeroShots(game.actors.heroShots);
63 | renderEnemyShips(game.actors.enemyShips);
64 | renderEnemyShots(game.actors.enemyShots);
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/src/enemy-shots.ts:
--------------------------------------------------------------------------------
1 | import {
2 | config,
3 | iEnemyShot,
4 | iEnemyShip,
5 | } from './constants'
6 |
7 | import { Observable } from 'rxjs';
8 |
9 |
10 | export const enemyShots$Fac = (enemyShips$: Observable, enemyShots: iEnemyShot[]): Observable => {
11 | return Observable.interval(config.enemyShot.generationInterval)
12 | .withLatestFrom(enemyShips$)
13 | .scan((acc, combo) => {
14 | let genTick = combo[0];
15 | let enemyShips = combo[1];
16 | if (enemyShips.length === 0) {return [...acc]}
17 |
18 | let shouldGenerate = false;
19 | let speed = config.enemyShot.speedYInit;
20 |
21 | switch(true) {
22 | case genTick < 30:
23 | if (Math.random() > 0.4) { // 60% chance, there will be a new enemyShip on each prodTick
24 | shouldGenerate = true;
25 | }
26 | break;
27 | case genTick >=30 && genTick < 90:
28 | if (Math.random() > 0.2) { // 80% chance, there will be a new enemyShip on each prodTick
29 | shouldGenerate = true;
30 | speed *= 1.3;
31 | }
32 | break;
33 | case genTick >= 90:
34 | shouldGenerate = true; // 100% chance, there will be a new enemyShip on each prodTick
35 | speed *= 2;
36 | break;
37 | default:
38 | break;
39 | }
40 |
41 | if (shouldGenerate) {
42 | let shootingEnemyShip = enemyShips[Math.floor(Math.random()*enemyShips.length)]
43 | let newEnemyShot = {
44 | x: shootingEnemyShip.x,
45 | y: shootingEnemyShip.y,
46 | speed: speed,
47 | collided: false
48 | }
49 | acc.push(newEnemyShot);
50 | return [...acc, newEnemyShot]
51 | } else {
52 | return [...acc]
53 | }
54 | }, enemyShots)
55 | .switchMap(enemyShots => {
56 | return Observable.interval(config.enemyShot.moveInterval)
57 | .map(moveTick => {
58 | enemyShots.forEach((enemyShot, index, arr) => {
59 | enemyShot.y += enemyShot.speed;
60 | if (enemyShot.y > config.canvas.height + config.enemyShot.halfBottomLength) {
61 | arr.splice(index, 1);
62 | }
63 | })
64 | return enemyShots;
65 | })
66 | })
67 | .startWith(enemyShots);
68 | }
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | export interface iPos {
2 | x: number;
3 | y: number;
4 | }
5 |
6 | export interface iStar extends iPos {
7 | size: number;
8 | speed: number;
9 | color: string;
10 | }
11 |
12 | export interface iHeroShip extends iPos {
13 | collided: boolean;
14 | }
15 |
16 | export interface iHeroShot extends iPos {
17 | collided: boolean;
18 | }
19 |
20 | export interface iEnemyShip extends iPos {
21 | speed: number;
22 | collided: boolean;
23 | }
24 |
25 | export interface iEnemyShot extends iPos {
26 | speed: number;
27 | collided: boolean;
28 | }
29 |
30 |
31 | export interface iGame {
32 | val?: number;
33 | actors: {
34 | stars: iStar[];
35 | heroShip: iHeroShip;
36 | heroShots: iHeroShot[];
37 | enemyShips: iEnemyShip[];
38 | enemyShots: iEnemyShot[];
39 | };
40 | startTime?: Date;
41 | running: boolean;
42 |
43 | }
44 |
45 | export const START = 'START';
46 | export const RESTART = 'RESTART';
47 | export const PAUSE = 'PAUSE';
48 | export const CONTINUE = 'CONTINUE';
49 | export const PAUSE_CLICKED = 'PAUSE_CLICKED';
50 | export const CONTINUE_CLICKED = 'CONTINUE_CLICKED';
51 |
52 | export const config = {
53 | canvas: {
54 | width: 300,
55 | height: 300,
56 | refreshInterval: 5
57 | },
58 | stars: {
59 | moveInterval: 30,
60 | count: 150,
61 | colors: [
62 | // '#000000',
63 | // '#191919',
64 | // '#323232',
65 | // '#4c4c4c',
66 | // '#666666',
67 | // '#7f7f7f',
68 | // '#999999',
69 | '#b2b2b2',
70 | '#cccccc',
71 | // '#e5e5e5',
72 | '#ffffff'
73 | ]
74 | },
75 | heroShip: {
76 | halfBottomLength: 20,
77 | color: '#2c6ed0',
78 | moveInterval: 5,
79 | speedX: 1
80 | },
81 | heroShot: {
82 | halfBottomLength: 8,
83 | color: '#2c6ed0',
84 | moveInterval: 5,
85 | speedY: 0.8
86 | },
87 | enemyShip: {
88 | generationInterval: 1000,
89 | halfBottomLength: 12,
90 | color: '#ffd700',
91 | moveInterval: 5,
92 | speedYInit: 0.3
93 | },
94 | enemyShot: {
95 | generationInterval: 500,
96 | halfBottomLength: 5,
97 | color: '#ffd700',
98 | moveInterval: 5,
99 | speedYInit: 0.5
100 | }
101 | }
102 |
103 |
104 | export const container = document.querySelector('#container');
105 | container.setAttribute('width', config.canvas.width + 'px');
106 | container.setAttribute('height', config.canvas.height + 'px');
107 |
108 |
109 | export const canvas = document.createElement('canvas');
110 | export const ctx = canvas.getContext('2d');
111 | container.appendChild(canvas);
112 | canvas.width = config.canvas.width;
113 | canvas.height = config.canvas.height;
114 |
115 | export const startButton = document.querySelector('#start');
116 | export const pauseButton = document.querySelector('#pause');
117 |
--------------------------------------------------------------------------------
/src/on-continue.ts:
--------------------------------------------------------------------------------
1 | import { iGame, iPos, iHeroShot, iEnemyShip, iHeroShip, config } from './constants';
2 | import { Observable, Subject, BehaviorSubject } from 'rxjs';
3 | import { stars$Fac } from './stars';
4 | import { heroShip$Fac } from './hero-ship';
5 | import { heroShots$Fac } from './hero-shots';
6 | import { enemyShips$Fac } from './enemy-ships';
7 | import { enemyShots$Fac } from './enemy-shots';
8 |
9 | const collided = (a: iPos, b: iPos): boolean => {
10 | return (Math.abs(a.x - b.x) < 10 && Math.abs(a.y - b.y) < 1) ? true : false;
11 | }
12 |
13 | const testCollision = (game: iGame): iGame => {
14 | let heroShots = game.actors.heroShots;
15 | let enemyShips = game.actors.enemyShips;
16 | let heroShip = game.actors.heroShip;
17 | let enemyShots = game.actors.enemyShots;
18 |
19 | // test each heroShot with each enemyShip
20 | for(let i = 0; i < heroShots.length; i++) {
21 | for(let j = 0; j < enemyShips.length; j++) {
22 | if (collided(heroShots[i], enemyShips[j])) {
23 | heroShots[i].collided = true;
24 | enemyShips[j].collided = true;
25 | break;
26 | }
27 | }
28 | }
29 |
30 | heroShots.forEach((heroShot, index, arr) => {
31 | if (heroShot.collided) {
32 | arr.splice(index, 1);
33 | }
34 | });
35 |
36 | enemyShips.forEach((enemyShip, index, arr) => {
37 | if (enemyShip.collided) {
38 | arr.splice(index, 1);
39 | }
40 | });
41 |
42 | // test each enemyShip with heroShip
43 | for(let i = 0; i < enemyShips.length; i++) {
44 | if (!enemyShips[i].collided) {
45 | if(collided(enemyShips[i], heroShip)) {
46 | enemyShips[i].collided = true;
47 | heroShip.collided = true;
48 | console.log('game over');
49 | }
50 | }
51 | }
52 |
53 | // test each enemyShot with heroShip
54 | for(let i = 0; i < enemyShots.length; i++) {
55 | if (!enemyShots[i].collided) {
56 | if(collided(enemyShots[i], heroShip)) {
57 | enemyShots[i].collided = true;
58 | heroShip.collided = true;
59 | console.log('game over');
60 | }
61 | }
62 | }
63 |
64 | return game;
65 | }
66 |
67 | export default (game: iGame): Observable => {
68 | const heroShip$ = >(new BehaviorSubject(game.actors.heroShip));
69 | heroShip$Fac(game.actors.heroShip).subscribe(heroShip$);
70 | const enemyShips$ = >(new Subject());
71 | enemyShips$Fac(game.actors.enemyShips).subscribe(enemyShips$);
72 | return Observable.combineLatest(
73 | stars$Fac(game.actors.stars),
74 | heroShip$,
75 | heroShots$Fac(heroShip$, game.actors.heroShots),
76 | enemyShips$,
77 | enemyShots$Fac(enemyShips$, game.actors.enemyShots),
78 | (stars, heroShip, heroShots, enemyShips, enemyShots) => {
79 | return Object.assign(game, {
80 | actors: {
81 | stars,
82 | heroShip,
83 | heroShots,
84 | enemyShips,
85 | enemyShots
86 | }
87 | })
88 | }
89 | )
90 | .map(game => {
91 | return testCollision(game);
92 | })
93 | .throttleTime(config.canvas.refreshInterval)
94 | };
--------------------------------------------------------------------------------
/screencast_script.md:
--------------------------------------------------------------------------------
1 | RxJS - Space Shooting - Part 0
2 |
3 | part 1 - reading the manual of rxjs
4 |
5 | V5.0
6 | manual:
7 | http://reactivex.io/rxjs/manual/overview.html
8 | testing:
9 | https://github.com/ReactiveX/rxjs/blob/master/doc/writing-marble-tests.md
10 | github:
11 | https://github.com/ReactiveX/rxjs
12 |
13 | V4.0:
14 | https://github.com/Reactive-Extensions/RxJS/tree/master/doc
15 | testing part for V4.0
16 | https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/testing.md
17 | github:
18 | https://github.com/Reactive-Extensions/RxJS
19 |
20 | the marbles:
21 | http://rxmarbles.com/
22 | the book - (V4.0) Reactive Programming with RxJS Untangle Your Asynchronous JavaScript Code - by Sergi Mansilla
23 | https://pragprog.com/titles/smreactjs/reactive-programming-with-rxjs
24 | the video - (V5.0) Extreme Streams: The What, How and Why of Observables - by Alex Wilmer
25 | https://www.youtube.com/watch?v=zAWB3lPixtk
26 |
27 |
28 |
29 | Part 2 - Development Environment and Tooling
30 |
31 | OS: windows 10
32 | Browser: Chrome
33 | Javascript Runtime: Node.js @ nodejs.org/en
34 | Version Control: Git @ git-scrm.com + Github
35 | Code Editor: VSCode @ code.visualstudio.com
36 |
37 | Module Bundler: Webpack
38 | Language: typescript 2 (setup VSCode to use typescript 2 @types)
39 | Test Framework and Runner: Jasmine + Karma
40 |
41 | Setup according to official doc and try to keep the setup at minimum.
42 |
43 |
44 |
45 |
46 |
47 |
48 | 2.1 - Git and Github
49 | -A) the book - Pro Git - git-scm.com/book/en/v2
50 |
51 | A) Use Git/Github to store files remotely
52 | 0) three trees (Chapter 7.7)
53 | 1) sign up in github
54 | 2) create new repo online
55 | 3) clone the repo
56 | 4) modify a file, git add, git commit, set up user.name and user.email, log in, git push
57 | 4.1) modify a file, git commit -a -m, git push
58 | 5) click over github
59 | 6) use Git in VSCode(show git output), edit, stage, check diff, commit, reset, sync
60 |
61 |
62 | B) Use Git/Github to follow along part 3 of my tutorial
63 | 1) branch (Chapter 3.1),
64 | 2) create branch, checkout branch
65 | git log --oneline --decorate --all
66 | 3) the main purpose of branches is to enable code to evolve stably
67 | present a simple 'git merge' example
68 | git log --oneline --decorate --all --graph
69 |
70 |
71 |
72 | Save and exit VIM by pressing esc and :wq and enter.
73 | logout: git credential-manager clear https://github.com
74 | REMOVE USER.NAME: git config --global --unset-all user.name
75 |
76 |
77 |
78 | 3.1 NPM Intro
79 |
80 | Part 2.3.1 - NPM - 'npm init' and 'npm install'
81 | Our code exists in a node package, we manage the meta data of this package by package.json.
82 | And we will use packages/modules from others, we install these packages using npm.
83 |
84 | NPM Docs:
85 | https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging
86 | Getting started with npm - Youtube Playlist:
87 | https://www.youtube.com/playlist?list=PLQso55XhxkgBMeiYmFEHzz1axDUBjTLC6
88 |
89 | npm init
90 | intall new package with --save and --save-dev
91 | run command line tool of a package, without install the package globally, 2 approaches
92 |
93 | Part 2.3.2 - NPM - 'scripts' in 'package.json'
94 | scripts:
95 | start
96 | prestart in package.json
97 | poststart
98 |
99 | Part 2.3.3 - NPM - 'npm install' with some ready-made package
100 | clone repo and install all the packages
101 |
102 |
103 | 4.1 Typescript Intro
104 | quick start
105 | basic types, type assertion<>
106 | const declaration
107 | Interfaces, extends,
108 | Generics type parameter
109 |
110 | tsconfig.json
111 | tsc --init
112 | noImplicitAny: true
113 |
114 | modules
115 | @types and run in node, and run in browser, fail, then use webpack
116 |
117 | 5.1 Webpack Intro
118 | webpack.config.js
119 |
120 |
121 |
122 | 6.1 Jasmine + Karma Intro
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | part 3 - coding
136 | 3.0 the book, best practices(immutable)
137 | 3.1 review 1.1
138 | 3.2 index.html, interfaces and other structures
139 | 3.2 stars field
140 | 3.3 hero ship and hero shots
141 | 3.4 enemy ships and enemy shots
142 | 3.5 collides
143 | 3.6 scores
144 |
145 | adding an actor:
146 | 1) interface
147 | 2) config
148 | 3) actorsInit
149 | 4) onContinue, 5 places
150 | 5) renderer, 2 places
151 |
152 | canvas 101:
153 | https://www.youtube.com/watch?v=RV3SaSH8lw0
154 | https://www.youtube.com/watch?v=MV_ITkqLzik
--------------------------------------------------------------------------------
/src/game1/shared.ts:
--------------------------------------------------------------------------------
1 | import { BehaviorSubject, Observable } from 'rxjs';
2 | import { onContinue$Fac } from './on-continue';
3 |
4 | export interface iPosition {
5 | x: number;
6 | y: number;
7 | }
8 |
9 | export interface iStar extends iPosition{
10 | size: number;
11 | speed: number;
12 | color: string;
13 | }
14 |
15 | export interface iHeroShip extends iPosition {
16 |
17 | }
18 |
19 | export interface iHeroShot extends iPosition {
20 |
21 | }
22 |
23 | export interface iEnemyShip extends iPosition {
24 | directionOnX: number;
25 | }
26 |
27 | export interface iActors {
28 | stars: iStar[];
29 | heroShip: iHeroShip;
30 | heroShots: iHeroShot[];
31 | enemyShips: iEnemyShip[];
32 | }
33 |
34 | export interface iGame {
35 | actors: iActors;
36 | paused: boolean;
37 | firstRun: boolean;
38 | gameOver: boolean;
39 | }
40 |
41 |
42 | export const config = {
43 | commons: {
44 | moveInterval: 5,
45 | collisionDist: 6
46 | },
47 | canvas: {
48 | width: 300,
49 | height: 300,
50 | margin: 10
51 | },
52 | star: {
53 | count: 100,
54 | moveInterval: 20,
55 | moveSpeed: {x: 0, y: 0.5}
56 | },
57 | heroShip: {
58 | halfBottomLength: 20,
59 | color: '#2c6ed0',
60 | moveSpeed: {x: 2, y: 0}
61 | },
62 | heroShot: {
63 | halfBottomLength: 8,
64 | color: '#2c6ed0',
65 | moveSpeed: {x: 0, y: 1}
66 | },
67 | enemyShip: {
68 | halfBottomLength: 12,
69 | color: '#ffd700',
70 | moveSpeed: {x: 0.2, y: 0.4},
71 | dispatchInterval: 1000
72 | },
73 |
74 | }
75 |
76 |
77 | const starsInit = (): iStar[] => {
78 | return Array.from({length: config.star.count}, (v, i) => {
79 | return {
80 | x: Math.random() * config.canvas.width,
81 | y: Math.random() * config.canvas.height,
82 | size: 1 + Math.random() * 1.5,
83 | speed: [0.25, 0.05][Math.floor(Math.random()*2)],
84 | color: '#ffffff' // whilte
85 | }
86 | })
87 | }
88 |
89 | export const actorsInit = (): iActors => {
90 | return {
91 | stars: starsInit(),
92 | heroShip: {
93 | x: config.canvas.width/2,
94 | y: config.canvas.height-30,
95 | },
96 | heroShots: [],
97 | enemyShips: []
98 | }
99 | }
100 |
101 | const gameContainer = document.createElement('div');
102 |
103 | export const startButton = document.createElement('input');
104 | startButton.value = 'START';
105 | startButton.setAttribute("type", "button");
106 | startButton.style.width = '100px';
107 | startButton.style.margin = '10px';
108 | gameContainer.appendChild(startButton);
109 |
110 | export const pauseButton = document.createElement('input');
111 | pauseButton.disabled = true;
112 | pauseButton.value = 'PAUSE';
113 | pauseButton.setAttribute("type", "button");
114 | pauseButton.style.width = '100px';
115 | gameContainer.appendChild(pauseButton);
116 |
117 | const canvasContainer = document.createElement('div');
118 | canvasContainer.style.margin = config.canvas.margin + 'px';
119 | export const canvas = document.createElement('canvas');
120 | export const ctx = canvas.getContext('2d');
121 | canvasContainer.appendChild(canvas);
122 | canvas.width = config.canvas.width;
123 | canvas.height = config.canvas.height;
124 | gameContainer.appendChild(canvasContainer);
125 |
126 | document.body.appendChild(gameContainer);
127 |
128 | export const gameState$$: BehaviorSubject = new BehaviorSubject({
129 | actors: actorsInit(),
130 | paused: true,
131 | firstRun: true,
132 | gameOver: false
133 | });
134 |
135 | export const gameRun$Fac = (gameState$$: BehaviorSubject): Observable => {
136 | return Observable.fromEvent(startButton, 'click')
137 | .switchMap(event => {
138 | startButton.value = 'RESTART';
139 | pauseButton.disabled = false;
140 | const game = gameState$$.getValue();
141 | pauseButton.disabled = false;
142 |
143 | if (game.firstRun) {
144 | game.firstRun = false;
145 | } else {
146 | game.actors = actorsInit();
147 | game.paused = true;
148 | game.gameOver = false;
149 | }
150 |
151 | const gameOver$ = gameState$$.pluck('gameOver').distinct().filter(gameOver => gameOver===true);
152 | const pauseClick$ = Observable.fromEvent(pauseButton, 'click')
153 | return Observable.merge(gameOver$, pauseClick$)
154 | .startWith('whatever')
155 | .switchMap(event => {
156 | switch(true) {
157 | case event===true:
158 | pauseButton.value = 'GAME OVER';
159 | pauseButton.disabled = true;
160 | return Observable.of(game)
161 | default:
162 | game.paused = !game.paused;
163 | switch (game.paused) {
164 | case true:
165 | pauseButton.value = 'CONTINUE';
166 | return Observable.of(game);
167 | case false:
168 | pauseButton.value = 'PAUSE';
169 | return onContinue$Fac(game);
170 | }
171 | }
172 | })
173 | })
174 | }
175 |
176 | export const isCollided = (a: iPosition, b: iPosition): boolean => {
177 | const dist = Math.pow(Math.pow((a.x - b.x), 2) + Math.pow((a.y - b.y), 2), 0.5)
178 | if( dist > config.commons.collisionDist ) {
179 | return false
180 | } else {
181 | return true
182 | }
183 | }
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // import { game$ } from './game';
2 | // import renderer from './renderer';
3 | // game$.subscribe(renderer);
4 |
5 | // import { Observable, BehaviorSubject } from 'rxjs';
6 | // import { gameState$$, gameRun$Fac } from './game1/shared';
7 | // import { renderer } from './game1/renderer';
8 |
9 | // gameState$$.subscribe(renderer);
10 |
11 | // gameRun$Fac(gameState$$).subscribe(gameState$$);
12 |
13 |
14 | // const observer = {
15 | // next: (val: any) => {console.log(val)},
16 | // complete: () => {},
17 | // error: () => {}
18 | // }
19 |
20 | // const observable = (observer: any) => {
21 | // observer.next('rxjs');
22 | // setTimeout(() => {observer.next('hiking 1')}, 0);
23 | // observer.next('hiking 2');
24 | // }
25 |
26 | // observable(observer);
27 |
28 | // const observable = {
29 | // subscribe: (observer: any) => {
30 | // observer.next('rxjs');
31 | // }
32 | // }
33 |
34 | // observable.subscribe(observer);
35 |
36 | // import { Observable, Subject, BehaviorSubject, ReplaySubject, AsyncSubject, Subscription } from 'rxjs';
37 |
38 | // const button = document.createElement('button');
39 | // document.body.appendChild(button);
40 | // button.textContent = 'click me';
41 |
42 | // const subscription = Observable.fromEvent(button, 'click')
43 | // .map((event: {clientX: number}) => event.clientX)
44 | // .subscribe((val) => console.log(val));
45 |
46 | // const observableX$ = Observable.create((observer: any) => {
47 | // observer.next(1);
48 | // observer.next(2);
49 | // setTimeout(() => {
50 | // observer.next(3);
51 | // }, 3000)
52 | // })
53 |
54 | // observableX$.subscribe((val: any) => {
55 | // console.log(`observerA receives ${val} at ${Math.floor(Date.now()/1000)}`)
56 | // })
57 |
58 | // setTimeout(() => {
59 | // observableX$.subscribe((val: any) => {
60 | // const moment = Date.now()/1000;
61 | // console.log(`observerB receives ${val} at ${Math.floor(Date.now()/1000)}`)
62 | // })
63 | // }, 2000);
64 |
65 | // actual time: = --------~ =--------~ = --------~ =--------~ =--------~ =--------~
66 | // observableX$: (1, 2)--------~ =--------~ = --------~ 3----...
67 | // subscriptionA: (1, 2)--------~ =--------~ = --------~ 3----...
68 | // subscriptionB: (1, 2)--------~ =--------~ =--------~ 3----...
69 |
70 | // const subject$$ = new Subject();
71 |
72 | // subject$$.subscribe(val=> console.log(`observerC receives ${val} at ${Math.floor(Date.now()/1000)}`));
73 |
74 | // setTimeout(() => {
75 | // subject$$.subscribe(val=> console.log(`observerD receives ${val} at ${Math.floor(Date.now()/1000)}`));
76 | // }, 2000)
77 |
78 | // observableX$.subscribe(subject$$)
79 |
80 | // actual time: = --------~ =--------~ =--------~ =----...
81 | // observableX$: (1, 2)--------~ =--------~ =--------~ 3----...
82 | // subject$$: (1, 2)--------~ =--------~ =--------~ 3----...
83 | // subscriptionC: (1, 2)--------~ =--------~ =--------~ 3----...
84 | // subscriptionD: =--------~ 3----...
85 |
86 |
87 | // var subject = new Subject();
88 |
89 | // subject.subscribe({
90 | // next: (v) => console.log('observerA: ' + v)
91 | // });
92 |
93 | // var observable =Observable.from([1, 2, 3]);
94 |
95 | // observable.subscribe(subject);
96 |
97 |
98 | // (val: any) => { console.log(`observer receives ${val} at ${Math.floor(Date.now()/1000)}`) }
99 |
100 |
101 | // var subject = new BehaviorSubject(0); // 0 is the initial value
102 |
103 | // subject.subscribe({
104 | // next: (val: any) => { console.log(`observerA receives ${val} at ${Math.floor(Date.now()/1000)}`) }
105 | // });
106 |
107 | // subject.next(1);
108 |
109 | // console.log(subject.getValue());
110 |
111 | // -----------------------------------
112 |
113 | // console.log(`the program started at ${Math.floor(Date.now()/1000)}`);
114 | // const observable$ = Observable.interval(1000).take(5);
115 | // // observable$.subscribe({
116 | // // next: (val: any) => { console.log(`observerA receives ${val} at ${Math.floor(Date.now()/1000)}`) }
117 | // // });
118 |
119 | // const subject = new ReplaySubject(2); // buffer 2 values for new subscribers
120 |
121 | // subject.subscribe({
122 | // next: (val: any) => { console.log(`observerA receives ${val} at ${Math.floor(Date.now()/1000)}`) }
123 | // });
124 |
125 | // observable$.subscribe(subject);
126 |
127 | // setTimeout(() => {
128 | // subject.subscribe({
129 | // next: (val: any) => { console.log(`observerB receives ${val} at ${Math.floor(Date.now()/1000)}`) }
130 | // });
131 | // }, 4000)
132 |
133 | // -----------------------------------
134 | // console.log(`the program started at ${Math.floor(Date.now()/1000)}`);
135 | // var subject = new AsyncSubject();
136 |
137 | // subject.subscribe({
138 | // next: (val: any) => { console.log(`observerA receives ${val} at ${Math.floor(Date.now()/1000)}`) }
139 | // });
140 |
141 | // subject.next(1);
142 | // subject.next(2);
143 | // subject.next(3);
144 | // subject.next(4);
145 |
146 | // subject.next(5);
147 | // setTimeout(() => {
148 | // subject.complete();
149 | // }, 1000)
150 |
151 |
152 |
153 | // -----------------------------------
154 |
155 | // Observable.prototype.multiplyByTen = function multiplyByTen() {
156 | // var input = this;
157 | // return Observable.create(function subscribe(observer: any) {
158 | // const nextNotificationHandler = (v: any) => {
159 | // if(isNaN(v)) {
160 | // observer.error('not an number');
161 | // }
162 | // observer.next(10 * v);
163 | // };
164 |
165 | // input.subscribe({
166 | // next: nextNotificationHandler,
167 | // error: (err: any) => observer.error(err),
168 | // complete: () => observer.complete()
169 | // });
170 | // });
171 | // }
172 |
173 |
174 |
175 |
176 |
177 | // const observableX$: Observable = Observable.create((observer: any) => {
178 | // console.log('observableX is subscribed');
179 | // observer.next(0);
180 | // setTimeout(() => {
181 | // observer.next(4)
182 | // }, 1000)
183 | // setTimeout(() => {
184 | // observer.next(6)
185 | // }, 2000)
186 | // setTimeout(() => {
187 | // observer.next('a')
188 | // }, 3000)
189 | // setTimeout(() => {
190 | // observer.complete()
191 | // }, 4000)
192 | // })
193 |
194 |
195 | // const observableY$ = observableX$.multiplyByTen();
196 | // observableY$.subscribe((val: any) => {
197 | // console.log(`observerA receives ${val} at ${Math.floor(Date.now()/1000)}`)
198 | // })
199 |
200 | // -----------------------------------
201 | // console.log(`app started at ${Math.floor(Date.now()/1000)}`)
202 |
203 | // const observable1 = Observable.from([10, 20, 30]);
204 | // observable1.subscribe({
205 | // next: x => console.log(`observable1 emits ${x} at ${Math.floor(Date.now()/1000)}`),
206 | // complete: () => console.log(`observable1 completed at ${Math.floor(Date.now()/1000)}`)
207 | // });
208 |
209 | // // -----------------------------------
210 | // const observable2 = Observable.of(1, 2, 3);
211 | // observable2.subscribe({
212 | // next: x => console.log(`observable2 emits ${x} at ${Math.floor(Date.now()/1000)}`),
213 | // complete: () => console.log(`observable2 completed at ${Math.floor(Date.now()/1000)}`)
214 | // });
215 |
216 |
217 | // -----------------------------------
218 |
219 | // var observable3 = Observable.interval(1000).take(5);
220 | // observable3.subscribe(x => console.log(`observable3 emits ${x} at ${Math.floor(Date.now()/1000)}`));
221 |
222 | // -----------------------------------
223 |
224 | // const clicks = Observable.fromEvent(document, 'click');
225 | // const clicksMapped = clicks.map((event: {clientX: number}, index: any) => {
226 | // return `clientX: ${event.clientX} at index ${index}`;
227 | // })
228 | // clicksMapped.subscribe(console.log);
229 |
230 | // const clicksScanned = clicks.scan((acc, event) => {
231 | // return ++acc
232 | // }, 0)
233 | // clicksScanned.subscribe(console.log);
234 |
235 | // clicks.map((event: {target: {tagName: any}})=> event.target.tagName).subscribe(console.log);
236 |
237 | // const tagNames = clicks.pluck('target', 'tagName');
238 | // tagNames.subscribe(x => console.log(x));
239 |
240 | // ---------------higer order observable--------------------
241 |
242 |
243 | // const clicks$ = Observable.fromEvent(document, 'click').scan((acc, curr)=>++acc, 0)
244 |
245 | // const seconds$ = Observable.interval(1000).map(tick=>++tick).startWith(0).take(10);
246 |
247 | // const mergeMapped$ = clicks$.mergeMap((click) => {
248 | // return seconds$.map(second => {
249 | // return {
250 | // click,
251 | // second
252 | // }
253 | // })
254 | // })
255 |
256 | // const mergeMapped$ = clicks$.mergeMap((click) => {
257 | // return seconds$
258 | // }, (click, second) => {
259 | // return {
260 | // click,
261 | // second
262 | // }
263 | // })
264 |
265 | // mergeMapped$.subscribe(console.log);
266 |
267 | // const switchMapped$ = clicks$.switchMap((click) => {
268 | // return seconds$.map(second => {
269 | // return {
270 | // click,
271 | // second
272 | // }
273 | // })
274 | // })
275 |
276 | // switchMapped$.subscribe(console.log);
277 |
278 |
279 | // const higherOrder$ = clicks$.map(click => {
280 | // return seconds$.map(second => {
281 | // return {
282 | // click,
283 | // second
284 | // }
285 | // })
286 | // })
287 |
288 | // higherOrder$.subscribe(console.log);
289 | // higherOrder$.mergeAll().subscribe(console.log);
290 | // higherOrder$.switch().subscribe(console.log);
291 |
292 |
293 | // const actualSecond$$ = new ReplaySubject(1);
294 | // seconds$.subscribe(actualSecond$$);
295 |
296 | // const secondsWithLog$ = Observable.interval(1000).map(tick=>++tick).startWith(0).take(10).do(console.log);
297 |
298 | // const higherOrder$ = clicks$.map(click => {
299 | // return seconds$.map(second => {return {click, second}}).withLatestFrom(actualSecond$$, (obj, actualSecond) => {
300 | // return Object.assign({}, obj, {actualSecond});
301 | // })
302 | // })
303 |
304 | // const canvas = document.createElement('canvas');
305 | // document.body.appendChild(canvas);
306 | // canvas.width = 400;
307 | // canvas.height = 400;
308 |
309 |
310 | // const ctx = canvas.getContext('2d');
311 |
312 | // const drawLine = (x: number, y: number, length: number, color: string = '#000000') => {
313 | // ctx.strokeStyle = color;
314 | // ctx.beginPath()
315 | // ctx.moveTo(x, y);
316 | // ctx.lineTo(x + length, y);
317 | // ctx.closePath()
318 | // ctx.stroke();
319 | // }
320 |
321 | // const drawLine2 = (x: number, y: number, length: number, color: any = '#000000') => {
322 | // ctx.strokeStyle = color;
323 | // ctx.beginPath()
324 | // ctx.moveTo(x, y);
325 | // ctx.lineTo(x, y + length);
326 | // ctx.closePath()
327 | // ctx.stroke();
328 | // }
329 |
330 | // const drawText = (x: number, y: number, text: any) => {
331 | // ctx.fillStyle = '#035640';
332 | // ctx.fillText(text, x, y)
333 | // }
334 |
335 |
336 | // actualSecond$$.subscribe((second: any) => {
337 | // drawLine(second*15, 10, 13, '#035640')
338 | // })
339 |
340 |
341 |
342 |
343 |
344 |
345 | // clicks$.withLatestFrom(actualSecond$$, (clickCount: number, actualSecond: number) => {
346 | // return {clickCount, actualSecond}
347 | // }).map(obj => {
348 | // drawText(obj.actualSecond*15, 20, 'C');
349 |
350 | // drawLine2(obj.actualSecond*15, 20, 20*obj.clickCount)
351 | // seconds$.subscribe((second) => {
352 | // drawText(second*15+obj.actualSecond*15, 20*(obj.clickCount+1), second);
353 | // // drawLine(second*15+obj.actualSecond*15, 20*(obj.clickCount+1), 13)
354 | // })
355 | // }).subscribe();
356 |
357 | // actualSecond$$.subscribe((second: any) => {
358 | // drawLine(second*15, 10+150, 13, '#035640')
359 | // })
360 |
361 | // clicks$.withLatestFrom(seconds$, (clickCount: number, actualSecond: number) => {
362 | // return {clickCount, actualSecond}
363 | // }).scan((acc: Subscription, obj: any) => {
364 | // drawText(obj.actualSecond*15, 20+150, 'C');
365 | // drawLine2(obj.actualSecond*15, 20+150, 20*obj.clickCount)
366 | // acc.unsubscribe();
367 | // return acc = seconds$.subscribe((second) => {
368 | // drawText(second*15+obj.actualSecond*15, 20*(obj.clickCount+1)+150, second);
369 | // })
370 | // }, Observable.empty().subscribe()).subscribe();
371 |
372 |
373 | // ---------------------------delay, debound, sample, throttle, audit---------
374 |
375 | // console.log(`the app started at ${Date.now()}`);
376 |
377 | // const clicks$ = Observable.fromEvent(document, 'click');
378 |
379 | // clicks$.subscribe((event: {clientX: number, clientY: number}) => {
380 | // console.log(`value at point (${event.clientX}, ${event.clientY}) at time ${Date.now()}`)
381 | // })
382 |
383 | // const delayed$ = clicks$.delay(1000);
384 | // delayed$.subscribe((event: {clientX: number, clientY: number}) => {
385 | // console.warn(`delayed: value at point (${event.clientX}, ${event.clientY}) at time ${Date.now()}`)
386 | // })
387 |
388 | // const debounced$ = clicks$.debounceTime(1000);
389 | // debounced$.subscribe((event: {clientX: number, clientY: number}) => {
390 | // console.error(`debounced: value at point (${event.clientX}, ${event.clientY}) at time ${Date.now()}`)
391 | // })
392 |
393 | // const sampled$ = clicks$.sampleTime(1000);
394 | // sampled$.subscribe((event: {clientX: number, clientY: number}) => {
395 | // console.info(`sampled: value at point (${event.clientX}, ${event.clientY}) at time ${Date.now()}`)
396 | // })
397 |
398 |
399 | // const throttled$ = clicks$.throttleTime(1000);
400 | // throttled$.subscribe((event: {clientX: number, clientY: number}) => {
401 | // console.info(`throttled: value at point (${event.clientX}, ${event.clientY}) at time ${Date.now()}`)
402 | // })
403 |
404 | // const audited$ = clicks$.auditTime(1000);
405 | // audited$.subscribe((event: {clientX: number, clientY: number}) => {
406 | // console.warn(`audited: value at point (${event.clientX}, ${event.clientY}) at time ${Date.now()}`)
407 | // })
408 |
409 | // --------------------------
410 |
411 | // console.log(`the app started at ${Date.now()}`);
412 |
413 | // const clicks$ = Observable.fromEvent(document, 'click').scan((acc, curr)=>++acc, 0)
414 |
415 | // const seconds$ = Observable.interval(1000).map(tick=>++tick).startWith(0).take(10);
416 |
417 | // clicks$.merge(seconds$).subscribe(console.log);
418 |
419 | // clicks$.combineLatest(seconds$).subscribe(console.log);
420 | // clicks$.combineLatest(seconds$, (click: number, second: number) => {
421 | // return {click, second}
422 | // }).subscribe(console.log);
423 | // clicks$.withLatestFrom(seconds$).subscribe(console.log);
424 | // clicks$.withLatestFrom(seconds$, (click: number, second: number) => {
425 | // return {click, second}
426 | // }).subscribe(console.log);
427 |
428 |
429 | // --------------------------
430 |
431 | // const source$ = Observable.create((observer: any) => {
432 | // observer.next(0);
433 | // observer.next(1);
434 | // observer.next(3);
435 | // observer.next(4);
436 | // observer.next(5);
437 | // })
438 |
439 | // source$.subscribe(console.log)
440 | // const filtered$ = source$.filter((value: any) => {
441 | // return value % 2 === 1;
442 | // })
443 |
444 | // filtered$.subscribe(console.log);
445 |
446 |
447 |
448 | // const source$ = Observable.create((observer: any) => {
449 | // observer.next(0);
450 | // observer.next(1);
451 | // observer.next(0);
452 | // observer.next(1);
453 | // });
454 |
455 | // const distinct$ = source$.distinct();
456 |
457 | // distinct$.subscribe(console.log);
458 |
459 | // const source$ = Observable.create((observer: any) => {
460 | // observer.next(0);
461 | // observer.next(1);
462 | // observer.next(0);
463 | // observer.next(1);
464 | // observer.next(1);
465 | // });
466 |
467 | // const distinctUC$ = source$.distinctUntilChanged();
468 |
469 | // distinctUC$.subscribe(console.log);
470 |
471 |
472 | // const source$ = Observable.create((observer: any) => {
473 | // observer.next(0);
474 | // observer.next(1);
475 | // observer.next('whateverbutnotanumber');
476 | // })
477 |
478 | // const errorOnNaN$ = source$.mergeMap((value: any)=> {
479 | // if(isNaN(value)) {
480 | // return Observable.throw(`the value ${value} is not a number`)
481 | // } else {
482 | // return Observable.of(value);
483 | // }
484 | // })
485 |
486 | // errorOnNaN$.subscribe({
487 | // next: (value: any) => console.log(value),
488 | // error: (error: any) => console.error(error)
489 | // })
490 |
491 | // -------------Scheduler-------------
492 | // import {Scheduler, TestScheduler} from 'rxjs'
493 |
494 | // const input$ = Observable.of(0, Scheduler.async)
495 | // .delay(100, Scheduler.asap)
496 | // .startWith(0)
497 | // .repeat(100)
498 | // .sampleTime(500, Scheduler.queue)
499 | // .observeOn(Scheduler.asap)
500 | // .subscribeOn(Scheduler.async);
501 |
502 | // console.warn('before observable');
503 | // setTimeout(() => console.log('setTimeout 0 before observable'), 0)
504 |
505 | // const input$ = Observable.of(0).repeat(10)
506 | // .observeOn(Scheduler.queue);
507 | // .observeOn(Scheduler.asap);
508 | // .observeOn(Scheduler.async);
509 | // .subscribeOn(Scheduler.queue);
510 | // .subscribeOn(Scheduler.asap);
511 | // .subscribeOn(Scheduler.async);
512 |
513 | // console.warn('before subscription');
514 | // setTimeout(() => console.log('setTimeout 0 before subscription'), 0)
515 |
516 | // input$.subscribe(console.log);
517 |
518 | // console.warn('end');
519 | // setTimeout(() => console.log('setTimeout 0 after end'), 0)
520 |
521 |
522 | // const main$ = Observable.of(1,2,3);
523 | // const sub$ = Observable.interval(500).take(3);
524 | // main$.mergeMap((val: any) => {
525 | // return sub$;
526 | // }).subscribe({
527 | // next: console.log,
528 | // complete: console.warn
529 | // });
530 |
531 | // const main2$ = Observable.create((observer: any) => {
532 | // observer.next(0);
533 | // setTimeout(() => {
534 | // observer.complete();
535 | // }, 1000)
536 | // });
537 | // const sub2$ = Observable.of(1,2,3);
538 | // main2$.mergeMap((val: any) => sub2$).subscribe({
539 | // next: console.log,
540 | // complete: console.warn
541 | // });
542 |
543 | // ----------------------------------------------
544 |
545 | // import { Observable } from 'rxjs';
546 |
547 | // const startButton = document.createElement('input');
548 | // startButton.value = 'START';
549 | // startButton.setAttribute("type", "button");
550 | // startButton.style.width = '100px';
551 | // startButton.style.margin = '10px';
552 | // document.body.appendChild(startButton);
553 |
554 | // const pauseButton = document.createElement('input');
555 | // // pauseButton.disabled = true;
556 | // pauseButton.value = 'PAUSE';
557 | // pauseButton.setAttribute("type", "button");
558 | // pauseButton.style.width = '100px';
559 | // document.body.appendChild(pauseButton);
560 |
561 | // const docLoad$ = Observable.fromEvent(document, 'load');
562 | // const startClick$ = Observable.fromEvent(startButton, 'click');
563 | // // startClick$.map(event=>(<{clientX: number}>event).clientX)
564 | // const pauseClick$ = Observable.fromEvent(pauseButton, 'click');
565 | // const game = {value: Math.floor(Math.random()*100), firstRun: true}
566 | // const appInit$ = Observable.of(game)
567 | // docLoad$.subscribe(console.log);
568 |
569 | // appInit$
570 | // .switchMap(game => {
571 | // return startClick$
572 | // .switchMap(event => {
573 | // if(game.firstRun) {
574 | // game.firstRun = false
575 | // } else {
576 | // game.value = Math.floor(Math.random()*100)
577 | // }
578 | // return pauseClick$
579 | // .scan((acc, curr) => {
580 | // return !acc
581 | // }, false)
582 | // .startWith(false)
583 | // .switchMap(pause => {
584 | // switch (pause) {
585 | // case true:
586 | // pauseButton.value = 'RESUME';
587 | // return Observable.of(game)
588 | // case false:
589 | // pauseButton.value = 'PAUSE';
590 | // return Observable.interval(1000)
591 | // .map(tick => {
592 | // game.value += 1
593 | // return game
594 | // })
595 | // .startWith(game)
596 | // default:
597 | // return Observable.throw('value of pause should be of type boolean')
598 | // }
599 | // })
600 | // })
601 | // .startWith(game)
602 | // })
603 | // .subscribe(console.log);
604 |
605 |
606 | // const identity = (arg: T): T => {
607 | // return arg;
608 | // }
609 |
610 | // interface item {
611 | // prop: T
612 | // }
613 |
614 | // const newItem: item = {prop: 'a'}
615 | // ----------------------------------------------
616 |
617 | import {Observable} from 'rxjs/Observable';
618 | import 'rxjs/add/observable/interval';
619 | import 'rxjs/add/operator/map';
620 |
621 | const someObservable$: Observable = Observable.interval(500).map((tick:any)=>++tick)
622 |
623 |
--------------------------------------------------------------------------------