C#中的Konami代码

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

做这个的最好方式是什么?

这是一个标准的C#windows窗体应用程序。

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

using System; using System.Collections.Generic; using System.Windows.Forms; namespace WindowsFormsApplication3 { public class KonamiSequence { List Keys = new List{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更新。

正确的顺序,就像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; } } } 

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

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

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

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

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

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

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

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

根据要求,这里有一个类解决了能够进入序列太慢而不能成为“秘密代码”的“问题”。 ;)

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

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

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

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

应该有执行时间表吗? 您可以点击“UUDDLRLRBABA”序列,但每按一次键击一次

我正在寻找相同的东西,我想出了一个非常简单的代码,只是有效。 键盘上的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 

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

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

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

  var keysIO = Observable.FromEventPattern(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(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")); 

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

 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 sequence; public static readonly KonamiCode Default=new KonamiCode(); } 

我知道这是一个老问题,但我在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

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

 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 _inputKeys = new Queue(); 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!"); } 

这是另一个实现,基于詹姆斯的回答和评论:

 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 ( 请参阅此文章 ),但请注意,只有在键入序列中的键时才能访问它。
  • 接受案例“UUUUUUUUUUDDLRLRBA”。
  • 当然,如果键入了错误的键,则重置序列。