Click here to Skip to main content
15,894,825 members
Articles / Containers / Virtual Machine

Building a Windows Phone 7 Puzzle Game

Rate me:
Please Sign up or sign in to vote.
4.99/5 (88 votes)
23 Jul 2010CPOL10 min read 227.2K   6.8K   186  
Get a head start with the new Windows Phone 7 developer tools. Learn how to create a Sokoban game in Silverlight for the WP7 platform.
#region File and License Information
/*
<File>
	<Copyright>Copyright � 2007, Daniel Vaughan. All rights reserved.</Copyright>
	<License see="prj:///Documentation/License.txt"/>
	<Owner Name="Daniel Vaughan" Email="dbvaughan@gmail.com"/>
	<CreationDate>2008-06-09 15:40:37Z</CreationDate>
	<LastSubmissionDate>$Date: $</LastSubmissionDate>
	<Version>$Revision: $</Version>
</File>
*/
#endregion

using System;
using System.Collections.Generic;
using System.IO;

namespace DanielVaughan.Sokoban
{
	/// <summary>
	/// Represents a single stage within a game.
	/// A level instance is able to load itself
	/// from a map resource.
	/// </summary>
	public class Level
	{
		Cell[][] cells;
		List<GoalCell> goals = new List<GoalCell>();

		/// <summary>
		/// Gets the level number.
		/// </summary>
		/// <value>The level number.</value>
		public int LevelNumber
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the game that this level is located.
		/// </summary>
		/// <value>The game that this level is located.</value>
		protected Game Game
		{
			get;
			set;
		}

		/// <summary>
		/// Gets the single <see cref="Actor"/> instance
		/// located on each level.
		/// </summary>
		/// <value>The actor. (The user moveable guy) </value>
		public Actor Actor
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets the <see cref="Cell"/> with the specified row number
		/// and column number.
		/// </summary>
		/// <value>The cell at the specified row number and column number.</value>
		public Cell this[int rowNumber, int columnNumber]
		{
			get
			{
				return cells[rowNumber][columnNumber];
			}
		}

		/// <summary>
		/// Gets the <see cref="Cell"/> with the specified location.
		/// </summary>
		/// <value>The cell at the specified location.</value>
		public Cell this[Location location]
		{
			get
			{
				if (InBounds(location))
				{
					return this[location.RowNumber, location.ColumnNumber];
				}
				return null;
			}
		}

		/// <summary>
		/// Gets the number of rows in the level.
		/// </summary>
		/// <value>The number of rows in the level.</value>
		public int RowCount
		{
			get
			{
				return cells != null ? cells.Length : 0;
			}
		}

		/// <summary>
		/// Gets the number of columns in the level.
		/// </summary>
		/// <value>The number of columns in the level.</value>
		public int ColumnCount
		{
			get
			{
				return cells != null && cells.Length > 0 ? cells[0].Length : 0;
			}
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Level"/> class.
		/// </summary>
		/// <param name="game">The game that the level belongs.</param>
		/// <param name="levelNumber">The level number for this level.</param>
		public Level(Game game, int levelNumber)
		{
			Game = game;
			LevelNumber = levelNumber;
		}

		/// <summary>
		/// Loads the level data from the specified map stream.
		/// </summary>
		/// <param name="mapStream">The map stream to load the level.</param>
		public void Load(TextReader mapStream)
		{
			if (mapStream == null)
			{
				throw new ArgumentNullException("mapStream");
			}

			List<List<Cell>> rows = new List<List<Cell>>();

			string gridRowText;
			int rowCount = 0;
			while ((gridRowText = mapStream.ReadLine()) != null && gridRowText.Trim() != string.Empty)
			{
				rows.Add(BuildCells(gridRowText, rowCount++));
			}

			cells = new Cell[rowCount][];
			for (int i = 0; i < rowCount; i++)
			{
				cells[i] = rows[i].ToArray();
			}
		}

		List<Cell> BuildCells(string rowText, int rowNumber)
		{
			List<Cell> row = new List<Cell>(rowText.Length);

			int columnNumber = 0;

			foreach (char c in rowText)
			{
				Location location = new Location(rowNumber, columnNumber++);
				switch (c)
				{
					case '#': /* Wall. */
						row.Add(new WallCell(location, this));
						break;
					case ' ': /* Empty. */
						row.Add(new FloorCell(location, this));
						break;
					case '$': /* Treasure in a square. */
						row.Add(new FloorCell(location, this, new Treasure(location, this)));
						break;
					case '*': /* Treasure in a Goal. */
						GoalCell goalCellWithTreasure = new GoalCell(location, this, new Treasure(location, this));
						goalCellWithTreasure.CompletedGoalChanged += new EventHandler(GoalCell_CompletedGoalChanged);
						goals.Add(goalCellWithTreasure);
						row.Add(goalCellWithTreasure);
						break;
					case '.': /* Goal. */
						GoalCell goalCell = new GoalCell(location, this);
						goalCell.CompletedGoalChanged += new EventHandler(GoalCell_CompletedGoalChanged);
						goals.Add(goalCell);
						row.Add(goalCell);
						break;
					case '@': /* Actor in a floor cell. */
						Actor actor = new Actor(location, this);
						row.Add(new FloorCell(location, this, actor));
						Actor = actor;
						break;
					case '!': /* Space. */
						row.Add(new SpaceCell(location, this));
						break;
					default:
						throw new FormatException("Invalid Level symbol found: " + c);
				}
			}
			return row;
		}

		void GoalCell_CompletedGoalChanged(object sender, EventArgs e)
		{
			foreach (GoalCell goal in goals)
			{
				if (!goal.HasTreasure)
				{
					return;
				}
			}
			OnLevelCompleted(EventArgs.Empty);
		}

		/// <summary>
		/// Tests whether the specified location is within 
		/// the Level grid.
		/// </summary>
		/// <param name="location">The location to test
		/// whether it is within the level grid.</param>
		/// <returns><code>true</code> if the location
		/// is within the <see cref="Level"/>; 
		/// <code>false</code> otherwise.</returns>
		public bool InBounds(Location location)
		{
			return (location.RowNumber >= 0
				&& location.RowNumber < RowCount
				&& location.ColumnNumber >= 0
				&& location.ColumnNumber < ColumnCount);
		}
		#region LevelCompleted event

		event EventHandler levelCompleted;

		/// <summary>
		/// Occurs when a level has been completed successfully.
		/// </summary>
		public event EventHandler LevelCompleted
		{
			add
			{
				levelCompleted += value;
			}
			remove
			{
				levelCompleted -= value;
			}
		}

		/// <summary>
		/// Raises the <see cref="E:LevelCompleted"/> event.
		/// </summary>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		protected void OnLevelCompleted(EventArgs e)
		{
			if (levelCompleted != null)
			{
				levelCompleted(this, e);
			}
		}
		#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
Engineer
Switzerland Switzerland
Daniel is a former senior engineer in Technology and Research at the Office of the CTO at Microsoft, working on next generation systems.

Previously Daniel was a nine-time Microsoft MVP and co-founder of Outcoder, a Swiss software and consulting company.

Daniel is the author of Windows Phone 8 Unleashed and Windows Phone 7.5 Unleashed, both published by SAMS.

Daniel is the developer behind several acclaimed mobile apps including Surfy Browser for Android and Windows Phone. Daniel is the creator of a number of popular open-source projects, most notably Codon.

Would you like Daniel to bring value to your organisation? Please contact

Blog | Twitter


Xamarin Experts
Windows 10 Experts

Comments and Discussions