24

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?

This is in a standard C# windows forms app.

Mike G
  • 4,232
  • 9
  • 40
  • 66
Anthony D
  • 10,877
  • 11
  • 46
  • 67
  • You are going to have to give some more details. What type of app are you doing, Console, GDI+, WPF? Just read the keyboard keys into a buffer and test for a match. – smaclell Jan 22 '09 at 16:20
  • Something interesting linked from the Wikipedia article: http://www.pcworld.com/article/163936/espncom_the_konami_code_and_a_whole_lotta_ponies.html . Apparently ESPN got cornified too! – Michael Myers May 01 '09 at 21:12

11 Answers11

29

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.

Sam Meldrum
  • 13,835
  • 6
  • 33
  • 40
  • Your state table isn't quite perfect (If you typed "Up-up-up", you code would restart back looking for "up-up" again, instead of looking for "down-down", but a finite state machine is the way to go. – James Curran Jan 22 '09 at 17:17
  • James, you're right. You'll need a fix for that. You could special case that an Up after 2 ups leaves position at 1. – Sam Meldrum Jan 22 '09 at 17:23
  • Bug fixed with a special case. – Sam Meldrum Jan 22 '09 at 17:30
  • 2
    Using special cases to fix the state machine does not seem like the right way to solve this problem. You would then need to adjust your hard coded "fix" every time you change the "secret code." I have provided a new solution that is simpler and more lightweight as an answer to this question. – James May 01 '09 at 21:48
  • Your code is messy, it should just compare incoming keys with the expected next key. If this test fails reset the pointer and compare again. – mP. May 01 '09 at 22:09
  • mP, that would mean that an invalid key causes the entire code to have to be entered again. So Up, Up, Up would invalidate the sequence and require the user to press two MORE Ups and continue the code, rather than just continuing the code. – James May 01 '09 at 23:35
  • 4
    @James, this wasn't intended to be production ready code - that's not what SO is for - I figured it was a good starting point. I don't think the code is particularly messy - it is clear what it is doing from the logic presented. Personally, I find yours harder to comprehend. The goal is not always "neatness" but maintainability and ease of understanding should be paramount. – Sam Meldrum May 05 '09 at 16:07
  • Hi. I wrote a solution that keeps track of all valid input sequences. So it solves the "up up up" problem as well as more complicated problems, described in answer. I wrote it in javascript but should translate easily to C#. https://stackoverflow.com/a/71309611 – Peaceful James Mar 01 '22 at 14:11
8

The correct sequence, the same way Konami itself would have implemented it:

  • get input
  • if input equals byte at index of code array, increment index
    • else, clear index
  • if index is greater than code length, code is correct

Here is how NOT to do it:

  • Accumulating a buffer of keystrokes and then doing a byte-by-byte string compare. Inefficient, at best. You're making calls into string parsing routines for every key press on the form and those routines are slow and bulky compared to some simple steps that could be taken to get the same exact effect.

  • A finite state machine that breaks every time if you repeat sequences in the code.

  • A finite state machine that has "special cases" hard coded. Now, you can't make modifications in one place. You have to change the code string plus add new code to deal with your inappropriately implemented state machine.

  • Instantiate a List object to hold something simple like a list of characters.

  • Involve String objects.

So, here's how to it:

using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public class KonamiSequence
    {
        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 _offset;
        private readonly int _length, _target;

        public KonamiSequence()
        {
            _length = _code.Length - 1;
            _target = _code.Length;
        }

        public bool IsCompletedBy(Keys key)
        {
            _offset %= _target;

            if (key == _code[_offset]) _offset++;
            else if (key == _code[0])  _offset = 2;  // repeat index

            return _offset > _length;
        }
    }
}

Now, it's fast, does not bother with strings or instantiate anything bulker than an array, and changes to the code are as simple as modifying the array.

The field initialization in the constructor takes the place of hard coding constants that are equivalent to the values needed. If we used constants, we could have shortened the code by 6 or so "lines." This is slightly wasteful, but allows the class to be as easily adaptable to new codes as possible -- you just need to change the array list. Plus, all of the "bulk" is handled at the time of instantiation, so it is not affecting the efficiency of our target method.

On second glance, this code could be made even simpler. The modulus is not needed, so long as you are resetting the value on correct code entry.

The core logic could actually be made into a single line of code:

_sequenceIndex =  (_code[_sequenceIndex] == key) ? ++_sequenceIndex : 0;
James
  • 1,788
  • 4
  • 22
  • 28
  • This is incorrect. up, up, up, down, ... (the correct code preceded by an up keystroke before it) would not be recognized by this code. Any repeated sequence at the beginning would not be correct - if the correct code was UDUDLR, then pressing UDUDUDLR would not be recognized, although it contains the correct code. – Philip Rieck May 01 '09 at 21:55
  • I have. Even with your latest edit, it is still not correct. Sending UUDDLRLRBA results in success as it should. Sending UUUDDLRLRBA results in failure even though it contains the correct code. In fact, because the offest is not reset, sending UBUUDDLRLRBA (which contains the correct code) is not reported as correct. – Philip Rieck May 05 '09 at 16:03
  • The code above works as revised. It is the closest possible thing to implement in C# that matches the original machine code that Konami used. Per your displeasure, it has now been changed to solve the problem with extra "Up" key events. Please note that the original Konami implementation suffered from the same problem. – James May 07 '09 at 18:54
  • The REAL Konami code is implemented in Konami games exactly the way I have demonstrated. Try if for yourself in Contra. An extra U will invalidate the code. – James Jul 25 '11 at 04:50
5

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"

user54650
  • 4,388
  • 2
  • 24
  • 27
  • It would solve the broken state machine problem, but would be inefficient. You're then causing (codelength) comparisons per keystroke and involving String objects. – James May 01 '09 at 21:59
