Click here to Skip to main content
15,891,993 members
Articles / Mobile Apps / Windows Mobile

Creating a Casual Puzzle Game in Managed Code for Windows Mobile Devices

Rate me:
Please Sign up or sign in to vote.
4.88/5 (24 votes)
12 Dec 2008CPOL17 min read 61K   1.4K   42  
This article shows how to create a casual (easy, simple, and fun) puzzle game in managed code for Windows Mobile devices.
using System;

using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Reflection;
using System.Media;

namespace com.langesite.fruitdrop
{
    public partial class frmMain : Form
    {
        public static readonly int BOARD_MAX_WIDTH = 8; // Game board width.
        public static readonly int BOARD_MAX_HEIGHT = 7; // Game board height.
        public static readonly int IMAGE_MAX_WIDTH = 28; // Max width of images.
        public static readonly int IMAGE_MAX_HEIGHT = 32; // Max height of images.

        private static Bitmap m_Bitmap = new Bitmap(IMAGE_MAX_WIDTH * BOARD_MAX_WIDTH, IMAGE_MAX_HEIGHT * BOARD_MAX_HEIGHT);
        private static int[,] m_PuzzleGrid = new int[BOARD_MAX_WIDTH, BOARD_MAX_HEIGHT];
        private static int[,] m_TestGrid = new int[BOARD_MAX_WIDTH, BOARD_MAX_HEIGHT];
        private static int m_Score = 0;
        private static bool m_IsGameOver = false;
        private static bool m_IsSoundOn = true;
        private static Random m_Random = new Random();
        private static SoundPlayer m_SoundPlayer = new SoundPlayer();

        #region Form Controls, Methods, and Events
        public frmMain()
        {
            InitializeComponent();
        }

        private void frmMain_Load(object sender, EventArgs e)
        {
            InitializeSound();
            InitializeGame();
        }

        private void InitializeSound()
        {
            // This method simply loads and plays a quiet wav file
            // to 'prime' the sound player.  Otherwise there is a
            // noticeable delay the first time a sound is played.
            // Could be changed to a game intro wav.
            if (m_IsSoundOn)
            {
                try
                {
                    m_SoundPlayer.Stop();
                    m_SoundPlayer.Stream = new MemoryStream(com.langesite.fruitdrop.Properties.Resources.Quiet);
                    m_SoundPlayer.Load();
                    m_SoundPlayer.Play();
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message);
                }
            }
        }

        private void pbGameBox_Click(object sender, EventArgs e)
        {
            ProcessTurn();
        }

        private void menuGameExit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void menuGameNew_Click(object sender, EventArgs e)
        {
            InitializeGame();
        }

        private void menuSoundOn_Click(object sender, EventArgs e)
        {
            m_IsSoundOn = true;
            menuSoundOn.Checked = true;
            menuSoundOff.Checked = false;
        }

        private void menuSoundOff_Click(object sender, EventArgs e)
        {
            m_IsSoundOn = false;
            menuSoundOn.Checked = false;
            menuSoundOff.Checked = true;
        }
        #endregion

        #region Game Methods
        private void ProcessTurn()
        {
            if (m_IsGameOver == false)
            {
                // Map the mouse cursor position to the correct image in the picture box.
                Point aPoint = pbGameBox.PointToClient(new Point(MousePosition.X, MousePosition.Y));

                int aPositionX = aPoint.X / IMAGE_MAX_WIDTH;
                int aPositionY = aPoint.Y / IMAGE_MAX_HEIGHT;

                AdjacencySearch(aPositionX, aPositionY);
            }
        }

        private void InitializeGame()
        {
            m_IsGameOver = false;

            m_Score = 0;
            lblScoreValue.Text = m_Score.ToString();

            // Dynamically size and position the picture box.
            pbGameBox.Width = (IMAGE_MAX_WIDTH * BOARD_MAX_WIDTH);
            pbGameBox.Height = (IMAGE_MAX_HEIGHT * BOARD_MAX_HEIGHT);
            pbGameBox.Left = (this.Width - pbGameBox.Width) / 2;

            InitializeGrid();
        }

