c# - tutorial - windows forms visual studio 2017
Código de Konami en C# (12)
Estoy buscando que una aplicación de C # implemente el código de Konami para mostrar un huevo de Pascua. http://en.wikipedia.org/wiki/Konami_Code
¿Cuál es la mejor manera de hacer esto?
Esto se encuentra en una aplicación estándar de formularios de Windows C #.
¿Debería haber una tabla de tiempo para la ejecución? puede pulsar la secuencia "UUDDLRLRBABA" pero con una pulsación de tecla de 1 por minuto
Capture las pulsaciones de teclas en un 13 (o en cualquier subconjunto del código, ya que probablemente no desee incluir la clave START) -character list / array / string / whatever antes de procesarlos normalmente. Cada vez que se agrega una clave, si (y solo si) es la última clave de la serie, haga coincidir el buffer con el código konami correcto.
Mi sugerencia es que, si presionan una tecla de flecha, lo asignen a la letra sensible ... luego, también mapeen B y A, simplemente borrando el búfer para cualquier otra pulsación de tecla.
Luego, haciendo del buffer una cadena, compárelo con: "UUDDLRLRBABA"
En las formas de Windows tendría una clase que sabe cuál es la secuencia y mantiene el estado de donde se encuentra en la secuencia. Algo como esto debería hacerlo.
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;
}
}
}
Para usarlo, necesitaría algo en el código de su Formulario que responda a eventos clave. Algo como esto debería hacerlo:
private KonamiSequence sequence = new KonamiSequence();
private void Form1_KeyUp(object sender, KeyEventArgs e) {
if (sequence.IsCompletedBy(e.KeyCode)) {
MessageBox.Show("KONAMI!!!");
}
}
Espero que sea suficiente para darle lo que necesita. Para WPF necesitarás pequeñas diferencias es muy similar (mira el historial de edición n. ° 1).
EDITAR: actualizado para winforms en lugar de wpf.
Recomiendo implementar como una lista de eventos de búsqueda y un puntero de referencia de "captura" a los elementos de esa lista.
Conceptualmente, inicia el puntero de captura en el primer elemento de la lista de búsqueda. Si el siguiente evento coincide con el elemento de búsqueda, el puntero de captura se incrementa al siguiente elemento. De lo contrario, se restablece al principio.
Si el puntero se incrementa más allá del último elemento, tiene una coincidencia completa.
Según lo solicitado, aquí hay una clase que resuelve el "problema" de poder ingresar la secuencia demasiado lentamente como para ser "código secreto". ;)
El código original en el cartucho NES se habría llamado dentro de una rutina de cuadro y, por lo tanto, habría seguido el tiempo contando los pasos de ejecución.
Como estamos relegados a la programación orientada a eventos y orientada a objetos, vamos a tener que involucrar eventos. Dado que estos eventos necesitarán forzar una "expiración", vamos a tener que involucrar un objeto 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;
}
}
}
La secuencia correcta, de la misma manera que Konami la habría implementado:
- obtener entrada
- si la entrada es igual a byte en el índice de matriz de códigos, índice de incremento
- else, índice claro
- si el índice es mayor que la longitud del código, el código es correcto
Aquí es cómo NO hacerlo:
Acumulando un buffer de pulsaciones de teclas y luego haciendo una comparación de cadenas byte a byte. Ineficiente, en el mejor de los casos. Está realizando llamadas en rutinas de análisis de cadenas para cada pulsación de tecla en el formulario y esas rutinas son lentas y voluminosas en comparación con algunos pasos simples que podrían tomarse para obtener el mismo efecto exacto.
Una máquina de estados finitos que se rompe cada vez que repites secuencias en el código.
Una máquina de estados finitos que tiene "casos especiales" codificados de forma rígida. Ahora, no puedes hacer modificaciones en un solo lugar. Debe cambiar la cadena del código y agregar un nuevo código para manejar su máquina de estado implementada incorrectamente.
Crea una instancia de un objeto List para mantener algo simple como una lista de caracteres.
Involucrar objetos String.
Entonces, aquí está cómo hacerlo:
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;
}
}
}
Ahora, es rápido, no molesta con cadenas de caracteres ni crea instancias de algo más grande que una matriz, y los cambios en el código son tan simples como modificar la matriz.
La inicialización del campo en el constructor toma el lugar de las constantes de codificación dura que son equivalentes a los valores necesarios. Si usamos constantes, podríamos haber acortado el código en 6 o más "líneas". Esto es un poco derrochador, pero permite que la clase sea tan fácilmente adaptable a los nuevos códigos como sea posible; solo necesita cambiar la lista de arreglos. Además, todo el "volumen" se maneja en el momento de la creación de instancias, por lo que no afecta la eficiencia de nuestro método objetivo.
En un segundo vistazo, este código podría hacerse aún más simple. El módulo no es necesario, siempre y cuando esté restableciendo el valor en la entrada de código correcta.
La lógica central podría convertirse en una sola línea de código:
_sequenceIndex = (_code[_sequenceIndex] == key) ? ++_sequenceIndex : 0;
Estaba buscando lo mismo y se me ocurrió un código MUY simple que simplemente funciona. Keypreview tiene que ser True en el formulario declara una cadena llamada "konami" en tu formulario
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
La respuesta se puede encontrar en Extensiones reactivas . Necesitas un buffer deslizante para que esto funcione. Lo que significa que tiene que comparar las últimas diez teclas con el código de Konami. Esto funciona usando dos declaraciones diferentes
- Una ventana para obtener una secuencia de transmisiones (lo que eventualmente lleva a 10 transmisiones simultáneas)
- Un buffer para resumir cada flujo en un IList
El buffer dentro de RX hace ambas tareas para nosotros. Almacena en búfer los últimos 10 elementos y saltea 1 (de manera que crea 10 búferes).
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");
});
EDITAR: eliminó la solución temporizada., Demasiado compleja
EDIT II: Rompí la solución de tiempo de espera también. La belleza de 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"));
He leído todas las respuestas y descubrí que la entrada repetida de la inicial de la secuencia era un problema común de las implementaciones. La siguiente es una implementación simple sin encontrar la repetición del problema inicial. No hay casos especiales, nada está realmente codificado, los enteros especificados en la clase son solo para un valor predeterminado.
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();
}
Sé que es una vieja pregunta, pero me embarqué en este mismo viaje en VB. Creé una clase para eso:
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
Este es solo un código de formulario de muestra detrás del uso de la clase 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
Aquí hay una solución bastante simple y eficiente:
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);
}
}
Ejemplo de uso:
private readonly KonamiSequence _konamiSequence = new KonamiSequence();
private void KonamiForm_KeyDown(object sender, KeyEventArgs e)
{
if (_konamiSequence.IsCompletedBy(e.KeyCode))
MessageBox.Show("Konami!");
}
Aquí hay otra implementación, basada en la respuesta y los comentarios de James :
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;
}
}
}
- No molesta el almacenamiento en caché de
_code.Length
( consulte este artículo ), sin embargo, tenga en cuenta que se accede solo cuando se escribe una clave de la secuencia. - Acepta el caso "UUUUUUUUUUDDLRLRBA".
- Por supuesto, restablece la secuencia si se escribe una clave incorrecta.