views:

1368

answers:

7
+14  Q: 

Konami Code in C#

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?

Update 1

This is in a standard C# windows forms app.

+5  A: 

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"

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
+2  A: 

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
A: 

should there be a time table for execution? you can hit the "UUDDLRLRBABA" sequence but at a keystroke of 1 per minute

The second answer that I provided handles enforcement of a window within which the code must be entered. Hope that helps!
James
+17  A: 

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
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
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
Bug fixed with a special case.
Sam Meldrum
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
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
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
@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
Awesome thread :]
Hal
+3  A: 

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
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
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
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
+2  A: 

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
A: 

hi... 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

ps.: If people in my work find out that I stick a konami code here, they'll be SOOOoo upset with me... hahahaha!!!!