Solution - Pipe Function
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
verifyPasscodefunction 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 thepasscodearray 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 knownPASSCODEObservable, 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')
});