Quantcast
Channel: BlueLine Game Studios » windows
Viewing all articles
Browse latest Browse all 4

Konami Code in XNA – for Windows, Xbox, and Windows Phone (WP7)

$
0
0

The Konami Code is a fun part of gaming culture and a very common method for adding Easter Eggs to games. After more than a year of work on Hive, I started thinking how absurd it was that we don’t have any Easter Eggs yet. When I went to look for a module for the Konami Code in XNA (because: how could there NOT be one?!), I found that the only solution currently out there seemed to be Charles Cox’s Konami Code for WP7.

At the time of this writing, BlueLine doesn’t make any WP7 games though: just Xbox 360 games that we hope to release to PC soon. To avoid completely reinventing the wheel, and to make sure my solution would be backward compatible to anyone already using Charles Cox’s module for WP7, I decided to just piggyback on that code. I’ll send my code back to him too, so hopefully he can add it to the version on his site. I’ll also structure this post to be very similar to his post so it can be merged more easily.

The solution is a single class file that you can drop into any XNA project, and by adding a small snippet of code to your update routine, you can have Konami Code functionality in minutes.

C# – KonamiCode.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#if WINDOWS_PHONE
using Microsoft.Xna.Framework.Input.Touch;
#endif
using Microsoft.Xna.Framework.Media;

namespace KonamiCodeXna
{

    // A delegate type that represents the code has either been entered right or wrong
    public delegate void CodeEnteredEventHandler(object sender, KonamiCodeEventArgs e);

    public class KonamiCodeEventArgs : EventArgs
    {
        /// <summary>
        /// The player index that entered the code.
        /// WARNING: This can be null (eg: on WP7, or when the code was entered using the keyboard).
        /// </summary>
        public PlayerIndex? PlayerIndex { get; private set; }

        public KonamiCodeEventArgs(PlayerIndex? playerIndex)
            :base()
        {
            this.PlayerIndex = playerIndex;
        }
    }

    /// <summary>
    /// Class for easily using the KonamiCode in XNA apps (Windows, Windows Phone, and Xbox).
    /// 
    /// Originally written for Windows Phone 7 by Charles N. Cox.
    /// Expanded to work on Xbox and Windows by Sean Colombo.
    /// </summary>
    public class KonamiCode
    {
        public event CodeEnteredEventHandler CodeEnteredRight;
        public event CodeEnteredEventHandler CodeEnteredWrong;

        // Track the progress by each PlayerIndex. If playerIndex is irrelevant, just key off of "null" instead.
        private Dictionary<PlayerIndex, int> numCorrectByPlayerIndex;
        private int numCorrectKeyboard;

        // To be able to tell when buttons are pressed/released, these vars will keep track of state from previous tick.
        private Dictionary<PlayerIndex, GamePadState> previousGamePadStateByPlayerIndex;
        private KeyboardState previousKeyState;

        public KonamiCode()
        {
            numCorrectKeyboard = 0;
            numCorrectByPlayerIndex = new Dictionary<PlayerIndex, int>();
            previousGamePadStateByPlayerIndex = new Dictionary<PlayerIndex, GamePadState>();
        }

#if WINDOWS_PHONE
        enum DirType
        {
            DirTypeUp,
            DirTypeDown,
            DirTypeLeft,
            DirTypeRight
        };

        private DirType[] correctSequence = new DirType[8] {
            DirType.DirTypeUp,
            DirType.DirTypeUp,
            DirType.DirTypeDown,
            DirType.DirTypeDown,
            DirType.DirTypeLeft,
            DirType.DirTypeRight,
            DirType.DirTypeLeft,
            DirType.DirTypeRight };