        public void InitializeGrid()
        {
            // Put a random image into each cell.
            for (int aColumn = 0; aColumn < BOARD_MAX_WIDTH; ++aColumn)
            {
                for (int aRow = 0; aRow < BOARD_MAX_HEIGHT; ++aRow)
                {
                    m_PuzzleGrid[aColumn, aRow] = m_Random.Next(1, 6);
                }
            }
            RenderGrid();
        }

        private void RenderGrid()
        {
            // Clear the picture box.
            pbGameBox.Image = m_Bitmap;

            Image aGameBoxImage = this.pbGameBox.Image;

            // Render the screen using preloaded images.
            using (Graphics g = Graphics.FromImage(aGameBoxImage))
            {

                for (int aRow = 0; aRow < BOARD_MAX_HEIGHT; ++aRow)
                {
                    for (int aColumn = 0; aColumn < BOARD_MAX_WIDTH; ++aColumn)
                    {
                        switch (m_PuzzleGrid[aColumn, aRow])
                        {
                            case 0:
                                g.DrawImage(pbZero.Image, new Rectangle(aColumn * IMAGE_MAX_WIDTH, aRow * IMAGE_MAX_HEIGHT, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT), new Rectangle(0, 0, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT), GraphicsUnit.Pixel);
                                break;
                            case 1:
                                g.DrawImage(pbOne.Image, new Rectangle(aColumn * IMAGE_MAX_WIDTH, aRow * IMAGE_MAX_HEIGHT, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT), new Rectangle(0, 0, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT), GraphicsUnit.Pixel);
                                break;
                            case 2:
                                g.DrawImage(pbTwo.Image, new Rectangle(aColumn * IMAGE_MAX_WIDTH, aRow * IMAGE_MAX_HEIGHT, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT), new Rectangle(0, 0, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT), GraphicsUnit.Pixel);
                                break;
                            case 3:
                                g.DrawImage(pbThree.Image, new Rectangle(aColumn * IMAGE_MAX_WIDTH, aRow * IMAGE_MAX_HEIGHT, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT), new Rectangle(0, 0, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT), GraphicsUnit.Pixel);
                                break;
                            case 4:
                                g.DrawImage(pbFour.Image, new Rectangle(aColumn * IMAGE_MAX_WIDTH, aRow * IMAGE_MAX_HEIGHT, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT), new Rectangle(0, 0, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT), GraphicsUnit.Pixel);
                                break;
                            case 5:
                                g.DrawImage(pbFive.Image, new Rectangle(aColumn * IMAGE_MAX_WIDTH, aRow * IMAGE_MAX_HEIGHT, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT), new Rectangle(0, 0, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT), GraphicsUnit.Pixel);
                                break;
                            case 9:
                                g.DrawImage(pbPop.Image, new Rectangle(aColumn * IMAGE_MAX_WIDTH, aRow * IMAGE_MAX_HEIGHT, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT), new Rectangle(0, 0, IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT), GraphicsUnit.Pixel);
                                break;
                        }
                    }
                }
            }
        }

        private void DrawGameOver()
        {
            Image aGameBoxImage = this.pbGameBox.Image;

            // Get Graphics Object.
            using (Graphics g = Graphics.FromImage(aGameBoxImage))
            {
                // Draw string on graphic.
                g.DrawString("Game Over", new Font("Verdana", 20, FontStyle.Bold),
                new SolidBrush(Color.Blue), 36, 2);
            }
        }

        private void SetupDrop()
        {
            for (int aColumn = 0; aColumn < BOARD_MAX_WIDTH; ++aColumn)
            {
                for (int aRow = 0; aRow < BOARD_MAX_HEIGHT; ++aRow)
                {
                    if (m_TestGrid[aColumn, aRow] != 0)
                    {
                        m_PuzzleGrid[aColumn, aRow] = 9;
                    }
                }
            }
        }

