Memory Game

By adamlubek

This recipe demonstrates an RxJS game to train your memory.

Example Code

( StackBlitz )

index.ts

// RxJS v6+
import { EMPTY, from, fromEvent, generate, interval, merge, noop } from 'rxjs';
import {
  map,
  pluck,
  scan,
  sequenceEqual,
  switchMap,
  take,
  tap
} from 'rxjs/operators';

const random = (): number => Math.floor(Math.random() * Math.floor(8));
const setInfo = (text: string) =>
  (document.getElementById('info').innerHTML = text);
const displayLevelChange = () =>
  document
    .querySelectorAll('.child')
    .forEach((c: HTMLElement) => (c.style.background = 'gray'));

const checkIfGameOver$ = (randomSequence: number[]) => (
  userSequence: number[]
) =>
  from(userSequence).pipe(
    sequenceEqual(from(randomSequence)),
    tap(match =>
      !match && userSequence.length === randomSequence.length
        ? setInfo('GAME OVER!')
        : noop
    )
  );

const takePlayerInput$ = (randomSequence: number[]) => _ =>
  fromEvent(document, 'click').pipe(
    take(randomSequence.length),
    scan(
      (acc: number[], curr: MouseEvent) => [
        ...acc,
        parseInt(curr.target['id'])
      ],
      []
    ),
    switchMap(checkIfGameOver$(randomSequence)),
    switchMap(result =>
      result
        ? (displayLevelChange(), memoryGame$(randomSequence.length + 1))
        : EMPTY
    )
  );

const showSequenceToMemorize$ = (memorySize: number) => (
  randomSequence: number[]
) =>
  interval(1000).pipe(
    tap(i =>
      setInfo(i === memorySize - 1 ? `YOUR TURN` : `${memorySize - i} elements`)
    ),
    take(randomSequence.length),
    map(index => randomSequence[index]),
    tap(value => document.getElementById(`${value}`).click()),
    switchMap(takePlayerInput$(randomSequence))
  );

const memoryGame$ = memorySize =>
  generate(
    1,
    x => x <= memorySize,
    x => x + 1
  ).pipe(
    scan((acc: number[], _: number): number[] => [...acc, random() + 1], []),
    switchMap(showSequenceToMemorize$(memorySize))
  );

const elementClick$ = (event: string, color: string) =>
  fromEvent(document.querySelectorAll('.child'), event).pipe(
    pluck('srcElement'),
    tap((e: HTMLElement) => (e.style.background = color))
  );

const clicks$ = merge(
  elementClick$('click', 'lightgray'),
  elementClick$('transitionend', 'white')
);

const game$ = merge(clicks$, memoryGame$(2));

game$.subscribe();

index.html

<style>
  .parent {
    border-spacing: 5px;
    width: 50%;
    padding: 0.5em;
  }

  .parent.perspective {
    perspective: 50em;
  }

  .child {
    margin: 0.5em;
    max-width: 2em;
    min-width: 2em;
    height: 2.8em;
    padding: 0.5em;
    display: table-cell;
    border: 1px solid rgba(0, 0, 0, 0.5);
  }

  .parent.perspective .child {
    transform: rotateX(40deg);
    transition: all 0.3s ease-in;
  }
</style>

<div id="info">Train Your Memory!</div>
<div id="grid" class="grid parent perspective">
  <div>
    <div class="child" id="1"></div>
    <div class="child" id="2"></div>
    <div class="child" id="3"></div>
  </div>
  <div>
    <div class="child" id="4"></div>
    <div class="child" id="5"></div>
    <div class="child" id="6"></div>
  </div>
  <div>
    <div class="child" id="7"></div>
    <div class="child" id="8"></div>
    <div class="child" id="9"></div>
  </div>
</div>

Operators Used

Last updated