C#中的Konami代码

时间:2009-01-22 16:17:18

标签: c# .net winforms

我希望有一个C#应用程序实现Konami代码来显示复活节彩蛋。 http://en.wikipedia.org/wiki/Konami_Code

这样做的最佳方式是什么?

这是在标准的C#windows窗体应用程序中。

12 个答案:

答案 0 :(得分:24)

在Windows窗体中,我会有一个类知道序列是什么,并保持序列中的位置状态。这样的事情应该做到。

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;
        }
    }
}

要使用它,您需要在表单的代码中响应密钥事件。这样的事情应该这样做:

    private KonamiSequence sequence = new KonamiSequence();

    private void Form1_KeyUp(object sender, KeyEventArgs e) {
        if (sequence.IsCompletedBy(e.KeyCode)) {
            MessageBox.Show("KONAMI!!!");
        }
    }

希望这足以满足您的需求。对于WPF,您需要的细微差别非常相似(请参阅编辑历史记录#1)。

编辑:为winforms而不是wpf更新。

答案 1 :(得分:8)

正确的顺序,就像Konami本身实现它一样:

  • 获取输入
  • 如果输入等于代码数组索引处的字节,则增加索引
    • 其他,清除索引
  • 如果index大于代码长度,则代码正确

以下是不这样做的方法:

  • 累积按键缓冲区,然后进行逐字节字符串比较。充其量只是效率低下。你正在为表单上的每个键按下调用字符串解析例程,并且这些例程比一些简单的步骤可以获得相同的确切效果而缓慢而笨重。

  • 有限状态机,如果在代码中重复序列,每次都会中断。

  • 具有“特殊情况”硬编码的有限状态机。现在,您无法在一个地方进行修改。您必须更改代码字符串并添加新代码以处理不适当实现的状态机。

  • 实例化一个List对象以保存简单的字符列表。

  • 参与String对象。

所以,这是如何做到的:

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;
        }
    }
}

现在,它很快,不会打扰字符串或实例化任何散装而不是数组,并且对代码的更改就像修改数组一样简单。

构造函数中的字段初始化取代了与所需值等效的硬编码常量。如果我们使用常量,我们可以将代码缩短6个左右的“行”。这有点浪费,但允许类尽可能轻松地适应新代码 - 您只需要更改数组列表。另外,所有“批量”都是在实例化时处理的,因此它不会影响我们的目标方法的效率。

乍一看,这段代码可以更简单。只要您在正确的代码输入上重置值,就不需要模数。

核心逻辑实际上可以组成一行代码:

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

答案 2 :(得分:5)

在正常处理之前,将按键捕捉到13(或代码的任何子集,因为您可能不想包含START键) - 字符列表/数组/字符串/等等。每次添加一个键时,如果(并且仅当)它是该系列中的最后一个键,则将缓冲区与正确的konami代码匹配。

我的建议是,如果他们按箭头键,将其映射到合理的字母......然后映射B和A,只需清除任何其他按键的缓冲区。

然后,将缓冲区设为字符串,将其与“UUDDLRLRBABA”

进行比较

答案 3 :(得分:4)

根据要求,这是一个解决“能够进入序列太慢而无法像”密码“的问题的类。” ;)

NES盒式磁带中的原始代码将在帧例程中被调用,因此可以通过计算执行过程来跟踪时间。

由于我们被降级为事件驱动的面向对象编程,我们将不得不涉及事件。由于这些事件需要强制执行“到期”,我们将不得不涉及一个Timer对象。

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;

        }
    }
}

答案 4 :(得分:2)

我建议你实现一个搜索事件列表和一个指向该列表元素的“捕获”引用指针。

从概念上讲,您启动捕获指针指向搜索列表的第一个元素。如果下一个事件与搜索元素匹配,则捕获指针将递增到下一个元素。否则,它将重置为开头。

如果指针超过最后一个元素,则表示您完全匹配。

答案 5 :(得分:1)

这是另一个基于James answer和评论的实现:

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.Lengthsee this article),但请注意,只有在输入序列中的密钥时才能访问它。
  • 接受案件&#34; UUUUUUUUUUDDLRLRBA&#34;。
  • 当然,如果键入了错误的密钥,则会重置序列。

答案 6 :(得分:0)

是否应该有执行时间表?您可以点击“UUDDLRLRBABA”序列,但按每分钟1次键击

答案 7 :(得分:0)

我正在寻找相同的东西,我想出了一个非常简单的代码,只是有效。 键盘上的Keypreview必须为True才能在表单上声明一个名为“konami”的字符串

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

答案 8 :(得分:0)

我已经阅读了所有答案,并发现重复输入序列的初始是实现的常见问题。以下是一个简单的实现,没有遇到重复的初始问题。没有特殊情况,没有什么是真正硬编码的,类中指定的整数仅用于默认值。

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();
}

答案 9 :(得分:0)

答案可以在Reactive Extensions中找到。你需要一个滑动缓冲区才能工作。这意味着您必须将最近的十次击键与Konami代码进行比较。这可以使用两个不同的语句

  • 获取溪流的窗口(最终导致10 同步流)
  • 用于将每个流汇总为IList的缓冲区

RX中的缓冲区为我们完成这两项任务。缓冲最后10个项目并跳过1个(这样可以有效地创建10个缓冲区)。

        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");
                                                    });

编辑:删除了定时解决方案。太复杂了

编辑II:我也破解了超时解决方案。 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"));

答案 10 :(得分:0)

我知道这是一个老问题,但我在VB中开始了同样的旅程。我为它创建了一个类:

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

这只是konami类使用背后的示例表单代码。

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

答案 11 :(得分:0)

这是一个相当简单有效的解决方案:

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);
    }
}

使用示例:

private readonly KonamiSequence _konamiSequence = new KonamiSequence();

private void KonamiForm_KeyDown(object sender, KeyEventArgs e)
{
    if (_konamiSequence.IsCompletedBy(e.KeyCode))
        MessageBox.Show("Konami!");
}