Detecting Sequences Detecting Sequences
This tutorial shows how to detect whether the user has performed a set of actions in sequence — starting with simple if/switch approaches and progressing to array and string-based strategies.
Use the ◀ ▶ buttons or arrow keys to step through the stages. Press keys inside the canvas to interact. Drag any number in the code to adjust it live.
Detect whether the user has performed a set of actions in sequence.
In this example, we will test whether the user is pressing a sequence of keys in order. The same logic can be used to detect other sequences, such as whether the user has clicked on a sequence of buttons, or moved the mouse into a sequence of regions of the canvas, or moved their hand in PoseNet through a sequence of positions.
This step sets up a “mode” variable. Eventually this will be used to display information about whether the user has matched or failed to match the sequence. In this step, it simply displays the most recent key that is pressed.
let mode = "Initial state" ;
function setup () {
createCanvas (windowWidth, windowHeight);
}
function draw () {
background ( 100 );
translate (width / 2 , height / 2 );
textAlign ( CENTER , CENTER );
textSize ( 50 );
fill ( 200 );
text (mode, 0 , 0 );
}
function keyPressed () {
mode = key;
} Copy The goal is to press the keys “1”, “2”, “3”, and “4”, in that order.
Introduce a variable whose value is the next key that we are looking for.
let PLAYING_MODE = "Playing" ;
let mode = PLAYING_MODE ;
let nextKey = "1" ;
function setup () {
createCanvas (windowWidth, windowHeight);
}
function draw () {
background ( 100 );
translate (width / 2 , height / 2 );
textAlign ( CENTER , CENTER );
textSize ( 50 );
fill ( 200 );
text (mode, 0 , 0 );
textSize ( 25 );
text ( `Next: ${ nextKey }` , 0 , 50 );
}
function keyPressed () {
if (mode !== PLAYING_MODE ) { return ; }
if (key === nextKey) {
if (key === "1" ) {
nextKey = "2" ;
} else if (key === "2" ) {
nextKey = "3" ;
} else if (key === "3" ) {
nextKey = "4" ;
} else if (key === "4" ) {
mode = "Success! 🎉" ;
}
} else {
mode = "Sorry 😞" ;
}
} Copy The previous step had a cascade of if statements that all looked for a common value.
This is a good use for a switch statement.
let PLAYING_MODE = "Playing" ;
let mode = PLAYING_MODE ;
let nextKey = "1" ;
function setup () {
createCanvas (windowWidth, windowHeight);
}
function draw () {
background ( 100 );
translate (width / 2 , height / 2 );
textAlign ( CENTER , CENTER );
textSize ( 50 );
fill ( 200 );
text (mode, 0 , 0 );
textSize ( 25 );
text ( `Next: ${ nextKey }` , 0 , 50 );
}
function keyPressed () {
if (mode !== PLAYING_MODE ) { return ; }
if (key === nextKey) {
switch (key) {
case "1" :
nextKey = "2" ;
break ;
case "2" :
nextKey = "3" ;
break ;
case "3" :
nextKey = "4" ;
break ;
case "4" :
mode = "Success! 🎉" ;
break ;
}
} else {
mode = "Sorry 😞" ;
}
} Copy The previous step used different lines of code for each item in the sequence. There was a switch statement with a case clause for each item in the sequence.
This step replaces that with an Array of target items, and an index to keep track of how many matching items have been entered.
Note: nextKey is computed twice: once on line 21, and again on line 28. This would make it a good candidate for a global variable, or for a function that performs this computation.
let PLAYING_MODE = "Playing" ;
let mode = PLAYING_MODE ;
let targetSequence = [ "1" , "2" , "3" , "4" ];
let matchCount = 0 ;
function setup () {
createCanvas (windowWidth, windowHeight);
}
function draw () {
background ( 100 );
translate (width / 2 , height / 2 );
textAlign ( CENTER , CENTER );
textSize ( 50 );
fill ( 200 );
text (mode, 0 , 0 );
textSize ( 25 );
if (matchCount < targetSequence. length ) {
let nextKey = targetSequence[matchCount];
text ( `Next: ${ nextKey }` , 0 , 50 );
}
}
function keyPressed () {
if (mode !== PLAYING_MODE ) { return ; }
let nextKey = targetSequence[matchCount];
if (key === nextKey) {
matchCount ++ ;
if (matchCount === targetSequence. length ) {
mode = "Success! 🎉" ;
}
} else {
mode = "Sorry 😞" ;
}
} Copy This strategy makes it possible to match sequences that contain repeated items, for example “1123” or “12112”.
let PLAYING_MODE = "Playing" ;
let mode = PLAYING_MODE ;
let targetSequence = [ "1" , "2" , "1" , "1" , "2" ];
let matchCount = 0 ;
function setup () {
createCanvas (windowWidth, windowHeight);
}
function draw () {
background ( 100 );
translate (width / 2 , height / 2 );
textAlign ( CENTER , CENTER );
textSize ( 50 );
fill ( 200 );
text (mode, 0 , 0 );
textSize ( 25 );
if (matchCount < targetSequence. length ) {
let nextKey = targetSequence[matchCount];
text ( `Next: ${ nextKey }` , 0 , 50 );
}
}
function keyPressed () {
if (mode !== PLAYING_MODE ) { return ; }
let nextKey = targetSequence[matchCount];
if (key === nextKey) {
matchCount ++ ;
if (matchCount === targetSequence. length ) {
mode = "Success! 🎉" ;
}
} else {
mode = "Sorry 😞" ;
}
} Copy Here’s another approach. It uses a string to collect the sequence of events that has occurred so far. (It uses a string instead of an array, because the code is simpler to test whether one string is a prefix of another.)
let PLAYING_MODE = "Playing" ;
let mode = PLAYING_MODE ;
let targetSequence = "1234" ;
let observedSequence = "" ;
function setup () {
createCanvas (windowWidth, windowHeight);
}
function draw () {
background ( 100 );
translate (width / 2 , height / 2 );
textAlign ( CENTER , CENTER );
textSize ( 50 );
fill ( 200 );
text (mode, 0 , 0 );
textSize ( 25 );
if (observedSequence. length < targetSequence. length ) {
let nextKey = targetSequence. charAt (observedSequence. length );
text ( `Next: ${ nextKey }` , 0 , 50 );
}
}
function keyPressed () {
if (mode !== PLAYING_MODE ) { return ; }
observedSequence += key;
if (observedSequence === targetSequence) {
mode = "Success! 🎉" ;
} else if ( ! targetSequence. startsWith (observedSequence)) {
mode = "Sorry 😞" ;
}
} Copy This approach can be extended to match one of a number of sequences. Here the user can enter any of the strings “1234”, “1221”, or “4321”.
This code makes use of the Array methods.
let PLAYING_MODE = "Playing" ;
let mode = PLAYING_MODE ;
let targetSequences = [ "1234" , "1221" , "4321" ];
let observedSequence = "" ;
function setup () {
createCanvas (windowWidth, windowHeight);
}
function draw () {
background ( 100 );
translate (width / 2 , height / 2 );
textAlign ( CENTER , CENTER );
textSize ( 50 );
fill ( 200 );
text (mode, 0 , 0 );
textSize ( 25 );
let nextKeys = targetSequences
. filter ( seq => seq != observedSequence)
. filter ( seq => seq. startsWith (observedSequence))
. map ( seq => seq. charAt (observedSequence. length ));
if (nextKeys. length >= 0 ) {
let sortedNextKeys = Array. from ( new Set (nextKeys));
sortedNextKeys. sort ();
text ( `Next: ${ sortedNextKeys . join ( " or " ) }` , 0 , 50 );
}
}
function keyPressed () {
if (mode !== PLAYING_MODE ) { return ; }
observedSequence += key;
if (targetSequences. includes (observedSequence)) {
mode = "Success! 🎉" ;
} else if ( ! targetSequences. some ( seq => seq. startsWith (observedSequence))) {
mode = "Sorry 😞" ;
}
} Copy
Try it on OpenProcessing: Detecting Sequences