|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Contents
IntroductionThe downloadable WPF Sokoban project is intended to be an instructional yet fun introduction to creating WPF applications, and an introduction to some of the new features of .NET 3.5. It is inspired by Sacha Barber's excellent article WPF: The Classic Snakes WPF'ed. This article provides an overview of data binding with WPF: styling and templating of controls. In a later article I intend to port this application to Silverlight, in order to explore some of the differences between the two technologies. Background
Sokoban, like many other brain teasing and time consuming puzzles, can be enjoyable to play but difficult to quit. It has few rules, and can, at times, be challenging. The game was invented by Hiroyuki Imabayashi in 1980. It is somewhat ubiquitous, and can be found on game consoles, mobile phones, and according to the Wikipedia entry, Canon power shots digital cameras. How to Play the GamePush the blue power cubes (treasures) onto the yellow power stations. When all cubes are seated in the power stations, our alien friend will be transported to the next level. Only one power cube may be pushed at a time, and they cannot be pulled. ControlsEither the arrow keys or the mouse can be used to control the actor. If the arrow keys are used, then the actor is able to move up, down, left, or right. If the mouse is used, then the actor will attempt to traverse the level grid to a point clicked by the user. The mouse can be used to move a power cube by positioning the actor adjacent to the power cube and then by single clicking on it. Undo and RedoUse Ctrl+Z and Ctrl+Y to undo and redo moves and jumps. Level Items
Moving AroundThe actor may move either in a single step to an adjacent floor cell, or with a jump to any reachable floor cell on the level grid. In the case of a jump, the application will calculate the best path i.e. that which is comprised of the fewest number of moves. Extending PlayabilityAlien Sokoban's playability may be extended by creating or modifying the map files located in the Levels directory. Map File FormatThe following characters are used within the map files to signify the structure of a level:
XAML and Data BindingThis application displays a game level using a <Style x:Key="Cell" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<!-- The cell, -->
<Rectangle Width="40" Height="40"
Style="{DynamicResource CellStyle}" />
<!-- and its content. -->
<Rectangle Style="{DynamicResource CellContentStyle}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Style triggers change the way the cell is displayed when the <MultiDataTrigger>
<MultiDataTrigger.Conditions>
<!-- If the cell contains the Actor. -->
<Condition Binding="{Binding Path=CellContents.Name}" Value="Actor" />
</MultiDataTrigger.Conditions>
<Setter Property="Fill" Value="{StaticResource PlayerCellContentBrush}"/>
</MultiDataTrigger>
Evaluation of the trigger occurs when the <Sokoban:Game x:Key="sokobanGame"/>
When the MainWindow is initialized, an instance of the <Label Name="label_Moves" Style="{StaticResource CenterLabels}"
Content="{Binding Level.Actor.MoveCount}"/>
Here, the Game Game
{
get
{
return (Game)TryFindResource("sokobanGame");
}
}
Sokoban ProjectAll game logic is contained within the Game Logic Overview
Figure: Class diagram providing an overview of the relationships between the main Sokoban project classes
CellsAs the previous diagram shows, a The
Figure:
Cell class and concrete implementations Cell contentsA
Figure:
CellContents base class and its two concrete implementations: Treasure and Actor Command ManagerEach modification of the
Figure: Class diagram of the
CommandManager and related classes MovesMoves are delivered to the The number of moves is accumulated by the
Figure:
MoveBase class and associated concrete classes Jumping and Path SearchingA bool Step(Location location, int steps)
{
bool found = false;
steps++;
if (location.Equals(destination))
{
if (minSteps == 0 || steps < minSteps)
{
route = new Move[steps];
minSteps = steps;
foundPath = true;
arrayIndex = minSteps - 1;
}
else
{
return false;
}
}
else
{
for (int i = 0; i < 4; i++)
{
Direction direction = GetDirection(i);
Location neighbour = location.GetAdjacentLocation(direction);
int traversedLength = traversed.Count;
if (level[neighbour].CanEnter
&& IsNewLocation(neighbour)
&& Step(neighbour, steps)) /* Recursive call. */
{
route[arrayIndex--] = new Move(direction);
found = true;
}
traversed.RemoveRange(traversedLength,
traversed.Count - traversedLength);
}
return found;
}
return foundPath;
}
The efficiency of this method should be improved. For levels with large open areas, it is too slow. It has been listed as a future enhancement. Expression Design and Resource DictionariesExpression Design was used to create all of the images seen in the game.
Figure: Expression Design
Rather than mocking up the whole game board in Photoshop, I chose to experiment with the design within Expression Design and export it as I went. It was certainly less efficient doing it this way, as time was wasted exporting and viewing repeatedly. The process was somewhat streamlined though: by exporting as a Resource Dictionary one is able to create a brush for each layer in the document.
Figure: Exporting with Expression Design
Within the App.xaml file, a <Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ResourceDictionaries/Cell.xaml" />
<ResourceDictionary Source="ResourceDictionaries/Banner.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Then a cell could be styled within a trigger, like so: <Setter Property="Fill" Value="{StaticResource WallCellBrush}"/>
Points of InterestAsynchronous Property ChangesThere are two actions that take place within the application that occur asynchronously. The first is level loading. In order to compensate for slow loading of map file data, such as in a web application, loading of level data is done asynchronously. The second is that in order to simulate the actor walking across a level, when a context.Send(delegate
{
OnPropertyChanged(new PropertyChangedEventArgs(property));
}, null);
Level CodesLevel codes are used to jump to a level after it has been successfully completed. The Future Enhancements
ConclusionThe aim of this project was to explore WPF, and to employ some of the new language features of C# 3.5 including extension methods, and automatic properties, and object initialisers. WPF makes it remarkably easy to rapidly create complex GUI interfaces, without having to write lots of plumbing code. In my next article, I intend to port "Alien Sokoban" to Silverlight. I hope you find this project useful. If so, then you may like to rate it and/or leave feedback below. Credits
References
History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||