        private void PerformDrop()
        {
            for (int aRow = BOARD_MAX_HEIGHT - 1; aRow > 0; --aRow)
            {
                for (int aColumn = 0; aColumn < BOARD_MAX_WIDTH; ++aColumn)
                {

                    while (m_PuzzleGrid[aColumn, aRow] == 9)
                    {
                        for (int aMoveRow = aRow; aMoveRow >= 0; --aMoveRow)
                        {
                            if (aMoveRow > 0)
                            {
                                m_PuzzleGrid[aColumn, aMoveRow] = m_PuzzleGrid[aColumn, aMoveRow - 1];
                            }
                            else
                            {
                                m_PuzzleGrid[aColumn, aMoveRow] = 0;
                            }
                        }
                        RenderGrid();
                    }
                }

                for (int aColumn = 0; aColumn < BOARD_MAX_WIDTH; ++aColumn)
                {
                    if (m_PuzzleGrid[aColumn, 0] == 9)
                    {
                        m_PuzzleGrid[aColumn, 0] = 0;
                    }
                }
            }
            ClearCheckGrid();
        }

        private void ClearCheckGrid()
        {
            for (int aColumn = 0; aColumn < BOARD_MAX_WIDTH; ++aColumn)
            {
                for (int aRow = 0; aRow < BOARD_MAX_HEIGHT; ++aRow)
                {
                    m_TestGrid[aColumn, aRow] = 0;
                }
            }
        }

        private void CheckGameOver()
        {
            // If there are no more adjacent matches, then the game is over.
            if (CheckForHorizontalMoves() == true || CheckForVerticalMoves() == true)
            {
                m_IsGameOver = false;
            }
            else
            {
                m_IsGameOver = true;
                DrawGameOver();

                if (m_IsSoundOn)
                {
                    try
                    {
                        m_SoundPlayer.Stop();
                        m_SoundPlayer.Stream = new MemoryStream(com.langesite.fruitdrop.Properties.Resources.Boing);
                        m_SoundPlayer.Load();
                        m_SoundPlayer.Play();
                    }
                    catch (Exception e)
                    {
                        MessageBox.Show(e.Message);
                    }
                }
            }
        }

        private bool CheckForHorizontalMoves()
        {
            bool aReturnVal = false;

            for (int aImageType = 1; aImageType <= 6; ++aImageType)
            {
                for (int aRow = 0; aRow < BOARD_MAX_HEIGHT; ++aRow)
                {
                    int aMatchCount = 0;
                    for (int aColumn = 0; aColumn < BOARD_MAX_WIDTH; ++aColumn)
                    {
                        if (m_PuzzleGrid[aColumn, aRow] == aImageType)
                        {
                            ++aMatchCount;
                        }
                        else
                        {
                            aMatchCount = 0;
                        }

                        if (aMatchCount > 1)
                        {
                            aReturnVal = true;
                        }
                    }
                }
            }
            return aReturnVal;
        }

        private bool CheckForVerticalMoves()
        {
            bool aReturnVal = false;

            for (int aImageType = 1; aImageType <= 6; ++aImageType)
            {
                for (int aColumn = 0; aColumn < BOARD_MAX_WIDTH; ++aColumn)
                {
                    int aMatchCount = 0;
                    for (int aRow = 0; aRow < BOARD_MAX_HEIGHT; ++aRow)
                    {
                        if (m_PuzzleGrid[aColumn, aRow] == aImageType)
                        {
                            ++aMatchCount;
                        }
                        else
                        {
                            aMatchCount = 0;
                        }

                        if (aMatchCount > 1)
                        {
                            aReturnVal = true;
                        }
                    }
                }
            }
            return aReturnVal;
        }

