Solution
import { from, fromEvent, pipe } from 'rxjs';
import { bufferCount, map, mergeMap, sequenceEqual } from 'rxjs/operators';
const PASSCODE = [1, 1, 1, 1];
const buttons = document.querySelectorAll('.btn');
const verifyPasscode = () =>
pipe(
bufferCount<number>(4),
mergeMap((passcode) => from(passcode).pipe(sequenceEqual(from(PASSCODE))))
);
fromEvent<MouseEvent>(buttons, 'click')
.pipe(
map((event: MouseEvent) => {
const target = event.target as HTMLButtonElement;
return parseInt(target.dataset.key!, 10);
}),
verifyPasscode()
)
.subscribe({
error: console.error,
next: console.log,
complete: () => console.log('complete')
});
Let's review the solution above:
- First, we define the
verifyPasscode
function that returns the Observable from thepipe()
function. - Within the
pipe()
function we use thebufferCount()
operator to buffer 4 next notifications and then emit an Observable that immediately emits a single next notification of all of the values in an array. - We then use the
mergeMap()
operator that receives thepasscode
array of numbers. Using thefrom()
operator we create a new Observable that emits each number value in sequence and then completes. Within thepipe()
of this new Observable we use thesequenceEqual()
operator to compare the Observable against the knownPASSCODE
Observable, which we also create using thefrom()
operator.
Bonus Solutions
import { from, fromEvent, pipe } from 'rxjs';
import {
bufferCount,
map,
mergeMap,
sequenceEqual,
tap,
delay
} from 'rxjs/operators';
const PASSCODE = [1, 1, 1, 1];
const buttons = document.querySelectorAll('.btn');
const dots = document.querySelectorAll('.dot');
const clear = (el: HTMLElement) => el.classList.remove('bg-black');
const fill = (el: HTMLElement) => el.classList.add('bg-black');
const clearElements = <T>(elements: NodeList) =>
pipe(
delay(1000),
tap(() =>
elements.forEach((el) => clear(el as HTMLElement))
) as OperatorFunction<T, T>
);
const fillElements = <T>(elements: NodeList) => {
let index = 0;
return pipe(
tap(() => {
if (index < PASSCODE.length) {
const el = dots.item(index) as HTMLElement;
fill(el);
index++;
} else {
dots.forEach((el) => clear(el as HTMLElement));
index = 0;
}
}) as OperatorFunction<T, T>
);
};
const verifyPasscode = () =>
pipe(
bufferCount<number>(4),
mergeMap((passcode) => from(passcode).pipe(sequenceEqual(from(PASSCODE))))
);
fromEvent<MouseEvent>(buttons, 'click')
.pipe(
map((event: MouseEvent) => {
const target = event.target as HTMLButtonElement;
return parseInt(target.dataset.key!, 10);
}),
fillElements(dots),
verifyPasscode(),
clearElements(dots)
)
.subscribe({
error: console.error,
next: console.log,
complete: () => console.log('complete')
});