Skip to content

Latest commit

 

History

History
183 lines (145 loc) · 4.54 KB

dinosaur-game.md

File metadata and controls

183 lines (145 loc) · 4.54 KB

Dinosaur Game (Chrome's offline game)

By adamlubek

This recipe demonstrates simplified clone of Chrome's (offline mode) Dinosaur Game implemented in RxJS.

Ultimate RxJS

Example Code

( StackBlitz )

index.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<KeyboardEvent>(document, 'keydown'),
  map((e: KeyboardEvent) => e.key)
);

const keyUps$ = rx(
  fromEvent<KeyboardEvent>(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

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<Obstacle>();

  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

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 += `<h3>LIVES: ${lives} , SCORE: ${score}</h3>`);

  private static paintObstacle = (x: number) =>
    (this.html().innerHTML += `<p style='top: 100px;left: ${
      15 + x * this.PAINTING_ADJUSTMENT
    }px'>x</p>`);

  private static paintPlayer = (y: number) =>
    (this.html().innerHTML += `<p style='top: ${100 - y}px;left: 10px'>Y</p>`);
}

interfaces.ts

export interface Obstacle {
  x: number;
}

export interface GameState {
  lives: number;
  height: number;
  score: number;
  obstacles: Array<Obstacle>;

  updateGame: (gameLoopIteration: number, keyboardKey: string) => void;
}

Operators Used