        public void checkGesture(GestureSample gs)
        {
            bool rightCode = true;
 
            switch(correctSequence[numCorrect])
            {
                case DirType.DirTypeUp:
                    if (gs.Delta.Y > 0 || (Math.Abs(gs.Delta.Y) < Math.Abs(gs.Delta.X)))
                        rightCode = false;
                    break;
                case DirType.DirTypeDown:
                    if (gs.Delta.Y < 0 || (Math.Abs(gs.Delta.Y) < Math.Abs(gs.Delta.X)))
                         rightCode = false;
                    break;
                case DirType.DirTypeRight:
                    if (gs.Delta.X < 0 || (Math.Abs(gs.Delta.Y) > Math.Abs(gs.Delta.X)))
                        rightCode = false;
                    break;
                case DirType.DirTypeLeft:
                    if (gs.Delta.X > 0 || (Math.Abs(gs.Delta.Y) > Math.Abs(gs.Delta.X)))
                        rightCode = false;
                    break;
            }

            this.RecordSuccessByPlayerIndex(rightCode, null);
        }
#else
        private Keys[] correctKeySequence = new Keys[]{ // for the keyboard
            Keys.Up,
            Keys.Up,
            Keys.Down,
            Keys.Down,
            Keys.Left,
            Keys.Right,
            Keys.Left,
            Keys.Right,
            Keys.B,
            Keys.A
        };

        private Buttons[] correctButtonSequence = new Buttons[]{ // for gamepads
            Buttons.DPadUp,
            Buttons.DPadUp,
            Buttons.DPadDown,
            Buttons.DPadDown,
            Buttons.DPadLeft,
            Buttons.DPadRight,
            Buttons.DPadLeft,
            Buttons.DPadRight,
            Buttons.B,
            Buttons.A,
        };
        private List<Buttons> buttonsToCheck; // which buttons to check for being up/down each tick. For performance reasons, we'll only check the buttons that are part of the sequence.

        /// <summary>
        /// Should be called once each update loop when listening for the code.
        /// </summary>
        public void checkKeyboard()
        {
            bool rightCode = false;
            KeyboardState keyboardState = Keyboard.GetState();

            // Only evaluate the state if SOMETHING was pressed (we only care about the sequence, not that they're IMMEDIATELY after each other).
            if (WasAnyKeyPressed(keyboardState))
            {
                if (WasKeyPressed(correctKeySequence[numCorrectKeyboard], keyboardState))
                {
                    rightCode = true;
                }

                this.RecordSuccessByPlayerIndex(rightCode, null);
            }

            previousKeyState = keyboardState;
        }

        /// <summary>
        /// Should be called once each update loop when listening for the code, for each playerINdex
        /// that is being listened to (will track their progress separately so that their keypresses
        /// don't interfere with each other).
        /// </summary>
        /// <param name="playerIndex"></param>
        public void checkPlayerIndex(PlayerIndex playerIndex)
        {
            bool rightCode = false;

            int numCorrectSoFar = 0; // the number-correct-so-far corresponds to the index in the sequence that should be expected
            if (numCorrectByPlayerIndex.ContainsKey(playerIndex))
            {
                numCorrectSoFar = numCorrectByPlayerIndex[playerIndex];
            }

            // This is called once per tick, then re-used by the other functions below.
            GamePadState currentGamePadState = GamePad.GetState(playerIndex);

            // Only evaluate the state if SOMETHING was pressed (we only care about the sequence, not that they're IMMEDIATELY after each other).
            if (IsAnyButtonPressed(playerIndex, currentGamePadState))
            {
                if (WasButtonPressed(correctButtonSequence[numCorrectSoFar], playerIndex, currentGamePadState))
                {
                    rightCode = true;
                }

                this.RecordSuccessByPlayerIndex(rightCode, playerIndex);
            }

            previousGamePadStateByPlayerIndex[playerIndex] = currentGamePadState;
        }

        /// <summary>
        /// Returns true if any key is currently pressed down, false otherwise.
        /// 
        /// If a user presses a key and holds it down, this method will return
        /// true every time it's called while that key is held down.
        /// </summary>
        /// <returns></returns>
        //public bool IsAnyKeyDown(KeyboardState keyboardState)
        //{
        //    Keys[] keys = keyboardState.GetPressedKeys();

        //    // Some systems return an empty array when nothing is pressed, some return one item containing Keys.None
        //    bool nothingPressed = ((keys.Length == 0) || ((keys.Length == 1) && (keys[0] == Keys.None)));
        //    return (!nothingPressed);
        //}

