Flappy Bird Game

By adamlubek

This recipe demonstrates RxJs implementation of Flappy Bird like game.

Example Code

( StackBlitz )

index.ts

// RxJS v6+
import { interval, merge, combineLatest, fromEvent } from 'rxjs';
import { tap, scan, takeWhile } from 'rxjs/operators';
import { paint } from './html-renderer';

const gamePipe = (x, y) => ({ x, y, checked: false });
const gameSize = 10;
const createPipes = y =>
  (random =>
    Array.from(Array(gameSize).keys())
      .map(e => gamePipe(e, y))
      .filter(e => e.x < random || e.x > random + 2))(
    Math.floor(Math.random() * Math.floor(gameSize))
  );

const gamePipes$ = interval(500).pipe(
  scan < any,
  any >
    (acc =>
      (acc.length < 2 ? [...acc, createPipes(gameSize)] : acc)
        .filter(c => c.some(e => e.y > 0))
        .map(cols => cols.map(e => gamePipe(e.x, e.y - 1))),
    [createPipes(gameSize / 2), createPipes(gameSize)])
);

const fly = xPos => (xPos > 0 ? (xPos -= 1) : xPos);
const fall = xPos => (xPos < gameSize - 1 ? (xPos += 1) : gameSize - 1);
const bird$ = merge(interval(300), fromEvent(document, 'keydown')).pipe(
  scan < any,
  any >
    ((xPos, curr) => (curr instanceof KeyboardEvent ? fly(xPos) : fall(xPos)),
    gameSize - 1)
);

const updateGame = (bird, pipes) =>
  (game => (
    pipes.forEach(col => col.forEach(v => (game[v.x][v.y] = 2))),
    (game[bird][0] = 1),
    game
  ))(
    Array(gameSize)
      .fill(0)
      .map(e => Array(gameSize).fill(0))
  );

const valueOnCollisionFor = pipes => ({
  when: predicate =>
    !pipes[0][0].checked && predicate ? ((pipes[0][0].checked = true), 1) : 0
});

combineLatest(bird$, gamePipes$)
  .pipe(
    scan < any,
    any >
      ((state, [bird, pipes]) => ({
        bird: bird,
        pipes: pipes,
        lives:
          state.lives -
          valueOnCollisionFor(pipes).when(
            pipes.some(c => c.some(c => c.y === 0 && c.x === bird))
          ),
        score:
          state.score + valueOnCollisionFor(pipes).when(pipes[0][0].y === 0)
      }),
      { lives: 3, score: 0, bird: 0, pipes: [] }),
    tap(state =>
      paint(updateGame(state.bird, state.pipes), state.lives, state.score)
    ),
    takeWhile(state => state.lives > 0)
  )
  .subscribe();

html-renderer.ts

const createElem = col => {
  const elem = document.createElement('div');
  elem.classList.add('board');
  elem.style.display = 'inline-block';
  elem.style.marginLeft = '10px';
  elem.style.height = '6px';
  elem.style.width = '6px';
  elem.style['background-color'] =
    col === 0
      ? 'white'
      : col === 1
      ? 'cornflowerblue'
      : col === 2
      ? 'gray'
      : 'silver';
  elem.style['border-radius'] = '90%';
  return elem;
};

export const paint = (game: number[][], lives, score) => {
  document.body.innerHTML = `Lives: ${lives}, Score: ${score}`;

  game.forEach(row => {
    const rowContainer = document.createElement('div');
    row.forEach(col => rowContainer.appendChild(createElem(col)));
    document.body.appendChild(rowContainer);
  });
};

Operators Used

Last updated