        private void RemoveZeroColumns(int[,] tGrid)
        {
            int aCurrentColumn = BOARD_MAX_WIDTH - 1;
            int aNonzeroCounter = 0;
            int aZeroColumnInsertedCounter = 0;

            // Loop until all columns are checked except the zero
            // column or any inserted columns.
            while (aCurrentColumn > aZeroColumnInsertedCounter)
            {
                for (int aCurrentRow = 0; aCurrentRow < BOARD_MAX_HEIGHT; aCurrentRow++)
                {
                    // Does cell contain non-zero?
                    if (tGrid[aCurrentColumn, aCurrentRow] > 0)
                    {
                        // Increment the non-zero counter.
                        aNonzeroCounter++;
                    }
                }

                if (aNonzeroCounter > 0)
                {
                    // If a non-zero was found in the current
                    // column, nothing to do but check the next
                    // column (from max column to first column).
                    aCurrentColumn--;
                    aNonzeroCounter = 0;
                }
                else
                {
                    // No non-zero values were found in the current
                    // column. Move all columns to the right.
                    for (int aMoveColumn = aCurrentColumn; aMoveColumn > 0; aMoveColumn--)
                    {
                        for (int aMoveRow = 0; aMoveRow < BOARD_MAX_HEIGHT; aMoveRow++)
                        {
                            tGrid[aMoveColumn, aMoveRow] = tGrid[aMoveColumn - 1, aMoveRow];
                        }
                    }

                    // Zero out the first column.
                    for (int aZeroRow = 0; aZeroRow < BOARD_MAX_HEIGHT; aZeroRow++)
                    {
                        tGrid[0, aZeroRow] = 0;
                    }

                    // Count the number of columns inserted to
                    // avoid forever loop.
                    aZeroColumnInsertedCounter++;
                }
            }
        }

