【问题标题】:Konami Code in C#C# 中的 Konami 代码
【发布时间】:2009-01-22 16:17:17
【问题描述】:

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

最好的方法是什么?

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

【问题讨论】:

  • 您将不得不提供更多细节。你在做什么类型的应用程序,控制台、GDI+、WPF?只需将键盘键读入缓冲区并测试匹配即可。
  • 一些有趣的链接来自维基百科文章:pcworld.com/article/163936/…。显然 ESPN 也被丑化了!

标签: c# .net winforms


【解决方案1】:

在 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。

【讨论】:

  • 您的状态表不是很完美(如果您键入“Up-up-up”,您的代码将重新开始寻找“up-up”,而不是寻找“down-down” ",但有限状态机是可行的方法。
  • 詹姆斯,你是对的。你需要解决这个问题。你可以特殊情况下,2 up 后的 Up 将位置留在 1。
  • 用特殊情况修复了错误。
  • 使用特殊情况修复状态机似乎不是解决此问题的正确方法。然后,每次更改“密码”时,您都需要调整硬编码的“修复”。我提供了一个更简单、更轻量级的新解决方案来回答这个问题。
  • @James,这并不是为了成为生产就绪代码 - 这不是 SO 的用途 - 我认为这是一个很好的起点。我不认为代码特别混乱 - 从呈现的逻辑可以清楚地看出它在做什么。就个人而言,我觉得你的更难理解。目标并不总是“整洁”,但可维护性和易于理解应该是最重要的。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    这是另一个实现,基于James answer 和 cmets:

    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),但请注意,只有在键入序列中的键时才能访问它。
    • 接受大小写“UUUUUUUUUDDLRBA”。
    • 当然,如果键入错误的键,则会重置序列。

    【讨论】:

      【解决方案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;
      
              }
          }
      }
      

      【讨论】:

        【解决方案5】:

        正确的顺序,与 Konami 本身的实现方式相同:

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

        以下是不这样做的方法:

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

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

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

        • 实例化一个 List 对象来保存一些简单的东西,比如一个字符列表。

        • 涉及字符串对象。

        所以,这里是如何做到的:

        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;
        

        【讨论】:

        • 这是不正确的。 up, up, up, down, ...(正确的代码前面有一个向上的按键)不会被这个代码识别。开头的任何重复序列都是不正确的 - 如果正确的代码是 UDUDLR,那么按 UDUDUDLR 将不会被识别,尽管它包含正确的代码。
        • 我有。即使使用最新的编辑,它仍然不正确。发送 UUDDLRLRBA 会导致成功。发送 UUUDDLRLRBA 会导致失败,即使它包含正确的代码。其实因为offest没有reset,所以发送UBUUDDLRLRBA(里面包含了正确的code)并没有报错。
        • 上面的代码按修改后的工作。这是在 C# 中实现的最接近 Konami 使用的原始机器代码的实现方式。根据您的不满,现在已对其进行了更改,以通过额外的“向上”键事件来解决问题。请注意,最初的 Konami 实现也遇到了同样的问题。
        • 真正的 Konami 代码在 Konami 游戏中完全按照我演示的方式实现。在Contra中为自己尝试一下。额外的 U 将使代码无效。
        【解决方案6】:

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

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

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

        【讨论】:

        • 它可以解决损坏的状态机问题,但效率低下。然后,您将在每次击键时进行(代码长度)比较并涉及 String 对象。
        【解决方案7】:

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

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

        如果指针在最后一个元素之后递增,则表示完全匹配。

        【讨论】:

          【解决方案8】:

          我正在寻找同样的东西,我想出了一个非常简单的代码,它可以工作。 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
          

          【讨论】:

            【解决方案9】:

            答案可以在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");
                                                                });
            

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

            编辑二:我也破解了超时解决方案。 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】:

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

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

              【讨论】:

              • 如果我的作弊码是“a a b a a c”并且我输入“a a b a a b”,那么我可以输入“a a c”来完成一个有效的序列吗?我不这么认为这段代码。
              【解决方案11】:

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

              【讨论】:

              • 虽然此链接可能会回答问题,但最好包含答案here 的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。
              猜你喜欢
              • 2010-12-05
              • 2021-05-24
              • 2012-07-21
              • 1970-01-01
              • 1970-01-01
              • 2020-06-16
              • 1970-01-01
              • 1970-01-01
              • 2011-02-24
              相关资源
              最近更新 更多