By adamlubek
This recipe shows usage of scan operator for state management in simple game
( StackBlitz )
// RxJS v6+
import { fromEvent, interval } from 'rxjs';
import { tap, scan, map, switchMap, takeWhile } from 'rxjs/operators';
import {
dot,
updatedDot,
setTimerText,
resetDotSize,
moveDot
} from './dom-updater';
interface State {
score: number;
intrvl: number;
}
const makeInterval = (val: State) =>
interval(val.intrvl).pipe(
map(v => 5 - v),
tap(setTimerText)
);
const gameState: State = { score: 0, intrvl: 500 };
const nextState = (acc: State) => ({
score: (acc.score += 1),
intrvl: acc.score % 3 === 0 ? (acc.intrvl -= 50) : acc.intrvl
});
const isNotGameOver = intervalValue => intervalValue >= 0;
const game$ = fromEvent(dot, 'mouseover').pipe(
tap(moveDot),
scan < Event,
State > (nextState, gameState),
tap(state => updatedDot(state.score)),
switchMap(makeInterval),
tap(resetDotSize),
takeWhile(isNotGameOver)
);
game$.subscribe(
n => {},
e => {},
() => setTimerText('ouch!')
);
const random = () => Math.random() * 300;
const elem = id => document.getElementById(id);
const setElementText = (elem, text) => (elem.innerText = text.toString());
const timer = elem('timer');
const setDotSize = size => {
dot.style.height = `${size}px`;
dot.style.width = `${size}px`;
};
export const dot = elem('dot');
export const updatedDot = score => {
if (score % 3 === 0) {
dot.style.backgroundColor =
'#' + ((Math.random() * 0xffffff) << 0).toString(16);
}
setElementText(dot, score);
};
export const setTimerText = text => setElementText(timer, text);
export const moveDot = () => {
setDotSize(5);
dot.style.transform = `translate(${random()}px, ${random()}px)`;
};
export const resetDotSize = () => setDotSize(30);
<style>
#dot {
margin-top: 10px;
height: 30px;
width: 30px;
background-color: lightgray;
border-radius: 50%;
transition: all 0.6s ease-in-out;
text-align: center;
color: white;
}
#timer {
position: absolute;
top: 150px;
left: 150px;
opacity: 0.1;
font-size: 60px;
}
</style>
<div id="timer"></div>
<div id="dot"></div>