        /// <summary>
        /// Returns true if any key was JUST pressed, false otherwise.
        /// 
        /// This means that if a key is pressed and held down, the first
        /// tick will return true and subsequent ticks will return false even while
        /// the key is still being held down.
        /// </summary>
        /// <returns></returns>
        public bool WasAnyKeyPressed(KeyboardState keyboardState)
        {
            Keys[] keys = keyboardState.GetPressedKeys();

            bool somethingNewIsPressed = false;

            // Some systems return an empty array when nothing is pressed, some return one item containing Keys.None
            bool nothingPressed = ((keys.Length == 0) || ((keys.Length == 1) && (keys[0] == Keys.None)));
            if (!nothingPressed)
            {
                IEnumerable<Keys> newlyPressed = keys.Except(previousKeyState.GetPressedKeys()); // ignore keys that were already down on the previous tick.
                somethingNewIsPressed = (newlyPressed.Count() > 0);
            }

            return somethingNewIsPressed;
        }

        private bool WasKeyPressed(Keys keyToCheck, KeyboardState keyboardState)
        {
            return (previousKeyState.IsKeyUp(keyToCheck) && keyboardState.IsKeyDown(keyToCheck));
        }

        /// <summary>
        /// Returns true if any of the buttons RELEVANT TO THE KONAMI CODE were pressed. For performance, does not
        /// check all buttons each tick, just checks the buttons that appear in the correctButtonSequence.
        /// </summary>
        /// <param name="playerIndex"></param>
        /// <param name="currentGamePadState"></param>
        /// <returns></returns>
        private bool IsAnyButtonPressed(PlayerIndex playerIndex, GamePadState currentGamePadState)
        {
            bool somethingPressed = false;

            if (buttonsToCheck == null)
            {
                // Not all users will have my (Sean's) Set<> class, so abuse a Dictionary instead.
                Dictionary<Buttons, bool> buttonSet = new Dictionary<Buttons, bool>();
                foreach (Buttons button in correctButtonSequence)
                {
                    buttonSet[button] = false; // the bool is trash basically. this is just a cheap way to implement a set.
                }
                buttonsToCheck = buttonSet.Keys.ToList();
            }

            foreach (Buttons button in buttonsToCheck)
            {
                somethingPressed = (somethingPressed || WasButtonPressed(button, playerIndex, currentGamePadState));

                if (somethingPressed)
                {
                    break;
                }
            }

            return somethingPressed;
        }

        private bool WasButtonPressed(Buttons button, PlayerIndex playerIndex, GamePadState currentGamePadState)
        {
            bool wasPressed;
            if(previousGamePadStateByPlayerIndex.ContainsKey(playerIndex)){
                wasPressed = (previousGamePadStateByPlayerIndex[playerIndex].IsButtonUp(button) && currentGamePadState.IsButtonDown(button));
            } else {
                wasPressed = currentGamePadState.IsButtonDown(button);
            }
            return wasPressed;
        }
#endif

        /// <summary>
        /// Records a successful/unsuccessful keypress by PlayerIndex. In cases where the
        /// PlayerIndex is irrelevant (often, on WP7 there is only one player), then null
        /// can be used.
        /// 
        /// NOTE: This method is typically called from other helpers such as checkGesture
        /// and checkGamePadHelper instead of being called directly.
        /// </summary>
        /// <param name="rightCode"></param>
        /// <param name="playerIndex"></param>
        public void RecordSuccessByPlayerIndex(bool rightCode, PlayerIndex? playerIndex)
        {
            if (rightCode)
            {
                // PlayerIndex is null for keyboard and non-null for game pads.
                if (playerIndex == null)
                {
                    numCorrectKeyboard++;
                    if(numCorrectKeyboard >= correctKeySequence.Length)
                    {
                        //reset the code, fire the event
                        numCorrectKeyboard = 0;
                        KonamiCodeEventArgs args = new KonamiCodeEventArgs(playerIndex);
                        CodeEnteredRight.Invoke(this, args);
                    }
                }
                else
                {
                    if (numCorrectByPlayerIndex.ContainsKey((PlayerIndex)playerIndex))
                    {
                        numCorrectByPlayerIndex[(PlayerIndex)playerIndex]++;
                    }
                    else
                    {
                        numCorrectByPlayerIndex[(PlayerIndex)playerIndex] = 1;
                    }

                    if (numCorrectByPlayerIndex[(PlayerIndex)playerIndex] >= correctButtonSequence.Length)
                    {
                        //reset the code, fire the event
                        numCorrectByPlayerIndex[(PlayerIndex)playerIndex] = 0;
                        KonamiCodeEventArgs args = new KonamiCodeEventArgs(playerIndex);
                        CodeEnteredRight.Invoke(this, args);
                    }
                }
            }
            else
            {
                //wrong type, reset the count, send event
                if (playerIndex == null)
                {
                    numCorrectKeyboard = 0;
                }
                else
                {
                    numCorrectByPlayerIndex[(PlayerIndex)playerIndex] = 0;
                }
                KonamiCodeEventArgs args = new KonamiCodeEventArgs(playerIndex);
                CodeEnteredWrong.Invoke(this, args);
            }
        }

    }
}

