Case Study: Cheat Code Entry - deanrad/rx-helper GitHub Wiki
Detecting a Cheat Code Was Entered
Given that we have two buttons, A and B, we want to print a message if a “secret combination” (ABBABA) is clicked within 5 seconds. To make things a bit harder the following must also be true:
- The message should be printed immediately when the secret combination is clicked. We should not wait until the end of the 5 second interval before the message is printed.
- As soon as the secret combination is fulfilled (within 5 seconds) the message should be displayed regardless of what has been clicked before. For example a combination like “BBABBABA” is ok as long as “ABBABA” is clicked within 5 seconds.
The Rx-Helper Way
The primary logic is in this Handler, which returns an Observable of events of type: 'won'
, corresponding to successful completions of the cheat code sequence in a short enough amount of time:
agent.on(({event: {payload: key}}) => (key==="A"),
seeIfNext5AreCorrectAndFastEnough,
{ processResults: true }
)
function seeIfNext5AreCorrectAndFastEnough({ event: { payload: key } }) {
const startedAt = new Date().getTime()
// Get the next 5 non-empty as [{value, timestamp}]
const next5Key$ = agent.getAllEvents('key').pipe(
map(a => a.payload),
filter(Boolean),
take(5),
timestamp(),
toArray())
// Return winning messages
return next5Key$.pipe(
// for batches which complete us
filter(batch => theCode.substr(1) === (batch.map(i => i.value).join(""))),
// if they are within the timeout
filter(batch => (batch[4].timestamp - startedAt) < codeTimeout),
map(batch => ({ type: 'win', payload: `${batch[4].timestamp - startedAt} msec`}))
)
}
Additionally we have some imports and constants:
const { agent } = require('rx-helper')
const { of, empty, from, interval, zip, race, timer } = require('rxjs')
const { map, take, toArray, filter, tap, timestamp } = require('rxjs/operators')
const codeTimeout = 5000
const keyInterval = 500
const theCode = "ABBABA" // the code
const testKeys = [
// "B", // spurious letter
// "A", "B", "B", // 3 that are good
// "B", // bad
"A", "B", // 2 good
"A", // bad, but starting its own
"B", "B", // up to 3 good ones
"", "", "", // should time out
"A", "B", // false start (tricky!)
"A", "B", "B", // getting there
"A", "B", "A", // finally!
]
Lastly, we show all events in the console, and subscribe to these test keystrokes for testing, spacing them out over time:
agent.filter(true, ({ event }) => console.log(event.payload || "/"))
agent.subscribe(zip(interval(keyInterval), from(testKeys), (_, k) => k), {type: 'key'})
null
And with an 800 msec keyInterval our log will show:
A B A B B / / / A B A B B A B A (win!)
While with an 500 msec keyInterval our log will show:
A B A B B / / / A (win!) B A B B A B A (win!)
Q.E.D.
Original Problem: https://blog.jayway.com/2014/09/16/comparing-core-async-and-rx-by-example/
Extended Discussion: https://github.com/Reactive-Extensions/RxJS/issues/276