diff --git a/README.md b/README.md index b9bcd85..b5a4c2b 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ Recipes for common use-cases and interesting solutions with RxJS. - [Car Racing Game](/recipes/car-racing-game.md) - [Catch The Dot Game](/recipes/catch-the-dot-game.md) - [Click Ninja Game](/recipes/click-ninja-game.md) +- [Dinosaur Game](/recipes/dinosaur-game.md) - [Flappy Bird Game](/recipes/flappy-bird-game.md) - [Game Loop](/recipes/gameloop.md) - [Horizontal Scroll Indicator](/recipes/horizontal-scroll-indicator.md) diff --git a/SUMMARY.md b/SUMMARY.md index a03aaa9..c1c3abb 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -122,6 +122,7 @@ - [Car Racing Game](recipes/car-racing-game.md) - [Catch The Dot Game](recipes/catch-the-dot-game.md) - [Click Ninja Game](recipes/click-ninja-game.md) + - [Dinosaur Game](recipes/dinosaur-game.md) - [Flappy Bird Game](recipes/flappy-bird-game.md) - [Game Loop](recipes/gameloop.md) - [Horizontal Scroll Indicator](recipes/horizontal-scroll-indicator.md) diff --git a/recipes/README.md b/recipes/README.md index 532e195..f68360b 100644 --- a/recipes/README.md +++ b/recipes/README.md @@ -10,6 +10,7 @@ Common use-cases and interesting recipes to help learn RxJS. - [Car Racing Game](car-racing-game.md) - [Catch The Dot Game](catch-the-dot-game.md) - [Click Ninja Game](click-ninja-game.md) +- [Dinosaur Game](dinosaur-game.md) - [Flappy Bird Game](flappy-bird-game.md) - [Game Loop](gameloop.md) - [Horizontal Scroll Indicator](horizontal-scroll-indicator.md) diff --git a/recipes/dinosaur-game.md b/recipes/dinosaur-game.md new file mode 100644 index 0000000..e9c4255 --- /dev/null +++ b/recipes/dinosaur-game.md @@ -0,0 +1,183 @@ +# Dinosaur Game (Chrome's offline game) + +_By [adamlubek](https://github.com/adamlubek)_ + +This recipe demonstrates simplified clone of Chrome's (offline mode) Dinosaur Game implemented in RxJS. + +[![Ultimate RxJS](https://ultimatecourses.com/static/banners/banner-rxjs.svg 'Ultimate RxJS')](https://ultimatecourses.com/courses/rxjs?ref=4) + +### Example Code + +( [StackBlitz](https://stackblitz.com/edit/rxjs-chrome-dinosaur-game?file=index.ts) +) + +#### index.ts + +```ts +import { + rx, + map, + interval, + scan, + fromEvent, + startWith, + combineLatestWith, + merge, + takeWhile, +} from 'rxjs'; +import { GameState } from './interfaces'; +import { Game } from './game'; +import { Painter } from './painter'; + +const keyDowns$ = rx( + fromEvent(document, 'keydown'), + map((e: KeyboardEvent) => e.key) +); + +const keyUps$ = rx( + fromEvent(document, 'keyup'), + map((e: KeyboardEvent) => e.type) +); + +const keys$ = rx(merge(keyDowns$, keyUps$), startWith('')); + +const stateUpdater$ = scan( + ( + gameState: GameState, + [gameLoopIteration, keyboardKey]: [number, string] + ) => ( + gameState.updateGame(gameLoopIteration, keyboardKey), + Painter.Paint(gameState), + gameState + ), + new Game() +); +const gameLoop$ = interval(60); + +rx( + gameLoop$, + combineLatestWith(keys$), + stateUpdater$, + takeWhile((gameState: GameState) => gameState.lives > 0) +).subscribe(); + +``` +#### game.ts + +```js +import { GameState, Obstacle } from './interfaces'; + +export class Game implements GameState { + lives = 3; + height = 0; + score = 0; + private jumping = false; + private falling = false; + obstacles = new Array(); + + updateGame = (gameLoopIteration: number, keyboardKey: string) => { + this.handleJumping(keyboardKey); + this.addObstacle(gameLoopIteration); + this.updateObstacles(); + this.collide(); + }; + + private setJumpingAndFallingWhen = ( + condition: boolean, + jumping: boolean, + falling: boolean + ) => (condition ? ((this.jumping = jumping), (this.falling = falling)) : {}); + + private handleJumping = (key: string) => ( + this.setJumpingAndFallingWhen( + key === 'ArrowUp' && !this.jumping && !this.falling, + true, + false + ), + this.falling ? (this.height -= 1) : {}, + this.setJumpingAndFallingWhen(this.height > 5, false, true), + this.jumping ? (this.height += 1) : {}, + this.setJumpingAndFallingWhen(this.height <= 0, false, false) + ); + + private getRandomInt = (): number => Math.floor(Math.random() * 50); + + private addObstacle = (gameLoopIteration: number) => { + this.obstacles.length < 2 && (gameLoopIteration & this.getRandomInt()) === 0 + ? this.obstacles.push({ x: 20 }) + : {}; + }; + + private obstaclesPresent = () => this.obstacles.length > 0; + + private updateObstacles = () => ( + this.obstacles.forEach((e: Obstacle) => (e.x -= 1)), + this.obstaclesPresent() && this.obstacles[0].x < 0 + ? (this.obstacles.shift(), (this.score += 1)) + : {} + ); + + private collide = () => + this.obstaclesPresent() && this.obstacles[0].x === 0 && this.height === 0 + ? (this.lives -= 1) + : {}; +} +``` + +#### painter.ts + +```js +import { GameState } from './interfaces'; + +export class Painter { + private static PAINTING_ADJUSTMENT = 10; + + static Paint(gameState: GameState) { + this.html().innerHTML = ''; + this.paintInfo(gameState.lives, gameState.score); + gameState.obstacles.forEach((e) => this.paintObstacle(e.x)); + this.paintPlayer(gameState.height * this.PAINTING_ADJUSTMENT); + } + + private static html = () => document.getElementsByTagName('body')[0]; + + private static paintInfo = (lives: number, score: number) => + (this.html().innerHTML += `

LIVES: ${lives} , SCORE: ${score}

`); + + private static paintObstacle = (x: number) => + (this.html().innerHTML += `

x

`); + + private static paintPlayer = (y: number) => + (this.html().innerHTML += `

Y

`); +} + +``` + +#### interfaces.ts + +```js +export interface Obstacle { + x: number; +} + +export interface GameState { + lives: number; + height: number; + score: number; + obstacles: Array; + + updateGame: (gameLoopIteration: number, keyboardKey: string) => void; +} +``` + +### Operators Used + +- [fromEvent](../operators/creation/fromevent.md) +- [interval](../operators/creation/interval.md) +- [map](../operators/transformation/map.md) +- [merge](../operators/combination/merge.md) +- [scan](../operators/transformation/scan.md) +- [startWith](../operators/combination/startwith.md) +- [takeWhile](../operators/filtering/takewhile.md)