To Use the Class

It’s really just three things:

  1. Instantiate a KonamiCode object.
  2. Subscribe to the CodeEnteredRight event (and, if you want, the CodeEnteredWrong event)
  3. Update it once, each game-tick (ie: in Update()).

Getting it Into a Default Project

To mirror the example given in Charles’ original post, I’ll show how to use this module in an example project to have the same outcome that his original code did, but have it also work on Windows and Xbox 360 (in addition to WP7).

Game1

//**Start Code For This Sample - REPLACE YOUR GAME1 CONSTRUCTOR WITH THIS
KonamiCode konami = new KonamiCode();
Color clearColor = Color.CornflowerBlue;
 
public Game1()
{
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";
 
    // Frame rate is 30 fps by default for Windows Phone.
    TargetElapsedTime = TimeSpan.FromTicks(333333);
 
    konami.CodeEnteredRight += new CodeEnteredEventHandler(konami_CodeEnteredRight);
    konami.CodeEnteredWrong += new CodeEnteredEventHandler(konami_CodeEnteredWrong);
 
    TouchPanel.EnabledGestures = GestureType.Flick;
 
}
 
void konami_CodeEnteredWrong(object sender, EventArgs e)
{
    clearColor = Color.Tomato;
}
 
void konami_CodeEnteredRight(object sender, EventArgs e)
{
    clearColor = Color.MediumSeaGreen;
}
//**End Code For This Sample

Update

//**Start Code For This Sample - REPLACE YOUR UPDATE LOOP WITH THIS
protected override void Update(GameTime gameTime)
{
    // Allows the game to exit
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();
 
    #region Konami code updating
#if WINDOWS_PHONE
	while (TouchPanel.IsGestureAvailable)
	{
		GestureSample gs = TouchPanel.ReadGesture();
		konamiCode.checkGesture(gs);
	}
#else // WINDOWS || XBOX
	// Need to check the code for each game pad that's in-use.
	foreach (PlayerIndex playerIndex in new PlayerIndex[] { PlayerIndex.One, PlayerIndex.Two, PlayerIndex.Three, PlayerIndex.Four })
	{
		if (GamePad.GetState(playerIndex).IsConnected)
		{
			konamiCode.checkPlayerIndex(playerIndex);
		}
	}

	// Then check the code for the keyboard (if there is one, there will be just one so it's checked separately, not per-player).
	konamiCode.checkKeyboard();
#endif
	#endregion

    base.Update(gameTime);
}
//**End Code For This Sample

Draw

//**Start Code For This Sample - REPLACE YOUR DRAW METHOD WITH THIS
protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(clearColor);
 
    // TODO: Add your drawing code here
 
    base.Draw(gameTime);
}
//**End Code For This Sample

If you run into any issues, please let us know.

Games using this Konami code class

If you put this into a game, let us know when it’s out! We’ll add it to this list of XNA games that have Konami Code built in, using this class.


Viewing all articles
Browse latest Browse all 4

Trending Articles