Unintended Inspiration

During a project planning meeting a while back, one of our clients made the comment “Wouldn’t it be cool if we had a cheat code in the app?” At the time everyone laughed it off, but I took a note and knew we had one more deliverable for the next release.   It was a no brainer that the Konami Code would be making an appearance.

Setup

The following is an example application that implements two codes or multi-step commands via the IOnGestureListener interface. For the example, I figured that implementing both the Konami Code and the old Mortal Kombat cheat menu code that was used on the SEGA Genesis would be helpful as they have a different number of commands. The logic for this implementation is also inspired by the JavaScript version found at snaptortoise.

Codes

To capture the codes, I setup a simple helper class. This class defines the direction and touch keys and, also, the two cheat codes.

using System;

namespace EightBot.MonoDroid.CheatCode
{
    public static class CodeHelper
    {
        public const String KEY_UP = ”UP”,
        KEY_DOWN = ”DOWN”,
        KEY_LEFT = ”LEFT”,
        KEY_RIGHT = ”RIGHT”,
        KEY_TAP = ”TAP”;
        
        public static readonly String
[] KonamiCode = { KEY_UP, KEY_UP, KEY_DOWN, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_LEFT, KEY_RIGHT, KEY_TAP, KEY_TAP, KEY_TAP }; public static readonly String[] MortalKombatCode = { KEY_DOWN, KEY_UP, KEY_LEFT, KEY_LEFT, KEY_TAP, KEY_RIGHT, KEY_DOWN }; } }

Command Buffer

Since each cheat code has a different length, I decided to setup a simple class to capture and buffer the previous commands. The CommandBuffer class will capture a fixed number of commands as specified in the constructor. The command buffer essentially acts as a wrapper around a linked list. New commands go to the end of the list and if the command list grows larger than the specified size, then commands are removed from the front. The ProcessCommandList method allows a list of commands to be compared to the current buffer. Since commands are added to the end of the queue, I compare the end of my command queue to the command list provided to the method.  The SequenceEqual method uses the default equality comparer for an object, so if using a custom object, ensure that this has been implemented correctly. If there is a match, the buffer is cleared.

using System;
using System.Collections.Generic;
using System.Linq;

namespace EightBot.MonoDroid.CheatCode
{
    internal class CommandBuffer<T>
    {
        private readonly object _queueLock = new object();

        private LinkedList<T> Buffer { get; set; }

        public int Size { get; private set; }

        public T[] Commands
        {
            get
            {
                lock (_queueLock)
                {
                    return Buffer.ToArray();
                }
            }
        }

        public CommandBuffer(int size)
        {
            Size = size;

            Buffer = new LinkedList<T>();
        }

        public void Add(T command)
        {
            Buffer.AddLast(command);

            lock (_queueLock)
            {
                int overflow = Buffer.Count - Size;

                if (overflow > 0)
                    Remove(overflow);
            }
        }

        public void Remove(Int32 count)
        {
            lock (_queueLock)
            {
                while (Buffer.Count > Size)
                {
                    Buffer.RemoveFirst();
                }
            }
        }

        public void Clear()
        {
            lock (_queueLock)
                Buffer.Clear();
        }

        public Boolean ProcessCommandList(T[] commandList)
        {
            if (commandList == null)
                throw new ArgumentNullException("commandList");

            //Too little or too much to do anything with
            if (commandList.Length == 0 || commandList.Length > Size)
                return false;

            bool matchSuccessful = false;

            lock (_queueLock)
                matchSuccessful = commandList.SequenceEqual(Commands.Skip(Commands.Length - commandList.Length));

            if (matchSuccessful)
                Clear();

            return matchSuccessful;
        }
    }
}

Bringing It All Together

To capture the events in my activity, I decided to use the GestureDetector.IOnGestureListener interface as it provided a pretty easy way to get my movements. The OnFling and OnSingleTapUp methods do all command detection.

public bool OnFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
    Task<bool> task = Task.Factory.StartNew(() =>
        {
            float xChange = e2.GetX() - e1.GetX();
            float yChange = e2.GetY() - e1.GetY();

            if (Math.Abs((int) xChange) <= MINIMUM_MOVEMENT_DISTANCE &amp;amp;&amp;amp;
                Math.Abs((int) yChange) <= MINIMUM_MOVEMENT_DISTANCE)
                return false;

            if (Math.Abs(xChange) > Math.Abs(yChange))
                ProcessCommand(xChange > 0 ? CodeHelper.KEY_RIGHT : CodeHelper.KEY_LEFT);
            else
                ProcessCommand(yChange > 0 ? CodeHelper.KEY_DOWN : CodeHelper.KEY_UP);

            return true;
        });

    return task.Result;
}

public bool OnSingleTapUp(MotionEvent e)
{
    Task<bool> task = Task.Factory.StartNew(() =>
        {
            ProcessCommand(CodeHelper.KEY_TAP);
            return true;
        });

    return task.Result;
}

The ProcessCommand method processes the individual commands and performs an action depending on the code provided.

private void ProcessCommand(String command)
{
    _commandBuffer.Add(command);

    string[] commandArray = _commandBuffer.Commands;

    if (_commandBuffer.ProcessCommandList(CodeHelper.MortalKombatCode))
    {
        MediaPlayer.Create(this, Resource.Raw.toasty).Start();
        RunOnUiThread(() => toastyImage.StartAnimation(toastyIn));

        command = "Mortal Kombat Kode";
    }

    if (_commandBuffer.ProcessCommandList(CodeHelper.KonamiCode))
    {
        MediaPlayer.Create(this, Resource.Raw.contra).Start();

        RunOnUiThread(() => contraImage.StartAnimation(moveLefttoRight));
        command = "Konami Code";
    }

    RunOnUiThread(() =>
        {
            tvLastCommand.Text = command;
            tvCommandList.Text = String.Join(Environment.NewLine, commandArray.Reverse());
        });
}

Additional Lives

So, after a quick weekend coding session, I let the client know that their new release was going to have some additional goodies. They were skeptical at first, but a quick demo later and the client was sold. I am not sure if they ever shared the fact that the solution had a code in it, but I like to think that word of it spread over time.

The code for this project can be found on github at EightBot.MonoDroid.CheatCode.

Feel free to leave us a comment and let us know what you think. If you would like to add some cheats to your apps, contact us here.