I am looking to have a C# application implement the Konami Code to display an Easter Egg. http://en.wikipedia.org/wiki/Konami_Code
What is the best way to do this?<
In windows forms I would have a class that knows what the sequence is and holds the state of where you are in the sequence. Something like this should do it.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace WindowsFormsApplication3 {
public class KonamiSequence {
List<Keys> Keys = new List<Keys>{System.Windows.Forms.Keys.Up, System.Windows.Forms.Keys.Up,
System.Windows.Forms.Keys.Down, System.Windows.Forms.Keys.Down,
System.Windows.Forms.Keys.Left, System.Windows.Forms.Keys.Right,
System.Windows.Forms.Keys.Left, System.Windows.Forms.Keys.Right,
System.Windows.Forms.Keys.B, System.Windows.Forms.Keys.A};
private int mPosition = -1;
public int Position {
get { return mPosition; }
private set { mPosition = value; }
}
public bool IsCompletedBy(Keys key) {
if (Keys[Position + 1] == key) {
// move to next
Position++;
}
else if (Position == 1 && key == System.Windows.Forms.Keys.Up) {
// stay where we are
}
else if (Keys[0] == key) {
// restart at 1st
Position = 0;
}
else {
// no match in sequence
Position = -1;
}
if (Position == Keys.Count - 1) {
Position = -1;
return true;
}
return false;
}
}
}
To use it, you would need something in your Form's code responding to key up events. Something like this should do it:
private KonamiSequence sequence = new KonamiSequence();
private void Form1_KeyUp(object sender, KeyEventArgs e) {
if (sequence.IsCompletedBy(e.KeyCode)) {
MessageBox.Show("KONAMI!!!");
}
}
Hopefully that's enough to give you what you need. For WPF you will need slight differences is very similar (see edit history #1).
EDIT: updated for winforms instead of wpf.
The answer can be found in Reactive Extensions. You need a sliding buffer for this to work. Meaning you have to compare the latest ten keystrokes with the Konami code. This works using two different statements
The buffer within RX does both these tasks for us. Buffers the last 10 items and skips 1 (so effectively creates 10 buffers).
var keysIO = Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown")
.Select(arg => arg.EventArgs.Key)
.Buffer(10, 1)
.Select(keys => Enumerable.SequenceEqual(keys, _konamiArray))
.Where(result => result)
.Subscribe(i =>
{
Debug.WriteLine("Found Konami");
});
EDIT: Removed the timed solution., too complex
EDIT II: I cracked the time-out solution as well. The beauty of SelectMany :-)
var keysIO = Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown")
.Select(e => e.EventArgs.Key)
.Window(10, 1)
.SelectMany(obs => obs.Buffer(TimeSpan.FromSeconds(10), 10))
.Where(keys => Enumerable.SequenceEqual(_konamiArray, keys))
.Subscribe(keys => Debug.Write("Found Konami"));
Here's a fairly simple and efficient solution:
public class KonamiSequence
{
private static readonly Keys[] KonamiCode = { Keys.Up, Keys.Up, Keys.Down, Keys.Down, Keys.Left, Keys.Right, Keys.Left, Keys.Right, Keys.B, Keys.A };
private readonly Queue<Keys> _inputKeys = new Queue<Keys>();
public bool IsCompletedBy(Keys inputKey)
{
_inputKeys.Enqueue(inputKey);
while (_inputKeys.Count > KonamiCode.Length)
_inputKeys.Dequeue();
return _inputKeys.SequenceEqual(KonamiCode);
}
}
Example usage:
private readonly KonamiSequence _konamiSequence = new KonamiSequence();
private void KonamiForm_KeyDown(object sender, KeyEventArgs e)
{
if (_konamiSequence.IsCompletedBy(e.KeyCode))
MessageBox.Show("Konami!");
}
Here is another implementation, based on James answer and comments:
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public class KonamiSequence
{
private readonly Keys[] _code = { Keys.Up, Keys.Up, Keys.Down, Keys.Down, Keys.Left, Keys.Right, Keys.Left, Keys.Right, Keys.B, Keys.A };
private int _index = 0;
public bool IsCompletedBy(Keys key)
{
if (key == _code[_index]) {
if (_index == _code.Length - 1) {
_index = 0;
return true;
}
++_index;
} else {
_index = 0;
}
return false;
}
}
}
_code.Length
(see this article), however note it's accessed only when a key from the sequence is typed.I've read all the answers, and found that repeated input of the initial of the sequence was a common problem of implementations. The following is a simple implementation without encountering the repeat of initial problem. No special cases, nothing is really hard-coded, the integers specified in the class is only for a default value.
public partial class KonamiCode {
public bool IsCompletedBy(int keyValue) {
for(var i=sequence.Count; i-->0; ) {
if(sequence[i]!=keyValue) {
if(0==i)
count=0;
continue;
}
if(count!=i)
continue;
++count;
break;
}
var isCompleted=sequence.Count==count;
count=isCompleted?0:count;
return isCompleted;
}
public KonamiCode(int[] sequence=default(int[])) {
this.sequence=
sequence??new[] { 38, 38, 40, 40, 37, 39, 37, 39, 66, 65 };
}
int count;
IList<int> sequence;
public static readonly KonamiCode Default=new KonamiCode();
}
Catch keypresses into a 13(or whatever subset of the code, since you probably don't want to include the START key)-character list/array/string/whatever before processing them normally. Every time a key is added, if (and only if) it's the last key in the series, match the buffer against the correct konami code.
My suggestion is, if they hit an arrow key, map it to the sensible letter... then map B and A as well, simply clearing the buffer for any other keypress.
Then, making the buffer a string, compare it to: "UUDDLRLRBABA"