Mine Sweeper Game

By adamlubek

This recipe demonstrates RxJS implementation of Mine Sweeper Game.

Example Code

( StackBlitz )

index.ts

// RxJS v6+
import { fromEvent, of } from 'rxjs';
import {
  map,
  tap,
  filter,
  pluck,
  switchMap,
  takeWhile,
  finalize
} from 'rxjs/operators';
import { renderMinefield, renderScore, renderGameOver } from './html-renderer';
import { size, mine } from './constants';
import { addMines, addMarks } from './mines';

const mines$ = of(
  Array(size)
    .fill(0)
    .map(e => Array(size).fill(0))
).pipe(map(addMines), map(addMarks), tap(renderMinefield));

const click$ = mines =>
  fromEvent(document, 'click').pipe(
    map(({ clientX, clientY }: MouseEvent) =>
      document.elementFromPoint(clientX, clientY)
    ),
    filter(elem => elem.id !== ''),
    tap(elem =>
      (val => (
        renderScore(val === mine || elem.innerHTML !== '_' ? 0 : val),
        (elem.innerHTML = val)
      ))(mines[elem.id[0]][elem.id[1]])
    ),
    pluck('id'),
    takeWhile(([x, y]) => mines[x][y] !== mine),
    finalize(renderGameOver)
  );

mines$.pipe(switchMap(click$)).subscribe();

mines.ts

import { size, mine } from './constants';

const randomNumber = () => Math.floor(Math.random() * Math.floor(size));

export const addMines = arr => {
  for (let i = 0; i < size / 2; i++) {
    arr[randomNumber()][randomNumber()] = mine;
  }

  return arr;
};

const mark = (arr, x, y) =>
  arr[x] !== undefined && arr[x][y] !== undefined
    ? (arr[x][y] += arr[x][y] === mine ? 0 : 1)
    : () => {};

export const addMarks = arr => {
  for (let ri = 0; ri < size; ri++) {
    for (let ci = 0; ci < size; ci++) {
      if (arr[ri][ci] === mine) {
        mark(arr, ri - 1, ci + 1);
        mark(arr, ri - 1, ci);
        mark(arr, ri - 1, ci - 1);
        mark(arr, ri, ci + 1);
        mark(arr, ri, ci - 1);
        mark(arr, ri + 1, ci + 1);
        mark(arr, ri + 1, ci);
        mark(arr, ri + 1, ci - 1);
      }
    }
  }
  return arr;
};

constants.ts

export const mine = 9;
export const size = 10;

html-renderer.ts

export const renderMinefield = arr =>
  arr.forEach((r, ri) =>
    (elem =>
      r.forEach(
        (c, ci) =>
          (col => (
            (col.innerText = '_'),
            (col.id = `${ri}${ci}`),
            elem.appendChild(document.createTextNode('\u00A0\u00A0')),
            elem.appendChild(col)
          ))(document.createElement('span')),
        document.body.appendChild(elem)
      ))(document.createElement('div'))
  );

export const renderScore = val =>
  (scoreElem => (scoreElem.innerText = parseInt(scoreElem.innerText) + val))(
    document.getElementById('score')
  );

export const renderGameOver = () =>
  (document.body.innerHTML += '<br/>GAME OVER');

const addElem = decorator =>
  (elem => (decorator(elem), document.body.appendChild(elem)))(
    document.createElement('span')
  );

addElem(elem => (elem.innerText = 'Score: '));
addElem(elem => ((elem.id = 'score'), (elem.innerText = '0')));

Operators Used

Last updated