Friday Update: Detecting the Konami Code with UniRx

Discussion in 'News' started by Proton, Dec 2, 2016.

  1. Proton

    Proton Administrator
    Developer Moderator

    Joined:
    May 5, 2015
    Messages:
    267
    Likes Received:
    419
    A bit of a different update this week, I wanted to show how my programming style has changed between Time Clickers and Time Warpers. The new concept we are using is called Reactive Programming (Rx).

    The Konami Code
    I experienced this code back in the 80s when playing Contra on Nintendo. Wikiepdia describes the Contra code as:
    [​IMG]
    My memory is different, I remember it ending with A,B. Also giving 99 lives not just 30. The mysteries of memory...
    [​IMG]

    The Old Way (Imperative Programming)
    There is a helpful Unity Answers post showing how the Konami code could be detected:
    Code (UnityScript):
       private var konamiCode = ["UpArrow", "UpArrow", "DownArrow", "DownArrow", "LeftArrow", "RightArrow", "LeftArrow", "RightArrow", "B", "A", "Return"];
       private var currentPos : int = 0;
       private var inKonami : boolean = false;
       function OnGUI() {
            var e : Event = Event.current;
            if( e.isKey && Input.anyKeyDown && !inKonami && e.keyCode.ToString() != "None" ) {
                konamiFunction(e.keyCode);
            }
        }

        function Update() {
            if( inKonami ) {
                //Easteregg!
            }
        }

        function konamiFunction( incomingKey) {

            var incomingKeyString = incomingKey.ToString();
            if( incomingKeyString == konamiCode[currentPos] ) {
                print("Unlocked part " + (currentPos + 1) + "/" + konamiCode.length + " with " + incomingKeyString);
                currentPos++;

                if( (currentPos + 1) > konamiCode.length ) {
                    print("You master Konami.");
                    inKonami = true;
                    currentPos = 0;
                }
            }
            else {
                print("You fail Konami at position " + (currentPos + 1) + ", find the ninja in you.");
                currentPos = 0;
            }
        }
    This is probably similar to how I would have written the detection the imperative way. Imperative Programming just means that there is some kind of state of the system changing, in this case the 2 variables currentPos & inKonami. Every time you input a correct key in the pattern, currentPos increments by 1.

    This method does have a problem though, it will not detect the Konami Code if you press the up key 3 times at the start instead of 2. After you press the 3rd up, it will say you failed the pattern and start all over. What you really want it to realize is that although 3 ups doesn't work, you do have 2 ups that could be the start of the correct pattern. But how do you get it to detect that?

    The New Way (Reactive Programming)
    Code (C#):
    /// <summary>
    /// Detect the Konami code: Up,Up,Down,Down,Left,Right,Left,Right,B,A
    /// Unity 5's input system is not reactive, so this uses a RxInput class that provides Cold input.
    /// </summary>
    public class ReactiveKonami : MonoBehaviour {

        void Start() {
            // The sequence of KeyCodes we are looking for
            var konamiCode = new KeyCode[] { KeyCode.UpArrow, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.B, KeyCode.A };

            // The stream of keys being pressed by the Player
            var keyCodeStream = RxInput.KeyCodeStream;

            // UniRx doesn't have Window yet, so this is the closest I could do
            // Start listening for the first KeyCode in the Konami code
            // Spawn a new Buffer starting with that KeyCode which also listens for new KeyCodes
            // As soon as the buffer is the same length as the konamiCode, compare them
            keyCodeStream
                .Where( keyCode => keyCode == konamiCode.First() )
                .SelectMany(keyCode => keyCodeStream.StartWith(keyCode).Buffer(konamiCode.Length).First())
                .Select(buf => buf.SequenceEqual(konamiCode))
                .Where( isKonamiCode => isKonamiCode == true )
                .Subscribe(_ => {
                    Debug.Log("Konami!");
                }).AddTo(this);
        }
    }
     
    This style of code looked strange to me at first, but I have grown to like it. Reactive Programming is all about transforming streams of data into different streams of data. The benefit is you can imagine what the data flow would look like:
    [​IMG]
    This is cool! :cool: It's a cliche among Rx devs, but you start to see streams everywhere.
    [​IMG]

    In this case, the user pressed Up 3 times at the start. Each of those times created a buffer that will listen for keys until the buffer is the same size as the Konami Code, then that buffer is checked to see if it matches.

    You can see it found the correct pattern even though we started it by pressing Up 3 times! ;)

    I think this could be optimized by killing a buffer as soon as it fails to match the pattern, but code clarity was more important here. Plus we could allow the sequence to end with either A,B or B,A pretty easily by making 2 konamiCode variables and checking against both of them. The lengths are the same so we can just check the final buffer against both of them:
    Code (C#):
    .Select(buf => buf.SequenceEqual(konamiCodeAB) || buf.SequenceEqual(konamiCodeBA) )
    I know Netflix uses this style of reactive stream programming, and I love their software. Imagine 1 system that creates a stream of movie recommendations, then they could transform that into a stream of thumbnails to display.

    I've got one more visual display of streams. In our WebGL experiment Time Gunners we found that depositing a bunch of cubes was way more satisfying if there was a slight delay between them appearing. The cubes to deposit got added to a stream, then that stream got transformed into a stream with a little bit of time between each deposit event. The result is a fun to watch real-time network reactive stream:
     
  2. MiguelDreamer

    MiguelDreamer Spec Ops

    Joined:
    Aug 29, 2015
    Messages:
    92
    Likes Received:
    222
    So that's why you and proton were playing time gunners yesterday... (the gif :p)

    Never expected a post like this in a Friday Update o_O @Proton Do you feel more comfortable programming now in Time Warpers, compared to Time Clickers? Really cool update! And yep, depositing cubes in a bunker is the most satisfying thing in Time Gunners! Let's see if in Time Warpers killing crawlers is satisfying too :)
     
    #2 MiguelDreamer, Dec 2, 2016
    Last edited: Dec 2, 2016
    MagicalWriter and Proton like this.
  3. Proton

    Proton Administrator
    Developer Moderator

    Joined:
    May 5, 2015
    Messages:
    267
    Likes Received:
    419
    Comfortability is a strange thing in programming. When I watch videos on new concepts like Rx, a common theme is that within teams there is resistance to using a new technology because people are comfortable with how they do things.

    I started playing FPS games like Doom & Duke Nukem using the arrow keys for movement, but in Half Life I saw the advantage of WASD so I had to transition. The transition was uncomfortable (my K/D ratio suffered for a bit) but eventually worth it.

    The tricky thing is to avoid the Hype Cycle:
    [​IMG]
    This is what contributes to developers being skeptical of new technologies. With Rx I think the peak happened back in 2015.

    This type of blog post is pretty deep behind the scenes. I might do more of these on occasion if there is interest.
     
    MiguelDreamer likes this.