├── 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 | --------------------------------------------------------------------------------