Код Konami в C # - PullRequest
       88

Код Konami в C #

22 голосов
/ 22 января 2009

Я хочу, чтобы приложение C # реализовало код Konami для отображения пасхального яйца. http://en.wikipedia.org/wiki/Konami_Code

Каков наилучший способ сделать это?

Это стандартное приложение C # для оконных форм.

Ответы [ 12 ]

24 голосов
/ 22 января 2009

В окнах форм у меня был бы класс, который знает, что такое последовательность, и содержит состояние, в котором вы находитесь в последовательности. Нечто подобное должно сделать это.

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

Чтобы использовать его, вам понадобится что-то в коде вашей формы, реагирующее на события key up. Примерно так должно это сделать:

    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.

8 голосов
/ 02 мая 2009

Правильная последовательность, так же, как это реализовала бы сама Konami:

  • получить ввод
  • если вход равен байту в индексе массива кода, индекс приращения
    • остальное, чистый индекс
  • если индекс больше длины кода, код правильный

Вот как НЕ сделать этого:

  • Накапливает буфер нажатий клавиш, а затем выполняет байтовое сравнение строк. Неэффективно, в лучшем случае. Вы выполняете вызовы в подпрограммах разбора строк для каждого нажатия клавиши в форме, и эти процедуры медленны и громоздки по сравнению с некоторыми простыми шагами, которые можно предпринять, чтобы получить тот же точный эффект.

  • Конечный автомат, который каждый раз ломается, если вы повторяете последовательности в коде.

  • Конечный автомат, в котором жестко заданы «особые случаи». Теперь вы не можете вносить изменения в одном месте. Вы должны изменить строку кода и добавить новый код, чтобы иметь дело с неверно реализованным конечным автоматом.

  • Создание объекта 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;
5 голосов
/ 22 января 2009

Поймать нажатия клавиш в 13 (или любое другое подмножество кода, так как вы, вероятно, не хотите включать клавишу START) список / массив / строку / что угодно, прежде чем обрабатывать их нормально. Каждый раз, когда добавляется ключ, если (и только если) это последний ключ в серии, сопоставляйте буфер с правильным кодом конами.

Мое предложение: если они нажмут клавишу со стрелкой, сопоставьте ее с разумной буквой ... затем сопоставьте B и A, просто очистив буфер для любого другого нажатия клавиши.

Затем, сделав буфер строкой, сравните его с: "UUDDLRLRBABA"

4 голосов
/ 02 мая 2009

По запросу, вот класс, который решает «проблему» возможности вводить последовательность слишком медленно, чтобы быть «подобным секретному коду». ;)

Исходный код в картридже 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;

        }
    }
}
2 голосов
/ 22 января 2009

Я рекомендую вам реализовать в виде списка поисковых событий и ссылочный указатель «перехвата» на элементы этого списка.

Концептуально вы начинаете указатель захвата на первый элемент списка поиска. Если самое следующее событие соответствует элементу поиска, указатель захвата увеличивается до следующего элемента. В противном случае он сбрасывается в начало.

Если указатель увеличивается после последнего элемента, у вас есть полное совпадение.

1 голос
/ 26 ноября 2016

Вот еще одна реализация, основанная на ответе Джеймса и комментариях:

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".
  • Конечно, сбрасывает последовательность, если набран неправильный ключ.
0 голосов
/ 12 июня 2015

Вот довольно простое и эффективное решение:

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!");
}
0 голосов
/ 08 апреля 2014

Я знаю, что это старый вопрос, но я отправился в то же путешествие в 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

0 голосов
/ 01 марта 2013

Ответ можно найти в Реактивные расширения . Вам нужен скользящий буфер для этого, чтобы работать. Это означает, что вы должны сравнить последние десять нажатий клавиш с кодом 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"));
0 голосов
/ 23 апреля 2012

Я прочитал все ответы и обнаружил, что повторный ввод инициала последовательности был распространенной проблемой реализаций. Следующее является простой реализацией без повторения исходной проблемы. Никаких особых случаев, на самом деле ничего не написано жестко, целые числа, указанные в классе, предназначены только для значения по умолчанию.

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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...