Memory Game

By adamlubek

This recipe demonstrates an RxJS game to train your memory.

Ultimate RxJS

Example Code

( StackBlitz )

Memory Game

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