        private void AdjacencySearch(int tPositionX, int tPositionY)
        {
            // Creat a stack for saving points.
            Stack<Point> aPointStack = new Stack<Point>();
            Point aPoint = new Point();
            int aMatchCount = 0;

            int aCurrX = tPositionX;
            int aCurrY = tPositionY;

            // Get value of selected image.
            int aCellValue = m_PuzzleGrid[tPositionX, tPositionY];
            if (aCellValue > 0)
            {
                // Cell value =   +X: Non-visited cell value.
                // Cell value =   -X: Cell visited.
                // Cell value =  -1X: Cell visited. Checking image to the north.
                // Cell value =  -2X: Cell visited. Checking image to the east.
                // Cell value =  -3X: Cell visited. Checking image to the south.
                // Cell value =  -4X: Cell visited. Checking image to the west.
                // Cell value =  -5X: Cell visited. Done checking.
                // Cell value =    9: Image marked for removal.  

                // Mark current object as 'visited' ... set it equal to -value.
                m_PuzzleGrid[aCurrX, aCurrY] = -m_PuzzleGrid[aCurrX, aCurrY];
                aPoint.X = aCurrX;
                aPoint.Y = aCurrY;
                aPointStack.Push(aPoint);

                while (aPointStack.Count > 0)
                {
                    Point aPeekPoint = aPointStack.Peek();

                    int aCheckValue = m_PuzzleGrid[aPeekPoint.X, aPeekPoint.Y];
                    if (aCheckValue > -10)
                    {
                        Point aPivetPoint = aPointStack.Pop();
                        m_PuzzleGrid[aPivetPoint.X, aPivetPoint.Y] -= 10;
                        aPointStack.Push(aPivetPoint);

                        if (aPivetPoint.Y > 0)
                        {
                            if (m_PuzzleGrid[aPivetPoint.X, aPivetPoint.Y - 1] == aCellValue)
                            {
                                aMatchCount++;
                                m_PuzzleGrid[aPivetPoint.X, aPivetPoint.Y - 1] = -m_PuzzleGrid[aPivetPoint.X, aPivetPoint.Y - 1];
                                aPointStack.Push(new Point(aPivetPoint.X, aPivetPoint.Y - 1));
                            }
                        }
                    }
                    else if (aCheckValue > -20)
                    {
                        Point aPivetPoint = aPointStack.Pop();
                        m_PuzzleGrid[aPivetPoint.X, aPivetPoint.Y] -= 10;
                        aPointStack.Push(aPivetPoint);

                        if (aPivetPoint.X < BOARD_MAX_WIDTH - 1)
                        {
                            if (m_PuzzleGrid[aPivetPoint.X + 1, aPivetPoint.Y] == aCellValue)
                            {
                                aMatchCount++;
                                m_PuzzleGrid[aPivetPoint.X + 1, aPivetPoint.Y] = -m_PuzzleGrid[aPivetPoint.X + 1, aPivetPoint.Y];
                                aPointStack.Push(new Point(aPivetPoint.X + 1, aPivetPoint.Y));
                            }
                        }
                    }
                    else if (aCheckValue > -30)
                    {
                        Point aPivetPoint = aPointStack.Pop();
                        m_PuzzleGrid[aPivetPoint.X, aPivetPoint.Y] -= 10;
                        aPointStack.Push(aPivetPoint);

                        if (aPivetPoint.Y < BOARD_MAX_HEIGHT - 1)
                        {
                            if (m_PuzzleGrid[aPivetPoint.X, aPivetPoint.Y + 1] == aCellValue)
                            {
                                aMatchCount++;
                                m_PuzzleGrid[aPivetPoint.X, aPivetPoint.Y + 1] = -m_PuzzleGrid[aPivetPoint.X, aPivetPoint.Y + 1];
                                aPointStack.Push(new Point(aPivetPoint.X, aPivetPoint.Y + 1));
                            }
                        }
                    }
                    else if (aCheckValue > -40)
                    {
                        Point aPivetPoint = aPointStack.Pop();
                        m_PuzzleGrid[aPivetPoint.X, aPivetPoint.Y] -= 10;
                        aPointStack.Push(aPivetPoint);

                        if (aPivetPoint.X > 0)
                        {
                            if (m_PuzzleGrid[aPivetPoint.X - 1, aPivetPoint.Y] == aCellValue)
                            {
                                aMatchCount++;
                                m_PuzzleGrid[aPivetPoint.X - 1, aPivetPoint.Y] = -m_PuzzleGrid[aPivetPoint.X - 1, aPivetPoint.Y];
                                aPointStack.Push(new Point(aPivetPoint.X - 1, aPivetPoint.Y));
                            }
                        }
                    }
                    else if (aCheckValue > -50)
                    {
                        // All four directions have been checked for the current image,
                        // so pop the stack to get the next image to check.
                        Point aCurrPoint = aPointStack.Pop();

                        if (aPointStack.Count > 0)
                        {
                            // If more than one image is on the stack, then there
                            // must have been at least one match, so clear the current one.
                            m_PuzzleGrid[aCurrPoint.X, aCurrPoint.Y] = 9;
                        }
                        else if (aPointStack.Count == 0 && aMatchCount == 0)
                        {
                            // If there are no images left on the stack and there
                            // are no matches, then it is an illegal click.  Set
                            // image back to original value.
                            m_PuzzleGrid[aCurrPoint.X, aCurrPoint.Y] = aCellValue;
                        }
                        else
                        {
                            // Finally, this is the original image since the stack
                            // is now zero and there was at least one match so clear
                            // the original image as well.
                            aMatchCount++;
                            m_PuzzleGrid[aCurrPoint.X, aCurrPoint.Y] = 9;
                        }
                    }
                }

                if (aMatchCount > 0)
                {
                    // Give the 'image clearing' image a moment to display.
                    RenderGrid();
                    Application.DoEvents();
                    System.Threading.Thread.Sleep(50);

                    if (m_IsSoundOn)
                    {
                        try
                        {
                            m_SoundPlayer.Stop();
                            m_SoundPlayer.Stream = new MemoryStream(com.langesite.fruitdrop.Properties.Resources.Pop);
                            m_SoundPlayer.Load();
                            m_SoundPlayer.Play();
                        }
                        catch (Exception e)
                        {
                            MessageBox.Show(e.Message);
                        }
                    }
                    else
                    {
                        System.Threading.Thread.Sleep(100);
                    }

                    // Calculate the score for this turn.
                    m_Score += Convert.ToInt32(Math.Pow(aMatchCount, 2));
                    lblScoreValue.Text = m_Score.ToString();

                    // Clear any images marked for removal and let the remaining
                    // images drop as needed.
                    SetupDrop();
                    PerformDrop();
                    RenderGrid();

                    // Handle and empty columns by move all remaining columns
                    // to the right as needed.
                    RemoveZeroColumns(m_PuzzleGrid);
                    RenderGrid();

                    // Check if there are any remaining moves.
                    CheckGameOver();
                }
            }
        }
        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Bill is a software engineer. He resides in NJ with his wife Lucy and their dog Yoda.

Having spend his salad days playing around with his Atari 400, in his spare time, he likes to tinker with game programming, 8-bit computers and the classic arcade machines of his youth.

Comments and Discussions