4

As requested, here's an class that resolves the "issue" of being able to enter the sequence too slowly to be "secret code like." ;)

The original code in the NES cartridge would have been called within a frame routine and thus would have tracked time by counting execution passes.

Since we're relegated to event-driven, object oriented programming, we're going to have to involve events. Since these events will need to enforce an "expiration," we're going to have to involve a Timer object.

using System;
using System.Windows.Forms;
using Timer=System.Timers.Timer;

namespace WindowsApplication1
{
    public class KonamiSequence
    {
        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 _sequenceIndex;

        private readonly int _codeLength;
        private readonly int _sequenceMax;

        private readonly Timer _quantum = new Timer();

        public KonamiSequence()
        {
            _codeLength = _code.Length - 1;
            _sequenceMax = _code.Length;

            _quantum.Interval = 3000; //ms before reset
            _quantum.Elapsed += timeout;
        }

        public bool IsCompletedBy(Keys key)
        {   
            _quantum.Start();      

            _sequenceIndex %= _sequenceMax;
            _sequenceIndex = (_code[_sequenceIndex] == key) ? ++_sequenceIndex : 0;

            return _sequenceIndex > _codeLength;
        }

        private void timeout(object o, EventArgs e)
        {
            _quantum.Stop();
            _sequenceIndex = 0;

        }
    }
}
James
  • 1,788
  • 4
  • 22
  • 28
2

I recommend you implement as a list of search events and a "capture" reference pointer to elements of that list.

Conceptually, you start the capture pointer to the first element of the search list. If the very next event matches the search element, the capture pointer is incremented to the next element. Otherwise, it is reset to the beginning.

If the pointer is incremented past the last element, you have a full match.

spoulson
  • 21,335
  • 15
  • 77
  • 102
1

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!");
}
Christian
  • 310
  • 3
  • 7
1

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;
        }
    }
}
  • Doesn't bother caching _code.Length (see this article), however note it's accessed only when a key from the sequence is typed.
  • Accepts the case "UUUUUUUUUUDDLRLRBA".
  • Of course, resets the sequence if a wrong key is typed.
Community
  • 1
  • 1
Gras Double
  • 15,901
  • 8
  • 56
  • 54
0

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();
}
Ken Kin
  • 4,503
  • 3
  • 38
  • 76
  • 1
    If my cheat code is "a a b a a c" and i enter "a a b a a b", can I then just enter "a a c" to complete a valid sequence? I don't think so with this code. – Peaceful James Mar 01 '22 at 14:10
  • @PeacefulJames You're correct , that case is intentionally prevented. You might want to consider another answer here which allows repeated initial input. – Ken Kin May 26 '22 at 16:26
0

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

  • A window to get a stream of streams (eventually leading to 10 simultaneous streams)
  • A buffer to summarize each stream into an IList

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"));
markwilde
  • 1,892
  • 1
  • 16
  • 23
0

I know it's an old question, but I embarked on this same journey in VB. I created a class for it:

Public Class Konami
    ' Here is the pattern to match
    Property KonamiOrder As List(Of Keys) = New List(Of Keys) From {Keys.Up, Keys.Up, Keys.Down, Keys.Down, Keys.Left, Keys.Right, Keys.Left, Keys.Right, Keys.B, Keys.A}

    ' Just calling these out ahead of time
    Property sequence As List(Of Boolean)
    Property ix As Integer = 0

    ' Hey new object, better set the important bits
    Public Sub New()
        me.reset()
    End Sub

    ' Reset on pattern failure, or completion
    Public Function reset() As Boolean
        Me.sequence = New List(Of Boolean) From {False, False, False, False, False, False, False, False, False, False}
        ix = 0

    End Function


    ' Here's where all the action happens
    Public Function checkKey(keycode As Keys)
        'Check to see what they pressed
        If sequence(ix) = False And keycode = KonamiOrder(ix) Then
            ' Hurray, they pressed the right key, better keep track of it
            sequence(ix) = True
            ix += 1
        Else
            ' Nope, reset
            Me.reset()
        End If

        'Is the code complete and correct?
        If sequence.Contains(False) Then
            ' Nope, send back failure
            Return False
        Else
            'Yep, reset so it can be used again and send back a success
            Me.reset()
            Return True
        End If
    End Function
End Class

This is just a sample form's code behind on the usage of the konami class.

Public Class Form1
    Private oKonami As New Konami

    Private Sub Form1_KeyUp(sender As Object, e As KeyEventArgs) Handles Me.KeyUp
        ' Send the Key press on its way, and get some logic going
        If oKonami.checkKey(e.KeyCode) Then
            ' Congrats, pattern match
            MsgBox("Konami Code Entered")
        End If
    End Sub

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        ' This will intercept the key events on this form
        Me.KeyPreview = True
    End Sub
End Class

https://github.com/the1337moderator/KonamiCodeforVB.net

  • While this link may answer the question, it is better to include the essential parts of the answer [here](http://meta.stackexchange.com/a/8259) and provide the link for reference. Link-only answers can become invalid if the linked page changes. – bummi Apr 08 '14 at 14:39
0

I was searching for the same thing and I came up with a VERY simple code that just works. Keypreview have to be True on the form declare one string named "konami" on your form

Private Sub frm_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp
    Dim i As String = "UpUpDownDownLeftRightLeftRightBA"
    If (e.KeyCode.ToString = "Up") And (konami <> "Up") Then konami = ""
    konami = konami & e.KeyCode.ToString
    'Debug.Print(konami)
    If konami = i Then '' << INSERT YOUR MESSAGE HERE >>  ''
    If e.KeyCode.ToString = "Return" Then konami = ""
    If konami.Length > 60 Then konami = ""
End Sub
Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438